pax_global_header00006660000000000000000000000064141625411130014510gustar00rootroot0000000000000052 comment=7eaf6bc6059f094d86df48c07c24e55f63b17447 PyTables-3.7.0/000077500000000000000000000000001416254111300132425ustar00rootroot00000000000000PyTables-3.7.0/.github/000077500000000000000000000000001416254111300146025ustar00rootroot00000000000000PyTables-3.7.0/.github/workflows/000077500000000000000000000000001416254111300166375ustar00rootroot00000000000000PyTables-3.7.0/.github/workflows/ci.yml000066400000000000000000000023161416254111300177570ustar00rootroot00000000000000name: CI on: [push, pull_request] jobs: build: name: ${{ matrix.os }} ${{ matrix.python }} ${{ matrix.name }} runs-on: ${{ matrix.os }} defaults: run: shell: bash -l {0} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] python: ['3.6', '3.7', '3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v2 with: fetch-depth: 0 submodules: true - name: Set up Python ${{ matrix.python }} uses: conda-incubator/setup-miniconda@v2 with: python-version: ${{ matrix.python }} auto-update-conda: true channels: conda-forge channel-priority: strict - name: Install dependencies run: | conda install setuptools pip wheel build packaging numpy numexpr cython bzip2 hdf5 lzo # conda install sphinx sphinx_rtd_theme numpydoc ipython - name: Source distribution run: | python -m build --sdist - name: Installation run: | pip install -v dist/*.tar.gz - name: 'Run test' run: | cd .. && python -m tables.tests.test_all pt2to3 -h ptrepack -h ptdump -h pttree -h PyTables-3.7.0/.github/workflows/ubuntu.yml000066400000000000000000000027251416254111300207120ustar00rootroot00000000000000name: ubuntu on: [push, pull_request] jobs: build: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 with: fetch-depth: 0 submodules: true - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python }} - name: Install APT packages run: | sudo apt-get update sudo apt install libblosc-dev libbz2-dev libhdf5-dev liblz4-dev liblzo2-dev libsnappy-dev libzstd-dev zlib1g-dev sudo apt install python3-all-dev python3-setuptools python3-packaging python3-numexpr # python3-numpy cython3 sudo apt install python3-pytest python3-pytest-doctestplus sudo apt install python3-numpydoc python3-sphinx python3-sphinx-rtd-theme python3-ipython sudo apt install latexmk texlive-fonts-recommended texlive-latex-recommended texlive-latex-extra texlive-plain-generic - name: Install dependencies run: | python -m pip install --upgrade setuptools pip wheel build python -m pip install --upgrade cython>=0.29.21 numpy>=1.19 python -c "import numpy as np; print('numpy:', np.__version__)" - name: Build PyTables run: make build env: PYTABLES_NO_EMBEDDED_LIBS: TRUE - name: Build HTML documentation run: make html - name: Build LaTeX documentation run: make latex - name: Source distribution run: make dist - name: Test run: make check PyTables-3.7.0/.github/workflows/wheels.yml000066400000000000000000000161421416254111300206550ustar00rootroot00000000000000name: Wheels # Publish when a (published) GitHub Release is created. on: push: branches: - master - 'releases/**' - 'ci/**' tags: - v* release: types: - published jobs: build_wheels: name: Build wheels on ${{matrix.arch}} for ${{ matrix.os }} runs-on: ${{ matrix.os }} env: HDF5_VERSION: 1.12.1 strategy: matrix: os: [ 'ubuntu-latest', 'macos-latest' ] arch: [x86_64, aarch64] exclude: - os: 'macos-latest' arch: 'aarch64' steps: - uses: actions/checkout@v2 with: fetch-depth: 0 submodules: true - uses: actions/setup-python@v2 name: Install Python with: python-version: '3.9' - uses: docker/setup-qemu-action@v1 if: runner.os == 'Linux' name: Set up QEMU - name: Install prerequisites for macOS if: runner.os == 'macOS' env: # Best compatibility, even with older releases of macOS. MACOSX_DEPLOYMENT_TARGET: "10.9" run: | brew reinstall --build-from-source --no-binaries --force bzip2 lz4 lzo snappy zstd zlib brew link --overwrite --force bzip2 zlib - name: Install cibuildwheel run: | python -m pip install --upgrade cibuildwheel - name: Build wheels for Linux or macOS (64-bit) run: | python -m cibuildwheel --output-dir wheelhouse env: CIBW_ARCHS_LINUX: ${{ matrix.arch }} CIBW_ARCHS_MACOS: ${{ matrix.arch == 'aarch64' && 'arm64' || 'x86_64'}} CIBW_BUILD: "cp36-* cp37-* cp38-* cp39-* cp310-*" CIBW_BEFORE_ALL_LINUX: "yum -y update && yum install -y zlib-devel bzip2-devel lzo-devel && ./ci/github/get_hdf5_if_needed.sh" CIBW_BEFORE_ALL_MACOS: "./ci/github/get_hdf5_if_needed.sh" CIBW_BEFORE_BUILD: "pip install -r requirements.txt cython>=0.29.21" CIBW_ENVIRONMENT: "DISABLE_AVX2='TRUE' HDF5_DIR=/tmp/hdf5 CFLAGS=-g0 HDF5_VERSION=${{ env.HDF5_VERSION }} LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/tmp/hdf5/lib/" MACOSX_DEPLOYMENT_TARGET: "10.9" CIBW_ENVIRONMENT_MACOS: HDF5_DIR=/tmp/hdf5 HDF5_VERSION=${{ env.HDF5_VERSION }} CFLAGS=-g0 LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/tmp/hdf5/lib/ BZIP2_DIR=/usr/local/opt/bzip2 LDFLAGS+="-L/usr/local/opt/bzip2/lib -L/usr/local/opt/zlib/lib" CPPFLAGS+="-I/usr/local/opt/bzip2/include -I/usr/local/opt/zlib/include" PKG_CONFIG_PATH="/usr/local/opt/zlib/lib/pkgconfig" CIBW_SKIP: '*-musllinux_*' - uses: actions/upload-artifact@v2 with: path: ./wheelhouse/*.whl build_wheels_windows: name: Build wheels on ${{matrix.arch}} for ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [windows-latest] arch: [win32, win_amd64] steps: - uses: actions/checkout@v2 with: fetch-depth: 0 submodules: true - uses: actions/setup-python@v2 name: Install Python with: python-version: '3.9' - name: Install Miniconda uses: conda-incubator/setup-miniconda@v2 with: channels: defaults,conda-forge use-only-tar-bz2: true - name: Install cibuildwheel run: | python -m pip install --upgrade cibuildwheel - name: Build wheels for Windows (${{ matrix.arch }}) run: cibuildwheel --output-dir wheelhouse env: CIBW_BUILD: "cp36-${{ matrix.arch }} cp37-${{ matrix.arch }} cp38-${{ matrix.arch }} cp39-${{ matrix.arch }} cp310-${{ matrix.arch }}" CIBW_BEFORE_ALL_WINDOWS: "conda create --yes --name=build && conda activate build && conda config --env --set subdir ${{ matrix.arch == 'win32' && 'win-32' || 'win-64' }} && conda install --yes blosc bzip2 hdf5 lz4 lzo snappy zstd zlib" CIBW_ENVIRONMENT_WINDOWS: 'CONDA_PREFIX="C:\\Miniconda\\envs\\build" PATH="$PATH;C:\\Miniconda\\envs\\build\\Library\\bin"' CIBW_ENVIRONMENT: "PYTABLES_NO_EMBEDDED_LIBS=true DISABLE_AVX2=true" CIBW_BEFORE_BUILD: "pip install -r requirements.txt cython>=0.29.21 delvewheel" CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair -w {dest_dir} {wheel}" - uses: actions/upload-artifact@v2 with: path: ./wheelhouse/*.whl test_wheels: needs: [ build_wheels, build_wheels_windows ] name: Test ${{ matrix.python-version }} ${{ matrix.arch }} wheels for ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest' ] python-version: ['3.7', '3.8', '3.9','3.10'] arch: ['x64', 'x86'] exclude: - os: 'ubuntu-latest' arch: 'x86' - os: 'macos-latest' arch: 'x86' steps: - uses: actions/download-artifact@v2 with: path: ./wheelhouse/ - name: Install Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.arch }} - name: Install tables on ${{ matrix.python-version }} run: | pip install numpy>=1.19.0 numexpr>=2.6.2 pip install --no-index --find-links wheelhouse/artifact/ tables - name: Run tests on ${{ matrix.python-version }} run: | python -m tables.tests.test_all build_sdist: name: Build source distribution runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 submodules: true - uses: actions/setup-python@v2 name: Install Python with: python-version: '3.7' - name: Install APT packages if: contains(${{ matrix.os }}, 'ubuntu') run: | sudo apt install libbz2-dev libhdf5-serial-dev liblzo2-dev sudo apt install latexmk texlive-fonts-recommended texlive-latex-recommended texlive-latex-extra texlive-plain-generic - name: Install dependencies run: | python -m pip install --upgrade setuptools pip wheel build python -m pip install -r requirements.txt python -m pip install cython python -m pip install sphinx>=1.1 sphinx_rtd_theme numpydoc ipython - name: Build sdist run: make PYTHON=python dist - uses: actions/upload-artifact@v2 with: path: dist/* # upload_pypi: # needs: [build_wheels, build_sdist] # runs-on: ubuntu-latest # # upload to PyPI on every tag starting with 'v' # if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') # # alternatively, to publish when a GitHub Release is created, use the following rule: # # if: github.event_name == 'release' && github.event.action == 'published' # steps: # - uses: actions/download-artifact@v2 # with: # name: artifact # path: dist # # - uses: pypa/gh-action-pypi-publish@master # with: # user: __token__ # password: ${{ secrets.pypi_password }} # # To test: repository_url: https://test.pypi.org/legacy/ PyTables-3.7.0/.gitignore000066400000000000000000000017461416254111300152420ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Sphinx documentation docs/_build/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mypy .mypy_cache/ # PyCharm .idea # PyTables tables/*.c src/version.h a.out tmp/ # misc *~ PyTables-3.7.0/.gitmodules000066400000000000000000000001221416254111300154120ustar00rootroot00000000000000[submodule "c-blosc"] path = c-blosc url = https://github.com/Blosc/c-blosc.git PyTables-3.7.0/.readthedocs.yml000066400000000000000000000001561416254111300163320ustar00rootroot00000000000000conda: file: environment.yml python: version: 3 setup_py_install: true extra_requirements: - docs PyTables-3.7.0/ANNOUNCE.txt.in000066400000000000000000000037351416254111300156260ustar00rootroot00000000000000=========================== Announcing PyTables @VERSION@ =========================== We are happy to announce PyTables @VERSION@. What's new ========== This is a minor version of PyTables. The main feature added is that compatibility with Python 3.10, numpy 1.21 and HDF5 1.12 has been improved, while support for Python 3.5 has been dropped. The CI infrastructure has been moved to GitHub Actions. In case you want to know more in detail what has changed in this version, please refer to: http://www.pytables.org/release_notes.html You can install it via pip or download a source package with generated PDF and HTML docs from: https://github.com/PyTables/PyTables/releases/v@VERSION@ For an online version of the manual, visit: http://www.pytables.org/usersguide/index.html What it is? =========== PyTables is a library for managing hierarchical datasets and designed to efficiently cope with extremely large amounts of data with support for full 64-bit file addressing. PyTables runs on top of the HDF5 library and NumPy package for achieving maximum throughput and convenient use. PyTables includes OPSI, a new indexing technology, allowing to perform data lookups in tables exceeding 10 gigarows (10**10 rows) in less than a tenth of a second. Resources ========= About PyTables: http://www.pytables.org About the HDF5 library: http://hdfgroup.org/HDF5/ About NumPy: http://numpy.scipy.org/ Acknowledgments =============== Thanks to many users who provided feature improvements, patches, bug reports, support and suggestions. See the ``THANKS`` file in the distribution package for a (incomplete) list of contributors. Most specially, a lot of kudos go to the HDF5 and NumPy makers. Without them, PyTables simply would not exist. Share your experience ===================== Let us know of any bugs, suggestions, gripes, kudos, etc. you may have. ---- **Enjoy data!** -- The PyTables Developers .. Local Variables: .. mode: rst .. coding: utf-8 .. fill-column: 72 .. End: PyTables-3.7.0/CITATION.bib000066400000000000000000000002431416254111300151310ustar00rootroot00000000000000@Misc{, author = {PyTables Developers Team}, title = {{PyTables}: Hierarchical Datasets in {Python}}, year = {2002--}, url = "https://www.pytables.org/" } PyTables-3.7.0/CONTRIBUTING.md000066400000000000000000000015731416254111300155010ustar00rootroot00000000000000# Contribute to PyTables PyTables is actively seeking additional maintainers and developers. If you are interested in becoming on, check out our developer resources on the PyTables website: http://www.pytables.org/development.html#pytables-development ## Documentation In general, the barrier to entry is lower when making a docs contribution. We encourage you to check out our open docs issues: https://github.com/PyTables/PyTables/labels/documentation ## Stay up to date If you want to stay up to date with PyTables development and communicate with the development team, consider joining our developer mailing list: https://groups.google.com/g/pytables-dev ## Something else? Do you have an idea for a contribution but you are not sure if it fits into any of the aforementioned options? Please open an issue and propose it! We are happy to help support contributions of all types. PyTables-3.7.0/FUNDING.yml000066400000000000000000000001101416254111300150470ustar00rootroot00000000000000github: [numfocus] custom: ['https://numfocus.org/donate-to-pytables'] PyTables-3.7.0/LICENSE.txt000066400000000000000000000032711416254111300150700ustar00rootroot00000000000000Copyright Notice and Statement for PyTables Software Library and Utilities: Copyright (c) 2002-2004 by Francesc Alted Copyright (c) 2005-2007 by Carabos Coop. V. Copyright (c) 2008-2010 by Francesc Alted Copyright (c) 2011-2021 by PyTables maintainers 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. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PyTables-3.7.0/LICENSES/000077500000000000000000000000001416254111300144475ustar00rootroot00000000000000PyTables-3.7.0/LICENSES/BLOSC.txt000066400000000000000000000022671416254111300160610ustar00rootroot00000000000000Blosc - A blocking, shuffling and lossless compression library Copyright (C) 2009-2012 Francesc Alted Copyright (C) 2013 Francesc Alted Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PyTables-3.7.0/LICENSES/CLOUD-SPTHEME.txt000066400000000000000000000041151416254111300171620ustar00rootroot00000000000000.. -*- restructuredtext -*- =================== Copyright & License =================== Cloud Sphinx Theme ================== cloud_sptheme is released under the BSD license, and is (c) `Assurance Technologies `_:: The "cloud_sptheme" python package and artwork is Copyright (c) 2010-2012 by Assurance Technologies, LLC. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Assurance Technologies, nor the names of the contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 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. Other Content ============= Most of the icons in ``cloud_sptheme:themes/cloud/static`` are from the `Tango Icon Project `_, which has released them into the Public Domain. PyTables-3.7.0/LICENSES/FASTLZ.txt000066400000000000000000000023121416254111300162110ustar00rootroot00000000000000FastLZ - lightning-fast lossless compression library Copyright (C) 2007 Ariya Hidayat (ariya@kde.org) Copyright (C) 2006 Ariya Hidayat (ariya@kde.org) Copyright (C) 2005 Ariya Hidayat (ariya@kde.org) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PyTables-3.7.0/LICENSES/H5PY.txt000066400000000000000000000030321416254111300157330ustar00rootroot00000000000000Copyright Notice and Statement for the h5py Project Copyright (c) 2008 Andrew Collette http://www.h5py.org All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: a. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. b. 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. c. Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 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. PyTables-3.7.0/LICENSES/HDF5.txt000066400000000000000000000072661416254111300157110ustar00rootroot00000000000000HDF5 (Hierarchical Data Format 5) Software Library and Utilities Copyright 2006-2007 by The HDF Group (THG). NCSA HDF5 (Hierarchical Data Format 5) Software Library and Utilities Copyright 1998-2006 by the Board of Trustees of the University of Illinois. All rights reserved. Contributors: National Center for Supercomputing Applications (NCSA) at the University of Illinois, Fortner Software, Unidata Program Center (netCDF), The Independent JPEG Group (JPEG), Jean-loup Gailly and Mark Adler (gzip), and Digital Equipment Corporation (DEC). Redistribution and use in source and binary forms, with or without modification, are permitted for any purpose (including commercial purposes) 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 materials provided with the distribution. 3. In addition, redistributions of modified forms of the source or binary code must carry prominent notices stating that the original code was changed and the date of the change. 4. All publications or advertising materials mentioning features or use of this software are asked, but not required, to acknowledge that it was developed by The HDF Group and by the National Center for Supercomputing Applications at the University of Illinois at Urbana-Champaign and credit the contributors. 5. Neither the name of The HDF Group, the name of the University, nor the name of any Contributor may be used to endorse or promote products derived from this software without specific prior written permission from THG, the University, or the Contributor, respectively. DISCLAIMER: THIS SOFTWARE IS PROVIDED BY THE HDF GROUP (THG) AND THE CONTRIBUTORS "AS IS" WITH NO WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED. In no event shall THG or the Contributors be liable for any damages suffered by the users arising out of the use of this software, even if advised of the possibility of such damage. Portions of HDF5 were developed with support from the University of California, Lawrence Livermore National Laboratory (UC LLNL). The following statement applies to those portions of the product and must be retained in any redistribution of source code, binaries, documentation, and/or accompanying materials: This work was partially produced at the University of California, Lawrence Livermore National Laboratory (UC LLNL) under contract no. W-7405-ENG-48 (Contract 48) between the U.S. Department of Energy (DOE) and The Regents of the University of California (University) for the operation of UC LLNL. DISCLAIMER: This work was prepared as an account of work sponsored by an agency of the United States Government. Neither the United States Government nor the University of California nor any of their employees, makes any warranty, express or implied, or assumes any liability or responsibility for the accuracy, completeness, or usefulness of any information, apparatus, product, or process disclosed, or represents that its use would not infringe privately- owned rights. Reference herein to any specific commercial products, process, or service by trade name, trademark, manufacturer, or otherwise, does not necessarily constitute or imply its endorsement, recommendation, or favoring by the United States Government or the University of California. The views and opinions of authors expressed herein do not necessarily state or reflect those of the United States Government or the University of California, and shall not be used for advertising or product endorsement purposes. PyTables-3.7.0/LICENSES/LZ4.txt000066400000000000000000000030101416254111300156130ustar00rootroot00000000000000LZ4 - Fast LZ compression algorithm Copyright (C) 2011-2014, Yann Collet. BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 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. You can contact the author at : - LZ4 homepage : http://fastcompression.blogspot.com/p/lz4.html - LZ4 source repository : http://code.google.com/p/lz4/ PyTables-3.7.0/LICENSES/SNAPPY.txt000066400000000000000000000027031416254111300162240ustar00rootroot00000000000000Copyright 2011, Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 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. PyTables-3.7.0/LICENSES/STDINT.txt000066400000000000000000000026421416254111300162210ustar00rootroot00000000000000Copyright (c) 2006-2013 Alexander Chemeris 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. 3. Neither the name of the product nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.PyTables-3.7.0/LICENSES/WIN32PTHREADS.txt000066400000000000000000000020771416254111300171530ustar00rootroot00000000000000Copyright (C) 2009 Andrzej K. Haczewski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PyTables-3.7.0/LICENSES/ZLIB.txt000066400000000000000000000017521416254111300157550ustar00rootroot00000000000000Copyright notice: (C) 1995-2013 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Jean-loup Gailly Mark Adler jloup@gzip.org madler@alumni.caltech.edu PyTables-3.7.0/LICENSES/ZSTD.TXT000066400000000000000000000027721416254111300156440ustar00rootroot00000000000000BSD License For Zstandard software Copyright (c) 2016-present, Facebook, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Facebook nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PyTables-3.7.0/MANIFEST.in000066400000000000000000000017621416254111300150060ustar00rootroot00000000000000include MANIFEST.in include *.txt THANKS README.rst include setup.py setup.cfg VERSION Makefile cpuinfo.py recursive-include tables *.py *.pyx *.pxd *.c recursive-include tables/tests *.h5 *.mat recursive-include tables/nodes/tests *.h5 *.dat *.xbm recursive-include src *.c *.h Makefile include hdf5-blosc/src/blosc_filter.? recursive-include c-blosc/blosc *.c *.h *.inc recursive-include c-blosc/internal-complibs *.c *.cc *.h recursive-include LICENSES * recursive-include utils * include doc/Makefile doc/make.bat #include doc/*.pdf recursive-include doc *.rst *.conf *.py *.*_t recursive-include doc *.html *.js *.css *.png *.ico recursive-include doc/source *.pdf objecttree.svg #recursive-include doc/source *.pdf *.svg recursive-include doc/html *.txt *.svg *.gif *.inv recursive-include doc/scripts *.py recursive-include doc/sphinxext * recursive-exclude doc/build * recursive-include examples *.py *.sh recursive-include bench *.sh *.py *.txt *.h5 *.gnuplot recursive-include contrib README *.py PyTables-3.7.0/Makefile000066400000000000000000000044001416254111300147000ustar00rootroot00000000000000# This Makefile is only intended to prepare for distribution the PyTables # sources exported from a repository. For building and installing PyTables, # please use ``setup.py`` as described in the ``README.rst`` file. VERSION = $(shell grep "__version__ =" tables/__init__.py | cut -f 3 -d ' ' | sed s/\"//g) SRCDIRS = src doc GENERATED = ANNOUNCE.txt PYTHON = python3 PYPLATFORM = $(shell $(PYTHON) -c "from distutils.util import get_platform; print(get_platform())") PYVER = $(shell $(PYTHON) -V 2>&1 | cut -c 8-10) PYBUILDDIR = $(PWD)/build/lib.$(PYPLATFORM)-$(PYVER) OPT = PYTHONPATH=$(PYBUILDDIR) .PHONY: all dist build check heavycheck clean distclean html all: $(GENERATED) build html dist: all latex # $(PYTHON) -m build --sdist # --no-isolation $(PYTHON) setup.py sdist cp RELEASE_NOTES.rst dist/RELEASE_NOTES-$(VERSION).rst cp doc/usersguide-$(VERSION).pdf dist/pytablesmanual-$(VERSION).pdf tar cvzf dist/pytablesmanual-$(VERSION)-html.tar.gz doc/html cd dist && \ md5sum -b tables-$(VERSION).tar.gz RELEASE_NOTES-$(VERSION).rst \ pytablesmanual-$(VERSION).pdf \ pytablesmanual-$(VERSION)-html.tar.gz > pytables-$(VERSION).md5 && \ cd - clean: $(RM) -r MANIFEST build dist tmp tables/__pycache__ $(RM) bench/*.h5 bench/*.prof $(RM) -r examples/*.h5 examples/raw $(RM) -r *.egg-info $(RM) $(GENERATED) tables/*.so a.out find . '(' -name '*.py[co]' -o -name '*~' ')' -exec rm '{}' ';' for srcdir in $(SRCDIRS) ; do $(MAKE) -C $$srcdir $(OPT) $@ ; done distclean: clean $(MAKE) -C src $(OPT) $@ $(RM) tables/_comp_*.c tables/*extension.c $(RM) doc/usersguide-*.pdf $(RM) -r doc/html $(RM) -r .pytest_cache # git clean -fdx html: build $(MAKE) -C doc $(OPT) html $(RM) -r doc/html cp -R doc/build/html doc/html latex: $(MAKE) -C doc $(OPT) latexpdf $(RM) doc/usersguide-*.pdf cp doc/build/latex/usersguide-$(VERSION).pdf doc %: %.in tables/__init__.py cat "$<" | sed -e 's/@VERSION@/$(VERSION)/g' > "$@" build: $(PYTHON) setup.py build check: build cd build/lib.*-$(PYVER) && env PYTHONPATH=. $(PYTHON) -m pytest --doctest-only --pyargs tables -k "not AttributeSet" cd build/lib.*-$(PYVER) && env PYTHONPATH=. $(PYTHON) tables/tests/test_all.py heavycheck: build cd build/lib.*-$(PYVER) && env PYTHONPATH=. $(PYTHON) tables/tests/test_all.py --heavy PyTables-3.7.0/README.rst000066400000000000000000000126731416254111300147420ustar00rootroot00000000000000=========================================== PyTables: hierarchical datasets in Python =========================================== .. image:: https://badges.gitter.im/Join%20Chat.svg :alt: Join the chat at https://gitter.im/PyTables/PyTables :target: https://gitter.im/PyTables/PyTables .. image:: https://github.com/PyTables/PyTables/workflows/CI/badge.svg :target: https://github.com/PyTables/PyTables/actions?query=workflow%3ACI .. image:: https://img.shields.io/pypi/v/tables.svg :target: https://pypi.org/project/tables/ .. image:: https://img.shields.io/pypi/pyversions/tables.svg :target: https://pypi.org/project/tables/ .. image:: https://img.shields.io/pypi/l/tables :target: https://github.com/PyTables/PyTables/ :URL: http://www.pytables.org/ PyTables is a package for managing hierarchical datasets and designed to efficiently cope with extremely large amounts of data. It is built on top of the HDF5 library and the NumPy package. It features an object-oriented interface that, combined with C extensions for the performance-critical parts of the code (generated using Cython), makes it a fast, yet extremely easy to use tool for interactively save and retrieve very large amounts of data. One important feature of PyTables is that it optimizes memory and disk resources so that they take much less space (between a factor 3 to 5, and more if the data is compressible) than other solutions, like for example, relational or object oriented databases. State-of-the-art compression ---------------------------- PyTables comes with out-of-box support for the `Blosc compressor `_. This allows for extremely high compression speed, while keeping decent compression ratios. By doing so, I/O can be accelerated by a large extent, and you may end achieving higher performance than the bandwidth provided by your I/O subsystem. See the `Tuning The Chunksize section of the Optimization Tips chapter `_ of user documentation for some benchmarks. Not a RDBMS replacement ----------------------- PyTables is not designed to work as a relational database replacement, but rather as a teammate. If you want to work with large datasets of multidimensional data (for example, for multidimensional analysis), or just provide a categorized structure for some portions of your cluttered RDBS, then give PyTables a try. It works well for storing data from data acquisition systems (DAS), simulation software, network data monitoring systems (for example, traffic measurements of IP packets on routers), or as a centralized repository for system logs, to name only a few possible uses. Tables ------ A table is defined as a collection of records whose values are stored in fixed-length fields. All records have the same structure and all values in each field have the same data type. The terms "fixed-length" and strict "data types" seems to be quite a strange requirement for an interpreted language like Python, but they serve a useful function if the goal is to save very large quantities of data (such as is generated by many scientific applications, for example) in an efficient manner that reduces demand on CPU time and I/O. Arrays ------ There are other useful objects like arrays, enlargeable arrays or variable length arrays that can cope with different missions on your project. Easy to use ----------- One of the principal objectives of PyTables is to be user-friendly. In addition, many different iterators have been implemented so as to enable the interactive work to be as productive as possible. Platforms --------- We are using Linux on top of Intel32 and Intel64 boxes as the main development platforms, but PyTables should be easy to compile/install on other UNIX or Windows machines. Compiling --------- To compile PyTables you will need, at least, a recent version of HDF5 (C flavor) library, the Zlib compression library and the NumPy and Numexpr packages. Besides, it comes with support for the Blosc, LZO and bzip2 compressor libraries. Blosc is mandatory, but PyTables comes with Blosc sources so, although it is recommended to have Blosc installed in your system, you don't absolutely need to install it separately. LZO and bzip2 compression libraries are, however, optional. Installation ------------ 1. Make sure you have HDF5 version 1.8.4 or above. On OSX you can install HDF5 using `Homebrew `_:: $ brew install hdf5 On debian bases distributions:: $ sudo apt-get install libhdf5-serial-dev If you have the HDF5 library in some non-standard location (that is, where the compiler and the linker can't find it) you can use the environment variable `HDF5_DIR` to specify its location. See `the manual `_ for more details. 3. For stability (and performance too) reasons, it is strongly recommended that you install the C-Blosc library separately, although you might want PyTables to use its internal C-Blosc sources. 3. Optionally, consider to install the LZO compression library and/or the bzip2 compression library. 4. Install!:: $ python3 -m pip install tables 5. To run the test suite run:: $ python3 -m tables.tests.test_all If there is some test that does not pass, please send the complete output for tests back to us. **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: text .. coding: utf-8 .. fill-column: 70 .. End: PyTables-3.7.0/RELEASE_NOTES.rst000066400000000000000000000040751416254111300160320ustar00rootroot00000000000000======================================= Release notes for PyTables 3.7 series ======================================= :Author: PyTables Developers :Contact: pytables-dev@googlegroups.com .. py:currentmodule:: tables Changes from 3.6.1 to 3.7.0 =========================== Improvements ------------ - Compatibility with Python 3.10, numpy 1.21 and HDF5 1.12. - Support for Python 3.5 has been dropped (:issue:`840` and :issue:`850`). - Windows: Significantly faster `import tables` PR #781. Thanks to Christoph Gohlke. - Internal C-Blosc sources updated to 1.21.1 (:issue:`931`). Note that, starting from C-Blosc 1.19 does not include the Snappy codec sources anymore, so Snappy will be not available if you compile from included sources; other packages (like conda or wheels), may (or may not) include it. - Stop using appveyor and deprecated ci-helpers (closes :issue:`827`). - Switch to `git submodule` for the management of vendored c-blosc sources. - CI moved to GitHub Actions (GHA). - Drop Travis-CI. - Improved code formatting and notation consistency (:issue:`873`, :issue:`868`, :issue:`865` thanks to Miroslav Å edivý). - Improve the use of modern Python including :mod:`pathlib`, f-strings (:issue:`859`, :issue:`855`, :issue:`839` and :issue:`818` thanks to Miroslav Å edivý). - Several improvements to wheels generation in CI (thanks to Andreas Motl @amotl and Matthias @xmatthias). - Simplified management of version information. - Drop dependency on the deprecated distutils. - Modernize the setup script and add support for PEP517 (:issue:`907`). Bugfixes -------- - Fix `pkg-config` (`setup.py`) for Python 3.9 on Debian. Thanks to Marco Sulla PR #792. - Fix ROFileNode fails to return the `fileno()` (:issue:`633`). - Do not flush read only files (:issue:`915` thanks to @lrepiton). Other changes ------------- - Drop the deprecated `hdf5Version` and `File.open_count`. - the :func:`get_tables_version` and :func:`get_hdf5_version` functions are now deprecated please use the coresponding :data:`tables.__version__` and :data:`tables.hdf5_version` instead. PyTables-3.7.0/THANKS000066400000000000000000000062551416254111300141650ustar00rootroot00000000000000March 2009 We would like to thank the people have contributed directly or indirectly to PyTables. Scott Prater for editing the user's manual in order to make it more readable in english, as well as conducting the tests of PyTables on Solaris. Alan McIntyre for porting PyTables to Windows. John Nielsen for suggesting improvements and delivering code for completely avoid the recursion algorithms and allowing pytables to bypass the ~1000 levels of deepness that Python recursion limit imposed. Tom Hedley for providing a nice patch for supporting complex datatypes for Arrays, Errays and VLArrays. This was the root for the support of complex types in Tables as well. Shack Toms for providing a Python version of the nextafter and nextafterf math functions that despite the fact they are standard in C99 standard, they are not at the official places in Microsoft VC++ 6.x nor VC++ 7.x. Jeff Whitaker for providing the NetCDF module and the utility for converting netCDF files to HDF5 (nctoh5). Norbert Nemec for providing several interesting patches. Andrew Straw for suggesting to bracket the most intensive parts of PyTables with BEGIN_ALLOW_THREADS and END_ALLOW_THREADS. That will allow much better performance of PyTables apps in mutiprocessors platforms. Antonio Valentino for providing several patches for supporting native multidimensional attributes and the CArray object. Ashley Walsh, for reporting several problems and fixes. It has helped testing OSX platform, specially UCL compressor issues. Russel Howe, for reporting and providing an initial patch for a nasty memory leak when reading VLArray types. The HDF5 team at NCSA for making such an excellent library for data persistence, and specially Pedro Vicente, Quincey Koziol and Elena Pourmal, for quickly including my suggested patches to the HDF5_HL and solving the reported bugs in HDF5 library. Todd Miller and Perry Greenfield for promptly helping me to understand many of the intricacies of the numarray package and Jin-chung Hsu for discussions on recarray module (now numarray.records module). They have been very receptive and promptly worked-out most of the improvements in numarray (specially in the records module) that were necessary for PyTables. Travis Oliphant for its impressive work and responsiveness with NumPy. Evan Prodromou for his lrucache package, a very sleek implementation of an LRU queue. He had a very helpful attitude with the licensing and technical issues. Gerard Vermeulen for Windows/MSVS-2005 testing. Enric Cervera for testing the binaries for MacOSX/Intel. Daniel Bungert, Steve Langasek and Alexandre Fayolle for their support in creating Debian packages for PyTables. Greg Ewing for writing the excelent Pyrex tool and allowing to beginners like me to quickly and safely start writing Python extensions. He was also very responsive about questions on Pyrex. Stefan Behnel, Robert Bradshaw, and Dag Sverre Seljebotn for their impressive work with Cython. Andrew Collette, for his excellent work on the h5py project, from which PyTables starts to stole ideas (and code too ;-). Guido, you know who. And last, but definitely not least!, To those companies that are supporting the PyTables project with contracts. PyTables-3.7.0/bench/000077500000000000000000000000001416254111300143215ustar00rootroot00000000000000PyTables-3.7.0/bench/LRU-experiments.py000066400000000000000000000054221416254111300177010ustar00rootroot00000000000000# Testbed to perform experiments in order to determine best values for # the node numbers in LRU cache. Tables version. from time import perf_counter as clock import tables as tb print("PyTables version-->", tb.__version__) filename = "/tmp/junk-tables-100.h5" NLEAVES = 2000 NROWS = 1000 class Particle(tb.IsDescription): name = tb.StringCol(16, pos=1) # 16-character String lati = tb.Int32Col(pos=2) # integer longi = tb.Int32Col(pos=3) # integer pressure = tb.Float32Col(pos=4) # float (single-precision) temperature = tb.Float64Col(pos=5) # double (double-precision) def create_junk(): # Open a file in "w"rite mode fileh = tb.open_file(filename, mode="w") # Create a new group group = fileh.create_group(fileh.root, "newgroup") for i in range(NLEAVES): # Create a new table in newgroup group table = fileh.create_table(group, 'table' + str(i), Particle, "A table", tb.Filters(1)) particle = table.row print("Creating table-->", table._v_name) # Fill the table with particles for i in range(NROWS): # This injects the row values. particle.append() table.flush() # Finally, close the file fileh.close() def modify_junk_LRU(): fileh = tb.open_file(filename, 'a') group = fileh.root.newgroup for j in range(5): print("iter -->", j) for tt in fileh.walk_nodes(group): if isinstance(tt, tb.Table): pass # for row in tt: # pass fileh.close() def modify_junk_LRU2(): fileh = tb.open_file(filename, 'a') group = fileh.root.newgroup for j in range(20): t1 = clock() for i in range(100): #print("table-->", tt._v_name) tt = getattr(group, "table" + str(i)) #for row in tt: # pass print(f"iter and time --> {j + 1} {clock() - t1:.3f}") fileh.close() def modify_junk_LRU3(): fileh = tb.open_file(filename, 'a') group = fileh.root.newgroup for j in range(3): t1 = clock() for tt in fileh.walk_nodes(group, "Table"): tt.attrs.TITLE for row in tt: pass print(f"iter and time --> {j + 1} {clock() - t1:.3f}") fileh.close() if 1: # create_junk() # modify_junk_LRU() # uses the iterator version (walk_nodes) # modify_junk_LRU2() # uses a regular loop (getattr) modify_junk_LRU3() # uses a regular loop (getattr) else: import profile import pstats profile.run('modify_junk_LRU2()', 'modify.prof') stats = pstats.Stats('modify.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats() PyTables-3.7.0/bench/LRU-experiments2.py000066400000000000000000000027231416254111300177640ustar00rootroot00000000000000# Testbed to perform experiments in order to determine best values for # the node numbers in LRU cache. Arrays version. from time import perf_counter as clock import tables as tb print("PyTables version-->", tb.__version__) filename = "/tmp/junk-array.h5" NOBJS = 1000 def create_junk(): fileh = tb.open_file(filename, mode="w") for i in range(NOBJS): fileh.create_array(fileh.root, 'array' + str(i), [1]) fileh.close() def modify_junk_LRU(): fileh = tb.open_file(filename, 'a') group = fileh.root for j in range(5): print("iter -->", j) for tt in fileh.walk_nodes(group): if isinstance(tt, tb.Array): # d = tt.read() pass fileh.close() def modify_junk_LRU2(): fileh = tb.open_file(filename, 'a') group = fileh.root for j in range(5): t1 = clock() for i in range(100): # The number #print("table-->", tt._v_name) tt = getattr(group, "array" + str(i)) #d = tt.read() print(f"iter and time --> {j + 1} {clock() - t1:.3f}") fileh.close() if 1: # create_junk() # modify_junk_LRU() # uses the iterador version (walk_nodes) modify_junk_LRU2() # uses a regular loop (getattr) else: import profile import pstats profile.run('modify_junk_LRU2()', 'modify.prof') stats = pstats.Stats('modify.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats() PyTables-3.7.0/bench/LRUcache-node-bench.py000066400000000000000000000034671416254111300203330ustar00rootroot00000000000000import sys import numpy as np import tables as tb from time import perf_counter as clock #import psyco filename = "/tmp/LRU-bench.h5" nodespergroup = 250 niter = 100 print('nodespergroup:', nodespergroup) print('niter:', niter) if len(sys.argv) > 1: NODE_CACHE_SLOTS = int(sys.argv[1]) print('NODE_CACHE_SLOTS:', NODE_CACHE_SLOTS) else: NODE_CACHE_SLOTS = tb.parameters.NODE_CACHE_SLOTS f = tb.open_file(filename, "w", node_cache_slots=NODE_CACHE_SLOTS) g = f.create_group("/", "NodeContainer") print("Creating nodes") for i in range(nodespergroup): f.create_array(g, "arr%d" % i, [i]) f.close() f = tb.open_file(filename) def iternodes(): # for a in f.root.NodeContainer: # pass indices = np.random.randn(nodespergroup * niter) * 30 + nodespergroup / 2 indices = indices.astype('i4').clip(0, nodespergroup - 1) g = f.get_node("/", "NodeContainer") for i in indices: a = f.get_node(g, "arr%d" % i) # print("a-->", a) print("reading nodes...") # First iteration (put in LRU cache) t1 = clock() for a in f.root.NodeContainer: pass print(f"time (init cache)--> {clock() - t1:.3f}") def timeLRU(): # Next iterations t1 = clock() # for i in range(niter): # iternodes() iternodes() print(f"time (from cache)--> {(clock() - t1) / niter:.3f}") def profile(verbose=False): import pstats import cProfile as prof prof.run('timeLRU()', 'out.prof') stats = pstats.Stats('out.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') if verbose: stats.print_stats() else: stats.print_stats(20) # profile() # psyco.bind(timeLRU) timeLRU() f.close() # for N in 0 4 8 16 32 64 128 256 512 1024 2048 4096; do # env PYTHONPATH=../build/lib.linux-x86_64-2.7 \ # python LRUcache-node-bench.py $N; # done PyTables-3.7.0/bench/bench-postgres-ranges.sh000077500000000000000000000004021416254111300210540ustar00rootroot00000000000000#!/bin/sh export PYTHONPATH=..${PYTHONPATH:+:$PYTHONPATH} pyopt="-O -u" #qlvl="-Q8 -x" #qlvl="-Q8" qlvl="-Q7" #size="500m" size="1g" #python $pyopt indexed_search.py -P -c -n $size -m -v python $pyopt indexed_search.py -P -i -n $size -m -v -sfloat $qlvl PyTables-3.7.0/bench/bench-pytables-ranges.sh000077500000000000000000000017311416254111300210370ustar00rootroot00000000000000#!/bin/sh #export LD_LIBRARY_PATH=$HOME/computacio/hdf5-1.8.2/hdf5/lib export PYTHONPATH=..${PYTHONPATH:+:$PYTHONPATH} bench="python2.7 -O -u indexed_search.py" flags="-T -m -v " #sizes="1g 500m 200m 100m 50m 20m 10m 5m 2m 1m" sizes="1g" #sizes="1m" working_dir="data.nobackup" #working_dir="/scratch2/faltet" #for comprlvl in '-z0' '-z1 -llzo' '-z1 -lzlib' ; do #for comprlvl in '-z6 -lblosc' '-z3 -lblosc' '-z1 -lblosc' ; do for comprlvl in '-z5 -lblosc' ; do #for comprlvl in '-z0' ; do for optlvl in '-tfull -O9' ; do #for optlvl in '-tultralight -O3' '-tlight -O6' '-tmedium -O6' '-tfull -O9'; do #for optlvl in '-tultralight -O3'; do #rm -f $working_dir/* # XXX esta ben posat?? for mode in '-Q8 -i -s float' ; do #for mode in -c '-Q7 -i -s float' ; do #for mode in '-c -s float' '-Q8 -I -s float' '-Q8 -S -s float'; do for size in $sizes ; do $bench $flags $mode -n $size $optlvl $comprlvl -d $working_dir done done done done PyTables-3.7.0/bench/bench-pytables.sh000077500000000000000000000016061416254111300175630ustar00rootroot00000000000000#!/bin/sh export LD_LIBRARY_PATH=$HOME/computacio/hdf5-1.8.1/hdf5/lib #export PYTHONPATH=..${PYTHONPATH:+:$PYTHONPATH} bench="python2.7 -O -u indexed_search.py" flags="-T -m -v -d data.nobackup" #sizes="1m 2m 5m 10m 20m 50m 100m 200m 500m 1g" sizes="2g 1g 500m 200m 100m 50m 20m 10m 5m 2m 1m 500k 200k 100k 50k 20k 10k 5k 2k 1k" #sizes="1m 100k" #for optimlvl in 0 1 2 3 4 5 6 7 8 9 ; do for idxtype in ultralight light medium full; do #for idxtype in medium full; do for optimlvl in 0 3 6 9; do for compropt in '' '-z1 -lzlib' '-z1 -llzo' ; do #for compropt in '-z1 -llzo' ; do rm -rf data.nobackup/* # Atencio: esta correctament posat? #for mode in -c '-i -s float' ; do for mode in -c '-i' ; do for size in $sizes ; do $bench $flags $mode -n $size -O $optimlvl -t $idxtype $compropt done done done done done rm -rf data.nobackup PyTables-3.7.0/bench/blosc.py000066400000000000000000000107631416254111300160040ustar00rootroot00000000000000import sys from pathlib import Path from time import perf_counter as clock import numpy as np import tables as tb niter = 3 dirname = "/scratch2/faltet/blosc-data/" #expression = "a**2 + b**3 + 2*a*b + 3" #expression = "a+b" #expression = "a**2 + 2*a/b + 3" #expression = "(a+b)**2 - (a**2 + b**2 + 2*a*b) + 1.1" expression = "3*a-2*b+1.1" shuffle = True def create_file(kind, prec, synth): prefix_orig = 'cellzome/cellzome-' iname = dirname + prefix_orig + 'none-' + prec + '.h5' f = tb.open_file(iname, "r") if prec == "single": type_ = tb.Float32Atom() else: type_ = tb.Float64Atom() if synth: prefix = 'synth/synth-' else: prefix = 'cellzome/cellzome-' for clevel in range(10): oname = '%s/%s-%s%d-%s.h5' % (dirname, prefix, kind, clevel, prec) # print "creating...", iname f2 = tb.open_file(oname, "w") if kind in ["none", "numpy"]: filters = None else: filters = tb.Filters( complib=kind, complevel=clevel, shuffle=shuffle) for name in ['maxarea', 'mascotscore']: col = f.get_node('/', name) r = f2.create_carray('/', name, type_, col.shape, filters=filters) if synth: r[:] = np.arange(col.nrows, dtype=type_.dtype) else: r[:] = col[:] f2.close() if clevel == 0: size = 1.5 * Path(oname).stat().st_size f.close() return size def create_synth(kind, prec): prefix_orig = 'cellzome/cellzome-' iname = dirname + prefix_orig + 'none-' + prec + '.h5' f = tb.open_file(iname, "r") if prec == "single": type_ = tb.Float32Atom() else: type_ = tb.Float64Atom() prefix = 'synth/synth-' for clevel in range(10): oname = '%s/%s-%s%d-%s.h5' % (dirname, prefix, kind, clevel, prec) # print "creating...", iname f2 = tb.open_file(oname, "w") if kind in ["none", "numpy"]: filters = None else: filters = tb.Filters( complib=kind, complevel=clevel, shuffle=shuffle) for name in ['maxarea', 'mascotscore']: col = f.get_node('/', name) r = f2.create_carray('/', name, type_, col.shape, filters=filters) if name == 'maxarea': r[:] = np.arange(col.nrows, dtype=type_.dtype) else: r[:] = np.arange(col.nrows, 0, dtype=type_.dtype) f2.close() if clevel == 0: size = 1.5 * Path(oname).stat().st_size f.close() return size def process_file(kind, prec, clevel, synth): if kind == "numpy": lib = "none" else: lib = kind if synth: prefix = 'synth/synth-' else: prefix = 'cellzome/cellzome-' iname = '%s/%s-%s%d-%s.h5' % (dirname, prefix, kind, clevel, prec) f = tb.open_file(iname, "r") a_ = f.root.maxarea b_ = f.root.mascotscore oname = '%s/%s-%s%d-%s-r.h5' % (dirname, prefix, kind, clevel, prec) f2 = tb.open_file(oname, "w") if lib == "none": filters = None else: filters = tb.Filters(complib=lib, complevel=clevel, shuffle=shuffle) if prec == "single": type_ = tb.Float32Atom() else: type_ = tb.Float64Atom() r = f2.create_carray('/', 'r', type_, a_.shape, filters=filters) if kind == "numpy": a2, b2 = a_[:], b_[:] t0 = clock() r = eval(expression, {'a': a2, 'b': b2}) print(f"{clock() - t0:5.2f}") else: expr = tb.Expr(expression, {'a': a_, 'b': b_}) expr.set_output(r) expr.eval() f.close() f2.close() size = Path(iname).stat().st_size + Path(oname).stat().st_size return size if __name__ == '__main__': if len(sys.argv) > 3: kind = sys.argv[1] prec = sys.argv[2] if sys.argv[3] == "synth": synth = True else: synth = False else: print("3 parameters required") sys.exit(1) # print "kind, precision, synth:", kind, prec, synth # print "Creating input files..." size_orig = create_file(kind, prec, synth) # print "Processing files for compression levels in range(10)..." for clevel in range(10): t0 = clock() ts = [] for i in range(niter): size = process_file(kind, prec, clevel, synth) ts.append(clock() - t0) t0 = clock() ratio = size_orig / size print(f"{min(ts):5.2f}, {ratio:5.2f}") PyTables-3.7.0/bench/bsddb-table-bench.py000066400000000000000000000177511416254111300201260ustar00rootroot00000000000000#!/usr/bin/env python ###### WARNING ####### ### This script is obsoleted ### # If you get it working again, please drop me a line # F. Alted 2004-01-27 import sys import struct import cPickle import numpy as np import tables as tb from bsddb import db import psyco # This class is accessible only for the examples class Small(tb.IsDescription): """Record descriptor. A record has several columns. They are represented here as class attributes, whose names are the column names and their values will become their types. The IsColDescr class will take care the user will not add any new variables and that its type is correct. """ var1 = tb.StringCol(itemsize=16) var2 = tb.Int32Col() var3 = tb.Float64Col() # Define a user record to characterize some kind of particles class Medium(tb.IsDescription): name = tb.StringCol(itemsize=16, pos=0) # 16-character String #float1 = Float64Col(shape=2, dflt=2.3) float1 = tb.Float64Col(dflt=1.3, pos=1) float2 = tb.Float64Col(dflt=2.3, pos=2) ADCcount = tb.Int16Col(pos=3) # signed short integer grid_i = tb.Int32Col(pos=4) # integer grid_j = tb.Int32Col(pos=5) # integer pressure = tb.Float32Col(pos=6) # float (single-precision) energy = tb.Float64Col(pos=7) # double (double-precision) # Define a user record to characterize some kind of particles class Big(tb.IsDescription): name = tb.StringCol(itemsize=16) # 16-character String #float1 = Float64Col(shape=32, dflt=np.arange(32)) #float2 = Float64Col(shape=32, dflt=np.arange(32)) float1 = tb.Float64Col(shape=32, dflt=range(32)) float2 = tb.Float64Col(shape=32, dflt=[2.2] * 32) ADCcount = tb.Int16Col() # signed short integer grid_i = tb.Int32Col() # integer grid_j = tb.Int32Col() # integer pressure = tb.Float32Col() # float (single-precision) energy = tb.Float64Col() # double (double-precision) def createFile(filename, totalrows, recsize, verbose): # Open a 'n'ew file dd = db.DB() if recsize == "big": isrec = tb.Description(Big) elif recsize == "medium": isrec = Medium() else: isrec = tb.Description(Small) # dd.set_re_len(struct.calcsize(isrec._v_fmt)) # fixed length records dd.open(filename, db.DB_RECNO, db.DB_CREATE | db.DB_TRUNCATE) rowswritten = 0 # Get the record object associated with the new table if recsize == "big": isrec = Big() arr = np.array(np.arange(32), type=np.float64) arr2 = np.array(np.arange(32), type=np.float64) elif recsize == "medium": isrec = Medium() arr = np.array(np.arange(2), type=np.float64) else: isrec = Small() # print d # Fill the table if recsize == "big" or recsize == "medium": d = {"name": " ", "float1": 1.0, "float2": 2.0, "ADCcount": 12, "grid_i": 1, "grid_j": 1, "pressure": 1.9, "energy": 1.8, } for i in range(totalrows): #d['name'] = 'Particle: %6d' % (i) #d['TDCcount'] = i % 256 d['ADCcount'] = (i * 256) % (1 << 16) if recsize == "big": #d.float1 = np.array([i]*32, np.float64) #d.float2 = np.array([i**2]*32, np.float64) arr[0] = 1.1 d['float1'] = arr arr2[0] = 2.2 d['float2'] = arr2 pass else: d['float1'] = float(i) d['float2'] = float(i) d['grid_i'] = i d['grid_j'] = 10 - i d['pressure'] = float(i * i) d['energy'] = d['pressure'] dd.append(cPickle.dumps(d)) # dd.append(struct.pack(isrec._v_fmt, # d['name'], d['float1'], d['float2'], # d['ADCcount'], # d['grid_i'], d['grid_j'], # d['pressure'], d['energy'])) else: d = {"var1": " ", "var2": 1, "var3": 12.1e10} for i in range(totalrows): d['var1'] = str(i) d['var2'] = i d['var3'] = 12.1e10 dd.append(cPickle.dumps(d)) #dd.append( # struct.pack(isrec._v_fmt, d['var1'], d['var2'], d['var3'])) rowswritten += totalrows # Close the file dd.close() return (rowswritten, struct.calcsize(isrec._v_fmt)) def readFile(filename, recsize, verbose): # Open the HDF5 file in read-only mode #fileh = shelve.open(filename, "r") dd = db.DB() if recsize == "big": isrec = Big() elif recsize == "medium": isrec = Medium() else: isrec = Small() # dd.set_re_len(struct.calcsize(isrec._v_fmt)) # fixed length records # dd.set_re_pad('-') # sets the pad character... # dd.set_re_pad(45) # ...test both int and char dd.open(filename, db.DB_RECNO) if recsize == "big" or recsize == "medium": print(isrec._v_fmt) c = dd.cursor() rec = c.first() e = [] while rec: record = cPickle.loads(rec[1]) #record = struct.unpack(isrec._v_fmt, rec[1]) # if verbose: # print record if record['grid_i'] < 20: e.append(record['grid_j']) # if record[4] < 20: # e.append(record[5]) rec = next(c) else: print(isrec._v_fmt) #e = [ t[1] for t in fileh[table] if t[1] < 20 ] c = dd.cursor() rec = c.first() e = [] while rec: record = cPickle.loads(rec[1]) #record = struct.unpack(isrec._v_fmt, rec[1]) # if verbose: # print record if record['var2'] < 20: e.append(record['var1']) # if record[1] < 20: # e.append(record[2]) rec = next(c) print("resulting selection list ==>", e) print("last record read ==>", record) print("Total selected records ==> ", len(e)) # Close the file (eventually destroy the extended type) dd.close() # Add code to test here if __name__ == "__main__": import getopt from time import perf_counter as clock usage = """usage: %s [-v] [-s recsize] [-i iterations] file -v verbose -s use [big] record, [medium] or [small] -i sets the number of rows in each table\n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 's:vi:') except: sys.stderr.write(usage) sys.exit(0) # if we pass too much parameters, abort if len(pargs) != 1: sys.stderr.write(usage) sys.exit(0) # default options recsize = "medium" iterations = 100 verbose = 0 # Get the options for option in opts: if option[0] == '-s': recsize = option[1] if recsize not in ["big", "medium", "small"]: sys.stderr.write(usage) sys.exit(0) elif option[0] == '-i': iterations = int(option[1]) elif option[0] == '-v': verbose = 1 # Catch the hdf5 file passed as the last argument file = pargs[0] t1 = clock() psyco.bind(createFile) (rowsw, rowsz) = createFile(file, iterations, recsize, verbose) t2 = clock() tapprows = t2 - t1 t1 = clock() psyco.bind(readFile) readFile(file, recsize, verbose) t2 = clock() treadrows = t2 - t1 print(f"Rows written: {rowsw}, Row size: {rowsz}") print(f"Time appending rows: {tapprows:.3f}") if tapprows > 0.001: print(f"Write rows/sec: {iterations / tapprows:.0f}") print(f"Write KB/s: {rowsw * rowsz / (tapprows * 1024):.0f}") print(f"Time reading rows: {treadrows:.3f}") if treadrows > 0.001: print(f"Read rows/sec: {iterations / treadrows:.0f}") print(f"Read KB/s: {rowsw * rowsz / (treadrows * 1024):.0f}") PyTables-3.7.0/bench/cacheout.py000066400000000000000000000004711416254111300164700ustar00rootroot00000000000000# Program to clean out the filesystem cache import numpy as np a = np.arange(1000 * 100 * 125, dtype='f8') # 100 MB of RAM b = a * 3 # Another 100 MB # delete the reference to the booked memory del a del b # Do a loop to fully recharge the python interpreter j = 2 for i in range(1000 * 1000): j += i * 2 PyTables-3.7.0/bench/chunkshape-bench.py000066400000000000000000000035251416254111300201060ustar00rootroot00000000000000#!/usr/bin/env python # Benchmark the effect of chunkshapes in reading large datasets. # You need at least PyTables 2.1 to run this! # F. Alted from time import perf_counter as clock import numpy as np import tables as tb dim1, dim2 = 360, 6_109_666 rows_to_read = range(0, 360, 36) print("=" * 32) # Create the EArray f = tb.open_file("/tmp/test.h5", "w") a = f.create_earray(f.root, "a", tb.Float64Atom(), shape=(dim1, 0), expectedrows=dim2) print("Chunkshape for original array:", a.chunkshape) # Fill the EArray t1 = clock() zeros = np.zeros((dim1, 1), dtype="float64") for i in range(dim2): a.append(zeros) tcre = clock() - t1 thcre = dim1 * dim2 * 8 / (tcre * 1024 * 1024) print(f"Time to append {a.nrows} rows: {tcre:.3f} sec ({thcre:.1f} MB/s)") # Read some row vectors from the original array t1 = clock() for i in rows_to_read: r1 = a[i, :] tr1 = clock() - t1 thr1 = dim2 * len(rows_to_read) * 8 / (tr1 * 1024 * 1024) print(f"Time to read ten rows in original array: {tr1:.3f} sec ({thr1:.1f} MB/s)") print("=" * 32) # Copy the array to another with a row-wise chunkshape t1 = clock() #newchunkshape = (1, a.chunkshape[0]*a.chunkshape[1]) newchunkshape = (1, a.chunkshape[0] * a.chunkshape[1] * 10) # ten times larger b = a.copy(f.root, "b", chunkshape=newchunkshape) tcpy = clock() - t1 thcpy = dim1 * dim2 * 8 / (tcpy * 1024 * 1024) print("Chunkshape for row-wise chunkshape array:", b.chunkshape) print(f"Time to copy the original array: {tcpy:.3f} sec ({thcpy:.1f} MB/s)") # Read the same ten rows from the new copied array t1 = clock() for i in rows_to_read: r2 = b[i, :] tr2 = clock() - t1 thr2 = dim2 * len(rows_to_read) * 8 / (tr2 * 1024 * 1024) print(f"Time to read with a row-wise chunkshape: {tr2:.3f} sec ({thr2:.1f} MB/s)") print("=" * 32) print(f"Speed-up with a row-wise chunkshape: {tr1 / tr2:.1f}") f.close() PyTables-3.7.0/bench/chunkshape-testing.py000066400000000000000000000062761416254111300205120ustar00rootroot00000000000000#!/usr/bin/env python """Simple benchmark for testing chunkshapes and nrowsinbuf.""" import numpy as np import tables as tb from time import perf_counter as clock L = 20 N = 2000 M = 30 complevel = 1 recarray = np.empty(shape=2, dtype='(2,2,2)i4,(2,3,3)f8,i4,i8') f = tb.open_file("chunkshape.h5", mode="w") # t = f.create_table(f.root, 'table', recarray, "mdim recarray") # a0 = f.create_array(f.root, 'field0', recarray['f0'], "mdim int32 array") # a1 = f.create_array(f.root, 'field1', recarray['f1'], "mdim float64 array") # c0 = f.create_carray(f.root, 'cfield0', # tables.Int32Atom(), (2,2,2), # "mdim int32 carray") # c1 = f.create_carray(f.root, 'cfield1', # tables.Float64Atom(), (2,3,3), # "mdim float64 carray") f1 = tb.open_file("chunkshape1.h5", mode="w") c1 = f.create_carray(f1.root, 'cfield1', tb.Int32Atom(), (L, N, M), "scalar int32 carray", tb.Filters(complevel=0)) t1 = clock() c1[:] = np.empty(shape=(L, 1, 1), dtype="int32") print("carray1 populate time:", clock() - t1) f1.close() f2 = tb.open_file("chunkshape2.h5", mode="w") c2 = f.create_carray(f2.root, 'cfield2', tb.Int32Atom(), (L, M, N), "scalar int32 carray", tb.Filters(complevel)) t1 = clock() c2[:] = np.empty(shape=(L, 1, 1), dtype="int32") print("carray2 populate time:", clock() - t1) f2.close() f0 = tb.open_file("chunkshape0.h5", mode="w") e0 = f.create_earray(f0.root, 'efield0', tb.Int32Atom(), (0, L, M), "scalar int32 carray", tb.Filters(complevel), expectedrows=N) t1 = clock() e0.append(np.empty(shape=(N, L, M), dtype="int32")) print("earray0 populate time:", clock() - t1) f0.close() f1 = tb.open_file("chunkshape1.h5", mode="w") e1 = f.create_earray(f1.root, 'efield1', tb.Int32Atom(), (L, 0, M), "scalar int32 carray", tb.Filters(complevel), expectedrows=N) t1 = clock() e1.append(np.empty(shape=(L, N, M), dtype="int32")) print("earray1 populate time:", clock() - t1) f1.close() f2 = tb.open_file("chunkshape2.h5", mode="w") e2 = f.create_earray(f2.root, 'efield2', tb.Int32Atom(), (L, M, 0), "scalar int32 carray", tb.Filters(complevel), expectedrows=N) t1 = clock() e2.append(np.empty(shape=(L, M, N), dtype="int32")) print("earray2 populate time:", clock() - t1) f2.close() # t1=time() # c2[:] = numpy.empty(shape=(M, N), dtype="int32") # print "carray populate time:", time()-t1 # f3 = f.create_carray(f.root, 'cfield3', # tables.Float64Atom(), (3,), # "scalar float64 carray", chunkshape=(32,)) # e2 = f.create_earray(f.root, 'efield2', # tables.Int32Atom(), (0, M), # "scalar int32 carray", expectedrows=N) # t1=time() # e2.append(numpy.empty(shape=(N, M), dtype="int32")) # print "earray populate time:", time()-t1 # t1=time() # c2._f_copy(newname='cfield2bis') # print "carray copy time:", time()-t1 # t1=time() # e2._f_copy(newname='efield2bis') # print "earray copy time:", time()-t1 f.close() PyTables-3.7.0/bench/collations.py000066400000000000000000000073501416254111300170470ustar00rootroot00000000000000import numpy as np import tables as tb from time import perf_counter as clock N = 1000 * 1000 NCOLL = 200 # 200 collections maximum # In order to have reproducible results np.random.seed(19) class Energies(tb.IsDescription): collection = tb.UInt8Col() energy = tb.Float64Col() def fill_bucket(lbucket): #c = np.random.normal(NCOLL/2, NCOLL/10, lbucket) c = np.random.normal(NCOLL / 2, NCOLL / 100, lbucket) e = np.arange(lbucket, dtype='f8') return c, e # Fill the table t1 = clock() f = tb.open_file("data.nobackup/collations.h5", "w") table = f.create_table("/", "Energies", Energies, expectedrows=N) # Fill the table with values lbucket = 1000 # Fill in buckets of 1000 rows, for speed for i in range(0, N, lbucket): bucket = fill_bucket(lbucket) table.append(bucket) # Fill the remaining rows bucket = fill_bucket(N % lbucket) table.append(bucket) f.close() print(f"Time to create the table with {N} entries: {t1:.3f}") # Now, read the table and group it by collection f = tb.open_file("data.nobackup/collations.h5", "a") table = f.root.Energies ######################################################### # First solution: load the table completely in memory ######################################################### t1 = clock() t = table[:] # convert to structured array coll1 = [] collections = np.unique(t['collection']) for c in collections: cond = t['collection'] == c energy_this_collection = t['energy'][cond] sener = energy_this_collection.sum() coll1.append(sener) print(c, ' : ', sener) del collections, energy_this_collection print(f"Time for first solution: {clock() - t1:.3f}s") ######################################################### # Second solution: load all the collections in memory ######################################################### t1 = clock() collections = {} for row in table: c = row['collection'] e = row['energy'] if c in collections: collections[c].append(e) else: collections[c] = [e] # Convert the lists in numpy arrays coll2 = [] for c in sorted(collections): energy_this_collection = np.array(collections[c]) sener = energy_this_collection.sum() coll2.append(sener) print(c, ' : ', sener) del collections, energy_this_collection print(f"Time for second solution: {clock() - t1:.3f}s") t1 = clock() table.cols.collection.create_csindex() # table.cols.collection.reindex() print(f"Time for indexing: {clock() - t1:.3f}s") ######################################################### # Third solution: load each collection separately ######################################################### t1 = clock() coll3 = [] for c in np.unique(table.col('collection')): energy_this_collection = table.read_where( 'collection == c', field='energy') sener = energy_this_collection.sum() coll3.append(sener) print(c, ' : ', sener) del energy_this_collection print(f"Time for third solution: {clock() - t1:.3f}s") t1 = clock() table2 = table.copy('/', 'EnergySortedByCollation', overwrite=True, sortby="collection", propindexes=True) print(f"Time for sorting: {clock() - t1:.3f}s") ##################################################################### # Fourth solution: load each collection separately. Sorted table. ##################################################################### t1 = clock() coll4 = [] for c in np.unique(table2.col('collection')): energy_this_collection = table2.read_where( 'collection == c', field='energy') sener = energy_this_collection.sum() coll4.append(sener) print(c, ' : ', sener) del energy_this_collection print(f"Time for fourth solution: {clock() - t1:.3f}s") # Finally, check that all solutions do match assert coll1 == coll2 == coll3 == coll4 f.close() PyTables-3.7.0/bench/copy-bench.py000066400000000000000000000020231416254111300167170ustar00rootroot00000000000000import sys from time import perf_counter as clock import tables as tb if len(sys.argv) != 3: print("usage: %s source_file dest_file", sys.argv[0]) filesrc = sys.argv[1] filedest = sys.argv[2] filehsrc = tb.open_file(filesrc) filehdest = tb.open_file(filedest, 'w') ntables = 0 tsize = 0 t1 = clock() for group in filehsrc.walk_groups(): if isinstance(group._v_parent, tb.File): groupdest = filehdest.root else: pathname = group._v_parent._v_pathname groupdest = filehdest.create_group(pathname, group._v_name, title=group._v_title) for table in filehsrc.list_nodes(group, classname='Table'): print("copying table -->", table) table.copy(groupdest, table.name) ntables += 1 tsize += table.nrows * table.rowsize tsizeMB = tsize / (1024 * 1024) ttime = clock() - t1 print(f"Copied {ntables} tables for a total of {tsizeMB:.1f} MB" f" in {ttime:.3f} seconds ({tsizeMB / ttime:.1f} MB/s)") filehsrc.close() filehdest.close() PyTables-3.7.0/bench/create-large-number-objects.py000066400000000000000000000023441416254111300221460ustar00rootroot00000000000000"This creates an HDF5 file with a potentially large number of objects" import sys import numpy as np import tables as tb filename = sys.argv[1] # Open a new empty HDF5 file fileh = tb.open_file(filename, mode="w") # nlevels -- Number of levels in hierarchy # ngroups -- Number of groups on each level # ndatasets -- Number of arrays on each group # LR: Low ratio groups/datasets #nlevels, ngroups, ndatasets = (3, 1, 1000) # MR: Medium ratio groups/datasets nlevels, ngroups, ndatasets = (3, 10, 100) #nlevels, ngroups, ndatasets = (3, 5, 10) # HR: High ratio groups/datasets #nlevels, ngroups, ndatasets = (30, 10, 10) # Create an Array to save on disk a = np.array([-1, 2, 4], np.int16) group = fileh.root group2 = fileh.root for k in range(nlevels): for j in range(ngroups): for i in range(ndatasets): # Save the array on the HDF5 file fileh.create_array(group2, 'array' + str(i), a, "Signed short array") # Create a new group group2 = fileh.create_group(group, 'group' + str(j)) # Create a new group group3 = fileh.create_group(group, 'ngroup' + str(k)) # Iterate over this new group (group3) group = group3 group2 = group3 fileh.close() PyTables-3.7.0/bench/deep-tree-h5py.py000066400000000000000000000063251416254111300174360ustar00rootroot00000000000000from pathlib import Path from time import perf_counter as clock import random import numpy as np import h5py random.seed(2) def show_stats(explain, tref): "Show the used memory (only works for Linux 2.6.x)." for line in Path('/proc/self/status').read_text().splitlines(): if line.startswith("VmSize:"): vmsize = int(line.split()[1]) elif line.startswith("VmRSS:"): vmrss = int(line.split()[1]) elif line.startswith("VmData:"): vmdata = int(line.split()[1]) elif line.startswith("VmStk:"): vmstk = int(line.split()[1]) elif line.startswith("VmExe:"): vmexe = int(line.split()[1]) elif line.startswith("VmLib:"): vmlib = int(line.split()[1]) print("Memory usage: ******* %s *******" % explain) print(f"VmSize: {vmsize:>7} kB\tVmRSS: {vmrss:>7} kB") print(f"VmData: {vmdata:>7} kB\tVmStk: {vmstk:>7} kB") print(f"VmExe: {vmexe:>7} kB\tVmLib: {vmlib:>7} kB") tnow = clock() print(f"WallClock time: {tnow - tref:.3f}") return tnow def populate(f, nlevels): g = f arr = np.zeros((10,), "f4") for i in range(nlevels): g["DS1"] = arr g["DS2"] = arr g.create_group('group2_') g = g.create_group('group') def getnode(f, nlevels, niter, range_): for i in range(niter): nlevel = random.randrange( (nlevels - range_) / 2, (nlevels + range_) / 2) groupname = "" for i in range(nlevel): groupname += "/group" groupname += "/DS1" f[groupname] if __name__ == '__main__': nlevels = 1024 niter = 1000 range_ = 256 profile = True doprofile = True verbose = False if doprofile: import pstats import cProfile as prof if profile: tref = clock() if profile: show_stats("Abans de crear...", tref) f = h5py.File("/tmp/deep-tree.h5", 'w') if doprofile: prof.run('populate(f, nlevels)', 'populate.prof') stats = pstats.Stats('populate.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') if verbose: stats.print_stats() else: stats.print_stats(20) else: populate(f, nlevels) f.close() if profile: show_stats("Despres de crear", tref) # if profile: tref = time() # if profile: show_stats("Abans d'obrir...", tref) # f = h5py.File("/tmp/deep-tree.h5", 'r') # if profile: show_stats("Abans d'accedir...", tref) # if doprofile: # prof.run('getnode(f, nlevels, niter, range_)', 'deep-tree.prof') # stats = pstats.Stats('deep-tree.prof') # stats.strip_dirs() # stats.sort_stats('time', 'calls') # if verbose: # stats.print_stats() # else: # stats.print_stats(20) # else: # getnode(f, nlevels, niter, range_) # if profile: show_stats("Despres d'accedir", tref) # f.close() # if profile: show_stats("Despres de tancar", tref) # f = h5py.File("/tmp/deep-tree.h5", 'r') # g = f # for i in range(nlevels): # dset = g["DS1"] # dset = g["DS2"] # group2 = g['group2_'] # g = g['group'] # f.close() PyTables-3.7.0/bench/deep-tree.py000066400000000000000000000073161416254111300165540ustar00rootroot00000000000000# Small benchmark for compare creation times with parameter # PYTABLES_SYS_ATTRS active or not. from pathlib import Path from time import perf_counter as clock import random import tables as tb random.seed(2) def show_stats(explain, tref): "Show the used memory (only works for Linux 2.6.x)." for line in Path('/proc/self/status').read_text().splitlines(): if line.startswith("VmSize:"): vmsize = int(line.split()[1]) elif line.startswith("VmRSS:"): vmrss = int(line.split()[1]) elif line.startswith("VmData:"): vmdata = int(line.split()[1]) elif line.startswith("VmStk:"): vmstk = int(line.split()[1]) elif line.startswith("VmExe:"): vmexe = int(line.split()[1]) elif line.startswith("VmLib:"): vmlib = int(line.split()[1]) print("Memory usage: ******* %s *******" % explain) print(f"VmSize: {vmsize:>7} kB\tVmRSS: {vmrss:>7} kB") print(f"VmData: {vmdata:>7} kB\tVmStk: {vmstk:>7} kB") print(f"VmExe: {vmexe:>7} kB\tVmLib: {vmlib:>7} kB") tnow = clock() print(f"WallClock time: {tnow - tref:.3f}") return tnow def populate(f, nlevels): g = f.root #arr = numpy.zeros((10,), "f4") #descr = {'f0': tables.Int32Col(), 'f1': tables.Float32Col()} for i in range(nlevels): #dset = f.create_array(g, "DS1", arr) #dset = f.create_array(g, "DS2", arr) f.create_carray(g, "DS1", tb.IntAtom(), (10,)) f.create_carray(g, "DS2", tb.IntAtom(), (10,)) #dset = f.create_table(g, "DS1", descr) #dset = f.create_table(g, "DS2", descr) f.create_group(g, 'group2_') g = f.create_group(g, 'group') def getnode(f, nlevels, niter, range_): for i in range(niter): nlevel = random.randrange( (nlevels - range_) / 2, (nlevels + range_) / 2) groupname = "" for i in range(nlevel): groupname += "/group" groupname += "/DS1" f.get_node(groupname) if __name__ == '__main__': nlevels = 1024 niter = 256 range_ = 128 nodeCacheSlots = 64 pytables_sys_attrs = True profile = True doprofile = True verbose = False if doprofile: import pstats import cProfile as prof if profile: tref = clock() if profile: show_stats("Abans de crear...", tref) f = tb.open_file("/tmp/PTdeep-tree.h5", 'w', node_cache_slots=nodeCacheSlots, pytables_sys_attrs=pytables_sys_attrs) if doprofile: prof.run('populate(f, nlevels)', 'populate.prof') stats = pstats.Stats('populate.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') if verbose: stats.print_stats() else: stats.print_stats(20) else: populate(f, nlevels) f.close() if profile: show_stats("Despres de crear", tref) if profile: tref = clock() if profile: show_stats("Abans d'obrir...", tref) f = tb.open_file("/tmp/PTdeep-tree.h5", 'r', node_cache_slots=nodeCacheSlots, pytables_sys_attrs=pytables_sys_attrs) if profile: show_stats("Abans d'accedir...", tref) if doprofile: prof.run('getnode(f, nlevels, niter, range_)', 'getnode.prof') stats = pstats.Stats('getnode.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') if verbose: stats.print_stats() else: stats.print_stats(20) else: getnode(f, nlevels, niter, range_) if profile: show_stats("Despres d'accedir", tref) f.close() if profile: show_stats("Despres de tancar", tref) PyTables-3.7.0/bench/evaluate.py000066400000000000000000000125511416254111300165050ustar00rootroot00000000000000import sys from time import perf_counter as clock import numexpr as ne import numpy as np import tables as tb shape = (1000, 160_000) #shape = (10,1600) filters = tb.Filters(complevel=1, complib="blosc", shuffle=0) ofilters = tb.Filters(complevel=1, complib="blosc", shuffle=0) #filters = tb.Filters(complevel=1, complib="lzo", shuffle=0) #ofilters = tb.Filters(complevel=1, complib="lzo", shuffle=0) # TODO: Makes it sense to add a 's'tring typecode here? typecode_to_dtype = {'b': 'bool', 'i': 'int32', 'l': 'int64', 'f': 'float32', 'd': 'float64', 'c': 'complex128'} def _compute(result, function, arguments, start=None, stop=None, step=None): """Compute the `function` over the `arguments` and put the outcome in `result`""" arg0 = arguments[0] if hasattr(arg0, 'maindim'): maindim = arg0.maindim (start, stop, step) = arg0._process_range_read(start, stop, step) nrowsinbuf = arg0.nrowsinbuf print("nrowsinbuf-->", nrowsinbuf) else: maindim = 0 (start, stop, step) = (0, len(arg0), 1) nrowsinbuf = len(arg0) shape = list(arg0.shape) shape[maindim] = len(range(start, stop, step)) # The slices parameter for arg0.__getitem__ slices = [slice(0, dim, 1) for dim in arg0.shape] # This is a hack to prevent doing unnecessary conversions # when copying buffers if hasattr(arg0, 'maindim'): for arg in arguments: arg._v_convert = False # Start the computation itself for start2 in range(start, stop, step * nrowsinbuf): # Save the records on disk stop2 = start2 + step * nrowsinbuf if stop2 > stop: stop2 = stop # Set the proper slice in the main dimension slices[maindim] = slice(start2, stop2, step) start3 = (start2 - start) / step stop3 = start3 + nrowsinbuf if stop3 > shape[maindim]: stop3 = shape[maindim] # Compute the slice to be filled in destination sl = [] for i in range(maindim): sl.append(slice(None, None, None)) sl.append(slice(start3, stop3, None)) # Get the values for computing the buffer values = [arg.__getitem__(tuple(slices)) for arg in arguments] result[tuple(sl)] = function(*values) # Activate the conversion again (default) if hasattr(arg0, 'maindim'): for arg in arguments: arg._v_convert = True return result def evaluate(ex, out=None, local_dict=None, global_dict=None, **kwargs): """Evaluate expression and return an array.""" # First, get the signature for the arrays in expression context = ne.necompiler.getContext(kwargs) names, _ = ne.necompiler.getExprNames(ex, context) # Get the arguments based on the names. call_frame = sys._getframe(1) if local_dict is None: local_dict = call_frame.f_locals if global_dict is None: global_dict = call_frame.f_globals arguments = [] types = [] for name in names: try: a = local_dict[name] except KeyError: a = global_dict[name] arguments.append(a) if hasattr(a, 'atom'): types.append(a.atom) else: types.append(a) # Create a signature signature = [ (name, ne.necompiler.getType(type_)) for (name, type_) in zip(names, types) ] print("signature-->", signature) # Compile the expression compiled_ex = ne.necompiler.NumExpr(ex, signature, [], **kwargs) print("fullsig-->", compiled_ex.fullsig) _compute(out, compiled_ex, arguments) return if __name__ == "__main__": iarrays = 0 oarrays = 0 doprofile = 1 dokprofile = 0 f = tb.open_file("/scratch2/faltet/evaluate.h5", "w") # Create some arrays if iarrays: a = np.ones(shape, dtype='float32') b = np.ones(shape, dtype='float32') * 2 c = np.ones(shape, dtype='float32') * 3 else: a = f.create_carray(f.root, 'a', tb.Float32Atom(dflt=1), shape=shape, filters=filters) a[:] = 1 b = f.create_carray(f.root, 'b', tb.Float32Atom(dflt=2), shape=shape, filters=filters) b[:] = 2 c = f.create_carray(f.root, 'c', tb.Float32Atom(dflt=3), shape=shape, filters=filters) c[:] = 3 if oarrays: out = np.empty(shape, dtype='float32') else: out = f.create_carray(f.root, 'out', tb.Float32Atom(), shape=shape, filters=ofilters) t0 = clock() if iarrays and oarrays: #out = ne.evaluate("a*b+c") out = a * b + c elif doprofile: import cProfile as prof import pstats prof.run('evaluate("a*b+c", out)', 'evaluate.prof') stats = pstats.Stats('evaluate.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats(20) elif dokprofile: from cProfile import Profile import lsprofcalltree prof = Profile() prof.run('evaluate("a*b+c", out)') kcg = lsprofcalltree.KCacheGrind(prof) with Path('evaluate.kcg').open('w') as ofile: kcg.output(ofile) else: evaluate("a*b+c", out) print(f"Time for evaluate--> {clock() - t0:.3f}") # print "out-->", `out` # print `out[:]` f.close() PyTables-3.7.0/bench/expression.py000066400000000000000000000125741416254111300171030ustar00rootroot00000000000000from pathlib import Path from time import perf_counter as clock import numpy as np import tables as tb OUT_DIR = Path("/scratch2/faltet/") # the directory for data output shape = (1000, 1000 * 1000) # shape for input arrays expr = "a*b+1" # Expression to be computed nrows, ncols = shape def tables(docompute, dowrite, complib, verbose): # Filenames ifilename = OUT_DIR / "expression-inputs.h5" ofilename = OUT_DIR / "expression-outputs.h5" # Filters shuffle = True if complib == 'blosc': filters = tb.Filters(complevel=1, complib='blosc', shuffle=shuffle) elif complib == 'lzo': filters = tb.Filters(complevel=1, complib='lzo', shuffle=shuffle) elif complib == 'zlib': filters = tb.Filters(complevel=1, complib='zlib', shuffle=shuffle) else: filters = tb.Filters(complevel=0, shuffle=False) if verbose: print("Will use filters:", filters) if dowrite: f = tb.open_file(ifilename, 'w') # Build input arrays t0 = clock() root = f.root a = f.create_carray(root, 'a', tb.Float32Atom(), shape, filters=filters) b = f.create_carray(root, 'b', tb.Float32Atom(), shape, filters=filters) if verbose: print("chunkshape:", a.chunkshape) print("chunksize:", np.prod(a.chunkshape) * a.dtype.itemsize) #row = np.linspace(0, 1, ncols) row = np.arange(0, ncols, dtype='float32') for i in range(nrows): a[i] = row * (i + 1) b[i] = row * (i + 1) * 2 f.close() print(f"[tables.Expr] Time for creating inputs: {clock() - t0:.3f}") if docompute: f = tb.open_file(ifilename, 'r') fr = tb.open_file(ofilename, 'w') a = f.root.a b = f.root.b r1 = f.create_carray(fr.root, 'r1', tb.Float32Atom(), shape, filters=filters) # The expression e = tb.Expr(expr) e.set_output(r1) t0 = clock() e.eval() if verbose: print("First ten values:", r1[0, :10]) f.close() fr.close() print(f"[tables.Expr] Time for computing & save: {clock() - t0:.3f}") def memmap(docompute, dowrite, verbose): afilename = OUT_DIR / "memmap-a.bin" bfilename = OUT_DIR / "memmap-b.bin" rfilename = OUT_DIR / "memmap-output.bin" if dowrite: t0 = clock() a = np.memmap(afilename, dtype='float32', mode='w+', shape=shape) b = np.memmap(bfilename, dtype='float32', mode='w+', shape=shape) # Fill arrays a and b #row = np.linspace(0, 1, ncols) row = np.arange(0, ncols, dtype='float32') for i in range(nrows): a[i] = row * (i + 1) b[i] = row * (i + 1) * 2 del a, b # flush data print(f"[numpy.memmap] Time for creating inputs: {clock() - t0:.3f}") if docompute: t0 = clock() # Reopen inputs in read-only mode a = np.memmap(afilename, dtype='float32', mode='r', shape=shape) b = np.memmap(bfilename, dtype='float32', mode='r', shape=shape) # Create the array output r = np.memmap(rfilename, dtype='float32', mode='w+', shape=shape) # Do the computation row by row for i in range(nrows): r[i] = eval(expr, {'a': a[i], 'b': b[i]}) if verbose: print("First ten values:", r[0, :10]) del a, b del r # flush output data print(f"[numpy.memmap] Time for compute & save: {clock() - t0:.3f}") def do_bench(what, documpute, dowrite, complib, verbose): if what == "tables": tables(docompute, dowrite, complib, verbose) if what == "memmap": memmap(docompute, dowrite, verbose) if __name__ == "__main__": import sys import getopt usage = """usage: %s [-T] [-M] [-c] [-w] [-v] [-z complib] -T use tables.Expr -M use numpy.memmap -c do the computation only -w write inputs only -v verbose mode -z select compression library ('zlib' or 'lzo'). Default is None. """ % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'TMcwvz:') except: sys.stderr.write(usage) sys.exit(1) # default options usepytables = False usememmap = False docompute = False dowrite = False verbose = False complib = None # Get the options for option in opts: if option[0] == '-T': usepytables = True elif option[0] == '-M': usememmap = True elif option[0] == '-c': docompute = True elif option[0] == '-w': dowrite = True elif option[0] == '-v': verbose = True elif option[0] == '-z': complib = option[1] if complib not in ('blosc', 'lzo', 'zlib'): print("complib must be 'lzo' or 'zlib' " "and you passed: '%s'" % complib) sys.exit(1) # If not a backend selected, abort if not usepytables and not usememmap: print("Please select a backend:") print("PyTables.Expr: -T") print("NumPy.memmap: -M") sys.exit(1) # Select backend and do the benchmark if usepytables: what = "tables" if usememmap: what = "memmap" do_bench(what, docompute, dowrite, complib, verbose) PyTables-3.7.0/bench/get-figures-ranges.py000066400000000000000000000177061416254111300204040ustar00rootroot00000000000000from pathlib import Path from pylab import * linewidth = 2 #markers= ['+', ',', 'o', '.', 's', 'v', 'x', '>', '<', '^'] #markers= [ 'x', '+', 'o', 's', 'v', '^', '>', '<', ] markers = ['s', 'o', 'v', '^', '+', 'x', '>', '<', ] markersize = 8 def get_values(filename): sizes = [] values = [] isize = None for line in Path(filename).read_text().splitlines(): if line.startswith('range'): tmp = line.split(':')[1] tmp = tmp.strip() tmp = tmp[1:-1] lower, upper = int(tmp.split(',')[0]), int(tmp.split(',')[1]) isize = upper - lower # print "isize-->", isize if isize is None or isize == 0: continue if insert and line.startswith('Insert time'): tmp = line.split(':')[1] #itime = float(tmp[:tmp.index(',')]) itime = float(tmp) sizes.append(isize) values.append(itime) elif line.startswith('Index time'): tmp = line.split(':')[1] #xtime = float(tmp[:tmp.index(',')]) xtime = float(tmp) txtime += xtime if create_index and create_index in line: sizes.append(isize) values.append(xtime) elif create_total and txtime > xtime: sizes.append(isize) values.append(txtime) elif table_size and line.startswith('Table size'): tsize = float(line.split(':')[1]) sizes.append(isize) values.append(tsize) elif indexes_size and line.startswith('Indexes size'): xsize = float(line.split(':')[1]) sizes.append(isize) values.append(xsize) elif total_size and line.startswith('Full size'): fsize = float(line.split(':')[1]) sizes.append(isize) values.append(fsize) elif ((query or query_cold or query_warm) and line.startswith('[NOREP]')): tmp = line.split(':')[1] try: qtime = float(tmp[:tmp.index('+-')]) except ValueError: qtime = float(tmp) if colname in line: if query and '1st' in line: sizes.append(isize) values.append(qtime) elif query_cold and 'cold' in line: sizes.append(isize) values.append(qtime) elif query_warm and 'warm' in line: sizes.append(isize) values.append(qtime) return sizes, values def show_plot(plots, yaxis, legends, gtitle): xlabel('Number of hits') ylabel(yaxis) title(gtitle) #ylim(0, 100) grid(True) # legends = [f[f.find('-'):f.index('.out')] for f in filenames] # legends = [l.replace('-', ' ') for l in legends] #legend([p[0] for p in plots], legends, loc = "upper left") legend([p[0] for p in plots], legends, loc="best") #subplots_adjust(bottom=0.2, top=None, wspace=0.2, hspace=0.2) if outfile: savefig(outfile) else: show() if __name__ == '__main__': import sys import getopt usage = """usage: %s [-o file] [-t title] [--insert] [--create-index] [--create-total] [--table-size] [--indexes-size] [--total-size] [--query=colname] [--query-cold=colname] [--query-warm=colname] files -o filename for output (only .png and .jpg extensions supported) -t title of the plot --insert -- Insert time for table --create-index=colname -- Index time for column --create-total -- Total time for creation of table + indexes --table-size -- Size of table --indexes-size -- Size of all indexes --total-size -- Total size of table + indexes --query=colname -- Time for querying the specified column --query-cold=colname -- Time for querying the specified column (cold cache) --query-warm=colname -- Time for querying the specified column (warm cache) \n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'o:t:', ['insert', 'create-index=', 'create-total', 'table-size', 'indexes-size', 'total-size', 'query=', 'query-cold=', 'query-warm=', ]) except: sys.stderr.write(usage) sys.exit(0) progname = sys.argv[0] args = sys.argv[1:] # if we pass too few parameters, abort if len(pargs) < 1: sys.stderr.write(usage) sys.exit(0) # default options outfile = None insert = 0 create_index = None create_total = 0 table_size = 0 indexes_size = 0 total_size = 0 query = 0 query_cold = 0 query_warm = 0 colname = None yaxis = "No axis name" tit = None gtitle = "Please set a title!" # Get the options for option in opts: if option[0] == '-o': outfile = option[1] elif option[0] == '-t': tit = option[1] elif option[0] == '--insert': insert = 1 yaxis = "Time (s)" gtitle = "Insert time for table" elif option[0] == '--create-index': create_index = option[1] yaxis = "Time (s)" gtitle = "Create index time for column " + create_index elif option[0] == '--create-total': create_total = 1 yaxis = "Time (s)" gtitle = "Create time for table + indexes" elif option[0] == '--table-size': table_size = 1 yaxis = "Size (MB)" gtitle = "Table size" elif option[0] == '--indexes-size': indexes_size = 1 yaxis = "Size (MB)" gtitle = "Indexes size" elif option[0] == '--total-size': total_size = 1 yaxis = "Size (MB)" gtitle = "Total size (table + indexes)" elif option[0] == '--query': query = 1 colname = option[1] yaxis = "Time (s)" gtitle = "Query time for " + colname + " column (first query)" elif option[0] == '--query-cold': query_cold = 1 colname = option[1] yaxis = "Time (s)" gtitle = "Query time for " + colname + " column (cold cache)" elif option[0] == '--query-warm': query_warm = 1 colname = option[1] yaxis = "Time (s)" gtitle = "Query time for " + colname + " column (warm cache)" filenames = pargs if tit: gtitle = tit plots = [] legends = [] for filename in filenames: plegend = filename[filename.find('-'):filename.index('.out')] plegend = plegend.replace('-', ' ') xval, yval = get_values(filename) print(f"Values for {filename} --> {xval}, {yval}") if "PyTables" in filename or "pytables" in filename: plot = loglog(xval, yval, linewidth=2) #plot = semilogx(xval, yval, linewidth=2) plots.append(plot) setp(plot, marker=markers[0], markersize=markersize, linewidth=linewidth) else: plots.append(loglog(xval, yval, linewidth=3, color='m')) #plots.append(semilogx(xval, yval, linewidth=3, color='m')) #plots.append(semilogx(xval, yval, linewidth=5)) legends.append(plegend) if 0: # Per a introduir dades simulades si es vol... xval = [1000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000] # yval = [0.003, 0.005, 0.02, 0.06, 1.2, # 40, 210] yval = [0.0009, 0.0011, 0.0022, 0.005, 0.02, 0.2, 5.6] plots.append(loglog(xval, yval, linewidth=5)) legends.append("PyTables Std") show_plot(plots, yaxis, legends, gtitle) PyTables-3.7.0/bench/get-figures.py000066400000000000000000000245231416254111300171220ustar00rootroot00000000000000from pylab import * linewidth = 2 #markers= ['+', ',', 'o', '.', 's', 'v', 'x', '>', '<', '^'] #markers= [ 'x', '+', 'o', 's', 'v', '^', '>', '<', ] markers = ['s', 'o', 'v', '^', '+', 'x', '>', '<', ] markersize = 8 def get_values(filename): sizes = [] values = [] for line in Path(filename).read_text().splitlines(): if line.startswith('Processing database:'): txtime = 0 line = line.split(':')[1] # Check if entry is compressed and if has to be processed line = line[:line.rfind('.')] params = line.split('-') for param in params: if param[-1] in ('k', 'm', 'g'): size = param isize = int(size[:-1]) * 1000 if size[-1] == "m": isize *= 1000 elif size[-1] == "g": isize *= 1000 * 1000 elif insert and line.startswith('Insert time'): tmp = line.split(':')[1] itime = float(tmp) sizes.append(isize) values.append(itime) elif (overlaps or entropy) and line.startswith('overlaps'): tmp = line.split(':')[1] e1, e2 = tmp.split() if isize in sizes: sizes.pop() values.pop() sizes.append(isize) if overlaps: values.append(int(e1) + 1) else: values.append(float(e2) + 1) elif (create_total or create_index) and line.startswith('Index time'): tmp = line.split(':')[1] xtime = float(tmp) txtime += xtime if create_index and create_index in line: sizes.append(isize) values.append(xtime) elif create_total and txtime > xtime: sizes.append(isize) values.append(txtime) elif table_size and line.startswith('Table size'): tsize = float(line.split(':')[1]) sizes.append(isize) values.append(tsize) elif indexes_size and line.startswith('Indexes size'): xsize = float(line.split(':')[1]) sizes.append(isize) values.append(xsize) elif total_size and line.startswith('Full size'): fsize = float(line.split(':')[1]) sizes.append(isize) values.append(fsize) elif query and line.startswith('Query time'): tmp = line.split(':')[1] qtime = float(tmp) if colname in line: sizes.append(isize) values.append(qtime) elif ((query or query_cold or query_warm) and line.startswith('[NOREP]')): tmp = line.split(':')[1] try: qtime = float(tmp[:tmp.index('+-')]) except ValueError: qtime = float(tmp) if colname in line: if query and '1st' in line: sizes.append(isize) values.append(qtime) elif query_cold and 'cold' in line: sizes.append(isize) values.append(qtime) elif query_warm and 'warm' in line: sizes.append(isize) values.append(qtime) elif query_repeated and line.startswith('[REP]'): if colname in line and 'warm' in line: tmp = line.split(':')[1] qtime = float(tmp[:tmp.index('+-')]) sizes.append(isize) values.append(qtime) return sizes, values def show_plot(plots, yaxis, legends, gtitle): xlabel('Number of rows') ylabel(yaxis) title(gtitle) #xlim(10**3, 10**9) xlim(10 ** 3, 10 ** 10) # ylim(1.0e-5) #ylim(-1e4, 1e5) #ylim(-1e3, 1e4) #ylim(-1e2, 1e3) grid(True) # legends = [f[f.find('-'):f.index('.out')] for f in filenames] # legends = [l.replace('-', ' ') for l in legends] legend([p[0] for p in plots], legends, loc="upper left") #legend([p[0] for p in plots], legends, loc = "center left") #subplots_adjust(bottom=0.2, top=None, wspace=0.2, hspace=0.2) if outfile: savefig(outfile) else: show() if __name__ == '__main__': import sys import getopt usage = """usage: %s [-o file] [-t title] [--insert] [--create-index] [--create-total] [--overlaps] [--entropy] [--table-size] [--indexes-size] [--total-size] [--query=colname] [--query-cold=colname] [--query-warm=colname] [--query-repeated=colname] files -o filename for output (only .png and .jpg extensions supported) -t title of the plot --insert -- Insert time for table --create-index=colname -- Index time for column --create-total -- Total time for creation of table + indexes --overlaps -- The overlapping for the created index --entropy -- The entropy for the created index --table-size -- Size of table --indexes-size -- Size of all indexes --total-size -- Total size of table + indexes --query=colname -- Time for querying the specified column --query-cold=colname -- Time for querying the specified column (cold cache) --query-warm=colname -- Time for querying the specified column (warm cache) --query-repeated=colname -- Time for querying the specified column (rep query) \n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'o:t:', ['insert', 'create-index=', 'create-total', 'overlaps', 'entropy', 'table-size', 'indexes-size', 'total-size', 'query=', 'query-cold=', 'query-warm=', 'query-repeated=', ]) except: sys.stderr.write(usage) sys.exit(0) progname = sys.argv[0] args = sys.argv[1:] # if we pass too few parameters, abort if len(pargs) < 1: sys.stderr.write(usage) sys.exit(0) # default options outfile = None insert = 0 create_index = None create_total = 0 overlaps = 0 entropy = 0 table_size = 0 indexes_size = 0 total_size = 0 query = 0 query_cold = 0 query_warm = 0 query_repeated = 0 colname = None yaxis = "No axis name" tit = None gtitle = "Please set a title!" # Get the options for option in opts: if option[0] == '-o': outfile = option[1] elif option[0] == '-t': tit = option[1] elif option[0] == '--insert': insert = 1 yaxis = "Time (s)" gtitle = "Insert time for table" elif option[0] == '--create-index': create_index = option[1] yaxis = "Time (s)" gtitle = "Create index time for " + create_index + " column" elif option[0] == '--create-total': create_total = 1 yaxis = "Time (s)" gtitle = "Create time for table + indexes" elif option[0] == '--overlaps': overlaps = 1 yaxis = "Overlapping index + 1" gtitle = "Overlapping for col4 column" elif option[0] == '--entropy': entropy = 1 yaxis = "Entropy + 1" gtitle = "Entropy for col4 column" elif option[0] == '--table-size': table_size = 1 yaxis = "Size (MB)" gtitle = "Table size" elif option[0] == '--indexes-size': indexes_size = 1 yaxis = "Size (MB)" #gtitle = "Indexes size" gtitle = "Index size for col4 column" elif option[0] == '--total-size': total_size = 1 yaxis = "Size (MB)" gtitle = "Total size (table + indexes)" elif option[0] == '--query': query = 1 colname = option[1] yaxis = "Time (s)" gtitle = "Query time for " + colname + " column (first query)" elif option[0] == '--query-cold': query_cold = 1 colname = option[1] yaxis = "Time (s)" gtitle = "Query time for " + colname + " column (cold cache)" elif option[0] == '--query-warm': query_warm = 1 colname = option[1] yaxis = "Time (s)" gtitle = "Query time for " + colname + " column (warm cache)" elif option[0] == '--query-repeated': query_repeated = 1 colname = option[1] yaxis = "Time (s)" gtitle = "Query time for " + colname + " column (repeated query)" gtitle = gtitle.replace('col2', 'Int32') gtitle = gtitle.replace('col4', 'Float64') filenames = pargs if tit: gtitle = tit plots = [] legends = [] for i, filename in enumerate(filenames): plegend = filename[:filename.index('.out')] plegend = plegend.replace('-', ' ') #plegend = plegend.replace('zlib1', '') if filename.find('PyTables') != -1: xval, yval = get_values(filename) print(f"Values for {filename} --> {xval}, {yval}") if xval != []: plot = loglog(xval, yval) #plot = semilogx(xval, yval) setp(plot, marker=markers[i], markersize=markersize, linewidth=linewidth) plots.append(plot) legends.append(plegend) else: xval, yval = get_values(filename) print(f"Values for {filename} --> {xval}, {yval}") plots.append(loglog(xval, yval, linewidth=3, color='m')) #plots.append(semilogx(xval, yval, linewidth=linewidth, color='m')) legends.append(plegend) if 0: # Per a introduir dades simulades si es vol... xval = [1000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000] # yval = [0.003, 0.005, 0.02, 0.06, 1.2, # 40, 210] yval = [0.0009, 0.0011, 0.0022, 0.005, 0.02, 0.2, 5.6] plots.append(loglog(xval, yval, linewidth=linewidth)) legends.append("PyTables Std") show_plot(plots, yaxis, legends, gtitle) PyTables-3.7.0/bench/indexed_search.py000066400000000000000000000412771416254111300176530ustar00rootroot00000000000000import random import subprocess from pathlib import Path from time import perf_counter as clock import numpy as np # Constants STEP = 1000 * 100 # the size of the buffer to fill the table, in rows SCALE = 0.1 # standard deviation of the noise compared with actual # values NI_NTIMES = 1 # The number of queries for doing a mean (non-idx cols) # COLDCACHE = 10 # The number of reads where the cache is considered 'cold' # WARMCACHE = 50 # The number of reads until the cache is considered 'warmed' # READ_TIMES = WARMCACHE+50 # The number of complete calls to DB.query_db() # COLDCACHE = 50 # The number of reads where the cache is considered 'cold' # WARMCACHE = 50 # The number of reads until the cache is considered 'warmed' # READ_TIMES = WARMCACHE+50 # The number of complete calls to DB.query_db() MROW = 1000 * 1000 # Test values COLDCACHE = 5 # The number of reads where the cache is considered 'cold' WARMCACHE = 5 # The number of reads until the cache is considered 'warmed' READ_TIMES = 10 # The number of complete calls to DB.query_db() # global variables rdm_cod = ['lin', 'rnd'] prec = 6 # precision for printing floats purposes def get_nrows(nrows_str): powers = {'k': 3, 'm': 6, 'g': 9} try: return int(float(nrows_str[:-1]) * 10 ** powers[nrows_str[-1]]) except KeyError: raise ValueError( "value of nrows must end with either 'k', 'm' or 'g' suffixes.") class DB: def __init__(self, nrows, rng, userandom): global step, scale self.step = STEP self.scale = SCALE self.rng = rng self.userandom = userandom self.filename = '-'.join([rdm_cod[userandom], nrows]) self.nrows = get_nrows(nrows) def get_db_size(self): sout = subprocess.Popen("sync;du -s %s" % self.filename, shell=True, stdout=subprocess.PIPE).stdout line = [l for l in sout][0] return int(line.split()[0]) def print_mtime(self, t1, explain): mtime = clock() - t1 print(f"{explain}: {mtime:.6f}") print(f"Krows/s: {self.nrows / 1000 / mtime:.6f}") def print_qtime(self, colname, ltimes): qtime1 = ltimes[0] # First measured time qtime2 = ltimes[-1] # Last measured time print(f"Query time for {colname}: {qtime1:.6f}") print(f"Mrows/s: {self.nrows / MROW / qtime1:.6f}") print(f"Query time for {colname} (cached): {qtime2:.6f}") print(f"Mrows/s (cached): {self.nrows / MROW / qtime2:.6f}") def norm_times(self, ltimes): "Get the mean and stddev of ltimes, avoiding the extreme values." lmean = ltimes.mean() lstd = ltimes.std() ntimes = ltimes[ltimes < lmean + lstd] nmean = ntimes.mean() nstd = ntimes.std() return nmean, nstd def print_qtime_idx(self, colname, ltimes, repeated, verbose): if repeated: r = "[REP] " else: r = "[NOREP] " ltimes = np.array(ltimes) ntimes = len(ltimes) qtime1 = ltimes[0] # First measured time ctimes = ltimes[1:COLDCACHE] cmean, cstd = self.norm_times(ctimes) wtimes = ltimes[WARMCACHE:] wmean, wstd = self.norm_times(wtimes) if verbose: print("Times for cold cache:\n", ctimes) # print "Times for warm cache:\n", wtimes hist1, hist2 = np.histogram(wtimes) print(f"Histogram for warm cache: {hist1}\n{hist2}") print(f"{r}1st query time for {colname}: {qtime1:.{prec}f}") print(f"{r}Query time for {colname} (cold cache): " f"{cmean:.{prec}f} +- {cstd:.{prec}f}") print(f"{r}Query time for {colname} (warm cache): " f"{wmean:.{prec}f} +- {wstd:.{prec}f}") def print_db_sizes(self, init, filled, indexed): table_size = (filled - init) / 1024 indexes_size = (indexed - filled) / 1024 print(f"Table size (MB): {table_size:.3f}") print(f"Indexes size (MB): {indexes_size:.3f}") print(f"Full size (MB): {table_size + indexes_size:.3f}") def fill_arrays(self, start, stop): arr_f8 = np.arange(start, stop, dtype='float64') arr_i4 = np.arange(start, stop, dtype='int32') if self.userandom: arr_f8 += np.random.normal(0, stop * self.scale, size=stop - start) arr_i4 = np.array(arr_f8, dtype='int32') return arr_i4, arr_f8 def create_db(self, dtype, kind, optlevel, verbose): self.con = self.open_db(remove=1) self.create_table(self.con) init_size = self.get_db_size() t1 = clock() self.fill_table(self.con) table_size = self.get_db_size() self.print_mtime(t1, 'Insert time') self.index_db(dtype, kind, optlevel, verbose) indexes_size = self.get_db_size() self.print_db_sizes(init_size, table_size, indexes_size) self.close_db(self.con) def index_db(self, dtype, kind, optlevel, verbose): if dtype == "int": idx_cols = ['col2'] elif dtype == "float": idx_cols = ['col4'] else: idx_cols = ['col2', 'col4'] for colname in idx_cols: t1 = clock() self.index_col(self.con, colname, kind, optlevel, verbose) self.print_mtime(t1, 'Index time (%s)' % colname) def query_db(self, niter, dtype, onlyidxquery, onlynonidxquery, avoidfscache, verbose, inkernel): self.con = self.open_db() if dtype == "int": reg_cols = ['col1'] idx_cols = ['col2'] elif dtype == "float": reg_cols = ['col3'] idx_cols = ['col4'] else: reg_cols = ['col1', 'col3'] idx_cols = ['col2', 'col4'] if avoidfscache: rseed = int(np.random.randint(self.nrows)) else: rseed = 19 # Query for non-indexed columns np.random.seed(rseed) base = np.random.randint(self.nrows) if not onlyidxquery: for colname in reg_cols: ltimes = [] random.seed(rseed) for i in range(NI_NTIMES): t1 = clock() results = self.do_query(self.con, colname, base, inkernel) ltimes.append(clock() - t1) if verbose: print("Results len:", results) self.print_qtime(colname, ltimes) # Always reopen the file after *every* query loop. # Necessary to make the benchmark to run correctly. self.close_db(self.con) self.con = self.open_db() # Query for indexed columns if not onlynonidxquery: for colname in idx_cols: ltimes = [] np.random.seed(rseed) rndbase = np.random.randint(self.nrows, size=niter) # First, non-repeated queries for i in range(niter): base = rndbase[i] t1 = clock() results = self.do_query(self.con, colname, base, inkernel) #results, tprof = self.do_query( # self.con, colname, base, inkernel) ltimes.append(clock() - t1) if verbose: print("Results len:", results) self.print_qtime_idx(colname, ltimes, False, verbose) # Always reopen the file after *every* query loop. # Necessary to make the benchmark to run correctly. self.close_db(self.con) self.con = self.open_db() ltimes = [] # Second, repeated queries # for i in range(niter): # t1=time() # results = self.do_query( # self.con, colname, base, inkernel) # results, tprof = self.do_query(self.con, colname, base, inkernel) # ltimes.append(time()-t1) # if verbose: # print "Results len:", results # self.print_qtime_idx(colname, ltimes, True, verbose) # Print internal PyTables index tprof statistics #tprof = numpy.array(tprof) #tmean, tstd = self.norm_times(tprof) # print "tprof-->", round(tmean, prec), "+-", round(tstd, prec) # print "tprof hist-->", \ # numpy.histogram(tprof) # print "tprof raw-->", tprof # Always reopen the file after *every* query loop. # Necessary to make the benchmark to run correctly. self.close_db(self.con) self.con = self.open_db() # Finally, close the file. self.close_db(self.con) def close_db(self, con): con.close() if __name__ == "__main__": import sys import getopt try: import psyco psyco_imported = 1 except: psyco_imported = 0 usage = """usage: %s [-T] [-P] [-v] [-f] [-k] [-p] [-m] [-c] [-q] [-i] [-I] [-S] [-x] [-z complevel] [-l complib] [-R range] [-N niter] [-n nrows] [-d datadir] [-O level] [-t kind] [-s] col -Q [suplim] -T use Pytables -P use Postgres -v verbose -f do a profile of the run (only query functionality) -k do a profile for kcachegrind use (out file is 'indexed_search.kcg') -p use "psyco" if available -m use random values to fill the table -q do a query (both indexed and non-indexed versions) -i do a query (just indexed one) -I do a query (just in-kernel one) -S do a query (just standard one) -x choose a different seed for random numbers (i.e. avoid FS cache) -c create the database -z compress with zlib (no compression by default) -l use complib for compression (zlib used by default) -R select a range in a field in the form "start,stop" (def "0,10") -N number of iterations for reading -n sets the number of rows (in krows) in each table -d directory to save data (default: data.nobackup) -O set the optimization level for PyTables indexes -t select the index type: "medium" (default) or "full", "light", "ultralight" -s select a type column for operations ('int' or 'float'. def all) -Q do a repeteated query up to 10**value \n""" % sys.argv[0] try: opts, pargs = getopt.getopt( sys.argv[1:], 'TPvfkpmcqiISxz:l:R:N:n:d:O:t:s:Q:') except: sys.stderr.write(usage) sys.exit(1) # default options usepytables = 0 usepostgres = 0 verbose = 0 doprofile = 0 dokprofile = 0 usepsyco = 0 userandom = 0 docreate = 0 optlevel = 0 kind = "medium" docompress = 0 complib = "zlib" doquery = False onlyidxquery = False onlynonidxquery = False inkernel = True avoidfscache = 0 #rng = [-10, 10] rng = [-1000, -1000] repeatquery = 0 repeatvalue = 0 krows = '1k' niter = READ_TIMES dtype = "all" datadir = "data.nobackup" # Get the options for option in opts: if option[0] == '-T': usepytables = 1 elif option[0] == '-P': usepostgres = 1 elif option[0] == '-v': verbose = 1 elif option[0] == '-f': doprofile = 1 elif option[0] == '-k': dokprofile = 1 elif option[0] == '-p': usepsyco = 1 elif option[0] == '-m': userandom = 1 elif option[0] == '-c': docreate = 1 elif option[0] == '-q': doquery = True elif option[0] == '-i': doquery = True onlyidxquery = True elif option[0] == '-I': doquery = True onlynonidxquery = True elif option[0] == '-S': doquery = True onlynonidxquery = True inkernel = False elif option[0] == '-x': avoidfscache = 1 elif option[0] == '-z': docompress = int(option[1]) elif option[0] == '-l': complib = option[1] elif option[0] == '-R': rng = [int(i) for i in option[1].split(",")] elif option[0] == '-N': niter = int(option[1]) elif option[0] == '-n': krows = option[1] elif option[0] == '-d': datadir = option[1] elif option[0] == '-O': optlevel = int(option[1]) elif option[0] == '-t': if option[1] in ('full', 'medium', 'light', 'ultralight'): kind = option[1] else: print("kind should be either 'full', 'medium', 'light' or " "'ultralight'") sys.exit(1) elif option[0] == '-s': if option[1] in ('int', 'float'): dtype = option[1] else: print("column should be either 'int' or 'float'") sys.exit(1) elif option[0] == '-Q': repeatquery = 1 repeatvalue = int(option[1]) # If not database backend selected, abort if not usepytables and not usepostgres: print("Please select a backend:") print("PyTables: -T") print("Postgres: -P") sys.exit(1) # Create the class for the database if usepytables: from pytables_backend import PyTables_DB db = PyTables_DB(krows, rng, userandom, datadir, docompress, complib, kind, optlevel) elif usepostgres: from postgres_backend import Postgres_DB db = Postgres_DB(krows, rng, userandom) if not avoidfscache: # in order to always generate the same random sequence np.random.seed(20) if verbose: if userandom: print("using random values") if onlyidxquery: print("doing indexed queries only") if psyco_imported and usepsyco: psyco.bind(db.create_db) psyco.bind(db.query_db) if docreate: if verbose: print("writing %s rows" % krows) db.create_db(dtype, kind, optlevel, verbose) if doquery: print("Calling query_db() %s times" % niter) if doprofile: import pstats import cProfile as prof prof.run( 'db.query_db(niter, dtype, onlyidxquery, onlynonidxquery, ' 'avoidfscache, verbose, inkernel)', 'indexed_search.prof') stats = pstats.Stats('indexed_search.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') if verbose: stats.print_stats() else: stats.print_stats(20) elif dokprofile: from cProfile import Profile import lsprofcalltree prof = Profile() prof.run( 'db.query_db(niter, dtype, onlyidxquery, onlynonidxquery, ' 'avoidfscache, verbose, inkernel)') kcg = lsprofcalltree.KCacheGrind(prof) with Path('indexed_search.kcg').open('w') as ofile: kcg.output(ofile) elif doprofile: import hotshot import hotshot.stats prof = hotshot.Profile("indexed_search.prof") benchtime, stones = prof.run( 'db.query_db(niter, dtype, onlyidxquery, onlynonidxquery, ' 'avoidfscache, verbose, inkernel)') prof.close() stats = hotshot.stats.load("indexed_search.prof") stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats(20) else: db.query_db(niter, dtype, onlyidxquery, onlynonidxquery, avoidfscache, verbose, inkernel) if repeatquery: # Start by a range which is almost None db.rng = [1, 1] if verbose: print("range:", db.rng) db.query_db(niter, dtype, onlyidxquery, onlynonidxquery, avoidfscache, verbose, inkernel) for i in range(repeatvalue): for j in (1, 2, 5): rng = j * 10 ** i db.rng = [-rng / 2, rng / 2] if verbose: print("range:", db.rng) # if usepostgres: # os.system( # "echo 1 > /proc/sys/vm/drop_caches;" # " /etc/init.d/postgresql restart") # else: # os.system("echo 1 > /proc/sys/vm/drop_caches") db.query_db(niter, dtype, onlyidxquery, onlynonidxquery, avoidfscache, verbose, inkernel) PyTables-3.7.0/bench/keysort.py000066400000000000000000000015661416254111300164030ustar00rootroot00000000000000from time import perf_counter as clock import numpy as np import tables as tb N = 1000 * 1000 rnd = np.random.randint(N, size=N) for dtype1 in ('S6', 'b1', 'i1', 'i2', 'i4', 'i8', 'u1', 'u2', 'u4', 'u8', 'f4', 'f8'): for dtype2 in ('u4', 'i8'): print("dtype array1, array2-->", dtype1, dtype2) a = np.array(rnd, dtype1) b = np.arange(N, dtype=dtype2) c = a.copy() t1 = clock() d = c.argsort() # c.sort() # e=c e = c[d] f = b[d] tref = clock() - t1 print("normal sort time-->", tref) t1 = clock() tb.indexesextension.keysort(a, b) tks = clock() - t1 print("keysort time-->", tks, " {:.2f}x".format(tref / tks)) assert np.alltrue(a == e) #assert numpy.alltrue(b == d) assert np.alltrue(f == d) PyTables-3.7.0/bench/lookup_bench.py000066400000000000000000000170611416254111300173500ustar00rootroot00000000000000"""Benchmark to help choosing the best chunksize so as to optimize the access time in random lookups.""" import subprocess from pathlib import Path from time import perf_counter as clock import numpy as np import tables as tb # Constants NOISE = 1e-15 # standard deviation of the noise compared with actual values rdm_cod = ['lin', 'rnd'] def get_nrows(nrows_str): powers = {'k': 3, 'm': 6, 'g': 9} try: return int(float(nrows_str[:-1]) * 10 ** powers[nrows_str[-1]]) except KeyError: raise ValueError( "value of nrows must end with either 'k', 'm' or 'g' suffixes.") class DB: def __init__(self, nrows, dtype, chunksize, userandom, datadir, docompress=0, complib='zlib'): self.dtype = dtype self.docompress = docompress self.complib = complib self.filename = '-'.join([rdm_cod[userandom], "n" + nrows, "s" + chunksize, dtype]) # Complete the filename self.filename = "lookup-" + self.filename if docompress: self.filename += '-' + complib + str(docompress) self.filename = datadir + '/' + self.filename + '.h5' print("Processing database:", self.filename) self.userandom = userandom self.nrows = get_nrows(nrows) self.chunksize = get_nrows(chunksize) self.step = self.chunksize self.scale = NOISE def get_db_size(self): sout = subprocess.Popen("sync;du -s %s" % self.filename, shell=True, stdout=subprocess.PIPE).stdout line = [l for l in sout][0] return int(line.split()[0]) def print_mtime(self, t1, explain): mtime = clock() - t1 print(f"{explain}: {mtime:.6f}") print(f"Krows/s: {self.nrows / 1000 / mtime:.6f}") def print_db_sizes(self, init, filled): array_size = (filled - init) / 1024 print(f"Array size (MB): {array_size:.3f}") def open_db(self, remove=0): if remove and Path(self.filename).is_file(): Path(self.filename).unlink() con = tb.open_file(self.filename, 'a') return con def create_db(self, verbose): self.con = self.open_db(remove=1) self.create_array() init_size = self.get_db_size() t1 = clock() self.fill_array() array_size = self.get_db_size() self.print_mtime(t1, 'Insert time') self.print_db_sizes(init_size, array_size) self.close_db() def create_array(self): # The filters chosen filters = tb.Filters(complevel=self.docompress, complib=self.complib) atom = tb.Atom.from_kind(self.dtype) self.con.create_earray(self.con.root, 'earray', atom, (0,), filters=filters, expectedrows=self.nrows, chunkshape=(self.chunksize,)) def fill_array(self): "Fills the array" earray = self.con.root.earray j = 0 arr = self.get_array(0, self.step) for i in range(0, self.nrows, self.step): stop = (j + 1) * self.step if stop > self.nrows: stop = self.nrows ###arr = self.get_array(i, stop, dtype) earray.append(arr) j += 1 earray.flush() def get_array(self, start, stop): arr = np.arange(start, stop, dtype='float') if self.userandom: arr += np.random.normal(0, stop * self.scale, size=stop - start) arr = arr.astype(self.dtype) return arr def print_qtime(self, ltimes): ltimes = np.array(ltimes) print("Raw query times:\n", ltimes) print("Histogram times:\n", np.histogram(ltimes[1:])) ntimes = len(ltimes) qtime1 = ltimes[0] # First measured time if ntimes > 5: # Wait until the 5th iteration (in order to # ensure that the index is effectively cached) to take times qtime2 = sum(ltimes[5:]) / (ntimes - 5) else: qtime2 = ltimes[-1] # Last measured time print(f"1st query time: {qtime1:.3f}") print(f"Mean (skipping the first 5 meas.): {qtime2:.3f}") def query_db(self, niter, avoidfscache, verbose): self.con = self.open_db() earray = self.con.root.earray if avoidfscache: rseed = int(np.random.randint(self.nrows)) else: rseed = 19 np.random.seed(rseed) np.random.randint(self.nrows) ltimes = [] for i in range(niter): t1 = clock() self.do_query(earray, np.random.randint(self.nrows)) ltimes.append(clock() - t1) self.print_qtime(ltimes) self.close_db() def do_query(self, earray, idx): return earray[idx] def close_db(self): self.con.close() if __name__ == "__main__": import sys import getopt usage = """usage: %s [-v] [-m] [-c] [-q] [-x] [-z complevel] [-l complib] [-N niter] [-n nrows] [-d datadir] [-t] type [-s] chunksize -v verbose -m use random values to fill the array -q do a (random) lookup -x choose a different seed for random numbers (i.e. avoid FS cache) -c create the file -z compress with zlib (no compression by default) -l use complib for compression (zlib used by default) -N number of iterations for reading -n sets the number of rows in the array -d directory to save data (default: data.nobackup) -t select the type for array ('int' or 'float'. def 'float') -s select the chunksize for array \n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'vmcqxz:l:N:n:d:t:s:') except: sys.stderr.write(usage) sys.exit(0) # default options verbose = 0 userandom = 0 docreate = 0 optlevel = 0 docompress = 0 complib = "zlib" doquery = False avoidfscache = 0 krows = '1k' chunksize = '32k' niter = 50 datadir = "data.nobackup" dtype = "float" # Get the options for option in opts: if option[0] == '-v': verbose = 1 elif option[0] == '-m': userandom = 1 elif option[0] == '-c': docreate = 1 createindex = 1 elif option[0] == '-q': doquery = True elif option[0] == '-x': avoidfscache = 1 elif option[0] == '-z': docompress = int(option[1]) elif option[0] == '-l': complib = option[1] elif option[0] == '-N': niter = int(option[1]) elif option[0] == '-n': krows = option[1] elif option[0] == '-d': datadir = option[1] elif option[0] == '-t': if option[1] in ('int', 'float'): dtype = option[1] else: print("type should be either 'int' or 'float'") sys.exit(0) elif option[0] == '-s': chunksize = option[1] if not avoidfscache: # in order to always generate the same random sequence np.random.seed(20) if verbose: if userandom: print("using random values") db = DB(krows, dtype, chunksize, userandom, datadir, docompress, complib) if docreate: if verbose: print("writing %s rows" % krows) db.create_db(verbose) if doquery: print("Calling query_db() %s times" % niter) db.query_db(niter, avoidfscache, verbose) PyTables-3.7.0/bench/open_close-bench.py000066400000000000000000000156561416254111300201130ustar00rootroot00000000000000"""Testbed for open/close PyTables files. This uses the HotShot profiler. """ import os import sys import getopt import pstats import cProfile as prof from pathlib import Path from time import perf_counter as clock import tables as tb filename = None niter = 1 def show_stats(explain, tref): "Show the used memory" for line in Path('/proc/self/status').read_text().splitlines(): if line.startswith("VmSize:"): vmsize = int(line.split()[1]) elif line.startswith("VmRSS:"): vmrss = int(line.split()[1]) elif line.startswith("VmData:"): vmdata = int(line.split()[1]) elif line.startswith("VmStk:"): vmstk = int(line.split()[1]) elif line.startswith("VmExe:"): vmexe = int(line.split()[1]) elif line.startswith("VmLib:"): vmlib = int(line.split()[1]) print("WallClock time:", clock() - tref) print("Memory usage: ******* %s *******" % explain) print(f"VmSize: {vmsize:>7} kB\tVmRSS: {vmrss:>7} kB") print(f"VmData: {vmdata:>7} kB\tVmStk: {vmstk:>7} kB") print(f"VmExe: {vmexe:>7} kB\tVmLib: {vmlib:>7} kB") def check_open_close(): for i in range(niter): print( "------------------ open_close #%s -------------------------" % i) tref = clock() fileh = tb.open_file(filename) fileh.close() show_stats("After closing file", tref) def check_only_open(): for i in range(niter): print("------------------ only_open #%s -------------------------" % i) tref = clock() fileh = tb.open_file(filename) show_stats("Before closing file", tref) fileh.close() def check_full_browse(): for i in range(niter): print("------------------ full_browse #%s -----------------------" % i) tref = clock() fileh = tb.open_file(filename) for node in fileh: pass fileh.close() show_stats("After full browse", tref) def check_partial_browse(): for i in range(niter): print("------------------ partial_browse #%s --------------------" % i) tref = clock() fileh = tb.open_file(filename) for node in fileh.root.ngroup0.ngroup1: pass fileh.close() show_stats("After closing file", tref) def check_full_browse_attrs(): for i in range(niter): print("------------------ full_browse_attrs #%s -----------------" % i) tref = clock() fileh = tb.open_file(filename) for node in fileh: # Access to an attribute klass = node._v_attrs.CLASS fileh.close() show_stats("After full browse", tref) def check_partial_browse_attrs(): for i in range(niter): print("------------------ partial_browse_attrs #%s --------------" % i) tref = clock() fileh = tb.open_file(filename) for node in fileh.root.ngroup0.ngroup1: # Access to an attribute klass = node._v_attrs.CLASS fileh.close() show_stats("After closing file", tref) def check_open_group(): for i in range(niter): print("------------------ open_group #%s ------------------------" % i) tref = clock() fileh = tb.open_file(filename) group = fileh.root.ngroup0.ngroup1 # Access to an attribute klass = group._v_attrs.CLASS fileh.close() show_stats("After closing file", tref) def check_open_leaf(): for i in range(niter): print("------------------ open_leaf #%s -----------------------" % i) tref = clock() fileh = tb.open_file(filename) leaf = fileh.root.ngroup0.ngroup1.array9 # Access to an attribute klass = leaf._v_attrs.CLASS fileh.close() show_stats("After closing file", tref) if __name__ == '__main__': usage = """usage: %s [-v] [-p] [-n niter] [-O] [-o] [-B] [-b] [-g] [-l] [-A] [-a] [-E] [-S] datafile -v verbose (total dump of profiling) -p do profiling -n number of iterations for reading -O Check open_close -o Check only_open -B Check full browse -b Check partial browse -A Check full browse and reading one attr each node -a Check partial browse and reading one attr each node -g Check open nested group -l Check open nested leaf -E Check everything -S Check everything as subprocess \n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'vpn:OoBbAaglESs') except: sys.stderr.write(usage) sys.exit(0) progname = sys.argv[0] args = sys.argv[1:] # if we pass too much parameters, abort if len(pargs) != 1: sys.stderr.write(usage) sys.exit(0) # default options verbose = 0 silent = 0 # if silent, does not print the final statistics profile = 0 all_checks = 0 all_system_checks = 0 func = [] # Checking options options = ['-O', '-o', '-B', '-b', '-A', '-a', '-g', '-l'] # Dict to map options to checking functions option2func = { '-O': 'check_open_close', '-o': 'check_only_open', '-B': 'check_full_browse', '-b': 'check_partial_browse', '-A': 'check_full_browse_attrs', '-a': 'check_partial_browse_attrs', '-g': 'check_open_group', '-l': 'check_open_leaf', } # Get the options for option in opts: if option[0] == '-v': verbose = 1 elif option[0] == '-p': profile = 1 elif option[0] in option2func: func.append(option2func[option[0]]) elif option[0] == '-E': all_checks = 1 for opt in options: func.append(option2func[opt]) elif option[0] == '-S': all_system_checks = 1 elif option[0] == '-s': silent = 1 elif option[0] == '-n': niter = int(option[1]) filename = pargs[0] tref = clock() if all_system_checks: args.remove('-S') # We don't want -S in the options list again for opt in options: opts = r"{} \-s {} {}".format(progname, opt, " ".join(args)) # print "opts-->", opts os.system("python2.4 %s" % opts) else: if profile: for ifunc in func: prof.run(ifunc + '()', ifunc + '.prof') stats = pstats.Stats(ifunc + '.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') if verbose: stats.print_stats() else: stats.print_stats(20) else: for ifunc in func: eval(ifunc + '()') if not silent: print("------------------ End of run -------------------------") show_stats("Final statistics (after closing everything)", tref) PyTables-3.7.0/bench/opteron-stress-test.txt000066400000000000000000000034771416254111300210610ustar00rootroot00000000000000Stress test on a 64 bits AMD Opteron platform ============================================= 2004-02-04. F. Alted Platform description: 4 processors AMD Opteron (64-bits) @ 1.6 GHz and 1 MB cache 8 GB RAM HD IBM DeskStar 120GXP 80 GB ATA/100 2 MB cache @ 7200 rpm SuSe Linux Enterprise Server (SLES) Linux kernel 2.4.21-178-smp ReiserFS filesystem Here's the command to do the stress test: time python /tmp/stress-test3.py -l zlib -c 6 -g400 -t 300 -i 20000 /tmp/test-big-zlib-6.h5 ls -lh /tmp/test-big-zlib-6.h5 The output: Compression level: 6 Compression library: zlib Rows written: 2400000000 Row size: 512 Time writing rows: 56173.557 s (real) 56154.84 s (cpu) 100% Write rows/sec: 42724 Write KB/s : 21362 Rows read: 2400000000 Row size: 512 Buf size: 39936 Time reading rows: 29339.936 s (real) 29087.88 s (cpu) 99% Read rows/sec: 81799 Read KB/s : 40899 real 1425m43.846s user 1308m34.340s sys 112m17.100s -rw-r--r-- 1 falted users 2.7G 2004-02-04 02:25 /tmp/test-big-zlib-6 .h5 The maximum amount of RAM taken by the test should be less than 300 MB (241 MB when the test was running for 5750 minutes, which is the last time I've check for it). Another test with the same machine: time python /tmp/stress-test3.py -l zlib -c 6 -g400 -t 300 -i 100000 /tmp/test-big-zlib-6-2.h5 ls -lh /tmp/test-big-zlib-6-2.h5 Compression level: 6 Compression library: zlib Rows written: 12000000000 Row size: 512 Time writing rows: 262930.901 s (real) 262619.72 s (cpu) 100% Write rows/sec: 45639 Write KB/s : 22819 Rows read: 12000000000 Row size: 512 Buf size: 49664 Time reading rows: 143171.761 s (real) 141560.42 s (cpu) 99% Read rows/sec: 83815 Read KB/s : 41907 real 6768m34.076s user 6183m38.690s sys 552m51.150s -rw-r--r-- 1 5350 users 11G 2004-02-09 00:57 /tmp/test-big-zlib-6 -2.h5 PyTables-3.7.0/bench/optimal-chunksize.py000066400000000000000000000073151416254111300203470ustar00rootroot00000000000000"""Small benchmark on the effect of chunksizes and compression on HDF5 files. Francesc Alted 2007-11-25 """ import math import subprocess import tempfile from pathlib import Path from time import perf_counter as clock import numpy as np import tables as tb # Size of dataset # N, M = 512, 2**16 # 256 MB # N, M = 512, 2**18 # 1 GB # N, M = 512, 2**19 # 2 GB N, M = 2000, 1_000_000 # 15 GB # N, M = 4000, 1000000 # 30 GB datom = tb.Float64Atom() # elements are double precision def quantize(data, least_significant_digit): """Quantize data to improve compression. data is quantized using around(scale*data)/scale, where scale is 2**bits, and bits is determined from the least_significant_digit. For example, if least_significant_digit=1, bits will be 4. """ precision = 10 ** -least_significant_digit exp = math.log(precision, 10) if exp < 0: exp = math.floor(exp) else: exp = math.ceil(exp) bits = math.ceil(math.log(10 ** -exp, 2)) scale = 2 ** bits return np.around(scale * data) / scale def get_db_size(filename): sout = subprocess.Popen("ls -sh %s" % filename, shell=True, stdout=subprocess.PIPE).stdout line = [l for l in sout][0] return line.split()[0] def bench(chunkshape, filters): np.random.seed(1) # to have reproductible results filename = tempfile.mktemp(suffix='.h5') print("Doing test on the file system represented by:", filename) f = tb.open_file(filename, 'w') e = f.create_earray(f.root, 'earray', datom, shape=(0, M), filters = filters, chunkshape = chunkshape) # Fill the array t1 = clock() for i in range(N): # e.append([numpy.random.rand(M)]) # use this for less compressibility e.append([quantize(np.random.rand(M), 6)]) # os.system("sync") print(f"Creation time: {clock() - t1:.3f}", end=' ') filesize = get_db_size(filename) filesize_bytes = Path(filename).stat().st_size print("\t\tFile size: %d -- (%s)" % (filesize_bytes, filesize)) # Read in sequential mode: e = f.root.earray t1 = clock() # Flush everything to disk and flush caches #os.system("sync; echo 1 > /proc/sys/vm/drop_caches") for row in e: t = row print(f"Sequential read time: {clock() - t1:.3f}", end=' ') # f.close() # return # Read in random mode: i_index = np.random.randint(0, N, 128) j_index = np.random.randint(0, M, 256) # Flush everything to disk and flush caches #os.system("sync; echo 1 > /proc/sys/vm/drop_caches") # Protection against too large chunksizes # 4 MB if 0 and filters.complevel and chunkshape[0] * chunkshape[1] * 8 > 2 ** 22: f.close() return t1 = clock() for i in i_index: for j in j_index: t = e[i, j] print(f"\tRandom read time: {clock() - t1:.3f}") f.close() # Benchmark with different chunksizes and filters # for complevel in (0, 1, 3, 6, 9): for complib in (None, 'zlib', 'lzo', 'blosc'): # for complib in ('blosc',): if complib: filters = tb.Filters(complevel=5, complib=complib) else: filters = tb.Filters(complevel=0) print("8<--" * 20, "\nFilters:", filters, "\n" + "-" * 80) # for ecs in (11, 14, 17, 20, 21, 22): for ecs in range(10, 24): # for ecs in (19,): chunksize = 2 ** ecs chunk1 = 1 chunk2 = chunksize / datom.itemsize if chunk2 > M: chunk1 = chunk2 / M chunk2 = M chunkshape = (chunk1, chunk2) cs_str = str(chunksize / 1024) + " KB" print("***** Chunksize:", cs_str, "/ Chunkshape:", chunkshape, "*****") bench(chunkshape, filters) PyTables-3.7.0/bench/plot-bar.py000066400000000000000000000060101416254111300164100ustar00rootroot00000000000000#!/usr/bin/env python # a stacked bar plot with errorbars from pathlib import Path from pylab import * checks = ['open_close', 'only_open', 'full_browse', 'partial_browse', 'full_browse_attrs', 'partial_browse_attrs', 'open_group', 'open_leaf', 'total'] width = 0.15 # the width of the bars: can also be len(x) sequence colors = ['r', 'm', 'g', 'y', 'b'] ind = arange(len(checks)) # the x locations for the groups def get_values(filename): values = [] for line in Path(filename).read_text().splitlines(): if show_memory: if line.startswith('VmData:'): values.append(float(line.split()[1]) / 1024) else: if line.startswith('WallClock time:'): values.append(float(line.split(':')[1])) return values def plot_bar(values, n): global ind if not gtotal: # Remove the grand totals values.pop() if n == 0: checks.pop() ind = arange(len(checks)) p = bar(ind + width * n, values, width, color=colors[n]) return p def show_plot(bars, filenames, tit): if show_memory: ylabel('Memory (MB)') else: ylabel('Time (s)') title(tit) n = len(filenames) xticks(ind + width * n / 2, checks, rotation=45, horizontalalignment='right', fontsize=8) if not gtotal: #loc = 'center right' loc = 'upper left' else: loc = 'center left' legends = [f[:f.index('_')] for f in filenames] legends = [l.replace('-', ' ') for l in legends] legend([p[0] for p in bars], legends, loc=loc) subplots_adjust(bottom=0.2, top=None, wspace=0.2, hspace=0.2) if outfile: savefig(outfile) else: show() if __name__ == '__main__': import sys import getopt usage = """usage: %s [-g] [-m] [-o file] [-t title] files -g grand total -m show memory instead of time -o filename for output (only .png and .jpg extensions supported) -t title of the plot \n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'gmo:t:') except: sys.stderr.write(usage) sys.exit(0) progname = sys.argv[0] args = sys.argv[1:] # if we pass too few parameters, abort if len(pargs) < 1: sys.stderr.write(usage) sys.exit(0) # default options tit = "Comparison of differents PyTables versions" gtotal = 0 show_memory = 0 outfile = None # Get the options for option in opts: if option[0] == '-g': gtotal = 1 elif option[0] == '-m': show_memory = 1 elif option[0] == '-o': outfile = option[1] elif option[0] == '-t': tit = option[1] filenames = pargs bars = [] n = 0 for filename in filenames: values = get_values(filename) print("Values-->", values) bars.append(plot_bar(values, n)) n += 1 show_plot(bars, filenames, tit) PyTables-3.7.0/bench/plot-comparison-lzo-zlib-ucl.gnuplot000066400000000000000000000017021416254111300234020ustar00rootroot00000000000000#set term post color set term post eps color set xlabel "Number of rows" set ylabel "Speed (Krow/s)" set linestyle 1 lw 7 set linestyle 2 lw 7 set linestyle 3 lw 7 set linestyle 4 lw 7 set logscale x # For small record size set output "read-small-lzo-zlib-ucl-comparison.eps" set tit "Selecting with small record size (16 bytes)" pl [1000:] [0:1000] "small-nc.out" u ($1):($10) t "No compression" w linesp ls 1, \ "small-zlib.out" u ($1):($10) t "ZLIB" w linesp ls 2, \ "small-lzo.out" u ($1):($10) t "LZO" w linesp ls 3, \ "small-ucl.out" u ($1):($10) t "UCL" w linesp ls 4 # For small record size set output "write-small-lzo-zlib-ucl-comparison.eps" set tit "Writing with small record size (16 bytes)" pl [1000:] [0:500] "small-nc.out" u ($1):($5) tit "No compression" w linesp ls 1, \ "small-zlib.out" u ($1):($5) tit "ZLIB" w linesp ls 2, \ "small-lzo.out" u ($1):($5) tit "LZO" w linesp ls 3, \ "small-ucl.out" u ($1):($5) tit "UCL" w linesp ls 4 PyTables-3.7.0/bench/plot-comparison-psyco-lzo.gnuplot000066400000000000000000000021661416254111300230230ustar00rootroot00000000000000#set term post color set term post eps color set xlabel "Number of rows" set ylabel "Speed (Krow/s)" set linestyle 1 lw 7 set linestyle 2 lw 7 set linestyle 3 lw 7 set linestyle 4 lw 7 # For small record size set output "read-small-psyco-lzo-comparison.eps" set tit "Selecting with small record size (16 bytes)" set logscale x pl [1000:] [0:1200] "small-psyco-lzo.out" u ($1):($10) t "Psyco & compression (LZO)" w linesp ls 2, \ "small-psyco-nc.out" u ($1):($10) tit "Psyco & no compresion" w linesp ls 3, \ "small-lzo.out" u ($1):($10) t "No Psyco & compression (LZO)" w linesp ls 1, \ "small-nc.out" u ($1):($10) tit "No Psyco & no compression" w linesp ls 4 # For small record size set output "write-small-psyco-lzo-comparison.eps" set tit "Writing with small record size (16 bytes)" set logscale x pl [1000:] [0:1000] "small-psyco-lzo.out" u ($1):($5) t "Psyco & compression (LZO)" w linesp ls 2, \ "small-psyco-nc.out" u ($1):($5) tit "Psyco & no compresion" w linesp ls 3, \ "small-lzo.out" u ($1):($5) t "No Psyco & compression (LZO)" w linesp ls 1, \ "small-nc.out" u ($1):($5) tit "No Psyco & no compression" w linesp ls 4 PyTables-3.7.0/bench/poly.py000066400000000000000000000142431416254111300156620ustar00rootroot00000000000000"""This script compares the speed of the computation of a polynomial for different (numpy.memmap and tables.Expr) out-of-memory paradigms.""" from pathlib import Path from time import perf_counter as clock import numpy as np import tables as tb import numexpr as ne expr = ".25*x**3 + .75*x**2 - 1.5*x - 2" # the polynomial to compute N = 10 * 1000 * 1000 # the number of points to compute expression (80 MB) step = 100 * 1000 # perform calculation in slices of `step` elements dtype = np.dtype('f8') # the datatype #CHUNKSHAPE = (2**17,) CHUNKSHAPE = None # Global variable for the x values for pure numpy & numexpr x = None # *** The next variables do not need to be changed *** # Filenames for numpy.memmap fprefix = "numpy.memmap" # the I/O file prefix mpfnames = [fprefix + "-x.bin", fprefix + "-r.bin"] # Filename for tables.Expr h5fname = "tablesExpr.h5" # the I/O file MB = 1024 * 1024 # a MegaByte def print_filesize(filename, clib=None, clevel=0): """Print some statistics about file sizes.""" # os.system("sync") # make sure that all data has been flushed to disk if isinstance(filename, list): filesize_bytes = sum(Path(fname).stat().st_size for fname in filename) else: filesize_bytes = Path(filename).stat().st_size print( f"\t\tTotal file sizes: {filesize_bytes} -- " f"({filesize_bytes / MB:.1f} MB)", end=' ') if clevel > 0: print(f"(using {clib} lvl{clevel})") else: print() def populate_x_numpy(): """Populate the values in x axis for numpy.""" global x # Populate x in range [-1, 1] x = np.linspace(-1, 1, N) def populate_x_memmap(): """Populate the values in x axis for numpy.memmap.""" # Create container for input x = np.memmap(mpfnames[0], dtype=dtype, mode="w+", shape=(N,)) # Populate x in range [-1, 1] for i in range(0, N, step): chunk = np.linspace((2 * i - N) / N, (2 * (i + step) - N) / N, step) x[i:i + step] = chunk del x # close x memmap def populate_x_tables(clib, clevel): """Populate the values in x axis for pytables.""" f = tb.open_file(h5fname, "w") # Create container for input atom = tb.Atom.from_dtype(dtype) filters = tb.Filters(complib=clib, complevel=clevel) x = f.create_carray(f.root, "x", atom=atom, shape=(N,), filters=filters, chunkshape=CHUNKSHAPE, ) # Populate x in range [-1, 1] for i in range(0, N, step): chunk = np.linspace((2 * i - N) / N, (2 * (i + step) - N) / N, step) x[i:i + step] = chunk f.close() def compute_numpy(): """Compute the polynomial with pure numpy.""" y = eval(expr) def compute_numexpr(): """Compute the polynomial with pure numexpr.""" y = ne.evaluate(expr) def compute_memmap(): """Compute the polynomial with numpy.memmap.""" # Reopen inputs in read-only mode x = np.memmap(mpfnames[0], dtype=dtype, mode='r', shape=(N,)) # Create the array output r = np.memmap(mpfnames[1], dtype=dtype, mode="w+", shape=(N,)) # Do the computation by chunks and store in output r[:] = eval(expr) # where is stored the result? # r = eval(expr) # result is stored in-memory del x, r # close x and r memmap arrays print_filesize(mpfnames) def compute_tables(clib, clevel): """Compute the polynomial with tables.Expr.""" f = tb.open_file(h5fname, "a") x = f.root.x # get the x input # Create container for output atom = tb.Atom.from_dtype(dtype) filters = tb.Filters(complib=clib, complevel=clevel) r = f.create_carray(f.root, "r", atom=atom, shape=(N,), filters=filters, chunkshape=CHUNKSHAPE, ) # Do the actual computation and store in output ex = tb.Expr(expr) # parse the expression ex.set_output(r) # where is stored the result? # when commented out, the result goes in-memory ex.eval() # evaluate! f.close() print_filesize(h5fname, clib, clevel) if __name__ == '__main__': tb.print_versions() print(f"Total size for datasets: {2 * N * dtype.itemsize / MB:.1f} MB") # Get the compression libraries supported # supported_clibs = [clib for clib in ("zlib", "lzo", "bzip2", "blosc") # supported_clibs = [clib for clib in ("zlib", "lzo", "blosc") supported_clibs = [clib for clib in ("blosc",) if tb.which_lib_version(clib)] # Initialization code # for what in ["numpy", "numpy.memmap", "numexpr"]: for what in ["numpy", "numexpr"]: # break print("Populating x using %s with %d points..." % (what, N)) t0 = clock() if what == "numpy": populate_x_numpy() compute = compute_numpy elif what == "numexpr": populate_x_numpy() compute = compute_numexpr elif what == "numpy.memmap": populate_x_memmap() compute = compute_memmap print(f"*** Time elapsed populating: {clock() - t0:.3f}") print(f"Computing: {expr!r} using {what}") t0 = clock() compute() print(f"**************** Time elapsed computing: {clock() - t0:.3f}") for what in ["tables.Expr"]: t0 = clock() first = True # Sentinel for clib in supported_clibs: # for clevel in (0, 1, 3, 6, 9): for clevel in range(10): # for clevel in (1,): if not first and clevel == 0: continue print("Populating x using %s with %d points..." % (what, N)) populate_x_tables(clib, clevel) print(f"*** Time elapsed populating: {clock() - t0:.3f}") print(f"Computing: {expr!r} using {what}") t0 = clock() compute_tables(clib, clevel) print( f"**************** Time elapsed computing: " f"{clock() - t0:.3f}") first = False PyTables-3.7.0/bench/postgres-search-bench.py000066400000000000000000000146601416254111300210700ustar00rootroot00000000000000from time import perf_counter as clock import numpy as np import random DSN = "dbname=test port = 5435" # in order to always generate the same random sequence random.seed(19) def flatten(l): """Flattens list of tuples l.""" return [x[0] for x in l] def fill_arrays(start, stop): col_i = np.arange(start, stop, type=np.int32) if userandom: col_j = np.random.uniform(0, nrows, size=[stop - start]) else: col_j = np.array(col_i, type=np.float64) return col_i, col_j # Generator for ensure pytables benchmark compatibility def int_generator(nrows): step = 1000 * 100 j = 0 for i in range(nrows): if i >= step * j: stop = (j + 1) * step if stop > nrows: # Seems unnecessary stop = nrows col_i, col_j = fill_arrays(i, stop) j += 1 k = 0 yield (col_i[k], col_j[k]) k += 1 def int_generator_slow(nrows): for i in range(nrows): if userandom: yield (i, float(random.randint(0, nrows))) else: yield (i, float(i)) class Stream32: "Object simulating a file for reading" def __init__(self): self.n = None self.read_it = self.read_iter() # No va! Hi ha que convertir a un de normal! def readline(self, n=None): for tup in int_generator(nrows): sout = "%s\t%s\n" % tup if n is not None and len(sout) > n: for i in range(0, len(sout), n): yield sout[i:i + n] else: yield sout def read_iter(self): sout = "" n = self.n for tup in int_generator(nrows): sout += "%s\t%s\n" % tup if n is not None and len(sout) > n: for i in range(n, len(sout), n): rout = sout[:n] sout = sout[n:] yield rout yield sout def read(self, n=None): self.n = n try: str = next(self.read_it) except StopIteration: str = "" return str def open_db(filename, remove=0): if not filename: con = sqlite.connect(DSN) else: con = sqlite.connect(filename) cur = con.cursor() return con, cur def create_db(filename, nrows): con, cur = open_db(filename, remove=1) try: cur.execute("create table ints(i integer, j double precision)") except: con.rollback() cur.execute("DROP TABLE ints") cur.execute("create table ints(i integer, j double precision)") con.commit() con.set_isolation_level(2) t1 = clock() st = Stream32() cur.copy_from(st, "ints") # In case of postgres, the speeds of generator and loop are similar #cur.executemany("insert into ints values (%s,%s)", int_generator(nrows)) # for i in xrange(nrows): # cur.execute("insert into ints values (%s,%s)", (i, float(i))) con.commit() ctime = clock() - t1 if verbose: print(f"insert time: {ctime:.5f}") print(f"Krows/s: {nrows / 1000 / ctime:.5f}") close_db(con, cur) def index_db(filename): con, cur = open_db(filename) t1 = clock() cur.execute("create index ij on ints(j)") con.commit() itime = clock() - t1 if verbose: print(f"index time: {itime:.5f}") print(f"Krows/s: {nrows / itime:.5f}") # Close the DB close_db(con, cur) def query_db(filename, rng): con, cur = open_db(filename) t1 = clock() ntimes = 10 for i in range(ntimes): # between clause does not seem to take advantage of indexes # cur.execute("select j from ints where j between %s and %s" % \ cur.execute("select i from ints where j >= %s and j <= %s" % # cur.execute("select i from ints where i >= %s and i <= # %s" % (rng[0] + i, rng[1] + i)) results = cur.fetchall() con.commit() qtime = (clock() - t1) / ntimes if verbose: print(f"query time: {qtime:.5f}") print(f"Mrows/s: {nrows / 1000 / qtime:.5f}") results = sorted(flatten(results)) print(results) close_db(con, cur) def close_db(con, cur): cur.close() con.close() if __name__ == "__main__": import sys import getopt try: import psyco psyco_imported = 1 except: psyco_imported = 0 usage = """usage: %s [-v] [-p] [-m] [-i] [-q] [-c] [-R range] [-n nrows] file -v verbose -p use "psyco" if available -m use random values to fill the table -q do query -c create the database -i index the table -2 use sqlite2 (default is use sqlite3) -R select a range in a field in the form "start,stop" (def "0,10") -n sets the number of rows (in krows) in each table \n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'vpmiqc2R:n:') except: sys.stderr.write(usage) sys.exit(0) # default options verbose = 0 usepsyco = 0 userandom = 0 docreate = 0 createindex = 0 doquery = 0 sqlite_version = "3" rng = [0, 10] nrows = 1 # Get the options for option in opts: if option[0] == '-v': verbose = 1 elif option[0] == '-p': usepsyco = 1 elif option[0] == '-m': userandom = 1 elif option[0] == '-i': createindex = 1 elif option[0] == '-q': doquery = 1 elif option[0] == '-c': docreate = 1 elif option[0] == "-2": sqlite_version = "2" elif option[0] == '-R': rng = [int(i) for i in option[1].split(",")] elif option[0] == '-n': nrows = int(option[1]) # Catch the hdf5 file passed as the last argument filename = pargs[0] # if sqlite_version == "2": # import sqlite # else: # from pysqlite2 import dbapi2 as sqlite import psycopg2 as sqlite if verbose: # print "pysqlite version:", sqlite.version if userandom: print("using random values") if docreate: if verbose: print("writing %s krows" % nrows) if psyco_imported and usepsyco: psyco.bind(create_db) nrows *= 1000 create_db(filename, nrows) if createindex: index_db(filename) if doquery: query_db(filename, rng) PyTables-3.7.0/bench/postgres_backend.py000066400000000000000000000120441416254111300202110ustar00rootroot00000000000000import subprocess from indexed_search import DB import psycopg2 as db2 CLUSTER_NAME = "base" DATA_DIR = "/scratch2/postgres/data/%s" % CLUSTER_NAME #DATA_DIR = "/var/lib/pgsql/data/%s" % CLUSTER_NAME DSN = "dbname=%s port=%s" CREATE_DB = "createdb %s" DROP_DB = "dropdb %s" TABLE_NAME = "intsfloats" PORT = 5432 class StreamChar: "Object simulating a file for reading" def __init__(self, db): self.db = db self.nrows = db.nrows self.step = db.step self.read_it = self.read_iter() def values_generator(self): j = 0 for i in range(self.nrows): if i >= j * self.step: stop = (j + 1) * self.step if stop > self.nrows: stop = self.nrows arr_i4, arr_f8 = self.db.fill_arrays(i, stop) j += 1 k = 0 yield (arr_i4[k], arr_i4[k], arr_f8[k], arr_f8[k]) k += 1 def read_iter(self): sout = "" n = self.nbytes for tup in self.values_generator(): sout += "%s\t%s\t%s\t%s\n" % tup if n is not None and len(sout) > n: for i in range(n, len(sout), n): rout = sout[:n] sout = sout[n:] yield rout yield sout def read(self, n=None): self.nbytes = n try: str = next(self.read_it) except StopIteration: str = "" return str # required by postgres2 driver, but not used def readline(self): pass class Postgres_DB(DB): def __init__(self, nrows, rng, userandom): DB.__init__(self, nrows, rng, userandom) self.port = PORT def flatten(self, l): """Flattens list of tuples l.""" return [x[0] for x in l] # return map(lambda x: x[col], l) # Overloads the method in DB class def get_db_size(self): sout = subprocess.Popen("sudo du -s %s" % DATA_DIR, shell=True, stdout=subprocess.PIPE).stdout line = [l for l in sout][0] return int(line.split()[0]) def open_db(self, remove=0): if remove: sout = subprocess.Popen(DROP_DB % self.filename, shell=True, stdout=subprocess.PIPE).stdout for line in sout: print(line) sout = subprocess.Popen(CREATE_DB % self.filename, shell=True, stdout=subprocess.PIPE).stdout for line in sout: print(line) print("Processing database:", self.filename) con = db2.connect(DSN % (self.filename, self.port)) self.cur = con.cursor() return con def create_table(self, con): self.cur.execute("""create table %s( col1 integer, col2 integer, col3 double precision, col4 double precision)""" % TABLE_NAME) con.commit() def fill_table(self, con): st = StreamChar(self) self.cur.copy_from(st, TABLE_NAME) con.commit() def index_col(self, con, colname, optlevel, idxtype, verbose): self.cur.execute("create index %s on %s(%s)" % (colname + '_idx', TABLE_NAME, colname)) con.commit() def do_query_simple(self, con, column, base): self.cur.execute( "select sum(%s) from %s where %s >= %s and %s <= %s" % (column, TABLE_NAME, column, base + self.rng[0], column, base + self.rng[1])) # "select * from %s where %s >= %s and %s <= %s" % \ # (TABLE_NAME, # column, base+self.rng[0], # column, base+self.rng[1])) #results = self.flatten(self.cur.fetchall()) results = self.cur.fetchall() return results def do_query(self, con, column, base, *unused): d = (self.rng[1] - self.rng[0]) / 2 inf1 = int(self.rng[0] + base) sup1 = int(self.rng[0] + d + base) inf2 = self.rng[0] + base * 2 sup2 = self.rng[0] + d + base * 2 # print "lims-->", inf1, inf2, sup1, sup2 condition = "((%s>=%s) and (%s<%s)) or ((col2>%s) and (col2<%s))" #condition = "((col3>=%s) and (col3<%s)) or ((col1>%s) and (col1<%s))" condition += " and ((col1+3.1*col2+col3*col4) > 3)" #condition += " and (sqrt(col1^2+col2^2+col3^2+col4^2) > .1)" condition = condition % (column, inf2, column, sup2, inf1, sup1) # print "condition-->", condition self.cur.execute( # "select sum(%s) from %s where %s" % "select %s from %s where %s" % (column, TABLE_NAME, condition)) #results = self.flatten(self.cur.fetchall()) results = self.cur.fetchall() #results = self.cur.fetchall() # print "results-->", results # return results return len(results) def close_db(self, con): self.cur.close() con.close() PyTables-3.7.0/bench/pytables-search-bench.py000066400000000000000000000144541416254111300210460ustar00rootroot00000000000000import random from pathlib import Path from time import perf_counter as clock import numpy as np import tables as tb # in order to always generate the same random sequence random.seed(19) np.random.seed((19, 20)) def open_db(filename, remove=0): if remove and Path(filename).is_file(): Path(filename).unlink() con = tb.open_file(filename, 'a') return con def create_db(filename, nrows): class Record(tb.IsDescription): col1 = tb.Int32Col() col2 = tb.Int32Col() col3 = tb.Float64Col() col4 = tb.Float64Col() con = open_db(filename, remove=1) table = con.create_table(con.root, 'table', Record, filters=filters, expectedrows=nrows) table.indexFilters = filters step = 1000 * 100 scale = 0.1 t1 = clock() j = 0 for i in range(0, nrows, step): stop = (j + 1) * step if stop > nrows: stop = nrows arr_f8 = np.arange(i, stop, type=np.float64) arr_i4 = np.arange(i, stop, type=np.int32) if userandom: arr_f8 += np.random.normal(0, stop * scale, shape=[stop - i]) arr_i4 = np.array(arr_f8, type=np.int32) recarr = np.rec.fromarrays([arr_i4, arr_i4, arr_f8, arr_f8]) table.append(recarr) j += 1 table.flush() ctime = clock() - t1 if verbose: print(f"insert time: {ctime:.5f}") print(f"Krows/s: {nrows / 1000 / ctime:.5f}") index_db(table) close_db(con) def index_db(table): t1 = clock() table.cols.col2.create_index() itime = clock() - t1 if verbose: print(f"index time (int): {itime:.5f}") print(f"Krows/s: {nrows / 1000 / itime:.5f}") t1 = clock() table.cols.col4.create_index() itime = clock() - t1 if verbose: print(f"index time (float): {itime:.5f}") print(f"Krows/s: {nrows / 1000 / itime:.5f}") def query_db(filename, rng): con = open_db(filename) table = con.root.table # Query for integer columns # Query for non-indexed column if not doqueryidx: t1 = clock() ntimes = 10 for i in range(ntimes): results = [ r['col1'] for r in table.where( rng[0] + i <= table.cols.col1 <= rng[1] + i) ] qtime = (clock() - t1) / ntimes if verbose: print(f"query time (int, not indexed): {qtime:.5f}") print(f"Krows/s: {nrows / 1000 / qtime:.5f}") print(results) # Query for indexed column t1 = clock() ntimes = 10 for i in range(ntimes): results = [ r['col1'] for r in table.where( rng[0] + i <= table.cols.col2 <= rng[1] + i) ] qtime = (clock() - t1) / ntimes if verbose: print(f"query time (int, indexed): {qtime:.5f}") print(f"Krows/s: {nrows / 1000 / qtime:.5f}") print(results) # Query for floating columns # Query for non-indexed column if not doqueryidx: t1 = clock() ntimes = 10 for i in range(ntimes): results = [ r['col3'] for r in table.where( rng[0] + i <= table.cols.col3 <= rng[1] + i) ] qtime = (clock() - t1) / ntimes if verbose: print(f"query time (float, not indexed): {qtime:.5f}") print(f"Krows/s: {nrows / 1000 / qtime:.5f}") print(results) # Query for indexed column t1 = clock() ntimes = 10 for i in range(ntimes): results = [r['col3'] for r in table.where(rng[0] + i <= table.cols.col4 <= rng[1] + i)] qtime = (clock() - t1) / ntimes if verbose: print(f"query time (float, indexed): {qtime:.5f}") print(f"Krows/s: {nrows / 1000 / qtime:.5f}") print(results) close_db(con) def close_db(con): con.close() if __name__ == "__main__": import sys import getopt try: import psyco psyco_imported = 1 except: psyco_imported = 0 usage = """usage: %s [-v] [-p] [-m] [-c] [-q] [-i] [-z complevel] [-l complib] [-R range] [-n nrows] file -v verbose -p use "psyco" if available -m use random values to fill the table -q do a query (both indexed and non-indexed version) -i do a query (exclude non-indexed version) -c create the database -z compress with zlib (no compression by default) -l use complib for compression (zlib used by default) -R select a range in a field in the form "start,stop" (def "0,10") -n sets the number of rows (in krows) in each table \n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'vpmcqiz:l:R:n:') except: sys.stderr.write(usage) sys.exit(0) # default options verbose = 0 usepsyco = 0 userandom = 0 docreate = 0 docompress = 0 complib = "zlib" doquery = 0 doqueryidx = 0 rng = [0, 10] nrows = 1 # Get the options for option in opts: if option[0] == '-v': verbose = 1 elif option[0] == '-p': usepsyco = 1 elif option[0] == '-m': userandom = 1 elif option[0] == '-c': docreate = 1 createindex = 1 elif option[0] == '-q': doquery = 1 elif option[0] == '-i': doqueryidx = 1 elif option[0] == '-z': docompress = int(option[1]) elif option[0] == '-l': complib = option[1] elif option[0] == '-R': rng = [int(i) for i in option[1].split(",")] elif option[0] == '-n': nrows = int(option[1]) # Catch the hdf5 file passed as the last argument filename = pargs[0] # The filters chosen filters = tb.Filters(complevel=docompress, complib=complib) if verbose: print("pytables version:", tb.__version__) if userandom: print("using random values") if doqueryidx: print("doing indexed queries only") if docreate: if verbose: print("writing %s krows" % nrows) if psyco_imported and usepsyco: psyco.bind(create_db) nrows *= 1000 create_db(filename, nrows) if doquery: query_db(filename, rng) PyTables-3.7.0/bench/pytables_backend.py000066400000000000000000000177521416254111300202010ustar00rootroot00000000000000import os from pathlib import Path import tables as tb from indexed_search import DB class PyTables_DB(DB): def __init__(self, nrows, rng, userandom, datadir, docompress=0, complib='zlib', kind="medium", optlevel=6): DB.__init__(self, nrows, rng, userandom) self.tprof = [] # Specific part for pytables self.docompress = docompress self.complib = complib # Complete the filename self.filename = "pro-" + self.filename self.filename += '-' + 'O%s' % optlevel self.filename += '-' + kind if docompress: self.filename += '-' + complib + str(docompress) self.datadir = datadir path = Path(self.datadir) if not path.is_dir(): if not path.is_absolute(): dir_path = Path('.') / self.datadir else: dir_path = Path(self.datadir) dir_path.mkdir(parents=True, exist_ok=True) self.datadir = dir_path print(f"Created {self.datadir}.") self.filename = self.datadir / f'{self.filename}.h5' # The chosen filters self.filters = tb.Filters(complevel=self.docompress, complib=self.complib, shuffle=1) print("Processing database:", self.filename) def open_db(self, remove=0): if remove and Path(self.filename).is_file(): Path(self.filename).unlink() con = tb.open_file(self.filename, 'a') return con def close_db(self, con): # Remove first the table_cache attribute if it exists if hasattr(self, "table_cache"): del self.table_cache con.close() def create_table(self, con): class Record(tb.IsDescription): col1 = tb.Int32Col() col2 = tb.Int32Col() col3 = tb.Float64Col() col4 = tb.Float64Col() con.create_table(con.root, 'table', Record, filters=self.filters, expectedrows=self.nrows) def fill_table(self, con): "Fills the table" table = con.root.table j = 0 for i in range(0, self.nrows, self.step): stop = (j + 1) * self.step if stop > self.nrows: stop = self.nrows arr_i4, arr_f8 = self.fill_arrays(i, stop) # recarr = records.fromarrays([arr_i4, arr_i4, arr_f8, arr_f8]) # table.append(recarr) table.append([arr_i4, arr_i4, arr_f8, arr_f8]) j += 1 table.flush() def index_col(self, con, column, kind, optlevel, verbose): col = getattr(con.root.table.cols, column) tmp_dir = self.datadir / "scratch2" tmp_dir.mkdir(parents=True, exist_ok=True) col.create_index(kind=kind, optlevel=optlevel, filters=self.filters, tmp_dir=tmp_dir, _verbose=verbose, _blocksizes=None) # _blocksizes=(2**27, 2**22, 2**15, 2**7)) # _blocksizes=(2**27, 2**22, 2**14, 2**6)) # _blocksizes=(2**27, 2**20, 2**13, 2**5), # _testmode=True) def do_query(self, con, column, base, inkernel): if True: if not hasattr(self, "table_cache"): self.table_cache = table = con.root.table self.colobj = getattr(table.cols, column) #self.colobj = getattr(table.cols, 'col1') self.condvars = {"col": self.colobj, "col1": table.cols.col1, "col2": table.cols.col2, "col3": table.cols.col3, "col4": table.cols.col4, } table = self.table_cache colobj = self.colobj else: table = con.root.table colobj = getattr(table.cols, column) self.condvars = {"col": colobj, "col1": table.cols.col1, "col2": table.cols.col2, "col3": table.cols.col3, "col4": table.cols.col4, } self.condvars['inf'] = self.rng[0] + base self.condvars['sup'] = self.rng[1] + base # For queries that can use two indexes instead of just one d = (self.rng[1] - self.rng[0]) / 2 inf1 = int(self.rng[0] + base) sup1 = int(self.rng[0] + d + base) inf2 = self.rng[0] + base * 2 sup2 = self.rng[0] + d + base * 2 self.condvars['inf1'] = inf1 self.condvars['sup1'] = sup1 self.condvars['inf2'] = inf2 self.condvars['sup2'] = sup2 #condition = "(inf == col2)" #condition = "(inf==col2) & (col4==sup)" #condition = "(inf==col2) | (col4==sup)" #condition = "(inf==col2) | (col2==sup)" #condition = "(inf==col2) & (col3==sup)" #condition = "((inf==col2) & (sup==col4)) & (col3==sup)" #condition = "((inf==col1) & (sup==col4)) & (col3==sup)" #condition = "(inf<=col1) & (col3", inf1, inf2, sup1, sup2 condition = "((inf2<=col) & (col", c['inf'], c['sup'], c['inf2'], c['sup2'] ncoords = 0 if colobj.is_indexed: results = [r[column] for r in table.where(condition, self.condvars)] # coords = table.get_where_list(condition, self.condvars) # results = table.read_coordinates(coords, field=column) # results = table.read_where(condition, self.condvars, field=column) elif inkernel: print("Performing in-kernel query") results = [r[column] for r in table.where(condition, self.condvars)] #coords = [r.nrow for r in table.where(condition, self.condvars)] #results = table.read_coordinates(coords) # for r in table.where(condition, self.condvars): # var = r[column] # ncoords += 1 else: # coords = [r.nrow for r in table # if (self.rng[0]+base <= r[column] <= self.rng[1]+base)] # results = table.read_coordinates(coords) print("Performing regular query") results = [ r[column] for r in table if (( (inf2 <= r['col4']) and (r['col4'] < sup2)) or ((inf1 < r['col2']) and (r['col2'] < sup1)) and ((r['col1'] + 3.1 * r['col2'] + r['col3'] * r['col4']) > 3) )] ncoords = len(results) # return coords # print "results-->", results # return results return ncoords #self.tprof.append( self.colobj.index.tprof ) # return ncoords, self.tprof PyTables-3.7.0/bench/recarray2-test.py000066400000000000000000000057451416254111300175550ustar00rootroot00000000000000import sys from pathlib import Path from time import perf_counter as clock import numpy as np import chararray import recarray import recarray2 # This is my modified version usage = """usage: %s recordlength Set recordlength to 1000 at least to obtain decent figures! """ % sys.argv[0] try: reclen = int(sys.argv[1]) except: print(usage) sys.exit() delta = 0.000_001 # Creation of recarrays objects for test x1 = np.array(np.arange(reclen)) x2 = chararray.array(None, itemsize=7, shape=reclen) x3 = np.array(np.arange(reclen, reclen * 3, 2), np.float64) r1 = recarray.fromarrays([x1, x2, x3], names='a,b,c') r2 = recarray2.fromarrays([x1, x2, x3], names='a,b,c') print("recarray shape in test ==>", r2.shape) print("Assignment in recarray original") print("-------------------------------") t1 = clock() for row in range(reclen): #r1.field("b")[row] = "changed" r1.field("c")[row] = float(row ** 2) t2 = clock() origtime = t2 - t1 print(f"Assign time: {origtime:.3f} Rows/s: {reclen / (origtime + delta):.0f}") # print "Field b on row 2 after re-assign:", r1.field("c")[2] print() print("Assignment in recarray modified") print("-------------------------------") t1 = clock() for row in range(reclen): rec = r2._row(row) # select the row to be changed # rec.b = "changed" # change the "b" field rec.c = float(row ** 2) # Change the "c" field t2 = clock() ttime = t2 - t1 print(f"Assign time: {ttime:.3f} Rows/s: {reclen / (ttime + delta):.0f}", end=' ') print(f" Speed-up: {origtime / ttime:.3f}") # print "Field b on row 2 after re-assign:", r2.field("c")[2] print() print("Selection in recarray original") print("------------------------------") t1 = clock() for row in range(reclen): rec = r1[row] if rec.field("a") < 3: print("This record pass the cut ==>", rec.field("c"), "(row", row, ")") t2 = clock() origtime = t2 - t1 print(f"Select time: {origtime:.3f}, Rows/s: {reclen / (origtime + delta):.0f}") print() print("Selection in recarray modified") print("------------------------------") t1 = clock() for row in range(reclen): rec = r2._row(row) if rec.a < 3: print("This record pass the cut ==>", rec.c, "(row", row, ")") t2 = clock() ttime = t2 - t1 print(f"Select time: {ttime:.3f} Rows/s: {reclen / (ttime + delta):.0f}", end=' ') print(f" Speed-up: {origtime / ttime:.3f}") print() print("Printing in recarray original") print("------------------------------") with Path("test.out").open("w") as f: t1 = clock() f.write(str(r1)) t2 = clock() origtime = t2 - t1 Path("test.out").unlink() print(f"Print time: {origtime:.3f} Rows/s: {reclen / (origtime + delta):.0f}") print() print("Printing in recarray modified") print("------------------------------") with Path("test2.out").open("w") as f: t1 = clock() f.write(str(r2)) t2 = clock() ttime = t2 - t1 Path("test2.out").unlink() print(f"Print time: {ttime:.3f} Rows/s: {reclen / (ttime + delta):.0f}", end=' ') print(f" Speed-up: {origtime / ttime:.3f}") print() PyTables-3.7.0/bench/search-bench-plot.py000066400000000000000000000110141416254111300201660ustar00rootroot00000000000000import tables as tb from pylab import * def get_values(filename, complib=''): f = tb.open_file(filename) nrows = f.root.small.create_best.cols.nrows[:] corrected_sizes = nrows / 10 ** 6 if mb_units: corrected_sizes = 16 * nrows / 10 ** 6 if insert: values = corrected_sizes / f.root.small.create_best.cols.tfill[:] if table_size: values = f.root.small.create_best.cols.fsize[:] / nrows if query: values = corrected_sizes / \ f.root.small.search_best.inkernel.int.cols.time1[:] if query_cache: values = corrected_sizes / \ f.root.small.search_best.inkernel.int.cols.time2[:] f.close() return nrows, values def show_plot(plots, yaxis, legends, gtitle): xlabel('Number of rows') ylabel(yaxis) xlim(10 ** 3, 10 ** 8) title(gtitle) grid(True) # legends = [f[f.find('-'):f.index('.out')] for f in filenames] # legends = [l.replace('-', ' ') for l in legends] if table_size: legend([p[0] for p in plots], legends, loc="upper right") else: legend([p[0] for p in plots], legends, loc="upper left") #subplots_adjust(bottom=0.2, top=None, wspace=0.2, hspace=0.2) if outfile: savefig(outfile) else: show() if __name__ == '__main__': import sys import getopt usage = """usage: %s [-o file] [-t title] [--insert] [--table-size] [--query] [--query-cache] [--MB-units] files -o filename for output (only .png and .jpg extensions supported) -t title of the plot --insert -- Insert time for table --table-size -- Size of table --query -- Time for querying the integer column --query-cache -- Time for querying the integer (cached) --MB-units -- Express speed in MB/s instead of MRows/s \n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'o:t:', ['insert', 'table-size', 'query', 'query-cache', 'MB-units', ]) except: sys.stderr.write(usage) sys.exit(0) progname = sys.argv[0] args = sys.argv[1:] # if we pass too few parameters, abort if len(pargs) < 1: sys.stderr.write(usage) sys.exit(0) # default options outfile = None insert = 0 table_size = 0 query = 0 query_cache = 0 mb_units = 0 yaxis = "No axis name" tit = None gtitle = "Please set a title!" # Get the options for option in opts: if option[0] == '-o': outfile = option[1] elif option[0] == '-t': tit = option[1] elif option[0] == '--insert': insert = 1 yaxis = "MRows/s" gtitle = "Writing with small (16 bytes) record size" elif option[0] == '--table-size': table_size = 1 yaxis = "Bytes/row" gtitle = ("Disk space taken by a record (original record size: " "16 bytes)") elif option[0] == '--query': query = 1 yaxis = "MRows/s" gtitle = ("Selecting with small (16 bytes) record size (file not " "in cache)") elif option[0] == '--query-cache': query_cache = 1 yaxis = "MRows/s" gtitle = ("Selecting with small (16 bytes) record size (file in " "cache)") elif option[0] == '--MB-units': mb_units = 1 filenames = pargs if mb_units and yaxis == "MRows/s": yaxis = "MB/s" if tit: gtitle = tit plots = [] legends = [] for filename in filenames: plegend = filename[filename.find('cl-') + 3:filename.index('.h5')] plegend = plegend.replace('-', ' ') xval, yval = get_values(filename, '') print(f"Values for {filename} --> {xval}, {yval}") #plots.append(loglog(xval, yval, linewidth=5)) plots.append(semilogx(xval, yval, linewidth=4)) legends.append(plegend) if 0: # Per a introduir dades simulades si es vol... xval = [1000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000] # yval = [0.003, 0.005, 0.02, 0.06, 1.2, # 40, 210] yval = [0.0009, 0.0011, 0.0022, 0.005, 0.02, 0.2, 5.6] plots.append(loglog(xval, yval, linewidth=5)) legends.append("PyTables Std") show_plot(plots, yaxis, legends, gtitle) PyTables-3.7.0/bench/search-bench-rnd.sh000077500000000000000000000064151416254111300177710ustar00rootroot00000000000000#!/bin/sh # I don't know why, but the /usr/bin/python2.3 from Debian is a 30% slower # than my own compiled version! 2004-08-18 python="/usr/local/bin/python2.3 -O" writedata () { nrows=$1 bfile=$2 worst=$3 psyco=$4 if [ "$shuffle" = "1" ]; then shufflef="-S" else shufflef="" fi cmd="${python} search-bench.py -R ${worst} -b ${bfile} -h ${psyco} -l ${libcomp} -c ${complevel} ${shufflef} -w -n ${nrows} data.nobackup/bench-${libcomp}-${nrows}k.h5" echo ${cmd} ${cmd} } readdata () { nrows=$1 bfile=$2 worst=$3 psyco=$4 smode=$5 if [ "$smode" = "indexed" ]; then #repeats=100 repeats=20 else repeats=2 fi cmd="${python} search-bench.py -R ${worst} -h -b ${bfile} ${psyco} -m ${smode} -r -k ${repeats} data.nobackup/bench-${libcomp}-${nrows}k.h5" echo ${cmd} ${cmd} return } overwrite=0 if [ $# > 1 ]; then if [ "$1" = "-o" ]; then overwrite=1 fi fi if [ $# > 2 ]; then psyco=$2 fi # Configuration for testing #nrowslist="50000" #nrowslistworst="50000" # Normal test #nrowslist="1 2 5 10 20 50 100 200 500 1000 2000 5000 10000 20000" #nrowslistworst="1 2 5 10 20 50 100 200 500 1000 2000 5000 10000 20000" nrowslist="1 2 5 10 20 50 100 200 500 1000" nrowslistworst="1 2 5 10 20 50 100 200 500 1000" #nrowslist="1 2 5 10" #nrowslistworst="1 2 5 10" # The next can be regarded as parameters shuffle=1 for libcomp in none zlib lzo; do #for libcomp in none lzo; do if [ "$libcomp" = "none" ]; then complevel=0 else complevel=1 fi # The name of the data bench file bfile="worst-dbench-cl-${libcomp}-c${complevel}-S${shuffle}.h5" # Move out a possible previous benchmark file bn=`basename $bfile ".h5"` mv -f ${bn}-bck2.h5 ${bn}-bck3.h5 mv -f ${bn}-bck.h5 ${bn}-bck2.h5 if [ "$overwrite" = "1" ]; then echo "moving ${bn}.h5 to ${bn}-bck.h5" mv -f ${bn}.h5 ${bn}-bck.h5 else echo "copying ${bn}.h5 to ${bn}-bck.h5" cp -f ${bn}.h5 ${bn}-bck.h5 fi for worst in "" -t; do #for worst in ""; do # Write data files if [ "$worst" = "-t" ]; then echo echo "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" echo "Entering worst case..." echo "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" echo nrowslist=$nrowslistworst fi # Write data file for nrows in $nrowslist; do echo "*************************************************************" echo "Writing for nrows=$nrows Krows, psyco=$psyco, worst='${worst}'" echo "*************************************************************" writedata ${nrows} ${bfile} "${worst}" "${psyco}" done # Read data files for smode in indexed inkernel standard; do ${python} cacheout.py for nrows in $nrowslist; do echo "***********************************************************" echo "Searching for nrows=$nrows Krows, $smode, psyco=$psyco, worst='${worst}'" echo "***********************************************************" readdata ${nrows} ${bfile} "${worst}" "${psyco}" "${smode}" done done # Finally, after the final search, delete the source (if desired) # for nrows in $nrowslist; do # rm -f data.nobackup/bench-${libcomp}-${nrows}k.h5 # done done echo "New data available on: $bfile" done exit 0 PyTables-3.7.0/bench/search-bench.py000066400000000000000000000420371416254111300172230ustar00rootroot00000000000000#!/usr/bin/env python import random import sys import warnings from pathlib import Path from time import perf_counter as clock from time import process_time as cpuclock import numpy as np import tables as tb # Initialize the random generator always with the same integer # in order to have reproductible results random.seed(19) np.random.seed(19) randomvalues = 0 worst = 0 Small = { "var1": tb.StringCol(itemsize=4, dflt="Hi!", pos=2), "var2": tb.Int32Col(pos=1), "var3": tb.Float64Col(pos=0), #"var4" : BoolCol(), } def createNewBenchFile(bfile, verbose): class Create(tb.IsDescription): nrows = tb.Int32Col(pos=0) irows = tb.Int32Col(pos=1) tfill = tb.Float64Col(pos=2) tidx = tb.Float64Col(pos=3) tcfill = tb.Float64Col(pos=4) tcidx = tb.Float64Col(pos=5) rowsecf = tb.Float64Col(pos=6) rowseci = tb.Float64Col(pos=7) fsize = tb.Float64Col(pos=8) isize = tb.Float64Col(pos=9) psyco = tb.BoolCol(pos=10) class Search(tb.IsDescription): nrows = tb.Int32Col(pos=0) rowsel = tb.Int32Col(pos=1) time1 = tb.Float64Col(pos=2) time2 = tb.Float64Col(pos=3) tcpu1 = tb.Float64Col(pos=4) tcpu2 = tb.Float64Col(pos=5) rowsec1 = tb.Float64Col(pos=6) rowsec2 = tb.Float64Col(pos=7) psyco = tb.BoolCol(pos=8) if verbose: print("Creating a new benchfile:", bfile) # Open the benchmarking file bf = tb.open_file(bfile, "w") # Create groups for recsize in ["small"]: group = bf.create_group("/", recsize, recsize + " Group") # Attach the row size of table as attribute if recsize == "small": group._v_attrs.rowsize = 16 # Create a Table for writing bench bf.create_table(group, "create_best", Create, "best case") bf.create_table(group, "create_worst", Create, "worst case") for case in ["best", "worst"]: # create a group for searching bench (best case) groupS = bf.create_group(group, "search_" + case, "Search Group") # Create Tables for searching for mode in ["indexed", "inkernel", "standard"]: groupM = bf.create_group(groupS, mode, mode + " Group") # for searching bench # for atom in ["string", "int", "float", "bool"]: for atom in ["string", "int", "float"]: bf.create_table(groupM, atom, Search, atom + " bench") bf.close() def createFile(filename, nrows, filters, index, heavy, noise, verbose): # Open a file in "w"rite mode fileh = tb.open_file(filename, mode="w", title="Searchsorted Benchmark", filters=filters) rowswritten = 0 # Create the test table table = fileh.create_table(fileh.root, 'table', Small, "test table", None, nrows) t1 = clock() cpu1 = cpuclock() nrowsbuf = table.nrowsinbuf minimum = 0 maximum = nrows for i in range(0, nrows, nrowsbuf): if i + nrowsbuf > nrows: j = nrows else: j = i + nrowsbuf if randomvalues: var3 = np.random.uniform(minimum, maximum, size=j - i) else: var3 = np.arange(i, j, dtype=np.float64) if noise > 0: var3 += np.random.uniform(-noise, noise, size=j - i) var2 = np.array(var3, dtype=np.int32) var1 = np.empty(shape=[j - i], dtype="S4") if not heavy: var1[:] = var2 table.append([var3, var2, var1]) table.flush() rowswritten += nrows time1 = clock() - t1 tcpu1 = cpuclock() - cpu1 print( f"Time for filling: {time1:.3f} Krows/s: {nrows / 1000 / time1:.3f}", end=' ') fileh.close() size1 = Path(filename).stat().st_size print(f", File size: {size1 / 1024 / 1024:.3f} MB") fileh = tb.open_file(filename, mode="a", title="Searchsorted Benchmark", filters=filters) table = fileh.root.table rowsize = table.rowsize if index: t1 = clock() cpu1 = cpuclock() # Index all entries if not heavy: indexrows = table.cols.var1.create_index(filters=filters) for colname in ['var2', 'var3']: table.colinstances[colname].create_index(filters=filters) time2 = clock() - t1 tcpu2 = cpuclock() - cpu1 print( f"Time for indexing: {time2:.3f} " f"iKrows/s: {indexrows / 1000 / time2:.3f}", end=' ') else: indexrows = 0 time2 = 0.000_000_000_1 # an ugly hack tcpu2 = 0 if verbose: if index: idx = table.cols.var1.index print("Index parameters:", repr(idx)) else: print("NOT indexing rows") # Close the file fileh.close() size2 = Path(filename).stat().st_size - size1 if index: print(f", Index size: {size2 / 1024 / 1024:.3f} MB") return (rowswritten, indexrows, rowsize, time1, time2, tcpu1, tcpu2, size1, size2) def benchCreate(file, nrows, filters, index, bfile, heavy, psyco, noise, verbose): # Open the benchfile in append mode bf = tb.open_file(bfile, "a") recsize = "small" if worst: table = bf.get_node("/" + recsize + "/create_worst") else: table = bf.get_node("/" + recsize + "/create_best") (rowsw, irows, rowsz, time1, time2, tcpu1, tcpu2, size1, size2) = \ createFile(file, nrows, filters, index, heavy, noise, verbose) # Collect data table.row["nrows"] = rowsw table.row["irows"] = irows table.row["tfill"] = time1 table.row["tidx"] = time2 table.row["tcfill"] = tcpu1 table.row["tcidx"] = tcpu2 table.row["fsize"] = size1 table.row["isize"] = size2 table.row["psyco"] = psyco print(f"Rows written: {rowsw} Row size: {rowsz}") print( f"Time writing rows: {time1} s (real) " f"{tcpu1} s (cpu) {tcpu1 / time1:.0%}") rowsecf = rowsw / time1 table.row["rowsecf"] = rowsecf print(f"Total file size: {(size1 + size2) / 1024 / 1024:.3f} MB", end=' ') print(f", Write KB/s (pure data): {rowsw * rowsz / (time1 * 1024):.0f}") print(f"Rows indexed: {irows} (IMRows): {irows / 10 ** 6}") print( f"Time indexing rows: {time2:.3f} s (real) " f"{tcpu2:.3f} s (cpu) {tcpu2 / time2:.0%}") rowseci = irows / time2 table.row["rowseci"] = rowseci table.row.append() bf.close() def readFile(filename, atom, riter, indexmode, dselect, verbose): # Open the HDF5 file in read-only mode fileh = tb.open_file(filename, mode="r") table = fileh.root.table var1 = table.cols.var1 var2 = table.cols.var2 var3 = table.cols.var3 if indexmode == "indexed": if var2.index.nelements > 0: where = table._whereIndexed else: warnings.warn( "Not indexed table or empty index. Defaulting to in-kernel " "selection") indexmode = "inkernel" where = table._whereInRange elif indexmode == "inkernel": where = table.where if verbose: print("Max rows in buf:", table.nrowsinbuf) print("Rows in", table._v_pathname, ":", table.nrows) print("Buffersize:", table.rowsize * table.nrowsinbuf) print("MaxTuples:", table.nrowsinbuf) if indexmode == "indexed": print("Chunk size:", var2.index.sorted.chunksize) print("Number of elements per slice:", var2.index.nelemslice) print("Slice number in", table._v_pathname, ":", var2.index.nrows) #table.nrowsinbuf = 10 # print "nrowsinbuf-->", table.nrowsinbuf rowselected = 0 time2 = 0 tcpu2 = 0 results = [] print("Select mode:", indexmode, ". Selecting for type:", atom) # Initialize the random generator always with the same integer # in order to have reproductible results on each read iteration random.seed(19) np.random.seed(19) for i in range(riter): # The interval for look values at. This is aproximately equivalent to # the number of elements to select rnd = np.random.randint(table.nrows) cpu1 = cpuclock() t1 = clock() if atom == "string": val = str(rnd)[-4:] if indexmode in ["indexed", "inkernel"]: results = [p.nrow for p in where('var1 == val')] else: results = [p.nrow for p in table if p["var1"] == val] elif atom == "int": val = rnd + dselect if indexmode in ["indexed", "inkernel"]: results = [p.nrow for p in where('(rnd <= var3) & (var3 < val)')] else: results = [p.nrow for p in table if rnd <= p["var2"] < val] elif atom == "float": val = rnd + dselect if indexmode in ["indexed", "inkernel"]: t1 = clock() results = [p.nrow for p in where('(rnd <= var3) & (var3 < val)')] else: results = [p.nrow for p in table if float(rnd) <= p["var3"] < float(val)] else: raise ValueError("Value for atom '%s' not supported." % atom) rowselected += len(results) # print "selected values-->", results if i == 0: # First iteration time1 = clock() - t1 tcpu1 = cpuclock() - cpu1 else: if indexmode == "indexed": # if indexed, wait until the 5th iteration (in order to # insure that the index is effectively cached) to take times if i >= 5: time2 += clock() - t1 tcpu2 += cpuclock() - cpu1 else: time2 += clock() - t1 tcpu2 += cpuclock() - cpu1 if riter > 1: if indexmode == "indexed" and riter >= 5: correction = 5 else: correction = 1 time2 = time2 / (riter - correction) tcpu2 = tcpu2 / (riter - correction) if verbose and 1: print("Values that fullfill the conditions:") print(results) #rowsread = table.nrows * riter rowsread = table.nrows rowsize = table.rowsize # Close the file fileh.close() return (rowsread, rowselected, rowsize, time1, time2, tcpu1, tcpu2) def benchSearch(file, riter, indexmode, bfile, heavy, psyco, dselect, verbose): # Open the benchfile in append mode bf = tb.open_file(bfile, "a") recsize = "small" if worst: tableparent = "/" + recsize + "/search_worst/" + indexmode + "/" else: tableparent = "/" + recsize + "/search_best/" + indexmode + "/" # Do the benchmarks if not heavy: #atomlist = ["string", "int", "float", "bool"] atomlist = ["string", "int", "float"] else: #atomlist = ["int", "float", "bool"] atomlist = ["int", "float"] for atom in atomlist: tablepath = tableparent + atom table = bf.get_node(tablepath) (rowsr, rowsel, rowssz, time1, time2, tcpu1, tcpu2) = \ readFile(file, atom, riter, indexmode, dselect, verbose) row = table.row row["nrows"] = rowsr row["rowsel"] = rowsel treadrows = time1 row["time1"] = time1 treadrows2 = time2 row["time2"] = time2 cpureadrows = tcpu1 row["tcpu1"] = tcpu1 cpureadrows2 = tcpu2 row["tcpu2"] = tcpu2 row["psyco"] = psyco tratio = cpureadrows / treadrows tratio2 = cpureadrows2 / treadrows2 if riter > 1 else 0. tMrows = rowsr / (1000 * 1000.) sKrows = rowsel / 1000. if atom == "string": # just to print once print(f"Rows read: {rowsr} Mread: {tMrows:.6f} Mrows") print(f"Rows selected: {rowsel} Ksel: {sKrows:.6f} Krows") print( f"Time selecting (1st time): {treadrows:.6f} s " f"(real) {cpureadrows:.6f} s (cpu) {tratio:.0%}") if riter > 1: print( f"Time selecting (cached): {treadrows2:.6f} s " f"(real) {cpureadrows2:.6f} s (cpu) {tratio2:.0%}") rowsec1 = rowsr / treadrows row["rowsec1"] = rowsec1 print(f"Read Mrows/sec: {rowsec1 / 10 ** 6:.6f} (first time)", end=' ') if riter > 1: rowsec2 = rowsr / treadrows2 row["rowsec2"] = rowsec2 print(f"{rowsec2 / 10 ** 6:.6f} (cache time)") else: print() # Append the info to the table row.append() table.flush() # Close the benchmark file bf.close() if __name__ == "__main__": import getopt try: import psyco psyco_imported = 1 except: psyco_imported = 0 usage = """usage: %s [-v] [-p] [-R] [-r] [-w] [-c level] [-l complib] [-S] [-F] [-n nrows] [-x] [-b file] [-t] [-h] [-k riter] [-m indexmode] [-N range] [-d range] datafile -v verbose -p use "psyco" if available -R use Random values for filling -r only read test -w only write test -c sets a compression level (do not set it or 0 for no compression) -l sets the compression library ("zlib", "lzo", "ucl", "bzip2" or "none") -S activate shuffling filter -F activate fletcher32 filter -n set the number of rows in tables (in krows) -x don't make indexes -b bench filename -t worsT searching case -h heavy benchmark (operations without strings) -m index mode for reading ("indexed" | "inkernel" | "standard") -N introduce (uniform) noise within range into the values -d the interval for look values (int, float) at. Default is 3. -k number of iterations for reading\n""" % sys.argv[0] try: opts, pargs = getopt.getopt( sys.argv[1:], 'vpSFRrowxthk:b:c:l:n:m:N:d:') except: sys.stderr.write(usage) sys.exit(0) # if we pass too much parameters, abort if len(pargs) != 1: sys.stderr.write(usage) sys.exit(0) # default options dselect = 3 noise = 0 verbose = 0 fieldName = None testread = 1 testwrite = 1 usepsyco = 0 complevel = 0 shuffle = 0 fletcher32 = 0 complib = "zlib" nrows = 1000 index = 1 heavy = 0 bfile = "bench.h5" supported_imodes = ["indexed", "inkernel", "standard"] indexmode = "inkernel" riter = 1 # Get the options for option in opts: if option[0] == '-v': verbose = 1 if option[0] == '-p': usepsyco = 1 if option[0] == '-R': randomvalues = 1 if option[0] == '-S': shuffle = 1 if option[0] == '-F': fletcher32 = 1 elif option[0] == '-r': testwrite = 0 elif option[0] == '-w': testread = 0 elif option[0] == '-x': index = 0 elif option[0] == '-h': heavy = 1 elif option[0] == '-t': worst = 1 elif option[0] == '-b': bfile = option[1] elif option[0] == '-c': complevel = int(option[1]) elif option[0] == '-l': complib = option[1] elif option[0] == '-m': indexmode = option[1] if indexmode not in supported_imodes: raise ValueError( "Indexmode should be any of '%s' and you passed '%s'" % (supported_imodes, indexmode)) elif option[0] == '-n': nrows = int(float(option[1]) * 1000) elif option[0] == '-N': noise = float(option[1]) elif option[0] == '-d': dselect = float(option[1]) elif option[0] == '-k': riter = int(option[1]) if worst: nrows -= 1 # the worst case if complib == "none": # This means no compression at all complib = "zlib" # just to make PyTables not complaining complevel = 0 # Catch the hdf5 file passed as the last argument file = pargs[0] # Build the Filters instance filters = tb.Filters(complevel=complevel, complib=complib, shuffle=shuffle, fletcher32=fletcher32) # Create the benchfile (if needed) if not Path(bfile).exists(): createNewBenchFile(bfile, verbose) if testwrite: if verbose: print("Compression level:", complevel) if complevel > 0: print("Compression library:", complib) if shuffle: print("Suffling...") if psyco_imported and usepsyco: psyco.bind(createFile) benchCreate(file, nrows, filters, index, bfile, heavy, usepsyco, noise, verbose) if testread: if psyco_imported and usepsyco: psyco.bind(readFile) benchSearch(file, riter, indexmode, bfile, heavy, usepsyco, dselect, verbose) PyTables-3.7.0/bench/search-bench.sh000077500000000000000000000065771416254111300172210ustar00rootroot00000000000000#!/bin/sh python="python2.5 -O" writedata () { nrows=$1 bfile=$2 heavy=$3 psyco=$4 if [ "$shuffle" = "1" ]; then shufflef="-S" else shufflef="" fi cmd="${python} search-bench.py -b ${bfile} ${heavy} ${psyco} -l ${libcomp} -c ${complevel} ${shufflef} -w -n ${nrows} -x data.nobackup/bench-${libcomp}-${nrows}k.h5" echo ${cmd} ${cmd} } readdata () { nrows=$1 bfile=$2 heavy=$3 psyco=$4 smode=$5 if [ "$smode" = "indexed" ]; then repeats=100 else repeats=2 fi if [ "$heavy" = "-h" -a "$smode" = "standard" ]; then # For heavy mode don't do a standard search echo "Skipping the standard search for heavy mode" else cmd="${python} search-bench.py -b ${bfile} ${heavy} ${psyco} -m ${smode} -r -k ${repeats} data.nobackup/bench-${libcomp}-${nrows}k.h5" echo ${cmd} ${cmd} fi if [ "$smode" = "standard" -a "1" = "0" ]; then # Finally, after the final search, delete the source (if desired) rm -f data.nobackup/bench-${libcomp}-${nrows}k.h5 fi return } overwrite=0 if [ $# > 1 ]; then if [ "$1" = "-o" ]; then overwrite=1 fi fi if [ $# > 2 ]; then psyco=$2 fi # The next can be regarded as parameters libcomp="lzo" complevel=1 shuffle=1 # The name of the data bench file bfile="dbench-cl-${libcomp}-c${complevel}-S${shuffle}.h5" # Move out a possible previous benchmark file bn=`basename $bfile ".h5"` mv -f ${bn}-bck2.h5 ${bn}-bck3.h5 mv -f ${bn}-bck.h5 ${bn}-bck2.h5 if [ "$overwrite" = "1" ]; then echo "moving ${bn}.h5 to ${bn}-bck.h5" mv -f ${bn}.h5 ${bn}-bck.h5 else echo "copying ${bn}.h5 to ${bn}-bck.h5" cp -f ${bn}.h5 ${bn}-bck.h5 fi # Configuration for testing nrowslist="1 2" nrowslistheavy="5 10" # This config takes 10 minutes to complete (psyco, zlib) #nrowslist="1 2 5 10 20 50 100 200 500 1000" #nrowslistheavy="2000 5000 10000" #nrowslist="" #nrowslistheavy="1 2 5 10 20 50 100 200 500 1000 2000 5000 10000 20000 50000 100000" # Normal test #nrowslist="1 2 5 10 20 50 100 200 500 1000 2000 5000 10000" #nrowslistheavy="20000 50000 100000 200000 500000 1000000" # Big test #nrowslist="1 2 5 10 20 50 100 200 500 1000 2000 5000 10000" #nrowslistheavy="20000 50000 100000 200000 500000 1000000 2000000 5000000" for heavy in "" -h; do # Write data files (light mode) if [ "$heavy" = "-h" ]; then echo echo "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" echo "Entering heavy mode..." echo "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" echo nrowslist=$nrowslistheavy fi # Write data file for nrows in $nrowslist; do echo "*************************************************************" echo "Writing for nrows=$nrows Krows, psyco=$psyco, heavy='${heavy}'" echo "*************************************************************" writedata ${nrows} ${bfile} "${heavy}" "${psyco}" done # Read data files #for smode in indexed inkernel standard; do for smode in inkernel standard; do # for smode in indexed; do ${python} cacheout.py for nrows in $nrowslist; do echo "***********************************************************" echo "Searching for nrows=$nrows Krows, $smode, psyco=$psyco, heavy='${heavy}'" echo "***********************************************************" readdata ${nrows} ${bfile} "${heavy}" "${psyco}" "${smode}" done done done echo "New data available on: $bfile" exit 0 PyTables-3.7.0/bench/searchsorted-bench.py000066400000000000000000000271141416254111300204430ustar00rootroot00000000000000#!/usr/bin/env python from time import perf_counter as clock from time import process_time as cpuclock import tables as tb class Small(tb.IsDescription): var1 = tb.StringCol(itemsize=4) var2 = tb.Int32Col() var3 = tb.Float64Col() var4 = tb.BoolCol() # Define a user record to characterize some kind of particles class Medium(tb.IsDescription): var1 = tb.StringCol(itemsize=16) # 16-character String #float1 = Float64Col(dflt=2.3) #float2 = Float64Col(dflt=2.3) # zADCcount = Int16Col() # signed short integer var2 = tb.Int32Col() # signed short integer var3 = tb.Float64Col() grid_i = tb.Int32Col() # integer grid_j = tb.Int32Col() # integer pressure = tb.Float32Col() # float (single-precision) energy = tb.Float64Col(shape=2) # double (double-precision) def createFile(filename, nrows, filters, atom, recsize, index, verbose): # Open a file in "w"rite mode fileh = tb.open_file(filename, mode="w", title="Searchsorted Benchmark", filters=filters) title = "This is the IndexArray title" # Create an IndexArray instance rowswritten = 0 # Create an entry klass = {"small": Small, "medium": Medium} table = fileh.create_table(fileh.root, 'table', klass[recsize], title, None, nrows) for i in range(nrows): #table.row['var1'] = str(i) #table.row['var2'] = random.randrange(nrows) table.row['var2'] = i table.row['var3'] = i #table.row['var4'] = i % 2 #table.row['var4'] = i > 2 table.row.append() rowswritten += nrows table.flush() rowsize = table.rowsize indexrows = 0 # Index one entry: if index: if atom == "string": indexrows = table.cols.var1.create_index() elif atom == "bool": indexrows = table.cols.var4.create_index() elif atom == "int": indexrows = table.cols.var2.create_index() elif atom == "float": indexrows = table.cols.var3.create_index() else: raise ValueError("Index type not supported yet") if verbose: print("Number of indexed rows:", indexrows) # Close the file (eventually destroy the extended type) fileh.close() return (rowswritten, rowsize) def readFile(filename, atom, niter, verbose): # Open the HDF5 file in read-only mode fileh = tb.open_file(filename, mode="r") table = fileh.root.table print("reading", table) if atom == "string": idxcol = table.cols.var1.index elif atom == "bool": idxcol = table.cols.var4.index elif atom == "int": idxcol = table.cols.var2.index else: idxcol = table.cols.var3.index if verbose: print("Max rows in buf:", table.nrowsinbuf) print("Rows in", table._v_pathname, ":", table.nrows) print("Buffersize:", table.rowsize * table.nrowsinbuf) print("MaxTuples:", table.nrowsinbuf) print("Chunk size:", idxcol.sorted.chunksize) print("Number of elements per slice:", idxcol.nelemslice) print("Slice number in", table._v_pathname, ":", idxcol.nrows) rowselected = 0 if atom == "string": for i in range(niter): #results = [table.row["var3"] for i in table.where(2+i<=table.cols.var2 < 10+i)] #results = [table.row.nrow() for i in table.where(2<=table.cols.var2 < 10)] results = [p["var1"] # p.nrow() for p in table.where(table.cols.var1 == "1111")] # for p in table.where("1000"<=table.cols.var1<="1010")] rowselected += len(results) elif atom == "bool": for i in range(niter): results = [p["var2"] # p.nrow() for p in table.where(table.cols.var4 == 0)] rowselected += len(results) elif atom == "int": for i in range(niter): #results = [table.row["var3"] for i in table.where(2+i<=table.cols.var2 < 10+i)] #results = [table.row.nrow() for i in table.where(2<=table.cols.var2 < 10)] results = [p["var2"] # p.nrow() # for p in table.where(110*i<=table.cols.var2<110*(i+1))] # for p in table.where(1000-30", positions) print("Total iterations in search:", niter) rowsread += table.nrows uncomprBytes += idxcol.sorted.chunksize * niter * idxcol.sorted.itemsize results = table.read(coords=positions) print("results length:", len(results)) if verbose: print("Values that fullfill the conditions:") print(results) # Close the file (eventually destroy the extended type) fileh.close() return (rowsread, uncomprBytes, niter) if __name__ == "__main__": import sys import getopt try: import psyco psyco_imported = 1 except: psyco_imported = 0 usage = """usage: %s [-v] [-p] [-R range] [-r] [-w] [-s recsize ] [-a atom] [-c level] [-l complib] [-S] [-F] [-i item] [-n nrows] [-x] [-k niter] file -v verbose -p use "psyco" if available -R select a range in a field in the form "start,stop,step" -r only read test -w only write test -s record size -a use [float], [int], [bool] or [string] atom -c sets a compression level (do not set it or 0 for no compression) -S activate shuffling filter -F activate fletcher32 filter -l sets the compression library to be used ("zlib", "lzo", "ucl", "bzip2") -i item to search -n set the number of rows in tables -x don't make indexes -k number of iterations for reading\n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'vpSFR:rwxk:s:a:c:l:i:n:') except: sys.stderr.write(usage) sys.exit(0) # if we pass too much parameters, abort if len(pargs) != 1: sys.stderr.write(usage) sys.exit(0) # default options verbose = 0 rng = None item = None atom = "int" fieldName = None testread = 1 testwrite = 1 usepsyco = 0 complevel = 0 shuffle = 0 fletcher32 = 0 complib = "zlib" nrows = 100 recsize = "small" index = 1 niter = 1 # Get the options for option in opts: if option[0] == '-v': verbose = 1 if option[0] == '-p': usepsyco = 1 if option[0] == '-S': shuffle = 1 if option[0] == '-F': fletcher32 = 1 elif option[0] == '-R': rng = [int(i) for i in option[1].split(",")] elif option[0] == '-r': testwrite = 0 elif option[0] == '-w': testread = 0 elif option[0] == '-x': index = 0 elif option[0] == '-s': recsize = option[1] elif option[0] == '-a': atom = option[1] if atom not in ["float", "int", "bool", "string"]: sys.stderr.write(usage) sys.exit(0) elif option[0] == '-c': complevel = int(option[1]) elif option[0] == '-l': complib = option[1] elif option[0] == '-i': item = eval(option[1]) elif option[0] == '-n': nrows = int(option[1]) elif option[0] == '-k': niter = int(option[1]) # Build the Filters instance filters = tb.Filters(complevel=complevel, complib=complib, shuffle=shuffle, fletcher32=fletcher32) # Catch the hdf5 file passed as the last argument file = pargs[0] if testwrite: print("Compression level:", complevel) if complevel > 0: print("Compression library:", complib) if shuffle: print("Suffling...") t1 = clock() cpu1 = cpuclock() if psyco_imported and usepsyco: psyco.bind(createFile) (rowsw, rowsz) = createFile(file, nrows, filters, atom, recsize, index, verbose) t2 = clock() cpu2 = cpuclock() tapprows = t2 - t1 cpuapprows = cpu2 - cpu1 print(f"Rows written:", rowsw, " Row size:", rowsz) print( f"Time writing rows: {tapprows:.3f} s (real) " f"{cpuapprows:.3f} s (cpu) {cpuapprows / tapprows:.0%}") print(f"Write rows/sec: {rowsw / tapprows:.0f}") print(f"Write KB/s : {rowsw * rowsz / tapprows / 1024:.0f}") if testread: if psyco_imported and usepsyco: psyco.bind(readFile) psyco.bind(searchFile) t1 = clock() cpu1 = cpuclock() if rng or item: (rowsr, uncomprB, niter) = searchFile(file, atom, verbose, item) else: for i in range(1): (rowsr, rowsel, rowsz) = readFile(file, atom, niter, verbose) t2 = clock() cpu2 = cpuclock() treadrows = t2 - t1 cpureadrows = cpu2 - cpu1 tMrows = rowsr / 1000 / 1000 sKrows = rowsel / 1000 print(f"Rows read: {rowsr} Mread: {tMrows:.3f} Mrows") print(f"Rows selected: {rowsel} Ksel: {sKrows:.3f} Krows") print( f"Time reading rows: {treadrows:.3f} s (real) " f"{cpureadrows:.3f} s (cpu) {cpureadrows / treadrows:.0%}") print(f"Read Mrows/sec: {tMrows / treadrows:.3f}") # print "Read KB/s :", int(rowsr * rowsz / (treadrows * 1024)) # print "Uncompr MB :", int(uncomprB / (1024 * 1024)) # print "Uncompr MB/s :", int(uncomprB / (treadrows * 1024 * 1024)) # print "Total chunks uncompr :", int(niter) PyTables-3.7.0/bench/searchsorted-bench2.py000066400000000000000000000271551416254111300205320ustar00rootroot00000000000000#!/usr/bin/env python from time import perf_counter as clock from time import process_time as cpuclock import tables as tb class Small(tb.IsDescription): var1 = tb.StringCol(itemsize=4) var2 = tb.Int32Col() var3 = tb.Float64Col() var4 = tb.BoolCol() # Define a user record to characterize some kind of particles class Medium(tb.IsDescription): var1 = tb.StringCol(itemsize=16, dflt="") # 16-character String #float1 = Float64Col(dflt=2.3) #float2 = Float64Col(dflt=2.3) # zADCcount = Int16Col() # signed short integer var2 = tb.Int32Col() # signed short integer var3 = tb.Float64Col() grid_i = tb.Int32Col() # integer grid_j = tb.Int32Col() # integer pressure = tb.Float32Col() # float (single-precision) energy = tb.Float64Col(shape=2) # double (double-precision) def createFile(filename, nrows, filters, atom, recsize, index, verbose): # Open a file in "w"rite mode fileh = tb.open_file(filename, mode="w", title="Searchsorted Benchmark", filters=filters) title = "This is the IndexArray title" # Create an IndexArray instance rowswritten = 0 # Create an entry klass = {"small": Small, "medium": Medium} table = fileh.create_table(fileh.root, 'table', klass[recsize], title, None, nrows) for i in range(nrows): #table.row['var1'] = str(i) #table.row['var2'] = random.randrange(nrows) table.row['var2'] = i table.row['var3'] = i #table.row['var4'] = i % 2 table.row['var4'] = i > 2 table.row.append() rowswritten += nrows table.flush() rowsize = table.rowsize indexrows = 0 # Index one entry: if index: if atom == "string": indexrows = table.cols.var1.create_index() elif atom == "bool": indexrows = table.cols.var4.create_index() elif atom == "int": indexrows = table.cols.var2.create_index() elif atom == "float": indexrows = table.cols.var3.create_index() else: raise ValueError("Index type not supported yet") if verbose: print("Number of indexed rows:", indexrows) # Close the file (eventually destroy the extended type) fileh.close() return (rowswritten, rowsize) def readFile(filename, atom, niter, verbose): # Open the HDF5 file in read-only mode fileh = tb.open_file(filename, mode="r") table = fileh.root.table print("reading", table) if atom == "string": idxcol = table.cols.var1.index elif atom == "bool": idxcol = table.cols.var4.index elif atom == "int": idxcol = table.cols.var2.index else: idxcol = table.cols.var3.index if verbose: print("Max rows in buf:", table.nrowsinbuf) print("Rows in", table._v_pathname, ":", table.nrows) print("Buffersize:", table.rowsize * table.nrowsinbuf) print("MaxTuples:", table.nrowsinbuf) print("Chunk size:", idxcol.sorted.chunksize) print("Number of elements per slice:", idxcol.nelemslice) print("Slice number in", table._v_pathname, ":", idxcol.nrows) rowselected = 0 if atom == "string": for i in range(niter): #results = [table.row["var3"] for i in table(where=2+i<=table.cols.var2 < 10+i)] #results = [table.row.nrow() for i in table(where=2<=table.cols.var2 < 10)] results = [p["var1"] # p.nrow() for p in table(where=table.cols.var1 == "1111")] # for p in table(where="1000"<=table.cols.var1<="1010")] rowselected += len(results) elif atom == "bool": for i in range(niter): results = [p["var2"] # p.nrow() for p in table(where=table.cols.var4 == 0)] rowselected += len(results) elif atom == "int": for i in range(niter): #results = [table.row["var3"] for i in table(where=2+i<=table.cols.var2 < 10+i)] #results = [table.row.nrow() for i in table(where=2<=table.cols.var2 < 10)] results = [p["var2"] # p.nrow() # for p in table(where=110*i<=table.cols.var2<110*(i+1))] # for p in table(where=1000-30", positions) print("Total iterations in search:", niter) rowsread += table.nrows uncomprBytes += idxcol.sorted.chunksize * niter * idxcol.sorted.itemsize results = table.read(coords=positions) print("results length:", len(results)) if verbose: print("Values that fullfill the conditions:") print(results) # Close the file (eventually destroy the extended type) fileh.close() return (rowsread, uncomprBytes, niter) if __name__ == "__main__": import sys import getopt try: import psyco psyco_imported = 1 except: psyco_imported = 0 usage = """usage: %s [-v] [-p] [-R range] [-r] [-w] [-s recsize ] [-a atom] [-c level] [-l complib] [-S] [-F] [-i item] [-n nrows] [-x] [-k niter] file -v verbose -p use "psyco" if available -R select a range in a field in the form "start,stop,step" -r only read test -w only write test -s record size -a use [float], [int], [bool] or [string] atom -c sets a compression level (do not set it or 0 for no compression) -S activate shuffling filter -F activate fletcher32 filter -l sets the compression library to be used ("zlib", "lzo", "ucl", "bzip2") -i item to search -n set the number of rows in tables -x don't make indexes -k number of iterations for reading\n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'vpSFR:rwxk:s:a:c:l:i:n:') except: sys.stderr.write(usage) sys.exit(0) # if we pass too much parameters, abort if len(pargs) != 1: sys.stderr.write(usage) sys.exit(0) # default options verbose = 0 rng = None item = None atom = "int" fieldName = None testread = 1 testwrite = 1 usepsyco = 0 complevel = 0 shuffle = 0 fletcher32 = 0 complib = "zlib" nrows = 100 recsize = "small" index = 1 niter = 1 # Get the options for option in opts: if option[0] == '-v': verbose = 1 if option[0] == '-p': usepsyco = 1 if option[0] == '-S': shuffle = 1 if option[0] == '-F': fletcher32 = 1 elif option[0] == '-R': rng = [int(i) for i in option[1].split(",")] elif option[0] == '-r': testwrite = 0 elif option[0] == '-w': testread = 0 elif option[0] == '-x': index = 0 elif option[0] == '-s': recsize = option[1] elif option[0] == '-a': atom = option[1] if atom not in ["float", "int", "bool", "string"]: sys.stderr.write(usage) sys.exit(0) elif option[0] == '-c': complevel = int(option[1]) elif option[0] == '-l': complib = option[1] elif option[0] == '-i': item = eval(option[1]) elif option[0] == '-n': nrows = int(option[1]) elif option[0] == '-k': niter = int(option[1]) # Build the Filters instance filters = tb.Filters(complevel=complevel, complib=complib, shuffle=shuffle, fletcher32=fletcher32) # Catch the hdf5 file passed as the last argument file = pargs[0] if testwrite: print("Compression level:", complevel) if complevel > 0: print("Compression library:", complib) if shuffle: print("Suffling...") t1 = clock() cpu1 = cpuclock() if psyco_imported and usepsyco: psyco.bind(createFile) (rowsw, rowsz) = createFile(file, nrows, filters, atom, recsize, index, verbose) t2 = clock() cpu2 = cpuclock() tapprows = t2 - t1 cpuapprows = cpu2 - cpu1 print(f"Rows written: {rowsw} Row size: {rowsz}") print( f"Time writing rows: {tapprows:.3f} s (real) " f"{cpuapprows:.3f} s (cpu) {cpuapprows / tapprows:.0%}") print(f"Write rows/sec: {rowsw / tapprows:.0f}") print(f"Write KB/s : {rowsw * rowsz / (tapprows * 1024):.0f}") if testread: if psyco_imported and usepsyco: psyco.bind(readFile) psyco.bind(searchFile) t1 = clock() cpu1 = cpuclock() if rng or item: (rowsr, uncomprB, niter) = searchFile(file, atom, verbose, item) else: for i in range(1): (rowsr, rowsel, rowsz) = readFile(file, atom, niter, verbose) t2 = clock() cpu2 = cpuclock() treadrows = t2 - t1 cpureadrows = cpu2 - cpu1 tMrows = rowsr / 1000 / 1000 sKrows = rowsel / 1000 print(f"Rows read: {rowsr} Mread: {tMrows:.3f} Mrows") print(f"Rows selected: {rowsel} Ksel: {sKrows:.3f} Krows") print( f"Time reading rows: {treadrows:.3f} s (real) " f"{cpureadrows:.3f} s (cpu) {cpureadrows / treadrows:.0%}") print(f"Read Mrows/sec: {tMrows / treadrows:.3f}") # print "Read KB/s :", int(rowsr * rowsz / (treadrows * 1024)) # print "Uncompr MB :", int(uncomprB / (1024 * 1024)) # print "Uncompr MB/s :", int(uncomprB / (treadrows * 1024 * 1024)) # print "Total chunks uncompr :", int(niter) PyTables-3.7.0/bench/shelve-bench.py000066400000000000000000000145411416254111300172430ustar00rootroot00000000000000#!/usr/bin/env python import struct import sys import shelve import numpy as np import tables as tb import psyco # This class is accessible only for the examples class Small(tb.IsDescription): """Record descriptor. A record has several columns. They are represented here as class attributes, whose names are the column names and their values will become their types. The IsDescription class will take care the user will not add any new variables and that its type is correct. """ var1 = tb.StringCol(itemsize=4) var2 = tb.Int32Col() var3 = tb.Float64Col() # Define a user record to characterize some kind of particles class Medium(tb.IsDescription): name = tb.StringCol(itemsize=16) # 16-character String float1 = tb.Float64Col(shape=2, dflt=2.3) #float1 = Float64Col(dflt=1.3) #float2 = Float64Col(dflt=2.3) ADCcount = tb.Int16Col() # signed short integer grid_i = tb.Int32Col() # integer grid_j = tb.Int32Col() # integer pressure = tb.Float32Col() # float (single-precision) energy = tb.Float64Col() # double (double-precision) # Define a user record to characterize some kind of particles class Big(tb.IsDescription): name = tb.StringCol(itemsize=16) # 16-character String #float1 = Float64Col(shape=32, dflt=np.arange(32)) #float2 = Float64Col(shape=32, dflt=np.arange(32)) float1 = tb.Float64Col(shape=32, dflt=range(32)) float2 = tb.Float64Col(shape=32, dflt=[2.2] * 32) ADCcount = tb.Int16Col() # signed short integer grid_i = tb.Int32Col() # integer grid_j = tb.Int32Col() # integer pressure = tb.Float32Col() # float (single-precision) energy = tb.Float64Col() # double (double-precision) def createFile(filename, totalrows, recsize): # Open a 'n'ew file fileh = shelve.open(filename, flag="n") rowswritten = 0 # Get the record object associated with the new table if recsize == "big": d = Big() arr = np.arange(32, dtype=np.float64) arr2 = np.arange(32, dtype=np.float64) elif recsize == "medium": d = Medium() else: d = Small() # print d # sys.exit(0) for j in range(3): # Create a table # table = fileh.create_table(group, 'tuple'+str(j), Record(), title, # compress = 6, expectedrows = totalrows) # Create a Table instance tablename = 'tuple' + str(j) table = [] # Fill the table if recsize == "big" or recsize == "medium": for i in range(totalrows): d.name = 'Particle: %6d' % (i) #d.TDCcount = i % 256 d.ADCcount = (i * 256) % (1 << 16) if recsize == "big": #d.float1 = np.array([i]*32, np.float64) #d.float2 = np.array([i**2]*32, np.float64) arr[0] = 1.1 d.float1 = arr arr2[0] = 2.2 d.float2 = arr2 pass else: d.float1 = np.array([i ** 2] * 2, np.float64) #d.float1 = float(i) #d.float2 = float(i) d.grid_i = i d.grid_j = 10 - i d.pressure = float(i * i) d.energy = float(d.pressure ** 4) table.append((d.ADCcount, d.energy, d.float1, d.float2, d.grid_i, d.grid_j, d.name, d.pressure)) # Only on float case # table.append((d.ADCcount, d.energy, d.float1, # d.grid_i, d.grid_j, d.name, d.pressure)) else: for i in range(totalrows): d.var1 = str(i) d.var2 = i d.var3 = 12.1e10 table.append((d.var1, d.var2, d.var3)) # Save this table on disk fileh[tablename] = table rowswritten += totalrows # Close the file fileh.close() return (rowswritten, struct.calcsize(d._v_fmt)) def readFile(filename, recsize): # Open the HDF5 file in read-only mode fileh = shelve.open(filename, "r") for table in ['tuple0', 'tuple1', 'tuple2']: if recsize == "big" or recsize == "medium": e = [t[2] for t in fileh[table] if t[4] < 20] # if there is only one float (array) #e = [ t[1] for t in fileh[table] if t[3] < 20 ] else: e = [t[1] for t in fileh[table] if t[1] < 20] print("resulting selection list ==>", e) print("Total selected records ==> ", len(e)) # Close the file (eventually destroy the extended type) fileh.close() # Add code to test here if __name__ == "__main__": import getopt from time import perf_counter as clock usage = """usage: %s [-f] [-s recsize] [-i iterations] file -s use [big] record, [medium] or [small] -i sets the number of rows in each table\n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 's:fi:') except: sys.stderr.write(usage) sys.exit(0) # if we pass too much parameters, abort if len(pargs) != 1: sys.stderr.write(usage) sys.exit(0) # default options recsize = "medium" iterations = 100 # Get the options for option in opts: if option[0] == '-s': recsize = option[1] if recsize not in ["big", "medium", "small"]: sys.stderr.write(usage) sys.exit(0) elif option[0] == '-i': iterations = int(option[1]) # Catch the hdf5 file passed as the last argument file = pargs[0] t1 = clock() psyco.bind(createFile) (rowsw, rowsz) = createFile(file, iterations, recsize) t2 = clock() tapprows = t2 - t1 t1 = clock() psyco.bind(readFile) readFile(file, recsize) t2 = clock() treadrows = t2 - t1 print(f"Rows written: {rowsw} Row size: {rowsz}") print(f"Time appending rows: {tapprows:.3f}") print(f"Write rows/sec: {iterations * 3 / tapprows:.0f}") print(f"Write KB/s : {rowsw * rowsz / (tapprows * 1024):.0f}") print(f"Time reading rows: {treadrows:.3f}") print(f"Read rows/sec: {iterations * 3 / treadrows:.0f}") print(f"Read KB/s : {rowsw * rowsz / (treadrows * 1024):.0f}") PyTables-3.7.0/bench/split-file.py000066400000000000000000000022101416254111300167360ustar00rootroot00000000000000""" Split out a monolithic file with many different runs of indexed_search.py. The resulting files are meant for use in get-figures.py. Usage: python split-file.py prefix filename """ import sys from pathlib import Path prefix = sys.argv[1] filename = sys.argv[2] sf = None for line in Path(filename).read_text().splitlines(): if line.startswith('Processing database:'): if sf: sf.close() line2 = line.split(':')[1] # Check if entry is compressed and if has to be processed line2 = line2[:line2.rfind('.')] params = line2.split('-') optlevel = 0 complib = None for param in params: if param[0] == 'O' and param[1].isdigit(): optlevel = int(param[1]) elif param[:-1] in ('zlib', 'lzo'): complib = param if 'PyTables' in prefix: if complib: sfilename = f"{prefix}-O{optlevel}-{complib}.out" else: sfilename = f"{prefix}-O{optlevel}.out" else: sfilename = f"{prefix}.out" sf = file(sfilename, 'a') if sf: sf.write(line) PyTables-3.7.0/bench/sqlite-search-bench-rnd.sh000077500000000000000000000056651416254111300212760ustar00rootroot00000000000000#!/bin/sh # I don't know why, but the /usr/bin/python2.3 from Debian is a 30% slower # than my own compiled version! 2004-08-18 python="/usr/local/bin/python2.3 -O" writedata () { nrows=$1 bfile=$2 smode=$3 psyco=$4 cmd="${python} sqlite-search-bench.py -R -h -b ${bfile} ${psyco} -m ${smode} -w -n ${nrows} data.nobackup/sqlite-${nrows}k.h5" echo ${cmd} ${cmd} } readdata () { nrows=$1 bfile=$2 smode=$3 psyco=$4 if [ "$smode" = "indexed" ]; then #repeats=100 repeats=20 else repeats=2 fi cmd="${python} sqlite-search-bench.py -R -h -b ${bfile} ${psyco} -n ${nrows} -m ${smode} -r -k ${repeats} data.nobackup/sqlite-${nrows}k.h5" echo ${cmd} ${cmd} # Finally, delete the source (if desired) if [ "$smode" = "indexed" ]; then echo "Deleting data file data.nobackup/sqlite-${nrows}k.h5" # rm -f data.nobackup/sqlite-${nrows}k.h5 fi return } overwrite=0 if [ $# > 1 ]; then if [ "$1" = "-o" ]; then overwrite=1 fi fi if [ $# > 2 ]; then psyco=$2 fi # The name of the data bench file bfile="sqlite-dbench.h5" # Move out a possible previous benchmark file bn=`basename $bfile ".h5"` mv -f ${bn}-bck2.h5 ${bn}-bck3.h5 mv -f ${bn}-bck.h5 ${bn}-bck2.h5 if [ "$overwrite" = "1" ]; then echo "moving ${bn}.h5 to ${bn}-bck.h5" mv -f ${bn}.h5 ${bn}-bck.h5 else echo "copying ${bn}.h5 to ${bn}-bck.h5" cp -f ${bn}.h5 ${bn}-bck.h5 fi # Configuration for testing nrowsliststd="1 2" nrowslistidx="1 2" #nrowsliststd="1 2 5 10 20 50 100 200 500 1000 2000 5000 10000 20000 50000" #nrowsliststd="1 2 5 10 20" #nrowslistidx="1 2 5 10 20" # nrowsliststd="1 2 5 10 20 50 100 200 500 1000 2000 5000 10000" # nrowslistidx="1 2 5 10 20 50 100 200 500 1000 2000 5000 10000" #nrowsliststd="1 2 5 10 20 50 100 200 500 1000 2000 5000 10000 20000 50000 100000" #nrowslistidx="1 2 5 10 20 50 100 200 500 1000 2000 5000 10000 20000 50000 100000" for smode in standard indexed; do #for smode in indexed; do echo echo "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" echo "Entering ${smode} mode..." echo "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" echo if [ "$smode" = "standard" ]; then nrowslist=$nrowsliststd else nrowslist=$nrowslistidx fi # Write data files for nrows in $nrowslist; do echo "*************************************************************" echo "Writing for nrows=$nrows Krows, $smode, psyco=$psyco" echo "*************************************************************" writedata ${nrows} ${bfile} "${smode}" "${psyco}" done # Read data files ${python} cacheout.py for nrows in $nrowslist; do echo "***********************************************************" echo "Searching for nrows=$nrows Krows, $smode, psyco=$psyco" echo "***********************************************************" readdata ${nrows} ${bfile} "${smode}" "${psyco}" done done echo "New data available on: $bfile" exit 0 PyTables-3.7.0/bench/sqlite-search-bench.py000066400000000000000000000346211416254111300205220ustar00rootroot00000000000000#!/usr/bin/python import os import random import sqlite3 import sys from pathlib import Path from time import perf_counter as clock from time import process_time as cpuclock import numpy as np import tables as tb randomvalues = 0 standarddeviation = 10_000 # Initialize the random generator always with the same integer # in order to have reproductible results random.seed(19) np.random.seed((19, 20)) # defaults psycon = 0 worst = 0 def createNewBenchFile(bfile, verbose): class Create(tb.IsDescription): nrows = tb.Int32Col(pos=0) irows = tb.Int32Col(pos=1) tfill = tb.Float64Col(pos=2) tidx = tb.Float64Col(pos=3) tcfill = tb.Float64Col(pos=4) tcidx = tb.Float64Col(pos=5) rowsecf = tb.Float64Col(pos=6) rowseci = tb.Float64Col(pos=7) fsize = tb.Float64Col(pos=8) isize = tb.Float64Col(pos=9) psyco = tb.BoolCol(pos=10) class Search(tb.IsDescription): nrows = tb.Int32Col(pos=0) rowsel = tb.Int32Col(pos=1) time1 = tb.Float64Col(pos=2) time2 = tb.Float64Col(pos=3) tcpu1 = tb.Float64Col(pos=4) tcpu2 = tb.Float64Col(pos=5) rowsec1 = tb.Float64Col(pos=6) rowsec2 = tb.Float64Col(pos=7) psyco = tb.BoolCol(pos=8) if verbose: print("Creating a new benchfile:", bfile) # Open the benchmarking file bf = tb.open_file(bfile, "w") # Create groups for recsize in ["sqlite_small"]: group = bf.create_group("/", recsize, recsize + " Group") # Attach the row size of table as attribute if recsize == "small": group._v_attrs.rowsize = 16 # Create a Table for writing bench bf.create_table(group, "create_indexed", Create, "indexed values") bf.create_table(group, "create_standard", Create, "standard values") # create a group for searching bench groupS = bf.create_group(group, "search", "Search Group") # Create Tables for searching for mode in ["indexed", "standard"]: group = bf.create_group(groupS, mode, mode + " Group") # for searching bench # for atom in ["string", "int", "float", "bool"]: for atom in ["string", "int", "float"]: bf.create_table(group, atom, Search, atom + " bench") bf.close() def createFile(filename, nrows, filters, indexmode, heavy, noise, bfile, verbose): # Initialize some variables t1 = 0 t2 = 0 tcpu1 = 0 tcpu2 = 0 rowsecf = 0 rowseci = 0 size1 = 0 size2 = 0 if indexmode == "standard": print("Creating a new database:", dbfile) instd = os.popen("/usr/local/bin/sqlite " + dbfile, "w") CREATESTD = """ CREATE TABLE small ( -- Name Type -- Example --------------------------------------- recnum INTEGER PRIMARY KEY, -- 345 var1 char(4), -- Abronia villosa var2 INTEGER, -- 111 var3 FLOAT -- 12.32 ); """ CREATEIDX = """ CREATE TABLE small ( -- Name Type -- Example --------------------------------------- recnum INTEGER PRIMARY KEY, -- 345 var1 char(4), -- Abronia villosa var2 INTEGER, -- 111 var3 FLOAT -- 12.32 ); CREATE INDEX ivar1 ON small(var1); CREATE INDEX ivar2 ON small(var2); CREATE INDEX ivar3 ON small(var3); """ # Creating the table first and indexing afterwards is a bit faster instd.write(CREATESTD) instd.close() conn = sqlite3.connect(dbfile) cursor = conn.cursor() if indexmode == "standard": place_holders = ",".join(['%s'] * 3) # Insert rows SQL = "insert into small values(NULL, %s)" % place_holders time1 = clock() cpu1 = cpuclock() # This way of filling is to copy the PyTables benchmark nrowsbuf = 1000 minimum = 0 maximum = nrows for i in range(0, nrows, nrowsbuf): if i + nrowsbuf > nrows: j = nrows else: j = i + nrowsbuf if randomvalues: var3 = np.random.uniform(minimum, maximum, shape=[j - i]) else: var3 = np.arange(i, j, type=np.float64) if noise: var3 += np.random.uniform(-3, 3, shape=[j - i]) var2 = np.array(var3, type=np.int32) var1 = np.array(None, shape=[j - i], dtype='s4') if not heavy: for n in range(j - i): var1[n] = str("%.4s" % var2[n]) for n in range(j - i): fields = (var1[n], var2[n], var3[n]) cursor.execute(SQL, fields) conn.commit() t1 = clock() - time1 tcpu1 = cpuclock() - cpu1 rowsecf = nrows / t1 size1 = Path(dbfile).stat().st_size print(f"******** Results for writing nrows = {nrows} *********") print(f"Insert time: {t1:.5f}, KRows/s: {nrows / 1000 / t1:.3f}") print(f", File size: {size1 / 1024 / 1024:.3f} MB") # Indexem if indexmode == "indexed": time1 = clock() cpu1 = cpuclock() if not heavy: cursor.execute("CREATE INDEX ivar1 ON small(var1)") conn.commit() cursor.execute("CREATE INDEX ivar2 ON small(var2)") conn.commit() cursor.execute("CREATE INDEX ivar3 ON small(var3)") conn.commit() t2 = clock() - time1 tcpu2 = cpuclock() - cpu1 rowseci = nrows / t2 print(f"Index time: {t2:.5f}, IKRows/s: {nrows / 1000 / t2:.3f}") size2 = Path(dbfile).stat().st_size - size1 print(f", Final size with index: {size2 / 1024 / 1024:.3f} MB") conn.close() # Collect benchmark data bf = tb.open_file(bfile, "a") recsize = "sqlite_small" if indexmode == "indexed": table = bf.get_node("/" + recsize + "/create_indexed") else: table = bf.get_node("/" + recsize + "/create_standard") table.row["nrows"] = nrows table.row["irows"] = nrows table.row["tfill"] = t1 table.row["tidx"] = t2 table.row["tcfill"] = tcpu1 table.row["tcidx"] = tcpu2 table.row["psyco"] = psycon table.row["rowsecf"] = rowsecf table.row["rowseci"] = rowseci table.row["fsize"] = size1 table.row["isize"] = size2 table.row.append() bf.close() return def readFile(dbfile, nrows, indexmode, heavy, dselect, bfile, riter): # Connect to the database. conn = sqlite3.connect(db=dbfile, mode=755) # Obtain a cursor cursor = conn.cursor() # select count(*), avg(var2) SQL1 = """ select recnum from small where var1 = %s """ SQL2 = """ select recnum from small where var2 >= %s and var2 < %s """ SQL3 = """ select recnum from small where var3 >= %s and var3 < %s """ # Open the benchmark database bf = tb.open_file(bfile, "a") # default values for the case that columns are not indexed t2 = 0 tcpu2 = 0 # Some previous computations for the case of random values if randomvalues: # algorithm to choose a value separated from mean # If want to select fewer values, select this # if nrows/2 > standarddeviation*3: # Choose five standard deviations away from mean value # dev = standarddeviation*5 # dev = standarddeviation*math.log10(nrows/1000.) # This algorithm give place to too asymmetric result values # if standarddeviation*10 < nrows/2: # Choose four standard deviations away from mean value # dev = standarddeviation*4 # else: # dev = 100 # Yet Another Algorithm if nrows / 2 > standarddeviation * 10: dev = standarddeviation * 4 elif nrows / 2 > standarddeviation: dev = standarddeviation * 2 elif nrows / 2 > standarddeviation / 10: dev = standarddeviation / 10 else: dev = standarddeviation / 100 valmax = round(nrows / 2 - dev) # split the selection range in regular chunks if riter > valmax * 2: riter = valmax * 2 chunksize = (valmax * 2 / riter) * 10 # Get a list of integers for the intervals randlist = range(0, valmax, chunksize) randlist.extend(range(nrows - valmax, nrows, chunksize)) # expand the list ten times so as to use the cache randlist = randlist * 10 # shuffle the list random.shuffle(randlist) # reset the value of chunksize chunksize = chunksize / 10 # print "chunksize-->", chunksize # randlist.sort();print "randlist-->", randlist else: chunksize = 3 if heavy: searchmodelist = ["int", "float"] else: searchmodelist = ["string", "int", "float"] # Execute queries for atom in searchmodelist: time2 = 0 cpu2 = 0 rowsel = 0 for i in range(riter): rnd = random.randrange(nrows) time1 = clock() cpu1 = cpuclock() if atom == "string": #cursor.execute(SQL1, "1111") cursor.execute(SQL1, str(rnd)[-4:]) elif atom == "int": #cursor.execute(SQL2 % (rnd, rnd+3)) cursor.execute(SQL2 % (rnd, rnd + dselect)) elif atom == "float": #cursor.execute(SQL3 % (float(rnd), float(rnd+3))) cursor.execute(SQL3 % (float(rnd), float(rnd + dselect))) else: raise ValueError( "atom must take a value in ['string','int','float']") if i == 0: t1 = clock() - time1 tcpu1 = cpuclock() - cpu1 else: if indexmode == "indexed": # if indexed, wait until the 5th iteration to take # times (so as to insure that the index is # effectively cached) if i >= 5: time2 += clock() - time1 cpu2 += cpuclock() - cpu1 else: time2 += clock() - time1 time2 += cpuclock() - cpu1 if riter > 1: if indexmode == "indexed" and riter >= 5: correction = 5 else: correction = 1 t2 = time2 / (riter - correction) tcpu2 = cpu2 / (riter - correction) print( f"*** Query results for atom = {atom}, " f"nrows = {nrows}, indexmode = {indexmode} ***") print(f"Query time: {t1:.5f}, cached time: {t2:.5f}") print(f"MRows/s: {nrows / 1_000_000 / t1:.3f}", end=' ') if t2 > 0: print(f", cached MRows/s: {nrows / 10 ** 6 / t2:.3f}") else: print() # Collect benchmark data recsize = "sqlite_small" tablepath = "/" + recsize + "/search/" + indexmode + "/" + atom table = bf.get_node(tablepath) table.row["nrows"] = nrows table.row["rowsel"] = rowsel table.row["time1"] = t1 table.row["time2"] = t2 table.row["tcpu1"] = tcpu1 table.row["tcpu2"] = tcpu2 table.row["psyco"] = psycon table.row["rowsec1"] = nrows / t1 if t2 > 0: table.row["rowsec2"] = nrows / t2 table.row.append() table.flush() # Flush the data # Close the database conn.close() bf.close() # the bench database return if __name__ == "__main__": import getopt try: import psyco psyco_imported = 1 except: psyco_imported = 0 usage = """usage: %s [-v] [-p] [-R] [-h] [-t] [-r] [-w] [-n nrows] [-b file] [-k riter] [-m indexmode] [-N range] datafile -v verbose -p use "psyco" if available -R use Random values for filling -h heavy mode (exclude strings from timings) -t worsT searching case (to emulate PyTables worst cases) -r only read test -w only write test -n the number of rows (in krows) -b bench filename -N introduce (uniform) noise within range into the values -d the interval for look values (int, float) at. Default is 3. -k number of iterations for reading\n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'vpRhtrwn:b:k:m:N:d:') except: sys.stderr.write(usage) sys.exit(0) # if we pass too much parameters, abort if len(pargs) != 1: sys.stderr.write(usage) sys.exit(0) # default options dselect = 3 noise = 0 verbose = 0 heavy = 0 testread = 1 testwrite = 1 usepsyco = 0 nrows = 1000 bfile = "sqlite-bench.h5" supported_imodes = ["indexed", "standard"] indexmode = "indexed" riter = 2 # Get the options for option in opts: if option[0] == '-v': verbose = 1 if option[0] == '-p': usepsyco = 1 elif option[0] == '-R': randomvalues = 1 elif option[0] == '-h': heavy = 1 elif option[0] == '-t': worst = 1 elif option[0] == '-r': testwrite = 0 elif option[0] == '-w': testread = 0 elif option[0] == '-b': bfile = option[1] elif option[0] == '-N': noise = float(option[1]) elif option[0] == '-m': indexmode = option[1] if indexmode not in supported_imodes: raise ValueError( "Indexmode should be any of '%s' and you passed '%s'" % (supported_imodes, indexmode)) elif option[0] == '-n': nrows = int(float(option[1]) * 1000) elif option[0] == '-d': dselect = float(option[1]) elif option[0] == '-k': riter = int(option[1]) # remaining parameters dbfile = pargs[0] if worst: nrows -= 1 # the worst case # Create the benchfile (if needed) if not Path(bfile).exists(): createNewBenchFile(bfile, verbose) if testwrite: if psyco_imported and usepsyco: psyco.bind(createFile) psycon = 1 createFile(dbfile, nrows, None, indexmode, heavy, noise, bfile, verbose) if testread: if psyco_imported and usepsyco: psyco.bind(readFile) psycon = 1 readFile(dbfile, nrows, indexmode, heavy, dselect, bfile, riter) PyTables-3.7.0/bench/sqlite-search-bench.sh000077500000000000000000000051051416254111300205020ustar00rootroot00000000000000#!/bin/sh # I don't know why, but the /usr/bin/python2.3 from Debian is a 30% slower # than my own compiled version! 2004-08-18 python="/usr/local/bin/python2.3 -O" writedata () { nrows=$1 bfile=$2 smode=$3 psyco=$4 cmd="${python} sqlite-search-bench.py -b ${bfile} ${psyco} -m ${smode} -w -n ${nrows} data.nobackup/sqlite-${nrows}k-${smode}.h5" echo ${cmd} ${cmd} } readdata () { nrows=$1 bfile=$2 smode=$3 psyco=$4 if [ "$smode" = "indexed" ]; then repeats=100 else repeats=2 fi cmd="${python} sqlite-search-bench.py -b ${bfile} ${psyco} -n ${nrows} -m ${smode} -r -k ${repeats} data.nobackup/sqlite-${nrows}k-${smode}.h5" echo ${cmd} ${cmd} # Finally, delete the source (if desired) #rm -f data.nobackup/sqlite-${nrows}k-${smode}.h5 return } overwrite=0 if [ $# > 1 ]; then if [ "$1" = "-o" ]; then overwrite=1 fi fi if [ $# > 2 ]; then psyco=$2 fi # The name of the data bench file bfile="sqlite-dbench.h5" # Move out a possible previous benchmark file bn=`basename $bfile ".h5"` mv -f ${bn}-bck2.h5 ${bn}-bck3.h5 mv -f ${bn}-bck.h5 ${bn}-bck2.h5 if [ "$overwrite" = "1" ]; then echo "moving ${bn}.h5 to ${bn}-bck.h5" mv -f ${bn}.h5 ${bn}-bck.h5 else echo "copying ${bn}.h5 to ${bn}-bck.h5" cp -f ${bn}.h5 ${bn}-bck.h5 fi # Configuration for testing nrowsliststd="1 2 5 10 20 50" #nrowslistidx="1 2 5 10 20 50" #nrowsliststd="1 2 5 10 20 50 100 200 500 1000 2000 5000 10000 20000 50000" nrowslistidx="1 2 5 10 20 50 100 200 500 1000 2000 5000 10000" #for smode in standard indexed; do for smode in indexed; do echo echo "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" echo "Entering ${smode} mode..." echo "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" echo if [ "$smode" = "standard" ]; then nrowslist=$nrowsliststd else nrowslist=$nrowslistidx fi # Write data files # for nrows in $nrowslist; do # echo "*************************************************************" # echo "Writing for nrows=$nrows Krows, $smode, psyco=$psyco" # echo "*************************************************************" # writedata ${nrows} ${bfile} "${smode}" "${psyco}" # done # Read data files ${python} cacheout.py for nrows in $nrowslist; do echo "***********************************************************" echo "Searching for nrows=$nrows Krows, $smode, psyco=$psyco" echo "***********************************************************" readdata ${nrows} ${bfile} "${smode}" "${psyco}" done done echo "New data available on: $bfile" exit 0 PyTables-3.7.0/bench/sqlite3-search-bench.py000066400000000000000000000115751416254111300206100ustar00rootroot00000000000000from pathlib import Path from time import perf_counter as clock import numpy as np import random # in order to always generate the same random sequence random.seed(19) def fill_arrays(start, stop): col_i = np.arange(start, stop, dtype=np.int32) if userandom: col_j = np.random.uniform(0, nrows, stop - start) else: col_j = np.array(col_i, dtype=np.float64) return col_i, col_j # Generator for ensure pytables benchmark compatibility def int_generator(nrows): step = 1000 * 100 j = 0 for i in range(nrows): if i >= step * j: stop = (j + 1) * step if stop > nrows: # Seems unnecessary stop = nrows col_i, col_j = fill_arrays(i, stop) j += 1 k = 0 yield (col_i[k], col_j[k]) k += 1 def int_generator_slow(nrows): for i in range(nrows): if userandom: yield (i, float(random.randint(0, nrows))) else: yield (i, float(i)) def open_db(filename, remove=0): if remove and Path(filename).is_file(): Path(filename).unlink() con = sqlite.connect(filename) cur = con.cursor() return con, cur def create_db(filename, nrows): con, cur = open_db(filename, remove=1) cur.execute("create table ints(i integer, j real)") t1 = clock() # This is twice as fast as a plain loop cur.executemany("insert into ints(i,j) values (?,?)", int_generator(nrows)) con.commit() ctime = clock() - t1 if verbose: print(f"insert time: {ctime:.5f}") print(f"Krows/s: {nrows / 1000 / ctime:.5f}") close_db(con, cur) def index_db(filename): con, cur = open_db(filename) t1 = clock() cur.execute("create index ij on ints(j)") con.commit() itime = clock() - t1 if verbose: print(f"index time: {itime:.5f}") print(f"Krows/s: {nrows / itime:.5f}") # Close the DB close_db(con, cur) def query_db(filename, rng): con, cur = open_db(filename) t1 = clock() ntimes = 10 for i in range(ntimes): # between clause does not seem to take advantage of indexes # cur.execute("select j from ints where j between %s and %s" % \ cur.execute("select i from ints where j >= %s and j <= %s" % # cur.execute("select i from ints where i >= %s and i <= # %s" % (rng[0] + i, rng[1] + i)) results = cur.fetchall() con.commit() qtime = (clock() - t1) / ntimes if verbose: print(f"query time: {qtime:.5f}") print(f"Mrows/s: {nrows / 1000 / qtime:.5f}") print(results) close_db(con, cur) def close_db(con, cur): cur.close() con.close() if __name__ == "__main__": import sys import getopt try: import psyco psyco_imported = 1 except: psyco_imported = 0 usage = """usage: %s [-v] [-p] [-m] [-i] [-q] [-c] [-R range] [-n nrows] file -v verbose -p use "psyco" if available -m use random values to fill the table -q do query -c create the database -i index the table -2 use sqlite2 (default is use sqlite3) -R select a range in a field in the form "start,stop" (def "0,10") -n sets the number of rows (in krows) in each table \n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'vpmiqc2R:n:') except: sys.stderr.write(usage) sys.exit(0) # default options verbose = 0 usepsyco = 0 userandom = 0 docreate = 0 createindex = 0 doquery = 0 sqlite_version = "3" rng = [0, 10] nrows = 1 # Get the options for option in opts: if option[0] == '-v': verbose = 1 elif option[0] == '-p': usepsyco = 1 elif option[0] == '-m': userandom = 1 elif option[0] == '-i': createindex = 1 elif option[0] == '-q': doquery = 1 elif option[0] == '-c': docreate = 1 elif option[0] == "-2": sqlite_version = "2" elif option[0] == '-R': rng = [int(i) for i in option[1].split(",")] elif option[0] == '-n': nrows = int(option[1]) # Catch the hdf5 file passed as the last argument filename = pargs[0] if sqlite_version == "2": import sqlite else: from pysqlite2 import dbapi2 as sqlite if verbose: print("pysqlite version:", sqlite.version) if userandom: print("using random values") if docreate: if verbose: print("writing %s krows" % nrows) if psyco_imported and usepsyco: psyco.bind(create_db) nrows *= 1000 create_db(filename, nrows) if createindex: index_db(filename) if doquery: query_db(filename, rng) PyTables-3.7.0/bench/stress-test.py000066400000000000000000000274221416254111300172020ustar00rootroot00000000000000import gc import sys from time import perf_counter as clock from time import process_time as cpuclock import numpy as np import tables as tb class Test(tb.IsDescription): ngroup = tb.Int32Col(pos=1) ntable = tb.Int32Col(pos=2) nrow = tb.Int32Col(pos=3) #string = StringCol(itemsize=500, pos=4) TestDict = { "ngroup": tb.Int32Col(pos=1), "ntable": tb.Int32Col(pos=2), "nrow": tb.Int32Col(pos=3), } def createFileArr(filename, ngroups, ntables, nrows): # First, create the groups # Open a file in "w"rite mode fileh = tb.open_file(filename, mode="w", title="PyTables Stress Test") for k in range(ngroups): # Create the group fileh.create_group("/", 'group%04d' % k, "Group %d" % k) fileh.close() # Now, create the arrays arr = np.arange(nrows) for k in range(ngroups): fileh = tb.open_file(filename, mode="a", root_uep='group%04d' % k) for j in range(ntables): # Create the array fileh.create_array("/", 'array%04d' % j, arr, "Array %d" % j) fileh.close() return (ngroups * ntables * nrows, 4) def readFileArr(filename, ngroups, recsize, verbose): rowsread = 0 for ngroup in range(ngroups): fileh = tb.open_file(filename, mode="r", root_uep='group%04d' % ngroup) # Get the group group = fileh.root narrai = 0 if verbose: print("Group ==>", group) for arrai in fileh.list_nodes(group, 'Array'): if verbose > 1: print("Array ==>", arrai) print("Rows in", arrai._v_pathname, ":", arrai.shape) arr = arrai.read() rowsread += len(arr) narrai += 1 # Close the file (eventually destroy the extended type) fileh.close() return (rowsread, 4, rowsread * 4) def createFile(filename, ngroups, ntables, nrows, complevel, complib, recsize): # First, create the groups # Open a file in "w"rite mode fileh = tb.open_file(filename, mode="w", title="PyTables Stress Test") for k in range(ngroups): # Create the group group = fileh.create_group("/", 'group%04d' % k, "Group %d" % k) fileh.close() # Now, create the tables rowswritten = 0 if not ntables: rowsize = 0 for k in range(ngroups): print("Filling tables in group:", k) fileh = tb.open_file(filename, mode="a", root_uep='group%04d' % k) # Get the group group = fileh.root for j in range(ntables): # Create a table # table = fileh.create_table(group, 'table%04d'% j, Test, table = fileh.create_table(group, 'table%04d' % j, TestDict, 'Table%04d' % j, complevel, complib, nrows) rowsize = table.rowsize # Get the row object associated with the new table row = table.row # Fill the table for i in range(nrows): row['ngroup'] = k row['ntable'] = j row['nrow'] = i row.append() rowswritten += nrows table.flush() # Close the file fileh.close() return (rowswritten, rowsize) def readFile(filename, ngroups, recsize, verbose): # Open the HDF5 file in read-only mode rowsize = 0 buffersize = 0 rowsread = 0 for ngroup in range(ngroups): fileh = tb.open_file(filename, mode="r", root_uep='group%04d' % ngroup) # Get the group group = fileh.root ntable = 0 if verbose: print("Group ==>", group) for table in fileh.list_nodes(group, 'Table'): rowsize = table.rowsize buffersize = table.rowsize * table.nrowsinbuf if verbose > 1: print("Table ==>", table) print("Max rows in buf:", table.nrowsinbuf) print("Rows in", table._v_pathname, ":", table.nrows) print("Buffersize:", table.rowsize * table.nrowsinbuf) print("MaxTuples:", table.nrowsinbuf) nrow = 0 if table.nrows > 0: # only read if we have rows in tables for row in table: try: assert row["ngroup"] == ngroup assert row["ntable"] == ntable assert row["nrow"] == nrow except: print("Error in group: %d, table: %d, row: %d" % (ngroup, ntable, nrow)) print("Record ==>", row) nrow += 1 assert nrow == table.nrows rowsread += table.nrows ntable += 1 # Close the file (eventually destroy the extended type) fileh.close() return (rowsread, rowsize, buffersize) class TrackRefs: """Object to track reference counts across test runs.""" def __init__(self, verbose=0): self.type2count = {} self.type2all = {} self.verbose = verbose def update(self, verbose=0): obs = sys.getobjects(0) type2count = {} type2all = {} for o in obs: all = sys.getrefcount(o) t = type(o) if verbose: # if t == types.TupleType: if isinstance(o, tb.Group): # if isinstance(o, MetaIsDescription): print("-->", o, "refs:", all) refrs = gc.get_referrers(o) trefrs = [] for refr in refrs: trefrs.append(type(refr)) print("Referrers -->", refrs) print("Referrers types -->", trefrs) # if t == types.StringType: print "-->",o if t in type2count: type2count[t] += 1 type2all[t] += all else: type2count[t] = 1 type2all[t] = all ct = sorted([(type2count[t] - self.type2count.get(t, 0), type2all[t] - self.type2all.get(t, 0), t) for t in type2count.keys()]) ct.reverse() for delta1, delta2, t in ct: if delta1 or delta2: print("%-55s %8d %8d" % (t, delta1, delta2)) self.type2count = type2count self.type2all = type2all def dump_refs(preheat=10, iter1=10, iter2=10, *testargs): rc1 = rc2 = None # testMethod() for i in range(preheat): testMethod(*testargs) gc.collect() rc1 = sys.gettotalrefcount() track = TrackRefs() for i in range(iter1): testMethod(*testargs) print("First output of TrackRefs:") gc.collect() rc2 = sys.gettotalrefcount() track.update() print("Inc refs in function testMethod --> %5d" % (rc2 - rc1), file=sys.stderr) for i in range(iter2): testMethod(*testargs) track.update(verbose=1) print("Second output of TrackRefs:") gc.collect() rc3 = sys.gettotalrefcount() print("Inc refs in function testMethod --> %5d" % (rc3 - rc2), file=sys.stderr) def dump_garbage(): """show us waht the garbage is about.""" # Force collection print("\nGARBAGE:") gc.collect() print("\nGARBAGE OBJECTS:") for x in gc.garbage: s = str(x) #if len(s) > 80: s = s[:77] + "..." print(type(x), "\n ", s) # print "\nTRACKED OBJECTS:" # reportLoggedInstances("*") def testMethod(file, usearray, testwrite, testread, complib, complevel, ngroups, ntables, nrows): if complevel > 0: print("Compression library:", complib) if testwrite: t1 = clock() cpu1 = cpuclock() if usearray: (rowsw, rowsz) = createFileArr(file, ngroups, ntables, nrows) else: (rowsw, rowsz) = createFile(file, ngroups, ntables, nrows, complevel, complib, recsize) t2 = clock() cpu2 = cpuclock() tapprows = t2 - t1 cpuapprows = cpu2 - cpu1 print(f"Rows written: {rowsw} Row size: {rowsz}") print( f"Time writing rows: {tapprows:.3f} s (real) " f"{cpuapprows:.3f} s (cpu) {cpuapprows / tapprows:.0%}") print(f"Write rows/sec: {rowsw / tapprows}") print(f"Write KB/s : {rowsw * rowsz / (tapprows * 1024):.0f}") if testread: t1 = clock() cpu1 = cpuclock() if usearray: (rowsr, rowsz, bufsz) = readFileArr(file, ngroups, recsize, verbose) else: (rowsr, rowsz, bufsz) = readFile(file, ngroups, recsize, verbose) t2 = clock() cpu2 = cpuclock() treadrows = t2 - t1 cpureadrows = cpu2 - cpu1 print(f"Rows read: {rowsw} Row size: {rowsz}, Buf size: {bufsz}") print( f"Time reading rows: {treadrows:.3f} s (real) " f"{cpureadrows:.3f} s (cpu) {cpureadrows / treadrows:.0%}") print(f"Read rows/sec: {rowsr / treadrows}") print(f"Read KB/s : {rowsr * rowsz / (treadrows * 1024):.0f}") if __name__ == "__main__": import getopt import profile try: import psyco psyco_imported = 1 except: psyco_imported = 0 usage = """usage: %s [-d debug] [-v level] [-p] [-r] [-w] [-l complib] [-c complevel] [-g ngroups] [-t ntables] [-i nrows] file -d debugging level -v verbosity level -p use "psyco" if available -a use Array objects instead of Table -r only read test -w only write test -l sets the compression library to be used ("zlib", "lzo", "ucl", "bzip2") -c sets a compression level (do not set it or 0 for no compression) -g number of groups hanging from "/" -t number of tables per group -i number of rows per table """ try: opts, pargs = getopt.getopt(sys.argv[1:], 'd:v:parwl:c:g:t:i:') except: sys.stderr.write(usage) sys.exit(0) # if we pass too much parameters, abort if len(pargs) != 1: sys.stderr.write(usage) sys.exit(0) # default options ngroups = 5 ntables = 5 nrows = 100 verbose = 0 debug = 0 recsize = "medium" testread = 1 testwrite = 1 usepsyco = 0 usearray = 0 complevel = 0 complib = "zlib" # Get the options for option in opts: if option[0] == '-d': debug = int(option[1]) if option[0] == '-v': verbose = int(option[1]) if option[0] == '-p': usepsyco = 1 if option[0] == '-a': usearray = 1 elif option[0] == '-r': testwrite = 0 elif option[0] == '-w': testread = 0 elif option[0] == '-l': complib = option[1] elif option[0] == '-c': complevel = int(option[1]) elif option[0] == '-g': ngroups = int(option[1]) elif option[0] == '-t': ntables = int(option[1]) elif option[0] == '-i': nrows = int(option[1]) if debug: gc.enable() if debug == 1: gc.set_debug(gc.DEBUG_LEAK) # Catch the hdf5 file passed as the last argument file = pargs[0] if psyco_imported and usepsyco: psyco.bind(createFile) psyco.bind(readFile) if debug == 2: dump_refs(10, 10, 15, file, usearray, testwrite, testread, complib, complevel, ngroups, ntables, nrows) else: # testMethod(file, usearray, testwrite, testread, complib, complevel, # ngroups, ntables, nrows) profile.run("testMethod(file, usearray, testwrite, testread, " + "complib, complevel, ngroups, ntables, nrows)") # Show the dirt if debug == 1: dump_garbage() PyTables-3.7.0/bench/stress-test2.py000066400000000000000000000163351416254111300172650ustar00rootroot00000000000000import gc import sys import random from time import perf_counter as clock from time import process_time as cpuclock import tables as tb class Test(tb.IsDescription): ngroup = tb.Int32Col(pos=1) ntable = tb.Int32Col(pos=2) nrow = tb.Int32Col(pos=3) time = tb.Float64Col(pos=5) random = tb.Float32Col(pos=4) def createFile(filename, ngroups, ntables, nrows, complevel, complib, recsize): # First, create the groups # Open a file in "w"rite mode fileh = tb.open_file(filename, mode="w", title="PyTables Stress Test") for k in range(ngroups): # Create the group group = fileh.create_group("/", 'group%04d' % k, "Group %d" % k) fileh.close() # Now, create the tables rowswritten = 0 for k in range(ngroups): fileh = tb.open_file(filename, mode="a", root_uep='group%04d' % k) # Get the group group = fileh.root for j in range(ntables): # Create a table table = fileh.create_table(group, 'table%04d' % j, Test, 'Table%04d' % j, complevel, complib, nrows) # Get the row object associated with the new table row = table.row # Fill the table for i in range(nrows): row['time'] = clock() row['random'] = random.random() * 40 + 100 row['ngroup'] = k row['ntable'] = j row['nrow'] = i row.append() rowswritten += nrows table.flush() # Close the file fileh.close() return (rowswritten, table.rowsize) def readFile(filename, ngroups, recsize, verbose): # Open the HDF5 file in read-only mode rowsread = 0 for ngroup in range(ngroups): fileh = tb.open_file(filename, mode="r", root_uep='group%04d' % ngroup) # Get the group group = fileh.root ntable = 0 if verbose: print("Group ==>", group) for table in fileh.list_nodes(group, 'Table'): rowsize = table.rowsize buffersize = table.rowsize * table.nrowsinbuf if verbose > 1: print("Table ==>", table) print("Max rows in buf:", table.nrowsinbuf) print("Rows in", table._v_pathname, ":", table.nrows) print("Buffersize:", table.rowsize * table.nrowsinbuf) print("MaxTuples:", table.nrowsinbuf) nrow = 0 time_1 = 0.0 for row in table: try: # print "row['ngroup'], ngroup ==>", row["ngroup"], ngroup assert row["ngroup"] == ngroup assert row["ntable"] == ntable assert row["nrow"] == nrow # print "row['time'], time_1 ==>", row["time"], time_1 assert row["time"] >= (time_1 - 0.01) #assert 100 <= row["random"] <= 139.999 assert 100 <= row["random"] <= 140 except: print("Error in group: %d, table: %d, row: %d" % (ngroup, ntable, nrow)) print("Record ==>", row) time_1 = row["time"] nrow += 1 assert nrow == table.nrows rowsread += table.nrows ntable += 1 # Close the file (eventually destroy the extended type) fileh.close() return (rowsread, rowsize, buffersize) def dump_garbage(): """show us waht the garbage is about.""" # Force collection print("\nGARBAGE:") gc.collect() print("\nGARBAGE OBJECTS:") for x in gc.garbage: s = str(x) #if len(s) > 80: s = s[:77] + "..." print(type(x), "\n ", s) if __name__ == "__main__": import getopt try: import psyco psyco_imported = 1 except: psyco_imported = 0 usage = """usage: %s [-d debug] [-v level] [-p] [-r] [-w] [-l complib] [-c complevel] [-g ngroups] [-t ntables] [-i nrows] file -d debugging level -v verbosity level -p use "psyco" if available -r only read test -w only write test -l sets the compression library to be used ("zlib", "lzo", "ucl", "bzip2") -c sets a compression level (do not set it or 0 for no compression) -g number of groups hanging from "/" -t number of tables per group -i number of rows per table """ try: opts, pargs = getopt.getopt(sys.argv[1:], 'd:v:prwl:c:g:t:i:') except: sys.stderr.write(usage) sys.exit(0) # if we pass too much parameters, abort if len(pargs) != 1: sys.stderr.write(usage) sys.exit(0) # default options ngroups = 5 ntables = 5 nrows = 100 verbose = 0 debug = 0 recsize = "medium" testread = 1 testwrite = 1 usepsyco = 0 complevel = 0 complib = "zlib" # Get the options for option in opts: if option[0] == '-d': debug = int(option[1]) if option[0] == '-v': verbose = int(option[1]) if option[0] == '-p': usepsyco = 1 elif option[0] == '-r': testwrite = 0 elif option[0] == '-w': testread = 0 elif option[0] == '-l': complib = option[1] elif option[0] == '-c': complevel = int(option[1]) elif option[0] == '-g': ngroups = int(option[1]) elif option[0] == '-t': ntables = int(option[1]) elif option[0] == '-i': nrows = int(option[1]) if debug: gc.enable() gc.set_debug(gc.DEBUG_LEAK) # Catch the hdf5 file passed as the last argument file = pargs[0] print("Compression level:", complevel) if complevel > 0: print("Compression library:", complib) if testwrite: t1 = clock() cpu1 = cpuclock() if psyco_imported and usepsyco: psyco.bind(createFile) (rowsw, rowsz) = createFile(file, ngroups, ntables, nrows, complevel, complib, recsize) t2 = clock() cpu2 = cpuclock() tapprows = t2 - t1 cpuapprows = cpu2 - cpu1 print(f"Rows written: {rowsw} Row size: {rowsz}") print( f"Time writing rows: {tapprows:.3f} s (real) " f"{cpuapprows:.3f} s (cpu) {cpuapprows / tapprows:.0%}") print(f"Write rows/sec: {rowsw / tapprows}") print(f"Write KB/s : {rowsw * rowsz / (tapprows * 1024):.0f}") if testread: t1 = clock() cpu1 = cpuclock() if psyco_imported and usepsyco: psyco.bind(readFile) (rowsr, rowsz, bufsz) = readFile(file, ngroups, recsize, verbose) t2 = clock() cpu2 = cpuclock() treadrows = t2 - t1 cpureadrows = cpu2 - cpu1 print(f"Rows read: {rowsw} Row size: {rowsz}, Buf size: {bufsz}") print( f"Time reading rows: {treadrows:.3f} s (real) " f"{cpureadrows:.3f} s (cpu) {cpureadrows / treadrows:.0%}") print(f"Read rows/sec: {rowsr / treadrows}") print(f"Read KB/s : {rowsr * rowsz / (treadrows * 1024):.0f}") # Show the dirt if debug > 1: dump_garbage() PyTables-3.7.0/bench/stress-test3.py000066400000000000000000000206441416254111300172640ustar00rootroot00000000000000#!/usr/bin/env python """This script allows to create arbitrarily large files with the desired combination of groups, tables per group and rows per table. Issue "python stress-test3.py" without parameters for a help on usage. """ import gc import sys from time import perf_counter as clock from time import process_time as cpuclock import tables as tb class Test(tb.IsDescription): ngroup = tb.Int32Col(pos=1) ntable = tb.Int32Col(pos=2) nrow = tb.Int32Col(pos=3) string = tb.StringCol(500, pos=4) def createFileArr(filename, ngroups, ntables, nrows): # First, create the groups # Open a file in "w"rite mode fileh = tb.open_file(filename, mode="w", title="PyTables Stress Test") for k in range(ngroups): # Create the group fileh.create_group("/", 'group%04d' % k, "Group %d" % k) fileh.close() return (0, 4) def readFileArr(filename, ngroups, recsize, verbose): rowsread = 0 for ngroup in range(ngroups): fileh = tb.open_file(filename, mode="r", root_uep='group%04d' % ngroup) # Get the group group = fileh.root ntable = 0 if verbose: print("Group ==>", group) for table in fileh.list_nodes(group, 'Array'): if verbose > 1: print("Array ==>", table) print("Rows in", table._v_pathname, ":", table.shape) arr = table.read() rowsread += len(arr) ntable += 1 # Close the file (eventually destroy the extended type) fileh.close() return (rowsread, 4, 0) def createFile(filename, ngroups, ntables, nrows, complevel, complib, recsize): # First, create the groups # Open a file in "w"rite mode fileh = tb.open_file(filename, mode="w", title="PyTables Stress Test") for k in range(ngroups): # Create the group group = fileh.create_group("/", 'group%04d' % k, "Group %d" % k) fileh.close() # Now, create the tables rowswritten = 0 for k in range(ngroups): fileh = tb.open_file(filename, mode="a", root_uep='group%04d' % k) # Get the group group = fileh.root for j in range(ntables): # Create a table table = fileh.create_table(group, 'table%04d' % j, Test, 'Table%04d' % j, tb.Filters(complevel, complib), nrows) rowsize = table.rowsize # Get the row object associated with the new table row = table.row # Fill the table for i in range(nrows): row['ngroup'] = k row['ntable'] = j row['nrow'] = i row.append() rowswritten += nrows table.flush() # Close the file fileh.close() return (rowswritten, rowsize) def readFile(filename, ngroups, recsize, verbose): # Open the HDF5 file in read-only mode rowsread = 0 for ngroup in range(ngroups): fileh = tb.open_file(filename, mode="r", root_uep='group%04d' % ngroup) # Get the group group = fileh.root ntable = 0 if verbose: print("Group ==>", group) for table in fileh.list_nodes(group, 'Table'): rowsize = table.rowsize buffersize = table.rowsize * table.nrowsinbuf if verbose > 1: print("Table ==>", table) print("Max rows in buf:", table.nrowsinbuf) print("Rows in", table._v_pathname, ":", table.nrows) print("Buffersize:", table.rowsize * table.nrowsinbuf) print("MaxTuples:", table.nrowsinbuf) nrow = 0 for row in table: try: assert row["ngroup"] == ngroup assert row["ntable"] == ntable assert row["nrow"] == nrow except: print("Error in group: %d, table: %d, row: %d" % (ngroup, ntable, nrow)) print("Record ==>", row) nrow += 1 assert nrow == table.nrows rowsread += table.nrows ntable += 1 # Close the file (eventually destroy the extended type) fileh.close() return (rowsread, rowsize, buffersize) def dump_garbage(): """show us waht the garbage is about.""" # Force collection print("\nGARBAGE:") gc.collect() print("\nGARBAGE OBJECTS:") for x in gc.garbage: s = str(x) #if len(s) > 80: s = s[:77] + "..." print(type(x), "\n ", s) if __name__ == "__main__": import getopt try: import psyco psyco_imported = 1 except: psyco_imported = 0 usage = """usage: %s [-d debug] [-v level] [-p] [-r] [-w] [-l complib] [-c complevel] [-g ngroups] [-t ntables] [-i nrows] file -d debugging level -v verbosity level -p use "psyco" if available -a use Array objects instead of Table -r only read test -w only write test -l sets the compression library to be used ("zlib", "lzo", "ucl", "bzip2") -c sets a compression level (do not set it or 0 for no compression) -g number of groups hanging from "/" -t number of tables per group -i number of rows per table """ try: opts, pargs = getopt.getopt(sys.argv[1:], 'd:v:parwl:c:g:t:i:') except: sys.stderr.write(usage) sys.exit(0) # if we pass too much parameters, abort if len(pargs) != 1: sys.stderr.write(usage) sys.exit(0) # default options ngroups = 5 ntables = 5 nrows = 100 verbose = 0 debug = 0 recsize = "medium" testread = 1 testwrite = 1 usepsyco = 0 usearray = 0 complevel = 0 complib = "zlib" # Get the options for option in opts: if option[0] == '-d': debug = int(option[1]) if option[0] == '-v': verbose = int(option[1]) if option[0] == '-p': usepsyco = 1 if option[0] == '-a': usearray = 1 elif option[0] == '-r': testwrite = 0 elif option[0] == '-w': testread = 0 elif option[0] == '-l': complib = option[1] elif option[0] == '-c': complevel = int(option[1]) elif option[0] == '-g': ngroups = int(option[1]) elif option[0] == '-t': ntables = int(option[1]) elif option[0] == '-i': nrows = int(option[1]) if debug: gc.enable() gc.set_debug(gc.DEBUG_LEAK) # Catch the hdf5 file passed as the last argument file = pargs[0] print("Compression level:", complevel) if complevel > 0: print("Compression library:", complib) if testwrite: t1 = clock() cpu1 = cpuclock() if psyco_imported and usepsyco: psyco.bind(createFile) if usearray: (rowsw, rowsz) = createFileArr(file, ngroups, ntables, nrows) else: (rowsw, rowsz) = createFile(file, ngroups, ntables, nrows, complevel, complib, recsize) t2 = clock() cpu2 = cpuclock() tapprows = t2 - t1 cpuapprows = cpu2 - cpu1 print(f"Rows written: {rowsw} Row size: {rowsz}") print( f"Time writing rows: {tapprows:.3f} s (real) " f"{cpuapprows:.3f} s (cpu) {cpuapprows / tapprows:.0%}") print(f"Write rows/sec: {rowsw / tapprows}") print(f"Write KB/s : {rowsw * rowsz / (tapprows * 1024):.0f}") if testread: t1 = clock() cpu1 = cpuclock() if psyco_imported and usepsyco: psyco.bind(readFile) if usearray: (rowsr, rowsz, bufsz) = readFileArr(file, ngroups, recsize, verbose) else: (rowsr, rowsz, bufsz) = readFile(file, ngroups, recsize, verbose) t2 = clock() cpu2 = cpuclock() treadrows = t2 - t1 cpureadrows = cpu2 - cpu1 print(f"Rows read: {rowsw} Row size: {rowsz}, Buf size: {bufsz}") print( f"Time reading rows: {treadrows:.3f} s (real) " f"{cpureadrows:.3f} s (cpu) {cpureadrows / treadrows:.0%}") print(f"Read rows/sec: {rowsr / treadrows}") print(f"Read KB/s : {rowsr * rowsz / (treadrows * 1024):.0f}") # Show the dirt if debug > 1: dump_garbage() PyTables-3.7.0/bench/table-bench.py000066400000000000000000000374101416254111300170440ustar00rootroot00000000000000#!/usr/bin/env python import numpy as np import tables as tb # This class is accessible only for the examples class Small(tb.IsDescription): var1 = tb.StringCol(itemsize=4, pos=2) var2 = tb.Int32Col(pos=1) var3 = tb.Float64Col(pos=0) # Define a user record to characterize some kind of particles class Medium(tb.IsDescription): name = tb.StringCol(itemsize=16, pos=0) # 16-character String float1 = tb.Float64Col(shape=2, dflt=np.arange(2), pos=1) #float1 = Float64Col(dflt=2.3) #float2 = Float64Col(dflt=2.3) # zADCcount = Int16Col() # signed short integer ADCcount = tb.Int32Col(pos=6) # signed short integer grid_i = tb.Int32Col(pos=7) # integer grid_j = tb.Int32Col(pos=8) # integer pressure = tb.Float32Col(pos=9) # float (single-precision) energy = tb.Float64Col(pos=2) # double (double-precision) # unalig = Int8Col() # just to unalign data # Define a user record to characterize some kind of particles class Big(tb.IsDescription): name = tb.StringCol(itemsize=16) # 16-character String float1 = tb.Float64Col(shape=32, dflt=np.arange(32)) float2 = tb.Float64Col(shape=32, dflt=2.2) TDCcount = tb.Int8Col() # signed short integer #ADCcount = Int32Col() # ADCcount = Int16Col() # signed short integer grid_i = tb.Int32Col() # integer grid_j = tb.Int32Col() # integer pressure = tb.Float32Col() # float (single-precision) energy = tb.Float64Col() # double (double-precision) def createFile(filename, totalrows, filters, recsize): # Open a file in "w"rite mode fileh = tb.open_file(filename, mode="w", title="Table Benchmark", filters=filters) # Table title title = "This is the table title" # Create a Table instance group = fileh.root rowswritten = 0 for j in range(3): # Create a table if recsize == "big": table = fileh.create_table(group, 'tuple' + str(j), Big, title, None, totalrows) elif recsize == "medium": table = fileh.create_table(group, 'tuple' + str(j), Medium, title, None, totalrows) elif recsize == "small": table = fileh.create_table(group, 'tuple' + str(j), Small, title, None, totalrows) else: raise RuntimeError("This should never happen") table.attrs.test = 2 rowsize = table.rowsize # Get the row object associated with the new table d = table.row # Fill the table if recsize == "big": for i in range(totalrows): # d['name'] = 'Part: %6d' % (i) d['TDCcount'] = i % 256 #d['float1'] = NP.array([i]*32, NP.float64) #d['float2'] = NP.array([i**2]*32, NP.float64) #d['float1'][0] = float(i) #d['float2'][0] = float(i*2) # Common part with medium d['grid_i'] = i d['grid_j'] = 10 - i d['pressure'] = float(i * i) # d['energy'] = float(d['pressure'] ** 4) d['energy'] = d['pressure'] # d['idnumber'] = i * (2 ** 34) d.append() elif recsize == "medium": for i in range(totalrows): #d['name'] = 'Part: %6d' % (i) #d['float1'] = NP.array([i]*2, NP.float64) #d['float1'] = arr #d['float1'] = i #d['float2'] = float(i) # Common part with big: d['grid_i'] = i d['grid_j'] = 10 - i d['pressure'] = i * 2 # d['energy'] = float(d['pressure'] ** 4) d['energy'] = d['pressure'] d.append() else: # Small record for i in range(totalrows): #d['var1'] = str(random.randrange(1000000)) #d['var3'] = random.randrange(10000000) d['var1'] = str(i) #d['var2'] = random.randrange(totalrows) d['var2'] = i #d['var3'] = 12.1e10 d['var3'] = totalrows - i d.append() # This is a 10% faster than table.append() rowswritten += totalrows if recsize == "small": # Testing with indexing pass # table._createIndex("var3", Filters(1,"zlib",shuffle=1)) # table.flush() group._v_attrs.test2 = "just a test" # Create a new group group2 = fileh.create_group(group, 'group' + str(j)) # Iterate over this new group (group2) group = group2 table.flush() # Close the file (eventually destroy the extended type) fileh.close() return (rowswritten, rowsize) def readFile(filename, recsize, verbose): # Open the HDF5 file in read-only mode fileh = tb.open_file(filename, mode="r") rowsread = 0 for groupobj in fileh.walk_groups(fileh.root): # print "Group pathname:", groupobj._v_pathname row = 0 for table in fileh.list_nodes(groupobj, 'Table'): rowsize = table.rowsize print("reading", table) if verbose: print("Max rows in buf:", table.nrowsinbuf) print("Rows in", table._v_pathname, ":", table.nrows) print("Buffersize:", table.rowsize * table.nrowsinbuf) print("MaxTuples:", table.nrowsinbuf) if recsize == "big" or recsize == "medium": # e = [ p.float1 for p in table.iterrows() # if p.grid_i < 2 ] #e = [ str(p) for p in table.iterrows() ] # if p.grid_i < 2 ] # e = [ p['grid_i'] for p in table.iterrows() # if p['grid_j'] == 20 and p['grid_i'] < 20 ] # e = [ p['grid_i'] for p in table # if p['grid_i'] <= 2 ] # e = [ p['grid_i'] for p in table.where("grid_i<=20")] # e = [ p['grid_i'] for p in # table.where('grid_i <= 20')] e = [p['grid_i'] for p in table.where('(grid_i <= 20) & (grid_j == 20)')] # e = [ p['grid_i'] for p in table.iterrows() # if p.nrow() == 20 ] # e = [ table.delrow(p.nrow()) for p in table.iterrows() # if p.nrow() == 20 ] # The version with a for loop is only 1% better than # comprenhension list #e = [] # for p in table.iterrows(): # if p.grid_i < 20: # e.append(p.grid_j) else: # small record case # e = [ p['var3'] for p in table.iterrows() # if p['var2'] < 20 and p['var3'] < 20 ] # e = [ p['var3'] for p in table.where("var3 <= 20") # if p['var2'] < 20 ] # e = [ p['var3'] for p in table.where("var3 <= 20")] # Cuts 1) and 2) issues the same results but 2) is about 10 times faster # Cut 1) # e = [ p.nrow() for p in # table.where(table.cols.var2 > 5) # if p["var2"] < 10] # Cut 2) # e = [ p.nrow() for p in # table.where(table.cols.var2 < 10) # if p["var2"] > 5] # e = [ (p._nrow,p["var3"]) for p in # e = [ p["var3"] for p in # table.where(table.cols.var3 < 10)] # table.where(table.cols.var3 < 10)] # table if p["var3"] <= 10] # e = [ p['var3'] for p in table.where("var3 <= 20")] # e = [ p['var3'] for p in # table.where(table.cols.var1 == "10")] # More # than ten times faster than the next one # e = [ p['var3'] for p in table # if p['var1'] == "10"] # e = [ p['var3'] for p in table.where('var2 <= 20')] e = [p['var3'] for p in table.where('(var2 <= 20) & (var2 >= 3)')] # e = [ p[0] for p in table.where('var2 <= 20')] #e = [ p['var3'] for p in table if p['var2'] <= 20 ] # e = [ p[:] for p in table if p[1] <= 20 ] # e = [ p['var3'] for p in table._whereInRange(table.cols.var2 <=20)] #e = [ p['var3'] for p in table.iterrows(0,21) ] # e = [ p['var3'] for p in table.iterrows() # if p.nrow() <= 20 ] #e = [ p['var3'] for p in table.iterrows(1,0,1000)] #e = [ p['var3'] for p in table.iterrows(1,100)] # e = [ p['var3'] for p in table.iterrows(step=2) # if p.nrow() < 20 ] # e = [ p['var2'] for p in table.iterrows() # if p['var2'] < 20 ] # for p in table.iterrows(): # pass if verbose: # print "Last record read:", p print("resulting selection list ==>", e) rowsread += table.nrows row += 1 if verbose: print("Total selected records ==> ", len(e)) # Close the file (eventually destroy the extended type) fileh.close() return (rowsread, rowsize) def readField(filename, field, rng, verbose): fileh = tb.open_file(filename, mode="r") rowsread = 0 if rng is None: rng = [0, -1, 1] if field == "all": field = None for groupobj in fileh.walk_groups(fileh.root): for table in fileh.list_nodes(groupobj, 'Table'): rowsize = table.rowsize # table.nrowsinbuf = 3 # For testing purposes if verbose: print("Max rows in buf:", table.nrowsinbuf) print("Rows in", table._v_pathname, ":", table.nrows) print("Buffersize:", table.rowsize * table.nrowsinbuf) print("MaxTuples:", table.nrowsinbuf) print("(field, start, stop, step) ==>", (field, rng[0], rng[1], rng[2])) e = table.read(rng[0], rng[1], rng[2], field) rowsread += table.nrows if verbose: print("Selected rows ==> ", e) print("Total selected rows ==> ", len(e)) # Close the file (eventually destroy the extended type) fileh.close() return (rowsread, rowsize) if __name__ == "__main__": import sys import getopt try: import psyco psyco_imported = 1 except: psyco_imported = 0 from time import perf_counter as clock from time import process_time as cpuclock usage = """usage: %s [-v] [-p] [-P] [-R range] [-r] [-w] [-s recsize] [-f field] [-c level] [-l complib] [-i iterations] [-S] [-F] file -v verbose -p use "psyco" if available -P do profile -R select a range in a field in the form "start,stop,step" -r only read test -w only write test -s use [big] record, [medium] or [small] -f only read stated field name in tables ("all" means all fields) -c sets a compression level (do not set it or 0 for no compression) -S activate shuffling filter -F activate fletcher32 filter -l sets the compression library to be used ("zlib", "lzo", "blosc", "bzip2") -i sets the number of rows in each table\n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'vpPSFR:rwf:s:c:l:i:') except: sys.stderr.write(usage) sys.exit(0) # if we pass too much parameters, abort if len(pargs) != 1: sys.stderr.write(usage) sys.exit(0) # default options verbose = 0 profile = 0 rng = None recsize = "medium" fieldName = None testread = 1 testwrite = 1 usepsyco = 0 complevel = 0 shuffle = 0 fletcher32 = 0 complib = "zlib" iterations = 100 # Get the options for option in opts: if option[0] == '-v': verbose = 1 if option[0] == '-p': usepsyco = 1 if option[0] == '-P': profile = 1 if option[0] == '-S': shuffle = 1 if option[0] == '-F': fletcher32 = 1 elif option[0] == '-R': rng = [int(i) for i in option[1].split(",")] elif option[0] == '-r': testwrite = 0 elif option[0] == '-w': testread = 0 elif option[0] == '-f': fieldName = option[1] elif option[0] == '-s': recsize = option[1] if recsize not in ["big", "medium", "small"]: sys.stderr.write(usage) sys.exit(0) elif option[0] == '-c': complevel = int(option[1]) elif option[0] == '-l': complib = option[1] elif option[0] == '-i': iterations = int(option[1]) # Build the Filters instance filters = tb.Filters(complevel=complevel, complib=complib, shuffle=shuffle, fletcher32=fletcher32) # Catch the hdf5 file passed as the last argument file = pargs[0] if verbose: print("numpy version:", np.__version__) if psyco_imported and usepsyco: print("Using psyco version:", psyco.version_info) if testwrite: print("Compression level:", complevel) if complevel > 0: print("Compression library:", complib) if shuffle: print("Suffling...") t1 = clock() cpu1 = cpuclock() if psyco_imported and usepsyco: psyco.bind(createFile) if profile: import profile as prof import pstats prof.run( '(rowsw, rowsz) = createFile(file, iterations, filters, ' 'recsize)', 'table-bench.prof') stats = pstats.Stats('table-bench.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats(20) else: (rowsw, rowsz) = createFile(file, iterations, filters, recsize) t2 = clock() cpu2 = cpuclock() tapprows = t2 - t1 cpuapprows = cpu2 - cpu1 print(f"Rows written: {rowsw} Row size: {rowsz}") print( f"Time writing rows: {tapprows:.3f} s (real) " f"{cpuapprows:.3f} s (cpu) {cpuapprows / tapprows:.0%}") print(f"Write rows/sec: {rowsw / tapprows}") print(f"Write KB/s : {rowsw * rowsz / (tapprows * 1024):.0f}") if testread: t1 = clock() cpu1 = cpuclock() if psyco_imported and usepsyco: psyco.bind(readFile) # psyco.bind(readField) pass if rng or fieldName: (rowsr, rowsz) = readField(file, fieldName, rng, verbose) pass else: for i in range(1): (rowsr, rowsz) = readFile(file, recsize, verbose) t2 = clock() cpu2 = cpuclock() treadrows = t2 - t1 cpureadrows = cpu2 - cpu1 print(f"Rows read: {rowsw} Row size: {rowsz}") print( f"Time reading rows: {treadrows:.3f} s (real) " f"{cpureadrows:.3f} s (cpu) {cpureadrows / treadrows:.0%}") print(f"Read rows/sec: {rowsr / treadrows}") print(f"Read KB/s : {rowsr * rowsz / (treadrows * 1024):.0f}") PyTables-3.7.0/bench/table-copy.py000066400000000000000000000066701416254111300167430ustar00rootroot00000000000000from time import perf_counter as clock import numpy as np import tables as tb N = 144_000 #N = 144 def timed(func, *args, **kwargs): start = clock() res = func(*args, **kwargs) print(f"{clock() - start:.3f}s elapsed.") return res def create_table(output_path): print("creating array...", end=' ') dt = np.dtype([('field%d' % i, int) for i in range(320)]) a = np.zeros(N, dtype=dt) print("done.") output_file = tb.open_file(output_path, mode="w") table = output_file.create_table("/", "test", dt) # , filters=blosc4) print("appending data...", end=' ') table.append(a) print("flushing...", end=' ') table.flush() print("done.") output_file.close() def copy1(input_path, output_path): print(f"copying data from {input_path} to {output_path}...") input_file = tb.open_file(input_path, mode="r") output_file = tb.open_file(output_path, mode="w") # copy nodes as a batch input_file.copy_node("/", output_file.root, recursive=True) output_file.close() input_file.close() def copy2(input_path, output_path): print(f"copying data from {input_path} to {output_path}...") input_file = tb.open_file(input_path, mode="r") input_file.copy_file(output_path, overwrite=True) input_file.close() def copy3(input_path, output_path): print(f"copying data from {input_path} to {output_path}...") input_file = tb.open_file(input_path, mode="r") output_file = tb.open_file(output_path, mode="w") table = input_file.root.test table.copy(output_file.root) output_file.close() input_file.close() def copy4(input_path, output_path, complib='zlib', complevel=0): print(f"copying data from {input_path} to {output_path}...") input_file = tb.open_file(input_path, mode="r") output_file = tb.open_file(output_path, mode="w") input_table = input_file.root.test print("reading data...", end=' ') data = input_file.root.test.read() print("done.") filter = tb.Filters(complevel=complevel, complib=complib) output_table = output_file.create_table("/", "test", input_table.dtype, filters=filter) print("appending data...", end=' ') output_table.append(data) print("flushing...", end=' ') output_table.flush() print("done.") input_file.close() output_file.close() def copy5(input_path, output_path, complib='zlib', complevel=0): print(f"copying data from {input_path} to {output_path}...") input_file = tb.open_file(input_path, mode="r") output_file = tb.open_file(output_path, mode="w") input_table = input_file.root.test filter = tb.Filters(complevel=complevel, complib=complib) output_table = output_file.create_table("/", "test", input_table.dtype, filters=filter) chunksize = 10_000 rowsleft = len(input_table) start = 0 for chunk in range((len(input_table) / chunksize) + 1): stop = start + min(chunksize, rowsleft) data = input_table.read(start, stop) output_table.append(data) output_table.flush() rowsleft -= chunksize start = stop input_file.close() output_file.close() if __name__ == '__main__': timed(create_table, 'tmp.h5') # timed(copy1, 'tmp.h5', 'test1.h5') timed(copy2, 'tmp.h5', 'test2.h5') # timed(copy3, 'tmp.h5', 'test3.h5') timed(copy4, 'tmp.h5', 'test4.h5') timed(copy5, 'tmp.h5', 'test5.h5') PyTables-3.7.0/bench/undo_redo.py000066400000000000000000000154141416254111300166560ustar00rootroot00000000000000"""Benchmark for undo/redo. Run this program without parameters for mode of use.""" from time import perf_counter as clock import numpy as np import tables as tb verbose = 0 class BasicBenchmark: def __init__(self, filename, testname, vecsize, nobjects, niter): self.file = filename self.test = testname self.vecsize = vecsize self.nobjects = nobjects self.niter = niter # Initialize the arrays self.a1 = np.arange(0, 1 * self.vecsize) self.a2 = np.arange(1 * self.vecsize, 2 * self.vecsize) self.a3 = np.arange(2 * self.vecsize, 3 * self.vecsize) def setUp(self): # Create an HDF5 file self.fileh = tb.open_file(self.file, mode="w") # open the do/undo self.fileh.enable_undo() def tearDown(self): self.fileh.disable_undo() self.fileh.close() # Remove the temporary file # os.remove(self.file) def createNode(self): """Checking a undo/redo create_array.""" for i in range(self.nobjects): # Create a new array self.fileh.create_array('/', 'array' + str(i), self.a1) # Put a mark self.fileh.mark() # Unwind all marks sequentially for i in range(self.niter): t1 = clock() for i in range(self.nobjects): self.fileh.undo() if verbose: print("u", end=' ') if verbose: print() undo = clock() - t1 # Rewind all marks sequentially t1 = clock() for i in range(self.nobjects): self.fileh.redo() if verbose: print("r", end=' ') if verbose: print() redo = clock() - t1 print("Time for Undo, Redo (createNode):", undo, "s, ", redo, "s") def copy_children(self): """Checking a undo/redo copy_children.""" # Create a group self.fileh.create_group('/', 'agroup') # Create several objects there for i in range(10): # Create a new array self.fileh.create_array('/agroup', 'array' + str(i), self.a1) # Excercise copy_children for i in range(self.nobjects): # Create another group for destination self.fileh.create_group('/', 'anothergroup' + str(i)) # Copy children from /agroup to /anothergroup+i self.fileh.copy_children('/agroup', '/anothergroup' + str(i)) # Put a mark self.fileh.mark() # Unwind all marks sequentially for i in range(self.niter): t1 = clock() for i in range(self.nobjects): self.fileh.undo() if verbose: print("u", end=' ') if verbose: print() undo = clock() - t1 # Rewind all marks sequentially t1 = clock() for i in range(self.nobjects): self.fileh.redo() if verbose: print("r", end=' ') if verbose: print() redo = clock() - t1 print(("Time for Undo, Redo (copy_children):", undo, "s, ", redo, "s")) def set_attr(self): """Checking a undo/redo for setting attributes.""" # Create a new array self.fileh.create_array('/', 'array', self.a1) for i in range(self.nobjects): # Set an attribute setattr(self.fileh.root.array.attrs, "attr" + str(i), str(self.a1)) # Put a mark self.fileh.mark() # Unwind all marks sequentially for i in range(self.niter): t1 = clock() for i in range(self.nobjects): self.fileh.undo() if verbose: print("u", end=' ') if verbose: print() undo = clock() - t1 # Rewind all marks sequentially t1 = clock() for i in range(self.nobjects): self.fileh.redo() if verbose: print("r", end=' ') if verbose: print() redo = clock() - t1 print("Time for Undo, Redo (set_attr):", undo, "s, ", redo, "s") def runall(self): if testname == "all": tests = [self.createNode, self.copy_children, self.set_attr] elif testname == "createNode": tests = [self.createNode] elif testname == "copy_children": tests = [self.copy_children] elif testname == "set_attr": tests = [self.set_attr] for meth in tests: self.setUp() meth() self.tearDown() if __name__ == '__main__': import sys import getopt usage = """usage: %s [-v] [-p] [-t test] [-s vecsize] [-n niter] datafile -v verbose (total dump of profiling) -p do profiling -t {createNode|copy_children|set_attr|all} run the specified test -s the size of vectors that are undone/redone -n number of objects in operations -i number of iterations for reading\n""" % sys.argv[0] try: opts, pargs = getopt.getopt(sys.argv[1:], 'vpt:s:n:i:') except: sys.stderr.write(usage) sys.exit(0) # if we pass too much parameters, abort if len(pargs) != 1: sys.stderr.write(usage) sys.exit(0) # default options verbose = 0 profile = 0 testname = "all" vecsize = 10 nobjects = 1 niter = 1 # Get the options for option in opts: if option[0] == '-v': verbose = 1 elif option[0] == '-p': profile = 1 elif option[0] == '-t': testname = option[1] if testname not in ['createNode', 'copy_children', 'set_attr', 'all']: sys.stderr.write(usage) sys.exit(0) elif option[0] == '-s': vecsize = int(option[1]) elif option[0] == '-n': nobjects = int(option[1]) elif option[0] == '-i': niter = int(option[1]) filename = pargs[0] bench = BasicBenchmark(filename, testname, vecsize, nobjects, niter) if profile: import hotshot import hotshot.stats prof = hotshot.Profile("do_undo.prof") prof.runcall(bench.runall) prof.close() stats = hotshot.stats.load("do_undo.prof") stats.strip_dirs() stats.sort_stats('time', 'calls') if verbose: stats.print_stats() else: stats.print_stats(20) else: bench.runall() # Local Variables: # mode: python # End: PyTables-3.7.0/bench/undo_redo.txt000066400000000000000000000104311416254111300170370ustar00rootroot00000000000000Benchmarks on PyTables Undo/Redo ================================ This is a small report for the performance of the Undo/Redo feature in PyTables. A small script (see undo_redo.py) has been made in order to check different scenarios for Undo/Redo, like creating single nodes, copying children from one group to another, and creating attributes. Undo/Redo is independent of object size --------------------------------------- Firstly, one thing to be noted is that the Undo/Redo feature is independent of the object size that is being treated. For example, the times for 10 objects (flag -n) each one with 10 elements (flag -s) is: $ time python2.4 undo_redo.py -n 10 -i 2 -s 10 data.nobackup/undo_redo.h5 Time for Undo, Redo (createNode): 0.213686943054 s, 0.0727670192719 s Time for Undo, Redo (createNode): 0.271666049957 s, 0.0740389823914 s Time for Undo, Redo (copy_children): 0.296227931976 s, 0.161941051483 s Time for Undo, Redo (copy_children): 0.363519906998 s, 0.162662982941 s Time for Undo, Redo (set_attr): 0.208750009537 s, 0.0732419490814 s Time for Undo, Redo (set_attr): 0.27628993988 s, 0.0736088752747 s real 0m5.557s user 0m4.354s sys 0m0.729s Note how all tests take more or less the same amount of time. This is because a move operation is used as a central tool to implement the Undo/Redo feature. Such a move operation has a constant cost, independently of the size of the objects. For example, using objects with 1000 elements, we can see that this does not affect the Undo/Redo speed: $ time python2.4 undo_redo.py -n 10 -i 2 -s 1000 data.nobackup/undo_redo.h5 Time for Undo, Redo (createNode): 0.213760137558 s, 0.0717759132385 s Time for Undo, Redo (createNode): 0.276151895523 s, 0.0724079608917 s Time for Undo, Redo (copy_children): 0.308417797089 s, 0.168260812759 s Time for Undo, Redo (copy_children): 0.382102966309 s, 0.168042898178 s Time for Undo, Redo (set_attr): 0.209735155106 s, 0.0740969181061 s Time for Undo, Redo (set_attr): 0.279798984528 s, 0.0770981311798 s real 0m5.835s user 0m4.585s sys 0m0.736s Undo/Redo times grow linearly with the number of objects implied ---------------------------------------------------------------- Secondly, the time for doing/undoing is obviously proportional (linearly) to the number of objects that are implied in that process (set by -n): $ time python2.4 undo_redo.py -n 100 -i 2 -s 10 data.nobackup/undo_redo.h5 Time for Undo, Redo (createNode): 2.27267885208 s, 0.779091119766 s Time for Undo, Redo (createNode): 2.31264209747 s, 0.766252040863 s Time for Undo, Redo (copy_children): 3.01871585846 s, 1.63346219063 s Time for Undo, Redo (copy_children): 3.07704997063 s, 1.62615203857 s Time for Undo, Redo (set_attr): 2.18017196655 s, 0.809293985367 s Time for Undo, Redo (set_attr): 2.23039293289 s, 0.809432029724 s real 0m48.395s user 0m40.385s sys 0m6.914s A note on actual performance and place for improvement ------------------------------------------------------ Finally, note how the Undo/Redo capability of PyTables is pretty fast. The next benchmark makes 1000 undo and 1000 redos for create_array: $ time python2.4 undo_redo.py -n 1000 -i 2 -t createNode -s 1000 data.nobackup/undo_redo.h5 Time for Undo, Redo (createNode): 22.7840828896 s, 7.9872610569 s Time for Undo, Redo (createNode): 22.2799329758 s, 7.95833396912 s real 1m32.307s user 1m16.598s sys 0m15.105s i.e. an undo takes 23 milliseconds while a redo takes 8 milliseconds approximately. The fact that undo operations take 3 times more than redo is probably due to how the action log is implemented. The action log has been implemented as a Table object, and PyTables has been optimized to read rows of tables in *forward* direction (the one needed for redo operations). However, when looking in *backward* direction (needed for undo operations), the internal cache of PyTables is counterproductive and makes look-ups quite slow (compared with forward access). Nevertheless, the code for Undo/Redo has been optimized quite a bit to smooth this kind of access as much as possible, but with a relative success. A more definitive optimization should involve getting much better performance for reading tables in backward direction. That would be a major task, and can be eventually addressed in the future. Francesc Alted 2005-03-10 PyTables-3.7.0/bench/widetree.py000066400000000000000000000100411416254111300164770ustar00rootroot00000000000000import hotshot import hotshot.stats import unittest import tempfile from pathlib import Path from time import perf_counter as clock import tables as tb verbose = 0 class WideTreeTestCase(unittest.TestCase): """Checks for maximum number of childs for a Group.""" def test00_Leafs(self): """Checking creation of large number of leafs (1024) per group. Variable 'maxchilds' controls this check. PyTables support up to 4096 childs per group, but this would take too much memory (up to 64 MB) for testing purposes (may be we can add a test for big platforms). A 1024 childs run takes up to 30 MB. A 512 childs test takes around 25 MB. """ maxchilds = 1000 if verbose: print('\n', '-=' * 30) print("Running %s.test00_wideTree..." % self.__class__.__name__) print("Maximum number of childs tested :", maxchilds) # Open a new empty HDF5 file #file = tempfile.mktemp(".h5") file = "test_widetree.h5" fileh = tb.open_file(file, mode="w") if verbose: print("Children writing progress: ", end=' ') for child in range(maxchilds): if verbose: print("%3d," % (child), end=' ') a = [1, 1] fileh.create_group(fileh.root, 'group' + str(child), "child: %d" % child) fileh.create_array("/group" + str(child), 'array' + str(child), a, "child: %d" % child) if verbose: print() # Close the file fileh.close() t1 = clock() # Open the previous HDF5 file in read-only mode fileh = tb.open_file(file, mode="r") print("\nTime spent opening a file with %d groups + %d arrays: " "%s s" % (maxchilds, maxchilds, clock() - t1)) if verbose: print("\nChildren reading progress: ", end=' ') # Close the file fileh.close() # Then, delete the file # os.remove(file) def test01_wideTree(self): """Checking creation of large number of groups (1024) per group. Variable 'maxchilds' controls this check. PyTables support up to 4096 childs per group, but this would take too much memory (up to 64 MB) for testing purposes (may be we can add a test for big platforms). A 1024 childs run takes up to 30 MB. A 512 childs test takes around 25 MB. """ maxchilds = 1000 if verbose: print('\n', '-=' * 30) print("Running %s.test00_wideTree..." % self.__class__.__name__) print("Maximum number of childs tested :", maxchilds) # Open a new empty HDF5 file file = tempfile.mktemp(".h5") #file = "test_widetree.h5" fileh = tb.open_file(file, mode="w") if verbose: print("Children writing progress: ", end=' ') for child in range(maxchilds): if verbose: print("%3d," % (child), end=' ') fileh.create_group(fileh.root, 'group' + str(child), "child: %d" % child) if verbose: print() # Close the file fileh.close() t1 = clock() # Open the previous HDF5 file in read-only mode fileh = tb.open_file(file, mode="r") print("\nTime spent opening a file with %d groups: %s s" % (maxchilds, clock() - t1)) # Close the file fileh.close() # Then, delete the file Path(file).unlink() #---------------------------------------------------------------------- def suite(): theSuite = unittest.TestSuite() theSuite.addTest(unittest.makeSuite(WideTreeTestCase)) return theSuite if __name__ == '__main__': prof = hotshot.Profile("widetree.prof") benchtime, stones = prof.runcall(unittest.main(defaultTest='suite')) prof.close() stats = hotshot.stats.load("widetree.prof") stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats(20) PyTables-3.7.0/bench/widetree2.py000066400000000000000000000067151416254111300165760ustar00rootroot00000000000000import unittest import tables as tb verbose = 0 class Test(tb.IsDescription): ngroup = tb.Int32Col(pos=1) ntable = tb.Int32Col(pos=2) nrow = tb.Int32Col(pos=3) #string = StringCol(itemsize=500, pos=4) class WideTreeTestCase(unittest.TestCase): def test00_Leafs(self): # Open a new empty HDF5 file filename = "test_widetree.h5" ngroups = 10 ntables = 300 nrows = 10 complevel = 0 complib = "lzo" print("Writing...") # Open a file in "w"rite mode fileh = tb.open_file(filename, mode="w", title="PyTables Stress Test") for k in range(ngroups): # Create the group group = fileh.create_group("/", 'group%04d' % k, "Group %d" % k) fileh.close() # Now, create the tables rowswritten = 0 for k in range(ngroups): print("Filling tables in group:", k) fileh = tb.open_file(filename, mode="a", root_uep='group%04d' % k) # Get the group group = fileh.root for j in range(ntables): # Create a table table = fileh.create_table(group, 'table%04d' % j, Test, 'Table%04d' % j, tb.Filters(complevel, complib), nrows) # Get the row object associated with the new table row = table.row # Fill the table for i in range(nrows): row['ngroup'] = k row['ntable'] = j row['nrow'] = i row.append() rowswritten += nrows table.flush() # Close the file fileh.close() # read the file print("Reading...") rowsread = 0 for ngroup in range(ngroups): fileh = tb.open_file(filename, mode="r", root_uep='group%04d' % ngroup) # Get the group group = fileh.root ntable = 0 if verbose: print("Group ==>", group) for table in fileh.list_nodes(group, 'Table'): if verbose > 1: print("Table ==>", table) print("Max rows in buf:", table.nrowsinbuf) print("Rows in", table._v_pathname, ":", table.nrows) print("Buffersize:", table.rowsize * table.nrowsinbuf) print("MaxTuples:", table.nrowsinbuf) nrow = 0 for row in table: try: assert row["ngroup"] == ngroup assert row["ntable"] == ntable assert row["nrow"] == nrow except: print("Error in group: %d, table: %d, row: %d" % (ngroup, ntable, nrow)) print("Record ==>", row) nrow += 1 assert nrow == table.nrows rowsread += table.nrows ntable += 1 # Close the file (eventually destroy the extended type) fileh.close() #---------------------------------------------------------------------- def suite(): theSuite = unittest.TestSuite() theSuite.addTest(unittest.makeSuite(WideTreeTestCase)) return theSuite if __name__ == '__main__': unittest.main(defaultTest='suite') PyTables-3.7.0/bench/woody-pentiumIV.txt000066400000000000000000000272251416254111300201510ustar00rootroot00000000000000This is for Debian woody! Below are some benchmarking figures obtained while reading and writing to a file with three tables, each table containing 10000 records. For reference, the same tests have been repeated using the shelve module that comes with Python. The tests were conducted on a platform with a 2 GHz AMD Athlon chip, an IDE disk at 4600 rpm, and 256 MB of RAM. Version 0.2 | medium size records | small size records | (47 Bytes) | (16 Bytes) +---------------------------+------------------------------ | rows/s filesize | rows/s filesize | write read | write read ------------+---------------------------+------------------------------ no compress| | record | 24400 39000 1184 KB | 32600 52600 506 KB tupla | 17100 81100 1184 KB | 66666 107142 506 KB ------------+---------------------------+------------------------------ compress | | record | 22200 37500 494 KB | 31900 51700 94 KB tupla | 16100 75000 494 KB | 63900 107142 94 KB ------------+---------------------------+------------------------------ Shelve | 25800 14400 2500 KB | 68200 17000 921 KB New version (15-Jan-2003) PyTables pre-0.3 Rec length | rows/s | KB/s | rows | filesz | memory | | write read | write read | | (MB) | (MB) | ------------+-----------------+-----------------+-------+--------+--------+ 16 B | 31000 166600 | 480 2600 | 3.e4 | 0.49| 6.5 | ------------+-----------------+-----------------+-------+--------+--------+ 56 B | 17300 136000 | 942 7460 | 3.e4 | 1.7 | 7.2 | ------------+-----------------+-----------------+-------+--------+--------+ 56 B* | 1560 136000 | 85 7560 | 3.e4 | 1.7 | 7.2 | ------------+-----------------+-----------------+-------+--------+--------+ 64 B* | 1540 130000 | 96 8152 | 3.e4 | 1.9 | 7.2 | ------------+-----------------+-----------------+-------+--------+--------+ 550 B* | 879 81100 | 472 43500 | 3.e4 | 19 | 7.2 | ------------+-----------------+-----------------+-------+--------+--------+ 550 B** | 12000 103000 | 6440 55400 | 3.e5 | 168 | 7.2 | ------------+-----------------+-----------------+-------+--------+--------+ 550 B** | 15500 81100 | 8350 43500 | 3.e4 | 19 | 7.2 | ------------+-----------------+-----------------+-------+--------+--------+ 550 B**c| 909 1100 | 490 1081 | 3.e4 | 0.76| 8.5 | ------------+-----------------+-----------------+-------+--------+--------+ 550 B***| 3600 81100 | 1950 43500 | 3.e4 | 19 | 7.2 | ------------+-----------------+-----------------+-------+--------+--------+ * These are figures obtained with a numarray as part of the record ** The numarray record fields are not set in each iteration *** Some numarray elements of a record field are changed on each iteration **c Like ** but with compression (level 1) New version (10-March-2003) PyTables pre-0.4 Rec | rows/s | KB/s | rows | filesz | memory |%CP|%CP length | write read | write read | | (MB) | (MB) |(w)|(r) --------+-----------------+-----------------+-------+--------+--------+---+---- 16 B |434000 469000 | 6800 7300 | 3.e4 | 0.49| 6.5 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 16 Bc |326000 435000 | 5100 6800 | 3.e4 | 0.12| 6.5 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 16 B |663000 728000 | 10400 11400 | 3.e5 | 4.7 | 7.0 | 99|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 16 B |679000 797000 | 10600 12500 | 3.e6 | 46.0 | 10.0 | 98| 98 --------+-----------------+-----------------+-------+--------+--------+---+---- 16 Bc |452000 663000 | 7100 10400 | 3.e6 | 9.3 | 10.0 | 98| 98 --------+-----------------+-----------------+-------+--------+--------+---+---- 16 B |576000 590000 | 9000 9200 | 3.e7 | 458.0 | 11.0 | 78| 76 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B | 3050 380000 | 163 20700 | 3.e4 | 1.7 | 7.2 | 98|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B* |194000 340000 | 10600 18600 | 3.e4 | 1.7 | 7.2 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B*c |142000 306000 | 7800 16600 | 3.e4 | 0.3 | 7.2 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B* |273600 589000 | 14800 32214 | 3.e5 | 16.0 | 9.0 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B*c |184000 425000 | 10070 23362 | 3.e5 | 2.7 | 9.7 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B* |203600 649000 | 11100 35500 | 3.e6 | 161.0 | 12.0 | 72| 99 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B* |184000 229000 | 10000 12500 | 1.e7 | 534.0 | 17.0 | 56| 40 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B*np|184000 229000 | 10000 12500 | 1.e7 | 534.0 | 17.0 | 56| 40 --------+-----------------+-----------------+-------+--------+--------+---+---- 550 B | 2230 143000 | 1195 76600 | 3.e4 | 19 | 9.4 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 550 B* | 76000 250000 | 40900 134000 | 3.e4 | 19 | 9.4 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 550 B*c | 13900 30000 | 7400 16100 | 3.e4 | 0.7 | 10.0 | 99| 99 --------+-----------------+-----------------+-------+--------+--------+---+---- 550 B* | 25400 325000 | 13600 174000 | 3.e5 | 167 | 11.0 | 71| 96 --------+-----------------+-----------------+-------+--------+--------+---+---- 550 B* | 18700 28000 | 10000 15100 | 6.e5 | 322 | 13.0 | 76| 9 --------+-----------------+-----------------+-------+--------+--------+---+---- 550 B*c | 7300 21000 | 3900 11300 | 6.e5 | 11 | 17.0 | 98| 99 --------+-----------------+-----------------+-------+--------+--------+---+---- * These are figures obtained with a numarray as part of the record ** The numarray record fields are not set in each iteration c With compression (level 1) np No psyco optimizations Shelve Rec length | rows/s | KB/s | rows | filesz | memory | | write read | write read | | (MB) | (MB) | ------------+-----------------+-----------------+-------+--------+--------+ 16 B | 68200 17000 | 1070 266 | 3.e4 | 0.94| 7.2 | ------------+-----------------+-----------------+-------+--------+--------+ 56 B | 25000 14400 | 1367 784 | 3.e4 | 2.5 | 10.6 | ------------+-----------------+-----------------+-------+--------+--------+ 56 B* | 2980 2710 | 162 148 | 3.e4 | 7.3 | 33 | ------------+-----------------+-----------------+-------+--------+--------+ 64 B* | 2900 2700 | 182 168 | 3.e4 | 7.5 | 33 | ------------+-----------------+-----------------+-------+--------+--------+ 550 B* | 1090 1310 | 590 710 | 3.e4 | 58 | 122 | ------------+-----------------+-----------------+-------+--------+--------+ 550 B** | 16000 14900 | 2400 1200 | 3.e4 | 2.4 | 10.6 | ------------+-----------------+-----------------+-------+--------+--------+ 550 B***| 28000 11900 | 2400 1100 | 3.e4 | 2.5 | 10.6 | ------------+-----------------+-----------------+-------+--------+--------+ * These are figures obtained with a numarray as part of the record ** The nuamrray records are not set on each iteration *** Some numarray elements of a record field are changed on each iteration Python cPickle & bsddb3 RECNO with variable length Rec | Krows/s | MB/s | Krows | filesz | memory |%CP|%CP length | write read | write read | | (MB) | (MB) |(w)|(r) --------+-----------------+-----------------+-------+--------+--------+---+---- 16 B | 23.0 4.3 | 0.65 0.12 | 30 | 2.3 | 6.0 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 16 B | 22.0 4.3 | 0.60 0.12 | 300 | 24 | 25.0 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B | 12.3 2.0 | 0.68 0.11 | 30 | 5.8 | 6.2 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B | 8.8 2.0 | 0.44 0.11 | 300 | 61 | 6.2 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- Python struct & bsddb3 RECNO with fixed length Rec | Krows/s | MB/s | Krows | filesz | memory |%CP|%CP length | write read | write read | | (MB) | (MB) |(w)|(r) --------+-----------------+-----------------+-------+--------+--------+---+---- 16 B | 61 71 | 1.6 1.9 | 30 | 1.0 | 5.0 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 16 B | 56 65 | 1.5 1.8 | 300 | 10 | 5.8 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 16 B | 51 61 | 1.4 1.6 | 3000 | 100 | 6.1 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B | 51 52 | 2.7 2.8 | 30 | 1.8 | 5.8 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B | 18 50 | 1.0 2.7 | 300 | 18 | 6.2 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B | 16 48 | 0.9 2.6 | 1000 | 61 | 6.5 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- PySqlLite Rec | rows/s | KB/s | rows | filesz | memory |%CP|%CP length | write read | write read | | (MB) | (MB) |(w)|(r) --------+-----------------+-----------------+-------+--------+--------+---+---- 16 B | 4290 1400000 | 200 48000 | 3.e4 | 1.4 | 5.0 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 16 B | 3660 1030000 | 182 51000 | 3.e5 | 15 | 5.0 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 16 B | 3580 230000 | 192 12380 | 6.e6 | 322 | 5.0 |100| 25 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B | 2990 882000 | 250 76000 | 3.e4 | 2.6 | 5.0 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B | 2900 857000 | 270 80000 | 3.e5 | 28 | 5.0 |100|100 --------+-----------------+-----------------+-------+--------+--------+---+---- 56 B | 2900 120000 | 302 13100 | 3.e6 | 314 | 5.0 |100| 11 --------+-----------------+-----------------+-------+--------+--------+---+---- PyTables-3.7.0/c-blosc/000077500000000000000000000000001416254111300145645ustar00rootroot00000000000000PyTables-3.7.0/ci/000077500000000000000000000000001416254111300136355ustar00rootroot00000000000000PyTables-3.7.0/ci/github/000077500000000000000000000000001416254111300151175ustar00rootroot00000000000000PyTables-3.7.0/ci/github/get_hdf5_if_needed.sh000077500000000000000000000023531416254111300211300ustar00rootroot00000000000000#!/bin/bash # vendored from https://github.com/h5py/h5py/blob/master/ci/get_hdf5_if_needed.sh set -e if [ -z ${HDF5_DIR+x} ]; then echo "Using OS HDF5" else echo "Using downloaded HDF5" if [ -z ${HDF5_MPI+x} ]; then echo "Building serial" EXTRA_MPI_FLAGS='' else echo "Building with MPI" EXTRA_MPI_FLAGS="--enable-parallel --enable-shared" fi if [[ "$OSTYPE" == "darwin"* ]]; then lib_name=libhdf5.dylib else lib_name=libhdf5.so fi if [ -f $HDF5_DIR/lib/$lib_name ]; then echo "using cached build" else pushd /tmp # Remove trailing .*, to get e.g. '1.12' ↓ curl -fsSLO "https://www.hdfgroup.org/ftp/HDF5/releases/hdf5-${HDF5_VERSION%.*}/hdf5-$HDF5_VERSION/src/hdf5-$HDF5_VERSION.tar.gz" tar -xzvf hdf5-$HDF5_VERSION.tar.gz pushd hdf5-$HDF5_VERSION chmod u+x autogen.sh if [[ "${HDF5_VERSION%.*}" = "1.12" ]]; then ./configure --prefix $HDF5_DIR $EXTRA_MPI_FLAGS --enable-build-mode=production else ./configure --prefix $HDF5_DIR $EXTRA_MPI_FLAGS fi make -j $(nproc) make install popd popd fi fi PyTables-3.7.0/contrib/000077500000000000000000000000001416254111300147025ustar00rootroot00000000000000PyTables-3.7.0/contrib/README000066400000000000000000000006531416254111300155660ustar00rootroot00000000000000In these directories you can find some scripts contributed by PyTables users. If you have any suggestion on them, please contact the original authors. nctoh5.py: Converts netcdf files to hdf5. You can find an improved nctoh5 utility included in utils/ directory Author: Jeff Whitaker make_hdf.py: Converts general python data structures into hdf5 Author: John Nielsen PyTables-3.7.0/contrib/make_hdf.py000066400000000000000000000243341416254111300170200ustar00rootroot00000000000000#!/usr/bin/env python import pickle from time import perf_counter as clock import tables as tb def is_scalar(item): try: iter(item) # could be a string try: item[:0] + '' # check for string return 'str' except: return 0 except: return 'notstr' def is_dict(item): try: item.items() return 1 except: return 0 def make_col(row_type, row_name, row_item, str_len): '''for strings it will always make at least 80 char or twice mac char size''' set_len = 80 if str_len: if 2 * str_len > set_len: set_len = 2 * str_len row_type[row_name] = tb.StringCol(set_len) else: type_matrix = { int: tb.Int32Col(), float: tb.Float32Col(), } row_type[row_name] = type_matrix[type(row_item)] def make_row(data): row_type = {} scalar_type = is_scalar(data) if scalar_type: if scalar_type == 'str': make_col(row_type, 'scalar', data, len(data)) else: make_col(row_type, 'scalar', data, 0) else: # it is a list-like the_type = is_scalar(data[0]) if the_type == 'str': # get max length the_max = 0 for i in data: if len(i) > the_max: the_max = len(i) make_col(row_type, 'col', data[0], the_max) elif the_type: make_col(row_type, 'col', data[0], 0) else: # list within the list, make many columns make_col(row_type, 'col_depth', 0, 0) count = 0 for col in data: the_type = is_scalar(col[0]) if the_type == 'str': # get max length the_max = 0 for i in data: if len(i) > the_max: the_max = len(i) make_col(row_type, 'col_' + str(count), col[0], the_max) elif the_type: make_col(row_type, 'col_' + str(count), col[0], 0) else: raise ValueError('too many nested levels of lists') count += 1 return row_type def add_table(fileh, group_obj, data, table_name): # figure out if it is a list of lists or a single list # get types of columns row_type = make_row(data) table1 = fileh.create_table(group_obj, table_name, row_type, 'H') row = table1.row if is_scalar(data): row['scalar'] = data row.append() else: if is_scalar(data[0]): for i in data: row['col'] = i row.append() else: count = 0 for col in data: row['col_depth'] = len(col) for the_row in col: if is_scalar(the_row): row['col_' + str(count)] = the_row row.append() else: raise ValueError('too many levels of lists') count += 1 table1.flush() def add_cache(fileh, cache): group_name = 'pytables_cache_v0'; table_name = 'cache0' root = fileh.root group_obj = fileh.create_group(root, group_name) cache_str = pickle.dumps(cache, 0).decode() cache_str = cache_str.replace('\n', chr(1)) cache_pieces = [] while cache_str: cache_part = cache_str[:8000]; cache_str = cache_str[8000:] if cache_part: cache_pieces.append(cache_part) row_type = {} row_type['col_0'] = tb.StringCol(8000) # table_cache = fileh.create_table(group_obj, table_name, row_type, 'H') for piece in cache_pieces: print(len(piece)) table_cache.row['col_0'] = piece table_cache.row.append() table_cache.flush() def save2(hdf_file, data): fileh = tb.open_file(hdf_file, mode='w', title='logon history') root = fileh.root; cache_root = cache = {} root_path = root._v_pathname; root = 0 stack = [(root_path, data, cache)] table_num = 0 count = 0 while stack: (group_obj_path, data, cache) = stack.pop() for grp_name in data: count += 1 cache[grp_name] = {} new_group_obj = fileh.create_group(group_obj_path, grp_name) new_path = new_group_obj._v_pathname # if dict, you have a bunch of groups if is_dict(data[grp_name]): # {'mother':[22,23,24]} stack.append((new_path, data[grp_name], cache[grp_name])) # you have a table else: # data[grp_name]=[110,130,140],[1,2,3] add_table(fileh, new_path, data[grp_name], f'tbl_{table_num}') table_num += 1 add_cache(fileh, cache_root) fileh.close() class Hdf_dict(dict): def __init__(self, hdf_file, hdf_dict=None, stack=None): if hdf_dict is None: hdf_dict = {} if stack is None: stack = [] self.hdf_file = hdf_file self.stack = stack if stack: self.hdf_dict = hdf_dict else: self.hdf_dict = self.get_cache() self.cur_dict = self.hdf_dict def get_cache(self): fileh = tb.open_file(self.hdf_file, root_uep='/pytables_cache_v0') table = fileh.root.cache0 total = [] print('reading') begin = clock() for i in table.iterrows(): total.append(i['col_0'].decode()) total = ''.join(total) total = total.replace(chr(1), '\n') print('loaded cache len=', len(total), clock() - begin) begin = clock() a = pickle.loads(total.encode()) print('cache', clock() - begin) return a def has_key(self, k): return k in self.cur_dict def keys(self): return self.cur_dict.keys() def get(self, key, default=None): try: return self.__getitem__(key) except: return default def items(self): return list(self.cur_dict.items()) def values(self): return list(self.cur_dict.values()) def __len__(self): return len(self.cur_dict) def __getitem__(self, k): if k in self.cur_dict: # now check if k has any data if self.cur_dict[k]: new_stack = self.stack[:] new_stack.append(k) return Hdf_dict(self.hdf_file, hdf_dict=self.cur_dict[k], stack=new_stack) else: new_stack = self.stack[:] new_stack.append(k) fileh = tb.open_file(self.hdf_file, root_uep='/'.join(new_stack)) for table in fileh.root: try: for item in table['scalar']: return item except: # otherwise they stored a list of data try: return [item for item in table['col']] except: cur_column = [] total_columns = [] col_num = 0 cur_row = 0 num_rows = 0 for row in table: if not num_rows: num_rows = row['col_depth'] if cur_row == num_rows: cur_row = num_rows = 0 col_num += 1 total_columns.append(cur_column) cur_column = [] cur_column.append(row['col_' + str(col_num)]) cur_row += 1 total_columns.append(cur_column) return total_columns else: raise KeyError(k) def iterkeys(self): yield from self.keys() def __iter__(self): return self.iterkeys() def itervalues(self): for k in self.iterkeys(): v = self.__getitem__(k) yield v def iteritems(self): # yield children for k in self.iterkeys(): v = self.__getitem__(k) yield (k, v) def __repr__(self): return '{Hdf dict}' def __str__(self): return self.__repr__() ##### def setdefault(self, key, default=None): try: return self.__getitem__(key) except: self.__setitem__(key) return default def update(self, d): for k, v in d.items(): self.__setitem__(k, v) def popitem(self): try: k, v = next(self.items()) del self[k] return k, v except StopIteration: raise KeyError("Hdf Dict is empty") def __setitem__(self, key, value): raise NotImplementedError def __delitem__(self, key): raise NotImplementedError def __hash__(self): raise TypeError("Hdf dict bjects are unhashable") if __name__ == '__main__': def write_small(file=''): data1 = { 'fred': ['a', 'b', 'c'], 'barney': [[9110, 9130, 9140], [91, 92, 93]], 'wilma': { 'mother': {'pebbles': [22, 23, 24], 'bambam': [67, 68, 69]}} } print('saving') save2(file, data1) print('saved') def read_small(file=''): a = Hdf_dict(file) print(a['wilma']) b = a['wilma'] for i in b: print(i) print(a.keys()) print('has fred', bool('fred' in a)) print('length a', len(a)) print('get', a.get('fred'), a.get('not here')) print('wilma keys', a['wilma'].keys()) print('barney', a['barney']) print('get items') print(a.items()) for i in a.items(): print('item', i) for i in a.values(): print(i) a = input('enter y to write out test file to test.hdf ') if a.strip() == 'y': print('writing') write_small('test.hdf') print('reading') read_small('test.hdf') PyTables-3.7.0/contrib/nctoh5.py000077500000000000000000000035151416254111300164630ustar00rootroot00000000000000#!/usr/bin/env python """ convert netCDF file to HDF5 using Scientific.IO.NetCDF and PyTables. Jeff Whitaker This requires Scientific from http://starship.python.net/~hinsen/ScientificPython """ import sys from Scientific.IO import NetCDF import tables as tb # open netCDF file ncfile = NetCDF.NetCDFFile(sys.argv[1], mode = "r") # open h5 file. h5file = tb.openFile(sys.argv[2], mode = "w") # loop over variables in netCDF file. for varname in ncfile.variables.keys(): var = ncfile.variables[varname] vardims = list(var.dimensions) vardimsizes = [ncfile.dimensions[vardim] for vardim in vardims] # use long_name for title. if hasattr(var, 'long_name'): title = var.long_name else: # or, just use some bogus title. title = varname + ' array' # if variable has unlimited dimension or has rank>1, # make it enlargeable (with zlib compression). if vardimsizes[0] == None or len(vardimsizes) > 1: vardimsizes[0] = 0 vardata = h5file.createEArray(h5file.root, varname, tb.Atom(shape=tuple(vardimsizes), dtype=var.typecode(),), title, filters=tb.Filters(complevel=6, complib='zlib')) # write data to enlargeable array on record at a time. # (so the whole array doesn't have to be kept in memory). for n in range(var.shape[0]): vardata.append(var[n:n+1]) # or else, create regular array write data to it all at once. else: vardata=h5file.createArray(h5file.root, varname, var[:], title) # set variable attributes. for key, val in var.__dict__.iteritems(): setattr(vardata.attrs, key, val) setattr(vardata.attrs, 'dimensions', tuple(vardims)) # set global (file) attributes. for key, val in ncfile.__dict__.iteritems(): setattr(h5file.root._v_attrs, key, val) # Close the file h5file.close() PyTables-3.7.0/cpuinfo.py000066400000000000000000002006561416254111300152700ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- # Copyright (c) 2014-2018, Matthew Brennan Jones # Py-cpuinfo gets CPU info with pure Python 2 & 3 # It uses the MIT License # It is hosted at: https://github.com/workhorsy/py-cpuinfo # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CPUINFO_VERSION = (4, 0, 0) import os, sys import glob import re import time import platform import multiprocessing import ctypes import pickle import base64 import subprocess try: import _winreg as winreg except ImportError as err: try: import winreg except ImportError as err: pass # Load hacks for Windows if platform.system().lower() == 'windows': # Monkey patch multiprocessing's Popen to fork properly on Windows Pyinstaller # https://github.com/pyinstaller/pyinstaller/wiki/Recipe-Multiprocessing try: import multiprocessing.popen_spawn_win32 as forking except ImportError as err: try: import multiprocessing.popen_fork as forking except ImportError as err: import multiprocessing.forking as forking class _Popen(forking.Popen): def __init__(self, *args, **kw): if hasattr(sys, 'frozen'): # We have to set original _MEIPASS2 value from sys._MEIPASS # to get --onefile mode working. os.putenv('_MEIPASS2', sys._MEIPASS) try: super().__init__(*args, **kw) finally: if hasattr(sys, 'frozen'): # On some platforms (e.g. AIX) 'os.unsetenv()' is not # available. In those cases we cannot delete the variable # but only set it to the empty string. The bootloader # can handle this case. if hasattr(os, 'unsetenv'): os.unsetenv('_MEIPASS2') else: os.putenv('_MEIPASS2', '') forking.Popen = _Popen class DataSource(object): bits = platform.architecture()[0] cpu_count = multiprocessing.cpu_count() is_windows = platform.system().lower() == 'windows' raw_arch_string = platform.machine() can_cpuid = True @staticmethod def has_proc_cpuinfo(): return os.path.exists('/proc/cpuinfo') @staticmethod def has_dmesg(): return len(program_paths('dmesg')) > 0 @staticmethod def has_var_run_dmesg_boot(): uname = platform.system().strip().strip('"').strip("'").strip().lower() return 'linux' in uname and os.path.exists('/var/run/dmesg.boot') @staticmethod def has_cpufreq_info(): return len(program_paths('cpufreq-info')) > 0 @staticmethod def has_sestatus(): return len(program_paths('sestatus')) > 0 @staticmethod def has_sysctl(): return len(program_paths('sysctl')) > 0 @staticmethod def has_isainfo(): return len(program_paths('isainfo')) > 0 @staticmethod def has_kstat(): return len(program_paths('kstat')) > 0 @staticmethod def has_sysinfo(): return len(program_paths('sysinfo')) > 0 @staticmethod def has_lscpu(): return len(program_paths('lscpu')) > 0 @staticmethod def has_ibm_pa_features(): return len(program_paths('lsprop')) > 0 @staticmethod def has_wmic(): returncode, output = run_and_get_stdout(['wmic', 'os', 'get', 'Version']) return returncode == 0 and len(output) > 0 @staticmethod def cat_proc_cpuinfo(): return run_and_get_stdout(['cat', '/proc/cpuinfo']) @staticmethod def cpufreq_info(): return run_and_get_stdout(['cpufreq-info']) @staticmethod def sestatus_allow_execheap(): return run_and_get_stdout(['sestatus', '-b'], ['grep', '-i', '"allow_execheap"'])[1].strip().lower().endswith('on') @staticmethod def sestatus_allow_execmem(): return run_and_get_stdout(['sestatus', '-b'], ['grep', '-i', '"allow_execmem"'])[1].strip().lower().endswith('on') @staticmethod def dmesg_a(): return run_and_get_stdout(['dmesg', '-a']) @staticmethod def cat_var_run_dmesg_boot(): return run_and_get_stdout(['cat', '/var/run/dmesg.boot']) @staticmethod def sysctl_machdep_cpu_hw_cpufrequency(): return run_and_get_stdout(['sysctl', 'machdep.cpu', 'hw.cpufrequency']) @staticmethod def isainfo_vb(): return run_and_get_stdout(['isainfo', '-vb']) @staticmethod def kstat_m_cpu_info(): return run_and_get_stdout(['kstat', '-m', 'cpu_info']) @staticmethod def sysinfo_cpu(): return run_and_get_stdout(['sysinfo', '-cpu']) @staticmethod def lscpu(): return run_and_get_stdout(['lscpu']) @staticmethod def ibm_pa_features(): ibm_features = glob.glob('/proc/device-tree/cpus/*/ibm,pa-features') if ibm_features: return run_and_get_stdout(['lsprop', ibm_features[0]]) @staticmethod def wmic_cpu(): return run_and_get_stdout(['wmic', 'cpu', 'get', 'Name,CurrentClockSpeed,L2CacheSize,L3CacheSize,Description,Caption,Manufacturer', '/format:list']) @staticmethod def winreg_processor_brand(): key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") processor_brand = winreg.QueryValueEx(key, "ProcessorNameString")[0] winreg.CloseKey(key) return processor_brand @staticmethod def winreg_vendor_id(): key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") vendor_id = winreg.QueryValueEx(key, "VendorIdentifier")[0] winreg.CloseKey(key) return vendor_id @staticmethod def winreg_raw_arch_string(): key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment") raw_arch_string = winreg.QueryValueEx(key, "PROCESSOR_ARCHITECTURE")[0] winreg.CloseKey(key) return raw_arch_string @staticmethod def winreg_hz_actual(): key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") hz_actual = winreg.QueryValueEx(key, "~Mhz")[0] winreg.CloseKey(key) hz_actual = to_hz_string(hz_actual) return hz_actual @staticmethod def winreg_feature_bits(): key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") feature_bits = winreg.QueryValueEx(key, "FeatureSet")[0] winreg.CloseKey(key) return feature_bits def obj_to_b64(thing): a = thing b = pickle.dumps(a) c = base64.b64encode(b) d = c.decode('utf8') return d def b64_to_obj(thing): try: a = base64.b64decode(thing) b = pickle.loads(a) return b except: return {} def run_and_get_stdout(command, pipe_command=None): if not pipe_command: p1 = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) output = p1.communicate()[0] output = output.decode(encoding='UTF-8') return p1.returncode, output else: p1 = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) p2 = subprocess.Popen(pipe_command, stdin=p1.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p1.stdout.close() output = p2.communicate()[0] output = output.decode(encoding='UTF-8') return p2.returncode, output def program_paths(program_name): paths = [] exts = filter(None, os.environ.get('PATHEXT', '').split(os.pathsep)) path = os.environ['PATH'] for p in os.environ['PATH'].split(os.pathsep): p = os.path.join(p, program_name) if os.access(p, os.X_OK): paths.append(p) for e in exts: pext = p + e if os.access(pext, os.X_OK): paths.append(pext) return paths def _get_field_actual(cant_be_number, raw_string, field_names): for line in raw_string.splitlines(): for field_name in field_names: field_name = field_name.lower() if ':' in line: left, right = line.split(':', 1) left = left.strip().lower() right = right.strip() if left == field_name and len(right) > 0: if cant_be_number: if not right.isdigit(): return right else: return right return None def _get_field(cant_be_number, raw_string, convert_to, default_value, *field_names): retval = _get_field_actual(cant_be_number, raw_string, field_names) # Convert the return value if retval and convert_to: try: retval = convert_to(retval) except: retval = default_value # Return the default if there is no return value if retval is None: retval = default_value return retval def _get_hz_string_from_brand(processor_brand): # Just return 0 if the processor brand does not have the Hz if not 'hz' in processor_brand.lower(): return (1, '0.0') hz_brand = processor_brand.lower() scale = 1 if hz_brand.endswith('mhz'): scale = 6 elif hz_brand.endswith('ghz'): scale = 9 if '@' in hz_brand: hz_brand = hz_brand.split('@')[1] else: hz_brand = hz_brand.rsplit(None, 1)[1] hz_brand = hz_brand.rstrip('mhz').rstrip('ghz').strip() hz_brand = to_hz_string(hz_brand) return (scale, hz_brand) def to_friendly_hz(ticks, scale): # Get the raw Hz as a string left, right = to_raw_hz(ticks, scale) ticks = '{0}.{1}'.format(left, right) # Get the location of the dot, and remove said dot dot_index = ticks.index('.') ticks = ticks.replace('.', '') # Get the Hz symbol and scale symbol = "Hz" scale = 0 if dot_index > 9: symbol = "GHz" scale = 9 elif dot_index > 6: symbol = "MHz" scale = 6 elif dot_index > 3: symbol = "KHz" scale = 3 # Get the Hz with the dot at the new scaled point ticks = '{0}.{1}'.format(ticks[:-scale-1], ticks[-scale-1:]) # Format the ticks to have 4 numbers after the decimal # and remove any superfluous zeroes. ticks = '{0:.4f} {1}'.format(float(ticks), symbol) ticks = ticks.rstrip('0') return ticks def to_raw_hz(ticks, scale): # Scale the numbers ticks = ticks.lstrip('0') old_index = ticks.index('.') ticks = ticks.replace('.', '') ticks = ticks.ljust(scale + old_index+1, '0') new_index = old_index + scale ticks = '{0}.{1}'.format(ticks[:new_index], ticks[new_index:]) left, right = ticks.split('.') left, right = int(left), int(right) return (left, right) def to_hz_string(ticks): # Convert to string ticks = '{0}'.format(ticks) # Add decimal if missing if '.' not in ticks: ticks = '{0}.0'.format(ticks) # Remove trailing zeros ticks = ticks.rstrip('0') # Add one trailing zero for empty right side if ticks.endswith('.'): ticks = '{0}0'.format(ticks) return ticks def to_friendly_bytes(input): if not input: return input input = "{0}".format(input) formats = { r"^[0-9]+B$" : 'B', r"^[0-9]+K$" : 'KB', r"^[0-9]+M$" : 'MB', r"^[0-9]+G$" : 'GB' } for pattern, friendly_size in formats.items(): if re.match(pattern, input): return "{0} {1}".format(input[ : -1].strip(), friendly_size) return input def _parse_cpu_string(cpu_string): # Get location of fields at end of string fields_index = cpu_string.find('(', cpu_string.find('@')) #print(fields_index) # Processor Brand processor_brand = cpu_string if fields_index != -1: processor_brand = cpu_string[0 : fields_index].strip() #print('processor_brand: ', processor_brand) fields = None if fields_index != -1: fields = cpu_string[fields_index : ] #print('fields: ', fields) # Hz scale, hz_brand = _get_hz_string_from_brand(processor_brand) # Various fields vendor_id, stepping, model, family = (None, None, None, None) if fields: try: fields = fields.rsplit('(', 1)[1].split(')')[0].split(',') fields = [f.strip().lower() for f in fields] fields = [f.split(':') for f in fields] fields = [{f[0].strip() : f[1].strip()} for f in fields] #print('fields: ', fields) for field in fields: name = list(field.keys())[0] value = list(field.values())[0] #print('name:{0}, value:{1}'.format(name, value)) if name == 'origin': vendor_id = value.strip('"') elif name == 'stepping': stepping = int(value.lstrip('0x'), 16) elif name == 'model': model = int(value.lstrip('0x'), 16) elif name in ['fam', 'family']: family = int(value.lstrip('0x'), 16) except: #raise pass return (processor_brand, hz_brand, scale, vendor_id, stepping, model, family) def _parse_dmesg_output(output): try: # Get all the dmesg lines that might contain a CPU string lines = output.split(' CPU0:')[1:] + \ output.split(' CPU1:')[1:] + \ output.split(' CPU:')[1:] + \ output.split('\nCPU0:')[1:] + \ output.split('\nCPU1:')[1:] + \ output.split('\nCPU:')[1:] lines = [l.split('\n')[0].strip() for l in lines] # Convert the lines to CPU strings cpu_strings = [_parse_cpu_string(l) for l in lines] # Find the CPU string that has the most fields best_string = None highest_count = 0 for cpu_string in cpu_strings: count = sum([n is not None for n in cpu_string]) if count > highest_count: highest_count = count best_string = cpu_string # If no CPU string was found, return {} if not best_string: return {} processor_brand, hz_actual, scale, vendor_id, stepping, model, family = best_string # Origin if ' Origin=' in output: fields = output[output.find(' Origin=') : ].split('\n')[0] fields = fields.strip().split() fields = [n.strip().split('=') for n in fields] fields = [{n[0].strip().lower() : n[1].strip()} for n in fields] #print('fields: ', fields) for field in fields: name = list(field.keys())[0] value = list(field.values())[0] #print('name:{0}, value:{1}'.format(name, value)) if name == 'origin': vendor_id = value.strip('"') elif name == 'stepping': stepping = int(value.lstrip('0x'), 16) elif name == 'model': model = int(value.lstrip('0x'), 16) elif name in ['fam', 'family']: family = int(value.lstrip('0x'), 16) #print('FIELDS: ', (vendor_id, stepping, model, family)) # Features flag_lines = [] for category in [' Features=', ' Features2=', ' AMD Features=', ' AMD Features2=']: if category in output: flag_lines.append(output.split(category)[1].split('\n')[0]) flags = [] for line in flag_lines: line = line.split('<')[1].split('>')[0].lower() for flag in line.split(','): flags.append(flag) flags.sort() # Convert from GHz/MHz string to Hz scale, hz_advertised = _get_hz_string_from_brand(processor_brand) info = { 'vendor_id' : vendor_id, 'brand' : processor_brand, 'stepping' : stepping, 'model' : model, 'family' : family, 'flags' : flags } if hz_advertised and hz_advertised != '0.0': info['hz_advertised'] = to_friendly_hz(hz_advertised, scale) info['hz_actual'] = to_friendly_hz(hz_actual, scale) if hz_advertised and hz_advertised != '0.0': info['hz_advertised_raw'] = to_raw_hz(hz_advertised, scale) info['hz_actual_raw'] = to_raw_hz(hz_actual, scale) return {k: v for k, v in info.items() if v} except: #raise pass return {} def parse_arch(raw_arch_string): arch, bits = None, None raw_arch_string = raw_arch_string.lower() # X86 if re.match('^i\d86$|^x86$|^x86_32$|^i86pc$|^ia32$|^ia-32$|^bepc$', raw_arch_string): arch = 'X86_32' bits = 32 elif re.match('^x64$|^x86_64$|^x86_64t$|^i686-64$|^amd64$|^ia64$|^ia-64$', raw_arch_string): arch = 'X86_64' bits = 64 # ARM elif re.match('^armv8-a|aarch64$', raw_arch_string): arch = 'ARM_8' bits = 64 elif re.match('^armv7$|^armv7[a-z]$|^armv7-[a-z]$|^armv6[a-z]$', raw_arch_string): arch = 'ARM_7' bits = 32 elif re.match('^armv8$|^armv8[a-z]$|^armv8-[a-z]$', raw_arch_string): arch = 'ARM_8' bits = 32 # PPC elif re.match('^ppc32$|^prep$|^pmac$|^powermac$', raw_arch_string): arch = 'PPC_32' bits = 32 elif re.match('^powerpc$|^ppc64$|^ppc64le$', raw_arch_string): arch = 'PPC_64' bits = 64 # SPARC elif re.match('^sparc32$|^sparc$', raw_arch_string): arch = 'SPARC_32' bits = 32 elif re.match('^sparc64$|^sun4u$|^sun4v$', raw_arch_string): arch = 'SPARC_64' bits = 64 return (arch, bits) def is_bit_set(reg, bit): mask = 1 << bit is_set = reg & mask > 0 return is_set class CPUID(object): def __init__(self): self.prochandle = None # Figure out if SE Linux is on and in enforcing mode self.is_selinux_enforcing = False # Just return if the SE Linux Status Tool is not installed if not DataSource.has_sestatus(): return # Figure out if we can execute heap and execute memory can_selinux_exec_heap = DataSource.sestatus_allow_execheap() can_selinux_exec_memory = DataSource.sestatus_allow_execmem() self.is_selinux_enforcing = (not can_selinux_exec_heap or not can_selinux_exec_memory) def _asm_func(self, restype=None, argtypes=(), byte_code=[]): byte_code = bytes.join(b'', byte_code) address = None if DataSource.is_windows: # Allocate a memory segment the size of the byte code, and make it executable size = len(byte_code) # Alloc at least 1 page to ensure we own all pages that we want to change protection on if size < 0x1000: size = 0x1000 MEM_COMMIT = ctypes.c_ulong(0x1000) PAGE_READWRITE = ctypes.c_ulong(0x4) pfnVirtualAlloc = ctypes.windll.kernel32.VirtualAlloc pfnVirtualAlloc.restype = ctypes.c_void_p address = pfnVirtualAlloc(None, ctypes.c_size_t(size), MEM_COMMIT, PAGE_READWRITE) if not address: raise Exception("Failed to VirtualAlloc") # Copy the byte code into the memory segment memmove = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t)(ctypes._memmove_addr) if memmove(address, byte_code, size) < 0: raise Exception("Failed to memmove") # Enable execute permissions PAGE_EXECUTE = ctypes.c_ulong(0x10) old_protect = ctypes.c_ulong(0) pfnVirtualProtect = ctypes.windll.kernel32.VirtualProtect res = pfnVirtualProtect(ctypes.c_void_p(address), ctypes.c_size_t(size), PAGE_EXECUTE, ctypes.byref(old_protect)) if not res: raise Exception("Failed VirtualProtect") # Flush Instruction Cache # First, get process Handle if not self.prochandle: pfnGetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess pfnGetCurrentProcess.restype = ctypes.c_void_p self.prochandle = ctypes.c_void_p(pfnGetCurrentProcess()) # Actually flush cache res = ctypes.windll.kernel32.FlushInstructionCache(self.prochandle, ctypes.c_void_p(address), ctypes.c_size_t(size)) if not res: raise Exception("Failed FlushInstructionCache") else: # Allocate a memory segment the size of the byte code size = len(byte_code) pfnvalloc = ctypes.pythonapi.valloc pfnvalloc.restype = ctypes.c_void_p address = pfnvalloc(ctypes.c_size_t(size)) if not address: raise Exception("Failed to valloc") # Mark the memory segment as writeable only if not self.is_selinux_enforcing: WRITE = 0x2 if ctypes.pythonapi.mprotect(ctypes.c_void_p(address), size, WRITE) < 0: raise Exception("Failed to mprotect") # Copy the byte code into the memory segment if ctypes.pythonapi.memmove(ctypes.c_void_p(address), byte_code, ctypes.c_size_t(size)) < 0: raise Exception("Failed to memmove") # Mark the memory segment as writeable and executable only if not self.is_selinux_enforcing: WRITE_EXECUTE = 0x2 | 0x4 if ctypes.pythonapi.mprotect(ctypes.c_void_p(address), size, WRITE_EXECUTE) < 0: raise Exception("Failed to mprotect") # Cast the memory segment into a function functype = ctypes.CFUNCTYPE(restype, *argtypes) fun = functype(address) return fun, address def _run_asm(self, *byte_code): # Convert the byte code into a function that returns an int restype = ctypes.c_uint32 argtypes = () func, address = self._asm_func(restype, argtypes, byte_code) # Call the byte code like a function retval = func() byte_code = bytes.join(b'', byte_code) size = ctypes.c_size_t(len(byte_code)) # Free the function memory segment if DataSource.is_windows: MEM_RELEASE = ctypes.c_ulong(0x8000) ctypes.windll.kernel32.VirtualFree(ctypes.c_void_p(address), ctypes.c_size_t(0), MEM_RELEASE) else: # Remove the executable tag on the memory READ_WRITE = 0x1 | 0x2 if ctypes.pythonapi.mprotect(ctypes.c_void_p(address), size, READ_WRITE) < 0: raise Exception("Failed to mprotect") ctypes.pythonapi.free(ctypes.c_void_p(address)) return retval # FIXME: We should not have to use different instructions to # set eax to 0 or 1, on 32bit and 64bit machines. def _zero_eax(self): return ( b"\x31\xC0" # xor eax,eax ) def _zero_ecx(self): return ( b"\x31\xC9" # xor ecx,ecx ) def _one_eax(self): return ( b"\xB8\x01\x00\x00\x00" # mov eax,0x1" ) # http://en.wikipedia.org/wiki/CPUID#EAX.3D0:_Get_vendor_ID def get_vendor_id(self): # EBX ebx = self._run_asm( self._zero_eax(), b"\x0F\xA2" # cpuid b"\x89\xD8" # mov ax,bx b"\xC3" # ret ) # ECX ecx = self._run_asm( self._zero_eax(), b"\x0f\xa2" # cpuid b"\x89\xC8" # mov ax,cx b"\xC3" # ret ) # EDX edx = self._run_asm( self._zero_eax(), b"\x0f\xa2" # cpuid b"\x89\xD0" # mov ax,dx b"\xC3" # ret ) # Each 4bits is a ascii letter in the name vendor_id = [] for reg in [ebx, edx, ecx]: for n in [0, 8, 16, 24]: vendor_id.append(chr((reg >> n) & 0xFF)) vendor_id = ''.join(vendor_id) return vendor_id # http://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits def get_info(self): # EAX eax = self._run_asm( self._one_eax(), b"\x0f\xa2" # cpuid b"\xC3" # ret ) # Get the CPU info stepping = (eax >> 0) & 0xF # 4 bits model = (eax >> 4) & 0xF # 4 bits family = (eax >> 8) & 0xF # 4 bits processor_type = (eax >> 12) & 0x3 # 2 bits extended_model = (eax >> 16) & 0xF # 4 bits extended_family = (eax >> 20) & 0xFF # 8 bits return { 'stepping' : stepping, 'model' : model, 'family' : family, 'processor_type' : processor_type, 'extended_model' : extended_model, 'extended_family' : extended_family } # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000000h:_Get_Highest_Extended_Function_Supported def get_max_extension_support(self): # Check for extension support max_extension_support = self._run_asm( b"\xB8\x00\x00\x00\x80" # mov ax,0x80000000 b"\x0f\xa2" # cpuid b"\xC3" # ret ) return max_extension_support # http://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits def get_flags(self, max_extension_support): # EDX edx = self._run_asm( self._one_eax(), b"\x0f\xa2" # cpuid b"\x89\xD0" # mov ax,dx b"\xC3" # ret ) # ECX ecx = self._run_asm( self._one_eax(), b"\x0f\xa2" # cpuid b"\x89\xC8" # mov ax,cx b"\xC3" # ret ) # Get the CPU flags flags = { 'fpu' : is_bit_set(edx, 0), 'vme' : is_bit_set(edx, 1), 'de' : is_bit_set(edx, 2), 'pse' : is_bit_set(edx, 3), 'tsc' : is_bit_set(edx, 4), 'msr' : is_bit_set(edx, 5), 'pae' : is_bit_set(edx, 6), 'mce' : is_bit_set(edx, 7), 'cx8' : is_bit_set(edx, 8), 'apic' : is_bit_set(edx, 9), #'reserved1' : is_bit_set(edx, 10), 'sep' : is_bit_set(edx, 11), 'mtrr' : is_bit_set(edx, 12), 'pge' : is_bit_set(edx, 13), 'mca' : is_bit_set(edx, 14), 'cmov' : is_bit_set(edx, 15), 'pat' : is_bit_set(edx, 16), 'pse36' : is_bit_set(edx, 17), 'pn' : is_bit_set(edx, 18), 'clflush' : is_bit_set(edx, 19), #'reserved2' : is_bit_set(edx, 20), 'dts' : is_bit_set(edx, 21), 'acpi' : is_bit_set(edx, 22), 'mmx' : is_bit_set(edx, 23), 'fxsr' : is_bit_set(edx, 24), 'sse' : is_bit_set(edx, 25), 'sse2' : is_bit_set(edx, 26), 'ss' : is_bit_set(edx, 27), 'ht' : is_bit_set(edx, 28), 'tm' : is_bit_set(edx, 29), 'ia64' : is_bit_set(edx, 30), 'pbe' : is_bit_set(edx, 31), 'pni' : is_bit_set(ecx, 0), 'pclmulqdq' : is_bit_set(ecx, 1), 'dtes64' : is_bit_set(ecx, 2), 'monitor' : is_bit_set(ecx, 3), 'ds_cpl' : is_bit_set(ecx, 4), 'vmx' : is_bit_set(ecx, 5), 'smx' : is_bit_set(ecx, 6), 'est' : is_bit_set(ecx, 7), 'tm2' : is_bit_set(ecx, 8), 'ssse3' : is_bit_set(ecx, 9), 'cid' : is_bit_set(ecx, 10), #'reserved3' : is_bit_set(ecx, 11), 'fma' : is_bit_set(ecx, 12), 'cx16' : is_bit_set(ecx, 13), 'xtpr' : is_bit_set(ecx, 14), 'pdcm' : is_bit_set(ecx, 15), #'reserved4' : is_bit_set(ecx, 16), 'pcid' : is_bit_set(ecx, 17), 'dca' : is_bit_set(ecx, 18), 'sse4_1' : is_bit_set(ecx, 19), 'sse4_2' : is_bit_set(ecx, 20), 'x2apic' : is_bit_set(ecx, 21), 'movbe' : is_bit_set(ecx, 22), 'popcnt' : is_bit_set(ecx, 23), 'tscdeadline' : is_bit_set(ecx, 24), 'aes' : is_bit_set(ecx, 25), 'xsave' : is_bit_set(ecx, 26), 'osxsave' : is_bit_set(ecx, 27), 'avx' : is_bit_set(ecx, 28), 'f16c' : is_bit_set(ecx, 29), 'rdrnd' : is_bit_set(ecx, 30), 'hypervisor' : is_bit_set(ecx, 31) } # Get a list of only the flags that are true flags = [k for k, v in flags.items() if v] # http://en.wikipedia.org/wiki/CPUID#EAX.3D7.2C_ECX.3D0:_Extended_Features if max_extension_support >= 7: # EBX ebx = self._run_asm( self._zero_ecx(), b"\xB8\x07\x00\x00\x00" # mov eax,7 b"\x0f\xa2" # cpuid b"\x89\xD8" # mov ax,bx b"\xC3" # ret ) # ECX ecx = self._run_asm( self._zero_ecx(), b"\xB8\x07\x00\x00\x00" # mov eax,7 b"\x0f\xa2" # cpuid b"\x89\xC8" # mov ax,cx b"\xC3" # ret ) # Get the extended CPU flags extended_flags = { #'fsgsbase' : is_bit_set(ebx, 0), #'IA32_TSC_ADJUST' : is_bit_set(ebx, 1), 'sgx' : is_bit_set(ebx, 2), 'bmi1' : is_bit_set(ebx, 3), 'hle' : is_bit_set(ebx, 4), 'avx2' : is_bit_set(ebx, 5), #'reserved' : is_bit_set(ebx, 6), 'smep' : is_bit_set(ebx, 7), 'bmi2' : is_bit_set(ebx, 8), 'erms' : is_bit_set(ebx, 9), 'invpcid' : is_bit_set(ebx, 10), 'rtm' : is_bit_set(ebx, 11), 'pqm' : is_bit_set(ebx, 12), #'FPU CS and FPU DS deprecated' : is_bit_set(ebx, 13), 'mpx' : is_bit_set(ebx, 14), 'pqe' : is_bit_set(ebx, 15), 'avx512f' : is_bit_set(ebx, 16), 'avx512dq' : is_bit_set(ebx, 17), 'rdseed' : is_bit_set(ebx, 18), 'adx' : is_bit_set(ebx, 19), 'smap' : is_bit_set(ebx, 20), 'avx512ifma' : is_bit_set(ebx, 21), 'pcommit' : is_bit_set(ebx, 22), 'clflushopt' : is_bit_set(ebx, 23), 'clwb' : is_bit_set(ebx, 24), 'intel_pt' : is_bit_set(ebx, 25), 'avx512pf' : is_bit_set(ebx, 26), 'avx512er' : is_bit_set(ebx, 27), 'avx512cd' : is_bit_set(ebx, 28), 'sha' : is_bit_set(ebx, 29), 'avx512bw' : is_bit_set(ebx, 30), 'avx512vl' : is_bit_set(ebx, 31), 'prefetchwt1' : is_bit_set(ecx, 0), 'avx512vbmi' : is_bit_set(ecx, 1), 'umip' : is_bit_set(ecx, 2), 'pku' : is_bit_set(ecx, 3), 'ospke' : is_bit_set(ecx, 4), #'reserved' : is_bit_set(ecx, 5), 'avx512vbmi2' : is_bit_set(ecx, 6), #'reserved' : is_bit_set(ecx, 7), 'gfni' : is_bit_set(ecx, 8), 'vaes' : is_bit_set(ecx, 9), 'vpclmulqdq' : is_bit_set(ecx, 10), 'avx512vnni' : is_bit_set(ecx, 11), 'avx512bitalg' : is_bit_set(ecx, 12), #'reserved' : is_bit_set(ecx, 13), 'avx512vpopcntdq' : is_bit_set(ecx, 14), #'reserved' : is_bit_set(ecx, 15), #'reserved' : is_bit_set(ecx, 16), #'mpx0' : is_bit_set(ecx, 17), #'mpx1' : is_bit_set(ecx, 18), #'mpx2' : is_bit_set(ecx, 19), #'mpx3' : is_bit_set(ecx, 20), #'mpx4' : is_bit_set(ecx, 21), 'rdpid' : is_bit_set(ecx, 22), #'reserved' : is_bit_set(ecx, 23), #'reserved' : is_bit_set(ecx, 24), #'reserved' : is_bit_set(ecx, 25), #'reserved' : is_bit_set(ecx, 26), #'reserved' : is_bit_set(ecx, 27), #'reserved' : is_bit_set(ecx, 28), #'reserved' : is_bit_set(ecx, 29), 'sgx_lc' : is_bit_set(ecx, 30), #'reserved' : is_bit_set(ecx, 31) } # Get a list of only the flags that are true extended_flags = [k for k, v in extended_flags.items() if v] flags += extended_flags # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000001h:_Extended_Processor_Info_and_Feature_Bits if max_extension_support >= 0x80000001: # EBX ebx = self._run_asm( b"\xB8\x01\x00\x00\x80" # mov ax,0x80000001 b"\x0f\xa2" # cpuid b"\x89\xD8" # mov ax,bx b"\xC3" # ret ) # ECX ecx = self._run_asm( b"\xB8\x01\x00\x00\x80" # mov ax,0x80000001 b"\x0f\xa2" # cpuid b"\x89\xC8" # mov ax,cx b"\xC3" # ret ) # Get the extended CPU flags extended_flags = { 'fpu' : is_bit_set(ebx, 0), 'vme' : is_bit_set(ebx, 1), 'de' : is_bit_set(ebx, 2), 'pse' : is_bit_set(ebx, 3), 'tsc' : is_bit_set(ebx, 4), 'msr' : is_bit_set(ebx, 5), 'pae' : is_bit_set(ebx, 6), 'mce' : is_bit_set(ebx, 7), 'cx8' : is_bit_set(ebx, 8), 'apic' : is_bit_set(ebx, 9), #'reserved' : is_bit_set(ebx, 10), 'syscall' : is_bit_set(ebx, 11), 'mtrr' : is_bit_set(ebx, 12), 'pge' : is_bit_set(ebx, 13), 'mca' : is_bit_set(ebx, 14), 'cmov' : is_bit_set(ebx, 15), 'pat' : is_bit_set(ebx, 16), 'pse36' : is_bit_set(ebx, 17), #'reserved' : is_bit_set(ebx, 18), 'mp' : is_bit_set(ebx, 19), 'nx' : is_bit_set(ebx, 20), #'reserved' : is_bit_set(ebx, 21), 'mmxext' : is_bit_set(ebx, 22), 'mmx' : is_bit_set(ebx, 23), 'fxsr' : is_bit_set(ebx, 24), 'fxsr_opt' : is_bit_set(ebx, 25), 'pdpe1gp' : is_bit_set(ebx, 26), 'rdtscp' : is_bit_set(ebx, 27), #'reserved' : is_bit_set(ebx, 28), 'lm' : is_bit_set(ebx, 29), '3dnowext' : is_bit_set(ebx, 30), '3dnow' : is_bit_set(ebx, 31), 'lahf_lm' : is_bit_set(ecx, 0), 'cmp_legacy' : is_bit_set(ecx, 1), 'svm' : is_bit_set(ecx, 2), 'extapic' : is_bit_set(ecx, 3), 'cr8_legacy' : is_bit_set(ecx, 4), 'abm' : is_bit_set(ecx, 5), 'sse4a' : is_bit_set(ecx, 6), 'misalignsse' : is_bit_set(ecx, 7), '3dnowprefetch' : is_bit_set(ecx, 8), 'osvw' : is_bit_set(ecx, 9), 'ibs' : is_bit_set(ecx, 10), 'xop' : is_bit_set(ecx, 11), 'skinit' : is_bit_set(ecx, 12), 'wdt' : is_bit_set(ecx, 13), #'reserved' : is_bit_set(ecx, 14), 'lwp' : is_bit_set(ecx, 15), 'fma4' : is_bit_set(ecx, 16), 'tce' : is_bit_set(ecx, 17), #'reserved' : is_bit_set(ecx, 18), 'nodeid_msr' : is_bit_set(ecx, 19), #'reserved' : is_bit_set(ecx, 20), 'tbm' : is_bit_set(ecx, 21), 'topoext' : is_bit_set(ecx, 22), 'perfctr_core' : is_bit_set(ecx, 23), 'perfctr_nb' : is_bit_set(ecx, 24), #'reserved' : is_bit_set(ecx, 25), 'dbx' : is_bit_set(ecx, 26), 'perftsc' : is_bit_set(ecx, 27), 'pci_l2i' : is_bit_set(ecx, 28), #'reserved' : is_bit_set(ecx, 29), #'reserved' : is_bit_set(ecx, 30), #'reserved' : is_bit_set(ecx, 31) } # Get a list of only the flags that are true extended_flags = [k for k, v in extended_flags.items() if v] flags += extended_flags flags.sort() return flags # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000002h.2C80000003h.2C80000004h:_Processor_Brand_String def get_processor_brand(self, max_extension_support): processor_brand = "" # Processor brand string if max_extension_support >= 0x80000004: instructions = [ b"\xB8\x02\x00\x00\x80", # mov ax,0x80000002 b"\xB8\x03\x00\x00\x80", # mov ax,0x80000003 b"\xB8\x04\x00\x00\x80" # mov ax,0x80000004 ] for instruction in instructions: # EAX eax = self._run_asm( instruction, # mov ax,0x8000000? b"\x0f\xa2" # cpuid b"\x89\xC0" # mov ax,ax b"\xC3" # ret ) # EBX ebx = self._run_asm( instruction, # mov ax,0x8000000? b"\x0f\xa2" # cpuid b"\x89\xD8" # mov ax,bx b"\xC3" # ret ) # ECX ecx = self._run_asm( instruction, # mov ax,0x8000000? b"\x0f\xa2" # cpuid b"\x89\xC8" # mov ax,cx b"\xC3" # ret ) # EDX edx = self._run_asm( instruction, # mov ax,0x8000000? b"\x0f\xa2" # cpuid b"\x89\xD0" # mov ax,dx b"\xC3" # ret ) # Combine each of the 4 bytes in each register into the string for reg in [eax, ebx, ecx, edx]: for n in [0, 8, 16, 24]: processor_brand += chr((reg >> n) & 0xFF) # Strip off any trailing NULL terminators and white space processor_brand = processor_brand.strip("\0").strip() return processor_brand # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000006h:_Extended_L2_Cache_Features def get_cache(self, max_extension_support): cache_info = {} # Just return if the cache feature is not supported if max_extension_support < 0x80000006: return cache_info # ECX ecx = self._run_asm( b"\xB8\x06\x00\x00\x80" # mov ax,0x80000006 b"\x0f\xa2" # cpuid b"\x89\xC8" # mov ax,cx b"\xC3" # ret ) cache_info = { 'size_kb' : ecx & 0xFF, 'line_size_b' : (ecx >> 12) & 0xF, 'associativity' : (ecx >> 16) & 0xFFFF } return cache_info def get_ticks(self): retval = None if DataSource.bits == '32bit': # Works on x86_32 restype = None argtypes = (ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint)) get_ticks_x86_32, address = self._asm_func(restype, argtypes, [ b"\x55", # push bp b"\x89\xE5", # mov bp,sp b"\x31\xC0", # xor ax,ax b"\x0F\xA2", # cpuid b"\x0F\x31", # rdtsc b"\x8B\x5D\x08", # mov bx,[di+0x8] b"\x8B\x4D\x0C", # mov cx,[di+0xc] b"\x89\x13", # mov [bp+di],dx b"\x89\x01", # mov [bx+di],ax b"\x5D", # pop bp b"\xC3" # ret ] ) high = ctypes.c_uint32(0) low = ctypes.c_uint32(0) get_ticks_x86_32(ctypes.byref(high), ctypes.byref(low)) retval = ((high.value << 32) & 0xFFFFFFFF00000000) | low.value elif DataSource.bits == '64bit': # Works on x86_64 restype = ctypes.c_uint64 argtypes = () get_ticks_x86_64, address = self._asm_func(restype, argtypes, [ b"\x48", # dec ax b"\x31\xC0", # xor ax,ax b"\x0F\xA2", # cpuid b"\x0F\x31", # rdtsc b"\x48", # dec ax b"\xC1\xE2\x20", # shl dx,byte 0x20 b"\x48", # dec ax b"\x09\xD0", # or ax,dx b"\xC3", # ret ] ) retval = get_ticks_x86_64() return retval def get_raw_hz(self): start = self.get_ticks() time.sleep(1) end = self.get_ticks() ticks = (end - start) return ticks def _actual_get_cpu_info_from_cpuid(queue): ''' Warning! This function has the potential to crash the Python runtime. Do not call it directly. Use the _get_cpu_info_from_cpuid function instead. It will safely call this function in another process. ''' # Pipe all output to nothing sys.stdout = open(os.devnull, 'w') sys.stderr = open(os.devnull, 'w') # Get the CPU arch and bits arch, bits = parse_arch(DataSource.raw_arch_string) # Return none if this is not an X86 CPU if not arch in ['X86_32', 'X86_64']: queue.put(obj_to_b64({})) return # Return none if SE Linux is in enforcing mode cpuid = CPUID() if cpuid.is_selinux_enforcing: queue.put(obj_to_b64({})) return # Get the cpu info from the CPUID register max_extension_support = cpuid.get_max_extension_support() cache_info = cpuid.get_cache(max_extension_support) info = cpuid.get_info() processor_brand = cpuid.get_processor_brand(max_extension_support) # Get the Hz and scale hz_actual = cpuid.get_raw_hz() hz_actual = to_hz_string(hz_actual) # Get the Hz and scale scale, hz_advertised = _get_hz_string_from_brand(processor_brand) info = { 'vendor_id' : cpuid.get_vendor_id(), 'hardware' : '', 'brand' : processor_brand, 'hz_advertised' : to_friendly_hz(hz_advertised, scale), 'hz_actual' : to_friendly_hz(hz_actual, 0), 'hz_advertised_raw' : to_raw_hz(hz_advertised, scale), 'hz_actual_raw' : to_raw_hz(hz_actual, 0), 'l2_cache_size' : to_friendly_bytes(cache_info['size_kb']), 'l2_cache_line_size' : cache_info['line_size_b'], 'l2_cache_associativity' : hex(cache_info['associativity']), 'stepping' : info['stepping'], 'model' : info['model'], 'family' : info['family'], 'processor_type' : info['processor_type'], 'extended_model' : info['extended_model'], 'extended_family' : info['extended_family'], 'flags' : cpuid.get_flags(max_extension_support) } info = {k: v for k, v in info.items() if v} queue.put(obj_to_b64(info)) def _get_cpu_info_from_cpuid(): ''' Returns the CPU info gathered by querying the X86 cpuid register in a new process. Returns {} on non X86 cpus. Returns {} if SELinux is in enforcing mode. ''' from multiprocessing import Process, Queue # Return {} if can't cpuid if not DataSource.can_cpuid: return {} # Get the CPU arch and bits arch, bits = parse_arch(DataSource.raw_arch_string) # Return {} if this is not an X86 CPU if not arch in ['X86_32', 'X86_64']: return {} try: # Start running the function in a subprocess queue = Queue() p = Process(target=_actual_get_cpu_info_from_cpuid, args=(queue,)) p.start() # Wait for the process to end, while it is still alive while p.is_alive(): p.join(0) # Return {} if it failed if p.exitcode != 0: return {} # Return the result, only if there is something to read if not queue.empty(): output = queue.get() return b64_to_obj(output) except: pass # Return {} if everything failed return {} def _get_cpu_info_from_proc_cpuinfo(): ''' Returns the CPU info gathered from /proc/cpuinfo. Returns {} if /proc/cpuinfo is not found. ''' try: # Just return {} if there is no cpuinfo if not DataSource.has_proc_cpuinfo(): return {} returncode, output = DataSource.cat_proc_cpuinfo() if returncode != 0: return {} # Various fields vendor_id = _get_field(False, output, None, '', 'vendor_id', 'vendor id', 'vendor') processor_brand = _get_field(True, output, None, None, 'model name','cpu', 'processor') cache_size = _get_field(False, output, None, '', 'cache size') stepping = _get_field(False, output, int, 0, 'stepping') model = _get_field(False, output, int, 0, 'model') family = _get_field(False, output, int, 0, 'cpu family') hardware = _get_field(False, output, None, '', 'Hardware') # Flags flags = _get_field(False, output, None, None, 'flags', 'Features') if flags: flags = flags.split() flags.sort() # Convert from MHz string to Hz hz_actual = _get_field(False, output, None, '', 'cpu MHz', 'cpu speed', 'clock') hz_actual = hz_actual.lower().rstrip('mhz').strip() hz_actual = to_hz_string(hz_actual) # Convert from GHz/MHz string to Hz scale, hz_advertised = (0, None) try: scale, hz_advertised = _get_hz_string_from_brand(processor_brand) except Exception: pass info = { 'hardware' : hardware, 'brand' : processor_brand, 'l3_cache_size' : to_friendly_bytes(cache_size), 'flags' : flags, 'vendor_id' : vendor_id, 'stepping' : stepping, 'model' : model, 'family' : family, } # Make the Hz the same for actual and advertised if missing any if not hz_advertised or hz_advertised == '0.0': hz_advertised = hz_actual scale = 6 elif not hz_actual or hz_actual == '0.0': hz_actual = hz_advertised # Add the Hz if there is one if to_raw_hz(hz_advertised, scale) > (0, 0): info['hz_advertised'] = to_friendly_hz(hz_advertised, scale) info['hz_advertised_raw'] = to_raw_hz(hz_advertised, scale) if to_raw_hz(hz_actual, scale) > (0, 0): info['hz_actual'] = to_friendly_hz(hz_actual, 6) info['hz_actual_raw'] = to_raw_hz(hz_actual, 6) info = {k: v for k, v in info.items() if v} return info except: #raise # NOTE: To have this throw on error, uncomment this line return {} def _get_cpu_info_from_cpufreq_info(): ''' Returns the CPU info gathered from cpufreq-info. Returns {} if cpufreq-info is not found. ''' try: scale, hz_brand = 1, '0.0' if not DataSource.has_cpufreq_info(): return {} returncode, output = DataSource.cpufreq_info() if returncode != 0: return {} hz_brand = output.split('current CPU frequency is')[1].split('\n')[0] i = hz_brand.find('Hz') assert(i != -1) hz_brand = hz_brand[0 : i+2].strip().lower() if hz_brand.endswith('mhz'): scale = 6 elif hz_brand.endswith('ghz'): scale = 9 hz_brand = hz_brand.rstrip('mhz').rstrip('ghz').strip() hz_brand = to_hz_string(hz_brand) info = { 'hz_advertised' : to_friendly_hz(hz_brand, scale), 'hz_actual' : to_friendly_hz(hz_brand, scale), 'hz_advertised_raw' : to_raw_hz(hz_brand, scale), 'hz_actual_raw' : to_raw_hz(hz_brand, scale), } info = {k: v for k, v in info.items() if v} return info except: #raise # NOTE: To have this throw on error, uncomment this line return {} def _get_cpu_info_from_lscpu(): ''' Returns the CPU info gathered from lscpu. Returns {} if lscpu is not found. ''' try: if not DataSource.has_lscpu(): return {} returncode, output = DataSource.lscpu() if returncode != 0: return {} info = {} new_hz = _get_field(False, output, None, None, 'CPU max MHz', 'CPU MHz') if new_hz: new_hz = to_hz_string(new_hz) scale = 6 info['hz_advertised'] = to_friendly_hz(new_hz, scale) info['hz_actual'] = to_friendly_hz(new_hz, scale) info['hz_advertised_raw'] = to_raw_hz(new_hz, scale) info['hz_actual_raw'] = to_raw_hz(new_hz, scale) vendor_id = _get_field(False, output, None, None, 'Vendor ID') if vendor_id: info['vendor_id'] = vendor_id brand = _get_field(False, output, None, None, 'Model name') if brand: info['brand'] = brand family = _get_field(False, output, None, None, 'CPU family') if family and family.isdigit(): info['family'] = int(family) stepping = _get_field(False, output, None, None, 'Stepping') if stepping and stepping.isdigit(): info['stepping'] = int(stepping) model = _get_field(False, output, None, None, 'Model') if model and model.isdigit(): info['model'] = int(model) l1_data_cache_size = _get_field(False, output, None, None, 'L1d cache') if l1_data_cache_size: info['l1_data_cache_size'] = to_friendly_bytes(l1_data_cache_size) l1_instruction_cache_size = _get_field(False, output, None, None, 'L1i cache') if l1_instruction_cache_size: info['l1_instruction_cache_size'] = to_friendly_bytes(l1_instruction_cache_size) l2_cache_size = _get_field(False, output, None, None, 'L2 cache') if l2_cache_size: info['l2_cache_size'] = to_friendly_bytes(l2_cache_size) l3_cache_size = _get_field(False, output, None, None, 'L3 cache') if l3_cache_size: info['l3_cache_size'] = to_friendly_bytes(l3_cache_size) # Flags flags = _get_field(False, output, None, None, 'flags', 'Features') if flags: flags = flags.split() flags.sort() info['flags'] = flags info = {k: v for k, v in info.items() if v} return info except: #raise # NOTE: To have this throw on error, uncomment this line return {} def _get_cpu_info_from_dmesg(): ''' Returns the CPU info gathered from dmesg. Returns {} if dmesg is not found or does not have the desired info. ''' # Just return {} if there is no dmesg if not DataSource.has_dmesg(): return {} # If dmesg fails return {} returncode, output = DataSource.dmesg_a() if output == None or returncode != 0: return {} return _parse_dmesg_output(output) # https://openpowerfoundation.org/wp-content/uploads/2016/05/LoPAPR_DRAFT_v11_24March2016_cmt1.pdf # page 767 def _get_cpu_info_from_ibm_pa_features(): ''' Returns the CPU info gathered from lsprop /proc/device-tree/cpus/*/ibm,pa-features Returns {} if lsprop is not found or ibm,pa-features does not have the desired info. ''' try: # Just return {} if there is no lsprop if not DataSource.has_ibm_pa_features(): return {} # If ibm,pa-features fails return {} returncode, output = DataSource.ibm_pa_features() if output == None or returncode != 0: return {} # Filter out invalid characters from output value = output.split("ibm,pa-features")[1].lower() value = [s for s in value if s in list('0123456789abcfed')] value = ''.join(value) # Get data converted to Uint32 chunks left = int(value[0 : 8], 16) right = int(value[8 : 16], 16) # Get the CPU flags flags = { # Byte 0 'mmu' : is_bit_set(left, 0), 'fpu' : is_bit_set(left, 1), 'slb' : is_bit_set(left, 2), 'run' : is_bit_set(left, 3), #'reserved' : is_bit_set(left, 4), 'dabr' : is_bit_set(left, 5), 'ne' : is_bit_set(left, 6), 'wtr' : is_bit_set(left, 7), # Byte 1 'mcr' : is_bit_set(left, 8), 'dsisr' : is_bit_set(left, 9), 'lp' : is_bit_set(left, 10), 'ri' : is_bit_set(left, 11), 'dabrx' : is_bit_set(left, 12), 'sprg3' : is_bit_set(left, 13), 'rislb' : is_bit_set(left, 14), 'pp' : is_bit_set(left, 15), # Byte 2 'vpm' : is_bit_set(left, 16), 'dss_2.05' : is_bit_set(left, 17), #'reserved' : is_bit_set(left, 18), 'dar' : is_bit_set(left, 19), #'reserved' : is_bit_set(left, 20), 'ppr' : is_bit_set(left, 21), 'dss_2.02' : is_bit_set(left, 22), 'dss_2.06' : is_bit_set(left, 23), # Byte 3 'lsd_in_dscr' : is_bit_set(left, 24), 'ugr_in_dscr' : is_bit_set(left, 25), #'reserved' : is_bit_set(left, 26), #'reserved' : is_bit_set(left, 27), #'reserved' : is_bit_set(left, 28), #'reserved' : is_bit_set(left, 29), #'reserved' : is_bit_set(left, 30), #'reserved' : is_bit_set(left, 31), # Byte 4 'sso_2.06' : is_bit_set(right, 0), #'reserved' : is_bit_set(right, 1), #'reserved' : is_bit_set(right, 2), #'reserved' : is_bit_set(right, 3), #'reserved' : is_bit_set(right, 4), #'reserved' : is_bit_set(right, 5), #'reserved' : is_bit_set(right, 6), #'reserved' : is_bit_set(right, 7), # Byte 5 'le' : is_bit_set(right, 8), 'cfar' : is_bit_set(right, 9), 'eb' : is_bit_set(right, 10), 'lsq_2.07' : is_bit_set(right, 11), #'reserved' : is_bit_set(right, 12), #'reserved' : is_bit_set(right, 13), #'reserved' : is_bit_set(right, 14), #'reserved' : is_bit_set(right, 15), # Byte 6 'dss_2.07' : is_bit_set(right, 16), #'reserved' : is_bit_set(right, 17), #'reserved' : is_bit_set(right, 18), #'reserved' : is_bit_set(right, 19), #'reserved' : is_bit_set(right, 20), #'reserved' : is_bit_set(right, 21), #'reserved' : is_bit_set(right, 22), #'reserved' : is_bit_set(right, 23), # Byte 7 #'reserved' : is_bit_set(right, 24), #'reserved' : is_bit_set(right, 25), #'reserved' : is_bit_set(right, 26), #'reserved' : is_bit_set(right, 27), #'reserved' : is_bit_set(right, 28), #'reserved' : is_bit_set(right, 29), #'reserved' : is_bit_set(right, 30), #'reserved' : is_bit_set(right, 31), } # Get a list of only the flags that are true flags = [k for k, v in flags.items() if v] flags.sort() info = { 'flags' : flags } info = {k: v for k, v in info.items() if v} return info except: return {} def _get_cpu_info_from_cat_var_run_dmesg_boot(): ''' Returns the CPU info gathered from /var/run/dmesg.boot. Returns {} if dmesg is not found or does not have the desired info. ''' # Just return {} if there is no /var/run/dmesg.boot if not DataSource.has_var_run_dmesg_boot(): return {} # If dmesg.boot fails return {} returncode, output = DataSource.cat_var_run_dmesg_boot() if output == None or returncode != 0: return {} return _parse_dmesg_output(output) def _get_cpu_info_from_sysctl(): ''' Returns the CPU info gathered from sysctl. Returns {} if sysctl is not found. ''' try: # Just return {} if there is no sysctl if not DataSource.has_sysctl(): return {} # If sysctl fails return {} returncode, output = DataSource.sysctl_machdep_cpu_hw_cpufrequency() if output == None or returncode != 0: return {} # Various fields vendor_id = _get_field(False, output, None, None, 'machdep.cpu.vendor') processor_brand = _get_field(True, output, None, None, 'machdep.cpu.brand_string') cache_size = _get_field(False, output, None, None, 'machdep.cpu.cache.size') stepping = _get_field(False, output, int, 0, 'machdep.cpu.stepping') model = _get_field(False, output, int, 0, 'machdep.cpu.model') family = _get_field(False, output, int, 0, 'machdep.cpu.family') # Flags flags = _get_field(False, output, None, '', 'machdep.cpu.features').lower().split() flags.extend(_get_field(False, output, None, '', 'machdep.cpu.leaf7_features').lower().split()) flags.extend(_get_field(False, output, None, '', 'machdep.cpu.extfeatures').lower().split()) flags.sort() # Convert from GHz/MHz string to Hz scale, hz_advertised = _get_hz_string_from_brand(processor_brand) hz_actual = _get_field(False, output, None, None, 'hw.cpufrequency') hz_actual = to_hz_string(hz_actual) info = { 'vendor_id' : vendor_id, 'brand' : processor_brand, 'hz_advertised' : to_friendly_hz(hz_advertised, scale), 'hz_actual' : to_friendly_hz(hz_actual, 0), 'hz_advertised_raw' : to_raw_hz(hz_advertised, scale), 'hz_actual_raw' : to_raw_hz(hz_actual, 0), 'l2_cache_size' : to_friendly_bytes(cache_size), 'stepping' : stepping, 'model' : model, 'family' : family, 'flags' : flags } info = {k: v for k, v in info.items() if v} return info except: return {} def _get_cpu_info_from_sysinfo(): ''' Returns the CPU info gathered from sysinfo. Returns {} if sysinfo is not found. ''' info = _get_cpu_info_from_sysinfo_v1() info.update(_get_cpu_info_from_sysinfo_v2()) return info def _get_cpu_info_from_sysinfo_v1(): ''' Returns the CPU info gathered from sysinfo. Returns {} if sysinfo is not found. ''' try: # Just return {} if there is no sysinfo if not DataSource.has_sysinfo(): return {} # If sysinfo fails return {} returncode, output = DataSource.sysinfo_cpu() if output == None or returncode != 0: return {} # Various fields vendor_id = '' #_get_field(False, output, None, None, 'CPU #0: ') processor_brand = output.split('CPU #0: "')[1].split('"\n')[0] cache_size = '' #_get_field(False, output, None, None, 'machdep.cpu.cache.size') stepping = int(output.split(', stepping ')[1].split(',')[0].strip()) model = int(output.split(', model ')[1].split(',')[0].strip()) family = int(output.split(', family ')[1].split(',')[0].strip()) # Flags flags = [] for line in output.split('\n'): if line.startswith('\t\t'): for flag in line.strip().lower().split(): flags.append(flag) flags.sort() # Convert from GHz/MHz string to Hz scale, hz_advertised = _get_hz_string_from_brand(processor_brand) hz_actual = hz_advertised info = { 'vendor_id' : vendor_id, 'brand' : processor_brand, 'hz_advertised' : to_friendly_hz(hz_advertised, scale), 'hz_actual' : to_friendly_hz(hz_actual, scale), 'hz_advertised_raw' : to_raw_hz(hz_advertised, scale), 'hz_actual_raw' : to_raw_hz(hz_actual, scale), 'l2_cache_size' : to_friendly_bytes(cache_size), 'stepping' : stepping, 'model' : model, 'family' : family, 'flags' : flags } info = {k: v for k, v in info.items() if v} return info except: return {} def _get_cpu_info_from_sysinfo_v2(): ''' Returns the CPU info gathered from sysinfo. Returns {} if sysinfo is not found. ''' try: # Just return {} if there is no sysinfo if not DataSource.has_sysinfo(): return {} # If sysinfo fails return {} returncode, output = DataSource.sysinfo_cpu() if output == None or returncode != 0: return {} # Various fields vendor_id = '' #_get_field(False, output, None, None, 'CPU #0: ') processor_brand = output.split('CPU #0: "')[1].split('"\n')[0] cache_size = '' #_get_field(False, output, None, None, 'machdep.cpu.cache.size') signature = output.split('Signature:')[1].split('\n')[0].strip() # stepping = int(signature.split('stepping ')[1].split(',')[0].strip()) model = int(signature.split('model ')[1].split(',')[0].strip()) family = int(signature.split('family ')[1].split(',')[0].strip()) # Flags def get_subsection_flags(output): retval = [] for line in output.split('\n')[1:]: if not line.startswith(' '): break for entry in line.strip().lower().split(' '): retval.append(entry) return retval flags = get_subsection_flags(output.split('Features: ')[1]) + \ get_subsection_flags(output.split('Extended Features (0x00000001): ')[1]) + \ get_subsection_flags(output.split('Extended Features (0x80000001): ')[1]) flags.sort() # Convert from GHz/MHz string to Hz scale, hz_advertised = _get_hz_string_from_brand(processor_brand) hz_actual = hz_advertised info = { 'vendor_id' : vendor_id, 'brand' : processor_brand, 'hz_advertised' : to_friendly_hz(hz_advertised, scale), 'hz_actual' : to_friendly_hz(hz_actual, scale), 'hz_advertised_raw' : to_raw_hz(hz_advertised, scale), 'hz_actual_raw' : to_raw_hz(hz_actual, scale), 'l2_cache_size' : to_friendly_bytes(cache_size), 'stepping' : stepping, 'model' : model, 'family' : family, 'flags' : flags } info = {k: v for k, v in info.items() if v} return info except: return {} def _get_cpu_info_from_wmic(): ''' Returns the CPU info gathered from WMI. Returns {} if not on Windows, or wmic is not installed. ''' try: # Just return {} if not Windows or there is no wmic if not DataSource.is_windows or not DataSource.has_wmic(): return {} returncode, output = DataSource.wmic_cpu() if output == None or returncode != 0: return {} # Break the list into key values pairs value = output.split("\n") value = [s.rstrip().split('=') for s in value if '=' in s] value = {k: v for k, v in value if v} # Get the advertised MHz processor_brand = value.get('Name') scale_advertised, hz_advertised = _get_hz_string_from_brand(processor_brand) # Get the actual MHz hz_actual = value.get('CurrentClockSpeed') scale_actual = 6 if hz_actual: hz_actual = to_hz_string(hz_actual) # Get cache sizes l2_cache_size = value.get('L2CacheSize') if l2_cache_size: l2_cache_size = l2_cache_size + ' KB' l3_cache_size = value.get('L3CacheSize') if l3_cache_size: l3_cache_size = l3_cache_size + ' KB' # Get family, model, and stepping family, model, stepping = '', '', '' description = value.get('Description') or value.get('Caption') entries = description.split(' ') if 'Family' in entries and entries.index('Family') < len(entries)-1: i = entries.index('Family') family = int(entries[i + 1]) if 'Model' in entries and entries.index('Model') < len(entries)-1: i = entries.index('Model') model = int(entries[i + 1]) if 'Stepping' in entries and entries.index('Stepping') < len(entries)-1: i = entries.index('Stepping') stepping = int(entries[i + 1]) info = { 'vendor_id' : value.get('Manufacturer'), 'brand' : processor_brand, 'hz_advertised' : to_friendly_hz(hz_advertised, scale_advertised), 'hz_actual' : to_friendly_hz(hz_actual, scale_actual), 'hz_advertised_raw' : to_raw_hz(hz_advertised, scale_advertised), 'hz_actual_raw' : to_raw_hz(hz_actual, scale_actual), 'l2_cache_size' : l2_cache_size, 'l3_cache_size' : l3_cache_size, 'stepping' : stepping, 'model' : model, 'family' : family, } info = {k: v for k, v in info.items() if v} return info except: #raise # NOTE: To have this throw on error, uncomment this line return {} def _get_cpu_info_from_registry(): ''' FIXME: Is missing many of the newer CPU flags like sse3 Returns the CPU info gathered from the Windows Registry. Returns {} if not on Windows. ''' try: # Just return {} if not on Windows if not DataSource.is_windows: return {} # Get the CPU name processor_brand = DataSource.winreg_processor_brand() # Get the CPU vendor id vendor_id = DataSource.winreg_vendor_id() # Get the CPU arch and bits raw_arch_string = DataSource.winreg_raw_arch_string() arch, bits = parse_arch(raw_arch_string) # Get the actual CPU Hz hz_actual = DataSource.winreg_hz_actual() hz_actual = to_hz_string(hz_actual) # Get the advertised CPU Hz scale, hz_advertised = _get_hz_string_from_brand(processor_brand) # Get the CPU features feature_bits = DataSource.winreg_feature_bits() def is_set(bit): mask = 0x80000000 >> bit retval = mask & feature_bits > 0 return retval # http://en.wikipedia.org/wiki/CPUID # http://unix.stackexchange.com/questions/43539/what-do-the-flags-in-proc-cpuinfo-mean # http://www.lohninger.com/helpcsuite/public_constants_cpuid.htm flags = { 'fpu' : is_set(0), # Floating Point Unit 'vme' : is_set(1), # V86 Mode Extensions 'de' : is_set(2), # Debug Extensions - I/O breakpoints supported 'pse' : is_set(3), # Page Size Extensions (4 MB pages supported) 'tsc' : is_set(4), # Time Stamp Counter and RDTSC instruction are available 'msr' : is_set(5), # Model Specific Registers 'pae' : is_set(6), # Physical Address Extensions (36 bit address, 2MB pages) 'mce' : is_set(7), # Machine Check Exception supported 'cx8' : is_set(8), # Compare Exchange Eight Byte instruction available 'apic' : is_set(9), # Local APIC present (multiprocessor operation support) 'sepamd' : is_set(10), # Fast system calls (AMD only) 'sep' : is_set(11), # Fast system calls 'mtrr' : is_set(12), # Memory Type Range Registers 'pge' : is_set(13), # Page Global Enable 'mca' : is_set(14), # Machine Check Architecture 'cmov' : is_set(15), # Conditional MOVe instructions 'pat' : is_set(16), # Page Attribute Table 'pse36' : is_set(17), # 36 bit Page Size Extensions 'serial' : is_set(18), # Processor Serial Number 'clflush' : is_set(19), # Cache Flush #'reserved1' : is_set(20), # reserved 'dts' : is_set(21), # Debug Trace Store 'acpi' : is_set(22), # ACPI support 'mmx' : is_set(23), # MultiMedia Extensions 'fxsr' : is_set(24), # FXSAVE and FXRSTOR instructions 'sse' : is_set(25), # SSE instructions 'sse2' : is_set(26), # SSE2 (WNI) instructions 'ss' : is_set(27), # self snoop #'reserved2' : is_set(28), # reserved 'tm' : is_set(29), # Automatic clock control 'ia64' : is_set(30), # IA64 instructions '3dnow' : is_set(31) # 3DNow! instructions available } # Get a list of only the flags that are true flags = [k for k, v in flags.items() if v] flags.sort() info = { 'vendor_id' : vendor_id, 'brand' : processor_brand, 'hz_advertised' : to_friendly_hz(hz_advertised, scale), 'hz_actual' : to_friendly_hz(hz_actual, 6), 'hz_advertised_raw' : to_raw_hz(hz_advertised, scale), 'hz_actual_raw' : to_raw_hz(hz_actual, 6), 'flags' : flags } info = {k: v for k, v in info.items() if v} return info except: return {} def _get_cpu_info_from_kstat(): ''' Returns the CPU info gathered from isainfo and kstat. Returns {} if isainfo or kstat are not found. ''' try: # Just return {} if there is no isainfo or kstat if not DataSource.has_isainfo() or not DataSource.has_kstat(): return {} # If isainfo fails return {} returncode, flag_output = DataSource.isainfo_vb() if flag_output == None or returncode != 0: return {} # If kstat fails return {} returncode, kstat = DataSource.kstat_m_cpu_info() if kstat == None or returncode != 0: return {} # Various fields vendor_id = kstat.split('\tvendor_id ')[1].split('\n')[0].strip() processor_brand = kstat.split('\tbrand ')[1].split('\n')[0].strip() stepping = int(kstat.split('\tstepping ')[1].split('\n')[0].strip()) model = int(kstat.split('\tmodel ')[1].split('\n')[0].strip()) family = int(kstat.split('\tfamily ')[1].split('\n')[0].strip()) # Flags flags = flag_output.strip().split('\n')[-1].strip().lower().split() flags.sort() # Convert from GHz/MHz string to Hz scale = 6 hz_advertised = kstat.split('\tclock_MHz ')[1].split('\n')[0].strip() hz_advertised = to_hz_string(hz_advertised) # Convert from GHz/MHz string to Hz hz_actual = kstat.split('\tcurrent_clock_Hz ')[1].split('\n')[0].strip() hz_actual = to_hz_string(hz_actual) info = { 'vendor_id' : vendor_id, 'brand' : processor_brand, 'hz_advertised' : to_friendly_hz(hz_advertised, scale), 'hz_actual' : to_friendly_hz(hz_actual, 0), 'hz_advertised_raw' : to_raw_hz(hz_advertised, scale), 'hz_actual_raw' : to_raw_hz(hz_actual, 0), 'stepping' : stepping, 'model' : model, 'family' : family, 'flags' : flags } info = {k: v for k, v in info.items() if v} return info except: return {} def CopyNewFields(info, new_info): keys = [ 'vendor_id', 'hardware', 'brand', 'hz_advertised', 'hz_actual', 'hz_advertised_raw', 'hz_actual_raw', 'arch', 'bits', 'count', 'raw_arch_string', 'l2_cache_size', 'l2_cache_line_size', 'l2_cache_associativity', 'stepping', 'model', 'family', 'processor_type', 'extended_model', 'extended_family', 'flags', 'l3_cache_size', 'l1_data_cache_size', 'l1_instruction_cache_size' ] for key in keys: if new_info.get(key, None) and not info.get(key, None): info[key] = new_info[key] elif key == 'flags' and new_info.get('flags'): for f in new_info['flags']: if f not in info['flags']: info['flags'].append(f) info['flags'].sort() def get_cpu_info(): ''' Returns the CPU info by using the best sources of information for your OS. Returns {} if nothing is found. ''' # Get the CPU arch and bits arch, bits = parse_arch(DataSource.raw_arch_string) friendly_maxsize = { 2**31-1: '32 bit', 2**63-1: '64 bit' }.get(sys.maxsize) or 'unknown bits' friendly_version = "{0}.{1}.{2}.{3}.{4}".format(*sys.version_info) PYTHON_VERSION = "{0} ({1})".format(friendly_version, friendly_maxsize) info = { 'python_version' : PYTHON_VERSION, 'cpuinfo_version' : CPUINFO_VERSION, 'arch' : arch, 'bits' : bits, 'count' : DataSource.cpu_count, 'raw_arch_string' : DataSource.raw_arch_string, } # Try the Windows wmic CopyNewFields(info, _get_cpu_info_from_wmic()) # Try the Windows registry CopyNewFields(info, _get_cpu_info_from_registry()) # Try /proc/cpuinfo CopyNewFields(info, _get_cpu_info_from_proc_cpuinfo()) # Try cpufreq-info CopyNewFields(info, _get_cpu_info_from_cpufreq_info()) # Try LSCPU CopyNewFields(info, _get_cpu_info_from_lscpu()) # Try sysctl CopyNewFields(info, _get_cpu_info_from_sysctl()) # Try kstat CopyNewFields(info, _get_cpu_info_from_kstat()) # Try dmesg CopyNewFields(info, _get_cpu_info_from_dmesg()) # Try /var/run/dmesg.boot CopyNewFields(info, _get_cpu_info_from_cat_var_run_dmesg_boot()) # Try lsprop ibm,pa-features CopyNewFields(info, _get_cpu_info_from_ibm_pa_features()) # Try sysinfo CopyNewFields(info, _get_cpu_info_from_sysinfo()) # Try querying the CPU cpuid register CopyNewFields(info, _get_cpu_info_from_cpuid()) return info # Make sure we are running on a supported system def _check_arch(): arch, bits = parse_arch(DataSource.raw_arch_string) if not arch in ['X86_32', 'X86_64', 'ARM_7', 'ARM_8', 'PPC_64']: raise Exception("py-cpuinfo currently only works on X86 and some PPC and ARM CPUs.") def main(): try: _check_arch() except Exception as err: sys.stderr.write(str(err) + "\n") sys.exit(1) info = get_cpu_info() if info: print('Python Version: {0}'.format(info.get('python_version', ''))) print('Cpuinfo Version: {0}'.format(info.get('cpuinfo_version', ''))) print('Vendor ID: {0}'.format(info.get('vendor_id', ''))) print('Hardware Raw: {0}'.format(info.get('hardware', ''))) print('Brand: {0}'.format(info.get('brand', ''))) print('Hz Advertised: {0}'.format(info.get('hz_advertised', ''))) print('Hz Actual: {0}'.format(info.get('hz_actual', ''))) print('Hz Advertised Raw: {0}'.format(info.get('hz_advertised_raw', ''))) print('Hz Actual Raw: {0}'.format(info.get('hz_actual_raw', ''))) print('Arch: {0}'.format(info.get('arch', ''))) print('Bits: {0}'.format(info.get('bits', ''))) print('Count: {0}'.format(info.get('count', ''))) print('Raw Arch String: {0}'.format(info.get('raw_arch_string', ''))) print('L1 Data Cache Size: {0}'.format(info.get('l1_data_cache_size', ''))) print('L1 Instruction Cache Size: {0}'.format(info.get('l1_instruction_cache_size', ''))) print('L2 Cache Size: {0}'.format(info.get('l2_cache_size', ''))) print('L2 Cache Line Size: {0}'.format(info.get('l2_cache_line_size', ''))) print('L2 Cache Associativity: {0}'.format(info.get('l2_cache_associativity', ''))) print('L3 Cache Size: {0}'.format(info.get('l3_cache_size', ''))) print('Stepping: {0}'.format(info.get('stepping', ''))) print('Model: {0}'.format(info.get('model', ''))) print('Family: {0}'.format(info.get('family', ''))) print('Processor Type: {0}'.format(info.get('processor_type', ''))) print('Extended Model: {0}'.format(info.get('extended_model', ''))) print('Extended Family: {0}'.format(info.get('extended_family', ''))) print('Flags: {0}'.format(', '.join(info.get('flags', '')))) else: sys.stderr.write("Failed to find cpu info\n") sys.exit(1) if __name__ == '__main__': from multiprocessing import freeze_support freeze_support() main() else: _check_arch() PyTables-3.7.0/doc/000077500000000000000000000000001416254111300140075ustar00rootroot00000000000000PyTables-3.7.0/doc/Makefile000066400000000000000000000011421416254111300154450ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = PyTables SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) PyTables-3.7.0/doc/make.bat000066400000000000000000000014141416254111300154140ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build set SPHINXPROJ=PyTables if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd PyTables-3.7.0/doc/man/000077500000000000000000000000001416254111300145625ustar00rootroot00000000000000PyTables-3.7.0/doc/man/pt2to3.1000066400000000000000000000026621416254111300160050ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH PT2TO3 1 "May 15, 2013" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME pt2to3 \- PyTables 2.x \-> 3.x API transition tool. .SH SYNOPSIS .B pt2to3 [ OPTIONS ] .RB filename .br .SH DESCRIPTION This tool displays to standard out, so it is common to pipe this to another file: $ pt2to3 oldfile.py > newfile.py. .SH OPTIONS A summary of options is included below. .TP .B \-r, \--reverse Reverts changes, going from 3.x \-> 2.x. .TP .B \-p, \--no-ignore-previous Ignores previous_api() calls. .TP .B \-o OUTPUT Output file to write to.. .TP .B \-i, \--inplace Overwrites the file in-place. .TP .B \-h Print help on usage. .br .SH SEE ALSO .BR ptrepack (1), ptdump (1). .br These utilities are documented fully by .IR "PyTables user's manual". .SH AUTHOR This manual page was written by Antonio Valentino . PyTables-3.7.0/doc/man/ptdump.1000066400000000000000000000034041416254111300161560ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH PTDUMP 1 "July 7, 2007" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME ptdump \- Lets you dig into the contents of a \fIPyTables\fR file. .SH SYNOPSIS .B ptdump .RB [\| \-dvacih \|] .RB [\| \-R \| \ start,stop,step] .RB file[:nodepath] .br .SH DESCRIPTION Allows you look into the contents of your \fIPyTables\fR files. It lets you see not only the data but also the metadata (that is, the structure and additional information in the form of attributes). .SH OPTIONS A summary of options is included below. .TP .B \-d Dump data information on leaves. .TP .B \-v Dump more metainformation on nodes. .TP .B \-a Show attributes in nodes (only useful when \-v or \-d are active). .TP .B \-c Show info of columns in tables (only useful when \-v or \-d are active). .TP .B \-i Show info of indexed column (only useful when \-v or \-d are active). .TP .BI \-R\ start,stop,step Select a range of rows in the form "start,stop,step" for \fIall\fR leaves. .TP .B \-h Print help on usage. .br .SH SEE ALSO .BR ptrepack (1). .br These utilities are documented fully by .IR "PyTables user's manual". .SH AUTHOR This manual page was written by Francesc Altet . PyTables-3.7.0/doc/man/ptrepack.1000066400000000000000000000060551416254111300164630ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH PTREPACK 1 "July 7, 2007" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME ptrepack \- Copy any PyTables Leaf, Group or complete subtree into another file. .SH SYNOPSIS .B ptrepack .RB \-h .RB \-v .RB \-o .RB \-R .IR start,stop,step .RB \-\-non\-recursive .RB \-\-dest-title=title .RB \-\-dont\-copyuser\-attrs .RB \-\-overwrite\-nodes .RB \-\-complevel=(0\-9) .RB \-\-complib=lib .RB \-\-shuffle=(0|1) .RB \-\-fletcher32=(0|1) .RB \-\-keep\-source\-filters .RB \-\-upgrade\-flavors .RB \-\-dont\-regenerate\-old\-indexes .RB sourcefile:sourcegroup .RB destfile:destgroup .br .SH DESCRIPTION Copy any Leaf, Group or complete subtree from a PyTables file into another file. .SH OPTIONS A summary of options is included below. .TP .B \-h Prints a help text. .TP .B \-v Show more information. .TP .B \-o Overwrite destination file. .TP .BI \-R\ RANGE Select a RANGE of rows in the form \fIstart,stop,step\fR during the copy of \fIall\fR the leaves. .TP .BI \-\-non\-recursive Do not do a recursive copy. Default is to do it. .TP .BI \-\-dest\-title=title Title for the new file (if not specified, the source is copied). .TP .BI \-\-dont\-copy\-userattrs Do not copy the user attrs (default is to do it). .TP .BI \-\-overwrite\-nodes Overwrite destination nodes if they exist. Default is not to overwrite them. .TP .BI \-\-complevel=(0-9) Set a compression level (0 for no compression, which is the default). .TP .BI \-\-complib=lib Set the compression library to be used during the copy. \fIlib\fR can be set to "zlib", "lzo", "ucl" or "bzip2". Defaults to "zlib". .TP .BI \-\-shuffle=(0|1) Activate or not the shuffling filter (default is active if complevel>0). .TP .BI \-\-fletcher32=(0|1) Whether to activate or not the fletcher32 filter (not active by default). .TP .BI \-\-keep\-source\-filters Use the original filters in source files. The default is not doing that if any of \-\-complevel, \-\-complib, \-\-shuffle or \-\-fletcher32 option is specified. .TP .BI \-\-upgrade\-flavors When repacking PyTables 1.x files, the flavor of leaves will be unset. With this, such a leaves will be serialized as objects with the internal flavor ("numpy" for 2.x series). .TP .BI \-\-dont\-regenerate\-old\-indexes Disable regenerating old indexes. The default is to regenerate old indexes as they are found. .br .SH SEE ALSO .BR ptdump (1). .br These utilities are documented fully by .IR "PyTables user's manual". .SH AUTHOR This manual page was written by Francesc Altet . PyTables-3.7.0/doc/man/pttree.1000066400000000000000000000042721416254111300161540ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH pttree 1 "May 15, 2013" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME pttree \- prints a quick overview of the contents of PyTables HDF5 files. .SH SYNOPSIS .B pttree [ OPTIONS ] .RB filename[:nodepath] .br .SH DESCRIPTION .B pttree is designed to give a quick overview of the contents of a PyTables HDF5 file by printing a depth-indented list of nodes, similar to the output of the Unix `tree` function. It can also display the size, shape and compression states of individual nodes, as well as summary information for the whole file. For a more verbose output (including metadata), see `ptdump`. .SH OPTIONS A summary of options is included below. .TP .B \-L MAX_DEPTH, \--max-level MAX_DEPTH maximum branch depth of tree to display (-1 == no limit) .TP .B \-S SORT_BY, \--sort-by SORT_BY artificially order nodes, can be either "size", "name" or "none" .TP .B \--print-size print size of each node/branch .TP .B \--no-print-size .TP .B \--print-shape print shape of each node .TP .B \--no-print-shape .TP .B \--print-compression print compression library(level) for each compressed node .TP .B \--no-print-compression .TP .B \--print-percent print size of each node as a % of the total tree size on disk .TP .B \--no-print-percent .TP .B \--use-si-units report sizes in SI units (1 MB == 10^6 B) .TP .B \--use-binary-units report sizes in binary units (1 MiB == 2^20 B) .TP .B \-h Print help on usage. .br .SH SEE ALSO .BR ptdump (1). .br These utilities are documented fully by .IR "PyTables user's manual". .SH AUTHOR This manual page was written by Antonio Valentino . PyTables-3.7.0/doc/scripts/000077500000000000000000000000001416254111300154765ustar00rootroot00000000000000PyTables-3.7.0/doc/scripts/filenode.py000066400000000000000000000022611416254111300176360ustar00rootroot00000000000000# Copy this file into the clipboard and paste into 'script -c python'. import tables as tb h5file = tb.open_file('fnode.h5', 'w') fnode = tb.nodes.FileNode.new_node(h5file, where='/', name='fnode_test') print(h5file.getAttrNode('/fnode_test', 'NODE_TYPE')) print("This is a test text line.", file=fnode) print("And this is another one.", file=fnode) print(file=fnode) fnode.write("Of course, file methods can also be used.") fnode.seek(0) # Go back to the beginning of file. for line in fnode: print(repr(line)) fnode.close() print(fnode.closed) node = h5file.root.fnode_test fnode = tb.nodes.FileNode.open_node(node, 'a+') print(repr(fnode.readline())) print(fnode.tell()) print("This is a new line.", file=fnode) print(repr(fnode.readline())) fnode.seek(0) for line in fnode: print(repr(line)) fnode.attrs.content_type = 'text/plain; charset=us-ascii' fnode.attrs.author = "Ivan Vilata i Balaguer" fnode.attrs.creation_date = '2004-10-20T13:25:25+0200' fnode.attrs.keywords_en = ["FileNode", "test", "metadata"] fnode.attrs.keywords_ca = ["FileNode", "prova", "metadades"] fnode.attrs.owner = 'ivan' fnode.attrs.acl = {'ivan': 'rw', '@users': 'r'} fnode.close() h5file.close() PyTables-3.7.0/doc/scripts/pickletrouble.py000066400000000000000000000013441416254111300207160ustar00rootroot00000000000000import tables as tb class MyClass: foo = 'bar' # An object of my custom class. myObject = MyClass() with tb.open_file('test.h5', 'w') as h5f: h5f.root._v_attrs.obj = myObject # store the object print(h5f.root._v_attrs.obj.foo) # retrieve it # Delete class of stored object and reopen the file. del MyClass, myObject with tb.open_file('test.h5', 'r') as h5f: print(h5f.root._v_attrs.obj.foo) # Let us inspect the object to see what is happening. print(repr(h5f.root._v_attrs.obj)) # Maybe unpickling the string will yield more information: import pickle pickle.loads(h5f.root._v_attrs.obj) # So the problem was not in the stored object, # but in the *environment* where it was restored. PyTables-3.7.0/doc/scripts/tutorial1.py000066400000000000000000000174351416254111300200060ustar00rootroot00000000000000"""Small but quite comprehensive example showing the use of PyTables. The program creates an output file, 'tutorial1.h5'. You can view it with any HDF5 generic utility. """ import os import sys import traceback SECTION = "I HAVE NO TITLE" def tutsep(): print('----8<----', SECTION, '----8<----') def tutprint(obj): tutsep() print(obj) def tutrepr(obj): tutsep() print(repr(obj)) def tutexc(): tutsep() traceback.print_exc(file=sys.stdout) SECTION = "Importing tables objects" import numpy as np import tables as tb SECTION = "Declaring a Column Descriptor" # Define a user record to characterize some kind of particles class Particle(tb.IsDescription): name = tb.StringCol(16) # 16-character String idnumber = tb.Int64Col() # Signed 64-bit integer ADCcount = tb.UInt16Col() # Unsigned short integer TDCcount = tb.UInt8Col() # unsigned byte grid_i = tb.Int32Col() # integer grid_j = tb.IntCol() # integer (equivalent to Int32Col) pressure = tb.Float32Col() # float (single-precision) energy = tb.FloatCol() # double (double-precision) SECTION = "Creating a PyTables file from scratch" # Open a file in "w"rite mode h5file = tb.open_file('tutorial1.h5', mode="w", title="Test file") SECTION = "Creating a new group" # Create a new group under "/" (root) group = h5file.create_group("/", 'detector', 'Detector information') SECTION = "Creating a new table" # Create one table on it table = h5file.create_table(group, 'readout', Particle, "Readout example") tutprint(h5file) tutrepr(h5file) # Get a shortcut to the record object in table particle = table.row # Fill the table with 10 particles for i in range(10): particle['name'] = f'Particle: {i:6d}' particle['TDCcount'] = i % 256 particle['ADCcount'] = (i * 256) % (1 << 16) particle['grid_i'] = i particle['grid_j'] = 10 - i particle['pressure'] = float(i * i) particle['energy'] = float(particle['pressure'] ** 4) particle['idnumber'] = i * (2 ** 34) # Insert a new particle record particle.append() # Flush the buffers for table table.flush() SECTION = "Reading (and selecting) data in a table" # Read actual data from table. We are interested in collecting pressure values # on entries where TDCcount field is greater than 3 and pressure less than 50 table = h5file.root.detector.readout pressure = [ x['pressure'] for x in table if x['TDCcount'] > 3 and 20 <= x['pressure'] < 50 ] tutrepr(pressure) # Read also the names with the same cuts names = [ x['name'] for x in table if x['TDCcount'] > 3 and 20 <= x['pressure'] < 50 ] tutrepr(names) SECTION = "Creating new array objects" gcolumns = h5file.create_group(h5file.root, "columns", "Pressure and Name") tutrepr( h5file.create_array(gcolumns, 'pressure', np.array(pressure), "Pressure column selection") ) tutrepr( h5file.create_array('/columns', 'name', names, "Name column selection") ) tutprint(h5file) SECTION = "Closing the file and looking at its content" # Close the file h5file.close() tutsep() os.system('h5ls -rd tutorial1.h5') tutsep() os.system('ptdump tutorial1.h5') """This example shows how to browse the object tree and enlarge tables. Before to run this program you need to execute first tutorial1-1.py that create the tutorial1.h5 file needed here. """ SECTION = "Traversing the object tree" # Reopen the file in append mode h5file = tb.open_file("tutorial1.h5", "a") # Print the object tree created from this filename # List all the nodes (Group and Leaf objects) on tree tutprint(h5file) # List all the nodes (using File iterator) on tree tutsep() for node in h5file: print(node) # Now, only list all the groups on tree tutsep() for group in h5file.walk_groups("/"): print(group) # List only the arrays hanging from / tutsep() for group in h5file.walk_groups("/"): for array in h5file.list_nodes(group, classname='Array'): print(array) # This gives the same result tutsep() for array in h5file.walk_nodes("/", "Array"): print(array) # And finally, list only leafs on /detector group (there should be one!) # Other way using iterators and natural naming tutsep() for leaf in h5file.root.detector('Leaf'): print(leaf) SECTION = "Setting and getting user attributes" # Get a pointer to '/detector/readout' table = h5file.root.detector.readout # Attach it a string (date) attribute table.attrs.gath_date = "Wed, 06/12/2003 18:33" # Attach a floating point attribute table.attrs.temperature = 18.4 table.attrs.temp_scale = "Celsius" # Get a pointer to '/detector' detector = h5file.root.detector # Attach a general object to the parent (/detector) group detector._v_attrs.stuff = [5, (2.3, 4.5), "Integer and tuple"] # Now, get the attributes tutrepr(table.attrs.gath_date) tutrepr(table.attrs.temperature) tutrepr(table.attrs.temp_scale) tutrepr(detector._v_attrs.stuff) # Delete permanently the attribute gath_date of /detector/readout del table.attrs.gath_date # Print a representation of all attributes in /detector/table tutrepr(table.attrs) # Get the (user) attributes of /detector/table tutprint(table.attrs._f_list("user")) # Get the (sys) attributes of /detector/table tutprint(table.attrs._f_list("sys")) # Rename an attribute table.attrs._f_rename("temp_scale", "tempScale") tutprint(table.attrs._f_list()) # Try to rename a system attribute: try: table.attrs._f_rename("VERSION", "version") except: tutexc() h5file.flush() tutsep() os.system('h5ls -vr tutorial1.h5/detector/readout') SECTION = "Getting object metadata" # Get metadata from table tutsep() print("Object:", table) tutsep() print("Table name:", table.name) tutsep() print("Table title:", table.title) tutsep() print("Number of rows in table:", table.nrows) tutsep() print("Table variable names with their type and shape:") tutsep() for name in table.colnames: print(f'{name}:= {table.coltypes[name]}, {table.colshapes[name]}') tutprint(table.__doc__) # Get the object in "/columns pressure" pressureObject = h5file.get_node("/columns", "pressure") # Get some metadata on this object tutsep() print(f"Info on the object: {pressureObject!r}") tutsep() print(f" shape: ==> {pressureObject.shape}") tutsep() print(f" title: ==> {pressureObject.title}") tutsep() print(f" type: ==> {pressureObject.type}") SECTION = "Reading data from Array objects" # Read the 'pressure' actual data pressureArray = pressureObject.read() tutrepr(pressureArray) tutsep() print(f"pressureArray is an object of type: {type(pressureArray)}") # Read the 'name' Array actual data nameArray = h5file.root.columns.name.read() tutrepr(nameArray) print(f"nameArray is an object of type: {type(nameArray)}") # Print the data for both arrays tutprint("Data on arrays nameArray and pressureArray:") tutsep() for i in range(pressureObject.shape[0]): print(f"{nameArray[i]} --> {pressureArray[i]}") tutrepr(pressureObject.name) SECTION = "Appending data to an existing table" # Create a shortcut to table object table = h5file.root.detector.readout # Get the object row from table particle = table.row # Append 5 new particles to table for i in range(10, 15): particle['name'] = f'Particle: {i:6d}' particle['TDCcount'] = i % 256 particle['ADCcount'] = (i * 256) % (1 << 16) particle['grid_i'] = i particle['grid_j'] = 10 - i particle['pressure'] = float(i * i) particle['energy'] = float(particle['pressure'] ** 4) particle['idnumber'] = i * (2 ** 34) # This exceeds long integer range particle.append() # Flush this table table.flush() # Print the data using the table iterator: tutsep() for r in table: print(f"{r['name']:<16s} | {r['pressure']:11.1f} | {r['energy']:11.4g} | " f"{r['grid_i']:6d} | {r['grid_j']:6d} | {r['TDCcount']:8d} |") # Delete some rows on the Table (yes, rows can be removed!) tutrepr(table.remove_rows(5, 10)) # Close the file h5file.close() PyTables-3.7.0/doc/source/000077500000000000000000000000001416254111300153075ustar00rootroot00000000000000PyTables-3.7.0/doc/source/FAQ.rst000066400000000000000000000534761416254111300164670ustar00rootroot00000000000000:author: FrancescAlted :date: 2011-06-13 08:40:20 .. py:currentmodule:: tables === FAQ === General questions ================= What is PyTables? ----------------- PyTables is a package for managing hierarchical datasets designed to efficiently cope with extremely large amounts of data. It is built on top of the HDF5_ library, the `Python language`_ and the NumPy_ package. It features an object-oriented interface that, combined with C extensions for the performance-critical parts of the code, makes it a fast yet extremely easy-to-use tool for interactively storing and retrieving very large amounts of data. What are PyTables' licensing terms? ----------------------------------- PyTables is free for both commercial and non-commercial use, under the terms of the `BSD 3-Clause License `_. I'm having problems. How can I get support? ------------------------------------------- The most common and efficient way is to subscribe (remember you *need* to subscribe prior to send messages) to the PyTables `users mailing list`_, and send there a brief description of your issue and, if possible, a short script that can reproduce it. Hopefully, someone on the list will be able to help you. It is also a good idea to check out the `archives of the user's list`_ (you may want to check the `Gmane archives`_ instead) so as to see if the answer to your question has already been dealt with. Why HDF5? --------- HDF5_ is the underlying C library and file format that enables PyTables to efficiently deal with the data. It has been chosen for the following reasons: * Designed to efficiently manage very large datasets. * Lets you organize datasets hierarchically. * Very flexible and well tested in scientific environments. * Good maintenance and improvement rate. * Technical excellence (`R&D 100 Award`_). * **It's Open Source software** Why Python? ----------- 1. Python is interactive. People familiar with data processing understand how powerful command line interfaces are for exploring mathematical relationships and scientific data sets. Python provides an interactive environment with the added benefit of a full featured programming language behind it. 2. Python is productive for beginners and experts alike. PyTables is targeted at engineers, scientists, system analysts, financial analysts, and others who consider programming a necessary evil. Any time spent learning a language or tracking down bugs is time spent not solving their real problem. Python has a short learning curve and most people can do real and useful work with it in a day of learning. Its clean syntax and interactive nature facilitate this. 3. Python is data-handling friendly. Python comes with nice idioms that make the access to data much easier: general slicing (i.e. ``data[start:stop:step]``), list comprehensions, iterators, generators ... are constructs that make the interaction with your data very easy. Why NumPy? ---------- NumPy_ is a Python package to efficiently deal with large datasets **in-memory**, providing containers for homogeneous data, heterogeneous data, and string arrays. PyTables uses these NumPy containers as *in-memory buffers* to push the I/O bandwith towards the platform limits. Where can PyTables be applied? ============================== In all the scenarios where one needs to deal with large datasets: * Industrial applications - Data acquisition in real time - Quality control - Fast data processing * Scientific applications - Meteorology, oceanography - Numerical simulations - Medicine (biological sensors, general data gathering & processing) * Information systems - System log monitoring & consolidation - Tracing of routing data - Alert systems in security Is PyTables safe? ----------------- Well, first of all, let me state that PyTables does not support transactional features yet (we don't even know if we will ever be motivated to implement this!), so there is always the risk that you can lose your data in case of an unexpected event while writing (like a power outage, system shutdowns ...). Having said that, if your typical scenarios are *write once, read many*, then the use of PyTables is perfectly safe, even for dealing extremely large amounts of data. Can PyTables be used in concurrent access scenarios? ---------------------------------------------------- It depends. Concurrent reads are no problem at all. However, whenever a process (or thread) is trying to write, then problems will start to appear. First, PyTables doesn't support locking at any level, so several process writing concurrently to the same PyTables file will probably end up corrupting it, so don't do this! Even having only one process writing and the others reading is a hairy thing, because the reading processes might be reading incomplete data from a concurrent data writing operation. The solution would be to lock the file while writing and unlock it after a flush over the file has been performed. Also, in order to avoid cache (HDF5_, PyTables) problems with read apps, you would need to re-open your files whenever you are going to issue a read operation. If a re-opening operation is unacceptable in terms of speed, you may want to do all your I/O operations in one single process (or thread) and communicate the results via sockets, :class:`Queue.Queue` objects (in case of using threads), or whatever, with the client process/thread. The `examples` directory contains two scripts demonstrating methods of accessing a PyTables file from multiple processes. The first, *multiprocess_access_queues.py*, uses a :class:`multiprocessing.Queue` object to transfer read and write requests from multiple *DataProcessor* processes to a single process responsible for all access to the PyTables file. The results of read requests are then transferred back to the originating processes using other :class:`Queue` objects. The second example script, *multiprocess_access_benchmarks.py*, demonstrates and benchmarks four methods of transferring PyTables array data between processes. The four methods are: * Using :class:`multiprocessing.Pipe` from the Python standard library. * Using a memory mapped file that is shared between two processes. The NumPy array associated with the file is passed as the *out* argument to the :meth:`tables.Array.read` method. * Using a Unix domain socket. Note that this example uses the 'abstract namespace' and will only work under Linux. * Using an IPv4 socket. See also the discussion in :issue:`790`. What kind of containers does PyTables implement? ------------------------------------------------ PyTables does support a series of data containers that address specific needs of the user. Below is a brief description of them: ::class:`Table`: Lets you deal with heterogeneous datasets. Allows compression. Enlargeable. Supports nested types. Good performance for read/writing data. ::class:`Array`: Provides quick and dirty array handling. Not compression allowed. Not enlargeable. Can be used only with relatively small datasets (i.e. those that fit in memory). It provides the fastest I/O speed. ::class:`CArray`: Provides compressed array support. Not enlargeable. Good speed when reading/writing. ::class:`EArray`: Most general array support. Compressible and enlargeable. It is pretty fast at extending, and very good at reading. ::class:`VLArray`: Supports collections of homogeneous data with a variable number of entries. Compressible and enlargeable. I/O is not very fast. ::class:`Group`: The structural component. A hierarchically-addressable container for HDF5 nodes (each of these containers, including Group, are nodes), similar to a directory in a UNIX filesystem. Please refer to the :doc:`usersguide/libref` for more specific information. Cool! I'd like to see some examples of use. ------------------------------------------- Sure. Go to the HowToUse section to find simple examples that will help you getting started. Can you show me some screenshots? --------------------------------- Well, PyTables is not a graphical library by itself. However, you may want to check out ViTables_, a GUI tool to browse and edit PyTables & HDF5_ files. Is PyTables a replacement for a relational database? ---------------------------------------------------- No, by no means. PyTables lacks many features that are standard in most relational databases. In particular, it does not have support for relationships (beyond the hierarchical one, of course) between datasets and it does not have transactional features. PyTables is more focused on speed and dealing with really large datasets, than implementing the above features. In that sense, PyTables can be best viewed as a *teammate* of a relational database. For example, if you have very large tables in your existing relational database, they will take lots of space on disk, potentially reducing the performance of the relational engine. In such a case, you can move those huge tables out of your existing relational database to PyTables, and let your relational engine do what it does best (i.e. manage relatively small or medium datasets with potentially complex relationships), and use PyTables for what it has been designed for (i.e. manage large amounts of data which are loosely related). How can PyTables be fast if it is written in an interpreted language like Python? --------------------------------------------------------------------------------- Actually, all of the critical I/O code in PyTables is a thin layer of code on top of HDF5_, which is a very efficient C library. Cython_ is used as the *glue* language to generate "wrappers" around HDF5 calls so that they can be used in Python. Also, the use of an efficient numerical package such as NumPy_ makes the most costly operations effectively run at C speed. Finally, time-critical loops are usually implemented in Cython_ (which, if used properly, allows to generate code that runs at almost pure C speeds). If it is designed to deal with very large datasets, then PyTables should consume a lot of memory, shouldn't it? --------------------------------------------------------------------------------------------------------------- Well, you already know that PyTables sits on top of HDF5, Python and NumPy_, and if we add its own logic (~7500 lines of code in Python, ~3000 in Cython and ~4000 in C), then we should conclude that PyTables isn't effectively a paradigm of lightness. Having said that, PyTables (as HDF5_ itself) tries very hard to optimize the memory consumption by implementing a series of features like dynamic determination of buffer sizes, *Least Recently Used* cache for keeping unused nodes out of memory, and extensive use of compact NumPy_ data containers. Moreover, PyTables is in a relatively mature state and most memory leaks have been already addressed and fixed. Just to give you an idea of what you can expect, a PyTables program can deal with a table with around 30 columns and 1 million entries using as low as 13 MB of memory (on a 32-bit platform). All in all, it is not that much, is it?. Why was PyTables born? ---------------------- Because, back in August 2002, one of its authors (`Francesc Alted`_) had a need to save lots of hierarchical data in an efficient way for later post-processing it. After trying out several approaches, he found that they presented distinct inconveniences. For example, working with file sizes larger than, say, 100 MB, was rather painful with ZODB (it took lots of memory with the version available by that time). The netCDF3_ interface provided by `Scientific Python`_ was great, but it did not allow to structure the hierarchically; besides, netCDF3_ only supports homogeneous datasets, not heterogeneous ones (i.e. tables). (As an aside, netCDF4_ overcomes many of the limitations of netCDF3_, although curiously enough, it is based on top of HDF5_, the library chosen as the base for PyTables from the very beginning.) So, he decided to give HDF5_ a try, start doing his own wrappings to it and voilà, this is how the first public release of PyTables (0.1) saw the light in October 2002, three months after his itch started to eat him ;-). How does PyTables compare with the h5py project? ------------------------------------------------ Well, they are similar in that both packages are Python interfaces to the HDF5_ library, but there are some important differences to be noted. h5py_ is an attempt to map the HDF5_ feature set to NumPy_ as closely as possible. In addition, it also provides access to nearly all of the HDF5_ C API. Instead, PyTables builds up an additional abstraction layer on top of HDF5_ and NumPy_ where it implements things like an enhanced type system, an :ref:`engine for enabling complex queries `, an efficient computational kernel, advanced indexing capabilities or an undo/redo feature, to name just a few. This additional layer also allows PyTables to be relatively independent of its underlying libraries (and their possible limitations). For example, PyTables can support HDF5_ data types like `enumerated` or `time` that are available in the HDF5_ library but not in the NumPy_ package; or even perform powerful complex queries that are not implemented directly in neither HDF5_ nor NumPy_. Furthermore, PyTables also tries hard to be a high performance interface to HDF5/NumPy, implementing niceties like internal LRU caches for nodes and other data and metadata, :ref:`automatic computation of optimal chunk sizes ` for the datasets, a variety of compressors, ranging from slow but efficient (bzip2_) to extremely fast ones (Blosc_) in addition to the standard `zlib`_. Another difference is that PyTables makes use of numexpr_ so as to accelerate internal computations (for example, in evaluating complex queries) to a maximum. For contrasting with other opinions, you may want to check the PyTables/h5py comparison in a similar entry of the `FAQ of h5py`_. I've found a bug. What do I do? -------------------------------- The PyTables development team works hard to make this eventuality as rare as possible, but, as in any software made by human beings, bugs do occur. If you find any bug, please tell us by file a bug report in the `issue tracker`_ on GitHub_. Is it possible to get involved in PyTables development? ------------------------------------------------------- Indeed. We are keen for more people to help out contributing code, unit tests, documentation, and helping out maintaining this wiki. Drop us a mail on the `users mailing list` and tell us in which area do you want to work. How can I cite PyTables? ------------------------ The recommended way to cite PyTables in a paper or a presentation is as following: * Author: Francesc Alted, Ivan Vilata and others * Title: PyTables: Hierarchical Datasets in Python * Year: 2002 - * URL: http://www.pytables.org Here's an example of a BibTeX entry:: @Misc{, author = {PyTables Developers Team}, title = {{PyTables}: Hierarchical Datasets in {Python}}, year = {2002--}, url = "http://www.pytables.org/" } PyTables 2.x issues =================== I'm having problems migrating my apps from PyTables 1.x into PyTables 2.x. Please, help! ---------------------------------------------------------------------------------------- Sure. However, you should first check out the :doc:`MIGRATING_TO_2.x` document. It should provide hints to the most frequently asked questions on this regard. For combined searches like `table.where('(x<5) & (x>3)')`, why was a `&` operator chosen instead of an `and`? ------------------------------------------------------------------------------------------------------------- Search expressions are in fact Python expressions written as strings, and they are evaluated as such. This has the advantage of not having to learn a new syntax, but it also implies some limitations with logical `and` and `or` operators, namely that they can not be overloaded in Python. Thus, it is impossible right now to get an element-wise operation out of an expression like `'array1 and array2'`. That's why one has to choose some other operator, being `&` and `|` the most similar to their C counterparts `&&` and `||`, which aren't available in Python either. You should be careful about expressions like `'x<5 & x>3'` and others like `'3 < x < 5'` which ''won't work as expected'', because of the different operator precedence and the absence of an overloaded logical `and` operator. More on this in the appendix about condition syntax in the `HDF5 manual`_. There are quite a few packages affected by those limitations including NumPy_ themselves and SQLObject_, and there have been quite longish discussions about adding the possibility of overloading logical operators to Python (see `PEP 335`_ and `this thread`__ for more details). __ https://mail.python.org/pipermail/python-dev/2004-September/048763.html I can not select rows using in-kernel queries with a condition that involves an UInt64Col. Why? ----------------------------------------------------------------------------------------------- This turns out to be a limitation of the numexpr_ package. Internally, numexpr_ uses a limited set of types for doing calculations, and unsigned integers are always upcasted to the immediate signed integer that can fit the information. The problem here is that there is not a (standard) signed integer that can be used to keep the information of a 64-bit unsigned integer. So, your best bet right now is to avoid `uint64` types if you can. If you absolutely need `uint64`, the only way for doing selections with this is through regular Python selections. For example, if your table has a `colM` column which is declared as an `UInt64Col`, then you can still filter its values with:: [row['colN'] for row in table if row['colM'] < X] However, this approach will generally lead to slow speed (specially on Win32 platforms, where the values will be converted to Python `long` values). I'm already using PyTables 2.x but I'm still getting numarray objects instead of NumPy ones! -------------------------------------------------------------------------------------------- This is most probably due to the fact that you are using a file created with PyTables 1.x series. By default, PyTables 1.x was setting an HDF5 attribute `FLAVOR` with the value `'numarray'` to all leaves. Now, PyTables 2.x sees this attribute and obediently converts the internal object (truly a NumPy object) into a `numarray` one. For PyTables 2.x files the `FLAVOR` attribute will only be saved when explicitly set via the `leaf.flavor` property (or when passing data to an :class:`Array` or :class:`Table` at creation time), so you will be able to distinguish default flavors from user-set ones by checking the existence of the `FLAVOR` attribute. Meanwhile, if you don't want to receive `numarray` objects when reading old files, you have several possibilities: * Remove the flavor for your datasets by hand:: for leaf in h5file.walkNodes(classname='Leaf'): del leaf.flavor * Use the :program:'ptrepack` utility with the flag `--upgrade-flavors` so as to convert all flavors in old files to the default (effectively by removing the `FLAVOR` attribute). * Remove the `numarray` (and/or `Numeric`) package from your system. Then PyTables 2.x will return you pure NumPy objects (it can't be otherwise!). Installation issues =================== Windows ------- Error when importing tables ~~~~~~~~~~~~~~~~~~~~~~~~~~~ You have installed the binary installer for Windows and, when importing the *tables* package you are getting an error like:: The command in "0x6714a822" refers to memory in "0x012011a0". The procedure "written" could not be executed. Click to ok to terminate. Click to abort to debug the program. This problem can be due to a series of reasons, but the most probable one is that you have a version of a DLL library that is needed by PyTables and it is not at the correct version. Please, double-check the versions of the required libraries for PyTables and install newer versions, if needed. In most cases, this solves the issue. In case you continue getting problems, there are situations where other programs do install libraries in the PATH that are **optional** to PyTables (for example BZIP2 or LZO), but that they will be used if they are found in your system (i.e. anywhere in your :envvar:`PATH`). So, if you find any of these libraries in your PATH, upgrade it to the latest version available (you don't need to re-install PyTables). ----- .. target-notes:: .. _HDF5: http://www.hdfgroup.org/HDF5 .. _`Python language`: http://www.python.org .. _NumPy: http://www.numpy.org .. _`users mailing list`: https://groups.google.com/group/pytables-users .. _`archives of the user's list`: https://sourceforge.net/p/pytables/mailman/pytables-users/ .. _`Gmane archives`: http://www.mail-archive.com/pytables-users@lists.sourceforge.net/ .. _`R&D 100 Award`: http://www.hdfgroup.org/HDF5/RD100-2002/ .. _ViTables: http://vitables.org .. _Cython: http://www.cython.org .. _`Francesc Alted`: https://github.com/FrancescAlted .. _netCDF3: http://www.unidata.ucar.edu/software/netcdf .. _`Scientific Python`: http://dirac.cnrs-orleans.fr/ScientificPython.html .. _netCDF4: http://www.unidata.ucar.edu/software/netcdf .. _OPeNDAP: http://opendap.org .. _`PyTables Manual`: http://www.pytables.org/usersguide/index.html .. _h5py: http://www.h5py.org .. _bzip2: http://www.bzip.org .. _Blosc: https://www.blosc.org .. _`zlib`: http://zlib.net .. _numexpr: https://github.com/pydata/numexpr .. _`FAQ of h5py`: http://docs.h5py.org/en/latest/faq.html#what-s-the-difference-between-h5py-and-pytables .. _`issue tracker`: https://github.com/PyTables/PyTables/issues .. _GitHub: https://github.com .. _`HDF5 manual`: https://portal.hdfgroup.org/display/HDF5/Datatypes .. _SQLObject: http://sqlobject.org .. _`PEP 335`: http://www.python.org/dev/peps/pep-0335 .. todo:: fix links that point to wiki pages PyTables-3.7.0/doc/source/MIGRATING_TO_2.x.rst000066400000000000000000000242271416254111300204620ustar00rootroot00000000000000================================== Migrating from PyTables 1.x to 2.x ================================== :Author: Francesc Alted i Abad :Contact: faltet@pytables.com :Author: Ivan Vilata i Balaguer :Contact: ivan@selidor.net Next are described a series of issues that you must have in mind when migrating from PyTables 1.x to PyTables 2.x series. New type system =============== In PyTables 2.x all the data types for leaves are described through a couple of classes: - ``Atom``: Describes homogeneous types of the atomic components in ``*Array`` objects (``Array``, ``CArray``, ``EArray`` and ``VLArray``). - ``Description``: Describes (possibly nested) heterogeneous types in ``Table`` objects. So, in order to upgrade to the new type system, you must perform the next replacements: - ``*Array.stype`` --> ``*Array.atom.type`` (PyTables type) - ``*Array.type`` --> ``*Array.atom.dtype`` (NumPy type) - ``*Array.itemsize`` --> ``*Array.atom.itemsize`` (the size of the item) Furthermore, the PyTables types (previously called "string types") have changed to better adapt to NumPy conventions. The next changes have been applied: - PyTables types are now written in lower case, so 'Type' becomes 'type'. For example, 'Int64' becomes now 'int64'. - 'CharType' --> 'string' - 'Complex32', 'Complex64' --> 'complex64', 'complex128'. Note that the numeric part of a 'complex' type refers now to the *size in bits* of the type and not to the precision, as before. See Appendix I of the Users' Manual on supported data types for more information on the new PyTables types. Important changes in ``Atom`` specification =========================================== - The ``dtype`` argument of ``EnumAtom`` and ``EnumCol`` constructors has been replaced by the ``base`` argument, which can take a full-blown atom, although it accepts bare PyTables types as well. This is a *mandatory* argument now. - ``vlstring`` pseudo-atoms used in ``VLArray`` nodes do no longer imply UTF-8 (nor any other) encoding, they only store and load *raw strings of bytes*. All encoding and decoding is left to the user. Be warned that reading old files may yield raw UTF-8 encoded strings, which may be converted back to Unicode in this way:: unistr = vlarray[index].decode('utf-8') If you need to work with variable-length Unicode strings, you may want to use the new ``vlunicode`` pseudo-atom, which fully supports Unicode strings with no encoding hassles. - Finally, ``Atom`` and ``Col`` are now abstract classes, so you can't use them to create atoms or column definitions of an arbitrary type. If you know the particular type you need, use the proper subclass; otherwise, use the ``Atom.from_*()`` or ``Col.from_*()`` factory methods. See the section on declarative classes in the reference. You are also advised to avoid using the inheritance of atoms to check for their kind or type; for that purpose, use their ``kind`` and ``type`` attributes. New query system ================ - In-kernel conditions, since they are based now in Numexpr, must be written *as strings*. For example, a condition that in 1.x was stated as:: result = [row['col2'] for row in table.where(table.cols.col1 == 1)] now should read:: result = [row['col2'] for row in table.where('col1 == 1')] That means that complex selections are possible now:: result = [ row['col2'] for row in table.where('(col1 == 1) & (col3**4 > 1)') ] - For the same reason, conditions for indexed columns must be written as strings as well. New indexing system =================== The indexing system has been totally rewritten from scratch for PyTables 2.0 Pro Edition. The new indexing system has been included into PyTables with release 2.3. Due to this, your existing indexes created with PyTables 1.x will be useless, and although you will be able to continue using the actual data in files, you won't be able to take advantage of any improvement in speed. You will be offered the possibility to automatically re-create the indexes in PyTables 1.x format to the new 2.0 format by using the ``ptrepack`` utility. New meanings for atom shape and ``*Array`` shape argument ========================================================= With PyTables 1.x, the atom shape was used for different goals depending on the context it was used. For example, in ``createEArray()``, the shape of the atom was used to specify the *dataset shape* of the object on disk, while in ``CArray`` the same atom shape was used to specify the *chunk shape* of the dataset on disk. Moreover, for ``VLArray`` objects, the very same atom shape specified the *type shape* of the data type. As you see, all of these was quite a mess. Starting with PyTables 2.x, an ``Atom`` only specifies properties of the data type (à la ``VLArray`` in 1.x). This lets the door open for specifying multidimensional data types (that can be part of another layer of multidimensional datasets) in a consistent way along all the ``*Array`` objects in PyTables. As a consequence of this, ``File.createCArray()`` and ``File.createVLArray()`` methods have received new parameters in order to make possible to specify the shapes of the datasets as well as chunk sizes (in fact, it is possible now to specify the latter for all the chunked leaves, see below). Please have this in mind during the migration process. Another consequence is that, now that the meaning of the atom shape is clearly defined, it has been chosen as the main object to describe homogeneous data types in PyTables. See the Users' Manual for more info on this. New argument ``chunkshape`` of chunked leaves ============================================= It is possible now to specify the chunk shape for all the chunked leaves in PyTables (all except ``Array``). With PyTables 1.x this value was automatically calculated so as to achieve decent results in most of the situations. However, the user may be interested in specifying its own chunk shape based on her own needs (although this should be done only by advanced users). Of course, if this parameter is not specified, a sensible default is calculated for the size of the leave (which is recommended). A new attribute called ``chunkshape`` has been added to all leaves. It is read-only (you can't change the size of chunks once you have created a leaf), but it can be useful for inspection by advanced users. New flavor specification ======================== As of 2.x, flavors can *only* be set through the ``flavor`` attribute of leaves, and they are *persistent*, so changing a flavor requires that the file be writable. Flavors can no longer be set through ``File.create*()`` methods, nor the ``flavor`` argument previously found in some ``Table`` methods, nor through ``Atom`` constructors or the ``_v_flavor`` attribute of descriptions. System attributes can be deleted now ==================================== The protection against removing system attributes (like ``FILTERS``, ``FLAVOR`` or ``CLASS``, to name only a few) has been completely removed. It is now the responsibility of the user to make a proper use of this freedom. With this, users can get rid of all proprietary PyTables attributes if they want to (for example, for making a file to look more like an HDF5 native one). Byteorder issues ================ Now, all the data coming from reads and internal buffers is always converted on-the-fly, if needed, to the *native* byteorder. This represents a big advantage in terms of speed when operating with objects coming from files that have been created in machines with a byte ordering different from native. Besides, all leaf constructors have received a new ``byteorder`` parameter that allows specifying the byteorder of data on disk. In particular, a ``_v_byteorder`` entry in a Table description is no longer honored and you should use the aforementioned ``byteorder`` parameter. Tunable internal buffer sizes ============================= You can change the size of the internal buffers for I/O purposes of PyTables by changing the value of the new public attribute ``nrowsinbuf`` that is present in all leaves. By default, this contains a sensible value so as to achieve a good balance between speed and memory consumption. Be careful when changing it, if you don't want to get unwanted results (very slow I/O, huge memory consumption...). Changes to module names ======================= If your application is directly accessing modules under the ``tables`` package, you need to know that *the names of all modules are now all in lowercase*. This allows one to tell apart the ``tables.Array`` *class* from the ``tables.array`` *module* (which was also called ``tables.Array`` before). This includes subpackages like ``tables.nodes.FileNode``. On top of that, more-or-less independent modules have also been renamed and some of them grouped into subpackages. The most important are: - The ``tables.netcdf3`` subpackage replaces the old ``tables.NetCDF`` module. - The ``tables.nra`` subpackage replaces the old ``nestedrecords.py`` with the implementation of the ``NestedRecArray`` class. Also, the ``tables.misc`` package includes utility modules which do not depend on PyTables. Other changes ============= - ``Filters.complib`` is ``None`` for filter properties created with ``complevel=0`` (i.e. disabled compression, which is the default). - 'non-relevant' --> 'irrelevant' (applied to byteorders) - ``Table.colstypes`` --> ``Table.coltypes`` - ``Table.coltypes`` --> ``Table.coldtypes`` - Added ``Table.coldescr``, dictionary of the ``Col`` descriptions. - ``Table.colshapes`` has disappeared. You can get it this way:: colshapes = dict( (name, col.shape) for (name, col) in table.coldescr.iteritems() ) - ``Table.colitemsizes`` has disappeared. You can get it this way:: colitemsizes = dict( (name, col.itemsize) for (name, col) in table.coldescr.iteritems() ) - ``Description._v_totalsize`` --> ``Description._v_itemsize`` - ``Description._v_itemsizes`` and ``Description._v_totalsizes`` have disappeared. - ``Leaf._v_chunksize`` --> ``Leaf.chunkshape`` ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: rst .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/MIGRATING_TO_3.x.rst000066400000000000000000000630171416254111300204630ustar00rootroot00000000000000================================== Migrating from PyTables 2.x to 3.x ================================== :Author: Antonio Valentino :Author: Anthony Scopatz :Author: Thomas Provoost This document describes the major changes in PyTables in going from the 2.x to 3.x series and what you need to know when migrating downstream code bases. Python 3 at Last! ================= The PyTables 3.x series now ships with full compatibility for Python 3.1+. Additionally, we plan on maintaining compatibility with Python 2.7 for the foreseeable future. Python 2.6 is no longer supported but may work in most cases. Note that the entire 3.x series now relies on numexpr v2.1+, which itself is the first version of numexpr support both Python 2 & 3. Numeric, Numarray, NetCDF3, & HDF5 1.6 No More! =============================================== PyTables no longer supports numeric and numarray. Please use numpy instead. Additionally, the ``tables.netcdf3`` module has been removed. Please refer to the `netcdf4-python`_ project for further support. Lastly, the older HDF5 1.6 API is no longer supported. Please upgrade to HDF5 1.8+. Unicode all the strings! ======================== In Python 3, all strings are natively in Unicode. This introduces some difficulties, as the native HDF5 string format is not Unicode-compatible. To minimize explicit conversion troubles when writing, especially :ref:`when creating data sets from existing Python objects `, string objects are implicitly cast to non-Unicode for HDF5 storage. To make you aware of this, a warning is raised when this happens. This is certainly no true Unicode compatibility, but mainly for convenience with the pure-Unicode Python 3 string type. Any string that is not castable as ascii upon creation of your data set, will hence still raise an error. For true Unicode support, look into the ``VLUnicodeAtom`` class. Major API Changes ================= The PyTables developers, `by popular demand`_, have taken this opportunity that a major version number upgrade affords to implement significant API changes. We have tried to do this in such a way that will not immediately break most existing code, though in some breakages may still occur. PEP 8 Compliance **************** The PyTables 3.x series now follows `PEP 8`_ coding standard. This makes using PyTables more idiomatic with surrounding Python code that also adheres to this standard. The primary way that the 2.x series was *not* PEP 8 compliant was with respect to variable naming conventions. Approximately :ref:`450 API variables ` were identified and updated for PyTables 3.x. To ease migration, PyTables ships with a new ``pt2to3`` command line tool. This tool will run over a file and replace any instances of the old variable names with the 3.x version of the name. This tool covers the overwhelming majority of cases was used to transition the PyTables code base itself! However, it may also accidentally also pick up variable names in 3rd party codes that have *exactly* the same name as a PyTables' variable. This is because ``pt2to3`` was implemented using regular expressions rather than a fancier AST-based method. By using regexes, ``pt2to3`` works on Python and Cython code. ``pt2to3`` **help:** .. code-block:: bash usage: pt2to3 [-h] [-r] [-p] [-o OUTPUT] [-i] filename PyTables 2.x -> 3.x API transition tool This tool displays to standard out, so it is common to pipe this to another file: $ pt2to3 oldfile.py > newfile.py positional arguments: filename path to input file. optional arguments: -h, --help show this help message and exit -r, --reverse reverts changes, going from 3.x -> 2.x. -p, --no-ignore-previous ignores previous_api() calls. -o OUTPUT output file to write to. -i, --inplace overwrites the file in-place. Note that ``pt2to3`` only works on a single file, not a directory. However, a simple BASH script may be written to run ``pt2to3`` over an entire directory and all sub-directories: .. code-block:: bash #!/bin/bash for f in $(find .) do echo $f pt2to3 $f > temp.txt mv temp.txt $f done .. note:: :program:`pt2to3` uses the :mod:`argparse` module that is part of the Python standard library since Python 2.7. Users of Python 2.6 should install :mod:`argparse` separately (e.g. via :program:`pip`). The old APIs and variable names will continue to be supported for the short term, where possible. (The major backwards incompatible changes come from the renaming of some function and method arguments and keyword arguments.) Using the 2.x APIs in the 3.x series, however, will issue warnings. The following is the release plan for the warning types: * 3.0 - PendingDeprecationWarning * 3.1 - DeprecationWarning * >=3.2 - Remove warnings, previous_api(), and _past.py; keep pt2to3, The current plan is to maintain the old APIs for at least 2 years, though this is subject to change. .. _create-signatures: Consistent ``create_xxx()`` Signatures *************************************** Also by popular demand, it is now possible to create all data sets (``Array``, ``CArray``, ``EArray``, ``VLArray``, and ``Table``) from existing Python objects. Constructors for these classes now accept either of the following keyword arguments: * an ``obj`` to initialize with data * or both ``atom`` and ``shape`` to initialize an empty structure, if possible. These keyword arguments are also now part of the function signature for the corresponding ``create_xxx()`` methods on the ``File`` class. These would be called as follows:: # All create methods will support the following create_xxx(where, name, obj=obj) # All non-variable length arrays support the following: create_xxx(where, name, atom=atom, shape=shape) Using ``obj`` or ``atom`` and ``shape`` are mutually exclusive. Previously only ``Array`` could be created with an existing Python object using the ``object`` keyword argument. .. _api-name-changes: API Name Changes **************** The following tables shows the old 2.x names that have been update to their new values in the new 3.x series. Please use the ``pt2to3`` tool to convert between these. ================================ ================================ **2.x Name** **3.x Name** ================================ ================================ AtomFromHDF5Type atom_from_hdf5_type AtomToHDF5Type atom_to_hdf5_type BoolTypeNextAfter bool_type_next_after HDF5ClassToString hdf5_class_to_string HDF5ToNPExtType hdf5_to_np_ext_type HDF5ToNPNestedType hdf5_to_np_nested_type IObuf iobuf IObufcpy iobufcpy IntTypeNextAfter int_type_next_after NPExtPrefixesToPTKinds npext_prefixes_to_ptkinds PTSpecialKinds pt_special_kinds PTTypeToHDF5 pttype_to_hdf5 StringNextAfter string_next_after __allowedInitKwArgs __allowed_init_kwargs __getRootGroup __get_root_group __next__inKernel __next__inkernel _actionLogName _action_log_name _actionLogParent _action_log_parent _actionLogPath _action_log_path _addRowsToIndex _add_rows_to_index _appendZeros _append_zeros _autoIndex _autoindex _byteShape _byte_shape _c_classId _c_classid _c_shadowNameRE _c_shadow_name_re _cacheDescriptionData _cache_description_data _checkAndSetPair _check_and_set_pair _checkAttributes _check_attributes _checkBase _checkbase _checkColumn _check_column _checkGroup _check_group _checkNotClosed _check_not_closed _checkOpen _check_open _checkShape _check_shape _checkShapeAppend _check_shape_append _checkUndoEnabled _check_undo_enabled _checkWritable _check_writable _check_sortby_CSI _check_sortby_csi _closeFile _close_file _codeToOp _code_to_op _column__createIndex _column__create_index _compileCondition _compile_condition _conditionCache _condition_cache _convertTime64 _convert_time64 _convertTime64_ _convert_time64_ _convertTypes _convert_types _createArray _create_array _createCArray _create_carray _createMark _create_mark _createPath _create_path _createTable _create_table _createTransaction _create_transaction _createTransactionGroup _create_transaction_group _disableIndexingInQueries _disable_indexing_in_queries _doReIndex _do_reindex _emptyArrayCache _empty_array_cache _enableIndexingInQueries _enable_indexing_in_queries _enabledIndexingInQueries _enabled_indexing_in_queries _exprvarsCache _exprvars_cache _f_copyChildren _f_copy_children _f_delAttr _f_delattr _f_getAttr _f_getattr _f_getChild _f_get_child _f_isVisible _f_isvisible _f_iterNodes _f_iter_nodes _f_listNodes _f_list_nodes _f_setAttr _f_setattr _f_walkGroups _f_walk_groups _f_walkNodes _f_walknodes _fancySelection _fancy_selection _fillCol _fill_col _flushBufferedRows _flush_buffered_rows _flushFile _flush_file _flushModRows _flush_mod_rows _g_addChildrenNames _g_add_children_names _g_checkGroup _g_check_group _g_checkHasChild _g_check_has_child _g_checkName _g_check_name _g_checkNotContains _g_check_not_contains _g_checkOpen _g_check_open _g_closeDescendents _g_close_descendents _g_closeGroup _g_close_group _g_copyAsChild _g_copy_as_child _g_copyChildren _g_copy_children _g_copyRows _g_copy_rows _g_copyRows_optim _g_copy_rows_optim _g_copyWithStats _g_copy_with_stats _g_createHardLink _g_create_hard_link _g_delAndLog _g_del_and_log _g_delLocation _g_del_location _g_flushGroup _g_flush_group _g_getAttr _g_getattr _g_getChildGroupClass _g_get_child_group_class _g_getChildLeafClass _g_get_child_leaf_class _g_getGChildAttr _g_get_gchild_attr _g_getLChildAttr _g_get_lchild_attr _g_getLinkClass _g_get_link_class _g_listAttr _g_list_attr _g_listGroup _g_list_group _g_loadChild _g_load_child _g_logAdd _g_log_add _g_logCreate _g_log_create _g_logMove _g_log_move _g_maybeRemove _g_maybe_remove _g_moveNode _g_move_node _g_postInitHook _g_post_init_hook _g_postReviveHook _g_post_revive_hook _g_preKillHook _g_pre_kill_hook _g_propIndexes _g_prop_indexes _g_readCoords _g_read_coords _g_readSelection _g_read_selection _g_readSlice _g_read_slice _g_readSortedSlice _g_read_sorted_slice _g_refNode _g_refnode _g_removeAndLog _g_remove_and_log _g_setAttr _g_setattr _g_setLocation _g_set_location _g_setNestedNamesDescr _g_set_nested_names_descr _g_setPathNames _g_set_path_names _g_unrefNode _g_unrefnode _g_updateDependent _g_update_dependent _g_updateLocation _g_update_location _g_updateNodeLocation _g_update_node_location _g_updateTableLocation _g_update_table_location _g_widthWarning _g_width_warning _g_writeCoords _g_write_coords _g_writeSelection _g_write_selection _g_writeSlice _g_write_slice _getColumnInstance _get_column_instance _getConditionKey _get_condition_key _getContainer _get_container _getEnumMap _get_enum_map _getFileId _get_file_id _getFinalAction _get_final_action _getInfo _get_info _getLinkClass _get_link_class _getMarkID _get_mark_id _getNode _get_node _getOrCreatePath _get_or_create_path _getTypeColNames _get_type_col_names _getUnsavedNrows _get_unsaved_nrows _getValueFromContainer _get_value_from_container _hiddenNameRE _hidden_name_re _hiddenPathRE _hidden_path_re _indexNameOf _index_name_of _indexNameOf_ _index_name_of_ _indexPathnameOf _index_pathname_of _indexPathnameOfColumn _index_pathname_of_column _indexPathnameOfColumn_ _index_pathname_of_column_ _indexPathnameOf_ _index_pathname_of_ _initLoop _init_loop _initSortedSlice _init_sorted_slice _isWritable _iswritable _is_CSI _is_csi _killNode _killnode _lineChunkSize _line_chunksize _lineSeparator _line_separator _markColumnsAsDirty _mark_columns_as_dirty _newBuffer _new_buffer _notReadableError _not_readable_error _npSizeType _npsizetype _nxTypeFromNPType _nxtype_from_nptype _opToCode _op_to_code _openArray _open_array _openUnImplemented _open_unimplemented _pointSelection _point_selection _processRange _process_range _processRangeRead _process_range_read _pythonIdRE _python_id_re _reIndex _reindex _readArray _read_array _readCoordinates _read_coordinates _readCoords _read_coords _readIndexSlice _read_index_slice _readSelection _read_selection _readSlice _read_slice _readSortedSlice _read_sorted_slice _refNode _refnode _requiredExprVars _required_expr_vars _reservedIdRE _reserved_id_re _reviveNode _revivenode _saveBufferedRows _save_buffered_rows _searchBin _search_bin _searchBinNA_b _search_bin_na_b _searchBinNA_d _search_bin_na_d _searchBinNA_e _search_bin_na_e _searchBinNA_f _search_bin_na_f _searchBinNA_g _search_bin_na_g _searchBinNA_i _search_bin_na_i _searchBinNA_ll _search_bin_na_ll _searchBinNA_s _search_bin_na_s _searchBinNA_ub _search_bin_na_ub _searchBinNA_ui _search_bin_na_ui _searchBinNA_ull _search_bin_na_ull _searchBinNA_us _search_bin_na_us _setAttributes _set_attributes _setColumnIndexing _set_column_indexing _shadowName _shadow_name _shadowParent _shadow_parent _shadowPath _shadow_path _sizeToShape _size_to_shape _tableColumnPathnameOfIndex _table_column_pathname_of_index _tableFile _table_file _tablePath _table_path _table__autoIndex _table__autoindex _table__getautoIndex _table__getautoindex _table__setautoIndex _table__setautoindex _table__whereIndexed _table__where_indexed _transGroupName _trans_group_name _transGroupParent _trans_group_parent _transGroupPath _trans_group_path _transName _trans_name _transParent _trans_parent _transPath _trans_path _transVersion _trans_version _unrefNode _unrefnode _updateNodeLocations _update_node_locations _useIndex _use_index _vShape _vshape _vType _vtype _v__nodeFile _v__nodefile _v__nodePath _v__nodepath _v_colObjects _v_colobjects _v_maxGroupWidth _v_max_group_width _v_maxTreeDepth _v_maxtreedepth _v_nestedDescr _v_nested_descr _v_nestedFormats _v_nested_formats _v_nestedNames _v_nested_names _v_objectID _v_objectid _whereCondition _where_condition _writeCoords _write_coords _writeSelection _write_selection _writeSlice _write_slice appendLastRow append_last_row attrFromShadow attr_from_shadow attrToShadow attr_to_shadow autoIndex autoindex bufcoordsData bufcoords_data calcChunksize calc_chunksize checkFileAccess check_file_access checkNameValidity check_name_validity childName childname chunkmapData chunkmap_data classIdDict class_id_dict className classname classNameDict class_name_dict containerRef containerref convertToNPAtom convert_to_np_atom convertToNPAtom2 convert_to_np_atom2 copyChildren copy_children copyClass copyclass copyFile copy_file copyLeaf copy_leaf copyNode copy_node copyNodeAttrs copy_node_attrs countLoggedInstances count_logged_instances createArray create_array createCArray create_carray createCSIndex create_csindex createEArray create_earray createExternalLink create_external_link createGroup create_group createHardLink create_hard_link createIndex create_index createIndexesDescr create_indexes_descr createIndexesTable create_indexes_table createNestedType create_nested_type createSoftLink create_soft_link createTable create_table createVLArray create_vlarray defaultAutoIndex default_auto_index defaultIndexFilters default_index_filters delAttr del_attr delAttrs _del_attrs delNodeAttr del_node_attr detectNumberOfCores detect_number_of_cores disableUndo disable_undo dumpGroup dump_group dumpLeaf dump_leaf dumpLoggedInstances dump_logged_instances enableUndo enable_undo enumFromHDF5 enum_from_hdf5 enumToHDF5 enum_to_hdf5 fetchLoggedInstances fetch_logged_instances flushRowsToIndex flush_rows_to_index getAttr get_attr getAttrs _get_attrs getClassByName get_class_by_name getColsInOrder get_cols_in_order getCurrentMark get_current_mark getEnum get_enum getFilters get_filters getHDF5Version get_hdf5_version getIndices get_indices getLRUbounds get_lru_bounds getLRUsorted get_lru_sorted getLookupRange get_lookup_range getNestedField get_nested_field getNestedFieldCache get_nested_field_cache getNestedType get_nested_type getNode get_node getNodeAttr get_node_attr getPyTablesVersion get_pytables_version getTypeEnum get_type_enum getWhereList get_where_list hdf5Extension hdf5extension hdf5Version hdf5_version indexChunk indexchunk indexValid indexvalid indexValidData index_valid_data indexValues indexvalues indexValuesData index_values_data indexesExtension indexesextension infType inftype infinityF infinityf infinityMap infinitymap initRead initread isHDF5File is_hdf5_file isPyTablesFile is_pytables_file isUndoEnabled is_undo_enabled isVisible isvisible isVisibleName isvisiblename isVisibleNode is_visible_node isVisiblePath isvisiblepath is_CSI is_csi iterNodes iter_nodes iterseqMaxElements iterseq_max_elements joinPath join_path joinPaths join_paths linkExtension linkextension listLoggedInstances list_logged_instances listNodes list_nodes loadEnum load_enum logInstanceCreation log_instance_creation lrucacheExtension lrucacheextension metaIsDescription MetaIsDescription modifyColumn modify_column modifyColumns modify_columns modifyCoordinates modify_coordinates modifyRows modify_rows moveFromShadow move_from_shadow moveNode move_node moveToShadow move_to_shadow newNode new_node newSet newset newdstGroup newdst_group objectID object_id oldPathname oldpathname openFile open_file openNode open_node parentNode parentnode parentPath parentpath reIndex reindex reIndexDirty reindex_dirty readCoordinates read_coordinates readIndices read_indices readSlice read_slice readSorted read_sorted readWhere read_where read_sliceLR read_slice_lr recreateIndexes recreate_indexes redoAddAttr redo_add_attr redoCreate redo_create redoDelAttr redo_del_attr redoMove redo_move redoRemove redo_remove removeIndex remove_index removeNode remove_node removeRows remove_rows renameNode rename_node rootUEP root_uep searchLastRow search_last_row setAttr set_attr setAttrs _set_attrs setBloscMaxThreads set_blosc_max_threads setInputsRange set_inputs_range setNodeAttr set_node_attr setOutput set_output setOutputRange set_output_range silenceHDF5Messages silence_hdf5_messages splitPath split_path tableExtension tableextension undoAddAttr undo_add_attr undoCreate undo_create undoDelAttr undo_del_attr undoMove undo_move undoRemove undo_remove utilsExtension utilsextension walkGroups walk_groups walkNodes walk_nodes whereAppend append_where whereCond wherecond whichClass which_class whichLibVersion which_lib_version willQueryUseIndexing will_query_use_indexing ================================ ================================ ---- **Enjoy data!** -- The PyTables Developers .. Local Variables: .. mode: rst .. coding: utf-8 .. fill-column: 78 .. End: .. _by popular demand: http://sourceforge.net/mailarchive/message.php?msg_id=29584752 .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ .. _netcdf4-python: http://code.google.com/p/netcdf4-python/ PyTables-3.7.0/doc/source/_static/000077500000000000000000000000001416254111300167355ustar00rootroot00000000000000PyTables-3.7.0/doc/source/_static/logo-pytables-small.png000066400000000000000000000405101416254111300233320ustar00rootroot00000000000000‰PNG  IHDRÈSÚùHsBIT|dˆ pHYs¾¾ä ÛtEXtSoftwarewww.inkscape.org›î< IDATxœì}i˜\Guö{ªî½½÷¬­},y´X–¼Ë cÆ6Ë(bH"ã„å ° HÉÇHÀ &!ÆÆ6lcy_åM–­Å­³öÌôô~—ª:ßÛ-KV÷hf$o|~ŸG¤™êºU·ëTå=§ˆ™ñ»"" ø|øzŠyìEÖ+x™A¼Øx>@DÔÓÓ#×®]k—gþ6&ÄŸ]uÕU¢&8¯àL ¿sBDbýúõVWWWä6o>5æûßÁ¼žwírz{{çæü ž?üN-"¢5kÖH¥Tô¬¡¡ù' ]C@ˆùø+~þó739EèPˆÚzå$úÿÖ‹=€c…ºZ•Éd"Bˆ–÷Þ{ï7l­—Ü&]­®ð›ÞÞ^ @©¿ 6POOXô?ws»ûfz-Œ,€Ñwûòvuý;®í[×gøwÕ˜ûÿôRþ^6´/ý­û*exÿÿ^ÝÛ¨íºuëd[[[¤R©´|á׿þ«ÅÙì_7hf~»téiß=ãŒm}}}A£E]ŒÁÁAYþËëçóªì Ík›’é òÊGÕ/ð+‚ò»…—œŠUWe.¸à«÷£_‹¾û¶Ê»Þ}—ºçÛ±þº‘¡MD"‘HØA$.¿ÿþ×76öÑ&݋Ӈ‡ßFm4˜{ýÚ²eK¤ôñkÏåÕ£÷N)@| õóK=ñ/½½½¯8~Çð’Q±ê;÷úõëeõü÷µé…§þ),ç è:ÐHˆ÷=sêÛ¿ÐÛ»eœˆ 33‰ÞÞ^KJ?oëÖågïÚõÏÄ,›=']­¾»klìKkÖ¬ñë}ÔŸ¿fÍ+“ÉD"Ÿ½ÿ,³tü&Ǧ=~Û|оúç{z²=_'"ýÊIò»ý!"Z·nìííµwœñÞŠ—~ý_ôâ³wÂŽ~éá['8Ý}y©T²jkÖ¬‘ŽãDÓž×þŽM›¾ikÝ1Õó¤1s>ðÀoéîî¶PSßê'Gwww¤£££Å?úw3Žˆª˜wí}—¼â)ûÝÁ‹r‚Ôm‹žž±víZiýé7_CÑôGØrÞD@ÓÝ؉\‘<ñœo®ïê*nÚ´ ÝÝÝ‘X,–þëŸþtCÒuO›Îó;ŠÅËü´§§' "îííÑh4bÛvºü¾[ÞŠ„wî,§&D\}0šHÜJDÌÌf–ý¼‚—^p#ˆDOOXñºKÒyïê¥HüC,äšõQ¹ÂÿÁ‡‰D"ùW·Ýöž“÷íûÇtÁ÷ü9ßÝë6ïÞ½[-Y²Ä"¢”mÛóÊÿ£qkå”Mêà±1|üâ´å[¿(‡6mÚ¤^Qµ^ÞxÁTº*µvíZ{Ñ'þ÷÷J¯ÿÓ툥~8SᎷ]Fã‰D"ùŽ'žX³j`àó3Ž8îÔïú±y±%K–XA8D”2ÆÌƒ£Ît<‡ŒààU»Qá^ÁË/˜€lذæÍ›gµµµÅiߦt4}šyK¯¸üöÛ¿ ‚ųù|z2‹3ï¹ñœàø5wëK>ö{Bˆ6ê¬tAš£¶Ë¨ ’ɤ“Éd^qû¾ÌñBípäû¾ô}? ]ÑåV]\0­ ҳ鯚LÓ¿âÛó¢·~ãie"šcZ;Êåxg>ïd Ù)²C$iîþg0g`'¢ÕÒ!}œøØX°g+¯fÞ ½î[>·_ Ö<5soLZ>²¤_)eår9ÐyÅP™â¡¡!tvv‚™Éc !bÖS·ù¯þãY èy+OåXj* DB`,™ôÆ’ÉèÓ'¯¾ñ“I¶"Ú·LŒ`î@(,óöïÄœXñÄÝ\¼`Z,bçsx׬ç)ú3ˇ»Ç„ͯ¨V¿x¡„;:: 3\)e@Õ~æÞñà´7/æxÛ”±¦¶œüæö?Þ `‚™“R°#mnÏå+È·ÏE¾}.¶Ÿüšæ-¾S"÷{@Ûof>&¶n<ã6!„ ‚ÀD£ÑWNŽ—9^™™yóæÍº\.ûDTdæ€ °™´·Ý•?š¾ƒ¥g¯äˆèW+‡‚K÷½?¹+Q¹ì¢n“žgÏ®Gì¸ðŽ›ñ'­{VåZ÷Û~pÖº4Ë™Ûé΃?Ù%&‡¶Èר¸QIfް‰ˆxÌ\0 `?€]D´Ë²¬!cL€¿qãÆc¦^Õßgooï¬]Ç«W¯æ 6Ì:㱞¯³eË–CÆP;%§Ýo}.6lÀÁ}ÕÆ‡™ôõBà˜²yë“_³f|]w·õw×]÷'>B@¢áæ‚ÍåB}Ϲè–G÷ßQyÓßž6Ò~ú6eí~بó7ÛÞ¥÷ÌHˆ#×¼6oßu¢Òm £ÞëÖÇMzî´•õ̽£‘{®¾‹™÷Ñøw~ÿJžêùì²=æûÅb³=róX÷On_Qìë;6yêµ*-r}ë¯Û×Ä÷ m›Ës“§|u¤2“]Ëo‘ÿuâÏ?ê+ÏmÃÀŸÇ†Ó¯¼òÊ)u&÷É'Ÿ,ÿ¥ã› ¼ü¹mÓÇì«øë/•Tcâæ­§ÉöôôÈË.»Ìù”ee¾tÓϯÁ§› Œ€Äçýç}·^rʽ:vË×·ÅnúÒn{׃£0º`ß±ºhß±ª±uÜÅÖCK'en ½áKEk÷ÃÓ:‚Dn%rÿ5÷Q¿b§b÷4>FQ 2-²rn‡UZŸ±Šßù£yOÜù½o:Ç€ÉKDbíÚµv¹\Žm÷5‰ž,gœé¦×É8¶m'ÊiH e&ñÀØW]uUÓõTôL&-•JiÃÔ°­Ï–½~ýzIÔø÷/4f<ˆƒKà¬[·N^pÁÖÆ¾U‰Ü#ñónúƽÿç[ÿçškÞ6ñÀ.§¢.žö gqù½¿~Õª¹;rD´›™÷ T]ÆìŸœ›Kíî˜Ön2gw{iqka\ µÀ0)wüôòf®¾ëâûN8.Y’®\Tšì+í X]­Ï©%YI¶$+XÞUWÛïEÅ\oOþúÏ—Lütr.úö¦Û‡Æó‰ýDT`æ”1¦ : G”l·ÊŸyàÞKDÁ,tj …q!D‡g¬®#~b(’$"{ÅŠ.Õò‚›µÍd2ÂãH˜[6‘†ùþ†…´,+–J¥Š[¶lñÍŲ,iŒ‰2s+€EŠŸ .;1­u4™LVð z" u#ê¦ÿ{÷8ÎÏÞ/ŸIÄ™i÷Ö`†žÙCì‡tâ²7ÞñA[òW”âÜÕÛÀüøÐðóÝà?¯xd!.&ñ±ö·á—•â'…/c™s?þvÕ»[Åj½ÖºèÎÇNøä~õꇤ”­DÔÊ :KÂ!õŽ8iÓߎ®y²¶gôe;Ž#Œ1Ž”2©YLYŒbº0Ž1F …#άT*Qkk«BƹÕg«á©j@2‚ˆmÛ"›Í6ì7 „^À$€NÓDÅ X8ìZû¤nDU«Uç“ÿö¶;>óÇ¿îîh-ž=£tŸ]G? ÷gÂ\,‹‹Ò÷]²(©[Yprø£ àêKåµ¥ÃÖ oŒ ËíûÐ)÷@†X¥›ï=峿¸ûÔ;¶™YÑ”Çïæ^_"€»íëŒØ.§]–.ew,ÿÝݯûð¦M›S=Žß÷ÉqADV‰£ôŸ¹ó `e4Dœ6âí·64œ5þ=wá¤{鉻ûÛ¬ê39c)¥(•JGüf«Õ*utt!„¥µŽjH§Q;‘”Ò*•JMµçyÄÌ’™fŽqûW³°Œ1ÒuÝ—ÚžÕ7Ø–eÅ‹ÅDëg¿ÿ®;æwLì|÷îºléÂÑKMéî 1tôºùF}ÇfAø?ñ(ð?´Í›/Žáô“|q1\ ØÀ­íeËÜð„™ –Û÷&‹ñÉÞØóµ'wÍ{œÙ¶mKfóÔó}¤Ú]€MÕnye™úÌœŸ§µà‰D"ÒÓÓ` ¿Çaf6̬X•»+'ì!" @œ™£šy–øáÊñù9tˆ™·Ñ# ´Ö&™LNKX•R¤”$ƒš±®ÉsÄ­µ&)¥ "‹›ÍD¤u#áÅÁE°bÅ `3sÂqœv¥Ô¢lë¼æ-›ÏË–Þyáç­èZToo˜¸XŽ˜|ÑâÉ‚ÀDŽé´íeÙ:³5p Ïùžvu ¾vu!€?z³…w¼%†åï¬`G:ôàž7ƒÓã`ìéÌëg_›+$FˆXÛ¶m´ÖLD$„8Ò—4 ÌÝãw$Çu2Ñ!ßA™EƒîLNß÷M,ó‰( `­X^©¦Ë×ÿ4ƒˆkÌéáÚŸ|®eYº­­mF§3ãØä¸0ó’²/uXõ@ïûRJ)ØÆ˜ØÜdyîÂtî¤öXiyk¤²`bK2²{°Õûâÿû6Ú¶›É÷å‡Þ{”šŠûn ðŸ¿PøÏ_áü5ðöÇ{°è™$8SÂLÔÁG¶/ÉÿÇ Þçbœ™«Dä‘BL‹¢ÎÌÏÔþÙBDs<¶;Ñ@m°ÈÌíêê’˜™ÑɉDB)¥ªRÊq{)å3gÌ'¢…z·jÞ…­²5JÏ€˲JÌlß¾ýHã %K–Àu§çí–RR,£+VÐsÿÞÞ^ò¶g¾‚2–Gú„ùâÉÅÏX– ÂGDæ•íT9j?å.,ý¶rÊ3WD4õ0µ¸Î^fÎ)¥J–eUÇÇÇýùóçëÝ»wSoo¯Øõ©¶dÆ®ÌÝ.OÞµfCèH˜©·­FÆ’gDû“ok}dþÛ>¼ì"òQël&}5Ïs8\Ÿ<îîÈ ññã¶U:ö®ù§!w:ã®ÇõꜲ׷õ‹wÏݼdŸ×2´ê+ÙJ£>,¥TÛ»—ç¢î­=˜¦^OO|8ëÓÀ“{žýù€¿ðéLü`ü€ÚÚh]Ø=üõW]à«.Ú[€¿x·Ã—¾-fn{äU›w· @?3ï`æg„{<ÏË !ÊŽãXžç!›FGäA ¢qž1Æ"¢Ôp$!ËDFætXåû¥„´Þ±~ýûoÈårtZñ¦T,¥… €‹9µsb‰3öé›s'\#¥ŒX–5  2Õ«AÈ6+—Ë“ÑhÔ+—˦££ƒ7tþø”9ó+Ÿħx1:›\³¶1pמOµ~À´·ï„ðç}{á~i‘ΠùíùªÙ@†¾ADÿÓÓÓC ή_ÝX¿~½¸*ó“•þüçÄj>•ÀÝä©!m6P?›«W:⟶4Š9ÕC=K`}Ê|á“Öjs‰Ÿ ~b<«ÍÚÎÀC¹ÏÆ?ÝþÅê`]P„eYs~òôkÝÁbÛÃ3x4lú{ଇþüsO’™âNß›ÅçáœÓ€«¿ôß =|áë>­º8/§!"ÚÎÌOxZ)Õ_­VGªÕj¾\.{¥R©nƒ5»¥,¥¬0suªˆbΤì[ '€.:År¥R*šN§“óEöT4Ù¨ :²ÓÔèöB šjŒ€/¥ÔÑhTüËqÿûyNénIæ-^rг¢>U€?x\´ððø§#gåóùi)ƶHÏ9¨¯Ïu„ù‘¥üd&“¾ïÏX?ëïïkÖ¬±8_û_˜kçï—dÞY#9Öí_Iàåüö˜ô¯”^·nÝÊ Ñ³=ÿvÍè¼O/¾ë&Gè+ø<ë”>Q€ßÛjU*.òê:LQ'3/ù¯Íçm1L3 dÅ€{¿œÒ³?3Þà™fj€Ÿ¸a‚ÅL±x!ðÕ›¯J[Sîxï§€ã/œÅjÞûµkí°•ˆ¶i­wk­Gâñx¾¿¿ßëïïWÉdrÚ.¸Ð©µ1FÑT„HÞ\Y@Bˆ®Š‰4t„kˆ•ÌÜ*„è´%Ù¬£­Þ¢aÛ¶ëv‡Á ~f6–eq©Tår9òåãn|OZTþ™B¯Zó¹—¶9Þ­µ²Ž”òhvd“þ»o¯¸q­mÛb&îÛb±(º»»­•+Wƾ¾ü7ïMËê¿R—öAˆÚ¤ÿù?Oüù_Ö™¢N¶\°`Aâüäö>›ô…S˜ÞmCŸN.%"!¤”Q"jÝ™›Ý4Ô=:Ý ÔaE€ß~¸ä  »cÎpj¾Ð&˜ðe½·¹#!™>ö^àÞ¹G=ìÞ|üŸ€“Þ$N(ÙüóK—ŽÒIû J©}²®ë·oßîoÚ´I¥R©ëÊDÄÏz½ë¿.Û•ª±Ú™yɨJ7T‰¼\ÑÉÌ ›†ê¤®ÏŸ^´mÛ1ÆPÍU:%˜™‚ °:;;£‰D¢­Ó*}|Ús¯iÙÿ¥”¥µ>ª¨vÊ > ¥´-Ëš–€c(‹Yét:–ŠÙívù3y^DèO½-ótº··×J¥RN2™L¾¯ýÎ R¯šnm¶ûÑÞÞ^Kc|fVĵÛÎÉÚšñB‘pÓ'·ti@¡€,ðçïŸø€ŸøCóü-[·Bﺸù[ÀȽ€Šyà¯Î}Ðz0Sò‹·¿ýþ µÖù ŽQb“EZ¤¤ÛpW.™h©F|œ? ³’-ÒÇI)3æGE°¬Q›ª±Ç<ŠÅ|ß·§K'"aŒqÇI_1çÎwF(8,ÿb*$¥÷gkRƒÄt²R_:÷ÉîZ$}Ê~8ÌQ’ÑhÔÑZ§/ï¸ýR‡T÷LžGàyoêÜù—ŽãD“Édœ™;Nˆ ÿÅÌÆ¬ÿxýq[æfÎÑ3ÆÊñâ{OœUØMØÀO?üñs|a€ðÇÞàOŒ¾Úïê8ï4àš/»o¼~@W€ÿ¾¸ä `ι€œ#Ü2=Ì›—ë©§ŽIß÷õÑ$5ÕvUÉÌò’äã'ÆÈoÈ-*™háͺÉýA{ÃgEHÍÐBˆLR¸ µ)˜XV)5má!„EDqcLG‡,ߨbÉûƒŽ†A '^•Øuf­Ÿ)ö¤NèaÕÚÌÇ"[#g!äbMÙP;µBˆÎ6YîiÔNCð.NUqãr³6ôE‰D"á8N‹eYóÚ­Rëôª.- ª¶Ã´{'NŽ ¼É2Æä„#5ÿú¼_>sÆð«mKÆí™UdWHDïÜܼ]÷Bà#—¿X²ˆ-DÈ} ¥i½ôÒ»{ï¿ÿ ßÂQ°E¿¼à§Ÿ`“Þʘðç4k7´ï‘Ùë·7Ô$ l-Œ.ÞêÎsR²Ú02>©ãY!k­œ~ b[‘$¢N‹tÀâÍ¥SÝ‹gV>Ùy8Þ=LÈãÂ_R˰ÜÄy°ÄçF×]cñ†¹?M/°r‡me6é…5âå”§ƒ„eYIfžc“nÈb¾>¿¦ú«Òi•×Ä·©?i»ó°Õ#ÈtcZ-ËJuÙãËø°¹MÔ|oâBoUt@ýŸŽ_6*7»Äò}¿ „•Rcæ•|§ó–]§Ì}늇g•AøöŸ‰ðO×éð¡uÀ[/NX¤ºêÀ‹J4èì,ÿ€ïφUÇ«pÞ‘Úxl{×Ϻ‡ˆÊÌœ: ŒÆS_`/î™rJ¸ ÑœNqX™Rk­Ù²Ž¬ 3³#„H茉 aÌ$«Ò3ó¨JóñÎác\Ljð÷>fuZyl»Ë}~‡n" ˆ(r$5‹C^—#„°˜9~Ãq©6€PMÔV3ŸˆÚŒ1-óœâŠFmŠ&fˆHOêxÃSYÀt f.K)Ç1ƒŒÞÒòPÞ‹ÏžT%€¯¼ ¨lòyà‹ÿœõ }:@xÑY8¶­W~éK·÷Äb±¦pÇ›ªÝçu|Œ™KÌ\qÙ.t¼¡Ò)‹ ³ÇP¾Ó¨nÝ BLÛ-„p˜9!„h ¿á†'‰+ŠDï’?Ÿ™©T;+„™T9"nØCzN½!šk‰5f°Ca‘ó¶„ðšŒÛŒ¦¡óSÂÄçËñEÚÓTnx ÕÆ=Aà†}Xdˆ \¥T¡VépÀ¯­ñ›Ÿ9mv%AÀ v±¼d ß,\X¸"™LÚÏ×ÕÍUãø×ÖÜmŒÉQ@ @uL§ªY)Qíì”ù¦‘ñ]^f€Àc¦+ ³<ãÌœ’0 m$ :`L qN2cµ~šªF&`æ}F$¸Qº–mcÌ‘T,ª›ˆÒïk±Ig pÓušª]‘ŽPÐÖxܬŒHj•0í¢¿¿_Y–U‚`‚ˆ뤶;÷®ÎVÒ3¨¹ƒ0tµÀHíÿ€YÔ{¾¡µ¨æó±étÚéïï?æ‰9ûƒŽòWÇÞ|Ó¤N ŒfjjV5«R ]½Q´$„×°0ƒf¡¶ù ™90Æ­õ´¤å0s¬Ù±mSM@ˆžl !ˆÈ2ƈfûˆ$V¨i¦I?$ºY¦F=±ª6îÆpH’0MD1ʼn(1© ßW‹¬8Ô8±y.‰¼6fXÖ¦M›tww·ŸH$òÌœµ,kÀþ‰Ö®ôxsÖ(#Ì4ið» €¥n°!½îE@HÜ{ï Å›o>}ÛÄDüqž1ÚBˆd29«ä‘êñ@<¡f h T»;´+XVeÙ[KTÊ1³U?A†UKÃ/J~<.ü†‹¾h¢£ÌìY–¥„3‰ö‹šQÜôûÄAHIiìîæmq¤8ˆPaf—ÐØ–ÂcÌ”RdaŒ±0[Y@{\B㓯ÖÊbfÚëw––ƦÞgÆv.¾³¼róƒúµÕb‚Dô¸1æA«–€c.»ì2ßó¼‚bÔ²¬fžg˜;¯Ûvæø‡Îúõa;¾„½W‡Úu#Ô•‰¹~ àmͧò¼@ûëÐßè{ÃðÄD"KDCÌf’™Ë¨éó‰Dãã÷HøÎÄë'*•>B"@@à½Æ˜íD´›ˆF´Ö!DÌSBT‚¶\£þ¢"ˆÄ…ßpqL|˜™=cŒªÓG¦;ÎZnFÓYc!›æ\2„£Æ¬g…öß´ÙtƒÌLá‰ÅM7¯Ó¢{Vε&DI5-fQqU¥vøó «"ûs¡¯ˆ -?5ºû™ÇÝ%ãu/!Bµ¸ÄÌžUëÈ\pÁjáÂ…U­uNJyày2»¸mûÄü¶íC‡wO-âmÛçóÛâ5ßëÒ`€§œØ´õ±ƒA˜Xr'ð“WLäû Õ É]Bˆa­uÞ÷}/‚Yyëˆh˜™Ëµ“¡ WûÙ>"Ú­”Ú§”šˆD"^hW‹*U‡‚¶IÅ‚-:4E5F¾#¯¡*0¡’ƒB_J©ªÕªñ}ŽÓTû8µ@¨þïÉ×l0—‰bãÙ¤Ç"Bm÷ç¼)õèŠÎPS^B¸‡Nµ¶ 5•¯©tÌtbæ)jEd謩mÏŽŠB៯J)ë»á3oŸ3ôÄèq•¼×_ðÙwœ1Ì@nŸê,z|„ä¢ú)Ò‚‹ù‹c3ùðÀ·\ ¦Ä¸Ûð43?à cÌSD´K)5*„(‹E¶t"zÒ³ ÀCƘ‡™ù1"zÚ³›™Gòù|¡¿¿ßSZëà ê¹;¦R‡–6)‘–Õ†‹t§?w/3J)cÛ6O7’nŒñ‰¨`Œ™ì!¢½~GúþíüåÛæZù†Ô–ç aV÷  ]Î%y*þ4n#K0Ç*¬øD憳™y mBì‘Rj|÷õõ™µk×úBˆ¢R*+¥`æN"Jö=õ*ËÓ6Mºñ*ÂsÁ1L-{‚Îô g¨ùy_Á³WÜ,p?€Gœ~t“?€­ hÃgæmÙ÷CRÊÉr¹\¶mÛÏårÁÆgÍÅ2Æ<ÃÌ{µÖDT6Æx®ëzÕjÕC¸këM›6™5kÖXmmmš™"r¸Y®‡ùæ;eñ0Q,½þÜæ PJ×uɶ§½Ù»Ì\`jªC®SæçüEû-oZhǪݱˆI)ÁaŘæ6Ñ1 II¯æbgæ‰'½®‘[J§´^œ|¢!±éxgô/o¿íº:ï.cŒ[,½C„™yݺu*—˹étzÒ3(¥lafg¸”.D¨"Tž’ìò2sV8CíMGyð^™0!¥÷x4­Ç1-ì@(S¨¡£ºe»1f€jµ:®”*g³Ù`ãÆGYf_1êºîp4Èår.BUM8„¹nÝ:Ca1ae“jV¥¦lR‡­Ž¢‰Žð,ËR‰DÂc¦½ׄc@Q)Un±üòÇ;oü“6Ù8²ÜTW‰ŽnñÒ»âHý!ˆˆ¦UJhÊ~ «òƘlÍ®ÎôåÏI§EÅ>'þL#öµ8=¾÷˯kÝóš{JK«Ñh48ÌÖ××gz{{}E)å(3Ç(,W3LD\Û ÓC;ý¹‹4ç†Å–¸!÷ý2Ì<ÑP0öOÑ& `ð›ÇO¹™ˆÆµÖy¥T!½ý˜\K`Œ©"´¨*ÙlÖo&t}}}èíí5–e)˲\cŒ;´NN÷9EafOk]¿9wÚ <ùA”…,¥ÔŸšó‹OµÉòâiO²†é,êc…f±–çâæÒéîÞ #tj0 ³ ø’U.&‚ý-²ºsHµŽ3s¡­@€ôÕ“=‘VY™Bdð0ÍÇ"Óýîö>wOiéGûúú÷×N](ª---9­µRVˆ(^‹àRJÇSB¤·û  ÓrÑ,nõ\I tûîE((¯žÎ먵ÿ-Â`d#ó,Ð TüHqkvÞn!¨\­V½þþ~õðÃÓ» a:¨ :‰è#ä”°ëº&‹ÌìQuÐÞÐÕÛy6ÆxA(ß÷ïûH§§Ç#å07[cð7s¹.cR¾ÐKñ¢×Ø™úý9Õ'ÜÅ c’¢bЂÈ%6Y[˜½x[«Æ)QQácâBˆ43§Sô»¹‹­Ov^7wŽ•?Lâ¿üK ®½nwOÏobúúúLOOOFK–ef.3³ó•ˆƒ8 *ƙ֭î+×8³±b)ÂÀâož(MÉâ ¿Exr4B@Â쫃Tó±Rr€ˆÊDäAtww¿X²pGG‡B¨š¡^ÍéD¡jþÕ¥1•Bø¶m«|>of˜ù(mÛv˜9:Ç*¼¾Q ë g ­ yNü™—D=Üé@BO2sv±3û›Ì/N>èWËœ ßÊö¬}Ä]ZÒZ—‰¨ÎïJ0s´¤û—XŸì¼.Þsç-æØ¥ïœÙeŸÜð…03oܸQïÛ·Ïcæb¡Pw]wÔó¼1×usALr˜h5  °Ùíj®24*-GY×U­FKwÀ|‡ !<…Îð„¥Óžc·Žæ[w3s%¡]ð¢Ñ&Ëå² ‚@ÕNГՄ´ø\ «ÖýB_¡£ÑèŒæ`Œq˜9•þ‚”tW5js}áÌꯊ§º< êÊK ©a;m¨¦ÊvLWkíyžWª™ {µÖ;‰¨ÀàpžøAîu OsIæø-|ð¦¡üZ„]oذÁlܸñà%nÍŸ?_F"‘’1¦ „(\_8s×I‘ý í‰Æî• €çjÝ…`ÀFõTúÑÚÿŸnÐOÀ"„jÔâdû&Úw¨H)ýR©tÄϘ™Ï<óL>í´ÓÔA†º;¦RÕ.{¼ùí[5<íÎß ÐZ›D"Á¾ßŒ,ÛQcŒ|ujǹ¦á»«¼2€ˆÙÁ1ÉÙ<:Сøƒj”™÷X¤šWvamˆHbáí æGʯ*šx*g’“:ÑZ2QëIwQñ w±=©ã­­²rØÜÛm÷Sh®éÕ|å•W Ðôôô ¦ÓU^t™S“ÿ‘ëþdæú.»Q*(Â…¾!ã7`;ÂxÆs_ÑœZÛéÑ&îíÚJD®çyjttô˜æ³E*•b)¥‚À·,«îê-ã3òت mã@h OLLðܹs§=)e”™m²Ô0a¨j]áèÀ– NáyeË1Œ1,„8"ÑÒÃt„ ”T@^ÐáÄÏýÇÖZ;]ÑÂùVåÿÎE“Â}d_¾#÷¸»xîëOæúµ…>cÚ:'×°qãFS(”ã8•šŠ5ÆÌãû‚Žìõ…3kVÞìx„Æ5¸À“xV8¢m•óœŠ GÉ‹æ‡&ÛG™Ù«V«³ªXr,‘ÉdX)eê±"ªé†ëƒQ2±±šÝø¾?cÊ#¢–€eÃZ/¤kÁàQÙ„¦þBcºû˜$ˆˆ¢©PW´•"¢Ö¢‰7œ¿Cº`b›· !Ô‚Y4£Œ£Ñ¨ò}¿ÊÌ9cÌBe){kåÔá­Þ‚üfAh`×Aq§x-B5l{Úx)µ@ÅãÐ7n|Q¤¯¯ƒ 0–e©éÐVI‹£ ££RJ/åºî´¯,¨£–S‘P,¾E6Ì<,„hšr¬ÀµškZÏÌ‘x¯¯­™šÖʪp4mYÖ¼Á ­¡€H2>M"¤£"nœ‹;êä·jµêÕ õa!Ä^ûáìÕùžÑЉúENU óx„Fû„÷˜¢âìô0’OïPñ<ÏÇ,¹VÇ®ë„Å\!Du¯ßž;Ò ò:>ÄÌžã8ª£cz™>¶"Z0ñÆ"°" 8UéÔc*;´‹#•u=%iht1DdßTÅ ³GÍ<ãÏk,DáÝ+e7¬êⳕ›•[¯¯¯Ï ¤SµÖ£Æ˜}æ@ N¨döò¯9T}˜J@l„‚±@Óü±™aÏxçv­uÕ¼˜úAàR©d”RŠ™=f®zl—óMòÓëÓ©­µ_.—u¹\63¼ÓƒHcŒ5¡â Yʬ¹YóÃPó™1•¬8œÛtn|ÇE³˜ÒaxU|Ǧúý jffs$!©%nÕ«jT·4Lè´Š‹ßÕz_SÚLNdz¨e7*X4¤ZËó­ÉCÜê«°ðo:¯ÿ£f}duzϬ¤'Éd2€B[[Û03'™9 ]E4~õdOâ#¿l'pèê=J™('yÛð|½}xA°ux¡;QJÖ)ä%f®We%¢I¥TµP(¨n¸áE?=€Ð“ài@µ–ŸÞ.;Åq,0û¾¶ÜeOð{ZïNE(8lw—O¸&þ ˜ â²1ÆBLù˜µÖ¾Â!rƘ‰>©b»„û˜ÍB¡È !„wPhV-Ñ«F{Õ|k"qœ=n/²Ç­ G; £ºÇïÈLb'ƒnô‡ság f6ëÖ­ ¢ÑhY1nŒÙoŒII)“"?<×ýø¬¤aù½…7µÖíˆ"ÂÒ9…: ²vLÖ^bæJí3®Rʳ,ËB5µE;Ž£s¹œ™mžy$a¥”ÑZDTý÷Ü…2ób"J"ä%0óSÌü0i˲XMÁ•›ê]µ‰¢µ®õþ†·IUL¤twåħ…€/„Ðuã<“ɰã8ÌÌÆøo=JD]Ìœ¬©TÞ&¢ÇŒ1åÚÏ"Z‰(a@´+˜[í÷笮Ð=Õ•¥»+'X4™yŸ1æi!ÄN!à‘û`¥{çCÕ¥º¶ñ‰ÐÞ&Y4Qç1w‰ý˜»„jjTa™ÝöcFf5Ú|"ªþxò5# Ç)LYÞ-„x Àc@àú¾ï9Ž3 `"aŒ‘Dä—8Öú´·0ú´·PÖœ~mýŒ¼Ë³‹™´ÖãŽã”<Ïóljh1ÆÄ‰ˆ ÃÚSA»àà(QX‡¡¿6î¡c* @@\³f¿fÍšb£–eÅÄ8¬¨ç‚Dªfë¹æ˜°#R›Š%"*+¥ªµ˜†'¥ô™9(—Ë*‘H¨\.g²Ù¬Éd2\£³ÏÞÕ V(¨å‘LÔv?ÁÌ „2FDY­uѲ,wºñ "¢žžù©UÛ;—Çî]¡àÍ)á½Ê"Ý0»'ÈlcæªRÊŸ˜˜Ðõ+›ëÙ‰‘HÄ'¢2ÂtÚ¡Ú”Ìì#äÆe™yRk]`æŠã8ÚcS¸¢*3@Ø"5"ªMDvÍ[U0,„Cx’ËÌ63 Ì΋RX1…ñlýßH­ˆrÌ<ÈÌýRÊÝÌœÕZ—¤”1¥T @N1ÌaA»€Jmqæ8,¦^­Ýýh¤”V]8ªLµ‚]SŸÜš“fX±›™û Ñd¹\öŒ1”ÒB8µyÖÓ[z.…· !v#L°QJ©€ÔU­9s渙Lf’ˆ9 Ö(„Ÿ ÔOB]HJp™ˆªZëêÁQ*•jùÞuøáx Ó1²3XJ©Ô9fûk÷óQÔ£…y"Ú/„UJ•µÖÁöíÛ§öëׄ£««+rbìáwtZ¥¯i »Ç=FDeÇq66VŽF£ ¡»˜Œ1¨©[y!Ä Â%RûŽ\SsGµÖCBˆáR©4‰DªÇT@j/ܬ[·NU«Õ 3G"Ô<õ—ajùÜecLµ¶c¸žçùÌÄãq•Ífˆú QÿRõ¸pME«¦Óé1˲ÈS`æÂÝUQ•Ã;ÇLj¨hÛv=½!ê‘Éd¢ÌœšÔñt§5õ^»‹·ý¶´ú­uÙã?ÇUÍmmmÊ÷ýŠbŒ™É²¬¼Ö:Y£1¦ÊÌ9Y)å¤1ÆÓZW84ØKƘfŽãÐ3!„°´ÖõSFQX/§µ.X–¥Œ1“µ3Æ0Â…gdXÇ'jŒq,Ë‚R*¨nyfÎ ! ¥RÉM$ì8”Rã„”²„ÐNµžÎEf÷}"‚ÊÄÄ„·lÙ2LLLèH$X–UD¨â¦˜9*„°jÑv¯¦¢Œ1y)e¡T*U‹Åbpà 7˜žž ”N§µRÊ‹D"9¥T‹eYqªÕþ­¹ Ë-oŒ)F"‘J¹\>¼hñ@½ðƒëºÅÚQ\¶,kDJ)}ß7µ 9ßu]_k´¶¶ÙlV—J%ÓÕÕe²Ù¬yâ03_uÕUzß¾}n[[Û$3û5U¡~§c”RªjŒ©D£ÑêöíÛ0¥ïš2™ŒÝÒÒ’™Ô‰)Iû»ü9߸èWÌœ³,«¤µö¦ÊÔÇ8>>^-‹¨yÃÆîþBÁBˆÀ÷}Wk]±,«šËåT©T¢d2éÇb± € Çqœƒ/éÐZ“ÖZ2³¬]´iˆÈ—RºBOkmlÛ¶<ÏËÙ¶-kùšC²ŸPJYRJK)…šmæc<˲Ül6ëÐår###ÜÞÞζmÕ¬H¶mSäjß÷Ýd2©;::Øó<@•J¥ªã8“RÊB¦PJ±eYÊ÷}?/‹yƒƒƒ~2™TõâDĽ½½<88hæÏŸ0sɶí1¥”SSýADºÞG[[Û1÷õõz¾ÖQx|©T²:;;­X,fù¾OŽã°eYú`ÈårfõêÕ¼aÆL šyÆ 488(s¹œ•J¥,˲ÜñBè±±1•L&Õ‘œ\pµpáÿkïÚuÚ¢è¹û°¼6Ɇ‘"A)M:GéÓ)RŠü û(_@›ÂM$Ú&ÕF#aá„,ÈYl²¯™ûÀ&9Æ>•µë{|gfgæÌëêj.=ÃÝbÖA&ˆýý}Y­VùÜÜ\? ›8Dšm¼Œ4Ó#"WJùKJyªªêoÏó.D٩ͦŒ¿7\3鯹IEND®B`‚PyTables-3.7.0/doc/source/_templates/000077500000000000000000000000001416254111300174445ustar00rootroot00000000000000PyTables-3.7.0/doc/source/_templates/layout.html000066400000000000000000000006171416254111300216530ustar00rootroot00000000000000{% extends "!layout.html" %} {%- block extrahead %} {{ super() }} {% endblock %} PyTables-3.7.0/doc/source/conf.py000066400000000000000000000147571416254111300166240ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) from pathlib import Path # -- Project information ----------------------------------------------------- project = 'PyTables' copyright = '2011–2021, PyTables maintainers' author = 'PyTables maintainers' # The short X.Y version import tables as tb # from packaging.version import Version # version = Version(tb.__version__).base_version version = tb.__version__ # The full version, including alpha/beta/rc tags release = tb.__version__ # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.3' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.mathjax', 'sphinx.ext.inheritance_diagram', 'sphinx.ext.extlinks', 'sphinx.ext.todo', 'sphinx.ext.viewcode', 'IPython.sphinxext.ipython_console_highlighting', #'numpydoc', 'sphinx.ext.napoleon', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { # 'sticky_navigation': True # Set to False to disable the sticky nav while scrolling. 'logo_only': True, # if we have a html_logo below, this shows /only/ the logo with no title text } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # Add any paths that contain custom themes here, relative to this directory. import sphinx_rtd_theme html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = '_static/logo-pytables-small.png' # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'PyTablesDoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. 'preamble': r'''\usepackage{bookmark,hyperref} \usepackage[para]{threeparttable} \DeclareUnicodeCharacter{210F}{$\hbar$}''', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('usersguide/usersguide', 'usersguide-%s.tex' % version, 'PyTables User Guide', 'PyTables maintainers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. latex_logo = 'usersguide/images/pytables-front-logo.pdf' # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. latex_use_parts = True # If false, no module index is generated. latex_domain_indices = False # -- Options for Epub output ------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project epub_author = author epub_publisher = author epub_copyright = copyright # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # -- Extension configuration ------------------------------------------------- # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} # -- External link options ---------------------------------------------------- extlinks = { 'issue': ('https://github.com/PyTables/PyTables/issues/%s', 'gh-'), } # -- Options for autodocumentation --------------------------------------------- autodoc_member_order = "groupwise" autoclass_content = "class" autosummary_generate = [] PyTables-3.7.0/doc/source/cookbook/000077500000000000000000000000001416254111300171155ustar00rootroot00000000000000PyTables-3.7.0/doc/source/cookbook/custom_data_types.rst000066400000000000000000000065161416254111300234060ustar00rootroot00000000000000:author: KennethArnold :date: 2009-07-14 21:51:07 ================================ Using your own custom data types ================================ You can make your own data types by subclassing Table (or other PyTables types, such as :class:`tables.Leaf`). This can be useful for storing a specialized type of data or presenting a customized API. Submitted by Kevin R. Thornton. :: import numpy as np import tables from tables import File, Table from tables.file import _checkfilters from tables.parameters import EXPECTED_ROWS_TABLE class DerivedFromTable(Table): _c_classId = 'DerivedFromTable' def __init__(self, parentNode, name, description=None, title="", filters=None, expectedrows=EXPECTED_ROWS_TABLE, chunkshape=None, byteorder=None, _log=True): super().__init__(parentNode, name, description=description, title=title, filters=filters, expectedrows=expectedrows, chunkshape=chunkshape, byteorder=byteorder, _log=_log) def read(self, start=None, stop=None, step=None, field=None): print("HERE!") data = Table.read(self, start=start, stop=stop, step=step, field=field) return data def createDerivedFromTable(self, where, name, data, title="", filters=None, expectedrows=10000, chunkshape=None, byteorder=None, createparents=False): parentNode = self._get_or_create_path(where, createparents) _checkfilters(filters) return DerivedFromTable(parentNode, name, data, title=title, filters=filters, expectedrows=expectedrows, chunkshape=chunkshape, byteorder=byteorder) File.createDerivedFromTable = createDerivedFromTable if __name__ == '__main__': x = np.random.rand(100).reshape(50,2) x.dtype = [('x',float), ('y',float)] h5file = tables.open_file('tester.hdf5', 'w') mtab = h5file.createDerivedFromTable(h5file.root, 'random', x) h5file.flush() print(type(mtab)) mtab_read = mtab.read() h5file.close() h5file = tables.open_file('tester.hdf5', 'r') mtab = h5file.root.random print(type(mtab)) mtab_read2 = mtab.read() print(np.array_equal(mtab_read, mtab_read2)) There is an issue that the DerivedFromTable read function will not be called when the file is re-opened. The notion that the H5 file contains a derived object gets lost. The output shows that the read function is only called before the function is closed: :: HERE! True Closing remaining open files:tester.hdf5...done I ran into this because I wanted a custom read that returned a more complex object implemented in C++. Using pybind11, I'm easily able to write to a Table via a record array. I was hoping that I could read back in, construct the correct C++-based type, and return it. The example seems to suggest that this is not possible. PyTables-3.7.0/doc/source/cookbook/hints_for_sql_users.rst000066400000000000000000000642031416254111300237470ustar00rootroot00000000000000:author: valhallasw :date: 2012-06-18 10:15:15 =================== Hints for SQL users =================== This page is intended to be **a guide to new PyTables for users who are used to writing SQL code** to access their relational databases. It will cover the most usual SQL statements. If you are missing a particular statement or usage example, you can ask at the `PyTables users' list`_ for it. If you know some examples yourself, you can also write them here! This page is under development: you can come back frequently to check for new examples. Also, this is no replacement for the `User's Guide`_; if you don't read the manual, you'll be missing lots of features not available in relational databases! Examples in Python assume that you have imported the PyTables package like this:: import tables .. .. contents:: Table Of Contents Creating a new database ======================= RDBMs happen to have several syntaxes for creating a database. A usual syntax is:: CREATE DATABASE database_name In PyTables, each database goes to a different HDF5_ file (much like SQLite_ or MS Access). To create a new HDF5_ file, you use the :func:`tables.open_file` function with the ``'w'`` mode (which deletes the database if it already exists), like this:: h5f = tables.open_file('database_name.h5', 'w') In this way you get the ``h5f`` PyTables file handle (an instance of the :class:`tables.File` class), which is a concept similar to a *database connection*, and a new :file:`database_name.h5` file is created in the current directory (you can use full paths here). You can close the handle (like you close the connection) with:: h5f.close() This is important for PyTables to dump pending changes to the database. In case you forget to do it, PyTables closes all open database handles for you when you exit your program or interactive session, but it is always safer to close your files explicitly. If you want to use the database after closing it, you just call :func:`open_file` again, but using the ``'r+'`` or ``'r'`` modes, depending on whether you do or don't need to modify the database, respectively. You may use several PyTables databases simultaneously in a program, so you must be explicit on which database you want to act upon (by using its handle). A note on concurrency under PyTables ------------------------------------ Unlike most RDBMs, PyTables is not intended to serve concurrent accesses to a database. It has no protections whatsoever against corruption for different (or even the same) programs accessing the same database. Opening several handles to the same database in read-only mode is safe, though. Creating a table ================ PyTables supports some other *datasets* besides tables, and they're not arranged in a flat namespace, but rather into a *hierarchical* one (see an introduction to the _ref:`object tree `); however, due to the nature of these recipes, we'll limit ourselves to tables in the *root group*. The basic syntax for table creation under SQL is:: CREATE TABLE table_name ( column_name1 column_type1, column_name2 column_type2, ... column_nameN column_typeN ) Table descriptions ------------------ In PyTables, one first *describes* the structure of a table. PyTables allows you to *reuse a description* for creating several tables with the same structure, just by using the description object (``description_name`` below) or getting it from a created table. This is specially useful for creating temporary tables holding query results. You can create a table description using a dictionary:: description_name = { 'column_name1': column_type1, 'column_name2': column_type2, 'column_name3': column_type3, ... 'column_nameN': column_typeN } or a subclass of :class:`tables.IsDescription`:: class description_name(tables.IsDescription): column_name1 = column_type1 column_name2 = column_type2 column_name3 = column_type3 ... column_nameN = column_typeN Please note that dictionaries are the only way of describing structures with names which cannot be Python identifiers. Also, if an explicit order is desired for columns, it must be specified through the column type declarations (see below), since dictionary keys and class attributes aren't ordered. Otherwise, columns are ordered in alphabetic increasing order. It is important to note that PyTables doesn't have a concept of primary or foreign keys, so relationships between tables are left to the user. Column type declarations ------------------------ PyTables supports lots of types (including nested and multidimensional columns). Non-nested columns are declared through instances of :class:`tables.Col` subclasses (which you can also reuse). These are some correspondences with SQL: ==================== ========================== SQL type declaration PyTables type declaration ==================== ========================== INTEGER(digits) tables.IntCol(itemsize) REAL tables.FloatCol() VARCHAR(length) tables.StringCol(itemsize) DATE tables.Time32Col() TIMESTAMP tables.Time64Col() ==================== ========================== See a complete description of :ref:`PyTables types `. Note that some types admit different *item sizes*, which are specified in bytes. For types with a limited set of supported item sizes, you may also use specific subclasses which are named after the type and its *precision*, e.g. ``Int32Col`` for 4-byte (32 bit) item size. Cells in a PyTables' table always have a value of the cell type, so there is no ``NULL``. Instead, cells take a *default value* (zero or empty) which can be changed in the type declaration, like this: ``col_name = StringCol(10, dflt='nothing')`` (``col_name`` takes the value ``'nothing'`` if unset). The declaration also allows you to set *column order* via the ``pos`` argument, like this:: class ParticleDescription(tables.IsDescription): name = tables.StringCol(10, pos=1) x = tables.FloatCol(pos=2) y = tables.FloatCol(pos=3) temperature = tables.FloatCol(pos=4) Using a description =================== Once you have a table description ``description_name`` and a writeable file handle ``h5f``, creating a table with that description is as easy as:: tbl = h5f.create_table('/', 'table_name', description_name) PyTables is very object-oriented, and database is usually done through methods of :class:`tables.File`. The first argument indicates the *path* where the table will be created, i.e. the root path (HDF5 uses Unix-like paths). The :meth:`tables.File.create_table` method has many options e.g. for setting a table title or compression properties. What you get back is an instance of :class:`tables.Table`, a handle for accessing the data in that table. As with files, table handles can also be closed with ``tbl.close()``. If you want to access an already created table, you can use:: tbl = h5f.get_node('/', 'table_name') (PyTables uses the concept of *node* for datasets -tables and others- and groups in the object tree) or, using *natural naming*:: tbl = h5f.root.table_name Once you have created a table, you can access (and reuse) its description by accessing the ``description`` attribute of its handle. Creating an index ================= RDBMs use to allow named indexes on any set of columns (or all of them) in a table, using a syntax like:: CREATE INDEX index_name ON table_name (column_name1, column_name2, column_name3...) and DROP INDEX index_name Indexing is supported in the versions of PyTables >= 2.3 (and in PyTablesPro). However, indexes don't have names and they are bound to single columns. Following the object-oriented philosophy of PyTables, index creation is a method (:meth:`tables.Column.create_index`) of a :class:`tables.Column` object of a table, which you can access trough its ``cols`` accessor. :: tbl.cols.column_name.create_index() For dropping an index on a column:: tbl.cols.column_name.remove_index() Altering a table ================ The first case of table alteration is renaming:: ALTER TABLE old_name RENAME TO new_name This is accomplished in !PyTables with:: h5f.rename_node('/', name='old_name', newname='new_name') or through the table handle:: tbl.rename('new_name') A handle to a table is still usable after renaming. The second alteration, namely column addition, is currently not supported in PyTables. Dropping a table ================ In SQL you can remove a table using:: DROP TABLE table_name In PyTables, tables are removed as other nodes, using the :meth:`tables.File.remove_node` method:: h5f.remove_node('/', 'table_name') or through the table handle:: tbl.remove() When you remove a table, its associated indexes are automatically removed. Inserting data ============== In SQL you can insert data one row at a time (fetching from a selection will be covered later) using a syntax like:: INSERT INTO table_name (column_name1, column_name2...) VALUES (value1, value2...) In PyTables, rows in a table form a *sequence*, so data isn't *inserted* into a set, but rather *appended* to the end of the sequence. This also implies that identical rows may exist in a table (but they have a different *row number*). There are two ways of appending rows: one at a time or in a block. The first one is conceptually similar to the SQL case:: tbl.row['column_name1'] = value1 tbl.row['column_name2'] = value2 ... tbl.row.append() The ``tbl.row`` accessor represents a *new row* in the table. You just set the values you want to set (the others take the default value from their column declarations - see above) and the effectively append the new row. This code is usually enclosed in some kind of loop, like:: row = tbl.row while some_condition: row['column_name1'] = value1 ... row.append() For appending a block of rows in a single shot, :meth:`tables.Table.append` is more adequate. You just pass a NumPy_ record array or Python sequence with elements which match the expected columns. For example, given the ``tbl`` handle for a table with the ``ParticleDescription`` structure described above:: rows = [ ('foo', 0.0, 0.0, 150.0), ('bar', 0.5, 0.0, 100.0), ('foo', 1.0, 1.0, 25.0) ] tbl.append(rows) # Using a NumPy container. import numpy rows = numpy.rec.array(rows) tbl.append(rows) A note on transactions ---------------------- PyTables doesn't support transactions nor checkpointing or rolling back (there is undo support for operations performed on the object tree, but this is unrelated). Changes to the database are optimised for maximum performance and reasonable memory requirements, which means that you can't tell whether e.g. ``tbl.append()`` has actually committed all, some or no data to disk when it ends. However, you can *force* PyTables to commit changes to disk using the ``flush()`` method of table and file handles:: tbl.flush() # flush data in the table h5f.flush() # flush all pending data Closing a table or a database actually flushes it, but it is recommended that you explicitly flush frequently (specially with tables). Updating data ============= We're now looking for alternatives to the SQL ``UPDATE`` statement:: UPDATE table_name SET column_name1 = expression1, column_name2 = expression2... [WHERE condition] There are different ways of approaching this, depending on your needs. If you aren't using a condition, then the ``SET`` clause updates all rows, something you can do in PyTables by iterating over the table:: for row in tbl: row['column_name1'] = expression1 row['column_name2'] = expression2 ... row.update() Don't forget to call ``update()`` or no value will be changed! Also, since the used iterator allows you to read values from the current row, you can implement a simple *conditional update*, like this:: for row in tbl: if condition on row['column_name1'], row['column_name2']...: row['column_name1'] = expression1 row['column_name2'] = expression2 ... row.update() There are substantially more efficient ways of locating rows fulfilling a condition. Given the main PyTables usage scenarios, querying and modifying data are quite decoupled operations, so we will have a look at querying later and assume that you already know the set of rows you want to update. If the set happens to be a slice of the table, you may use the :`meth:`tables.Table.modify_rows` method or its equivalent :meth:`tables.Table.__setitem__` notation:: rows = [ ('foo', 0.0, 0.0, 150.0), ('bar', 0.5, 0.0, 100.0), ('foo', 1.0, 1.0, 25.0) ] tbl.modifyRows(start=6, stop=13, step=3, rows=rows) tbl[6:13:3] = rows # this is the same If you just want to update some columns in the slice, use the :meth:`tables.Table.modify_columns` or :meth:`tables.Table.modify_column` methods:: cols = [ [150.0, 100.0, 25.0] ] # These are all equivalent. tbl.modify_columns(start=6, stop=13, step=3, columns=cols, names=['temperature']) tbl.modify_column(start=6, stop=13, step=3, column=cols[0], colname='temperature') tbl.cols.temperature[6:13:3] = cols[0] The last line shows an example of using the ``cols`` accessor to get to the desired :class:`tables.Column` of the table using natural naming and apply ``setitem`` on it. If the set happens to be an array of sparse coordinates, you can also use PyTables' extended slice notation:: rows = [ ('foo', 0.0, 0.0, 150.0), ('bar', 0.5, 0.0, 100.0), ('foo', 1.0, 1.0, 25.0) ] rownos = [2, 735, 371913476] tbl[rownos] = rows instead of the traditional:: for row_id, datum in zip(rownos, rows): tbl[row_id] = datum Since you are modifying table data in all cases, you should also remember to ``flush()`` the table when you're done. Deleting data ============= Rows are deleted from a table with the following SQL syntax:: DELETE FROM table_name [WHERE condition] :meth:`tables.Table.remove_rows` is the method used for deleting rows in PyTables. However, it is very simple (only contiguous blocks of rows can be deleted) and quite inefficient, and one should consider whether *dumping filtered data from one table into another* isn't a much more convenient approach. This is a far more optimized operation under PyTables which will be covered later. Anyway, using ``remove_row()`` or ``remove_rows()`` is quite straightforward:: tbl.remove_row(12) # delete one single row (12) tbl.remove_rows(12, 20) # delete all rows from 12 to 19 (included) tbl.remove_rows(0, tbl.nrows) # delete all rows unconditionally tbl.remove_rows(-4, tbl.nrows) # delete the last 4 rows Reading data ============ The most basic syntax in SQL for reading rows in a table without using a condition is:: SELECT (column_name1, column_name2... | *) FROM table_name Which reads all rows (though maybe not all columns) from a table. In PyTables there are two ways of retrieving data: *iteratively* or *at once*. You'll notice some similarities with how we appended and updated data above, since this dichotomy is widespread here. For a clearer separation with conditional queries (covered further below), and since the concept of *row number* doesn't exist in relational databases, we'll be including here the cases where you want to read a **known** *slice* or *sequence* of rows, besides the case of reading *all* rows. Iterating over rows ------------------- This is similar to using the ``fetchone()`` method of a DB ``cursor`` in a `Python DBAPI`_-compliant package, i.e. you *iterate* over the list of wanted rows, getting one *row handle* at a time. In this case, the handle is an instance of the :class:`tables.Row` class, which allows access to individual columns as items accessed by key (so there is no special way of selecting columns: you just use the ones you want whenever you want). This way of reading rows is recommended when you want to perform operations on individual rows in a simple manner, and specially if you want to process a lot of rows in the table (i.e. when loading them all at once would take too much memory). Iterators are also handy for using with the ``itertools`` Python module for grouping, sorting and other operations. For iterating over *all* rows, use plain iteration or the :meth:`tables.Table.iterrows` method:: for row in tbl: # or tbl.iterrows() do something with row['column_name1'], row['column_name2']... For iterating over a *slice* of rows, use the :meth:`tables.Table.iterrows|Table.iterrows` method:: for row in tbl.iterrows(start=6, stop=13, step=3): do something with row['column_name1'], row['column_name2']... For iterating over a *sequence* of rows, use the :meth:`tables.Table.itersequence` method:: for row in tbl.itersequence([6, 7, 9, 11]): do something with row['column_name1'], row['column_name2']... Reading rows at once -------------------- In contrast with iteration, you can fetch all desired rows into a single *container* in memory (usually an efficient NumPy_ record-array) in a single operation, like the ``fetchall()`` or ``fetchmany()`` methods of a DBAPI ``cursor``. This is specially useful when you want to transfer the read data to another component in your program, avoiding loops to construct your own containers. However, you should be careful about the amount of data you are fetching into memory, since it can be quite large (and even exceed its physical capacity). You can choose between the ``Table.read*()`` methods or the :meth:`tables.Table.__getitem__` syntax for this kind of reads. The ``read*()`` methods offer you the chance to choose a single column to read via their ``field`` argument (which isn't still as powerful as the SQL ``SELECT`` column spec). For reading *all* rows, use ``[:]`` or the :meth:`tables.Table.read` method:: rows = tbl.read() rows = tbl[:] # equivalent For reading a *slice* of rows, use ``[slice]`` or the :meth:`tables.Table.read|Table.read` method:: rows = tbl.read(start=6, stop=13, step=3) rows = tbl[6:13:3] # equivalent For reading a *sequence* of rows, use the :meth:`tables.Table.read_coordinates` method:: rows = tbl.read_coordinates([6, 7, 9, 11]) Please note that you can add a ``field='column_name'`` argument to ``read*()`` methods in order to get only the given column instead of them all. Selecting data ============== When you want to read a subset of rows which match a given condition from a table you use a syntax like this in SQL:: SELECT column_specification FROM table_name WHERE condition The ``condition`` is an expression yielding a boolean value based on a combination of column names and constants with functions and operators. If the condition holds true for a given row, the ``column_specification`` is applied on it and the resulting row is added to the result. In PyTables, you may filter rows using two approaches: the first one is achieved through standard Python comparisons (similar to what we used for conditional update), like this:: for row in tbl: if condition on row['column_name1'], row['column_name2']...: do something with row This is easy for newcomers, but not very efficient. That's why PyTables offers another approach: **in-kernel** searches, which are much more efficient than standard searches, and can take advantage of indexing (under PyTables >= 2.3). In-kernel searches are used through the *where methods* in ``Table``, which are passed a *condition string* describing the condition in a Python-like syntax. For instance, with the ``ParticleDescription`` we defined above, we may specify a condition for selecting particles at most 1 unit apart from the origin with a temperature under 100 with a condition string like this one:: '(sqrt(x**2 + y**2) <= 1) & (temperature < 100)' Where ``x``, ``y`` and ``temperature`` are the names of columns in the table. The operators and functions you may use in a condition string are described in the :ref:`appendix on condition syntax ` in the `User's Guide`_. Iterating over selected rows ---------------------------- You can iterate over the rows in a table which fulfill a condition (a la DBAPI ``fetchone()``) by using the :meth:`tables.Table.where` method, which is very similar to the :meth:`tables.Table.iterrows` one discussed above, and which can be used in the same circumstances (i.e. performing operations on individual rows or having results exceeding available memory). Here is an example of using ``where()`` with the previous example condition:: for row in tbl.where('(sqrt(x**2 + y**2) <= 1) & (temperature < 100)'): do something with row['name'], row['x']... Reading selected rows at once ----------------------------- Like the aforementioned :meth:`tables.Table.read`, :meth:`tables.Table.read_where` gets all the rows fulfilling the given condition and packs them in a single container (a la DBAPI ``fetchmany()``). The same warning applies: be careful on how many rows you expect to retrieve, or you may run out of memory! Here is an example of using ``read_where()`` with the previous example condition:: rows = tbl.read_where('(sqrt(x**2 + y**2) <= 1) & (temperature < 100)') Please note that both :meth:`tables.Table.where` and :meth:`tables.Table.read_where` can also take slicing arguments. Getting the coordinates of selected rows ---------------------------------------- There is yet another method for querying tables: :meth:`tables.Table.get_where_list`. It returns just a sequence of the numbers of the rows which fulfil the given condition. You may pass that sequence to :meth:`tables.Table.read_coordinates`, e.g. to retrieve data from a different table where rows with the same number as the queried one refer to the same first-class object or entity. A note on table joins --------------------- You may have noticed that queries in PyTables only cover one table. In fact, there is no way of directly performing a join between two tables in PyTables (remember that it's not a relational database). You may however work around this limitation depending on your case: * If one table is an *extension* of another (i.e. it contains additional columns for the same entities), your best bet is to arrange rows of the same entity so that they are placed in the same positions in both tables. For instance, if ``tbl1`` and ``tbl2`` follow this rule, you may do something like this to emulate a natural join:: for row1 in tbl1.where('condition'): row2 = tbl2[row1.nrow] if condition on row2['column_name1'], row2['column_name2']...: do something with row1 and row2... (Note that ``row1`` is a ``Row`` instance and ``row2`` is a record of the current flavor.) * If rows in both tables are linked by a common value (e.g. acting as an identifier), you'll need to split your condition in one for the first table and one for the second table, and then nest your queries, placing the most restrictive one first. For instance:: SELECT clients.name, bills.item_id FROM clients, bills WHERE clients.id = bills.client_id and clients.age > 50 and bills.price > 200 could be written as:: for client in clients.where('age > 50'): # Note that the following query is different for each client. for bill in bills.where('(client_id == %r) & (price > 200)' % client['id']): do something with client['name'] and bill['item_id'] In this example, indexing the ``client_id`` column of ``bills`` could speed up the inner query quite a lot. Also, you could avoid parsing the inner condition each time by using *condition variables*:: for client in clients.where('age > 50'): for bill in bills.where('(client_id == cid) & (price > 200)', {'cid': client['id']}): do something with client['name'] and bill['item_id'] Summary of row selection methods ================================ +----------------------+-----------------+---------------------+-----------------------+-------------------------+ | | **All rows** | **Range of rows** | **Sequence of rows** | **Condition** | +----------------------+-----------------+---------------------+-----------------------+-------------------------+ | **Iterative access** | ``__iter__()``, | ``iterrows(range)`` | ``itersequence()`` | ``where(condition)`` | | | ``iterrows()`` | | | | +----------------------+-----------------+---------------------+-----------------------+-------------------------+ | **Block access** | ``[:]``, | ``[range]``, | ``readCoordinates()`` |``read_where(condition)``| | | ``read()`` | ``read(range)`` | | | +----------------------+-----------------+---------------------+-----------------------+-------------------------+ Sorting the results of a selection ================================== *Do you feel like writing this section? Your contribution is welcome!* Grouping the results of a selection =================================== By making use of the :func:`itertools.groupby` utility, you can group results by field:: group = {} # dictionary to put results grouped by 'pressure' def pressure_selector(row): return row['pressure'] for pressure, rows_grouped_by_pressure in itertools.groupby(mytable, pressure_selector): group[pressure] = sum((r['energy'] + r['ADCcount'] for r in rows_grouped_by_pressure)) However, :func:`itertools.groupby` assumes the incoming array is sorted by the grouping field. If not, there are multiple groups with the same grouper returned. In the example, mytable thus has to be sorted on pressure, or the last line should be changed to:: group[pressure] += sum((r['energy'] + r['ADCcount'] for r in rows_grouped_by_pressure)) ----- .. target-notes:: .. _`PyTables users' list`: https://lists.sourceforge.net/lists/listinfo/pytables-users .. _`User's Guide`: https://www.pytables.org/usersguide .. _HDF5: http://www.hdfgroup.org/HDF5 .. _SQLite: http://www.sqlite.org .. _NumPy: http://www.numpy.org .. _`Python DBAPI`: http://www.python.org/dev/peps/pep-0249 PyTables-3.7.0/doc/source/cookbook/index.rst000066400000000000000000000006141416254111300207570ustar00rootroot00000000000000================= PyTables Cookbook ================= -------- Contents -------- .. toctree:: :maxdepth: 1 hints_for_sql_users PyTables & py2exe Howto (by Tommy Edvardsen) How to install PyTables when you're not root (by Koen van de Sande) tailoring_atexit_hooks custom_data_types simple_table inmemory_hdf5_files threading PyTables-3.7.0/doc/source/cookbook/inmemory_hdf5_files.rst000066400000000000000000000115531416254111300236030ustar00rootroot00000000000000==================== In-memory HDF5 files ==================== The HDF5 library provides functions to allow an application to work with a file in memory for faster reads and writes. File contents are kept in memory until the file is closed. At closing, the memory version of the file can be written back to disk or abandoned. Open an existing file in memory =============================== Assuming the :file:`sample.h5` exists in the current folder, it is possible to open it in memory simply using the CORE driver at opening time. The HDF5 driver that one intend to use to open/create a file can be specified using the *driver* keyword argument of the :func:`tables.open_file` function:: >>> import tables >>> h5file = tables.open_file("sample.h5", driver="H5FD_CORE") The content of the :file`sample.h5` is opened for reading. It is loaded into memory and all reading operations are performed without disk I/O overhead. .. note:: the initial loading of the entire file into memory can be time expensive depending on the size of the opened file and on the performances of the disk subsystem. .. seealso:: general information about HDF5 drivers can be found in the `Alternate File Storage Layouts and Low-level File Drivers`__ section of the `HDF5 User's Guide`_. __ `HDF5 drivers`_ Creating a new file in memory ============================= Creating a new file in memory is as simple as creating a regular file, just one needs to specify to use the CORE driver:: >>> import tables >>> h5file = tables.open_file("new_sample.h5", "w", driver="H5FD_CORE") >>> import numpy >>> a = h5file.create_array(h5file.root, "array", numpy.zeros((300, 300))) >>> h5file.close() Backing store ============= In the previous example contents of the in-memory `h5file` are automatically saved to disk when the file descriptor is closed, so a new :file:`new_sample.h5` file is created and all data are transferred to disk. Again this can be time a time expensive action depending on the amount of data in the HDF5 file and depending on how fast the disk I/O is. Saving data to disk is the default behavior for the CORE driver in PyTables. This feature can be controlled using the *driver_core_backing_store* parameter of the :func:`tables.open_file` function. Setting it to `False` disables the backing store feature and all changes in the working `h5file` are lost after closing:: >>> h5file = tables.open_file("new_sample.h5", "w", driver="H5FD_CORE", ... driver_core_backing_store=0) Please note that the *driver_core_backing_store* disables saving of data, not loading. In the following example the :file:`sample.h5` file is opened in-memory in append mode. All data in the existing :file:`sample.h5` file are loaded into memory and contents can be actually modified by the user:: >>> import tables >>> h5file = tables.open_file("sample.h5", "a", driver="H5FD_CORE", driver_core_backing_store=0) >>> import numpy >>> h5file.create_array(h5file.root, "new_array", numpy.arange(20), title="New array") >>> array2 = h5file.root.array2 >>> print(array2) /array2 (Array(20,)) 'New array' >>> h5file.close() Modifications are lost when the `h5file` descriptor is closed. Memory images of HDF5 files =========================== It is possible to get a memory image of an HDF5 file (see `HDF5 File Image Operations`_). This feature is only available if PyTables is build against version 1.8.9 or newer of the HDF5 library. In particular getting a memory image of an HDF5 file is possible only if the file has been opened with one of the following drivers: SEC2 (the default one), STDIO or CORE. An example of how to get an image:: >>> import tables >>> h5file = tables.open_file("sample.h5") >>> image = h5file.get_file_image() >>> h5file.close() The memory ìmage of the :file:`sample.h5` file is copied into the `ìmage` string (of bytes). .. note:: the `ìmage` string contains all data stored in the HDF5 file so, of course, it can be quite large. The `ìmage` string can be passed around and can also be used to initialize a new HDF5 file descriptor:: >>> import tables >>> h5file = tables.open_file("in-memory-sample.h5", driver="H5FD_CORE", driver_core_image=image, driver_core_backing_store=0) >>> print(h5file.root.array) /array (Array(300, 300)) 'Array' >>> h5file.setNodeAttr(h5file.root, "description", "In memory file example") ----- .. target-notes:: .. _`HDF5 drivers`: http://www.hdfgroup.org/HDF5/doc/UG/08_TheFile.html#Drivers .. _`HDF5 User's Guide`: https://portal.hdfgroup.org/display/HDF5/HDF5+User+Guides .. _`HDF5 File Image Operations`: http://www.hdfgroup.org/HDF5/doc/Advanced/FileImageOperations/HDF5FileImageOperations.pdf PyTables-3.7.0/doc/source/cookbook/no_root_install.rst000066400000000000000000000101001416254111300230440ustar00rootroot00000000000000:author: localhost :date: 2008-04-21 11:12:44 .. todo:: update to use new SW versions Installing PyTables when you're not root ======================================== By `Koen van de Sande `_. .. warning:: contents of this recipe may be outdated. This guide describes how to install PyTables and its dependencies on Linux or other \*nix systems when your user account is not root. Installing the HDF5_ shared libraries and Python extension NumPy requires some non-trivial steps to work. We describe all steps needed. They only assumption is that you have Python 3.6 or higher and a C/C++ compiler (gcc) installed. Installing HDF5 --------------- * First go to or make a temporary folder where we can download and compile software. We'll assume you're in this temporary folder in the rest of this section. * Download `hdf5-1.12.1.tar.gz` from https://www.hdfgroup.org/downloads/hdf5 * Extract the archive to the current folder:: tar xzvf hdf5-1.12.1.tar.gz * Go to the extracted HDF5 folder:: cd hdf5-1.12.1 * Run the configure script:: ./configure * Run make:: make install * We've now compiled HDF5_ into the `hdf5` folder inside the source tree. We'll need to move this to its final location. For this guide, we'll make a `software` folder inside your home directory to store installed libraries:: mkdir ~/software * Move the files to the right location:: mv hdf5 ~/software/ Installing NumPy ---------------- * From the `NumPy page on PyPI `_ download NumPy 1.21.5 (at time of writing) to our temporary folder. * Extract the archive:: tar xzvf numpy-1.21.5.tar.gz * Go to the NumPy folder:: cd numpy-1.21.5 * Build and install the Python module into our software folder:: python3 setup.py install --home=~/software Python wrapper script --------------------- We've installed all dependencies of PyTables. We need to create a wrapper script for Python to let PyTables actually find all these dependencies. Had we installed them as root, they'd be trivial to find, but now we need to help a bit. * Create a script with the following contents (I've called this script `p` on my machine):: #!/bin/bash export PYTHONPATH=~/software/lib/python export HDF5_DIR=~/software/hdf5 export LD_LIBRARY_PATH=~/software/lib/python/tables:~/software/hdf5/lib python3 $* * Make the script executable:: chmod 755 p * Place the script somewhere on your path (for example, inside a folder called `bin` inside your home dir, which is normally added to the path automatically). If you do not add this script to your path, you'll have to replace `p` in scripts below by the full path (and name of) your script, e.g. `~/pytablespython.sh` if you called it `pytablespython.sh` and put it in your home dir. * Test your Python wrapper script:: p * It should now start Python. And you should be able to import `numpy` without errors:: >>> import numpy .. note:: you could do this differently by defining these environment settings somewhere in your startup scripts, but this wrapper script approach is cleaner. Installing PyTables ------------------- * From the `PyPI page `_ download PyTables 3.7.0 (at time of writing) to our temporary folder. * Extract the archive:: tar xzvf pytables-3.7.0.tar.gz * Go to the PyTables folder:: cd pytables-3.7.0 * Install PyTables using our wrapper script:: p setup.py install --home=~/software Running Python with PyTables support ------------------------------------ * Use your Python wrapper script to start Python:: p * You can now import `tables` without errors:: >>> import tables >>> tables.__version__ '3.7.0' Concluding remarks ------------------ * It is safe to remove the temporary folder we have used in this guide, there are no dependencies on it. * This guide was written for and tested with HDF5 1.12.1, PyTables 3.7.6 and Numpy 1.21.5. Enjoy working with PyTables! *Koen* ----- .. target-notes:: .. _HDF5: http://www.hdfgroup.org/HDF5 PyTables-3.7.0/doc/source/cookbook/py2exe_howto.rst000066400000000000000000000045531416254111300223120ustar00rootroot00000000000000:author: localhost :date: 2008-04-21 11:12:45 .. todo:: update the code example to numpy ============================================================= How to integrate PyTables in your application by using py2exe ============================================================= This document shortly describes how to build an executable when using PyTables. Py2exe_ is a third party product that converts python scripts into standalone windows application/programs. For more information about py2exe please visit http://www.py2exe.org. To be able to use py2exe you have to download and install it. Please follow the instructions at http://www.py2exe.org. Let’s assume that you have written a python script as in the attachment :download:`py2exe_howto/pytables_test.py` .. literalinclude:: py2exe_howto/pytables_test.py :linenos: To wrap this script into an executable you have to create a setup script and a configuration script in your program directory. The setup script will look like this:: from setuptools import setup import py2exe setup(console=['pytables_test.py']) The configuration script (:file:`setup.cfg`) specifies which modules to be included and excluded:: [py2exe] excludes= Tkconstants,Tkinter,tcl includes= encodings.*, tables.*, numpy.* As you can see I have included everything from tables (tables.*) and numpy (numpy.*). Now you are ready to build the executable file (:file:`pytable_test.exe`). During the build process a subfolder called *dist* will be created. This folder contains everything needed for your program. All dependencies (dll's and such stuff) will be copied into this folder. When you distribute your application you have to distribute all files and folders inside the *dist* folder. Below you can see how to start the build process (`python setup.py py2exe`):: c:pytables_test> python3 setup.py py2exe ... BUILDING EXECUTABLE ... After the build process I enter the *dist* folder and start :file:`pytables_test.exe`. :: c:pytables_test> cd dist c:pytables_testdist> pytables_test.exe tutorial.h5 (File) 'Test file' Last modif.: 'Tue Apr 04 23:09:17 2006' Object Tree: / (RootGroup) 'Test file' /detector (Group) 'Detector information' /detector/readout (Table(0,)) 'Readout example' [25.0, 36.0, 49.0] DONE! ----- .. target-notes:: .. _py2exe: http://www.py2exe.org PyTables-3.7.0/doc/source/cookbook/py2exe_howto/000077500000000000000000000000001416254111300215515ustar00rootroot00000000000000PyTables-3.7.0/doc/source/cookbook/py2exe_howto/pytables_test.py000066400000000000000000000026531416254111300250130ustar00rootroot00000000000000import tables as tb class Particle(tb.IsDescription): name = tb.StringCol(16) # 16-character String idnumber = tb.Int64Col() # Signed 64-bit integer ADCcount = tb.UInt16Col() # Unsigned short integer TDCcount = tb.UInt8Col() # Unsigned byte grid_i = tb.Int32Col() # Integer grid_j = tb.IntCol() # Integer (equivalent to Int32Col) pressure = tb.Float32Col() # Float (single-precision) energy = tb.FloatCol() # Double (double-precision) with tb.open_file("tutorial.h5", mode="w", title="Test file") as h5file: group = h5file.create_group("/", "detector", "Detector information") table = h5file.create_table(group, "readout", Particle, "Readout example") print(h5file) particle = table.row for i in range(10): particle['name'] = f'Particle: {i:6d}' particle['TDCcount'] = i % 256 particle['ADCcount'] = (i * 256) % (1 << 16) particle['grid_i'] = i particle['grid_j'] = 10 - i particle['pressure'] = float(i * i) particle['energy'] = float(particle['pressure'] ** 4) particle['idnumber'] = i * (2 ** 34) particle.append() table.flush() with tb.open_file("tutorial.h5", mode="r", title="Test file") as h5file: table = h5file.root.detector.readout pressure = [x['pressure'] for x in table.iterrows() if x['TDCcount'] > 3 and 20 <= x['pressure'] < 50] print(pressure) PyTables-3.7.0/doc/source/cookbook/simple_table.rst000066400000000000000000000104151416254111300223100ustar00rootroot00000000000000:author: FrancescAlted :date: 2010-04-20 16:44:41 =================================================== SimpleTable: simple wrapper around the Table object =================================================== Here it is yet another example on how to inherit from the :class:`tables.Table` object so as to build an easy-to-use Table object. Thanks to Brent Pedersen for this one (taken from https://pypi.python.org/pypi/simpletable). :: """ SimpleTable: simple wrapper around pytables hdf5 ------------------------------------------------------------------------------ Example Usage:: >>> from simpletable import SimpleTable >>> import tables # define the table as a subclass of simple table. >>> class ATable(SimpleTable): ... x = tables.Float32Col() ... y = tables.Float32Col() ... name = tables.StringCol(16) # instantiate with: args: filename, tablename >>> tbl = ATable('test_docs.h5', 'atable1') # insert as with pytables: >>> row = tbl.row >>> for i in range(50): ... row['x'], row['y'] = i, i * 10 ... row['name'] = "name_%i" % i ... row.append() >>> tbl.flush() # there is also insert_many() method() with takes an iterable # of dicts with keys matching the colunns (x, y, name) in this # case. # query the data (query() alias of tables' readWhere() >>> tbl.query('(x > 4) & (y < 70)') #doctest: +NORMALIZE_WHITESPACE array([('name_5', 5.0, 50.0), ('name_6', 6.0, 60.0)], dtype=[('name', '|S16'), ('x', ' 0 if verbose and are_open_files: sys.stderr.write("Closing remaining open files:") if Version(tables.__version__) >= Version("3.1.0"): # make a copy of the open_files.handlers container for the iteration handlers = list(open_files.handlers) else: # for older versions of pytables, setup the handlers list from the # keys keys = open_files.keys() handlers = [] for key in keys: handlers.append(open_files[key]) for fileh in handlers: if verbose: sys.stderr.write("%s..." % fileh.filename) fileh.close() if verbose: sys.stderr.write("done") if verbose and are_open_files: sys.stderr.write("\n") import sys, atexit atexit.register(my_close_open_files, False) then, you won't get the closing messages anymore because the new registered function is executed before the existing one. If you want the messages back again, just set the verbose parameter to true. You can also use the `atexit` hooks to perform other cleanup functions as well. PyTables-3.7.0/doc/source/cookbook/threading.rst000066400000000000000000000205121416254111300216140ustar00rootroot00000000000000========= Threading ========= .. py:currentmodule:: tables Background ========== Several bug reports have been filed in the past by the users regarding problems related to the impossibility to use PyTables in multi-thread programs. The problem was mainly related to an internal registry that forced the sharing of HDF5 file handles across multiple threads. In PyTables 3.1.0 the code for file handles management has been completely redesigned (see the *Backward incompatible changes* section in :doc:`../release-notes/RELEASE_NOTES_v3.1.x`) to be more simple and transparent and to allow the use of PyTables in multi-thread programs. Citing the :doc:`../release-notes/RELEASE_NOTES_v3.1.x`:: It is important to stress that the new implementation still has an internal registry (implementation detail) and it is still **not thread safe**. Just now a smart enough developer should be able to use PyTables in a muti-thread program without too much headaches. A common schema for concurrency =============================== Although it is probably not the most efficient or elegant solution to solve a certain class of problems, many users seems to like the possibility to load a portion of data and process it inside a *thread function* using multiple threads to process the entire dataset. Each thread is responsible of: * opening the (same) HDF5 file for reading, * load data from it and * close the HDF5 file itself Each file handle is of exclusive use of the thread that opened it and file handles are never shared across threads. In order to do it in a safe way with PyTables some care should be used during the phase of opening and closing HDF5 files in order ensure the correct behaviour of the internal machinery used to manage HDF5 file handles. Very simple solution ==================== A very simple solution for this kind of scenario is to use a :class:`threading.Lock` around part of the code that are considered critical e.g. the :func:`open_file` function and the :meth:`File.close` method:: import threading lock = threading.Lock() def synchronized_open_file(*args, **kwargs): with lock: return tb.open_file(*args, **kwargs) def synchronized_close_file(self, *args, **kwargs): with lock: return self.close(*args, **kwargs) The :func:`synchronized_open_file` and :func:`synchronized_close_file` can be used in the *thread function* to open and close the HDF5 file:: import numpy as np import tables as tb def run(filename, path, inqueue, outqueue): try: yslice = inqueue.get() h5file = synchronized_open_file(filename, mode='r') h5array = h5file.get_node(path) data = h5array[yslice, ...] psum = np.sum(data) except Exception as e: outqueue.put(e) else: outqueue.put(psum) finally: synchronized_close_file(h5file) Finally the main function of the program: * instantiates the input and output :class:`queue.Queue`, * starts all threads, * sends the processing requests on the input :class:`queue.Queue` * collects results reading from the output :class:`queue.Queue` * performs finalization actions (:meth:`threading.Thread.join`) .. code-block:: python import os import queue import threading import numpy as np import tables as tb SIZE = 100 NTHREADS = 5 FILENAME = 'simple_threading.h5' H5PATH = '/array' def create_test_file(filename): data = np.random.rand(SIZE, SIZE) with tb.open_file(filename, 'w') as h5file: h5file.create_array('/', 'array', title="Test Array", obj=data) def chunk_generator(data_size, nchunks): chunk_size = int(np.ceil(data_size / nchunks)) for start in range(0, data_size, chunk_size): yield slice(start, start + chunk_size) def main(): # generate the test data if not os.path.exists(FILENAME): create_test_file(FILENAME) threads = [] inqueue = queue.Queue() outqueue = queue.Queue() # start all threads for i in range(NTHREADS): thread = threading.Thread( target=run, args=(FILENAME, H5PATH, inqueue, outqueue)) thread.start() threads.append(thread) # push requests in the input queue for yslice in chunk_generator(SIZE, len(threads)): inqueue.put(yslice) # collect results try: mean_ = 0. for i in range(len(threads)): out = outqueue.get() if isinstance(out, Exception): raise out else: mean_ += out mean_ /= SIZE * SIZE finally: for thread in threads: thread.join() # print results print('Mean: {}'.format(mean_)) if __name__ == '__main__': main() The program in the example computes the mean value of a potentially huge dataset splinting the computation across :data:`NTHREADS` (5 in this case) threads. The complete and working code of this example (Python 3 is required) can be found in the :file:`examples` directory: :download:`simple_threading.py <../../../examples/simple_threading.py>`. The approach presented in this section is very simple and readable but has the **drawback** that the user code have to be modified to replace :func:`open_file` and :meth:`File.close` calls with their safe version (:func:`synchronized_open_file` and :func:`synchronized_close_file`). Also, the solution shown in the example does not cover the entire PyTables API (e.g. although not recommended HDF5 files can be opened using the :class:`File` constructor) and makes it impossible to use *pythonic* constructs like the *with* statement:: with tb.open_file(filename) as h5file: do_something(h5file) Monkey-patching PyTables ======================== An alternative implementation with respect to the `Very simple solution`_ presented in the previous section consists in monkey-patching the PyTables package to replace some of its components with a more thread-safe version of themselves:: import threading import tables as tb import tables.file as _tables_file class ThreadsafeFileRegistry(_tables_file._FileRegistry): lock = threading.RLock() @property def handlers(self): return self._handlers.copy() def add(self, handler): with self.lock: return super().add(handler) def remove(self, handler): with self.lock: return super().remove(handler) def close_all(self): with self.lock: return super().close_all(handler) class ThreadsafeFile(_tables_file.File): def __init__(self, *args, **kargs): with ThreadsafeFileRegistry.lock: super().__init__(*args, **kargs) def close(self): with ThreadsafeFileRegistry.lock: super().close() @functools.wraps(tb.open_file) def synchronized_open_file(*args, **kwargs): with ThreadsafeFileRegistry.lock: return _tables_file._original_open_file(*args, **kwargs) # monkey patch the tables package _tables_file._original_open_file = _tables_file.open_file _tables_file.open_file = synchronized_open_file tb.open_file = synchronized_open_file _tables_file._original_File = _tables_file.File _tables_file.File = ThreadsafeFile tb.File = ThreadsafeFile _tables_file._open_files = ThreadsafeFileRegistry() At this point PyTables can be used transparently in the example program presented in the previous section. In particular the standard PyTables API (including *with* statements) can be used in the *thread function*:: def run(filename, path, inqueue, outqueue): try: yslice = inqueue.get() with tb.open_file(filename, mode='r') as h5file: h5array = h5file.get_node(path) data = h5array[yslice, ...] psum = np.sum(data) except Exception as e: outqueue.put(e) else: outqueue.put(psum) The complete code of this version of the example can be found in the :file:`examples` folder: :download:`simple_threading.py <../../../examples/threading_monkeypatch.py>`. Python 3 is required. PyTables-3.7.0/doc/source/dev_team.rst000066400000000000000000000011211416254111300176200ustar00rootroot00000000000000======================== PyTables Governance Team ======================== The PyTables team includes: * Francesc Alted * Ivan Vilata * Scott Prater * Vicent Mas * Tom Hedley * `Antonio Valentino`_ * Jeffrey Whitaker * `Josh Moore`_ * `Anthony Scopatz`_ * `Andrea Bedini`_ * `Tom Kooij`_ * `Javier Sancho`_ .. _Anthony Scopatz: https://github.com/scopatz .. _Antonio Valentino: https://github.com/avalentino .. _Josh Moore: https://github.com/joshmoore .. _Andrea Bedini: https://github.com/andreabedini .. _Tom Kooij: https://github.com/tomkooij .. _Javier Sancho: https://en.jsancho.org/ PyTables-3.7.0/doc/source/development.rst000066400000000000000000000023741416254111300203710ustar00rootroot00000000000000==================== PyTables Development ==================== If you want to follow the development of PyTables and take part in it, you may have a look at the PyTables project pages on `GitHub `_. The source code for PyTables may be found at the `GitHub project site`_. You can get a copy of the latest version of the source code (under development) from the master branch of the project repository using git:: git clone --recursive git@github.com:PyTables/PyTables.git Also, be sure to subscribe to the `Users' Mailing List`_ and/or the `Developers' Mailing List`_. .. _`GitHub project site`: https://github.com/PyTables .. _`Users' Mailing List`: https://groups.google.com/group/pytables-users .. _`Developers' Mailing List`: https://groups.google.com/group/pytables-dev Other resources for developers: * `GitHub project site`_ * :ref:`library_reference` * `Git Repository browser `_ * `Issue tracker `_ * `Developers wiki `_ * `Users' Mailing List`_ * `Developers' Mailing List`_ * Continuous Integration: - `GitHub Actions (GHA) `_ .. todo:: improve this section PyTables-3.7.0/doc/source/downloads.rst000066400000000000000000000037311416254111300200370ustar00rootroot00000000000000========= Downloads ========= Stable Versions --------------- The stable versions of PyTables can be downloaded from the file `download area`_ on SourceForge.net. The full distribution contains a copy of this documentation in HTML. The documentation in both HTML and PDF formats can also be downloaded separately from the same URL. A *pure source* version of the package (mainly intended for developers and packagers) is available on the `tags page`_ on GitHub. It contains all files under SCM but not the (generated) files, HTML doc and *cythonized* C extensions, so it is smaller that the standard package (about 3.5MB). Windows binaries can be obtained from many different distributions, like `Python(x,y)`_, ActiveState_, or Enthought_. In addition, Christoph Gohlke normally does an excellent job by providing binaries for many interesting software on his `website `_. You may be interested to install the latest released stable version:: $ pip install tables Or, you may prefer to install the stable version in Git repository using :program:`pip`. For example, for the stable 3.1 series, you can do:: $ pip install --install-option='--prefix=' \ -e git+https://github.com/PyTables/PyTables.git@v.3.1#egg=tables .. _`download area`: http://sourceforge.net/projects/pytables/files/pytables .. _`tags page`: https://github.com/PyTables/PyTables/tags .. _`Python(x,y)`: http://code.google.com/p/pythonxy .. _ActiveState: http://www.activestate.com/activepython .. _Enthought: https://www.enthought.com/products/epd Bleeding Edge Versions ---------------------- The latest, coolest, and possibly buggiest ;-) sources can be obtained from the new github repository: https://github.com/PyTables/PyTables A `snapshot `_ of the code in development is also available on the `GitHub project page`_. .. _`GitHub project page`: https://github.com/PyTables/PyTables PyTables-3.7.0/doc/source/images/000077500000000000000000000000001416254111300165545ustar00rootroot00000000000000PyTables-3.7.0/doc/source/images/NumFocusSponsoredStamp.png000066400000000000000000000325361416254111300237340ustar00rootroot00000000000000‰PNG  IHDR+u™5PÐ pHYs  šœtEXtSoftwareAdobe ImageReadyqÉe<4ëIDATxÚì}XSWûxBBBØ{+¢8AAQ@QqàBE\Õª¶_ûuý:¾Î¯ÿ¶_«Ú:ZkÝ{ࢲÜ,•%{oÙƒÿ›ÜÜLB ‚xßç><—›sÏyÏ9ï>ç¾ßÙىà úØ`€Æ`€q `ðÜØ«µ‹YÌêÕ³áÆî‹ŸÉ£u”løæ=^ö=õç6ïM ‡_‘JzÉÊ˜Éæ‘ÑV¯} r¯?æíñ‡ü‰ÎÐSúü8¸Àt£c à'•'0ƒŒ¯6 ÆšzNúugSo_Ãȸió—0¿ÐÇ?޼HȺr¹µÓÉÀ¤ª<1é÷t TB0§htÁÓ|è5'íºí—?6ßÁ²‚Ñ Gî…uÕ@ý0JŽÛŽÀó¾B X¨Ëô5_ÃÞ…iqóû‚é@Ö•sˆN¢„™èV jS>½¤ýŒ-;7a¡É‹_œÏ<}h`ë@Ý@䋞J˜}%Є=­ª?L=ˆ¯êê¡¿s H>à:t„ ˜u›†£íÀN r¸‡š-æÇÉí[°`š¡-¸0œKŸ¿\Ž üoÁsMú‚8ò19œ÷^@?¬Ý ¯Øÿø—Jaè#è:tmð/Ï*Èœ@ °…q@«G@˜êATü*¯ ~…F¡Ë€<ÌåÒ=ð¨bd(À*F‚’Ù¨±PF>Jò!RGJ"‹àߣ郶 #žHÀ–ƒQ]5bÔéÀ)ïzâ¦úÃòè~i$èL(2Îp!~º©ù»pSs]ã»ý—;®œCF& :o0ÊfL>«È¹ð/7û>ü ¬MÀØÕ´è!wþÓS(s€È`xSÂ/.`Æ„bh&QZä\qxK>»@ëðŠF ‰¢ðÜkdl˜Z)AD#tÐøõ{ÀTˆ’$xpÖÕÀ¯PüŠ®ÊK˜­x“– å!¢ÿPÀOÐ}¨=þ€\Ð." !BjCèP! ¦…zŸÍ`‚˜¦ò'€èI„šõÁVÅ D¨JIm éÔk@IÀŸFRs t …u¿Û9úƒèndÜ)!aˆÕ­ÊÕx•í°zý¤Z¨MX'Õ@4Ð(ºÍQƒÃ¤1˜¹:ØE:…H\$h¦²+ UY”ÿ*½wbJÜ9„o£Uâ  ôŒ€î1—8Ƀ††-;¯ÃëHðVŽ$Ü#Î-¢½m¿üYÿX¨>v»žØÊ§Yj–oTùU qËŽMH8À`zðØ÷:ä"¨/•¥ùž‚a{M1@YŠ”¯È< bs¬-˜„X#Ͼ‹Ãí3cøÒ‰^Ä&”›17‘™´ù€Y¡`З€}¡‹b€Æ`€Æ`€q `€q ¼ €­¾,ÀárKkëª:Ø |A»HÌ ¥O!­Löd’›%ÃÍÁÞÄÄ+Œ10dçåºûðrCk“-w³öknJ ¦’fy»/ ïhoÞs +ò|/;õÜX:: önX½å%Åår‘ûq‘3Èd²z™§r9Ìv¸¡2,½‡ùÖPnFrcåâêâæ®^ ±¾¾¶¸n( ËA†¶¢ß½#I4‰ÿXD ²º:#'×XN9ÂÕ¹ûïôx<Þ¡kÉ;e19†faº5í_AþaãÇõÙzOËËk›˜, " ÍÎÊr°»;ÍÜ\Ÿ×E"Qsk+ú ÝÜÜÌ̬G8p¹\&‹…~bmi©¿ PSWWXQYßÒ €I¼ÅÌÅÎn‡;•By&hÒÑæ¸í‰ÝBcpwïiǨÈHñ<¹ ¹gMÑH í{¶Ø•äÁMÓ0Ü3l.-7},cÅð….ßU/P’vÓéÀ¸)íÛYX0ŒBCÀùò¦9 ‘ùö[‘–m,Ú=€ÃÅèä@±XüÏ¥Äï² «¸|ƒ[ŠÅÛ/^¸1áÖíoÃ'N ð7 òÅååo¥'Ö·€BH W)&ÇûÒ(SmèKÆŽ?j¤Žzò‹žRú&óÏàÑ«fFö™cÉ·6¤g¡Ÿ<ˆ‹öó¢kX„‹)i'óžÞhcÕ²¹š#+xü 9Ć>sGTð„nÙG—Jéh»wòè¤å«†º'ñ8YGö…nxÛ(µ Ïì‡}|øøñ› ÉÛØº‹ÑLI 33Pt<‘°™Íå‹DZÅG;7òìµØ{Y›b¢m¬­ F¬¬²ò“3 ñ mbí» á§'L6\¿—&N¸šòß)A¡cúÉÀ‚©¸ïRâ·9…lžî’ЋB6®½ ÖiY½ß_0Wvã:&Åóbbu˜X/¸¥$t,{…FÖÌÇ™l súU×€D6ˆÿï“RìÄ0#GX˜…:;Œõöðõô´d0Ð/‚Aõ¸¤ìNiYr]sZ r͸ÃUIÛ÷ížq¸züæ›|d3\¿§çÁ– å:ä?ì?=jji™ñÇßêìnC¿uñí ÂÃôW#¶66o/œ—ýáÆ}“ÆxÑ”è^$îü,§èݽ‡õÿžæÜÍ[=Ì{–Ïo’ÛWü} ¯¾à1û g?€U®vaã ôŸQ öO 2[ï?4yåZÃm’c{Üž ÎÍš2f°·aïZÐhè[ÛÚfþs4‡¥t±%›þ4rñTÃó«„%‘ÓæMýîÄéÍEÕh»tGq•àŸƒ¬]ѽhhn~#塺ãgoJ\ìd=ÙËÃÇÕÙÆÒÚjkg–ÕÕÝ)­8Q^óˆ¥*î/4´m?{áÍysž?µl>uön»*û™ðÓ¬é³<œÇzyz8;1ètI˜Ì²êêì’²”êºK íM|¤§ò1ó Ä(©Á…KŒ7èœ|Ž·d¹aÝ)/*t{ò¬‰\-Í©v66ÏÞ>Ÿ¿`×e µ2ß·È(«y0D_/‘™¹üâž@þüï²ZÛCǾŽ[¢ûõÎ%4òø*´ûÑÿ[0G%2£1ØËœÌOp¸“IÉo§g5óèßä-ogXX¢}–­-M¿ÕW]¨¬®ÙZZ'ÿ×Ü”´aâ\–|óΠö•Wð×N2·§wɸ›xÑéïÕŸg> ùü;¸hÓ¸áoÜVìFø&÷é²–Ê~N.—[Æâ*{P=6Þü,é8eORøu`s‹ê:wSé+”ËáRÍÍk¦ÈÁ¶‰ÇÁ Í£§D´¹Én`È5_åMy·ÛY·<ì3´°`”‡Ì0Þ`À´­NžÈ½(~¿îÂÔ‹²e˜¢ÑûT–UVf£ö(FÛ3zcõO¯>|Œ…°¼ÖÎe³ÙŒ íiÊ~ ní¥å•U}ÃãmÙj™2`Ô ;j1rãPQ$ÿÐ^^½lY[.S€+Öõ-ÎWd¢ÃQCõ!2 Ýá(Ž@˜’•=À8pyP ŠãYÁ†î=ž˜–Þ7hie]9`Ôศ9­ö®²ØLümÅL»â¥%c&éøþù@zmƒbæðøIcF÷!23G)e0I+*`èï7l¦êª/8½óSçmû;õaæóæ@€!±¯à¥Ÿ 5ˆÇãÙ³ek›¶E¹y4ì·~x3™!]¾Ç™˜xõi¬6ÅŠx)èmŸ?Œôõµ4SìSËjiÇ 8ømþ,•ª$ÖµL¿:î§ß·ž:ÓÐØôü8ÐÚÖ¶lœ,äewå¤[+ûFÍi¶‘Åèy§5xƒ]©(j‡¸xzõ9ÂE<Å€2ïã/ÇA„ù‘ôSÀæ<twq92}UK´9§ƒûaV¡×{çmýóPâÃ<áûñ>ËÖòo_ëðÉMuÏÇ›óâŽ/@`ÍŽµÞ÷ ÜÛäeææøŒP|Îüèv†}™,Äg6s¡Û}RS§»€½¥¥‡«jȧ¡© ½ïM§õùz›™¦µÉ"CUn BØøÀó$bÜ¥µ<Ín—P,NllOlÌz÷NîR'ë·"B}½½{‘íRÆNñ̸"Q‰ Ç„sæ÷U8Î(4g^É¥£ä†j\ggãñ=>#6ËtÅH[z4*®® ¹ç …,6ÛœJxLâ?澇û‡'ΩnÔ‘iª](ú«¢áï}§:Xþwv¤—»^QC²¹ Š]ƒ'Jô2¹±Ö€/ ú›lž!Sãnï—äç!÷…YÁ9Dî勇} LžÒNKs³¾ÏBSÞ×Îæppl¬­woX¶$j=ƒ¨s7?°è‰Ú–€}§~9¯ÏF6C8ÐÉÕµh´L'HÔà î úϙϳu”º}âúã{e n¤Ã 0 »M3ÏúáaÇÏs›X€1~~‡7®ÏÛ°ìó¡Þ4]Úü…O²‹–íØÍçóσãÖâ¤Ù…€+º1rrï’“QUZRôø‘KW.&ù²aŸ]9­—ß÷‘¶òî3³’ÇD7¸:9}¶lñãßL^ þ‚µ©Öšñµ-±»êþ¬Ñ@tñð¬ñŸ4`Ô`@ôB®•äN$*9üO£\Ú»Ž‹šÓO´²TZ{¨c2û¥Z–"úG&šÐi4ÜËÆŒw½ôÃ7öNòbhV‰kš¾8rÒ˜‘…¾x5îÁ-œXŒ¨Á:(J"‘g,v=² î½²R;Å2¡Åž³¬7 ­« ¦øúèšM§ØÙØ•ó„2ÜJÚ:ú|ÜÊø ÉëD6Ž”`jjº4r*\×2nxóÎcµ\o¿U.~òd̰aÆÔîƒW Ö¦yÄl>$j!ù§S(@r15Û8ΜÝm#!ŸÛh»4¦÷$Þ…™—Ëîû´=¹¨<>ä—ý®©‚2Þ}m½‡ê·£B±ø›¤T­PG×"£]Ìjž€xƒ÷_poˆ¾nÚôÖìXB?Ëü=’ªàLµ,V"“_\ÜÄV?GZÒp/=m]»òmoÕ£uÛªjk¬¼‡­ð“å¦\:.î7iü ƒÀ…K¹²ÃI@Í™×ï'{´dMïÓÍЗ3•NÎñrÇ8V, `(­ÖŠÄgÒïŸlÁ”zJÖM5·ÏŸy¡ŽL&W‡ËÒÆðg-!ô¿£/¦Q: !¯¨‘9]^£ý&&¡}ºM¼_‰‰É—Aª£‘ZÓÐ+è3bd•¯ìp9ó G^t58~q‡ÆàÙ:Ž™=¯¢7ÄË ýÙ虆־:P!ïéÓôVE((Ô‚òœZ÷sˆ ž`«šzÒÁî”h¿¤ÍÌÌj"æ7Ï\Òo·Ú-uµ“ßW²ygoôMʶ­I7Ѽ;Ôã:%¾"ÆS•Ö «´, Î|Fûg m“Ÿ)SƒsæúåÑez¸˜e$ÓþÈ}%|ò…‡å‡þ¿{9ÑSB5.™pϺrí‚ä· œ5·ÛÊ…BaêŸ[ùU²d» š¿/­¨8XÛ&ÿ׊DŒ™Šq x2è8Ô‘ƒm\^oq Ä X°÷C¦\ G/èÙûýiÃ¥ï-vvt\ähu¸+‘f6“³ûb«³5$ðö¬By‰…›Ç[ÛÚê®üÎÙSƒ’Nw‹ÃÇç/sQ‹O<Tr¹ÔÞ˜B™¬¶ö(õxOˆ¸S¬î¿I†~ ÉÆ—aã¼ý öÅ<ôC7ôòìQü$r zsðæWwEºé †€¬A‚àñ<¡ûñ1!jÞ`%¶R°î™ä›ñµŠ¼}D“w樞AWÛ5ÞfÔŒ TµÃÔó#v Ííª›ŠhæÆ¾\eKÐDËøÍÛ!-\…Ûô±Â$t/ND¦²qĺ§¾½UñÁŽÙK³%jˆ—×:7»e² [Â5Gã/¾µD8‰Dâð]urb'Z'àñæcCü­»?/mÜœy·Û[DµJÆg§uØ’•È}EUõ›©JùÂ>æim¥š×ÁÎNåIysSýýtôÄçKÑO Ö/s—~6igc-+L§òž²YÚÁRÖÛx[kk#NS-S©~k-G¾GLyàîc_^ˆ¨A朸n_a™Ë¢göÌfí«(“/»˜ZÛ¾<>Æ×1óNoÝ]×µ)ìFsÇûþòŠdÌ}ýà28~¼Bk:ÆŽÖâ#ñM¨“ýüÌÉïhḚ̂°°5§6¢vf6·õ™œ&­LK$ÍL+P»‚0{ü}ú½6¥Wœ¨ãž™ÍU2Œ=½h…ÊDí|™¤5H¸w³ûòNn2#‡Å¬,1$ÏOSÞ#ù½‡×ËÃ@â;¦ŒGÛÝÛKª¿?r¢÷Zäp8‹wÈlWP­©‰É®ÙÚòQ~|‹Ù³<·ZUuúØÓÑ¥´±´m:ÑLB÷EZ¡1£ßÅeå•uà†y¯s ÿä0ù‘ nïw[ÞzØù}YÚ C<òì»2f&›y†{™ jRÈjW%cïë'¥_8Ümµµ·ÏÛ¹÷z“’ãôÝÈAÇk{e²‹ÒöH¶@ðWÂý[Lʸ]£œbˆ¹…¢p/'»*%á‹tË•ñùÓåë*_»Ov²Óê1¶ôØ„ÞtåºJxqŠ›s¯s u$®³û`Ì€@$·Ä¡OIìiü¦ª¬Ì¡H¶-«ÒÓï…N¤olŠ]äGW üXX÷Ç®ŽcîÍZ1Êíæy‰Äåó¬·|‘1MPL¬ŽÅ=f{{ööŸå§ùáðxþò7_è|mÏn.ÎÉëãï9œÑ®XV|¨ªáÄîã‹,×LÐ{Û4X€çSÓÿxø(µMCtq£—ÓO«–é¹ç)pÄð7ïgþ^\£â°¯m>^›br%Õ™JµñL®H\'¶p4/ ûP;[¢aÇøQ#7ÜË”¯ÊtémÜ™Æö3‰©Ä+鮲u§h©QŒ«ä Tôž6¸ÛCUê¾ß‡÷«¨²JïÛür¸H&‰Îf¦Vb! ÏwÖà‰èµTàóaÞÚó¦Ÿp‘\Ô–ÿlÒ—c_{'·8Ï¡R²£“Çu=º=+õ2/l–Ǹ`GW…jåñŠr²›ÒoºßKò`)ddEÄ‚àÐ0ÜË v66—ßÞð9¢i$÷Áꦃ§¯x]I‰²¦OñöðõqsV …‚’Ò»…IåU‰M­ ûKh&„ß‚F.ŸÙ#ľ[òdÇ?×5¤Ó~¨è`U °ÐXú™Ø¹4-©ÿ·|Iî¶Ý)­ü^¡X\Êâ”v‡a¨•ù+–jZYî?b]FŽPSx¸¹ŒÅ-C=ÐV¬£Õ{‹æë@ äq Ù­5'÷ìA립â™d²Ï׿}ý¾}yòĶò)îÀVá­UªˆÆhãì:¢Öf†X¬’¥½2<:øwqHê_Ö¬ŒJÏØxón…Úçó%œmp•×ã’ïš›’œˆ œd¥P$j!™Õ°9ê'þ¡!ŒAݶhŽ·G¿$‘H'Ö­\ñ÷ mtÊŸA=²$ÚÃÕUñį_µüŸƒ‰†ä̲cì%ÎTË6àØàiÒ×$ßiä˜k»ý–5+tïÖêÆœ0ìk#°R¸³–è_Þ‚Áù¿í¥SâIJcÑÉaj€“EÍ8eL8Võ>™°ñŒ÷Ð0=xBÖÛ¯þÇ×®}‹#‹/(bó°÷Yü,®¨œÉÒÁ~Þ4êЀ„÷Þ4€ý P('6®Û‹®!ëɓ٧¯(¾Çmåì¨hŒ~TpôÍWåL¸©¬žÅhlåysàåô „ý¾á;1X üvõÆŽ²ºÏ²‹f)ññRÊŸûº·Ë§³#Û™ÉóÞ½÷xKaŬ;wÃÆ“p¶ Ÿ!³Œ•?j„™Øt=u7Jv ¿œ¹ìgnJús¨I£FTÔÖ­O¼ù„Éþæ\ÂŽõ¯ evž»ìG&šl›0*bÔˆ6fÇç‰×ÏÕ·¾vëþÔÀ´†øi´ÏÔáÚ::~K½s¾©ã;& óäá.³ÊÕîÝÉÁ]šÊø§/ŸâlkÛÐÞ¾û~ÎÑš¦•×Ò‹ýG«ähÜ?yìpi^w ÙH¾¯;¹"†Ç—ÔGg.^e é”?çJhbFvrpx'‘•ÍÓÎJòî|åç-=$ÔÉÁ^wµh¨ø×‡›SRòï[÷’Û¿9yö‡•±ÚzñÞ¥kÀ~Sm-~œniAßšpå×’º÷ïxï`ÎÀMesË3b˜ÖÆÑ–Iµ°\vj¢·“"Û·-YÒn]o m¸lçJ¼,¼rnl¢$W‡ˆ«3±iG§Ä¤¦ôä¬1˜€½ÕMŸ66ÚÛv< —ÇC¸41! ƒ(8ås yÒ2µÔ‘ÊÖùÉ$…h/s =Ž«¾±±¬VÕ¹òóö2×ûÜB¡PX#C£_q`c›$]Egòrs³;íìÏngv<ùcí %ž'‘ždMg}‰pÏt@ˆWŽ@øSƃI(oPm]矙 Ž:¤H9¿W¿¡5œ ꪶö–4åx×$ Xåá´½°|ËÅËß®ê>°ÆìRaãó½õ=ÚEŒëÅ£O¦¤ÿûa¾ÊÃ[‹£Æ®ÏëÍ-­ß½Àäñ‡QMýzâ÷60;:~Ï”„Fƒ¨º8ð«IÑ ),îÄÝGÏ Ò#v ðpu (O[ý2»hƒ@:µ‰Ç»\ß’—§}ÕH6_-Œ~ŸÅ¢Íú#öÀ7ÈÛöw+‡“Ér…ÂatêÚJyæXìùÛþFî? ;r„R 'dÜþ²š¿Êê?b2 ”Šþnä³ô¹?úêJõs^æû‹ÚCG‡në÷ßsRÜÙ‰äcžëhýÛ‚Yêy¸>JJcܔ䰷ù"f¾±º¦»ÚÈ_·wð…1¸Ç$“¯¦MÖQUDÐøýlöº›ª¹üg“6WVi<XÜ»â@ݱºÖ]³ÿ¦r²SôÞè¡ëÒ²~ºž¶¨^Yp¬­¬4&tÒÕJÿäÀ:ž°£“G2!úRðÓ?š7K%` vãžLu¨å3·fX¬qµÛZ\½3áj¿ÝCÐ,Ë» Ö~pÍß!# râÇ1¨$<®ŠÅÉãð/Õ·ú&Ýü6N5%d)_d*=JÃŽÃ3b×tWÛÀdñ;;Y"‰ápuñì‘ÝõnAxØ`'§%§K¸üwå6´ü¼z™±Ž@nã<­ƒ;5üë̼Sõ-_••õ^+ý‘7x8èˆPK¢ ôü¯×QàÈð»Žl-(ÛàéÔ?9d„î>ʬÐk×¾Ÿ£ò𯅳üwcRþ¹ QãSÓ_MÛ”_šš6C9²wlf¨žÖl@wµ™ŸÀãñÜ6ïlçñŸVUÒC¾Œê›úºýŠÇ“Û·—T›8ò¿•ËŒ‚ê›N–_¼²RGë_ƒÝ>Ì.Úœ˜Ô{­ô^˜]i®ÎNK-ëùÂCÕM¼þ0÷ѲŸ·ÂÕçŠD,_å‹{àUÏš&=òrþÓ~2;d2y*Cm¾ZX¬ç+`Ý}cí2'Éêâ–§Ué™Yº¤ÛÖ0w7îÞ{vT×FM·1%ªk«ïì^ëî>wÚýßÑ^<ÕpH2!à„âç9Íï…‡:x¦¸£›hbTßŒŠ¤™ªlmº".,žB®q¥‘®c*ûÚJ0¾×ÎæreÛ/kêêžò$”äêØKè~¨éœ7u+=<­IqšGn½äLÓ¡TE|2PZ&¥A!VÓ³²‘™žà3H[Í ‹*¥ÑˆPo¾¢upºnJ¸acÕ8Ð’Á˜D—åø»tp`MRf÷ãB…y"Oë®ÀqÎ’°pOPU#Ûñ¨°‘Œ~ÞžFAÕÆÚz¥³êÞºÑC|R¨°íñ$í³ìEik¸º.Í•±ýh:åŸù3ôA5¡“ÐEHC‰8m¼Ôãh}¸Jé´÷—/iß{fóSÅq bî× C¼¼ kExÖ¬õðz ‡Ë‘:µæD¢%Uõ„&[rt†)‰h¦åsÉ&2¡ªma.ãávé÷& RÐ:<$M(Ým“ŠDÍ\É5<ÞŠlª”ápyb1ÈZ:‰HW> 0g¢ÎFÌÍÈêëàH•Œ{žÖ“Ãjt€@(ä(Ÿ_G&‘ÈÊnŒH,f©ô¨2æ.D>Ñ„@U;sJÞM Ô‹’,š«%š˜IGžÏçs¥î¨|²¤CÁ†‘ š‘‰:Ï“O ‰@°Âš¤m+›Ã’ÒDd(&xìRÄ`:LºÒ1 B¡Íã«ôXt ²âó[µÎNøÁZíW¤ƒFœkìÜ 0èKÀ²T`€Æ`€q `€q `ˆ`ˆb€b€ÁÀ…ÿ/À½ "¼ÝâíIEND®B`‚PyTables-3.7.0/doc/source/images/favicon.ico000066400000000000000000000011471416254111300207000ustar00rootroot00000000000000‰PNG  IHDR‘h6bKGDÿÿÿ ½§“ pHYs©}F€tIME× *>AüQôIDAT(Ï•’_HSqÇ¿¿»œ­VkÁòÂÒrxײ"¢ ìMX a>õRQ=š />J¤ †=DDÂèMldÓ)–>H©H&D²µ²[©wsW[ëÞ­ýû‚ºŽŠürÞ_ΗÏù2"Âf$ü{imE" €ü€6нZås©ù5žôù8 ußÊðtsjÿE_¦‘Ezò&Úÿµ ÀlôRS­]‰ý°‹S“=Ã{ë˜Ó¶-E*øª ™øžyÇ ÿhCd±½¤€Ü'³I?ÈË?TΟ3\à:ùºèõtoÕéÙ3ùCs%ŠDS¼óðV%taW,™Lz½^QN½“~ï.<8Àí¤eÜ¢]ǨŠïá}ÏÛ"‘A"âœòïn/ TÏôX¼6ü4<TV£ãzihÌ6Ò× ªê/*Üo¤ (ˆÙnÔ7­¸w ¡ô0xDÐu¿#dY~ûÀIAÐCèã ¢ ‹]5—˦ËÓ_ image/svg+xml PyTables-3.7.0/doc/source/images/pytables-logo.svg000066400000000000000000000474771416254111300221010ustar00rootroot00000000000000 image/svg+xml PyTables-3.7.0/doc/source/index.rst000066400000000000000000000043711416254111300171550ustar00rootroot00000000000000=================================== Welcome to PyTables' documentation! =================================== PyTables is a package for managing hierarchical datasets and designed to efficiently and easily cope with extremely large amounts of data. You can download PyTables and use it for free. You can access documentation, some examples of use and presentations here. PyTables is built on top of the HDF5 library, using the Python language and the NumPy package. It features an object-oriented interface that, combined with C extensions for the performance-critical parts of the code (generated using Cython), makes it a fast, yet extremely easy to use tool for interactively browsing, processing and searching very large amounts of data. One important feature of PyTables is that it optimizes memory and disk resources so that data takes much less space (specially if on-flight compression is used) than other solutions such as relational object oriented databases. You can also find more information by reading the PyTables :doc:`FAQ`. PyTables development is a continuing effort and we are always looking for more developers, testers, and users. If you are interested in being involved with this project, please contact us via `github`_ or the `mailing list`_. .. image:: images/NumFocusSponsoredStamp.png :alt: NumFocus Sponsored Stamp :align: center :width: 300 :target: http://www.numfocus.org Since August 2015, PyTables is a `NumFOCUS project`_, which means that your donations are fiscally sponsored under the NumFOCUS umbrella. Please consider `donating to NumFOCUS`_. -------- Contents -------- .. toctree:: :maxdepth: 1 User’s Guide Cookbook FAQ other_material Migrating from 2.x to 3.x downloads Release Notes project_pointers Development Development Team ============= Helpful Links ============= * :ref:`genindex` * :ref:`search` .. _github: https://github.com/PyTables/PyTables .. _`mailing list`: https://groups.google.com/group/pytables-users .. _`NumFOCUS project`: http://www.numfocus.org/open-source-projects.html .. _`donating to NumFOCUS`: https://numfocus.salsalabs.org/donate-to-pytables/index.html PyTables-3.7.0/doc/source/other_material.rst000066400000000000000000000077031416254111300210470ustar00rootroot00000000000000============== Other Material ============== Videos ====== These are the videos of a series dedicated to introduce the main features of PyTables in a visual and easy to grasp manner. More videos will be made available with the time: * `HDF5 is for Lovers, SciPy 2012 Tutorial `_: a beginner's introduction to PyTables and HDF5. Presentations ============= Here are the slides of some presentations about PyTables that you may find useful: * HDF5 is for Lovers, SciPy 2012 Tutorial, July 2012, Austin, TX, USA, `slides (pdf) `_, `video `_, `exercises `_, `solutions `_, and `repository `_. * `An on-disk binary data container `_. Talk given at the `Austin Python Meetup `_, Austin, TX, USA (May 2012). * `Large Data Analysis with Python `_. Seminar given at the `German Neuroinformatics Node `_, Munich, Germany (November 2010). * `Starving CPUs (and coping with that in PyTables) `_. Seminar given at `FOM Institute for Plasma Physics Rijnhuizen `_, The Netherlands (September 2009). * `On The Data Access Issue (or Why Modern CPUs Are Starving) `_. Keynote presented at `EuroSciPy 2009 `_ conference in Leipzig, Germany (July 2009). * `An Overview of Future Improvements to OPSI `_. Informal talk given at the `THG headquarters `_ in Urbana-Champaign, Illinois, USA (October 2007). * `Finding Needles in a Huge DataStack `_. Talk given at the **EuroPython 2006 Conference**, held at CERN, Genève, Switzerland (July 2006). * `Presentation given at the "HDF Workshop 2005" `_, held at San Francisco, USA (December 2005). * `I `_ and `II `_ **Workshop in Free Software and Scientific Computing** given at the Universitat Jaume I, Castelló, Spain (October 2004). In Catalan. * `Presentation given at the "SciPy Workshop 2004" `_, held at Caltech, Pasadena, USA (September 2004). * `Slides `_ of presentation given at **EuroPython Conference** in Charleroi, Belgium (June 2003). * `Presentation for the "iParty5" `_ held at Castelló, Spain (May 2003). In Spanish. * `Talk `_ on PyTables given at the **PyCon 2003 Convention** held at Washington, USA (March 203). Reports ======= * White Paper on `OPSI indexes `_, explaining the powerful new indexing engine in PyTables Pro. * `Performance study `_ on how the new object tree cache introduced in PyTables 1.2 can accelerate the opening of files with a large number of objects, while being quite less memory hungry. * `Paper version `_ of the presentation at PyCon2003. Other sources for examples ========================== The examples presented above show just a little amount of the full capabilities of PyTables. Please check out the documentation and the :file:`examples/` directory in the source package for more examples. PyTables-3.7.0/doc/source/project_pointers.rst000066400000000000000000000016651416254111300214420ustar00rootroot00000000000000================ Project pointers ================ * `Project Home Page `_ * `GitHub Project Page `_ * `Online HTML Documentation `_ * `Download area `_ * `Git Repository browser `_ * `Users Mailing List `_ * `Announce Mailing List `_ * `Developers Mailing List `_ * Continuous Integration: - `GitHub Actions (GHA) `_ * `Project page on PyPi `_ * `Project Page on SourceForge.net `_ (needs update) * `Project page on Launchpad `_ (going to be closed) PyTables-3.7.0/doc/source/release-notes/000077500000000000000000000000001416254111300200555ustar00rootroot00000000000000PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v0.7.1.rst000066400000000000000000000027431416254111300235560ustar00rootroot00000000000000PyTables 0.7.1 is out! ---------------------- This is a mainly a bug-fixing release, where the next problems has been addressed: - Fixed several memory leaks. After that, the memory consumption when using large object trees has dropped sensibly. However, there remains some small leaks, but hopefully they are not very important unless you use *huge* object trees. - Fixed a bug that make the __getitem__ special method in table to fail when the stop parameter in a extended slice was not specified. That is, table[10:] now correctly returns table[10:table.nrows+1], and not table[10:11]. - The removeRows() method in Table did not update the NROWS attribute in Table objects, giving place to errors after doing further updating operations (removing or adding more rows) in the same table. This has been fixed now. Apart of these fixes, a new lazy reading algorithm for attributes has been activated by default. With that, the opening of objects with large hierarchies has been improved by 60% (you can obtain another additional 10% if using python 2.3 instead of python 2.2). The documentation has been updated as well, specially a more detailed instructions on the compression (zlib) libraries installation. Also, a stress test has been conducted in order to see if PyTables can *really* work not only with large data tables, but also with large object trees. In it, it has been generated and checked a file with more than 1 TB of size and more than 100 thousand tables on it!. PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v0.7.2.rst000066400000000000000000000023621416254111300235540ustar00rootroot00000000000000What's new in PyTables 0.7.2 ---------------------------- This is a mainly a maintenance release, where the next issues has been addressed: - Fixed a nasty memory leak located on the C libraries (It was occurring during attribute writes). After that, the memory consumption when using large object trees has dropped quite a bit. However, there remains some small leaks that has been tracked down to the underlying numarray library. These leaks has been reported, and hopefully they should be fixed more sooner than later. - Table buffers are built dinamically now, so if Tables are not accessed for reading or writing this memory will not be booked. This will help to reduce the memory consumption. - The opening of files with lots of nodes has been optimized between a factor 2 and 3. For example, a file with 10 groups and 3000 tables that takes 9.3 seconds to open in 0.7.1, now takes only 2.8 seconds. - The Table.read() method has been refactored and optimized and some parts of its code has been moved to Pyrex. In particular, in the special case of step=1, up to a factor 5 of speedup (reaching 160 MB/s on a Pentium4 @ 2 GHz) when reading table contents can be achieved now. Enjoy!, -- Francesc Alted falted@openlc.org PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v0.8.rst000066400000000000000000000144761416254111300234260ustar00rootroot00000000000000What's new in PyTables 0.8 ---------------------------- On this release, many enhancements has been added and some bugs has been fixed. Here is the (non-exhaustive) list: - The new VLArray class enables you to store large lists of rows containing variable numbers of elements. The elements can be scalars or fully multidimensional objects, in the PyTables tradition. This class supports two special objects as rows: Unicode strings (UTF-8 codification is used internally) and generic Python objects (through the use of cPickle). - The new EArray class allows you to enlarge already existing multidimensional homogeneous data objects. Consider it an extension of the already existing Array class, but with more functionality. Online compression or other filters can be applied to EArray instances, for example. Another nice feature of EA's is their support for fully multidimensional data selection with extended slices. You can write "earray[1,2:3,...,4:200]", for example, to get the desired dataset slice from the disk. This is implemented using the powerful selection capabilities of the HDF5 library, which results in very highly efficient I/O operations. The same functionality has been added to Array objects as well. - New UnImplemented class. If a dataset contains unsupported datatypes, it will be associated with an UnImplemented instance, then inserted into to the object tree as usual. This allows you to continue to work with supported objects while retaining access to attributes of unsupported datasets. This has changed from previous versions, where a RuntimeError occurred when an unsupported object was encountered. The combination of the new UnImplemented class with the support for new datatypes will enable PyTables to greatly increase the number of types of native HDF5 files that can be read and modified. - Boolean support has been added for all the Leaf objects. - The Table class has now an append() method that allows you to save large buffers of data in one go (i.e. bypassing the Row accessor). This can greatly improve data gathering speed. - The standard HDF5 shuffle filter (to further enhance the compression level) is supported. - The standard HDF5 fletcher32 checksum filter is supported. - As the supported number of filters is growing (and may be further increased in the future), a Filters() class has been introduced to handle filters more easily. In order to add support for this class, it was necessary to make a change in the createTable() method that is not backwards compatible: the "compress" and "complib" parameters are deprecated now and the "filters" parameter should be used in their place. You will be able to continue using the old parameters (only a Deprecation warning will be issued) for the next few releases, but you should migrate to the new version as soon as possible. In general, you can easily migrate old code by substituting code in its place:: table = fileh.createTable(group, 'table', Test, '', complevel, complib) should be replaced by:: table = fileh.createTable(group, 'table', Test, '', Filters(complevel, complib)) - A copy() method that supports slicing and modification of filtering capabilities has been added for all the Leaf objects. See the User's Manual for more information. - A couple of new methods, namely copyFile() and copyChilds(), have been added to File class, to permit easy replication of complete hierarchies or sub-hierarchies, even to other files. You can change filters during the copy process as well. - Two new utilities has been added: ptdump and ptrepack. The utility ptdump allows the user to examine the contents of PyTables files (both metadata and actual data). The powerful ptrepack utility lets you selectively copy (portions of) hierarchies to specific locations in other files. It can be also used as an importer for generic HDF5 files. - The meaning of the stop parameter in read() methods has changed. Now a value of 'None' means the last row, and a value of 0 (zero) means the first row. This is more consistent with the range() function in python and the __getitem__() special method in numarray. - The method Table.removeRows() is no longer limited by table size. You can now delete rows regardless of the size of the table. - The "numarray" value has been added to the flavor parameter in the Table.read() method for completeness. - The attributes (.attr instance variable) are Python properties now. Access to their values is no longer lazy, i.e. you will be able to see both system or user attributes from the command line using the tab-completion capability of your python console (if enabled). - Documentation has been greatly improved to explain all the new functionality. In particular, the internal format of PyTables is now fully described. You can now build "native" PyTables files using any generic HDF5 software by just duplicating their format. - Many new tests have been added, not only to check new functionality but also to more stringently check existing functionality. There are more than 800 different tests now (and the number is increasing :). - PyTables has a new record in the data size that fits in one single file: more than 5 TB (yeah, more than 5000 GB), that accounts for 11 GB compressed, has been created on an AMD Opteron machine running Linux-64 (the 64 bits version of the Linux kernel). See the gory details in: http://pytables.sf.net/html/HowFast.html. - New platforms supported: PyTables has been compiled and tested under Linux32 (Intel), Linux64 (AMD Opteron and Alpha), Win32 (Intel), MacOSX (PowerPC), FreeBSD (Intel), Solaris (6, 7, 8 and 9 with UltraSparc), IRIX64 (IRIX 6.5 with R12000) and it probably works in many more architectures. In particular, release 0.8 is the first one that provides a relatively clean porting to 64-bit platforms. - As always, some bugs have been solved (especially bugs that occur when deleting and/or overwriting attributes). - And last, but definitely not least, a new donations section has been added to the PyTables web site (http://sourceforge.net/projects/pytables, then follow the "Donations" tag). If you like PyTables and want this effort to continue, please, donate! Enjoy!, -- Francesc Alted falted@pytables.org PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v0.9.1.rst000066400000000000000000000047211416254111300235560ustar00rootroot00000000000000What's new in PyTables 0.9.1 ---------------------------- This release is mainly a maintenance version. In it, some bugs has been fixed and a few improvements has been made. One important thing is that chunk sizes in EArrays has been re-tuned to get much better performance. Besides, it has been tested against the latest Python 2.4 and all unit tests seems to pass fine. More in detail: Improvements: - The chunksize computation for EArrays has been re-tuned to allow the compression rations that were usual before 0.9 release. - New --unpackshort and --quantize flags has been added to nctoh5 script. --unpackshort unpack short integer variables to float variables using scale_factor and add_offset netCDF variable attributes. --quantize quantize data to improve compression using least_significant_digit netCDF variable attribute (not active by default). See https://www.ogc.org/standards/netcdf for further explanation of what this attribute means. Thanks to Jeff Whitaker for providing this. - Table.itersequence has received a new parameter called "sort". This allows to disable the sorting of the sequence in case the user wants so. Backward-incompatible changes: - Now, the AttributeSet class throw an AttributeError on __getattr__ for nonexistent attributes in it. Formerly, the routine returned None, which is pretty much against convention in Python and breaks the built-in hasattr() function. Thanks to Robert Nemec for noting this and offering a patch. - VLArray.read() has changed its behaviour. Now, it always returns a list, as stated in documentation, even when the number of elements to return is 0 or 1. This is much more consistent when representing the actual number of elements on a certain VLArray row. API additions: - A Row.getTable() has been added. It is an accessor for the associated Table object. - A File.copyAttrs() has been added. It allows copying attributes from one leaf to other. Properly speaking, this was already there, but not documented :-/ Bug fixes: - Now, the copy of hierarchies works even when there are scalar Arrays (i.e. Arrays which shape is ()) on it. Thanks to Robert Nemec for providing a patch. - Solved a memory leak regarding the Filters instance associated with the File object, that was not released after closing the file. Now, there are no known leaks on PyTables itself. - Improved security of nodes name checking. Closes #1074335 Enjoy data!, -- Francesc Altet falted@pytables.org PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v0.9.rst000066400000000000000000000115171416254111300234200ustar00rootroot00000000000000What's new in PyTables 0.9 ========================== On this release you will find a series of quite exciting new features, being the most important the indexing capabilities, in-kernel selections, support for complex datatypes and the possibility to modify values in both tables *and* arrays (yeah, finally :). New features: ------------- - Indexing of columns in tables. That allow to make data selections on tables up to 500 times faster than standard selections (for ex. doing a selection along an indexed column of 100 million of rows takes less than 1 second on a modern CPU). Perhaps the most interesting thing about the indexing algorithm implemented by PyTables is that the time taken to index grows *linearly* with the length of the data, so, making the indexation process to be *scalable* (quite differently to many relational databases). This means that it can index, in a relatively quick way, arbitrarily large table columns (for ex. indexing a column of 100 million of rows takes just 100 seconds, i.e. at a rate of 1 Mrow/sec). See more detailed info about that in http://www.pytables.org/docs/SciPy04.pdf. - In-kernel selections. This feature allow to make data selections on tables up to 5 times faster than standard selections (i.e. pre-0.9 selections), without a need to create an index. As a hint of how fast these selections can be, they are up to 10 times faster than a traditional relational database. Again, see http://www.pytables.org/docs/SciPy04.pdf for some experiments on that matter. - Support of complex datatypes for all the data objects (i.e. Table, Array, EArray and VLArray). With that, the complete set of datatypes of Numeric and numarray packages are supported. Thanks to Tom Hedley for providing the patches for Array, EArray and VLArray objects, as well as updating the User's Manual and adding unit tests for the new functionality. - Modification of values. You can modify Table, Array, EArray and VLArray values. See Table.modifyRows, Table.modifyColumns() and the newly introduced __setitem__() method for Table, Array, EArray and VLArray entities in the Library Reference of User's Manual. - A new sub-package called "nodes" is there. On it, there will be included different modules to make more easy working with different entities (like images, files, ...). The first module that has been added to this sub-package is "FileNode", whose mission is to enable the creation of a database of nodes which can be used like regular opened files in Python. In other words, you can store a set of files in a PyTables database, and read and write it as you would do with any other file in Python. Thanks to Ivan Vilata i Balaguer for contributing this. Improvements: ------------- - New __len__(self) methods added in Arrays, Tables and Columns. This, in combination with __getitem__(self,key) allows to better emulate sequences. - Better capabilities to import generic HDF5 files. In particular, Table objects (in the HDF5_HL naming schema) with "holes" in their compound type definition are supported. That allows to read certain files produced by NASA (thanks to Stephen Walton for reporting this). - Much improved test units. More than 2000 different tests has been implemented which accounts for more than 13000 loc (this represents twice of the PyTables library code itself (!)). Backward-incompatible API changes: ---------------------------------- - The __call__ special method has been removed from objects File, Group, Table, Array, EArray and VLArray. Now, you should use walkNodes() in File and Group and iterrows in Table, Array, EArray and VLArray to get the same functionality. This would provide better compatibility with IPython as well. 'nctoh5', a new importing utility: - Jeff Whitaker has contributed a script to easily convert NetCDF files into HDF5 files using Scientific Python and PyTables. It has been included and documented as a new utility. Bug fixes: ---------- - A call to File.flush() now invoke a call to H5Fflush() so to effectively flushing all the file contents to disk. Thanks to Shack Toms for reporting this and providing a patch. - SF #1054683: Security hole in utils.checkNameValidity(). Reported in 2004-10-26 by ivilata - SF #1049297: Suggestion: new method File.delAttrNode(). Reported in 2004-10-18 by ivilata - SF #1049285: Leak in AttributeSet.__delattr__(). Reported in 2004-10-18 by ivilata - SF #1014298: Wrong method call in examples/tutorial1-2.py. Reported in 2004-08-23 by ivilata - SF #1013202: Cryptic error appending to EArray on RO file. Reported in 2004-08-21 by ivilata - SF #991715: Table.read(field="var1", flavor="List") fails. Reported in 2004-07-15 by falted - SF #988547: Wrong file type assumption in File.__new__. Reported in 2004-07-10 by ivilata Bon profit!, -- Francesc Altet falted@pytables.org PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v1.0.rst000066400000000000000000000174721416254111300234160ustar00rootroot00000000000000============================ What's new in PyTables 1.0 ============================ :Author: Francesc Altet :Contact: faltet@carabos.com :Author: Ivan Vilata i Balaguer :Contact: ivilata@carabos.com This document details the modifications to PyTables since version 0.9.1. Its main purpose is help you ensure that your programs will be runnable when you switch from PyTables 0.9.1 to PyTables 1.0. API additions ============= - The new ``Table.col()`` method can be used to get a column from a table as a ``NumArray`` or ``CharArray`` object. This is preferred over the syntax ``table['colname']``. - The new ``Table.readCoordinates()`` method reads a set of rows given their indexes into an in-memory object. - The new ``Table.readAppend()`` method Append rows fulfilling the condition to a destination table. Backward-incompatible changes ============================= - Trying to open a nonexistent file or a file of unknown type raises ``IOError`` instead of ``RuntimeError``. Using an invalid mode raises ``ValueError`` instead of ``RuntimeError``. - Getting a child node from a closed group raises ``ValueError`` instead of ``RuntimeError``. - Running an action on the wrong type of node now (i.e. using ``file.listNodes()`` on a leaf) raises a ``TypeError`` instead of a ``NodeError``. - Removing a non-existing child now raises a ``NoSuchNodeError``, instead of doing nothing. - Removing a non-empty child group using ``del group.child`` fails with a ``NodeError`` instead of recursively doing the removal. This is because of the potential damage it may cause when used inadvertently. If a recursive behavior is needed, use the ``_f_remove()`` method of the child node. - The `recursive` flag of ``Group._f_walkNodes()`` is ``True`` by default now. Before it was ``False``. - Now, deleting and getting a non-existing attribute raises an ``AttributeError`` instead of a ``RuntimeError``. - Swapped last two arguments of ``File.copyAttrs()`` to match the other methods. Please use ``File.copyNodeAttrs()`` anyway. - Failing to infer the size of a string column raises ``ValueError`` instead of ``RuntimeError``. - Excessive table column name length and number of columns now raise ``ValueError`` instead of ``IndexError`` and ``NameError``. - Excessive table row length now raises ``ValueError`` instead of ``RuntimeError``. - ``table[integer]`` returns a ``numarray.records.Record`` object instead of a tuple. This was the original behavior before PyTables 0.9 and proved to be more consistent than the last one (tables do not have an explicit ordering of columns). - Specifying a nonexistent column in ``Table.read()`` raises a ``ValueError`` instead of a ``LookupError``. - When ``start >= stop`` an empty iterator is returned by ``Table.iterrows()`` instead of an empty ``RecArray``. Thanks to Ashley Walsh for noting this. - The interface of ``isHDF5File()`` and ``isPyTablesFile()`` file has been unified so that they both return true or false values on success and raise ``HDF5ExtError`` or errors. The true value in ``isPyTablesFile()`` is the format version string of the file. - ``Table.whereIndexed()`` and ``Table.whereInRange()`` are now *private* methods, since the ``Table.where()`` method is able to choose the most adequate option. - The global variables ``ExtVersion`` and ``HDF5Version`` have been renamed to ``extVersion`` and ``hdf5Version``, respectively. - ``whichLibVersion()`` returns ``None`` on querying unavailable libraries, and raises ``ValueError`` on unknown ones. The following modifications, though being (strictly speaking) modifications of the API, will most probably not cause compatibility problems (but your mileage may vary): - The default values for ``name`` and ``classname`` arguments in ``File.getNode()`` are now ``None``, although the empty string is still allowed for backwards compatibility. File hierarchy manipulation and attribute handling operations using those arguments have changed to reflect this. - Copy operations (``Group._f_copyChildren()``, ``File.copyChildren()``, ``File.copyNode()``...) do no longer return a tuple with the new node and statistics. Instead, they only return the new node, and statistics are collected via an optional keyword argument. - The ``copyFile()`` function in ``File.py`` has changed its signature from:: copyFile(srcfilename=None, dstfilename=None, title=None, filters=None, copyuserattrs=True, overwrite=False, stats=None) to:: copyFile(srcfilename, dstfilename, overwrite=False, **kwargs) Thus, the function allows the same options as ``File.copyFile()``. - The ``File.copyFile()`` method has changed its signature from:: copyFile(self, dstfilename=None, title=None, filters=None, copyuserattrs=1, overwrite=0, stats=None): to:: copyFile(self, dstfilename, overwrite=False, **kwargs) This enables this method to pass on arbitrary flags and options supported by copying methods of inner nodes in the hierarchy. - The ``File.copyChildren()`` method has changed its signature from:: copyChildren(self, wheresrc, wheredst, recursive=False, filters=None, copyuserattrs=True, start=0, stop=None, step=1, overwrite=False, stats=None) to:: copyChildren(self, srcgroup, dstgroup, overwrite=False, recursive=False, **kwargs): Thus, the function allows the same options as ``Group._f_copyChildren()``. - The ``Group._f_copyChildren()`` method has changed its signature from:: _f_copyChildren(self, where, recursive=False, filters=None, copyuserattrs=True, start=0, stop=None, step=1, overwrite=False, stats=None) to:: _f_copyChildren(self, dstgroup, overwrite=False, recursive=False, **kwargs) This enables this method to pass on arbitrary flags and options supported by copying methods of inner nodes in the group. - Renamed ``srcFilename`` and ``dstFilename`` arguments in ``copyFile()`` and ``File.copyFile()`` to ``srcfilename`` and ``dstfilename``, respectively. Renamed ``whereSrc`` and ``whereDst`` arguments in ``File.copyChildren()`` to ``wheresrc`` and ``wheredst``, respectively. Renamed ``dstNode`` argument in ``File.copyAttrs()`` to ``dstnode``. Tose arguments should be easier to type in interactive sessions (although 99% of the time it is not necessary to specify them). - Renamed ``object`` argument in ``EArray.append()`` to ``sequence``. - The ``rows`` argument in ``Table.append()`` is now compulsory. - The ``start`` argument in ``Table.removeRows()`` is now compulsory. API refinements =============== - The ``isHDF5()`` function has been deprecated in favor of ``isHDF5File()``. - Node attribute-handling methods in ``File`` have been renamed for a better coherence and understanding of their purpose: * ``getAttrNode()`` is now called ``getNodeAttr()`` * ``setAttrNode()`` is now called ``setNodeAttr()`` * ``delAttrNode()`` is now called ``delNodeAttr()`` * ``copyAttrs()`` is now called ``copyNodeAttrs()`` They keep their respective signatures, and the old versions still exist for backwards compatibility, though they issue a ``DeprecationWarning``. - Using ``VLArray.append()`` with multiple arguments is now deprecated for its ambiguity. You should put the arguments in a single sequence object (list, tuple, array...) and pass it as the only argument. - Using ``table['colname']`` is deprecated. Using ``table.col('colname')`` (with the new ``col()`` method) is preferred. Bug fixes (affecting API) ========================= - ``Table.iterrows()`` returns an empty iterator when no rows are selected, instead of returning ``None``. ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: text .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v1.1.1.rst000066400000000000000000000023421416254111300235440ustar00rootroot00000000000000============================== What's new in PyTables 1.1.1 ============================== :Author: Francesc Altet :Contact: faltet@carabos.com :Author: Ivan Vilata i Balaguer :Contact: ivilata@carabos.com This document details the modifications to PyTables since version 1.0. Its main purpose is help you ensure that your programs will be runnable when you switch from PyTables 1.0 to PyTables 1.1.1. API additions ============= - None Backward-incompatible changes ============================= - ``Table.read()`` raises a ``KeyError`` instead of a ``ValueError`` when a nonexistent field name is specified, for consistency with other methods. The same goes for the ``col()`` method. - ``File.__contains__()`` returns a true value when it is asked for an existent node, be it visible or not. This is more consistent with ``Group.__contains__()``. API refinements =============== - Using ``table.cols['colname']`` is deprecated. The usage of ``table.cols._f_col('colname')`` (with the new ``Cols._f_col()`` method) is preferred. Bug fixes (affecting API) ========================= - None ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: text .. coding: utf-8 .. fill-column: 72 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v1.1.rst000066400000000000000000000023521416254111300234060ustar00rootroot00000000000000============================ What's new in PyTables 1.1 ============================ :Author: Francesc Altet :Contact: faltet@carabos.com :Author: Ivan Vilata i Balaguer :Contact: ivilata@carabos.com This document details the modifications to PyTables since version 1.0. Its main purpose is help you ensure that your programs will be runnable when you switch from PyTables 1.0 to PyTables 1.1. API additions ============= - something... Backward-incompatible changes ============================= - ``Table.read()`` raises a ``KeyError`` instead of a ``ValueError`` when a nonexistent field name is specified, for consistency with other methods. The same goes for the ``col()`` method. - ``File.__contains__()`` returns a true value when it is asked for an existent node, be it visible or not. This is more consistent with ``Group.__contains__()``. API refinements =============== - Using ``table.cols['colname']`` is deprecated. The usage of ``table.cols._f_col('colname')`` (with the new ``Cols._f_col()`` method) is preferred. Bug fixes (affecting API) ========================= - something... ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: text .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v1.2.1.rst000066400000000000000000000014361416254111300235500ustar00rootroot00000000000000============================== What's new in PyTables 1.2.1 ============================== :Author: Francesc Altet :Contact: faltet@carabos.com :Author: Ivan Vilata i Balaguer :Contact: ivilata@carabos.com This document details the modifications to PyTables since version 1.2. Its main purpose is help you ensure that your programs will be runnable when you switch from PyTables 1.2 to PyTables 1.2.1. API additions ============= - None Backward-incompatible changes ============================= - None Deprecated features =================== - None API refinements =============== - None Bug fixes (affecting API) ========================= - None ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: text .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v1.2.2.rst000066400000000000000000000014361416254111300235510ustar00rootroot00000000000000============================== What's new in PyTables 1.2.2 ============================== :Author: Francesc Altet :Contact: faltet@carabos.com :Author: Ivan Vilata i Balaguer :Contact: ivilata@carabos.com This document details the modifications to PyTables since version 1.2. Its main purpose is help you ensure that your programs will be runnable when you switch from PyTables 1.2 to PyTables 1.2.2. API additions ============= - None Backward-incompatible changes ============================= - None Deprecated features =================== - None API refinements =============== - None Bug fixes (affecting API) ========================= - None ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: text .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v1.2.3.rst000066400000000000000000000014361416254111300235520ustar00rootroot00000000000000============================== What's new in PyTables 1.2.3 ============================== :Author: Francesc Altet :Contact: faltet@carabos.com :Author: Ivan Vilata i Balaguer :Contact: ivilata@carabos.com This document details the modifications to PyTables since version 1.2. Its main purpose is help you ensure that your programs will be runnable when you switch from PyTables 1.2 to PyTables 1.2.3. API additions ============= - None Backward-incompatible changes ============================= - None Deprecated features =================== - None API refinements =============== - None Bug fixes (affecting API) ========================= - None ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: text .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v1.2.rst000066400000000000000000000065751416254111300234220ustar00rootroot00000000000000============================ What's new in PyTables 1.2 ============================ :Author: Francesc Altet :Contact: faltet@carabos.com :Author: Ivan Vilata i Balaguer :Contact: ivilata@carabos.com This document details the modifications to PyTables since version 1.1. Its main purpose is help you ensure that your programs will be runnable when you switch from PyTables 1.1 to PyTables 1.2. API additions ============= - The user is now allowed to set arbitrary Python (non-persistent) attributes on any instance of ``Node``. If the name matches that of a child node, the later will no longer be accessible via natural naming, but it will still be available via ``File.getNode()``, ``Group._f_getChild()`` and the group children dictionaries. Of course, this allows the user to overwrite internal (``^_[cfgv]_``) PyTables variables, but this is the way most Python packages work. - The new ``Group._f_getChild()`` method allows to get a child node (be it visible or not) by its name. This should be more intuitive that using ``getattr()`` or using the group children dictionaries. - The new ``File.isVisibleNode()``, ``Node._f_isVisible()`` and ``Leaf.isVisible()`` methods tell whether a node is visible or not, i.e. if the node will appear in listing operations such as ``Group._f_listNodes()``. Backward-incompatible changes ============================= - ``File.objects``, ``File.groups`` and ``File.leaves`` can no longer be used to iterate over all the nodes in the file. However, they still may be used to access any node by its path. - ``File.__contains__()`` returns a true value when it is asked for an existent node, be it visible or not. This is more consistent with ``Group.__contains__()``. - Using ``Group.__delattr__()`` to remove a child is no longer supported. Please use ``Group._f_remove()`` instead. - The ``indexprops`` attribute is now present on all ``Table`` instances, be they indexed or not. In the last case, it is ``None``. - Table.getWhereList() now has flavor parameter equal to "NumArray" by default, which is more consistent with other methods. Before, flavor defaulted to "List". - The ``extVersion`` variable does no longer exist. It did not make much sense either, since the canonical version of the whole PyTables package is that of ``__version__``. - The ``Row.nrow()`` has been converted into a property, so you have to replace any call to ``Row.nrow()`` into ``Row.nrow``. Deprecated features =================== - The ``objects``, ``groups`` and ``leaves`` mappings in ``File`` are retained only for compatibility purposes. Using ``File.getNode()`` is recommended to access nodes, ``File.__contains__()`` to check for node existence, and ``File.walkNodes()`` for iteration purposes. Using ``isinstance()`` and ``*isVisible*()`` methods is the preferred way of checking node type and visibility. Please note that the aforementioned mappings use the named methods internally, so the former have no special performance gains over the later. API refinements =============== - The ``isHDF5File()`` and ``isPyTablesFile()`` functions know how to handle nonexistent or unreadable files. An ``IOError`` is raised in those cases. Bug fixes (affecting API) ========================= - None ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: text .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v1.3.1.rst000066400000000000000000000024401416254111300235450ustar00rootroot00000000000000============================== What's new in PyTables 1.3.1 ============================== :Author: Francesc Altet :Contact: faltet@carabos.com :Author: Ivan Vilata i Balaguer :Contact: ivilata@carabos.com This document details the modifications to PyTables since version 1.2. Its main purpose is help you ensure that your programs will be runnable when you switch from PyTables 1.2 to PyTables 1.3.1. API additions ============= - The Table.Cols accessor has received a new __setitem__() method that allows doing things like: table.cols[4] = record table.cols.x[4:1000:2] = array # homogeneous column table.cols.Info[4:1000:2] = recarray # nested column Backward-incompatible changes ============================= - None Deprecated features =================== - None API refinements =============== - Table.itersequence has changed the default value for 'sort' parameter. It is now False by default, as it is not clear if this actually accelerates the iterator, so it is better to let to the user doing the proper checks (if he is interested at all). Bug fixes (affecting API) ========================= - None ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: text .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v1.3.2.rst000066400000000000000000000024211416254111300235450ustar00rootroot00000000000000============================== What's new in PyTables 1.3.2 ============================== :Author: Francesc Altet :Contact: faltet@carabos.com :Author: Ivan Vilata i Balaguer :Contact: ivilata@carabos.com This document details the modifications to PyTables since version 1.2. Its main purpose is help you ensure that your programs will be runnable when you switch from PyTables 1.2 to PyTables 1.3.2. API additions ============= - The ``Table.Cols`` accessor has received a new ``__setitem__()`` method that allows doing things like:: table.cols[4] = record table.cols.x[4:1000:2] = array # homogeneous column table.cols.Info[4:1000:2] = recarray # nested column Backward-incompatible changes ============================= - None Deprecated features =================== - None API refinements =============== - ``Table.itersequence()`` has changed the default value for the ``sort`` parameter. It is now false by default, as it is not clear if this actually accelerates the iterator, so it is better to let the user do the proper checks (if interested). Bug fixes (affecting API) ========================= - None ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: text .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v1.3.3.rst000066400000000000000000000014371416254111300235540ustar00rootroot00000000000000============================== What's new in PyTables 1.3.3 ============================== :Author: Francesc Altet :Contact: faltet@carabos.com :Author: Ivan Vilata i Balaguer :Contact: ivilata@carabos.com This document details the modifications to PyTables since version 1.2. Its main purpose is help you ensure that your programs will be runnable when you switch from PyTables 1.2 to PyTables 1.3.3. API additions ============= - None Backward-incompatible changes ============================= - None Deprecated features =================== - None API refinements =============== - None Bug fixes (affecting API) ========================= - None ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: text .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v1.3.rst000066400000000000000000000024301416254111300234050ustar00rootroot00000000000000============================ What's new in PyTables 1.3 ============================ :Author: Francesc Altet :Contact: faltet@carabos.com :Author: Ivan Vilata i Balaguer :Contact: ivilata@carabos.com This document details the modifications to PyTables since version 1.2. Its main purpose is help you ensure that your programs will be runnable when you switch from PyTables 1.2 to PyTables 1.3. API additions ============= - The Table.Cols accessor has received a new __setitem__() method that allows doing things like: table.cols[4] = record table.cols.x[4:1000:2] = array # homogeneous column table.cols.Info[4:1000:2] = recarray # nested column Backward-incompatible changes ============================= - None Deprecated features =================== - None API refinements =============== - Table.itersequence has changed the default value for 'sort' parameter. It is now False by default, as it is not clear if this actually accelerates the iterator, so it is better to let to the user doing the proper checks (if he is interested at all). Bug fixes (affecting API) ========================= - None ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: text .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v1.4.rst000066400000000000000000000031621416254111300234110ustar00rootroot00000000000000============================ What's new in PyTables 1.4 ============================ :Author: Francesc Altet :Contact: faltet@carabos.com :Author: Ivan Vilata i Balaguer :Contact: ivilata@carabos.com This document details the modifications to PyTables since version 1.3. Its main purpose is help you ensure that your programs will be runnable when you switch from PyTables 1.3 to PyTables 1.4. API additions ============= - The ``Table.getWhereList()`` method has got a new ``sort`` parameter. The default now is to get the list of parameters unsorted. Set ``sort`` to True to get the old behaviour. We've done this to avoid unnecessary ordering of potentially large sets of coordinates. - Node creation, copying and moving operations have received a new optional `createparents` argument. When true, the necessary groups in the target path that don't exist at the time of running the operation are automatically created, so that the target group of the operation always exists. Backward-incompatible changes ============================= - None Deprecated features =================== - None API refinements =============== - ``Description._v_walk()`` has been renamed to ``_f_walk()``, since it is a public method, not a value. - ``Table.removeIndex()`` now accepts a column name in addition to an ``Index`` instance (the later is deprecated). This avoids the user having to retrieve the needed ``Index`` object. Bug fixes (affecting API) ========================= - None ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: text .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v2.0.x-pro.rst000066400000000000000000000504571416254111300244630ustar00rootroot00000000000000=========================================== Release notes for PyTables Pro 2.0 series =========================================== :Author: Francesc Alted i Abad :Contact: faltet@pytables.com :Author: Ivan Vilata i Balaguer :Contact: ivan@selidor.net Changes from 2.0.3 to 2.0.4 =========================== - Selections in tables works now in threaded environments. The problem was in the Numexpr package -- the solution has been reported to the upstream authors too. Fixes #164. - PyTables had problems importing native HDF5 files with gaps in nested compound types. This has been solved. Fixes #173. - In order to prevent a bug existing in HDF5 1.6 series, the ``EArray.truncate()`` method refused to accept a 0 as parameter (i.e. truncate an existing EArray to have zero rows did not work). As this has been fixed in the recent HDF5 1.8 series, this limitation has been removed (but only if the user has one of these installed). Fixes #171. - Small fixes for allowing the test suite to pass when using the new NumPy 1.1. However, it remains a small issue with the way the new NumPy represents complex numbers. I'm not fixing that in the PyTables suite, as there are chances that this can be fixed in NumPy itself (see ticket #841). Changes from 2.0.2.1 to 2.0.3 ============================= - Replaced the algorithm for computing chunksizes by another that is more general and useful for a larger range of expected dataset sizes. The outcome of the new calculation is the same than before for dataset sizes <= 100 GB. For datasets between 100 GB <= size < 10 TB, larger values are returned. For sizes >= 10 TB a maximum value of 1 MB is always returned. - Added support for the latest 1.8.0 version of the HDF5 library. Fixes ticket #127. - PyTables compiles now against latest versions of Pyrex (0.9.6.4). For the first time, the extensions do compile without warnings! Fixes #159. - Numexpr module has been put in sync with the version in SciPy sandbox. - Added a couple of warnings in User's Guide so as to tell the user that it is not safe to use methods that can change the number of rows of a table in the middle of a row iterator. Fixes #153. - Fixed a problem when updating multidimensional cells using the Row.update() method in the middle of table iterators . Fixes #149. - Fixed a problem when using 64-bit indexes in 32-bit platforms. Solves ticket #148. - Table.indexFilters is working now as documented. However, as per ticket #155, its use is now deprecated (will be removed in 2.1). Fixes #155. Changes from 2.0.2 to 2.0.2.1 ============================= - Optimization added for avoid to unnecessarily update index columns that have not been modified in table update operations. Fixes #139. Changes from 2.0.1 to 2.0.2 =========================== - Fixed a critical bug that returned wrong results when doing repetitive queries affecting the last row part of indices. Fixes #60 of the private Trac of Carabos. - Added ``__enter__()`` and ``__exit__()`` methods to ``File``; fixes #113. With this, and if using Python 2.5 you can do things like: with tables.openFile("test.h5") as h5file: ... - Carefully preserve type when converting NumPy scalar to numarray; fixes #125. - Fixed a nasty bug that appeared when moving or renaming groups due to a bad interaction between ``Group._g_updateChildrenLocation()`` and the LRU cache. Solves #126. - Return 0 when no rows are given to ``Table.modifyRows()``; fixes #128. - Added an informative message when the ``nctoh5`` utility is run without the NetCDF interface of ScientificPython bening installed. - Now, a default representation of closed nodes is provided; fixes #129. Changes from 2.0 to 2.0.1 ========================= - The ``coords`` argument of ``Table.readCoords()`` was not checked for contiguousness, raising fatal errors when it was discontiguous. This has been fixed. - There is an inconsistency in the way used to specify the atom shape in ``Atom`` constructors. When the shape is specified as ``shape=()`` it means a scalar atom and when it is specified as ``shape=N`` it means an atom with ``shape=(N,)``. But when the shape is specified as ``shape=1`` (i.e. in the default case) then a scalar atom is obtained instead of an atom with ``shape=(1,)``. This is inconsistent and not the behavior that NumPy exhibits. Changing this will require a migration path which includes deprecating the old behaviour if we want to make the change happen before a new major version. The proposed path is: 1. In PyTables 2.0.1, we are changing the default value of the ``shape`` argument to ``()``, and issue a ``DeprecationWarning`` when someone uses ``shape=1`` stating that, for the time being, it is equivalent to ``()``, but in near future versions it will become equivalent to ``(1,)``, and recommending the user to pass ``shape=()`` if a scalar is desired. 2. In PyTables 2.1, we will remove the previous warning and take ``shape=N`` to mean ``shape=(N,)`` for any value of N. See ticket #96 for more info. - The info about the ``chunkshape`` attribute of a leaf is now printed in the ``__repr__()`` of chunked leaves (all except ``Array``). - After some scrupulous benchmarking job, the size of the I/O buffer for ``Table`` objects has been reduced to the minimum that allows maximum performance. This represents more than 10x of reduction in size for that buffer, which will benefit those programs dealing with many tables simultaneously (#109). - In the ``ptrepack`` utility, when ``--complevel`` and ``--shuffle`` were specified at the same time, the 'shuffle' filter was always set to 'off'. This has been fixed (#104). - An ugly bug related with the integrated Numexpr not being aware of all the variations of data arrangements in recarray objects has been fixed (#103). We should stress that the bug only affected the Numexpr version integrated in PyTables, and *not* the original one. - When passing a record array to a table at creation time, its real length is now used instead of the default value for ``expectedrows``. This allows for better performance (#97). - Added some workarounds so that NumPy scalars can be successfully converted to numarray objects. Fixes #98. - PyTables is now able to access table rows beyond 2**31 in 32-bit Python. The problem was a limitation of ``xrange`` and we have replaced it by a new ``lrange`` class written in Pyrex. Moreover, ``lrange`` has been made publicly accessible as a safe 64-bit replacement for ``xrange`` for 32-bit platforms users. Fixes #99. - If a group and a table are created in a function, and the table is accessed through the group, the table can be flushed now. Fixes #94. - It is now possible to directly assign a field in a nested record of a table using the natural naming notation (#93). Changes from 2.0rc2 to 2.0 ========================== - Added support for recognizing native HDF5 files with datasets compressed with szip compressor. - Fixed a problem when asking for the string representation (str()) of closed files. Fixes ticket #79. - Do not take LZO as available when its initialisation fails. - Fixed a glitch in ptrepack utility. When the user wants a copy of a group, and a group is *to be created* in destination, the attributes of the original group *are* copied. If it is *not to be created*, the attributes will *not be* copied. I think this should be what the user would expect most of the times. - Fixed the check for creating intermediate groups in ptrepack utility. Solves ticket #83. - Before, when reading a dataset with an unknown CLASS id, a warning was issued and the dataset mapped to ``UnImplemented``. This closed the door to have the opportunity to try to recognize the dataset and map it to a supported CLASS. Now, when a CLASS attribute is not recognized, an attempt to recognize its associated dataset is made. If it is recognized, the matching class is associated with the dataset. If it is not recognized, then a warning is issued and the dataset becomes mapped to ``UnImplemented``. - Always pass verbose and heavy values in the common test module to test(). Fixes ticket #85. - Now, the ``verbose`` and ``--heavy`` flag passed to test_all.py are honored. - All the DLL's of dependencies are included now in Windows binaries. This should allow for better portability of the binaries. - Fixed the description of Node._v_objectID that was misleading. Changes from 2.0rc1 to 2.0rc2 ============================= - The "Optimization tips" chapter of the User's Guide has been completely updated to adapt to PyTables 2.0 series. In particular, new benchmarks on the much improved indexed queries have been included; you will see that PyTables indexing is competitive (and sometimes much faster) than that of traditional relational databases. With this, the manual should be fairly finished for 2.0 final release. - Large refactoring done on the ``Row`` class. The most important change is that ``Table.row`` is now a single object. This allows to reuse the same ``Row`` instance even after ``Table.flush()`` calls, which can be convenient in many situations. - I/O buffers unified in the ``Row`` class. That allows for bigger savings in memory space whenever the ``Row`` extension is used. - Improved speed (up to a 70%) with unaligned column operations (a quite common scenario when dealing with ``Table`` objects) through the integrated Numexpr. In-kernel searches take advantage of this optimization. - Added ``VLUnicodeAtom`` for storing variable-length Unicode strings in ``VLArray`` objects regardless of encoding. Closes ticket #51. - Added support for ``time`` datatypes to be portable between big-endian and low-endian architectures. This feature is not currently supported natively by the HDF5 library, so the support for such conversion has been added in PyTables itself. Fixes #72. - Added slice arguments to ``Table.readWhere()`` and ``Table.getWhereList()``. Although API changes are frozen, this may still be seen as an inconsistency with other query methods. The patch is backwards-compatible anyway. - Added missing overwrite argument to ``File.renameNode()`` and ``Node._f_rename()``. Fixes ticket #66. - Calling ``tables.test()`` no longer exits the interpreter session. Fixes ticket #67. - Fix comparing strings where one is a prefix of the other in integrated Numexpr. Fixes ticket #76. - Added a check for avoiding an ugly HDF5 message when copying a file over itself (for both ``copyFile()`` and ``File.copyFile()``). Fixes ticket #73. - Corrected the appendix E, were it was said that PyTables doesn't support compounds of compounds (it does since version 1.2!). Changes from 2.0b2 to 2.0rc1 ============================ - The ``lastrow`` argument of ``Table.flushRowsToIndex()`` is no longer public. It was not documented, anyway. Fixes ticket #43. - Added a ``memlevel`` argument to ``Cols.createIndex()`` which allows the user to control the amount of memory required for creating an index. - Added ``blocksizes`` and ``opts`` arguments to ``Cols.createIndex()``, which allow the user to control the sizes of index datasets, and to specify different optimization levels for each index dataset, respectively. These are very low-level options meant only for experienced users. Normal users should stick to the higher-level ``memlevel`` and ``optlevel``. - Query tests have been tuned to exhaustively check the new parametrization of indexes. - A new algorithm has been implemented that better reduces the entropy of indexes. - The API Reference section of the User's Manual (and the matching docstrings) has been completely reviewed, expanded and corrected. This process has unveiled some errors and inconsistencies which have also been fixed. - Fixed ``VLArray.__getitem__()`` to behave as expected in Python when using slices, instead of following the semantics of PyTables' ``read()`` methods (e.g. reading just one element when no stop is provided). Fixes ticket #50. - Removed implicit UTF-8 encoding from ``VLArray`` data using ``vlstring`` atoms. Now a variable-length string is stored as is, which lets users use any encoding of their choice, or none of them. A ``vlunicode`` atom will probably be added to the next release so as to fix ticket #51. - Allow non-sequence objects to be passed to ``VLArray.append()`` when using an ``object`` atom. This was already possible in 1.x but stopped working when the old append syntax was dropped in 2.0. Fixes ticket #63. - Changed ``Cols.__len__()`` to return the number of rows of the table or nested column (instead of the number of fields), like its counterparts in ``Table`` and ``Column``. - Python scalars cached in ``AttributeSet`` instances are now kept as NumPy objects instead of Python ones, because they do become NumPy objects when retrieved from disk. Fixes ticket #59. - Avoid HDF5 error when appending an empty array to a ``Table`` (ticket #57) or ``EArray`` (ticket #49) dataset. - Fix wrong implementation of the top-level ``table.description._v_dflts`` map, which was also including the pathnames of columns inside nested columns. Fixes ticket #45. - Optimized the access to unaligned arrays in Numexpr between a 30% and a 70%. - Fixed a die-hard bug that caused the loading of groups while closing a file. This only showed with certain usage patterns of the LRU cache (e.g. the one caused by ``ManyNodesTestCase`` in ``test_indexes.py`` under Pro). - Avoid copious warnings about unused functions and variables when compiling Numexpr. - Several fixes to Numexpr expressions with all constant values. Fixed tickets #53, #54, #55, #58. Reported bugs to mainstream developers. - Solved an issue when trying to open one of the included test files in append mode on a system-wide installation by a normal user with no write privileges on it. The file isn't being modified anyway, so the test is skipped then. - Added a new benchmark to compare the I/O speed of ``Array`` and ``EArray`` objects with that of ``cPickle``. - The old ``Row.__call__()`` is no longer available as a public method. It was not documented, anyway. Fixes ticket #46. - ``Cols._f_close()`` is no longer public. Fixes ticket #47. - ``Attributes._f_close()`` is no longer public. Fixes ticket #52. - The undocumented ``Description.classdict`` attribute has been completely removed. Fixes ticket #44. Changes from 2.0b1 to 2.0b2 =========================== - A very exhaustive overhauling of the User's Manual is in process. The chapters 1 (Introduction), 2 (Installation), 3 (Tutorials) have been completed (and hopefully, the lines of code are easier to copy&paste now), while chapter 4 (API Reference) has been done up to (and including) the Table class. During this tedious (but critical in a library) overhauling work, we have tried hard to synchronize the text in the User's Guide with that which appears on the docstrings. - Removed the ``recursive`` argument in ``Group._f_walkNodes()``. Using it with a false value was redundant with ``Group._f_iterNodes()``. Fixes ticket #42. - Removed the ``coords`` argument from ``Table.read()``. It was undocumented and redundant with ``Table.readCoordinates()``. Fixes ticket #41. - Fixed the signature of ``Group.__iter__()`` (by removing its parameters). - Added new ``Table.coldescrs`` and ``Table.description._v_itemsize`` attributes. - Added a couple of new attributes for leaves: * ``nrowsinbuf``: the number of rows that fit in the internal buffers. * ``chunkshape``: the chunk size for chunked datasets. - Fixed setuptools so that making an egg out of the PyTables 2 package is possible now. - Added a new ``tables.restrict_flavors()`` function allowing to restrict available flavors to a given set. This can be useful e.g. if you want to force PyTables to get NumPy data out of an old, ``numarray``-flavored PyTables file even if the ``numarray`` package is installed. - Fixed a bug which caused filters of unavailable compression libraries to be loaded as using the default Zlib library, after issuing a warning. Added a new ``FiltersWarning`` and a ``Filters.copy()``. Changes from 1.4.x to 2.0b1 =========================== API additions ------------- - ``Column.createIndex()`` has received a couple of new parameters: ``optlevel`` and ``filters``. The first one sets the desired quality level of the index, while the second one allows the user to specify the filters for the index. - ``Table.indexprops`` has been split into ``Table.indexFilters`` and ``Table.autoIndex``. The later groups the functionality of the old ``auto`` and ``reindex``. - The new ``Table.colpathnames`` is a sequence which contains the full pathnames of all bottom-level columns in a table. This can be used to walk all ``Column`` objects in a table when used with ``Table.colinstances``. - The new ``Table.colinstances`` dictionary maps column pathnames to their associated ``Column`` or ``Cols`` object for simple or nested columns, respectively. This is similar to ``Table.cols._f_col()``, but faster. - ``Row`` has received a new ``Row.fetch_all_fields()`` method in order to return all the fields in the current row. This returns a NumPy void scalar for each call. - New ``tables.test(verbose=False, heavy=False)`` high level function for interactively running the complete test suite from the Python console. - Added a ``tables.print_versions()`` for easily getting the versions for all the software on which PyTables relies on. Backward-incompatible changes ----------------------------- - You can no longer mark a column for indexing in a ``Col`` declaration. The only way of creating an index for a column is to invoke the ``createIndex()`` method of the proper column object *after the table has been created*. - Now the ``Table.colnames`` attribute is just a list of the names of top-level columns in a table. You can still get something similar to the old structure by using ``Table.description._v_nestedNames``. See also the new ``Table.colpathnames`` attribute. - The ``File.objects``, ``File.leaves`` and ``File.groups`` dictionaries have been removed. If you still need this functionality, please use the ``File.getNode()`` and ``File.walkNodes()`` instead. - ``Table.removeIndex()`` is no longer available; to remove an index on a column, one must use the ``removeIndex()`` method of the associated ``Column`` instance. - ``Column.dirty`` is no longer available. If you want to check column index dirtiness, use ``Column.index.dirty``. - ``complib`` and ``complevel`` parameters have been removed from ``File.createTable()``, ``File.createEArray()``, ``File.createCArray()`` and ``File.createVLArray()``. They were already deprecated in PyTables 1.x. - The ``shape`` and ``atom`` parameters have been swapped in ``File.createCArray()``. This has been done to be consistent with ``Atom()`` definitions (i.e. type comes before and shape after). Deprecated features ------------------- - ``Node._v_rootgroup`` has been removed. Please use ``node._v_file.root`` instead. - The ``Node._f_isOpen()`` and ``Leaf.isOpen()`` methods have been removed. Please use the ``Node._v_isopen`` attribute instead (it is much faster). - The ``File.getAttrNode()``, ``File.setAttrNode()`` and ``File.delAttrNode()`` methods have been removed. Please use ``File.getNodeAttr()``, ``File.setNodeAttr()`` and ``File.delNodeAttr()`` instead. - ``File.copyAttrs()`` has been removed. Please use ``File.copyNodeAttrs()`` instead. - The ``table[colname]`` idiom is no longer supported. You can use ``table.cols._f_col(column)`` for doing the same. API refinements --------------- - ``File.createEArray()`` received a new ``shape`` parameter. This allows to not have to use the shape of the atom so as to set the shape of the underlying dataset on disk. - All the leaf constructors have received a new ``chunkshape`` parameter that allows specifying the chunk sizes of datasets on disk. - All ``File.create*()`` factories for ``Leaf`` nodes have received a new ``byteorder`` parameter that allows the user to specify the byteorder in which data will be written to disk (data in memory is now always handled in *native* order). ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: rst .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v2.0.x.rst000066400000000000000000000456221416254111300236630ustar00rootroot00000000000000======================================= Release notes for PyTables 2.0 series ======================================= :Author: Francesc Alted i Abad :Contact: faltet@pytables.com :Author: Ivan Vilata i Balaguer :Contact: ivan@selidor.net Changes from 2.0.3 to 2.0.4 =========================== - Selections in tables works now in threaded environments. The problem was in the Numexpr package -- the solution has been reported to the upstream authors too. Fixes #164. - PyTables had problems importing native HDF5 files with gaps in nested compound types. This has been solved. Fixes #173. - In order to prevent a bug existing in HDF5 1.6 series, the ``EArray.truncate()`` method refused to accept a 0 as parameter (i.e. truncate an existing EArray to have zero rows did not work). As this has been fixed in the recent HDF5 1.8 series, this limitation has been removed (but only if the user has one of these installed). Fixes #171. - Small fixes for allowing the test suite to pass when using the new NumPy 1.1. However, it remains a small issue with the way the new NumPy represents complex numbers. I'm not fixing that in the PyTables suite, as there are chances that this can be fixed in NumPy itself (see ticket #841). Changes from 2.0.2 to 2.0.3 =========================== - Replaced the algorithm for computing chunksizes by another that is more general and useful for a larger range of expected dataset sizes. The outcome of the new calculation is the same than before for dataset sizes <= 100 GB. For datasets between 100 GB <= size < 10 TB, larger values are returned. For sizes >= 10 TB a maximum value of 1 MB is always returned. - Fixed a problem when updating multidimensional cells using the Row.update() method in the middle of table iterators . Fixes #149. - Added support for the latest 1.8.0 version of the HDF5 library. Fixes ticket #127. - PyTables compiles now against latest versions of Pyrex (0.9.6.4). For the first time, the extensions do compile without warnings! Fixes #159. - Numexpr module has been put in sync with the version in SciPy sandbox. - Added a couple of warnings in User's Guide so as to tell the user that it is not safe to use methods that can change the number of rows of a table in the middle of a row iterator. Fixes #153. Changes from 2.0.1 to 2.0.2 =========================== - Added ``__enter__()`` and ``__exit__()`` methods to ``File``; fixes #113. With this, and if using Python 2.5 you can do things like: with tables.openFile("test.h5") as h5file: ... - Carefully preserve type when converting NumPy scalar to numarray; fixes #125. - Fixed a nasty bug that appeared when moving or renaming groups due to a bad interaction between ``Group._g_updateChildrenLocation()`` and the LRU cache. Solves #126. - Return 0 when no rows are given to ``Table.modifyRows()``; fixes #128. - Added an informative message when the ``nctoh5`` utility is run without the NetCDF interface of ScientificPython being installed. - Now, a default representation of closed nodes is provided; fixes #129. Changes from 2.0 to 2.0.1 ========================= - The ``coords`` argument of ``Table.readCoords()`` was not checked for contiguousness, raising fatal errors when it was discontiguous. This has been fixed. - There is an inconsistency in the way used to specify the atom shape in ``Atom`` constructors. When the shape is specified as ``shape=()`` it means a scalar atom and when it is specified as ``shape=N`` it means an atom with ``shape=(N,)``. But when the shape is specified as ``shape=1`` (i.e. in the default case) then a scalar atom is obtained instead of an atom with ``shape=(1,)``. This is inconsistent and not the behavior that NumPy exhibits. Changing this will require a migration path which includes deprecating the old behaviour if we want to make the change happen before a new major version. The proposed path is: 1. In PyTables 2.0.1, we are changing the default value of the ``shape`` argument to ``()``, and issue a ``DeprecationWarning`` when someone uses ``shape=1`` stating that, for the time being, it is equivalent to ``()``, but in near future versions it will become equivalent to ``(1,)``, and recommending the user to pass ``shape=()`` if a scalar is desired. 2. In PyTables 2.1, we will remove the previous warning and take ``shape=N`` to mean ``shape=(N,)`` for any value of N. See ticket #96 for more info. - The info about the ``chunkshape`` attribute of a leaf is now printed in the ``__repr__()`` of chunked leaves (all except ``Array``). - After some scrupulous benchmarking job, the size of the I/O buffer for ``Table`` objects has been reduced to the minimum that allows maximum performance. This represents more than 10x of reduction in size for that buffer, which will benefit those programs dealing with many tables simultaneously (#109). - In the ``ptrepack`` utility, when ``--complevel`` and ``--shuffle`` were specified at the same time, the 'shuffle' filter was always set to 'off'. This has been fixed (#104). - An ugly bug related with the integrated Numexpr not being aware of all the variations of data arrangements in recarray objects has been fixed (#103). We should stress that the bug only affected the Numexpr version integrated in PyTables, and *not* the original one. - When passing a record array to a table at creation time, its real length is now used instead of the default value for ``expectedrows``. This allows for better performance (#97). - Added some workarounds so that NumPy scalars can be successfully converted to numarray objects. Fixes #98. - PyTables is now able to access table rows beyond 2**31 in 32-bit Python. The problem was a limitation of ``xrange`` and we have replaced it by a new ``lrange`` class written in Pyrex. Moreover, ``lrange`` has been made publicly accessible as a safe 64-bit replacement for ``xrange`` for 32-bit platforms users. Fixes #99. - If a group and a table are created in a function, and the table is accessed through the group, the table can be flushed now. Fixes #94. - It is now possible to directly assign a field in a nested record of a table using the natural naming notation (#93). Changes from 2.0rc2 to 2.0 ========================== - Added support for recognizing native HDF5 files with datasets compressed with szip compressor. - Fixed a problem when asking for the string representation (str()) of closed files. Fixes ticket #79. - Do not take LZO as available when its initialisation fails. - Fixed a glitch in ptrepack utility. When the user wants a copy of a group, and a group is *to be created* in destination, the attributes of the original group *are* copied. If it is *not to be created*, the attributes will *not be* copied. I think this should be what the user would expect most of the times. - Fixed the check for creating intermediate groups in ptrepack utility. Solves ticket #83. - Before, when reading a dataset with an unknown CLASS id, a warning was issued and the dataset mapped to ``UnImplemented``. This closed the door to have the opportunity to try to recognize the dataset and map it to a supported CLASS. Now, when a CLASS attribute is not recognized, an attempt to recognize its associated dataset is made. If it is recognized, the matching class is associated with the dataset. If it is not recognized, then a warning is issued and the dataset becomes mapped to ``UnImplemented``. - Always pass verbose and heavy values in the common test module to test(). Fixes ticket #85. - Now, the ``verbose`` and ``--heavy`` flag passed to test_all.py are honored. - All the DLL's of dependencies are included now in Windows binaries. This should allow for better portability of the binaries. - Fixed the description of Node._v_objectID that was misleading. Changes from 2.0rc1 to 2.0rc2 ============================= - The "Optimization tips" chapter of the User's Guide has been completely updated to adapt to PyTables 2.0 series. In particular, new benchmarks on the much improved indexed queries have been included; you will see that PyTables indexing is competitive (and sometimes much faster) than that of traditional relational databases. With this, the manual should be fairly finished for 2.0 final release. - Large refactoring done on the ``Row`` class. The most important change is that ``Table.row`` is now a single object. This allows to reuse the same ``Row`` instance even after ``Table.flush()`` calls, which can be convenient in many situations. - I/O buffers unified in the ``Row`` class. That allows for bigger savings in memory space whenever the ``Row`` extension is used. - Improved speed (up to a 70%) with unaligned column operations (a quite common scenario when dealing with ``Table`` objects) through the integrated Numexpr. In-kernel searches take advantage of this optimization. - Added ``VLUnicodeAtom`` for storing variable-length Unicode strings in ``VLArray`` objects regardless of encoding. Closes ticket #51. - Added support for ``time`` datatypes to be portable between big-endian and low-endian architectures. This feature is not currently supported natively by the HDF5 library, so the support for such conversion has been added in PyTables itself. Fixes #72. - Added slice arguments to ``Table.readWhere()`` and ``Table.getWhereList()``. Although API changes are frozen, this may still be seen as an inconsistency with other query methods. The patch is backwards-compatible anyway. - Added missing overwrite argument to ``File.renameNode()`` and ``Node._f_rename()``. Fixes ticket #66. - Calling ``tables.test()`` no longer exits the interpreter session. Fixes ticket #67. - Fix comparing strings where one is a prefix of the other in integrated Numexpr. Fixes ticket #76. - Added a check for avoiding an ugly HDF5 message when copying a file over itself (for both ``copyFile()`` and ``File.copyFile()``). Fixes ticket #73. - Corrected the appendix E, were it was said that PyTables doesn't support compounds of compounds (it does since version 1.2!). Changes from 2.0b2 to 2.0rc1 ============================ - The API Reference section of the User's Manual (and the matching docstrings) has been completely reviewed, expanded and corrected. This process has unveiled some errors and inconsistencies which have also been fixed. - Fixed ``VLArray.__getitem__()`` to behave as expected in Python when using slices, instead of following the semantics of PyTables' ``read()`` methods (e.g. reading just one element when no stop is provided). Fixes ticket #50. - Removed implicit UTF-8 encoding from ``VLArray`` data using ``vlstring`` atoms. Now a variable-length string is stored as is, which lets users use any encoding of their choice, or none of them. A ``vlunicode`` atom will probably be added to the next release so as to fix ticket #51. - Allow non-sequence objects to be passed to ``VLArray.append()`` when using an ``object`` atom. This was already possible in 1.x but stopped working when the old append syntax was dropped in 2.0. Fixes ticket #63. - Changed ``Cols.__len__()`` to return the number of rows of the table or nested column (instead of the number of fields), like its counterparts in ``Table`` and ``Column``. - Python scalars cached in ``AttributeSet`` instances are now kept as NumPy objects instead of Python ones, because they do become NumPy objects when retrieved from disk. Fixes ticket #59. - Avoid HDF5 error when appending an empty array to a ``Table`` (ticket #57) or ``EArray`` (ticket #49) dataset. - Fix wrong implementation of the top-level ``table.description._v_dflts`` map, which was also including the pathnames of columns inside nested columns. Fixes ticket #45. - Optimized the access to unaligned arrays in Numexpr between a 30% and a 70%. - Fixed a die-hard bug that caused the loading of groups while closing a file. This only showed with certain usage patterns of the LRU cache (e.g. the one caused by ``ManyNodesTestCase`` in ``test_indexes.py`` under Pro). - Avoid copious warnings about unused functions and variables when compiling Numexpr. - Several fixes to Numexpr expressions with all constant values. Fixed tickets #53, #54, #55, #58. Reported bugs to mainstream developers. - Solved an issue when trying to open one of the included test files in append mode on a system-wide installation by a normal user with no write privileges on it. The file isn't being modified anyway, so the test is skipped then. - Added a new benchmark to compare the I/O speed of ``Array`` and ``EArray`` objects with that of ``cPickle``. - The old ``Row.__call__()`` is no longer available as a public method. It was not documented, anyway. Fixes ticket #46. - ``Cols._f_close()`` is no longer public. Fixes ticket #47. - ``Attributes._f_close()`` is no longer public. Fixes ticket #52. - The undocumented ``Description.classdict`` attribute has been completely removed. Fixes ticket #44. Changes from 2.0b1 to 2.0b2 =========================== - A very exhaustive overhauling of the User's Manual is in process. The chapters 1 (Introduction), 2 (Installation), 3 (Tutorials) have been completed (and hopefully, the lines of code are easier to copy&paste now), while chapter 4 (API Reference) has been done up to (and including) the Table class. During this tedious (but critical in a library) overhauling work, we have tried hard to synchronize the text in the User's Guide with that which appears on the docstrings. - Removed the ``recursive`` argument in ``Group._f_walkNodes()``. Using it with a false value was redundant with ``Group._f_iterNodes()``. Fixes ticket #42. - Removed the ``coords`` argument from ``Table.read()``. It was undocumented and redundant with ``Table.readCoordinates()``. Fixes ticket #41. - Fixed the signature of ``Group.__iter__()`` (by removing its parameters). - Added new ``Table.coldescrs`` and ``Table.description._v_itemsize`` attributes. - Added a couple of new attributes for leaves: * ``nrowsinbuf``: the number of rows that fit in the internal buffers. * ``chunkshape``: the chunk size for chunked datasets. - Fixed setuptools so that making an egg out of the PyTables 2 package is possible now. - Added a new ``tables.restrict_flavors()`` function allowing to restrict available flavors to a given set. This can be useful e.g. if you want to force PyTables to get NumPy data out of an old, ``numarray``-flavored PyTables file even if the ``numarray`` package is installed. - Fixed a bug which caused filters of unavailable compression libraries to be loaded as using the default Zlib library, after issuing a warning. Added a new ``FiltersWarning`` and a ``Filters.copy()``. Important changes from 1.4.x to 2.0 =================================== API additions ------------- - ``Column.createIndex()`` has received a couple of new parameters: ``optlevel`` and ``filters``. The first one sets the desired quality level of the index, while the second one allows the user to specify the filters for the index. - ``Table.indexprops`` has been split into ``Table.indexFilters`` and ``Table.autoIndex``. The later groups the functionality of the old ``auto`` and ``reindex``. - The new ``Table.colpathnames`` is a sequence which contains the full pathnames of all bottom-level columns in a table. This can be used to walk all ``Column`` objects in a table when used with ``Table.colinstances``. - The new ``Table.colinstances`` dictionary maps column pathnames to their associated ``Column`` or ``Cols`` object for simple or nested columns, respectively. This is similar to ``Table.cols._f_col()``, but faster. - ``Row`` has received a new ``Row.fetch_all_fields()`` method in order to return all the fields in the current row. This returns a NumPy void scalar for each call. - New ``tables.test(verbose=False, heavy=False)`` high level function for interactively running the complete test suite from the Python console. - Added a ``tables.print_versions()`` for easily getting the versions for all the software on which PyTables relies on. Backward-incompatible changes ----------------------------- - You can no longer mark a column for indexing in a ``Col`` declaration. The only way of creating an index for a column is to invoke the ``createIndex()`` method of the proper column object *after the table has been created*. - Now the ``Table.colnames`` attribute is just a list of the names of top-level columns in a table. You can still get something similar to the old structure by using ``Table.description._v_nestedNames``. See also the new ``Table.colpathnames`` attribute. - The ``File.objects``, ``File.leaves`` and ``File.groups`` dictionaries have been removed. If you still need this functionality, please use the ``File.getNode()`` and ``File.walkNodes()`` instead. - ``Table.removeIndex()`` is no longer available; to remove an index on a column, one must use the ``removeIndex()`` method of the associated ``Column`` instance. - ``Column.dirty`` is no longer available. If you want to check column index dirtiness, use ``Column.index.dirty``. - ``complib`` and ``complevel`` parameters have been removed from ``File.createTable()``, ``File.createEArray()``, ``File.createCArray()`` and ``File.createVLArray()``. They were already deprecated in PyTables 1.x. - The ``shape`` and ``atom`` parameters have been swapped in ``File.createCArray()``. This has been done to be consistent with ``Atom()`` definitions (i.e. type comes before and shape after). Deprecated features ------------------- - ``Node._v_rootgroup`` has been removed. Please use ``node._v_file.root`` instead. - The ``Node._f_isOpen()`` and ``Leaf.isOpen()`` methods have been removed. Please use the ``Node._v_isopen`` attribute instead (it is much faster). - The ``File.getAttrNode()``, ``File.setAttrNode()`` and ``File.delAttrNode()`` methods have been removed. Please use ``File.getNodeAttr()``, ``File.setNodeAttr()`` and ``File.delNodeAttr()`` instead. - ``File.copyAttrs()`` has been removed. Please use ``File.copyNodeAttrs()`` instead. - The ``table[colname]`` idiom is no longer supported. You can use ``table.cols._f_col(column)`` for doing the same. API refinements --------------- - ``File.createEArray()`` received a new ``shape`` parameter. This allows to not have to use the shape of the atom so as to set the shape of the underlying dataset on disk. - All the leaf constructors have received a new ``chunkshape`` parameter that allows specifying the chunk sizes of datasets on disk. - All ``File.create*()`` factories for ``Leaf`` nodes have received a new ``byteorder`` parameter that allows the user to specify the byteorder in which data will be written to disk (data in memory is now always handled in *native* order). ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: rst .. coding: utf-8 .. fill-column: 78 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v2.1.x-pro.rst000066400000000000000000000067411416254111300244610ustar00rootroot00000000000000======================================= Release notes for PyTables 2.1 series ======================================= :Author: Francesc Alted i Abad :Contact: faltet@pytables.org Changes from 2.1.1 to 2.1.2 =========================== Bug fixes --------- - Solved problems with Table.modifyColumn() when the column(s) is multidimensional. Fixes #228. - The row attribute of a table seems stalled after a table move or rename. Fixes #224. - Fixed a problem with ``len(array)`` in 32-bit platforms when array is large enough (> 2**31). - Added missing `_c_classId` attribute to the `UnImplemented` class. ``ptrepack`` no longer chokes while copying `Unimplemented` classes. - The ``FIELD_*`` sys attrs are no longer copied when the ``PYTABLES_SYS_ATTRS`` parameter is set to false. - The ``FILTERS`` attribute is not added anymore when ``PYTABLES_SYS_ATTR`` parameter is set to false. - Disable the printing of Unicode characters that cannot be printed on win32 platform. Fixes #235. Other changes ------------- - When retrieving a row of a 1-dimensional array, a 0-dim array was returned instead of a numpy scalar. Now, an actual numpy scalar is returned. Closes #222. - LZO and bzip2 filters adapted to an API fix introduced in HDF5 1.8.3. Closes #225. - Unsupported HDF5 types in attributes are no longer transferred during copies. A new `_v_unimplemented` list have been added in `AttributeSet` class so as to keep track of such attributes. Closes #240. - LZO binaries have disappeared from the GnuWin32 repository. Until they come eventually back, they have been put at http://www.pytables.org/download/lzo-win. This has been documented in the install chapter. Changes from 2.1 to 2.1.1 ========================= Bug fixes --------- - Fixed a memory leak when a lot of queries were made. Closes #203 and #207. - The chunkshape="auto" parameter value of `Leaf.copy()` is honored now, even when the (start, stop, step) parameters are specified. Closes #204. - Due to a flaw in its design, the `File` class was not able to be subclassed. This has been fixed. Closes #205. - Default values were not correctly retrieved when opening already created CArray/EArray objects. Fixed. Closes #212. - Fixed a problem with the installation of the ``nctoh5`` script that prevented it from being executed. Closes #215. - [Pro] The ``iterseq`` cache ignored non-indexed conditions, giving wrong results when those appeared in condition expressions. This has been fixed. Closes #206. Other changes ------------- - `openFile()`, `isHDF5File()` and `isPyTablesFile()` functions accept Unicode filenames now. Closes #202 and #214. - When creating large type sizes (exceeding 64 KB), HDF5 complained and refused to do so. The HDF5 team has logged the issue as a bug, but meanwhile it has been implemented a workaround in PyTables that allows to create such large datatypes for situations that does not require defaults other than zero. Addresses #211. - In order to be consistent with how are stored the other data types, Unicode attributes are retrieved now as NumPy scalars instead of Python Unicode strings or NumPy arrays. For the moment, I've fixed this through pickling the Unicode strings. In the future, when HDF5 1.8.x series would be a requirement, that should be done via a HDF5 native Unicode type. Closes #213. ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: rst .. coding: utf-8 .. fill-column: 72 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v2.1.x.rst000066400000000000000000000067411416254111300236630ustar00rootroot00000000000000======================================= Release notes for PyTables 2.1 series ======================================= :Author: Francesc Alted i Abad :Contact: faltet@pytables.org Changes from 2.1.1 to 2.1.2 =========================== Bug fixes --------- - Solved problems with Table.modifyColumn() when the column(s) is multidimensional. Fixes #228. - The row attribute of a table seems stalled after a table move or rename. Fixes #224. - Fixed a problem with ``len(array)`` in 32-bit platforms when array is large enough (> 2**31). - Added missing `_c_classId` attribute to the `UnImplemented` class. ``ptrepack`` no longer chokes while copying `Unimplemented` classes. - The ``FIELD_*`` sys attrs are no longer copied when the ``PYTABLES_SYS_ATTRS`` parameter is set to false. - The ``FILTERS`` attribute is not added anymore when ``PYTABLES_SYS_ATTR`` parameter is set to false. - Disable the printing of Unicode characters that cannot be printed on win32 platform. Fixes #235. Other changes ------------- - When retrieving a row of a 1-dimensional array, a 0-dim array was returned instead of a numpy scalar. Now, an actual numpy scalar is returned. Closes #222. - LZO and bzip2 filters adapted to an API fix introduced in HDF5 1.8.3. Closes #225. - Unsupported HDF5 types in attributes are no longer transferred during copies. A new `_v_unimplemented` list have been added in `AttributeSet` class so as to keep track of such attributes. Closes #240. - LZO binaries have disappeared from the GnuWin32 repository. Until they come eventually back, they have been put at http://www.pytables.org/download/lzo-win. This has been documented in the install chapter. Changes from 2.1 to 2.1.1 ========================= Bug fixes --------- - Fixed a memory leak when a lot of queries were made. Closes #203 and #207. - The chunkshape="auto" parameter value of `Leaf.copy()` is honored now, even when the (start, stop, step) parameters are specified. Closes #204. - Due to a flaw in its design, the `File` class was not able to be subclassed. This has been fixed. Closes #205. - Default values were not correctly retrieved when opening already created CArray/EArray objects. Fixed. Closes #212. - Fixed a problem with the installation of the ``nctoh5`` script that prevented it from being executed. Closes #215. - [Pro] The ``iterseq`` cache ignored non-indexed conditions, giving wrong results when those appeared in condition expressions. This has been fixed. Closes #206. Other changes ------------- - `openFile()`, `isHDF5File()` and `isPyTablesFile()` functions accept Unicode filenames now. Closes #202 and #214. - When creating large type sizes (exceeding 64 KB), HDF5 complained and refused to do so. The HDF5 team has logged the issue as a bug, but meanwhile it has been implemented a workaround in PyTables that allows to create such large datatypes for situations that does not require defaults other than zero. Addresses #211. - In order to be consistent with how are stored the other data types, Unicode attributes are retrieved now as NumPy scalars instead of Python Unicode strings or NumPy arrays. For the moment, I've fixed this through pickling the Unicode strings. In the future, when HDF5 1.8.x series would be a requirement, that should be done via a HDF5 native Unicode type. Closes #213. ---- **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: rst .. coding: utf-8 .. fill-column: 72 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v2.2.x-pro.rst000066400000000000000000000362601416254111300244610ustar00rootroot00000000000000======================================= Release notes for PyTables 2.2 series ======================================= :Author: Francesc Alted i Abad :Contact: faltet@pytables.org Changes from 2.2.1rc1 to 2.2.1 ============================== - The `Row` accessor implements a new `__contains__` special method that allows doing things like:: for row in table: if item in row: print "Value found in row", row.nrow break Closes #309. - PyTables is more friendly with easy_install and pip now, as all the Python dependencies should be installed automatically. Closes #298. Changes from 2.2 to 2.2.1rc1 ============================ - When using `ObjectAtom` objects in `VLArrays` the ``HIGHEST_PROTOCOL`` is used for pickling objects. For NumPy arrays, this simple change leads to space savings up to 3x and time improvements up to 30x. Closes #301. - The `Row` accessor implements a new `__contains__` special method that allows doing things like:: for row in table: if item in row: print "Value found in row", row.nrow break Closes #309. - tables.Expr can perform operations on scalars now. Thanks to Gaëtan de Menten for providing a patch for this. Closes #287. - Fixed a problem with indexes larger than 32-bit on leaf objects on 32-bit machines. Fixes #283. - Merged in Blosc 1.1.2 for fixing a problem with large datatypes and subprocess issues. Closes #288 and #295. - Due to the adoption of Blosc 1.1.2, the pthreads-win32 library dependency is dropped on Windows platforms. - Fixed a problem with tables.Expr and operands with vary large rowsizes. Closes #300. - ``leaf[numpy.array[scalar]]`` idiom returns a NumPy array instead of an scalar. This has been done for compatibility with NumPy. Closes #303. - Optimization for `Table.copy()` so that ``FIELD_*`` attrs are not overwritten during the copy. This can lead to speed-ups up to 100x for short tables that have hundreds of columns. Closes #304. - For external links, its relative paths are resolved now with respect to the directory of the main HDF5 file, rather than with respect to the current directory. Closes #306. - ``Expr.setInputsRange()`` and ``Expr.setOutputRange()`` do support ``numpy.integer`` types now. Closes #285. - Column names in tables can start with '__' now. Closes #291. - Unicode empty strings are supported now as attributes. Addresses #307. - Cython 0.13 and higher is supported now. Fixes #293. - PyTables should be more 'easy_install'-able now. Addresses #298. Changes from 2.2rc2 to 2.2 (final) ================================== - Updated Blosc to 1.0 (final). - Filter ID of Blosc changed from wrong 32010 to reserved 32001. This will prevent PyTables 2.2 (final) to read files created with Blosc and PyTables 2.2 pre-final. `ptrepack` can be used to retrieve those files, if necessary. More info in ticket #281. - Recent benchmarks suggest a new parametrization is better in most scenarios: * The default chunksize has been doubled for every dataset size. This works better in most of scenarios, specially with the new Blosc compressor. * The HDF5 CHUNK_CACHE_SIZE parameter has been raised to 2 MB in order to better adapt to the chunksize increase. This provides better hit ratio (at the cost of consuming more memory). Some plots have been added to the User's Manual (chapter 5) showing how the new parametrization works. Changes from 2.2rc1 to 2.2rc2 ============================= - A new version of Blosc (0.9.5) is included. This version is now considered to be stable and apt for production. Thanks for all PyTables users that have contributed to find and report bugs. - Added a new `IO_BUFFER_SIZE` parameter to ``tables/parameters.py`` that allows to set the internal PyTables' buffer for doing I/O. This replaces `CHUNKTIMES` but it is more general because it affects to all `Leaf` objects and also the `tables.Expr` module (and not only tables as before). - `BUFFERTIMES` parameter in ``tables/parameters.py`` has been renamed to `BUFFER_TIMES` which is more consistent with other parameter names. - On Windows platforms, the path to the tables module is now appended to sys.path and the PATH environment variable. That way DLLs and PYDs in the tables directory are to be found now. Thanks to Christoph Gohlke for the hint. - A replacement for barriers for Mac OSX, or other systems not implementing them, has been carried out. This allows to compile PyTables on such platforms. Fixes #278 - Fixed a couple of warts that raise compatibility warnings with forthcoming Python 2.7. - HDF5 1.8.5 is used in Windows binaries. Changes from 2.2b3 to 2.2rc1 ============================ - Numexpr is not included anymore in PyTables and has become a requisite instead. This is because Numexpr already has decent enough installers and is available in the PyPI repository also, so it should be easy for users to fulfill this dependency. - When using a Numexpr package that is turbo-loaded with Intel's VML/MKL, the parameter `MAX_THREADS` will control the number of threads that VML can use during computations. For a finer control, the `numexpr.set_vml_num_threads()` can always be used. - Cython is used now instead of Pyrex for Pyrex extensions. - Updated to 0.9 version of Blosc compressor. This version can make use of threads so as to accelerate the compression/decompression process. In order to change the maximum number of threads that Blosc can use (2 by default), you can modify the `MAX_THREADS` variable in ``tables/parameters.py`` or make use of the new `setBloscMaxThreads()` global function. - Reopening already opened files is supported now, provided that there is not incompatibility among intended usages (for example, you cannot reopen in append mode an already opened file in read-only mode). - Option ``--print-versions`` for ``test_all.py`` script is now preferred over the deprecated ``--show-versions``. This is more consistent with the existing `print_versions()` function. - Fixed a bug that, under some circumstances, prevented the use of table iterators in `itertool.groupby()`. Now, you can safely do things like:: sel_rows = table.where('(row_id >= 3)') for group_id, grouped_rows in itertools.groupby(sel_rows, f_group): group_mean = average([row['row_id'] for row in grouped_rows]) Fixes #264. - Copies of `Array` objects with multidimensional atoms (coming from native HDF5 files) work correctly now (i.e. the copy holds the atom dimensionality). Fixes #275. - The `tables.openFile()` function does not try anymore to open/close the file in order to guess whether it is a HDF5 or PyTables one before opening it definitely. This allows the `fcntl.flock()` and `fcntl.lockf()` Python functions to work correctly now (that's useful for arbitrating access to the file by different processes). Thanks to Dag Sverre Seljebotn and Ivan Vilata for their suggestions on hunting this one! Fixes #185. - The estimation of the chunksize when using multidimensional atoms in EArray/Carray was wrong because it did not take in account the shape of the atom. Thanks to Ralf Juengling for reporting. Fixes #273. - Non-contiguous arrays can now safely be saved as attributes. Before, if arrays were not contiguous, incorrect data was saved in attr. Fixes #270. - EXTDIM attribute for CArray/EArray now saves the correct extendable dimension, instead of rubbish. This does not affected functionality, because extendable dimension was retrieved directly from shape information, but it was providing misleading information to the user. Fixes #268. API changes ----------- - Now, `Table.Cols.__len__()` returns the number of top level columns instead of the number of rows in table. This is more consistent in that `Table.Cols` is an accessor for *columns*. Fixes #276. Changes from 2.2b2 to 2.2b3 =========================== - Blosc compressor has been added as an additional filter, in addition to the existing Zlib, LZO and bzip2. This new compressor is meant for fast compression and extremely fast decompression. Fixes #265. - In `File.copyFile()` method, `copyuserattrs` was set to false as default. This was inconsistent with other methods where the default value for `copyuserattrs` is true. The default for this is true now. Closes #261. - `tables.copyFile` and `File.copyFile` recognize now the parameters present in ``tables/parameters.py``. Fixes #262. - Backported fix for issue #25 in Numexpr (OP_NEG_LL treats the argument as an int, not a long long). Thanks to David Cooke for this. - CHUNK_CACHE_NELMTS in `tables/parameters.py` set to a prime number as Neil Fortner suggested. - Workaround for a problem in Python 2.6.4 (and probably other versions too) for pickling strings like "0" or "0.". Fixes #253. Changes from 2.2b1 to 2.2b2 =========================== Enhancements ------------ - Support for HDF5 hard links, soft links and external links (when PyTables is compiled against HDF5 1.8.x series). A new tutorial about its usage has been added to the 'Tutorials' chapter of User's Manual. Closes #239 and #247. - Added support for setting HDF5 chunk cache parameters in file opening/creating time. 'CHUNK_CACHE_NELMTS', 'CHUNK_CACHE_PREEMPT' and 'CHUNK_CACHE_SIZE' are the new parameters. See "PyTables' parameter files" appendix in User's Manual for more info. Closes #221. - New `Unknown` class added so that objects that HDF5 identifies as ``H5G_UNKNOWN`` can be mapped to it and continue operations gracefully. - Optimization in the indexed queries when the resulting rows increase monotonically. From 3x (for medium-size query results) and 10x (for very large query results) speed-ups can be expected. - Added flag `--dont-create-sysattrs` to ``ptrepack`` so as to not create sys attrs (default is to do it). - Support for native compound types in attributes. This allows for better compatibility with HDF5 files. Closes #208. - Support for native NumPy dtype in the description parameter of `File.createTable()`. Closes #238. Bugs fixed ---------- - Added missing `_c_classId` attribute to the `UnImplemented` class. ``ptrepack`` no longer chokes while copying `Unimplemented` classes. - The ``FIELD_*`` sys attrs are no longer copied when the ``PYTABLES_SYS_ATTRS`` parameter is set to false. - `File.createTable()` no longer segfaults if description=None. Closes #248. - Workaround for avoiding a Python issue causing a segfault when saving and then retrieving a string attribute with values "0" or "0.". Closes #253. API changes ----------- - `Row.__contains__()` disabled because it has little sense to query for a key in Row, and the correct way should be to query for it in `Table.colnames` or `Table.colpathnames` better. Closes #241. - [Semantic change] To avoid a common pitfall when asking for the string representation of a `Row` class, `Row.__str__()` has been redefined. Now, it prints something like:: >>> for row in table: ... print row ... /newgroup/table.row (Row), pointing to row #0 /newgroup/table.row (Row), pointing to row #1 /newgroup/table.row (Row), pointing to row #2 instead of:: >>> for row in table: ... print row ... ('Particle: 0', 0, 10, 0.0, 0.0) ('Particle: 1', 1, 9, 1.0, 1.0) ('Particle: 2', 2, 8, 4.0, 4.0) Use `print row[:]` idiom if you want to reproduce the old behaviour. Closes #252. Other changes ------------- - After some improvements in both HDF5 and PyTables, the limit before emitting a `PerformanceWarning` on the number of children in a group has been raised from 4096 to 16384. Changes from 2.1.1 to 2.2b1 =========================== Enhancements ------------ - Added `Expr`, a class for evaluating expressions containing array-like objects. It can evaluate expressions (like '3*a+4*b') that operate on arbitrary large arrays while optimizing the resources (basically main memory and CPU cache memory) required to perform them. It is similar to the Numexpr package, but in addition to NumPy objects, it also accepts disk-based homogeneous arrays, like the `Array`, `CArray`, `EArray` and `Column` PyTables objects. - Added support for NumPy's extended slicing in all `Leaf` objects. With that, you can do the next sort of selections:: array1 = array[4] # simple selection array2 = array[4:1000:2] # slice selection array3 = array[1, ..., ::2, 1:4, 4:] # general slice selection array4 = array[1, [1,5,10], ..., -1] # fancy selection array5 = array[np.where(array[:] > 4)] # point selection array6 = array[array[:] > 4] # boolean selection Thanks to Andrew Collette for implementing this for h5py, from which it has been backported. Closes #198 and #209. - Numexpr updated to 1.3.1. This can lead to up a 25% improvement of the time for both in-kernel and indexed queries for unaligned tables. - HDF5 1.8.3 supported. Bugs fixed ---------- - Fixed problems when modifying multidimensional columns in Table objects. Closes #228. - Row attribute is no longer stalled after a table move or rename. Fixes #224. - Array.__getitem__(scalar) returns a NumPy scalar now, instead of a 0-dim NumPy array. This should not be noticed by normal users, unless they check for the type of returned value. Fixes #222. API changes ----------- - Added a `dtype` attribute for all leaves. This is the NumPy ``dtype`` that most closely matches the leaf type. This allows for a quick-and-dirty check of leaf types. Closes #230. - Added a `shape` attribute for `Column` objects. This is formed by concatenating the length of the column and the shape of its type. Also, the representation of columns has changed an now includes the length of the column as the leading dimension. Closes #231. - Added a new `maindim` attribute for `Column` which has the 0 value (the leading dimension). This allows for a better similarity with other \*Array objects. - In order to be consistent and allow the extended slicing to happen in `VLArray` objects too, `VLArray.__setitem__()` is not able to partially modify rows based on the second dimension passed as key. If this is tried, an `IndexError` is raised now. Closes #210. - The `forceCSI` flag has been replaced by `checkCSI` in the next `Table` methods: `copy()`, `readSorted()` and `itersorted()`. The change reflects the fact that a re-index operation cannot be triggered from these methods anymore. The rational for the change is that an indexing operation is a potentially very expensive operation that should be carried out explicitly instead of being triggered by methods that should not be in charge of this task. Closes #216. Backward incompatible changes ----------------------------- - After the introduction of the `shape` attribute for `Column` objects, the shape information for multidimensional columns has been removed from the `dtype` attribute (it is set to the base type of the column now). Closes #232. **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: rst .. coding: utf-8 .. fill-column: 72 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v2.2.x.rst000066400000000000000000000354011416254111300236570ustar00rootroot00000000000000======================================= Release notes for PyTables 2.2 series ======================================= :Author: Francesc Alted i Abad :Contact: faltet@pytables.org Changes from 2.2.1rc1 to 2.2.1 ============================== - The `Row` accessor implements a new `__contains__` special method that allows doing things like:: for row in table: if item in row: print "Value found in row", row.nrow break Closes #309. - PyTables is more friendly with easy_install and pip now, as all the Python dependencies should be installed automatically. Closes #298. Changes from 2.2 to 2.2.1rc1 ============================ - When using `ObjectAtom` objects in `VLArrays` the ``HIGHEST_PROTOCOL`` is used for pickling objects. For NumPy arrays, this simple change leads to space savings up to 3x and time improvements up to 30x. Closes #301. - tables.Expr can perform operations on scalars now. Thanks to Gaëtan de Menten for providing a patch for this. Closes #287. - Fixed a problem with indexes larger than 32-bit on leaf objects on 32-bit machines. Fixes #283. - Merged in Blosc 1.1.2 for fixing a problem with large datatypes and subprocess issues. Closes #288 and #295. - Due to the adoption of Blosc 1.1.2, the pthreads-win32 library dependency is dropped on Windows platforms. - Fixed a problem with tables.Expr and operands with vary large rowsizes. Closes #300. - ``leaf[numpy.array[scalar]]`` idiom returns a NumPy array instead of an scalar. This has been done for compatibility with NumPy. Closes #303. - Optimization for `Table.copy()` so that ``FIELD_*`` attrs are not overwritten during the copy. This can lead to speed-ups up to 100x for short tables that have hundreds of columns. Closes #304. - For external links, its relative paths are resolved now with respect to the directory of the main HDF5 file, rather than with respect to the current directory. Closes #306. - ``Expr.setInputsRange()`` and ``Expr.setOutputRange()`` do support ``numpy.integer`` types now. Closes #285. - Column names in tables can start with '__' now. Closes #291. - Unicode empty strings are supported now as attributes. Addresses #307. - Cython 0.13 and higher is supported now. Fixes #293. - PyTables should be more 'easy_install'-able now. Addresses #298. Changes from 2.2rc2 to 2.2 (final) ================================== - Updated Blosc to 1.0 (final). - Filter ID of Blosc changed from wrong 32010 to reserved 32001. This will prevent PyTables 2.2 (final) to read files created with Blosc and PyTables 2.2 pre-final. `ptrepack` can be used to retrieve those files, if necessary. More info in ticket #281. - Recent benchmarks suggest a new parametrization is better in most scenarios: * The default chunksize has been doubled for every dataset size. This works better in most of scenarios, specially with the new Blosc compressor. * The HDF5 CHUNK_CACHE_SIZE parameter has been raised to 2 MB in order to better adapt to the chunksize increase. This provides better hit ratio (at the cost of consuming more memory). Some plots have been added to the User's Manual (chapter 5) showing how the new parametrization works. Changes from 2.2rc1 to 2.2rc2 ============================= - A new version of Blosc (0.9.5) is included. This version is now considered to be stable and apt for production. Thanks for all PyTables users that have contributed to find and report bugs. - Added a new `IO_BUFFER_SIZE` parameter to ``tables/parameters.py`` that allows to set the internal PyTables' buffer for doing I/O. This replaces `CHUNKTIMES` but it is more general because it affects to all `Leaf` objects and also the `tables.Expr` module (and not only tables as before). - `BUFFERTIMES` parameter in ``tables/parameters.py`` has been renamed to `BUFFER_TIMES` which is more consistent with other parameter names. - On Windows platforms, the path to the tables module is now appended to sys.path and the PATH environment variable. That way DLLs and PYDs in the tables directory are to be found now. Thanks to Christoph Gohlke for the hint. - A replacement for barriers for Mac OSX, or other systems not implementing them, has been carried out. This allows to compile PyTables on such platforms. Fixes #278 - Fixed a couple of warts that raise compatibility warnings with forthcoming Python 2.7. - HDF5 1.8.5 is used in Windows binaries. Changes from 2.2b3 to 2.2rc1 ============================ - Numexpr is not included anymore in PyTables and has become a requisite instead. This is because Numexpr already has decent enough installers and is available in the PyPI repository also, so it should be easy for users to fulfill this dependency. - When using a Numexpr package that is turbo-loaded with Intel's VML/MKL, the parameter `MAX_THREADS` will control the number of threads that VML can use during computations. For a finer control, the `numexpr.set_vml_num_threads()` can always be used. - Cython is used now instead of Pyrex for Pyrex extensions. - Updated to 0.9 version of Blosc compressor. This version can make use of threads so as to accelerate the compression/decompression process. In order to change the maximum number of threads that Blosc can use (2 by default), you can modify the `MAX_THREADS` variable in ``tables/parameters.py`` or make use of the new `setBloscMaxThreads()` global function. - Reopening already opened files is supported now, provided that there is not incompatibility among intended usages (for example, you cannot reopen in append mode an already opened file in read-only mode). - Option ``--print-versions`` for ``test_all.py`` script is now preferred over the deprecated ``--show-versions``. This is more consistent with the existing `print_versions()` function. - Fixed a bug that, under some circumstances, prevented the use of table iterators in `itertool.groupby()`. Now, you can safely do things like:: sel_rows = table.where('(row_id >= 3)') for group_id, grouped_rows in itertools.groupby(sel_rows, f_group): group_mean = average([row['row_id'] for row in grouped_rows]) Fixes #264. - Copies of `Array` objects with multidimensional atoms (coming from native HDF5 files) work correctly now (i.e. the copy holds the atom dimensionality). Fixes #275. - The `tables.openFile()` function does not try anymore to open/close the file in order to guess whether it is a HDF5 or PyTables one before opening it definitely. This allows the `fcntl.flock()` and `fcntl.lockf()` Python functions to work correctly now (that's useful for arbitrating access to the file by different processes). Thanks to Dag Sverre Seljebotn and Ivan Vilata for their suggestions on hunting this one! Fixes #185. - The estimation of the chunksize when using multidimensional atoms in EArray/Carray was wrong because it did not take in account the shape of the atom. Thanks to Ralf Juengling for reporting. Fixes #273. - Non-contiguous arrays can now safely be saved as attributes. Before, if arrays were not contiguous, incorrect data was saved in attr. Fixes #270. - EXTDIM attribute for CArray/EArray now saves the correct extendable dimension, instead of rubbish. This does not affected functionality, because extendable dimension was retrieved directly from shape information, but it was providing misleading information to the user. Fixes #268. API changes ----------- - Now, `Table.Cols.__len__()` returns the number of top level columns instead of the number of rows in table. This is more consistent in that `Table.Cols` is an accessor for *columns*. Fixes #276. Changes from 2.2b2 to 2.2b3 =========================== - Blosc compressor has been added as an additional filter, in addition to the existing Zlib, LZO and bzip2. This new compressor is meant for fast compression and extremely fast decompression. Fixes #265. - In `File.copyFile()` method, `copyuserattrs` was set to false as default. This was inconsistent with other methods where the default value for `copyuserattrs` is true. The default for this is true now. Closes #261. - `tables.copyFile` and `File.copyFile` recognize now the parameters present in ``tables/parameters.py``. Fixes #262. - Backported fix for issue #25 in Numexpr (OP_NEG_LL treats the argument as an int, not a long long). Thanks to David Cooke for this. - CHUNK_CACHE_NELMTS in `tables/parameters.py` set to a prime number as Neil Fortner suggested. - Workaround for a problem in Python 2.6.4 (and probably other versions too) for pickling strings like "0" or "0.". Fixes #253. Changes from 2.2b1 to 2.2b2 =========================== Enhancements ------------ - Support for HDF5 hard links, soft links and external links (when PyTables is compiled against HDF5 1.8.x series). A new tutorial about its usage has been added to the 'Tutorials' chapter of User's Manual. Closes #239 and #247. - Added support for setting HDF5 chunk cache parameters in file opening/creating time. 'CHUNK_CACHE_NELMTS', 'CHUNK_CACHE_PREEMPT' and 'CHUNK_CACHE_SIZE' are the new parameters. See "PyTables' parameter files" appendix in User's Manual for more info. Closes #221. - New `Unknown` class added so that objects that HDF5 identifies as ``H5G_UNKNOWN`` can be mapped to it and continue operations gracefully. - Added flag `--dont-create-sysattrs` to ``ptrepack`` so as to not create sys attrs (default is to do it). - Support for native compound types in attributes. This allows for better compatibility with HDF5 files. Closes #208. - Support for native NumPy dtype in the description parameter of `File.createTable()`. Closes #238. Bugs fixed ---------- - Added missing `_c_classId` attribute to the `UnImplemented` class. ``ptrepack`` no longer chokes while copying `Unimplemented` classes. - The ``FIELD_*`` sys attrs are no longer copied when the ``PYTABLES_SYS_ATTRS`` parameter is set to false. - `File.createTable()` no longer segfaults if description=None. Closes #248. - Workaround for avoiding a Python issue causing a segfault when saving and then retrieving a string attribute with values "0" or "0.". Closes #253. API changes ----------- - `Row.__contains__()` disabled because it has little sense to query for a key in Row, and the correct way should be to query for it in `Table.colnames` or `Table.colpathnames` better. Closes #241. - [Semantic change] To avoid a common pitfall when asking for the string representation of a `Row` class, `Row.__str__()` has been redefined. Now, it prints something like:: >>> for row in table: ... print row ... /newgroup/table.row (Row), pointing to row #0 /newgroup/table.row (Row), pointing to row #1 /newgroup/table.row (Row), pointing to row #2 instead of:: >>> for row in table: ... print row ... ('Particle: 0', 0, 10, 0.0, 0.0) ('Particle: 1', 1, 9, 1.0, 1.0) ('Particle: 2', 2, 8, 4.0, 4.0) Use `print row[:]` idiom if you want to reproduce the old behaviour. Closes #252. Other changes ------------- - After some improvements in both HDF5 and PyTables, the limit before emitting a `PerformanceWarning` on the number of children in a group has been raised from 4096 to 16384. Changes from 2.1.1 to 2.2b1 =========================== Enhancements ------------ - Added `Expr`, a class for evaluating expressions containing array-like objects. It can evaluate expressions (like '3*a+4*b') that operate on arbitrary large arrays while optimizing the resources (basically main memory and CPU cache memory) required to perform them. It is similar to the Numexpr package, but in addition to NumPy objects, it also accepts disk-based homogeneous arrays, like the `Array`, `CArray`, `EArray` and `Column` PyTables objects. - Added support for NumPy's extended slicing in all `Leaf` objects. With that, you can do the next sort of selections:: array1 = array[4] # simple selection array2 = array[4:1000:2] # slice selection array3 = array[1, ..., ::2, 1:4, 4:] # general slice selection array4 = array[1, [1,5,10], ..., -1] # fancy selection array5 = array[np.where(array[:] > 4)] # point selection array6 = array[array[:] > 4] # boolean selection Thanks to Andrew Collette for implementing this for h5py, from which it has been backported. Closes #198 and #209. - Numexpr updated to 1.3.1. This can lead to up a 25% improvement of the time for both in-kernel and indexed queries for unaligned tables. - HDF5 1.8.3 supported. Bugs fixed ---------- - Fixed problems when modifying multidimensional columns in Table objects. Closes #228. - Row attribute is no longer stalled after a table move or rename. Fixes #224. - Array.__getitem__(scalar) returns a NumPy scalar now, instead of a 0-dim NumPy array. This should not be noticed by normal users, unless they check for the type of returned value. Fixes #222. API changes ----------- - Added a `dtype` attribute for all leaves. This is the NumPy ``dtype`` that most closely matches the leaf type. This allows for a quick-and-dirty check of leaf types. Closes #230. - Added a `shape` attribute for `Column` objects. This is formed by concatenating the length of the column and the shape of its type. Also, the representation of columns has changed an now includes the length of the column as the leading dimension. Closes #231. - Added a new `maindim` attribute for `Column` which has the 0 value (the leading dimension). This allows for a better similarity with other \*Array objects. - In order to be consistent and allow the extended slicing to happen in `VLArray` objects too, `VLArray.__setitem__()` is not able to partially modify rows based on the second dimension passed as key. If this is tried, an `IndexError` is raised now. Closes #210. - The `forceCSI` flag has been replaced by `checkCSI` in the next `Table` methods: `copy()`, `readSorted()` and `itersorted()`. The change reflects the fact that a re-index operation cannot be triggered from these methods anymore. The rational for the change is that an indexing operation is a potentially very expensive operation that should be carried out explicitly instead of being triggered by methods that should not be in charge of this task. Closes #216. Backward incompatible changes ----------------------------- - After the introduction of the `shape` attribute for `Column` objects, the shape information for multidimensional columns has been removed from the `dtype` attribute (it is set to the base type of the column now). Closes #232. **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: rst .. coding: utf-8 .. fill-column: 72 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v2.3.x.rst000066400000000000000000000063501416254111300236610ustar00rootroot00000000000000======================================= Release notes for PyTables 2.3 series ======================================= :Author: PyTables maintainers :Contact: pytables@googlemail.com Changes from 2.3 to 2.3.1 ========================= - Fixed a bug that prevented to read scalar datasets of UnImplemented types (closes :issue:`111`). Thanks to Kamil Kisiel. - Fixed a bug in `setup.py` that caused installation of PyTables 2.3 to fail on hosts with multiple python versions installed (closes :issue:`113`). Thanks to sbinet. Changes from 2.2.1 to 2.3 ========================= Features coming from (now liberated) PyTables Pro ------------------------------------------------- - OPSI is a powerful and innovative indexing engine allowing PyTables to perform fast queries on arbitrarily large tables. Moreover, it offers a wide range of optimization levels for its indexes so that the user can choose the best one that suits her needs (more or less size, more or less performance). Indexation code also takes advantage of the vectorization capabilities of the NumPy and Numexpr packages to ensure really short indexing and search times. - A fine-tuned LRU cache for both metadata (nodes) and regular data that lets you achieve maximum speed for intensive object tree browsing during data reads and queries. It complements the already efficient cache present in HDF5, although this is more geared towards high-level structures that are specific to PyTables and that are critical for achieving very high performance. Other changes ------------- - Indexes with no elements are now evaluated as non-CSI ones. Closes #312. - Numexpr presence is tested now in setup.py, provided that user is not using setuptools (i.e. ``easy_install`` or ``pip`` tools). When using setuptools, numexpr continues to be a requisite (and Cython too). Closes #298. - Cython is enforced now during compilation time. Also, it is not required when running tests. - Repeatedly closing a file that has been reopened several times is supported now. Closes #318. - The number of times a file has been currently reopened is available now in the new `File.open_count` read-only attribute. - The entire documentation set has been converted to sphinx (close :issue:`85` and :issue:`86`) that now also has an index (closes :issue`39`). - The entire test suite has been updated to use unittest specific assertions (closes :issue:`66`). - PyTables has been tested against the latest version of numpy (v. 1.6.1 and 2.0dev) and Cython (v, 0.15) packages. Closes :issue:`84`. - The setup.py script has been improved to better detect runtimes (closes :issue:`73`). Deprecations ------------ Support for some old packages and related features has been deprecated and will be removed in future versions: - Numeric (closes :issue:`76`) - numarray (closes :issue`76` and :issue:`75`) - HDF5 1.6.x (closes :issue`96`) At the API level the following are now deprecated: - the tables.is_pro constant is deprecated because PyTables Pro has been released under an open source license. - the netcdf3 sub-package (closes :issue:`67`) - the nra sub-package **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: rst .. coding: utf-8 .. fill-column: 72 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v2.4.x.rst000066400000000000000000000122661416254111300236650ustar00rootroot00000000000000======================================= Release notes for PyTables 2.4 series ======================================= :Author: PyTables maintainers :Contact: pytables@googlemail.com .. py:currentmodule:: tables Changes from 2.3.1 to 2.4 ========================= New features ------------ - Improved HDF5 error logging management: * added a new function, :func:`silenceHDF5Messages`, for suppressing (and re-enabling) HDF5 messages. By default HDF5 error logging is now suppressed. Closes :issue:`87`. * now all HDF5 error messages and trace-backs are trapped and attached to the :exc:`exceptions.HDF5ExtError` exception instances. Closes :issue:`120`. - Added support for the float16 data type. It is only available if numpy_ provides it as well (i.e. numpy_ >= 1.6). See :issue:`51`. - Leaf nodes now have attributes for retrieving the size of data in memory and on disk. Data on disk can be compressed, so the new attributes make it easy to compute the data compression ration. Thanks to Josh Ayers (close :issue:`141`). - The maximum number of threads for Blosc_ and Numexpr_ is now handled using the :data:`parameters.MAX_BLOSC_THREADS` and :data:`parameters.MAX_NUMEXPR_THREADS` parameters respectively. This allows a more fine grained configuration capability. Closes :issue:`142`. - `ndim` (read-only) attribute added to :class:`Leaf`, :class:`Atom` and :class:`Col` objects (closes :issue:`126`). - Added read support for variable length string attributes (non scalar attributes are converted into numpy_ arrays with 'O8' type). See :issue:`54`. Other improvements ------------------ - Dropped support for HDF5 1.6.x. Now PyTables uses the HDF5 1.8 API (closes :issue:`105`). - Blosc_ updated to v. 1.1.3. - The Blosc_ compression library is now automatically disabled on platforms that do not support unaligned memory access (see also https://github.com/FrancescAlted/blosc/issues/3 and http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=661286). - Improved bzip2 detection on Windows (:issue:`116`). Thanks to cgohlke. - For Windows, the setup.py script now has the ability to automatically find the HDF5_DIR in the system PATH. Thanks to Mark (mwiebe). - Improved multi-arch support in GNU/Linux platforms (closes :issue:`124`) Thanks to Julian Taylor and Picca Frederic-Emmanuel. - Use new style syntax for exception raising. Closes :issue:`93`. - Fixed most of the warnings related to py3k compatibility (see :issue:`92`). - Fixed pyflakes_ warnings (closes :issue:`102`). - Cython_ extensions updated to use new constructs (closes :issue:`100`). - Reduced the number of build warnings (closes :issue:`101`). - Removed the old lrucache module. It is no more needed after the merge with PyTables Pro (closes :issue:`118`). - Added explicit (import time) testing for hdf5dll.dll on Windows to improve diagnostics (closes :issue:`146`). Thanks to Mark (mwiebe). Documentation improvements -------------------------- - new cookbook section (contents have been coming from the PyTables wiki on http://www.pytables.org) - complete rework of the library reference. Now the entire chapter is generated from docstrings using the sphinx autodoc extension. A big thank you to Josh Ayers. Closes :issue:`148`. - new sphinx theme based on the cloud template Bugs fixed ---------- - Fixed a segfault on platforms that do not support unaligned memory access (closes: :issue:`134`). Thanks to Julian Taylor. - Fixed broken inheritance in :class:`IsDescription` classes (thanks to Andrea Bedini). Closes :issue:`65`. - Fixed table descriptions copy method (closes :issue:`131`). - Fixed open failures handling (closes :issue:`158`). Errors that happen when one tries to open an invalid HDF5 file (e.g. an empty file) are now detected earlier by PyTables and a proper exception (:exc:`exceptions.HDF5ExtError`) is raised. Also, in case of open failures, invalid file descriptors are no more cached. Before is fix it was not possible to completely close the bad file and reopen the same path, even if a valid file was created in the meanwhile. Thanks to Daniele for reporting and for the useful test code. - Fixed support to rich structured numpy.dtype in :func:`description.descr_from_dtype`. Closes :issue:`160`. - Fixed sorting of nested tables that caused AttributeError. Closes :issue:`156` and :issue:`157`. Thanks to Uwe Mayer. - Fixed flavor deregistration (closes :issue:`163`) Deprecations ------------ - The :data:`parameters.MAX_THREADS` configuration parameter is now deprecated. Please use :data:`parameters.MAX_BLOSC_THREADS` and :data:`parameters.MAX_NUMEXPR_THREADS` instead. See :issue:`142`. - Since the support for HDF5 1.6.x has been dropped, the *warn16incompat* argument of the :meth:`File.createExternalLink` method and the :exc:`exceptions.Incompat16Warning` exception class are now deprecated. .. _pyflakes: https://launchpad.net/pyflakes .. _numpy: http://www.numpy.org .. _Blosc: https://github.com/FrancescAlted/blosc .. _Numexpr: http://code.google.com/p/numexpr .. _Cython: http://www.cython.org **Enjoy data!** -- The PyTables Team .. Local Variables: .. mode: rst .. coding: utf-8 .. fill-column: 72 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v3.0.x.rst000066400000000000000000000265631416254111300236670ustar00rootroot00000000000000======================================= Release notes for PyTables 3.0 series ======================================= :Author: PyTables Developers :Contact: pytables@googlemail.com .. py:currentmodule:: tables Changes from 2.4 to 3.0 ======================= New features ------------ - Since this release PyTables provides full support to Python_ 3 (closes :issue:`188`). - The entire code base is now more compliant with coding style guidelines describe in the PEP8_ (closes :issue:`103` and :issue:`224`). See `API changes`_ for more details. - Basic support for HDF5 drivers. Now it is possible to open/create an HDF5 file using one of the SEC2, DIRECT, LOG, WINDOWS, STDIO or CORE drivers. Users can also set the main driver parameters (closes :issue:`166`). Thanks to Michal Slonina. - Basic support for in-memory image files. An HDF5 file can be set from or copied into a memory buffer (thanks to Michal Slonina). This feature is only available if PyTables is built against HDF5 1.8.9 or newer. Closes :issue:`165` and :issue:`173`. - New :meth:`File.get_filesize` method for retrieving the HDF5 file size. - Implemented methods to get/set the user block size in a HDF5 file (closes :issue:`123`) - Improved support for PyInstaller_. Now it is easier to pack frozen applications that use the PyTables package (closes: :issue:`177`). Thanks to Stuart Mentzer and Christoph Gohlke. - All read methods now have an optional *out* argument that allows to pass a pre-allocated array to store data (closes :issue:`192`) - Added support for the floating point data types with extended precision (Float96, Float128, Complex192 and Complex256). This feature is only available if numpy_ provides it as well. Closes :issue:`51` and :issue:`214`. Many thanks to Andrea Bedini. - Consistent ``create_xxx()`` signatures. Now it is possible to create all data sets :class:`Array`, :class:`CArray`, :class:`EArray`, :class:`VLArray`, and :class:`Table` from existing Python objects (closes :issue:`61` and :issue:`249`). See also the `API changes`_ section. - Complete rewrite of the :mod:`nodes.filenode` module. Now it is fully compliant with the interfaces defined in the standard :mod:`io` module. Only non-buffered binary I/O is supported currently. See also the `API changes`_ section. Closes :issue:`244`. - New :program:`pt2to3` tool is provided to help users to port their applications to the new API (see `API changes`_ section). Improvements ------------ - Improved runtime checks on dynamic loading of libraries: meaningful error messages are generated in case of failure. Also, now PyTables no more alters the system PATH. Closes :issue:`178` and :issue:`179` (thanks to Christoph Gohlke). - Improved list of search paths for libraries as suggested by Nicholaus Halecky (see :issue:`219`). - Removed deprecated Cython_ include (.pxi) files. Contents of :file:`convtypetables.pxi` have been moved in :file:`utilsextension.pyx`. Closes :issue:`217`. - The internal Blosc_ library has been upgraded to version 1.2.3. - Pre-load the bzip2_ library on windows (closes :issue:`205`) - The :meth:`File.get_node` method now accepts unicode paths (closes :issue:`203`) - Improved compatibility with Cython_ 0.19 (see :issue:`220` and :issue:`221`) - Improved compatibility with numexpr_ 2.1 (see also :issue:`199` and :issue:`241`) - Improved compatibility with development versions of numpy_ (see :issue:`193`) - Packaging: since this release the standard tar-ball package no more includes the PDF version of the "PyTables User Guide", so it is a little bit smaller now. The complete and pre-build version of the documentation both in HTML and PDF format is available on the file `download area`_ on SourceForge.net. Closes: :issue:`172`. - Now PyTables also uses `Travis-CI`_ as continuous integration service. All branches and all pull requests are automatically tested with different Python_ versions. Closes :issue:`212`. Other changes ------------- - PyTables now requires Python 2.6 or newer. - Minimum supported version of Numexpr_ is now 2.0. API changes ----------- The entire PyTables API as been made more PEP8_ compliant (see :issue:`224`). This means that many methods, attributes, module global variables and also keyword parameters have been renamed to be compliant with PEP8_ style guidelines (e.g. the ``tables.hdf5Version`` constant has been renamed into ``tables.hdf5_version``). We made the best effort to maintain compatibility to the old API for existing applications. In most cases, the old 2.x API is still available and usable even if it is now deprecated (see the Deprecations_ section). The only important backwards incompatible API changes are for names of function/methods arguments. All uses of keyword arguments should be checked and fixed to use the new naming convention. The new :program:`pt2to3` tool can be used to port PyTables based applications to the new API. Many deprecated features and support for obsolete modules has been dropped: - The deprecated :data:`is_pro` module constant has been removed - The nra module and support for the obsolete numarray module has been removed. The *numarray* flavor is no more supported as well (closes :issue:`107`). - Support for the obsolete Numeric module has been removed. The *numeric* flavor is no longer available (closes :issue:`108`). - The tables.netcdf3 module has been removed (closes :issue:`68`). - The deprecated :exc:`exceptions.Incompat16Warning` exception has been removed - The :meth:`File.create_external_link` method no longer has a keyword parameter named *warn16incompat*. It was deprecated in PyTables 2.4. Moreover: - The :meth:`File.create_array`, :meth:`File.create_carray`, :meth:`File.create_earray`, :meth:`File.create_vlarray`, and :meth:`File.create_table` methods of the :class:`File` objects gained a new (optional) keyword argument named ``obj``. It can be used to initialize the newly created dataset with an existing Python object, though normally these are numpy_ arrays. The *atom*/*descriptor* and *shape* parameters are now optional if the *obj* argument is provided. - The :mod:`nodes.filenode` has been completely rewritten to be fully compliant with the interfaces defined in the :mod:`io` module. The FileNode classes currently implemented are intended for binary I/O. Main changes: * the FileNode base class is no more available, * the new version of :class:`nodes.filenode.ROFileNode` and :class:`nodes.filenode.RAFileNode` objects no more expose the *offset* attribute (the *seek* and *tell* methods can be used instead), * the *lineSeparator* property is no more available and the ``\n`` character is always used as line separator. - The `__version__` module constants has been removed from almost all the modules (it was not used after the switch to Git). Of course the package level constant (:data:`tables.__version__`) still remains. Closes :issue:`112`. - The :func:`lrange` has been dropped in favor of xrange (:issue:`181`) - The :data:`parameters.MAX_THREADS` configuration parameter has been dropped in favor of :data:`parameters.MAX_BLOSC_THREADS` and :data:`parameters.MAX_NUMEXPR_THREADS` (closes :issue:`147`). - The :func:`conditions.compile_condition` function no more has a *copycols* argument, it was no more necessary since Numexpr_ 1.3.1. Closes :issue:`117`. - The *expectedsizeinMB* parameter of the :meth:`File.create_vlarray` and of the :meth:`VLArrsy.__init__` methods has been replaced by *expectedrows*. See also (:issue:`35`). - The :meth:`Table.whereAppend` method has been renamed into :meth:`Table.append_where` (closes :issue:`248`). Please refer to the :doc:`../MIGRATING_TO_3.x` document for more details about API changes and for some useful hint about the migration process from the 2.X API to the new one. Other possibly incompatible changes ----------------------------------- - All methods of the :class:`Table` class that take *start*, *stop* and *step* parameters (including :meth:`Table.read`, :meth:`Table.where`, :meth:`Table.iterrows`, etc) have been redesigned to have a consistent behaviour. The meaning of the *start*, *stop* and *step* and their default values now always work exactly like in the standard :class:`slice` objects. Closes :issue:`44` and :issue:`255`. - Unicode attributes are not stored in the HDF5 file as pickled string. They are now saved on the HDF5 file as UTF-8 encoded strings. Although this does not introduce any API breakage, files produced are different (for unicode attributes) from the ones produced by earlier versions of PyTables. - System attributes are now stored in the HDF5 file using the character set that reflects the native string behaviour: ASCII for Python 2 and UTF8 for Python 3. In any case, system attributes are represented as Python string. - The :meth:`iterrows` method of :class:`*Array` and :class:`Table` as well as the :meth:`Table.itersorted` now behave like functions in the standard :mod:`itertools` module. If the *start* parameter is provided and *stop* is None then the array/table is iterated from *start* to the last line. In PyTables < 3.0 only one element was returned. Deprecations ------------ - As described in `API changes`_, all functions, methods and attribute names that was not compliant with the PEP8_ guidelines have been changed. Old names are still available but they are deprecated. - The use of upper-case keyword arguments in the :func:`open_file` function and the :class:`File` class initializer is now deprecated. All parameters defined in the :file:`tables/parameters.py` module can still be passed as keyword argument to the :func:`open_file` function just using a lower-case version of the parameter name. Bugs fixed ---------- - Better check access on closed files (closes :issue:`62`) - Fix for :meth:`File.renameNode` where in certain cases :meth:`File._g_updateLocation` was wrongly called (closes :issue:`208`). Thanks to Michka Popoff. - Fixed ptdump failure on data with nested columns (closes :issue:`213`). Thanks to Alexander Ford. - Fixed an error in :func:`open_file` when *filename* is a :class:`numpy.str_` (closes :issue:`204`) - Fixed :issue:`119`, :issue:`230` and :issue:`232`, where an index on :class:`Time64Col` (only, :class:`Time32Col` was ok) hides the data on selection from a Tables. Thanks to Jeff Reback. - Fixed ``tables.tests.test_nestedtypes.ColsTestCase.test_00a_repr`` test method. Now the ``repr`` of cols on big-endian platforms is correctly handled (closes :issue:`237`). - Fixes bug with completely sorted indexes where *nrowsinbuf* must be equal to or greater than the *chunksize* (thanks to Thadeus Burgess). Closes :issue:`206` and :issue:`238`. - Fixed an issue of the :meth:`Table.itersorted` with reverse iteration (closes :issue:`252` and :issue:`253`). .. _Python: http://www.python.org .. _PEP8: http://www.python.org/dev/peps/pep-0008 .. _PyInstaller: http://www.pyinstaller.org .. _Blosc: https://github.com/FrancescAlted/blosc .. _bzip2: http://www.bzip.org .. _Cython: http://www.cython.org .. _Numexpr: http://code.google.com/p/numexpr .. _numpy: http://www.numpy.org .. _`download area`: http://sourceforge.net/projects/pytables/files/pytables .. _`Travis-CI`: https://travis-ci.org **Enjoy data!** -- The PyTables Developers .. Local Variables: .. mode: rst .. coding: utf-8 .. fill-column: 72 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v3.1.x.rst000066400000000000000000000223511416254111300236570ustar00rootroot00000000000000Changes from 3.1.0 to 3.1.1 =========================== Bugs fixed ---------- - Fixed a critical bug that caused an exception at import time. The error was triggered when a bug in long-double detection is detected in the HDF5 library (see :issue:`275`) and numpy_ does not expose `float96` or `float128`. Closes :issue:`344`. - The internal Blosc_ library has been updated to version 1.3.5. This fixes a false buffer overrun condition that made c-blosc to fail, even if the problem was not real. Improvements ------------ - Do not create a temporary array when the *obj* parameter is not specified in :meth:`File.create_array` (thanks to Francesc). Closes :issue:`337` and :issue:`339`). - Added two new utility functions (:func:`tables.nodes.filenode.read_from_filenode` and :func:`tables.nodes.filenode.save_to_filenode`) for the direct copy from filesystem to filenode and vice versa (closes :issue:`342`). Thanks to Andreas Hilboll. - Removed the :file:`examples/nested-iter.py` considered no longer useful. Closes :issue:`343`. - Better detection of the `-msse2` compiler flag. Changes from 3.0 to 3.1.0 ========================= New features ------------ - Now PyTables is able to save/restore the default value of :class:`EnumAtom` types (closes :issue:`234`). - Implemented support for the H5FD_SPLIT driver (closes :issue:`288`, :issue:`289` and :issue:`295`). Many thanks to simleo. - New quantization filter: the filter truncates floating point data to a specified precision before writing to disk. This can significantly improve the performance of compressors (closes :issue:`261`). Thanks to Andreas Hilboll. - Added new :meth:`VLArray.get_row_size` method to :class:`VLArray` for querying the number of atoms of a :class:`VLArray` row. Closes :issue:`24` and :issue:`315`. - The internal Blosc_ library has been updated to version 1.3.2. All new features introduced in the Blosc_ 1.3.x series, and in particular the ability to leverage different compressors within Blosc_ (see the `Blosc Release Notes`_), are now available in PyTables via the blosc filter (closes: :issue:`324`). A big thank you to Francesc. Improvements ------------ - The node caching mechanism has been completely redesigned to be simpler and less dependent from specific behaviours of the ``__del__`` method. Now PyTables is compatible with the forthcoming Python 3.4. Closes :issue:`306`. - PyTables no longer uses shared/cached file handlers. This change somewhat improves support for concurrent reading allowing the user to safely open the same file in different threads for reading (requires HDF5 >= 1.8.7). More details about this change can be found in the `Backward incompatible changes`_ section. See also :issue:`130`, :issue:`129` :issue:`292` and :issue:`216`. - PyTables is now able to detect and use external installations of the Blosc_ library (closes :issue:`104`). If Blosc_ is not found in the system, and the user do not specify a custom installation directory, then it is used an internal copy of the Blosc_ source code. - Automatically disable extended float support if a buggy version of HDF5 is detected (see also `Issues with H5T_NATIVE_LDOUBLE`_). See also :issue:`275`, :issue:`290` and :issue:`300`. - Documented an unexpected behaviour with string literals in query conditions on Python 3 (closes :issue:`265`) - The deprecated :mod:`getopt` module has been dropped in favour of :mod:`argparse` in all command line utilities (close :issue:`251`) - Improved the installation section of the :doc:`../usersguide/index`. * instructions for installing PyTables via pip_ have been added. * added a reference to the Anaconda_, Canopy_ and `Christoph Gohlke suites`_ (closes :issue:`291`) - Enabled `Travis-CI`_ builds for Python_ 3.3 - :meth:`Tables.read_coordinates` now also works with boolean indices input. Closes :issue:`287` and :issue:`298`. - Improved compatibility with numpy_ >= 1.8 (see :issue:`259`) - The code of the benchmark programs (bench directory) has been updated. Closes :issue:`114`. - Fixed some warning related to non-unicode file names (the Windows bytes API has been deprecated in Python 3.4) Bugs fixed ---------- - Fixed detection of platforms supporting Blosc_ - Fixed a crash that occurred when one attempts to write a numpy_ array to an :class:`Atom` (closes :issue:`209` and :issue:`296`) - Prevent creation of a table with no columns (closes :issue:`18` and :issue:`299`) - Fixed a memory leak that occured when iterating over :class:`CArray`/:class:`EArray` objects (closes :issue:`308`, see also :issue:`309`). Many thanks to Alistair Muldal. - Make NaN types sort to the end. Closes :issue:`282` and :issue:`313` - Fixed selection on float columns when NaNs are present (closes :issue:`327` and :issue:`330`) - Fix computation of the buffer size for iterations on rows. The buffers size was overestimated resulting in a :exc:`MemoryError` in some cases. Closes :issue:`316`. Thamks to bbudescu. - Better check of file open mode. Closes :issue:`318`. - The Blosc filter now works correctly together with fletcher32. Closes :issue:`21`. - Close the file handle before trying to delete the corresponding file. Fixes a test failure on Windows. - Use integer division for computing indices (fixes some warning on Windows) Deprecations ------------ Following the plan for the complete transition to the new (PEP8_ compliant) API, all calls to the old API will raise a :exc:`DeprecationWarning`. The new API has been introduced in PyTables 3.0 and is backward incompatible. In order to guarantee a smoother transition the old API is still usable even if it is now deprecated. The plan for the complete transition to the new API is outlined in :issue:`224`. Backward incompatible changes ----------------------------- In PyTables <= 3.0 file handles (objects that are returned by the :func:`open_file` function) were stored in an internal registry and re-used when possible. Two subsequent attempts to open the same file (with compatible open mode) returned the same file handle in PyTables <= 3.0:: In [1]: import tables In [2]: print(tables.__version__) 3.0.0 In [3]: a = tables.open_file('test.h5', 'a') In [4]: b = tables.open_file('test.h5', 'a') In [5]: a is b Out[5]: True All this is an implementation detail, it happened under the hood and the user had no control over the process. This kind of behaviour was considered a feature since it can speed up opening of files in case of repeated opens and it also avoids any potential problem related to multiple opens, a practice that the HDF5 developers recommend to avoid (see also H5Fopen_ reference page). The trick, of course, is that files are not opened multiple times at HDF5 level, rather an open file is referenced several times. The big drawback of this approach is that there are really few chances to use PyTables safely in a multi thread program. Several bug reports have been filed regarding this topic. After long discussions about the possibility to actually achieve concurrent I/O and about patterns that should be used for the I/O in concurrent programs PyTables developers decided to remove the *black magic under the hood* and allow the users to implement the patterns they want. Starting from PyTables 3.1 file handles are no more re-used (*shared*) and each call to the :func:`open_file` function returns a new file handle:: In [1]: import tables In [2]: print tables.__version__ 3.1.0 In [3]: a = tables.open_file('test.h5', 'a') In [4]: b = tables.open_file('test.h5', 'a') In [5]: a is b Out[5]: False It is important to stress that the new implementation still has an internal registry (implementation detail) and it is still **not thread safe**. Just now a smart enough developer should be able to use PyTables in a muti-thread program without too much headaches. The new implementation behaves differently from the previous one, although the API has not been changed. Now users should pay more attention when they open a file multiple times (as recommended in the `HDF5 reference`__ ) and they should take care of using them in an appropriate way. __ H5Fopen_ Please note that the :attr:`File.open_count` property was originally intended to keep track of the number of references to the same file handle. In PyTables >= 3.1, despite of the name, it maintains the same semantics, just now its value should never be higher that 1. .. note:: HDF5 versions lower than 1.8.7 are not fully compatible with PyTables 3.1. A partial support to HDF5 < 1.8.7 is still provided but in that case multiple file opens are not allowed at all (even in read-only mode). .. _pip: http://www.pip-installer.org .. _Anaconda: https://store.continuum.io/cshop/anaconda .. _Canopy: https://www.enthought.com/products/canopy .. _`Christoph Gohlke suites`: http://www.lfd.uci.edu/~gohlke/pythonlibs .. _`Issues with H5T_NATIVE_LDOUBLE`: https://forum.hdfgroup.org/t/issues-with-h5t-native-ldouble/2991 .. _Python: http://www.python.org .. _Blosc: http://www.blosc.org .. _numpy: http://www.numpy.org .. _`Travis-CI`: https://travis-ci.org .. _PEP8: http://www.python.org/dev/peps/pep-0008 .. _`Blosc Release Notes`: https://github.com/FrancescAlted/blosc/wiki/Release-notes .. _H5Fopen: https://portal.hdfgroup.org/display/HDF5/Files PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v3.2.x.rst000066400000000000000000000216431416254111300236630ustar00rootroot00000000000000======================================= Release notes for PyTables 3.2 series ======================================= :Author: PyTables Developers :Contact: pytables-dev@googlegroups.com .. py:currentmodule:: tables Changes from 3.2.3 to 3.2.3.1 ============================= Fixed issues with pip install. Changes from 3.2.2 to 3.2.3 =========================== Improvements ------------ - It is now possible to use HDF5 with the new shared library naming scheme (>= 1.8.10, hdf5.dll instead of hdf5dll.dll) on Windows (:issue:`540`). Thanks to Tadeu Manoel. - Now :program: `ptdump` sorts output by node name and does not print a backtrace if file cannot be opened. Thanks to Zbigniew JÄ™drzejewski-Szmek. Bugs fixed ---------- - Only run `tables.tests.test_basics.UnicodeFilename` if the filesystem encoding is utf-8. Closes :issue:`485`. - Add lib64 to posix search path. (closes :issue:`507`) Thanks to Mehdi Sadeghi. - Ensure cache entries are removed if fewer than 10 (closes :issue:`529`). Thanks to Graham Jones. - Fix segmentation fault in a number of test cases that use :class:`index.Index` (closes :issue:`532` and :issue:`533`). Thanks to Diane Trout. - Fixed the evaluation of transcendental functions when numexpr is compiled with VML support (closes :issue:`534`, PR #536). Thanks to Tom Kooij. - Make sure that index classes use buffersizes that are a multiple of chunkshape[0] (closes :issue:`538`, PR #538). Thanks to Tom Kooij. - Ensure benchmark paths exist before benchmarks are executed (PR #544). Thanks to rohitjamuar. Other changes ------------- - Minimum Cython_ version is now v0.21 .. _Cython: http://cython.org Changes from 3.2.1.1 to 3.2.2 ============================= Bug fixed --------- - Fix AssertionError in Row.__init_loop. See :issue:`477`. - Fix issues with Cython 0.23. See :issue:`481`. - Only run `tables.tests.test_basics.UnicodeFilename` if the filesystem encoding is utf-8. Closes :issue:`485`. - Fix missing PyErr_Clear. See :issue:`486`. - Fix the C type of some numpy attributes. See :issue:`494`. - Cast selection indices to integer. See :issue:`496`. - Fix indexesextension._keysort_string. Closes :issue:`497` and :issue:`498`. Changes from 3.2.1 to 3.2.1.1 ============================= - Fix permission on distributed source distribution Other changes ------------- - Minimum Cython_ version is now v0.21 .. _Cython: http://cython.org Changes from 3.2.0 to 3.2.1 =========================== Bug fixed --------- - Fix indexesextension._keysort. Fixes :issue:`455`. Thanks to Andrew Lin. Changes from 3.1.1 to 3.2.0 =========================== Improvements ------------ - The `nrowsinbuf` is better computed now for EArray/CArray having a small `chunkshape` in the main dimension. Fixes #285. - PyTables should be installable very friendly via pip, including NumPy being installed automatically in the unlikely case it is not yet installed in the system. Thanks to Andrea Bedini. - setup.py has been largely simplified and now it requires *setuptools*. Although we think this is a good step, please keep us informed this is breaking some installation in a very bad manner. - setup.py now is able to used *pkg-config*, if available, to locate required libraries (hdf5, bzip2, etc.). The use of *pkg-config* can be controlled via setup.py command line flags or via environment variables. Please refer to the installation guide (in the *User Manual*) for details. Closes :issue:`442`. - It is now possible to create a new node whose parent is a softlink to another group (see :issue:`422`). Thanks to Alistair Muldal. - :class:`link.SoftLink` objects no longer need to be explicitly dereferenced. Methods and attributes of the linked object are now automatically accessed when the user acts on a soft-link (see :issue:`399`). Thanks to Alistair Muldal. - Now :program:`ptrepack` recognizes hardlinks and replicates them in the output (*repacked*) file. This saves disk space and makes repacked files more conformal to the original one. Closes :issue:`380`. - New :program:`pttree` script for printing HDF5 file contents as a pretty ASCII tree (closes :issue:`400`). Thanks to Alistair Muldal. - The internal Blosc library has been downgraded to version 1.4.4. This is in order to still allow using multiple threads *inside* Blosc, even on multithreaded applications (see :issue:`411`, :issue:`412`, :issue:`437` and :issue:`448`). - The :func:`print_versions` function now also reports the version of compression libraries used by Blosc. - Now the :file:`setup.py` tries to use the '-march=native' C flag by default. In falls back on '-msse2' if '-march=native' is not supported by the compiler. Closes :issue:`379`. - Fixed a spurious unicode comparison warning (closes :issue:`372` and :issue:`373`). - Improved handling of empty string attributes. In previous versions of PyTables empty string were stored as scalar HDF5 attributes having size 1 and value '\0' (an empty null terminated string). Now empty string are stored as HDF5 attributes having zero size - Added a new cookbook recipe and a couple of examples for simple threading with PyTables. - The redundant :func:`utilsextension.get_indices` function has been eliminated (replaced by :meth:`slice.indices`). Closes :issue:`195`. - Allow negative indices in point selection (closes :issue:`360`) - Index wasn't being used if it claimed there were no results. Closes :issue:`351` (see also :issue:`353`) - Atoms and Col types are no longer generated dynamically so now it is easier for IDEs and static analysis tool to handle them (closes :issue:`345`) - The keysort functions in idx-opt.c have been cythonised using fused types. The perfomance is mostly unchanged, but the code is much more simpler now. Thanks to Andrea Bedini. - Small unit tests re-factoring: * :func:`print_versions` and :func:`tests.common.print_heavy` functions moved to the :mod:`tests.common` module * always use :func:`print_versions` when test modules are called as scripts * use the unittest2_ package in Python 2.6.x * removed internal machinery used to replicate unittest2_ features * always use :class:`tests.common.PyTablesTestCase` as base class for all test cases * code of the old :func:`tasts.common.cleanup` function has been moved to :meth:`tests.common.PyTablesTestCase.tearDown` method * new implementation of :meth:`tests.common.PyTablesTestCase.assertWarns` compatible with the one provided by the standard :mod:`unittest` module in Python >= 3.2 * use :meth:`tests.common.PyTablesTestCase.assertWarns` as context manager when appropriate * use the :func:`unittest.skipIf` decorator when appropriate * new :class:tests.comon.TestFileMixin: class .. _unittest2: https://pypi.python.org/pypi/unittest2 Bugs fixed ---------- - Fixed compatibility problems with numpy 1.9 and 1.10-dev (closes :issue:`362` and :issue:`366`) - Fixed compatibility with Cython >= 0.20 (closes :issue:`386` and :issue:`387`) - Fixed support for unicode node names in LRU cache (only Python 2 was affected). Closes :issue:`367` and :issue:`369`. - Fixed support for unicode node titles (only Python 2 was affected). Closes :issue:`370` and :issue:`374`. - Fixed a bug that caused the silent truncation of unicode attributes containing the '\0' character. Closes :issue:`371`. - Fixed :func:`descr_from_dtype` to work as expected with complex types. Closes :issue:`381`. - Fixed the :class:`tests.test_basics.ThreadingTestCase` test case. Closes :issue:`359`. - Fix incomplete results when performing the same query twice and exhausting the second iterator before the first. The first one writes incomplete results to *seqcache* (:issue:`353`) - Fix false results potentially going to *seqcache* if :meth:`tableextension.Row.update` is used during iteration (see :issue:`353`) - Fix :meth:`Column.create_csindex` when there's NaNs - Fixed handling of unicode file names on windows (closes :issue:`389`) - No longer not modify :data:`sys.argv` at import time (closes :issue:`405`) - Fixed a performance issue on NFS (closes :issue:`402`) - Fixed a nasty problem affecting results of indexed queries. Closes :issue:`319` and probably :issue:`419` too. - Fixed another problem affecting results of indexed queries too. Closes :issue:`441`. - Replaced "len(xrange(start, stop, step))" -> "len(xrange(0, stop - start, step))" to fix issues with large row counts with Python 2.x. Fixes #447. Other changes ------------- - Cython is not a hard dependency anymore (although developers will need it so as to generated the C extension code). - The number of threads used by default for numexpr and Blosc operation that was set to the number of available cores have been reduced to 2. This is a much more reasonable setting for not creating too much overhead. **Enjoy data!** -- The PyTables Developers .. Local Variables: .. mode: rst .. coding: utf-8 .. fill-column: 72 .. End: PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v3.3.x.rst000066400000000000000000000025051416254111300236600ustar00rootroot00000000000000======================================= Release notes for PyTables 3.3 series ======================================= Changes from 3.2.3.1 to 3.3.0 ============================= Improvements ------------ - Single codebase Python 2 and 3 support (PR #493). - Internal Blosc version updated to 1.11.1 (closes :issue:`541`) - Full BitShuffle support for new Blosc versions (>= 1.8). - It is now possible to remove all rows from a table. - It is now possible to read reference types by dereferencing them as numpy array of objects (closes :issue:`518` and :issue:`519`). Thanks to Ehsan Azar - Get rid of the `-native` compile flag (closes :issue:`503`) - The default number of threads to run numexpr (MAX_NUMEXPR_THREADS) internally has been raised from 2 to 4. This is because we are in 2016 and 4 core configurations are becoming common. - In order to avoid locking issues when using PyTables concurrently in several process, MAX_BLOSC_THREADS has been set to 1 by default. If you are running PyTables in one single process, you may want to experiment if higher values (like 2 or 4) bring better performance for you. Bugs fixed ---------- - On python 3 try 'latin1' encoding before 'bytes' encoding during unpickling of node attributes pickled on python 2. Better fix for :issue:`560`. - Fixed Windows 32 and 64-bit builds. PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v3.4.x.rst000066400000000000000000000065621416254111300236700ustar00rootroot00000000000000======================================= Release notes for PyTables 3.4 series ======================================= Changes from 3.4.3 to 3.4.4 =========================== Improvements ------------ - Environment variable to control the use of embedded libraries. Thanks to avalentino. - Include citation in repository. :issue:`690`. Thanks to katrinleinweber. Bugs fixed ---------- - Fixed import error with numexpr 2.6.5.dev0 :issue:`685`. Thanks to cgohlke. - Fixed linter warnings. Thanks to avalentino. - Fixed for re.split() is version detection. :issue:`687`. Thanks to mingwandroid. - Fixed test failures with Python 2.7 and NumPy 1.14.3 :issue:`688` & :issue:`689`. Thanks to oleksandr-pavlyk. Changes from 3.4.2 to 3.4.3 =========================== Improvements ------------ - On interactive python sessions, group/attribute `__dir__()` method autocompletes children that are named as valid python identifiers. :issue:`624` & :issue:`625` thanks to ankostis. - Implement `Group.__getitem__()` to have groups act as python-containers, so code like this works: ``hfile.root['some child']``. :issue:`628` thanks to ankostis. - Enable building with Intel compiler (icc/icpc). Thanks to rohit-jamuar. - PEP 519 support, using new `os.fspath` method. Thanks to mruffalo. - Optional disable recording of ctime (metadata creation time) when creating datasets that makes possible to get bitwise identical output from repeated runs. Thanks to alex-cobb. - Prevent from reading all rows for each coord in a VLArray when indexing using a list . Thanks to igormq. - Internal Blosc version updated to 1.14.3 Bugs fixed ---------- - Fixed division by zero when using `_convert_time64()` with an empty nparr array. :issue:`653`. Thanks to alobbs. - Fixed deprecation warnings with numpy 1.14. Thanks to oleksandr-pavlyk. - Skip DLL check when running from a frozen app. :issue:`675`. Thanks to jwiggins. - Fixed behaviour with slices out of range. :issue:`651`. Thanks to jackdbd. Changes from 3.4.1 to 3.4.2 =========================== Improvements ------------ - setup.py detects conda env and uses installed conda (hdf5, bzip2, lzo and/or blosc) packages when building from source. Bugs fixed ---------- - Linux wheels now built against built-in blosc. - Fixed windows absolute paths in ptrepack, ptdump, ptree. :issue:`616`. Thanks to oscar6echo. Changes from 3.4.0 to 3.4.1 =========================== Bugs fixed ---------- - Fixed bug in ptrepack Changes from 3.3.0 to 3.4.0 =========================== Improvements ------------ - Support for HDF5 v1.10.x (see :issue:`582`) - Fix compatibility with the upcoming Python 2.7.13, 3.5.3 and 3.6 versions. See also :issue:`590`. Thanks to Yaroslav Halchenko - Internal Blosc version updated to 1.11.3 - Gracefully handle cpuinfo failure. (PR #578) Thanks to Zbigniew JÄ™drzejewski-Szmek - Update internal py-cpuinfo to 3.3.0. Thanks to Gustavo Serra Scalet. Bugs fixed ---------- - Fix conversion of python 2 `long` type to `six.integer_types` in atom.py. See also :issue:`598`. Thanks to Kyle Keppler for reporting. - Fix important bug in bitshuffle filter in internal Blosc on big-endian machines. See also :issue:`583`. - Fix allow for long type in nextafter. (PR #587) Thanks to Yaroslav Halchenko. - Fix unicode bug in group and tables names. :issue:`514` PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v3.5.x.rst000066400000000000000000000036721416254111300236700ustar00rootroot00000000000000======================================= Release notes for PyTables 3.5 series ======================================= :Author: PyTables Developers :Contact: pytables-dev@googlegroups.com .. py:currentmodule:: tables Changes from 3.5.1 to 3.5.2 =========================== - Fixed compatibility with python 3.8: Fixed `Dictionary keys changed during iteration` RuntimeError while moving/renaming a node. Thanks to Christoph Gohlke for reporting and Miro HronÄok for help with building PyTables for python 3.8alpha (cython compatibility). see :issue:`733` and PR #737. - Fixed a bug in offset calculations producing floats instead of ints affecting python 3. See PR #736. Thanks to Brad Montgomery. Changes from 3.5.0 to 3.5.1 =========================== - Maintenance release to fix how PyPi repo is handling wheel versions. Changes from 3.4.4 to 3.5.0 =========================== Improvements ------------ - When copying data from native HDF5 files with padding in compound types, the padding is not removed now by default. This allows for better compatibility with existing HDF5 applications that expect the padding to stay there. Also, when the `description` is a NumPy struct array with padding, this is honored now. The previous behaviour (i.e. getting rid of paddings) can be replicated by passing the new `allow_padding` parameter when opening a file. For some examples, see the new `examples/tables-with-padding.py` and `examples/attrs-with-padding.py`. For details on the implementation see :issue:`720`. - Added a new flag `--dont-allow-padding` in `ptrepack` utility so as to replicate the previous behaviour of removing padding during file copies. The default is to honor the original padding in copies. - Improve compatibility with numpy 1.16. - Improve detection of the LZO2 library at build time. - Suppress several warnings. - Add AVX2 support for Windows. See PR #716. Thanks to Robert McLeod. PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v3.6.x.rst000066400000000000000000000025071416254111300236650ustar00rootroot00000000000000======================================= Release notes for PyTables 3.6 series ======================================= :Author: PyTables Developers :Contact: pytables-dev@googlegroups.com .. py:currentmodule:: tables Changes from 3.6.0 to 3.6.1 =========================== Maintenance release to fix packaging issues. No new features or bugfixes. Changes from 3.5.3 to 3.6.0 =========================== PyTables 3.6 no longer supports Python 2.7 see PR #747. Improvements ------------ - Full python 3.8 support. - On Windows PyTables wheels on PyPI are linked to `pytables_hdf5.dll` instead of `hdf5.dll` to prevent collisions with other packages/wheels that also vendor `hdf5.dll`. This should prevent problems that arise when a different version of a dll is imported that the version to which the program was linked to. This problem is known as "DLL Hell". With the renaming of the HDF5 DLL to `pytables_hdf5.dll` these problems should be solved. Bugfixes -------- - Bugfix for HDF5 files/types with padding. For details see :issue:`734`. - More fixes for python 3.8 compatibility: Replace deprecated time.clock with time.perf_counter Thanks to Sergio Pascual (sergiopasra). see :issue:`744` and PR #745. - Improvements in tests as well as clean up from dropping Python 2.7 support. Thanks to Seth Troisi (sethtroisi). PyTables-3.7.0/doc/source/release-notes/RELEASE_NOTES_v3.7.x.rst000066400000000000000000000000501416254111300236550ustar00rootroot00000000000000.. include:: ../../../RELEASE_NOTES.rst PyTables-3.7.0/doc/source/release_notes.rst000066400000000000000000000110121416254111300206640ustar00rootroot00000000000000====================== PyTables Release Notes ====================== Migration ---------- .. toctree:: :maxdepth: 1 Migrating from 2.x to 3.x Migrating from 1.x to 2.x PyTables -------- .. toctree:: :maxdepth: 1 release-notes/RELEASE_NOTES_v3.7.x release-notes/RELEASE_NOTES_v3.6.x release-notes/RELEASE_NOTES_v3.5.x release-notes/RELEASE_NOTES_v3.4.x release-notes/RELEASE_NOTES_v3.3.x release-notes/RELEASE_NOTES_v3.2.x release-notes/RELEASE_NOTES_v3.1.x release-notes/RELEASE_NOTES_v3.0.x release-notes/RELEASE_NOTES_v2.4.x release-notes/RELEASE_NOTES_v2.3.x release-notes/RELEASE_NOTES_v2.2.x release-notes/RELEASE_NOTES_v2.1.x release-notes/RELEASE_NOTES_v2.0.x release-notes/RELEASE_NOTES_v1.4 release-notes/RELEASE_NOTES_v1.3.3 release-notes/RELEASE_NOTES_v1.3.2 release-notes/RELEASE_NOTES_v1.3.1 release-notes/RELEASE_NOTES_v1.3 release-notes/RELEASE_NOTES_v1.2.3 release-notes/RELEASE_NOTES_v1.2.2 release-notes/RELEASE_NOTES_v1.2.1 release-notes/RELEASE_NOTES_v1.2 release-notes/RELEASE_NOTES_v1.1.1 release-notes/RELEASE_NOTES_v1.1 release-notes/RELEASE_NOTES_v1.0 release-notes/RELEASE_NOTES_v0.9.1 release-notes/RELEASE_NOTES_v0.9 release-notes/RELEASE_NOTES_v0.8 release-notes/RELEASE_NOTES_v0.7.2 release-notes/RELEASE_NOTES_v0.7.1 PyTables Pro ------------ .. toctree:: :maxdepth: 1 release-notes/RELEASE_NOTES_v2.2.x-pro release-notes/RELEASE_NOTES_v2.1.x-pro release-notes/RELEASE_NOTES_v2.0.x-pro Release timeline ---------------- =============== =========== ========== PyTables 3.5.2 2019-05-31 PyTables 3.5.1 2019-03-14 PyTables 3.5.0 2019-03-13 PyTables 3.4.4 2018-06-11 PyTables 3.4.3 2018-04-17 PyTables 3.4.2 2017-04-19 PyTables 3.4.1 2017-04-12 PyTables 3.4.0 2017-04-11 PyTables 3.3.0 2016-09-12 PyTables 3.2.3.1 2016-07-05 PyTables 3.2.3 2016-07-04 PyTables 3.2.2 2015-09-22 PyTables 3.2.1.1 2015-08-31 PyTables 3.2.1 2015-08-04 PyTables 3.2.0 2015-05-06 PyTables 3.2.0rc2 2015-05-01 PyTables 3.2.0rc1 2015-04-21 PyTables 3.1.1 2014-03-25 PyTables 3.1.0 2014-02-05 PyTables 3.1.0rc2 2014-01-22 PyTables 3.1.0rc1 2014-01-17 PyTables 3.0 2013-06-01 PyTables 3.0rc3 2013-05-29 PyTables 3.0rc2 2013-05-17 PyTables 3.0rc1 2013-05-10 PyTables 3.0b1 2013-04-27 PyTables 2.4 2012-07-20 PyTables 2.4rc1 2012-07-16 PyTables 2.4b1 2012-07-07 PyTables 2.3.1 2011-10-28 PyTables 2.3 2011-09-23 PyTables 2.3rc1 2011-09-11 PyTables 2.2.1 2010-11-05 PyTables 2.2.1rc1 2010-11-03 Pytables 2.2 2010-07-01 PyTables 2.2rc2 2010-06-17 PyTables 2.2rc1 2010-05-20 PyTables 2.1.2 2009-09-14 PyTables 2.1.1 2009-03-13 PyTables Pro 2.1.1 2009-03-13 PyTables 2.1 2008-12-19 PyTables 2.1rc2 2008-11-18 PyTables 2.1rc1 2008-10-31 PyTables 2.0.4 2008-07-05 PyTables Pro 2.0.4 2008-07-05 PyTables 2.0.3 2008-03-07 PyTables Pro 2.0.2.1 2007-12-24 PyTables Pro 2.0.1 2007-09-20 PyTables 2.0.1 2007-09-20 PyTables Pro 2.0 2007-07-12 PyTables 2.0 2007-07-12 PyTables 2.0rc2 2007-05-28 PyTables 2.0rc1 2007-04-26 PyTables 1.4 2006-12-21 PyTables 1.3.3 2006-08-24 PyTables 1.3.2 2006-06-20 PyTables 1.3.1 2006-05-02 PyTables 1.3 2006-04-01 PyTables 1.2.3 2006-02-23 PyTables 1.2.2 2006-02-16 PyTables 1.2.1 2005-12-21 PyTables 1.2 2005-11-22 PyTables 1.1.1 2005-09-13 PyTables 1.1 2005-07-14 PyTables 1.0 2005-05-12 PyTables 0.9.1 2004-12-02 PyTables- 0.9 2004-11-08 PyTables 0.8.1 2004-07-13 PyTables 0.8 2004-03-03 PyTables 0.7.2 2003-09-22 PyTables 0.7 2003-07-31 PyTables 0.5.1 2003-05-14 PyTables 0.5 2003-05-10 Pytables 0.4 2003-03-19 =============== =========== ========== PyTables-3.7.0/doc/source/usersguide/000077500000000000000000000000001416254111300174665ustar00rootroot00000000000000PyTables-3.7.0/doc/source/usersguide/bibliography.rst000066400000000000000000000125501416254111300226760ustar00rootroot00000000000000Bibliography ============ .. _HDFG1: :ref:`[HDFG1] ` The HDF Group. What is HDF5?. Concise description about HDF5 capabilities and its differences from earlier versions (HDF4). ``_. .. _HDFG2: :ref:`[HDFG2] ` The HDF Group. Introduction to HDF5. Introduction to the HDF5 data model and programming model. ``_. .. _HDFG3: :ref:`[HDFG3] ` The HDF Group. The HDF5 table programming model. Examples on using HDF5 tables with the C API. ``_. .. _MERTZ: :ref:`[MERTZ] ` David Mertz. Objectify. On the 'Pythonic' treatment of XML documents as objects(II). Article describing XML Objectify, a Python module that allows working with XML documents as Python objects. Some of the ideas presented here are used in PyTables. ``_. .. _CYTHON: :ref:`[CYTHON] ` Stefan Behnel, Robert Bradshaw, Dag Sverre Seljebotn, and Greg Ewing. Cython. A language that makes writing C extensions for the Python language as easy as Python itself. ``_. .. _NUMPY: :ref:`[NUMPY] ` Travis Oliphant and et al. NumPy. Scientific Computing with Numerical Python. The latest and most powerful re-implementation of Numeric to date. It implements all the features that can be found in Numeric and numarray, plus a bunch of new others. In general, it is more efficient as well. ``_. .. _NUMEXPR: :ref:`[NUMEXPR] ` David Cooke, Francesc Alted, and et al. Numexpr. Fast evaluation of array expressions by using a vector-based virtual machine. It is an enhaced computing kernel that is generally faster (between 1x and 10x, depending on the kind of operations) than NumPy at evaluating complex array expressions. ``_. .. _ZLIB: :ref:`[ZLIB] ` JeanLoup Gailly and Mark Adler. zlib. A Massively Spiffy Yet Delicately Unobtrusive Compression Library. A standard library for compression purposes. ``_. .. _LZO: :ref:`[LZO] ` Markus F Oberhumer. LZO. A data compression library which is suitable for data de-/compression in real-time. It offers pretty fast compression and decompression with reasonable compression ratio. ``_. .. _BZIP2: :ref:`[BZIP2] ` Julian Seward. bzip2. A high performance lossless compressor. It offers very high compression ratios within reasonable times. ``_. .. _BLOSC: :ref:`[BLOSC] ` Francesc Alted. Blosc. A blocking, shuffling and loss-less compression library. A compressor designed to transmit data from memory to CPU (and back) faster than a plain memcpy(). ``_. .. _GNUWIN32: :ref:`[GNUWIN32] ` Alexis Wilke, Jerry S., Kees Zeelenberg, and Mathias Michaelis. GnuWin32. GNU (and other) tools ported to Win32. GnuWin32 provides native Win32-versions of GNU tools, or tools with a similar open source licence. ``_. .. _PSYCO: :ref:`[PSYCO] ` Armin Rigo. Psyco. A Python specializing compiler. Run existing Python software faster, with no change in your source. ``_. .. _SCIPY1: :ref:`[SCIPY1] ` Konrad Hinsen. Scientific Python. Collection of Python modules useful for scientific computing. ``_. .. _SCIPY2: :ref:`[SCIPY2] ` Eric Jones, Travis Oliphant, Pearu Peterson, and et al. SciPy. Scientific tools for Python. SciPy supplements the popular Numeric module, gathering a variety of high level science and engineering modules together as a single package. ``_. .. _OPTIM: :ref:`[OPTIM] ` Francesc Alted and Ivan Vilata. Optimization of file openings in PyTables. This document explores the savings of the opening process in terms of both CPU time and memory, due to the adoption of a LRU cache for the nodes in the object tree. ``_. .. _OPSI: :ref:`[OPSI] ` Francesc Alted and Ivan Vilata. OPSI: The indexing system of PyTables 2 Professional Edition. Exhaustive description and benchmarks about the indexing engine that comes with PyTables Pro. ``_. .. _VITABLES: :ref:`[VITABLES] ` Vicent Mas. ViTables. A GUI for PyTables/HDF5 files. It is a graphical tool for browsing and editing files in both PyTables and HDF5 formats. ``_. .. _GIT: :ref:`[GIT] ` Git is a free and open source, distributed version control system designed to handle everything from small to very large projects with speed and efficiency ``_. .. _SPHINX: :ref:`[SPHINX] ` Sphinx is a tool that makes it easy to create intelligent and beautiful documentation, written by Georg Brandl and licensed under the BSD license ``_. .. |Kuepper| unicode:: K U+00FC pper .. Kuepper .. todo:: remove the above substitution. It is no more needed with sphinx 1.0.8 PyTables-3.7.0/doc/source/usersguide/condition_syntax.rst000066400000000000000000000132161416254111300236170ustar00rootroot00000000000000.. _condition_syntax: Condition Syntax ================ .. currentmodule:: tables Conditions in PyTables are used in methods related with in-kernel and indexed searches such as :meth:`Table.where` or :meth:`Table.read_where`. They are interpreted using Numexpr, a powerful package for achieving C-speed computation of array operations (see :ref:`[NUMEXPR] `). A condition on a table is just a *string* containing a Python expression involving *at least one column*, and maybe some constants and external variables, all combined with algebraic operators and functions. The result of a valid condition is always a *boolean array* of the same length as the table, where the *i*-th element is true if the value of the expression on the *i*-th row of the table evaluates to true That is the reason why multidimensional fields in a table are not supported in conditions, since the truth value of each resulting multidimensional boolean value is not obvious. Usually, a method using a condition will only consider the rows where the boolean result is true. For instance, the condition 'sqrt(x*x + y*y) < 1' applied on a table with x and y columns consisting of floating point numbers results in a boolean array where the *i*-th element is true if (unsurprisingly) the value of the square root of the sum of squares of x and y is less than 1. The sqrt() function works element-wise, the 1 constant is adequately broadcast to an array of ones of the length of the table for evaluation, and the *less than* operator makes the result a valid boolean array. A condition like 'mycolumn' alone will not usually be valid, unless mycolumn is itself a column of scalar, boolean values. In the previous conditions, mycolumn, x and y are examples of *variables* which are associated with columns. Methods supporting conditions do usually provide their own ways of binding variable names to columns and other values. You can read the documentation of :meth:`Table.where` for more information on that. Also, please note that the names None, True and False, besides the names of functions (see below) *can not be overridden*, but you can always define other new names for the objects you intend to use. Values in a condition may have the following types: - 8-bit boolean (bool). - 32-bit signed integer (int). - 64-bit signed integer (long). - 32-bit, single-precision floating point number (float or float32). - 64-bit, double-precision floating point number (double or float64). - 2x64-bit, double-precision complex number (complex). - Raw string of bytes (str). Nevertheless, if the type passed is not among the above ones, it will be silently upcasted, so you don't need to worry too much about passing supported types, except for the Unsigned 64 bits integer, that cannot be upcasted to any of the supported types. However, the types in PyTables conditions are somewhat stricter than those of Python. For instance, the *only* valid constants for booleans are True and False, and they are *never* automatically cast to integers. The type strengthening also affects the availability of operators and functions. Beyond that, the usual type inference rules apply. Conditions support the set of operators listed below: - Logical operators: &, \|, ~. - Comparison operators: <, <=, ==, !=, >=, >. - Unary arithmetic operators: -. - Binary arithmetic operators: +, -, \*, /, \**, %. Types do not support all operators. Boolean values only support logical and strict (in)equality comparison operators, while strings only support comparisons, numbers do not work with logical operators, and complex comparisons can only check for strict (in)equality. Unsupported operations (including invalid castings) raise NotImplementedError exceptions. You may have noticed the special meaning of the usually bitwise operators &, | and ~. Because of the way Python handles the short-circuiting of logical operators and the truth values of their operands, conditions must use the bitwise operator equivalents instead. This is not difficult to remember, but you must be careful because bitwise operators have a *higher precedence* than logical operators. For instance, 'a and b == c' (*a is true AND b is equal to c*) is *not* equivalent to 'a & b == c' (*a AND b is equal to c)*. The safest way to avoid confusions is to *use parentheses* around logical operators, like this: 'a & (b == c)'. Another effect of short-circuiting is that expressions like '0 < x < 1' will *not* work as expected; you should use '(0 < x) & (x < 1)'. All of this may be solved if Python supported overloadable boolean operators (see PEP 335) or some kind of non-shortcircuiting boolean operators (like C's &&, || and !). You can also use the following functions in conditions: - where(bool, number1, number2): number - number1 if the bool condition is true, number2 otherwise. - {sin,cos,tan}(float|complex): float|complex - trigonometric sine, cosine or tangent. - {arcsin,arccos,arctan}(float|complex): float|complex - trigonometric inverse sine, cosine or tangent. - arctan2(float1, float2): float - trigonometric inverse tangent of float1/float2. - {sinh,cosh,tanh}(float|complex): float|complex - hyperbolic sine, cosine or tangent. - {arcsinh,arccosh,arctanh}(float|complex): float|complex - hyperbolic inverse sine, cosine or tangent. - {log,log10,log1p}(float|complex): float|complex - natural, base-10 and log(1+x) logarithms. - {exp,expm1}(float|complex): float|complex - exponential and exponential minus one. - sqrt(float|complex): float|complex - square root. - abs(float|complex): float|complex - absolute value. - {real,imag}(complex): float - real or imaginary part of complex. - complex(float, float): complex - complex from real and imaginary parts. PyTables-3.7.0/doc/source/usersguide/datatypes.rst000066400000000000000000000140561416254111300222240ustar00rootroot00000000000000.. _datatypes: Supported data types in PyTables ================================ All PyTables datasets can handle the complete set of data types supported by the NumPy (see :ref:`[NUMPY] `) package in Python. The data types for table fields can be set via instances of the Col class and its descendants (see :ref:`ColClassDescr`), while the data type of array elements can be set through the use of the Atom class and its descendants (see :ref:`AtomClassDescr`). PyTables uses ordinary strings to represent its *types*, with most of them matching the names of NumPy scalar types. Usually, a PyTables type consists of two parts: a *kind* and a *precision* in bits. The precision may be omitted in types with just one supported precision (like bool) or with a non-fixed size (like string). There are eight kinds of types supported by PyTables: - bool: Boolean (true/false) types. Supported precisions: 8 (default) bits. - int: Signed integer types. Supported precisions: 8, 16, 32 (default) and 64 bits. - uint: Unsigned integer types. Supported precisions: 8, 16, 32 (default) and 64 bits. - float: Floating point types. Supported precisions: 16, 32, 64 (default) bits and extended precision floating point (see :ref:`note on floating point types`). - complex: Complex number types. Supported precisions: 64 (32+32), 128 (64+64, default) bits and extended precision complex (see :ref:`note on floating point types`). - string: Raw string types. Supported precisions: 8-bit positive multiples. - time: Data/time types. Supported precisions: 32 and 64 (default) bits. - enum: Enumerated types. Precision depends on base type. .. _floating-point-note: .. note:: Floating point types. The half precision floating point data type (float16) and extended precision ones (fload96, float128, complex192, complex256) are only available if numpy_ supports them on the host platform. Also, in order to use the half precision floating point type (float16) it is required numpy_ >= 1.6.0. .. _numpy: http://www.numpy.org The time and enum kinds area little bit special, since they represent HDF5 types which have no direct Python counterpart, though atoms of these kinds have a more-or-less equivalent NumPy data type. There are two types of time: 4-byte signed integer (time32) and 8-byte double precision floating point (time64). Both of them reflect the number of seconds since the Unix epoch, i.e. Jan 1 00:00:00 UTC 1970. They are stored in memory as NumPy's int32 and float64, respectively, and in the HDF5 file using the H5T_TIME class. Integer times are stored on disk as such, while floating point times are split into two signed integer values representing seconds and microseconds (beware: smaller decimals will be lost!). PyTables also supports HDF5 H5T_ENUM *enumerations* (restricted sets of unique name and unique value pairs). The NumPy representation of an enumerated value (an Enum, see :ref:`EnumClassDescr`) depends on the concrete *base type* used to store the enumeration in the HDF5 file. Currently, only scalar integer values (both signed and unsigned) are supported in enumerations. This restriction may be lifted when HDF5 supports other kinds on enumerated values. Here you have a quick reference to the complete set of supported data types: .. table:: **Data types supported for array elements and tables columns in PyTables.** ================== ========================== ====================== =============== ================== Type Code Description C Type Size (in bytes) Python Counterpart ================== ========================== ====================== =============== ================== bool boolean unsigned char 1 bool int8 8-bit integer signed char 1 int uint8 8-bit unsigned integer unsigned char 1 int int16 16-bit integer short 2 int uint16 16-bit unsigned integer unsigned short 2 int int32 integer int 4 int uint32 unsigned integer unsigned int 4 long int64 64-bit integer long long 8 long uint64 unsigned 64-bit integer unsigned long long 8 long float16 [1]_ half-precision float - 2 - float32 single-precision float float 4 float float64 double-precision float double 8 float float96 [1]_ [2]_ extended precision float - 12 - float128 [1]_ [2]_ extended precision float - 16 - complex64 single-precision complex struct {float r, i;} 8 complex complex128 double-precision complex struct {double r, i;} 16 complex complex192 [1]_ extended precision complex - 24 - complex256 [1]_ extended precision complex - 32 - string arbitrary length string char[] * str time32 integer time POSIX's time_t 4 int time64 floating point time POSIX's struct timeval 8 float enum enumerated value enum - - ================== ========================== ====================== =============== ================== .. rubric:: Footnotes .. [1] see the above :ref:`note on floating point types `. .. [2] currently in numpy_. "float96" and "float128" are equivalent of "longdouble" i.e. 80 bit extended precision floating point. PyTables-3.7.0/doc/source/usersguide/file_format.rst000066400000000000000000000347231416254111300225200ustar00rootroot00000000000000PyTables File Format ==================== PyTables has a powerful capability to deal with native HDF5 files created with another tools. However, there are situations were you may want to create truly native PyTables files with those tools while retaining fully compatibility with PyTables format. That is perfectly possible, and in this appendix is presented the format that you should endow to your own-generated files in order to get a fully PyTables compatible file. We are going to describe the *2.0 version of PyTables file format* (introduced in PyTables version 2.0). As time goes by, some changes might be introduced (and documented here) in order to cope with new necessities. However, the changes will be carefully pondered so as to ensure backward compatibility whenever is possible. A PyTables file is composed with arbitrarily large amounts of HDF5 groups (Groups in PyTables naming scheme) and datasets (Leaves in PyTables naming scheme). For groups, the only requirements are that they must have some *system attributes* available. By convention, system attributes in PyTables are written in upper case, and user attributes in lower case but this is not enforced by the software. In the case of datasets, besides the mandatory system attributes, some conditions are further needed in their storage layout, as well as in the datatypes used in there, as we will see shortly. As a final remark, you can use any filter as you want to create a PyTables file, provided that the filter is a standard one in HDF5, like *zlib*, *shuffle* or *szip* (although the last one can not be used from within PyTables to create a new file, datasets compressed with szip can be read, because it is the HDF5 library which do the decompression transparently). .. currentmodule:: tables Mandatory attributes for a File ------------------------------- The File object is, in fact, an special HDF5 *group* structure that is *root* for the rest of the objects on the object tree. The next attributes are mandatory for the HDF5 *root group* structure in PyTables files: * *CLASS*: This attribute should always be set to 'GROUP' for group structures. * *PYTABLES_FORMAT_VERSION*: It represents the internal format version, and currently should be set to the '2.0' string. * *TITLE*: A string where the user can put some description on what is this group used for. * *VERSION*: Should contains the string '1.0'. Mandatory attributes for a Group -------------------------------- The next attributes are mandatory for *group* structures: * *CLASS*: This attribute should always be set to 'GROUP' for group structures. * *TITLE*: A string where the user can put some description on what is this group used for. * *VERSION*: Should contains the string '1.0'. Optional attributes for a Group ------------------------------- The next attributes are optional for *group* structures: * *FILTERS*: When present, this attribute contains the filter properties (a Filters instance, see section :ref:`FiltersClassDescr`) that may be inherited by leaves or groups created immediately under this group. This is a packed 64-bit integer structure, where - *byte 0* (the least-significant byte) is the compression level (complevel). - *byte 1* is the compression library used (complib): 0 when irrelevant, 1 for Zlib, 2 for LZO and 3 for Bzip2. - *byte 2* indicates which parameterless filters are enabled (shuffle and fletcher32): bit 0 is for *Shuffle* while bit 1 is for*Fletcher32*. - other bytes are reserved for future use. Mandatory attributes, storage layout and supported data types for Leaves ------------------------------------------------------------------------ This depends on the kind of Leaf. The format for each type follows. .. _TableFormatDescr: Table format ~~~~~~~~~~~~ Mandatory attributes ^^^^^^^^^^^^^^^^^^^^ The next attributes are mandatory for *table* structures: * *CLASS*: Must be set to 'TABLE'. * *TITLE*: A string where the user can put some description on what is this dataset used for. * *VERSION*: Should contain the string '2.6'. * *FIELD_X_NAME*: It contains the names of the different fields. The X means the number of the field, zero-based (beware, order do matter). You should add as many attributes of this kind as fields you have in your records. * *FIELD_X_FILL*: It contains the default values of the different fields. All the datatypes are supported natively, except for complex types that are currently serialized using Pickle. The X means the number of the field, zero-based (beware, order do matter). You should add as many attributes of this kind as fields you have in your records. These fields are meant for saving the default values persistently and their existence is optional. * *NROWS*: This should contain the number of *compound* data type entries in the dataset. It must be an *int* data type. Storage Layout ^^^^^^^^^^^^^^ A Table has a *dataspace* with a *1-dimensional chunked* layout. Datatypes supported ^^^^^^^^^^^^^^^^^^^ The datatype of the elements (rows) of Table must be the H5T_COMPOUND *compound* data type, and each of these compound components must be built with only the next HDF5 data types *classes*: * *H5T_BITFIELD*: This class is used to represent the Bool type. Such a type must be build using a H5T_NATIVE_B8 datatype, followed by a HDF5 H5Tset_precision call to set its precision to be just 1 bit. * *H5T_INTEGER*: This includes the next data types: * *H5T_NATIVE_SCHAR*: This represents a *signed char* C type, but it is effectively used to represent an Int8 type. * *H5T_NATIVE_UCHAR*: This represents an *unsigned char* C type, but it is effectively used to represent an UInt8 type. * *H5T_NATIVE_SHORT*: This represents a *short* C type, and it is effectively used to represent an Int16 type. * *H5T_NATIVE_USHORT*: This represents an *unsigned short* C type, and it is effectively used to represent an UInt16 type. * *H5T_NATIVE_INT*: This represents an *int* C type, and it is effectively used to represent an Int32 type. * *H5T_NATIVE_UINT*: This represents an *unsigned int* C type, and it is effectively used to represent an UInt32 type. * *H5T_NATIVE_LONG*: This represents a *long* C type, and it is effectively used to represent an Int32 or an Int64, depending on whether you are running a 32-bit or 64-bit architecture. * *H5T_NATIVE_ULONG*: This represents an *unsigned long* C type, and it is effectively used to represent an UInt32 or an UInt64, depending on whether you are running a 32-bit or 64-bit architecture. * *H5T_NATIVE_LLONG*: This represents a *long long* C type (__int64, if you are using a Windows system) and it is effectively used to represent an Int64 type. * *H5T_NATIVE_ULLONG*: This represents an *unsigned long long* C type (beware: this type does not have a correspondence on Windows systems) and it is effectively used to represent an UInt64 type. * *H5T_FLOAT*: This includes the next datatypes: * *H5T_NATIVE_FLOAT*: This represents a *float* C type and it is effectively used to represent an Float32 type. * *H5T_NATIVE_DOUBLE*: This represents a *double* C type and it is effectively used to represent an Float64 type. * *H5T_TIME*: This includes the next datatypes: * *H5T_UNIX_D32*: This represents a POSIX *time_t* C type and it is effectively used to represent a 'Time32' aliasing type, which corresponds to an Int32 type. * *H5T_UNIX_D64*: This represents a POSIX *struct timeval* C type and it is effectively used to represent a 'Time64' aliasing type, which corresponds to a Float64 type. * *H5T_STRING*: The datatype used to describe strings in PyTables is H5T_C_S1 (i.e. a *string* C type) followed with a call to the HDF5 H5Tset_size() function to set their length. * *H5T_ARRAY*: This allows the construction of homogeneous, multidimensional arrays, so that you can include such objects in compound records. The types supported as elements of H5T_ARRAY data types are the ones described above. Currently, PyTables does not support nested H5T_ARRAY types. * *H5T_COMPOUND*: This allows the support for datatypes that are compounds of compounds (this is also known as *nested types* along this manual). This support can also be used for defining complex numbers. Its format is described below: The H5T_COMPOUND type class contains two members. Both members must have the H5T_FLOAT atomic datatype class. The name of the first member should be "r" and represents the real part. The name of the second member should be "i" and represents the imaginary part. The *precision* property of both of the H5T_FLOAT members must be either 32 significant bits (e.g. H5T_NATIVE_FLOAT) or 64 significant bits (e.g. H5T_NATIVE_DOUBLE). They represent Complex32 and Complex64 types respectively. Array format ~~~~~~~~~~~~ Mandatory attributes ^^^^^^^^^^^^^^^^^^^^ The next attributes are mandatory for *array* structures: * *CLASS*: Must be set to 'ARRAY'. * *TITLE*: A string where the user can put some description on what is this dataset used for. * *VERSION*: Should contain the string '2.3'. Storage Layout ^^^^^^^^^^^^^^ An Array has a *dataspace* with a *N-dimensional contiguous* layout (if you prefer a *chunked* layout see EArray below). Datatypes supported ^^^^^^^^^^^^^^^^^^^ The elements of Array must have either HDF5 *atomic* data types or a *compound* data type representing a complex number. The atomic data types can currently be one of the next HDF5 data type *classes*: H5T_BITFIELD, H5T_INTEGER, H5T_FLOAT and H5T_STRING. The H5T_TIME class is also supported for reading existing Array objects, but not for creating them. See the Table format description in :ref:`TableFormatDescr` for more info about these types. In addition to the HDF5 atomic data types, the Array format supports complex numbers with the H5T_COMPOUND data type class. See the Table format description in :ref:`TableFormatDescr` for more info about this special type. You should note that H5T_ARRAY class datatypes are not allowed in Array objects. CArray format ~~~~~~~~~~~~~ Mandatory attributes ^^^^^^^^^^^^^^^^^^^^ The next attributes are mandatory for *CArray* structures: * *CLASS*: Must be set to 'CARRAY'. * *TITLE*: A string where the user can put some description on what is this dataset used for. * *VERSION*: Should contain the string '1.0'. Storage Layout ^^^^^^^^^^^^^^ An CArray has a *dataspace* with a *N-dimensional chunked* layout. Datatypes supported ^^^^^^^^^^^^^^^^^^^ The elements of CArray must have either HDF5 *atomic* data types or a *compound* data type representing a complex number. The atomic data types can currently be one of the next HDF5 data type *classes*: H5T_BITFIELD, H5T_INTEGER, H5T_FLOAT and H5T_STRING. The H5T_TIME class is also supported for reading existing CArray objects, but not for creating them. See the Table format description in :ref:`TableFormatDescr` for more info about these types. In addition to the HDF5 atomic data types, the CArray format supports complex numbers with the H5T_COMPOUND data type class. See the Table format description in :ref:`TableFormatDescr` for more info about this special type. You should note that H5T_ARRAY class datatypes are not allowed yet in Array objects. EArray format ~~~~~~~~~~~~~ Mandatory attributes ^^^^^^^^^^^^^^^^^^^^ The next attributes are mandatory for *earray* structures: * *CLASS*: Must be set to 'EARRAY'. * *EXTDIM*: (*Integer*) Must be set to the extendable dimension. Only one extendable dimension is supported right now. * *TITLE*: A string where the user can put some description on what is this dataset used for. * *VERSION*: Should contain the string '1.3'. Storage Layout ^^^^^^^^^^^^^^ An EArray has a *dataspace* with a *N-dimensional chunked* layout. Datatypes supported ^^^^^^^^^^^^^^^^^^^ The elements of EArray are allowed to have the same data types as for the elements in the Array format. They can be one of the HDF5 *atomic* data type *classes*: H5T_BITFIELD, H5T_INTEGER, H5T_FLOAT, H5T_TIME or H5T_STRING, see the Table format description in :ref:`TableFormatDescr` for more info about these types. They can also be a H5T_COMPOUND datatype representing a complex number, see the Table format description in :ref:`TableFormatDescr`. You should note that H5T_ARRAY class data types are not allowed in EArray objects. .. _VLArrayFormatDescr: VLArray format ~~~~~~~~~~~~~~ Mandatory attributes ^^^^^^^^^^^^^^^^^^^^ The next attributes are mandatory for *vlarray* structures: * *CLASS*: Must be set to 'VLARRAY'. * *PSEUDOATOM*: This is used so as to specify the kind of pseudo-atom (see :ref:`VLArrayFormatDescr`) for the VLArray. It can take the values 'vlstring', 'vlunicode' or 'object'. If your atom is not a pseudo-atom then you should not specify it. * *TITLE*: A string where the user can put some description on what is this dataset used for. * *VERSION*: Should contain the string '1.3'. Storage Layout ^^^^^^^^^^^^^^ An VLArray has a *dataspace* with a *1-dimensional chunked* layout. Data types supported ^^^^^^^^^^^^^^^^^^^^ The data type of the elements (rows) of VLArray objects must be the H5T_VLEN *variable-length* (or VL for short) datatype, and the base datatype specified for the VL datatype can be of any *atomic* HDF5 datatype that is listed in the Table format description :ref:`TableFormatDescr`. That includes the classes: - H5T_BITFIELD - H5T_INTEGER - H5T_FLOAT - H5T_TIME - H5T_STRING - H5T_ARRAY They can also be a H5T_COMPOUND data type representing a complex number, see the Table format description in :ref:`TableFormatDescr` for a detailed description. You should note that this does not include another VL datatype, or a compound datatype that does not fit the description of a complex number. Note as well that, for object and vlstring pseudo-atoms, the base for the VL datatype is always a H5T_NATIVE_UCHAR (H5T_NATIVE_UINT for vlunicode). That means that the complete row entry in the dataset has to be used in order to fully serialize the object or the variable length string. Optional attributes for Leaves ------------------------------ The next attributes are optional for *leaves*: * *FLAVOR*: This is meant to provide the information about the kind of object kept in the Leaf, i.e. when the dataset is read, it will be converted to the indicated flavor. It can take one the next string values: * *"numpy"*: Read data (structures arrays, arrays, records, scalars) will be returned as NumPy objects. * *"python"*: Read data will be returned as Python lists, tuples, or scalars. PyTables-3.7.0/doc/source/usersguide/filenode.rst000066400000000000000000000301231416254111300220040ustar00rootroot00000000000000.. _filenode_usersguide: filenode - simulating a filesystem with PyTables ================================================ .. currentmodule:: tables.nodes.filenode What is filenode? ----------------- filenode is a module which enables you to create a PyTables database of nodes which can be used like regular opened files in Python. In other words, you can store a file in a PyTables database, and read and write it as you would do with any other file in Python. Used in conjunction with PyTables hierarchical database organization, you can have your database turned into an open, extensible, efficient, high capacity, portable and metadata-rich filesystem for data exchange with other systems (including backup purposes). Between the main features of filenode, one can list: - *Open:* Since it relies on PyTables, which in turn, sits over HDF5 (see :ref:`[HDGG1] `), a standard hierarchical data format from NCSA. - *Extensible:* You can define new types of nodes, and their instances will be safely preserved (as are normal groups, leafs and attributes) by PyTables applications having no knowledge of their types. Moreover, the set of possible attributes for a node is not fixed, so you can define your own node attributes. - *Efficient:* Thanks to PyTables' proven extreme efficiency on handling huge amounts of data. filenode can make use of PyTables' on-the-fly compression and decompression of data. - *High capacity:* Since PyTables and HDF5 are designed for massive data storage (they use 64-bit addressing even where the platform does not support it natively). - *Portable:* Since the HDF5 format has an architecture-neutral design, and the HDF5 libraries and PyTables are known to run under a variety of platforms. Besides that, a PyTables database fits into a single file, which poses no trouble for transportation. - *Metadata-rich:* Since PyTables can store arbitrary key-value pairs (even Python objects!) for every database node. Metadata may include authorship, keywords, MIME types and encodings, ownership information, access control lists (ACL), decoding functions and anything you can imagine! Finding a filenode node ----------------------- filenode nodes can be recognized because they have a NODE_TYPE system attribute with a 'file' value. It is recommended that you use the :meth:`File.get_node_attr` method of tables.File class to get the NODE_TYPE attribute independently of the nature (group or leaf) of the node, so you do not need to care about. filenode - simulating files inside PyTables ------------------------------------------- The filenode module is part of the nodes sub-package of PyTables. The recommended way to import the module is:: >>> from tables.nodes import filenode However, filenode exports very few symbols, so you can import * for interactive usage. In fact, you will most probably only use the NodeType constant and the new_node() and open_node() calls. The NodeType constant contains the value that the NODE_TYPE system attribute of a node file is expected to contain ('file', as we have seen). Although this is not expected to change, you should use filenode.NodeType instead of the literal 'file' when possible. new_node() and open_node() are the equivalent to the Python file() call (alias open()) for ordinary files. Their arguments differ from that of file(), but this is the only point where you will note the difference between working with a node file and working with an ordinary file. For this little tutorial, we will assume that we have a PyTables database opened for writing. Also, if you are somewhat lazy at typing sentences, the code that we are going to explain is included in the examples/filenodes1.py file. You can create a brand new file with these sentences:: >>> import tables >>> h5file = tables.open_file('fnode.h5', 'w') Creating a new file node ~~~~~~~~~~~~~~~~~~~~~~~~ Creation of a new file node is achieved with the new_node() call. You must tell it in which PyTables file you want to create it, where in the PyTables hierarchy you want to create the node and which will be its name. The PyTables file is the first argument to new_node(); it will be also called the 'host PyTables file'. The other two arguments must be given as keyword arguments where and name, respectively. As a result of the call, a brand new appendable and readable file node object is returned. So let us create a new node file in the previously opened h5file PyTables file, named 'fnode_test' and placed right under the root of the database hierarchy. This is that command:: >>> fnode = filenode.new_node(h5file, where='/', name='fnode_test') That is basically all you need to create a file node. Simple, isn't it? From that point on, you can use fnode as any opened Python file (i.e. you can write data, read data, lines of text and so on). new_node() accepts some more keyword arguments. You can give a title to your file with the title argument. You can use PyTables' compression features with the filters argument. If you know beforehand the size that your file will have, you can give its final file size in bytes to the expectedsize argument so that the PyTables library would be able to optimize the data access. new_node() creates a PyTables node where it is told to. To prove it, we will try to get the NODE_TYPE attribute from the newly created node:: >>> print(h5file.get_node_attr('/fnode_test', 'NODE_TYPE')) file Using a file node ~~~~~~~~~~~~~~~~~ As stated above, you can use the new node file as any other opened file. Let us try to write some text in and read it:: >>> print("This is a test text line.", file=fnode) >>> print("And this is another one.", file=fnode) >>> print(file=fnode) >>> fnode.write("Of course, file methods can also be used.") >>> >>> fnode.seek(0) # Go back to the beginning of file. >>> >>> for line in fnode: ... print(repr(line)) 'This is a test text line.\\n' 'And this is another one.\\n' '\\n' 'Of course, file methods can also be used.' This was run on a Unix system, so newlines are expressed as '\n'. In fact, you can override the line separator for a file by setting its line_separator property to any string you want. While using a file node, you should take care of closing it *before* you close the PyTables host file. Because of the way PyTables works, your data it will not be at a risk, but every operation you execute after closing the host file will fail with a ValueError. To close a file node, simply delete it or call its close() method:: >>> fnode.close() >>> print(fnode.closed) True Opening an existing file node ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you have a file node that you created using new_node(), you can open it later by calling open_node(). Its arguments are similar to that of file() or open(): the first argument is the PyTables node that you want to open (i.e. a node with a NODE_TYPE attribute having a 'file' value), and the second argument is a mode string indicating how to open the file. Contrary to file(), open_node() can not be used to create a new file node. File nodes can be opened in read-only mode ('r') or in read-and-append mode ('a+'). Reading from a file node is allowed in both modes, but appending is only allowed in the second one. Just like Python files do, writing data to an appendable file places it after the file pointer if it is on or beyond the end of the file, or otherwise after the existing data. Let us see an example:: >>> node = h5file.root.fnode_test >>> fnode = filenode.open_node(node, 'a+') >>> print(repr(fnode.readline())) 'This is a test text line.\\n' >>> print(fnode.tell()) 26 >>> print("This is a new line.", file=fnode) >>> print(repr(fnode.readline())) '' Of course, the data append process places the pointer at the end of the file, so the last readline() call hit EOF. Let us seek to the beginning of the file to see the whole contents of our file:: >>> fnode.seek(0) >>> for line in fnode: ... print(repr(line)) 'This is a test text line.\\n' 'And this is another one.\\n' '\\n' 'Of course, file methods can also be used.This is a new line.\\n' As you can check, the last string we wrote was correctly appended at the end of the file, instead of overwriting the second line, where the file pointer was positioned by the time of the appending. Adding metadata to a file node ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can associate arbitrary metadata to any open node file, regardless of its mode, as long as the host PyTables file is writable. Of course, you could use the set_node_attr() method of tables.File to do it directly on the proper node, but filenode offers a much more comfortable way to do it. filenode objects have an attrs property which gives you direct access to their corresponding AttributeSet object. For instance, let us see how to associate MIME type metadata to our file node:: >>> fnode.attrs.content_type = 'text/plain; charset=us-ascii' As simple as A-B-C. You can put nearly anything in an attribute, which opens the way to authorship, keywords, permissions and more. Moreover, there is not a fixed list of attributes. However, you should avoid names in all caps or starting with '_', since PyTables and filenode may use them internally. Some valid examples:: >>> fnode.attrs.author = "Ivan Vilata i Balaguer" >>> fnode.attrs.creation_date = '2004-10-20T13:25:25+0200' >>> fnode.attrs.keywords_en = ["FileNode", "test", "metadata"] >>> fnode.attrs.keywords_ca = ["FileNode", "prova", "metadades"] >>> fnode.attrs.owner = 'ivan' >>> fnode.attrs.acl = {'ivan': 'rw', '@users': 'r'} You can check that these attributes get stored by running the ptdump command on the host PyTables file. .. code-block:: bash $ ptdump -a fnode.h5:/fnode_test /fnode_test (EArray(113,)) '' /fnode_test.attrs (AttributeSet), 14 attributes: [CLASS := 'EARRAY', EXTDIM := 0, FLAVOR := 'numpy', NODE_TYPE := 'file', NODE_TYPE_VERSION := 2, TITLE := '', VERSION := '1.2', acl := {'ivan': 'rw', '@users': 'r'}, author := 'Ivan Vilata i Balaguer', content_type := 'text/plain; charset=us-ascii', creation_date := '2004-10-20T13:25:25+0200', keywords_ca := ['FileNode', 'prova', 'metadades'], keywords_en := ['FileNode', 'test', 'metadata'], owner := 'ivan'] Note that filenode makes no assumptions about the meaning of your metadata, so its handling is entirely left to your needs and imagination. Complementary notes ------------------- You can use file nodes and PyTables groups to mimic a filesystem with files and directories. Since you can store nearly anything you want as file metadata, this enables you to use a PyTables file as a portable compressed backup, even between radically different platforms. Take this with a grain of salt, since node files are restricted in their naming (only valid Python identifiers are valid); however, remember that you can use node titles and metadata to overcome this limitation. Also, you may need to devise some strategy to represent special files such as devices, sockets and such (not necessarily using filenode). We are eager to hear your opinion about filenode and its potential uses. Suggestions to improve filenode and create other node types are also welcome. Do not hesitate to contact us! Current limitations ------------------- filenode is still a young piece of software, so it lacks some functionality. This is a list of known current limitations: #. Node files can only be opened for read-only or read and append mode. This should be enhanced in the future. #. Near future? #. Only binary I/O is supported currently (read/write strings of bytes) #. There is no universal newline support yet. The only new-line character used at the moment is ``\n``. This is likely to be improved in a near future. #. Sparse files (files with lots of zeros) are not treated specially; if you want them to take less space, you should be better off using compression. These limitations still make filenode entirely adequate to work with most binary and text files. Of course, suggestions and patches are welcome. See :ref:`filenode_classes` for detailed documentation on the filenode interface. PyTables-3.7.0/doc/source/usersguide/images/000077500000000000000000000000001416254111300207335ustar00rootroot00000000000000PyTables-3.7.0/doc/source/usersguide/images/Q7-10m-noidx.png000066400000000000000000002377211416254111300234560ustar00rootroot00000000000000‰PNG  IHDRÐß}™SsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwXÇÿð÷Q¤‰ˆÒDª "¡Y±*ÊÙ±—¨`‰ýgKK5FQ£F£1š –ˆÆ{Á‚ X)@”.>¿?xv¿,wpwT1ózžfggf÷v—ÏíÎÌŠˆˆÀ0 Ã0 Ã0Œ\”j» Ã0 Ã0 S—°ša†a†aÀh†a†a†Q  †a†aF,€f†a†a°ša†a†aÀh†a†a†Q  †a†aF,€f†a†a°ša†a†aÀh†a†a†Q  †a†aF,€f†a†a°ša†a†aÀh†a†a†Q  †a†aF,€f†a†a°ša†a†aÀh†a†a†Q  †a†aF,€f†a†a°ša†a†aÀh†a†a†Q  †a†aF,€f†a†a°ša†a†aÀh†a†a†Q  †a†aF,€f†a†a°ú3“ššŠ°°0¤¦¦ÖvSjܵk×0wî\¼y󦶛R¦œœœ>} .ÄòåËk»9uʶmÛ°jÕªÚnS æÎ‹#GŽTkoÞ¼Add$ˆ¨Zë©k°dÉäææÖvS¤:wîæÎ‹ŒŒŒ*/ûàÁƒ˜;wn•—[¢¢¢0wî\Ü»wO ƪU«Ø1]KXý™øé§Ÿ`aa]]]´k׺ººhÖ¬vîÜYÛM«R.\À¢E‹’’"±,<<[¶lARRR-´L¶ÐÐPèééaÈ! BXXXm7©N9zô(~ûí·ÚnS ¶lÙ‚‹/ÊÌ———‡Õ«WcðàÁ077‡H$‚M¹ëܼyÍš5ƒ‰‰ lmmѨQ#lݺUî¶5 "‘"‘<šÇÏÏϳoß>¹Ëþ|óÍ7‡ššZµ×E‹!""Bîunܸ-[¶àãÇUÞžÿý[¶l©òr«CLL ¶lÙ‚'OžðiÆÆÆX±bj±eÿ],€®ãòòòзo_Ìž=&&&عs'BCCñË/¿ÀÐÐÓ¦Mƒ§§' j»©UâÊ•+X·nÒÒÒ$–µk×¾¾¾022ª…–ɶmÛ6dgg#..wîÜÁéÓ§k»I S§deeaÙ²e†••ÔÕÕËÍwww(++ÃßßèÚµ+æÌ™ƒŸþY¡ºÕÔÔÊ Ž÷íÛW#hU»rå ‚ƒƒkì.ìÓ§O±nÝ:<}úTîuÜÜÜàëë‹úõëWcËê¦-Z oß¾ìÉ\-at·}ûvœ={^^^Æ”)SЭ[7L:¡¡¡?~AYÎÎÎdjjJíÛ·§îÝ»ÓÒ¥KiÊ”)åÖÿúõk²°° %%%=z4­_¿ž¾ýö[êÖ­ùùùñùBBBHKK‹ ÉÇLJV®\I­Zµ"%%%Ú¹s§ LWWW244¤Î;“™™}ýõ×Ô¢E @+V¬ Í›7“ššyyyÑ AƒHII‰ (33“/ãúõë€Äb1éëë“ùùù‘‰D"úùçŸ%ê477¤=|øŒŒŒHYY™zõêEË–-ãÛ1uêT>_JJ YZZRÓ¦MéÝ»w|º··7‰D":sæL¹ûˆèþýû¤££Czzz´xñbZ³f µjÕŠú÷ïOÈ××—Ï{èÐ!@gÏž•(ÇÃà iwïÞ%}}}RVV&±XLË–-#+++©çwÉ}ïââBK–,¡‰'RVVéêêR=$êŒ'?~¼ÌílÛ¶-yxxÐ’%KhÍš5äææF***diiIÙÙÙ‚¼jjjÔ±cG255%Z°`5oޜФI“y_¿~MÆÆÆ¤©©IóæÍ£ 6³³3 8Pâó’—¡¡!µlÙRê²û÷ïêÝ»·Ä²M›6Áñ_–‘#GŠ'GGG\n;¹ÿñññ|Úœ9s 6Œ´µµiêÔ©Ô¿RRR¢ Ðû÷ïexyyêÓ§mÞ¼™&MšDÔ±cG*ÅÆÆ’¥¥%‰D"rqq!___²³³ã·óñãGúâ‹/¨qãÆǧÏ;—®Ý7nÜ F‘ŠŠ õïߟ–.]Jæææ€–/_.¨ÿĉ¤ªªJVVV´jÕ*Z¾|9™ššò×™¿þúKb 4ˆ444¨°°°Ü}ÉT-@×a[·n%´}ûörómܸ‘ÐŽ;ø4EècÇŽš?>ñé< äííͧqt½zõ(66VPöëׯIEE…¾úê+Azjj*ijjÒСCen3w1ŽŽ–XV^mggG©©©|ú’%K©ªªÒ… øô§OŸòfNnn.Y[[“žžžàÂ\PP@½{÷&---zóæÌ¶;::’£££ -??ŸlllHSS“^½zŧ§¤¤™™5nÜXÐn¬ ¹¤#FH$¢óçÏK,ËÉÉ!"¢ÂÂBrpp zõê ömzz:Y[[“ŽŽŽ`Û]]]ù/2DDTTTDvvv¤¬¬L¦¦¦‚@uýúõ€öìÙçq4 çÓ333©E‹Ô°aCJNNÔY:€vqq!UUUzöìŸVTTD“&M"‘HDaaa|ú­[·HUU•<<<¨¨¨ˆþøã@>>>ríÇÞ½{“’’=yòD°,,,*@‘³³3©««SLLŒ }ìØ±¤¤¤D>ìäåå%QöüùóI$ÑÓ§Oéßÿ= «W¯ÊÜÎ’Çgÿþý€~üñGAºšš M›6 Úݾ}{RQQ|~S¦L!ȧåååñALUЧOŸ&4oÞ<‰eçÏŸ'ôÿ÷2ë(@oÚ´‰TUU)))‰ˆˆ8@(""¢ÜZ$QDD„ ÜþùGêçȶ_ý5Ÿ¶`Á‰Dü †ÔÔTRVV¦.]ºªª*ÿÅôõë×€–.]*s»¾þúk q&úßufÒ¤I”——ǧOž<™ÐåË—ù´Ÿ~úIêy´|ùr@?ýôŸÆý8~ü¸ÌöqÊ  ;wîLééé|úöíÛ mذO»ví /¿üRP.×îÒ´§§' 7nÒ¹ýuîÜ9>íñãǤ©©I;w¦üü|:yò¤ÄgšŸŸOvvv¤¥¥E¯_¿æÓ ièС¤ªªÊŸ¯ùùùdeeEúúúôáÃ>o||·<66¶BõlÛ¶ ªªªøæ›oNкuk‰Gšàââ333AZÓ¦M1`À:tHðˆòÀÈÊÊÂÔ©S+Ô>yŒ1BÐ_²GSSSôìÙ“OoÞ¼9LLLÝ Î;‡çÏŸcÚ´ihܸ1Ÿ®¬¬ ooo|üøQj—y„……!** ®®®011áÓ6lˆ~ýúáÇR÷¯´.Ò¤¦¦âðáÃðôô”úˆ™øôðáC„‡‡£K—.°°°à—kkkcÀ€HKK“ÚÇnâĉPVVˆD"¸¹¹¡°°ƒ†¾¾>ŸÛß/^¼(£eË–°··çÿÖÒÒBÿþý‘šš*uÛ9>Dhh(ßå„#‰0iÒ$Ξ=˧·oßëׯÇùóç1cÆ LŸ>;wÆêիˬƒ“””„ÿýNNN‚îÚÚÚèÛ·¯ÌõËsçÎܼy“ïQr;&Nœˆ¢¢"œ?^b½Ñ£GK¤M›6 °k×.>ˆ°{÷nØÛÛ£sçÎ2ÛSò<ÉÌÌDBB\]]¡­­;wîHäWWWt‰DpwwGAA¢££ùô?ÿüzzz‚óMUUC† ‘Ù¦Šàº—YYYI,㎗Ò]ÐdùòË/OwßèСZµjUîzmÛ¶•ÈóÇ<—‡UUU~9Pü˜žˆ ¸‹Paa!Ö¬Yƒüü|„††?“IÉ}\–ˆˆ¨©© ®;¥M:ªªªüß½zõÁ À?ÿü0nÜ8ÁºÜ¾*¹UmìØ±ÐÖÖæÿîÓ§Dû¸®UÆ ¬;bĉ®@¯^½B@@ÜÝÝáìì,X6iÒ$\“lmm±}ûv\»v S¦L··7Zµj…íÛ·óy‚ƒƒñèÑ#L˜0M›6åÓ•””0~üxäççãÂ… Š»@¾xñ=zô@£Fø¼FFFèÚµk™û»&)2» Sy*²³0Ÿ*îd|÷î]¹ù¸å 6¬P=ÐÒÒÂôéÓ%–¥¦¦âÝ»wÈÈÈ\Èúõë'µ¬éÓ§ãøñã8xð ¦L™ ¸?[³fͪu waåØÚÚJMç–…‡‡ós¥xzz òfggʃ 0¸L%õêÕ ;vìÀóçÏéÚÚÚrBðìÙ3@‡*ÕŽÍ›7K´COOíÚµ¤qûµÿþRÓ¥õ£íÞ½»Dš»»;6mÚTî—>îŸddd¤ÄçrQQQ‚´¹sçâÒ¥Køå—_ШQ#üõ×_c¤áúÕK;FÝÝݞѡ$n;ÂÃÃ%¶ƒësYz;444àêê*QVóæÍáîîŽ}ûöaõêÕPSSC`` ¢££±mÛ6¹ÚóðáC|÷Ýw ”˜OžûÒ^R=ç>þ 7¥dbb">~üˆ~ýúI-ÕuÞs_v_½z%±,..NG^èÓ§öíÛOOO ‚¥²H»>þjjjèÖ­› ½~ýúèÔ©BCC‘žžŽ  k×®¨W¯.\¸€Ñ£G#((ÖÖÖpqqµµ5.\¸€>}ú ((êêêr]bccadd%%é÷ьѾ}{AZéÏ(¾v˜˜˜à‹/¾ämÑ¢ÌÌÌ$®U©ôuÆÒÒ 4´ïåË—PRR’8_ `oo/¸ÖsçbBB‚ÔkŠªªªÄµÞÛÛ—.]ÂÞ½{¡¡¡Ã‡CKK‹_ÎÝŒ¹sçŽD™\q®Lî:#íšØ½{w©_¤ÿŽIÆT  ë0¬à[^úް4TjBv"BJJ š4i‚&MšHäçÒJ¯Wò.fIîîî°¶¶Æ®]»0eÊ\¿~>ÄÚµkåTQ¥GpsuIÙ-‰ÛóáÀ‰‰‰ÔQÿÓ¦Mƒ]…ÚÅ 2)yg‚Ã¥¥§§ Òw…Êí[:À©ŠvhiiI|fÜߥëãÒK'€ô'(\åÍýÊwRg^™2e œœœiDÄéÑÒÒ’{d¿<ûG^¥÷·†††R·cêÔ©ŒA™S¸Í˜1ƒÆßÿÑ£Gc×®]ÐÔÔÄØ±ce¶->>={ö„²²2fÍš{{{ØØØ ^½zèÞ½»Ä €ôc«ôç]•ûO^\¹?–XÆ¥U¤nooo 2_ý5êÕ«'õI@iÒ®‡éééhÔ¨444$–qíÊÈÈ@ƒ  ©©‰Î;ów˜/^¼ÈßeîÙ³'óâÅ‹èÒ¥‹\SêYZZ"00………üS¤’äù\¹6:::J­£iÓ¦¸s爨Z®ïeµ±tû´´´¤^»›6m* e‹'N<âp×)uuu‰U\™M›6…žžžÄºS§NE›6mø¶Ò¯‰å=iæ¾$ZZZ–™‡©z,€®ÃÚµkMMM8p+W®”ú5;;þþþPWWÜmÕÔÔ”zçúÖ­[‚¿E"š7oŽüü|ìØ±£Òm‰D˜6m¾ùæܽ{;wªj™³sH[¿¦µlÙ@ñ#Jiw¬+ƒûÇ„#F–qÿ,¥=‚–÷¨ZÖ—¬’í?~|•·£ãžö”ž000¢ÁiÓ¦ 4h€Ý»w+4/qY”••1uêT„„„ÀÇÇùùùr/àú—~¢ò÷ßWªMõêÕCÛ¶m.ñ¥BÚþ« 5ÂÀ‹„„>ˆpëÖ-~¹¢TUU±~ýzL:_ýu…ÛÇu³(½ýáááHLLDÇŽ'wÇyÉ’%PRRâówïÞ"‘K—.ä“…ërQÙ.ÎÎÎ(((8Ö¹s¥dwîú-­+Puá®[¥¿¨Ý¸qC0(îvbllŒ?þøYYY2Ë~ðàæÏŸ777ìÙ³[¶lÁåË—ÁwÇŽ¡®®Ž]»vÉ|åv›6m ¦¦¦ðͧOŸBSS“Ý®i5>l‘©RäääDhäÈ‘tþüyŠ‹‹£sçÎñS$999QVV–`=nfŽ#FÐíÛ·éüùóäééIúúú#}“““ÉÐÐôõõé·ß~£7oÞPff&EDDÐÖ­[£Ååe=nÜ8~tÉY0dIKK#êÕ«>|˜‚ƒƒù)„Ê›…#22RPN|||™30H›fláÂ…€FMááᔕ•E¯_¿¦ÿý—¾üòKzþü¹Ì¶K›…ƒˆhÑ¢E€¦OŸN111OË–-#4jÔ(A^ggç2g(Ë™3gµiÓ†BBB(==^¿~Mþù'8q‚Ï·bÅ ~ä}tt4%&&Òwß}G"‘ˆ<==eJ›ƒèŸÁõë×éÙÙÙ3-p³pèêêÒСC)::šRRRhݺu¤¤¤D”Y'7cA¯^½èæÍ›”™™I tùòeš>}:l}øðLMMÉÔÔ”Ý^TTDb±˜êÕ«'˜­£,?ÿü3 1cÆPll,}øðV¯^M7–˜…#??Ÿš5kFººº´uëVzñâùùù‘……5nÜXâøâÊîÛ·/ݾ}›233)>>žBBBhÊ”)*sß—Oªªªüç.¯ .ruu¥[·nQff&ùûû“‘‘Õ¯_ŸœùÕÔÔhäÈ‘åH›…„;{ôèAOž<¡ôôtÚ¹s')4 lj'hïÞ½´wï^jРñ—žõ$44”TTT¨}ûötóæMzöì?­gééÃÊRrŽò”7 GÉ4Nzz:žžPZZݾ}›¤žCùùùü4›¥?Ó6mÚÒÑÑágÅ‘%""BbNY×™ÈÈH@k×®åÓbbbH]],--éÒ¥K”žžNAAAdff&1³ Qñ4{ŽŽŽ´ÿ~ºtéEEE•ÛÎòfá(9ÕGGG‡ Äÿ““C¦¦¦Ô¨Q#  ¬¬,º}û6ÙÚÚ’®®®Ä,ÿý7 .]ºPhh(¥§§Ó»wïèÚµk4gÎ  ¢âÙ‚Z¶lIúúú‚Y˜FŒAJJJħ­Y³†Ÿ¾ïîÝ»ôñãGzûö-]¼x‘¼½½ÿ³fÏžMhÑ¢EôîÝ;zûö-Íš5‹okéY8ŠŠŠÈÜÜœ\]]ËÝLÕcôg 77—æÏŸÏÿÃ,ù3gÎÁÜ»œììl~^IîÇÅÅ…Ž9"õ$}ñâ¹»»K”¯®®N‹-âóÉ@sS Y[[ ¦Æ“ÇO?ýDݺu#Áż:è‚‚Ú°aijjJì[[[Á\Øe)+€ÎËË£… ’H$”ëååE‚¼  ‰Š?===AùÊÊÊ´{÷n>O~~>”^¹P IDAT-]º””””ùF-1µYUÐ[¶l¡~ýúñÓ}qqéŽeÕyàÀþ‹_ÉsssºuëÏ“ª¢¢"1[bb"‘µµµ`:,iŠŠŠhñâÅ‚:ÌÌÌ(((H"€&*ž¸à©¨¨Ðï¿ÿ.õø"*À5j$±Íš5ÓòÐDÄÏÅ[zoYfÏž-8µ´´èܹsÔ²eËJÐDÅÓŒ)++óŸuãÆ),,L¡ÚÑÑQbq?Ò‚ˆ€€ÒÐÐàóˆD"š7ožÜ×ê  ‰ˆ=zDööö‚mhܸq™×ÏZ°` ›Æ¬ô—NYúõëG­Zµ’ØŠÐDD—/_æç5æ~LLL_ü8ǧ>}úðŸIÉiP¥©lMTüeÁÒÒRpYºt)??tiÿý7?ÏÉccc>0?~<‰D"‰c<55•,--ÉØØ˜Ÿî°¨¨ˆ¶oßÎ*ùÓ¼ysÁôs9994jÔ(AžvíÚñçTéÿÍÜÔ³§OŸ.w?2UOD$ã™Sgäåå!""‘‘‘8sæ 8€9sæàÇ”šŸˆpïÞ=DGGÃÞÞ-Z´@vv6¡¯¯/Ỉ‹‹Cdd$ÒÓÓall '''A¾¬¬,¼{÷|?PiBBBàææ†uëÖaáÂ…ÚÞŒŒ |øðºººÐÑÑáÿ666F½zõ¿­1%%M›6 ¼+,,Ä«W¯ ££]]]A¹‰‰‰ÈËË“Úÿ633‘‘‘xùò%tuuammfÍšÉÕÞ·oß({0È»wïðàÁäååÁÑÑQêôRñññ(**ªÐà§œœDDDàùóçhذ!Z·n-˜jŽóþý{Ü¿999ptt”ºPPP ÑFî3hÒ¤‰` !66ÚÚÚüÌ7nÜ@§N°cÇL›6 ÑÑѸÿ>Z´hV­ZIÌPV@ñÛÚ¢¢¢ðôéShiiÁÒÒ’dË}ÖjjjRÂ&''#==]æ1ËyõêîÞ½ 333888àÕ«W°´´„¯¯/V¬X!È›’’‚Û·o(~d_¿~ýr¯œœDEEáÙ³g¨_¿>š5kÆ÷¡”g?”Ô¿„††âíÛ· ¿ùùó燉‰ œœœP¯^=¼yóJJJ‚} MMM‰ãèãÇHJJ‚¡¡¡Ä ¹wïÞáöíÛhܸ1Ú¶m UUUÄÄÄŽò¼}û¶Ì·ª««Kü•““ƒ°°0¤§§ÃÙÙY0E˜,IIIøøñ#LMM¥¶ãdffâýû÷ÐÓÓã÷wNNi¥"** QQQ055…]™ÇaJJ ÒÒÒ$Êãê–v=+wž={b±˜O/ë:“ŸŸ7oÞð×Ü’òòòðøñc¼xñVVV°µµ-w0#·oêׯ/up'55©©©‚ýϳ\ßú’âââ ®®.Ñ-077wïÞŇбcGèééáýû÷ÈÌÌ”ÚETT444`ff;;;~bll,TUU¥^‹¹6—þœ²²²‰/^@GGÍš5+³þÓ§Oñøñc´jÕ 666eþo‹Åøðáaj  å”——‡íÛ·ãÊ•+ÐÕÕÅ Aƒ$:ùJŠŠŠ0räH=z~~~ðññ©í& ¸¹¹!22OŸ>•kpóù)@×U111eеåúõëèܹ3–-[Væø†Š!ª¨¨(ôZsæÓ‰/¾øêÏÏT›…CN™™™ˆÇÂ… ‘™™‰9sæ@SSSî5MII þù'ÜÜÜPPP 1Bº6¤¦¦âÇÄíÛ·‚={ö°à™aªÐ¹sçpþüy=zM›6ýä¾83Ÿž7âĉÈÍÍ•kú;æÓ‘””???<×@Ë©Q£FX¿~=ÿ·——:ôÉÐ@ñÈ÷™3gÖv3x™™™Ø·oLLL°yófL˜0¡¶›ÄÔ"555˜››×ú»ÊRQQ¹¹y…§3«J·nÝÂ?ÿüwww̘1Cj7,†)ÉÑѱÌyœ™O›‹‹ \\\j»ÿY¬ Gzôè‰'J¼¾”a†a†ù¼ý§æÎËË+sð çãÇxòä ÿ ]i–,Y###<3 Ã0 Ãü}öôƒ0lØ0XXX@MM ½{÷–š///ãÆƒŽŽlllиqc9rD"ߊ+ðèÑ#üþûïÕÝt†a†aæôÙÐ (**Âĉù· I3þ|œ:u —.]Bzz:fÍš…Q£F ^m½bŠܺu ÇŽL‰Æ0 Ã0 Ãüwü§ú@‹ÅbäääH¼r4##ÆÆÆ˜5kÖ®]˧£W¯^Ø¿?ž?ŽæÍ›ÃÒÒ’Ÿc¸[·nøõ×_kr†a†a˜ZÆfápóæMdffÂÎÎNîàà€‹/¬¬¬-X^Þ¤ú%íÞ½”H733«`‹†a†aªF\\œDÚ˜1c0yòäZhMÝðÙwá÷†8[[[Aº½½=QTT‘HuuuÁ¼Ý8<ˆ›7o"??_‡ÜÜÜJ§ÅÅÅñ?UQ^é4®lE׉‰Att´Ì|/^¼œ¼¥ó…††"&&¦Z¶ (~éDaaaµí?®|E×½~ýº`ÐkYù®\¹Â—/-ߥK—ªmÛàÊ•+UZ^é4®|E× ­t¾ääd„……UÛ¶%''#""¢ÊÊ+–À—¯ÈºOŸ>EXX˜Ì|/3_XX’““«mÿ=|øiiiUV^é´{÷îñå+²îµk× 3ß•+W””Tf¾K—.UÛ¶UæÜªês°tÚùóç+¯  €¯¿:¶-++ ׯ_¯²òJ§ÅÄÄ>EÖ•gÿ"00P"ßû÷Ÿ»wïJ½ñÇ”P;o¯äêê*‘¾iÓ&@>|¤oÛ¶Prrr¥êíÞ½;ÙÛÛWªŒò?~œŽ?þÉ•öìY:tèP¥Ë·µµ¥””…ë—×Ô©S);;û“+Μ9rm·¬ò­¬¬®[^^^Ÿdùò®W^¾Ó§O“‡‡G…ê—ǽ{÷hóæÍŸ\ùÑÑÑäëë[éò=<<èôéÓ ×/¯µk×Rddä'WþæÍ›éÞ½{•.Ÿ»Ï—’’B¶¶¶ª_ÙÙÙ4uêÔO²|yöŸ¬òííí©{÷îªÿ¿‚uá ¯¯ˆŽŽF£Føô/^@UUµJ^ §§Wé2ÊbccSmeW¦|kkkäääTºünݺA]]½BmGÿþý¡¢R}§BEË‹Årm·¬ò»víªpÝŠðôôü$Ë—w½òò5kÖ nnnª_FFFèØ±ã'W~Æ åÚnY廹¹¡Y³f ×/¯®]»Vëµµ¢åwìØFFF•.Ÿ»ϧ®®ŽnݺU¨~y¨¨¨ ÿþŸdùòì?YåWçyõ¹`4Š ÀãÇѶm[>ýáÇ022‚H$ª­¦ÉåS «³üªRÁÊ”/‹«µüªò9ÿ®nFFFrZ5]¾"tu¶_–ê0+Z¾¼_Zª»ý²°s·â>÷š‘õFñÅNGG>äÓˆááár1 Ã0 Ã0ÌÃg:++ ÿþû/àÝ»wÈËËC@@ÀÝÝõëׇ¦¦&¦L™‚Ÿþ...hݺ56lØ€””̘1£Òm033“è¨ÏÈoþüùüÔŒâ-ZTÛM¨³ÌÌÌ0f̘ÚnF5fÌö(¸ع[qõêÕÃüùók»u–¡¡!ÔÔÔj»Ÿ´Ïþô‡0mÚ4L›6 oß¾Åû÷ïù¿KŽ’^»v-&Nœˆ±cÇÂÔÔ'OžÄÉ“'áääT%톷·7¼3òÛ´i“ÌW°3eóóó«í&ÔYqqql$z%2 Ã0UÇÓÓ³Vž1 óùøì»pÔW®\a4Ã0L59wîîß¿_ÛÍ`æ3Áî@×yvíÚ•=.a†©Õ9Ÿwu‰ŠŠªõi>몢¢"<þ-Z´¨í¦ÔI¹¹¹l¡ ìt ILL¬í&0 Ã0uDXqlaå°˜E6@×33³ÚnÃ0 S‡°'’§®®Ž_~ù¥¶›Qg±˜E6@×èèhüõ×_xôèQm7…a†aF£Gð×_!::º¶›òÉct ©W¯6luuõÚn Ã0 Ã0Œuuu4lؽ¼L,€®!zzz‹Å°¶¶®í¦0 Ã0u@TTTm7¡Î***ÂÓ§Ok»u޵µ5Äb1{ƒ¨X]CX‡üÏÃ7àçç‡ääd¹òûùùáôéÓÕܪòååå!66ÙÙÙµR¿¢û¬´´´4øùù}Ó<žXXXT¨žò¤¥¥ˆ0}útÀóçÏqæÌ„††âôéÓø÷ßå.K__Ÿ)'$$÷ïßÇÈ‘#addħ7jÔ¨JÚÿ©HIIÁ’%KЮ];øøøÀÂÂ!!!8yò$<==qìØ1 2Df9AAAðññAÆ annŽ˜˜…ïd·oßsæÌùìö1Ã0 óßÅè:£>WÈÌÕ¢EL…kÈÏÞ¿Ÿ À¢Ü|††²ÛQººº‚G—ppp@`` Ξ=‹>}úÈU޹¹9~üñGAÚܹsqÿþ}Ì;;v¬ÒvJ´µµqùòetíÚ•O›2e BBBàææ†o¿ýV®ºwïÞxþü9š5k†›7o¢S§N ·¥gÏžèÙ³§Âë1 Ã0̧Šuá¨!Šv?`þG[[^^^€{÷î!!!£FÂþýû¥æ?xð F…øøx¹ÊOJJ¦M›Ð§OØÛÛ£]»vøòË/qóæÍr×Û»w/<==ѺukLš4 111roÓË—/1sæLtíÚmÚ´———Ô)cbbðóÏ?cÈ!°±±A§N0sæL¼|ù²Üòµ´´Á3ÇÕÕ¶¶¶xòä‰\ÝpŒaee‘H$÷¶•vòäIŒ5 oß¾åÓ:„Q£F!-- üñ Œ5 Ïž=“«Ü÷ïßcòäÉøê«¯ý«Ïž=‹±cÇ¢uëÖpqqÁ‚ ––&X÷Áƒ5j®_¿ŽÐÐPLœ8سgbcc1jÔ(œ={<À”)Sàää2»ã|üø¾¾¾‹Å°³³Ã!CpìØ± ì­ÿ5jvìØøøxÌŸ?:t€‹‹ 6lØ€¢¢"‰üYYYðõõE¯^½ààà€Ñ£G—ÙÿžˆðÛo¿aĈ°··Gûöí1qâDܸqC/55>>>èÑ£œœœ0nÜ8„„„H”·uëVŒ?………ذaz÷înݺaÍš5üµïÂ… ü~öôôÄãÇe$%%aÔ¨Q8zô(îÞ½‹)S¦ uëÖ8p Ô}¹qãFLš4 X·núöí‹V­Z!33Sî}ü©cƒ+Ž "¬³ÈÆèRSò‹Š€ÔÔŠýäåÕH+…ˆ`dd„§OŸbÙ²eaÙ²eˆŒŒD“&Mä*óâÅ‹X½z5ttt ‹abb‚“'O¢sçÎeL«W¯ÆÊ•+ѬY3tèÐGŽA»víððáC™õ8qŽŽŽØµk´´´`cc´k×Nð¼|ùNNNX¹r%ÔÔÔ0räHØÛÛãܹs¸uë–\ÛV!11¦¦¦PQ©™Pð÷÷Gzz:ŸöàÁøûûcáÂ…˜0a  ¥¥tèÐAæ€Ã/^ S§N8sæ f̘Áwùæ›oзo_œ:u ŽŽŽ "üðÃprrBRR¿~||<üýý±cÇxzz"%%;w†²²2RRRàïïýû÷ÃÕÕW¯^EË–-Œ#FàСC‚¶ÄÄÄÀÉÉ «V­BBBºté‚+W®`ذaXºti…÷›¿¿?Ž=ŠnݺáСC°±±ÁãDZpáB‰~èIIIhÛ¶-¾ÿþ{èèè Gxüø1ú÷ïU«V òæåå¡W¯^˜4i^¿~ www´k×÷îÝô¹}ùò%±eËÁÅÅ7oÞD=°eËA™×®]ƒ¿¿?¼¼¼°bÅ èéé!66K—.ÅÒ¥KqþüyôíÛOŸ>…‘‘Nœ8.]º>ç?Âßßûö탇‡rrrлwoÄÄÄ`ذaظq£ ÎË—/ãÈ‘#=z4öìÙ###|ñÅ ¢ ïóO DXqlaå°A„r ¦Úyyy‘••yyyÑñãÇËÌãååUf;z@rüÈ›OÚ/Ñ2óúRtttµì+CCC277¤¥§§“…… S§NÑ®]»sþý÷_@Û¶m“Zþœ9s]¿~O{÷îeee òegg“‘‘™˜˜Paa!Ÿ¾víZ@ÆÆÆôîÝ;>ýöíÛ€((€àsMOO'CCCjÞ¼¹`ý¤¤$²²²"KKKÊÍÍ%"¢ 6 ”YTTD?~”º}²lݺ•¯¯¯Âë^¿~М9sZÛg‘‘‘|š [[[ŠåÓ·oßNhݺu|Ztt´ Í7oÞ$}}}jÕªÅÄÄðùBBBøý““ç_½z•ÐW_}ŧ={–’’Ý¿_ÐÞ{÷î@§OŸæÓ_½zEÊÊÊÔºukAþÁƒ“H$¢k×®ñi¹¹¹4vìXRVV¦ðé#GŽ$555¹ö׆U«VQ^^eeeQË–-ISS“²³³ù¼Ó§O'ôÇÚн{wRVV¦Çóé«V­"´f͉:ß¿Ïÿ>bÄ@|ZFF9::’ººº`ß9’‡‡%''Q~~>uïÞ”””¨Q£FÆçß³g  6ðiÜç €<ȧgggSûöíICCƒâããùôAƒñurçLy\]]+tÜ3ÌÉñãÇ1 S6vº†tíÚûö탧§gm7å“—’’‚E‹aÑ¢E6l wwwôíÛ0fÌ4hпþú«`Ý]»vASScÇŽ•»>}}}hhhÒÔÕÕ1yòd¼~ýZÐõ€#‹¡¯¯ÏÿÝ®];ØÛÛãäÉ“åÞ==xð 1{ölÁúzzzøòË/»wï(î ?F/I$ASSSîíãܹs ,€–,Y¢ðúÕaܸq‚ÑÞP|×ZšþùÝ»wG«V­põêU˜››óË6mÚ‘H„%K–@MMOïܹ3Z·n-µ@ûöíáèè(µ.GGGþx´oß‘‘‘ü]Îèèh?~}ûöô¯W¯f̘ÂÂB…f=)MCCsçÎ…ªª*ÿ·»»;²²²ø.CEEEسg´µµ1räHAÆŽ‹ÂÂBìÙ³‡Oß¼y3lmmáãã#Q_ãÆééé8räLMMáîîÎ/¯_¿>FŒœœ8p@bý¯¾úŠ?nUTTЫW/¡sçÎhÓ¦ ŸÇ ­Û’––†Êÿ­®®ŽáÇ#;;[âî?P| ±—>0LÕðôôľ}û¤vd„Ø ÂÏŒ¾>PÑØèÈàÚµªmOEdddðÓ5hÐíÛ·Ç!C0mÚ4¾/®––Ƈ;wâíÛ·066Fbb"Nœ8±cÇBGGGîúˆ¿ÿþ;vìØ—/_âýû÷‚ÇÀqqq011¬Ó£G‰rzõê…‡âÅ‹eÎ8Áõi¼qã†DðÀêQQQèØ±#†Ž `àÀhß¾=<==1vìØ M/ôøñcôéÓúúú8uêÔ'p”þBibb===©°zõj :¿ÿþ» HŠƒ1©móó󑜜ŒäädÁgÓ¯_?¹ÛQºqãÒÓÓ¡££Ã÷ãÍÈÈÀ´iÓ$ò+++Ëݧ[š^½zA[[[ÖºukÅXmllðæÍäåå¡OŸ>| Íá‚_®/hBBRRR0tèP()•}ÿäÙ³g "ôêÕKj›–.]*Ñ_YYb±XÆ}9éß¿¿ ½I“&Ð××—ú9»ººJŸÜùöüùsAºH$BïÞ½Ë܆a˜êÂèRSòuu¹s+¶nTÔ§@›™™É5 oúô騾};öîÝ‹¥K—â·ß~C~~>¦L™¢P}kÖ¬Á·ß~‹Þ½{cæÌ™°µµ…¾¾>þùçlÚ´ yR:‡K›ÂÏÒÒ€äã’¸¹óòò$òijjbäÈ‘044Püå!66¿ýööìÙƒ¥K—bùòå0`~þùg¹ûx?{ö îîîPVVÆ… wmk[Æ’S&*++KíÇZTT„¢¢"äååIÔ˜mmm‰ƒ`gg;;;‰>óÍ›7W¸møö%$$ðˤÕ;|øð2ïp˃»›[^RRR@ê[N¡®®ÎçùðáC™å–ÄåçŽé’¸4.GEEZZZ‚4.H—ö…VIIIêç,­Nî|+}Î4hÐ@ð$çs›ÚnFTTT„çÏŸ£E‹µÝ”:)77Wâ&#ÄèÂ:äW½/¾øݺuÃîÝ»±hÑ"ìÞ½ OO·}ûv˜˜˜àŸþÜùÚ¹sg™ë\¹r]ºt¤q³” rÛÊ•+ѪU+™mÓÖÖÆœ9s0gÎ<þ¿þú+Ö¯_GGG¬X±BæúÑÑÑèÙ³' péÒ%´lÙRæ:Ÿª!C†à›o¾Áĉ1pà@?~\ÐõÆÚÚRóW.h6lþïÿþ¯Æê-‰;Þ‚ƒƒ%–ݸq999hÖ¬ÀÊÊ JJJˆŽŽ.·L.pp°Ä‹_¸zªë‹˜´Y>¸·P~J_þj‚ŸŸ{™JqƒÙËT*&11‘½LEÖº†Tö@,**#ó§°°²¯k~-Geßa­iÓ§OGLL .\ˆ—/_*|÷9?? hݺµ xÎÍÍ-÷ÜAAA‚¿‹ŠŠ„ÆK½ƒÆá‚à+µµ5Ö­[‡&MšàŸþ‘™?..Ý»wGff&agWö‹xˆ999ÈÏÏW¸]5iüøñ8tè‚‚‚ ‹‘‘‘Á/ëÒ¥ ^¾|‰Ë—/×X{¡¥¥…ýû÷£°°°Æê-IGGÍš5ý{÷ø;ÌœÀÀ@Å}½âþÄíڵÙ3gÊæÑÜÜúúú¸zõªÄ‹s¸2Köi®J=ÜÙþw¾uèСZêüT±à¹âØ›+‡ϲ±;ÐuÄ„ hÝzŸÌ|úúí+\ÇàÁnd¿.YUµ¡ÔÇÛµaèСÐ××ǦM›<ªªª°µµÅÙ³gqêÔ)xxxàãÇøúë¯Ë ˆnܸ={ö`ܸqÈËËòeËœœŒï¿ÿ^¢jI½zõ Aƒ°qãF4lØÓ§O‡ŽŽ>~üˆ°°0ìÝ»[·n…¶¶66mÚ]]] 0zzzHOOÇáÇ/µvIÉÉÉèÞ½;bccùéÕÎ;'ÈãêêÊßÁ…¥¥%<<<ùòòòøà…ë¿ËçiÕªUß>|84551lØ0¸»»ãܹsÐÕÕÅÊ•+qøðaŒ3?ÿü3z÷î 555¼{÷.\ÀãÇñý÷ßWi[tuu±zõjÌ›7£GÆúõëaaaüü|<{ö DïÞ½áââR¥õ–¶jÕ*Œ;“&M¯¿þŠÆ#88Û·o‡©©)¾úê+>ïÆÑ­[7 0{ö샃 qçμzõ LJ²²2–,Y‚yóæaÚ´iزe ´µµñÏ?ÿàÏ?ÿ„ ÆŒS-Û¢¡¡™3gb×®]ÐÕÕŹsç°ÿ~ØÙÙaРAÕR'Ã0Œ¢X]GL›¦X`Xnððp«özªR½zõ0iÒ$øùùaäÈ‘ äìØ±ƒ € ®®Žœœ¸¸¸ÀÏϳgÏ–ºÎÞ½{1mÚ4Ìœ9(,,ĤI“ðÍ7ßȬoß¾}øæ›o°dÉ,^¼ 4àçHæ±Åóðnß¾@ñ Éììl¨ªª¢wïÞØ¼ys¹uÄÅÅñƒ¼–/_.5Ott´Ì×±'''K*]ôÔ IDAT¼ù1 Šgt˜[ÑN÷•Я_?œ:u ƒ ‚››a``€ .`òäÉ8p ”••¡¡¡ÌÌL())aÒ¤IÕÒ–Ù³g£°°Ë—/Ç‘#G ¥¥…œœ¢Q£F‚™<ªË˜1c‡åË—ÃÀÀÈÎÎF‹-pøða¨««óy»víŠÃ‡cÆŒprr‚¦¦&òòòPPP€ `øðမ3gâíÛ·øá‡ðÇ@]]ÙÙÙhݺ5:Ä÷Å®jÜÛBõôôÛqìØ±J½Ð‡a¦*‰HÚ(¦Jy{{#77·Ü¾™ÞÞÞØ#»ÇC$ÁÖÖVîuV­Z___\¿~]fÿç7oÞ )) -Z´L—””„ÀÀ@dff¢}ûöppp@ZZâââ`mmúõë(øöí[´jÕ ¹¹¹¸víâââàìì,u°Øýû÷ѨQ#©Ã^¼xððpÄÅÅ¡I“&°³³“Øî/^àÎ;HLLD‹-àââ"×vÙÙÙxòäI¹ylmmùn+yyyˆˆˆ@ƒ øþ¯PPP uª1NÓ¦Meâ*¹Ï¸A)ñññHLL„Ä ]?~ ¾\ÛŒŒŒ`dd$Èûúõk¼ÿ†††ü ÊÂÂBÜ»wHOO‡™™œùÁ™@ñ¬/^¼€………ÄÓnßIÛ6îø±··— “’’pïÞ=<}úÚÚÚ°¶¶†³³³`ûbcc‘––‡r÷Pö±“œœ,q\râââpûöm$&&â‹/¾@‡$¦h䤧§ãÎ;ˆˆˆ€ŽŽZ·n-µ›Ï‹/†äädØÛÛ£C‡OYÊÚ®òöséÏ9&&–––ðõõŲeËðàÁܾ}–––èÚµ«ÄqÌÌLØÛÛ—³ÿÇÍÍ nnnrøT°A„ÇVÎèÑ£¡¦¦öŸIÊÃèàíí[·naáÂ…prr‚“““Ô<  •––+++888HôKf¦î(@WG[hoooö?¡‚rrr0wî\ÖZA÷ïßÇýû÷±~ýztèÐå`]8jˆ™™™Ô»0LÅDDD 00ÇŽCZZ¶nÝZÛMb†©R,x©86ˆ°b6l 6ˆPlŽbdd777™}Nùܼy+V¬€H$ÂÞ½{Ëa‚a˜OŸ’’tttýµ†©Ypss“è.ÇHbw ™:i„ ˜0aBm7ƒa˜*bffVîKˆ†a>%ìt ©©72 Ã0ŸnêHFqEEEü+ìű˜E6@×ö&B†aF~~~µÝ„:‹{!S1,f‘Ð5„uÈg†aÁVDX9,f‘Ð Ã0 Ã0 £@3 Ã0 Ã0ŒX]CX‡|†aFlaűA„•ÃbÙX]CX‡|†aFlaűA„•ÃbÙX]CX‡|†aFlaűA„•ÃbÙØ‹TjÈ•+Wàíí OOOxzzÖvs>Y—.]<:jР,--ѤI…ËJKKÃõë×åÊ«¡¡WWW™ù~ýõWlܸgÏž…¥¥¥Ìü666}úÈ•×ÜÜ1112ó}øðOž<‘»Ø“'O/WÞª”šš ggg¾ H$ŒƒÊõÊä   Ìž=QQQ(,,Ì™3?þø£ÜmQtŸ•–ŸŸ'Ožàýû÷ZÿSòæÍ999 "lÛ¶ ½zõ‚¹¹9BCC±qãFü?{çÖÔõÆñoØ {/e(¢ .pPATD°¢b­‚­ ·uWëh­PG­{·ŽŠEÅU·¢‚¢‚8²$² ;ç÷¿¤Æ$$`Ïçyò<äœ÷Œ{sCÞûÞwœ9s ,À¶mÛDΓ6mÚ`Ú´iPWW—È’>a¸ººÂÜÜ\‚#¡ÔÎù–ä© ‡ÔÔTôìÙ={öÄõë×qûöíFÜ!E\^½zEoZ$„Íf#11;vlî­´J***¾¸ßÅÆ†*ÐR¢1òW†¬Äÿ@ÑÀ"n[¥A%Ò­Ó±;m7¿MÄ…ã´Æ•kWðí/ß"·. [ÛÆÙ²qîã9¸ wÁ“ëO   Ð uêB^^žû£Ñ©S' 6 ïß¿Gdd$Ο?/ö±ªª*œyÚ8–z[[[¾¾üü|¼{÷òòòèСƒXÚ¼¼<¤§§ÃÆÆ¦Þ7/l6ÉÉÉ`±XèÔ©Sç4??¯_¿†––Ú·o9¹º¿ºúúúxýú5 ·mРA°±±……Nœ8!–=vìXŒ;%‘mll ccã:eòóó‘’’+++¨©©Õ{Ï)//Çëׯ¡¦¦ žó ˆÊÊJÄÅÅÁÔÔúúú<}UUUˆ‡H¥”Éd"##VVVÐÐÐhðqH‚8ç;77ïß¿GçÎ^·gÏžåþœœLèf"$$„úAK'ˆúAKFvv6õƒ "” ½“±ïÊ>Ù ì¯hW5pâÌ ‰×`±X˜½z6òü)D‡àMÇ7øaù¯!)~~~j-ÒL& ˜0a‚@ÙÉ“'C^^^l׉Ç£[·nÐÖÖF·nÝ`ggUUUÌ™3,Kà˜¼¼<ôïߺººppp€ºº:vîÜ)Öz„üþûïÐÐЀ••ºví UUU,_¾œç±;Pk1ïÕ«tuuÑ»wotêÔ ººº\ ½0ddd*&&&°³³CAAØóJHH OJ®%K–pÛtïÞÚÚÚb»‡DDD M›6pqqA^^€Zו±cÇBUUhß¾= xB¸|ù2 BCC1}úthhh {÷îØ»w/bccÁ`0ðÇà矆¦¦&all ñ¯]»sss¡gÏžÐÔÔ„··7rrr$:g`0B_BÇrη —$&“ '''èéé¡k×®ÐÔÔÄÁƒ%Ú#¥é¡Ê³äР†A•gÑP t+aè~¼·y_§L¾M>–o[޼¶y­ñ&æ RõS*ϪÚUáîûÍßîß¿022‚¡¡!|}}qòäIlÞ¼™Ç¥¤¨¨GÅðáÃÅ~„ '''¬]»mÛ¶ÅÛ·o-[¶@VVV`*¤1cÆ`ðàÁøã?PRR‚Ÿ~ú Ó§O‡šš¾ýöÛ:×›3g¶mÛoooøûûCGG{öìApp0***°~ýzÀÕ«W1oÞ< >.\€µµ5²²²píÚ5ÈÈHv“ƒ/^ÀÑÑQ¤[øøø ]»v8yò$rssñÇ`Þ¼ypqq©Óß}çΘ5kFމC‡AQQUUUèÛ·/2331yòdŒ3IIIرc|}}qëÖ-|õÕW<ó¬\¹ÆÆÆ8vìttt ¬¬ÌíÛ¾};Øl6V®\ ;;;ìܹçÎÚ5k°nÝ:®\DD†Ì;NNN8~ü8þúë/ :>¬÷ç5þ|øûûó´eggcêÔ©ÐÔÔëéˆ ¼½½1räHìÝ»yyyXºt)¡©© ‰æ¤P(”ÿ"Íÿ J‹‡Ov"„d·¹ot!H²Eh/Z,¯&¥¥¥R‰È/((À¹sçpúôiÈËËcÀ€€   œ}úˆ}lŸóý÷ߣªª ¿üò‹Äs4&]ºtAXX÷½¥¥%<<N:…Ñ£G×ë¼ØÙÙÁÎîß/|YY\]]!##ƒsçÎI¬@ÛØØðXø/\¸}}},[¶Œ*Ð …R¨ ‡”hh¡jU ©Ÿ¸+(-&Oä%þ‡ôôtÂÐÐúúúP« uîÜàæækkkìÙ³‡gìîÝ»aaaz¯›œœŒþù‡ÆЮ];TWW#55•OvàÀ<ï•••áâ₤¤$¤§§ ]ãâÅ‹¨©©Áœ9søúƇÊÊJ®ÇI«÷óÏ?ãåË—õ>žÏY½z5Ο?iÓ¦It~šŽk‡¾}ûB^^^à9¯¨¨À7ß|ƒ 6`Û¶mX¿~=›ÊÙ³g¡©©É½^8¨©©aÈ!xòä Øl6Oß Aƒ„ÊxzzòøcËÊÊÂÍÍ YYY¨¬¬ÄÅÅ!99“&M⻡ô÷÷‡ŒŒ ?~,Æ™!ß~û-¢££qäÈôêÕKâ¹ÜÝÝyÞkkk£{÷…Z ´¡äÐJ„ ƒV" µ@K‰†þ8y¸xàÂÝ `·g *ÛÆ_óÿ’h—q/1ê—QÈ3«Ã„ hÉjAV¶?Ò¦MîãkNèÁƒó¹dL›6 óæÍÃýû÷ѧO<|øÏž=ÃÚµkE} “Éĸqã PQQžžJJJÔ~vŸGÂs,áŸ2hÐ üóÏ?HNN†™™™ÀµÞ¾} ðòòâë㤙KJJP«@Oš4 ‡ÆÁƒaee…ï¾ûS¦L¶¶¶ØÇÔæ^µjÆŒƒíÛ·×klSò¹K…ŠŠ ´µµQ\\Ì'ûûï¿sƒf̘Áן€ÂÂBYZª««QSSƒ¬¬,nnduÞH|¾7 Ö/ÍfƒÅbAAA €Í›7cëÖ­|òl6[¬ôˆu±hÑ"œ>}7nĈ#4—°ëöÑ£GHNN†Aƒæ§4.4ˆPrhaàA„¢¡ ´”hè…øýøï±õÐV¼1#ÔGÙô‘)vØ CUÉrB:ÂNη nš‚e´b´ðcÐÍ/.â’âÇÄž={ЧOìÞ½òòò˜8qb½Ö›4i"##±k×.¸»»ÃÂÂ222øí·ß°dÉ®bû)¥¥¥|m…»®lœ¿ÿþªªªe8Vvƒ½{÷â×_Åpúôi,]º?ÿü3®]»&¶+ÇÎ;1þ|øúúâï¿ÿ–غ)¨Ïؘ1cpûömcÀ€èÚµ+O?ƒÁ€ 6lØ tŽÏo<45…\èbîs£¶xñbôíÛW LC”ÒÝ»wcÆ ˜>}:~ø¡áÁ»’^·”æ*Ï’CƒUžECèV‚²²2þ\ÿ'ÆÎ‹tçtàÓ Y€a”!æûÏop®Ýc»á«_!É> lãO¬Ýl@3FÃ-†ãëá_7hÆBSSþþþ8vìV­Z…ãÇcøðáõ**SSSƒ7n ÿþ˜:u*Oßç™>%22Ççi»vípý•acc V©rqqkúúúX´h-Z„¸¸8888`×®]b)Ðû÷ïÇŒ3àåå…ãÇ·ˆÀAI±´´Äš5k0pà@¸¹¹áÊ•+%Xœ }œ4hÞ½{‡¨¨(¡ãþùçž4p>|ÀÇÑ£G:³øúúBEEË–-ãKYÔæ/æøé ²¶oßÚÚÚ\׺8tè¦L™www„……Õia,..Æ®]»pñâE‘ó6'íÚµÃíÛ·add„âîÝ3ÂŒ7øí·ßŽt>J—.]`oo­[· ôÛ&„M…Xqqq=z4ºté‚ãÇ7Ú ÁåË—yüÀ“““¯¾úªÞnA …ò_¦õš£ZåollŒ+'®¨UÚtuuý‘¼ŠŠ n¯Í ›——UUÕûx·gÏžèÞ½;bbb`iiÉ$%  6 'Ož„ŸŸ|}}ññãG¬_¿ÎÎÎxðà@jUUU 4'NDii)7ÕÝÚµkë\ÏÜÜë֭ìY³Ð­[7Lš4 ffføðábccqìØ1$''CSS³fÍ»wï0tèP˜››ƒÉdâüùóÈÉÉÁâÅ‹ë\'11'N›Í†²²2_`lݺzzzjË?ÁÓÓC‡åÊroJ>~ü 6ã“Ɉ].½±022ÂÍ›71hÐ xzzâìÙ³pwwǤI“†eË–!&&C† ¶¶6ÒÒÒ ‹Åµ¶6rrrØ·oÜÜÜàèèˆÙ³g£K—.`±Xxûö-Ž;†õë×Ã××·^óNœ8EEEèׯŸÀ‚7óæÍ“¨J›ÍưaÃðÍ7ß   6l€¢¢"BBBxäN:…S§N>| 6µ'¨òðáÃRõ”R7´¡äÐJ„ ƒV" U ¥DSD¸^1­)¶UÊÊʪÞÊz@@bbb0yòd‘Áƒ°¶¶æIK·sçNÈÉÉáÌ™3‡­­-V®\ ]]],]º”GVGGÖÖÖ8|ø06mÚ„E‹!''öööøçŸàææÆ³žµµ5ŸEzÆŒ°··Ç²e˰bÅ q«/NŸ>›ÍÁÃÃÛ·oÇO?ý„òòrÂÝÝ¡¡¡Üê€Â¨®®†••àõë×e8™$€ÚêÖÖÖhÛ¶-LUU7+çx€S½}ªl ƒsÎ>ýg¬¯¯kkk.%VVV<ûàìMWW—Û¦§§‡7nÀßß ,Àààà€‹/bÛ¶mسg"""PYY‰6mÚÀÉÉ “'OæŽWUU…µµ5ÔÕÕùÖWRR‚µµµÀkŸsý|jîÙ³'âãã±páBìÙ³YYY`0055Å!CàèèÈ•511ážÃºÐÓÓƒµµ5®^½*°ÆŒPTTäžÛO•YAmœó†åË—ã‡~@^^ŠÞ½{óÌÏd2¹Ÿ±¢¢"¬­­¹Á­ÆPD(94ˆ°aÐ BÑ0ýOØäâîÝ»èׯ|}}Z£8å©é?Ëúãïïððp¤¦¦6(`‹Íf£¢¢‚/ç³(ÊÊÊê=æS ¡®®^§ò_TT$PÙ£§ººeeeR¼>CYY¹Åû›7ôºmm¸ººÂÕÕ«V­jî­P( ¢<­ŠÆŠ`ȉŸmJ\ÂÃÃÎÕY¨N"œ–ýþ ‚^ˆMCtt4Nœ8%K–48—ŒŒŒD EC• ‘2Ty®?rrrRWž4Ëš’ð_Rž)”/…V ²gƒ!Ï€Zw5¨÷T‡¬zãMsŒ|£E84ˆÒ*9sæ áìì ccc,[¶¬¹·D¡P(J“RSREÀf±Qx§é¤#çd*ÒháiCh)A«ú4.puuÅÞ½{+•²â …"Mh%BÉù+’‚¢GE¼l 4¾N~©i<\ª³ˆ†*ÐR‚–Ém\úôéƒM›6aâĉ<Áe …ò¥ðyvŠøp‚¿$J_–¢¦¸F`ŸzOu0dÏ'šê,¢¡ ´” Ñ¬ …B©4nFr¾ÄJ„Å‹¶3äPí.¸²­¤PE4T¦P( …BiÁTdT "C°[E»6m#ê«”¡ 4…B¡P(J ¦èa‘Ð>u'š¥©9  ´” ù …B©4ˆPr¾¤ Âê¢j°^²ö)™+AÁ°ñ+SE4T–Ô!ŸB¡P(õJΗDXü¸Xh† u禱>SE4T–Ô!ŸB¡P(õJΗDHª ŠcÊiÉAÅZ¥IÖ¥:‹h¨M¡P( …Ò)y^6‹-°O½—:ÐøÕ¼)bBh ¥¼ÿQQQ(//K>** IIIM¼«–M}ÏÙçTTT ** ¼3铘˜ˆG‰%ýÅøpR(ÉšºNUÇÆM]G©rͽÿ Ô!_<¦M›†‚‚î{uuuXXXÀÇÇ666õš+11Ë—/KVOO[·n)wèÐ!,]º èÔ©“HùÞ½{# @êb !ˆŠŠÂ©S§ðèÑ#dffÂØØ½zõÂÂ… add$Ö<•••xþü9bbbðôéS`èС˜0a‚Ø{©ï9ûœ¬¬,ôîÝ+W®ÄªU«ê=¾%±|ùr„‡‡‹u3áîîWWW„‡‡7Ù~Øl6^½z…˜˜ÄÄÄ€ÉdÂÁÁK–,i²5)âóêÕ+‰¾3”Úk;11;vlî­HLYJ*³+ö©9¨AF©él PTTl²ù¿¨-%ê?}Ü8ä>y¹º?²LEE\yô22õÿbíÚº[¶@WI©N¹7åå»}[l%¬>„‡‡#//«,§§§#//Ë—/ÇêÕ«ÅVˆ ´´±±±|@~~>ÌÌÌ ôÉqš˜˜4δ²³³Ñ§O())ÁÑÑÎÎθsçîÝ»‡½{÷"** ¶¶¶"ç ÅĉyÚ ë¥@ëèèÀÚÚšþ3n<þŽŽŽàúõëÈÍÍÅÔ©SŽùñÇqùòe¼|ù©©©Ø±cnݺ…_~ù¥ÎµØl6üýýñæÍ;v ÙÙÙHMMEXX²²²Ä•=rä=z„Ý»w#''QQQHLLDrr²Äî'·oßFbb"†*ÑøÆfñâÅØ»w/233‘žžŽ‰'"** GŽ:¦  žžž8{ö,NŸ>3fRSS1fÌXZZâÎ;x÷î233±jÕ*œ:uJ`èòåË1þ|"11‹-âö…„„ÀÃé©©ÈÈÈÀßÿ‚‚,[¶ŒgŽ5kÖàÀXµj233ñæÍ¹té¹téÙ¾};qss#ˆ¥¥%)..&„’––FdeeÉôéÓyÆÏ™3‡0 ’””$p~Ahap¬Š999Ü6Ž5uĈ|ò&&&DEE…°X,n>³@Ÿ{öŒ S§Nå¶}jNKK#¶¶¶D__Ÿ„öí H<ø9ÚÚÅÇãYFºþ¿mƒæoÞÜ(ó‹âÇ2d÷½¬¬,|}}±nÝ:®ßoÛ¶máåå…#GŽ`ýúõPQQAyy9:wwwXZZÖkÍwïÞ᯿þÂÛ·o‘šš0™LnŸ®®.¼››ßîîî8xð ’““…f¸xòä ÀÔÔ§OŸ!„°Ùlhjjâããakk‹Q£FañâÅðôôÄ„ 0räH±(?§¨¨C‡ŇCCC‰æilüüüxÞÛÙÙA]]] •4..½{÷†ªª*¢¢¢`aaÁÓÿøñchkk£¸¸˜{nÙl6!°²²ÂÍ›7Áb± ¢òoå.///¡{ûúë¯ù,=ýû÷Ǿ}ûPZZŠ6mÚàùó稨¨@·nÝpæÌîz„”••AVV¯_¿–äÔÔ “É„——ÔÕÕÁý^”••!11vvv000àÃñùòe£ï‡Ò4Ð BÉi­A„eoËPõ±J`ŸZ750ä¥S9…І*Э™ëÖaÛ×_cI~>.ÉËÃméR(Ö#½›8ÌÍËÃݺá@j*˜ `mmݨkÃÈÈ/^P›º]»vƒÝ‚‚‚pîÜ9?~'Nĉ'ŸŸ/ÔíB—.]‚··7TUUѹsgØØØ@OO/^¼ÀëׯQRRÂ7†X÷)NNN8xð 222„*Щ©©€U«V | §©©‰¼¼<€¹¹9þùçlܸÁÁÁX»v-ììì0cÆ Š®´´^^^xúô)Ž;ÖbÜ7ðÝè0 (++£ªŠÿäÚµk(,,ÄÊ•+ù”gHJJBee%_Ú=êêêøøñ#]—+Œ ›°6mÚwœ"9aaa8{ö,Ÿ¼ªªªÀë§!°X,x{{#//wïÞ…±±1·/==„ÇÕ¹sg¡7' ¥e ´pŠ j=›6u¥~Pºò©z»v8Ò€ÌÂÐÖÖF»Áƒ»gþÖ×—šõ¨MÁæàà RÎÓÓ–––سg&NœˆÝ»wÃÐÐ>>>õZï‡~€‚‚^¿~Ícµ›5k–Ð1ÏŸ?çSR8þØuYw9ÊN||¼X¹§ÝÝÝáîîŽÌÌL„‡‡ãàÁƒ˜6mdddÄJ WVV†áÇãþýûøûï¿1räH‘cZ*³gÏFff&V¯^ YYYüôÓO<ýÆÆÆPSSãZùÅAÒ”Ÿ® ;vì¨WnlIa³Ù7nž>}ŠsçΡk×®<ýœ4tŸÇµORŠŠŠø,Ó ¥eP•S…²¤2}*T §IU¶– "”]‰pæºuðkÓ®õ°DÖ—¹k×b…¡!Фh}® S§NETTŽ=Šû÷ïã»ï¾;•PkI|ýú5†Ê£XBpæÌ¡ã"##ùÚ®^½ 999´oß^è8;;;À•+WÄÞ#P›nlÆŒ¸sçtttpøða‘c***0bÄܸqþù'ÆŽ[¯5[222Ø·of̘+V`éÒ¥<ývvvxñâÒÓÓ¥¶§.]º"""¤²Þ‚ ŽÍ›7 |’ ¦¦SSSDGGóTôj¯O­º2Û D(9­1ˆ°èa‘о¦.œò9´z²h¨-%Z‰ðsú¹ºÂpèPLnë3mmmt;VªÖçú2qâD(**bÒ¤I`0˜4iR½ÆËËËÃÀÀׯ_Gaa!·}ÿþýu>ê¾rå /é™3gœœŒñãÇ×™ŸÙßß666X´hîß¿ÏÓWYY‰ãÇsóñþóÏ?x÷îLJJ yÜQYY‰‘#GâÊ•+ؼy3ÆŒƒòòržù$×wVVœ1oÞ<¾¹8ò••µ%ekjj¸m555ua0ضm,X€Ì™3‡{+V¬Œ?YYY<ã²³³ºX4333Lš4 'OžÄ–-[xÎ)P›»¹±” ;v`Ó¦M˜;w.7óˆ æÏŸššlܸ‘ÛVPP€={ö@^^žïÉJEEÊË˹?˜l6›ûù r¥¡HæÞB«…S‰°µÀ.c£ä™`w/#(™Õ]!¸±ilåK„>Má¿ÿèQÈÊÊ6ú¼ŸòÓºuM¾FCÐÓÓÃÈ‘# OOO¾±¢X¶lfÍšccc¸ºº‚Éd"++ .Äúõ뎙9s&úôéƒ>}ú ´´÷îÝCçÎùŠ—|Ž‚‚Ž;ôë×]»v…™™>|ø€„„   JJJ Å¡C‡Ð¥KXYYáýû÷xòä D®óòåK\¸p@­ëÃìÙ³ùdRRR`nn V‰zøð!7‘“Éä«N·mÛ6lÛ¶ ¸ ´áŽþüóÏ`±Xؽ{7°wï^Ìž=VVVppp€¶¶6ÒÒÒðòåKôíÛ·Þî=â°aÃ0™LÌ™3›6m‚­­-X,‘žžŽððptêÔ©ÁëlÙ²pýúu.NwîÜššf̘[·naÍš58uêÌÍÍ¢¢"lÞ¼VVV<ã xn¯^½ eee@@@ dkFè¹—œÖDXS R%¸rJsN¡A„¢¡ t+FŠ­´•çåË—×{ÍÞ½{#44T¬àAoooòüs˜9s&:vìˆS§N¡¤¤îîî9r$ ¡­­ÍLæêêŠàà`L™2žžžˆˆˆ@jj*‚ƒƒ1uêTn.^ÁÁÁ\· vvvxúô)þþûo<{ö iii°µµ…ŸŸ†έַf͸¸¸ ::ÙÙÙpqqÁªU«àââ"ÒmllŒààà:e´´´xþæ œSSS«sž~ýúÕ¹ðï9Ó××ç¶ 2ššš+=._¾œG–³·Ï×Z½z5:uê„ÔÔTÄÇÇÃÎÎpwwDZcÇðòåK¢_¿~˜?>† Ækmmàà`>bàßs'èØ8×GÁ œ?/^ÄÝ»wñæÍhkk£_¿~2d¯ü7ß|ƒ=zˆÐ BÉiéA„E‹¶3äPëÞüîTg U ) …B¡P¤Dyj9*³*ö©Ú«BF™ªf­ú)Q( …B¡H‰–šºŽR?¨-%¨C>…B¡Pê "”œ–DX]P Ö+–À>%K%(è+HyG‚¡:‹h¨-%¨C¾xTTT ¼¼œû"DpŠq „ðÌU×KÜW®\Á´iÓÄþ<§M›†ƒJ| …‚ÌÌL<|øïÞ½CMà q`³Ù(//Guuu#î°epòäIL›6 eeeͽ … "”œ–DXü¸„-øwMÃYCÊ»ÕYDCh)Ñù/^¼hô9?çåË— Rºê‹™™”••y^ÖÖÖXºt) ê5×Çùæö²¶¶kΧOŸb÷îÝÈÏÏK~÷îݸqãF½öݰX,ÁÔÔ¦¦¦pvv†……ŒŒŒ°mÛ6±çyùò%fΜ‰Þ½{CUUÊÊÊX°`Aî¼y¸wïvïÞM­.” "”œ–DÈ®d£ø‰ÔuÚrP²R’òŽ„CƒEC+¶RX,\‡ Áýë×ÅVþ$á›™31mÂL l²5>GKK +V¬$''ãôéÓ Á7pïÞ=ÈÊÊŠ5O»víÌÓ{÷îaúôéhÛ¶-·]C£åÜù7EEEؽ{7¼¼¼ðã?¢C‡¸qã>ŒY³fAFFÓ§O9Ï£G°cÇtìØÎÎÎÍr3@¡P(_%ÏJÀ.c ìSwRƒÁòŽ( *Э”5[¶ ð»ï0{Í\9|¸IÖ¸yçÒ¬­ñÇÑ£˜<~¼ØŠkCQWWÇܹs¹ï7oÞ [[[<|øçÏŸ‡¯¯¯XócÉ’%ÐÔÔ„……|}}Eºï(++ó(Ïzôè{{{¤¤¤ˆåˬ¥¥5µ†E…ïÙ³®®®ÈÍÍÅš5kзo_¨««#&&PUUÅm×ÓÓƒ½½=æÍ›‡ÒÒR¾¹²²²0nÜ8˜™™¡sçΘ2e ŠŠŠ0lØ0,_¾œ+—––WWW„†† ÝO^^^û~ñâ¦M›†.]º@[[íÚµƒ»»;nÞ¼É'ûûï¿ÃÕÕ, Ë—/‡³³3ÔÔÔ˜˜(tþÐÐP¸ºº"-- üñúõë===¸¸¸àÎ;Ç„……aРA022BçÎ1aÂddd”ŨQ£Ð©S'hiiÁÎÎsçÎåóóþóÏ?áææ}}}ØÙÙaêÔ©øøñ#̧çóÚµk:t(ŒŒŒ0xð`œ;w““ƒ3fÀÆÆfff˜={6ŸKLHH\]]Q^^޹sçÂÞÞ¦¦¦5jÒÒÒxdáêêŠS§NáÚµkðõõ…¡¡!/^,ôœ~iÐ BÉiiA„åIå¨Ê­Ø§ê¨ Å–¡Ž…‡‡#00·oßnî­´xZÆ'öÀÜÜÛzZk¶lAº§' {Ä|¿z5^±Xú:tíÞ˜˜JJ(ûê+l>~\ª¾ÐŸÃQv”””`hhˆªª*£ªŠ÷RMM Ö®]‹òòr‰5÷ƒpþüy¸¹¹aåÊ•ðóóCzz:¾þúkìÝ»Wà˜¹sçâôéÓ5j¦L™‚'Ož OŸ>ˆŠŠ¹Þàää„ÈÈH¸ºº" OŸ>Å€x¾øøxôíÛGŽ““‚ƒƒ1~üx|üø bÛçTWWãÝ»whß¾=ää¤ó*99·nÝÂ÷ßÐÐPôîÝÓ¦Mƒªª*ÊÊÊЧOüôÓO¨¬¬Ä¼yó`hhˆÍ›7£wïÞ< Xvv6zôè3gÎ`äÈ‘˜6mÒÓÓ1dÈܼyqqq\Y‹…[·nñ)eŸî§²RpV.\ÀÇáåå…_~ùˆ‹‹ƒ››"""xdß¼yƒ[·naìØ±ˆˆˆ@ÿþý1eÊ() ÷iLKKí[·0{öl,[¶ VVV0`¢¢¢0`À>å{Íš5ðóóCAA~øáx{{ãܹs°··ç»¡:pàzöì‰ÇcäÈ‘øõ×_1xð`œ>}………\¹™3gâûï¿›ÍÆ’%K0`À:tööö<çŽs><ˆáǃÍfcĈ¸ÿ>ÆŒƒèèhx{{ãÒ¥Kðòò‚’’¶nÝÊç/ÿêÕ+ܺu ¾¾¾ˆ‰‰A`` Ƈ˗/£gÏž>>ܶ„„€ ÝOVV·s]äççsÛJJJøÆ555âààÀÓ>uêT€ôïߟTUUÕu:¸p®%RXXÈm #È?üÀmKJJ" ÄÙÙ™TTTpÛ?~LrÛ233‰ºº:qppà™—Bª««¹×ó£GƒÁ Æ #555\™ˆˆ€Œ7ŽÛÆ9ŸÈãǹíOž|øÀ×Ïd2ajjÊÓöù1Šƒ——Ï{555XZZ"77—ÛÆ¹n Ä7~ðàÁxóæ ’““a``À•íÙ³gëräÜÝÝyÚeee1hÐ œ8q)))<¾ôŸïUFFÖÖÖxÿþ=ìííyúlll@`ÊÇσÁ``àÀ8zô(˜L& …®I¡´&ØålÇ T0P€’EËI]G©T–‘_V õùÿúùáòŽøçï¿´ÆÍ;woaÁc}æPÖ¯6¯[×ä9Œ¹ t]L:6lÀÞ½{Œ}ûöÍfcÊ”)õZoÉ’%øý÷ß1nÜ8¸¸¸ÀÆÆzzz8räÖ¬Y#ÐÒ¨¯¯Ï×f`` Vñ'—u‡Z*Ñáÿ7AÊÊÊxûö-°ÿ~ìÞ½[·n…³³3BCCaaa!Öñ=yòC† ±±1®]»]]]±Æ56Ÿ¦ À TÓÓÓx,;v„²²2€ZßvB÷ý§(**ÖKþüI'NÄ©S§0sæLtëÖ ;w†¦¦&6oÞŒ]»v ¼.>?FqøÔÒÍÁ`ð쳸¸ö˜s} GÙä]r®?UÕº£ú‹‹‹¡¨¨---¡srÖµWaí€àó-è8„}þËùh_½z…N:5÷6Z%l6‰‰‰ƒ©¥IÉÓJÁÿsZ’ïóçTTTð=1¤ðBh)ÑЪ>, GoÜ@õÒ¥‚ttðœÍÆëׯ”zɦMÈ›:Up§­Ðâо}{xxx௿þŠ+°ÿ~ôîÝvvvõšçСC°²²ÂÁƒyn êʪqóæM|õÕW%77„XYYñ´§§§ãñãÇ"Ç76ýúõ¾Ï½¤¤÷î݃‘‘Wæ\cÛ·o—hδ´4$$$ K—.MV`èó5™L&^¼x^½zñ¹èü—¡Ê³ä´„J„¥¯JQ] 8]¨jwU0äZnáª<‹†Z [ v:`­šP×ãg¨ Èg¡B°ÜÓ³î5ÈN˜€¼¼<¡~¾Òdذa055Ehh(455ë<òòòèÑ£®^½Š}ûöÁÇÇL& .„ššÊË˺ÄÅÅáçŸÆ´iÓPZZŠ%K– ¸¸kÖ¬©Ó½¥oß¾˜0aöìÙƒ   ˜™™áLjÅ_ý…ãÇC]]+W®„’’¼½½annŽÌÌL„††‚ÉdÂÛÛ»Îãb2™8p >~üˆï¾ûN ’9nÜ8nŽçŒŒ XXXÀÓÓ—/_æÊ”••áàÁƒ€””µù‘9?L}ûö­·ÅÿS¶nÝ xxxà·ß~Cß¾}¡  €wïÞáüùóPSSâE‹«W¯ÆÙ³gáëë‹åË—ÃÌÌ —/_ÆÝ»w¡¬¬Ìó9ÉÊÊÂÇÇaaa˜?>…;wB[[»Î@O ¶¤¼±±1Ž=ŠbРAxóæ fÏž }}}>ÑM‰——\\\päÈ888`Ô¨QÈÍÍÅ¢E‹PYY‰5kÖp]‚\\\0fÌìß¿ŠŠŠ˜:u*LLL’’‚'N`ñâÅÐÖÖF@@6n܈-[¶ cÇŽ2d2220óÿñ¿ýö[“Ï¥K—°k×.Œ=ÙÙÙ ¬\¹²ÉÖ¤P¤°Â)Ôz6,¿>¥Ð<É?þ[4f»/AiìD±zõj€Ìš5K¤¬ 4v±±±¤]»vÜ”YˆŸŸÙ³g@nܸÁ•å¤û矈¥¥%ϘeË–ñ¤#„?!µ)ëV®\IyÆËÈȾ}ûrÓ~-[¶ŒÈÉÉñÈ‘€€RTTTçq~šLØ+%%…+Ÿ’’BOOOžy²²²êœcÓ¦M"Ϲ ´qŸòøñcÒ½{w¾¹õôôÈþýûydãââHŸ>}ˆ¼¼‡ÉdBYYYà£ð²²2¼zõ iiiÐÕÕ…µµ5_€_ii)âââ:p3ˆ¢ººš'‹ƒ ôôô¸Öòššäääð•±Ùì:­­êêê"3ž””” ¤¤úúúu>ÉÈÈ@BBª««abb[[[¡ÖüŠŠ ÈËËs3utèпþú+~üñG¹ÊÊJ<{ö yyyprr‚¦¦¦Àýp® KvAAž~üSSS888ü RSSØÚÚò€á\jjj|ëåååÍfó]»œïϧߵÀÀ@Ù?âäÉ“°µµ…‹‹‹Äû¼uënÞ¼ &“ }}}ôíÛb­ªªÂÇ…ŒŒ TTTÀÔÔîîîprrª×>JJJðäɼ~ý555øî»ï  @ƒw(JÓP–\†ªUûÔÔ £Dí”_*T¦´(-Z$ðÑ‘™™víÚ…Áƒ7ɺ‡¢E‹pñâEtëÖ­IÖ¨/÷î݃ŸŸ˜L&ddd`aa¬¬,î “É„Wž‚M›6áÇDEEtuu¹¹¹PRRÂúõë1sæLž5ÒÓÓ„©S§J¤@B0nÜ8=z –––HII›Í†——Ξ= YÙºsŸÞ»wnnn ÔÔÔ ¤¤Ë—/Ç„ pàÀ0¢Ÿº»»ãÆ`³ÿäñ÷÷§ 4…Bi2„¦®cj½Ô¤»ŠT¡·FR‚:ä‹OÛ¶m‘ŸŸüü|<~ü ,@FFüýýñþýû&Y“Åb!;;•••M2})//ÇèÑ£ñáÃ=z?~Dbb"JJJ‹yóæA^^žg̬Y³0þ|ôêÕ ¯_¿FNNrrrðòåK888`Ö¬YX¸pa£îóÈ‘#8zô(ˆ´´4$&&"33>>>¸pávîÜ)rŽöíÛ#<<yyy(((@~~>lmmùÜ1bccñþý{Œ5 ÿcï¾Ã›*ÛŽ3ºiiKi)ÈV–ÊPÜ2^ñÕ²ôÁ‰¨€þDP­ Ž”¡ŠTDÄ*²¤‚jÙZiKºw’óû£mèHš6I›¤ÜŸëêÕ䜓'OÇIî<羟硇2mW©T\ýõ•Šc:ÄŠ+¸é¦›Ø¾}{¹àúºë®ã×_妛nbÉ’%Lœ8‘:TýG¨¦={öÅWJSDš4iÂŒ3øúë¯Ù³gO¥Q7oNóæÍM÷µZ-½zõâµ×^cÈ!ìÞ½›ñãÇ[íˆ L·?ÿüs[~!\ʼyó$ÚF¥E„µ™µ? óûÜmᔊ.]º$yÐVH]Gõ˜—KÆ.ó µMí­vZAÄàÁƒùöÛo9sæ Pœ7;sæLÞyçr—ì'OžÌ;#···iÛòåËY¾|9ÿýw¹6.\È´iÓxå•Wøàƒ¸ÿþûMû_zé%SüƉŒŒ$;»xª"Ö¯_Ï‹/¾H“&Møå—_L»öÚk6l·Þz+Ó¦M£¨¨ˆ=zƒ^¯çÍ7ßdΜ9èõzÓcxà>üðCÓ‡ŒŒâ¿±µô‡Ro¾ù&F£‘Y³fU™ðôôdæÌ™Œ1‚·ß~›?þ¸ZíZÓ©S' øAY¥÷K÷Û¢4U¥K—.6·!„;“àÙvµ]Dh,0’ý§ù©ëÌÈ‘#iÛ¶-111dddŤI“¸pá‚Ùþnß¾eË–±iÓ&Μ9c Ð_xá^ýuFͶmÛ8vìÏ=÷[·neâĉ¦ÇßxãøøøðÕW_1cÆ Ž?^åïçÈ‘#hµZ `ñ˜{uJÅ_ýUe[5qÿý÷ÌܹsÙ½{7ƒýû÷óúë¯À¨Q£jÜfjj*6l`Ö¬Y„‡‡ÛÔ†BÔ¦ìCÙ ̯œâ³µê6„{“Z¸´“'O2gζlÙ‚¯¯/ýû÷'11‘¥K—ÁÊ•+iß¾=­[·fÁ‚ôêÕ‹O>ùÄ”ûvôèQEáù矧W¯^øùùѼys|ðA† @XX˜©¯eË–DDDAãÆxë­·P…÷ߟ=zÀàÁƒyöÙgÉÉ1¿tkjj*+W®dРA´jÕŠnݺqòäI–/_ÎwÜÁ§Ÿ~Ê€¸îºëX°`<ò_~ù¥)¸ eõêÕ4kÖŒ¹sçÒ±cGZ´hÁsÏ=ÇùóçË=—Á`àÌ™34oÞ¼Êkoooš4iRi$ÞáááÄÇÇÓ´iSúö틟Ÿ7ß|3üý÷ß´oß¾Úm• 6nܘ‡zˆ6mÚO³fÍÖ_!„°›™¿gšÝ¥öVÓàz÷œºNÔŒÐuDò«ïܹs¨T*T*íÛ·çÕW_%00Õ«WÓºuk>ž÷ߟ£G²bÅ –,Y‚ ªÝVÇŽ™:u*ï¼ó±±±,]º” 6°dɇôUw#+Ú®6W"´4uJ­Â¿gý˜ºNb뤈°Ž8*!_ã§qÚÔ8j¯ºù¼Õ°aC¦OŸnqéH«¹¼t›¿ÿ•±'žx‚ñãdze˾þúk6mÚÄÿþ÷?-ZÄÔ©S«ì‹J¥"((ˆ'NTÚwùòet:ÙÇùúúVÚVZØøõ×_[ü(;›GEZ­–#FÄÝwßÍ?þÈÿýßÿо}{vîÜÉ©S§hÛ¶­ÙÇ;vŒ‚‚ºvíjñ9jjÆ øúú–›àßÿþ7/½ôQQQ¦>ÖÔ¸qã˜5kQQQ¦ B\M¤ˆÐvµUDX˜\HÞé<³û|#|ÑÖ°JŠ­«é«H@ïz»÷ô8ö*-þûñÇyâ‰'ÊíûñÇ*åÞjµZ† ưaÃX±b×^{-«V­2Ð>>>fS:tèÀž={8{ö,­Zµ2mÿé§ŸÊÍbMÇŽâ©÷î»ï¾j?®¢víÚ”›Å#22’;w2wî\>úè#³›;w®éXGIJJB£ÑTm/Ý–””dsÛ¾¾¾x{{STd~•/!„¨k™{Íç>Cqñ ¸zH ‡p;½{÷¦uëÖìØ±ƒ””ÓöŒŒ ¶mÛFXX˜)?úâÅ‹ †r÷ôôÄßߟÔÔTÓ¶ÒUø>\éùJƒô¥K—š¶ååå±jÕªõû¾ûî#<<œ™3g–{îRÉÉɦ¼æØØX¢¢¢*õ`þüùtïÞÝ´-22’ž={²zõj–/_^é1 .äóÏ?§wïÞ¦TG¸á†ÈÊÊ*73ÀæÍ›INNæúë¯/·}åʕ̞=»\ðWéç4¬\¹’ÄÄÄJ+$FGG3{öl‹3 !Dm(ü§ìCæ§®ó ÷Ä»¥{O]'jFF k **Š-[¶pþüy6nÜHpppµ+ ùŽãééÉ¢E‹9r$7ß|3cÇŽE«Õ²fÍ’’’X½z5 WAøá‡|ôÑG >œ¶mÛ’““ÃÏ?ÿÌÑ£GyõÕWMm¶iÓ†¶mÛòÒK/ñÍ7ßÎàÁƒyà3f _}õ‹-âǤ}ûöÄÄÄзo_ÚµkgvÞes6lȪU«5jDFFÒ¶m[.]ºÄ_ýÅÖ­[ùûï¿iÕª.\àÁ¤E‹ôîÝ›¶mÛrñâE~þùg 3M½Å©&QQQ<øàƒLž<™µkךŠ÷îÝKLL 7Üp7n4ÛßÍ›7[,X*;ÇuEsçÎå§Ÿ~b„ ¬^½š{_ý•_~ù­V[)zåÊ•üõ×_LŸ>Ý4j={ölvíÚÅ­·ÞÊ5×\ÃéÓ§ùå—_Ðét4mÚ”wß}·\ÑÑѬY³†šoâ ›7o®|ÈŒ3èܹs¹ãûôéC“&MÊdFFF¢Õj‰‰‰aóæÍøúúÒ®]; Ääɓ˭ Å « 0À´èL)SžyÅ¥¼Ë.¬#„»•mçè•sŽäP`~ Ì£±~]¬Ï‚äNd%BëTŠ¢Xø<%,iÒ¤ ‡ª²à«¬ÒœÓª^«sŒp-üñ={öä­·Þâå—_vvwÌ*((àÞ{ïeÇŽ|üñÇŒ7ÎÙ]Â)î¸ãî¸ãŽrWn„¨c¡‘Äå‰è3ÍO]öï0|ÚúÔq¯j—Ä$ÖI´Õ°zõj’““M÷9ÂØ±cñóósé ÔËË‹èèhºuëÆã?Î×_íì. !„[Éø-Ãbðìá[ï‚gQ=WM Gaa!gÏž5]7Çh4rìØ1.\¸À7ÞX鹸z½ýöÛŒ;–àà`ôz=™™™4nܘÏ>ûÌåÿOüýýÙ·o999•R8„BX¦×é-μ¡ÒªdvŸ¨ÿêý»éž={˜2e GŽ¡°°Ûo¿ÝlQ”N§ãÞ{ïåÀ„……qñâEÞ|óM^yå‡ôCŠÝÛž={ؽ{7çÏŸÇh4Ò¡CzöìY)×Uyxx¸M_…ŤˆÐvŽ*"¼¼í2ŠÞ|¦kÀ-x9g]†Ú&E„ÖÕû£ÑÈwÜÁš5kèÕ«—Åãž|òIþùçÎ;Ç… X»v-¯¾ú*ß}÷Cú!E„î-((ˆÁƒóä“OòÔSOѯ_? H…µJV"´#V"Ì=‘K^¼ùES´ µÞZß$f±®ÞÐ}ûöeÁ‚<ôÐC4lØÐì1ÉÉÉlÚ´‰±cÇÒ´iSÆŒCëÖ­ùàƒLÇ=ÿüó´jÕ •JE¯^½¸÷Þ{«Ý©fBQRÀe;{W"T iÛÒ,îê„ÊCesû®Nbëê}]¤¨¨È´R\©.]º°oß>Óý pöìY’’’8{ö,[·n­Ñs|úé§DGG—û2· †BˆÚaîuW¶É¶ŠÛ2÷eRt¹ˆâ~ -·| ý‹îòšäY|¬;n+I^~ùe>ýôSU“HLL S§Nå¶wéÒ…”””r«¦ !„¢þÒgéÉØ™avŸJ­¢A·uÜ#ኮªy H~~~¥"ÂÅ‹3mÚ4RRR 1m_¾|9“'O&--Í®|×ÈÈH øüóÏ«<ä’BÔwœZŠmgOaê¦T²ÿ2¿d·O jdo÷\ÞèÑ£ñòò’˜¤ õ~Žê(]%>>¾\}üøq¼¼¼R,V„üÂÂBÒÓÓí~.!„å¹ã•DY‰Ðv¶®D˜>ŸìÃæƒg¯† »®Žiëd%Bë$€®¹æŽ=Ê-·ÜbÚ~äÈÓ>{UçñóÏ?¯r”Z!„íî¹çgw¡F$x¶ME„ ¤}—®ËÞˆÚçêÈ|•àÙ:  ^½zΘ8q"ùùù9rÄt¿¶½ñÆL:µNžK!®F¥W…0'ûÏl ͯÙàÙÄÿîþuÜ#áÊê}‘‘ÁŠ+8sæ EEE¦¹5üq‚ƒƒÑjµ¼øâ‹<÷Üs´mÛ–o¼‘%K– R©xòÉ'뤟-Z´O|B!„ó¤m·…C!„œœc9ƒg`ôÊ IDATzÔq„»Z!„Wc‘‘´, B¥•ÊAažÐu¤ ÀüÔ8Â:)"´"ÙNŠí#E„ö‘s×vÕ)"Ì܉>Ýü;>í|ð½Ö·6ºæ$f±Nè:R•…y .¤°°ÐÙÝp[¥Ó6ŠšKHH`ݺuÎî†ÛZ·n|±ƒœ»¶+]‰Ð}ºžŒÝf÷©4*‚×V×Ü‚Ä,ÖIaˆŒŒäÌ™3<ùä“tîÜ™Î;;»KB!ÄU+%*…œc9f÷5¼¥!Aý¯Ž%»+Š%66–+Vкuk)"¬‚Œ@×OOOe*6!„‰òNçY ž5 44¼ýê›u£”··7xzz:»+.ïêšØÐ‰š5kÆÀÝ !„âêe„´mUÞ„Úëê[l×®íÚµcýúõÎîŠË»zÿKê˜$äÛNŠí#…H¶“"BûH¡}äܵ¥"ÂÌý™&›¯©ñºÆ ¿ëýj»knAbë$€®#’o;)"´"ÙNŠí#E„ö‘s×væŠ 9ÒI7ÿÿ+•J¦­‰YªCŠ뀬è#„B8WêæT²f›Ý×àÆ„ ©ã¹.‰[¬“h!„BÔk‰dÿi>xV{« ºçêœuCØNh!„BÔ[Š¢ ûN®·7¼½!?MÝvJ¸=  ëˆ$äÛNŠí#…H¶“"BûH¡}äܵ]Ù"œÃ9œ7ÿìÑØƒ†7_½ÓÖY"1‹u@ב_~ù…ÈÈH¢££Ý·#E„ö‘B$ÛI¡}¤ˆÐ>rîÚ®´ˆÐX`$í'ËÓÖ –H¨Œèèh"##ùå—_œÝ—'E„u@’ñ…Bˆº—öcšÅ%»}#| }(´Ž{ä$n±N>w !„¢Þ)º\Dæ¾L³ûTZAÜ·pÐh4Jj£“ÉJ„B!„¨wtÛt(óÙn À#È£Ž{ä8“^x€•ï¾ëäž\½dºŽHB¾í¤ˆÐ>Rˆd;)"´ÚGÎ]ÛeÏæØ¾cf÷ij ¼5°Ž{ä8F£‘ÕÛ¶±zÛ¶Z{o”˜Å:  ëˆ¬êc;)"´"ÙNŠí#E„ö‘s×6Š^!yk2íýÈìþ þA¨<ÜwÅÁI/¼@Á A d‰v4‰Y¬“"Â: ÉøB!DÝHÿ-ôíæ—ìönåM“È&uÜ#Ç1øvéBÁ²exMžLî‘#¨ÕŽ•¸Å:B!D½ ÏÔ“ù›…ÂAµŠà×q«tôµÔêZ…U“Z!„nOÑ+¤D¥`,4ŸìßÃÏ0Ï:î•c¤±(!Uß}^Ù1p`­æB Ë$€®#qqq¬^½šC‡9»+nGŠí#…H¶“"BûH¡}äÜ­>EQHN¥àBqñ›Q1ræòÓ~¯†À;Ý«pШ(lÓéyô(ÍöîåÙ—^B2¤xô¹”ZMÁàÁ…>tè«W¯–ÿ½jºŽÒªU+ÝëvRDh)D²ÚGŠí#çnõ¥ÿœNNlŽé~‘¡¨\aà]¨}Ü#ä9›ŸÏÌ3ghµoÿ:|˜/SR(4àÐ!0 ò pØ(t`` ­Zµ’÷Üj"Â: ÉøB!DíÈþ+›ÔM©÷{]ãEøøppá‰7òF6¥¦òqR;ÒÒ(˜ùk4\³z5q¡¡(æh@õý÷<^Xè°y¡%n±Î=>Ž !„BT.ŸË›/[ܯ Ô::ÔeƒçCÙÙL>q‚¦{öðð±cl/<÷mØÿFDp±W/Nÿþ;J¿~ÛQúõcõ÷ßKºc’•…Bávô:=)R,®6¨öRúp(?M÷¬jéz=ë.]âãþá`VV¹}M<=y¬IÆ5iB__–-[†1+ ïéÓ«lוÅ{ï½ÇäÉ“k­ïâ  ëˆ¬êc»øøxÚµkçðy.¯qqqDDD8»n)77—ÔÔTZ´há쮸¥„„BBBð- DÍȹk™!ÏÀ¥ÿ]Âk0»_Q)¤÷H§E¨kœ» ðKz:'%±1%…ü2#ÅZ•ŠA1®IîmÔ­ªüpùäÉ“ë<(.((ÀËË«NŸÓÝHDRGdUÛI¡}¤ÉvRDh)"´œ»æ)…” )].²xŒß=~¼¿ñýZëƒN§cô#X=îbAsΣÝï¿sסCüïÒ%Sð|­¯/o·iÃùÞ½‰îÜ™ûBB*ÏÎ"1‹uRDX$_!„pŒÔ¯SÉþ3Ûâþ€^¬ÝSî¼ÿ~vÆÄpz×.Z¶lYn_‘¢°95•Oþù‡ïu: eÂ,?†Q3.<œ¾ Öjí!q‹u’Â!„B·±+£ÊàÙ§ƒÁj7xÖétì>}ã+¯pßøñüõÓOËÉáãþá³þ!¥¨üèx¯€Æ‡‡ó`h(þ×Êɶq¹:==ü‘ØØXbccINN&""‚Î;Ó­[7n½õVgwQ!„u,÷X.iÛÓ,î÷lâIèµ?ãÆˆ (z䈈àXVoÇĭѰ/³ü⡞žü;,ŒqMšÐÑϯv;%êœËä@§¤¤ðòË/Ó²eKzè!>ýôS222çСCÌž=›Ûn»o¼‘/¾øÂí¦j‘"BÛÉJ„ö‘¥l'+ÚGV"´œ»W\, eS XH:Õøk{8 •gqôl4kå÷wY§c×éÓУú±c™>y²)xÖ”nìÔ‰ ½{³ m[· ž%f±Î%èŸ~ú‰V­Zñǰ~ýzòòò8sæ ?ýôQQQÄÄÄ––FRR#GŽdêÔ©Üpà ÎîvHB¾í¤ˆÐ>Rˆd;)"´ÚGÎÝbú =ÉŸ'£™žU*BG‡¢ ¸’Ç­wÞi÷sŸÊË#*9™Oâî¿þ¢ÉÈ‘èËFD€ÑHËŒ æ´n͹^½ØÒ¥ ÷7nŒ‡‹ÚBbë\¢ˆððáèT*ºtéR­ã‹ŠŠØ´i£Fªåž9Fdd$»ví¢oß¾ 6ŒaÆ9»KB!„Ë3ùç“(¼daEG5Æïºò£¼î¸ƒã—.³v-Ý»w¯ÖsÉÏç¬,deñGV³²HÓ믕¯¾ K–”`\]¿üÒ” í΢££‰ŽŽ6Å,RDh™KÐõT³ !„å•i–(F…äÏ“É;‘gñ˜ ~A4ìS~6‹S§NñÈ#èŸ~šK—rî÷ß+=îl~¾)P>•ÅìltE–§ÅÓªTx¿ù&Ù˜Ò7Êíé%NFEUš‘Ã]IÜbËdddàçç‡V«5Ý߸q#ÁÁÁ2z+„B8€µ¶6}ðÑGL™1ƒ‚äd‹Ç¤mK«2xnЭA¥àà¾ñãÑÍšqÑÏ-{÷RØ®²³Móå*‚eJEG__zøûÓÝߟþþ´(,¤eJŠÙàŠs¡ËÎÈ!ê?—È.ëüùó“””@vv6­[·fÒ¤I >œ×^{ÍÉ=´$äÛ®¾æçç×jû‘cÇÖjûΤÓéX»vm­µ_ÛE„±±±Lwâkš¾ìåéZPß‹cbbjµýÚýÓétÜ>p ‡ªµç¨Ê ‹Stï½¼öÿgvæï™dîÏ4»À»µ75 ÐhäL~>¿ed°xÿ~â  ];¸pÃøñ ùÏqô(o;Ç:]¹àY­RÑÉÏG›4aiûöìéÖ¬[oåpÏž|ÁSÍšqs@?þxñÌ–”ÌÈqîÜ9Û~!.Fbë\.€>xð ×]wÍ›7`Íš5xyy‘˜˜ÈºuëØ°aƒ“{h›Ú¬¦Þ¹s';wö­Y¼xq­¸wÞsgÏž­µö·nÝZkm[“MË뮫µöW~ôkþ÷?öíÛWkÏñð¿ÿ]km[3bŸ9³ÖÚŠŠbÀàÁµÖþЧŸfQT”Ó> ö1‚ _|Qkí÷<˜¨¨¨Zkð¨Qlß¾½ÖÚ¯Jff&½ï¾›cÇŽÕJû©©©Œ›8‘ÔJû#&LÀ8n÷OšT+íWåƒ>"§[7”1cx×L‘nÞ‰<Ò¾/ž®®HQ¸\TÄ™ü|þÊÎfWF›”tžêœBC Û³ï;i³o·ýù'Óžz ã¸qPTQQЬC|4JOOgçöíhvïF³d‰Å/ƒ^σãÇ×Éï°¶I¡u.—ýÙgŸ±jÕ*~ûí7nºé&z÷îÍ’%K(,,¤aÆÄÇÇ›lwIÔ–-䦦ÖJûMK.)%þñG­´_•ÜÜ\ü[´àñÇcÅ»ï:¼ýcÇŽÑå¾û¸¡m[|ÿ½ÃÛ7x……1gÆ ^œ6Íáí[sï˜1|w⯠ʛ¯¼âðöý;w&ûÙgiºr%÷ïwxûo¾ý63çÏgëÚµ 8ÐáíWE§ÓÑä®»ÐwïμÎkåïפ{w.çäpiÏ‚ƒ»8ñcÇèúì³o¼‘Ç YY çOURSS ¿ë.ü=<ÐÕB¦Óé½å‚}}I>x°VÚ»åùùñO-™U¹gÔ(¶‡„ÐöÈN–¼_9RŸ¡CÙÓ¡Íû.=wŠ/F;{61‹×éÌV;“õÎ;àë‹**ŠÑÜ=y2 HMÌ#dC9yz2õzÓ²×¥ò½áÛAåo¦áÄDXº*Î^rñ"a‹sbß>›1ÉÏÏ'>>¾ZÇúùùѶmÛ?‡«‘hë\.ú¦›nbòäÉ ˆ‰‰aÑ¢Eh4 ÙÙ–W!rUùÞÞlß¾»ï¾Û¡íîܹ“äÓíÛn»Í¡í[3æÉ'1>õÿŠâ½ùóQ«{Qcèãc|í5޼÷/^¤Y³fmêŒFâõ?¬ó:;;›ŸŽwßeáÔ© W–ŒòЦ —‚‚Ø·o½zõrèsÌ]³eÙ2þ=}:)u@›3èÒ…7¦Msøßoûöí¤†…a¸ë.FL˜ÀÏ_}åÐö‡Lš„á‰' ,ŒÕS¦ð~-œ?U>~<ú‰ÉØ¿Ÿ/6nd䈎m &pyÇŽZyí1aú H­¥ö«’™™ÉÎøx˜4‰³±±;vŒŽ;:¬ýÔÔTöŸ?Ó¦‘øçŸ8p Ú3IT‡éÜôãÇ3ôÉ'ùõçŸÉ7«õ•WÝã †JÛ²¾ù†ünÝÀ×eøpÖ=ý4ëú÷Ç'Wað·*²rÌ÷Û ŸïRᬥ«—×Tøzýµ×8gn¸Y3Rýý‰?tȦߣ··7]»v­ñãDýærôµ×^Kpp0]»v¥¨¨ˆ=zЧO xº;À-«\•öíóâ‹)yèÙg1¼ø¢év]ŽBçææ²õàAˆŒ¤07—§^xÁ¡£ÐÇŽãŒJ-[RôØcÜ7nœCG¡F#«¾þeÙ2ò xgÑ¢: ¢G=ñE£Gƒ‡ùwÝÅ«sæTD…BE¡Àh¤Àh´z{Ú¢E(óç?vìX=ý4_þô¾j5¾M¥ï5³ôÍ·ß&ïÖ[!<]óælÛ¶Íá£Ð Åù¥?W¡ÑH¢rùrñbS¦Û»7/¼ó“¦LÁS¥ÂK­ÆK­ÆS¥ÂS­¶ia²1/¾ˆáå—!$„Ýk×¢Ó鬎B…¼’#×`°xûT\g½½!<€Â!C¸cÒ$"gͳL¿Ëþ ^%ßK¾r·Köi«ù7LMMå÷‹¡KŒmÛòÄk¯94€Öétì-ùû[¶dô /8tºt)e¦LÁЪU­¼¶Våþ (zøa€âÛ'ž ö×_QŴ·•5?jº}иqè{¬¸ýqãááwßöÓuÜü0ó5xjh¨Õ Õ Ñ˜¾‡ee·†øšE>uêõzhßÞìÓÆçþI“ÌÎÈ!„-\.€øóÏ?Y±biiiüç?ÿ1m‰‰aüøñø–|ru+j5)aa¼ÍeF‰UeÞðʾõY¼]æø#{öp)$BC¸ÂÒ­[ézË-î¼y¯MšDáÈ‘ R¡ ÈÇO?Í—_ƨRaPôŠ‚AQ0À•Û¶WÚ¬;äIpátìȡի™ºw/Áááx” %_¦Û¶›ö©ÕåS©xgÖ, ­eäHfM›ÆÀ‰)R”+_F£]÷ ËÞ/s;/'‡mGŽÀĉ(÷ÝÇÜ)Sˆîßßb@l¨I¶Õ֭н{ñ(OB´h.8˜»Ö­ #eZ•Êl`méû²Õ«Q/ÀɈ_äÙk¯½èVz Ëü,…ÕØW`4¢·ô3¿ñ”)èQFbÁ”),¸é&³‡— @½ªq;}ÿ~’ÃÂÀß’“)zä"F¦ûüùUÈÕÍe~é%˜:õJÿûõã·§Ÿæ·¸8°cñµJe ¦+×eo}áŠJ.ÑâëKzǎܼx1×ôëgz½QqåµG¥RÕhûÓ¦p&'CƤ†…1põjš–¹bÏkß–)S®t…„FÿÕ« ½ùær¯)å^_¨üzSý•ŽÉÎÆø÷ßPš;ܲ%§Õj|?û 1¸“‘Qüšwýõ¦s÷Ÿ ¸éóÏ¡CûÛç¨X·0a,Z V»à¥Vã]ƒ¯Ó7ò[Ïž(ßÇ ã®±Ÿ²hBK4 ÌÿÿÞH`Ï@‹ý1ͼQJQàâE¸æšâû%3r8z4¿¾*((ÀËËËÙÝpi.@7lØéÓ§WÚþøã;¡7’–†qÆŒâŠûeËÓæôéÅ“º—0ŽÇ3Ó§ç€Õ¶‚øë/SˆFCÑàÁ š<žxÂþöÏ+™hÙ²øEýé§1FF²ä¹çà­·ìo_Q`Ëxï½âû^^ä÷îÍõ/¿ ¾”mÖÛoÃèÑWî{x`¼çŽ~ø!”ŒlÙeËX° øöºuÅÿ+ãÇÿî*.PB¯(dêõX®{/cýz¸í6(}mÔˆÜ-xó‹/ÀBë0YYpùrù餼¼ o_ظÑì߯Ðh¤À`¨Þs,Z³fÁ¥K°k<ü0)Ÿ}ƶs犃j{œ;¦ÑgÔj:V­²ëü1* ùŠR)o´œŒ HO‡ÎM›”Ñ£ÙÿòËìwDlV$%ÿ}Ö­ƒ¾}QÆãû7ÞpÌk_ÙöK(ãÆñãë¯;îµµ*‹•ûðŸ[K—^9çìm¿ôÃMé¹;aBqà[’ÎhNu>k³³‰º|cũؚ7GÀ[F#×víZ퀸¦>ÿåw*mïqØ“¦M{³ó·ß¸ÓL¢_g?ÞQyººRIIIü}èegŽ01œ;‡¦uë+›22˜øÌ3ܵ«Æ}¿Ú\ºt‰-Z8».Í%謬,ükø¦”™™I@@@-õ¨„†BHHñ§áƒ¡[7ûÚ;|¸ø ¸dôÙôááÅûj;_kéRxðÁò£eÊ(ŠÝ£hÊÒ¥(¥—Ÿ}¶ø{§Nŕթ©Å¿K{|ò üë_ -s Œ ÏkçË\Y3é×Ï)SˆíÙ½JUíQürÇ”¹mißî7ß$½âÔ†¾¾Ð¥ -öï' dùc[SçÌ!¿4À,ûaðškÅ»$x+[¿^ö:ƒÅÛ%Ç_^¼˜ÂŠÿ’×Öf±±øßtš’«M¦ï˜å¶Y8ÆÒ~­J…>;›ÅÉÉ{ö,ÿü-Z nЀiF#MJRlÁÏÑ阑•…¡ôõ»ôÜ GÂûÀ ݺٜ‚uçý÷c´0kŽqüx>xõUNïÙcµ[”μA…Ñçö' s,pÝuìùî»J´Ws/B†…”»QQxx8úôôÚèöUK‚gë\"€Þ´ióçÏgêÔ©<òÈ#U^68~ü8Ë–-ã‹/¾ %%¥{é ãÇüÖ[l™0°ýMäþgŸå²™QzƧѼy|¹c‡ãú\AA^ƒÐ?ÿ|ù ªûîãþM›xõ­·Lo>eߘʾ)™{ÃÒ¨T?vŒ®¾¾Ì]Œ¤Û‡²÷»ï(*¹¬Zš¡/“&QÕöBƒ11-_^¾mooT}úðØîÝ<øä“xZ€­Ý·ö6vï˜177ʬÕbì×Ö_}eWAá}|`v”Šß$ç¿öíÈ|óí·™yûí(ÏÓFP·iÃW¹¹µ6#‡N§ã© Ì/fàåEá-· [¿Þ®\ö&'ÂË/WÞÑ£ykײ±E ›gä8vì‰>>åGŸK©Õ »3gÖÚŒ©©©4ÍÌ,þ0ZÑèÑd͜ɹ’š [èt:Â.]‚Š&Àر(óæqîé§íj¿IÅ«¥ÆG?w.Çíhßš{FÂha.`ãøñDÏše׌}&MÂP’û\©ýqã˜3}ºÍ3r”Í7«ysÎ{zrèСZ™‘ãùwßÅøÊ+ Ó™¶…'kèý«'”\0ч‡óóΦ Z¤%ô¡PTZÛd„¨-.@3•JÅœ9s˜1c·ÝvíÚµ£mÛ¶qêÔ)âãã‰å?þààçŸvv·mBF“&äÆÄØ\5¾sçNÒCCË>— %=4õáõ6#ÇðgžA_’û\‘2p [ž~š¨Å‹mžQ`èãc(M ©¨S'ެYCJR’Í3rL™>}IîsEʨQD=û,ÿ­…)å dæØØ+©/Ÿÿ¾ûìš‘cåG‘Ó£øø˜? 4Ôî9æ®YƒbáR²12²Vgä(;{€9ʨQvÍÈQ:ó†¥+Eõ”ÃG¡ÓÓÓiÔ¥ ÆÉ“«>pófæÉóÏcçÇÓ·o_{º^Éý‘‘lJK3‰¶”Á€vÕ* Ο¯ñ‹p×^½8š–†ªÌ ñÔ)Ô­[Z•n;tˆ‹'OnîRx¦LŸÎ²øx¸÷^Ëååáµhù§OרmkŒF#ÁmÚPh%[1hl0P2mcMÚ߸qc¥UíV¬XÁ“O>Yn›¯¯/C† ©QûÞaaÝrË•€ÈŽ„.9xõÍØØXÞ[µ ¥¹ž}o¾™GjXŒù믿rç!hÊ×åå¡èt¨Ë¼“’¸¿W/¾øü󵟞ž^íeÁƒ‚‚¾XÔ¸‰ùìçŸÑ4jTåq†³gù)*ŠÛo¿½Fíþ÷¿‰úí74eÎIcb"ª  Te>ÐŽç×-[jüÚ4rôh¾Ú·µ•sÞËέ[þÚ7`äHNVcu6[ÐÇãT…öÏ>L« #ÒAžž|¿qcÎîH†)RÈOÈ·xL£Áðïa_±n~~>S§NeåÊ•vµsµ‰ŽŽ&::šï¿ÿžH]— ë YÑÇuLŸ5‹=GŽX=ÎøßŠ„……Õ~§„â*›Ãå­—1æZž%& wÁ»ê§¨9‰[¬s‰h!êʼ×_wv„⪃°ß® IDATbÈ6pùÛËäÏ­ò8ßk} î/Á³p@ !„¢VäÎáòw—1æU½Èg¸'!#B°iÙP!œÀ=“¨ÜPAÙ ÞEÄÇÇWÊëÕçà\ä«Innnµó–Ee äæV=ê(,sçs×eàÒºK¤|•b5xÖøkŠÚÓq!‰Ñh$>>Þaí]m$f±Nè:r©Å'¼… RXXèìn¸­yóæ9» n+!!uëÖ9»nkݺuòÄîzîfý™ÅÅ÷.’ŸgõX¯f^„=†6À±Ä YXƒ¥ÉEy³X'E„u@’ñ…BÔwú =—¿¹LÞIë³J«"ðÎ@z RKÞ†«‘¸Å:—ÎÉÉaöìÙtìØ///ö•¬¼ôßÿþ—9sæ8¹wB!„(+ë@‰ï'V+xöjîEÓÿ4¥aŸ†< ·å’ô¨Q£xï½÷¸ûî»iTfÎÒ:0þ|ŠŠ,L¾.„Bˆ:£O×séÓK\þæ2Æ‚ªsU*‚>.:꡵Ãåè°uëV~üñG–-[F`™ÔºuëFFF.\pbm# ù¶“"Bû¸s!’³I¡}¤ˆÐ>®|î*ŠBæþL.¾‘¼ÓÕunQ<êÐ; NfÚ"BûHÌbËÐqqqtèÐn¸ ÜjKEEEhµZ²³³Õ=›IB¾í¤ˆÐ>îZˆä ¤ˆÐ>RDhW=w‹tE\Zs ÝVJaÕeT*Áƒ ŽG£ºu–"BûHÌbËîÞ½›Ûo¿ÄÄDBCCéÚµ+«V­¢W¯^|öÙg<öØcdggãëëëì®V›$ã !„p{ dþžIÚö4”"롃w+oÝ×`I×p7·Xçr#Ðݺu£eË–L™2…'N R©Ðëõ|õÕWÌ;—áÇ»Uð,„B¸»¢Ô"’>IB·Mg5xV{ª ¾7˜°ÇÂ$xõ–Ë­DèããÃúõëyà¸öÚkÑjµ 8œœn¼ñF–/_îì. !„WŨ¹7“ôŸÓQôÕun]2ê$³¨ß\.€èÙ³'qqqlÚ´‰£G¢ÑhèÚµ+ÇG£Ñ8»{6‘„|ÛÅÇÇÓ®]»rùð¢úââ∈ˆpv7ÜRnn.©©©´hÑÂÙ]qK „„„ÈUC9ûÜ-J)"5:•‚‹Öß¿Ô^j‚úÑ {T*çOMg49yò$:tpvWÜRAA^^^Îî†KsÙˆÄÇLJ‡~˜9sæðÆoðÀ¸mð ’o)"´«"¹)"´ÚÇiç®2~Ë ñƒÄjÏ>m}h:©)þ=ü]"x)"´—Ä,Ö¹\aYÿý7qqqTìâ°aÜÔ#ÛH2¾BwPx©ÔèT “¬Z¨½Ô¦A·uÐ3Q—$n±Î%S8æÎ˪U«8{ö¬Ùý.ó[d0ÈÏÏG«Õ¢Õºä¯]!ÄUJ1(dü–AÆo(ëï±>í|¹/M€û^•éõzôz=ƒÁ­¯ú×—Káøý÷ß™1c#GŽä?þ --ôôôr_îhÿþýL:•mÛ¶9»+B!„IARI&‘þKºÕàYí­¦ÑÐF„=&Ás=´mÛ6¦NÊþýûÝ—çrC¡gÏž¥I“&Ì;·^}úéÖ­+W®tv7Ü’ÚÇÙ…HîLŠí#E„ö©ísW1(düšAú®t¨Æb¯>|‚Æßõß›¥ˆÐ6ƒfðàÁŒ=ÚÙ]qy.‘ôéÓ‡ÔÔTþùçgwÅ¡$!ßvRDh)"´ÚGŠíS›çnÁÅ’>H"}§õàYí£&dxa‡¹Eð RDh/‰Y¬sÉ" ð믿2eÊî¸ã<<Ü{>IIÆBát äžÌ%û`6yç¡­¿ýûFøÒhp#4 Ü#pŽ!q‹u.—Â0pà@/^LÿþýÍîwÁ˜_!„pIúL=Ùf“}0}†¾ZÑøjúW ºÈ B˜ãrtzz:={ö¤sçÎŒ;–¶mۺ̼’B!„;PŒ y'òÈ:EþÉüj6—òëèGð `4~2ê,„%.@ïØ±µZÍ?ü@PP³»ã0²¡í¤ˆÐ>RDh;)"´ÚÇ–sWŸ^2Úüg6úÌê6—Òøi¾7¿N~5zœ+’"BûÈJ„Ö¹\DÒ¼ys¼½½ñññqvWJòm'E„ö‘"BÛI¡}¤ˆÐ>Õ>ws<‡Kk/qaÉÒM¯qðì×ɦ“šÖ‹à¤ˆÐ^³XçrE„ƒAƒqçwòì³Ïº}!H2¾BÇÓ§éÉ:˜EöŸÙ² 6µáìAà=øu¬³p ‰[¬s¹ŽÒÑŠéÓ§3wî\úôéS)ˆŽŽŽvFׄB§R ¹q¹Å¹ÍgòÁ†!0•F…ïu¾4èÖïÖÞRg$„ \.€ðööfèСÎî†BáŠ.‘}0›ìCÙrlmnì7\ßµ¯Ëep áV\.€nݺu½a–"BÛI¡}¤ˆÐvRDh)"´Ïñ£ÇinlNölòÏæÛÔ†ÊC…_G?ü»ûãÕâê) “"BûH¡u‘ÔIÈ·ÚGŠm'E„ö‘"ÂêQC–üsùdÿ™MÚö4R¾Haæc3IݘjSðìÙÄ“à{ƒiþ\sB†‡\UÁ3H¡½$f±Î%Š÷ìÙCTTãÇ'88˜ùóçWyüâÅ‹ë¨gŽ!ÉøBquSŒ †Lzž"]Eº"Óm}š¥Èþ·bµ—¿Î~4èÖ¯fWWÀ,Kâë\"…ãÒ¥KìÛ·ûï¿oooöíÛçì. !„5¢ôéú+q™ïút=Š¡vÆ«¼šyáßÝ¿Î~¨<¥ PˆºàôðáÃñôô¤}ûö„‡‡K-„Âeé3ô¥Q”\Tn4Ùi¨ÑŠöP{«ñëZœÛìæY'Ï)\ƒ^¯çûï¿`À€hµ.Ê]u\æ·>fÌV¯^ͰaÜݕZ!E„¶“"BûH¡í¤ˆÐ>î\D¨( † Cq œRDarañíÔ"ŒÆ:ééÔS´ i[n›wKotk€_'?TZm¶¤>êt:z èÅ™Œ3›ÿª§©i؆½Ûöì°ç’"Bë$"©#’o;)"´ÚNŠíãE„Jñ‚$yñydìÊ uS*‰«9?÷<_àÒÿ.¡ûAGö¡l .ÔYð °b×  x‰í†·4¤ÙÓÍh2¶ ®o Á³õ­ˆP§ÓѬk3NÜpý=Æ;ï4¢£'þ†xšumFrr²ÃžObë\¢ˆ 00°ÞŽ@K2¾B8YI \˜\HaJ¡id¹(µÈ!|Ž¢òTáì6H‹G°^ͼð¹Ö•Fæ«YÛîm9Ýý44³p@"´ŽiÍ郧ò|·Xç2)ß|ó gÏžµzÜÔ©Sk¿3B!ÜŽbTÐëôåƒäÒ@Yï²ÆWƒ6Xk ’ËÞÖ4Ð8»{ÂŤ§§“—`9xh çóÏ“žžN```õíjæRô'Ÿ|R­ã$€Bˆ«˜ ù 9†òArJE—] PVÖ¿8(ÖW’ÕÞ’=)ªoÓ¦MZX_}ÒÐÜÀ¦M›;vlôJ¸T½víZ äìnÔ )"´ÚGŠm'E„ö1WD¨”C¾¡Üwc¾cc¾ÑâvÓ~WH¹P6ðJp\švQ(«=ì½’s×võ©ˆ0;7»zÑšäåå9ä9¥ˆÐ:—  ýüüêí¥‡ÄS‰dÎFå¡Bí¡Få¡B¥½r-W¶K®[9 .dñâÅx{{›Ý¯ŒF”Ââ7fc±ÒWév¥P©´ÍXhD¥V™þ&eÿ6åþ.öU<Æì>'üÌ›7¯Þå±)zcQI ¥€ÚóÊïß‘ˆŽŽfúôém·&½‚RTüUú3;r›‰ T*¨®ÜVP*mCuåøŠ©¸ýƒ­0°ë@Úµ1À.üÖ„ <‚<ðh\üåê‰Gc´!Ž ’«RÏݺRZD¸råJgwÅf©¹©|pà–§.G9£@ϪWŸU3bć<÷¥K—dàÀ )"¬‘‘‘dÊfÁ°Õ{€Š+viÐæa9 S{¨¯Ì§R!~P©Š7((åî—}®ªî[z|¥ûŠ‚ (Å·Q0ÝV¡ª´Òÿ:…ÊÛËì+ý÷T Š)ø­$»Å²ŠÊAy™€¼ô÷n.@1=¾ôogÃv•¶øƒ™J« ¨µjÐXÞni¿J«*LÉÿ¢( çJ ¦/´¡§Rg,2ZÜ^1Ð+·O_æÿ§â¯¹ôƒ§ÊT›‚kOU¹ûe·WܦöTƒGq`Ž0”üê0€Qo,^ÃPà®l/û½ô1-ÛVÙ•Þ*~Vá`*ÐiM²é+Ä£Öe!Ê:|é0K~_º#ëÈ×—,ãþ10 hdáA:hü}c’ã3‡Zç2#Ð 6ÄÃÃÃÙÝp (… †Bë9OÂM(ÅÁ¥¡¨øoj žümUÅAkm­°VSбø*õèw,J¥Vç"WQöñ©á„Ó#ßÄÃ’}KøùìÏåöuïÎÃ<ÌËÿy™Âá…TáÁiàñ•»¶ïª» ×  Ï;çì.Ôª?ÎÿÁóÑÏÓ?¢?ý#ú;»;B8†‚ËÏB”e ”C=ðl쉶±ÏÆž( —’YÉ'~²ýË8ve :JÃðë†óÌÍÏзE_ÿ8˜[ßBz@º©¨P“ !03=Û÷8$ß;::šèèhvíÚEß¾}ín¯>s™Žú,22ÝKG,uvWÜÒ™ËghܵJ.£ÚÂÜjf¢zòŠòHËM£iæÎîŠ[JÌH$È7ŸÚ{5xWÎQöhäþ²ÚÎÕ‹OêN²lÿ2þûçÉ*Ì2môdb·‰<}ÓÓ´hh>ùÔ©S¬[_¼ÀÓÃ=LÛ¶Ž}=z4^^^’ÂQ—®ïR³SÝ·õÑÞ˜9p&^Z©¶ÅŠ]+ªŸ/ÊIÌH䇸x²ï“ÎîŠ[úúÈ×ôè_õ8¨½Õ¨½ÔÅß+ÜVy©ÐxkL·+㣮·…×RDh;W-"Ü~f;‹÷-æÛøoMµD!L¹y ^ÿ(~~U¶Ñ¶m[^{åµZí§Z'#Ðu 22’Üã¹¼;òÝ+EBrÙ»V©*=1+}^ªóÙúøŠ³?8p*,Ó e`•— —Æô\1@V©ëþ…¨t& sÁuUÁx¹™ÊÌ7ÌAU”2W" £b>ð•Vˆ«ŠN§ãæþ7sFC{¦u›x|Öã¼úŸWymzqZž ûXòû¾<ö%z£Þôø [ðTϧ˜Ø}"AÞ§ÒîFè:"+ÚNV"´]…Hê’¹¯"*UñU Íû0­×4ÖXÇkÓ^C½½Š)|c}™þâôÚúë”Ä,ÖIad|!„âŠ3gÎË€ðôô´»½3&§êƒâ€ ùÍZµ–.¡]èÙ¬'=›öä¦f7Ñ)´S¥Y3n¸ók£ôQÊ?¦ök-ß~ø-ýï©Ð¸›’¸Å:IáB!D˜üÂd>Üø!ú =JCõt5ú~þâgºvíZ£¶òõùO=Îá¤Ãä©ó¬? )u gÓžôlV,ßÐä|´>V~èçCLŸ5%k— Ðcl`D£Óh d×÷»:ç´p}@ !„W™ßÿí;¶Ó»Won¿ýö:©1étk'ŽûGyT’…<Ñeêè~wÖ½½Ž‘#FVzœ‚Âé´Ó¹t„#ÉGLßO\>A1d¨F ¡k³®üõô_6ÿ ó^ŸÇ¼×瑞žÎ‰'èÙ³§ÍmUWLL »wï®Ö±}úô©“>  ëŒÚNŠí#…H¶«Í"Âääd6mÚDJj Æ£sçÎK Ù¼y3›¾ÝDh£PF Aß¾}Ö~ll,³ß™Í¯û~ÅßߟÁw fÞÿÍsX1á'k>aö¢Ùè²u¨QÓªi+¢VEÕÉÿyjj*}õáüåóiŠÐ´ûóã†騱£Ýí2dôöÝO^Q^^´oÚžmQÛ ±»ý·Þy‹×—¿Ž¡•CêÔhÎhxtð£|´ü#»Û·ø¼óß"N‡r‹™¬ÑÐÒóè³ÒûÎÞÄgÄ— ”&%§ÈJzFÅ"¿’"B]9D• â~8äç ¬³@5--Ož}–g¬·è¸m›CžSŠ­“è:ùÿìÝwxåöÀñï¶$@!”ˆD ) ri ˆ EP)¢‚DEÁ†¨÷go×¶ˆv‘¢ (]‹”«(5„j¨IHBÊîÌï!}wgwf³!x>Ï3OvggÏL6;›³ï¼ç}ãYöó2fÍœE—.]{Ë–-Ìý~.·¼5¨ÿ„AkŘ÷ã<š5mÆ-·ÜBDDDÀb9r„©Ó¦òþ‡ïÓ³gO†ß1œ=z,þÆyoò{¬üs%G_Ì›†ðÐC,¾žü‘ißLcSò&:´ìÀ½Ãïåºë® Xü/¿ü’Ïç|ÎÆµ¹¶ëµŒ¹w 7ß|sÀâp:ØíÁÿ.¾e˾›ûGO¥ßþôìÙ3`_²:İÑÃØ¸y#9grhÒ¬ o>û&}nìc:ö©S§hÛ£-ûÏîGi¬ †©ØöÚ¨r² ‹f, h"ëÎÌÙ3¹÷©{Ékœ‡£@XwY‰JbÍOkhÒ¤‰©øwÞ'sVÌÁÕÉû€ B×…2å­) ½c¨áØŠ¢Ð¬c3vÚv¢\«@8Z¢tìK쌼i$½÷‘©ã÷&q~"·?|;ù7åCt±2´>°ÏÜý ¯ÔCû‡lÿµÑ¦VÖ-[g8vNN5ck’scNÙ)³Àú•EŸ-2ý;x2söL†¿<åVJŸ¢'!äûŽl:BTT”¡ø=óï¯~õ7ÿVòÁ>ËÎ?üáw_×=ô`¹}9j 7ñU°Íµ1ÿÍùôécþ‹NiÙÙÙDÅE‘{W.¸k˜SÁ>Íζ¥Û } Q…ˆÆdÝ–ÕÜl!3BHý;•ÈÈH¿ãoܸ‘¶ƒÛâ깯ƒu•iMcØa·ÉÌË$=7ôœtÒsÓIËI+¼]úgñÇ6¿³u¸NºqX œûŽI‹º-Ê$Ë5Bk¸}z‹N-ض¥“RØE€4°'Ú™“0‡z?†óÔâÅ‹ù¾o_>U·ßoµ2pÁz÷îýI¡>I ƒ >>žiOÓZ2ÁñƒM‹6¾Ü˜œœL‹î-È”¥?GÓÀ1×ÁŽßvp饗š>vwÞžð6Oþ4J?7ÿ„“ öŸµ9¾Ãø·à»î¿‹¯¶…Ú]-ù! ÕV#mWšáVÏ^·öbYî2Ô¶nÞúö]'·Ÿ,·±k£b£8Ýý´–¸—¶âöıcíÃñk\ZƒŒ›2ÀÝÕÞ}Ðhc#öýµÏpüç^{Ž·f¼…³¯ŠÿKÕZÉf¿7›ýËçŸTff&u®¨CÎ-9P·Ôƒ¹`ûÞÆì7f3h ±Ë´¿þú+=èë—ÛA>-k-ÜZ÷V¾ö­¡ø­º´âïFC¬‡ ²¡ê쪤ïNx«~^^5šÔ gXNÙó¶ÀAh¼±1»þ·Ëïø§N⢫/"D¾çRÓ¡ÖOµ8‘|Âïø‡"¦{ Î!NÏåBÄ7¤ïJ÷;¾žá÷ ç«3_Á^6:Mÿ×”ík·ûôØÑ|²ïhíe£ƒÐvo[Öÿ¼¾p•ŠJ¾+Ÿ|%§âôx{è¡lŠÛT²å¼´¨òM½3Èm"œ‘›QÔçØ*0‹ÂÄØ£t¨ýsm¦ÍFËè–4Œhè÷®žxæ &ÏšŒ3‰¡b=f¥¦­&+æ®H›Šä©:Ð­Ï ´/$‚ 4@:DýÅÉ䓆âE_ͱ^Ç´–EwNA%u8–tÌP|oŽ;FÃŽ É»3Ïã¥8Ë: Ãc†3íÃi~ÇONN¦yßæ8‡yù'¹ºfwå×ù¿úãÆ´»«ÎA^âïƒÖû[óç/ú_ÏèÇFóé®OQÿåù´³.²2åá)Œ6ÂïøÃG çëc_£¶òß²Ô¤»&ñà¨ýŽŸ””DË>-qÞå,ûå Bg„rbû ÂÃÃýޝçÊNW²9n34ò°A.„}Fú®tCWAj\VƒŒÛ2ÀËw'ût;×ôÚÒîTœäºrÉså‘çÊ#שÝnÞ±9yC½inùÕÂ󃟧]·v¨¨¨ªŠŠö÷,¸]ð±íÏã뗭烟>@½ÎûG¾}¶„i (ª‚¢*¸WÑmÕåqýß ÿfÉÎ%ð/¯á±Î°rçÛw[A •b·Ý¬ß³hIÙI Óxm™e¡íض¨6µð¹ñÌÜOý0e°›FƒÒûŸb¡öõ±X,X°øüóÀÄ8ow‚·§U°|n¡ÚCÕ “cEuß"YÆÀÝèÏþ0¸Ç·îØ,6j„Õ FhŸ‘a‘,xnö¹áÍvx¤Þ#L|o¢÷í|päÈ’’’èܹs…t1+žZ¡Ýú ’@ûâÂxWUÅ?7j@Zhï/}ŸÚõ´f‚R½Ûé§Ò9a?á9yˆ‚“Ž“LýïT"kù©Ï›)oM!¯çä@m§2gÚúlíSøÏ§ø?"o·g¼9g»R²'µß©0a»ÖÌXÃÛkÞ.‘0øòó‡w~к…x[VmáÙåÏ®*øÞ/•=úúø´yÓP‡yO`”kþýî¿ÙY'P”ùrûۥߖ½Lz‚­Ñj•ÿ|ü,m,„ØB±…j Õ~ÚA1d` IDATCˬ+¾þ¦a7áìã!y…ÜN¹Œ|d$S>‚KuáR\û¹ýèvðÖ >r/Ï呉Ьs3œŠ—â©8µÛj±Û¥Öçåç‘éÈ,™<çÙ”hiw^éäêG¯¦z§êZrì&Q.HhK8IÙ+Fn¨1*¯~ù*zƒÑM>œ‘NÆLS¶…_Ïo@›RëÒÑ^OGÑ*%ZaÆÂe»°èÙ†nr ÖVYÿÇzÿãëQÐMžT‡Êñl-ù”MžK»X´ø™y™þÇ÷tΖÙÌB£ÈF%àaZ\z»Çª9Üõ?[ÞÊ@míáóOÇ:O­~ÊÿßÍèèhΜ9sÁ$Ͻ[´à?6G¥°ú°9*ŠO˜<ƒúâÂygïJ+1 c'Œ…ö~ÆÙ€çÖ·âñ)Ü=înt‘ð*ŠŠ2<±@®#—ÁßöùC»ÐÀ€Rë~zSâÝšgËãÉ%Oú?—ævÀ‡Þù¶|Æ­ø¹:U¼ÑÔ€'Oðúª×5EW?"àØÉc<¸ÀÿhŽut¶¹ æÌšÃœ7æøß›ãh_¤t¨—¨|úͧeÎ9])û»¥£M¾P¼®¯.ÞpØû¥|w¬hI˜å3GlãÃvyxm/`µX ›Å†3ÊI~Z>4(¶Ñf´b¸âIàihÔ¤ö0;‹¥0†…b·Ý¬?Öäûïwßõ©Ëq =îëÝa/|nA<3÷¿ûá;Neœoå.u…roÛ{ýþrÿ}ø÷dffj…‘JŸ» „¨!<Òñ6v«‡ÕáÓíW¼ÈöÔíx¬B8 u«Õeï£{½¿ÈÌ>Wë¾Vó´VXZœ ¬?Yytè£4hÐÀÝÓý–——GBBüq@âU¸éÓaÌ^ÊÏç%àÓs«_²ZyiÆŒ€ï.55µ\FºH,¥krðþAì‰ða¼xœx¿h”߯ÛTð?y­…ç,P½Ø:w »$ÂµŠªUyëõ.pAˆ£¨¹©tO§Ò-Œ¾>®º|è1•XµÙ± ŽÛ—«‹…,WVÙÖÏþ%ï’‡ñÍ—^!à®Ö´P´cדÇã´`Ánµc³Ú°[íÚm‹vÛÖÄÆ‘ÕGJ¾~µ)™<œ„ÍZТE ¯­õîÖß½ànòÉ÷zøÖ=V&>:‘×u(q‰(qÙ¿à÷ñõñÿµýw¾|'ÊåÞ³ø´޼r¤(9¶ÚJ$Ê·KKº5‰··ÀÕ¼ØDé×ÎU²«°ïÉ}^ÁC}ÑèúF¸®òò”áÎp–ŒXâw|=ÿJý£?ÒÛóëgÙdaÀõ˜Üg²ßñcwÄòÒÏ/¡v*öþ+}î&à íoàžïø¿éMi=°5Î;=_³ýlcÒk“üŽí«”¿R¸¦÷5l[¿ %VÑú(²âØë`ÜcãxlÌcÛWXXØ…‘<;£FAb" µ%ý',Œ#9Ú·áòh}$yö$ÐÄvÐÆÆe©RU›ýÈ×Ëôitº¥Ê5Þÿ Z÷YY1Õ#ª{ÝÎ_œþ€))SJŒ­Y†U”*üùП…­7Å[r¼Ýžl›Ìëk_G­ë%ˇ[Gž=âsÿÂG̃sD­ç%þYˆ²Gqòyc}Ô½‰[ÇΓ;½¿~û``—Ì}a®ßñ›ÿØœmǶy¿ü¾vÈGÿþÈcOë™þÙ®lï­èépQ­‹x¼ÇãØ,6lV[À~vý¡+y:Y´u¯•¹ÏÏ¥k×®eew‰_qá…“•—åõ‹‚}›ù æ*Òý¼éç¬Ø²< ’“áGÂyhpà‡SŒíË#=‰“'<¾ÿ,,ôëܨ*þÂÑ´iSâªÆ±}ïvððÒX¶òò˜—ýŽ Ð Az\Õƒ%[–x…ãGß}ô¡øzî»÷>Þ˜ôûvïƒÆn68Õ7UgÖžY†â¿ðÔ |0õާw_èwªü· óöÌ3¿E‹ÜÕë.¦ÎŸŠÒG)ùß_ëJ+m"Û.ÀõExx8›VobïÞ½Ì;—í»¶Ó÷á¾ÜtÓM•rh¹r÷ý÷0z4Ö«Ÿ}ÆK6/õí P.­ÏÂ7RDeŠ@ó¤ælY½ÅP¼Æm³ç_{<_Ê<—ýq»ÿØm(¾7™™™Ô¹²9CsJôk,βÂÂÓ]Ÿæ—Þð;¾¢(D\AÖY/#[Zùdô'Œ¼{¤ßñjÆÖ$í–´²Wα}ocá; Ëe(±;wÒìÆfZ‘¤»úüsEx[áíß¿Ÿ&]›—‡‘ò´"»Ó;Næÿ%Šá£†óuêמû1ÖÅV>ý9w¸ÛïøzúíÏüœù¨WyØD&Frz÷iCñ§Í˜Æ= ÷ ô÷ðu+t8Ýß—ýn(¾¢(D^É™gÊv9© µµö‡µ´iSº3q`ìܹ“×· ïÆ¼2}„-,ÔÙ]‡ÔíÆ;_gffrI«KHk–†Ú¦Øß(_{_´¯Óž5‹ÖŽТs ¶çmG¹Nц{SãÚ8ÐÝöÆM0ß›ììlÚöhKR^JkEëòs ,Û,Ô=^— K6а¡ÿ#G8tè­»·æÄÅ'P¯TµÏ¨,°$[¨öW5~ùöÓC ~üÙÇ<öÚcä×ÍGi `=fÅvÈÆ£Ãå­×Þ2[HZ<ò|õUѺ;î€?„sCLv¬£õ7 äÈÅI¡>™Ú-X ®šíj˪±vñZáÖ-YG•EUà€›@Õ¥Uùcù†ã{Îg¯†mŽÍmSËÿ,4<ÝÐPò `µZ™÷Å<³ZŸWЊ¯T´~rK­´¯ÕÞpò ðëw¿ömì)õ@6ؾµ1ð_ËmÞ&Mš0vèXl_Ù £ÔƒÇÁ>ÃΔ·¦Á¢Q£F¼üðËØ¿¶CAY0bØa™Âwg(y˜ñÉ ªÿ]Ýs[2Ä䯔Kò 83‘¨ÍQX¶ºùöqBæ†ðÛ¿Ž?bønks¶Ù6­ÿs>…?­Ë­4ÚÙˆßoµZ9‘|‚ö'ÛãøÚu…ËZ ö¹vj/©Í¶åÛÊ-yíýwdÓolLèœP¬K¬Ø²:=”Aµ™JžAû|8µëà '|z8ŽiB§‡9;’¯þï+ÓÉ3h¾S™BÌ/1T›Qˆ¯#hÔšÍ?m.×ä jÕªlýïV¼º€^§zqQâEtÚ߉é÷Mçè¶£¦’gÐZÙ%ãõž¯·&ŽêS«³<†‡?Lúžô€Ì|7ú¾ÑœÝ–-3¶ð~÷÷Y÷Á:r÷ç^pɳ¢($''WôaøoÉhÑ¢(y®U æÌÙ³ “gÐZžË³õYfOö*Ê݈#TKu‹Ò8D½úº«Õ³gÏšŽ™žž®Æµ‹SMªõ:«j½Îª:š:Ô¸vqjzzzŽÚ»+V¨Õcª«övÕr£Eµu´©ŽFuÐðA‰øðaõ’«.QCbCTKm‹j³«U/­ªN˜4! ñ³²²Ô¶ÝÚªÕ.¯¦:š;Ô°+ÂÔZ—×R,XøzÖ®]«F7‹VÚ†©övµJÓ*j£«©»ví Hü;v¨ [5T«ÄUQ-µj“ªê×\¡žÂÀ>Lv L“:H¤C¾qÉÉÉÄÆÆ½{Ê…"))‰¦M›VôaTJÙÙÙœ8qBÆD5(%%…ÚµkSµª3³ˆ2äÜ5NQvíÚE\\\EJIN'¼ö¼þºv ];m¢”Ë/wû” 6pèÐH ¯Nðlذ! ´ÌD¨O2’ IM ô¼¼ÿ äåù2ƒ†pgüøñ}•VJJ 3gάèè´fΜIJJJEF¥%ç®q3žW¶mƒà?ÿÑ’g‡^}Ö¬ñ˜<©Dê,î§Q7Br}Ò$Ò‚eÜ1›T’>lÆ5mÚ”§Ÿ~º¢£Ò’×Î9w;¯f"T­ŸóóÏkýžZ¶ÔZ[µªØcó@r}’@ !„B”‡={´6V¯Öî[­ðÿ¯¼2ûb¥&]8ü››ËóÏ?O¿~ýHHH@0B!„[ W^Y”<ÇÆÂªU0~¼$ÏI ýðÜsϱmÛ6žzê)æÎˇ~èós¥ˆÐ¸äädÅÃÔÊBWRRREB¥•-}xMHII!;;»¢£Ò’s׸ ‰ðÐ!èÝx²²´Q5zþþ®¹¦bŽÉO’³è“ÚË–-ãƒ>àšk®á•W^á—_~ñù¹Ò!ß8)"4G ‘Œ“"Bs¤ˆÐ9w dá‘#Gh×°!×Ö­«»Üß±£6÷’%Ú“6„¥KaÒ$¨D£ÑH΢Oú@û(==C‡Ñ AZ´hÁï¿ÿîóó¥C¾qçM!H%%…HÆI¡9òÚ™#ç®q,"¬W¯ê™3|›žN”—íÞj?^´â®»`âD¨QÃÔþO2õtC$gÑ÷jÎËËÓmÉÌÊÊbÇŽeº ?~œˆˆˆÂû‘‘‘œ>}gÁ8ŽB!„¸ =ñᇼcµnX< P·.üðL›f:y^³fÌ0B”“ >þûï¿4h111„††Ò³gO·Ûååå1|øpjÔ¨AÓ¦M©U«ß~ûmáã±±±deeöçÛ¾};­ZµÂn—F|!„âB6xèPÖDDà©180p lÙýû›Þçܹн;œ= ðVëÝØlž‹å=Óû¾»à³¿£G¢( ÷Üsß}÷Çíüq~úé'~ýõWZµjÅ[o½ÅàÁƒiÔ¨íÚµ cÇŽ,Y²„°xñb:vìèóqH‡|ãd&Bsd63ãd&Bsd&BsäÜ5®„Jkûöíê¸qã*ú0*­qãÆ©Û·o¯èè´äÜ5îìÙ³ê¨Q£ôøqµ}•*êIPÕbË« NxöÙ€ìÂåRÕG- ¡ªK—$´_®¿þzyÿé&=`ݺudffÒ¢E‹민òJ–/_^xÿÚk¯%55•9sæ°gÏ.×z³È‰'˜>}:‰‰‰%–'N”ØNÖ•]÷ñdzxñâóâX*㺩S§ž7ÇRÙÖžÇR×=ýôÓ4mÚô¼8–ʸ® ˆð|8–ʶ®x¡éx‹ÁðápñÅŒ={–»‚­²yժѨm[ÓÇœ“·Ýï¿H½z'Xµ zô(ßתàvbb"Ï<ó Ó§OGè»à»pøâðáÃ4kÖ¬Äú–-[²|ùrE)ì>àp8¸ôÒKƒ~ŒB!„’¬,øúkmÒ“½{ W^²Z9­(Ô,†½ø¢éÝ:·Üƒ{5jóçkó°ˆó“EUÿ9ÓéõîÝ›œœV¬XQbý{ï½Çã?ÎÉ“'‰Š*¤fòäÉ<üðÜ:uŠš5kÞo||< C !„çµmÛà£`útÈÈ(Z·ß>ÈìÝ»Y?|8¯) ×U«Æ†ÌLS»Ü½n¼vîÔîwïßÅþ :É[ôIŠ:Ýï-ö-`÷îÝ8"##MïCŠ“™Í‘ÙÌŒ“™Í‘™Í‘s×8¿f"Ìχo¾ë¯‡æÍµIO ’çÆáí·áàAmXºöí Gäx>­ÏëÖAÇŽEÉó]wÁ¢E›<ƒä,¾¨_¿>[·n-±~óæÍ\tÑEX,ÓûY}Œ“™Í‘ÙÌŒ“™Í‘™Í‘s×7©©©4ŠâÚ5Š–š5éÒ²e‰u""¸ó¦›Šžxà¼ð\r Üq\¶Ù´þ‹k™í¿ÿ µj•ØçØÉ“ù6,ŒGŸ|ÒðqÏ›§åìs¯<ÿ¼–£;†CŒä,ú¤4СCjÔ¨ÁæÍ› שªÊ¦M›èׯ_@ö!Ã`'3š#—àŒ“™Í‘×Î9w}M¤Ýάӧ)óŸ¶XãËX«•wÜ¡M­ýÑGðãàr#GÂý÷kIµƒ‡¥Y©ü1i<ú¨6LÝ®ÎÈ‘†Ãœä,ú.ø:;;›¥K—pìØ1òòòHLLà†n <<œªU«rÿý÷óá‡Ò¥KZ·nÍÛo¿ÍéÓ§yðÁ+òð…B¡ã•Y³x¡gO¦yèîwø=$„ ¯¼»v•|°Kxà¸õV¿š¯4P᧪ðä“ðÎ;Úýðpøö[èÝÛïP¢‚]ðE„ mÛ¶n[½z5±±±¸\.{ì1¦M›FFF111|ôÑGôÀ»:>>žÕ«WÓ©S'ú÷ïOÿÌP$„Bˆ"×Ö­ËìãÇièæ±Ç€öh£hP½º64݃jýžƒ 7WëãüÍ7Úýzõ`ÁhÝ:(»÷IÁpv9‹\ñì‚O ý¥( ),Onn.³fÍ XÌ™‰Ð™ÍÌ8™‰Ð™‰Ð9wý³|ùrfôìÉTEAvqh­Ï}€u  ÷ÀÚŒááA;¶Ó§¡_?XµJ»ß¬™V,x¾~´ 2„ÐÐPI ½Œ¤«ÕÐä¹€tÈ7NŠÍ‘B$㤈Ð)"4GÎ]deÁòåðòËtã vª*< áÜ&¯]s ¬^ ÿ £G5yÞ·®¹¦(yîÚþûßó7yÉY|!-ÐA ã) !„ø§JMM¥Yƒ´×¹Š˜ª(ÔhÕŠ_þ÷?Ï:¥%«ViË€ÓYøðrà+àË‚Í>¬MO7ûkòÇз/ä£C‡Â—_BHH…ŽÏ$oÑwÁ !„¢âDGGÓ±Y3^ؼ™ö^¶`³ñÒ矗\yà@Q²¼j•6щ»v?«Z´ {çμ8c32¸xÍjeìäÉüu|¶`6:^V–vÿé§á7 #ãŠó€$ÐB!„(W“ü‘ûbcYZ¬µ¸¸í@úÅÓ*, >û¬(aÞ·Ï}@‡Ú´ÑFÐèÜ:u‚sÝ/_0€{öäEaMõê$ V>¿”Ÿ|=¤’g³ÁäÉ0jTÐC”#I ƒDfõ1NŠÍ‘B$㤈Ð)"4çB:w5jDÈW°ÎC+ôó pÅîT­ªMÙ×¹³–4·o¯­s£{÷î¼X«w8ÁØI“÷Kø@Uá¹ç`Ü8í~µj0gŽÖ£2ÉÍÍ%44´¢ã¼&IlÞ¼™Ù³g³eË–Š>”JGŠÍ‘B$㤈Ð)"4'çnlT×ÙlÜb·{\úØíÔ Añ0–²!ÙÙ°};,ZÄäÁƒyÑMCHpZUiuútÑÊš5áæ›á­·`íZHKƒŸ†—^Ò¦ïÓùRöü´i¬ ap[Ÿóò´‘ñ ’çèhmrÃÊ”t( û•"ãd&BsäµóÝ3?ÎéÇK¬ F\b­Z5>øì3¿¯È½6q"GŒàÍâ3ï•ò¹ÅBëô/vf&ì߯uµØ·¯äí}ûŠæ©`=ЮXˆç€„:u [·¢.-Z˜ê,|ã7²cÿ~ÃÏ/.;;›‹.j‹ªFyÜFU!'\®FÀW4mª SCš°°0"##©W¯^EÊyOè iРA@&e•—Õj¥ëu×1pÁnñ²Ý» 9÷Ü´ãBèëíµß¹Sw;{­Züºu«ß îŸK–pã¶m´ò²Íàݨ(¬_|áWl€ÁÆñþÓ–žŽ»ZÀç¡¡¬™0¡äeãâ·Ožôë8&W­Êýgϲä\!`pºAZ<èﯤ+::: qòòòÈÎnŽËõΖ9@w:w†ÄDˆòœoŸ·bcc‰eöìÙ}(ç=I …¢I³gÓ3*Š[òóÝ>ž Ì ã¯¾Ü$%%ñÝwßù´íUW]ÅÍ7ßìWü'N””äÓ¶ÑÑÑ4iÒįøzf͚źß~ÓßÐbážð{šâ÷Þz‹Ußè% Úí<5a:tð+þ°ÛogÝwßq…Nbú›¢°dÍ¿ãë©]§w®Yƒ·¿úfàÅÚµ Õk|8>cš6e¬—+T÷[­¼öõ×~Ç.ðè¤I¼1bo¹é¢ñ¥ÅB÷ví°>ñDÉ$¹x· _„‡kÍ®i?‹/ѨNW^Ɇ͛i ›6ͯø5kÖdÊK/ñZ±ß/8Ô*¶Ý×€í¥— %ÐC®»Ž;t¶ûUUéõļñöÛ~Åד‘Á¾O?e´ÎgÇV+/¾þºßñë]|1Ž?ÿä?Åâj¢uEPAv;­Zykguïã)S葘È|_>v÷Ô¯ðä`âÌ™ôªY“Û¼Ôc¼j·3éÇ Åoܸ1θ86nÛFÁÌÎI@Á™{Øŧî®dªªÖR|ú´¶œ:åöçàÓ§yßj%MQJ´B;ÏU•ßW®„•+½hD„Ç䘘¨UËëÓA‘cTl,ï;œºøbCï=Š¢°k×.âââÛ›ˆˆ #y–"B}2‘JÄÇÇóóôéô+–L.T^™6áÇû+99™ø+®àC$æA«•©Û·üãKëÖäoÚDC¾iKU•ä#Güþ‚pK—.XV¯æªbñ¨*½,–ßö>öž9ãwuÿÈÈKLÄ[g'ð¢ÍÆž¼¼Àü¡(pô(8@æ®]ô1‚5¥ú"æ׆†ò¿Ã‡ ]ûS…Îqq¸N*±~wV«U+±.*6–…ë×û½fÕ«sOf¦ÛKÀ…ûVÇİjï^¿ãëi[£Ë22¼î¿¯ÝÎĤ$7nìwü:ðغut>w? H zòæ×V©Âÿ²³ýŽ Ð§I&îÚE¬‡Ç@§°0Öde•˨3kÔ`yFžÎšÕÀ‡;3S/‰ò CÍš,LK£àÝ;èOQ8øõöÛùxÎCñïîÓ‡[-b€‡Ç‡X­<úßÿ–K 0²nž7wÍ›€§›5cáÖ­¾s:áìY­ãlNœ=Ëî;Ó¯ Î}6ÄSÏm~¿ÅÂÀ¾}é]½zÙä8-M/ͳ?€â_Ï>ö£ÍØGž“㘭¸/nºòJoÙ”?ÿ,—:''‡±cǤ†&--Úµï÷© G:Ý9v쿦÷YѺuëÆ%—\"54^H tÜ ªL>÷—üáwò GõØXB’“iæa›m@õØØrùæýùœ9ü»ys¾ñÒ ½ p¶oïwò ðÙ·ß2´aC^)ÖÊô ”8>Ò·¯¡¡±&~ý5×GF2-?O_>î9Ò÷FUµi¦€ƒµŸ¥o>\8[V8wî÷(Þ = ›«µàÔ¯-[jË•Wj?¯¸ÂkÓ†Õj¥fÕª¼º{wa V¡b­fsŸ V¶¼øÑGl1‚{ù7Øfãƒ~0_Ϙ 7r$ozØÿFÀg(yøhÞ<†6lÈòs₩%Ï`þ Á„ x¹ys¾òpþLµXè~ß}å6dãÈ·ÞâÝä¯ß«³ Ç;y2¯ N¹øÅ_;H á7?¯ ÷Á7ßÐ#*ŠnZ¡÷‡/ºˆW_­Í\átB~~ÉÅ争íÛÓó§Ÿèç&Y}ÞbáƒK/…~ýJ$Å%~¿í&Fc´/QÖ%ÏG€ÍªÊ§F»:8Zâ[³&ƒ£¢˜°a§NjžÛß!!ü¾v-ÈÈR IDAT\v™–@Áädäw”Kò Z1Ü…R€^dèN}Òñññ0mZá‡ásV+M?ÿœáwßm(^rr2O5oÎþ °ÛysëÖr»tÕ£iSvì ¥‡Ç»;Ì+gõÿ1‚w…O,R~˜×'N ÀÑ_˜ÒÒÒ¨Uë~åŸÓ-Syë“è Ë~©V×o¼QKÀ\.­µÃéôùvœÓIvݺl=|˜æ¥âo²ëÔ!nëVرCKº »Ýó}?›üý÷üûª«˜ï&I[Ô¸úê¢ä¹à«ÿ€?š6¡-[¶7ˆëÜ™ðS§´êoÿà'>ù$×/XÀ §³L+ô@÷K.ÁÚ­[QrldœjÕàâ‹¡aC·?Ã6$nذ¾Г,<ôôê›7Ö-ÚÏ;ŠZ].m<ÕíÛ¡x±Vx¸6äSË–4jÙ.¹„?÷ìáj7‡5¸²_?ÿ“gEÑŽ#7—GÇã‡v[ˆô¬ÕÊ»Ï<K–µâéýôe›bÛŽ‰eÜÆ¼Yê»ÿFÀi·Óø–[<&Ç…‹—ô€¡ÀòRë ¯´.Ó¾ï— ÀËÀW¥ÖOºçæb­_ßT|=#wK­˜¹u«Ö‚jÂXà5 ¡Ø:õÜýß¶lÑÞÛ&|€öå³x½8Lù'Ï”L Ÿ>hЪW‡°0m©RÅÐíÆaa8|û÷Óšs­Ïµjñéñã›zð°aLxøaŽ¥§óEHkK¼! > O=åõ#CüCI tÄÇdzrÚ4º µ<öüï¼QR2ðPúBùàM´.å©Ú?ÄÒ­ÐÝ™V+Ñ «AÇ€Îh¿k,Ú¬?×KѺA˜1è *¶Î‰öx=:3 é$Ç4lX8¥¬7™™™ôŒŠâ—ü|:yêW›ŸÉÉ%“ê-[`ï^¯ï~àAŠZ° ‘TàZ«•_ú÷'ÌéÔ¾œKŠ —â÷‹ß.õe©#°J´ïFS6ñ,me¥öß-¹1Öy£¤‚÷_àWüÐíX Z¡@'´+Á˜Ýª#ð3¶B¯Aû‚¨)c: µÆfµ_ѺGaµj] BB´ÅÀí»W¬ ÿÁƒ…IìP‹…1ññtˆ‰Ñ¶µÛµŸÅ—®yÛmÜ4>ý¿€gýéûìƒÝ»w3¦iSÞu:I°Z¸`AÀ‡AýÕW¼8|8·=òÈÙúl¶ˆ0+ Þ~Þy²²ÒЮ\ø-Љ‰‰$&&²råJºté"-Ð^Hñññ¤L›Æ@/`m€âöBkIjqîþà `I€â{“ ü­E¸À*à=àûÄO¥¨p4Z«ÝÒs±§ ~6Ð ø [¡?vÛí¼Ù¸±éäØWñ7ÝÄ‘… éòÜs<çÏÐuYY°u«–PO®à&´¾ãWSTˆô-ð Z «Yî ‘nž¯ãØze±”MX<üœ‘žÎ–}ûxóÜS7O‡‡³ä†Ê&?–Ôìl†ŽÍd—‹DÀa±sÇ<7thÙnn'=Ê+¯½ÆWçšµ>·XØÛ¥ ¯ß~{Q·ÒÝ@<­ó÷¾ÕÊóçs0!—Îí¿·ÝÎÌ%KˆªQ£è9Ï3p{ö¼y¬3†ºŠB? >$„ߎ%,"B»’eRÁ—Ï5ùùìâë×ç·M<ä‹ììlnˆŒdM~>7Ùí¼o°hÕ›^Í›ãÚ¶¬Úµù½Ød$tõײtÕªrës_‘Œ* |ù%¼ð9R°6 ‹å~TõÂO  H¡>I ƒ  t=‹…fC†0üšk´D  k„ÁÛÉðäÍ7“x®¥¿ÍÆ[?ýD\ÆEÝ>\®¢¥ø}où¸m÷ß'áäÉÂVèîV+3ÇŒ!ºzuÓÿ€±Xøî»<¶o_a_Ôkl6–¾ÿ>áUª$þÈ×_çÆµk¹­°ch(ë ú>Iff&­bcÙuôh`?›7³åJxõUžKTàZ‹…_.»Œ°ÐP­14TkÑÓ»íá±cư(;›šh_¨ˆŽfùÌ™î_’bü|Ý‹÷…63ò†'}ñÛanä O F䈡|GÞð¤Ã¹96˜yÃcüs#r,ÃÜÈžÜݧ-bŽÍÆ#«W—ÛÈžŒìߟzóæñG€[Ÿ ìÞ½›ÖMšðÍÂ…2 W,] ÿ÷P|ÄÙý ^y%ƒ[ni‰Õz…N…˜˜|vìøµ\3¤´TQîFŒ¡µ}DDÀc÷Œ‹S7ƒºÔžqqïÍŽ;Ô›ívUu%¨Ú·hü£GªÝUu¨#úö hü¬¬,µ½Ã¡* ~ ê“£F4¾¯Îž=[.qû¶l©þï\ïïo@}Ûm?kÆ õ «UUAd³©7n h|=Ó§LQŸ´ZÕ?AíÙ¬YÀã¼ÿÞ±XÔמ>àñwìØ¡Þi·«ŸY,ê³<ðøz>ÿøcõE«U½ÑáPOž<ðø³¾þZkµªíBBÊå=~æÌµµÝ®v©_?à±}‘••¥Ör8Ô]»v•Û>¦OŸ^n±E‘Í›Uµwïâ;ªÚ°¡ªÎ˜¡ªŠRÑGW1FŒ¡Ž1¢¢ã¼& tŒ1Bmi±¨Ó§L xì;v¨·Øíê-v»ºcÇŽ€Ç×sÃå—«›@íæp¨G xüíÛ«+Aíèp¨gΜ xü{ûõSç€ú¯ÐPÕår<~EÚ·oŸÚÛnWP;–SÓ¾F õwP»ÅÄ<¶/þ¡^g³•[3 }{µihh¹ÄVUU½16VmUォÂÃÕ!;—[üê¨Ûo/·ø#‡ Qÿý÷r‹¯gûöí¶oaÞ‘#ªzß}ªj³%ÎÕ««ê믫j9µkT’@ë“Q8‚ä°ÝnxØ:oâââȹì²ÂÛÁ6yþ|\q—y#€>š7«ë×ç†= [çÍÄ™3‰­^áññ\?ÀFa»â îݼ™«ŒŒ¼áƒ±“&Ñ{øpV”Ó¸ÏzÆL˜À´÷ßxÿÓ ³g3sf JëÊš°`ßΜYaï½ç¦L¡{÷îåÿ…Ï>£gÏžåÿ³rüÛø"3|z¨YD/tN§“1cž@QŠF)QU•ŒŒ4jÔ(9ñKûöm¸ãŽá$$À›oBf¦¶Þfƒûîƒÿü LapÁ‘™õI$uËé<À§Ž „¸¸8šôèÁG&&Gð&::š¨¦My·œâW­Z•'ß}—1cÆ”KüŠ6ùǹü²ËH›>½\â6Œ]ûö•Ûdz†ß}7ns7¢w`äää”krÇs/¿\nñõÜVޝÀž={8zô(‘,¼ý'?~¼ôAõAff&Ÿ|òŠò|±µyhsÖ)¶.Ÿ™3Çó Ã)^sÚ§6âF3O³“ý¥¦¦Êd*:¤ˆ0¤3¾¨H»wï.·Z!„¨hþLµ­ ¶ª’qÕUÚ0u7ÜPÞGXùHÞ¢ïºf-„(C’g!„(R¿>|ñüù§$ÏÂ8éÂ$»víbêÔ©´jÕªÂ.w !„ÿdÕªisSU+=—½à¯¿þ⯿þb×®]ÄÆÆê?áLZ ƒ$44”˜˜é h@rr2ŠÌ£jXRRREB¥•MJJJEF¥•’’Bv€ÇÏþ'‘s× m„ú’ªV•äÙ›ÈÈHbbb¤€Ð’@‰ªªtíÚ•˜˜˜Š>”J'!!¼¼¼Š>ŒJküøñ}•VJJJ¹ŽÂq¡›9s¦|1AÎ]}À‡j3–”$TÀUn111tíÚ)Ó'E„A ñ…BˆÀØ·æÎ…o¿…õëAUÓ€ûÎTÛåMò}ÒZ!„å&//oN~¾þEï=Úñ •Y¿g|÷¶lØPG)„$B!D¹ÉÎÎfÅŠS(ÊÛ:[æ‘”ôxa½k—–0û­6bFiMšÀ Aл7të.Wà]O$’ÜÜÜŠ>„J+99™ØØØ n¦À`‘ÙÌŒËÎÎæÄ‰2¡€A)))Ô®]›ªU«Vô¡TJÒ¹k±Dz#PåàrÁohIó_•Ý¢iS-i¾í6¸òJm]Z¨ê> x½‚ ê[—Å‚ðÌD¨Oè IMM­èC¨´˜0aB¹LEýO ³™—’’Bbb"O?ýtEJ¥4sæLú÷ïÁ$ÁöOþøãàíô˜˜Hbb"+V¬ W¯^}8ç5)" éŒ/„›»îz”mÛôû:DG‡³`Á4¿ã‡‡_GVÖoºÛ…†ö"'g GŽ”m]ÎÉqÿ¼¸új-a.X.¾X{ÌŸ"Â:unàØ±Õ~üf¢8G%|Õ¨QgNœÐÿ—zšS§ÜÌ¢Ãbqú]]¬ÖòíOîtÂìÙ%×…„@ëÖ%æ† }ÁÌ™/ú´íe—½ïÇÑ Qq$)"4NŠÍ‘"BãYDø÷ß³re]Tõ-w‰•.~àgøúëX,Eç©¢äb±8J¬ƒ³üòËg´iÓÆ¯ø÷ÞûÌ™ó_,›Î–gøå—ÏhÛ¶­_ñõœz÷ÊÏ?Äb‰ðº¢ìdΜ×4h_ñ7lØÁ™3_R2áô/±Îj}ÑÐçáßï&+k z­´Vë‹?~ÜïøÍ}5Òx`j‰5yyШ‘ÿI±¯BBàСÀÇ 6)"4Gf"Ô' tÈÑ8ù4çBIž—-[Æ7>„Õëu;UÍ¢sç‹øå½‚¥’þøãÞ|sªzg™ÇV¬(~ïË–}d0®8ÙÙ*.×Dôx‹e<.—+@{ýgŽ^²{·6%µ»åäɲëNŸÖºN”5µÌUõ>ò€ÍQQP§Ô®­-óç{ÚGIýF•BXX˜üï0Ar}’@ !|Öºu_Ž×oöjÚ´?ÿüU@÷ŸŸª&?ÿ-“ÈÈxÖÐ>,–+PU½a&“€å†âû*'Gka¬V ªT)×]ýc¸\Z—‡œœ’?}]——çÛ~rs!Öûw xïO­(púô""¼_îö×Eµ#+K¿_Yýú¡ìØñ³ßñCCcP”ºÛU©²‡ŒŒm~Çß¶í,yy?èn—–v³ß±++EÑZSSáØ1ígÁRüþÞ½¾Og¼aƒ–ÖšX­š–¹[¼=V|›³g}ÿΜÑ&ÈÈÉÑ–Ü\÷·‹ß?zԷت % ÕŠ¿.žnïÝë[|EQ£´D³xœŸïÛóËSõêZ˰»åÝw}KÒ˜æÿ(vBˆ“:Hrr®#'gz±5?òçŸú@gddàt>Ó©×J–HFÆ:¿SÏ–-{ÉÌ\xïl³ÝN^^žß ®ûøÉ@,Å“^£ñÏžµàt~/ǯ”èÿii5ÉÍ]¢»Ý¡C׊o±\ŽÓùS©µe ‘Åh|éÖçOÁ§ªj-ozËîž œJ^Îܹ®ºJK×Z7Ë‹ËÚ ª ÷l¤´ 6Ptžª*,/ß|¿¦Žö…ͦ%æ¾|²Ûá‹/Ê&È5kjɯ''ºK ½úCQ2±X¾ÐÝÎåº0êu¤ˆÐ)"Ô' tМ dÒæå“T”’L@/éž”-DªHyy•å~ÉÌôüXr²ï-¸7j£8fg?K)Ý—7#6mòþL‡êÖ…èhmÉΆßôç±àòËaÌíõ(½¼Nî_»ÏLJ‚özT«Vt¿xwO·ð<‰Gq tî —]¦u sÿÓ×uaaÚß2<\{íõØlp×]úÛù&pçî—_>É‘#Gt· 8 û«hRDhŽê“:hähœ|š3ÕíÚŒ -áÊÍ-ùÓÛm_[Zsr´$Æ]bŒ~˜Š¨d²)î áªUƒîÝ‹’ãèè’Ért´ÖâXÜœ9°r¥o_êÖ…ôÿhóó='Ûÿ÷”¤Ãb#àšk´ä1,Lû2¢w»[7í‹KIe_;«¦L>}üûÝþõ/m&<_Žÿ©§ü_ÑTõ4Vké±’/J®S B‚=Œ\E“"Bs$yÖ' tZ¼X+r¹|_vîô=þ´iÚèKÑbµº¿íë}_/#+ Ü{¯÷K–îøÐ 혥èõÑ»}ê”oñ].mf-»]Û‡ÑÅf+yß×~˜99л·¿›Ëå{Ò˜•5jø¶­.¬Àl¼‹Ö ®Ýöuô³èh¸çíïçë²y3¼ù¦o nÓ¦0ož¹ß­<8Zâ^:yxë-ßhÐÞ{þæ[2L»9Ë—Ê)> ªT¹>G#„Ð# túë/m)/GŽhKEPUøþûò_Þý(}-Z*.,Ñï*}Þ²Z¡k×¢6o‹·mªV-ºŒ¿p!Ü|³o]2.¾Þxÿc^¸PK2}í&bÌÏ> v«µ\B~—KèB»ÝØ Ê¥ÇBœß$š•@I …>S”Ølwû°¥Íü~jÓ¦ éé,LÆìBqᲨjùÖœ ˆçÌ™3|ñEÉY ÂÃñÛå;Œžäädbcc %ÌB ‘ÌÈÎÎæÄ‰r9Ó ””j×®í÷Œ¡B#ç®qRDhÎ!C •nD^HF$§OŸ&22²Ä"ɳoÈ;ÿ¦X«4ÆûZu*ÊHIIaæÌ™}•ÖÌ™3IñuØ Q†œ»Æ cR}tÿLZ ƒ >>‚!„Bœÿ$oÑ'-ÐB!„BøAh!„B!ü täú2{†p+99Å—ù›…[III}•Vvv¶ôá5!%%…ìììŠ>ŒJKÎ]ãE!99¹¢£Ò’œEŸ$ÐA"ò“"Bs¤É8)"4GŠÍ‘s×8)"4Gr}RDÒ_!„•…ä-ú¤Z!„B?H-„B!„$éoœš#…HÆI¡9RDhŽœ»ÆI¡9’³è“:H¤C¾qRDhŽ"'E„æH¡9rî'E„æH΢OŠƒ >>—ËÅgŸ}†Ýn—)¼…BqÞq:8Nî»ï>l6›z!-ÐA²~ýzÆŽËâÅ‹+úP„B!ÊX¼x1cÇŽeýúõ}(ç=iF!„•…ä-ú¤:H¤C¾qRDhŽ"'E„æH¡9rî'E„æH΢Oè ‘ùÆI¡9Rˆdœš#E„æÈ¹kœš#9‹>éÂr)D!„•…ä-ú¤Z!„B?H-„B!„$éoœš#…HÆI¡9RDhŽœ»ÆI¡9’³è“:H¤C¾qRDhŽ"'E„æH¡9rî'E„æH΢OŠƒ@:ã !„¢²¼EŸ´@ !„BáI …B!„ðƒ$ÐA"ò“"Bs¤É8)"4GŠÍ‘s×8)"4Gr}’@‰tÈ7NŠÍ‘B$㤈Ð)"4GÎ]㤈ÐÉYôIaHg|!„BT’·è“h!„B!ü ´B!„~:H¤C¾qRDhŽ"'E„æH¡9rî'E„æH΢Oè ‘ùÆI¡9Rˆdœš#E„æÈ¹kœš#9‹>)" éŒ/„BˆÊBò}Ò-„B!„$B!„Â’@‰tÈ7NŠÍ‘B$㤈Ð)"4GÎ]㤈ÐÉYôI$Ò!ß8)"4G ‘Œ“"Bs¤ˆÐ9w“"Bs$gÑ'E„A ñ…BQYHÞ¢OZ ƒdõêÕÄÇÇ“˜˜Xч"„BQFbb"ñññ¬^½º¢å¼'-ÐA ßä„BQYHÞ¢OZ ƒD:ä'E„æH!’qRDhŽš#ç®qRDhŽä,ú$éoœš#…HÆI¡9RDhŽœ»ÆI¡9’³è“.A —B„BQYHÞ¢OZ …B!„ðƒ$ÐB!„BøAè ‘ùÆI¡9Rˆdœš#E„æÈ¹kœš#9‹>I ƒD:ä'E„æH!’qRDhŽš#ç®qRDhŽä,ú¤ˆ0¤3¾B!* É[ôI ´B!„~Z!„B?H$Ò!ß8)"4G ‘Œ“"Bs¤ˆÐ9w“"Bs$gÑ' tH‡|㤈Ð)D2NŠÍ‘"BsäÜ5NŠÍ‘œEŸtÆB!De!y‹>iB!„Â’@ !„BáI ƒD:ä'E„æH!’qRDhŽš#ç®qRDhŽä,ú$éoœš#…HÆI¡9RDhŽœ»ÆI¡9’³è“"Â ÎøB!„¨,$oÑ'-ÐB!„BøAh!„B!ü tH‡|㤈Ð)D2NŠÍ‘"BsäÜ5NŠÍ‘œEŸ$ÐA"ò“"Bs¤É8)"4GŠÍ‘s×8)"4Gr}RDÒ_!„•…ä-ú¤Z!„B?H-„B!„$éoœš#…HÆI¡9RDhŽœ»ÆI¡9’³è“:H¤C¾qRDhŽ"'E„æH¡9rî'E„æH΢OŠƒ@:ã !„¢UâIDAT²¼EŸ´@ !„BáI …BñÿíÝ{\Tuþ?ð †#ƒ(ˆ wAQÅT4 /Y˜mš+›ØE\[-ÓrS^Ú]ÝLW-+«Õ­H1/¡à 4!PAå¢2r4˼¿ø›ósd@„àý|<|<:ŸóžÏ¼Ïéæ=gÎçscZàºð ù-ǃ[‡"µ"lDØ:|î¶"l®Y èvÂ7ä·"lˆÔr<ˆ°uxaëð¹Ûr<ˆ°u¸fy<DØøf|ÆcŒu\·<_Ö¾}û0wî\óUÆcŒ±.Š h-ÔÔÔ`Ê”)¸zõ*ß×ÇcŒ1ÖEq­…Y³faêÔ©‰DZ¿–oÈo9DØ:<©åxaëð ÂÖás·åxaëpÍòx\@·¾!¿åxaëð@¤–ãA„­Ãƒ[‡ÏÝ–ãA„­Ã5ËãuÓum…ˆ —Ëqÿþ}¸»»7———‡ôôt¸¸¸ÀÅÅEh///Gtt4ÀÙÙ-Î…?@cŒ1Ö‘píÒ¼Nwº¼¼ÁÁÁ°°°€££#<<<4Æ)•JDDDÀÞÞpssChh(jjj„õ … …‚‚dŒ1Æc‚NW@+•J¸ººâŸÿü'æÏŸßdÜŽ;ðõ×_ãĉÈËËÃåË—qúôi¬\¹ ‹±hÑ",Z´ãÇo¯ô[äÊ•+mz¯\Kûÿý÷ß‘žžÞfý?-‡B}}½ÞõäÈá ][ôÿ´ìß¿_/ûÒ×µuþÍ)..FRR’Þõ¯P(pêÔ©6ëÿi9sæ JJJô®ÿ¤¤$·YÿO Ÿ»-W__C‡éeÿO²_Ú:ÿ® ÓÐb±;vìÀ_þòH¥Ò&ãvìØáÇ#88àéé‰iÓ¦á믿F]]Æ×¬Zµ R©÷ïßGHHž{î¹'Ϋ©>Ÿ†Î^@———·é ÂÎ^@———kýÞÚèÌÂ555P(-zÿ'ÑÙ h…BñDÇpKuöšÏÝ–Ç)•Ê6ݽ€nËš¥³èÔRY¿~=–-[†G7±¦¦=zôÀ›o¾‰­[· í›6mÂâÅ‹‘••Õì}ÓÚ3f 1tèPtïÞ]mµµ5Œ…å¼¼<­Û¾Oéiô÷h›j0Á€´z­R©ÁÁÁ¡Ù¸ºº:tïÞ Ð÷óÏ?càÀ°µµ}êÛfllŒÄÄDøùù¡°°°Möß… àçç###­^[XXˆ!C†³¾4———‡áÇÃÈÈHcܾ}û0tèÐ6Ù6cccœ9sO­¿GÛRRR„1Ú¼V.—#((¨UqÈÊÊ‚››[›lÛ½{÷P\\ ™LÖ&ûO$áÎ;ÉdZ½6;;•••2dH³q™™™¨¯¯‡———Ƹ¸¸8xxx@©T¶ÉþËÎÎÆ€`nnÞ&û¯´´ŽŽŽ077×êµgÏž…££#úöíÛlÜ™3gàææ+++qûöíÃôéÓõîÜzÚçà£mGÅèÑ£[g``€˜˜L™2¥M¶M"‘àâÅ‹>|x›ì?¹\Ž‚‚µñWOsÿ544àĉ R«#JJJ`nnŽÔÔTøùùáĉ`šuÉúúõëprrÂÖ­[ñæ›o íÇŽøqãpüøqŒ3æ©å±sçN#ùU#cŒ1Ƙ®h08kÖ,„‡‡ë ›Ž¡ÓÎÂÑœŠŠ @Ÿ>}ÔÚUW T럖ððp>cŒ1Æ:‰Nwô“PÊN²ž••¥¶ž1ÆcŒ±GuɺOŸ>‰DÈÈÈPk¿|ù2 ÿþºH‹1ÆcŒu]²€644Ä”)Sššªvtrr2|}}¹€fŒ1ÆcMê”ô¶mÛ°~ýzÄÇÇx0˜pýúõHKKbÞ}÷]\¿~óæÍÉ'ðÞ{ï!..ï¿ÿ~»å™——‡ÀÀ@ØÙÙ!44´M§Ëêlêêê0vìXXZZÂÛÛ[×ét8_|ñ<<< •J1uêT$&&ê:¥eáÂ…prr‚ƒƒ^~ùeäääè:¥)44ºN£CIJJ‚‰‰ ¤R)¤R©VÓ©²~øáxzzÂÖÖ–÷Ÿ***„ãN*•B"‘téÏßNY@ÇÆÆbÿþý(++ðaðÿ~ìß¿¹¹¹B̰aÃpäÈ\»v Ó§OG||<¾ÿþ{LŸ>½Ýò\¾|9&Mš„œœôíÛ7nl·÷î茌Œ°aÃáqëL;R©gΜ\.Ç+¯¼‚wÞyG×)u(K–,Áï¿ÿŽk×®Á××Wx{r{ö쥥¥®Óèüýý!—Ë!—Ëqüøq]§Ó¡$$$`åÊ•øüóÏQXX¨q†,¦Y¯^½„ãN.—cÊ”)xå•Wt–ÎtêiìôƒƒRSSaaa¤¤$¬X±ÇŽÓuZJRR"""Ô~]`Ú©©©™™***УG]§ÓáDGGcçÎ8|ø°®Sé0þøã„††bÏž=ðññá_ß´””„7ÞxK–,ÁèÑ£agg§ë”:”iÓ¦ÁÏÏ“'O† ‰k¡»wïÂÎÎYYY]vâ….9>(,,D]]ðó¥§§'RRR T*ahØ)`zê“O>Á‹/¾Èų–Ö¯_èèhTTTàèÑ£ºN§CY¸p!Ö¬YÃÇ\ ˜ššbèСÈÌÌÄÆ!“ɰ{÷n]§Õa\¹rr¹ (..ƨQ£ðïÿ[×iu8ßÿ=‚‚‚ºlñ pÝbr¹ÙÙÙ°°°€ŸŸŸÆ˜{÷îáôéÓ¸qãüüüàãã#¬322BCCƒ°¬*œ ÚƒR©Dÿþýñ·¿ý­ÓOPQQ . ;;}ûöEhh¨Æ¸7nààÁƒ(((€¦OŸÞè Êðå—_¶ë˜1½DL+±±±$‘H Q£FiŒËËË#www‹Å4dÈ222¢·ß~›”J¥ãèèH%%%DDtæÌ?~|{l‚N-X°€¡¡! uëÖiŒÛ¶mÑĉ)44”ºuëF6lh—˜˜Hƒ jë´õ†¯¯¯°ïPVV–ƸåË—“¡¡!ùøøD"!©TJÙÙÙj1QQQ4hÐ º}ûv{¤®sééédcc#ì;{{{q4zôhzæ™gÈ××—ŒiúôéTSS£1þöíÛdjjJ m˜½îmذAØw4þ|q111Ô£G ¢—_~™LMMiÁ‚Âúýë_äææFnnnäììL†††äææF÷ïßo¯Mщàà`µ¿}ÑÑÑãþú׿’‰‰ ½øâ‹L"‘ˆ¢¢¢šìwäÈ‘ÛVië…ÊÊJrttŽ?T]]Ý(®¶¶–fΜIÝ»w'___êÙ³'ùùù Ÿ³DDK—.¥Ï?ÿ\X3f 8p ]¶CW¢££ÉÀÀ@8þšª[Ο?O‰„ D¯¾ú*õîÝ›&NœØh_gddPß¾}©®®®²×_\@kéÂ… ôé§ŸR||<5ªÉqòäÉäááAåååDDtäÈ200 Ÿ~úIˆ™={6-[¶ŒÊÊÊhÖ¬Y´råÊvØÝ:yò$%$$Peee“tII ÓG}$´mܸ‘ éÆBÛùóçiçÎäââB‰‰‰”ŸŸß.Û Kk×®¥½{÷ÒçŸÞd}ìØ1@?þø#UUUÑСC)00Pˆùé§ŸÈÁÁÒÒÒ¨¨¨ˆŠŠŠ¨¾¾¾Ý¶CrsséÿøÅÅÅѬY³š, /^L–––týúu"zðaaffFŸ|ò‰súôiª¯¯§²²2úøãéÕW_mMЩäädŠ‹‹£;wî½½½Æº¾¾žìììhöìÙBÛ/¿üB(>>¾Q|QQ™››·iÞúâÀ”œœLiiiMÐçÎ#´gÏ¡í7Þ KKKá \||<¥¤¤\.§Ï>ûŒììì:ý—·ªª*Z½z5:tˆ>øàƒ& è-[¶±±1¥¤¤уãËÖÖ–ÂÃÃ…˜Ã‡ÓرcI¡PPFFõëׯÉ/ÇÅÕ«WéàÁƒTTTD!!!MÖ- Æ/^¤nݺÑÎ;Õâ/^LK–,ië´õЭÐÔ˜ŸŸO†††´f͵vµ«Ì7oÞ¤I“&‘³³3ýéO¢ÊÊʶNY¯4U@oÞ¼™PzzºÐ–——GhÕªUBÛüùó),,Lø×ÜUšÎ&&&¦Éúå—_&+++µ¶-[¶ÊÌÌ$"¢E‹ѰaÃÔþÉåòvÉ]ÌŸ?_c]]]MôÚk¯©µOš4‰¤R©°xð P»¢§T*©gÏž¿d( úóŸÿܦ¹ê›¬¬¬& è7ß|“ ÕŠÃG¿ÇÄÄÐäÉ“)((ˆV­ZEW®\i·ÜõÁ¦M›š, ½¼¼( @­íwÞ!SSSáóU©TÒ‡~HÏ>û,MŸ>Nž<Ùië¦ê–«W¯µ DDÏ>û,1B­íwÞ¡œœœ¶L³Cà{ ÛÀ¥K— T*!“ÉÔÚˆÓ§O ËýúõáC‡Ú;=½—••‘H777¡ÍÎνzõ·Û·o×Ezz/55TkS‹©©©ðððÀ¦M›t‘šÞ“ËåP(öŸ§§'> …B DEEé(Cý–™™ àÁþR100€‡‡®\¹Ò(ÞÜÜ_~ùe»å§ï²²²àää¡Mu,ªþöM˜0&LÐI~ú¬¾¾éééWk8p îÝ»‡ììl <XµjV­Z¥£Lõ“êü|øÜì¿#GލµmÞ¼¹ÝòÒg<ÝC(,, ù@,))Amm­.Òê0 áîîŽnÝÔ¿ßÉd2aß²¦j,UëXÓnݺMî?Õz¦Yaa!žyæ™Fƒãd2ï»'PXXØès£OŸ>èÓ§Ÿ»QTT¥R©ñsà¿}£Ú?š.ü•••¡ººZié5. Û@UU4zÂVïÞ½AD| >FCCD"Q£öîÝ»«Í\ÂS*•¨©©AïÞ½ÕÚÍÍÍa`` ›L3Õ¹ùèþSË|î6¯¡¡FFFfG‰DP*•:ʪãhhhÐ8ãH$â¿}ÑÜçîÃë™fªãëÑÏ^Õ2qÝúõë~²ÌÈÈ@=`nn®‹´: ddd4úÀÍÈÈ€Ž²ê ammŒŒ µö«W¯‚ˆ„c“iÖ§Oh´ÿTç²j=ÓÌÆÆ•••ÈÏÏWkÏÈÈ€µµµŽ²ê8TûVVV†‚‚þÛ÷Í}î>¼ži¦:¾=þ._¾ 333ôìÙSié5. Û€½½=Í¢jkš³³3ª««qíÚ5¡íÖ­[¸sçœu˜YÇ`ooË—/«µ©ŽE>þšgkk ÷ŸH$âú1Ts«î…VÉÌÌ„ƒƒƒ.RêPœ‘““ƒºº:¡-==]XÇšÖ«W/XXXhüÜøoß㨎/Mû=͸€n¾¾¾ppp@bb¢ÐVQQË—/#,,L‡™u ³fÍ‚‘‘bbb„6Õ“Þ^{í5]¥Õa„……!;;üñ‡Ð–˜˜+++ë03ýgmm1cÆàìÙ³ "~ºLIIÁŒ3Ý—ÏÔ½ð H$jOfLNN†B¡Àܹsu˜YÇ0{ölÔÕÕáäÉ“B[LL zöì‰iÓ¦é0³Ž!,, .\Àýû÷…¶_ýÆ ãú1¼¼¼0hÐ ÄÅÅ myyyÈÈÈÀìÙ³u˜™Óñ, Nii)EFFRdd$ÙÛÛ“­­­°|ëÖ-!n×®]d``@‹/¦ï¾ûŽÈÖÖ–nÞ¼©Ãìu/::šBBB($$„««+…„„¨ÍKôàA "‘ˆ,X@ .¤gžy†Þzë-e­?¶oßN‘‘‘4uêT@³gϦÈÈHaŠ+"¢;wî““ 2„¾ùæúàƒÈÐÐ>ýôSf®TçêÀÉÌÌLX¾té’O&&&4kÖ,Ú½{7M›6ÌÍÍÕbº¢sçΠ箉‰ ÙÙ٠˵µµBÜ®]»ÈÈȈ^{í5Zºt)Y[[Ó /¼Ðéç*~œíÛ·SHHòññ¡Z¾|¹ZÜŒ3¨wïÞôî»ïÒܹsÉÐÐ6oÞ¬£¬õÇÚµk)22’‚‚‚½ýöÛI§Nb²²²¨wïÞ4aÂÚ½{7…‡‡S·nÝèðáÃ:Ì\÷Š‹‹…sU"‘X,–~~Bll,õèу&MšDË—/'GGG|ãÇ×UÚzA$5yî>ü˜óqãÆ!!!ÿýï‘‘‘ððp¼õÖ[èÕ«W»æÛQý¿ß)cŒ1ÆcÕµ/ 0ÆcŒ1¦%. cŒ1ÆÓÐŒ1ÆcŒi hÆcŒ1Æ´À4cŒ1ÆcZàš1ÆcŒ1-pÍcí,-- ÙÙÙºN£YÅÅňGII‰ÆõÉÉÉËåÍöqçΜ:uJmŽ|Æë ¸€fŒu þþþð÷÷GVV–Zû7àï襤¤vËeÑ¢Eظqc»½Ÿ6ÊËË1|øpH¥RÌ™3.\Ð7{ölìÚµ«Ù¾’““Œ»wï }ïÚµKí1óŒ1Öñ“c]Âo¿ýX±böîÝ+´WWWã·ß~ƒB¡ÐUjzåðáÃÈÍÍÅíÛ·[ý2777¬[·=zôaîܹHLL„••ÕÓH—1Æt‚¯@3ƺŒçž{ûöíCJJJ³qåå娭­Uk»ÿ¾Ú#Ókkk…¢»¾¾—.]R{$8\¿~¿ÿþ{³ïURR‚ôôt(•Ê&c ‘––Ö¨P(¨«« 33³Ù÷#"äææââÅ‹¶±¢¢±±±ÉdP*•(//o¶/•Û·oãâÅ‹ò³³³CDDD"”J%*++wïÞ…B¡hô¥¥ªª ÉÉÉÈÉÉiv0Ƙ®qÍë2&OžŒ€€,[¶¬Ù8kkkìÙ³G­mÛ¶mpuu–÷ìÙ±XŒ@,ÃÇlj?þø#òóó!“ÉàââÌœ9³Ñ{ÔÖÖbÆŒèß¿?¼¼¼`ooäädµ˜sçÎÁÓÓýû÷‡ŸŸÄb16oÞ¬#‹±uëVx{{cÀ€˜7o^“Û•––www8::bèС‹ÅøòË/…õ®®®øæ›opòäIˆÅbØÛÛ7»Ÿjjj0mÚ4ØØØÀÛÛÎÎÎHOOÖ;v b±åååÈË˃ŸŸ`ܸq‹Å‹Å€²²2C,còäÉÉdH$;7cŒéÐŒ±.eݺu8~ü8Ž?þTúûä“OpèÐ!cÊ”)ˆˆˆÀK/½„¥K—B¡Pà‹/¾À?ü€ÜÜ\µ×}÷Ýw077ǵk×––'''L™2ÕÕÕ\™7n¼¼¼žžŽÒÒR¬_¿‘‘‘8qâ„Z_~ø!fΜ‰[·n!&&FcžÕÕÕxá… ‘Hð믿âÆxýõ׎ӧOx0ppΜ9 =ö¶–M›6ÁÛÛùùù8wîLMMñÕW_iŒ•J¥Âý牉‰ "`ûöí¸yó&òòòpûömTWW7úÃcú„ hÆX—2räHLœ8ñ±W¡ŸÔâÅ‹1jÔ(XYY!""eeeÉdxýõ×aff†ððpH$ÄÅÅ©½ÎÈÈ[¶l­­-¼¼¼°víZ#::°uëVÔÔÔ`óæÍðôô„™™.\___|ñÅj}ùûûãý÷߇••,,,4æùÓO?¡  6lÀˆ#`ccƒ-[¶ÀÊÊ ›6mjѶ;99aåÊ•èׯ† ‚—^z GÕºŸôôtØÛÛÃÒÒЭ[7L˜0¡E91ÆX{àA„Œ±.gíÚµk´îùçŸÇŸŸºº:DEE!!!᩾ÿ½{÷°zõjTUUáÖ­[X·nÌÍÍ1mÚ4nk‰DX²d rrr„×]»v gÏžÕúýf̘KKK¬Zµ 7nÜ@uu56lØ€‚‚DDD<µíjŽqäȵ"úàÁƒ¸wïž°¬š>O5 ’1Æô ÐŒ±.ë£>ÒØþÊ+¯ ¤¤èÛ·/þþ÷¿cÑ¢EOõ½gΜ‰ØØXH¥R8::âèÑ£øî»ï„A€öööØ»w/Nœ8WWW¸»»ÃÆÆnnnÂCa´annŽÿýï8{ö,\\\`gg‡>ø+V¬ÀäÉ“Ÿê¶5géҥػw/LMM…ÛX>þøcXYYÁËË ®®® Åœ9sÚ5/ÆÓ†©æbŒ±NìÊ•+°¶¶æVÉËËCUUìììÔîé-))Abb",--áëë‹»w´ÎÎÎ\%-**‚»»»ð¥R‰ìììF}åååÁØØX¸Cµ,‘H––†¢¢" >\˜…âaUUU¸|ù2är9z÷î oooµ§ø]¹r¶¶¶033{¢ýP^^Ž . ¢¢>>>0`€Úú¢¢"444 ÿþÍö“›› 333µùšïÞ½‹‚‚¸¹¹ÁÀÀ÷îÝC~~>\]]Ý{]XXˆÊÊJ¸»»C©T"-- ¹¹¹°´´ÄðáÃallüDÛÃcºÀ4cŒ1ÆcZà[8cŒ1ÆÓÐŒ1ÆcŒi hÆcŒ1Æ´À4cŒ1ÆcZàš1ÆcŒ1-pÍcŒ1Ƙ¸€fŒ1ÆcL \@3ÆcŒ1¦. cŒ1ÆÓÐŒ1ÆcŒi hÆcŒ1Æ´À4cŒ1ÆcZø?[ÃNªÀö9²IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/Q7-10m-noidx.svg000066400000000000000000003777151416254111300235010ustar00rootroot00000000000000 PyTables-3.7.0/doc/source/usersguide/images/Q8-1g-idx-SSD.png000066400000000000000000002624311416254111300234570ustar00rootroot00000000000000‰PNG  IHDRÐß}™SsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìwX××Ç¿»°,e©"] Š °D"v‰Ñ$$v±%±$–hŒŠ%+ØK{‹±!XPPŒŠ ^÷¼øÌüfwYDcÌ{?ÏÃã³gν÷Ü;3×3wΜ+!"ƒÁ`0 ƒÁÐ é»6€Á`0 ƒÁxŸ`4ƒÁ`0 ƒQ ˜Í`0 ƒÁ`Tæ@3 ƒÁ`0Õ€9Ð ƒÁ`0 F5`4ƒÁ`0 ƒQ ˜Í`0 ƒÁ`Tæ@3 ƒÁ`0Õ€9Ð ƒÁ`0 F5`4Cknܸ͛7ãûï¿ÇÚµkqéÒ%”——¿k³þ1öíÛ'''ÄÄļkSÔrôèQŒ1 6Dƒ Þµ9ï£Fbcö¥I“&øâ‹/´Ö/++õk×°gÏ8pàµÛU*•¸té6oÞŒiӦ᧟~ÂöíÛqïÞ½×®ó]‰Æ£  €—½Íû¦gÏžèСÃ[©ûM³nÝ:899áÆ¼lþüùðóóûõÿäÿ'tßµŒ?™™™Çï¿ÿ055EVVÀÏÏ›6mB½zõÞ¥‰oŒíÛ·cúô騾};¼¼¼Çòóóqÿþ}¾#ë4óÇ wïÞhÛ¶-CCÃwmÒ{ųgÏpÿþýwmã-žžggg­tÛµk‡‹/ò÷¹‰‰ ²³³«ÝæÝ»wñé§ŸâÌ™3”––¢¬¬ R©=zôÀ/¿ü¢µ]ïš²²2üðà Ì-oó¾yüøñký» ''÷ïßGII /ëÕ«¦L™‚íÛ·càÀïÐ:ÆÛ€­@34’““üñǘòóó‘ŸŸ«W¯bÖ¬Y8vìâããß°õo-[¶àÞ½{3fŒ@¾páB\»víYõïÆÅÅݺuÜ9s@DïÚÆ†9Ð Ìš5 éééX°`æÎ ;;;€¹¹9F…]»v!''“&M”ËÍÍEnn®¨¾òòrdgg£¸¸Xe{yyyˆGRRJKKÕ–çžò qþüy¤§§#''yyy*ë%"dgg ^=V¦°°wœóòòììlôõõamm ¹\Η)))Avv6ÿŠ.33.\Ù®T*‘€´´4µísv¦¤¤àìÙ³ÈÌÌÔ¨ËQVV†ììl¤¥¥¡nݺüU~(//ÇÍ›7üü|•uåçç#''‡ÿ}ÿþ}œ:uJëÉÿùóç8{ö,._¾¬ö\”——ãÖ­[ˆ‹‹S«“ŸŸ—/_ò¿SRRpóæM•z±±±*ÇJ©T ®µ‚‚œ?Zõ¥2<À™3gðøñc•vdggC©TŠŽ ;;eeeZµS\\Œ .àÁƒþwíV<ŸÜ9WuÆ®2ééé8sæŒÚq¨<ö<@LL ŠŠŠÔ¶ €?®j Tõ111'OžÄ;wÔŽMÅ{šˆpýúu\¹rEíü¼›„„„×~¨OHH@TTFŽ ccãj—W*•GQQNž<‰qãÆÁÁÁ‰zzzhÒ¤ ¦L™‚[·nÁÛÛ›/Wyn,**B\\œ(Ü#++ çÎCZZšÊûRÕ½ÏÍSÜ\ÆQ\\¬Õ9S*•˜3gBBBDáµk×F:u²ÊóHJJ .\¸ vÞ^ß›7o"11Q«kH©TâÎ;8wîÿF´"999jW¯¹ù½òø•––"))IãÜÄñôéSœ?^cŸ`ôèÑHJJ®]»ªè㽃 5<þœôôôÈÎÎŽŠ‹‹Õêùûûºzõ*/óòò"///‘nBB E‹ ä™™™Ô¿’H$€±±1EFF ôbcc -_¾œÂÃÃI&“š4iuëÖŒŒŒ(''GÔîÁƒ EDD¨íGxx8ßvÅ¿I“&ÑÖ­[ :tˆ/³bÅ @ÇŽ£.]ºðö›˜˜PTT-]º”LMMùúºvíJ/_¾µ¿yóf233´Ý³gOzþü¹Z›‰ˆ:¤Òî1cÆð:kÖ¬!…BÁ“J¥ôÅ_P~~¾ ®àà`ªS§]¹r…yý¼¼<6\¾|™|||íëëëÓÒ¥Kz6l ccc^G"‘Ð!C(77W J&&&”””DÎÎμ¾““%''SII 8ôôôøþÌ™3GPÇÍ›7 Í™3‡FÍ_+Ü9ÈÎÎè÷ë×är¹¨oýõÙÛÛ úæççGiii¼ÎŽ;=ZP6==ÌĮ̀qãÆTPP q ‰ˆÖ­[Gúúú|;ü=Sñ|rç|ëÖ­¢:ÔõãÀdcc#èG@@¥§§ ô*޽‹‹ ¯G‰DÔGŽÎ;“™™jìã¤I“×"²´´¤7Št) €Ž9Bæææ¼¾ ?^e+ÞkvvvtéÒ%211¡ÐÐPv©ÂËË‹LLLªUfç΀>þøãj•ãÎóÂ… iôèÑüµÍ÷GQçÎãfkkKýõ_‡R©$+++êÚµ« îqãÆ8p @þé§Ÿ’\.¯òÚ>>dmmÍÛ}ïÞ="zå”    :}ú4%&&Ò„ õèÑCPWpp0Rݺué§Ÿ~¢øøx:zô¨Æ¨ÄÄD’ÉdT·n]Ú¸q#%''SBBýú믂s±eË@þþþCׯ_§©S§êܹ³ ÎÐÐP’Ëåäàà@Æ £˜˜š>}:) jÙ²% 8üüühË–-´cÇjÒ¤‰è?/ζ¶¶¦æÍ›Ó¹sç(%%…æÎKR©”BBBmªrŽ9B‰„|||(""‚èûï¿'+++ª_¿¾`\¾üòK@{öì!¢W×µŸŸRRR’ÆsHD´wï^@mÚ´¡¸¸8º{÷.ýðÃT·nÝ;Ð{öì!äëëKË–-£„„š:u*™››“‡‡•––ŠÆÞÖÖ–fÍšEqqqtüøqÊÏϧ:©©©ÈINII!‰DBcÇŽ­²Ÿ£G¦_ý•.^¼HÉÉÉôÛo¿Q‹-H"‘ÐÅ‹ºŽŽŽdeeEÖÖÖ4vìX:{ö,}÷ÝwdllL®®®TVVÆë^¾|™¤R)5jÔˆNž|˜ÜÜÜH"‘Pll,_O¿~ýH¡PÎi“&MH&“‘µµµ Mjß¾}•¶qóSLLŒè˜:ÚÐÐìììhÈ!tòäIZ°`ÙØØP­Zµ‹OŸ>¥ZµjQ:uhÏž=”žžN;vì +++2339Ћ/æç³õë×S||<1‚ ©S§N¼^yy9’¾¾>¿¸“™™IŽŽŽdkk+°aøðá$‘H¨wïÞôÇЩS§hðàÁ¤««K£F´_q¿{÷.ÅÅÅQ»víÈÚÚZ­ݬY3rrrªrœïÌf¨eÍš5€æÎ«Qo×®]€&NœÈ˪ã@GDDš7ož@·¨¨ˆLLLÈÍÍ—q´¾¾¾hÕ¤¼¼œêÕ«G>>>ùãÇIWWW«!Î!®ø‡&Ú××W »aÃ~…¢¢óT\\LúúúäééÉË233ÉÌÌŒìííÿéMž<™ÐÁƒ«´= €²ââb²±±!CCCÑJJHH Ó§Oó²àà`@“'O®²½ŠíÊd2º}û¶Z²²2rtt$===Ñjö‡~HèèÑ£¼,44”Ð?þ(Ð8p  ü¯¿þ"ôí·ßò2Î Ze0` sçÎñ²ÊŽ@yy9yxx¡¡¡èÁ+22R´ºUXXHMš4!333zðàîÖ®]«v\*âëëK‰„žD}öÙg€âããEvW|Fô¿Õ×ÊîÓ§ Ë—/‹ŽeggSVVÿWñ^àæF™L&zCµjÕ*ÑõMDtåÊÞ™¬¬{öìY""zòä I$úꫯçèöíÛ€fÍšUeŸFMèï¿ÿSç@ Ù³g ä , ´aÃ^Æ-Zlß¾] »téR p Ÿ>}JÆÆÆäââBåååý±cÇ:qâ/{øð!YXXPÆ )//>üðCÒÑÑ¡S§Nñ:ׯ_'êÒ¥‹¨o=zô ]]]~þ¸~ý:I$êÖ­›@/33“tttÔ:Ð ‰DRå›<Æû‹f¨…‹+«*;þìÙ³×jçÀ044ÄgŸ}&ËårôêÕ ·oßÅSÀÀÀ@ “J¥GBB.\¸ÀËׯ_²²²j¥±ª.}úôünÛ¶-ÀÃü\OO-Z´ÀßÿÍËΟ?ÌÌLŒ1ººÂÄ8Ü—Û—.]z-»nß¾G! @» ÁÁÁ€S§N‰ÊuëÖM«ú‹‹‹qúôiôèÑ®®®jõÒÒÒpÿþ}´iÓFFF‚c!!!jí üæÆµ²¼uëÖÐÑÑQ™ÌÝÝöööYÇŽ@cJÂÔÔTܸqü1LMMÇzöì }}}ÁyÑ××ÇŽ;P\\ŒŽ;bÞ¼y4h†ª¶ ŽÂÂBþƒ\+++•¶¾.·nÝÂÝ»w1tèPÔªUKp¬OŸ>Éd*¯/U×@hh(lll°zõj^VZZŠuëÖ¡mÛ¶‚k]ùùù8~ü86mÚ„•+WâäÉ“°±±AJJŠHW¡P K—.Yûöí@ýáôéÓ°²²‚§§§@·¦ãW]¸yÓÄÄDt¬N:055åÿ $Òñóó͹ÜuÚ¹sgÜËË ÖÖÖ8sæ ÿFPPàøñã€'N@"‘àÛo¿…¾¾>/çþÕ&M\RR `ccS¥nE*Ï‹ªÎ[LL $ o7Gå¾À™3g““ƒQ£FA*º/ªæJDEE!99¾¾¾Ø½{7f̘!øüСC(//} }ûöEYY®^½ àÕ5FD"ÛLMMÑ´iSµãàææÆÇx3þ;0š¡¯>:ÒwœûÀ°ºÜ¹s077‡D"ü­[·N¥ •'[ŽaÆA.—ã·ß~Ö¬YƒF¡M›6¯eŸ6T®»nݺ*åܱŠ3Þ¹s0yòdQÿ›4i¯&Š«[•Ñ©S'9 …­[·ÖªþÔÔT(•Ê*óÀj²ƒ“U¶ÃØØXäqãZÙ>…B…B¡òÃUmrÿ‘§¦¦ªµùÖ­[€•+WŠÎ‹‰‰ ŠŠŠDç¥Aƒ˜1c’““amm•+Wª­¿"Üašl}]¸~,^¼XÔsss”––Šú!—ËÑ®];Q]ºººøì³ÏpêÔ)¾Þ={öàÉ“'×Êž­[·ÂÉÉ ;vÄÇŒŸ~ú ‹/Fff&ž>}*Ò÷ññ=,sŽw¾óòò™™‰ÀÀ@H$®¿¿?ttt´²íMÀ=Hªú`xÙ²eX±b.\¨¶¼ª¹íÎ;044Ty_vìØ%%%üéââGGGœ8qpìØ1x{{ÃÖÖ~~~¹±±1|}}«ìS^^ Dc« KKKÑCuåó¼º===aaa!Ðuss=øróÈØ±cE×r‹-ˆçÊ®]»bРA¸yó&Z¶l‰©S§ Žs×qHHˆ¨Î>úHP'7_T÷>å ªú0‘ñ~Áò@3ÔâææU¦(âŽ7nܘ—I$•_ˆ«J'“Éàää„õë׫mÃÑÑQð[ݪ¸……z÷îmÛ¶aáÂ…ˆGjj*"""4ö¡¦T^ ©J^™LX°`š5k¦RÇÚÚúµìâU_©s_¨W^™622ÒÚáàê¯üuÿ›°CÓÖÕù\Õ—ø\– }}}µå¸1˜0a‚ÚùÚµk ~ð}8{ö,¿Ò¯ ÎM¶VDSÿ+ßc\?¦NÊ?4UÆÌÌLdžžžJÝÏ?ÿ³gÏÆš5k°`Á¬Zµ fffèÝ»·Z›8îܹƒ!C† aÆ8xð <<å¸}û6Úe¼ÿ0š¡wwwx{{ãðáÃHLLT9aeffbíÚµ¨]»6>øà^nff†¸¸8”––ò“>|XT‡‡‡öïßfÍš‰^1¿ÇÇæÍ›±yófDGGÃÀÀƒÖª,çÄ©KÕõ6àVYKKKøFëæV€Ž=ŠüQpìèÑ£P£ —ËqåÊ­í¨Ì›°CÇŽɸU8Mmrç%77Wëó2bÄܺu ;wîÄwß}‡ÁƒãêÕ«üʹ: «««ÑÖŠpobb"ú÷ïÏË‹‹‹qòäI•ýÈÏÏ#×—>øàDEEaذa8vìÆŒ£ña„ãôéÓ())Á÷ß/Xù¼yó&nß¾ýÚ;Úéééñ¡ •çœèèèתóuéÑ£¦L™‚… âË/¿=¾®®®ˆ‰‰Att4>üðC^^ZZŠ“'OB¡P²ƒ‚‚°~ýzDEEáþýû¼#ß¡CL:k×®Eff¦Ö»ü5jÔÀ+GPÛ·SÚâì쌘˜¤¦¦ îÇK—.!;;["»–ËË˵º–KKK1`À”––âðáÃ0`úõ뇸¸8þƒ«S.—WY'gß±cÇàçç'8¦ê>å¸}û6ÌÍÍEéþï7,„ƒ¡©TŠˆˆ, £xùò% „œœLŸ>]°R¿~}äçç#..Ž—aÇŽ¢v>ù䔕•áÛo¿UiGuw¢òóóƒ——/^ŒÝ»w£OŸ>¢Vup!IIIÕj³&´jÕ nnnX¸p!žÈd2ôèÑãµm—J¥4hNœ8¡òáˆ[Qä^_»vMçZ^^޽{÷BGGGà¼I?~,ذ‚ˆpàÀÈd2m:::"00‘‘‘*ß‘ ×í† ‰ & wïÞØ¾};rss1hР*óÚêêê¢GHKKCbb"/W*•8xð HßÙÙR©Tô@²{÷nMÀ«7I­[·ÆªU«TÆ`*•J•9Û5ñÕW_áùóçèÓ§ˆŸþ¹Vå¸Õ¾‡ ä[¶l©VûªèÝ»7òóóEŽÌ¾}ûj\wu¨W¯¦NŠôôt 2Dåü¥j¥]½zõüùçŸùÑ£G‘——ÇçàÂ@~üñGèééñß4kÖ &&&üô¶4çdr+©o’¾}ûŸ§={öˆtýýýQ¯^=ÌŸ?/^¼/))¼ùöÛoqþüy,_¾ÁÁÁX¿~=1vìX^',, …Ó¦MS™_<77—ƒÑ¥K`ÿþý‚·ÉÉÉ|x‰*nß¾­õ÷Œ÷ˆwóí"ã}bÉ’%$—ËÉ‚† F4jÔ(rrr"4räHQ™Ë—/ð*òøñãiܸqdffF:teá "ÙÚÚÒüùóiÍš5Ô©S'•¸\Ä•á2¾¨Ê%[9×/—…£U«VdiiIsæÌ¡µk×R×®] M˜0AP^ÕyINN&sss200 qãÆQTT­\¹’ÆGŽŽŽ´~ýz¾-###jÑ¢•””ðåýõW•i UqõêU222"kkkš7o­Y³†:wîLmÚ´eá "úè£5kÖŒ,X@AAAdffFÞÞÞ¢~\¿~LMMÉÈȈ&L˜@6l +VÐØ±cÉÞÞ^ÍCÝØWD©Tò9¢Û¶m[eß8>|H&&&dffFcÆŒ¡ÈÈH:t(ÙÛÛSýúõE)˸<Е‰ŽŽ&üø½Ê“leeEµjÕ¢™3gRdd$õë×¼½½ÉÐÐPë,3gΤÐÐP %cccÒÕÕå1B«:Š‹‹)<<œ$ Y[[ÓG}D³gϦùóçÓÈ‘#ÉÎÎŽ¤R)M›6/£.G>G—.]ø\Α‘‘4cÆ R(djjʧ¬¬HÆ ùÔ˜ᲕTNi§‰ììl211eI!Òœº2?&à¹õ‰^•‡‡éêêÒøñãiÆ 4|øp²³³#'''Ñ5qìØ1200 KKKš6mmÞ¼™–,YB_|ñ™››ósñ¡C‡H"‘ˆ²/qE¶mÛÆËV­ZER©”êׯOsçÎ¥-[¶Ð/¿üBƒ "###ÊÊÊâu¿ÿþ{@:u¢5kÖмyóÈÆÆ†Z·n­2 Ç­[·´ÊfÅxÿ`4C+¨OŸ>T¿~}Áf'«W¯V[f×®]Ô°aC’J¥äææF3gΤ[·n‘——mÚ´I¤¿qãFòõõ%…BÁÿÇóÁÐŽ;xk×®‘——íܹS£½999|NØêr÷î]Šˆˆ nݺ‘——¿ùÊáÇÉËË‹OEôjÓ///ºvíš ŽââbòòòR™"jÒ¤IÔ¢E ‘<--úõëÇo(`ddDÞÞÞôí·ßÒ³gϪ´{ذa*S1½Ú¨ 00LLLH&“‘——— uÇðáéC‡U¶U™¬¬,úâ‹/¨^½z$‘HH.—S«V­D¯Î_‡¨víÚ$“ÉÈÓÓ“V®\)ªoìØ±*³'N——— UGÛ¶mŽ8ç@Ï;—6oÞLÍš5#]]]rqq¥L$R^ž={FŸ}ö¹¸¸T*%¹\Nîîî4nÜ8JKK#¥RIƒ¦æÍ›Sjjª¨ü_|AÍš5Ó˜æ¯âøðC‡¥G©t srrhèСdjjJÔ¾}{ºté’Ú~dddЧŸ~JÎÎÎ$•JI__Ÿ<<<è믿¤ùS7ö•áò«ÚE§OŸæÏ…¥¥%õèуîÝ»GÆ £?üP Û¥K6l˜¨Ž .——Ÿs›#==ºuëF …‚,,,¨wïÞôìÙ3ѵ¡‰áÇói8+ÿU¶¯*¢££©W¯^Ô AÒÑÑ!CCCjÒ¤ 2D”òOÓÜHô*á´iÓÈÍͤR)YYYQXX˜(E#Ç?ü@^^^´|ùr|Æ äåå%z€¬Šo¿ý–ôõõEs‘ªëMÝ<òüùsÁœÊ‘••E$333ªU«uîÜ™nݺEƒV9æwïÞ¥^½z‘ …BA>>>ôý÷ßSff&åççS§N¨cÇŽ¢ÔqÅÅÅÔ½{wj×®À1Ž‹‹£Î;“••¿™K›6mhþüù¢ô¢K—.%777ÒÑÑ!Z¿~=mÚ´‰¼¼¼èÖ­[Ý‘#G’©©©Ê ¾ï7ÌfT›¢¢":xð ééé¹Ð‹4 IDATQÆ «Ü)¯òäSJ¥RãÆÚÀmJQy'¼÷…Ê9›ß$Õ=ÕE[Ûß¶è7Õfqq1¿{ÙÛ¤¢YYY*huúÚPÓû‹ˆ¨M›6dnn^å΃êx›ç¿â+ÿ*¾™ø7Õ¥-ÏŸ?'…B!ÚñóM¢T*«}îÞÆ\ù¦æ°—/_’B¡Ðê ãýƒÅ@3ª\.G—.]°qãFܾ}]»vE~~¾Zýʹ«B"‘¨Í  ………˜TÅÛ<ÿÿdÚ:m©øaã¿©.m177ÇW_}…_ýeeeo¥ ‰DRís÷6æÊ75‡­_¿R©TeŽiÆûs ¯Mß¾}ñ÷ßcûöíïÚ¯> ñöö†……nܸŋרQ`0ÿcåÊ•pssCXX¼¼¼´ÎýÌøo0cÆ =z´Êb¯èÝ»7®_¿.JwÉøo 3cÆŒïÚÆûK­ZµP»ví…£JDHOOÇ|€¥K—¾ñtKŒ÷ "‚T*Eûöíáääô®Í©eee äs³¿+²³³a``€ñãÇcΜ9oõM ã߇L&ƒ……Å¿r…ÿ߈±±±ÚÜÒŒ÷ Q5óé0 ƒÁ`0ÿa! ƒÁ`0 F5`4ƒÁ`0 ƒQ ˜Í`0 ƒÁ`Tæ@3 ƒÁ`0Õ€9Ð ^厾rå ^¼x!’={öL«:RSS‘””ô¶LüÏ’œœŒäääj—ËÈÈÀ•+WPRRò¬b0´£°°W¯^Åýû÷ßµ)ï”§OŸŠîÇÇãÊ•+ÕÎ]PP šŒÿüN F yðà~ùå\¹r077‡³³333³j×™œœ ¬X±_~ù¥@¶hÑ"Œ;¶Ê:†ÎÛ¤ {÷îÅÆ‘ššŠââbÔ­[>>>F‡x½G!//¯Æ)Ìž>}ŠÌÌL¸¹¹A*}óÏÎQQQˆŽŽæ›˜˜ÀÉÉ ŽŽŽhÚ´©ÚTrýúõ\¹r¥Zí­\¹3gÎDZZÚ{Ÿ¦î]‘’’‚ü‘ÿ-‘H`aa'''ôïßæææjËfdd`òäÉH$ˆˆˆ€±±±Hçùóç˜0aZ¶l‰¯¾úŠ—O›6 ééé‚6áää„V­ZÁÂÂâµú³k×.ìܹIIIððð@XXúôéóZuUÅ… 0{öl=zèß¿?¶nݪV¿¤¤kÖ¬Á¡C‡––===ØÚÚ¢eË– …§§§Êr<À÷ß/縸8¬X±B+[6lˆÉ“'ó¿W®\‰óçÏ#((üq5z­žuëÖaÊ”)‚û1""óæÍÃãÇamm­u]×®]CëÖ­ó1ƒñoƒ9ÐŒ÷ŠØØX¡¨¨õë×G›6m““ƒsçÎaÓ¦M°°°À‡~øFÚÒÓÓƒ£££JÇ ¦ :ëׯ‡\.G«V­àããƒÔÔT,_¾;vìÀ½{÷xÝñãÇc÷îÝ(**ªQ›sæÌADD²²²ÞJbÿØØXDEE¡^½zÐÓÓCqq1>|ˆÒÒRèèèà“O>Á´iÓàèè((gccóÆmahdzgÏSSSXYYˆ’’‚òòrL™2;vì@HHˆÊ²6l@TTÀÏÏŸþ¹H'//QQQ(**8Ð{÷îŵk×ø‡Âüü|¬Uªbîܹoì~ä®ËšÌÇ ÆÛ†­@3Þž?Ž;wî U«Vèܹ³è¸ºïß¿£G"99öööh×®¼½½«l/;;‡FÓ¦MEá7nÜÀ¡C‡››‹6mÚ ((Hë~œ;wðõ×_C___p¬V­Z ã}:j×® "µk×päÈ<{ö 666ðóóC‹- ‘H;wî„èšNOOǹsç(x%^VV†sçÎáèÑ£(//‡££#Úµkwwwc¥ SSSŒ5 HMMEjjªèú?wî’““1oÞ¤¤¤ yóæjï³k×®áÆøðÃs]aa!Nž<‰“'OB.—ÃÙÙAAApppÐØ®R©ÄÞ½{Q\\ŒîÝ»ÃÈÈðâÅ ;v ñññ011»»;‚‚‚4† 15æ]{ð †¶äææ’D"¡:uêPII‰Ve6mÚD …‚tuuÉÝÝ I*•Òwß}'ÐSµÍÉ-Z$Ð]·nÉd2ÒÓÓ#www’ËåôñÇS§N´Zž?> ©S§V©Û¶m[Ò××'T§NþïСCDDôóÏ?266¦Æ“\.'äééIÿý7_OÏž=ÉÐÐ_UäꉊŠâuV¬XÁëØÛÛ“D"!úñÇ«´“Hõ tEJJJ¨^½z€rssy¹——yyyñ¿ËÊʨW¯^$‘HÈÐК4iÂÛ3wî\^OÕ trr2Õ«WéÆjmMKK#‰DB£G+//'òööØ4eÊÒÑÑ!äàà@H¡PЦM›å?ùä@Û¶m#²´´$cccJKK£ääd²µµ%dmmMMš4!333@|r¹\´jKD´uëVÀŸ"¢}ûö‘±±1I$rvv&www222ÒêZTµÍѶm[@ÉÉÉ¢cÜ…‡ÒÍ›7 }óÍ7"½ê¬@WäÑ£G$“ɨ~ýúUöˆ¨y󿀒’’ò[·njÞ¼¹Võ,X°€d2éë듇‡éé鑾¾>­\¹’×=z4YYY244äï¥K—.©­7,,ŒÐÞ½{µ²ƒcðàÁ$“ÉèéÓ§tíÚ5µãÌQÕ ôƒH*•Rxx8õïߟär9effjmÓ… ¨nݺ$‘HÈÍÍj×®M>>>ôÍ7߈îÇI“&‰ì‰ˆˆ }}}ÒÑÑ¡† ’««+Éår àuT­@RXXÉd2ŠŒŒäåܵ(—ËÉÓÓ“œœœHGGGå5Í`¼I˜Íx¯èÚµ+ &MšÐÏ?ÿLÉÉɤT*Uꦤ¤žž5nܘ=zDDDôá‡Ú·o¯«­’’BºººÔªU+zþü9eeeQ»víHGGG+§%))‰ôôôH.—Sß¾}iÏž=Ã*4…pœ>}šÎ;ÇÿÎÍÍ¥¨¨(@ƒ èj áàÂJzôèA/^$"¢;wîPÿþý 8q¢Ê~Uå@ 0€ÐåË—yYezß¾}€¾ûî;Áëîû÷ïÓµk×øß•è³gÏ’¹¹9ùøøðç[daa!z;|ø0 Å‹ó²%K–úòË/ù0¡øøxòóó#}}}JMMåu9ÚÑÑ‘bcc‰èÕ+ÿÒÒR4héèèœ-¥RI111”ŸŸÏ˪ã@»¸¸­­­ 4¦¸¸˜þúë¯*Ç@}ûöm’H$äàà *“——G …‚:wîÌËZ´hAÖÖÖ¢p×u ‰ˆÜÜÜHGGG«‡e[[[ÒÓÓµ_^^Ndkk[e§OŸ&Ô±cGÊÉÉ!"¢çÏŸSëÖ­I*•ÒÕ«Wyݬ¬,µªØ°a SSS>|8:uŠ 4–yùò%R=xYÓ¦MUŽ3GUô¬Y³?g•Ú;wðòåK´nÝÇŒOnn.ôõõUŽªì}ûöÅÇÑ®];¬^½?ÖÊVMôéÓûöíCXXþøãäääT»Ž„„,^¼?ÿü3Ž9‚’’üôÓO*3Ú¬[·µjÕBÏž=yYÿþý¡§§‡õë×ר/©|­Ü»w·nÝâÿîÞ½ àU<©TŠÒÒR•õ”––B*•òñåê¸~ý:j×® ___¼]»vÐÓÓÃÕ«WkÔŸaÆ!11ÇŽÃÈ‘#Q·n]¬\¹ 6TçÍÅ*ðÁ¼làÀÉd¯5ΧOŸFJJ ÌËttt0pà@\¾|×®]«²n :vì(+ ´jÕªÊòÖÖÖ BDD>ùä>|Xcv¡ . uëÖÐÓÓÃùóçáåå%8ÎÅ99ãÇGll,”Je•v0o„wíÁ35%++‹_騸’éää$Xm©Hß¾} =yò„ˆ´_vrr¢ ¨¬³E‹Z¯@«âÒ¥KÔ¨Q#‘šV 7nÜHR©”jÕªE-Z´ O>ù„ÆŒCmÚ´­©[¾sç ’Ëå*ÿ´é—6+нzõ"týúu^VyšèÕ }·nÝøúFѯ¿þ*xíÍ­@+ 222¢[·nUice† B2™Œ¿V¯^Mh÷îݼÎÞ½{ «ê¯]»v¼>·]VV&j¯¨¨ˆ–/_Nnnnü˜‡††ÒñãÇzÕY~öì}÷ÝwdaaAÈÈȈÂÃÃEñÀª¨ÂQ^^N/^¤víÚÚ±c‡@?99™¯¯/mݺUðçááÁÇërÔdÚÉɉär9ÆãååůޢÒÛÒÑÑ¡¢¢"A%%%¤§§§2¥"J¥’¤R© ,¥"-Z´ þ÷ë¬@«jóÀ¤P(Aô¿·d~~~Z3‡¦h7ož ¾iÓ¦iÝ—!C†•1Ó'NÔ*úÞ½{4räH222"dnnN'N¤û÷ïó:Üuɦ“øøxêÓ§BâììL , ììì*ûÃ`Ôæ@3þ“……I¥RÞÉjÑ¢Éd2•¯ôÝÝÝI&“ñN޶tË–-IOOO¿Gô*ƵV­Z5r ‰ˆÖ®]KÀ«tWêèòòr¾ÍÊñ”ýúõÓÚÎÉÉ!4lذÙ^•››KuêÔ!Aú>U4ǃhÙ²eÔªU+ ø ‘s ÿúë/²··§:uêPbbbµl>yò$ … Ñ«ç,--q·ñññ€~þùg­êäM(•JЧo¾ù†lllH"‘ÐÝ»wùㆆ†Ô³gOQ¹… ŠhŽÂÂBú믿èÓO?%…BAÖÖÖUÆ«‹~øð!YXXµµµÀ™ÿüs@¿ýö/ãœQmÇ™C““Ã;¬êþ,,,D!J•ábU¡f\ÈMU4ÇË—/é?þ ^½z‘L&#___þw].Z´ˆºtéBR©”V­Z¥Ñ¶ŒŒ Z»v-5j”F}£¦°Æ‚ÂÂB”••A.—ói¬\]]QZZŠ˜˜îÇqóæM¸ººV;Áƒ PRR‚3gÎäqqqÈÍÍ­Y'ðj [‚0KKK”––òÇ8ÒÒÒ››‹AƒÁÀÀ€—ãÀ¢º---/_¾ÈkÕªGGG?~ÅÅÅ5îƒ:¦M›†'Ož`ôèÑ¢ô}ê°³³Ãˆ#pöìY¸¸¸`íÚµ"WWWœ:u FFF Ä¥K—´¶©]»v¨W¯¢¢¢’’‚3gÎ`РA‚ÔuîîîÐÑÑÁáǵ®·*$ |}}1þ|ìÚµ D$HíW·n]ÄÆÆŠÊýùçŸjëÔ××G§N°nÝ:Ìž=8xðàkÙgccƒùóç### ,”——cÆ °³³CZZšè/55–––X·nÝkµÉQVV†Q£FA©TbÒ¤I¼ÜÏÏ!!!ü_ûöíùc]»v$%% êJLLׄ««+=z$ªƒ»·k’:NÜ}mbbàUÿ7lØggg•ã|÷î]˜ššVkœwìØüü|,X°@e³fÍÂóçϱoß>õ4hÐpôèQ¼¤¤D4ÏV…±±1ÂÂÂðûï¿ã«¯¾Â… pëÖ-޾¾>vïÞ=z <<K—.U[_:u0tèP?~­[·ÆæÍ›Q^^^-›ŒêÀèjðóÏ?#,,  @dddµâT5çÙ³g0`ŸS•#;;3fÌ@vv6üüüx§xôèÑH$X¾|9gGDøå—_ãÆ«¶ #FŒðj+ܲ²2¯‹eË–i]ǪU«0gÎ<{öL ONNæ•víÚñòN:A©TâØ±c}kkkH¥R9rDðEDDòòòDívêÔ Àÿò.WdÆŒ¸wïFŒÂÂBÁ±ôôtу6”——ãÞ½{8tè±hÑ"¸¹¹aöìÙË;wiiiYvv6^¼xÁ篮Œ““N: tèÐAt¨C"‘à“O>ÁÕ«W1~üxÀ!C: …&LÀñãÇ1þ|QŒebb¢Vñ£°{÷näçç dœÓÀ=ø@Ó¦M‘‘‘ýû÷xÇ»mÛ6Q¿òòò°ÿ~‘£ÀÅ«/m|0`€Êqvqq©ö8s»ž6LeŸ}ötttªŒ­îÛ·/,--±uëV}Z$¿sçðy+¢§§‡;w¢_¿~=z4æÏŸÏKHHÀ7úEEEøûï¿¡££Ãv@d¼]ÞõøûÄöíÛ)!!Μ9CAAAl—¤˜'OžT*%dffF­[·¦fÍšñ2{{{AÑÌ™3I*•’½½=õìÙ“ÜÝÝ õéÓG£Z<ÐãÇ'Ô A0`¹»»SÛ¶m©M›6Z…püøã|æ gggêСÕ­[—•ÊåhåÈÍÍ%KKK@uëÖ¥ бcLjˆhܸqüë×>}ú9;;Ó_|AèÞ½{|=åååäââBÀ«\Ð 4 mÛ¶ñÇÇÇïØ­[7êÙ³'ùøøT*ÕêU5ÂÁ¥èãb‰‰ ýðÃ|z°ŠTá˜4iI$òöö¦~ýúQpp0™˜˜T*¥={öðzªò@gdd§§'i•zˆ(55•µ®˜û¹"………|ü¶³³3õìÙ“ºuëF 6]7šB8©V­Z@}ô5mÚ”ttt¨^½z‚˜Öëׯó¯Ü½½½ÉÚÚšLMMù\»\ÇãÇùxààà`0`_¬2»"šò@ý/Ä´iÓø\ÆšÂdΞ=K諯¾""Í!ø{nü¹ûxõêÕUf*©Ì–-[H&“‘©©)uêÔ‰ÌÌÌHWWW”§[Ÿ}ö WWW ãó–?^ WÝŽnݺñýmÔ¨‘±±1[¿fÍ^·gÏžˆsZW„K¹Ç3‡ª.v¯^½4ÚØ©S'ÒÑÑ¡‡jÔÛ¹s'Éår²²²¢¾}û’¿¿?ÙÚÚòñÑšB8¢££ x•G½{÷îÔ·o_²··'4bľœª<Ðååå|3gÎ$"¢E‹ñßHôîÝ›ºwïNæææ€V¯^­± FMÑ™1cÆŒ7ì“ÿgiÔ¨¬­­áàà€ÂÂBœ;wNð%:ãíbdd„áÇ£I“&033Cyy9ŒŒŒÐ¡CŒ1«W¯†™™™ L@@:tè<}ú^^^øæ›o0cÆ ÑŽ` …°±±ÉìììxYpp07nŒ¢¢"””” ,, ,€B¡@“&Màçç§±þþþ …££#  àîîŽîÝ»céÒ¥øì³Ïúzzz˜0aBCCakk WWWøùùÁÂÂ;w†‹‹ ”J%”J%ºt邈ˆØÙÙÁÁÁ|¸„D"ÁèѣѧO888ÀÙÙ­ZµâûŒ®]»ÂÐÐYYY(//‡‡‡¾ù愇‡ VHÕáææ´mÛ|ðÂÃÃ1qâDÌž=:uR›}£yóæhÞ¼9ÀÃà 6áÅ‹pttĘ1c°|ùrøøøÊ999 ú¨P(зo_èééáéÓ§hÞ¼¹`GFU˜ššÂ¾¾¾øüóÏáìì,ÒÑÕÕEß¾}ѺukÈår<}úúúúhÖ¬¦M›†^½z V»6lˆÀÀ@Q=mÚ´µµ5ŠŠŠÀÀ@Ìœ9?ýô“`‡E+++ôìÙ“ÏÓ­[7,^¼žžž°²²B¤GA IDAT`` ÌÌÌ```???˜šš"??ÅÅÅèÕ«æÍ›‡¯¿þZt«ÂØØ¨_¿¾èX“&M “É`ll KKKtíÚUef{{{èéé¡nݺ|V}}}ÂÃÃC Û¼ys´mÛþþþèÝ»7¾úê+L:3g΄¯¯¯V¶WÄÓÓÁÁÁP(ÈÊÊBHH~úé'­Â78zôè___H$<þ~~~˜5k¾üòK‘®®®®Úq«LŸ>}øÝ#õõõQ\\ ___ôéÓëׯç¯"Bjj*ºvíŠÐÐPµõ©gŽÆ#00¿_SRR`kk‹aÆ æ¶Ê8;;óóFÅ].+ãááàà`þþl×®"""àââ"šs¸z9{¬¬¬Ð¼ys"77R©ƒÆÂ… ÙA€ÿ]—œÍ‰¡¡¡°´´D~~>àëë‹FA"‘ ++ –––>|8–-[&Ê”Ä`¼i$DÕÌãõÿœ7âÒ¥K¸qã.\X£mŽ ƒÁ`0ïÿ¯b KJJªŒ[ÎÏÏGrr²Ú\’fff033CvvöËË`0 ƒÁxøÏ;ÐW¯^EïÞ½áää¹\ŽÎ;«Ô+))ÁàÁƒabb‚† ÂÜÜ;wîéuëÖ Ó¦MüyóðÃ?¼mó ƒÁ`0ÿ2þótFF”J%† OOOµzãÇÇþýûœœŒ9ýû÷G||<€W_cW\½NJJíŠÄ`0 ƒÁøïóÿ*:$$EEE8yò¤@ž›› Œ9sçÎåå666èÔ©¢¢¢ðèÑ#øùù¡~ýúxöìÌÍͱ|ùò·–”Á`0 ƒñïDóçéÿOˆ‹‹C^^žèƒÀ&Mšàøñã^9ÓwîÜÁ½{÷`mmZµji]ÿš5k°eË‘ÜÁÁ¡f†3 ƒÁ`Ôôôt‘làÀ¢¬PŒÿñŸáІG€(Í’§§'žùä“w^ç¤I“pü3Ã*¢ .¸"*.¹¥æ.îKÖW“Ê4K 5Í­¢4Ë,×R\*Ë­RËåç’š‘©Yjn¤©¥–¨˜¸‚ ȾÌüþ¸lÃ\6‡Ÿ÷ë5/g÷ÌD‡‡3çyÎoܾ}›äädüüü€¬¯};v,+V¬ [·n´nÝšE‹qïÞ=&L˜Pä>¸¹¹-Þ0iÒ$“_»=ŠÞÿý’î‚ÅqttdÒ¤I%Ý ‹ó0¾’/ dlQ'c‹1[Ô¹ººbggWÒݰhe¾ Ç;w7nãÆãÖ­[DEEe>Ïl7þ|FÅðáéS§;wîdçδjÕªXúqîÜ9¶lÙB@@@±œ¯,X¶l±±±%Ý ‹ãëë[Ò]°8±±±,[¶¬¤»aqüýý 2í…BÆu2¶“±ÅP@@[¶l1øF^¨{¤6RÉNGLLL±$fðööææÍ›Lž<<<<ŠíÜB!„Å!$$„>ýôSjÕªÅÚµkKºK«Ì/á((­V[¬Ás†Zµj{V®B!DqɘäÛ²eKIwÅâ•ù%B!„B'  Í$22’ÀÀ@¢¢¢Jº+#$$„ÔÔÔ’î†Å ,é.XœÔÔTBBBJº'**JÆ2¶¨“±Å˜Œ-†¢¢¢ $22²¤»bñ$€6“K—.áçç'X6’è£N}ŒI¢:I"T'c‹:[ŒÉØb(00???.]ºTÒ]±x’DhÞÞÞ²_!„OâÓdZ!„BˆZ!„BˆÚL‚‚‚d#•$ÑG¬“7&‰>ê$‰PŒ-êdl1&c‹¡ŒTd+oÓ$€6“øøxœ±··/é®X IôQ'‰>Æ$ÑG$ª“±EŒ-Ædl1doo³³3ñññ%Ý‹'I„f ‹ñ…BQZHÜbšÌ@ !„BQ@ !„BQ@›IRRRIwÁâH¢:Iô1&‰>ê$‰PŒ-êdl1&c‹:‰YL“ÚL:„··7~~~%Ý‹!‰>ê$Ñǘ$ú¨“$Bu2¶¨“±Å˜Œ-†üüüðööæÐ¡C%Ý‹'I„f ‹ñ…BQZHÜbšÌ@ !„BQ@ !„BQ@›‰,È7&‰>ê$Ñǘ$ú¨“$Bu2¶¨“±Å˜Œ-ê$f1Mh3‰ˆˆ(é.XIôQ'‰>Æ$ÑG$ª“±EŒ-ÆdlQ'1‹i’Dh²_!„¥…Ä-¦É ´™øûûK;!„BX¬Œ2vòÍ–i2mò—œB!J ‰[L“h3‘ùÆ$ÑG$ú“Du’D¨NÆu2¶“±EÄ,¦Im&² ߘ$ú¨“Dc’è£N’ÕÉØ¢NÆc2¶¨“˜Å4YÂaòUˆB!J ‰[L“h!„B! @h!„B! @h3‘ùÆ$ÑG$ú“Du’D¨NÆu2¶“±EÄ,¦Im&² ߘ$ú¨“Dc’è£N’ÕÉØ¢NÆc2¶¨“˜Å4I"4ƒŒ¢äžžžxyyáååUÒ]B!„0àç營Ÿ_fÌ"I„¹“Ú $›U!„¥…Ä-¦É!„B! @h3‘ùÆ$ÑG$ú“Du’D¨NÆu2¶“±EÄ,¦Im&² ߘ$ú¨“Dc’è£N’ÕÉØ¢NÆc2¶¨“˜Å4Ym²–H!„¥…Ä-¦É ´B!„ ´B!„ ´™È‚|c’è£N}ŒI¢:I"T'c‹:[ŒÉØ¢NbÓ$€6YoL}ÔI¢1IôQ'I„êdlQ'c‹1[ÔIÌbš$š,ÆB!Di!q‹i2-„B!DH-„B!DHm&GŽÁÛÛ??¿’îŠÅDu’ècL}ÔI¡:[ÔÉØbLÆC~~~x{{säÈ‘’îŠÅ“ÚLÜÝÝY»v-^^^%Ý‹!‰>ê$Ñǘ$ú¨“$Bu2¶¨“±Å˜Œ-†¼¼¼X»v-îîî%Ý‹'I„f ‹ñ…BQZHÜbšÌ@ !„BQ@ !„BQ@›‰ìêcL}ÔI¢1IôQ'I„êdlQ'c‹1[ÔIÌbšÐf"»ú“Du’ècL}ÔI¡:[ÔÉØbLÆu³˜&I„f ‹ñ…BQZHÜbšÌ@ !„BQ@ !„BQ@›‰,È7&‰>ê$Ñǘ$ú¨“$Bu2¶¨“±Å˜Œ-ê$f1Mh3‘ùÆ$ÑG$ú“Du’D¨NÆu2¶“±EÄ,¦I¡Èb|!„B”·˜f]Ò(-’““Y¾|9þþþTªT‰Aƒ1pàÀ’î–B!„03  ó)66–°°0¦NJll,o½õôîÝ»¤»&„B!ÌHÖ@ç“‹‹ .¤C‡ôîÝ›W_}•Í›7çûõ² ߘ$ú¨“Dc’è£N’ÕÉØ¢NÆc2¶¨“˜Å4   A¯×³gϺwïžï×È‚|c’è£N}ŒI¢:I"T'c‹:[ŒÉØ¢NbÓ©$ÂäädlmmsmÇ7hذ!Z­úßÓ¦M#444ß3в_!„({RSSÙ¿¾Úº»»Ó¤I“‡Ü£â!q‹ie~ ô?ÿüÃܹsù믿¸zõ*Ý»wçСCFí’““=z4›7o&-- ggg¾ùæ^xáƒv³fÍ" €;v˜é!„ÂÅÆÆ2bÄ—DG¿’g;½>/¯½lÛ¶¤H×;þ<û¶nSsŸ }‡ ¡Y³fEºžÈ]™ ÃÃÃÑétŒ5ŠíÛ·çÚîwÞa×®]yò$~~~ØØØ˜ë-!„ÂBÙÙ5"%åE­î£×ÿ[äk¹ººòÛ×_3ÏÄ‹\]þÆE¾žÈ]™_ýä“O²cÇf̘AÍš5UÛ€Î'NKóæÍ Ž·lÙ’РA¸págÏžåìÙ³¬X±"ß×8~ü8ëׯÇÏÏÏà–ó—ߣtlÙ²e|ÿý÷ÑK:æëëk1}±”c‰>–ÐK:–‘Dh }±¤cI„–ÐK:–‘Dh }±”cÙ“‹r>ðL+Ž>¿íëËguëæzÕ·ªVåÕ©S tŒ˜dÚ´i¬_¿žóçÏ#ò&4pëÖ-š6mjp¼E‹DDD ÓéÐh4ØÛÛÜ ²Œ£bÅŠÅÚç²`É’%”/_¾¤»aq$iس³3K–mí`Yäåå…——WIwÃâ,Y²ggç’î†Å‘±ÅXi[ªT©‚kŸ>¨…¸ççÇÇÅÅ¥È×y{¤ªpôïߟÄÄD£$ÂÏ?ÿœwÞy‡;wîüÐ-_¾œI“&q÷î]*UªTèëJ6«BQöÜ¿ŸÇ›Ex¸© ü>Ï??‹íÛ‹!X×ë‰Ú½›÷† cMz¹Ædlm™àî·9–¸Å´2ŸD˜U«VàÊ•+ôåË—±±±)–™Œ´´4±¶¶ÆÚZ>v!„¢4»qæÎ…â(™¬×ëIÒëIÔéHHK#1Çý„´4ƒƒI_Y‚»»»üA‘C```©©j.©©©„††âááQÒ]±(ëåëVC2¶¨“±ÅXaÆ–ÿþ__X·’Sõ`­+Xé•›VÖz°ÕNù×6Ž@Çjü|çŽ ëtY±NG¢N‡êr€¨(Pnwî(ÇìíÁÞžVZ-û´Z¥'Ìwr¢ï“O’¦×cUȸeïÞ½ìÚµ‹ãÇãééY¨s<*dt:v숓“çÎË<¦×ëù÷ß4hP±\ÃÍͯ¾úªXÎUV,[¶ŒY³fÉZÅ|}}åk³2}JÛZŇ-cBYmHÆu…[ô7½>ë~öÇz=h4†ÿfk“ó¹Œ@±0Ïéõzt ÜôzÒ²=NÓé²îëõèòñ\Lt4ë/fô'Ÿª×gÞÒ²Ýϸ݊гÓÿèÑiô0LšD¬­‘šzÍħ˜Èy\Xúë:w6Ñ4::+h7~¾n]hÞ‡¦MqÜ¿ŸÛgÎàX¿>$êt”/äìqÆ$_¯^½ õúGI™_Ïo¿ýÀœ9sHNNæã? OŸ>8::0uêTV¬XÁæÍ›iݺ5‹-båÊ•œ}@(Ê}= 4 ½>ë~Îç²=F¯Ïû¹ôã9Û>,9¯ùož3§ÌA?Ç FcðKÈóqöcƒ&ÛM«ÑdýwÎyL?—íçD«ÒV“ñÞ1ü%«úË7Ï!çs`ü‹5ó†ŸÑóÙ~±çl›ý§&óg(½möÇ9¾ óºÌ>à=½&Û{Гw aô\¶àÁ(ÈÈcT>£ì}0xœ£]Q^WÜòü,s>Ÿ£]æý\ÚœŒÎ]\¯+nùý1g@[Väw‹w[[ÛÌ]‹o߆#GàÂÃýKš4Ù¶í'âãŸÈåLR€«@Z¶„Áà ’¸88^ š¯_7~y͚м94kÙŠÄÇdzï›oè;v,¼Q«•‹¸W…¬6­ÌÏ@ß¹s‡qãÆËxìïïŸùµÍüùóILLdøðáÄÄÄàîîÎÎ;‹ ww=:õçȸt)ë9F‰a»v…jÕ ..òÚÔ©x+Ïs&¤è¸“Ö˜¸dø÷_eeưgq¼~Q š¯\1þÌ«V…-”À9—B8Ö¯Ÿy Q§3ñIä. €€€®\¹B½zyNº2@שS‡pµ¯@r°²²âË/¿dÉ’%ÄÄÄûWV¶¶ØW¬ˆµ]±žW!„ùceU‡ú˜h•È•+‡È:¢Õ*±l×®P¹rÖñòåËS¯N:]¼H^QÞŠyê•®ìÿ%ûkA´¸u‡å!@šaÃJ•”€¹ys%BχN} ßOBh{{{œ3gßEîÊ|]PZ­ö¡¬›s¨T îÝ‹ý¼¥ÙÝ«Wq®]­dúˆº|™* ”t7,Š.-û7nà’¾y€PÄß»(ã‹È"c‹:[TèÒ öT4[ââ”­¬ U+ðô„ÜBƒÎO>É7oòtLŒêó÷“ÕÿØÏð°K(Ë9²¤ØWÀ¦U3%h®U+ó¸­FC9++ìµZÊiµØ§ß îg{>û­°<<<ððð`ݺu…>Ç£Bh3‰•ívœ\·Ž>>ØË&3üW®Äkñâ’î†EIŽ‹ãäºuôŸ1£¤»bQ®:@“~ýJ¸'–EÆu¥ilIIIaóæŸÑéLƒM›Ö£}ûÇ w¡Ô8\í ÇÚ·‡.] B…¼Oáä䄦V-îÇÄÌBkõzì’“9’³gÏR3)‰rÉÉØ''£µ*Çá˜ÎHìÍÙä6Œ¬jÍŒZÊÛdŦÞyLL Ûw|ÏÞÃ{èß½?ƒ F[ ?÷¸¹¹ù: Y™¶å––q‹§c»õÌûx"Ö€µFƒµV‹µFƒUÆãlǬ5ÂoÞdùÓOóÙõëX§¥a–†mJ ¡ÀçÀ— $ÿyyÁ°aЧGŽY3x0DF*o¥ؼ9÷™îìvÿº›·æ¼Å þC_3=qù–†ú—ë³ä£% è_¸r¹QQQDEE1mÚ4œœœ$‰02m&1·n¼oîíÚQ¹]»¬ `x?ýq~îkÈ¥ªCöû* ªEä|ógb•:RëWŸ3'Õ &9žËó±Êë†ìÿ]u¨dÞCf†œ?%%ûç“ý3õ*š¯%{ÛÕ0²?oÎjjýÊþ~ÔÞCn=²WHÉ,äHXi4hM<—ó³ÎÙƒÇ9Úåëu*çxòüÈåçÁ¨yüÜd¶Éy¾ü|FÙÚnÚüÁÁ“³%‘©. .fðà¢í# ZeGåqÎjMyµÍùÙ©gù}.ãXLt4vóàÖØôÏE“m°ÊüM“ç/ñÁ{ã3>N®^…‹•ª.(÷/^Tv T*aT7ñ)YQ«N$= °œ³¦ åSR¸GöԻŠï=ý4Œ O=¥ÔpN×­œ:ƒÁ?ÿÀ޽СìÜ ç~­ÏýËk¾¯Þ'œìÓÔúz.×»Ìß1쩾‡Ö­Zç»ÿñ÷÷çÚµk´hÑ¢À¯”Hm&}Ÿx‚µ¾¾%Ý ! Ì ¸6tkr”X$Ç/Ýüþ2.¡’„B”‡ðøÏôú~‡Šh^¾¼zTòRµZ4Iihz G` Œ¡Ëo†f¸zæÏ‡ï¾cJr2 €åéO]¬žy7?¿\_^·.üñx{Ãöí¤Ñ[¶(3Òj†¿1œðΆÁs&-„w gä䑜=p¶ÀoÇÓÓOOO üÚGÐf’””TÒ]°8²[˜:KÛ-,û,'æ js\Gv"T';ÂÂ…_óãÇÈ>k›˜ø;;GÃ`Íöí[OùG$UcicKq9^¹©©Zš6…ÇwwX¼X)!—% 8¶ü÷Ÿ8¯[)JR êìÌ•û÷©Ìtrbì”)&OU¾k–²Êÿþ @Η‡‡‡sÇþäUÐË"­# §zuS³íê’’’°“ªay’ÈÅL"""Jº Gv S';“ÕÉN„pòäEþüó 0Hßòf«Uˇ”Ãêš²<¶Ô®(gÿ7{ɹû÷aéÒŒG÷ ü°=IWÀ¶ľ©Ïä}¡à`˜7þïÿ } m€¸ž=ÆÖªW?Kbá€K4¦åµþ¯1{ÚìQ²üÒÒk7k40hñS¦ÐzêH‚{C9ø+†_…[^€Ó->;ó_8ðÞ[ïöN‚ï(ÁqðÝ`‚uÁÔý$˜‹!èlï³Ø»;Û Rõjy*&U¤N:ùûP²ÉØH%--ÍtãGœÐfbkk‹³³3öÙ„Bˆü WWhÔHÙУkW¥>±ZiçÝ»ÿåß—æ8ú0¬ÉÖ²åÌ›÷°z\xÑÑÑpçWj3ØDËîÜLÌ×9©Sþ6 kI­šÇ® ÕöQѦî5¼•CçÏÃÇ+k,26(Ñh`ð`øðChÙ’ñ_år«ËPNyúÖ“spRÇ6‹åómŸãØÂ‘HmdV°|'˜{‰÷Ô;›s½–šuiÖÞƒGOc"Šv¼ëH£Bìz,©äŸÐfR«V-úç– „â‘tõªDPr[³Fy\£†HgÔ?Ê–Pp Nסb,Ä8BtˆþpOogyNŸvÂ6ñ>GøÇ<ÚMÇ™KŽ}óuÎêÕ«“pç:ï§àp?·Vz>¶¾O{×Jè?vÇYÕQ´Z¢öæò„aDºW%>%ø³§Ùwvº~Ùvÿs‚؆g¨Á¤•“ / µ-µ+Ö¦¡KCVnHç†ü¡!{64„{õ¹kcË+«`ÐÄå|pàbS¢+Vä­áo¡-Ć*©lÙ²¥À¯}ÔHm&’DhL’Õ•ÕDŸ¢$Bu’D˜›ÀKþ—”‹)Ëiò÷š àÕWÁߎ‡饣ÃÂ`Û6åJ¹a½°ùjN€n€#Tba[W¸µ¢@}Žˆˆ ?[Gh4\]] tîì>ÿÞ} +_ò; ¹£Ú.ø•†´ª\3ß玭ëÄêÄæ¥æ9Sã`­Ž¥}”™–š¦…M-à“®:.UÙÇ÷eÍ^§jà9cWWà/Ð4×P«b­Ì 9û¿õ+ÕÇÞÚð[ê)]àëÆðƘ#Fœ£Q£`œ¬jžBR÷„¡¹r½ IDAT¬kéÀîH9œÂ«s$å?zv9WèRt’DhšåŽ.eŒ$“$Bue9ѧ°$‰P$æf9“-ÉîÝðÖ[pùrÁ^çè}¤ÜOKƒ³gáèQ% >zTY  ì( µÞïYJü/”`Úû¬~›´´öù¾¾÷ AÔ ¥b[¤Ç¤¥qÅÝ_þü³`oåŠ×_W [€æ?[k™‘‚ê,ôÇZ;«9S±¢M>ÏŸDгžï+Á‡ à€„/#kÕü `Z²ò‘¥jaÃãðIW¸ì’ËI­Q‚hSbà§Þáãw?¦œu¹|õ7Ãë¯+I‘ʦ+— jÌ‚àßàßÏ \ú2„J$=x‡ë©ý¸¸›áÃ/:€–$BÓd'B3ðöö$9L!ŠSb"´jåÃ¥K³0,ÛÚúpôè,Ú·/¹ úòeðñ]»²Žµh±±O’”dzÓ‹J•Îðk®ÏeÓ~hFÚËòÞ3$ªþÒ”Û¡¹Ô€ËáÏcÇ80hÓ ëÀø¤Jzÿô;wÎ×93„…Á³Ï‰Êãõtüü]Ñ×~åð¤I¼c¸\!_§^{Ͱì¥Ú}†Ðû¡ü}ëoN8Ãõz<`FŽ¢,ñÀH`ƒ4¯ÃåéCH®S‡=zø¥/ù1õG³?þXø?®%n1Mf ÍÄßßooo¼¼¼d¶H!Šè÷ß•™¹üµONVv~›1¦Ns®KHPJ/\¨Ì²‚²]óœ90aÜ¿¿)_%öllòžimÔH¹ÛDgj…+Äër†sêØ¹3K5"&*ŠŠ*ÏÇ5bznÁsJŠ2M‘u»}›ð"8þc'DPÛÔ± Òµ(è¡T‚XŽ0gŸ…^L¼~fÎÌWßÝÓoÏ„ÃP”€Ù![›@­ÆÐÅÕ•­kóT½z9O£jõÂÕô›Ô›ÝrV=QhîhèR¹K‘‚gP6]™?_Ù üañóóÃÏÏ<==Þ…Ê  ÍÄÓÓSþ’Bˆ"º{&O†Â §II0}º²VøÛo¡uÁw:.°;àw”ÙCP&EGŽ__e“€ÊÙ‹½F—ÛŽàY4¦-Xmì7-bi.³ÐK+Vä®]•ªÙäÌû÷Ô+NTžË~ Ùðù‰(sF¸X ÛãÂ0gìS¬q…ÿ:Û2Îu(õò<4}¬)Ó^𯂠¸Þþzf5‡ZD¶`Ý÷ëŠÐÛ,»WÆ$_Æ ´ÈÐf"I„Æ$‰P$“$BuZáÆðöÛ©ødýfâ’S8QÁ…í ~ G·ùü”Õ¯_Ÿ_¾ÿ¥Ð¯·$’Dhš$š,ÆBˆ‚KKSÑ™3!>^9V«–2‹[”¿ΜQÖ Ÿ=«<Öj•2aóæƒCÞ¯ÍË–-0e ÜL_ ke¥¬Óþøc¨T©ðç-ˆ«ÑW²m'ož„}€Ðظæ¢VGÜHm› -¢Á66ó9;+;7ÌøvãéR§‹ñ‹¿ù†—_•@E”Ùç± ²eýú¬@9—20žyFÙ”e4~~ « }m(Ûì·¡¯ª„) ÖÀ“Qðõ0HËh¿ð$sÓ’Îu:3ºõh†4‚£m^¤Ó¯Ñ®Í._¦ÛÖ­ôèÓÇdû’æççdzς$–<™Ba‡ãĉM¶³²‚.]F1a‚-gÎ(Ç4ezþ|¥ÎqQ´n §N)3Ñsæ(3Ó_|?ÿ «VA¯^;_@€€:”u¬K%ÐoÕªh}-ˆŸƒ~æÕ_ÍÜÝ®íËmiØŒ}öY'’”Ê)ØÜ±¡êõª<ÛáYfý;‹ýûíû†žõ7@Û¯ ZIiIl<·‘ç6ÒÒµ%ãÛŽgxËá8Ú:¢ß¼Æã à |¤‡™vpØ1šéûfîssÝÀc÷nx饌2{0d|÷]î´üsùô=²æøBûÂ÷¿f žC…ð ¼þÔëŒn=š&U ¶ümüüùÌ™<™¥ x–Ef Í@þ’B<؇~€zUß,åÊ-!9ù+ÒÒ”%ÍšÁ7ß@+£åËÅ‹ÊÒŠãÙ¶uöðð!>þ6VVyog’!Cޱj•3©éµ€«WWó#Š¿¯¹IÕ¥2íÀ4>=öiæ2†w:½ƒo_l´6„……qìÏcœü÷$í[¶§sÇÎÔ¨Q#óõAAJ ¹ €ºGqè¶’”†?¢ËÊæ«`[ߤ®Œû Ö:=ÑöÐÅ öF@×::ì/Û3´âPÖ.[kÔG__%S§Sþúp½Æõèë\¹ÎµhÃû7îÞ qg"<“ãD9ËqDƒwª7ß}ù]¡?¿ÐÐPÜÝÝ ýzs’hË!3ÐfräÈ)c—ƒ$ª“$Bc’D¨®t&¶ÅTÍæ„¥x±t½÷ØæË(ÈØòØcJíä/¿T®ŸQo…É~jµ>¬\©Ü·¶V’gÎ,ú yA܈¹ÁÐíC9výÎöάõZˠƃ2ÛÔ¨QƒçŸ}žf5S[5Rê/Û¶u%~CWl+EòÜGk8­ýšÐû¡´~ÀÈõ{°ÖA¼ x .j ýˆè§œ'±A"{þÜÃ#poáÎõèë„D^céúëüz†]G[éöÕ®375š¹¦6BTÛ $çß^÷àñ¶…/—ššJjj~vB±•+¯ÁÞþPžm/£ |îŒ2vGŽ¡[·n…ëà#B"3qww—¿är$Bu’DhL’Õ•å$ÂΕRsUÖïšRбE«U68yæ3F©1:òoïÞJÞ´iÁûZ{Cö2âÇDÅ+Hµ­Ù–­/l¥ž³z ¶¼ÆGGغ/†÷߇ä{UÙñÎûŒ~m*#ú-£Ý‚É”KK%E Ïÿôe·{‚®NÖy"¤ÏÔ>ðT¶“W!³ú‡ˆ×_ßZkM­ µpsr£ŽSÜœÜð;æG`\ ”Ïý3p½íJß}so`Bi[þ÷¿ÿÒ#_mË—ÏãƒËEÆ$_¯‚®czÉ3¯B„"c Ç,LÍì:;ûðß³¨T©dþ¸~â NŸž…©~ZYù°zõ,¼½ÍÛÏ4}3Î`þÑù™K6&¶›ÈgO~†­‰e'ùq𠪔 |Œ‹³î†sj: ¼8¶53q‚g Ù¤Tᱚu¨ç↛“›A ìæäF Çh5†k§CCCé6²×{\W½Œ6BËà¤Á|¿æû¿Y¡JâÓdZ!„E)_ÞpGfs«WNŸ6ÝÎÕµhÕ@ #,6ŒaÛ‡qøêa@Y›¼ú™Õ i6¤Ø®Ñ³§òþ' eù?}qNUf¸g{x°­©‰­õàbåFÌÞqƒ˜:LîÆ‹ËaeU°~¸»»óɸOøhÙG\mw5³Ò:¨x©"­âZ±zÓê‚¿A!ŠÐB!D!<¬ ?..ŽË—/Ó Aƒ¯á\9ÀË?¼LDœ²¯À㮳mÈ6º4,ö>Ô¶ ãÇ}РÔä›Âb>½¹"P¶ÌM8Ü ÔAü(lmaåJemua:‚Þ]{óÎŒw>L’. F<7‚1¯Ž)ü‰…("  ÍDv"4&I„ê$‰Ð˜$ª+I„_QÇ–ÔÔ Úd;.‰´´´B]CÍßgþfÌ»c¸Ãâ+ÄãðÀÊTæë…_³çÁ枃N¯, ~­Ík,}j)öÖùßÛ9ßcËݻЯšÿ.pvÀ‡,Û7âÿ‚'aÌêÛ„ëõ!¾=®®Ê6æÅQ9¥fÍšlY½¥è'R!c‹:Ù‰Ð43ì‰$@v"T³lÙ2bccM7|Äøúú–t,NF¢0äïHXc¬™§¢Ž-ׂþ¦-mLó¯ù¿5 ˜:€3Îp­ë5¢ZEq­ë5Ît8ƒç›žÌúr:½Žò6åYÿìzV \U àò9¶ÄÆÂÓO+Å­&M¢ÕÏsñ÷[[;ˆô… µ!>Çëâõµ!j1åÊÙñ×_§ì`q“±EÄ,¦I¡Èb|!Ä£..êÕó!2r¦’ójÕò!  ä*ôxyMäöO{8Fh®mŽ¡å%ÇÖœ½¾¿Èý¼uë†vâZŸk¹7Ú†6âÇ1?Ò´êC*÷‘”¤ÏeHFŒ€uë2ת4oîÍùókÿÀy4T¸N‰mêÀý5@}Z´ðæß×>œ> ³¸Å4ùî\!ÄC£Äe‘‘ßam].Ï-³““Ï™«kª¬­mâþ`]P©¹LÁ”ŠŠåzs>õVyÏÝ¡kdׇ<§¦*[fσ)u³-ôÎZSî„ûÀõÛ@5ƒS岡eŠÐfâïï/©!9wï“OÂ_¼§góæM^¯jE… ÌÓA}û¶'2r*Ïï Œž?¦ÑXÇÇ›Åa[^rñ率‰F•á© E¾–*½FŽ„;•ǽzÁ÷ßg˜óPÍtQjdl¤âïï§§gIwÇ¢Im&íÚµ“¯Br$Bu’DhL}ÔYzaDôí çÒ'”_x¡.7Ö5<]QÇ–×_‰×_‰S§rüÓOé¤3œ…^^¡G&ŽÀ±U+¸sÊ•+RIްÓðÈǤ{djP¡¯yŒ-o¼ÿ÷Êýà§Ÿ”m 2¶ʘä6lXIwÅâÉ-f" òI¡:I"4&‰>ê,9‰ðæMèÞ=+x~åؼÙÔÌsñ(òؾ¾Lüá–åž-cbp|ï=ejÝÍ *T€¶m•5ß|¢”Ÿ¸pRRr½Dbj"ëþYG‡Õ¸kͧ7!øZî·OoBõZµ ÿžÈel™>–/Wî7o{ö(Û>"dlQ'1‹i’Dh²_ñ( UV\¹¢<~ýu¥pInŽbRj*ìÞ kÖ(AdzÉ@W £ ÄKvv|cg‡cLŒésZ[CƒФ <ö<ö7j:òUì!VmänÂ]¥Ý-x|=œÍ£ G[k–ô§CûâYw À¢E0uªr¿A8zjÔȵy«VÞüóÏZ“§}üqoΞ5ÝNX.‰[L“ïÎ…B›  èÝnÜPûøÀ矗lŸò¤$Ë­[áá†Ï5nÌÄ#ûÍ7t¾v?´ZóM.Tf©/^„À@åߌû7of½>5.]Rn?ý@màc`|E¬×j–§ZÛîlhx™ŸÏ_b JÎâN-T~¢m‚ç7†çAhhî "" $„ ÀÒš5aß¾<ƒg!„!  …B‹€èÓG‰Í>ø@YÑð0}òÑGþö[j–+—g»“ ;^)9Û¶)³ÍG6,_^©F1z4té‚#ðxt4|ú)ËêÖeÕŒJ»5”[¯^†¯ð 3¨Žû÷onœÜ‡õ¥`êF¥b-8®£Üø/ü÷Ðx¨Ò÷Εعãç}.›6ÅcÛ6ú''çÚf/âà Ïõê™üë¾þÖ[\Ù°Õ—/g ÜÉúwØ8z4ÎÁÁJмy³R[/»Ž• yèPeMs6gÌ ïºux Œ£©õÁ*p¨j+BwáçäGJïè 6iÐ:®¯—ïÎ3º†T¹©Ú‹Ð%¨íŸít{wïaWµ*¸ºBÆÊÍÃ#ë_£>™<™—¿ûŽþ!!™Çì#Ëj­–ûöAÓü•Æ[±bN¾Ú•&2¶¨“M“ÈÅLdA¾±eË–1kVÉm–`©|}}eÝY‰>K–,)é®X”ŒÂ’.yì˜Rç9:}÷ëO?…wÞ1ϵ«T©‚kŸ>\X³†Œ0p0‹¬íZ>sqÁ÷Ø1%xήjU%ñoôè\ƒÈý¿ïç»­ßq¯º#¿ÿEüü™L™4ŨÌ^tR4ëÿYÏÊS+¹uÑà¹65Ú0¡Ý†5†ƒJì7àâEÆðòìÙôÎÚF|5°1ãAD„rSKÍ\Û5lH'Ÿdïµk™³Ð¾ÀÚôæ{£FaW¶ |ˆdlQ››|“I"4YŒ/„(«„•5¥ ÃøñæíCTTï=ñk®oFrØd®$±²R¦ÊG‚gžÉµ,Hrr2/¿þ2‡#Ù4Ê:ÐÞÐRïB=V-XEÏn=9~†•§V²éÜ&âRâ2_oomÏÐfC™ÐníkµÏ÷{Y6o³gÓ?9™½¶¶„ŒˤîÝ!8BB²þ 3y®$”e!ÛUž\£¯\‘YF¡JâÓdZ!D¡üò <÷$&*qéêÕþ{׬2f¡Ïû-Ír<÷J% ê×W‚fooÈG98Ÿ|ø‰ŸHi—­tn:.׺Ìw‡àþ’;ÝÿËàu.Œk;Ž‘­FâRÎ¥Àï%ûÒ‹Õnnl\¼X½&sl¬Hgªs×¹. ±¶¦Ç¤I< Q@ !„(°„_„äd¥ZÛÿýŸ²|¸D$$ðއïYY±&½ü@àÚ UV­‚=ò]Gï¿ÿþã§s?‘Ò9—:ÎVÕ)ЍMQð4Xi¬Øx ãÛŽ§oƒ¾h(|½>;;;zŒÉÛsæÐcäÈ܃\GGhÕJ¹å”-¸sñ"//^Lÿlk¾W»»³qòäB÷Q!´ÙH¡1I"T'I„Æ$ÑG]I%nÚ¯¾ªTi³µ…­[aÐ ³vAñà¬XŸ}F•Û·qE šíQ’?«]›…þ ü|~ûý7ª›X"Q ¬“­™ÖmcŸK튵 ÷TŒ™<™î›7ã[Ø 7[pmô°²bïìÙ¸''jk›w`þˆ‘±E$š&;š‰$“ÕÉN„Æd·0u%±áš5JÞ]jª²ƒõÎ%<ß»³gƒ»;¼ÿ>ܾ À;MšðY•*,NÕûõ+Ôœý}eÓéA5*Õ`zçéÅ<ƒ2 ý«¿±0c&Ofµ›¾Àj77ÆÈìs&[ÔIÌbšÐfríÚ5¼½½ñóó+é®XŒ%K–H’´aÌÙÙY²äUxyy™µÇÒ¥0f ètÊ$çž=J>žÙDF´iP·.ÌšwÓwòkÓvì Ê… TæF߸¹ñ΂ù>ulr,›6óÜ÷Ïñý­ïáŽé×”×”h³tNNNÅv®Œe!•Òÿ•™Å,2¶òóóÃÛÛ›k* ¹Â|wn&žžž !J­ ”É^''%°S'3]üÖ-eÛéo¾Q6AÉЩ“Rlúé§øç߈ªh…·­ V¶°Ío£_­­­êiãRâØ´‹­ç·òKð/$¤&(OÔNõóèS4ÔwÍ«e)ò²ñHÈø£Ü»$²K  …BäiæL˜“¾‡FåÊðÛoʤoA}>>Ç6m¢|.m†›:[ÿJÑÑJäþÝw=¤gO%pNßP§Ó1ñ݉ütþ'Â<Â(×Ú\ãìá·X¾a9ë¿XO›VJ‡ãSâÙ¼›­ç·²'xñ)ñ×®d_ ¯Þ^„ņ±ÿú~Rë¤wPuÿªË·Û¾-ø‡PBŠ{Yˆ:  ÍD’I¡:I"4&‰>ꊚDèïïÏo|‰Úæé®_‡ððxàM\]=Ù¿š7/ÔåøÂ \]¶Œ%·nåÚæ&0·gO*½óŽRÚ#5[Û¿?|ôäØücÆü¬»½Ž„NÊ rBkÀ R*¥p¾îy†LÂÌÏf²ëÚ.ví6¨× àlï̠ƃÒl}ôÅFkCⓉ =„cãNË;^.Z®ÁýŒ;+>^««ká>ˆV¬KCÊ[ÔI¡i¹˜‰,È7&;ª“ÉnaꊺaTTgϾ˜z½•+Gqø04n\¨KàááAb›6ܼu‹Ü*1/*WŽ)‡+ ­A)=7h2ãüÄFí¯_¿Î†ÃHè’uð$Jd{À.·½Ì+>¯@¶õÚí*fÍýôÃÖÊpVÜÞÞžw²ë—]|·í;nDÝ œM9Ú5kÇ¿|@¥J• ÿA”[ŒÉØ¢Nv"4Mv"4ÙÑGa‰üüüxöYÈOýÕWðúëEOX aY÷Ð7¹ÀWZ- Ó§ç9å½fÝÆìƒÞÃį² Â° <Óø†4“Obg%3lB¨‘¸Å4™BaRq­VÈkz0ÅÊ †‡>€FLžïØéc諚žªêT•à7ƒq*/K„E'´Bó¸s~ú‰)±±,ÒhX’í Л@bݺx:¤ÔwΧZÀ_€‰¸¸’u% ž…ÅFê@›‰$ !5U%ÃýXÒ]°8©©©„„„”t7,NTTTf"aadÛõúቈ€¯¾‚¾}¡zu=C‡HÔë¹™­Ù"WW¦ìß_ à _Ï~T ¯fxð. Ëö8ê¸Ô)ä(;dl1&c‹:‰YL“ÚL$‰Ð˜ìD¨Nv"4VÚv ;uêãdÜÿþ—÷mà@N:Uèëf'”øõWxí59²Ð—ÎÛðå—н;Ô¬ ãÇÃþýY5jÕbÊ+¯°(½zÈM ±]»BUBhÚ´)}Ýû¢ Ïöëì$œõÐí”k>[Sø÷SFÈØb¬´-æ"1‹i’Dh²_ˆGK||<#›7gÝ•+y¶{µ^=¾ ÀÁ!÷2r>ÿüs–½÷5¬lòl––¤syûí·3%'+ñëöíàç§ì„­ÈØÕtá?š¨öqå üðƒr;qrþjqw‡çŸWn;‚Føùh×.լɤÇ ]J,!!Z½kq¯Ê=hhÒŸ¸¯Ôkö}Û—Ÿ{±PçâQ$q‹i²Z!Š™ƒƒí‡ 㯅 ñÌe™’¿µ5í‡ ËWð àîîŽMŠ ‡S®c•K›4 upww'9YÙðdÛ6عîß7lkgõë_$îâ*™—çµc‰ââÅ1Æô¥KJÀ¼};œ9cüÂF²‚f•tS>ÿœÉþ‰s›6EªÃ{ôÖQî=yÎCÝ£u©`Ur6åh\·1 ·.¤F…>·B¨‘Z!‚ñÓ§3æûïñ¼|Yõù•uë²júô|ŸO£ÑpA¬b-ãP_ú´ G®1ˆ/¾Ðàí 11†ÏÛÛ+{‘¼ð ¿ýÖˆÙƒ“ù‡S™“¶9éÇ©E£ŒŠçÎeÍçÏ¿ yó¬ ¹E‹<ß“‡‡Ž={2e^Þ|^ôè™v`•ž¨ÄÙ·Îâl/µå…—аuëVvíÚÅõë×ùá‡pqqÉ÷keA¾1Ù‰PìDh¬4îæàà@›¡CñW™…ö×jiÓ°!Û¶A¹rÊÍÁ!ë~ö[Æq ^,áwÆp+ #}° ÊìóÜH ‡g]«\9xê)%h0³ž«P¡1•Ùz7œ¡zõŒÂ­+ÊW´§Í¶m0m7jÓ&+h.àN+ËÖ¯ÇÞÞ¾@¯ÉnÛùmœ; À{žïálï,cK.dl1VÇsM“5аaÜœœ5jT¯^=_¯óööæÚµküþû﹇¥‹ìD¨ÂÛÛ[Öåpÿþ}fÍšUêv ‹gL‹lüï?ƒã/«€ü-ÞP謬ˆM³c ÖØË+è V0¯ÅštÁgâ­pkÖŽ¦+Ò²KìªV„  bŽmlHMMeXóæl½tÉhZ ±±asJŠál‹F:dÍõêìƒ)&©ºTš.oJðÝ`jV¨IÈ›!”³.'cK.dl1VZÇ–‡­W¯^¸¹¹ÉÏK$€.„êÕ«söJ€Ü IDATìÙÐ ‹ñ…x䄇ói›6t Ã3ý?p˜\ÈSê€ÁÀ6È\ ¼l§€¥•ìì bEÖêõ8ݽ˳:ÁÓ?Ñ€7(;zz*ósÏAíÚ…|Åçë¿¿fÜ®qÊý_3ö‰±%Ü#!ʉ[L“ï·„âaøç8ñaaŒÌzeýú¬:rDyï[Ð?ÿðËŽdÊQ“Ú°“x£, Ùˆ–nTã6Pû”'1}LJ‚ÈH†/¢ÌdgÌBë [zõ‚ÁƒáÙg‹o;ÂbŸÏìC³hèÒQ­G•p„’G&€NNN&44{{{ÜÜÜTÛèt:.\¸À7hݺ5®ôËBQŠìÜ /¿ ±±8mš6Å?(€6/¾ˆC­œ›X›vÁÏŸ „¹:Ó‚\`M¸Ä9ÞF øá·CÏ ^½àÁåc|?Û1ë˜ú:ÅŽK—x>ýKÉíZ-&OÆzáÂbùHŠÛ'¾ ,6 €{}Œµö‘ùu&„°e~Ä9vìo¾ù&çÎ#99™îÝ»sèÐ!£vwïÞåé§Ÿæï¿ÿÆÕÕ•›7oòñÇ3½Yòy‘$Bc’è£N}Œ•ªDŸO?…©SA§SÖ ÏœÉøwßeLË–ª¼‘;-טÂç¼À5¦}ñ†^£''å–ó>›ÇŠÐß©¡ç¹ôÑsœtÜûsÉŸWbÚÛÓŠ¡ÏÅçnÂ]ø/à‰OðB³ ž—±EŒ-ÆJÕØbF’DhZ™ß‰P§ÓÑ£GÖ­[GÇŽsm7~üxÂÃùzõ*7nÜàÿþïÿøðÃùå—_Š¥²«1Ù‰Pìf¬Tì–’cÆÀ”)Jðlo›7ÃÌ™™9Ú šïºÏ¦$ð*Kpa .$ðj¡Ï³sÏN>?ø97{ß$´l²Vn¡àfï›|zàSvÿº»Xú\\æûÏ':)Z¹ßg>šé2¶¨“±ÅX©[J€Ä,¦=RI„ýû÷'11ÑhúöíÛÔ®]›éÓ§3sæÌÌãõë×§eË–øù)¹îS¦Laûöí™™5mÚ”={ö˜¼®,Æ¢Œ»wOI®;xPy\½:üô´oŸÙ$>> ÐôÆyå•/Ñ霰¶VªÅ¥Ä„PÎ%kö,))š+Þ¤gÏžù:o“®M¸Ôë’2¢ƒfË•ãç'’y¬á¾† *T¿‹Û˜4\ÚÄÔDzÕëÅW”t—„(s$n1­ÌÏ@çÇéÓ§III¡iÓ¦Ç[´hÁŸþ™ùxñâÅ„††Fhhh¾‚çì×X¿~=~~~·¨¨(ƒvrLŽÉ±Rv,(H)évð ~@T³fpòdfðœÑÎÁÁ!3x.è5RSaéÒžèt?¢Ñx±qãbÖxõ(W2k–û÷/æìÙµ\¼ø#={öÌ×5BCC‰Hˆ 3çP W;Àew ŽÅºÄòÍ7ßXÄg?ëÐ,!|ûøèµrLŽÉ1õc1É´iÓX¿~=×®]CäMhàÖ­[4kÖÌàx‹-ˆŒŒ$5—­x…8èØ1ks‘öía÷n¨S§X/3{6œ8¡ÜúièÕK¹¯ÑhÐhrÛCд   âì㠎Ŷ…Ćíî•¿—9N–¤À¨@Öž] À€FhW³]ÉvHñÈ’%À’%Kxûí·‰ŒŒ¤J•*™Ç—-[Æo¼Á½{÷ŠTßÛÛ›¤¤$6oÞ\ès”E’è£N}ŒYd¢ÏêÕ0a‚²ö”µÏ (õ’‹Ñ@÷î–¦ì’}ꔲ¼Èœ=Ê>nDhh(&v"¼}xžíjœªÁ±eÇpww/ÔuŠËó[ŸgÇÅXi¬˜@“*êÿŸÈØ¢NÆc9¶X€aÆagg'K8ò 3й!JPá¿‹/bggW,»Y………‘˜˜(³ÙÙH¢:Iô1fQ‰>:,£Ï66J0½hQ±Ï110|¸<ÛÙÁÆYÁ3€¿¿?þþþ…>¿»»;÷”ÝYr£Â)ñàùä͓츸€‘­Gæ<ƒŒ-¹‘±Å˜E- 55•ÄÄDÂÂÂJº+Oh vúŽZçÏŸ78~îܹÌçŠ*,, öîÝ[,ç+ –,Y"[íª¿ø9;;[ÆV»±±à奔ªpqß~ƒÑ£Êå&L€ÐP徯/¤WÂËäåå…——W¡Ï!òÑÍ£•ísãáÍÂñõ/Ùàëýýï`omÏÌî3ól+c‹:[ŒYÌØb!öîÝ‹Ðù 4бcGjÔ¨Áßÿy,11‘sçÎñÜsÏË5:uêÄW_}Å€Šå|B3»~]ÙÊú矕Ç+ “{ôx(—Û´I™qè×Þz«xÏòæIº}×;5ï€Tø­ÜÏÖà>TÙ_»òvèëë™v`Þ~Þ$§%oGòá·Ë¿q0T©p2©ý$jW,ùmÄ…(‹ ÀW_}E§NJº+¯Ì/‹ŽŽfåÊ•\¹r…”””̯±ÆŽ‹‹‹ ÖÖÖL:•É“'Ó AZ·nÍ_|F£aüøñ%Ù}!„%8u žyÂÓ× ÷î Û·ÃCšå¼zU™}¨\Ö®Uöd).®Àk‹±ÉÊ2‡Óðlµg™÷å<.ž¸Àc ãƒo? É)‰›p5ú*ëþYÇ•ûWØ1t•ËU.¾åA¼8Ù91ÍÓ²6uB<šÊ|ŸYǹR¥J™‡ †‹‹ >>>ØØØ°jÕ*æÍ›Gûöí9räõêÕ+–~±eËš7oNóæÍ‹åœ¥$ú¨“Dc%šè³u+x{CB‚òxÜ8XºÒÏ­N#F@´²O«WCêm “D¸ãâ^úá%’Ò’°ÒXñÕ€¯x­Ík|·ô;Õלs’A[ñç?9rõWwd×K»h\¹qþßX!m=¿•Óa§˜Úe*.å\L¾FÆu2¶“$BCD£FJº;­Ì/á¨Q£þù§ê­nݺm'NœÈÙ³g¹wï¿þúk±ºñññ8;;cŸ=è'‰>ê$ÑÇX‰%ú|ü1¼ø¢<[YÁ’%°råC žæÏ‡£G•ûcÆ(K®sSÐ$ÂoÏ|ËmCHJKÂÖÊ–ï_ø>3xÎKµòÕ8øêA^lþ"!wC踺#®<ÜMLRu©|øû‡Tw¬ŽOGŸ|½NÆu2¶“$BCööö8;;gnü$r÷H•±+)²£¥LR’’˜±¹Bøþ{xꩇzÙS§ sgHM…Fàôi(_¾xνøØbÞÝ÷.åmÊã÷¢}ê÷)ðyfšÅìó°ÖZ³â+ÓfLñt2‡¯þúŠñ»•etËŸ^΄vÊu„†$n1­ÌÏ@ !DDF*;•dÏîîpìØCžãâàå—•àÙÆF¹|qÏø 3xv)çÂW*x˜Õc›žß„•©ºTÆþ<–É¿MF§Ï«^ÁŧÄ3çðTjÀ˜'N.„…! ÄÌ$::šÀÀ@ªT©RèM„…“À¥K—L7¼|'êݸ¡<îÜüü jÕ‡ÛA”*Ξ mÛýœ:½Žñ»ÇóÍßßP³BM~ñͪ63ñʼ k> wgw¼¶xq;î6ŸÿŒà;Álz~޶ŽEï8ðʼn/‹UJiÍí5­M±œW‘»¨¨(¢¢¢ˆŽŽÆÉÉ©¤»cÑd ‡x{{sæÌ† †§§'žžž%Ý%‹ ‰>ê$ÑÇXQ}’““içîÎÈÄÄÜ¥¤ðg\=ôzƲƒÉêÕÊ&ÙŽðüóÊýnÝààÁüíÉ’WarZ2#~ÁÖó[ðpñ`߈}¸;»W· ½Ê€M8©ÔÐoU½?û¹Èeæî&Ü¥þõ‰NЦUõVœ~ý4ò_†DÆu2¶“$BCy›7o¦uëÖ²„#²„ÃL*W®Ìûï¿/Ás6’è£N}Œ5ÑÇÖÖ–ño½E“¸8|îÝS¿ÅÆ¢×ë¥Ñ(Ƀ6˜%x¾uKI¥*Þ† ùßÐ0·$Âø”xžÙüLfðü¸ëãøò/ÖàÀÝÙc£Ñߣ?gÃÏÒ~U{þºõW‘ÎëëïKt’R†d~ïù žAÆ–ÜÈØbL’ yzzòþûïS¹²yÊT–f2m²_ˆ’—œœÌðæÍÙš±N"‡½@¨ ã6m‚ÁƒÍÒ'½úö…éÅ,6oVŠ~ŽÄ{üoãÿ8~ã8]êta×K»p¶x;ó¥éÓðÙëò“J RκžÛÀó=_às݈¹AÃ¥ ILM¤{Ýîò>T̽B˜"q‹i2-„x$ØÚÚÒkôh~±µýöî<.ªò{àøgØAÄ%qÅ$÷½0KJËLm×ÜCÍJÍ~.Y™e¦ùÕl³rOSËŒ\Ç4÷Ü¢Üs É]Pvæ÷Ǖ͹8,ÃÌ8ï×k^]îܹ÷ŒÁÃáÎsž£úü·NN Ý»×jÉ3ÀçŸç$Ï.yNJJâ?þà?þÈ^rêrÂeÚ/mŸþôY݇[©·x÷×w9sšEÏ/ÂÅQý—Ü"b"XzLiæÔ0ˆ¶5Û–tÈBˆ\²©œ;wÎbäJ+¹m%:N©Ü%22ƒÁ`ë04'""ÂÖ!hŽÁ` 22²Øçqqq¡cÏžl¾«/ö·õê1ôƒŠ}þ‚JNV–¬KKSú³¬Xžžæ_wûömÚ>Û–_*ýÂùvçI¨›@BÝη;Ï®j»ˆZ©02`$+z¬°ÉÊëuæadÏ·^~b9O}÷7’o˜}í{»Þ#أΑéON/r 2¶¨“±Å”¥Æ–Ò"«‘ŠNW¸ºƒ²Hh+qpp K—.R雋ú¨“BS+ôùç†þðKs•~lvq¡ãË/ã’ÏÔŽ¢ˆÍ·ê4èýu8À¸q±´kW°óù¿!œi}c•;ñGßyT:Að†Ì}f.:Û ïMª4áÐðCñ?~œv½­]‚\]ù¡Ç4¨Ô€){¦—G—]˜Üt2›nâbæEnzÝÄ%Ù…ø¨x¨ #ÆŒäY¡y’@ !J·ß~ƒçžƒ[·”¯?ý—ñãyª\9¦ÌžÍcÇÚ6¾rttDg4?/QgÔáXЬÜJ&·ŸLýJõ²a)R˜¼z2<¸+Ï'“  ;­ãø‚ãÐŦá !„Y2ÉÅJöíÛGpp0z½ÞÖ¡h†ú¨“BSE.ôÙ¶ ºtQ’gX°Æ”9&/XP"wŸKBóæÍñ¼yW¥aÒG.ž7=iÞ¼¹Õâ*¨¾Mû²½ÿvœö9Á d'Ϲ9æzŒ…KëZ2¶¨“±Å”æ¥×ë fß¾}¶Eó$¶???–-[FPù¹Že…ú¨“BSE*ôY¿^xA™pìäß}¯¾šý´‹‹ /tëfáHKއ‡Õ*Uƒk¹væ."®;ð¨ÿ£xxxX;¼I<“ˆccG¸Çâ ñãYøcñh[ÔÉØbJŠó bÙ²eøùùÙ:Í“)VR»¶Ìé»ÛìÙ³m‚&Iц)ooïÂ}¿¬XC†({..Ý»—\€V0çÐŽ48k'€j@Üç®:Ðàd–l[b£ÍÛºƒÔûRÍ—GFFF‘§¢ÈØ¢NÆS…[ÊÉYÌ“ZQº,\#GBf&¸»+w¢;w¶Úåÿý·àMQ ê³ýŸñæö7Á¼ú{ñÐßq6â,·+Ý Â Ô `É–%šX}C!J;I …¥ÇgŸÁ›o*Û*À¦MðøãV¹ô?ÿÀôéðÃÊoKùßoÿcÒ®ITr¯ÄŽÁ;hU­iii„‡‡дiS»˜ËÝ)°óÏ#¥JÊ=óvöÖ\!¤Bä&s ­$5ÕüÇ–eú¨³§BŸôôt®\¹R Glll‘¯S BŸ)Sr’gøõW«$Ï0p 4jË—[6yž¼{rvò\µ\Uvï¦UµV€2‡»víÚÔ®]Û.’g€'Ÿx’û¯Þéùãõ—¯ö{5ÿ @Æuö4¶X‹ª“œÅüPÙ¾ï>سŠ|½‚8u úö…&M”»ÎÊþ^€O>80ó8“ïùßÚñÓöMÀ·¼/{ƒ÷Ò¬j³<Ç„††jÙ7V‚\\\X>{95÷Ö4Y=Àã¬m3Ûòêâ%Ð2¶¨³§±ÅZ¤ˆPä,æI'B+&44”ÀÀ@‚‚‚d%Qª|6iÏšEà=îø ¨WoNž´üê™™0b,Z¤|]«–rçù,{\Nœ€>‚µk!kôÔé”Å÷߇–-!**ŠU«¶è|}út1©xÿ¿­ÿÇW¿ –g-v½´ K¾ ›:{ö,}Gôå²î27=oR.½宕ch÷¡¼ÿæûètæ×»BXž^¯G¯×gç,Rxš?I ­@ZbŠÒ,))‰—›7gåÙ³ªÏ‡:9qð­·?}ºe/l0ÀK/ÁJ¥M4þþ°s'Ô©cÙëÜñçŸ0mlØ“8;8@¯^ðÞ{Ьٽ__FŒŒØ4‚…G•eÜü¼ýØýÒnü¼ýŠr ÊÝÊ»Q£F89IYŽZ y‹y2Z !ŠÅÃÃ_|‘ßfÍâ1•»ÐóêÔañ¤I>_BBG޹÷Aéé0u*•CCi Ê<Š;À×·pÁÀáÃ0uªR˜ÅÑ^|QIœ5²Ìu2™ ûyËŽ/ÀßÇŸ]/í¢–g-Ë\@ƒ*W®LÇŽm†Bš$ÐV"òMEFFâçç'wîAÆ Í¨!#&MbXHýûožý¿étÈÏbŒFªf}´ŸšÊîÌLš>ø Òq°R¥|Ï™œœÌË/O¤ ?†AAí8°;û÷+‰óÖ\31aÀ˜4 ê×7®‚2d¼~0?†ÿ@ÃÊ ÙõÒ.|Ëßû‚˜˜@IDE[ÔÙãØRÒ QQQøû—ž)R–ššŠ«««­ÃÐ4]¬D&䛚3gS¦LÁÛÛÛÖ¡hÊÌ™3íîc3êÛ—}3fðx®YaóŒF–ìÙ£õT® -Zä}4j¤4;¹ÃÉɉ‘o½Eʼn JQ–;‹¦oÝ9Æl?ú(lÞ ^^÷Œ/55•={R¹ti†™wOLÌ,_Þ;sö:;àAðî»P¯žÙŽBIÏL§ßš~¬ý{-ͪ6cçàT-WÕìk³ ¥®"/[ÔÙãØRÒ²Š¥™J^W¯^•f*fÈh+¹D¢,Húè#†¾ÿ>!w¾Þ§Óq¸BÆßºuï:;+It‹Ê]ê-Hoܘ¾:°æÌÔÊÉô@Lýú¼|ì 01..ަM§pñ¢¹_’Y©ºrœ‹ ÃĉPmS3R齪7Ïl UµV켃JîùßMBˆ’&y‹yrZQ|6àñÁ{öÀ¼ºuù6, RR”¥+Nœ€ãÇ•ÿþõ¤¥)¯MO‡“'•Ç÷ßà tõòbƒƒA™™y.e¾/_ž£G ”<…«+ ï¼£,ìQ’ Étéζ³ÛhS£ ÛnÃÛMîš !„ÖI-„(ž'”N"™™Œ(_ž¡ÞÞpå }û*ËÖyxÀO(,ééð÷ߦ‰õ9½/ÅÇÓèyîBopt¤ë‡â\¾|‰¼Ø»·@Óµ dמ]Lûr1 wæ+—¯Ì„Qøôâ§ìŽÚ @»ZíØ<`3ž®ž–¹¨Bˆ% ´•H¡))ôQgW…>W¯*CÀÁ~ ààAÞZ¼˜Ý÷ZyÃÙš7Wƒåì¿t);™v>q‚®»w³áÚ5ž¢€zÀ÷õê2zt‰½¥-,—<=”_þý…k­¯åŒ¶ø}æï¤§¤C'èà×Mý7Qι\¡Ï/E„êdlQgWc‹•H¡:)"4O:ZÉž={F¯×Û:Ínaêì¦[XjªÒ=$:ZùzÆ xáFLšÄ¨3ŠÖ4¥zuxæeÒqH/]¸À÷<Àm`°ÁÍ®#FàììlÉwR"¾Yþ 뮬ãZ›kyoU8AúcéP ]jÄæ›‹”<ƒýu"´[ÔÙÍØbEÒ‰0/½^Opp0{² ¿E¾¤ˆÐ d2¾(• ‚+”í—^‚úþ^<{6•'N¤[J ½ê×'$<¼Ð taŠ{öœÂš5Å«È7Ô{´纜»çqu·×ålèYé¼'„ÐÉ[Ì“;ÐBˆÂ›1#'yn×.§•v xiÔ(¾¯]}1ï>'%Y8°{8sæ IUÌ_0Ñ'‘3gÎX!"!„–¤¹ bqqqìØ±ƒððpÂÃùví 6¤iÓ¦´nÝšÇ{ÌÖ! Q¶­_¯tem·õëó¬ãliÎÎÎt1‚éŸ|ÂþQ£ŠtŽ•+!6ÖÂÝCtt4±®æ/xËýÑÑÑ4hÐÀ Q !„°Í$Ðׯ_çóÏ?gÞ¼y$$$P»vmêÕ«‡¯¯/ÇgÍš5ÄÅÅѲeKÞ}÷]zö쉃ƒýÜ@—"BSRè£NÓ…>Ç+S7ŒF¨P~þªT)ñËxõUt*éîóâÅðúë{ÑéÆP¹rþù¾ÑX¼Â™¸”8~ûÇv% ¥Ïxþ|nûФI“"_OŠÕÉØ¢NÓc‹H¡:)"4O£ËÎ;éÖ­>ú(!!!tìØ•ßpW®\áÛo¿e̘1L›6“'OÚ Ú¢‘N„¦¤[˜:Ív »rEYq#1”ۺ͚YåÒ©©©„……úu ÀÈ‘`4záé¹›µkᡇîýšÂþÒ0bdoÔ^ÿ¹˜µ¯%Å tOä޲¨µšt¨”R‰êÅXòC:ª“±EfÇ’N„ꤡyš("Þ@Û6m-”BX€ä-æiâtóæÍ u¼³³³Ý$ÏB” Æå$ÏC†h>yþâ 7NÙ®T vì€V­Š^C¦ÍÿlfñŸ‹ÙüÏf2ŒÙϹ9¹Ñ³QO^ný2íýÚ£CG{Ÿö¼ýÉÛœ«²n4_‚ºgêòñ„%yB;¥‰únñññ”+W.{þZ||ªÂÚ>úH™®¨Ì‹Ð°O>·ÞR¶«T;•~-wËÌÌ$))‰òèhy3’%Ç–°üør.'\Îó\‹ûZðrë—Ð|Ý*æy®wPo:>Þ‘VÿÀŽÐt ìÄ€ÏàããS´7(„Âæ4—@ÿ÷ßøùùE­ZµHHH nݺ$%%‘ššÊ{ï½Ç´iÓlf¡I¡))ôQ§©BŸµkaòde»nÝ_q#?-ôùßÿr¹ï>øõW¸»Foç®Lút73n’ªKÅ[çMëû[3wÖ\Ê•Ëih’lHfí_kYüçbö߇‘œÙnž®žôkÚ—[¿ÌCÕï=/ÄÇLJѯŽfô«–ïž(E„êdlQ§©±E#¤ˆPš§¹e,þüóO5jD­ZµX¾|9®®®\ºt‰•+WòÓO?Ù8¢‘"BSÒ-Lfº…ýù' œ³âÆÆ`£D­ ݦNÍIž}}aÏÓäyì{céÿI=tˆÈG#ùï‘ÿkÆŠŒ´zª—/_æØ•cŒÚ<ŠêŸUgÐúAì=¿7;ynW«K»-åòøË,xnÙ乤I'Bu2¶¨ÓÌØ¢!Ò‰Pä,æi¢ˆ0·ï¿ÿžE‹ñÛo¿ЦMyä¾üòKÒÒÒðòòâÌ™3Ù ¶=ÉøÂî\¾ pñ¢²âÆÏ?óÏÚ:ª|½ÿ¾2Ó fMص x ï1ûB÷Ñû½¹öð5õ“܆ò{Ë“ð\ÞÄ«£?Ž’ IDATŠG·ÌË­_¦ae¹{'„(ý$o1OsŸoµiӆѣG“‘‘Att4‡æ‹/¾ÀÑÑ‘ŒŒ ¹³ DIJN†nÝ”ä”IÅNž'N„¬kµkÃîÝpÿý¦ÇM˜>kù$Ï Á'¢Á¡ŽîïÄË­_¦[Ãn8;­û¡BˆÒIs tƒ ðññ¡y󿤧§óÐCÑ®];€ìuŸëÔ©cË‹$44”àà`‚‚‚¤RhÛ!pø°²=lXÎrôæ›ðÙgʶŸŸ’<ûù©{#ù8š9¡tpèÀòÿ[Nm/YUQ¶èõzôz=¡¡¡Ú:MÓÜh€cÇŽ1hÐ ºwïΪU«²÷>|˜aÆáááaÃèŠ& €eË–IòœKdd$ƒÁÖahNDD„í.>u*dÕ<þ8ÌŸo»Xr1 DFæ]ky̘œä¹^=Ø·/ÿä9-- ƒ®ßkîÐÀ£Ý$Ï111Ù…„"‡Œ-êl:¶h”ÚØR–±lÙ2lŠæiî4€——ï¼óŽÉþW^yÅÑX†LÈ7%ÝÂÔÙ¬[ØêÕ0eв}ÿýÊ Eh]\ñññ4hð8îî9 7gf¦wŸ6ܸ·o§Õ©_ÿví‚5ò?§‹‹ žŽžf¯ít͉Ž=;÷-Xt"T'c‹:éDhJ:ª“N„æi¢ˆðöíÛT¨P¡P¯¹uëžžæ!jLÆšwäˆrÇ99<=aÿ~hÜØ&¡ÄÅÅÑ´é.^4÷ -Ž ¦púôl|}ÍŸwÂøâÂdÔÎP?À~;ü8öË1I¼„ešä-æib ÇúõëiÖ¬K–,1»^òßÿÍÈ‘#©W¯ž•¢¢”»tI)LNGG ±Yò\XíÛS äÀýqw2þÈ€*OÁçfŒ›!ɳB³41…cÀ€èt:¦OŸÎ»ï¾Ëã?Ž¿¿?õêÕ£bÅŠœ={–3gÎΑ#GèÕ«»wï¶uØBØ¿¬7.]R¾þôSèÚÕ¶1BA×ùŸ{x.ÓB§A7ðØëA9ïrÄ܃±¼÷kîÔ¸Rƒéc¦Ó§GŸ’ X!D© ‰ÚÑÑ‘Aƒ1`À~úé'Ö¬Yúuë8{ö,T¨P&Mšðàƒ²lÙ2»ì¤$MI·0uVëf4ÂK/)Ó7^~Y©ÌÓ$¾[XHxoly€ª•«úK(×2ømÿoD]Œ¢CPiûHZzkt"T'c‹:éDhJ:ª“N„æib GúõëÇÚµk9}ú4 \¸p[·n±ÿ~æÎk·?üRDhJº…©³Z·°)S”ÂAPæBÌ›gë€ieFPønaÛÏngðúÁd3ñtõdëÀ­<àó 6døáLo:žêd—É3H'ÂüÈØ¢N:š’N„ê$g1OE„¥LÆšóÓOз¯²]¯:>>¶ 8yfÏŽcÙ²)æ‹{öœÂš5êǼxŽË;’˜žˆ›“[n¥}ö–Z!JÉ[̓Ϸ„(kVš¥xyÁ¦M6MžÓÒ”óæÍKÝLý;æožýáYÓqÔ9Ò+D’g!„# ´¥L·öí©píN:铜;‡Á`à¶NdžŸ~‚bL‹ÊÈÈàpV×B3|}}ótýï?X¸/†ÜŸ:9)ËO''-¦èøhžþþi¥ó ðÍ ßЭA·¢L!„P! ´•H¡))ôQWÜBŸAÇ“0bÁ÷˜º (?p tî\ä뀲†û /L"5µ÷=3“éÚõÝfqMš4‰¿þú‹·ß~›µk×2OCË~ ‘[Ïø¹fM wí7??ð=¶EX4isç*Ó3æÎµLòœiÌdàºÙÉsPà =¿¨ø'B!ò¡É)}úôáСCôíÛ—¸¸¸ìýõë×gìØ±¼õÖ[8;;[=®;v°yófjÔ¨ÁÔ©S™7o£F²z¢ô˜÷É'ܺrÅìqžÕª1r‚™é·oþ}°kº]»èú4+€à\‡¬ðð ÿ¤IúäÄÒöîµüyGmÅê¿”õ¬;øu ¤WŽ:GË_H!„¸Cs ôÑ£GÙ¼y3ÇŽ£eË–üúë¯ÙϵnÝšøøx.\¸@ݺu­W||</^¤F4mÚ”ýû÷øõRDhJ } :"‚—.¥Y®™Tÿ÷ç:&L§ãè•B¸”øýwصKy9¢¬²qGÏ;(?èàçºuY;`@‰¼sªT)úkó+ôy÷û,8²€Ö¾­ÙÐw®Že§ðEŠÕÉØ¢NŠMI¡:)"4OsS8"""¨_¿>-[¶”î„YÒÓÓqrr*r‡©´´4ÒÒÒîyLbb"§OŸ&333Ïþëׯãéé™ýµ··7±±± wH®NŠMI·0xóãÙ\»6 !û±*×vC`síÚ¼ùñÇžü}O> ÞÞÊrÿû8“<;;C»vèÞŸþï¾ËŠ;]öV”/OÿwÞ±ÉÝçâRëöÕÁ¯øhßG<àó[lÁÓÕSí奖t"T'c‹:éDhJ:ª“œÅ<Í%Ð~~~œ={–k×®yè 6‘‘A½zõ |¾'NЫW/üüüpuuåé§ŸV=.--AƒáååEÆ ©T©«³Úþþþ$&&’””ÀßÿMË–- |‡CªYMÍž=»ÌWÉW®\ßNË•Ô.Ëõ|˜N‡oµjTìÞ YŸj88ÀCÁ„ °e ÄÆ*I¦N¥çGñs:¤?שCOÝ}.£ÑÈõë×éÝ»7·oß`eØJÆl@õ ÕÙ>h;UËUµe˜6$+p¨±E„™òöö–8THÎbžæ>ßjݺ5uêÔá7Þ`Ú´ièt: ëÖ­cÆŒtïÞŸïÊ•+dff2tèPÖ¬Y“ïqãÆcÓ¦MìÞ½›–-[2kÖ,úöíK:uhÓFY:ë‘GaÛ¶mtïÞ­[·òÈ#ûý 1nÆ &ìØÁÒóçMžûÜh䓃M_Ô´©rúÉ'•uàòIt:ýßy‡ ×^ãåºûœ{-gK2|<ûc¾Yû ‰IvMÆ+΋ º œiv£—w¶ÚŽŸ·_É!„B¨Ð\íîîNHH½zõ¢Aƒ899Ñ¥KiÕªU¡?jéܹ3ï4‹øã?HII19æöíÛ,_¾œ×_Ç{ €iÓ¦±dÉæÎ›@O˜0矞3fpíÚ5¶mÛVÌw+TvuÅ·NÂΟ§Y®ýa€/PÀß?'a~â ¨Zð»­= @¿~}‰Ü}NO‡7®ÌYøÓƒ±‹]$tÊyí-nA"°ÜžtcÓ[›hRÅKy!„… ¹)DDD°bÅ &L˜À¸qãX½z5‡Æ×××â×;xð 4mÚ4ÏþæÍ›ç)bl×®W¯^å§Ÿ~âßÿ¥Aƒ¾Æ¡C‡øî»ïÐëõyYE@YÊÒ¾ÈÈHÖ®]«‰X¬¾/#¶n…þýÑW®Lð¾}|~ç˜@üÏÃq_ ÑÑðÏ?è»v%¦cÇ<ÉsA®«Óéè1`7nܰøûøàÒÓ;Ç©_9“'‡òñÇdzƒgí‹dôè>¾Æò•Ëùõܯ$Ô¸“—°~Çó“Ÿ_¾©ø©Jú…†BµjðÒK°sgNòÜ LŸQQJRœ»S•‡‡¯¼ò óçÏgãÆ\¸p1cÆËÈ‘#-‡tõ1eÝÂú ÎOuê˜Ü…6?ôpqQæ8¬_—/Ü9pg)Ä‚Xµj•#¶œ_~©S•íF`éRË_#ØÁ¨Í£Ð»ê! t‘w>ýÉ*"Hê{«³øãÅe¾Õ¬t"Tgc‹5H'BSÒ‰Pä,æi²ˆðÓO?eïÞ½¼ñÆtèÐggç"Ÿë¿ÿþËw¥‡ÐÐPüýýeŽòرcY¾|9·nÝÂÏÏùóç[ätpp0çÎcĈ4mÚÔd¹ÂÉ~ºÿ~V9£TÕ•2IIÊ õ¬òƒåË-Ÿ<_M¼Ês+ŸãÈ¥#T`SÿMT-W•ÔÔTNž<ÉÕkWiÙ¢%5kÖ´ìÅ…Bàææ†··7...¶Eó4—@ÇÅÅ@Ó¦M2dõêÕ+‘öÃùqpp°xò P£F ‹Í§¶å¸hÝXôAYy£û„ ¥¶bù•WàäIeûw”U÷,éyæ‡gˆŠ‹à…/ðcÏñpöÀÕÕµì­.„6àïï¿¿?!!!¶Eó4—@ïÚµ ¶oß^¬µ—µ&55ÕÖ!hNdd$~~~öµ¬ß™3ðæ›ôz;9ÑÃ` ¤vmVn±KDDDаaC‹¯8¾þZYÂà©§à£,{þ½ç÷Ò=¤;±)±Œ ÅW]¿ÂA—·<Ã`0•=åJ(² µZtj+v9¶X–Æ­±E]jjj™¯11GsE„µjÕÂÍÍ ÷|V:°WçÎ#""B*æs±»BƒAYÃ-) GŽ£GÓÏÝcÆXôî³V }~ÿÆW¶k׆´ì •ÃäéïŸ&6%:>éô sž™c’<ƒúäGŠÕÙÝØb%Z[´DÆ–¼bbbˆˆˆàܹs¶Eó4WD˜‘‘Á³Ï>ËO<Á¸qãŠU@¨ÁÁÁ„……Ñ»wo ´uH¢(&O†iÓ”íqãȘ5‹;³nÛ¶R7}ãÊhÝZYDÄÕBC•"BK™:ƒI¿Nˆ7'7¾ëþ½÷¶Ü„BZÖå«W¯¦Y³fRDxšK ³V«Ø¶m^^^´k×Î$‰Öëõ6Š®h¤%f)°?<ödd@³fÊ2®®¤¥¥•ºb ƒžxBIš/†aÃ,sî c#É¢£‹ðq÷aCß Ö–?*…B+$o1O“ÄÜÜÜèÖ­›­ÃB‘ƒ)ɳ«+¬X¡üJ]ò Ê´¬äyøpË%Ï i ôY݇-‘[¸¿âýl°…ú•ê[æB!„•h.®[·®ÝÝa.)"4e7…>cÆ( !ƒRE×¼y‰^Ζ…>+WÂW_)ÛJ¡%\N¸Ì³?<˱+ÇhS£ ûm¤j¹ªz½ú¨“"Buv3¶X™š’±Eš§¹"ÂÒ*,,ŒÂÃÃmŠfØE¡Ï† °d‰²Ý¡ŒWâ—´U¡OX˜rÇ JX»6ûF{ü÷ßtÜV[Ѩc#Ú=ߎ¯~Å©ë§h»¸mvòÜ­A7v¿´»ÀÉ3H¡O~¤ˆP]Œ-6 E„¦dlÉ+<<œÂÂÂlŠæibôüÁªU«6l>>>|òÉ'÷<~öìÙVŠÌ2‚ƒƒ¹xñ"ãÇÏ^cQØ«W•ùÎׯƒ——²ríÚ¶ŽªDÄÅ)E‚gÏ*+mlßO>Yð×/ûq,ø€è€h¨³ßý”;égÒ1t5€¼Þæu¾ìò¥êJB!l+22’ÈÈH>ûì3jÔ¨!s ïAŸo]½z•УGÜÜÜ8pà€­C²8i¤b‡† S’g€¹sKmòl4*S¼³f©Ì˜Q¸ä922’÷½Ï…'/˜<—Ü$ª¿Âg_~ƸGJþ¾Bˆ¢‘F*§‰º{÷¸ðÀàëë[*hag,€_~Q¶_| °m<%hÚ4Ø´IÙîÙ&L(Üë_yë.´1Mž³UÏŠžt*ß©èA !„¢™ÏQ ÀÁƒmF‰‘"BS‘‘‘ [‡aêÌ™œ"5jÀüùV½|DD„Õ®µe |ø¡²Ý¨åÓºKñ—ÀãÞÇܪq‹í{¶þäw "##‹üúÒ*&&Fš3©ÐìØbcÖ[ì…Œ-ê$g1O3 tiwõêU[‡ 9š,ôÉÕmNÉ(­ÜRÞZ…>çÎ)7Ö33¡BX·Ê—/Ü9RSSI6&›?ÐþŠü«h"…>ù‘"Buš[4@ŠMÉØ¢Nró41…£,ˆŽŽ&88˜   ‚‚‚lŽ&h²tÚ4¥I Àÿý<õ”ÕC°FÑFr2ôè±±Yׄ¢¬nåêêJÇ fs¸æÀc+üîðööÖæ÷‹ÉX¢N¾WÔIA˜)[òÒëõèõz¢££©]Jë~,ES ôÆ‰ŠŠ2{ܘ1cJ> ”ÁKë€éÓ•í&M”jºRêÕWáøqeûí·•dº¨ZÔkÁ_×þÂX5ÿ}j\ªA§'e´BhYÖM¾¬N„"šJ ¿ýöÛg ´Ð¸„eêFF¸¸(ÝÝÜlU‰˜;¾ÿ^ÙîØ1ço†¢1~!A!»AeÝh^êø5jÔ(Þ…„BÐT½bÅ ž}öY[‡Q"dB¾)Mu ;6g·iÓ eK›…RÜna;àÚ5gÕç’’”¹Ïnné„„ü€£c‘/ET\½îMæ™°œ[:“^3ÜkPýluúôáÉý"H·°üH'Buš[4D:š’±Et"4OSE„åÊ•ÃÛÛÛìÃÉ„|Sš)ôùùgX¼XÙ~üqxóM›†SÜBŸ7œ _¦úø÷ßeË€eÔªåLqò®Ø”XºþЕ+ W  |µô+?·˜¾ñ}éxª#kLdל]|ñ¿/pp(ÞP#…>꤈PfÆ‘"BS2¶¨“œÅîô±£B!¬G3 ôùóçKíügP>j F¯×Û:‘eáœnƒ½zÁàÁ¶ÇNLØ>U§VоN{–-C‡ÎÆQ !„(.½^Opp°L +Í$Ð¥]@@@©¢RT6íöÏ?0nœ²]½º’Lk„–»…}}èk>Ûÿ«4FßW«cÉšH·0uÒ‰Pt"T§å±ÅVdlÉ+((ˆeË–`ëP4Oh+‘ ù¦lVèsw·Á¥KÁÇÇúq䣸…> ä.ë#Ö3f«²„¤oy_¶ Ø‚·›uŠz¥ÐGª“"BuRDhJÆu’³˜§™"ÂÒL&ãkÌÀÔ©ÊöèÑðÕW¶ÇBŒFe1‘‘#ƒ1–™=¾0E„û/ì§ãòŽ$’©àR}CöѲší–úBQr$o1OÉeKînƒÁÇ¥£øíèQ9²ü¹ÿ¹ù/üøɆdœœXÝgµ$ÏB!Ê4™Â!ÊŽÄD4H™ãàì¬ttw·uTÅ«$ÎmÚä$Ï–\ÌæzÒuº®èJL’2ÏvÑó‹è\¯³å. „BØ!¹m%҉Дջ… YÅ"~­[[纅TnaF£2uûí·!«†ÌÉ ÆŒðpg®] 6{êÕïi'¥'ñüÊç9«thœÒa CZ)Ð{°4é¦N:ª“N„ꤡ)[ÔI'Bódt±™ojΜ9L™2Å:Ý%7n„o¾Q¶•ÌS£fΜyÏygÇŽÁ¨Q°ξ`î\hÜà›bÇaÌ ßÚ~¼x€¡­†òAûŠ}Þ¢Ê*ô™={¶ÍbТ¬BYÝ'/«Ž-vÄÜØRÉØ¢îêÕ«Ô®]ÛÖahšZLÆ·±k×”nƒ×®A… pâÔ­kë¨8}ú4ÉÉÉfswwç¾ûðÞ{°`AÎJ¾¾ðé§Ð¿¿eãµyóÏ s½Îlê¿ 'ù{[!ÊÉ[̓߈V’ÕH%((HîYÛ°aJò ÊŠIžºwÇ•+Ìçá—ì·á䯿®ÌDñô´lL³~Ÿ•<·ªÖŠ5}ÖHò,„e€^¯G¯×J`` ­ÃÑ4ù­h%ò—œ-,Z›6)Û=zÀ¿ªµÂÅ¥ ±±cÌ{<{;0æÍSnª[Úá?òÎÎw¨íU›_üBy—ò–¿BÍɺɬ±ß•Z$«pX‰š*ñna¹» úú*É´]Pïvß}°|9üö[É$Ï{Ïï%XŒ#ÞnÞl°ßò¾–¿PH·0uÒ‰Pt"T'MÉØ¢Nróä´•”õ"ÂÞ{ß¿ûŽûr-wèúuZVª„‹CÎßq&'³÷Ä *V¬höœ±±±ùÿ з¯²tPiÑ"œ+U*Þ›°š™À²<{*W†Ó§ÁË«d®ø×õ¿ "-# WGWô}õ4®Ò¸d.VRè£NŠÕI¡:)"4%c‹:)"4OŠ­@&ãÃåË—™üðÃ|óßùsøfà@>ýþûóÓiÓØ3{6MÝÜLŸ¼}[yëÝÝ9ƒ‡‡GQB/Q-[sâÄ2³Ç¦k`a]N¸LÛÅm‰ŽF‡Ž•=WÒ·iß¹–B퓼Å<¹-¬Â×ׯöí9½b ò9æÓZµ˜:kVÏ9rüxŽ-_Î̳gó=&T§£ÊèÑšLžµàvÚmžùá¢ã£ø¸ÓÇ’< !„fÈh?ãÅ7 IDATa5ãgÍâÓZµTŸ; xµo¯oÁçÜzxxÐúÅ ½G³„ù5j0âÛ­_¬e†L½Wõæø¥@ñõ6¯3áÑ 6ŽJ!„Ð>¹m%2!ßô.t$à‡òMø©›S¯^…_¥Å¶‡‡ú#×s#žy†áßO ÊÔPZl‡wŸ#€’ïöÊÆWØvvA ƒø²Ë—%~Í¢’naꤡ:éD¨N:š’±Et"4OF+)ëE„YÆÏšÅäÍ›ùææMæS€«€WJ ¾;vú|@k ¸{ÅÊùuëòͤIÅŒØL‹-íý²ôøRÚÖlËÊž+qÐi÷))ôQ'E„꤈Pš’±Eš' ´•È7"žŽïGáuó&§¬áj‚›S›4QVÎHJRÉÉÊSRÌžv0œ¼ t¨£#­_|QówŸ †dàø]{ǘìSŽ+¼ôôtNœ8Áñðã´lÚ’-Z°âÔ ¦ì™€¿?ûmÄÝÉýÞ'²1oooù§Bguò½¢N’gS2¶¨“œÅ,] ®®øúú’T»6¯¾ÿ¾Å.9bÒ$Þ®\Ù.î>ÇÆÂ;ï(ÛuêÀĉ9ÏÍœ9³Xçž9g&—š^º÷õŒeÆ×3ŠukÊ*ôy…††fŠsæÌ!!!ÁÖahNqÇ–ÒHÆ–¼ôz=ÁÁÁìÙ³ÇÖ¡hžt"´‚2×ÑçúuèÝöîU¾®V Ö¬víò–™™‰ƒƒeÿ†ûîÛoéÕ·¯æè‘#•¿'Ö¯KÖ‚µéÚ†Ãm›=.à@‡¶²Ü……B” e.o)™-,ëèQèÞ²ÖenÓÖ­ƒ5Lµtò 0xèP‹ŸÓÒŽ…… •í®]-›ßÿ9'ÓN 3Ç@³Í,wq!„¢ ‘ÚJJõ„|ƒÆŽ…Áƒsæ;Ï™K–(Yb>Êb¡Ï·ßÂÁƒÊö[o)K×Ý­°…>Ɇd>ßÿ9÷y?ã·'¹y2üvï×Ô>Q›ÆÛO‹s)ôQ'E„êÊâØRRDhJÆu¥:g±I ­¤Ôv"Œ‰§Ÿ†¬…è«V…_Un³šQÖ }nÞÌ)ôóË[8˜[A }²纳ë2~ûx®&*ßcÍü›1¼çpª…Vƒô»^”Õ~«Æ{/¿GõêÕ‹öFl@ }ÔI¡º²6¶”š’±E]©ÍY,HŠ­ÀÞ&ã'&&’ž~wæ¥âäI* ŒãùóÊ×Ê|çš5K6@;5b,X lëõЭ[ÑΓlHfÁ‘|úqvÒ Ð´jS&·ŸL¯Æ½Ð¡ãБC¼6ñ5ndÞ ±B"ån—£’C%ÌX@›‡ÚXà !„(ì-o±©"&fÏœÉoóçÓÐÍ-ÿƒ’’ø9.ŽF#@Y®nþ|¸×kʰ£GaÑ"eû™gL“猌 >ó)[vo!>9/w/º>Ñ•7_GGG 'qžõû,®$\É~mã*ù ýônÒºìýmjß;þ$11‘³gÏR¯^=Ê™iH#„Bó$&FO˜À_+W2ûßó=æP¨à䟣G[->{c4*ËÖåW8xîÜ9ºíÎé:§Ii‘¢L¬Ê„ƒÇòãS?òã7?²5f«Iâܨr#&·ŸLŸ&}pÐå?«\¹r4oÞ¼„ÞBQöÈh+±§ ùžžž4îÖ÷Xík`tåʰsg‘“ç²Rè³d º³ÜòÛoçm×™™I¯á½8ñð RêÞIžcH©›Â‰‡OТ{ Æm—<7¬Ü•=W>2œ¾MûÞ3y.-¤ÐGª++cKaI¡)[ÔÙSÎb+¥ÿ7¯FØÛ„üÑS¦ðµŸŸês€Æ÷݇çŸBûöE¾FY(ôÉ]8X·nÎv–¯~Å_Õÿ÷\;sׄ¹Cz³t8 *5`EœyŠ~Mû•‰Ä9‹ú¨“"Bueal) )"4%c‹:{ËYlAŠ­À^'ãO7ŽŽ_~IÛÌÌ<û”/Ïü³gñ¬ZÕF‘Ù×^Ëiš²a¼ðBÞç;õêÄÎF;ï=™ÊM7áÄ–8êK,V!„ì7o±¦²s KšÚ]è:‡—乎o¾Q¶Ÿ}Ö4yˆMŒ5_‰àn:7Iž…BºÖ­[Ç!Cxâ‰'ˆŽŽ¶u8%.{.t®}_×­Ëè)Sl’ÝÈÌÌ)tsË¿ã`¥ •L×j¾[úã„B¡ ’@BJJ ݺuãôéÓ$%%êµö:!ôûïóµNYí€NGãnÝðôô´È¹Ks¡Ï’%pø°²ýöÛpÿýêÇuïÒrQw--wWMX¹¨rtïÒÝòAÚ)ôQ'E„êJóØRRDhJÆuöš³X“$Ѕп‚‚‚pqq)ôkíuB¾çÅ‹469|]¥ŠEï>—ÖBŸ7rº ªæöêW©u±Üε3wMØmhv£¯yµ$BµRè£NŠÕ•Ö±¥¸¤ˆÐ”Œ-êì5g±&YÚJj×®mëŠæèQF]gºvµØÝg€ÙYí¿K™wßU’h€/¿¼wo™«‰W‰y<ÖƒÎ_‡±‘‚€4(YŽæ·›³né:t:]þ')¼½½Kí÷KqÙ:M’ïuRfJÆuv›³XQ©M F#QQQ¤¦¦Ò°aÃ|‹ŽŽ&<<œx€x {||<ëׯÀßߟÀÀÀY“ŽÁìêJ?dÌ:|/V¶Ÿ{ž>ÿc3™ \7‡è Ã]‡s9ü2Wc¯r_Åûèþ|w† bÀ…BQ`¥.Ž'((ˆ?ÿü“[·nJ2}·ÌÌLFŽÉÂ… ©U«.\à¹çžcÕªU¸¹¹‘™™I\\@¡ç;—*Gðê÷·ƒÑ¶ÌL5*§pðË/ï}üÌЙüzîW†´ÂÂn ­¥B!Š«ÔÍÎÌ̤~ýú|úé§¼újþóF.\ÈÒ¥KÙµkÑÑÑ„……ñÛo¿ñÁP±bEÆŒØ1cxúé§‹—]NÈÏÈ€ãǕ퇲øéK[¡ÏâÅ9…ƒï¼“á ÀÿýÁ{”﵆•2癜9xRècJ }ÔI¡ºÒ6¶XŠŒ-¦dlQg—9‹••ººbÅŠ,\¸áÇã—O'=PèGy„'žx€&MšÐ½{w–.]JzºúºbS§NÅÏÏÔÔT:wîLÇŽ —]NÈÿë/HNV¶|Ðâ§/M…>¹ ï¿_Yy#?±)±ô[ÛC¦7'7~êõÎÙÏK¡))ôQ'E„êJÓØbI2¶˜’±E]æ,VVêè‚HIIáĉ4iÒ$ÏþfÍšqýúuΞ=«úºÉ“'ÅåË—9þ<¿þúk¯Ãwß}‡^¯Ïó¸ûö-Z”³¢Ú;Ж¼ÆìٳٳgvÞ¯™}aaa8p€Y³f±uëV8ýxæ™Yܼ¹8Àرa¸¹å¾A+¯¬#þÙÓŸñïþó·lÙ2M¼_-íË*ôÑB,ZÚDPP&bÑÒ¾Ù³gãíí­‰X´´/«ˆP ±he_î"B[ÇbË}Y9Éĉùî»ï敺9ÐqéÒ%Õ:ëù{–YHxzB®˲êÅßåâÅg1Îáèè€NWPfº$&žpr*ϼy¿ðúëUϱ%r ¿\ÿ< G£Œ ‰þ¢ÞŠïB!„Å¥3ªUØ•3gÎdâĉ&E„ǧU«V¬^½š^½zeï§Y³f¬_¿Þ¢ËCÙmOù¶máàAèÐvï¶u46ײe0'N,3{\‹Á?nzÜÉ«'yxñäR¨íU›ã¯§¢[EË*„BƒÝæ-VT&§pT«V €3gÎäÙÿ÷ßçyÞ’’’’HII±ŸÂƒNœP¶K`þ3”­BŸÄôD^\ó")†œœø±çù&ÏRècJ }ÔI¡º²4¶†Œ-¦dlÉË`0’’R¶W+ 2™@W­ZN:•gXX5kÖ´ø5ÿýwÆŒÃÖ­[-~îqꤤ(Û%°”­BŸÑ›G£üòú°Ã‡}úΩS§²»¼uéÒ…›7orèÐ!‹Æa—…,Y/¿¬lŸ9#s )úŽ•a+°nëvdû í8èÊäß®B!ì€]æ-VV*‹çÏŸO||<{÷îrþêîÒ¥ -[¶`üøñ´oßžaÆ1pà@¶mÛÆŽ;X½zµÍâÖ”; TðòÛÆbÇ"oFòڦרZ®*+z¬äY!„°s¥ò7ùöíÛÑëõÄÆÆòðÃg/Ïrîܹìc~øa¶nÝÊÙ³géÑ£{÷î%$$„=z”HLçÎ#$$„ððð9¿Å9¢ü·uk¸s‡^NZF}×ôåvÚmtèø®ûwT+oùùõB!„%„‡‡’'_êJåèõë×è¸:dߥ.i:oooÜÜܬr½bIO‡“'•íšÿ J¡ŸŸNNöñm[¸ãßÙùG/+wòß|ôM:×ë\ ×EDDÈ2Šw1 DEEá/Ÿ†ä‘U@X¹reG¢-ö6¶X‹Œ-¦dlÉËÍÍ ooïì©­"¥ò´988Ð¥Kûø! ‡¬6ž%´ØO¡Ñ“'CttÁ_óË?¿ðÅ/hS£ Ó;N/ðk¥ÐÇ”ú¨“"Buö2¶X›Œ-¦dlÉËßߟ.]ºàà é¡9¥ºˆP+ìn2þâÅ0|¸² õêÙ6JI—^‚U«úã̾¦þCÓ¹Ùç1I1x¹zqìµcÔõ®[Ò¡ !„awy‹ Èç[VODD•+WÖþÇ­Y󟽽Ëtò|å tëY‹²Üw_ :N•*ù¿Æˆ‘åœ#&Iùh}Ñó‹$yBa²Ö–ÇËËËÖáhš$ÐV^¯'00ÀÀ@[‡soY+p”àô ­;yž{þûOùº}{X»¶•*ÝûuS÷Nåï=ÊúâÃ[§O“>%©Ba„††M³fÍlަÉ$+©_¿>ï¼óŽö“ç´4«‚v»…mÚíÚå$ÏC†Àöí˜Mž÷ßÇÔ½ShR¥ _vý²H×—na¦¤[˜:éD¨N«c‹­ÉØbJÆ–¼yçw¨_¿¾­CÑÿ\™¶‘ ¬Þ÷ñÇðí·àâbzlll,‡"66–É7°nÆ ÜÜù©÷O¸;¹))ô1%…>꤈PÇ-±Å”Œ-êì&g±!)"´»šŒ¿h¼úª²ýï¿P·lÌß5`Ô(åí”++V@P鱿ïÿ×&¾FœK (»<ñ×ã¹ýÐm¨ Ÿ[È+¾bÝ7 „BXˆ]å-6"s ­$«‘JÓ¦MiÚ´©­ÃÉ_ÖügŸ2“<ÇÆB¯^°k—òu°q#´jezì¼%ó˜ºz*W¯‚³²/Ž8HvCK§–’< !„°Kááᄇ‡sîÜ9ê–‘ ¨d ‡•¸¸¸ØG#•¬8ÊHad$<òHNòüàƒÊªjÉóÿýÇÇß}ÌÕGr’çlÎÀÓs*†èÂ,-„BhDV#µy‹"I ­¤råÊÚo¤’–¦Ì«$ж.ôÙ»~NŸV¾îÙöíƒêÕÕŸúùT¢[Ü;9¾ÐêÓ¾˜V¬¸¤ÐÇ”ú¨“"Bu¶[´JÆS2¶ä•ÕHEóËíj€$ÐVbòOžÌ) ,á8À¶…>ß~ :ÁÍ›Ê×'ÂêÕàá‘ÿkNýs ̬Ä„Ÿ /VlRècJ }ÔI¡:)"T'c‹)[ÔÙEÎbcRDhv3áBxí5eûÜ9ðó³i8%!3SI–gÍR¾vqo¾ÁƒÍ¿¶í³m9Øæ ÙãÚlÃÁÍæB!´Ènò’;Ð"GÖüçJ•Jeòœ˜¨LÓÈJž+W†_-Xò P³zM¸mæ [PßOÖÏB!J3I ­$44”àà`ôz½­CÉ_)î@xñ"<ödýó7jBAûÚ¬ [ÉŽ ;`ß½«~²:FL(^°B!„ èõz‚ƒƒejXÈ2vV íBRSs ­0ÿ”B???œœJöÛðèQxá¸tIùúé§aÕ*ðò2ÿÚëI×±ikÿ^ ^@Ep9âBÚƒi Ë{¬÷ oúµíGófÍ‹oDD 6,Ö9ì]XXK–,Éþ:33“[·náíímè´'99w÷¢5í)­âââðôôÄáÿÙ;ï°(®¯¿ Ò¥ˆ(`Ebì6Ä^Ñ ¢±F-‰‰%‰%‰¿h,Qc×X¢Q°‚Šb{,`ìˆ ˆ(¶(ˆ…~¿?6»²²ÀÒQç}žyX÷Ìî0œ=sŠ–ä#ÊÈóçÏ)S¦Lq«Q¢øXï-7¦ÿþ™ÆÝÝÝqww§_¿~Å Õû…d@%> ÿòeHI‘¿."ô’%K˜1cFžo\ß}7—½{¯ “ig)ÑÑ÷IOß óŰhhb³û‡ú3"`_=ÀÒÈ’U¿¯âéù§ü²òâÊÅñÌðæ¯Í1{bÆw#¾cˆ×,ì>W®,r2À¿D[; `ìØœçKŒc\à86^Ù¨ë]«7˺,ÃÂÐÀËÓ‹ëׯsóÖMj:Ô¤víÚèè¼[:o|ìÆ³##£’ò$!!!ñ¢HÌŽo³”$ZBŽ"°\9øÀþpd2ðõ•w̉ºk(÷î`n`ÎÒÎKñ¬ã©"§££ƒ³³3ÎÎÎ…¡²„„„„„„D F2 %ä|À „åÊAÛ¶Ù˼L~É„ƒX¼J9Ö¥FVw_uiëBÖPBBBBBBâ}B œ+"’’’Š[…¬IL„ë×寋Ѐ.ªna9EV¿{§åNJãÙXט?ºÿA@ÿ€b1ž¥naÅI‰¶YJ’]D•Ü2v—/ƒÂ-¢øg(þnaoRßðÕ¯p]ïJd\$®¶®\ýâ*Cë-6½¤naÅ¢Œ]PPPq«Râ‘B8Šˆ:”Üä0Eü3©:¿I„ùé¡yîþ9¼ý¼¹õï- J0»ílÆ6‹ìÝútEL‰½N$$$$$>heì4I4üØ‘<ÐoãŸË—‡Ê•‹W 9{ŽÍýqÉiÉ|wä;\Ö¸(禕šòÏÈ×d\±Ïïqqq¬X±‚þùG#ù°nݺBÖ*g^¿~Mrrrq«¡BBBNmNßòüùó" “È?ééé<þ\Yr2'^¾|©¬ï.Q²‘ h‰·è" ßÈ+Ã!àâ¹-ÛùÏÃh´º³NÍ"M¤¡«­Ë,·Yœr û²Rûí÷‰´´45ÚDU!4^#--­€ÏPÎŒ3pttTnNNNtëÖ™3gòâÅ‹\Ï×¢E •ù²Û4‰Åøð!£Fbÿþý­¿|ùr¾úê«\ë]:t///¬¬¬022ÂÀÀ;;;/^¬±1š––ÆöíÛ™2e :t fÍš¸¹¹åY'Ú·oµµ5&&&áììÌÂ… yýúµŠìhÑ¢FFF˜››£««K¥J•èß¿?7nÜPÊ=}úGGGÆŒ“g½$òÏæÍ›qvvÆÀÀsssttt¨V­C† ᢫ׄ……Ñ¿,,,066ÆÐÐSSSÚ·oÏÎ;Ud{öì©ü­Y³&Í›7ÇÓÓ“o¿ýVã¿C‰‚A á("Jl@þ›7 ¸ùqŽÜt"LKƒåËaêÔ·†³¶¶|ü-1 a 8zòa­tæŸÇœssHI—7‹q¶rfCÏ Ô-_· O©@:æÌ¢E«™3ç ººå³•KL¼ÁîÝshÚ´i®×¸|ù2íÛF_?ûk$9ù_FvaêÔ/s½FN<|ø[·náááAÙ²e‰'00€€¥ç×ÂÂBãùj×®­Ò0âñãÇ;vŒš5kâä¤ÚAóCën8uêTBCCéÚµ+µjÕ"::š;v0nÜ8BCCYºtiŽs$$$лwolmm¹ÿ~ž¼…/^¼`È!ìØ±<<ùä“|CRRzzzùšãƒGH:ÞÞÞÂÕÕµ¸ÕPÏ™3BÈÉ…ص«H—?~¼xþüyŽr§N Q¯Þ[5A!Fž#œœ>öÝ„¾u¡ÓÌPÈúÊ„v'¡[£´°¨\SØ»ôãL3Ì@hÿ¨-~8úƒHNK.‚3ÌÞÞÞÅ­B±ãçç'LMM³Üÿë¯ËœU¹&Ôm&&ËÅÙ³gó¤Ã¥K—D™2 r\.‰©SäõT³eĈ—.]RŽ%&&Š@Œ3&_ó;vLbÒ¤Iy:þæÍ›³fÍÒH¾GÙ~®…Éîݻŋ/TÆÞ¼y#Ê•+'‘ãIIIbïÞ½âñãÇB!,--…M®u;v¬ÄرcEbbb¦ý§N#GŽTþngg'd2™¸|ùr&Ù—/_*õBˆØØXˆ=zäZ/‰ü“˜˜(ttt„xøða¦ýÏž=qqqÊßG-1gΜL²iii"**Je¬^½zBOO/“ì«W¯ÄôéÓ…®®®¨X±¢ÊïâííãÿWWWéQHè"¢ÄvõÉØ°ˆ=Ð9%>z'ÂÆoañbE]ço :„çO{=W:œÓH!­I Ï#ÂxvíéæòØ3G G6ôÜ@£ ï¤ )‰P";ôôô7n›6mââáW¿üò éééüðÙäïÝ»ÇüùóéÒ¥ íÚµËq~!dÿþýܼy“„„ìììèÞ½;Ùt#º~ý:k×®%88˜jÕª1xð`Z´h¡Ñ9¥¦¦²råJNŸ>Mtt4Õ«W§OŸ>tîÜYE.))‰'NpøðaNŸ>žžvvv|öÙg´lÙ2Û5ºuë–iL__ŸAƒ1wî\._¾LµjÕ²CWW7“N¹åêÕ«,[¶ ggg-Z„L–9ïÂÅÅ^½zEDD 4Èô”ä;ŒŒò¤Ëýû÷™;w.={öÄÒÒ’5kÖpþüy¬¬¬>|8mÕÐÿ÷ßY¸p!—.]"11‘zõêñÅ_`gg—IVñ¹ž={–;wîP¦Lš5kưað²²RÑcÑ¢E\¹rgggƵµjÑŸ~ú sss† ÂâÅ‹9qâfff|úé§J/í¾}ûð÷÷çöíÛ4kÖŒ~øCCCå—.]âÏ?ÿdäÈ‘<~ünܸAíÚµ7n\&Ïý´iÓ¨X±" `É’%œ={|}}Õ¾§ááᤤ¤Ð£G,--3í/S¦ŒÊïŠsÛ·o§C‡¨È߸qgggæÍ›G©R¥(_¾<ëׯ§~ýúìÛ·OEöÁƒ4jÔˆ1cÆpçÎj×®žž .T‰Õ={ö,õêÕcÅŠcll̲eËprrâìÙ³*s®]»– 6(ód2;wîäÓO?eÏž=üñÇtíÚ•°°0¢¢¢˜5kV¦/N·nÝbÑ¢E,X°wwwâââ°µµeË–-4kÖŒsçΩȯZµŠ7Ò©S'þüóOôôô²M@UÍÇרLkùòò0´‚*qûÝwßaddÄÞ½{ d>‰l(nøÇ€&KŠ:uäÏ¡»t)Ôe<<Æ‹R¥ÖŸl7SÓN¢V­ç*Èûö"&&óœ{„^o=exFV[õVÕ õÜ$ ž‚ áÉ– ³BOOäzÓÕ½$d²’Â!„&L€ò¾rçΡ¥¥%ÆŽ«"—’’"¬¬¬„‹‹‹ÚùÕ…p¤¥¥‰7n¨È¥¦¦Š>}ú™L¦¢‹"„¾¾¾Êñ§OŸŠêÕ« sss¯WÂ1dÈ???‘žž.„‡%ôë×Ohii‰þùGyŽ€Jhƒ‚Œ! ¹áÎ;ÂÔÔTÔ¬YS¤¤¤äúø¼„pxzz @m8FV(>ïR¥J‰Î;‹åË—‹ððpµ²¹ á¸té’òóû믿DjjªBþ¹jii‰&Mš¨È·iÓFhkk‹óçÏ+Çîܹ#,,,D¥J•Äëׯ•ã}ûö2™LlݺUeŽÄÄDñèÑ#!„üZ«[·®0117oÞTÊ\¹rEˆºu늴´4å¸ÄСC•×Õ£G„©©©033vvvâêÕ«Bù5ûÙgŸ @œ8qB9‡„¶¶¶VŽß¾}[˜˜˜ˆ† ªèkii)1lØ0]²ãÓO?€044}úôk×®D’UÎÉ™iÚiïUÉ'©aÁ!¤¤@RRî·ääüÕ/HÖ­[ÇìÙ³ùî»ï¨[·.óçÏÇÈÈHé¡­Zµ*:t`ãÆ*Im»víâáÇŒ1Bãµ´´´¨Y³¦Ê˜¶¶6ãÆCÁ™3g2cff¦L®([¶,<{öŒ­[·f¹Ö“'OX¿~=Í›7ÇÝÝ]é‘522büøñ¤§§+×ÕÕEKKKmrc¹rå4>?IIIôëׄ„V¯^­Q2sApûöm*ç¢dèœ9sðóó£yóæìÛ·Q£FaooϧŸ~Êßÿoj֬ɀÐÖÖÀÑÑ‘úõësõêU¥ÌÍ›79zô(5¢Q£·OòªV­J·n݈‰‰a×®]DEE±eË<==U® ‡ )¼®ÇçêÕ«´iÓF%t¢nݺ´nÝš«W¯rüøñLúNœ8@îÁuqq!..Ž>}úP§N@~ÍvïÞ@í“™F©$ÚU¯^6mÚpñâE.\¸I~̘1hiif2mÞ¼™ 6àèèÈÖ­[2d¶¶¶ 2„늮¿ÿáêêÊåË—:t(7oÞäûï¿§E‹Ô«W7’’’¢Ñš±µµàÎ;¹>VÑHåĉ¹>öcCŠ."lmmK^lë?ÿ¼-cQÄñÏr–33•ÑÒ¥aÆ 7.û6ܺ:º AiM-´4¾ñ•fÏž]ò®•÷==èÓ4Œ^Páñcðñ‘wº/n~ÿýwttt°±±aÀ€L›6êÕ«+eFE`` Û¶mcàÀ¬\¹’2eÊd2brâæÍ›,Z´ˆ .£RE(:::“|ëÖ­3ýµk׎ٳggûO<44”ôôttuu™>>SöcC2 ‹ˆŸ±a±Ô€ÎœDhh.È“s¢­K[–¬\›rÙ”‘`ZÊô½2 %ã¹àÐÓƒ/¾€ûì3öìÙ rå/8pàQQQJOan¨U«µjÕbĈìÙ³‡îÝ»óã?æË€VçÁWxä @F¨ºòxŠz ÃM!›“§Éœï¿Ùéúî¾wÏ!#êÖTŒ=}úTe¼víÚêO@ûñãdzdÉÆŽËO?ý¤öK‹……žžžxzz2wî\œœœ8zô(gÏž¥Y³f¯yçÎŒ3%aæ†i³”0Þ«B¢àQTà°¶–o%€2eäùŒšÐƵ Fw ›†b¦ÿ˜2aø„‚QNB¢£­­ÍðáÃ9}ú47nÜ`õêÕ!rm ®X±‚W¯^±nÝ:þüóO¾ýö[zõê…±±q–Ǩ ë8uê ÷g…¢êEß¾}³L*ž3gŽR^GG‡Ï>ûŒsçÎq÷î]æÎËÅ‹?~¼Fç–žžÎàÁƒÙºu+³gÏΗљWžÍíÛ·ç{®nݺaooO\\\&£¯ Qû'OžÌ´/((x–¢U÷´"·sæåK†&(®Ouc…e<>ccceOvX[[Ó¿•F99qäÈbccså1—È’ý1SDccAƒûE®™pxO>?@MÜÒ7JãVÆþý ~q‰@2˜í&D~cßSs\#ÛopḚaÃÐÑÑaÉ’%¬[·Ž-ZdŠgΉÈÈHôõõ3• {·#ZFÎ;—©sÞ¡C‡Ô–^SP³fMLLLؾ}{®»9V©R…o¾ùOOOþþûoîß¿Ÿ­¼‚ádzqãF~üñG&Mš”«õ Š~ýúáääĪU«”%ÌÞ%==]å±}VïÍ£GˆŽŽVv',LêÖ­‹ŽŽŽòs͈¢ª†Âh«]»6lÚ´)ÛN ŠäìæÌoC¬P·æ¡C‡ÐÒÒÊ—ñ™]¾Íõë×IHHP‰ÏN^‹]©R%ÖŽ‰‰aìØ±hii1wî\ 5–È+RGQâ:¾zŠdµÿâÄ ’/`ÇؼŽ5OЀpÀ–¼\†ã÷ç÷s¿ƒØzÚRþlyžˆ'Ä›Äcd€ásCFôÁ„1ïŸ÷YêD˜3 Ö¦sçM@öeÒ´µÁÒ²KžÖ°°° E‹pRSsöR6kÖ'Ok4VVV¸»»³bÅ „üöÛo¹ž£V­ZìÝ»—¹sç2a ٵkWŽe±¾ùæ,X€žžGŽaëÖ­Ô©SGmýe†††Ìš5‹Ñ£G3xð`~ûí7åcÿçÏŸ³eËiݺ5çÎãܹsÊ–Ç`dd¤LLËŠqãÆ±víZ:wîLûöí3%ßÙÚÚªÔ&nÒ¤ —/_&>>^¥#›Ây,µL&SÎebb¢ =È mmm–-[F»víhÖ¬ÿûßÿèÖ­ÕªUãÑ£G„„„ðóÏ?£§§‡››‰‰‰8880zôhzôè©©©œ={–%K–˜˜ˆ——W¡‡©YYY1bÄ–,YÂܹsùúë¯ÑÖÖfëÖ­|8óçÏÇÔÔ{yŒŒ¨_¿>µkׯÃÃ;w²víZŒ‚U«VŒ‡‡G¾Â'²#::šß~ûñãÇ£¥¥Å† 8{ö,žžžT­Z5Ïó^»vÏ>ûŒ±cÇÒ¾}{lllxùò%'Ožä—_~PÉIpww§råÊ 4ˆÚµkcddÄÕ«Wñ÷÷çìÙ³TªT)Sø†Bò¤èDªU«HLLdêÔ©y꾚©¡c†Ù‰ðäÉ·u¸vïVÙ5~üÂÑq€¨YÓ;Û­Z5•U‰‰BìÜ)隷ÿn©¯ñžç8V±bÎÝ ÇŽS–¨«ñ{ óB^ãîÍ›7ââÅ‹"::ºà߯"D*”s»…¬ÊØeÇÑ£G ÌÍÍÅ›7o²•UWÆîÉ“'¢ZµjÊ2\fffÂÄÄDìÞ½[büøñJYE»ï¾ûN4mÚTèéé);ûÙÙÙ‰ëׯ«¬§®Œ]zzºøå—_„¾¾¾D… „¹¹¹²l›ŸŸŸBˆ€€eÉ5sssaaa!Q©R%¥Lv(J eµ-X Zа^½zÈô6iÒ$Ë9Zµj•£ nݺ%š5k¦}ºDdd¤Êxdd¤ÄôéÓ•cŠ2vË—/666¢téÒÂÌÌLÂÅÅ%SIDKKKÑ¡C‡œÞJ%¢J•*ÊÏK___hkk+?»aÆ)Ë !ÿ/Uª”R^ñ¢V­ZââÅ‹*ó+®Ëw7###Ñ£GÊ#J É]D”¸€üŒ „ïTàˆ‰yFhèÞ­Žñ.+~Ijj:ÇŽÉ=ÍÛ·C\œªŒ•ôí+o€rìØ»3d߉PãDZøübj˜×àØ cT4–ÇXêëëÓ Xª‰,R¡„‚qãÆáîî®¶Ë[V(¦x{{ç˜<èääD`` J> ®_¿ÎŽ;ˆŒŒ¤V­Z¸¸¸P¶lYUbR+W®L`` L:•“'OŒ½½=mÛ¶U–S0}útÆŽ«2&“ɘ2e ^^^üý÷ß„††b``€ nnnÊJ]ºtáîÝ»œ8q‚˜˜ŒŒŒh×®ÆOkþüóO•ïòn¨Ë²eËxñ⺺º*ã .$îÝÝä&ŒÂÞÞžS§NÊÕ«W ÃÊÊ š7o®ô(+*‘DFFröìYbbbHNN¦råÊ4jÔ(“ÇÛÜÜœÀÀ@µ]ðÞÅÎÎŽÀÀ@µa>#Gޤcǎʦ= ÷°ïܹ“Ó§O«t"tuuÍTPGG‡_ý•Áƒsþüy"##)[¶,ÿ÷ÿ§–aiiÉáÇ RéDتU«LfþüóOµe ?ûì3š6mšéœ--- TIDU`kkËåË— âæÍ›Ô«W77·LŸ·O¦ë8;ªU«ÆÝ»w¹~ý:ÁÁÁÄÄÄ ¥¥EåÊ•qqqÉÓ½bÅ ~ýõWNœ8Add$Ïž=ÃÚÚ{{{Z·né=P\— ÿÛ±°°ÀÆÆ&O7²£ÄÙ,%™%¥Òé‡Ë Aƒ€fyyÁ_É“æ}ìƒ7žAŠ9“(^J”ÍRB‘<ÐEÄ©S§4hþþþÅ­ÊÛøçŠ5oûWÄŒÙ7æ£4ž%$$$$$Š E·ºNªHè"¢yóæ%dz¨ð@—Ð’ocöaé…¥ÀÛ° ëÒ%£Õ¸„„„„„ćл»;îîîÊ*Y#y ‹ˆ“D˜P` „ù%<<fcïvÊâYŽ4Ô!„ ,,Œ±ãÇ2ú‹Ñ¸¶vÅÄÄ„ÑûF³ìÂ2Ê:plбÎxya‰yZ!!!!!ñÑñèÑ#):$ºˆ(1bÆ„Yx øa ?üß}7n€LgÎ@…A>wჾÄS‹§ü[þ_No:ùsLÊšp£Ö Ðþ¸g’%$$$$Š—c³”`$úcCÿ\©Rf—ò;ìØ!ÿÙ¬YÁÏÇN£ß´~nãYBBBBBB¢ä#Ð tñÏW¯¾ •þôÓü/ûêÕ+FMÅ£V@]ù»J • …ÇÉx–(ÑHtQ"òãã!<\þ:Zá}y—Áüräè"­"Uçg€ÊTÖôšé `æ·3ó¿à{ŒÔ‰PBAdd¤Jþ„®®.666”-[6Oó…„„œœ¬‘¬““†††ÙÊܽ{—¾}û2zôh¼¼¼rœsÒ¤I„„„pèÐ!t(h^¾|É©S§¸s熆†ÔªU‹Æçi®ØØX¢££ÑÕÕ¥~ýú9<àêÕ«„……aii‰½½=ÎÎÎjeo߾ͥK—xðàæææT­Z•Æ«tŒKII!88sssìíís<‡ž={jüùå—´´4ˆ§bÅŠT®\9×sܽ{—‹/ƒ©©)6664iÒDíµš””ÄÉ“'¹sç)))X[[S¿~}ªV­šiÎØØXåï&&&ØØØ`dd”û“ü@:j€(t¼½½…««kq«!ÄÑ£BÈS… ÌV´N¹Xƒ³ô艣cÌȰ5E0Yu̶µ­HNN.˜EßS¼½½‹[…bÇÏÏO˜ššj,ûömñ—Ï_—¿¸ÿ~¡ètÿþ}±k÷.±qóF&ÒÓÓ eŒŒ1B™6;;;±mÛ¶\Ïgii©v>uÛ¥K—rœïæÍ›³fÍÒhý=zäês-HÆ/ôõõ3g“&MDXX˜Fs¼~ýZtéÒEXYY)·±±É“>÷îÝ:tPûÞ;;;‹={ö¨¬ëéé)d2™”?¡¯¯/3ÜÏccc zôè‘£‘‘‘¹úüòÊÑ£GÅÿýßÿ CCC¥Þ“&MÊÕ©©©bäÈ‘¢T©R™Þ±aÃùýû÷‹*Uª(e2Ê7mÚTEvüøñj? Ñ¡Cqúôé|¿% ooïÿϸººJÿ‹r@ò@%" _ÄaapíšüuA„oľŒåFÜÌ¡j:©j¡…v~[¾çHI„šAÿÑý¹«}—§ež¢¦ÅR >)û ë¯Ï³§6#qqq 7ˆó±çyfñŒ­,þ²À&͆M‹7Q£F8“ìY½z5Õ«WçÅ‹ìÙ³‡­[·Ò§OŽ9‚«««Æóøøø¨< »rå “&M¢OŸ> 帯¯/¦¦¦¬Zµ SSSŒŒŒpssÃÍÍ-×:4Ïž=ãÕ«WY†d|þùç|þùçåÉ€öõõEGG‡•+Wªw-Z´ E‹*²$$$ðý÷ß3bÄ帽½=öööŒ=ZíÿÎ|ûí· 0€Í›7ÓªU+åyHHH–ÊDŽ­aüsݺ`föß­¾|þõçÌœ;“³gÏf{ì™{g0«yVxn÷dø~ÒDØ‚Á=È&¼ìÕ²|7ú»\œÄÇLŸÏû¨ÏeaõÂ1aDæ¹`ä„‘Ü®{QVdÞiZ? ß¨~¡f!âéé @Ø™¾íÛ·§Fj=‘GŽÁÌÌŒuëÖi4wjj*ýúõ£lÙ²X[[coo¡¡!Ý»w'&&&ËãfÏžM¹rå¨Zµ*¦¦¦Œ7Ž””Ö¼zõ*-Z´ÀØØ˜êÕ«cllŒ——Ïž=S‘»xñ" ÀÚÚšêÕ«S¡BÊ”)ÃÌ™9çMd4ž3Ò­[7•ØÂdÛ¶mœ?oooã9#:::ôèÑ'`ÇÅÅQ¡B¥ñ\˜4nÜ333µÛ»ñâ»wï¦jÕª”-[–*Uª`nnÎÌ™3U¼ÁELL eÊ”¡‚%¡ש““S¾×577gÍš5˜˜˜ðÍ7ß”Œ|&‰ä."Šý..""䯳 ߈ŠzkgW¶[B£žsyPã©åR!~;óµ“k³eõå,:>šW6òç?rûÙm•ùÌ ÌéW§ÞÎÞ˜ô7¡ã ŽD5ST’Í®›Ñ±\Gzu/€ŒÅ÷)‰0g¢££¹§§ÞxVn™Îá‡ñ¹êƒL&ËÓ:‡®"­]ZÖ†ðÀäT¯^=Okä…ÊÿWв_¿~ 2„Ý»wÓë¬ßåË—“’’’i<+ÒÒÒgÚ´iÔªU‹äädðññ¡K—.„„„d ³Z¿~=B–,YBùòåñññañâÅÄÅűaÆl×;uênnnXZZòõ×_ÓªU+¶lÙ‚¯¯/×®]#88---’““éÖ­ZZZL:'''’’’¸rå /_¾Ôô­ËÄ™3gò•˜þþûo  ‘¼‘‘vvvܼy“_~ù…áÇS.›'ˆùeÆŒ¼xñBeìÆüüóÏ*Iu>>>ôïߟ 0þ|ŒY·nS§N%&&†+V¨^õêÕãôéÓLž<™¯¾úŠŠ+f)«ð"Ïš5‹òåËÓªU«<ß@þ´¢sçÎøúúFݺuó<×û‚”D˜3’­!©©©,_¾œ'N`dd„»»;=zôÐø²Ø;fì@˜zçÎÿ^è­æDÒ÷¼lŸáFZž[>çTü)Úôiä…“øëÆ_‹<†à­®”V):UïÄ çAtµïŠ®öÛG¿gwžeÐøAܺt‹GÑ0³5Ã$Å„ C'0Ü{xAžñ{‹Ô‰0gNÿ}š§fOs”{¬ÿ˜þ«ûC^B¡ãQÖ+Ïv ÓÇœ9¦È èàà`æÌ™¼ Éðôôd„ ¬ä#K IDATZµJÅP~øð!»wïÆËËKc丹ž.\PëÚµ+*T`úôéìÙ³www•ý·nÝ"** Ú´iÃýû÷ù믿˜8q"uêÔQ»Vzz:cÆŒAKK‹àà`¥aØ©S'œœœ˜0a[¶l¡_¿~„††òðáCæÎ«ò¾mÛ¶—:¶mÛF`` ÞÞÞJÝ ›ÐÐPlmm5>fÙ²e 8ï¿ÿžéÓ§ãââB‡øüóÏ $Î?#;wVùýñãÇL™2… *°qãF@n\Mš4 CCC‚‚‚”aRíÚµãþýû¬^½š1cÆdù¹ç… àááÁüùóY°`Mš4¡mÛ¶Œ5 kkÕÒ§ýúõSæ ¸ººR©R%Z¶lÉ Aƒh×®]žÖWäܼyó£0 ¥N„9#…phHbb"QQQL˜0Aƒñã?ªÄ¨åD±_ˆ;fã–‡o<¥T…™¼lòB½)ܪu‹!‡p4ò¨Òxv¶rfA‡Üÿú>»ûí¦WÍ^*Æ3€••û·ìçö¡Û\Ø}˾— =*ÏŒçœ)mXí4 ’MSÉ»›  AB©ôR©((zö쉣£#*T aÆœ:uŠvíÚ1qâD ðööæÐ¡CDEE)[»v-)))*q šòæÍ®^½J`` þþþXXXoÃF2R£FLhÇŽBpðàÁ,×çòåËôïß?“Wµ_¿~hiiqúôi033cÛ¶mJ|~¸|ù2Æ £J•*üöÛoùžOSÎsssiß¾=wïÞeýúõ8;;sòäI¾ûî;ªT©Â_|¡—]¼yó†îÝ»óäÉöìÙC¥J•ù¦{÷îѲeËL9;w&==½ÀK6jÔˆ;wî°mÛ6\\\8þd§òñaÁ „ÀËË‹ .àïïÏ'Ÿ|¢Ü)ÿ›PçÍm×®“'O&\Ñs ÑÖÖÆÃÃ^¿~ÍöíÛùý÷ßÙ°aáááÊ/[ jÔ¨ÁâÅ‹ùý÷ß fÅŠlÚ´‰þýûS½zudãLzÅÓ¢ Õ’(ÙHtñóó£gϞŭ†æ(<ÐÙÜ0üüþ‹ò0BXg÷ùåÍÊsgÌ õ³o¶ !QÐXXXР\öþ»W}‚`pǯN^8Zä=ž|`—̹:‡7öoÔ ü µ jgYüôÓOY6×Pààà@›6mX»v-3fÌàðáÃDEE±lÙ²\­åããÃÔ©S• 6jÖ¬I… ”qÞêÄòšD©˜ËÝÝ=S%VVVÊ×_}õƒfýúõìÚµ‹Ù³gó믿2kÖ,¾ýö[ÖŒˆˆ mÛ¶Èd2Ž9Rä‘¢bKXXXž&&&Œ9www*W®Ìž={HIIAGGƒ˜# ™4i;vì`áÂ…ÊDË’„¡¡!ÄÃÃkkkΜ9Clll¦p™LFÆ ùã?(S¦ óæÍcçι2 ¯_¿@Íš5 ì$Þo>èŽÔÔTs” Í”ä÷úõk‚‚‚ RƬ)øñÇÑÑÑaøpÍÊ5‰ðùs¸sGþ:›øçíÛå?Ë”¶ rrLK™æËx/ñõK‹ƒw¯7 õl\¶‘ÚWk£õ ómÌ Â€†ÿ6äÇÉ?æki§Ñøec ndÚ§õP ‡`6¯Üœ¯5 ’Q£FK@@+W®ÄÈÈHãd5èèè°hÑ"ÜÜܔɻwïÎòuuuð³{¬0FLLL”ÞÕw·:¨cffÆ—_~ɱcǸ{÷.ŽŽŽÌŸ?_£{ITT®®®$%%qøðáb1†ºvíŠL&cÞ¼yùžËÊÊ {{{RSSIHH(íä¬ZµŠ¹sç2fÌÆŸiµjÕÔ†i(ÆŠ¢>:È é†ÿý_{þüyŽòŠ/jOŸæœC¡ ((ˆ#GŽP»ví&´¡Ø ¼|pôË—/¸+tuÞfdؾCX¹Y‰é¿N/:ý%>:üüü„©©iq«QìLœ8QXZZŠk×®i|Ldd¤Dýúõs”=}ú´°´´3gÎTŽ¥§§‹)S¦}}}SSSѧOqÿþ}aii)¦Nª”½}û¶°´´‹/óçÏ*T€(]º´;v¬HNNVYoРAÂÞÞ>“¢k×®¢téÒPÎÑ«W/qõêU!„W®\®®®BOOOB[[[4iÒDüðÃ"666Çsmذ¡°´´Ìr[µj•м›››°´´‰‰‰*ã;wÎrŽž={æ¨GÆ÷yéÒ¥¢Q£FÂÐÐPBKKKT­ZUüôÓOâßÿB‘––&fÏž-Z¶l©ê1???abb"$rŸŸŸÄŠ+ò5Ojjª¸wï^&c('bccÕ:!4!==]Ü»wO<}ú4[½bccÅóçÏó´FI$--MDGGçh¨¥§§‹G‰{÷î‰ÔÔÔ"Ò.gÄãÇ‹l½'Ožˆèèh‘’’’£ì›7oDdd¤ˆ/ÍÞÆqÆû®ŸŸŸðóó“'Oþù§puu• èøàB84!66!D¦(EL܃Ôffg|Œ—[é-¯€G•+Sí¿¢ì…NBÜ»'EüóîÝoÃ7>ýn?»Íü³óA {4æÜ¸s¤§§£¥¥…¿¿?õ &ì$<<\müßÇŽ”D(‘WÒÒÒøá‡°··gðàÁùšK[[;×åá@µjFn‘Éd9®©­­¯5J"ZZZT®\9G9™L¦ì>Y’(]ºt¦zÐ…InòôõõsÕ´FBŽºœ‰w(n ¾0ÉÊ"±cÇ•ñk×® @ìܹ³@õðöö®ÿ…oL/WN„\¼X ógËÊÐqø°Z‘îÝå»Ë•"5UˆNuÌ@hÿ¨-.Å^*4ÕÆÿAy‘ é[¿‘[ž={&:tè ¬­­ öíÛWÜ*IHH”P4 Ï<Ð9óQz ¯Þ͈ˆPÙ_Tw©Ÿ‹Ú“ù&c5ë¾| ŠFaîî°7|7áŒl8g«ìkÏæ)‰P=R¡DnÑÒÒÂÌÌ OOO>ýôS\\\Š[% ‰÷ÔÔTRSS ½¶ý‡ÀGi@[YY¡¥¥Å7TƯ^½  ¬{ZœºÊd ìÕ«ÀçÎE;;PÓõjï^P”ÊîÖ3‘ñû¿ÀÂЂŸÛLf·„„DábjjН¯oq«!!!ñž³ÿ~8þ<Íš5+nuJ4¥]ªT)ÜÜÜ”³‚Ë—/coo¯,_4t„`\QzŸá­:‹øç;ä?ÍÌà‚Þ¯DÆÉ[´Îr›Eý2E¡¡„„„„„„D  k×®tíÚ•Aƒ·*%ž®‘ È Áûûû+=Ìþþþøûû+{ÙŒ?žàà`fϞ̓X·n;wîTÛu© ¸| ðÇ…2¿Zž>…»wå¯ÕÐoÞÀ¾}ò×mzE1ïì¯4ªÐˆ¡Ÿ -tõ¤N„ê‘’%$$$$Š©aÎ|ô÷ßÏÈ‘#9xð –––Œ9’‘#GrâÄ ¥L—.]X³f ¿ÿþ;+VäÛo¿åÇä‹/¾(î—*Å-àÚÖ­PÕx¾÷ï‡W¯ä¯8}Å›Ô7ȱ´ËRd~“©¡z¤N„ÅÁµk×ðõõÍô„^"3dÇ… 4’Htn)lãÀ±I:V«Fu(º0…ºzu05UÙ•œ €v2¥º Œ~f¹Í*Ý$$$$$$$JÕ«W§cÇŽR è¢dèqÅÁÁðÏ?…¿žÂ­&þùðáÿ"Iš- A7 €ŸÛüŒ…¡æê%$$$$$$$>F$ºˆxòä ¡Í›óT[[>°fMá.øøñÛ„jâŸwìLîCKy©ºz–õÙ°ÂJ2 %ªGJ"”(ž>}Jhh(Ož<)nUJ<’]Dܺu ÿ3gmÒD>°iÓÛÌ…AÆÂw<Щ©°kÐ~èʳ—t^‚¶L»ðôQƒ”D¨)‰Pâ}!99™ÐÐPž={¦‘üýû÷¹}ûv!k%¡))))¹úü$>|BCCñ÷÷çÖ­[Å­J‰çƒL",‰4oޜɓ'CÍšò–ÏŸËÝÀ΂ ZMáñãð¯qÔÙ€—“Í«4/=²AJ"T”D(¡`óæÍ*Õƒtuu±±±¡I“&4ožû¿ÙI“&¯a ~øJ•*e+sçÎj֬ɬY³ä÷·=z4AAAÄÅÅi¤CAòìÙ38zô(wîÜAOO{{{FE:u4žçéÓ§Btt4æææüïÿË“Nlݺ•7npçÎ,--±··gàÀÔªUKE6..Ž¥K—Ìýû÷155¥J•*´lÙ’nݺQ¦Œ¼nÿ‹/˜8q"ÎÎÎŒÌ!Yýþýû¹úüòŠ‚ððp‚ƒƒ¹téñññtìØwwwç8zô([·nUþ®­­MÅŠ©S§]ºtA[;kйsçX·nåÊ•ãçŸÕ7»|ù2Ë—/§OŸ>´iÓwå3fŒÊšÖÖÖØØØ`kkKÓ¦MÑÑÑÉr݈ˆÖ®]˹sç(W®­ZµbøðáÙêZÜ4oÞœæÍ›KOB5@2 ‹š.]ÀÊ >”'–­ˆ¶·cc•]Ûv¤BgùMÁXׄ9íæŽ…„ßάŸ62zzÙÊÅ&'3ßÇ'W’‚ÐÐPÆõéC…lþAÄ%%á9e ž…ð·|âÄ V®\‰µµ5ººº¼|ù’ÿý€nݺ±sçNJ•Òü6ÈãÇ•¿'''óüùsŒŒŒ(]º´Šìرcs4 ß'ºtéÂßÿM¹råprrâîÝ»>|˜+V°zõj† ’ãñññ”+WNeÌÆÆ&×´‚¥K—2iÒ$’““qttÄÁÁ»wï²wï^æÌ™Ã—_~Éo¿ýÀÍ›7qqqáùóç”-[9pàkÖ¬ÁÇÇOOO^¿~ÍÊ•+éÑ£GŽtQ±jÕªLº˜™™åÊ€¾rå +W®ÄÂÂ###’““‰ F9r„Ê•+«=vÞ¼ylß¾€~ýúeúrÉÊ•+qttT1 W®\‰žžVVVüûï¿Ê'§¶¶¶Ì˜1ƒÏ>û,“QBÇŽIIIÁÕÕ•¨¨(|}}9xð >>>èåpï’x…Ž···ðöö~;0i’ „L&Dxxá,Z©’|þýU†ÓÒ„0n·@0Á Äü3ó g} ‰|àçç'LMM³ÜŸ <«U“_ãYl‰ >uvΗ}6¯²YC€`k+ž?ž¯u²bĈ—.]B‘žž.N:%š6m*1kÖ¬|ÍìØ1ˆI“&åéø›7oæJ=zdû¹&S¦LP;sæŒÐÓÓúúúâÉ“'9ÎñêÕ+ñÍ7ßqëÖ-aii)lllr­ËâÅ‹ 7n,ÂÂÂTö½yóFüøã¢C‡Ê1ˆõë׋ôôtåxzzº8~ü¸¸víšr,66V¢G9êY ×QN;vLüïÿû÷ïÛ¶mËÓ5·`ÁåXhh¨ùD˜™™‰ÐÐPåØŒ3 V­Z•›S/p2Ù$y”ùØ‘<ÐEDXX¾¾¾Ô©S‡:C‡Â¯¿Êÿý®Y¿üR°‹=z11ò×ïÄ?="¡át*èÔb\“q»v.ÇÖÖ6W´ÐÐP‹[MéÒ¥qöðàÔo¿Ñ<‹DÔÕff ûé§|­3jÖ,–õíË7YĈ^ÔÒ¢F×®˜™™åkM‘Éd¸¸¸0iÒ$zöìÉáÇ™|{{{µž7u$$$pôèQnß¾Íëׯ±±±¡M›6Yzø^½zÅñãǹ|ù2vvv´k×NV  aaaüý÷ßDGGS½zuÚ´iCùòå3É%%%qæÌNŸ>žžU«VÅÕÕ•²eËf;ÿ/jî³Íš5£oß¾lذóçÏÓ¹sçlç044dîܹŸ“:žcccÜÜÜTÆÎ;Gpp0¯^½ÂÉÉ WWWtuuUdZ·n­¼6ƒ‚‚ LWfÍšÅÆ9}ú´Z™¿þú‹ääd¦NJZZýõ³gÏÎ×ÿòåËÓ·o_zôèAݺuY°`ÿÏÞ™‡Çt½ü“Ub‹l’Ø‘ – ö]¨¥ÖÒ(¡öŸjt£Uªh+TK¿¥öØ5b«½vjBH‹HD‚l²'ç÷GÌ4c&ÉLÈù<Ï}’9÷=ç¼÷νç¾sîû¾www k-ŠË—/3|øpêÔ©£¬7yòdf͚ŲeË=zô+÷_”Hpp0µk×Öµ:Åi¹h‰ÄÄD*T¨€‰‰IV^æ¶máÄ X³fφÂô‰R¸o€šýÕ‘)P*€_{,ÂP_w—ÀâÅ‹ùöÛoµf|¼)øøøH?è|0aútFoßNë»wÕö¥Çø¨W¯×ê£}çÎ,­QƒÄ§O)­aÿÂjÕXœƒOeQÒ¬Y3"##,_éÍ›7†ŠìÚµkùè£8tèP¾ è””,--IOOÇÖÖ–ôôt?~L™2eX¾|9žžžjubcci×®·nÝ‚°°0ìííÙ¹s'®®®¹ö—žžÎôéÓùñÇÉÈÈÀÆÆ†¨¨(,,,Xµjï¾û®RÖÇLJY³f‘””DõêÕILL$**ŠÁƒ³iÓ¦] XH%11Qת{d-ѰaCºu놣£cVÁ¨QY#"`ïÞÂíL@¨¯)‹Ï„á¦Ñ:*>H׎…ÛoY¸p¡4ž5 çü¡œ…Ö0“´ÂÐQúúЭÛkoã%ü µ=ûœE0•b†hüøñ¤¥¥±zõj5Ùß~ûš5kªÍæ„ ,àÉ“'<|ø¨¨(Ξ=K:u7nœÒhÏÎüùóiÓ¦ ±±±„††ròäIbccyÿý÷ÉÌÌ̵¿ àããøqã "22’£GâààÀ°aÃˆŽŽ²‚÷¦NŠ»»;OŸ>åîÝ»DFFòðáCÆŽ›¯c{™¤¤$¶oߎ™™nòå Talå…¡¡!ÄÆÆÒ²eK.\HHHH‘éwõêU’““U¶ùóç¨ß|ó Û¶mcΜ9<{öŒˆˆvïÞÍ;wðôôDQd:fgïÞ½%’B$/hš|¡“AôÏÃo¹ Û{ æ ]”¾Ï >Л7oW®\ÇŽÞÞÞÂÜÜ\bÿþýJYWWWQ£F Ù3gÎ@øøøhl¿ >Ð[¶l€Xµj•²Lámll,UäÇŒ#±cÇeÙË>Ðñññ¢\¹rÂÁÁA¤§§«Ô÷÷÷WÑ]Ñ×ܹsóÔ5¿(ÎïŠ+^©þ«ø@+ü™###ó]'""BŒ?^”-[VuêÔË—/ÉÉÉj²¢ôž={„èÛ·¯ÈÈÈBü•1 IDATçW\µjU5ù¡C‡ @ìÚµKc{¯êw¯ð~ÿý÷Åœ9sÄÌ™3Eûöí…¡¡¡ptt—.]R«óÿ÷·oßV–½ÿþûÂÐÐPíüÄúeÌÌÌ„¹¹¹òs¯^½ BCCÕd§N*±wïÞüz¡#}  é¡+LMÁÓ–.…?ÿÌš‰.¬µç3ÐÙPùíÒoÜŽ»€Þ©¯±å퉮—”\”³Ð?ýDëŒ V0ªfM(€ÿm^ŒeÉ¿ÿòù ë‹zzZ}VdXPP¥J~úé'<<<”eãÆcüøñ9r„Î;Y³ÏFFFŒ1¢@ý¥¥¥ñÇpáÂû¬PtÊÌÌÌ5äíDÐZ"%%E½pÔ¨,:==Ëú«¯^¿£ˆxø0ëÿ¯$Ÿ$=aÚ_ӲʞԢ•Þg¼ÈÈ£Sd¡fdaÁÈî «_Ÿ®\)Ô>ÚK›6%ñâEJ ííµêû¼bÅ •y +Uª¤öºúƒ>`òäÉ,_¾œÎ;óìÙ3¶nÝJß¾}5ãåDll,îîîܺu‹*UªP·n]”«†ÆÅÅ©ÕÑäŽÐ²eK*Æ# (ŒñuëÖ±nÝ:2aŠUÍ›7ãããòeËX´hÕªUcüøñŒ=º@þ¤‹-â‹/¾`àÀ¬Zµ*ßõ '''NŸ>MHHˆš¿z^”/_ž &0až>}ʼyó˜;w.£G.ôjÂÃÃéÙ³'–––ìÞ½[Å÷Zñ´jÕJ­ž¢¬¨ÜL)ûbbb8yò$cÆŒ¡mÛ¶ààà ”óóó#&&†Úµkãïï¯,ÏÈÈÀÄÄ„U«VŠýøñcâãã©_¿¾²L‘þ100P-ð600PE¦¸’’’"SíåôÖÇŽcøðá*72ÿ磼jUÖ‹á×%û „/f §™ÊÓ¤Yöÿ€¾Æ*j¹¡fäJ„#»/tadÞȉñsæ°ÄÂB'¾Ïnnn´oßž–-[R¹re5ã²ÎÃ|€¿¿?=býúõ$%%1f̘õõÓO?qëÖ-–/_NXXdùòå ><Ç:š]¸yó&9ÖSäT^¼x1BÛÑ£G•òõêÕÃ××—°fÍêÔ©Ã×_] äß~û oooz÷î͆ ´¾¨…"3§¬ùÅ‚ٳgcccÿÿþ›ïròCBB={ö$>>ž½{÷ªúŠïMñgGQVÐ¥B… ôêÕ‹õë×§–gZñÃhýúõôíÛW¹ 0€äädnÞ¼Éùóç_[ǨdÛÈn@¿Lq7 ýýý>|x¡fKy[‘´–ððð`Íš5ê¯2GŽÌúûï¿YK¾.Š //>¼Èï—Ï*»Ý½»Ó¯ßëwSÈ BÍÈ Â‚3aútV©Â1ºÁkcÈÊÈq¡F æV©‚·2oä‡ìÁ„¿ýö›2\AÀÄÄ///•r??¿ë?~œŒ.4 :@Íš5s¬çâ₞žž2kA~©X±"^^^|8–––øøøä˜. ***϶çÜÐаÐf 3224hlß¾]-“  †W|ÇÙQ”ië-ZçÎ1bàÀ@Öì÷_ýE÷îÝ RÛ:¾îÛ‡øøx¾üòKŒŒŒøøãÿRÂvïÞ@é~¤ 11‘{÷îѬY³\Xê’>}ú°fÍ÷0‰f¤­k† Éò‡†¬• _Å ´“¢Li>úó#2E&z&°!M›B.©\%’7’²eËÒôý÷]ĆíÿùøP¯_¿bûï~ýú´jÕŠ~ø›7o2f̳չakkKrr2'OžT–Ý¿_eå—‰eãÆÊÏ=bÛ¶mXZZ2pàÀëYYY1nÜ8víÚÅŠ+ÔöߺuK9»}÷î]5ƒDÁýû÷IOOÏÓ^¿~=£G¦S§Nøùù©å*~™O>ù„>}ú–––«\AQ,ýýøñcÚ¶m«6‹›À”)SòbeËÔÔT† ¢6[ªÈŽ…››[VŠÔBÀÛÛ›?ÿü“%K–hôq¨V­½{÷æúõë*oÂÂÂðóóÃÎÎŽŠ>ùaêÔ©0sæL kBÁÈ‘#qrrRÛ:wîL£Fؼy3IIIê+--Ö¬Yƒ““¡¡¡|õÕW*Y+5j„»»;~~~\»vMY>sæL„Œ?¾p\¢[t¼X¢È3šõƒ²"ûML„xݨ~[Û¬¶† +/¯T®8Hû„(Äv‰¤ÈÈoŽì¤¤¤‘6ºéGõ•󃯯¯23Æ£Gr•Õ”áêÕ«ÂÈÈH‰nݺ‰ˆ *(³x{{+e™1<==…µµµðððC‡¶¶¶ÂØØXlܸQ¥?M+ÆÅÅ ˆzõê OOOѯ_?Ñ AˆÕ«W !þ˒ФIáéé)<==…½½½Ä'Ÿ|’çyqpp€022¥J•RÛ-Z¤"ïêê*‘””¤RÞ¶m[e^dÄP|îÒ¥Kžz(X±b…(W®œÐ××ŽŽŽ¢G¢^½zÂÀÀ@ˆ/¿üR!Drr²²[[[Ñ®];¥€°¶¶VÉ2ñ:Y8bbb LMME»víÔ¶¡C‡*ëÞ¹sGÔªUK‰.]ºˆ~ýú sssQ®\9µ !!!Êsddd$a``ã¹×„¦•³3lØ0ˆ{{{Q¡Bµ %ÙùñÇ |}}…¹gá000fffÂÌÌLèëë+¿úõë í߸qCT®\Y˜šš Q¯^=ˆ>ø@¤¥¥åy¼E‰ÌÂQ8Èè--¡1ˆPÁÈ‘àë ÉɰaL˜ðj<|/r´&¹ºðÕ‘¬ Ds=žž@ÿþ¯ÖtQ ƒ5#ƒ_¼fß´~ºuëF…  äOÚ¡Cúöí«ôU͉jÕª1eÊ•  4àòåËüòË/Ü»wkkkV¯^M·nÝÈÈÈ uëÖJYKKK¦L™B÷îÝ™:u*k×®åêÕ«ôíÛ—aÆ©öïߟFÙrÓCÖêvû÷ïç?þàèѣܺu SSSÚ´iÃwß}§œmÛ¶-¿ÿþ;gÏžåÁƒ”.]šÏ?ÿœN:Q·nÝ<Ï˸qãxöìYŽû_ÖëÃ?äáÇjãÓ AƒhÑ¢…Æ6jÔ¨‘§ FE×®]ùã?¸yó&wïÞ¥Aƒ 8¡C‡R½zuJ•*EHHþù'çÎ#""‚ŒŒ Þÿ}š6mŠ———Ê‚,eË–eÊ”)]/^ÆÌÌŒ)S¦(§T©RL™2%GùìÁ¨5jÔàÊ•+,^¼X¹áرc=z´Úy(_¾<“&MʱݗϽ&š6mÊ”)Sr\ hÆŒØÙÙq÷î]Œ‹‹K®n-C† !::Zé_»vm¦L™BÓ¦M•2†††*çÃÀÀ;;;ìííqppÀÅÅE™™äeœ9sæ k×®åܹs¸¹¹ñÙgŸáååUà·Bº@æžZÊv^‚>|8¡¡¡üõ×_š„€Úµ³ü 6„WÍ °k¼Ht¿à§|¿€çwp÷Ï>¸ºB@À«5]Lš4I®D¨áÇ—x?hE ‹b9cIþ™={6ß|ó §OŸVfÂH$Š€àÜž3;v¤Zµj%þY”ÒZKT«V-çzzÿ¨fÒ(/ê }f<Û@ÛJÜý3+p±8Í>ƒ "Ì 9`I Š‚˜˜<ȼyóxï½÷¤ñ,‘H^™\m h­ñ×_Ñ»wo,X@@@ªi‡¼¼@ñªðUƒ _dà±5%Þ0ccš?ýŸr·c:$‰‰ŠŠÂÜÜ,,,øñÇu­’D"yQ¼ý;uê”®U)öHZKDEÙrêTGfÏÖ£cÇc´i³H%z;;xç¬ÿ7m‚ÄÄ‚wòbú¸õs>mñ)'vd­ÀT·nÖ&‘HÞ>,,,Ø·oÜ¿_ÎI$’WB‘Æ.{¬ƒD3Ò€Ö©©5yöl’rKHЫV±@l,lß^°<€yC/U‚*å«0¢æ4™Š›ûd*V7“ü‡¦E)$’Ü066¦[·n¸ººêZ‰Dòkâ h-’wR|Þy'k& îÆ‘Íoúb%˜ßu>ûw•Q.nX h¹¡fäJ„‰D"Ñ%ùYȧ¤# h­‘Wª X.÷äIÎwëONd­À”®šµcË þø#k_YÉ=Š2ˆP32ˆP"‘H$ºDºå4 ‹#GfeåX¹2ßÕîÎZ%,ÈZù}~%* 1ÅqöY"‘H$‰äMEÐÅš5A±¨ÁÚµá?‚þ ú'$¹:ãbí‚¿?dffíïׯˆt•H$‰D")HZk„ù̬¡È »wç*š˜–Èü-Þ¼H¼Aƒ(Ý7ªTæÍ_A]- ƒ5#ƒ%‰D¢KdaÞHZ[˜ƒšu¡Ü@ >wÙþýAáüR0á¹ çè;¼/n=ÝpëéFã¾±;®ÜoâÞš§OA‘!¯_¿ÿ /¿a‰äeâĉ˜˜˜(·òåËS¿~}FEHHHÛsppPi/·íÚµky¶ÍÌ™3ßzóæÍtéÒSSSjÖ¬I•*U(_¾<_ý5‰ù\ù5--E‹1tèPœœœ055¥N:Ò£ çLo“=sæÌ|Ð3gÎ,r6nÜÈØ±ciܸ1eÊ”ÁÄÄDúK4"]8´Å˱rµã8R¾;ö¹x1L¨dÀâKÀ½{8”×ã ¾Ìö1`õbì¿d£Çƒ•ÿ’’R (ÞîDèàà€¡¡¼ ³sëÖ-œœœt­†¤––FJJ cÇŽÅÖÖ–ØØXüýýY¹r%Û¶mãúõëzÍ:zôhâãÿ‹Á eÓ¦M¸»»Ó®];YkkëB;ŽâÀÂ… ‰ˆˆÀÛÛgggBCCÙ²e ?üðwïÞeÓ¦My¶ñüùs>þøcJ—.««+÷ïß/°qeiiÉ”)ShѢūФ™:u*>ÄÅÅ…²eËòèÑ#¼©Ð5)))”*UJ×jk¤å¢-ž¼Š¯K?îÓ4øð‰à·‡ÿýÚ&üîµ°°€^UYí°xñb¾ýö[¹˜ÊKøøøH?h‰ ãÆ£á‹Õ,XÀ„ X²d ³fÍâ÷¬Zúõ×_«|>vì›6m¢]»v:w­(j~úé'š7o®òƒ}úôéT¯^Í›73cÆŒ<¸–-[–ÀÀ@œœœ000ÀÖÖ¶ÀzX[[¿õçúMfïÞ½8::RªT)ºuëÆt­’NˆŠŠ’>Ðy hmaöÒÇh+âÃæ‘yI›JiLù* }£4Ò2ÒHËüïoÐ% ¤ïC°H‚§/Å6‰Èú›f ,ytÙ€Þ½³b ‹32ˆP3ÒxÎ?á))¬ŽŒÔYÿCml°71Ñz¿ãÆcÉ’%\½zoooY±b…šì­[·øòË/>|8}úôɳm!«W¯fÏž=“””DÍš5éÝ»7ãÇÇÀÀ@c½£G²téR®^½JÍš55jýò™„þùóçøøøpúôiBCCqttä½÷ÞãÃ?T‘‹‰‰a÷îÝ>|˜Ó§OSªT)e_½{÷εV­Z©•àååÅwß}ÇÍ›7ó4  qqqÉ×1åDxx8&L`ذaÊóÌäÉ“?~<åË—ç×_åÂ… ØÚÚ2vìX<==ól733“3fÈ_|AË–-•mÏ;—k×®‘@½zõ˜]£ì«pîÜ9æÌ™“ãþ?þ˜Ž;*?ûúú²uëV‚‚‚¨\¹2m۶嫯¾RÓçu¿ß·i<çôÕúÑútqnÏÌ# `8QŽæü¯ÿ‡wso>où9_µþŠoÚ}Ãì³i<} ¥2ah€z[Šè@kH4#)© PüÝ7$’Â@éBèlÓÕ‹ÝÔÔTô_9T¬X‘ßÿ¿ÿþ[MvéÒ¥ìÞ½[9ƒ)))Œ?žŒŒ ºwïN=¸wï'NÌÑ;qâ À‚AƒFÿþýY°`AžýÝ¿WWW¾ûî;bccéÙ³'AAAŒ9’1cÆ(儼óÎ;Œ=šØØX† F¯^½HNNfË–-ù:6M„……P³fÍWn£ ÄÇdzsçN‚ƒƒ•eOŸ>eçά\¹’Î;sûömš7oÎ… 2d[·n͵ͤ¤$ú÷ïÏœ9sèÑ£‡ÒxÞµk5Â××;;;Z¶lɾ}ûhÕª;vìPic×®]ìØ±ƒvíÚqûömÚ´iC58xð »wï¦cÇŽøúúÒ¸qcž>}Êüùó2dˆJ;™™™ôîÝ›Q£F€‡‡éééÌ™3‡æÍ›“””T§###*T¨ ¶±sçN>üïõì„ :t(=bÀ€ØØØðý÷ßÓ´iSbb4ÇI$yQÌç(ß>ôëãt͉U{WQ¦ œ< ÂæÍж-ŒÿR…6m vmfìiC~i¡êL­ 40¡®Í Âòå¡K­ŽD"Ñ2ééé,^¼€&/GŽÉÌ™3Y¾|9îîîJÙ¤¤$Ö­[G×®]qppÈWûÆÆÆ„……Q±bE•ò1cưbÅ >ùä•>öíÛÇèÚµ+_~ù%Í›7gúôé|ðÁ¹úSñÅܽ{—³gÏ*Ûýù矙0aË–-cĈ´hÑ‚þù‡³gÏ2yòdæÎ«ÒFrrr¾ŽíeÙºu+Íš5£~ýú¯ÔFa²}ûvNœ8¡Ì€0oÞ<ªT©ÂÏ?ÿÌ{ï½§±Î“'OèÕ«×®]cçÎôèÑ€„„Ƈ —.]ÂÜÜÈrjÑ¢'NÄÃÃҥK+Û:}ú4sçÎeòäÉjý\»vÏ>ûŒ~øccc222èÔ©;vìàÞ½{T¯ž•[uÍš5ìÞ½›™3g2}útô^,DàççGÿþý™7o3fÌxísÕ¸qcµ·ugÏžeË–-´mÛVy¾NŸ>Í’%KèׯÛ¶mSþè\¾|9cÇŽåûï¿çÇ|m}$%9­%Œ¢¨v¼}úð÷Þ¿)W®úúàë •+gÉ|ò \¾¬¡ò‹”vuÒé½Ã6kQà ¨þ$Ë­À¾ó@ÎÍt{ö„7Á÷_®D¨¹¡äe~üñG&MšÄˆ#°³³cÍš5XZZòÍ7ß`kkKß¾}Ù²e qqqÊz[·n%&&Fe&7/ôõõUŒçŒŒ ’““•î.\P«cmmM—l¿ÚK—.Mÿþýyþü9Û·oϱ¯‡²mÛ6ºté¢b”ëëë3nÜ8 +û€¹¹9†††DEE‘‘‘¡ÒŽÉ+¸ÑÄÅÅ1hÐ 233Y¹r¥Ò°Ò%-Z´PIfkkKãÆ Ò(÷î]Z¶lÉ;w8zô¨Òx†¬ï>""‚‰'*g€òåËãééIxx8/^TiOOO///}éééññÇclœ•GÕÀÀ@Ù_ö1káÂ…”+WŽI“&)gÈJV¹revîÜ™ßÓQ îܹCïÞ½©V­;vìPê¹råJ†ªò{zzbll̪U«ŠDŸ7™y$oä ´–h`Ó€{N¨üÚ°¶†M› cGHI÷ÞƒK—À,»Ï´—|ý5¤§³©^'~¬Y‹³WÎÒ&, ÈòëÐkèMü‹UÞ÷ D¨D(y™`hhˆ±±1NNN4oޜɓ'«ºãÇgëÖ­lذñ/^e-_¾;;;zõêU þN:Å÷ßÏ¥K—ˆŽŽVÉB pyÈN‡TŒ%€.]º0sæÌ\Óí)Übcc>|¸Ú~###¥qfmmÍÀY»v-ûöí£OŸ> 0€Î;«õ‰‰‰ôèуàà`¶oßN½zõ T¿¨ÐäÇݰaC.\¸ –!22’-ZP®\9Μ9£æ‚rãÆ ë»TøÊg¯ Y“mÛ¶U–7iÒº¹ººªùÅ*Þ€úœý›÷óu¬ýÆÆ¬»’õú±tièÖ­(Ž ðY¸p¡4ž5 gÉË>|˜ÈÈHBCC9yò$óçÏWs±hß¾=NNNÊ@ÂÀÀ@Μ9Ç~X T‘çΣS§N„……1vìX|}}9}ú4{öìþó¿ÎNÕªUÕÊßìió^&:: ÇtYžžž*éÞ6nÜÈáÇiÑ¢6l k×®8;;³oß¾|_rr2ï¾û.gΜaÍš5¼ûî»ù®[Ôh2⳦/§RÓ××ÇÐЄ„y¬K1—.]===•ÍÎÎŽáÇ«}o¹ùçG·gÏž‘ššª±O===Ú·oOÿBžáIMM¥o߾ܿŸ]»v©CLL *T |ùòjuíííTÞÚH²ÆsÞÈèbÂäÉYþÐ{÷‚Ÿüò x{g5 üý!>¶lɲ²/]@Ô«ßž¬×UÝ»gÑIIÀÊȈr˜1Ó6ÆÆy i‘qãÆ1iÒ$.^¼ÈºuëÐ××gÔ¨QjcõêÕ¤¦¦²jÕ*š5k¦,߸qcŽuNž<©VvìØ1@³q­ V­¬œõ;vÌ·_l§NèÔ©‰‰‰øûû3iÒ$&OžL÷îÝ󬛚šJÿþý9rä¿ÿþ»ZÜ›DÅŠñóó£S§N´oßžƒ*g„á¿sûùçŸãêꪬ¬¬¨P¡•*UbõêÕZésäÈ‘œ|77·ë999amm͆  ìsYºti<==éß¿?„††æ*Ÿ––ÆÀùóÏ?Y²d‰Zм—‰/öjÔ¨Á‰'°²²¢S§Nœ9sF¹¯M›6Z÷ñmÓ¦ gΜQº%3fÌÀ××—9sæädÙ¼ysR]I!((ˆÐÐP6l˜cjF‰$7Š×èÿsâÄ †® ˆÑ„¥%lÝ FF––åýìDDDð÷… üÝ¥ Ÿ9Ãß?ýÄßÏžñ7ð{¸9ð7FFãæ¡­Czmd¡fd¡äU©P¡ƒÆ××·ÀÁƒ 7nLrr2Ó¦M#""‚ÄÄD~ûí7匲&LMM5j‘‘‘¤¤¤°aönÝJóæÍU‚ _¦T©R,X°€þù‡¾}ûrãÆ 222HJJ" €/¿üRé:rìØ1&OžL`` )))$''súôi¶mÛ†……• 90bÄvíÚE÷îݱµµÅßß_e»s玊|›6m077WËðqâÄ e””åL¸¿¿ÿk-ÏýªT­Z•'NPµjUºvíÊÑ1Ré IDAT£G¬Yý°xñb¦OŸ®ô{~úô)äý÷ßÏÕ½æUùùçŸÑ××çÝwßåÀ$%%‘‘‘Á½{÷X¼xq¡-"³}ûvfÍšEÏž=4h!!!*[BB'N¤bÅŠÌ;—žžÎíÛ·ù"8ÿ;…ÿä ._¾¬ü>¾Ý»wïÆßߟ½{÷ŠîņΉ't­JñGHŠ///Ñ¡C‡|Ëÿô“µõê%Ä7Eó ÄÒ²eÅRÈÚôõ•ÿÿ¤WFÌ¥¼¨gTAܼy³¤pñööÏž=ÓµÅ///]« svìØ!ÌÌÌt­†Î;v¬Ä•+Wò]çÂ… vvv"---WÙ£G @L™2EY/ÜÝÝ ôõõ…¾¾¾°±±'Nœ€ðööVÊ @Ìœ9StïÞ]ÂÐÐP¢I“&"44T¥¿wß}Wã÷ºjÕ*aaa!add$ôôô ¬­­Å¡C‡„B:tH @èéé CCC¡¯¯/ÜÜÜıcÇò}žÃ–*³Ð€cz–˜˜œgÉ’5y¦r*N,^¼X)-ùŠR—”¬­­‰ŒŒäÊ•+Ìœ9SÍ÷X"‘H «Ú,% i@k‰×YÕgÈËÀÀË¿à þ[¦ô ,_ðå—L˜ð€Ë—/¿¾²ZB®D¨és&‘H$]"W"̹á@ÖÊ‚õ€öe8ôâÿ&€× Ip%‘H$‰DRÔHú $Œ…L¦#eI Œ7Ï÷Y"‘H$‰äMFºph‰ÂuÈwæ/°7àÍͼ!ƒ5#ƒ%‰D¢KdaÞHZK¶C~¿p‡¥…Ú¦¶‘A„š‘A„‰D"Ñ%2ˆ0o¤ ‡–(|‡üº…ÜžöY¸PºŸhBf‘˜˜¨Læ/‘H$’ÂáÔ©S´nÝ:WD˜7Ò€. .ääɓҵkW† ‚‰‰‰®Õ’HÞ:ªU«†§§§®ÕH$’·ŽÖ­[çi@KòFРjÕªL›6””¾ýö[âãã™4i’®Õ’HÞ:7n,gâ%‰DRl‘>Рÿþ4jÔwwwÌ™3gò]W:ä«#ƒ5#ƒÕIOOçßÿյŎèèh¢££u­F±CŽ-š‘c‹:rlÑŒ´YòFÐdãÆ|úé§øúú2uêÔ|×{]‡|cã¿05]“ëflü×kõ¡md¡fd¡: ,^¼X×j;N:Å©S§t­F±CŽ-š‘c‹:rlÑŒ "Ì›·Ú…#==ôôô\ý”¿>«W¯N©R¥”剉‰œ?[[[œœœ°°°ÀÊÊŠçÏŸsëÖ-6l˜§¯»¼vëÖ­9p ?+ö9P¯^½×êK"‘H$‰äum—··Î€NHH`âĉ\¼x‘   222Bh”]¸p!Ó¦Mãùóç”*UЉ'2oÞ<ôôôˆgóæÍ´hÑBi@wëÖnݺѩS'¼¼¼ lÚ´‰ÿýïüôÓOØØØ°lÙ2–-[†——™™™$''+ëáêêZäÇ“·nÝ*t¶‚¶™žžÎž={ µÍ×eÿþý*ß“.Úü÷ß ,Ô6_·yøðá<_«…ž9‘œœÌþýûuÞf^Ç\zæ†[4#ÇÍȱE9¶” Þ:ÚÊÊŠC‡1oÞ<š6mš£Ü¢E‹pwwgܸq”)S†¾}ûÒ¿~ýõW233ÕäŸ?Ž““:u¢Q£F¬Y³†¯¿þ:ßz¥¥¥½ÒñäÄÛð‹‰‰)ô@Ÿ·á![ ýòÛþKOO'&&¦@ýåÅÛð‹‹‹#..®@ýå…[4#ÇÍȱE·al)l›åmDOääßðàããÃW_}¥æÂ‘––F©R¥øðÃùý÷ßÕäÿùçÕÚKOO'$$„Š+R¾|ù|ëѱcGΞ=‹››FFF*ûlllT|¯CCCóUvùòeÌĮ̀Y³fëæT–––†‘‘‘2z^u3228þ<•+WÎQNˆP­Z5µövïÞM:u°··e_.;räÍš5£\¹r…v^ÂÃÃiÒ¤ ÆÆÆùªNjj*9Ê]½z•† bll¬ÖžŸŸnnn¯¥óËe }ûö…Ö^hh(!!!´mÛ6ßu/]º„³³3?ÎQîÂ… Êü¤Ùë&$$pôèQ\]] í¼¤¦¦ròäIZ·n]hçåîÝ»DEEÑ¢E‹|×U,j“œ¹¹9AAA4kÖL­îåË—¬‰ƒÂ:/¡¡¡DGGãââRh祠㕡¡!çÏŸ§E‹¯4^íÞ½›:ðôéÓB;/çÏŸÇÊÊŠÊ•+ël¼ŠŽŽ&55•êÕ«¿ÒxåççG¿~ý^ûÞÏ^vêÔ)ªU«¦Óñ*00gggLMM <^¥¥¥qôèQzõêUhç%55•€€lmmu:^å4ž*È>^)ü£££133ãÊ•+4kÖŒ¿þz³’h“i@‡††booÏÏ?ÿÌ'Ÿ|¢,ß»w/={öäøñã*7Úëòû￳qãFµr¹ÒD"‘H$]£)`ÐÓÓ“Q£Fé@›7ƒ·.ˆ0?ÿüsôôôˆŒŒÔµ*:'99”[JJŠ®Õ*\¾|™–-[R©R%œœœä³xçw”×I•*U000(ôø¨7‘ãÇÓ­[74h@¿~ý´š‹½Ø!ÞBÜÜ܄ڶvíZ¹U«V ;;;KKKñÝwßiUÏ>ø@üðÃ"))IŒ=ZLŸ>]«ýWΟ?/D:ut­J±áèÑ£"22R!Ä‘#GD5DzzºŽµÒ=áááÊó°aÃÑ¢E kT|øûï¿Å€„™™™ˆˆˆÐµ::'))IØØØèZbÇãDžضm›HOOÏž=©©©ºV«X±nÝ:Ñ­[7]«Q,puuB±hÑ"ñÞ{ïéX#ÝñVfá¸páB¾äFŒÁˆ#xúô)E¬•:§NbÑ¢E˜˜˜ðá‡2mÚ4­ëPiÚ´iÉþU«ìK¹·oßžGA•*Ut§T1 {¼BõêÕyþü¹µ)>¤¤¤ðÉ'Ÿ°}ûvœu­N±!55___š4iBݺuu­N±`åÊ•4kÖ 777îß¿/]5ðûï¿3qâD]«Q,HLLTŽ»vvv$%%éX#ÝñVÐEÆsxx8iiiT¨P.\¸@ff&úúo¥g¤X¶l­Zµ*ñƳ‚Õ«W³råJ\DgC__ŸN:qûömV¬X¡\˜Âаd?oݺſÿþËÀ)[¶,FFFìÙ³G¹KIçŸþ!((ˆÞ½{ëZ•bÁæÍ›éÛ·/U«V%..ŽíÛ·ëZ%Q²GŽ× $$„àà`*T¨ \!ìež?ÎÉ“'¹ÿ>Íš5£Q£FÊ}ddd(?+ g==½"×½¨ˆˆˆàâÅ‹„‡‡ãææ†›››F¹ .pøðaRRRh×®:tв¦Ú%11‘«W¯K³fÍrüÁvëÖ-NŸ>••mÛ¶ÅÜÜ\MfïÞ½,]ºô­ð~òä ×®]#%%…nݺi”ÉÌÌäܹsP·n]Z¶l©ö`ïÓ§7fݺuüßÿýGÕ†úEB\\—/_&88[[ÛÚ÷ïßg÷îÝùä===ˆ3fh”[µj•044¢OŸ>ÂÈÈHÌœ9SM.((è­ðîÑ£‡000P^/GÕ(7oÞ<¡¯¯/4h lll„¸zõªŠÌ¾}û„³³³ Ó‚æEÇ“'O„ƒƒƒòœä4 %''‹~ýú cccÑ´iSQ¦LѪU+ñìÙ3òééé¢\¹r"**ª(Õ/2vìØ¡¼ôõõs[.]º$¬¬¬„«««2dˆ°°°Ý»wIIIB!6oÞ,êÔ©£Üôõõ…£££ ÒâÑ…=¶(˜0a‚˜7o^i]ôÖØâçç'>üðCåç©S§æzÞŠ3=ÆÆÆÊ{(§±åÉ“'ÂÍÍMT®\Y :T8::Š5jˆ{÷î©È¥§§ ;;;qýúu-h_t–Ý!*T¨ 222„B<|øP”)SFù¹¤! èrùòeñË/¿ˆãÇ‹víÚåx!öìÙSÔ­[WÄÆÆ !„Ø¿¿ÐÓÓÛ·oWÊ 6L|õÕWâÙ³gÂÓÓ3ÇÛÀñãÇűcÇD\\œ(Uª”Æc‰eÊ”“'OV–-Y²Dèéé‰àà`eÙåË—ÅæÍ›…½½½8{ö¬ ÑÆ! ¿üò‹Ø²e‹Ø¸qc޹‹/ @,_¾\!Djjªhß¾½¨W¯žRæÀ¢jÕªâܹs"""BDDD¼±>±±±â»ï¾{÷îŸþy޹ùóç‹Ò¥K‹€€!„aaaÂÆÆF|ôÑGJ™“'OŠ””‘ –.]*Zµj¥•c( nß¾-vïÞ-"""„‡‡GŽcKëÖ­E«V­DZZšBˆ«W¯ CCCñûï¿k”Óƒ kl ÇŽ<7n–––âáÇÚ:ŒB§°Æ–Ç‹ªU«Š%êÕ«'µu…JBB‚ظq£?üðCŽcË´iÓ„………xðàBˆ¸¸8áèè(>øà¹]»v‰¦M›¹ÞEMaÚ-µkׇâ×_Ý»wׯ!K¤ýäô úúújY=jÕª¥2ËüðáCÑ£Gå_Ô*k…œrË—/€8þ¼²,::Zbêԩʲ>úH 4H¹½œ=åMäìÙ³9>äÆŒ#J•*¥’UcÆ gΜBd øÍ›7WÙÞÔ‡\væÌ™“ãC®víÚ¢sçÎ*e£GfffÊÙÖqãÆ‰š5kŠzõê‰?þøŸ)RÓØrûöm¨ÍœÖ«WO´lÙRc[~ø¡ˆ‰‰) 5µÎëŒ-ׯ_-[¶Ÿ}ö™8{ö¬¶Ô.R^wlQ”5jÔHtìØQ¬[·Nj9¹-•*Uï¼óŽJÙG}$J•*%âââ”eK—.;wî,R=µM^vËìÙ³UÊ_¶[Ž;&,\]]ÅÈ‘#ßš1÷U>ÐEÀµk×ÈÌÌT‹~¯_¿>'OžT~¶³³cÏž=ÚVOg¡§§§r^,--±³³#((HY¶hÑ"]¨§3®\¹B:u000P–Õ¯__¹¯E‹Ìž=›Ù³gëJE­“˜˜Hpp°šotýúõY±bwïÞÅÙÙ™¥K—êHCÝ ÈNãââ¢R^¿~}öï߯±ÎÊ•+‹\/]£[²gÖxyl©W¯[·nÕ•Š:!?c €§§'žžž:ÑQÛÄÅÅñðáC† ¢R^¿~}RRR¸wï 4ÈÑúmDa·h[²Û-íÚµ£]»vÚV¯X"Ó=ááá€æ‡\tt4©©©ºPK焇‡S­Z5Ê”)£R^¿~}å9+‰„‡‡«]+NNN•Øó’Û=”}I#·óòìÙ³›R*<<{{{Ê–-«R.Ç9¶¼Œâ¸5Mpeß_ÒvKÁ‘t˜˜ LQ§ÀÂÂ!D‰}ÈeddhŒd666VÉHRÒHLLT»VŒŒŒ([¶¬òZ*iäveß_ÒPÜ'ŠŒ ÷UI½222ÔΠȱEŽ-ê(®‡—ŸE%ý’vKÁ‘t`gg ¶È7055ÅÌÌLjéœJ•*qçÎ’““Uʯ_¿^¢óÕÚÙÙ©]+<{öLy-•4r»‡²ï/i(îÅyPpýúuÊ•+§6[Rc‹f䨢Nn÷Pöý% i·i@ööö€æT±¯$âèèHff¦Ê Çýû÷qttÔ¡fºÅÞÞ>ÇÁ¼¤^/ÖÖÖ”.]:ÇóR­Z5]¨¥s÷ÉÍ›7Uʯ_¿^¢ï!ÅØrûöme™[䨢 ,,,4žt£˜Ž‘vKÁ‘tдiSªW¯ÎÙ³g•eqqq\¿~AƒéP3Ý2xð`ŒŒŒ8xð ²Lø4lØ0]©¥s Ä£G¸{÷®²ìÔ©S”-[–wÞyG‡šé===ÈÅ‹IKKS–Ÿ>}švíÚakk«CítGƒ puuU¹‡BCC¹qãF‰¾‡cË”erl‘cKN :”óçÏ @ZZGŽ¡gÏž:Y™¸8 í–W@×i@Þ4ž}ºhݺµ°´´TæœÛvKÑ ÓØŒŒ "##pwwP~NOOWÊyyyQ¦LÖ¬YÑ#GhÔ¨6lxkýÎ,,,hذ!€ò/@¹råTä¦M›†³³3{öì!>>ž… 2bÄ­êªMbcc•ׇâW|dd$111JSSSNž<É´iÓX¼x1–––,Z´ˆ±cÇêDgmðøñc?~L•*U4hòelª_¿>ÇgîܹøøøP«V-öïßÿÖ.ýnll¬ñ(Uª”òÿ.]ºpâÄ |}}¹qã£Fb„ ”/_^«új 9¶hFŽ-šqvvÆÎÎNí²´´Tþ_µjUNŸ>ͲeË U«V¬\¹’Úµkk[]­ í–¢AO!t­„D"‘H$‰Dò¦ } %‰D"‘H$’ h‰D"‘H$‰¤HZ"‘H$‰D")Ò€–H$‰D"‘H €4 %‰D"‘H$’ h‰D"‘H$‰¤HZ"‘H´L@@ÁÁÁºV#W"##9~ü8ÑÑÑ÷Ÿ?ž\Ûxúô)ÇŽSÉ5+‘H$oÒ€–H$%wwwÜÝÝ R)¿ÿ>îîîüý÷ßZÓeÒ¤IüüóÏZë¯ ÄÆÆÒ¢E ðòòâòåËå† Æš5krmëüùótèЄ„eÛkÖ¬áñãÇ…­¶D"‘h¹¡D")œ;wÈZ±î?þP–'%%qîÜ9•ÜJ2{÷îåÞ½{ýôSÚµk‡µµ5ãÆãÙ³g8;;3|øpÊ•+ǨQ£°²²âСC*õ X¼x1•+W¦AƒüðÃDFF²cÇ~ýõW’““ùßÿþ‡‹‹ åÊ•ãã?¦iÓ¦¬X±B¥-www¾üòK¬­­©P¡‚F=·oß΃˜;w.-[¶¤R¥J,^¼kkk,XðJÇ^³fMf̘Mš4aàÀ8p Àíboo¥¥%†††tëÖí•t’H$m ƒ%I‰ã‡~ qãÆøùùáììüZmyxx(ÿwqq G*2ÎÎΨ”µlÙR\ЬY3Ê—/¯œ¥½zõ*eË–å£>"33!™™™<}ú”ŒŒ •¶ºwïž§ž·oß TªOIDAT¦\¹r¸»»+Ëôôôèܹ3—.]ÊçѪòòq6jÔˆï¾ûŽÔÔTŒóÝÎøñãéÝ»7vvvôéÓ‡‘#Gªè)‘H$Å i@K$’GÆ 4hÓ¦McÛ¶mjûõôôÔÊ^¸SP¦LµzÙËå/Å™˜˜¨|600ÀÈÈH™3999+++5ã¸wïÞ˜™™©”UªTI£nÙIOOÇØØ}}Õ&&&Ê Ä‚òòq*Ú.h`ëÖ­ cݺulÛ¶V­ZѺukŽ;¦ñ»H$]# h‰DR"™={6uëÖeݺujû\\\Ô²YL—.]´¢›D"‘é-‘HJ$ŽŽŽŒ9’E‹©í{çwصkaaa¤¥¥±mÛ6Nœ8Q¨ý?þœÙ³g“˜˜HTTsæÌÁÌÌìÿÛ»ŸÔ¡0Œã †˜Xˆ©?–Nþcb–«£‹ÆuóïppbrQFØ ‰ Ñ!]p'ð†æoŒ'!÷ÞÜûýl§Í9íéô¤yûV’>Ê‚Á öööôððàÏ{||T¡P0¾Þææ¦,ËÒé驞žžôöö¦³³3ÕëuíììŒm__±m[‰DBWWW#!úòòRÝn×Ûç ?¨€¿ Àëèèè—Ç·¶¶Ôl6µ¸¸¨™™kwww¬×Îf³º¹¹Ñ‚‰„®¯¯u~~î8??¯\.§|>/Çq亮b±˜R©”ÿS“““º¸¸P¡PÐÒÒ’lÛÖÁÁµ¶¶6Ö½}e_¹\N~ËÉɉ¢Ñ¨–——å8ŽÖ××µ½½ý[ï LÞ‡}„àV.—5==í÷ªÕjêõz²m{¤¦·ÙlªX,ʲ,­®®ªÓéèååEÉdRÒÇ[RÏó亮?g0èþþþÓZµZM¡PÈ/ÅŽ#‘ˆJ¥’<ÏS&“ñ»Pü¬×ëéîîNÕjUSSSJ§Ó#ñ+—ËšU8þÖshµZº½½U»ÝÖÊÊŠæææFÎ{ž§~¿¯x<þå:•JEápx¤_s§ÓQ½^W*•R P·ÛÕóó³ÇùT{Ýh4ôúú*×u5 T*•T©TdY–2™ŒB¡Ð·ö0@ `€  @Ѐ4`€  @Ѐ4`€  @ЀÈþ*K½òévIEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/Q8-1g-idx-SSD.svg000066400000000000000000003751151416254111300234760ustar00rootroot00000000000000 PyTables-3.7.0/doc/source/usersguide/images/Q8-1g-idx-compress.png000066400000000000000000002723121416254111300246600ustar00rootroot00000000000000‰PNG  IHDRÐß}™SsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwxÕúðïlßM¯$z‡P5R.RT$H)^‰» "(Vlˆ^ôwQPA/ ¼U ¢H»ˆBBB Hz/Û÷ýývÈff³©»I|?Ï“'É™Ù3gfwϾ3{Þ3cŒ1ÆcÕ¢ðucŒ1ÆkJ8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€f5R\\Œ£G"''Ç×MñºãÇcîܹ8s振›â–ÕjÅ®]»°páBÌŸ?ß×ÍiR>ûì3>fÍÔ3Ï<ƒ?þØgÛ/++ÃñãÇqæÌ˜L&Ÿµ£®þ÷¿ÿá™gžAYY™XÖï›eË–á7ÞhºëÛ?ü€¹sçâÏ?ÿ˶lÙ‚åË—û°U¬!qͪeݺuèÒ¥ ‚‚‚pýõ×#""­[·Æ’%Kàp8|ݼzóË/¿`áÂ…¸té’dYRR–/_Ž .x¿aÕðÇ 22·Ýv¾ÿþ{>|Ø×MjR¾þúk¼÷Þ{¾nk+W®ÄŽ;ªµî[o½…;ï¼:t€ ®Õ6–/_ŽN:! ýû÷G÷îÝa0п|úé§°X,µªÛW.\ˆ_ýƒA,kÈ÷ÍÆñé§Ÿ6HÝõíøñãX¾|9²³³Å2Ì;'NœðaËXCQùº¬q#"ÜsÏ=X·núõë‡+V wïÞHIIÁ矎gžyß~û-vîÜ ???_7·ÎŽ?Ž7ß|'NDëÖ­]–ÅÆÆâÅ_D§N|Ôºªýë_ÿBAAΞ=ÛhÛÈXc÷üóÏC­V£_¿~(((¨Õ‚¼¼<Œ;‡ÂàÁƒñàƒ¢{÷î0›Íøí·ß°sçN$$$@«ÕbêÔ© °õïÈ‘#øöÛo%'"“'OFll¬ZÕ¸9ݺuÃk¯½†Í›7ûº9¬žqͪ´~ýz¬[·“&MÂúõë¡ÓéC‡Ž÷Þ‹ùóçãŸÿü'–.]Š—_~ÙÇ­mX±±±úƒâìÙ³hÙ²%ÏŒÕÁÉ“'Ñ©S'( ôíÛ·Vß8-\¸‡ÂâÅ‹±hÑ"(×¾ì½ýöÛñüóÏã“O>Ahhh=¶¼a½úê«èܹ3ÆŒãR>yòdµ¨ñ>ú(yäüñÇèÑ£‡¯›Äêá`n™L&,\¸Z­ï½÷ž<; ‚€×_111xûí·‘‘‘!.{øá‡ñðÃKê>_ý5vî܉qãÆ!** ¯¾ú*üqL:D$©ûìÙ³ˆÇgŸ}ævß{ï=ñ«Èyóæ!>>ñññX³f €ò1nñññ8zô¨ø˜¯¿þñññ8sæ V¯^áÇ#&&3gÎDRRàĉHHH@Û¶mѧO·c1333ñÀ oß¾ˆˆˆÀСC«l¯ÓÑ£G_ýyyyb»?üðCqsçÎaÚ´ièÚµ+Z¶l)ÃÊž{î9LŸ>eee˜;w.úõë‡ÐÐPã6Íf3/^Œ!C† ""mÚ´Á¸qãðÃ?¸¬—––†»ï¾ݺuCtt4n½õVlß¾]RßâÅ‹1yòd˜L&<ùä“èÕ«ºwïŽ Àh4þóŸÿ`̘1hÑ¢†ŽãÇ»ÔqùòeÄÇÇcÓ¦Møî»ï0vìXDEEaÈ!Õ:®NÅÅŘ?> €ððp 0o½õ–Ë•É7">>^ruÎf³!!!“'OFqq±Çm¥¥¥aêÔ©ˆ‰‰Áu×]‡E‹¡°°Pò|:ŸóÊÇ–,Y‚ñãÇKÊ ñÄO ..ááá8p –-[&y¿T<öO=õ®»î:„„„àðáÈÇçŸ.Ûö¥K—bìØ±(--­r¿þúk̘1Ý»wGHHzöì‰Y³f!==]²îÌ™3ñôÓO#;;³fÍB§NбcG<ôÐC²Ç3//÷ÝwÚ·oØØX<úè£ÛSY—.]\Þš:zô(>úè#ôïßÏ=÷œl]‚ `Ö¬Y¸õÖ[ŲŠ}ãwß}‡ñãÇ#::Ï?ÿ¼¸ÎÊ•+1lØ0DFF¢OŸ>xøá‡‘ŸŸ/.'"Lœ8K–,qÙÞºuë/jñá‡bôèÑQJJ ¾üòKÌš5 ‚ ¸,“{½9û“É„'žx½zõBëÖ­1}útÙçÙù>ïÑ£:v숙3gºŒ%®ìÊ•+˜={6úôéƒÈÈH 6 7ntYç­·ÞB||¼¤_ÈÏÏÇí·ßŽûï¿ßå=üûï¿cúôébß4räHìÝ»Wvû›7oưaÃ…Ûn» ;wîtÛÖ{î¹*• ï¾û®ÛuXEŒ¹qôèQ@wÝuW•ë½øâ‹€6oÞ,–õéÓ‡úôé#Y÷øñã€Þ}÷]—ò'žx‚PTTÍ›7¦M›F†‚‚‚èôéÓâz‡"4nÜ8 £û￟ž{î9ú÷¿ÿMË–-#ôý÷ßK¶ûä“O:uê”Ûýغu+5ŠÐßÿþwZ°`-X°€¾ùæ""úüóÏ €ø?ÑÊ•+ Mž<™´Z-Íœ9“&L˜@ …‚zôèA'Nœ ˆˆºá†èñǧÐÐP@›6m’눈R©T4zôhZ´hµk׎ÐÓO?]åñOJJ¢ PëÖ­)00Pl÷Ž;ˆˆèÈ‘#H!!!ôä“OÒ+¯¼B}úô!ôÖ[o¹Ô5jÔ( ¥›nº‰LÏ>û,Íž=›JKKÝn¿°°zöìIèŽ;î 7ß|“^|ñEºõÖ[iÞ¼yâz'Ož¤ ¤yóæÑ«¯¾Jýû÷'ôÊ+¯¸Ô9aÂò÷÷§Q£F‰¯ g›}ôQúôÓOI£ÑД)Shúôé¤R©( €rrrÄ:NŸ>MhäÈ‘BóæÍ£·Þz‹ @è7ÞpÙæ]wÝEZ­Ö¥,--Ú·oO‚ ÐСCéÅ_¤^½zš2eЏžÉd¢¾}ûRpp0]¸pA,Ÿ?> µk×Vù={–"""(00æÏŸOK—.¥ë®»ŽÆŽKèñÇ×ýæ›o}þùç’zäö#55•Ú´iC‚ Ј#è…^ =zºçž{ÜûÐ3Ï!"¢¿ÿýï€n¾ùfZ²d =ðÀ¤V«©uëÖ”žž.Ö3bÄjÓ¦KÝ#GŽ$Ô»wo—ò¸¸8êÑ£‡Ç¶mذÐÎ;%Ëä^oÎ~ä–[n¡ÈÈHš;w. 2„P×®]Éf³‰ëÚl6û,µiӆР/¼ ®—žžN-Z´ Î;Sqq±X>~üxR©T”˜˜(–mÞ¼™t: š1c=ñÄN …‚þýï»lßù9Ó¿Zºt)ÍŸ?ŸÂÃÃÅÏŽãÇKŽQ×®]%Çž5}@3·œãóÏ?_åzëׯ'´xñb±¬&ô¾}ûÄγbÇzþüyR*•/–9hAè÷ßw©;??Ÿ ÝqÇ.åf³™ÂÃÃéoû›Ç}vć’,«*€nÕªeee‰åK—.%¤V«iÆ byff& ‚@7Þx£Xf·Ûéºë®#Nç|ÙívºóÎ;I©TVø; 6ŒÚ¶m+)2d ‚àRGii)uïÞôz=¥¥¥‰åÎÛo¿‡Çm=öØc@òACTX:Ý|óÍ€Ž;&–FêÓ§i4JMMË'L˜@húôéd±X\êÂÂÂèÏ?ÿË?ûì3@o¿ý¶Xæ  Ю]»Är‹ÅB$½^ïR‡\ pçwúùçŸ]ÊŸ}öY@ß~û­X–œœL4pà@²X,ôÕW_‘ 4sæÌ*ŸÓŒ3€Ë»ÉdO2ê@7ŽA|¸;O,÷ïß/–9ým·ÝFv»Ýeý%K–:xð KùêÕ« €xÒV•üü|IÙW_}EháÂ….åmÛ¶£Š¯GçIErr²XöôÓOKNN‡¸®·è{ï½—Ð?þX£Ç9ûÆÊï"¢~øÐĉ]Žƒ³OzðÁŲW^y…PJJ •¿†ôz=ýío#AÄ~*??Ÿ =úè£ÛöÜsÏJJJ’,s@ 3fÙlËŸzê)Éëä“O>‘|~=òÈ#bÀídµZ)66–üüüèòåËb¹Ýn§qãÆ‘Z­vyM|ÿý÷$͘1ƒˆˆþùÏJNžóóó)""Bh;OŠ###©¨¨ˆˆÊOÒ)66–ŒF£¸îáÇÅçN.€7niµZ—Ï7ÖôñæVZZ eË–U®ç\~þüùZmçý÷ß,X°J¥R,o×®n¾ùfìÞ½V«Õå1}úô‘Œ' ÆôéÓ±}ûv—á$[¶lANNæÌ™S«öUÇw܈ˆñÿ›nº ˆ;ï¼S,ŒŒDÏž=‘šš*–ýôÓO8zô(î¾ûn´mÛV,W(¸÷Þ{a·Ûñý÷ßת]çÏŸGbb"úö틞={Šåƒ'N„ÑhÄ–-[$›1c†ä«Z9D„Õ«WcРA¸ûî»%˵Z- =={öìA÷îÝѯ_?q¹N§Ã¤I“`±Xd“l V«ÅÿGŒ"¨Q£\^—Îãí6SQxx8n¾ùfñµZ &Àh4â¿ÿý¯Û}ËÊÊÂæÍ›qË-·`À€.ËfÍš.Ã`:wîŒU«VáçŸÆœ9s0sæLtëÖ ÿ÷ÿçvNv»›6mB«V­ð·¿ýM,×jµ7nœÇÇW%-- ;vìÀ˜1cзo_ûá4}útÉðƒüãÐh4Xµj•Kù‡~ˆÖ­[ã¶ÛnóØžŠ³Z”••!##ýû÷GLLŒìl‚ `öìÙ.¯Ç‘#G(šå´aÃÉñwÜq‡Ç6Õ'ç˜é˜˜ɲ[o½Çž{î9É:ݺusyå¹(0mÚ4—ãpûí·C¯×cÆ b™óµ¾gÏåý‹ÑhÄ믿"¾}ûûöíƒÃáÀ-·ÜâqŸ~ÿýw(•J´k×ÎãºÍš5 Füßù¼%''‹eëÖ­—~¦L™"©oÿþý8uêî½÷^´jÕJ,wö•V«»wïvÙÞÂ… ±~ýz<þøãX¸p!F… ˆëlÚ´ ÙÙÙxüñÇáïï/–kµZL›6 YYY8rä`çÎ(**Âøñã]†4ÆÅŹôÝ•uéÒf³Ù¥ßgM'2·œJVVV•ë9—‡……Õj;IIIÐëõ²&iii°Ùl¸xñ¢Kr\||¼l]>ø >úè#|òÉ'xöÙg«V­BHHˆ¤ƒ®OcÇŽuùßÜ5 *•J²ìË/¿ÿw}'OžÄĉ]ÖµÙlPëŽ7%%Àµ®ŠFމ7ÞxCrâ£V«e×—sùòeFÜpà ujÇ‹/¾(i‡N§ÃðáÃ]ÊœÇuôèÑ.å­ZµB`` ËRN7Ýt“äd`ĈÊǺóÇ222$Ï‹³}•Ÿ—©S§bß¾}Xµjôz=6mÚT­Ùi._¾ «ÕêèWlk]tû‘––&»jµZ² …Bö=É“'cÓ¦MX¶l‚ƒƒqìØ19r/¿ürµÆ§¦¦âå—_ÆÎ;‘——ç²Lnʸ~ýú¹KЫW/Ÿo«ÕŠ+W®`èС’\ çsí-ζfffJNN…B›Í†={öÈî¯ÜqOIIB¡¼>´Z-n¼ñF|ÿý÷ÈÎÎFDDââ‽{÷böìÙØ½{7bbb0tèPÄÆÆbïÞ½˜2e vïÞ ¥R‰aÆyܧ‹/",,Ì%ö$ C‡u)«ü¼åïÁèèhtëÖÍeݺ´Àµ¾òÈ‘#’ײsJÀʯåÅ‹ãÀX±bZ¶l‰ÿûß.ýóý±}ûvìÚµËå±¹¹¹b#FŒû ¹“Ž#F`íÚµ’ràÚkââÅ‹èÒ¥‹ì:¬éáš¹Õ¹sgžƒ7çòöíÛ{¬“dü àï﨨(ɲ¨¨( 6Lò¡èîl¿ÿþ0`V¯^… "55û÷ïÇc=&©£>¸üïì +—;—U<ÊOXZ´h!YΜ9ˆ‹‹«U»ŠŠŠ@€T,«œŒŠÀÀÀÕ/·Ÿum‡V«u¹ú \;®•?XËä^_rÛtžV•ØçüðlÑ¢¢££%Ëd? KJJ”KAAAnë¯ÈÙŽªÚZ]•s?¢¢¢d÷cÖ¬Y’osÜž?øàƒØ°aÖ­[‡Gy~ø!”J¥x5»*ùùù¸ùæ›Q\\Œx±±±èÞ½;t:î¼óNÉ7MζTæ|8÷Õd2ÁápÈ«ÈÈH—o¶Z×®]”÷‹•¿¹pž8 $$Döñr}[QQ ƒlÀí|Í!""*• C‡ÅÞ½{ADؽ{·xß|óÍbÂÛîÝ»W­×hûöíqüøq˜L¦j÷£ƒArÜ+?o@ùk_nŸÕjµË·zÀµ¾²U«V—/ûÛߪ}ø£>† 0sæLlÞ¼Ó¦MÃþýû%ßBTæÜ÷ªÚZ‘3¨8TÉé×_•ÝáÇã™gž©Ö~TeÈ!ˆÅªU«0sæLlذcÆŒ‘ **Û»w/.^¼ˆ×^{Mü†(bNŸ>]ë)${¬<»Ý^«zkcðàÁ€5kÖ`úôéõRgÇŽqäÈüúë¯8p Ë²={ö@¥R¹¡Î@ùÇÄÑ£Gñè£(¿rº|ùrø >øà<üðÃ.WŠu>üðÃ’¡3rmÊ_Ç•Ožå^{NÉÉÉÐétb ΚÍÜò÷÷ÇK/½„¢¢"<ûì³²W÷V¬X3gÎ`Ê”). mÚ´AzzºËX7øâ‹/$uŒ1øÏþS/íž2e ÂÂÂðÞ{ïaíÚµ¸ñÆѽ{÷j=Ö9NØ›w¼á†àçç‡Õ«W×û]»víŠÐÐPìÛ·OâäW]ù*YMèõzÄÅÅaûöíUN;Õ±cG´hÑ€Ùl®÷vTåäÉ“’aHÎ@uРAn×¥K´lÙŸ}ö™xU¹*§NÂc=†¡C‡âã?Æ{gƒâ…^ðøXÄÆÆâÈ‘#âU¶Êm­¨M›6®|8=zT2¦gÏžˆŒŒÄš5kÄ+quõàƒâ·ß~Ãc=†’’’jŸ fffŠmªèË/¿¬s{à 7àÏ?ÿ”ô9î¦"k(7Ýt&L˜€Ý»w×hºÄª8_§•‡$%%!-- ×_½ËIšóŠóóÏ?»Ý.þ?lØ0¨T*ñäEnÈçóUùØÖ‡AƒÁn·KÐÊû ”ëÐétXµj•ìçQeÛ·oÇŠ+pÿý÷ã_ÿúžzê)|úé§.Ï‹sˆOÅi"Ýqž¼Tn[III•w~MNNF·nÝê4="k„|’ºÈš «ÕJÆ ³òwìØA/^¤={öÐ?þñ@]ºt¡‚‚—Ç9gæ=z4}:EEEIfá0™LÔ¥K ¤åË—SZZ•••QRR­^½šî»ï>q]ç,+W®¬²ÝÎlo´nݺío@@ 2„6lØ@û÷ï§sçÎQÕ³pTžµÃh4š3gŽdrYëï¼óŽ8=ß‘#G¨´´”®\¹Bûöí£Y³fÑ/¿üâ±íîfápN»4}útJII¡ììlzûí·I¡PÐðáÃ]Ö5jµhÑÂã¶*:tè ‚@ݺu£]»vQaa!¥§§Ó–-[\Žý| Nu—œœL999´bÅ R*•4hÐ —:'L˜ ;ûÁÖ­[ mݺU²,((Èe¦ç,!!!tÓM7QRRч~H††êòx¹çeË–-€H?üðRvv6ýüóÏ4oÞ<Ú¶m•OãÖ½{w £K—.‰Ÿ1c ‚ ;µbe7n§1KMM¥ÂÂBzÿý÷)22R2 ‡Ãá ~ýú‘^¯§%K–Pjj*­X±‚ºtéBááá’ýpNC6dÈJLL¤¢¢"ÊÊÊ¢Ÿ~ú‰{ì1úúë¯=ûŠ ÉßߟP›6m$³u¸säÈ@×_=ýøãTZZJ;vì ˜˜ v™q¨|ŽaÆIêqÎܳfͱìàÁƒâsõÛo¿Qii)}þùçMjµºÚ³p|ÿý÷´fÍZ³f µnÝšôz½øÿ–-[ªU‡súC4uêTúâ‹/è÷ß§äädÚ½{7Ýÿý€&Mš$>ÆÝŸDDeeeCAAA´yóf*(( 'NP\\œd–¢ò×GDDLSçœ2N¯×»Ì’S•ÔÔTÁeš8'w³pÈõ#ééé€,X –ýù矤×ë©C‡”˜˜HF£‘vïÞM­Zµ¢ÀÀ@Ékâõ×_ݱcÇľrÏž=” ΂qñâE ¡ØØX*++#¢k3ðøùùÑ™3gÄ:ï¹çyä:}ú4Fºxñ"mß¾&NœH………âºñññ¤T*iÅŠTXXH©©©4~üx ‘…£¨¨ˆT*•ìgkÚ8€fÙl6zå•WH¯×‹A©ógÖ¬Y²ÓRÙívqÞRçOÿþýi÷îݲW®\¡;î¸CR¿Z­¦Y³f‰ëU7€NII!A(44Ôeº¡êøøãiĈ¤Ñh\:û†  ­ZµŠ‚ƒƒ%Ç cÇŽ’)ûä¸  ív;½úꫤR©\ê?~Ü¥?¹ýöÛÅ©K+ÐË—/'…B!; kÚ¢j|ÂʧÚ:sæ N:…½{÷bÕªU¸çž{°víZ·c‡ûí7$''£GèÞ½;, ®\¹â6QíÊ•+øã?——‡èèhôîÝÛe\œÙlFzz:ªL\;sæ ºwïŽyóæáwÞ©Õþ–––";;AAA ÿoÑ¢ôz=€ò¤’ÜÜ\DGG‹›Û__ IDATS¶åI2/^”MÆÊÎÎFYY™lâŒÑhÄéÓ§‘’’‚ÀÀ@´oßÞe `U222`³Ùd§ÏÊÇ™žù¤¯›"1iÒ$òóósù:ýµ8¯@W¾ã`S“ŸŸ/{Ú—’““I¥RÑìÙ³}ÝæEcÆŒq¹ «ÚÎ;I:yò¤¯›ÂÏÂQCW®\ÁÊ•+qüøqqÞÉ¿ªU«Vá†n€ÑhDff¦ìlÞd³Ùðꫯâäɓغu+^{í5·Wcc5·ÿ~|ýõרºu+ðÚk¯ùºIÌ‹Þ|óM|ñÅ(++“|Ĥl6V®\)ÎÍš k衇ÂÛo¿[o½Õ×Mñ9¥RÙ w÷«)»Ý޵k×"** ¯¿þ:žxâ _7‰ùZ­FÛ¶m«=sc¥P(жmÛ}ÝÞPNœ8/¾øă>(;/k¾zöì)™A…¹7~üx_75 ]ëׯǑ#Gðî»ï"88gΜ‘½ùcŒ1Æk¾þ2W - .\¸Nç69Æápà?þÀå˗ѯ_?—! ™™™x÷Ýw«œ,1ÆcŒ5Í~VïŸ~ú ×_=еkWÜsÏ=²ëåååaðàÁèׯî»ï>DEE¹ŒïÛ¶m²³³1fÌ >%%%˜4iRRR¼µ+Œ1Æc¬höC8±mÛ6\ýõX¾|9´Z-öïß/Yï®»îÂ/¿ü‚Ÿ~ú -[¶Äúõëq÷Ýwã믿ÆèÑ£a4QXX(®ßµkW$&&¢{÷îoÓËcŒ1Æšf@W“É$  ³²²ƒE‹áÅ_Ë;tè€Þ½{cÛ¶m’ºœtDDDC7›1ÆcŒ5"|éÀ±cÇ`µZÑ£G—ò^½zá矖}LRRRµëÿ裰aÃI¹§0ÆcŒ5´´´4IÙôéÓqß}÷ù 5MC³]W®\Éô<½zõBvv6l6[êß°a~ùåX­V—ò´´4˜ÍæZ•;v ©©©õV_ZZRSS]ÞDžk·ÛqèС*×KKK물^bb"RSSëÔæÊe{öìAqqq½Õ—––†C‡¹Ìùíé±þù'Ο?_åz‡물޾}ûêÜæÊeß}÷]½Ö—––†Ôè±G…Ñh¬r½ÄÄDÙÇ:—Õçq±X,سgO½—sçÎáСC5z¬sŸÝ­W\\ŒÃ‡Ë>655U|ßÖ×~¤¥¥áرcõz\jÚ_9û–ªÖ«ª¿JLLûŸúÚÇãܹs>í¯œ}KUëUÕ_íÛ·¯Îm–ëÇ}Ý_9û–ªÖs×_Uìwêk?, >ìóþÊ]ê$×_åääÀjµâرc²þX¾¼‹‹·5І &)÷Ýw egg»”¿÷Þ{€òóóë´Ý#FP¯^½êTGe[·n¥­[·ú´N£ÑHsæÌ©u}ûö¥ .Ô¨ž<þøãu~¾êZç7ß|CŸþy­ëìØ±cÚW3gÎôy , ôôôZÕyáÂêÛ·o¶çI~~~½ßݯ6uz:ŽUÕ¹páBZ¸pa¶ç ÷-ò¸o‘Ç}‹Tsè[zõêE#FŒ¨Ñöþjx Î圜œìrc€Ó§OC«Õ"88¸ÎÛ(..ÆK/½„áÇcøðáu®¯[·nu®£®uªT*Œ;¶Öu6¬ÞorNçÓ:;uê“ÉTë:‡ R£öUÇĉ}^ç-·ÜÿZÕ„aÆÕh{žèt:ÄÇÇû¼NODZª: P£mU÷-ò¸o‘Ç}‹TSî[öïßýû÷£¸¸˜o”䉯#xorwúÇ$´jÕ*—òo¼±^ÎØGŒAƒ ¢óçÏ×ûŒ¦¬!®è4 qE§©kˆ+:ÍAC\-n¸o‘Ç}‹÷-®òóóéüùó4hÐ ¾í0pà@DGGãèÑ£b™ÉdÂo¿ý†I“&ÕË6t:ÚµkW/W³cŒ1Æê[pp0ÚµkWïß¶4GÊ—^zé%_7¢!bÙ²eHLLľ}ûŸŸ£ÑˆÄÄDôèÑz½ …J¥o¼ñ ŒF#ž|òI\ºt «W¯FHHHÚ°oß>dffÂÏÏY»ÖäuêÔ ‘‘‘P(ø<®¢nݺñWg•¨T*téÒ¡¡¡¾nJ£Ž6mÚÀ`0øº) ÷-ò¸o‘â¾ÅÕ©S§°wï^««àý÷ßGII‰¯›Ñè,Y²Ä×MhtJJJðþûïûºNbb¢K¦=+Ç}‹<î[¤¸oq¥Ó録²2_7¥ÑûKÝHÅWk×®õi;cŒ1Æ<á¸Å³fš1ÆcŒ±úÄ´—$&&"!!Aö¶àŒ1Æc¾¶mÛ6$$$ðаjàÚKâââ°víZ_AJJJïòØ9sÆ×Mhtl6RRR|ÝŒF'''999¾nF£Ã}‹<î[¤¸oq5qâD¬]»qqq¾nJ£Ç´—dffúº 'úÈãD)Nô‘ÇI„ò¸o‘Ç}‹÷-ò8fñŒ“½€ã3Æc¬©à¸Å3¾í%<š1Æc®>¾í|&ÇcŒ±¦‚ãÏø ´—˜Íf_7¡ÑáDyœè#ʼn>ò8‰P÷-ò¸o‘â¾EÇ,žqí%< _Š}äq¢'úÈã$ByÜ·Èã¾EŠûy³xÆC8¼€¿ aŒ1ÆXSÁq‹g|ÚK8‰1Æc'V_ö>“cŒ1ÆXSÁq‹g|ÚKx@¾'úÈãD)Nô‘ÇI„ò¸o‘Ç}‹÷-ò8fñŒh/áùRœè#}¤8ÑG'Êã¾E÷-RÜ·Èã˜Å3ÂáüUcŒ1Æš Ž[<ã+ÐŒ1ÆcŒÕÐ^³p0Æc¬1ãY8ªh/‰‹‹ÃÚµk1qâD_7¥ÑàDyœè#ʼn>ò8‰P÷-ò¸o‘â¾ÅÕĉ±víZÄÅÅùº)Ð^Âò¥8ÑG'úHq¢ò8‰P÷-ò¸o‘â¾Å•ÍfƒÉdBzzº¯›Òèq¡$$$àСC1bÆŽ‹±cÇúºIŒ1Æc.¾úê+|õÕWØ·o ÄI„UàÚ 8›•1ÆcMÇ-žñÆcŒ1Æj€h/áùRœè#}¤8ÑG'Êã¾E÷-RÜ·Èã˜Å3 ½„ïê#ʼn>ò8ÑGŠ}äq¡<î[äqß"Õú³¹þþ8fñŒÇ@{%bŒ1ÆX}3™."3ó3 Ý>‚ ®—z9nñŒ¯@3ÆcŒ516[!²³7ÈŽÒÒSHOÿ6[¯›õ—Á4cŒ1ÆXâpX‘•µv{©Xf±¤#=}5L¦ ¾kØ_Ð^Âò¥8ÑG'úHq¢ò8ÑGª©&ú44N"”Ç}‹<î[¤šJß’“³ ‹ûqÉaac¡Õ¶ª·íqÌâÐ^Âò¥8ÑG'úH5ÅDoà$ByÜ·Èã¾Eª)ô-……‰(-ýÝíòÀÀð÷ï[¯Ûä˜Å3N"ô¾£cŒ1ÆjÊhLAfæzò¡šN×QQ÷ ¾¯‡rÜâ_fŒ1Ækdl¶ÞÆI„ò¸o‘Ç}‹Tcì[ˆÙÙ[«¼›`XØxhµÑ ÖŽY<ãÚKx@¾'úÈãD©¦èã œD(ûyÜ·H5ƾ¥°ðÊÊN»]8þþ½´ ³xÆI„µ@D¡Úëó`|ÆcŒyRV–„¬¬p7îY¯ïˆÈÈ„†½þÉq‹g|ºFÖ­[£cÇŽ˜:u*Ÿ¡1Æc¬^X­9ÈÉÙ÷Iƒ!ŸÜàÁ3«~j`íÚµ¸téRRR…þóŸ¾ncŒ1Æš8‡Ã|5iP~ì±B¡A‹Ó Tê½Ü2æÐ5ТE €B¡@hhh’Tx@¾'úÈãD©Æ˜èÓp¡<î[äqß"Õú–ò¤ÁÿÂjuÿ^ ›µ:Òkmâ˜Å3 k衇B·nݰwï^¼üòËÕ~÷âDyœè#Õ}N"”Ç}‹<î[¤CßRX¸Fc²ÛåÁÁCáç×Ë-☥:þI„D„¤¤$$''#44C† ‘]¯°°ûöíÃÅ‹qà 7`РA’uòòò’’‚×^{ ±±±xíµ×ú(vìØ›nº ³gφÃápY/447Üp-Z„­[·zccŒ1ÖÌX,YÈÉÙ wÁ³Z†ððIðvð̪§ÙÐÑÑÑxçwpüøqÜ|óÍn×{øá‡€¤¤$ìÞ½»víÂÇŒ/¾øPTT„Ÿ~ú ‹iiixÿý÷qçwzk7cŒ1ÖL8&dem‘Ev¹B¡Edä4(:uY2,Èß_ßMd4ûºwïÞ¸ï¾ûзo_¨T*Ùu.]º„ï¾ûS¦LÁP~[Ì!C† K—.X½z5Àn·cÉ’%èÙ³'fΜ‰ž={bÞ¼yÕnÈ—âDyœè#Õ}#N"”Ç}‹<î[¤|Ó·²³¿€Í–çf¹€ððIP«Ã=Öd:gBÆš &¢ðÇÂzk!Ç,ž5ûº:Nž< ‡Ã=\éÇÆÆâĉ€|ùå—8{ö,öíÛ‡  88¸ÚÛØ±c¦M›†xÀå'9Ù5qà¯TöþûïcöìÙ¢-©lÉ’%¦-¥Ì™èÓÚҘʜI„¡-©Ì™DØÚҘʜI„¡-¥¬b¡·¶{äȧ0¯í‹}…óçsÅÿƒƒ‡ã‰'ÞõX_ɉÌš: ©WRù{óQò[í^÷Θ¤gÏž˜6m8Vµ¿D¡S||úè#(®ç,Æ¥Ìd2q—q—q—qY3,ËÍ=†ÂÂíP(®k6›mP«•P( Ý1EŒ;ÜÕW°¯?Àl3C­TCqõæ*‚J@Д v ¬QûL&“KÙœ9s T*9‰° òcþbŒF# ((È¥Üù¿óUFRN:މ˸ŒË¸ŒË¸ŒËšv™Å’‰’’o\‚gÐjËC1µ:·C·õ‘½%¥'K˫Һ¬C6BÑÖ"øÝïE¨Âå±UµÏù·ó·R©”¬Ï\ñ”'ÀéÓ§]ÊO:ƒÁ€ÀÀÀ:o#11 ضm[ëbŒ1ÆXÓa·—]M´Ê.W(thÑbÁýÅ:»ÑŽÌgŠÁ³;úz¨kw}tÛ¶mHHHàùå«híÚµüþûï.å¿ýö›¸¬®âââ°víZLœ8±^êk8ÑG'úHq¡Î€é‚©Êm DøäpªÚM}7qâD¬]»qqqµzü_ Ю¿þztìØÑåŒ+??§NÂÔ©Sëe|W)¾[˜<¾[˜Tc¸[XcÄw"”Ç}‹<î[¤¼Ñ·äçï‚ÑxÎíò›¡×wr»Ü|ÙŒŒ2`Í‘¿z €°ÛÂ:*‚P÷y£9fñ¬Ù'æääà©§žìÚµ v»ñññ€7ÞxC¾ñùçŸãïÿ;æÌ™ƒ~ýúaõêÕÈÎÎÆ¡C‡Ð¢E‹:µÁùuÈ!C0qâD¾ ÍcŒý””œDNηËýüb1Ùíò²ÓeÈÞ’ ²ºÕÂ«ç<+O¶mÛ†mÛ¶‰1 'º÷—J"9r¤ÛeÓ¦MChh(Ö¬Yƒ?þÄ3ÏpÜâœaŒ1Æ«£¢¢_Ÿ¿Ëír­¶%ÂÂÆIÊ©FdoʆÃìpûXA) lBü{û×K[YÝqÍcŒ1VKv{ rr¶Áht?–Z©ôCDÄ]×°«øh1rwæîcg(ô DÞ ];éí¸™ïðh/9pàßÊ»Nô‘lj>RœD(“åqß"û©ºö-eegðçŸÿª2x%""¦@¥º6ô‚ˆ¿;¹;ªžU!*DÏŠöZð켕÷¼²½¦Œh/i×®ßÊ»Nô‘lj>RœD(“åqß"û©Úö-Däæ~‰¬¬p8ªNú ‡N×öÚcm„œ/rP˜(?K‡“6F‹èû¢¡—Nu×Pœ·òn×®×¶ÙTq¡ð`|Æc¬y0›/#'g ¬Ö<ëÄ!,lŒø¿£ÌÌÏ3a¾d®òq†î„O ‡Bí›ëœ·xÆc cŒ1Æ< r °ð €¨Šq -BCGÃß¿¯Xf͵"s}&lyU/ „‘!€P/Íf „h/±Ûí0™LP©TP©ø°3ÆcM…Õš‡œœ-0›/{\W«mððIP«CÄ2sšY³`/³»}œ ::qõÒæÚ°Ùl°Ùl°ÛíP*•>kGSÀc ½äСC|#•J8ÑG'úHq¡ žk7R9tèOÛÑpí%mÚ´Á|€±cÇúº)'úÈãD)N"”ÇI„ò¸o‘Ç}‹TU}‹ÃQ†¬¬ÈÍýD–*ëQ«Ã= ÁÁà ×B«‚ ýßlÍ}º™2@‰¨{£`èb¨ÝNÔ£±cÇâƒ>@›6m|Ý”F“½€ã3ÆcM‡Ñ˜‚œœm°Û=Ÿˆ\‡QP(4× @îW¹(>V\åcÕ‘j´˜Ñª Æ5´“ãÏ×3ÆcŒ1æ#D6äçïBQÑ/×U( ƒ¡›K¹Ãì@ö¦lSU>^ßQˆ)Phy0@SÄ4cŒ1Æþò,– dgÿVk¶ÇuõúNŸ¥òÚ­µí%vX³¬Èû.–̪‡|ô@ØØ0HÛ„qí%eee< G%)))h×®JΜ9ƒnݺy^ñ/Äf³áÂ… èÔ©“¯›Ò¨8ÃÃÃ}Ü’Æ…ûyÜ·H•÷-ç‘…‚‚½ rŸèÔÒÜMa”¦Ú`ÉÎ5Ç k¶SÕSÛ]­!7… èÆ Ïëú€s޲²2 ¾“ݘñ¹—ò8ÑGŠ“åÕg!ÁjÍ„Ñx‚ €J µ:*U0¡i}øsß"¯9ô-d#Øòm.²5× kŽ£vm¬úó°êÏÁ®ÍTÿÚ\É)BëÈ`±Lph¡ÏMi펕 €&BmŒÁ#‚!¨„ZÕãKf³ZmíOþ ¸wñ’ÌÌL_7¡Ñyÿý÷ñÒK/!88ØóÊ8f©0™ÎC¥ „V­¶AãùÁÌ’%KxÜY%Î$ÂeË–ùº)Š3pâĉµz¼Í–£ñL¦ó0ÏÃá(“¬# (•AP«Ã®Ô¡âo•*¤Q×õÙ·4‡fs/ž‡Õ«ÿ &ju(û<³–t ,阯˜aI·À–g9®¥n‘`ƒ] VÃyXÃÏÁ¡¬Yòh™ÉŠ»ÿ‡ùÓ‡TæV0d‚ÂXõ@¨‚:\ uÄÕŸp54( û¸VGff&ßÐN"ôŒß0¬ÖgQV– ³9Mfú!M$´ÚÖWê¨Ta„¦w5€±ú`·_ –˃f›­°Ž5 P©‚\‚ꊿ¯Ñø‚ÃaÙ| &Óy˜L`6_à:ÖW”P«Ã¡VG@£‰„Jåüâr+j¯µùj°ì ”-W,°æY™…T¥°èÏÁf8«.­ÆC3äPB›?º¢ëªð¡Ô!Ò Y®† i¾Ÿ%·xƽ›—œ?7nDll,bcc}Ýœ&‰È“éŒÆ³0ÏÂjÍóôX,™°X2Q\| Pè¡Õ¶ªT·‚BÑ4²¢«)»Ý“éÂÕ@ê|µnQ3›­6[L¦s•– P©«®k’På€Ãaˆ,nÿ'2Ãá°@TP*ý PøA©ôƒJå/þ­TÐØ¯¾Ö„Ãa…ÅrFãy˜Í`6ÿéq>c"»Ø?––^+•K`}-ÀP?£Ãä€9Ý kºõZÀœg)Ìâ¡üo‡pµLi†]›,` IDAT›& ²‘u ¤‚ʵ±Ô–ŽÐ‡@ÝæZ¬ WA¦† l¾re§N©S§pþüy´oßÞ×ÍiÔ8€öFƒàà`èt¬Õ„ÍVt5`N†ÉtGÕwwòÄá0ÂhLјrµD€Z­6:]yP­VG ¾>(ó&"+L¦4ñ*³Å’Žú4jÙØl…°Ù a2—,U*Ä€Z”’`¸<v–Õý £+J¥¾B@íWáo±ÌY®P4®± D6˜Í—Å“£ò€¹~Ž‘ K,– ÙÀºâÕjµ:juy`MdÝn‚ÃQþSþü™`3•ÁœUKn1,y%°ä—Ân4‚„kÁ²Ãß Ô÷s,CTjhÕàçß †NІûA®†*HÅÝ>N‡àà`h4Moø£·qí%áááˆ÷u3ùD‚Ù|eeÉ0ÏÂbÉhàV¬ÖlX­Ù()9P(´ÐhZ‰µV…BßÀí¸¦©&úÙa·—]GKWƒC½Œ‘å$By99Y°XÒa0\ ¤.y¼òØXØíŰۋ\¬÷º/^ÌCLL0”JwW™ v{ìö²j]•¿v5[…BWéG®¬âO݃o";Ìæ?a6_¸z•ùR­æÔÔtìX»„ÓŠuEåÃt r€l{±ö{ùïb;ìF™×c_GT”%ztþÑð ¨ÐùÅ b¤,ö-ÁÜ·@§NЩS'|úé§¾nJ£Ç´—T?‰Ð‡ÃŽò©òo×2A®~ ª‚B¡† ¨¡P¨ÑT¾št&úha4¦Àd: £1v»4‘É›3L¦s._I«ÕaWƒiçUêÈ'Ø’‰lWƒa#Ž21Øp8ª*“ŸiF¡Ð‰ÁtÅßîþ– ¸›saùûºüjÝn¬pÏ$)»öS^öí·Ç@äÀ­·Öß — ¨ Pèa·—ÀwW¯ëæÓOcîÜá ¬ŸHÈvu¼xÍÇŒ ‚BT ‚4Ð.¿"~íÿòÄèò!&Sˆ¬uÞ•+ñöÛµK8Ù‡¥|þd²F3ì%vØŠmp«q+ëú"J}y¬4(¡0(®ý­UA§kƒ¡ ôú.P©Ü'“6ç¾¥.8‰Ð3N"ô‚„„”•%áÝwgÀüV†¯ý® 1 ¾T«<– ‚ÊeyÅ2…B "@èjÛIü»üCöÚßrerËíöR)°X._-«;¥2€­Áƒp…B¦%´Ú(•þ¨xEC„«Çª¼¬âßrëÉ=F  Š«º€ô@Q¡ìÚÿ•—9àp]^çß•Ëìö2Õm¨L]”q¬ªó«õòUœ¯'ÇÕ׌óÄÒ!)“[O~ç1¯ø¸>_reKŸ_ùeD6™@Ø_ª‚ €F ®=ôúÐhZ_}¿[aµæÁj͖̓›-6›óÿBŸ¶™ÕŽRé‡Ã(ûYCVº›¯þ¶\ûí,#»÷ŸwA-@ ‚2@ ¥¿J¿òÀ¹â ¥Òz}g ]¡×wl’305&œDè_ö‡Ã«5Ç[[»z5°ùÏ=]þásõJCgh4-Vk.ÌæËâÅ’‰ÊYèuápX®Ž?¼Pou²rÎàÒfó”$ÊjK­Ž€^ß:]{ètídiA ¦…øžª¨üŠlA…€ºâïÔç{ÕžRpõĨ= Êôƒ½Ô sQ¬æ,˜M™åCØìÙ°+óÁ÷Ï›B£(’”bÀ¬ÐÉÛ§VG^ ˜»@§sšÁXCãš59J¥z}'èta0t’Ÿ\>gmüýû(O®2›¯Àl¾$Õå_Q3Öü©TAÐéœs{¨TuªïÚL áÐKÞ~X­òÁµÍVPoÉn‚ „B¡… h Ph%;VØí¥p8J¯þ.«·oº+¥Ò:];ñyV«Ã`¾lFÁ70ž30]]S JÄ@‰ò!É$ØAêØÕ¹°©sàÐäÁ¡É…CUj "…FqíªòÕ€Y¡½, ‚J¥sØ‹VÞ¢ÕÆÀ`èZåÐ ÆÐ^b±4ÄoòœèsFÓz}ùUf­6¦ÆãA ®-tº¶b™ÍVPé*uz£HÀªK¢Ose·;pùrÚ¶ õuS•üüò¡J!!—r…½¾½4—ß4Ã[P«Ë§ª“×å3s”ÔùbP UÃÿvþ_ÕÓr ÊD"#ìöRñÇ\Ûí%’rwcú…B®ôúöÐjÛA£‰—Y³¬ÈÚ›…²3׆³¥æ¤¢cxGÙºRB°„Aa ƒ]*,°Ã®Îƒ] »&M.ìê\8TE¨j@*mùi Të¡òƒ:ÄÚ0hàôwŽû¾ ‚JeùooÌGÍ ÊòøN„žqí%99|µ³²ª}ÊÞöâÐ •*¨Þ·¯RC¥ †Ÿ_ù¼ÜåSC¥_ ¦Ëƒêºßh¢æê%ѧ™)-µàÓOã…x&›Š~ý5 0jToètmÅaju‹Fzà A|ß5$¹;–'] P®NUYµòDÚŠAv©L2§Ü 5>\¡ÐB«m{õäHþy¶æYQ¸¿%¿•Hš±2q%ÞžøvÍ6JJ(-PZ"€ ÓÚAaƒM• R•5¤-Tzt- ÐDk i©&ZU`ã 78‰P'zÆI„^€’’y RÃ`è®3ôúöâ.fv{ñÕùV/U¸J]÷lø¦F¡Ð\®Ëp5±Ï…B¥²üA¸öwùÔuª« Š¥âo׿Ë*|µnlö_­×ŒpõŠœtz´Š358grP*ý¡ÕF¡©Ì¾ÓüÑÕù«ågQqήRu^N¡Ð@«m#ËÐh¢Ý^•µÛQðCJŽ—x-ÑO¡ûöÎ;<ª*ýãŸ;3™™Òéz•jATD:ˆ((EPt£â.* "º‹»ŠèÏ®‹(¨kAQ„ * –(¨€H/$B ¤'ÓÛýýqSf2“̤Âù<Ï}fæÜsÏ=Srò½ï}‹â¯¬‰Ö k¡CÛ\‹®…u£úWÞ]P9D¡.¾B¹LÐéZ3%3‚%S†ÚãuÙGû%IÆå²#ËvdÙQôh÷j+}h›çXþ‚” ¥Ù®V‡Ü©(°I¹g¸X¨Õ éFHH7@ÉÇj·Ÿwsû¸PÔS.Ú¯ïï×÷>Åo·¬öÊÊ­ëúh=†Tò]V…â´†N§&Üou§ÉIþ¶| ÿ,D¶×@u>•„*´(/L­d¿S£ S•¼.~® m‚Ë! ëˆ¤¤s,Yò `À€56®Z]û©z”´{Å?¾ñåƒ$©Ñj[ Õ¶ Q£~y6¥‚º4½ §ð.Ý'—X5k¢°IM"IR‰h ¾ß‚ËåÂ0Øoñ&—ÕEÁüQ€Ëø] šHE»‹ãâçêPµHfq³mÛ6¶mÛÆèÙ³çÅžN½Fè:¢Y³fŒ7ŽØØKO H’ºVD—ïJ„‚À+JßMC6ZŠ@ßde)©1/Åu¥6iÈk‹ì)ØY@þ¶|\¦À…³>NÏù¶çé=¤w-ÎîÒC¬-žtíÚ•ØØX¶nÝz±§Rﹼ̇³Ù\òÃ(,^¼ƒAW–åå—_¾ØS¨wú<)¶ öœÀ˜5Ñ"GÚ#TøÔ *Ð-þix÷·@ h˜ŽšÈû)Ûy[ÀÇhÂ5D Œ Ñ•Ä=f Z ‚z„å„…Üs±ž¼˜‹:DMÄ€® Ù1‚:@ü•ÕVký¯jUפ¤¤àpÔLY߆DRRÒÅžB½Ãáp’’r±§QïÈÊÊ* $”r©®-Ž|V\ cYFÀâYÒJDÞIËG[~}x…âY¬-ÞˆµÅ7B³øGè:âüùó{ õŽèSˆ BoD oD¡o.µµEvÉüQÀÙ%g15ù?4áýÃiõh+"G¢Òùÿw.ÖoÄÚâ¡Yü#‚ëáŒ/_XÏXÉÞ-#0?gI%Ú;”ÈA‘h"„¦ vºÅ?â¯O ‚:Æeu‘÷cVTT‚n!D ‰"(6¨Öç'*FèJ`4Ù½{7QQQôèÑãbOG — ÆCFr6åà,tÔ?¸C0‘C#ѵÐÕòÌA èÉÎΦk×®¼óÎ;Ì›7nݺ‘‘‘ðñÂ!ß›K5Ч¶>Þˆ@߈ BßÔ׵ŞkçüççÉ\xÖ6×Ò,¾MïiZ#âY¬-ÞˆµÅ7B³øG艈ˆàرc¬X±‚ 60zôh–,YðñÂ!ß›K-Ч®>Þˆ@߈ BßÔ·µEvÊäoËçì;g1'û¯ð&i%¢FFÑüÁæèÛêklbmñF¬-¾šÅ?"ˆ°Š<ù䓼úê«~û g|@ ¸<±¤[Èþ&û…Àª†t !úæh (¸¨ÝâñZvíÚÅÊ•+Ùµk×ÅžŠ@ ê!N³“¼ò(Ü]P &BCôèhBº„ÔþäAµiðÚb±°k×.víÚűcÇèØ±#sæÌñÙ711‘åË—súôiúöíËã?Nll¬GŸÃ‡3yòdÖ­[G“&Mêâ-à°ß@îæ\œFÿ~Î’JÉç1(•VxU *&==ÇÔ·{÷îÄÅÅÕòŒ._¼€NHH`Ê”)4mÚ‹ÅBŸ>}| èõë×3qâD&NœÈ°aÃøä“OHHHà·ß~#::P0ÆÇgŸ}ÆUW]U©y‡|oRRRhÛ¶-MƒÿVФ¤$ºvíz±§Q¯p8¤¥¥Ñ±cÇ‹=•zEqaÙ ýË‹µ¶Ø³íd›å¸% þºV:bÆÄ m¦­å™)\ÊkË‘#Gøõ_9qúC¯JÿkûÓ¨Q£j{©­-N§“§Láa£±Â~²l÷î*ŸÇjµ¢Ó‰¬/Ñà/w‡Ê©S§ÈÈÈ ÿþåö›7o·Þz+Ÿþ9sçÎåÇääÉ“|ðÁ€’…cðàÁôïߟ}ûöñÞ{ïñÝwß<áïM} ô©/ˆ@oD oD¡oêzm‘2y¿äqöݳ‰g•^EÌ-14 y‰g¸4×–œœnžt3ƒŸÌC¿=Ä+™¯0véXúŒêÊ5+ª=þ¥¶¶´k׎^r£ÝÎär¶ívz H»víª|¡YüsYŽ5 ‹ÅBbb¢GûîÝ»¹êª«xë­·˜9sfI{¿~ý°X,ìß¿ŸœœÞÿ}ãÚ·oϤI“üžW8ã AÃÄ’V$˜X`hP¢GE£S×òÌ.}œN'W¸š½ÝöBã2;eˆú=ŠE÷/âžÉ÷Tzìýö3÷ù¹œ+<‡Ùa&6$–ÁW æù§ž¯ò‹ãÇ3{þlÒ3Ó1ÛÍÄ„Æ0nØ8ž˜ù’$Ui̲lÛ¶üã%ú&'²Üî»ìûÔ ötÄÿþ7Té¼úÜöÏçþÉá¸ÃŠxNÜSžK{!—¿ñoòòò*uŽWß~•þ#ú³%r û$u@*;®ÜÁkŸ¾Fëz››ðx‹…Y³ž¥i‹ÖôÝ› Í7°ïº}xŒ?NýÁSßý›æÝâ¸ÿþy¬[·©ÚŸË¾}û8|øA~µã4¸÷: $Øc8|x´GŽxçˆ/Ò$;väöÛow¶@X ·ß~›Y³fqîÜ9š5kVÒ¾páBæÌ™CAAAµ|­âÅ•œ@ 4 ÷’·%§)€ AµDøõáD Œ@$lV•¡÷ˆÞì¿a…}ÔIjþwëÿ¸oÊ}¨$ÿŸïƒùØHÎ 8ç»CŒÎÍ·_|ÐóòòèÔù~²¢þ€»Ê)®–%Á²AŒÑ‹5kÞ hÜòX—À„;ezh ¹Bû8/©±a ÂÄ?5Áüxx)κu0nܸ*Gèÿˆè-@«U|ÐL&ÏÛ!Æ"'ý   jŸ£°°¼¼<ôz=z}Í%Æ¿”A„¾¹”}j‹K-Ч®A„¾©­µÅ–i#ç›,' Ô·Ñ3&† ÆÕÿR\JkKzN:ç¬åˆ\7œÍœLû`&?H„>‚èàè ·W…sýÝÆuy@tÑëhØ~|;ýõWÀÉ U;a|•‰ce躟\Ck ¬²ŒÕåÂârau¹J^ûj+ym2aMN&kß>¿ë,!ØøŽ1,‹NSü)Cæù´ÛžÀ±X,X, k$H³!#” ТE :DûöíKÚ8@tttÞ;wòì³Ï2jÔ(FUíñ‹/æÙgŸ%22òbO¥^ñòË/‹«þ2ú¼ùfõ¬7 âÛ¬Uµ25Tjzm‘2y¿æQð[²ÓÿM[uˆš¨áQ„ö ­1ßך ºkË«¯¾ÇñãÅ"Ú·oÆ“ON¯ÔØÇ²±5}+[OnekúVŽç‡ü4z‘ɳä“g-äx^:H*@R¶âç’ L„D–î³J°Ë·E*ÔätVqÝ’» ê£I$©‘$ 2’J’ IRƒ¤F–%¬Ã;@«Ž ©•1Tj@ ª Pi•Ç©Aü‘߈Q¿,E§Ñ¡×èÑ©uÏÕèP¹ô¨\:œv rVA'Ž¡?uŒà '‘d—‡$î/ÁVà–¢Ÿäf-t‘àî“°ô-à?•ú@Iç»iÓ&vîÜÉСC+}üå„pá@ñîС/½ôÿüç?KÚ»wïN“&M¼úW–øøxl.‹?ú<7YÆ%ËÞíì“)Z$ @–KŸ—ÝçöY®x_Q{Ù¾µ…ûÈîsÀí=•í뵿L¿ºD–e$ÏGJ¿§ªì« Êû­¸·—÷qß_2VÙcËŒ]üÅíyymH’ç~ðùº˜’eËí;÷XÊŠÚË[ÞÊûŒ+úyü¾ÊŒð>÷9TôÊ{_ÅmeÆq®22x·WÔ×­Íë÷Y4§²¿Q¯ï¤œß±\NŸ²ýjƒòÆ/÷³–ÁQèÀ‘ãÀ‘ëÀ–cÇ‘ë@¶½/©üqddôíô4êÓI'ùü |Wæ;®ÍϤìw[ü?Å}_y¯ÿýŸ÷8svàÝО€ 8BÀÞlCA ¢E‹u<óÌžc»Ó)»8Sx†”œTŽç'5'•B»Aé,¹­2û€ZrÛ'¹=ª:«¥_·þèc‚1ÙM˜fÌvsÉcq›Kv)É@?’8’ÄÂ:0fоü.*´É‡ÎÙÊí£ê»U¥"%HæX{™ähJ‚²•}Å@AOàÔjø4˜ϽÍäÉ“xÞÿ 4JZ˜Áƒ³fÍ}ôQôz=;vìàØ±cÌ›7¯FÎqØdâ­3gjd,@ Ô28MNœ'΢Íàô°2»\. -…俣×é oîó®¤:DMpç`4‘*°!°¤uB~~>iéiœ9†–M[Ò6®-•Ç N „| ²!ÊU´Ç¹G ù˜îÄÙˆs6[éq.g Ïp2ï$éùéœ*8…ÍisYâ.¦ #."Ž6mжֲeçL­}g›À Í¥V ë;ÜïÜmNf‡™e'—‘KnÅ Ð)®±­×(Ùí’¦ôQy´š­Ùº6~†,p@v„„éué#ÄrÁÜ!t>Ü곃áX,‹t\¸L@SeŸ¹#l*,:G±‡›J íŒ%IµCƒЧNâšk® 77Y–K·mÛVâS¹xñb†ÎW\A·nÝHLLdüøñU¾zóšÇ®]$ÌK×#è:bDŒ)‚‘Ái,#–Î ]2²s²IMOÅdÃä„BОÓ¡‹ KÇ.¨Õj$•„®}œ¾â[—ËÅ÷?Ï‘³G( -D–Ù•¼‹F{Ñ­E7F JU™ÀF‚WÕ™Þê!ÊWe®ոTIÎI.Ìg Ïâ”}\Fé£ÁÙ†6mˆŽöØŸy6“½§÷bjaòÈ&$¢3¢?q|@3תµhÕZÚ·hÏnÃnä°ò¿÷HC$cG%44´Ü>² éé°ï°ê7YÍ´¬àü«$T†^´HGS)ƒ6Öô2%c:OÙ{²$‘Îé¦áœjBN¨ŒC¶`“­h3ó±¸Ü|ðƒáL£Ò稵`·U) !!„„¶mÛVåx— ^@GGG—ë7Ù´iÓ’çݺuc÷îÝ|÷Ýwœ² §NÁ¡Cpø0”ÖêéÆ÷Ò^î“]øâ ࣹ3ôí ¡ À»SHtê;#uè@Ž&À•eº>|˜Õ[¾…V¥Vù‚Ž.pÚÀnÙ² ÒBhÛ¶mÀŸM1ãÆcܸqL™2¥ÒÇ^n4x°¹I“&%~?5!+˧ˌ˖1hölôá•nÈl{÷]q±U›ÑÈÎeËõÌ3{*õŠô?ÿwµœJ– [†mï~Àµcÿ.¤jYœN''NŸðÏÅ8ôŽ„<¤š¯N¤à¸á8Îæn–ßoÀ4ŪèŒtrüÜqN¤ ][E(ÊÈXV, f‡Yy´+‡³:¢ý¤î‹Fñ T’ŠZ”¸dÄEÄ¡×T>(¿}»ö´o×Y–±Ûí%™³*‹Ýngß¾ÌÆ ð×÷Ð"B ð}ÜÒÒá,ìÅ gwôAûKtzº"šÂBÏ1% Tª ²œ=9Ã>ŸVèÀ½r6º2nª&MÐu숮[7tíÚ¡S©Ð©Tè‹u*:Iòhûeß>V¯MûoÐÔ\$˜Ë|'gÔ9 ZÄΟ?O\\\•¿hðº>‘ôý÷Ävè ¬‹E1ä!ž½Ñ‡‡‹ß‹„pGž[† {¦½Äcà”¹Õ377‹Îâ%žedrÂr8u§Úɱ“ÇrcýÐîûSlßáiž·äM±&¾Lü}7=‡«Ãêáïë…ÿå×T Ù‚Ñô.®êÔ’ uÍ¥ð“$©Ê⹕J´ëßáÄIФ@‡«à`;ptg¸,Øòü¼Aþ=ù9€KNÀ%¡B¢{‰ë®•èÝæ?Oö…»øÃõ3ÿužEír¡q:ÑÙí$Ûí ¶Ù˜f·£v¹»þztÇ£»ùf´U¸£–L[•ƒŒµÖÑVäÖe‚–OKè¾ Yl0!!!•?))É«àŠÀ7B@×s¼2$”ió5íþ\®p9  ’¤ÒG÷ç(kIÙ6UÙŒ+>2â¸gcÁ½Ÿ>ý}dÌ©ŠÛ°ÓàÄrÒ‚õ¤Õ­˜‰ïgå_’ƒÈ-Fñ¼ )„»r0ÛÍô…ô õ…âPÙ¡(;‚ólW£V«}fòõ”=—¿ãªÊ)‡ K²’ýÁYVNPô¾-I2–è²yUdŸ}9!+9ƒŠÚÊî—»Œü[6oêË£o†Ñ£¡eK·ï¾lÆ(_mìó÷»VI’,{µðÙÆ O·VÒ©¸â@X”®Æý¬y¤’B*­J>6µnº &M‚;î€ÆE¥Åóò‚øß¶U̳l$Çô;‘²~n÷"T îÉmGT#šñÉ—‹ý~w1bÄNœÕjå‘'á§Ÿ~¡ø’„å‡1´ïPŸXŒN§«ÖyþºŽ¸®S'Þ{ào!LÑ»/‘Lͦ $…”ÞîéÈj‰â9¸?//ÍU ©µêŸâ ‚ì« |¦¬Âóû®(õœÏc}ô)Þ_QJ¼ŠR)ú|€èò•J®¼ôr~SË}UÚWvÌrÚ«óÊ~eÅoñó†„ËâÂxȈqŸKºå×PQ*hkÑ6W¶œkù4ÿEìEÙdÑ\&­Xöïl.dl·±ÜÖå6Z‡·®™ùT“ã¹Ç1ïû»Ü¿« IDAT bNW¬ÆeˆJ‹bäÄ‘D飈 Žòz<ÅÛ¯Eñç¯Q ã>¸ÆPþ˜;ÂöD"Û"8{>ü>š«ˆÏøx˜0*ˆÍ«U$µÉìK`q7j5 X*š›4)Óá×_‰X¼˜}yû‘œN €‡åE»wJq÷Å3üé§„× »¢N§ãÃEâr¹8zô(]ºt©vÜV×®]éÚµ+ 51ÍÐu„l³~‘ƒåÜÅI‰pÿ'[ÇÿpE%Bß\JÕÂê Q‰Ð7 ¾¡ æãf { ˜’LÈöÀ.“O朤Ud+Ô*ï5WÒH5 B×\W"˜µMµHšÒõ¯a4oi°·¬ d—Ì–[Ørb 3¾›Aßf}¹­ËmŒí:–¾ÍúVúíV§ìä›cßðîŸïò}ê÷È:YÉ;ÜÜ­SàöS‘2$ù¯Ík¼”˜÷¬YSÚ¦ÑtıM‚ŽÛÀWÜjðû•\ѹÓ§ÃÇÃ_)×}‰‰Ê6cLœ¨ˆéo¬ó;eÓ´ ð\[úö…M›|ˆf“ –/‡Å‹áÀë“ð뮣[D;¾ÿžk].µiû Ö¨p.‹J¥¢[·n5>®ÕjVl?åRG¤¦¦²wï^š5kV’FïrGT"ô¨Dè¨D蛆Z‰Ðžeǰ׀q¿G£ÒÇ/Û¹ŒÙƒfIPÓ ¡¬k®#¨q’ºbÕöÔº1§™¡+P޵Tõ“Ž X=Ñ¡!œ3*¥¡÷dìaOÆü²€Öá­¹­ËmÜÖå6µ„V]=ßÝò8Sx†wÈ}À™B·Z×@ð¦`Ì·šKý–·Å?tLêÈ3¯yÆdeÁsÏÁ{ï½èú!8{ Ö®Í ;û:ò??ƒ½ÏYä^V¥ZtHûuímA„öZ\®ý<ò<ò<Ÿ|¢hÎóç•̬líÛÃßþ¦leÝ ùå—_ú š7o^aÙí³gaÃøòK çÎe{Ýϼ<áÑÖ´i§xNIwÞQ&ž—WÚ®×Ô)Ê•Á•W2³ €‡z÷fVz:ÝÇ«Uñ\ddd‘‘Ajj*Ý»w¿ØÓ©×\V•/ñññ>|˜»îº‹þýûÓ¿ÿ‹=%@ ¨W8ÍNŒ ëik•ÇQ‡ª íJXï0´Íµ•r(v¸ÌÞ4›%.<`“C€&nÿ&]À1°ïnZFH88ŸTS*_ýšõG׳ÿü~¯qÃuáŒê8б]Æ2ºÓh"õ¾ùùùÌ|j&O$ßœODp=Úõàíßö(|"#ó}ê÷¼·ë=6Ýà‘c9BÁ=½ïáWýƒ³Ïòà³’~M:5  îÏ8>xöF QQÍfxóMxùåÒ,k*•"nŸZ¶„³gÏb+*òcâ|—øégÓ‰kÇèA£:H)ý¬ÕjiÑ¢…çgë€1ýÍ7àVgI‚Áƒ«ôøñJF·¬¬,z7mÁŠÜ½Êã'àüUøåÏŸ=Ú÷탯¿V¶b+8\ 1íyˆŠ/fþÄAN¯¾lßó³2éÅ‹aófOóuÛ¶ðÐCðÀãqü =Æw_|ÁÆääKN@oß¾íÛ·óÅ_н{waÌ©! ëQS ¼‘]2–T‹â¢qÔ„ì¨Ú¿#I-Ü9˜°Þaw ökaöE®%—‰_NäÇ?* …Íà‹•`X ‘ ºŒA¹ Àq-[ÎæàAÏ»hiyi|}ôk¾>ú5¿œü‡ËÓ‚®QiØf âêÑe,m#Ûðç_r×ì»HíŠÜØÍ_?S¢Ã¾|öÆgtèÑ¥{–òþ_ïs<÷¸Ç¸ýZöcúÕÓ™Üc2Áš`úôI~~sœNYŽßqè È!$“%ŒXÍõ¨Õ!DDœcΜÍüç?JŽãbF‚W_…ž=KÛNž¿’´!iJ1’²8 ü›p,c-ؤRᦠ㮞w1ýêé^>×}úijoß'e*ö!ËŸ¯D+fg+YYeg36+‹±M²±ŽÈâÂá ÓŽ.k˜…•;ñtg_Lç$ÓXÔR”<Î ì*Ú^ñ_Z±8´«‚×÷;Ü=o£‹ÜQ¾Ójúä“—½x…TGh@ T§Ù‰éˆ ãA#Ö4+²«êªY¥UÒMqÑеÕÕZ5ÎäœdnýâVŽf+•Ûú6ëË×S¾&ÈÜŠéÓáÃÁY‰˜FµZ韒¢d3[°ž|RyîÏÂø‰ñl]º•¼¾yåö‰LŽäo÷ü- ñœNÅÐ[[Ì~å Â|·ØžE€GÎ 71\òèï¹›Ë!/{<Ë™3Š}8Œf¬bwbá9Úq÷PjŠçAËÝJ‰ÃJ †^¸À·Ï<Ã-6Kãâøâ±šwÝ4\„€®#D¡7"ˆÐ7"ˆÐD蛺"”m2¦£Šh6§˜‘UÍ’Z"¸c0!=Bé‚J[½ÄîøZ[~:ñ¾œP,xG·;xÔg¼÷v¯¼……J?ââàøq)ÊФ‰•LJ·Þ‚ôte›1þïÿ`î\˜>=°Ø³;n»ƒV|Ħs›p5wyíWSq­t-ãÇŽ÷;Ö×_+Öp÷”t¥$Q6ˆ°Ò\½`oZ­ä‘YŸ»wçê¥K}ŠášÀÀ#¼À»È¤“ÆC(â¹zÜÿØcLùàΦ§3lÚ4´ÚÚ)vs)"‚ý#”Kq¾‚¨åËDèDè"ôM]ÊS² ÓA¦c—Óö…¤’еÕÖ3Œn!•òkö;OYæ¿KþËúŸÖsè¯C´îÚšvÍÚñÆü7ø>ç{fnœY,øïÿM§ÓÏÓç ‰Ó§KǸõV%÷ñ®]7‘à?“„N§ãá‡uÌš¥¤a{é%HN†Œ E@¿ôÌž 3g*™Ð*bÕ«¸Öý$îL$³U&ÄÙÐøtc5ÄÒ—Vxüo¿)¾Ìgó"¬YYpË-°s'³EAAÌ·ÛYÔ¸1³?þ* ¬²lE‘ç iÄó8oaàn·vÿ:å¡Õj0u*ÿyí5N ë³çÏŸ'..îbO£^#ò@×ñññ¤§§3kÖ,‘…C Ô\`>nÆxЈ)É„Ëâm  ô­õ„ö%¤{ê°ªçköÅ/¿üί¿îäýUor¡[¶nwûŒ Ý¬ÃÖÅ W€N­ãñN±éÕ»Ù½»´Û•W*qoƒUo.N'|ù%¼ø¢Rº˜ˆ%Úc)ڊسgë7¯g÷ÁÝ\ÙãJÆŽKß¾}Ëíè<õ”by.&:ôúxΞýÄïœ{÷Žgï^ÿýHO‡‘#!)Iy}Ë-LÍÍåÿ~ÿÿ Àò@ÇÄl63mÚ<ÜoÞº\..\H§Y³¶}Ç»‰©So¯Òyl6›6nä¶±c«1Û†CqŽ·Þz‹¸¸8aÌ©a²,c=mŸ߈ñ—©¢Ð6×Ú#”Сh"jï_ÍÛoÉW[vÃÔtÏ’Û¡`»Ã ß‚*JGßœoxñéÒ !­[+b÷î»ËÏ}\Ôj˜2&O†õë•ôp»v)YÛ^|-‚üC±N7/›fºˆ¾}ûV(˜‹9u æÏ‡eËÀUôU+éèþõ/xüñæ$'Çû§S§r&âÎáÊx.6×ßs,]ÊìÝ»2d_¾ñ†ÿ1*Ipp0Ÿ^ûwœ´Z­Ï‚*!,Ðu€¨è#ê%2XÒ-˜›01â,¨zU@€ Ø Ñ[ó4|1|øT~(Ø£³Ëïd>l™G€HÂÑùØc• ö« ›7+>ÑEÞ6ètpÿýŠËE›60mÚ<’“Ïù«S§æ¼úêK¼ô,^ –¢Ú"jµ’JoÁhQÓ‰:þøÆŒ)Jœ3^½äŠãý÷ÞãïÓ§×ðI¡[ü#,Ðu„"ôFúFz#‚}S• BÙ%c=iÅx؈鈩Z¥´4‘šѬmV÷AXçóÒ¡WñœƒÝVìb­‚,h4ð÷¿Ã³ÏBãÆu3¿‘#•í×_!½e X­ðî»JÖ©Sá?Α”ô‰ß±’“ãùê+ÈsKÖqûíŠu;%£ÒkËÆJv “IyýÒKÊ•‡—ºxk‹oD¡„r©#D¡7"ˆÐ7"ˆÐDè›@ƒe§Œå„EÍI¦j»g¨ÃÔ„^JHt­j/íœ?.œéP6@o'0p³.K!N­Üpƒÿq}jé„Á¥Ðuį¿þJ||<ãÆ«Õ¢—"ˆÐ7"ˆÐèã)ÙDzb:æ43QAQÕK¥UÜ)˜î!w ®Ñª€î¼òÊ{üßâ§1LÉ‚²úT}e6üøËÇ}‰ýj#V§gðµÆ†|ôœÇãJ ¶P”Òÿ„Uò <­¸th=lë¹_ µ|·Rs:}:“/f¬+t‚^Ï-3fÔØÚeÊÜv›’& woÅòܬYµÎu© ÖOŠ|C†~ÇærEÖÂ_ Ô–4 Y Y8òþ;û@¬R|𻇢o¯GÒÔmöŒn}¯%éÚà¯vÇW‘}ï!ï÷)pb(8•œÒ!!pÇJaÃw†;ïœÍÆm¤à ‡ÔéVsìØWþ/Ø÷íS"ûV¯Æqì“ÕPb…^ ñ¾ŽU© U«RQí¾µo圻gÏ)4>¸ŸH+wZChKfþ8°¢âùW†Œ 5JyÏ ¤öذÁÝqAƒGèÿ ´@ Ôsd‡LîO¹üQ@FJŸ¨BT„v %¤[úvú ¬,ùÖ|²5ç~$×]wðöUöäÖòÓííÝ["šIN.iÖc$‰u’Ä.—b}ŽŽfÕ!pü¸bÎÏ/ÇåRJ^§§ÃÏ?{Ÿ':Ú§¸nîr²‘ìã}zûø‚÷!qˆ‘4Wû{nïöÆ ÏÈ ¨<§i»ûéÓØílÅ ½jUíW–B@ A=ÆvÞFÖÚ,lçm£S+)纇¢k£«±”s•Åh7²áèV\ɦ”MX;ZáàÏeÛ¨¥sç î»O)4ÒªUù]³OâÀ¼y w–_F5mÊÌ™3K÷ì)Í))žHôï'2uÜ8î5ŠÛc­^Ï­  ™1ÃmÙŠöµ;§¤‚+&'GÙþüÓãtßç$5O"ñ™›ÂOjBзÈG©ÿ˜=›ŒûîcZaa¹}>š’ïùƒ”|Ï „€®#D%BoD¡oD¡7—e  ù¿ç“÷S²Ó·Ù9×” @THêp5¡Ý‹,ÍqzÊÍ¿VËX6¦ldåÁ•|sìLvSéÎvÀ:5tu–ï–œL¨«;v”ëñàÁ”`ý›orç‘#%o9%»]ñʲ&8˜ÿþ7j7÷ RS=rÍL˜ ¤Æ(ãÖ™3YóĬˆ‹ã˲™7bb”­_?ïəͥ–ê²[ZšRI¥ˆæ²“vÀ> ·Ûû€kF6l‚¨6J>æ6m m[å±øyLŒÇ©ÇŒϤçžãoû÷S\T= (^Yì(ÂýË'ž€W_­ànØ\–kKˆJ„þÊ¥Ž•½A„¾A„Þ\n>Ž<Yë²°œ¬ø–ýŸé¢Ws÷Ãw+–æJV÷Ù]v¶¤naåÁ•¬?ºÞ3O3€9 ŽÜ‡r }¬Æfy‹èó*ØØÈ°ÀËë©ÕjÆÎšÅWsæ0Ál`1ð,J5oXª×^SĬ;’¤$a.Í嘺§NŸNÿ—^bÆÌ™•»à†+®P¶²8pꔇ¨ž}äs6oæ[é݆…ÀA©ß—Wê¯\–ÐP/a}ïС,KIaZQ)î—)-ײ ¸÷Þ{/kñ —ßÚ(¢¡Da œñA î)$wS®ÿ"(4º¦Q㪜A#!!Ûo7· ù ‚•æAà„ß²n]¨G N§ì$1-‘Ov­dý±µ:ʸXÁѱpðNH Î $i6²ü,¨·AìãÐ3Ú@Ž47€á#Z¶|†ƒ¿¸v:ÜÙ£«“’¼ ï«+0µ¸A’àúëKEsË–ã‡~`РAµ~Çì™éÓ¹ãý÷é#Ëì•$ÖÆs#G*ë´48yRy¬À5ÃIÀçPb…Åú|wëÖ|™ž^ÓÓ4„nñ°@ A=Àit’½!S’Éo_u#5±ãb îPqæ‰ÀÍÏû$ K…–E6•3à‡w ëUp(yçì™å¿üƲ¿V²£p M™;kö`86N†äÑÄDè¹öZ¸îÅÐûöÛ°~=àç‡Áù] Û Ž6à¼Åñ¢ò¨ÕjÆ=ü0kæÎe¢›õVV«$ n¸AÍãÇ,šÝ6lX•æVYf¿ø"mÚIJ“'YÇ•+•àòäæz‹êâÇ´4ÅZ Ü‹bmžævè²à`î]¸°–߉@аZ .2¦£&²¿ÎÆi,?®˜Ð+B‰ƒ*¸†ò6K)Ðá ¸3ÃÓ¥¢£ íSàˇ!w ÓV§’³m&r£ÓÊþâÿN-¤ŒB:4™+4·rÃ5a\÷OE0wîìyªw=j™è`P½ù''ûï2å“O˜d³1R÷ïÕ*ã&L@½p¡’8ú ::š6£FññÐfÔ(¢}‰g€¨(eëÛ×÷þ‚HKcLZ“zˆ¿=KE¾Ï:ñåøñµõ‚Ë! ëDè"ô"ô¦¡úÈ6™ìMÙvüöUéUDŽ&¬Wi·¬¬,bcc«>‰è%0!ÃwPŸ ¸#Ö-$Û] »4Jw×dní4ŽADÒ¯4jTñ©š6 §S§HRÅóKªŠjV;J¾âwÞ~YF ܬ–$®”eâ€U;óå_\rÙ%f¿ø"}׬aÏ‹/V}ðpèÕ zõâ^»e÷ÝÇ€ÂB¶5jĽϖt Yë²päú/Š¢o§'v\,šÏ%{Û¶mU^Sòòò E§ƒlY´@4P(ÑBwÛOæ¡›ÆsmÏÊ‹ö%Kž«Òüþ÷?%Ï¡C™2}:“ž~šß’’è­×sû£¢¾ÄÄ3(Vè·o/ßú\IŠ3r$îß­];a}v£!®-Õ¡¸”wbb"#G޼ØÓ©×ˆ Â:@8ã ‚bd§L^b¿ »*^~%DäÐHÂû‡W:ÃF <úèÞ:þ\m¯¸ã5÷šÍëϽ^­ó­Z¶Œ?6lðÛO aþ[o•^\oݪX›¿úÊ#õ=]º°ü½÷PÏ™Ãê¸8V:tI èÚà›¯¾ba|<}ò c„€øAèÿ ´@ Ô¶ 6²Öea;ç¿(ж™–Ø;bÑ6ÑÖø<¶o‡×_‡µkc¡__ña´Þ®ÚçmÓ±#~ú‰¹¹¹åö1sûõ#R£Qœ¦ß}ðìÔ§<ü0Ü}·RßÛ)>ȵ/¼ÀìÙ³…xvcÌøñ|÷Í7B< 5„Ð@PËȲLÁöò~ÌCvø¹é'AÄ DŽ¬Ñ²Û.—’ã7à·ßŠÕMá‚ë3ÀI-j ¯ÿ 7°¸kW4üAX9}Þ‹Œdv«VJП{ª6NÉ¢ñðÃJ„b9¨ÕjÞX¾œªœØY²téÅž‚@Ð`¨¡0n?D¡7)))8þý?/7’’’.öꇃ”²å–/d›Œa¯Œ2ÈÝœëW{6LŸ:Tï\ ÊÝâá-UÄeqac2Šsó¶¢=Å^Ñ«Ðs‚Ñ´l©æpæa&­žÄ¡ÌC4 mÊò;–3¬ý°’98(ޤ¼ŒÉN`b÷î 8°´qß¾RÁüÛoŠ«FYZ¶TÄòÈ‘0|8DEyu™ùúëüߘ1œîÜ™ßpƒÿ5@ÄÚâ±¶x#*úæüùóÄÅÅ]ìiÔkDa%xá… âÅ_$))‰fÍštœpÆ.d§Œù˜Ã^æ3²³òKdRRŸ¯‚~ç>¢çPìÃEû;…–¡XÐba4ô›Ã˾‘8éÉ¿ÙÇQÊ&•S¬Ï]8À Ìø(‘¥K1ÙM m7”åw,§Y˜÷šµòãqÌšÅÔr²V, Cóâ‹LnÜXÍ›7+Á€eÑéàÆÁ#ƃFœ¦Š]4*B§ã¯¼¿¸ãqv®gW±‘Ó}^šT0ŽKRãÐèqéA¯Ç*99“­e-2Ý9Íx<—î5¨HPÝD‡.'0¶Nè³VÅ-}'1ášx¤°0 õÚ\*zödõáÃ^Vh'018˜5V+*—Ë{’;— æAƒ¼ªBFFFÀk©@ ¨„nñpá—-ÎB'†ý { Ø3¨ÈWšH a½ÃíJPt†•ì@WrhE.I@±çiMÅâ@%;ÑÚ`7‚ ‚H  0¸J¬Ð2°+]?£9)ÅkW+Ë?Vˤ  VHSËØSV“ÌæÒ‚Á!¥¾Ìíª_Þ[ˆg@p)"tŒF#§OŸ¦S§N¨T5WgFz#}|#}¼©é@ã!#†=Ì©f¨â=8I+Ú=”°>aèÚè$‰óçáíÿÀ¢E0-,’&`<|XtÜaÑ9”!I]Ñ›nfþ?í\Û»·R2ÐbñÞŠÚO%'³c[ ú¦çèdsñU6 *šû/ ÐÑ ¡ÐTæ=ÙlL²Ù˜L+´X ¬éÛ·T0_=T7t Ö߈µÅDèD蟿ºìß¿Ÿ^x={ö’’ÂÀILLôêg³ÙxàX±bN§“ÈÈHÞÿ}&NœX#óA„Þˆ@߈@oj2ÐÇ”d"sufÕ–@ßVOXŸ0Bº… Ò*ÙIIðßÿ§ŸBˆ5—y ü2è%8ÓŽ-»:@ÆmFh¾éÀßûOqþŠ£@öÞ½üíΛ0ÝåôXêleß³ÑpðxLÚUzv|’HŸŽ”D£±ôÑý¹Û£ÊhdÒîݬøã¦:”"0+ôz&½ü2ªG­Úguk‹oÄÚâ"ô"ôOƒÐgΜÁh4r×]w±víÚrûÍ™3‡o¾ù†Ÿþ™>}úðꫯ2yòdÚ´iC¿~ýª=ñCôF,X¾ÿ༉ŒŒ¬‘ß‹£ÐAÖ×Yþ;–!(&ˆÐÞ¡„õCQºlnÝ ¯¿®Tœ–e¸‘­|ÎÝ´æ§ÃaêðKcp¾¯“Q\3¥‡ vna_~wTGUl†’­ÐVèñÚ`3‘š­­ˆ Ò®Ó÷ÊË´k•6€ðNa¨ƒC :ZÙd’ËÅ„ž=™rø0kÛ·gÍÌ™•þ¬êbmñX[¼©©µ¥¡!4‹¼€¾ù曹ùæ›ؾ};÷Ú¶E²lÙ2f̘Á7ÞÀóÏ?ÏG}Ä’%KJô3Ï<Ã×_Mvv6Æ ãŠ+®`ÕªUu÷fA•‘e™ì„l\&Áp>PéU„^Qä¢ÑºôV¦ËëÖÁk¯ÁŽJ›'ó¥çø7/ ’•Äu1j¦Ms’S”bù@Û¢BÝNÒ9—gw< iLè8à£g¸^ÚQú¼£ÖÈ… zî¨T*&ÍËŠY³˜4wnº± AC¢Þ­Žyyy¬^½šùóç3~üxn¼ñF|ðA-ZÄÖ­[kåœ;vìÀ`0УLÚ¥^½zñã?–¼þ׿þEbb"™™™lÛ¶?ü°ìPårúôiöîÝKRR’Çf2™<ú‰6Ñ&Új§í¯/ÿ"')Ç£-5+³Ý\òZRIœ 9Cè˜PZÏmMÌ­1èZëHJJ"+ËÄ;ï(‰'&L€;’]ô'9Úì&ž–Ÿã˜ìÄÌî<ÈUŠxÎìŠå¹Äú\ÔF`,ÓVDHP1ÆÚ‡¶§wÓÞô»ªúL}i?œ¼VÙÈ)=6:?šÞ½{W鳚ô·¿±6.ŽeM›2¦ŒûZ}ú.E›hm5ÛV¬I¾ûî;öîÝK~~>‚Š©7:33“yóæÑ¦M&OžÌ§Ÿ~J~~>Í›7gïÞ½<ûì³ 8¾}û²zõj\¾R*U‘³gÏн{wöž={rþüù’s…„„Y²5jÔ(às>|˜•+W’à±eeyÞN¾œÚRRRX»vm½˜K}jKJJª7s©/m‡ƒ”””*g;ocÍGkÈ5åzôû>é{rM¹5"jx-kÉV×VÌMÌH%ÇEf&̘‘@—.Y<òHi¡½ÐÐÞý G´½éñ«ÇðÔSƒ¹)vöŒ"E«èlÅò\l}.j‹Éaýìõüõ÷¿x<êqvÞ½“üåã|Ɖñ)#s£çòó¤ŸÙ;}/;fí OD8P4`è®l%ç0Cë ÖÄÆÆVé³R©TÜùÄ4îÛ—œœœJ[ŸÚRRRp8õb.õ©-))©ÞÌ¥¾´¯-õa.³­X“¼õÖ[¬\¹’ãÇ#¨˜z‘ú‡~`ìØ±\ýõÌ™3‡¡C‡¢Õj½úedd°téR–,YBLL û÷ï¯ÔyF…Åbñ "\¸p!sæÌ!;;›h7ŸÁ%K–0cÆ rrrˆòQE+PâããIOOç§Ÿ~ªò ‘Ù³g‹@ÄÇÇ _Å2äý?{wóñ?püµ›ksÉ}ˆ+îTÜ’¸RTE+gŠ¢¨£´´úëWQ´zD©«Z÷Q”P­ÖMƒ8ãq'±ä¾w÷óûce‰Ý\Ždó|<ö!™Ï|$™¼3;¦L™òLk%•Äõ¹×YüÍZärk½ç“ÌÒ wºŒ$“$ ï¼Ó˜¯¾Ë•+ðý÷°|¹v3ŒÕ«ÃÄQ© :5“å¿éÊ×´vdðëÉÈY·èB®e¹¤ƒß)?Žüs¤Ð÷_î´¹æhgÐî‘—öU lužžž…nói9¿d²§j)=ÄØb˜[ô=ÏØR–µmÛ–Ê•+‹ï—|ÅhWWWÂÃéW¯^¾õÜÝÝùüóÏùä“Oؼyó »¾‹‹ ׯ_Ï@_½z33³2«ÕjÖ­[Gݺuõ–мªÄ€e˜°ô=O¢Oü®x²b³HKs"9¹c®çTÈøoælÍ–€Ýºw‡-[´ës4m Ÿ|ݪœDÞ7._@iƒºÂŸµµ3¶&2:ÕìD§;ñõ´¯¹Ýú6<½ó[6Tø¯+—®,Ò½xzz:7”crÓã&).ÚÓm.ØPùne–Ï]þ\Á3”îÀ9‡[ c‹>‘D˜Û¹sç8wîjõ³&õª0Šº~ýúEªoffF¯^½^Øõ=<<8þ}ˆØÁáÇù÷¿èгÍ›77øÎ Ba) ìííÅXRF@?-11kkkÝ&ø‰‰‰üñÇ8::PˆýR‹ªY³fØÙÙqöìY]™$Iœ9s†®]»¾kT¨PŽ;\Q„B“¦AªÌã ”,nÃ%ÙQÕy|®XXÀ€0~Jrvv~ám—fbl1LŒ-úÄI„¹ÅÄÄè"VÉŸÑì‘ãäÉ“¼öÚkTªT €åË—caaÁÝ»wY³fM‘÷]V*•̵k׸}û¶îó{÷îéêÍœ9“Aƒѯ_?*UªÄÖ­[Ùºu+ 6|!÷uíÚ5öíÛGttô i¯,˜7o)))%Ý £3kÖ¬’î‚ÑÉ9-¬°’O$“™¦ÿ„År¤:„õý“ŒæJ¨©†Ž÷`ø¨Þ¬þÆ¢uí6Õãõ~x}½ˆ ËÏ'Û¾FöÉc,™~†!‡<¿laaa„……•hŒ‘[ c‹¾¢Ž-e]tt4ûöí»p‚QìÂñ¤•+W²xñbÝžÏ~~~4oÞœ9sæ••…—/_ÖØ/šF£!))é…fo"CŠC¶2›»‹î"e?Ú222™·–ÌJ÷¸Øä GjØùB lA ¯DX»ÆhŸRY["›?“ÁÅr‚ %IÄ-3º÷·üüüøè£P«Õܼy“cÇŽñã?`bb‚Z­~©3 r¹\l}$¥”¤–PnRæ ž²²²ÈVÅlqŸÓ©Ç1;G¶6ÃÚ(–YÚðõÅtíZíþu‚ ‚€еkׯÑÑ‘úõë“-[¶Ðíû\¥J•’ìâ3 #88˜€€€—’)$ìM ónf®2I‚C‡L±ÊΠÇéý 9÷öL›eð––HÚ-8>ý¦M³§÷¡A({rT Ãß_?1ZxÌèh€ˆˆ,X@||<Çו;vŒÁƒce•×ÉÆË××W¼ò‘èc˜HôÑW˜DŸŒè æ>~6=6n„k×dœ±?wZ<ó ¿^ ü*A€òåaåJxóÍv/ƒH"4LŒ-†‰±EŸH"Ì-g’/((¨¤»bôŒ.‰ÀÎÎŽI“&ñÍ7ßPµjU]ùСCY°`A öìÙÅÆÆ–tŒŽHô1L$úè+(ÑG“¡A¹9÷–u÷îÁ¢EpíÜ5•áx†9vÚ@ÙµÀ{ÀIJpæŒÑÏ ’ó"ÆÃÄØ¢O$&b–‚Earr2¶¶EËfOJJ¢\¹r/©G/–XŒ//×ý÷I=—ªûüÔ)øë/P© Óí<;ŽæÁíkXžƒ"Ñ›…V½dà[£ŸFÞD.7ʹA„b!â–‚Åo‰Í›7S¯^=~ýõW233ó­{ñâEFŒAõR–ÐsöìYfÍš%f‹áK9¢ žÕjmà¼e ¨L þjŽäÕ5¨ö®å™«ëÍB¯’Á­ vtÛ¶[Ï‚ ¼²Â˜5kV®ƒåÃŒbXß¾}‘Éd|ýõ×|þùç´jÕŠ5jP½zu¸zõ*—/_æÜ¹s?~œÀÀ@öîÝ[ÒÝ.’Ê•+ Ö*  ¤JPñðï‡$'ÃúõpûŽ*Ïý\©p.7ð°õ`NÇ9þ/鯷duØ!…\Œ"€611¡ÿþôíÛ—ßÿ7²iÓ&®^½ŠZ­ÆÖÖoooš4i²eËJe„B¡(•ý~™D¢a"ÑGŸ¡DI£Ý²N“©áÆ Ø°RMnÏ6°Ž#É2‰ã53Êo_·ýšr&V0r$“ÃôL€ÕÖÖLüyA© žE¡abl1LŒ-úDanÎÎÎ8;;£P(Jº+FϨޫ”ËåñÇpéÒ%RRR¸}û6III>|˜Ÿþ¹Ôþð‹ùúD¢a"ÑGŸ¡DŸÄÿɸ™Ax8¬X“Aj…?¡Ño`‡F¦áAëz€¹oÏ¥\r´kóç#‚Yei‰ØR¥ =úõ+‘ûz^"‰Ð01¶&Æ}"‰Ð0³Ì(’Ë:±_ O¥Rå %IâæÍ›<|øš5kbccq Þ&ãØQ[.Å߃ÿ€¹6`2“›Ñ¨G#>þ¦rSmFa@ܸ¡m°Y3¤?þ Ç[oÑùÆ ì, °ÿ’¸UA£$â–‚‰÷·ŠÉáÇ>|8]ºt¡K—.%ÝA0Z)))ôì9ƒÔÔ Tf[Q• Ï $»,d¿Yc~×™€¤ÉX™T3ê<Þû¹¦cMÞmû.Þý‘ÉeÚEÑï¿iiÚ ÁÁ°p!2 úLšÄ·ÿ÷)¥³Ï‚ /Ú¶mÛØ¶m‡¦yóæ%Ý£&èbâççGHHˆX“'…`aQeÖ&xkÔKÓ•K¾Yø®‹åÃHUTPN»Ÿ†™-j½wo<úz C‚É_ÀŒÚš˜ÀwßÁرº¶zôëG//d2Y±Þ› ‚±êر#íÚµãƒ>(é®=£Z]–©T* … Ÿ…J¥*énÈÈÈ’îB‰KÍ>Íÿ{•JETTTIwÃh˜šš¢P(ÄÏO!ˆº˜ˆùúD¢a"ÑRMÎ@ó'ŽåγCf4¿üè-EÀÜnW¡SÍ·±0±Àºž5Ö–w¡Y3ضM[ÏÛŽÕ&–A"‰Ð01¶&Æ}"‰Ð0³L$±_(«âãã™:f RZZu›vîLÐûïXïÁƒ¸ùUC= IW&“dt8Ý÷÷ÇSÁñfy>>S;S 7$$hŸïÚV®„"žr*‚ðªqKÁŒr=Ajj*³gÏfýúõ\½z•ýû÷Ó¬Y3–.]ÊÝ»w™¯6NaaaZÒ]„jìwß±ÚÉ {0ø0®Öª…ëÖù¶s6î,„ޤæBo4féÈ5rÜÝht½‘þ M°3·Åî¿(fŒÑÏÖÖÚU¦NÁ³ B…††,–†‚ÑÍ@Ÿ8q‚¿ÿþ›ˆˆ6lÈîÝ»uÏ5nܘÄÄDnß¾MÕªUK°—Eçëë+Þ yŠ8-̰ÒvZ˜ëÖü\£)œ…þÙɉ‘ß|ƒ&Cƒ&]ûP§©Ñ¤kÈHÉ`ÏÅ=üuj;×c¯¢ÈVðVFk,Ò,P,³ÄÌ^û½¡‘4$e%Q΢œ¶Q Xݰ¦¹ì ö·ÖkË<=a˨_¿XîÛˆ“ c‹a¥ml)â$ÂÜ ((¨¤»bôŒnt‰ŒŒ¤V­Z4lØОN˜#;;SSÓR™"äë›7oS¦LÁÞÞ¾¤»bTfÍšUêþØùÍ7ÌëÞqMH¡j,PcA:`U“Šû«psïM]ý‡é9~÷8§bN‘®JGT£H2PVÇæz4­4jd2 dŠŠõR$A2?dˆ&ž¶#CmÚhgž_±@2g–(  „{b\ÄØbXi[^¶œ$Â’îŠQ‰¥råÊ%Ý £ft´§§'W¯^%..WW×\ô–-[P«ÕT¯^½{ølÄ7¢>1`VÁÉÌMù+Ó–øc†™®ü0Ðì¶æþ ÎÎ<°–‰’‹Ò}”V™3eÚ½ƸªãïkÎŽ+˱̺O]I €9­ñ{ô±ÖŽ2†D9BBàœm³abl1¬4Ž-/›½½½ø~1@Ä,3ºß87¦J•*Œ=šiÓ¦!“ÉP©TlÚ´‰™3gÒ­[7¬¬¬Jº›‚ ÉÉÉ4jô6ªÌX‚Ü_çÈUSü=—Ä-% ”JP*qZ>z¤˜š ”9‘fV g/K\ëÜGãà@f;?"öï¡nªáw›.p ùn|ú9Õg~ýÒïSAždt´¥¥%ëÖ­#00ÚµkcjjJÇŽIMM¥Q£Fb¿FA0"jµy tkNÿH¶  œÍÿL ³œãÀpÈù³È6*56ÄAvœNj3›}ÌÌ0µ´$R&Ãë©6夳Âô«vïÅ´U«âºUAAÐ1º´ w‘‘‘lÞ¼™óçÏcbbBýúõéÖ­&&&%ݽg’™™YÒ]0:"ÑǰҔè“™APFú]ÊÆT>2“ƒ¿ÎYCB5¸Úí8ÈÓ;âxßÚŽñ4ª¤ÄIRêf§yòg$;›úÙÙl¼5n“‚'Öœ’¤ãÿ&Šà‘D˜1¶VšÆ–â"’ ËÌÌÄ¢¤»aÔŒvt±´´¤OŸ>%ÝF$ê‰>†•–DŸ”Ó)$Ì?OŸ´lÌ$íòä^pëd¤Abµ'*[‘¶x¸6 M_¨Q˹BŽÜJމ¥ rK9òŒDä÷oc{ ùkÈoEáqþ$‰qø“Á&Ò AÃøµX7iR ݵqI„†‰±Å°Ò2¶'‘Dh˜H",˜QŸDxéÒ%"##yº‹¥í—EΞŠþþþº-b¡4K>žÌƒ%ç–-C–™À–Úpª<È@ç;]#Ö>–LÓL2c2)¶ GŽnÖË…Û‚^¥Rñ^:l¼r°I¡ iöl‚Gz‰w'‚ðj %44T³ˆ?¸òf”ôÌ™3Y¼x1ÑÑÑŸ7Â.çK‰)”%IáI\[t«50ÏR°­œxb²â¼ÓyŽÕ;ö¸ Ô•ö®ù÷ßåE¾Þ²yó°ýäºgdX«¿Ÿ?/ÞšAx‰DÜR0£;‰ðÈ‘#|þùçôìÙ“ãÇOBBB®‡ %ãø–ã¬òæ«×é‚çíUáD²©n‹¹ÓUNçžoš@Ô›ØÚ:<Ó5û Îêʕ٨PðÎG‰àYA(qF@pG‘Y IDATGGGãîîÎÌ™3iÒ¤ öööØÙÙåz”F"‰P_TT*•ª¤»at"##Kº zÂn†ñáäY7ãgZU¶¶|§»3G•uáA8aÏ ÇDTÐ>©ö:À†7!å‡g¾¶©©)]FŽdj¹rô>üùo¦ Q*•ºDBá11¶fŒcKIS©TDEE•t7ŒŽˆY fttË–-Q*•ÄÄÄ”tW^(‘D¨oÞ¼y¥òTÉ—mÖ¬Y%Ý¢þ¡ÕÒV|<ùc,wg0ð4Øh—<³Ç¬)‡bc’f /ަŒãì?þ0Ï5¹áàBHþPþMêËelói5=¦°q£ÈtA0v"n)˜QNçtìØ‘Ú·ooðy#Œù¡ÔSiT¬>»š™ÿÍâÒƒHd’ ÿKþ4ºöOʱSi£ç;ž-ñéÕKKÈV™åŽMpçÓ¶$iðöί† ‚ ”F@'$$àëëKݺuyÿý÷©^½:2™¬¤»%¥Æ¡C‡HJJàáÇœ†=o¢¹¹=Ù¡é¼ ©ï¨­G ¶©ï4v’]å[®tÞÉÄ]Ï U0Ùö3C¶áã-e½ëôûÚç7†N™Ép r-¶àD¢O^D¡abl1L$êc‹a"f)˜Ñ%ªÕj:wîÌo¼ÁÇ\êA»_­VóË/¿`jj*fE„—ªFíŽ\u8 oßÏ»Ò^ P Pza~ä3úÕ âϸg$ÀŠpïž¶nƒе+ÈdÈ-ä¸öqEQåùvÕAŒJ¥B¥RñÁ`bb"’óat‘ÜÍ›7˜4i3gΤeË–zAthhhItí¹=z”±cÇÒ¥KºtéRÒÝÊ0ez$ä<4þ5ÇýúÆwêÆûŸCÖß±dF%ÁªUƒçºuÏ 9nýܰ¨(ÞÖA(‹þùç¶mÛÆÑ£GiÞ¼yIwǨ]  P(èÚµkIwã…jÞ¼9 .,én¯€lY ´ºÂ,îÛr÷d4éjâVÅ‘y#V¯†;w´u^{ ºuÓÏVrÜú»aQ^Ï‚ eUÎ$_Î6vBÞŒ.€®Zµj©œa.ˆH"Ô'} {îDI®=V;¿Ík$0•™ IU³"†ì»é°f ܺ¥}¾V- ¹Ü¸aîjþì}zN"ÑÇ0‘Dh˜[ I„úÄØb˜H",˜Ñ%–UbA¾>‘ècس&úd©³øéÈOdT‹‡‚¾Ý"1ËÏk×Bt´ö¹êÕ¡W/Ë1µ3Åý}÷ žA$úäE$&ÆÃD¡>1¶&b–‚Eá¡C‡X¿~=ƒÆÑёٳgç[?$¤tf&Nô^&¤aõÙÕ|¹÷K¢¢!øÄð,´6 ë,büà÷à÷ßáÊísU«BŸ>`jŠ©ƒ)îÝ1µ³x‚ ¯·Ì(~3ÆÆÆN÷îÝQ(„‡‡—t—¡TøóòŸ|¾ûsÎÅÓ•™f–C•ñ,ûzÞ›'^å~÷¢ƒr6¶Î‘°aÃãà¹re SSÌœÍpà†i9£"AÁ¨ÅoÇnݺannNÍš5)_¾¼ ¡a7Ø´koÔ•yØzÐÍñKæùÔí€zðKØ*¡\$™cŸäI‡¤±Â„7®Al²öÅ+Bß¾`f†¹›9nýÝ0±1)™›A#g4@ß¾}Y¶l%Ý•—B$ê‰>ú¢¢¢Ø±síßjo0©ålÜY>Ûõ]ùKWæ p`¢ÿD|5£éÒÁI킹ù]ÆŽ…jÕ> ==›,ìŽØ Ï„Š§`çQðìáýú¹9¸ösÅÄʸ‚g‘èc˜H"4LŒ-†‰$B}bl1L$L$± _ŸHôylcèFj¶¬IËñ-õí(ZŽoIÍ–5Ùº€ë ×鿹? 6ÔÏVfV|æÿׯ\£³ÝDz¼kIz:8ʾ¡—õd2WÉÅiÚGì7‹Èú:œ;÷sÇvŽÜ¹Íwwèß,,°¨dÛ7£ žA$úäE$&ÆÃD¡>1¶&b–‚E!€½½}™‹ñ…üLùf ?ü‰øÆñ¹“þ$°?aO-÷ZœªrŠ,ufr3†4Âÿµþ?ÊÛ”çÆ hÑîÞ™ þ÷ÅA,æweÒƒdàFíÐðø@¢0ÀÓÁŠC†€• On}Ü™ç·÷ ‚ð*qKÁŒêý­?ÿü“蜭´ò1vìØ—ß>|ȨQ£8uêmÛ¶å»ï¾C¡G Ï.òR$Kv,!¾U¼þ“2HðIàè_GÁdN2z×íÍ´¶Ó¨îP€û÷¡}{mð 0{6Œß’~»j‘rø0&xG[¤'~Ô³€8SSü++,kXâòž 23< ‚ BaUýÛo¿ª^IП~ú)NNNìܹ“‘#Gòý÷ß3yòäé‹P6̘3ƒ; îä_É*\¬À_“ÿ¢[]qJ tê—/k?Ÿ0Æ×~‘èÇöÀÑ%w™: Lž:ŠÛö¾i®à9+K{ÊöñãÚÏ €o¿}\¿A–3MÚ‘I"O’Ĺ»ã_»6Öu­qéîR*2!D¢a"‰Ð01¶&’õ‰±Å0‘DX0£úÕimm½½}’pÿþ}pqÑF;ÞÞÞ„‡‡SØ%äbA¾>‘è¦IiÌÝ»Ÿx´:–ûó¹ûµõrh4Ú¼¿]»´ŸvHæ·~{ÍøÞy‡ GoâÚN§éÃDŽ>u½£ ~o¿MC\z”ŽàD¢O^D¡abl1L$êc‹a"f)Ø+óçyVVÑÑÑ( *W®l°ŽF£áÂ… ܾ}›Fáææ¦{.---×_cdee¡Ñh01)xׂ¼®ù*+m'J¾ :µgéÚÕìL~\¶õ©:ŸØjë I|Ý/›õ‡YL8m­Â©¾ó<ü«´ ƒ±¼…„)Ñ.×È’$Ìy4ûììÌÛÝëàØÉÑð)…FÊÞÞ^|¿P“®_ñ½b˜HÓ'ÆÃDÌR0£  íìì033+¸b:tˆÑ£GsöìY²²²hݺ5ûöíÓ«÷ðáC:uêĉ'pssãÎ;LŸ>]·Æ¹J•*H’Dbb"vvvœ?ŸBÏ‚`ˆ„„}k{výçouÎéf–ÌS8A‡d8Êÿe$<®ðxbšL\‰uîTÁS{0JÅŠøeeqdÝ:^OO爥%ÍÆuÀ©³Ó˽1AA(ãŒ&€¾qãÆKiW£ÑЦM&L˜Àœ9sò¬÷á‡Ã7ððð`õêÕôëׯóöÛoàïïϯ¿þÊG}ÄŠ+hÞ¼ùKé³Pö¥d¥ÐwS_¶^Ù ¾0v/ì4°Lþ ôa:ö?ýÀ“{¾hlí7óƒfÍȬՂØËU‘ä¹×¬UŽ:9‘|û6ª¤Óÿcÿ—vO‚ ‚ðª(%+ Ÿ¿¿?ß}÷½{÷ÆÎÎÎ`¸¸86oÞÌûᅦ‡ =±jÕª,Z´HWoêÔ©¬ZµŠJ•*Y¤Ý@D¡¾¨¨(T*UIw£Ø]O¸Nó_›³õ’v±FwŸšT¯èÉùGÏG>ú÷<à8’LÎ9겄!ŒµYÂõmç‘'ÆÃŽdŸLìí:hä†>üÚ·ç+‹¼ûËà—|g/J¥"**ª¤»at”J¥.‘PxìU[ Yp¥WŒ[ 1KÁÊ|]'Ož$;;›:uêä*¯W¯áááºÏkÖ¬ÉÉ“'¹}û6»víÂÕÕµÐר±c“&MbÖ¬Y¹7oÞÌUïU*›7oÓ§O7оWÙ°IÃðûƇcçøþ_¸7ß ßéW~5šïs^÷è1U¡àãÏ>ãÔ츙ÇS¾Œ±ú?‚v ¦jç: “ñõ䯉˜&C£»Æ‚°ÜM¼«ûÜëÝ×Èh« ÂSkÚŒéÿ¥ ²œDcè‹1•å$C_Œ©,'‰ÐúbLe9I„ÆÐc){2‰°¤ûR’e91IÇŽ™4iÇŽC(€ô éСƒÔºuk½ò_ýU¤óçÏç*Ÿ*Û R¿öí¥Å‹÷JVV{%8&™š^”BC÷/+6KÚ3atáó Òõÿ]×=vÜ¥+‹û#N’4Æñÿ'ÊD™(e¢Ì8Ërb’¿þúKŠˆˆ¤JBÞŒæ(ïâбcG222ô’CBB7n÷ïßϵŸê¼yóøè£ˆ®íóÄ‘˜eÃÆ•+É1‚~ùlµÊÆÅüùöï¶mH›7“ýÏ_˜gdç®lbÂQ {6dwàO“ÆÜÀ‚–™SØ%= Ì‰0ó)deÙ ý ÝÄÊ•[é×OûÒle61KcP§ªóì‹u]kœ»;#“—¢í6A„'â–‚‰%€»»;—sŽt{äâÅ‹XXX¼½§ÃÂÂ&44ô¹ÛJF~ýØR¥ y…¬j`‹‡=>„¶mÁÍ Dª žÓÍdÄwl Ë–Al,Ckvá»ìÕ\ÊOFÆ(ÎI]YŒsRW23G!IÁ@0ŽƒçÙÄ,Ï?x¶ªc…Kw< ‚ …Jpp°Ø_¾D T¬X€óçÏç*?{ö¬î¹çåëë˲eËľ­O(m‰>2™Œ÷&NdµµÁç×Èå¼wù2²±caï^xto¬`yC˜0¢ñ·¯à°} NúÛÉÅò C°%–or•?:¿U¼J<'ç<{Y•ªCR C$ú&’ +mcKqI„úÄØ’[@@Ë–-Ã××·¤»bôÊЯØg׬Y3Ê—/ω'teœ={–îÝ»¿kˆS}ô•ÆÓÂzôëÇOO½Yh5°E£¡Ç£ÏÓ+¸² ¥9oƒÛøç‹ÞL›s×ê\Á™ Ú¢Ý{#7U‚Š˜e1¨“ò žkYáÒÓ™IÙšy§…&N"4¬4Ž-ÅAœD¨OŒ-†‰˜¥`e~ tbb" ,`éÒ¥dgg3tèP†Š££# ]=~üxfÍšE£F˜3gäĉT­Zõ¹úÌÙ³géÙ³'þþþøû‹½xK³+W’6lÒÓue+«J•|ÿ}–WKbПÐHdȘÖv“_Ÿl°­† ƒ9}zY×lZ÷6ùªø¼gÕ,kXâÚÛ™iÙ žA„â‘óGù† ¨W¯žX£9HåeIKKÓ­;vppÐ}¤  ÇŽ‹™™¿üò 3fÌÀÏÏ€._¾|®½œó3räHFŽùRúagg‡——×Ki[(>aû÷ x·3ý4«Àbs8Õ4…jí«‘é› • Š]¶m¥¾[}ƒí\¼ß.ä=KÔt +•áCRž \ƒDð,‚ <gggœó†‰$BÃJÛØR\D¡>1¶&b–‚‰º˜ìÛ·Olc÷”Ò–è³§wzÅ&p¸ëíÍà†? L@ÖBF«û­p±rѽN­†õëÁÏÚ´mÛ´³LåʺÒ,,ÏödåÙ‹Š¸õuCn^öŒE¢a"‰Ð°Ò6¶‘D¨OŒ-¹ålc÷ôy‚¾2ŸDh ĆäeÀ¾}¨Ú¾©·ËÏPˆµ«õÖ m  ç5Ü¿r‡ÔTøí7øñG¸~ýqSп?ŒÝ»rñâ¨\—2:Onrçì|”‘#{=n£‚nýÝ+Ê~ðœ—72}úô’î† B©óî»ï2uêÔ<ŸqKÁÊühAxnׯC` ¦¤›A@oˆµÑ>•Ö“ÇÁ3€R³2ùâ X°@{aGG1FÒž±0uêââ¢uud*öGl0M0ÉÕkëºÍË›ãÚÏõ•žA»|áÞ½{|öÙg%ÝA„RcÍš5ܼy³¤»Qê‰Zò“š ]»Âƒ yNx<ñüÓ1l¤'Xñõ׋ªUƒqã`Ð °²Ê]=0ð]ÝÇšL ±«bɬ™÷Ú3s7sÜú»abi’gW‰ƒƒcÇŽ-én‚ ”§N*é.” ¯öV1:pà€Xý£Oô‘$0Ξ`nEGÖ¤𚃎V€¦Maø|Y;ëütðü$u²šØ±dÞÊäªòªÁ:æ®æ¸ pÃÄêÕ žE¢ ÂË—³úÀ%Ý£'èbâéé)Žò~ŠÑ'úL ›6pºqÆ~‰À­<êß0…S-(gU‰ <Á¤€x7óv&wß%óŽvæyAؽ:fÎfÚàÙúÕ žA$ú‚ ‡œ£¼===Kº+FO,á(&•+W.é.’îBÞ6o†¯¾ ÚÍ‚Öíï ‘-Üamcð8cµ™~€“îpåmHOÕÃyýõÂ]&%"…Û ©çò~ð]®:fNf¸ tÃÄæÕ žìííûûE¡ 1KÁD-O;{V»tC’HRÈèÐ+“DØ(ÝHYv RÜ î\üÌC–dvêþxðÏ’&ç[ÍÔÁ÷î˜Ø¾ºÁ³ ‚ @ “<Ð& ¦¤ ‘Á{—`B‹ ìø4Ž3)¶Ï dÖÑž¤RDš4 qëãȈÎÈ·ž©½)îÁÁ³ ‚ @“œ$€€±ú‘¨¨(<==155’oC•Ьî]1´ió¤vpÈ»t]J÷׺³S ~îKdÅd·.UBÞÉ“W•W©Ó ®½]ÅÌó#*•ŠèèhjÔ¨QÒ]1:qqq¤¥=ÎnU(¸¹¹!“=Ûé”ÑÑÑ…®[©R%L XäîÜ9¾øâ FMÛ¶m lsâĉ¤¦¦–ØšwµZÍÅ‹‰ŽŽÆÆÆ†ÚµkS¾|ùgj+--¸¸8,,,ž¹ €ŒŒ .]ºÄ7¨X±"5jÔ œáS˜xøð!—.]"&&<==õÖ³feeq÷î]lmmqrr2ØŽðj %44”ЪU«’îŽQ3’È¥ìËI"›7oS¦LÁÞÞ¾¤»À½¡A”?p€uua{·ºëõµœj•÷¡€…’z.å%Rvþg-¹°„5!k™–í㹋"'‰°(ë ### }tqûöí±Êo›”<ÄÅÅqèСBÕõõõ¥B… E¾FA†Ê–-[r•) ^ýufΜI“&MŠÔ^ÕªU ]÷Þ½{¸»»ç[G©T²eË–BOìß¿Ÿ„„„B÷áEúøãYµj÷ïßוÉd2úôéÃܹsqpp(° ¥RÉøñã9yò$/^D­VÓ´iSÂÃÃ‹ÜŸØØX†Οþ‰Z­Ö•›˜˜Ð»wo&OžÌk¯½hƒìñãdzdɲž¬ªT©ÂÖ­[©_¿>.\ Q£F 6Œ… ¹_BÙ•3ÉW˜?v_u"€.&bA¾>cJ Û59ˆvK7p²<ü;ù=Â{üе™5ÙÙpûv @… yà åÊ=µ×ñ»ãI KÌ¿rplïÈÚ)kŸãNʦgI" YÇ¢E€km¯#"¢á3eŸ,J]µZMvvv÷"”nbhá•©ŒäÙ9k7æjÈ6‘¡\±ˆà§‚çM›`ÎíÇÃ?®ý¬ûYÜûå^Á³¹›9àYx¡úõëÀÅ‹‘$‰¶mÛòî»ï¬ûçŸâååÅ¿ÿþ[¨¶“““1b5jÔÀÜÜ…BAÅŠ™0aB®õØOûá‡xíµ×°°° víÚ,X ¿çy^"##éÒ¥ ®®®( jÔ¨Á´iÓôc:wîŸ|ò 6ÄÒÒ333êÔ©ÃêÕ« ¼Æ“Ás333ºuën}¸©©i¡‚ËüüöÛoœ9s†aÃ†å žŸäææFß¾}¸uëÙÙÙ4jÔ(Wðü"„……áååÅßÿÍÂ… iР …‚ *ðÝwß!IúKÒ¶mÛ†ŸŸ666ØÚÚÒ¬Y3¶oßn°ýóçÏÓ¥KÜÝݱ´´D¡PЮ];®\¹’«ÞªU«hÔ¨666ØÙÙѪU+<¨×^ƒ ?~Ãv e\I&n¸°‘±}Q nÎrÉøé{¼» ÍUïêUíÜvvÚ“ 3¡•v) å&%šLM¾õ¬êXáàŒÜüñß²‘‘‘xyyé~ʺ—™D˜•¥=ô¦¹s:g΀&ÿ/q‰¹þ(ÖÎΙLFË–-™>}:4jÔ(Wݹsçrÿþ}Z·n]¨¶}èׯ_¡gÝUQß½y%IÂK7pà@ÉÝÝ]8p ´yóæ’îN‰Ûw`Ÿ44Hr÷t—Z½ÓJš2kŠ”ššZ,×Îî _ IDATVgKãþ'1iM=$I{`·¤5R¯nF†$5j¤«"mÚTpûFJØŸ ]Ÿr]ºþ¿|S®Kñûã ¶1pàÀç¼Ë²'>>^3fL®² Hµk×Îó5ÆýO‚뺯_ÞÂÖ3ôØ.ÁÚBÕ[±bíKù¿éÚµ«H÷îÝÓ•]»vMjÖ¬™H!!!’$IÒÍ›7%iøðá¹^%Éd2iìØ±Û_»v­H?þø£®L­V¬;pà@ N:¥+Û»w¯H¦¦¦Rll¬®<;;[ªS§Ždkk+)•J]yÓ¦Ms}]³²²$///©\¹rRbbb®ë9R¤£GJ’$I;vìiÉ’%†ÿ³žAÎýðÁÏôz@jÚ´i‘^ãçç'z÷›ŸÉÔÔT$©ÿþÒï¿ÿ.©T*½º 6¬Àv·oß.’­­­” +¿wïžHþþþº²ÄÄDÉÙÙYªP¡‚”––¦+ONN–\]]%gggÝ=¥¦¦JUªT‘Ê—/ŸëëŸC£ÑH’$I·oß–,--¥úõëKÙÙÙºçãââ$ ©zõêRVV–®ÜÂÂB¤MO ØÙÙÙ’•••H]»vÕµ-I’4tèP >¬+ûñÇ%@ªT©’”‘‘‘ëþlll$ooï\?€Hëׯ/ðÿÓ 80Ïß3›7oγy+Ý"•":txåòV©Tôÿ°?3Yk·–˜à4<ÀÔKSiÔ¾a‡Â^êõï¥ÜãåoðcøL ƒ ³žxã d?ê¯;""´ÞÅÍ“&Kƒrƒ’ø=ñÚá4r 9nAnØ·2¼ûˆØ­EŸ8‰°`}úô¡cÇŽøøøP­Z5ÂÃÃñ÷÷gĈ€v˹Î;³fÍRSSu¯ûå—_$)×Ì`Arf×$I"&&†“'OŽ ]Fñ´&Mšàêú8¡ÓÔÔ”·ß~›äädÝZ^CŽ;Fdd$ï¿ÿ¾ÞÖmýû÷´3ÔÞÞÞÈårþý÷_âââ }?y9wîÆ ÃÓÓ“Ù³g?w{…uëÖ- Ež[Õ2fÌ®_¿ÎäÉ“‘Éd¬\¹’÷Þ{š5k2wî\½9ŠªK—.¹’FÝÝÝ©W¯W¯^Õ•8p¥RIûöí±´´Ô•ÛØØÐ¡C”J%aaÚqþСCܸqƒ?þØàVz9³ÁÛ·o'==Î;çšUwqq¡U«V\½z•Ó§Oçz­;wÖ}njjJ³fÍíÏÉ“ë§ýýµ À†–ç´oß>×,l¹råhݺ5çÏŸçÒ¥K¹ê*Š\×,­rŽòîСCIwÅè‰Z(6¾œÀ†Ì (›)!g™ž hªh¸Üö2&@©T¾”k¸q€Æ‹v3ŒNW`ÆîGOT­ª]—ñÔÛk×BÎîNÍšÁ7ßäß¾*^E̯1¤^HÍ·ž™“å?(e-Ë|ë ÅÇÁAûõÞ»·èo¾¶A.v+Vd̘1lذäZûᇒ””Äï¿ÿh“Á–.]J«V­tÛ¡ÖìÙ³ñðð |ùò4iÒ„æÍ›óÑGp÷î]½ú†¶Åj×®7oÞÌó:QQQ€6jÚ´)¾¾¾øøøÐ¤I†  f<<<½^ºM›6zI­9ËAžn'§<1Q—¤œïOCe9÷‘£uëÖÏ´¦Pz‰5ÐB±¸~ý:o$³ekÁMàºïu†|<„СEn?##ƒãÇóßÑÿ¨R¡ -š¶ÐmKöýáï™´k* /%lØl†\Êغžšý¸t r&㜜à÷ß!¿œ ŒkÄmˆC“žÿbXËš–¸ôpA®·ssíIϰ‹ —Ã[ô–˜5kÖ¸'s‡¨V­‹/fРAlÞ¼™¸¸8~øá‡"]kÉ’%|úé§ðÞ{ïQ§N*UªÄÉ“'i×®]®=‹sJn³±±Èwv4g¶Üßß_ì<)çú9/^ÌÇÌÂ… Ùºu+C† áÓO?eÉ’%ºdÀ‚DEEѶm[$Ib×®]EÚðE¨]»6‡æêÕ«EÞÇ´ïx{{óí·ßÒ³gOüüüX¾|93fÌxæ>åµ£„ôDaN®OÎ×õI¶¶¶Àã¯uNÝ‚vo)L›Oçå·ûEaî#‡¡kæõ=[ÐÏžPöˆº˜¼êI„;öìànù§f¥ö<~Ä.Ÿ¾\ä¶·m߯ø™ã‰q‰!É1 “ã&¸þæJS÷¦hÚjؽ×ls·:`• 2¬Xuëæj+=!%åq•ü¶ÃL<œHÂÎ$Mþ‡£ØùÛáð¦bç%‘D¨OœDøbÈd2† ÆÄ‰9}ú4‹-ÂÉɉÀÀÀ"µ³qãF¬¬¬X¼x1...ºòäùš={ö0yòä\e;wîòßs6gf¼^½zŒ;¶Pýóòò"$$„:Ä»ï¾ËŒ3 @GGGÓ¶m[233Ù»wo‘gæ_„·Þz‹eË–1oÞ<½}¹‹Ê××gggîß¿$IÏ|Beaäü³sçÎ\Ivÿüóðø žœº‘‘‘ùžx÷d›9K„žnÓÐV/‚¡ûÈùž­V­ÚK¹¦±I„SaÅ$66¶¤»P¢Žœ>‚äôTyxjâ)21’zsëÑkC/¾Üû%kήá体¤f^±~ÓzÞÿþ}.¿q™¤úIPÔµÕÜkqPëP¶ÎÞ T±­ÄåƒM°»ùèë0eŠÁEÍ#F@Îò͉¡S'Ã÷#©$”›•ÄÿŸoð,3“áè‚C»ÂϳfÍ*\ÅWHÎI„E·Í÷‘™Y¸Ó ó¢Ñ/ðpø¹®ñ" 4 &NœÈÞ½{8p`‘Q&%%ann®7Cgh÷‡Ò[J±cÇLMMuëP ÉY;ýóÏ??ÓRŒ-ZмysNœ8AFFF¾uoÞ¼Éo¼Arr2;wî¤^½zùÖ?yò$Ç/rŸ D‹-X±b…Þ)“9"##™óhÍ””ÝR—§mß¾¥R‰——×K žš7oŽ¥¥%;vìÈU.Iÿüó–––4mÚTW×ÁÁAw L^Ú´iƒ\.×k3++‹Ý»wãääDƒ ^üÍ€Þ5Õj5»wï¦|ùòe~’ãUY CÌ@“WýTŸÆÞYzx)<¹Œ°£~=)[âÜÃsœ{˜; I†Œ å*àåì¥{TVTfâQ¶SNÝ€úP5²*çÝ;a¹ïgmyðÿ§W}Ù2í U+˜>Ýð½dÝËB¹EIVLþI9¦ö¦¸övÅܽðLhû±¬Hõ_Ï’D8vlo:v,LpÜ;Wr[Q4nܘóÊ´áëëûL×xÑœ Ôí‹\”äÁo¼ñ‡føðá|øá‡€ödÑœå†5'''Þyç¦NŠ .$<<œ>ø ßãÃmmm™7o½zõ¢E‹ºã«“““¹|ù2«W¯f„ ¼õÖ[üöÛoìØ±ƒ   jÖ¬IFF{öìáï¿ÿ¦Y³flѾ}{¢££éÒ¥ [¶lÑ ^©ûÄ»V:u2¸V9$$$WÙíÛ·ukëÖ­[àŒ¿L&cñâÅtéÒ…€€úõë—ë$Âdz|ùr3f J¥’Zµjѹsg:vìHíÚµIKKcïÞ½lܨ=au„ ù^óEððð`üøñLŸ>>}ú0jÔ($I"$$„k×®ñÅ_è¢qttdÆŒ|øá‡øûûóÙgŸé¶±Û¶m´hÑooo Ä’%K1bÁÁÁ¤¥¥ñõ×_“˜˜È¼yó .µxRRR8p cÆŒA¥R1}út|È©S§pww%×JI•%ä+åhªæ³N8ª¹Uû–7‘ÊH®Å_C-i×RJHÜNºÍí¤Ûìºö(c?¨@þ3»µ!h£ËßÏõëÃòåÚõO8wN;û àê ëÖé'‡i²5$îK$épRK6ž \{¹"·oò”//¯—>KäêêZ*wÖ:t(«W¯¦uëÖÔ®]»È¯ŸoY¹ÈwñâŲ6¥Ü#t)ñ.9ä§ÉÓp÷vgáÙ…J »t:þ€©/ž+ÛÍfŠRq$ž8Éÿb_`f¦¾ÀHVlLl0}®C£Ý診…È$¨ _§ÁàŸ 0WS¦xÍE`€÷ÞËîp'çžÏɈË?G™¦±&•WB¯ZÞ>–ÁÃÃC\õç 3ˆPS):¡¡¡Ì›7³gÏòðáCŽ?žïª @ x7 ~Ðù t)ñ®|ïEÝcľ\}v}-}v_È¥•JzÄ 3Õüº}¿ÕÐÅ; 𨨨‰èÿ}÷Û·²;"÷`¾GÀÖV˜OŸ®rÎÏOQm JغUá‘—ÎóÃÏI¼›˜ÿ R¡w4 ‹GŒñ¬Š¨Døú$''óàÁZ¶l‰‡‡Ýrú) @Q¦S§N%œø&ð®h–×AhA±±êò*fARº"Õ”£µ#ø“ðQâT¦þÎ|\™Ïs•¾‰Àv¹=zF÷¯Q£&»òèÈr‹á÷ÐÔäÛ³çTŽ¿xƒAJŠ"XpǨTI"îj<1Çb'ç]@ËD‹ }*``'ªO Ê?uêÔÁÛÛ»¬ÍÊ=}ôQ¡s£ Þ=„€¼6aña|rð<ý=Ði0«ý,æv™‹¶Æ%üÒqb'uùš‹ä”œK0¤ݱIº†ö¿ÿ*Jêé)6}ýÿö3·Wß­]Ë‚¶mY­¦tð#@·W/ª«IxÿÉ' ØŸ?Ú4L#|s4ÉAHI&ãƘ¿gކ®ˆà]CèRâM¯D˜[U¢÷0ö¯±D%FPÓ¬&[l¡}µöjÇ Æƒ…ôc./ñlQÔR¹OGøAQð#ÕôQ*Èd §Gu==´“’x*«ÐK,-™õë¯*]W¬€}ûû}{Ãøv/xº:)=ÿ„4Úµ±øÀ½¯ïëœ"ˆPD(¥‡¨D˜?bù¬”xƒ“““?m<Ž}±ûÀ»žv8}èÄYŸ³Ä§Æ3úÐhúïì¯Ï£ìGq}âõ\ų6iÌá‰#X Ä«‰…5N’u·cb˜‘œÌ’§šNN*~\/ÂÌ™Šý¦USYÚí)±ÿÆä+žeš2Ì:šQeB•Ï *ª£è•@PXÞDÍRÚˆèRâMsȈˆ £sG’Ö2MyüAÚ>üöC$#‰XGE¥­ŠY÷Á:ú×W-‰ÍËç\ ;Ž\ç, Ѽ…ïózš³œè‘LÊîìßþ ÆZZ œœœïßêIIh=ʣǕ«ÐKll˜µti6ž?‡ÁƒQÚh½`Iï—h¿ÌÕY·ª.Z cY¸Š‚EEª"‚ ôxÓ4KY ´@-.ã]¸ßæ~öÒÛÚÓ1ŽO W§^lì·+£\r®J© —±ðø6tQ¤ƒ3¦›‘3‘›Ì§,!"S*’ÜÄãJùgáÈÊŒ¶oϯ¡¡ŠÕçÖ­³}H¸ºBjHÎ<ç£niTÍ;å(2mæ]Í1nmŒL£dö @ xó.¥DHHx#ª¦]¸p¿t?Uñœ•ÎPó~M?œ«xN¾DhÝ.è|9])ƒ 4ñ`6­¸Ä]~ækÌÙI]Òé¬ìóâ´kýU8›«W¯ŽfëÖå7-îÅ‹ ÛÐÚ9UÍ(ÙééÁš5:,Xë×CF†bì;àë¯áóÏsÀ?­]‹¦¦&qWãx~4†íäÄ)t<ï¿ÖÖª}Ê[AD¨Š"Ì;vd»k¡§§‡­­-mÚ´ÁÖÖ¶Ðã¹»»¸íŒ3ò­Övúôiºté¦M›pssËwÌŽ;[&wbRSS9vì'Ož$((###4hÀ¸qã077/ð8éééܺu‹Ë—/óäÉlll3fL‘lzüø1^^^ܾ}›àà`lll¨_¿>C‡U±)!!ßÿ7n†¹¹9¶¶¶tîÜ™víÚ¡££„ cÍš5´hÑ‚¾}ûÉ®œ¨{ßÔkÚ´)fff\¸p¡@ãÊårîܹÃåË— Á‚Ï>û¬XläΛ¤YÊ ! *ܸŽäWê‡Anî¿´ˆ 3á矓ømi2_‡Mf[þ;Ýr0Õþ^M÷Ê?~ãÇ+° «WçŸÂôépô(¼| ³fÁš5°x1 qqq$$$dë—“AüÑxÒ§qþ<*Ž7nlD‹9&QÞ‚‚‚Øy`'§/Ÿ¦‚YzuìÅGý?R Šâ 66–{wpÜç8r¹œ®m»2ì£aT¨P¡ØæPÇŽ;8xð 2gkZZr¹mmm¦OŸÎ‚ 5Þܹs Üv„ oU¹c{{{îÞ½‹¶¶6µk×&$$„ÄÄD.\È?ÿüCÛ¶mó#00Æ“””¤<Öºuë" è 60mÚ4âââ033£N:œ>}šèèhfÍšÅâÅ‹™0a·nÝ¢[·nDDD ««‹ƒƒ÷ïßgÛ¶mÌ;ooo:uê(ôܹs?~|± è’ ..Ž*Uª¯~%žS ÌÉØ²š—v¢Uùõ„CãÆàåÿü™ž/ÁGAÇŽ0hз88,¤yóå´l¾‚aM6ócëÃüúíV­º€Ïà2ÙvzôÈ^aP»¢6VnVXô±âù-gÁ²8pböÝÙ®}˜?þdÔ¡Q8tsÀ÷šo±Ìqàï8ördÒÙIìµÚËþ*û™ra Íû4g×þ]Å2G~‘œœÌóçÏÙ´iVVVxxxpàÀB#IR¶mûöí,[¶L圕U.ÙwÞPìííÙ³g ܽ{—ˆˆ6lØ@LL ...rCÑÖÖfÈ!üòË/øøøÙ–%K–0vìXªW¯Î•+Wˆ‰‰áòåËDEE@ïÞ½9uꔲýðáɈˆ`Ó¦MÄÆÆrþüyîÝ»Gdd$kÖ¬ÁÒҲȶ• .¼Ö œY¶l§N¢bÅŠÅh@ðzˆèRâMªDø4à:mÓvRÇW®i€Ž\q©%é¦LŠå$pˆM¢È¥,uïî¦Pµjæñ÷÷ÇÖÖ-­¼?†½{CŠÕgwwˆŽ†3g2Ï~‡6&t%k’ITÓßÀà™î\2M¦íM1íhŠL«|fוU)j%Âõ¬gÁÉÄuûï ¤ÖKåŽíNÈåC—_ë‡ùÚkŒóGdÈlKR ‰ jA|ºìSjØÔ uËÖEž£0˜ššâææFtt43fÌààÁƒôë×K—.aff†JŸøøxnݺE5°Vç礆ŒŒ Ο?O`` iiiÔ­[—V­Z¡§—{¡¡ÔÔTüüü¸}û6 4 eË–ùþÿgåÙ³gøùùñìÙ3ìììhÙ²¥Úji’$qýúu.]º„¡¡!vvv4oÞ™,ïÿùmÛ¶e{lhhÈèÑ£Ù´içÎãîÝ»4nÜ8Ï1ªU«ÆÆ üœÔñôéSæÎKµjÕ¸téÙÝËjÕªÅÎ;  22’7nкuk7SSSÆ_d[®\¹BzzºÚs&&&4lØ0×¾·oß&==fÍš©=Ì… ÐÑÑ¡]»v*"ßÐÐ-[þ»³©©©>p]PüˆJ„ù#t)ñ&Uõ©VÏ‘gwÂøCz@RÖ³r …3DÐ CX¼Ù¤I…šgåÊ•¸»»cf–Îg--øì3>æÍƒ_…´4Ð'ƒî„Q܃R4^‰šÒ.ˆRT<<<„t2ƒ ãýòåK毛Ÿ]}J•*ùTï)FT\Ô®]€‰'2bÄ8ÀàÁƒ•í$IbýúõØÛÛÓªU+µcåD&“ñùçŸ3|øplmm‰åèѣ̜9“uc IDAT#FpçΕի¯¾úŠÑ£GsòäIôôôØ´i“&Mâ“O>áŸþÉs¾üwwwF››Íš5cûöíÌ›7rëÖ-´µµ `öìÙôèу_ý•Úµk“––ÆåË—‰-Ì˧$!!OOO,,,hÔ¨Q‘Æ(,·oß K—.joccC½zõ¸wïÎÎÎŒ5Šž={æy7   1¿¥Á!žŇ÷oR,òwz©ý’Í>›QÜN)$Á@¼?""¹yëf‰ èÈÈH¢¢¢Ø³g+W®D&“1|øp>úè#¦Nʺuë² ècÇŽñèÑ#V­ZUà¹,--™3gŽò±……C‡%88˜/¿ü’Ó§OÓ½{÷l}tuuY±b¯n 7޽{÷røðaüüüpppÈõy-\¸F±aÃåñqãÆ‘À´iÓØ»w/C† !00I’puuU^8hkkYèLž<™ÐÐP¶lÙR(w“×áþýûhhh`ccSà>»víbÖ¬Y:tˆƒ¢­­Í{ï½ÇŒ3èÚµk‘m©\¹r¶Ç‡fþüùôë×~ø¡Hc&%%ñí·ßboo@½zõððð OŸ>,Z´ˆ5kÖÙ^ ´ºÄÆÆLýúõßrÿ }ü-pòY(]rd³[Žxe¼“qeà“–x?‘È=‘h¦çü—‚—©€?FTÕ áßEšÖoŠæM2,ó¾í®•¬EU몹gœÉƒôjé„= #ƒ¼ç0K1ö†má'(M›6Íö¸B… ¬ZµJ™yAWW—O>ù„Å‹ ˜k×®ÅÐÐP)´ Š$I=z”›7oNZZQQŠ–=Riß©S'¥xΤ{÷î=z4OíëëKbb"ýúõSqÈ`7nÜ`È!tìØ333¾ù梢¢0`U “¡ŽŸ~ú‰M›61räHFŒQäq KJJ ššš…òùmÖ¬GŽáÁƒ¬_¿ooo<==ñôôÄÑÑ‘;vP·w ³ríÚ5\\\ppp`Û¶m*ïgaÈyÕ¡C´´´¸téÒkÙ(”B@‚‘#GrâÄ "##¹~ýz¡½Þ¤ ÂLž·6föI¸óß1o48ÃDÒ–ªì|­ñ D˜•¸«q<ÿç9’\ʵÍK´8†%qù¸v”WD¡*E "ìЮ•ÖU"¬nniàhéÈÅ/.Ù¶:íë@@žmŒ#Œ_[¼äÇòåË155Uænܸ±Jйñãdzxñb6lØÀ‚  ãСC¸ººbbbRà¹bccéÚµ+~~~èëëS¿~}ªU«¦L+£Ò§C‡*Ç:wî @h¨ê®L2+PþøãüøãjÛdÓéêê²mÛ6¾ÿþ{¦L™Â”)SprrbòäÉ 0 Pß5«W¯fÆŒ8;;óÛo¿¸_q`ggGPPOŸ>-Ô*4(Vs/^ (\AæÌ™Ã¾}ûøì³Ïðòò*²MOž<¡oß¾XXXð×_å雵k×Vy^ÆÆÆØÛÛ˜™{TP¦ˆ Âüù» Áš5k -p”zVÞ¤ B%¶ño §²ú¶D2'×.…!3P¦ ÄzÇýWtžâ9 cýÆŠg•ÕQ”J„666ônØ­§¹‹&ëKÖ¬ú±à® ê˜=~6¦7Ls=o|˘Ï?þüµVë ‚‹‹ nnn 2„6mÚ¨ÍÏ\«V-zôèÁï¿ÿNzz:7n$==qãÆj®ï¾û???~ûí7ðõõåàÁƒLª(Œ$Iªÿ£™"7+™+Õy‰÷Ì ã7òìÙ3µÛêÕ«•í{õê…~~~¸»»Ž‹‹ ³fÍ*ðóÛ´iŸ~ú)½zõbçΥ溑Iƒ EŒ×¡Q£F¬^½mmí×J©OŸ>}ˆçŸþyíô…OŸ>UYT’$‰ààà• Jž7R³”2B@‚×¹â~“òµ´À¤ú0y- ó§é$2žèÕÂÜ|ææËIMõ{­y–/_^ /KI.u(ŠX#€ž Ï¬HæÍNu$‚U)j%ÂU‹WÑ1¢#¦wL!«¦K†jgª1µïTš;4ϵA3r ƒª ¢ò¥Êd‹¹Í€JW*Ñ¿r¦ŒŸòZs''N$,,Œ:x0“K—.amm››[¶Ôpûöí˵ÏÉ“'UŽ?~€š5kæÚ/3pïæÍ›XYY©ÝLMU/`ìííùî»ïxðà-Z´`Ë–-¹¦cËÊÖ­[3f ݺucß¾}ÅZl§ Œ;---ÜÝÝÕ^Œd’µXKn```Pä‹€ŒŒ Ì;wسgO±R&%%©úÛ·oYèT•‚’áMÒ,e…Ð~úi&]]ÂÖ) Z¢X…^Y©"{O~ω9q¢3G®ÀØØ¸Dm‘Ò$"wFï›ÛJu?éü‹D:@X¶M.W—Zð. ««Ë¿ûÿeIŸ%´¿ÚžF>p¸è@Ÿà>ü»ú_þ7åÅ2ÏúåëÙýånºÜîBÓóMir¾ ntbÛ´mlþus±ÌQ\ôíÛ¦L™BPPP¡WŸA‘[8,,,Ûªr¦;Hnܾ}›k×®)GGGsøðaªT©BÏž=sígooO—.]Xµj—/_V9ʳgÏÅŠvN÷¹\NZZñññùæÞ¹s'®®®899qðàÁ|³XÌš5‹iÓ¦åÙ¦(4lØ)S¦pýúu Dttt¶óééé,X°€I¯R‡†……áîîNDDD¶v’$±téR^¼xQ苤L&OžŒ§§'kÖ¬á½÷Þ+ÚRCÖüΠ¸pEê;àMà­ó–$‰7npåÊnܸAJJJ®½?fåÊ•\»v 0vìXåÕuPPÎÎÎ|øá‡Ì›7¯ÔžCYS¹rUþ} õÁé‘I¥ 0ØÍ6mÚ”š‰Dl‹ %T½ÿx£FµÐÖö#Ü2† «hr[ÃÒÑ©P,éœo.cFŽaÌÈ—R. :rÂéD‰ÎQhjj2nÜ8æÌ™S¤àAP¬zyyÑ®];œÑÒÒbïÞ½ôíÛ—Í›Õ_0 8÷Þ{`ddľ}û cëÖ­ùþ®[·Ž=zжm[œiР±±±Ü¾}›Ó§Oó÷ßcmm——Ó¦M£gÏžÔ­[—ÔÔTÎ;Çõë×ùßÿþ—oPÞèÑ£ÉÈÈ ((GGG•óK—.¥wïÞÊÇ›7o&99™¥K—fk×­[7žüôÓOyÚ Ÿo™LƲeË8~ü8Ô©S‡ÀÀ@®]»FLL ß|ó ÉÉÉÌ;—… âèèHíÚµ‰çÔ©S<þ\PZX>|ÈêÕ«177çèÑ£=z4Ûù† fËÆRPêׯ¯¯/]ºt¡mÛ¶\¿~ÇÓ£G ”­mÿþý¹{÷. È0£|-[µjÅüQèù‚âà­ÐáááØÛÛcdd„‰‰ OŸ>U+ ƒ‚‚èØ±#–––ôïߟ'NàääÄñãÇiÞ¼95jÔàÂ… @ñT?z“‚·l83ÅíµîOõ1 •OkÖä#w÷b'¯ ÂôØtÂÿ '-*÷ ×£ÃÌ6˜´*xðÓ›€"T¥¨•ߺv튙™úúúîÓ·o_æÌ™Ã!Cò ¬Y³&®®®Ù*Î 8½{÷òóÏ?óï¿ÿÒ´iS–,Y¢L—Ö¤Ie[+++\]]3f cÆŒá·ß~ãÔ©S´hÑ‚?þøƒŽ;f›¯OŸ>$&f¿kT§NnݺÅO?ýĹsçØ¶mfffÔ­[—7âää@=øâ‹/ðññaÿþýT¬X‘^½z±lÙ2Ú·oŸïë2tèPÒÒrÿαȑ²ÓÅÅEm{ªU« ²èPÐϰ®®.K–,aàÀìÚµ‹›7orâÄ jÔ¨Áˆ#7nœrÁ§zõêœ:uŠÃ‡sùòe.^¼ˆ$I888СC&OžL… ”cW¨PWWWµo²bdd„««k®ç³º·¨{ßÔëß¿?Lœ8‘ï¿ÿ///tuu™7o_~ù¥Ê]‚&Mš(]tr¾–â{²äA„ù#“òr°zIMM% ;;;-ZÄ—_~©Ö‡lòäÉìß¿Ÿ»wïbllLzz:­[·¦J•*üõ×_jÇöññáÆ|ýõ×L™2…úõëãââ’¯Mnnn„„„pâDù_ÊÈ€ºãxäb†^†œxM4Ó3`Ú4(ÀªIa˜:uªÚJ„©a©„o '#.÷Ô`2-•V AÑýÒË+nnnÂ:±±±¸»»góƒ^³f Ë—/W[åL7³gÏfÑ¢E\¹rEíJ«@ x{É,÷ž×ïL×®]©^½ºø-ʃ·ÎZGG‡ äñþçŸÒªU+¥¯––]ºtáðáÃ*þf™$%%ËÌ™3ÑÕÕååË—¶ëMqÈß¶ ¥]™œVOPˆg€W+<ʼnº ¤À$Â6…å)ž5ô5°üØò­Ï ‚ÕQÔ B*—.]âçŸfĈB< µ¼)š¥,yëtAˆŽŽ&666Û-IPÜ*’ËåjÓ-¯möìÙÊmìØ±žsß¾}ôïß77·l[ÎÕ³²ûì3Ž?®|¬§§‡­­-;vdâĉèë.ž 0þ÷gÏž¥bÅŠy¶yðà'NdÓ¦MØÙÙå;æÂ… ‰ÅÅÅ¥Àvû÷ïgÛ¶mœ òlçÏÅ{÷¨\¹r¡íºzõ*Ãzôà=“<ÛŽ‹ãçÝ»yï½÷ =G~„††rÿþ}Fމ¾¾>QQQxzzrðàA6lØÀ•+W0Èç5ÈŠ}·­Ο?ƒƒõêÕËv®$ª¶–%_|ñ‰‰‰|ðÁÔ¯_Ÿ»wïràÀFÅãÇùöÛoó#88˜aÆ!“ɨS§>ÄÌ̬жDDD0|øpŽ?N³fÍøøã©S§\¾|™±cÇræÌ6oÞ ÀÊ•+™ëèÀ¬YŠýs³PIRxÌ—_s4)CʳA*:WDCWC…å‚Ϫ%ˆPWW§‘#9>oïåâfñSõêü°hÑkÙöŲe,ëÝ›yÑÑjϯ65e₯5Ga144dÒ¤IxyyqåÊ$I⫯¾ÂÚÚš)S¦¨´¿}û6ëׯgèС´nÝ:ßñÓÓÓ9|ø0^^^<|ø¤¤$êÖ­‹‹‹ ={ö̵ߥK—زe 7nÜ ~ýúL˜0‡=§äädV®\É¥K—xöìvvv¸ººÒ!Dz„„¼½½9~ü8/^ÄÐÐ;;;FEóæÍóœcÀ€*Ç,,,øè£Ø²e ·nÝ¢mÛ¶yŽajjJ÷îÝ ôœrãĉìÞ½›ž={2gεmzõêE¯^½…ßj\\}úôQÏ@±/R|ùå—$%%©=W§N>ûì3å㈈–/_ŽŸŸr¹œfÍš1eÊ;›4iR¬6 І··7GŽ!**Jè|7ÏK‰æÍ›ãááAçΕWxr¹¼Ôí¸zVì66 !q!ôºéÐôñ«'ô–§Ê‰ÜÉsÏçyŠg™† óîæTv©Œ†®øØ J‡±3g²¾F µçîæ;yõ9“­Z`gGŒšsIÀùš5yï•Ð)M2£èSSS‘Éd<{öŒéÓ§«½m¿téRV­ZE\^«œ„††Ò¿nܸA•*U°°°`÷îݼÿþûÌËôËÁÉ“'éÕ«áááÔ®]›àää„——W¾óâààÀÌ™3ñõõÅÚÚšíÛ·Ó¹sg~þùge»´´4ºt逸rå ŽŽŽXYYqìØ1¶lÙR ç¦Ž/^”ZZ¯Ý»w0{ö쵯T©gΜ!:— ¹âäáÇܻw/ÛvùòeV¬XÁÞ½{•í®\¹BÓ¦MùùçŸ100ÀÔÔ”uëÖÑ´iSNeáÊï¿ÿ>Ë—/§U«VemJ¹G¬@—û÷_âÔ©ÿn&%±~ýœKÕŽÌÕg--Èün¾q›ØäXœž‚VÚ+÷‹è´èW)ê"ò¢Ò4Ô¤ÒG•ЫY¶®+‚w]]]:¸ºrlÞ<ºçX…þI&cþŽP ¾$–ss_mjÊ„|íñ‹ÂÆÿVö&NœÈæÍ›Ù¸q#_}õ•²Ý‹/رc~øaSlVªT‰€€lmm•ÇRSSyÿý÷ùñÇ5jÕªUËÖç?þÀÛÛ›N:ðøñcZ´hÁĉ¹ÿ~žþÔ“'OæáÇœ>}Z¹âœh8{ölú÷ïOµjÕ¸rå —/_ÆÝÝï¾û.ÛQQQzn9¹víGŽÁÉɉªU«iŒÂrïÞ=@|^Œ>|8[·nÅÆÆ†Þ½{Ó§Ozôè¡vEúuÙ³gO¶ÇéééôíÛmmm¥Ÿ¸$IL˜0¸¸8.^¼HãÆ¸ÿ>mÚ´aüøñܺu«Dk‚Ò@,– Ž<~¼F¹EEM(u®_‡C‡û ™¿}™î…) ’x/‘gëžå+žumt©2¾ŠZñìïï_îÒD•2<ÿ‘žžŽ¿¿‘úª[…¾T$*§¦BJÊko-RS ²zš&>¥¸ú¼råJ<<<˜9s&uëÖeëÖ­TªT‰iÓ¦кukذa’ôßÝ¢?ÿü“ÄÄDÆ_๠³‰gPd2ùôÓOIIIárfyÓ,Ô«WO)žªU«FïÞ½yôèQž«Ð·oßæðáÃôïß?›»†™™“&MRf½”¾Ëê2ä—-D/_¾ÄÅÅ™Lƺuë Ý¿¨<|ø]]ÝBÝÙ´i[·n¥iÓ¦ìÛ·Ñ£GS«V-FŽÉ7JÐZøôÓOñòòbݺutíÚ€‹/rõêU:vì¨ÏvvvôìÙ“û÷ïsìØ±µKPtÊ[âƒòˆ¸ô+5¡øÙ®øj+}~ø$ 45!ËçBºë êÔKKµcHr‰Ø±¼8÷òvwƸ¥1Þ¯€LS}ÍÊ•+qwA„9A„ª¼N%B]]]:ººrtî\z¼º`[bb®®Š$èÅÄÔgÏX¾{7î¯|C×—êêóüùóÅóµµµeìØ±|ûí·ÙDØÄ‰7nÇŽ£G¬]»–Zµj:ÈÑ××—+Vpýúuž>}JjjªÒ--$$D¥}¦°ÊJ÷îÝùý÷ß Ìuž;wîŠôœ. ©© —³»wïààà€½½=³fÍbçÎ 0€!C†P»víB=7PøR÷îÝ›€€víÚEƒ =FQ©X±"Ož>è>µkóE)ŠƒgÏžåë‚1lØ0f̘ÁºuëèÑ£çÏŸçæÍ›,X° PÙ"Ξ=K×®]•9†]]]±µµ%$$„©S§’œ¬ú‘3ü—s:&F¹‚LŸíððp¥`ÎJÏž=³ù&ûøø°eËV®\É7ß|Ü9sèÛ·/ ,(°KDrr2ýúõãüùóüñÇjƒ K;;;®_¿Nppp‘DŒ££#ŽŽŽLž<™ 60vìXæ#}ï IDATÍ›WìzÏž=Ìž=›aÆñ}¦à+2}±ÕÙŸy,22²Xí¼>÷îÝãìÙ³Îoÿ.#t©Ñ(X@HI¹ú¬¡Y3Z…Ň@ãH0Jxõ£F@§„¾JQ÷2ïuZæZTv©ŒŽ•Nží‚ÒDWW—NnnxÍ›Çn++~\¼¸D智l+úôÁ4-ñeäûœ†††|üñǬ[·ŽððpÖ®]‹¶¶6£F*Ô8+V¬ ==}ûöѬY3åñU«VåÚÇÇLJ/¾ø"Û±³gÏäé[œ™ `„ ²S__ŸqãÆ1nÜ8 È«Õ&áf‘»# Y’oŠ:d`ÖÅŒJC+¡¡Wð”"T¸òWåu‚3ÑÕÕåão¾az ­>g2uÙ2&”rÞçÂШQ#:tèÀâÅ‹IJJ*Tð`& 6äéÓ§¬]»–ÔÔT$Iâ÷ßçüùó¹öyñâsçÎUþÏïÝ»—cǎѵk×C’$ ”ï¼!>>žÞ½{+ï@(S³&''+ÝlêÔ©ÃðáÃñõõeåÊ•Èår$IbóæÍœ:uо}ûfËË­î5 Îõµ”,"ˆ°H‚ÇÕÕU‚.’‰"sÛ/íß¿¿Äç$--Åœªžo½¾µ„;R¨¹©ô;)Ìq¶4/HzôÝ£mÁÁRâÃÄ"ÙöùçŸK111¯ù ß>\]]ËÚ„rGLLŒôùçŸg;¶zõjÉÎήŒ,*?ôë×O¤gÏž¸Ï¶mÛ$@ªS§Ž$—Ëól»}ûv –-[¦< YZZJ€dbb"I•+W–vîÜ)Ò‚ ”mOž<)ÒÂ… ¥ HúúúR… $@²··—‚ƒƒ³Í׺uk•÷5%%Eš5k–¤¥¥%Éd2ÉÆÆF255•IWWW:{ö¬$I’´aà Ex³T©R%ÉÜÜ\¤ÚµkKÞÞÞù¾.ºººÊþê¶íÛ·gkoii)™ššªŒS£F\ÇpqqÉ׎L|}}¥Æ+ûjkk+÷¤cÇŽI’$IáááRÍš5•çttt”m555%WWW)--M9®ŸŸŸHãÇÏ׆óçÏK€´iÓ&I’$éîÝ»y¾F:uRöŒŒ”zõê%’±±±ò=ëÔ©“ôäɵóôµWW×|gºté"~‹òAø@—FÀ5ÀêÕV:,X™‹¼9WŸãÃãI½”ÊÇA}H‹©H42°hùTÌDÇZ‡Ê.•Ñ2+ÚÇHªGªòºA„o3ß}÷&L B… îS¥JÆŽ›ïmýÎ;ãéé™- E­ZµxðàûöíãÉ“'4mÚ”öíÛ£««‹§§'vvvʶM›6ÅÓÓ“&Mš0iÒ$N:Å7hÒ¤ ݺuSÉ2±|ùr•;S:::xxx0nÜ8.]ºÄÇ122¢V­ZtíÚUY^{ôèÑtìØBCC©X±"Ý»w/pEµ¿þú‹ŒŒÜ]Õìíí³=Þ¾}»Ú‚X›7oεRŸµµul…ûŠŸŸ·oßææÍ›,”ðo>™éÕ-ÖdºÌlÛ¶† Š<+Ð¥„ÍKš6õ'"Ÿ«W·!I°hÑD† Ë5åòkãái`M2_uK$tY"é/þ[Ù y¡ˆ¬þâÕ--xµ2•2-zUÀ¸¹qÉ-J„ØØX<==9sæ Çç×_âY d£M›6´iÓ†k×®•µ)å! K ''G¶oW|üô̘aaàì ÞÞ &ˆ¹ÈÈÓäû$soC".$Ò¨ŽËç3\ïñËÇÔŒÓ2 jUEž;5hkb`g€q ãb˲áïï­­­(嚃{÷î‰ö9HOO'((ˆ:Y˜ EPPÆ £zõê|ñÅE ï)))j³«þCdá(%ÂÃÕûÓ§Cfv¤ `̘×_ž$'þz<;"x¼è1^³"¨‘.r²TÏÍÆãÑM‡Šq¯|þrøÃi[hcÚÞë1ÖØL³Á¢¯E±¦¨[¹r%ñññÅ6ÞÛ‚‡‡GY›PîȬD((:öööH’Dpp0K—.ESS³¬M唬šE ±ôWJXÄ[ðä×'hèj Ó‘ñC7 ´/hpý®Œ;jð«¡Œ‘Ÿh"Ó•!Ó‘¡©›c_G††®dq9Îx™¡ôgNN†W±, põªb¿fMõ.ÍQ‰Q$¥'QçE–ƒÕ«£[Eýúú60D»’v‰½ ‚sCøœ©"‚ ô(H€é»ŽÐ¥„”*‘™½4æç}aÝSxñ®®ƒ:/ KðºZ4t\¦%#=V}åsçþ˼‘ëêó+÷ê/%ôÃ@#¥hÙ˜êy @ ¼kŽ2ÄÀ† Efè}û ""ï>òT9ñ¹ŠçÄD¸rE±_£†bˉL[Æ-£[œ­–ÚìÁ /Lšh ñ,@P„€.%î„ÝaõÙÕ\ ¹’íxåÊ0`€¢J`j*lÛ¦ÁEåüyEÚ:€Žÿ;®¡¯Q3#*¹T¢úÿªó§íŸ„Tò§ÅÓW¹J³”ï.-D%Bõˆ´lªG%B@ äÍÙ³gñððÀÏϯ¬M)÷]JèjéÒ£~jW¬­rÎκuSì¿x;wB¹üs%9.]RìÛØ@={-LZ™`5ÒŠê3«S±E ̓è´x :i¯§œŠøÌŠŽ"T"TE AÉS¿~}œU  T>Ð¥D튵ՊçLÚ·W¸oܸ¡(€ò÷ßЯ_áæ¸p"Sµ Æ—T®£¶Ê˜ÏcœB²,-‚ÂÔ#‚UA„@PòT¬X‘Š+ŠTª@èRB¯ºzU@ž"GJ•§ÈUöŒ’x¶\ÎÓ`9~×$*W†¶móXºUt‘WÓç»å†„ MóæðþˆÜ»œ{|€!2@[[Eh@ A¾]JhUФµI¾í¦…–-!ô1ì8.gï9=º¨Ü2MúuõÑ2Ñ⇠äUJºo¿Í{Žs!çIÐ1TH/“Õg@ ‚7! K‰”””µ³´„C‡š6!Aƒac4ðñFrïË–)ö›5ƒ?̽mjF*Wž^¡A˜$¼ à+#-*ªGT"TET"Ìðüùsåc===lmm133+C«Ê–û÷ïsíÚ5ž={†……µjÕ¢eË–èè¨/%Iþþþ\»vèèhš5kFÓ¦M144TÛþÒ¥KèëëÓ¤I“"Û˜À©S§ ÄÌÌ {{{7n\àþIIIøúúâïïOLL U«V¥E‹Ô¬Y³Ð¶$%%qïÞ=RRRhР¦¦¦…Cðv!*IP⸺ºJ]ºt)TŸ½{%I&“$¤Zµ$)**÷¶Šv I»wç=®Ïc w¤qð_§[· e[qñùçŸK111e2wyÆÕÕµ¬M(wÄÄÄHŸþy¶c«W¯–ìììÊÈ¢òC¿~ý$@ekÒ¤‰äååUbó>|øPêÔ©“´;¿/R$>>^8p $“É$@ùôõõ¥'N¨ô¹{÷®Ô¬Y3e»Ì>úúúÒªU«ÔΣ««+5kÖ¬Èv:tHª\¹²Hʹû÷ï/%%%åÛ?99YÒÒÒRyÏ555¥O>ùDJKK++V¬š4i"ijj*Çðôô,ò󼸺ºæû;Ó¥Kñ[”" G)Qت>À÷ß+öaàÀÿÒÓe%1~úI±ß°¡¢]^œ Qø?++TPt,–/_þN¯’å†"TEæÏÞ½{9yò${öìaĈøûûóÁpãÆ™/>>žS§NZ"ã…Ï>ûŒ½{÷2lØ0Ž;F||<ááá=z”Aƒ‘–ãKôÔ©S4oÞÖ¬YCPPqqqx{{cooϤI“3fL±ÚÊСCIIIaëÖ­ÄÆÆâç燋‹ û÷ïgöìÙùŽ¡¡¡ÁÔ©Sñòò"88˜°°0vîÜIûöíÙ¸q#sæÌ)-W¯^E[[›O>ù„îÝ»¿îS¼EˆJ„ù#î—c¾þn߆íÛáÔ)øôSX·.{›µk!2ò¿öj’nd#3€°s¨®Hÿ‘_'à Fž$'-ZÍÕg)¡c¥ƒL«äÿÇÚµk‡••ÄÊÊŠ%K–°víZ~ýõ×lm_¾|Itt4¶¶¶j3õd%00===,--ÑÔÔ,´]ñññÄÆÆbccS¨>OŸ>¥^½zÙŽ§¤¤J5T\¿222ؽ{7¬]»Vé~a``@÷îÝUbzz:“&M"==‹/boo¯<שS'¼½½éÓ§¿ýö#GޤcÖÄú¯Á™3gHHH`Ê”) 6 {{{V¬XÁ¾}ûðôôÌ÷bQ[[›Å‹g;6xð`¨_¿>ÿüó?þøc¾¶lÞ¼Y¹ïîîαcÇŠðŒ‚w! Ë97µkg¹{÷gÖ¯7ÀËKá' —+ÒÞèéÁ¢E‰T¯>§<|š}ûPõ%T‹.[ÿg ´HN&bG>%>KªŸUE»¢v©Ï;dÈ–,Yƒ”Ç|||7nwîÜA’$ŒŒŒ5j‹-BOOOÙîÖ­[,^¼˜ÿý—'Ož`hhȧŸ~ÊÂ… Ù¶mãÇàË/¿ÄÝÝ'''þþûoÂÂÂ1b'OžD.—cccÃâÅ‹9}ú4ûöí#,,L9_ß¾} aß¾} :???222ÈÈÈ@CCƒëׯ3qâD._¾Lzz:zzz 4ˆŸþYy+66–„„êÕ«—«ïrV~ÿýwîÜ¹Ãøñ㳉çLtttX´hŽŽŽ|õÕWœ={¶ï€z¬­­”;™˜˜˜``` <_,,,ÐÓÓ£J•*¯e£@ È! K‰‚æDOfÎŒâ“O†΄„(òDç$9®_?@TTT®cù?÷'"!—2Îÿ¬´GªEª"‚ ¯¯/•+WÆ:wîL5øóÏ?±¶¶fÿþýüòË/9dAñ~uîÜ™G±`ÁÚµkG@@Ó§OG__ŸØØØl¶ÆÇÇóøñcºu놳³3ß~û-‘‘‘H’ÄÙ³géÖ­–––Ìœ9“:°}ûvvíÚÅýû÷¹pá2™ ªV­ŠŸŸK–,aÔ¨QXXXäúú\¼x€#rÏùéàà@£F¸|ù2EZÏI‡¨R¥ kÖ¬aðàÁÔ®]›””–,Y‹/2dH‘Æ bþüù$&&âêêúÚv ÞmDaþåRJœ>}š &зo_úöí[¨¾ææÅcC¦û†ÒÿYOZ´(žÁ‹ÀÊ•+qww~Ð9ððð~Ð9ȬD(ü óG’$|||øå—_èÒ¥ Ó§O'##ƒcÇŽQ«V-幨ØX¶lÙ‚§§'½zõâñãÇòõ×_3eÊå¸ÿgï<⸺ü.E± VìØÑ`ïÆ^¢±¡1FŒ]P£1Q“»&¢‰1v4±Lì%ŠØ¢¢‚ˆ ÍJ‘^æû;ºì ÒÔû>Ï>²wî½sfvœ9sî);w–ÿ®V­š>>Œ1BVž•|üñÇèèèàåå%·õéÓ‡6mÚD£F8}ú4sçÎ¥jÕªL:•§OŸÊ}¿ ÉÎJýúöðððlûå†ÀÀ@nÜ¸ŽŽ¥K—ÆØØ˜§OŸâííM||¼ÖóaggG‹-033#((ˆ-[¶ðèQѹ, ÞnzöìɪU«T^˜šèBBWW7ÛGaàâI©$h!e4+…@ðÎЬY3Œå<ÐíÛ·§mÛ¶@†¥ÐhQêÒ¥ …‚€€ #ÃÃÂ… ™={6­[·ÆÚÚšÁƒ3iÒ$5å;+îÝ»GéÒ¥iÕª•J»±±1öööœ={VmLéÒ¥±³³SióóóààÁƒ?~I’HOO—ÿ}ýØ”ñÉ'ŸðÉ'ŸÅŽ;X»v-ëÖ­ãéÓ§ìܹ+++nݺEHHµk×ÎòXBBBòͯøÖ­[|ðÁtìØ‘ÐÐP*T¨@zz:ûöícèСøûû³{÷n­æªX±¢üššÊöíÛ7nÏž=cïÞ½ù"¯àýBOO==½|qWz× ô{ÂóÄçø=ö£{($¡@ ï«W¯V LË+&L`øðáüþûï½þL©R¥hÛ¶-·oß&00PeÛ¡C‡€W¾Ð™‹@FÊ5;;;bbb ÀÆÆ333nß¾­Öÿã?`×®]*ínnnHÊ0-°±±¡~ýú¸ººÊ@2£”7))Iö½ÎÌñãÇ ¤zõêr0a—.]èß¿?'OždÍš5jcîܹÃܹs122ÂÙÙYk™sBùrüzfÈ¢|X._®äÞ½{¸¹¹©¤4²£¨u–·aú+$Šºªg°'úi`ª¤bá¾!‚Â4#‚ÕA„oÎòåËiß¾=;wæë¯¿¦bÅŠ}:‡¦G”+WŽË—/óÛo¿‘˜˜ÈæÍ›iÒ¤‰šl>ÄÑÑQ£ÜsçÎÍ2»Ç”)Sظq#+V¬àÎ; <˜;wî°k×.™={¶ŠûƯ¿þʆ 8uꔜ’ð·ß~cãÆôïߟêÕ«ÍÉ“'9wîñññ¸¹¹©ìóرcLš4‰•+WªX¾½¼¼dEþÊ•+ò99pà€|¾,•…ïE­³¼ ú= %=…Ëa—i†)7ÅAo޹¹¹VUíìì8þ<ŽŽŽ|öÙg¤¥¥Q¦LfϞ͒%Kä~VVVXZZ²xñbÐÑÑ¡iӦ̜9“Ï?ÿW —...ìÞ½›cÇŽ±ÿ~Ú´iÃàÁƒŒTvÍ›7ÇÝÝ={öЦMNœ8Á¬Y³ÔŠ…”)S†¸¸¸,å¾~ý:3fÌ`áÂ…r?zôè!+·%K–äûï¿çï¿ÿfÙ²e$''JxóæÍùé§Ÿ:t¨ÊÜU«VÅËË‹o¿ý–½{÷Ê©þÊ•+‡——õë×W“©B… $&&ÊŠff>ûì³,è*Uªðï¿ÿ2gÎ<<<Ø¿¿<çòåË™:uªJÿÒ¥Kcii©âöÒ¬Y3,,,X¶l™ì'®§§GëÖ­ùúë¯éÞ½»Ê%J”ÀÒÒRͯ:88Xå,--¹zõ*W¯^ÀÙÙY(ÐA(¤Ü¬§ ò„Òw/¯–Å£Gâè¸##‹lû%&>ÁÕušÚÍórØeZýÒ çóðãÑ—ÁÁPµjžäŠ®®®¬ZµJ£ {yþüy¶UïÒÓÓyüø1úúú”)S&_ö›œœŒµµ5õë×ÏSÙhI’ ÅØØ8Ûôs’$ñèÑ#RSS©X±¢ŠÒŸ <|ø'''ÜÝÝYºt)³gÏεœÚ’––FXX¥K—¦T©R¹ŸššÊ£G$)WÇ)x¿ÑF'yS½å}@X  ‰7 "ìÞ½;Ýsî˜ÊüÏraµjÅByA„šA„êˆJ„ù‹‘‘QŽ%£uttÞÈúxíÚ5êÖ­+[=SRRpvv&,,Œ¥K—æiN…BA•*U´ê—Ù©]»6{öì¡GÌ™3‡råʽQcvèêêRõ îÅzzz¢l· @A„9#^Ws¿¿?›6mÂÍÍ-Ë Ž¬(J‡|9€PY@¥˜¸oˆ B͈ BuÞ(ˆPP$¬Y³† *ЦMºtéBÕªUY»v-#FŒ`ĈE-^¶qèÐ!¼¼¼hذaQ‹#:"ˆ0g„éOK¢££8p C† !11‘Y³fqôèQ­o®Eé>ä<6O LÜKÿçvíŠL–×AašKfêˆ Â·… Ò´iSîÞ½K||<:t mÛ¶tíÚµ¨EÓ µÂ.Áû‚"Ì¡@kI©R¥ðõõE¡ÈÈIU®\9Ö­[‡««kK–=AQA„ņÑ;øµÆbbï.ÖÖÖLŸ>½¨Å‚A(ÐZ¢Tœ•„††ÊùD‹3J÷ ÙÿÙÜ4(:@ Þr„t8qâû÷ïÏUtvQU"TÊTÚ¶E1¨ì@F¡2“à"«„:©©©µ@ð^PÔÕ“ßÞ9:%%…?þøƒùóçÓ¿zöì™eßË—/ˉè{£ î IDATöìÉáÇåm÷ïßÇÆÆæÍ›'·Ÿ={–Ï?ÿœcÇŽå*µÓýû÷¹}û¶ÊçÙ³gy;È\àâIÅX¨ñìe¶Âbä¾!‚5#‚ÕA„@Pxˆ œyç\8ž>}Êðáñ±±A¡PpóæMý¼½½éܹ3=zô`åÊ•9r„¾}ûâîîNÏž=±¶¶V³^ºt‰O>ù\§ÓJññá@ûöò÷?SSÙ|æL¾åWÕDLR 7Ý`p1õAašA„êˆ B@ (ø€ñãÇãååUÄÒ½Hï0ÿûßÿ$M‡èíí-Òž={TÚ}||$@:pà@¾Êáàà uIé«òå¥ë>>ù:¿&RÓS%“ïM¤n£3ö+$yxø~sÃôéÓ¥çÏŸµÅ‡¢¡Øñüùsiúôé*mëׯ—êÖ­[D ´%..Nòóó“¢¢¢´ê$°Tśܞ3MøûûK!!!ù(UÑðìÙ3ÉÏÏOJLḺoHHˆäïï_R½Ý888äøœéÔ©“xåÀ;ç­ J§OŸª´+¿çä’L€3@@:4jÜ8ßçÏÌõÈë¼H~ñÊÿY¡ÈHaWŒAašA„êˆ Â¬quuÅÛÛ[þndd„••íÚµ£yóæ¹žÏÑÑQë¾K—.¥T©RÙö¹té:ubóæÍŒ;6Ç9‡ FTTT‘¤sŒˆˆÀÝÝ3g΄‰‰ õêÕcêÔ©Ô¨QCëyÂÃùvíW¯^%,,Œ5j0gέÇçöœiÂÞÞ[[[•ìRo#[¶laæÌ™ü÷ߨÚÚfÛ÷ÓO?ÅÛÛ›ˆˆˆ•)**Jþ}166fÅŠºÏÂ"""‚ˆˆLLLŠZ”bÏ{©@WªT €[·n©´ûúúªlÏO"€ï Æäñf˜[”ùŸeºAƒŒ"*àâðáÃÐ%J” ^½zj)î|}}177§fÍšù¾Ïš@Ibd\\¾Ï­ ÏOôÒ¡Í×þÏÅ(}@ È._¾LPPOž<áøñãÔ«Wßÿ=×+J ”òãêê À7ß|£¶­\¹rp$EG¿~ý8wî¡¡¡9r„pèÐ!RSSqpp %%%Ç9J•*żyóøã? Ì“mÛ¶%""‚aÆåi¼ `™:u*[·nåæÍ›žM«°±³³cƌԯ_¿¨E)ö¼“褤$$I’«Ü)ßõõõÑÕÕà³Ï>cþüù\¹r…-ZÄÁƒ3f †††ù.“BÁVI"uÅ ô&O†—ržÁž4 ã”âW@EI@@VVV*™PA„™-Xï;©©©å:ÿ:@Dr2Wbc @*íèhf†Iÿ]]]ºté´iÓ˜4iÇÇÁÁwwwÊ•+G›6mÔÆwïÞ¥mÛ¶¶$ù@W—_SSéóà}þü вð0æ!!1! -¦T”¬[·ŽE‹i|¾Ï¸¸¸?èL(+æÅ:*5µHh»R¥ UVÒªU+ â¬P(X·n—.]",,LíÁ¸zõj–,Y"»±åÄDZ²²B’$*UªD\\ÏŸ?§L™2lß¾^½z© §U«VìÚµ GGG^¼xAõêÕ FOOf̘‘/ò¼ >|<==éÖ­[Q‹S¬y·^^²ÿ~¼¼¼Ô>ƒ ’ûèëë³cÇ®]»Æ_|ÁÙ³g9zô¨Öoñ¹¥kÏž¸ZZÒ`ٲهÏLþÏU«‚†·è¢fÕªUByÖ€PžÕA„¹gÏž=²õoÒ¤IDGGËíJRSSÙ´imÛ¶¥aÆZÍmllÌ/¿üBtt4!!!<~ü˜Ó§OcnnÎøñãyñâ…Ú˜¯¿þšqãÆñüùsÂÂÂððð 00qãÆå¸¿¯¾ú WWW¾üòKîÝ»GDD‡¢dÉ’Œ9’„„ #®eÅŠôïߟ¨¨(xüø1÷ïßç£>ÒêØ2óìÙ3:D•*U¨[·nžæÈOfΜɀ $,,Œ¯¿þš{÷î©XE5±`Á™6m»wïÆÈȈ´´4H@@›7o&22’°°0~úé'¼¼¼pvvV›gÓ¦Mòøñcž?ή]»ämÀßߟ“'OÃ(Q¢³fÍ’W„Ž;†££#;wÆËË‹ÈÈH|||èÞ½;3fÌàÊ•+ùr®>ýôS9¿±òsÿþ}*UªDÕªUiÚ´)þþþ888P£F ‚ƒƒ $"";;;fΜÉéÓ§óEž·>}úàêê*”g-x'è¦M›bgg§öɼ´¨P(hܸ1#FŒ eË–êJ`^¶,(ß„¯\3g l_¯…ÿ³@ð¾póæM¼½½9~ü8ŽŽŽüôÓOèéé1~üxúöíKåʕٸq£Ê8wwwÂÃõ®ª P¶lYÆ'GêëêêÒ¡C¦M›&/ƒg¦L™2|ñÅ(÷¥?üîÝ»óï¿ÿráÂ…,÷õðáCÖ®]K‹-X²d ÖÖÖ²k…““¡¡¡òKÁ£G€ %àucˆ•••F×m?~<?æçŸ.Ëõ5â»ï¾ÃÊÊ sss,X@‰%8“Å3%%%þ÷¿ÿ±råJV¬X!ÇöíÛñõõeÒ¤IŒ;SSSLLLøüóÏéÞ½;Û·o'22Re¾äädV®\‰……€J¶†äädÖ¯_O§NÐÓÓ£ÿþôèуàà`îß¿/÷›3gúúúlذ;;;7nÌš5kò¼RqqqôéÓ‡¨¨(<<<°´´2VýRRRøê«¯¨\¹2¸¸¸ï^°¬ x']8Š#¾¾¾¸T«†½‘ö‰‰ðãðÚ²c~ââI§P.®øú? ‚ü¥k×®*ßkÕªÅ?ü §²ÓÓÓãÓO?eñâÅܸqC¶6oذ33³\[hpssÃÇLJ°°0’““‰}é*󺲤¤cÇŽ²ò¬¤[·nüõ×_øúúÊŠTf|||HIIÁÞÞžS§N‘žžŽ$IH’$§ÐS„·k×KKKœœœ¸yó&}ôÍš5SÛ¯¶8;;sàÀfÏž­UŽÂ`È!*ßõõõ±³³Óè~KïÞ½9wî{öìQs£¸xñ"õêÕãäÉ“òyMOOÇÚÚI’¸{÷®¬hØÚÚÊJfflmmÕ|˜;uê„››!!!Ô®]›„„®_¿Ž·o߯ÏÏOå7µ²²*†éééŒ1<<Ëj¾J—œüdÖ¬Yüõ×_¬_¿^ö{VDýúõÕ\ uuuiÛ¶-$%%H‚↠W‘ªÍ¥HHHÈPŠæÏ‡Í›!=–/‡_Í×ý\|x‘4)í•ÿséÒ ¥_ca#‚5#‚Õy“ ÂRºº4,€âHÚbXHËþmÛ¶Í1ˆ¬råÊôíÛ—mÛ¶ñÃ?ðË/¿ IR®Ü7-Z$’½n™Ý³g»wïÖ8F“UQ™‹?»|³ÊTyþù§šU­[·fß¾}„††âááÁŽ;˜>}:~~~¬_¿>Çñ‘²ïûï¿güøñ¬[·N«1Å‘víÚѱcGæÍ›Ç¨Q£Øºu«ŠÁÂÂÂ}}}bbbä—ŸœxSƒ‡ÒõcÊ”)¬]»öæÒ–uëÖ±zõjœœœ4 ²´´äÞ½{$''c`` ²íæÍ›˜šš¾Ê3dü>òó®!èBB™Šˆ5`Ð øóOؾ–,z¹A-€°m[(~{šAašʳ:oDXÉÐ!ïX¾â7aÒ¤Iìß¿Ÿ;vðÛo¿ñÁРAƒ\ÍáííM¥J•ÔÜöíÛ—å˜'N¨µ;v ÛÔeÊeä£Gj¥@+©\¹2'NdâĉtèÐ]»v±zõj5)3...,\¸Ñ£G³qãÆ<»¾øâ Œ™1c‰‰‰¸¹¹Éç Q£F¸¹¹qêÔ)™S KKKÊ•+lj'HKK“SˇbÆŒ 80Ë•nß¾§§':u’ÛïÞ½Ëýû÷å`Ã÷ YgdIñÔ¬ÞAîß¿››7nÜedsRäó¸g°'–/ ¶²Jy1ußEC×®]©U«NNNDFF2qâÄ\ÏQ¡BÂÂÂ2îg/¹uë‡Êr̽{÷d…2î‰ÿý75jÔ {÷îYŽ«_¿>dÓ¦Mìß¿_mûÿýGPPa-Ì\¼$99™°°0ÒÓÓs´ž._¾œyóæ1lØ06oÞœcÐà¸qã²u(.LŸ> 6pðàA(×F˜8q"åÊ•cÚ´i¨ŒIMMÅÃã@äY°`~~~8;;«§ ãòåËù²ooo†N³fÍØ¾}{–¿§2mÞúõëål!’$ÉyÁ§Nš/ò¼ ܸq777q U„º000ÀÌÌ,c™¬aà ÅöÜ9X¿>í#–˜Ó¥t.<¼@—×Ûµ{ãyÁ»ƒB¡ÀÑÑgggÌÍÍ:th®ç˜1c´jÕJV~Ož<É”)SäÌ™ùä“O6lööö˜˜˜päÈ’’’ؾ}{ŽŠíÏ?ÿLDDƒ ¢yóæÔ«WØØXîÞ½‹ŸŸÿüóVVVœ={–Ï?ÿœÖ­[S»vmñôô$,,Œ~ø!G…xÁ‚@F*TM^¶mÛ¦r¾þùç•’ÎJ2×"¸té’ì"ñÑG±uëÖlå(>ûì3Œ7n}ûöåàÁƒ”-[–;v0jÔ(6lˆ½½=UªT!<<ooo¢££5ß›2eÊ|||Xµj E‹èééÄ•+Wprr¢eË–o¼ŸÕ«WóâÅ éÝ»·Úö¥K—ÒªU+:uêÄÌ™3Y¹r%ׯ_§yóæÜ¸qƒëׯ3tèPTÆõë×£G/h’$É¿oݺuñññycÙ‹ ###ÌÌÌr\©ºÐ°°°P \˜=;C~þ<#¨0Çß||“è¤èWî7¡‚BjFª#‚³fðàÁØØØ¨¤Ë å2õ˜1crô}­_¿>sçÎUQh:wîÌùóçquu%44”úõë³wï^lmm‘$I%e\µjÕ˜;w.C‡ÅÑÑ‘;wróæMFÅ„ ÔrO3FMi«P¡çÎcÇŽxzzâï =zô`ÕªUòñ 0€ .JÙ²eY¸p!]ºtѪÂ&‹èëd’›:uªJ~c%Ÿ|ò ÏŸ?×8G“&Mr”CyÎ^Ï‚P§NæÎ«±ªßàÁƒÕÜ ¦N*€Q2zôhÊ”)ÃÙ³g9qâ}ûö¥[·nܾ}› 6àëë˃(_¾‹UÍÙ³gk,ÿ¬¼~^/–£££Ã¦M›=z4îîîr&Ž 0eÊúôé#÷mÙ²%sçÎUÉ’Æ £ÝkF£Þ½{g;îõø›+VеkW<<<¸{÷.-[¶dþüùË©80ËR×ÙUâ|¨U«µjÕbË–-E-J±G!I’TÔB¼ëŒ;–àà`Nž<ùªQ’22pܹVVðÆå½]¯¸2éÐ$.o„adø?{z¾ÑœÉŒ3D¡ÆŽ+ü 3Å¢E‹Tü ]]]YµjU¤¼z×™ÿüs¡< ˆ œ t!qëÖ-V­Z¥ZqkÌP.÷äCyoÏ`Oì‚^úˆb®@ ‚ÂÃ××—²eË2dÈj׮͢E‹ŠZ$@P̸pá«V­’SL ²F(Ð…D… èØ±#VVV¯Œ`ò䌿߰¼wø‹pîGÝåÿ¬P@6 ïÁûEÍš5ù矸uë·nÝÂÜܼ¨EÅ +++:vì˜cNyP  ’%Kbkk«~QNž %Jdüýãyžÿ|ÈyàµüÏõêA6Å ŠpÞw„O¯:©©©ji¶¹ÃÔÔ”ž={R/Ÿ«Ÿ ‚w‡ *`kk«1 @¡@‘‘‘š7”- /úåòÞyÀ3ØÝt°{ø²á-pßX·n/^¼(j1ŠY¥{ŸQV"AÁ“¥Î" t!‘­Cþ¬YÕ%)£¼wð ñÄ6L’_6¼ùŸE¡fD¡:"ˆP  D˜3B.Ô¬ Ê|›Û·CDDöý3‘šÀáÿ½r߀·Â-@ð6"èBÂ××Î;§¹ÃìÙÿ桼÷åÐˤ¤§¼R +WÎÈ--@ %çÎÃÅÅ__ߢ¥Ø#èB¢B… 0 ë svv¯²f¬_qqZÏí’Q,EV ßë³"ÔŒ"TG AÁcccÀD- t!‘€ YwrvÎøWYÞ[K<ƒ=©õ *(ãñÞZjFª#‚ à±°°ÀÆÆ†„„„¢¥Ø#èBB+‡ü~ý v팿W­‚´´‡HHx=ôz+ýŸE¡fD¡:"ˆP  D˜3B.Nèè¼*ï}ÿ>ìÝ›ãÛOnó,áÙ+ºT)hܸàd‚·€èèhüüü×ÚU,&&†   $I*`é2HHHàîÝ»DDDäiŸ©©©<|ø???ž={V¾]>|˜E‹‘˜˜˜ïsÿùçŸoÍêàíÛ·Y´h‘p,`„]Üpp€rå2þÖ¢°Šgp&ÿç¶m3q@ð^ðÑGadd$ÌḬ̀µµeÚ´iDä2£Onxôè...\¹r¥Àö‘~ýõWjÔ¨™™õë×§R¥J”*UŠ®]»j ŒJJJbΜ9Ô¬Y333¬­­177§{÷îܸqCã>ÌÌÌhݺuže  OŸ>”(Q‚ºuëR±bE,--Y³fVãSRRèÔ©¥K—¦jÕªÔ¯_Ÿ²eËR£F ¶mÛ¦µgΜÁÉɉöíÛSºtiŒŒŒ8vìX^+_ñóóÃÅÅ…û÷ïk=æðáÃ,^¼X(зo³xñb¡@0BÓ*$’’’´ëhlœ«òÞž!ž”‹ƒºO^6¼%î ‚³BÜôÔA„Y“œœLRRS¦Lá‹/¾`Ô¨Q 6ä8Gzz:1118::²aölÙ„ HLLd̘1ìØ±C+YÖ¯_ÏÊ•+‰ˆˆÀÒÒ’¤¤$Ò´p, |||˜7owîÜÑzL×®]™;w.FFF(ÙûA^¯ï÷ IPà888H:uÒ~ÀãÇ’dl,I I}údÛµöšÚÒ€ádôI:}ú ¥-<¦OŸ.=þ¼¨Å(v888µÅŽçÏŸKÓ§OWi[¿~½T·nÝ"’¨øÐ¿ ÂÃÃå¶´´4iðàÁ }ùå—²ßÿþûO¤•+WÈüy¡|ùò’¾¾¾¤¶-55UŠUiëÝ»·H¿üò‹ZÿÀÀ@©V­Z’¡¡¡¨²ÍÐÐPjÒ¤IždôððiðàÁ*í¾¾¾ µhÑ"OóJ’$8qB${{{­úûûûKÑÑÑ’$IÒÂ… %@úçŸò¼ÿüd×®]ÅJžaÆI¥K—.j1´bÿþý íß¿_ãv‡Ÿ3:uÏ¢Ð+*Åý}#88˜±cÇ2`À(‹¦d……E†+‡«+:”QÞ»^=µnããÿÌŸ‰J÷ }}hÕ*ÿ…/ DP˜fD¡:oD‡ÇÿÈg‰´§R¥Ièë—-Ô}êèè0qâDöîÝ‹ÜÍ·ß~Ë… xüø1 6düøñôîÝ[eü‹/pwwçøñãœ={===¬¬¬ppp`ذaœ|8cÇŽ•çxúô)=âƒ> zõêjçDWWùû‘#Gøûï¿éÞ½;Ÿ~ú©Zkkk–.]Ê Aƒ˜7onnn¹ÿ!4@ÇŽUÚ4h@¹råäíy¡}ûö˜ššjA¡V­ZyÞ—’™3g°dɾýö[N:EBBíÚµãÿû¥J•R韖–ƪU«8räÔ®]›>}úðù矣P(ضm›ìÎòÍ7ßàêê @Ïž=qttÌR–M›6áîîÎŽ;(Y²$±*qòäIöïßÏÊ•+9tè¡¡¡ØÚÚòÃ?¨]+’$±bÅ <ȳgÏhݺ5óæÍËrŸ111|÷Ý`6 IDATw\¼x‘‡R§N>þøcF-÷ùí·ßøë¯¿prr¢ÝkU‚“““qtt$-- 6È–óððp¾ûî;®]»ÆÓ§O©_¿>“'O¦k×®jûÿ÷ßY±b·nÝÂÆÆ†‘#Gbhh˜¥¼9qàÀ8@pp°$ÌáÂQHØÛÛóûï¿ç¬<+Ñ¢¼÷ùós+èæÍ3\@ÁkHHRj‘} pÒ2“œœ d(Ó!!!ØÚÚ²víZjÔ¨Á€àÃ?”•a%C† aôèÑ:[*ñ%R²îK÷ gç7V xKÐÖ…#.ÎOºa‘}’“èyÐ䑘˜(}øá‡ -^¼X’$I5j”Ú’xRR’Ô¾}{IOOOº}û¶$I’)ÒgŸ}¦¶¯„„ùïì\8Μ9#Ò¨Q£TÚ×®]+’¡¡¡J{‡d·†´´4•mC‡• …äåå%·¥¦¦JŽŽŽjí_}õ•DƋԬY3iñâÅÒ¥K—4ž·^½zI€äïï¯q»’.]ºH€ôðáC¹íM\8$I’þúë/ÉÊÊJªY³¦4mÚ4éÃ?”Œ¥ñãÇKOŸ>ÕzžÈÈHiúôéÒ„ ¤ºuëJFFFÒ‚ ¤¸¸¸\Ë”WŽ&MšH€4aÂùúHOO—† "Òÿý'÷]·nH .T™cúôé mß¾]nË‹ ‡rž×]$@jÛ¶­ôøñ«ÿ‹K–,‘ióæÍr›§§§H#GŽT™÷›o¾‘5޾}ûJ:::ÒÕ«Wå¶ÔÔTiܸq’B¡Pi¿xñ¢¤¯¯/õêÕKJOO—ÜÝÝÕþŸ¥¤¤HõêÕ“ÌÍÍ¥°°0¹=>>^êСƒdjj*EFFÊ}kÔ¨!YZZJ=’ûúùùIºººoì‘g½å=BX  ‰<9ä+ «dQÞÛ3Ä“ÖA_óñ–½1Š B͈ BuDaÎ|ýõ×̘1ƒQ£FQ¾|y:„••3gÎ$))‰;wR¡Bzöì)100àã?&55•-[¶P¢D J–,ÉóçÏÕþjœµk×.>þøc•öáÇËËôš3fŒl1 å?þ [·nØÙÙÉíººº8::"I’Š5û›o¾áüùóŒ1___.\H«V­èС*©â‚‚‚¨R¥J¶Ç¢Üž9ñMèС}úôáÞ½{¬[·ŽC‡Q¾|yF¥bÅÏ‰ØØX8ÀÞ½{¹sç4hЀ%J䛬Ú2uêTùúP(ôêÕ ÈȦ¡DùºëÍëß5læŸ~ú©J!³~ýú©É§tŸ5j”ÊØ×Ý1”Ü¿wwwúôéC³fÍäv]]]&Nœˆ$I”ݺuàîÝ»˜˜˜0zôh\]]9yò$ýúõcðàÁôêÕKE¹ÍŽ{÷îa``@‡TÚ-,,hÖ¬™Æôp†††jòݺu ÈðÝάtèë뫽XµiÓ†6mÚ°zõj<==Ù¾}; oß¾|ûí·,X°KKKüüüxòäI¶J´rÉÝÜÜ<çׂÈÈHš4iB‰%زe  àÞ½{lذN:±páB-Z¤Õ\5kÖ”óV_½z•_~ù…?þ˜‹/²råÊ|‘W¬¬¬hÔ¨‘J›­­-‘îP‰¿¿?µjÕÂÊÊJ¥o“&M(W®œ| ƒ RùÞ¨Q# Tä ÀÀÀ€öíÛ«ôµ²²¢V­Z<~üXnSfyò䉯kSWWWÍ2sæLN:ÅŠ+(Q¢»wïÆø5·K圾¾¾js*«ö*çT^÷;wVÛw·nݸxñ¢úIÈ‘‘‘Â:„ºÈó…øZyïÔ™0}uºÖáƒY|=û—®~RݺÁ‡o¢¡f„ò¬Ž¨D˜3ׯ_'""‚   NŸ>Í7ß|CéÒ¥ 4߇*Uª„®®.QQQrÛúõë9sæ :tà?þ OŸ>Ô®]›?ÿüS+Y166Öh±ÎJ­P¡‚šåTix(Q¢ …Bí3räHËôëXXXпþøãN:À²eËäíuêÔ 000Ûc D¡Ph LÌ üñ‘‘‘Ì™3‡1cÆPªT)š6mÊO?ýD¥J•puuÍu*9…BA‹-X³f åË—gÇŽ…šŽNy½ŽòeKiõONN&..Nã5¨P(¨ZµªÊ5X2* •U‰èèh,,,4Zð3Ë­|±Ê*`oÔ¨Q´ÊÔ¯P(ä¹Ë–-«beÎiN¨[·®,+@ÕªUs”5/å9g„º¸3`ÔªDÎÿ‚í#%ÛeHè¦C›³ÝöE?¢ORÒEß ï"††U±´•sÇBO/ûÀ§Â@©ü)É×9{ö,iiiÔ¬YS¥½}ûö´oßž„„<<ÁûŠ®nIŒkÙG¡0ÈYȦ|ùòT«VK—.É–+%ÊÊsYUÖ366fèСŒ9’àà`nÞ¼ d(Éúúú„„„¨Q.+ÿóÏ?*í§NÊU•¸† bnnÎÖ­[å¬"Y‘––FLLŒÆmwïÞ%((ˆ²eËR¶lFJÁÑ£GÓ¨Q#¶nÝÊåË—ÕÆ$$$0{öl•Œ oŠÒ]ÄÝÝ]¥=55•Ç«ôQÊ¥U¼HRR/^ÄÈÈHå…(99™¨¨¨"÷kmݺµÆ—¨“'O’žž®r *•M×WA¡\ÍP¾ä(9þ¼Z¶Ž×¯Mm~›ëׯ3cÆ ºté®]»X´hûöícíkñMmÚ´AWW—ß~û-ÇùZ´h®®.'OžTi—$Iö“,B.$Þ䯕8|8Ïô3~*çó¯ÚåôuÀ¿MÒ9ás¢@J˜"ˆP3"ˆPDøæ|ûí·¤¥¥1jÔ(Eý²õVP„@Þr]‰0/^”~°1’« v‹Ä"¤Ý 2¾‡™f|7d&]¼x1%/XD%B͈ÔAêˆJ„Y£)&ÒÓÓ%I___$===9ÝÛ;wä~ÿþû¯d`` ’B¡ôõõ%…B!5kÖL:zô¨Êœ‡–ºuë&I€Ô±cGyÛãÇ¥?üPÞ_õêÕ¥]»vIC‡•*Uª¤2O‡¤êÕ«g)û† $333 å¤òåËKgΜ‘$)#Õ—2%/SÙ)?†††Ò¤I“¤äädµ¹¤V­Z©111‘Ö¯_/¥§§«144TëÿúçõÔzšøå—_¤²e˪ëÙ³§Jú2I’¤‰'J€têÔ)¹mëÖ­òy}ýS²dIÉÉÉIJIIQ™cýúõSnÞ¼9ÛãðóóËö8$)#¦”~Y¥9Ûó‚··7¡¡¡,_¾œÐÐPŽ=ª± „2«IVXYYå˜òïÅ‹\¹r…€€ÌÍÍiР666jý"##yþü9ÕªUS n‹‹‹ãòåË==ÿþûëׯӠAš5kFbb">¤J•*Ï«6:É›è-ï B.ÆŽ˹sç°··×®”7p=ò:Û®oc§ïN®…Qi´ÖR/WUÂM âËÕµ'% Ú¼ÐeÃw9ÿ¦@ð.#èâM\\œ\NYÉŸþÉСCùæ›oøê«¯ŠH²œ äƒ> 11‘3gÎиqã¢I È7²SŽ•î;JE(ÐY#²pzеGW9•„Ɔ²Ów'Û|¶áûÈ÷Õ†Ú`¢nñ Û^wM‹‡äxhkVB(Ï X0uêT‚‚‚hݺ5&&&ܸqƒ={ö`mmÍŒ3ŠZ¼l©Q£GeïÞ½\¸pA(Ђ÷¥‘OSnk*B.$îéÜcìá±X­±bõ¢Õ|ØãCb“cÙç·m>Û8tŠtéÕ² öÕìÝd4úÕ$ÖL˜„sšæeÃ5º:|ñ“k¡K~€•••ÚòñûÎíÛ·5.7¾Ï¤¦¦D­ZµŠZ– 8M›6ñ×_µµ5_|ñóæÍËv¹¾¸Ð¨Q#µâ ÁûBæTƒu„æRX$BZ4îU¿ÇøïƳ(}ÿÆÿËÁ;‰O‰WéjcaèƣÙh$VfVÍ`àò5L»y“ÌiÉ€§M=œGŒ(Œ#É7D%B͈J„êˆJ„o}ûö¥oß¾E-†@ È¢aκ°PAÒ…Èv‘Lš= †¾Ú\¾dy†7ÎèÆ£iQIsû‰ßϦѣ™”)×é¦R¥˜øý÷$xÁ!”!ÍåYQ‰P  ¡<çŒP ‹ 4%1°Å@F5E÷šÝÑÓÉþçèÙ¯Õ¨ÁxooÙ œªQƒIýú´Ô@ D!•"à’»;ìfçàô®Ý;GåYÉ'‹óëk%e55å“Å‹ JL@ A&„]Xd*¸W:©4µ««ç“̉žýúqÊÚš$ ‰ ësÏ·Ôú,*jF¤eSGT"‚ÂCT"Ì¡@™*kš>7¥nݺyšjü7ßðk©RüjdÄøo¾ÉኆuëÖå{©Øw—¢¡Ø¡ "AÁYÔ"{„taQúÕŸ&~&Ì3C­‚–¶ôì×!ÖÖ IL~K­Ï ‚³Bª“Uaxx¸ÈW*¹@Y$%;DaÎ:¬ZµŠ³gÏ¢§§G÷îÝ9rdŽåZUH‡²×ÊÒµBW¦N˜úF²|ú[ž‚ü nݺ 8°¨Å‚· {{ûhAÎ:T­Z• ””Ä¢E‹ˆÕº¢–Ù#3loØ2}âtôɹ”wN¼­~ÏA~Ñ©S':uêTÔbà=Dø@ç‚ÁƒÓ´iSììì>|8çÏŸ×zlÏö=9µ÷T¾(Ïï "ˆP3"ˆPD¨™'OžðäÉ“¢£Ø!î-š÷uĽE3"ˆ0g„KvîÜɬY³Ø¾};óçÏ×zœpÈWGjFª#‚5sîÜ9Î;WÔb;ĽE3âÞ¢Ž¸·hFè,9óN+Щ©©$&&æØçöíÛjo[ñññœ>}šÓ§O«¼µ—)S âââ´~›νð@ E„Ð]²çS _¼xÁ¸qãhÔ¨FFFgÙwÕªU˜™™Q¯^=J—.ÍìÙ³‘$ €ØØXÜÜÜpssãâÅ‹ò˜ž={2þ|V­ZÅ¢E‹ úp@ ÅŒwNNLLäáÇôêÕ‹þýûgÙïÏ?ÿdæÌ™,[¶Œ/^°k×.Ö¬YÃòå˰´´ÄÕÕWWWHOOW±fûùùѤI“?ž¬¸}ûv¾û³åvÎÔÔT<<<òuÎ7åðáÃ9®:ôœܸq#_ç|S8Päs?~<Çeõ‚3+9|øp‘Ï™Ó1„œÙ!î-š÷͈{‹:âÞò~ðÎ)Ð;vŒ¥K—Ò²eË,û­]»;;;)Y²$dðàÁüôÓO¤§§«õ‹‹ÃÆÆ†.]ºÐ´iS~ÿýw¾üòK­åJIIÉÓñdÅ»ð‹ŠŠÊ÷@Ÿwá!+ù´ámÈ¥¦¦•«ýåÄ»ð‹‰‰!&&&WûË qoÑŒ¸·hFÜ[Ôyî-ù­³¼‹($¥ÏÂ;ˆ‹‹ óæÍ#ó!¦¤¤`hhÈ'Ÿ|¯¿þªÖßßߟZµj©Í—ššJPPåË—§T©RZËѹsg¼¼¼hÑ¢úúú*Û,--144”¿kÕvíÚ5J—.MÍš5s=6«¶””ôõõåê9MKKãÒ¥KT®\9Ë~Ê@„jÕª©ÍçîîNݺu©^½zžeÎÜvâÄ Zµj…©©i¾—ÐÐPš7oŽVcCCCINNFWW7Ë~>>>ØÚÚb`` 6ß¾}ûhÑ¢ÅÉœ¹íÈ‘#tìØ1ßæ &((ˆöíÛk=öêÕ«Ô¯_ŸÇgÙïòåËr~Ò×Ǿxñ‚S§NѤI“|;/ÉÉÉœ={{{û|;/DFFÒ¦M­Ç*‹dÕÏÜÜ???Zµj¥6öÚµk@†á ¿ÎKpp0Ož<¡Aƒùv^r{¿ÒÓÓãÒ¥K´iÓ&O÷+www:uêijgÏòí¼\ºt *W®\d÷«'OžœœŒµµužîWûöícÐÿÛ»ó (®<à_@@‚‡£pD.‡[Ep—¸FQA¢®.FïlpãÍf­«ë*–W¥’MÐÕòŠÑÈšxÄõ(EÅ‹ˆˆƒ (F¹DAäf~ûGjºXYa™ß§Š’~ý¦û×m¿×¿iº_O™òÊm¿uÙåË—1hÐ Qû«¬¬,Èår˜™™uº¿jjjÂùóç1qâÄ.Û/P(0`€¨ýU{ý©ZëþJ}¿syy9,--qóæMáܹs`Úée]PP™L†/¾øK–,ÊOœ8ÈÈH$''k4´WµcÇ$$$´)ç7ý0ÆcLlÚœ1c,X B4¯½|‘JEE@&“i”;99iÌï* ,àƒ1Æc¬—èu÷@¿ [[[¿üù¢µhÌgŒ1ÆcìEz™@0†††¸sçŽFyff&ÀÞÞ^Œ°cŒ1ÆØk@/è>}ú`ôèÑB¬–‘‘7778;;‹cŒ1Æëéze˜˜ˆ#GŽW˜9‚#GŽ //O¨‹7n`ýúõ())Á7ß|ƒÃ‡#66Vgq`äÈ‘pttDTTT—¥óº;v,lllàáá!v(=FBB¼¼¼ “ɤ¤$±CêÖ¯_899!** ©©©b‡Ô£|úé§000@ii©Ø¡ˆ®¾¾FFFprr~^|­¾JOOGHHìííáááÁç"&LŽuùóQ¯£äädŒ7>>>˜2eŠNÇbïq¨  ;;»6?ß~û­F½]»v‘T*%dmmMkÖ¬Ñiœï½÷­]»–êêêèƒ> +Vètý=Ujj*) rww;”ãüùóTZZJDDIIIäììLÍÍÍ"G%¾ââba?ìÛ·FŒ!rD=ÇO?ýDS§N%KKKR*•b‡#ººº:²³³;ŒçñãÇ$•JéÀÔÜÜL•••ÔØØ(vX=Êž={hܸqb‡Ñ#øúúRbb"ÅÇÇÓïÿ{‘#O¯…ãúõë/Uoîܹ˜;w.ž>}ûöżyó°|ùrÇÐê÷·Z-Þzë-ßËÊÊ T*áàà ^P=@ëçŒçÏŸ‹MÏÑÐЀ%K–ààÁƒËåb‡Óc466â»ï¾ÃðáÃ1tèP±Ãévî܉    ??ŸoaÔbÇŽøÓŸþ$v=Bmm­ÐïJ¥RÔÕÕ‰‘xzeÝYb$ÏÅÅÅhjjBÿþýžžž¸~ý:T* {å5¬‹lݺ¡¡¡zŸ<«}óÍ7عs'ŠŠŠpòäI±ÃéV¯^9sæðÑ­bôèѸwï¶oß.¼˜¢Oý> Þ½{?ÿü3¦M›†~ýúÁØØÇ^Ä¢ïîß¿ììlDEE‰J°ÿ~Lž<ŽŽŽ¨®®ÆÁƒÅI4úÝs¼‚¼¼<äää ÿþÂÂ^ôüùs\ºt ùùù ‚¿¿¿0ÏÈÈ---´:q600èöØ»‹R©DZZŠ‹‹€€€­õ®_¿Ž³gÏ¢¡¡aaa5j”Ž#Õ­ÚÚZddd ªª AAAí~a»{÷.®\¹üæ7¿D"iSçĉزeK¯¸º¢¢·nÝBCCƧµŽJ¥Âµk× P(0tèP„„„´9±Oš4 Æ Þ={ðÇ?þçÏŸ×EøÝ¢ººéééÈÉÉÁ€Ú=içççãØ±c(**‚¿¿?¦L™"¼åTÈÅþäÃIDAT¡PàÚµkX³f.CïV]Ñ·˜˜˜àÀÂôرcqìØ1Lž<¹Ûãï.]Ñ·ôïßfffÂ_n'NœˆÓ§Ocâĉ:Ù†®ÖØØˆÌÌL¡o‰‰‰ÑZ¯¦¦‡FVV\]]1iÒ$­CØîÚµ 3gÎì_(^5o€•+WbòäɈˆˆÀ¶mÛÏ?ÿ\á÷N“&M"cccZµjU›zÙÙÙ½â興222Ž—óçÏk­·qãF244$²³³#©TJuNž]•·(•Jêß¿?µ´´QII ™›› Óú†èNJOO§¯¾úŠ’““),,¬Ý122’†JUUUDDtêÔ)200 ƒ ufÍšEŸ}öUVVÒŒ3Ú=1¼’““éÂ… T]]M¦¦¦Z·¥ªªŠÌÍÍiéÒ¥BÙ¿þõ/200 œœ¡,==öïßO2™ŒRRR(//O›Ð-¾úê+úþûï)!!¡Ý“\ZZ mÛ¶Qcc#½õÖ[äåå%Ô9}ú49::Òµk×H©T’R©|mô©ªª¢5kÖЉ'èÓO?m÷$÷ùçŸÓo¼A …‚ˆˆ ÉÎÎŽ-Z$Ô¹té544PMM mÙ²…BCCu² ÝáÞ½{tìØ1R*•Þnß2räH ¥¦¦&""ÊÈÈ >}úÐŽ;´ÖÝ"쪾%++‹.\¸@EEE”@ÖÖÖTRR¢«Íèr]Õ·<~ü˜)//=zD^^^”••¥«ÍèR555”@999´víÚvû–åË—“••Quu5¹¸¸Ð{ï½§QïèÑ£Øíqw·®Ì[ÜÜÜèÌ™3ÔÒÒB›7o¦ñãÇëbz$N _A{'¹ÂÂB244l3ª‡«««ÆUæ’’Šˆˆî³gϺ;dhï$·mÛ6@©©©BYyy9 eË– e‹-¢èèháçÅÑS^G)))ížäþð‡?©©©Æ¨ûöí#tõêU"ú¥ÃÿÕ¯~¥ñóºžäZ[·n]»'9777zûí·5Ê>øà²´´®¶ÆÄÄÐ!CÈËË‹>þøã×þJ‘Z{}˽{÷@›+§^^^¢uYóæÍ£§OŸvG˜:÷*}Kff&M›6BBBèÏþ3¥¤¤è*ìnõª}‹ºÌßߟ~ûÛßÒž={tv·ë¨o±··§ &h”-Z´ˆLMM©ººZ(Û²e ýç?ÿéÖ8uíå-qqqå/æ-.\ éÓ§“¯¯/ÍŸ?¿×ô¹ÿ¾ºܺu *•ªÍÓïÞÞÞ¸té’0-•Jqüøq]‡'šììlhìkkkH¥Rdgg eñññb„'š›7oÂÝÝFFFB™···0oĈˆ‹‹C\\œX!ê\mm-rrrÚÜííííÛ·ãáÇË娲e‹HŠC=:§§§F¹··7N:¥õ3;wîìö¸Ä¦î[Z¬ñbßâåå…~øA¬Eñ2} ̘13fÌ%F]«®®FII fΜ©Qîí톆äææÂÇÇÚ½º7Rç-Úú–ÖyKXXÂÂÂt^ÄÃ=tƒââbÚOråååhll#,ÑcРA077×(÷ööö™>*..ns¬xxxÀØØXo÷KGm¨õ|}ÓÑ~©¬¬ÔÛ!¥Š‹‹!“ÉЯ_?rî[¸oy‘z»µ]àj=_ßpÞÒyœ@wƒÚÚZ†¨S³²²éíI®¥¥Eë“Ì&&&#’è›ÚÚÚ6ÇŠ±±1úõë'Kú¦£6Ôz¾¾Q·õˆjêv¥¯í¨¥¥¥Í>¸oá¾¥-õñðâ¹HßÛç-Ç t7J¥Ðæe ·o߆™™,--ÅKtöööxðàêëë5Ê333õz¼Z©TÚæXQ*•¨¬¬Ž%}ÓQj=_ߨۉz?¨eff¢ÍX}Á}‹vÜ·´ÕQj=_ßpÞÒyœ@w™L@{UÏÓG...P©T ´ººùùùpqq12qÉd²v;s}=^lmmñÆo´»_ $FX¢S·“;wîh”gffêuR÷-÷îÝʸoá¾E+++XYYiÝ/FFFprr'0‘qÞÒyœ@wƒÀÀ@ <)))BYuu5233-bdâš>}:Œ‘˜˜(”©|š5k–Xa‰.::eeexøð¡Pvùòeôë×&L12ñ`Ú´iHKKCSS“P~åÊ„……aÀ€"F'øúúj´¡‚‚ܾ}[¯Ûºo9}ú´PÆ} ÷-íyÿý÷‘ššŠªª*@SS’’’)Ê›‰{Î[þbòº©¨¨ ØØXŠ%™LF¦[¿Àa÷îÝd``@Ÿ|ò íÝ»—BCCiàÀ¯õ¸£ILL¤ððp 'CCC2d…‡‡Ó»ï¾«Q/..ŽúôéC~ø!-^¼˜,,,hþüù"EÝýöíÛG±±±Mèw¿ûÅÆÆj ÍW[[KžžžäééI»ví¢Õ«W“±±1­^½ZÄȻתU«(66–BCC €Ð†®\¹"Ô¹uëYZZRdd$íÛ·fÏžM&&&tæÌ#ï>¥¥¥B²±±!‰D"L·~yNbb"™™™QDDýío#ggg6l˜0vkoÃ}‹vÜ·h7kÖ, '777 ;'NœêÐàÁƒÉ××—V¬XA#GŽ$kkkaÌùÞ†ó–îÁÃØuRKK JKKÁÁÁ L777 õfÏž sssìÞ½IIIð÷÷Ǿ}ûzí}gVVVðóóá_°°°Ð¨·|ùrÈår?~Ïž=Ã?ÿùOÌ;W§±êRUU•p|¨¿Å—––âéÓ§B333\ºt Ë—/ǦM›`mmøøx|øá‡¢Ä¬ ?ÆãÇáàà€èèhaµ~°ÉÛÛÉÉÉØ°aÖ¯_WWWœ:uª×¾úÝÄÄDkSSSá÷1cÆàâÅ‹øî»ïpûöm,X°}ôÞ|óMÆ«+Ü·hÇ}‹vr¹R©´M²¶¶~wttÄ•+W°uëV( „††bçÎpssÓu¸:ÁyK÷0 ";ÆcŒ1Æ^|4cŒ1ÆcÀ 4cŒ1ÆcÀ 4cŒ1ÆcÀ 4cŒ1ÆcÀ 4cŒ1ÆcÀ 4cŒ1ÆcÀ 4cŒé˜B¡@NNŽØat¨´´ÉÉÉ(//×:?55yyy.ãÉ“'¸pá‚ÆX³Œ1ÖpÍÓ ÁÁÁFvv¶Fy~~>‚ƒƒñÓO?é,–Å‹ã‹/¾ÐÙú:£ªª #FŒ€““fÏžôôt­õfÍš…Ý»ww¸¬ÔÔTŒ5 555²wïÞÇwuØŒ1¦Sü&BƘ^¸ví€_ÞXwèÐ!¡¼®®×®]Óxƒ›>;qârssQVVöÊo7twwǺuë`ffP*•˜;w.RRR`kkÛá2Ƙ(ø 4cLoŒ=‡Æõë×;¬WUU…ÆÆF²††TWW ÓBÒÝÜÜŒ[·n¡¡¡Aã3>ÄÏ?ÿÜáºÊËË‘•••JÕnââb(Š6Ë€§OŸ¢©© PTT„;wît¸>"Bnn.222Úlcuu5!—Ë¡R©PUUÕá²ÔÊÊÊ‘‘Ñ&>GGGÄÄÄÀÄÄ*• Ïž=ÔÔÔàéÓ§m¾´ÔÖÖ"55÷ïßïp0ƘØ8fŒéÈÈH„††â³Ï>ë°ž4ʶlÙ777a:!!‰G…D"¿¿?lllðÃ? °°r¹®®®puuÅôéÓÛ¬£±±S§N…ƒƒ||| “ÉššªQ'-- žžžppp@PP$ ¾þúk:‰›7o†ŸŸ „ùóç·»] …pvvF@@$ vîÜ)Ìwss÷ß~‹óçÏC"‘@&“u¸Ÿêëë1yòdØÛÛÃÏÏ...ÈÊÊæŸ={‰UUU(((@PP`̘1H$H$€ÊÊJŒ5 ‰‘‘‘Ëå°±±épÝŒ1&&N czeݺuHJJBRRR—,oãÆ8~ü8JKKñÎ;ï &&Ó¦MÃÒ¥KñôéSlß¾ßÿ=rss5>·wï^XZZâÁƒP(2dÞyçÔÕÕøåÊô˜1càãレ¬,TTT`ýúõˆÅ¹sç4–µbÅ LŸ>=ÂÉ“'µÆYWW‡‰'ÂÆÆW®\A~~>æÌ™ƒ àÒ¥K~yppöìÙýÏÛZ¾üòKøùù¡°°iii077Ç®]»´Öurrî?OIIˆ[·nEII PVV†ººº6_`c¬'áš1¦W~ýë_cüøñÿó*ôËúä“O[[[ÄÄÄ ²²r¹sæÌ……,Xœ9sFãsFFFØ´i¬]»¥¥¥øñÇ›7oF}}=¾þúkxzzÂÂÂü1±}ûveã¯ý+lmmÑ¿­q|r¹ü•–.üîéé ˆˆˆÐ¨#—Ë¡P(4ÊBBB„‡ë ((o¾ù¦p•6##ýúõâE‹ R©@DP©Txòä ZZZ4–5~üøÿç½{÷`aaàà`¡ÌÀÀo¿ý6nܸñ’[«éÅíô÷÷Çš5kÐØØ“—^ÎÂ… ©TŠI“&aþüùq2ÆXOà 4cLïøùù!::Ë—/ÇÚÌ700hSöâwjæææm>׺L]þâCq}ûöÕ˜622‚±±±0fr}}=lllÚ$ÇQQQ°´´Ô(³··×[kÍÍÍ011¡¡æûöí+<„ØY/n§zÙ}päÈ‘(,,Äž={pàÀ„††bäÈ‘¸pá‚Öÿ Æ'ÐŒ1½‡¡C‡bÏž=mæyzz¶Í"11±K×ñâE455ÁØØpóæMTTT*úøøàìÙ³?~<ììì^y}®®®¨¨¨ÀÍ›71|øp¡üÌ™3ðõõ}åå¿ u­m4sss,\¸ .DZZqöìYŒ3F'±1ÆXgð=ÐŒ1½äââ‚ùóç#>>¾Í¼ &àèÑ£(,,DSS8€‹/véúŸ?ޏ¸8ÔÖÖâÑ£GX·n,--1yòd¿ÜÖ`bb‚¿üå/¸ÿ¾ð¹àêÕ«^ßÔ©SammÕ«W#??uuuذaŠŠŠÓeÛÕGGG8;;ãÔ©SIô±cÇðüùsaZ=|žúJÆëi8fŒé­¿ÿýïZËß}÷]”——cðàÁ0`þñ`ñâÅ]ºîéÓ§#11NNNpvvÆéÓ§±wï^á!@™L†C‡áܹspssƒ‡‡ìííáîî.¼¦3,--ñïÿW¯^…««+±lÙ2,_¾‘‘‘]ºmYºt):sssá6–U«VÁÖÖ>>>pssCTTfÏž­Ó¸c¬3 H=ŽcŒõbwïÞ…0ö°ZAAjkkáèè¨qOoyy9RRR`mmÀÀ@ÔÔÔ ¢¢...~¹JªT*ááá!|F¥R!''§Í² `jj*ÜŠ¡ž¶±±B¡€R©Äˆ#„Q(Z«­­Eff&òòò`ee???·øÝ½{„……ÅK퇪ª*¤§§£ººþþþ4hÆ|¥R‰––888t¸œÜÜ\XXXhŒ×\SSƒ¢¢"¸»»ÃÀÀÏŸ?Gaa!ÜÜÜÚÜ{]\\ŒgÏžÁÃÃ*• …¹¹¹°¶¶Æˆ#`jjúRÛÃcbàš1ÆcŒ±Nà[8cŒ1ÆëN cŒ1ÆëN cŒ1ÆëN cŒ1ÆëN cŒ1ÆëN cŒ1ÆëN cŒ1ÆëN cŒ1ÆëN cŒ1ÆëN cŒ1ÆëN cŒ1ÆëN cŒ1Æë„ÿé·Îß"ÿgQIEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/Q8-1g-idx-compress.svg000066400000000000000000004107361416254111300246770ustar00rootroot00000000000000 PyTables-3.7.0/doc/source/usersguide/images/Q8-1g-idx-optlevels.png000066400000000000000000003310461416254111300250420ustar00rootroot00000000000000‰PNG  IHDRÐß}™SsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwxÕúðïlßM¯$z‡P5R.RT$H)^‰» "(Vlˆ^ôwQPA/ ¼U ¢H»ˆBBB Hz/Û÷ýývÈff³©»I|?Ï“'É™Ù3gfwϾ3{Þ3cŒ1ÆcÕ¢ðucŒ1ÆkJ8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€fŒ1Æc¬8€f5R\\Œ£G"''Ç×MñºãÇcîܹ8s振›â–ÕjÅ®]»°páBÌŸ?ß×ÍiR>ûì3>fÍÔ3Ï<ƒ?þØgÛ/++ÃñãÇqæÌ˜L&Ÿµ£®þ÷¿ÿá™gžAYY™XÖï›eË–á7ÞhºëÛ?ü€¹sçâÏ?ÿ˶lÙ‚åË—û°U¬!qͪeݺuèÒ¥ ‚‚‚pýõ×#""­[·Æ’%Kàp8|ݼzóË/¿`áÂ…¸té’dYRR–/_Ž .x¿aÕðÇ 22·Ýv¾ÿþ{>|Ø×MjR¾þúk¼÷Þ{¾nk+W®ÄŽ;ªµî[o½…;ï¼:t€ ®Õ6–/_ŽN:! ýû÷G÷îÝa0п|úé§°X,µªÛW.\ˆ_ýƒA,kÈ÷ÍÆñé§Ÿ6HÝõíøñãX¾|9²³³Å2Ì;'NœðaËXCQùº¬q#"ÜsÏ=X·núõë‡+V wïÞHIIÁ矎gžyß~û-vîÜ ???_7·ÎŽ?Ž7ß|'NDëÖ­]–ÅÆÆâÅ_D§N|Ôºªýë_ÿBAAΞ=ÛhÛÈXc÷üóÏC­V£_¿~(((¨Õ‚¼¼<Œ;‡ÂàÁƒñàƒ¢{÷î0›Íøí·ß°sçN$$$@«ÕbêÔ© °õïÈ‘#øöÛo%'"“'OFll¬ZÕ¸9ݺuÃk¯½†Í›7ûº9¬žqͪ´~ýz¬[·“&MÂúõë¡ÓéC‡Ž÷Þ‹ùóçãŸÿü'–.]Š—_~ÙÇ­mX±±±úƒâìÙ³hÙ²%ÏŒÕÁÉ“'Ñ©S'( ôíÛ·Vß8-\¸‡ÂâÅ‹±hÑ"(×¾ì½ýöÛñüóÏã“O>Ahhh=¶¼a½úê«èܹ3ÆŒãR>yòdµ¨ñ>ú(yäüñÇèÑ£‡¯›Äêá`n™L&,\¸Z­ï½÷ž<; ‚€×_111xûí·‘‘‘!.{øá‡ñðÃKê>_ý5vî܉qãÆ!** ¯¾ú*üqL:D$©ûìÙ³ˆÇgŸ}ævß{ï=ñ«Èyóæ!>>ñññX³f €ò1nñññ8zô¨ø˜¯¿þñññ8sæ V¯^áÇ#&&3gÎDRRàĉHHH@Û¶mѧO·c1333ñÀ oß¾ˆˆˆÀСC«l¯ÓÑ£G_ýyyyb»?üðCqsçÎaÚ´ièÚµ+Z¶l)ÃÊž{î9LŸ>eee˜;w.úõë‡ÐÐPã6Íf3/^Œ!C† ""mÚ´Á¸qãðÃ?¸¬—––†»ï¾ݺuCtt4n½õVlß¾]RßâÅ‹1yòd˜L&<ùä“èÕ«ºwïŽ Àh4þóŸÿ`̘1hÑ¢†ŽãÇ»ÔqùòeÄÇÇcÓ¦Møî»ï0vìXDEEaÈ!Õ:®NÅÅŘ?> €ððp 0o½õ–Ë•É7">>^ruÎf³!!!“'OFqq±Çm¥¥¥aêÔ©ˆ‰‰Áu×]‡E‹¡°°Pò|:ŸóÊÇ–,Y‚ñãÇKÊ ñÄO ..ááá8p –-[&y¿T<öO=õ®»î:„„„àðáÈÇçŸ.Ûö¥K—bìØ±(--­r¿þúk̘1Ý»wGHHzöì‰Y³f!==]²îÌ™3ñôÓO#;;³fÍB§NбcG<ôÐC²Ç3//÷ÝwÚ·oØØX<úè£ÛSY—.]\Þš:zô(>úè#ôïßÏ=÷œl]‚ `Ö¬Y¸õÖ[ŲŠ}ãwß}‡ñãÇ#::Ï?ÿ¼¸ÎÊ•+1lØ0DFF¢OŸ>xøá‡‘ŸŸ/.'"Lœ8K–,qÙÞºuë/jñá‡bôèÑQJJ ¾üòKÌš5 ‚ ¸,“{½9û“É„'žx½zõBëÖ­1}útÙçÙù>ïÑ£:v숙3gºŒ%®ìÊ•+˜={6úôéƒÈÈH 6 7ntYç­·ÞB||¼¤_ÈÏÏÇí·ßŽûï¿ßå=üûï¿cúôébß4räHìÝ»Wvû›7oưaÃ…Ûn» ;wîtÛÖ{î¹*• ï¾û®ÛuXEŒ¹qôèQ@wÝuW•ë½øâ‹€6oÞ,–õéÓ‡úôé#Y÷øñã€Þ}÷]—ò'žx‚PTTÍ›7¦M›F†‚‚‚èôéÓâz‡"4nÜ8 £û￟ž{î9ú÷¿ÿMË–-#ôý÷ßK¶ûä“O:uê”Ûýغu+5ŠÐßÿþwZ°`-X°€¾ùæ""úüóÏ €ø?ÑÊ•+ Mž<™´Z-Íœ9“&L˜@ …‚zôèA'Nœ ˆˆºá†èñǧÐÐP@›6m’눈R©T4zôhZ´hµk׎ÐÓO?]åñOJJ¢ PëÖ­)00Pl÷Ž;ˆˆèÈ‘#H!!!ôä“OÒ+¯¼B}úô!ôÖ[o¹Ô5jÔ( ¥›nº‰LÏ>û,Íž=›JKKÝn¿°°zöìIèŽ;î 7ß|“^|ñEºõÖ[iÞ¼yâz'Ož¤ ¤yóæÑ«¯¾Jýû÷'ôÊ+¯¸Ô9aÂò÷÷§Q£F‰¯ g›}ôQúôÓOI£ÑД)Shúôé¤R©( €rrrÄ:NŸ>MhäÈ‘BóæÍ£·Þz‹ @è7ÞpÙæ]wÝEZ­Ö¥,--Ú·oO‚ ÐСCéÅ_¤^½zš2eЏžÉd¢¾}ûRpp0]¸pA,Ÿ?> µk×Vù={–"""(00æÏŸOK—.¥ë®»ŽÆŽKèñÇ×ýæ›o}þùç’zäö#55•Ú´iC‚ Ј#è…^ =zºçž{ÜûÐ3Ï!"¢¿ÿýï€n¾ùfZ²d =ðÀ¤V«©uëÖ”žž.Ö3bÄjÓ¦KÝ#GŽ$Ô»wo—ò¸¸8êÑ£‡Ç¶mذÐÎ;%Ëä^oÎ~ä–[n¡ÈÈHš;w. 2„P×®]Éf³‰ëÚl6û,µiӆР/¼ ®—žžN-Z´ Î;Sqq±X>~üxR©T”˜˜(–mÞ¼™t: š1c=ñÄN …‚þýï»lßù9Ó¿Zºt)ÍŸ?ŸÂÃÃÅÏŽãÇKŽQ×®]%Çž5}@3·œãóÏ?_åzëׯ'´xñb±¬&ô¾}ûÄγbÇzþüyR*•/–9hAè÷ßw©;??Ÿ ÝqÇ.åf³™ÂÃÃéoû›Ç}vć’,«*€nÕªeee‰åK—.%¤V«iÆ byff& ‚@7Þx£Xf·Ûéºë®#Nç|ÙívºóÎ;I©TVø; 6ŒÚ¶m+)2d ‚àRGii)uïÞôz=¥¥¥‰åÎÛo¿‡Çm=öØc@òACTX:Ý|óÍ€Ž;&–FêÓ§i4JMMË'L˜@húôéd±X\êÂÂÂèÏ?ÿË?ûì3@o¿ý¶Xæ  Ю]»Är‹ÅB$½^ïR‡\ pçwúùçŸ]ÊŸ}öY@ß~û­X–œœL4pà@²X,ôÕW_‘ 4sæÌ*ŸÓŒ3€Ë»ÉdO2ê@7ŽA|¸;O,÷ïß/–9ým·ÝFv»Ýeý%K–:xð KùêÕ« €xÒV•üü|IÙW_}EháÂ….åmÛ¶£Š¯GçIErr²XöôÓOKNN‡¸®·è{ï½—Ð?þX£Ç9ûÆÊï"¢~øÐĉ]Žƒ³OzðÁŲW^y…PJJ •¿†ôz=ýío#AÄ~*??Ÿ =úè£ÛöÜsÏJJJ’,s@ 3fÙlËŸzê)Éëä“O>‘|~=òÈ#bÀídµZ)66–üüüèòåËb¹Ýn§qãÆ‘Z­vyM|ÿý÷$͘1ƒˆˆþùÏJNžóóó)""Bh;OŠ###©¨¨ˆˆÊOÒ)66–ŒF£¸îáÇÅçN.€7niµZ—Ï7ÖôñæVZZ eË–U®ç\~þüùZmçý÷ß,X°J¥R,o×®n¾ùfìÞ½V«Õå1}úô‘Œ' ÆôéÓ±}ûv—á$[¶lANNæÌ™S«öUÇw܈ˆñÿ›nº ˆ;ï¼S,ŒŒDÏž=‘šš*–ýôÓO8zô(î¾ûn´mÛV,W(¸÷Þ{a·Ûñý÷ßת]çÏŸGbb"úö틞={Šåƒ'N„ÑhÄ–-[$›1c†ä«Z9D„Õ«WcРA¸ûî»%˵Z- =={öìA÷îÝѯ_?q¹N§Ã¤I“`±Xd“l V«ÅÿGŒ"¨Q£\^—Îãí6SQxx8n¾ùfñµZ &Àh4â¿ÿý¯Û}ËÊÊÂæÍ›qË-·`À€.ËfÍš.Ã`:wîŒU«VáçŸÆœ9s0sæLtëÖ ÿ÷ÿçvNv»›6mB«V­ð·¿ýM,×jµ7nœÇÇW%-- ;vìÀ˜1cзo_ûá4}útÉðƒüãÐh4Xµj•Kù‡~ˆÖ­[ã¶ÛnóØžŠ³Z”••!##ýû÷GLLŒìl‚ `öìÙ.¯Ç‘#G(šå´aÃÉñwÜq‡Ç6Õ'ç˜é˜˜ɲ[o½Çž{î9É:ݺusyå¹(0mÚ4—ãpûí·C¯×cÆ b™óµ¾gÏåý‹ÑhÄ믿"¾}ûûöíƒÃáÀ-·ÜâqŸ~ÿýw(•J´k×ÎãºÍš5 Füßù¼%''‹eëÖ­—~¦L™"©oÿþý8uêî½÷^´jÕJ,wö•V«»wïvÙÞÂ… ±~ýz<þøãX¸p!F… ˆëlÚ´ ÙÙÙxüñÇáïï/–kµZL›6 YYY8rä`çÎ(**Âøñã]†4ÆÅŹôÝ•uéÒf³Ù¥ßgM'2·œJVVV•ë9—‡……Õj;IIIÐëõ²&iii°Ùl¸xñ¢Kr\||¼l]>ø >úè#|òÉ'xöÙg«V­BHHˆ¤ƒ®OcÇŽuùßÜ5 *•J²ìË/¿ÿw}'OžÄĉ]ÖµÙlPëŽ7%%Àµ®ŠFމ7ÞxCrâ£V«e×—sùòeFÜpà ujÇ‹/¾(i‡N§ÃðáÃ]ÊœÇuôèÑ.å­ZµB`` ËRN7Ýt“äd`ĈÊǺóÇ222$Ï‹³}•Ÿ—©S§bß¾}Xµjôz=6mÚT­Ùi._¾ «ÕêèWlk]tû‘––&»jµZ² …Bö=É“'cÓ¦MX¶l‚ƒƒqìØ19r/¿ürµÆ§¦¦âå—_ÆÎ;‘——ç²Lnʸ~ýú¹KЫW/Ÿo«ÕŠ+W®`èС’\ çsí-ζfffJNN…B›Í†={öÈî¯ÜqOIIB¡¼>´Z-n¼ñF|ÿý÷ÈÎÎFDDââ‽{÷böìÙØ½{7bbb0tèPÄÆÆbïÞ½˜2e vïÞ ¥R‰aÆyܧ‹/",,Ì%ö$ C‡u)«ü¼åïÁèèhtëÖÍeݺ´Àµ¾òÈ‘#’ײsJÀʯåÅ‹ãÀX±bZ¶l‰ÿûß.ýóý±}ûvìÚµËå±¹¹¹b#FŒû ¹“Ž#F`íÚµ’ràÚkââÅ‹èÒ¥‹ì:¬éáš¹Õ¹sgžƒ7çòöíÛ{¬“dü àï﨨(ɲ¨¨( 6Lò¡èîl¿ÿþ0`V¯^… "55û÷ïÇc=&©£>¸üïì +—;—U<ÊOXZ´h!YΜ9ˆ‹‹«U»ŠŠŠ@€T,«œŒŠÀÀÀÕ/·Ÿum‡V«u¹ú \;®•?XËä^_rÛtžV•ØçüðlÑ¢¢££%Ëd? KJJ”KAAAnë¯ÈÙŽªÚZ]•s?¢¢¢d÷cÖ¬Y’osÜž?øàƒØ°aÖ­[‡Gy~ø!”J¥x5»*ùùù¸ùæ›Q\\Œx±±±èÞ½;t:î¼óNÉ7MζTæ|8÷Õd2ÁápÈ«ÈÈH—o¶Z×®]”÷‹•¿¹pž8 $$Döñr}[QQ ƒlÀí|Í!""*• C‡ÅÞ½{ADؽ{·xß|óÍbÂÛîÝ»W­×hûöíqüøq˜L¦j÷£ƒArÜ+?o@ùk_nŸÕjµË·zÀµ¾²U«V—/ûÛߪ}ø£>† 0sæLlÞ¼Ó¦MÃþýû%ßBTæÜ÷ªÚZ‘3¨8TÉé×_•ÝáÇã™gž©Ö~TeÈ!ˆÅªU«0sæLlذcÆŒ‘ **Û»w/.^¼ˆ×^{Mü†(bNŸ>]ë)${¬<»Ý^«zkcðàÁ€5kÖ`úôéõRgÇŽqäÈüúë¯8p Ë²={ö@¥R¹¡Î@ùÇÄÑ£Gñè£(¿rº|ùrø >øà<üðÃ.WŠu>üðÃ’¡3rmÊ_Ç•Ožå^{NÉÉÉÐétb ΚÍÜò÷÷ÇK/½„¢¢"<ûì³²W÷V¬X3gÎ`Ê”). mÚ´AzzºËX7øâ‹/$uŒ1øÏþS/íž2e ÂÂÂðÞ{ïaíÚµ¸ñÆѽ{÷j=Ö9NØ›w¼á†àçç‡Õ«W×û]»víŠÐÐPìÛ·OâäW]ù*YMèõzÄÅÅaûöíUN;Õ±cG´hÑ€Ùl®÷vTåäÉ“’aHÎ@uРAn×¥K´lÙŸ}ö™xU¹*§NÂc=†¡C‡âã?Æ{gƒâ…^ðøXÄÆÆâÈ‘#âU¶Êm­¨M›6®|8=zT2¦gÏžˆŒŒÄš5kÄ+quõàƒâ·ß~Ãc=†’’’jŸ fffŠmªèË/¿¬s{à 7àÏ?ÿ”ô9î¦"k(7Ýt&L˜€Ý»w×hºÄª8_§•‡$%%!-- ×_½ËIšóŠóóÏ?»Ý.þ?lØ0¨T*ñäEnÈçóUùØÖ‡AƒÁn·KÐÊû ”ëÐétXµj•ìçQeÛ·oÇŠ+pÿý÷ã_ÿúžzê)|úé§.Ï‹sˆOÅi"Ýqž¼Tn[III•w~MNNF·nÝê4="k„|’ºÈš «ÕJÆ ³òwìØA/^¤={öÐ?þñ@]ºt¡‚‚—Ç9gæ=z4}:EEEIfá0™LÔ¥K ¤åË—SZZ•••QRR­^½šî»ï>q]ç,+W®¬²ÝÎlo´nݺío@@ 2„6lØ@û÷ï§sçÎQÕ³pTžµÃh4š3gŽdrYëï¼óŽ8=ß‘#G¨´´”®\¹Bûöí£Y³fÑ/¿üâ±íîfápN»4}útJII¡ììlzûí·I¡PÐðáÃ]Ö5jµhÑÂã¶*:tè ‚@ݺu£]»vQaa!¥§§Ó–-[\Žý| Nu—œœL999´bÅ R*•4hÐ —:'L˜ ;ûÁÖ­[ mݺU²,((Èe¦ç,!!!tÓM7QRRч~H††êòx¹çeË–-€H?üðRvv6ýüóÏ4oÞ<Ú¶m•OãÖ½{w £K—.‰Ÿ1c ‚ ;µbe7n§1KMM¥ÂÂBzÿý÷)22R2 ‡Ãá ~ýú‘^¯§%K–Pjj*­X±‚ºtéBááá’ýpNC6dÈJLL¤¢¢"ÊÊÊ¢Ÿ~ú‰{ì1úúë¯=ûŠ ÉßߟP›6m$³u¸säÈ@×_=ýøãTZZJ;vì ˜˜ v™q¨|ŽaÆIêqÎܳfͱìàÁƒâsõÛo¿Qii)}þùçMjµºÚ³p|ÿý÷´fÍZ³f µnÝšôz½øÿ–-[ªU‡súC4uêTúâ‹/è÷ß§äädÚ½{7Ýÿý€&Mš$>ÆÝŸDDeeeCAAA´yóf*(( 'NP\\œd–¢ò×GDDLSçœ2N¯×»Ì’S•ÔÔTÁeš8'w³pÈõ#ééé€,X –ýù矤×ë©C‡”˜˜HF£‘vïÞM­Zµ¢ÀÀ@Ékâõ×_ݱcÇľrÏž=” ΂qñâE ¡ØØX*++#¢k3ðøùùÑ™3gÄ:ï¹çyä:}ú4Fºxñ"mß¾&NœH………âºñññ¤T*iÅŠTXXH©©©4~üx ‘…£¨¨ˆT*•ìgkÚ8€fÙl6zå•WH¯×‹A©ógÖ¬Y²ÓRÙívqÞRçOÿþýi÷îݲW®\¡;î¸CR¿Z­¦Y³f‰ëU7€NII!A(44Ôeº¡êøøãiĈ¤Ñh\:û†  ­ZµŠ‚ƒƒ%Ç cÇŽ’)ûä¸  ív;½úꫤR©\ê?~Ü¥?¹ýöÛÅ©K+ÐË—/'…B!; kÚ¢j|ÂʧÚ:sæ N:…½{÷bÕªU¸çž{°víZ·c‡ûí7$''£GèÞ½;, ®\¹â6QíÊ•+øã?——‡èèhôîÝÛe\œÙlFzz:ªL\;sæ ºwïŽyóæáwÞ©Õþ–––";;AAA ÿoÑ¢ôz=€ò¤’ÜÜ\DGG‹›Û__ IDATS¶åI2/^”MÆÊÎÎFYY™lâŒÑhÄéÓ§‘’’‚ÀÀ@´oßÞe `U222`³Ùd§ÏÊÇ™žù¤¯›"1iÒ$òóósù:ýµ8¯@W¾ã`S“ŸŸ/{Ú—’““I¥RÑìÙ³}ÝæEcÆŒq¹ «ÚÎ;I:yò¤¯›ÂÏÂQCW®\ÁÊ•+qüøqqÞÉ¿ªU«Vá†n€ÑhDff¦ìlÞd³Ùðꫯâäɓغu+^{í5·Wcc5·ÿ~|ýõרºu+ðÚk¯ùºIÌ‹Þ|óM|ñÅ(++“|Ĥl6V®\)ÎÍš k衇ÂÛo¿[o½Õ×Mñ9¥RÙ w÷«)»Ý޵k×"** ¯¿þ:žxâ _7‰ùZ­FÛ¶m«=sc¥P(жmÛ}ÝÞPNœ8/¾øă>(;/k¾zöì)™A…¹7~üx_75 ]ëׯǑ#Gðî»ï"88gΜ‘½ùcŒ1Æk¾þ2W - .\¸Nç69Æápà?þÀå˗ѯ_?—! ™™™x÷Ýw«œ,1ÆcŒ5Í~VïŸ~ú ×_=еkWÜsÏ=²ëåååaðàÁèׯî»ï>DEE¹ŒïÛ¶m²³³1fÌ >%%%˜4iRRR¼µ+Œ1Æc¬höC8±mÛ6\ýõX¾|9´Z-öïß/Yï®»îÂ/¿ü‚Ÿ~ú -[¶Äúõëq÷Ýwã믿ÆèÑ£a4QXX(®ßµkW$&&¢{÷îoÓËcŒ1Æšf@W“É$  ³²²ƒE‹áÅ_Ë;tè€Þ½{cÛ¶m’ºœtDDDC7›1ÆcŒ5"|éÀ±cÇ`µZÑ£G—ò^½zá矖}LRRRµëÿ裰aÃI¹§0ÆcŒ5´´´4IÙôéÓqß}÷ù 5MC³]W®\Éô<½zõBvv6l6[êß°a~ùåX­V—ò´´4˜ÍæZ•;v ©©©õV_ZZRSS]ÞDžk·ÛqèС*×KKK물^bb"RSSëÔæÊe{öìAqqq½Õ—––†C‡¹Ìùíé±þù'Ο?_åz‡물޾}ûêÜæÊeß}÷]½Ö—––†Ôè±G…Ñh¬r½ÄÄDÙÇ:—Õçq±X,سgO½—sçÎáСC5z¬sŸÝ­W\\ŒÃ‡Ë>655U|ßÖ×~¤¥¥áرcõz\jÚ_9û–ªÖ«ª¿JLLûŸúÚÇãܹs>í¯œ}KUëUÕ_íÛ·¯Îm–ëÇ}Ý_9û–ªÖs×_Uìwêk?, >ìóþÊ]ê$×_åääÀjµâرc²þX¾¼‹‹·5І &)÷Ýw egg»”¿÷Þ{€òóóë´Ý#FP¯^½êTGe[·n¥­[·ú´N£ÑHsæÌ©u}ûö¥ .Ô¨ž<þøãu~¾êZç7ß|CŸþy­ëìØ±cÚW3gÎôy , ôôôZÕyáÂêÛ·o¶çI~~~½ßݯ6uz:ŽUÕ¹páBZ¸pa¶ç ÷-ò¸o‘Ç}‹Tsè[zõêE#FŒ¨Ñöþjx Î圜œìrc€Ó§OC«Õ"88¸ÎÛ(..ÆK/½„áÇcøðáu®¯[·nu®£®uªT*Œ;¶Öu6¬ÞorNçÓ:;uê“ÉTë:‡ R£öUÇĉ}^ç-·ÜÿZÕ„aÆÕh{žèt:ÄÇÇû¼NODZª: P£mU÷-ò¸o‘Ç}‹TSî[öïßýû÷£¸¸˜o”䉯#xorwúÇ$´jÕ*—òo¼±^ÎØGŒAƒ ¢óçÏ×ûŒ¦¬!®è4 qE§©kˆ+:ÍAC\-n¸o‘Ç}‹÷-®òóóéüùó4hÐ ¾í0pà@DGGãèÑ£b™ÉdÂo¿ý†I“&ÕË6t:ÚµkW/W³cŒ1Æê[pp0ÚµkWïß¶4GÊ—^zé%_7¢!bÙ²eHLLľ}ûŸŸ£ÑˆÄÄDôèÑz½ …J¥o¼ñ ŒF#ž|òI\ºt «W¯FHHHÚ°oß>dffÂÏÏY»ÖäuêÔ ‘‘‘P(ø<®¢nݺñWg•¨T*téÒ¡¡¡¾nJ£Ž6mÚÀ`0øº) ÷-ò¸o‘â¾ÅÕ©S§°wï^««àý÷ßGII‰¯›Ñè,Y²Ä×MhtJJJðþûïûºNbb¢K¦=+Ç}‹<î[¤¸oq¥Ó録²2_7¥ÑûKÝHÅWk×®õi;cŒ1Æ<á¸Å³fš1ÆcŒ±úÄ´—$&&"!!Aö¶àŒ1Æc¾¶mÛ6$$$ðаjàÚKâââ°víZ_AJJJïòØ9sÆ×Mhtl6RRR|ÝŒF'''999¾nF£Ã}‹<î[¤¸oq5qâD¬]»qqq¾nJ£Ç´—dffúº 'úÈãD)Nô‘ÇI„ò¸o‘Ç}‹÷-ò8fñŒ“½€ã3Æc¬©à¸Å3¾í%<š1Æc®>¾í|&ÇcŒ±¦‚ãÏø ´—˜Íf_7¡ÑáDyœè#ʼn>ò8‰P÷-ò¸o‘â¾EÇ,žqí%< _Š}äq¢'úÈã$ByÜ·Èã¾EŠûy³xÆC8¼€¿ aŒ1ÆXSÁq‹g|ÚK8‰1Æc'V_ö>“cŒ1ÆXSÁq‹g|ÚKx@¾'úÈãD)Nô‘ÇI„ò¸o‘Ç}‹÷-ò8fñŒh/áùRœè#}¤8ÑG'Êã¾E÷-RÜ·Èã˜Å3ÂáüUcŒ1Æš Ž[<ã+ÐŒ1ÆcŒÕÐ^³p0Æc¬1ãY8ªh/‰‹‹ÃÚµk1qâD_7¥ÑàDyœè#ʼn>ò8‰P÷-ò¸o‘â¾ÅÕĉ±víZÄÅÅùº)Ð^Âò¥8ÑG'úHq¢ò8‰P÷-ò¸o‘â¾Å•ÍfƒÉdBzzº¯›Òèq¡$$$àСC1bÆŽ‹±cÇúºIŒ1Æc.¾úê+|õÕWØ·o ÄI„UàÚ 8›•1ÆcMÇ-žñÆcŒ1Æj€h/áùRœè#}¤8ÑG'Êã¾E÷-RÜ·Èã˜Å3 ½„ïê#ʼn>ò8ÑGŠ}äq¡<î[äqß"Õú³¹þþ8fñŒÇ@{%bŒ1ÆX}3™."3ó3 Ý>‚ ®—z9nñŒ¯@3ÆcŒ516[!²³7ÈŽÒÒSHOÿ6[¯›õ—Á4cŒ1ÆXâpX‘•µv{©Xf±¤#=}5L¦ ¾kØ_Ð^Âò¥8ÑG'úHq¢ò8ÑGª©&ú44N"”Ç}‹<î[¤šJß’“³ ‹ûqÉaac¡Õ¶ª·íqÌâÐ^Âò¥8ÑG'úH5ÅDoà$ByÜ·Èã¾Eª)ô-……‰(-ýÝíòÀÀð÷ï[¯Ûä˜Å3N"ô¾£cŒ1ÆjÊhLAfæzò¡šN×QQ÷ ¾¯‡rÜâ_fŒ1Ækdl¶ÞÆI„ò¸o‘Ç}‹Tcì[ˆÙÙ[«¼›`XØxhµÑ ÖŽY<ãÚKx@¾'úÈãD©¦èã œD(ûyÜ·H5ƾ¥°ðÊÊN»]8þþ½´ ³xÆI„µ@D¡Úëó`|ÆcŒyRV–„¬¬p7îY¯ïˆÈÈ„†½þÉq‹g|ºFÖ­[£cÇŽ˜:u*Ÿ¡1Æc¬^X­9ÈÉÙ÷Iƒ!ŸÜàÁ3«~j`íÚµ¸téRRR…þóŸ¾ncŒ1Æš8‡Ã|5iP~ì±B¡A‹Ó Tê½Ü2æÐ5ТE €B¡@hhh’Tx@¾'úÈãD©Æ˜èÓp¡<î[äqß"Õú–ò¤ÁÿÂjuÿ^ ›µ:Òkmâ˜Å3 k衇B·nݰwï^¼üòËÕ~÷âDyœè#Õ}N"”Ç}‹<î[¤CßRX¸Fc²ÛåÁÁCáç×Ë-☥:þI„D„¤¤$$''#44C† ‘]¯°°ûöíÃÅ‹qà 7`РA’uòòò’’‚×^{ ±±±xíµ×ú(vìØ›nº ³gφÃápY/447Üp-Z„­[·zccŒ1ÖÌX,YÈÉÙ wÁ³Z†ððIðvð̪§ÙÐÑÑÑxçwpüøqÜ|óÍn×{øá‡€¤¤$ìÞ½»víÂÇŒ/¾øPTT„Ÿ~ú ‹iiixÿý÷qçwzk7cŒ1ÖL8&dem‘Ev¹B¡Edä4(:uY2,Èß_ßMd4ûºwïÞ¸ï¾ûзo_¨T*Ùu.]º„ï¾ûS¦LÁP~[Ì!C† K—.X½z5Àn·cÉ’%èÙ³'fΜ‰ž={bÞ¼yÕnÈ—âDyœè#Õ}#N"”Ç}‹<î[¤|Ó·²³¿€Í–çf¹€ððIP«Ã=Öd:gBÆš &¢ðÇÂzk!Ç,ž5ûº:Nž< ‡Ã=\éÇÆÆâĉ€|ùå—8{ö,öíÛ‡  88¸ÚÛØ±c¦M›†xÀå'9Ù5qà¯TöþûïcöìÙ¢-©lÉ’%¦-¥Ì™èÓÚҘʜI„¡-©Ì™DØÚҘʜI„¡-¥¬b¡·¶{äȧ0¯í‹}…óçsÅÿƒƒ‡ã‰'ÞõX_ɉÌš: ©WRù{óQò[í^÷Θ¤gÏž˜6m8Vµ¿D¡S||úè#(®ç,Æ¥Ìd2q—q—q—qY3,ËÍ=†ÂÂíP(®k6›mP«•P( Ý1EŒ;ÜÕW°¯?Àl3C­TCqõæ*‚J@Д v ¬QûL&“KÙœ9s T*9‰° òcþbŒF# ((È¥Üù¿óUFRN:މ˸ŒË¸ŒË¸ŒËšv™Å’‰’’o\‚gÐjËC1µ:·C·õ‘½%¥'K˫Һ¬C6BÑÖ"øÝïE¨Âå±UµÏù·ó·R©”¬Ï\ñ”'ÀéÓ§]ÊO:ƒÁ€ÀÀÀ:o#11 ضm[ëbŒ1ÆXÓa·—]M´Ê.W(thÑbÁýÅ:»ÑŽÌgŠÁ³;úz¨kw}tÛ¶mHHHàùå«híÚµüþûï.å¿ýö›¸¬®âââ°víZLœ8±^êk8ÑG'úHq¡Î€é‚©Êm DøäpªÚM}7qâD¬]»qqqµzü_ Ю¿þztìØÑåŒ+??§NÂÔ©Sëe|W)¾[˜<¾[˜Tc¸[XcÄw"”Ç}‹<î[¤¼Ñ·äçï‚ÑxÎíò›¡×wr»Ü|ÙŒŒ2`Í‘¿z €°ÛÂ:*‚P÷y£9fñ¬Ù'æääà©§žìÚµ v»ñññ€7ÞxC¾ñùçŸãïÿ;æÌ™ƒ~ýúaõêÕÈÎÎÆ¡C‡Ð¢E‹:µÁùuÈ!C0qâD¾ ÍcŒý””œDNηËýüb1Ùíò²ÓeÈÞ’ ²ºÕÂ«ç<+O¶mÛ†mÛ¶‰1 'º÷—J"9r¤ÛeÓ¦MChh(Ö¬Yƒ?þÄ3ÏpÜâœaŒ1Æ«£¢¢_Ÿ¿Ëír­¶%ÂÂÆIÊ©FdoʆÃìpûXA) lBü{û×K[YÝqÍcŒ1VKv{ rr¶Áht?–Z©ôCDÄ]×°«øh1rwæîcg(ô DÞ ];éí¸™ïðh/9pàßÊ»Nô‘lj>RœD(“åqß"û©ºö-eegðçŸÿª2x%""¦@¥º6ô‚ˆ¿;¹;ªžU!*DÏŠöZð켕÷¼²½¦Œh/i×®ßÊ»Nô‘lj>RœD(“åqß"û©Úö-Däæ~‰¬¬p8ªNú ‡N×öÚcm„œ/rP˜(?K‡“6F‹èû¢¡—Nu×Pœ·òn×®×¶ÙTq¡ð`|Æc¬y0›/#'g ¬Ö<ëÄ!,lŒø¿£ÌÌÏ3a¾d®òq†î„O ‡Bí›ëœ·xÆc cŒ1Æ< r °ð €¨Šq -BCGÃß¿¯Xf͵"s}&lyU/ „‘!€P/Íf „h/±Ûí0™LP©TP©ø°3ÆcM…Õš‡œœ-0›/{\W«mððIP«CÄ2sšY³`/³»}œ ::qõÒæÚ°Ùl°Ùl°ÛíP*•>kGSÀc ½äСC|#•J8ÑG'úHq¡ žk7R9tèOÛÑpí%mÚ´Á|€±cÇúº)'úÈãD)N"”ÇI„ò¸o‘Ç}‹TU}‹ÃQ†¬¬ÈÍýD–*ëQ«Ã= ÁÁà ×B«‚ ýßlÍ}º™2@‰¨{£`èb¨ÝNÔ£±cÇâƒ>@›6m|Ý”F“½€ã3ÆcM‡Ñ˜‚œœm°Û=Ÿˆ\‡QP(4× @îW¹(>V\åcÕ‘j´˜Ñª Æ5´“ãÏ×3ÆcŒ1æ#D6äçïBQÑ/×U( ƒ¡›K¹Ãì@ö¦lSU>^ßQˆ)Phy0@SÄ4cŒ1Æþò,– dgÿVk¶ÇuõúNŸ¥òÚ­µí%vX³¬Èû.–̪‡|ô@ØØ0HÛ„qí%eee< G%)))h×®JΜ9ƒnݺy^ñ/Äf³áÂ… èÔ©“¯›Ò¨8ÃÃÃ}Ü’Æ…ûyÜ·H•÷-ç‘…‚‚½ rŸèÔÒÜMa”¦Ú`ÉÎ5Ç k¶SÕSÛ]­!7… èÆ Ïëú€s޲²2 ¾“ݘñ¹—ò8ÑGŠ“åÕg!ÁjÍ„Ñx‚ €J µ:*U0¡i}øsß"¯9ô-d#Øòm.²5× kŽ£vm¬úó°êÏÁ®ÍTÿÚ\É)BëÈ`±Lph¡ÏMi펕 €&BmŒÁ#‚!¨„ZÕãKf³ZmíOþ ¸wñ’ÌÌL_7¡Ñyÿý÷ñÒK/!88ØóÊ8f©0™ÎC¥ „V­¶AãùÁÌ’%KxÜY%Î$ÂeË–ùº)Š3pâĉµz¼Í–£ñL¦ó0ÏÃá(“¬# (•AP«Ã®Ô¡âo•*¤Q×õÙ·4‡fs/ž‡Õ«ÿ &ju(û<³–t ,阯˜aI·À–g9®¥n‘`ƒ] VÃyXÃÏÁ¡¬Yòh™ÉŠ»ÿ‡ùÓ‡TæV0d‚ÂXõ@¨‚:\ uÄÕŸp54( û¸VGff&ßÐN"ôŒß0¬ÖgQV– ³9Mfú!M$´ÚÖWê¨Ta„¦w5€±ú`·_ –˃f›­°Ž5 P©‚\‚ꊿ¯Ñø‚ÃaÙ| &Óy˜L`6_à:ÖW”P«Ã¡VG@£‰„Jåüâr+j¯µùj°ì ”-W,°æY™…T¥°èÏÁf8«.­ÆC3äPB›?º¢ëªð¡Ô!Ò Y®† i¾Ÿ%·xƽ›—œ?7nDll,bcc}Ýœ&‰È“éŒÆ³0ÏÂjÍóôX,™°X2Q\| Pè¡Õ¶ªT·‚BÑ4²¢«)»Ý“éÂÕ@ê|µnQ3›­6[L¦s•– P©«®k’På€Ãaˆ,nÿ'2Ãá°@TP*ý PøA©ôƒJå/þ­TÐØ¯¾Ö„Ãa…ÅrFãy˜Í`6ÿéq>c"»Ø?––^+•K`}-ÀP?£Ãä€9Ý kºõZÀœg)Ìâ¡üo‡pµLi†]›,` IDAT›& ²‘u ¤‚ʵ±Ô–ŽÐ‡@ÝæZ¬ WA¦† l¾re§N©S§pþüy´oßÞ×ÍiÔ8€öFƒàà`èt¬Õ„ÍVt5`N†ÉtGÕwwòÄá0ÂhLјrµD€Z­6:]yP­VG ¾>(ó&"+L¦4ñ*³Å’Žú4jÙØl…°Ù a2—,U*Ä€Z”’`¸<v–Õý £+J¥¾B@íWáo±ÌY®P4®± D6˜Í—Å“£ò€¹~Ž‘ K,– ÙÀºâÕjµ:juy`MdÝn‚ÃQþSþü™`3•ÁœUKn1,y%°ä—Ân4‚„kÁ²Ãß Ô÷s,CTjhÕàçß †NІûA®†*HÅÝ>N‡àà`h4Moø£·qí%áááˆ÷u3ùD‚Ù|eeÉ0ÏÂbÉhàV¬ÖlX­Ù()9P(´ÐhZ‰µV…BßÀí¸¦©&úÙa·—]GKWƒC½Œ‘å$By99Y°XÒa0\ ¤.y¼òØXØíŰۋ\¬÷º/^ÌCLL0”JwW™ v{ìö²j]•¿v5[…BWéG®¬âO݃o";Ìæ?a6_¸z•ùR­æÔÔtìX»„ÓŠuEåÃt r€l{±ö{ùïb;ìF™×c_GT”%ztþÑð ¨ÐùÅ b¤,ö-ÁÜ·@§NЩS'|úé§¾nJ£Ç´—T?‰Ð‡ÃŽò©òo×2A®~ ª‚B¡† ¨¡P¨ÑT¾št&úha4¦Àd: £1v»4‘É›3L¦s._I«ÕaWƒiçUêÈ'Ø’‰lWƒa#Ž21Øp8ª*“ŸiF¡Ð‰ÁtÅßîþ– ¸›saùûºüjÝn¬pÏ$)»öS^öí·Ç@äÀ­·Öß — ¨ Pèa·—ÀwW¯ëæÓOcîÜá ¬ŸHÈvu¼xÍÇŒ ‚BT ‚4Ð.¿"~íÿòÄèò!&Sˆ¬uÞ•+ñöÛµK8Ù‡¥|þd²F3ì%vØŠmp«q+ëú"J}y¬4(¡0(®ý­UA§kƒ¡ ôú.P©Ü'“6ç¾¥.8‰Ð3N"ô‚„„”•%áÝwgÀüV†¯ý® 1 ¾T«<– ‚ÊeyÅ2…B "@èjÛIü»üCöÚßrerËíöR)°X._-«;¥2€­Áƒp…B¦%´Ú(•þ¨xEC„«Çª¼¬âßrëÉ=F  Š«º€ô@Q¡ìÚÿ•—9àp]^çß•Ëìö2Õm¨L]”q¬ªó«õòUœ¯'ÇÕ׌óÄÒ!)“[O~ç1¯ø¸>_reKŸ_ùeD6™@Ø_ª‚ €F ®=ôúÐhZ_}¿[aµæÁj͖̓›-6›óÿBŸ¶™ÕŽRé‡Ã(ûYCVº›¯þ¶\ûí,#»÷ŸwA-@ ‚2@ ¥¿J¿òÀ¹â ¥Òz}g ]¡×wl’305&œDè_ö‡Ã«5Ç[[»z5°ùÏ=]þásõJCgh4-Vk.ÌæËâÅ’‰ÊYèuápX®Ž?¼Pou²rÎàÒfó”$ÊjK­Ž€^ß:]{ètídiA ¦…øžª¨üŠlA…€ºâïÔç{ÕžRpõĨ= Êôƒ½Ô sQ¬æ,˜M™åCØìÙ°+óÁ÷Ï›B£(’”bÀ¬ÐÉÛ§VG^ ˜»@§sšÁXCãš59J¥z}'èta0t’Ÿ\>gmüýû(O®2›¯Àl¾$Õå_Q3Öü©TAÐéœs{¨TuªïÚL áÐKÞ~X­òÁµÍVPoÉn‚ „B¡… h Ph%;VØí¥p8J¯þ.«·oº+¥Ò:];ñyV«Ã`¾lFÁ70ž30]]S JÄ@‰ò!É$ØAêØÕ¹°©sàÐäÁ¡É…CUj "…FqíªòÕ€Y¡½, ‚J¥sØ‹VÞ¢ÕÆÀ`èZåÐ ÆÐ^b±4ÄoòœèsFÓz}ùUf­6¦ÆãA ®-tº¶b™ÍVPé*uz£HÀªK¢Ose·;pùrÚ¶ õuS•üüò¡J!!—r…½¾½4—ß4Ã[P«Ë§ª“×å3s”ÔùbP UÃÿvþ_ÕÓr ÊD"#ìöRñÇ\Ûí%’rwcú…B®ôúöÐjÛA£‰—Y³¬ÈÚ›…²3׆³¥æ¤¢cxGÙºRB°„Aa ƒ]*,°Ã®Îƒ] »&M.ìê\8TE¨j@*mùi Të¡òƒ:ÄÚ0hàôwŽû¾ ‚JeùooÌGÍ ÊòøN„žqí%99|µ³²ª}ÊÞöâÐ •*¨Þ·¯RC¥ †Ÿ_ù¼ÜåSC¥_ ¦Ëƒêºßh¢æê%ѧ™)-µàÓOã…x&›Š~ý5 0jToètmÅaju‹Fzà A|ß5$¹;–'] P®NUYµòDÚŠAv©L2§Ü 5>\¡ÐB«m{õäHþy¶æYQ¸¿%¿•Hš±2q%ÞžøvÍ6JJ(-PZ"€ ÓÚAaƒM• R•5¤-Tzt- ÐDk i©&ZU`ã 78‰P'zÆI„^€’’y RÃ`è®3ôúöâ.fv{ñÕùV/U¸J]÷lø¦F¡Ð\®Ëp5±Ï…B¥²üA¸öwùÔuª« Š¥âo׿Ë*|µnlö_­×ŒpõŠœtz´Š358grP*ý¡ÕF¡©Ì¾ÓüÑÕù«ågQqήRu^N¡Ð@«m#ËÐh¢Ý^•µÛQðCJŽ—x-ÑO¡ûöÎ;¼©ªãŸ$MÒ½'”–M E–"""C¦¨(‚¸ë|ʼn‚'¯yE8PAEQAAªLAv)«-£»MWvrß?nÚ¦M:)móyžû$9÷Ü{ϽIN¾ùÝßý•=‚=жѢ‰Ò m£Eå×úÊ» †"¬›–W(Zm4!!ã3#¨3e¨ª¼®þX×z…BÂn· I$Éêx´¸´U¾®o[Õ}Õ$g(¨ÌþPÿç*•/^^]€á5¡¥P©üðöŽÇÛ;ó±Z,ÙNn9Žž’c½ü¨PT>w^ïl’3™Pe»Êý¸f‘(Ï`âœÙ¤1Èâ¬\ìz¹}^ýucþ̨T>õ´ôIN©ñª mç6ùKeçŒ$UnÚjî[þ>8?V¿êZçúþº_'ûíVÀU…²|ëº5ZõCQñ^6†ò´†6›ÿ:ó¨Ûô6Š’‹(Ù^‚di‚ê|JJG Ÿ¯JÎ~á«B髬x]þ\©Ú.B@7))™¼ûî: Ä Aƒšl¿*ÕÙOÕ#§Ý+üq'ˆ/ M4š6øùõoáÑT êÊô‚U…wå:©ÂªÙ…Mš…BQ!ÚÕjáû-¸°‘ÿzÕY¼Én²S¼¹˜âÍÅØMõ¿ƒ£m£Å#PÁÎâ¸ü¹ÊG%’Y\À$''“œœÌÞ½{éÙ³gK§U#t3Éøñã =÷‚B¡:+¢Ë}%BAý+*ª¼7ç³ÑRú¸'/ONy.Î+g“óyn‘¬ÅÛŠ)J.®¯¿pöŒñ$»}6½†õ:‹£;÷sKUâââ eÓ¦M-=”VÏ…e>lA CÅS ³páBJKEpeufÏžÝÒChu”úªRn-T弜[ìP²£„Sð×Âz‹gM¤†ð[‰¼;’yŸÏ;˃<÷sKUBCC‰‹‹Ã`0´ôPZ="ˆ°Îø@ h”î-¥hC–‚ú0{{84ŸáS/h0B·ÔÍùwK ‚óý!=ºßu˜³ÍõÞÆÃ߃€Áøõõ÷˜‚³ˆÐ@ ´"ŒÇ®/Ät²þÅ\TÞ*à{‰¯ÈŽ!4â[ÖL˜L­¿ªUssäȬ֦)ë{>‘’’ÒÒChuX­VŽ9ÒÒÃhuäååU *9Wçk‘•œå9d}–Uoñ¬Ð(¼2¶µÅ ­âYÌ-®ˆ¹Å=B³ÔÐÍDvvvK¡Õq^ú4"ˆÐèãDèžsmn‘ìÅ›‹9ýîiô‡ôuo(<øð'ú±h‡¢ÔÖýs.æWÄÜâ¡YêF6Â_ î02‘ÿc>æ¬úù9+” |zù8$á…)8;ÝR7âÛ'A3c7ÙÑ­×Q¼½¸~EEàïMа Ô¡ê³>>@P;B@7€²²2vîÜIPP -=@ œƒ”í/£`M¶[½ú{uò"px Ú6Ú³<2@P_„t=ÉÏÏ'..Ž÷Þ{3fOVVV½·ù®œ«>gèãŠôq"tOk[,…²—e“»2·^âY¥!21’ˆÛ#šD<‹¹Å1·¸Gh–ººžššÊòåËùñÇ;v,ï¾ûn½·ù®œk>Í…ôqEú¸Gº§µÍ-’M¢(¹ˆÓïÆp¸î o ‚ QADÝ…g{Ï&‡˜[\s‹{„f©DØHžzê)æÌ™Sg_áŒ/&Æ #ù«ó±äÔ¯Š wœ7Ác‚E€  Eº¥nÄ7´ìرƒ¯¾úŠ;v´ôP@Ð ±lèÖé(ÙYR¯ A‚ÇãÝÍûìN œ1ç½€6ìØ±ƒ;vššJçÎyâ‰'ÜöݰaK—.åäÉ“ôéÓ‡iÓ¦Z¥Ï˜ç€!(5«RP;999ìܹ³^}ûöí+tÊYä¼ÐIIIÜ|óÍDDD`4éÝ»·[½jÕ*&NœÈĉ¹êª«X²d IIIüõ×_rÆøñãùâ‹/èׯ_ƒÆ!ò]9räíÛ·ÇÃã¼ÿ6ˆ””âââZz­ «ÕJZZ;wné¡´*Ê«ÿÑ¿Ði©¹Å’o!ÿ§|ŒÇŒõê¯Ö2.M¤æ,LFÌ-®œksËßÿÍ7&£T^\k?»}ß|cdüøñ:ŽÉdB«Y_jã¼ÿ»;|øpNœ8AVV ¨±ßŒ3¸æškX¶lÓ§Ogýúõ¤§§³xñb@ÎÂ1tèP ÀîÝ»ùàƒøùçŸë=áïJk ôi-ˆ@WD {D¡{š{n‘lº?uœ~ÿt½Ä³ÒSIÈÕ!DÝÕlâÄÜâŽsqn±Ùa±L®u±ÙÑ1„f©›óÞôVgŸ;wrðàA¦L™RÑABBË–-ãé§ŸF¡PðØc Óé*,Óõ!&&¦#?ÿyûí·[z­´áJ`` ø¼¸¡±Ö¥óæü¬ÓA‚yõ ôIð!xt0*_ÕY™+bnqåLç–¢¢"¾ÿê+°Õí®Ó!>ž+‡mô±š¡Yêæ¼·@ׇãÇУG*í={ö¬XÌ3Ïî©S§¨J"P=¿w"Pµ_]ÇHth’Î;sýõ׋;[õà‚Jc7zôhŒF#6l¨Ò¾`Á}ôQ233‰ŒŒ¬hŸ7oO<ñÅÅÅøùù5ú¸‰"Œ@ œ7”ì*A÷››¾A‚*þý €R-lVç#ß,]Jé”)$Öà6d&õèÁ·{÷¢P(ÎèXß'%qÃM€f¨í ±ËjI~Tý€$¾ÿ¾ñw©„n©›óÞ…£>h4²š^¯¯Ò^VV€Z­>ãc””” ÓéðôôÄÓ³éãŸËˆ B÷ˆ@Wε@ŸæBºçlÍ-æ\3« 0¦×/HÐ3Ö“q!¨ÃÎü7¤)s‹+M1·L¸õV®{õUn;tÈ­¨úT«åæ§ŸÆ,I˜l6L’„ÉnÇh·c²Û+^»k+]¤·³ÿ°ÄæÝžp ÀI÷ƒ‘€ƒ¾>£ÑˆÑh¤¤¤äŒ ‡B¹mÚ´`ÿþýtìØ±¢}ïÞ½7‰àݶm3gÎdôèÑŒ=úŒ÷w>°páBfΜI```K¥U1{ölñ¯¿å>º*å·Y…/tUšzn‘¬º:Šÿ*F²Õ}ÓVå­"hD>½}ÎØâØ”œïs‹]’°W{”À¥­âQ’ÐétÌ?ŸÞx›$a•$l’„ \^[íöŠvçuF‹…m}»s_L[®’”ØT*,*&µƒFÃ_-—úy³?#£AçSP©©ò’žv{=6R [£ëç’ï† X³f Û¶mcøðáÛÉ‚pá@öîÔ©¯¿þ:O?ýtE{÷îÝ wéßP1Ûí,üøc$¨º8¾Ä.íõX'!W  Iª|^}Ók$©öuŽöê}ÏÎ{vNçT½¯ËújýšI’@¡¨úHåûÔ˜ugƒš>+Îí5}Fœ×Wì«ú¶Õö]þÅéyMm(U׃Û×åTL[Nïy•©ÌÑ^ÓôVÓ5®íTåóUm¿õ^ç<†ÚΡ¦ó*o«¶wÁ­p¨­o5ÑQýséî3êòžÔð9–jèS½ßÙ ¦ý×x­%°–X±X±Z1X°Z‘ÌŽóRÔ¼ ÏžøõöC¡U¸½n·«öŸÍkRý½-ÿMq^WëëÚÀýgÀݺ꟟†¬³X­¨<<Ü~–[ŠÕ¿®f—eaûíÜo¨\ö/PêJ ]û_Ž&:ÂÃÁ×ÕJl·ËB¹\4¸ËÃ#ë.<¨¹àNðÊdLe'y`ÖÝ̘1£Qç$\8êFX :0tèP¾ùæ{ì1<==Ùºu+©©©þðUç€^Ï;.Îÿ@ h1$°émØJmØJK©ÍÕʬu,µ òVáÕÕ @%XÊm¸rêô)ÖlXƒÁjÀ¦°¡Uh‰‰ŒaÔÐQ¨TÍŸÍÄ£ÑHÑî¹UkÇh€=ÀE@Ð8Üd0ÃTnèé aaXƒÃɶ‡q´8œÝ™a˜]…uHtí*/eeÁlüÆÌܨkS4»QæI—.]šðLÕ9ïô‰'¸ä’K(,,D’¤Š@Áäää ¿§… 2bÄzôèA||<6l`„ Lž<¹iƱcIÓ§7r$q#G6É>@PO$°•UËenÄrQ(hcµxÆxÖ~ CÐ(þHþƒi;)m[ZÅ´›[œKÚçiÜ~ãí-ã«k·ÃÞ½h’“¹UoGÕ×€ÙÿÔj®0𠀄•‚§sÌ©Ñ'Nàqâm¶À`À€¹„aô GFp\8þÂÀÇ€”%:Â9ˆžxÜût$i¼I#¾Q1III$%%‘œœÌ Ag–Kú|ç¼wá(++ãÇt»îꫯ®òÅËÉÉáçŸ&==þýû3jÔ(”Ê3šNLL䟂&¼óÎïë|¢ =Àèh”-lAhmä=Jh§N-=ŒV…ÝfCwò$Á±±-=”V…¾°ï  Ië¢ -¿À($ØJlXK¬ØËìHö¦ý¹óòÀ»«7J¯s#»Æ¹6·œ:}Šå¿/§¬]™û&h_О;'ßÙèc4xn1™`ÇغJJ*š-*Ø {‚-?SBO´;·Pv‰UþcU þyJ"•Îñ'Ì`#\Ê#Œ\´Ô£R±·7„…a $KR°ñð!n5PÙíhÍf<Íf<-–=Ê]Óg0räPÚ¶mÛˆ«7ß|3Z­V¸pÔÂyoöññ©·9<<¼Âï§©)uDË *ÙöÙg ™:Oÿ–J«"ùý÷?wnK£Ua.+cÛgŸ1úÅ[z(­ŠŒíÛÄ]-›œ%Üe&ùýÅ\zÝÐzŸˤR£Ä«³êðÖ‘]£¾œksËš k(kSƒxÐB&™œ:uªÑB±ÞsKQlÙ;w‚Ù\Ѭ×*Ù¬¶³ã"0j€lèÙ:všº(’;Bq¨bÿòo‡MƒgY':z]Ìð6m‰ÓèðÁc¿Ý]—ÃÈ+ÜŸœ$Á]¯rC¿|ûí°x IDAT;Ø$‰Ý¥¥$±©¨ˆä¢"²œ2{8£V(èéãE¸5‹Â¬ìÞ¶ ãÖ,9i4À~O•säè!»7Eìˆ`Ó[›\LeË–-lÙ²…/¿üRè:÷Λ‰îÝ»3uêÔ–†@ ´$»„ñ¨QvÑ8¤G²6Ξ£P)ðêê…o/_¼ºx]°fgÇŒ¡Û† DÕbÌ”$ Â’_~©×>OŸ>Ù!ô Kÿó³Ï²tÖ,n½î:¼¼¼Ðh4´iÓ¦^û,4ò̺gXüÏbÊ£mº…tã!o0eôzN<<›vÁöuà‘ú«ìWb&Ða·ÏÀÓÓëº]ÇuÝ®Ãd3±öÈZVXɇ~ ØTŒÑf„ #LjKÙê6X~É(•pÅðã"T£°Ç?} E+Do”ï’*0r$<¼ÆŽ•7)-õÀ¨:ˆBq1¬[_ãy*ºzà"ŸŸJ¡ ¯Ÿ}ýüx4ZθqÔ` Ù!¦7qH/Wf±H;Kõ€?Žƒ‘ãPô?‰dØ …{àØ¤)ŽóXûÄg€мŠHKKk°€.7òýûï¿ ÚîBDh@ 4+ÆFôûô”í/ÃVêþVv}жÑâÛÛïoTÞ¢ “3Ï/XÀƒ3ÃákëŽéQQ<¿`A½÷9bÄÝäçËwPKÙFÙ“rvˆôQc¸dè4|é@HÈöï_Sçþ>ßý9ÓN®>ðsW<ÇS—?…¾D8 Ò>`ìSï±jUZ®ív-×v»“ÍįGå‹]ËY9JƒÒÐËp…¬€>žZ }2¾º„¢Òg€ÄDxè!¨®G%Iâû>¬{@ƒ.'""¢ÆÕ¼¼èäåÅÏ\‹…¿œõ®’,åóÑ ûµ0öÒJ‹ùØ[ààÛÐÃHPqñññõºV‚Æ!t3a25þ–äùŠ"t"tEºç\ "4g™e}eXu ÷k.Gå§Â÷"_|zù  wŸ®KÌ-йsgÊúö%ó§Ÿ(÷èMÊg–L ¬oß}§ÔêH²³gzˆŸý‚¶_Ê"ÖPv`:X}‰ŒL¬u?ó2eõþLÿ³¢mLç1,»ŽAÐ—×Æ®ÅÅrŠd…¢rç×ZŠ-W°-*Ʊ¿ô’ì®röŒ¬,hÛVÔ#n„}ïdÑ|ûí•´]ðóóc´³%»‰S«ÊxÇw[o³±µ¤„ä¢"þ÷ÛϔŴ“3|¼üråFÃFÂÏ_B× ‚ÁD;¬ÛAÖ͹Qô<`ÿþý$%%‘’’ÒÒCi5,\¸ÒÒÒ–F«cöìÙ-=„VGii) .léa´:’““INNnéaÔˆ%Ï‚nƒŽS OqúƒÓ%5J<+<ø$øq[ÑG4"¨Fñ bn)gÚÛo3×) Îyf™Å´ÆåZàÊÿÂý£ª¶ß5îxž(=ëoï9Ÿ•9o½Åýwß}Ne êܹ3ú¾}9ýÓO´–8ÚOzµšÎ6@i©¬>ë°Öë¬VŽ\æjæÏØæoƒÞ/Uíл7¬Z“Ò8ªMÀ¯[h,ŒÐÝØø‘®ý‰S=‚Íÿ¸Üß®‚­’¿á¿¼k: %×À˜LxàøYð1Pþ9º€mÛ*ûöï_õuLÚüÆ»m.éw >šöåºrøh|×ÿÉe—^vFÇš¥n„€n&bbbZå@ 8³mûvú_rI½·±•ÚÐÐSº·TΠq¹<<ðé%gÑP‡¨ëìÿÊsϱáãiç`«‰¿ôz¶:Ô(¬ÓéxsÙ2N°àÕW¼}‹±gÓŠ‹™ ¼åÔ<˜–‘÷Ý'7xzÊøâ‹+—øxP*)µÙxûäIÞîÝËïS¦0ÖlÆ(LH€øxLwÝÍá3òö|ìÀ›íÚqm-½±˜Íf®9’­6 ÑÔìëí‚N¯¿ï¼#‹b†i“'óÆÚµü';›£¢˜¶q#tê‡Áöíò²cìÚ…Ùjeñ¸q¼vÛmd†„Tìº_j*ÞšÇw÷ÝWë ˽wøÉgÄz®b÷æ“>‡ƒŸ<§ùiùÑï?)Ž3$0[ÂÙF`½~$à_À5Ç/<”šÊqÇy« ^ž0ßü|P«)[²„Ó·áP¼¶î†ëÁcoi0èê1––aÓ¦MHF#½Þ}·Ö~F£‘M›61dÈFGÖP.ÍDvvvK¡Õ±páBQ‰Ð ³gÏ~ÐÕ("~ÐU) l*÷°isæ{çÏ7­\‰d•Ð֣ߧGŸZÿrÚîP(hÛkñíé‹w¼wƒüškcê¬YL[³†OÓÓXÌD–B{ ¢G"88¸Þû›”˜ÈÏ>ÃÙ¹<@Þƒ Ådz­kWî±ZÙ_RÂÍO=…¢z*´3Ä`00ý¥—ØÊãÏ?ÏÜÿþ·"¿r˜Lðî»ðÚkPP ·)pÓMðÚktîØÃÕWóÀÏ?×§OåѸ8y¹ýv¬’Äg§OóÊÑ£¤Ûí»NHKãåO>áúM›˜âëË•sþÇ> ;U³Øm %†€`tm2ÐeËAœñÆý¼Ñô ïÏòìl¾ÌÉá^]’ø½°ß yððaÆsKDãBBðr\S³ù$°¡ò šdðþtïRásRÆÖÑ—ðíž=Ýúûûó¡ÅBï‘#å*,[†eÄ,ýˆ,ÂX.Ë?@ rÝp€ªÕ [C† !åÏ?ëîx†dggsÖs.#*6‰‰‰dddðè£Vø AkbÛöíŒ[¼˜Ü[nA))èþá,ºx"m­m°íuï &àÙΟ¼»{£ò=;·”_¼óN&|ñ½ªý¤ÝååÅ›×_Optt¥¿€ó&;Ü:¹dffÒhO+àÍn ¤9çJNI­[ÁñGC­P¡Ñ¥Ñ¥ÕYþÜ©-J£!B£A]‡kÊÂE yóó7É(QcûC”SÿCŒŸ…iwLãáûvÝ@’`ùrxî9p¶„ sæÈ¾Ì¶oß΀ÁƒÙ²q#—8ÝY°K_åä03-ÃNÇ]½½™Ù¾=7ùù¡Ü³‡Â_·óóçчS¤ÛM•ÀyD Â6°ÑÖÝ^^^¼på L8µ²ª/û?%%|™“ÃW99œ®vwÖO¥âú°0&“ñýÏTT°–$f-]LþÀ˸Uo¥oÏ>üìÏáA•òu ðð`V‡<›‹rØ0(,”ß×%KàŽ;0™LÌ™ó>K­oW\ч#®¬»ãyFJJ )))¼óÎ;ÄÄÄcN- ´@ dßç«°Ú§Ix*G±tÑï\=~<]½¼ðh€_2€&JƒO‚> >xÈ?5ÿõÇŽÕ½±RÉØk®Áßß¿æ>lÚ$ç0ûí7¦îÙÃ’T‘i`7ÐÎ` øË/k?žsôZh(fƒžçì…¼ê@¦Ãú\A\|û­œ½Â׋$qÒdâ¤ÉTéSì¢VWÕÎbû›ÏñKÊRJà áêé P`Ÿ|i[góÜoÏqèÈ!Ìqòë×ÃSOÁΕm ð¿ÿÉeòª±hÅ ¤iÓX´bE…€þ.7—ÓÒØ_VVѯ½§'/¶oϨïù¿KµèRNª?€[¬øFB§à¾Ð&d;ñ7§å}˜§$âµéû(]AûùùÑÏÏ7:väÏ¢"¾ÌÎæ›Ü\tV+%6ŸgeñyVá=»2),Œ[""H_·ŽâaC1 ÆŸsç²çžÛÙᔪprx8ó:w&òØ15JÏᅫ`:­VË /ˆŠÀ‚¦AX ›ÄÄD@¤'­ ŒFv%ýË»ËÿBׯêúÝ»!:uXݼ¼èáãCçZÄ´:T]!šÕ¡®Âéë%KØðÀ\WKLˆ ˜æãÇQ:»GH’œ–Á!˜IN–]œx˜ôr¼Nôòâ­®] .,„¼<Ð׿8‡èš@®»J}Ò{ÉÚÄÇ‘–LfH%þ¡(¼Â@‚ÕɆýáÀj•Ý0^ªL§œñ AÃ.C£ÓèãÏó'öÉ'„­[Gxa!¡EEh"#å‚wÞéZ]ÙWþâûî#ý±Çˆ?ŸÿÍ™ÃÅÅüã$øÛhµ<˽QQU,å›7øqÏÀ®ðèaP‚ú¼ø=ÅH H ”…õÁPùñ@ ƒb- ò?ƒ‰1cÖIdÍŸ_õ@àñâ‹HW]…ÍÛ»ú0(²Ù(2ªø0W®,’¯ã 7ÀìÙðÌ3°r%ee<Õ³'¶m‹›TgK—Â]wÉz[¥‚ÇßùÏVd€ðt\ƒ¡pù·ò#@‰æ÷´‘$M¾æÏ—ÿÙlðÍ7òÒ·/<ú(Lž 5dyðS©¸#2’;"#É6™èóÚkd>ýte• ¯Ë/'áßñìØQ.Å=|x¥xž1£^âÄÜR"ˆ°n„º·BAs#Y%ŒÇ¢ù»Á5psq1¿®Z%[pkJ—f6³{7wÜ6‘½Ñ6V…—±B[ˆ¾Z2³ ® 㦰0†ÕèæQÝ †œr`)²Ugrõ""dÁ\.šÛ¶­ñ¼ ¸!.ŽÁ7ÜPÕú\Oô=QÃ:R\º Ô—Ѷ´Œ2z‚¤ôJ”ÿì%㛯i[XT.GÊʳ C}HoãÃæloû©9–8ëå—»ôóøë/&¼þ:‹ívNÞ<‰â'Ÿ!7,Œ\‹…³™\‹Åís£Ý}ÆAÇŽ•;xRS«ømå•”WFw`ž_Œ[ñ `½ür’ÛEq¥÷öÄ.eÌ®|îí{/·w¹eÍ?ãéÙÙ\^ZÊ)gñ б#þø#yyy.î?3fÀì¹&èóÊ+ÞÀ”ʇaÛ·‡/¡ëBÉÈÌÀ%¶œÅ³2SÉ`ÿÁ•â GøðCùŽÂGÉéö22d·”—_–Û'M’…oõ¼ã’ÄkLéO¸žœJEZ¯^|}ÅL>tHn»ÿ~!ž͆°@7⟜@ 8[ØÍv ‡¢ù°»¹î”s[Š‹Y[P ^V+BC Q;\0<@«FÝM¦£¦ÂÌD@@€Ë¾Š­VVåç³"'‡_ 1Û«?\£aBh(7…‡sE@ÊÌLì3fpãçŸó€JŤn ó_15+ ±c™ŽGª. űcÜsð ‹ßx£bhwO)æó€yà—YÑ7Ì;ŒG.}„‡û?ŒÖ®åÞ©÷ò[ÆoäµÏƒ BÓB3‚Þþï\M*ôý÷²{‡Ã©‚Ë.“…ô„ –”×µ+·Ý†µ†ÒáØløÜ}7Ó32˜yûírºº&ÎË}¡"tKÝÝ $&&²qãFÌøñ㛬èÁ¹Ž"t"tEú¸¢?¬'cC†4Aê º7p°µ¸˜5ŽbÞ*‰‘‘Døhñêâ…wwo¼ºx5º* Îj%)/¯srX_XXQN¹œ(³™×®eÒo¿qbß>žkÓ†ãLà²eôøë/6>\5óF#ièܲèŸE<üóÃXìP¡ì¿»‡ŽúÂ}ý@Rp.Ò`]ý‡"=û46S³ž¶Êu;=Àì©ÂOHÛ0?®˜Ö“/÷~I±©¸Ê>®Œ½’{ûÞËÝoÄÓóJæ 22ªdÈ?Ÿ‹£WÚñÂÛ¤ú¿žEëÛ¶gÚeÓ¸§ï=xyT-æ²wï^’Ö&±c÷.îu1ãG§gÏž ;ñ;e!ýõ×U3ª´m‹4e ÁŸ~ŠnÊÙ »T¿ýÆûÜ—œ\k¿šsKU’’’HJJªÐ,B@׌ÐÍ@y!•ßÿ½¥‡Òª˜:uª"tCbb¢˜´ª¡Óé˜9s¦ôì&;¿Púo)¿¦ü Àȸ‘õÚÖYòòⵚÛkH{xØÄÓK—2tøðFZÌ-î6l˜(¤RB@7âVˆ@ h ŒiFò’ò°êêP«Î¶’’Jó9ÑŠ;«Y:¦=|ÏÂ(ر¦N%oß>¾<˜C‡ògïÞØ x幈#CÐ÷ß³büx®:ôìŽÉAž>‰+'²!mÑþѼyýwÜœ¦ÇŽ[ýᙾuìe*•EÃeïèhèÔÉuéØjÒó½{'²{÷Ç+ñtg©\DWrIÿzõJäß—TÙ6%/…v~Äç»?'WŸ[¹b³'ªã>Ø:Ù¨‘#@G#\&‹çÝ,¾ó&öv-ÄÒ,˜Í°b…l•Þ±€g›w¶í»<=yãÈBk *4¡[êFÜ;‚VŽd•(ü½âÍÅÐ@“‡Ò[Ɇp#ϪóÉŒ‚`­šß{÷¦§ÏÙ,È…,fÌ€/¾I"øÏæÍügìX² àµÕ«y¿{w¬NiÌ ÇŽeÆüù Ð)))¤9—±®…«®º ödïắ®#M'o×+x C²¿ã®Ÿ²±÷(»ï·©sJ%8»|Ûí²—DFüñ‡kÿà`÷âºjiiéLã:'iPcÆl™¸Ð8掜ˬá³XuhíüˆuÇÖa7±4Bm)Â;{C×2ˆ§ùí“xzÖyÚgn»M^þþÞy‡'¾ý–'­V>­Öu/5y²Ï‚Ch@ hŘ³Íä}—‡9Û\ïmT¾*¼ã½ñéîÃÇy<|T®Ì¢V³¾W¯³'ž xóM9ßpyyhµy^|ˆ6/[†uÊ”ªÛjµŒeÝ4HD¿ðÂ$%]„BQKàð9‡à÷Ìß¹ãû;(³Èãó?r»—¿Çî¾¥0Gvoau¤÷©óØQQ°q£\åèQ×%3S. XNA¼lß^û~õÜÃV¢çž:ÇPŽF¥ab÷‰Lì>‘ô¢tº,ëåʲÚ7 R‚¹«ã*/n” ñÙcà@8Г'‰7޽»wW±B¿Õ®o8 ‚–@èfÂTKéÚ DèDèÊè#AÑßEè~×!ÙÜ› õ…y¡òWáÓÝïxo…Õ­Ïåhµ¤ÄÆòÃúõ\Û¨ °P$ƒê9PNó8 ;hJ9=h5KÒŽ‚0Ê•(Ò†qÙ@ˆ~ “íd±ùR—Xþ‘Ì@2Pûõ2›OÖºÞËKNƒÜ£‡ë:›M®>}ô($&ÂI—]¹F# °j•ûcùøTÖ’ÉJóªžB2àœª|Ô~µžCk 44”¨‘#ÙûñÇô”$ÞŠ‰á×_o’}_hsK}•ëFèfB|]–{„xv%00ð‚ù¼”ì*¡pMa­EPPÀ„;&4"È%ÏC¯, IDATƒÆ§OóPj*²x^ß«½›Z<Ÿ:%û9/]Zé§*ÞwŸ[€uüAªŸÖ¸[ÛÅsç+¯PØP­þBŸ‡ËNC”í‚ýoÀÑîp]RŒla&'~©«¸ý±ŽÜx#DÚè¼5 ÌãéÉãÑÑìšv Æ@6ÙÖŒJ5®öœÇµn+‹Ýr뱫€vÅß_ÇiiPRRu]YYeÍ” °Îã³*;UÏ º.’pŸ„F¿¹yâõ×yò·ßx"#ƒ¨#šÄú ÖÜÒ„f©!  (Ox¤¨¡¢ž­ÌFþùèSôn×;£òS:>¯N^.ë><}šÄóº¦Ïƒì–ñ¿ÿÕèç\:7·k{ö¸¬“€Ÿóó9i2¡kßžYðl÷îõ“æ[èó$Œ.”-Ì »*ÄæAáFXDCõÕ|÷äWtm_y=ž?žA¶Yö/ŸÕ¡žJ%—]ÖŸË.sã“ÑÂtèÿþ+?/,”…tZ¤§W}LK.O‚íŸÀ%¥®;Ûî ‡'áÕ¹Èu]+$44”¨#xì‡XÑDÖgàLZ š/¿ù€['NtY§?¤'ÿ‡|leµ¤sàÓÇq!(½”p˜»;„æ¢Ó§™âÏAüvÑEô©E<ïÚµ‹U‹WM%ᥒëî»>))²Ÿó‰•ëÜø9×Ĥë¯gÒõ²oqÒê$¹˜Ì‚L<=<éÛ£/¼ú*ÃŽá„ÉÄK¹¹ .*bP-‚ °Ðá¯VÏΗ€&)’M~AP`åõ8i2ñ–ã\.öóã–ˆˆ:Ï¡µ$/}jˆuìÙöí›ëÚÃîùÐ3¢Ê Óö†Bîc`zHlÆQŸO¼þ:FF6™õY 8„€n&D¡+"ˆÐ="ˆÐ•s=ÐG’$^wDjÝrãVhÉ,‘¿&ŸÒn,„ÕPz* ŒïE•ð¡—^à•+Yœ™ÉÎâ¹W/úúÕîßËÎU«xçôéZû=Ê£;vTMѳ'Ì› tµ0™LL¼{"É%É^TÝäö-Ù[øeü/L}q3¼|1ÛíLÚ¿Ÿ]_LDµê$6üø#¼÷ü¾!n8YU< Ç–{¶´é7+Šj ûÙcÇ08þ<¼Õ¹³[ýÝX,9„…Í®W¿úRáAczNþNîõÏ` ôçzÒ’„††òÜ+¯4é>Ïõ¹ål!‚ëF(—fbÆ $&&ŠRÞNˆ B÷œëA„:ŽEŸ|ÂSO<Ñdû<×}–­\Izß¾Ïo›4 c†‘¼ïó°Ö]ųƒ'ÇÛçÙGn¯˜´uz=åa{}A¡@c4ò–¿?ýêÏÁÁÁôºöZ -¢O Vè] ½òòÎsø‡…ÁË/×èç\=õ¿xÿ‚µkÕó–"$Ž8Æ;¯=ÈÌ~æÙœ2Ífn:p€õ½za±™Øy,E+Òøac…R¦Aß Ð¶Ú5Ü œrÛ½M˜Í•©ÿ))a©#¸ûúÐP®¨ÃÒ}6ùé§÷0kðôœÜÈ#xƒÀò‘ü(¨à\Ÿ[ššòRÞ6l`Ô¨Q-=œV¨DØ ˆŠ>‚ ‰Gžž¿ü¡õëÅŸ#dësϱcÙ?}: o¼ÉŸÓ—Ròw ’½öéWá¡ px þü1›ÍÜÙ³'_92kŒ ç·?”;.ZO=E`i)—ÌŸÏ«W×ÛzTPPÀãýúñY ÅHîæÁìçü µú9ׯܹïòÂ×OaW‹Ÿw)¬ !zÞ|ö«ä"^§~Äpxžœ~®:[h ŽzKÉÝu¼â39äßùS§C­P°¿ºx¹ú“ŸËT­nX3îª B·Ô°@ ‚&C§Ó±z÷nòæÎeÁ«¯¶ôZœe+Wr¬OP©ÔA'Å8Þžû+Á;£T(PJ…•ã±üµ5L…nŒRhêS%x(HO?Í´~ ¤ €m•µ¡ð;xWß{馛të5X£!fà@ved¸X¡w1@püœk½+¾Á˜PG¤/)ò)úûèûøtÄÐöÐm‡¼MÝ<ÐÒ!8–üÀb Žå¹Z¡±úÊL%Iyyü©Óð`Û¶çxˆÀË+±Î~íÛ·œå] 8—Z 4/ÌKƸqØ»ucõêÕ¼¢Ó]°VèÓ&Ÿge1ó½÷0=ÿ<Ý÷KôÛ©@ω 8év;Iû`W°çC±ÓÊNàñÇá7à§*u7ÝDüO°Q¥âóiÓ 7rr ;Ûý£ós½žÇÇϪåm†y_-Wï8Còôyœ²†àztöL6ؼ®ü/¨5Ðå%úfärÿv\3¨Q~Q(PðÏÀ]Üúà­*95x­l àÑ)âããƒE’xúØ1@ö16öŒÏ­5²|ùü–‚@p^#t3!‚]A„î9WƒË­ÏvGñŒŒqãšÌ }®ú˜ìv~ÈÏçÓÌL~-,D¹~#±GÒm­a¹ŽN Švíð:q°KvÀ.IûAò 8.a¯É».=]NEŽdéÒ…ÉÉhýüä(»ŒliÞ”'uØ¥Ps×]7R<[íV¶œÜÂÚ£kYsd ;3wb±C>à_ÇÆ9ðú°‡~0œGöF…åÆNÜÞ·/ÞN¾×ýúõá¤õ ›8ŒÃ=c‹´UJ±#‚;‡ÜÉËŽ€ËwNž$U/[ÁŸ%X­nÔù‹œ«sËÙä\™[šDX7B¹4¢¡+"ˆÐ=çb¡]’¸ù•WH7®²­[7>üö[|öìáÎΉodÁ hý>ÿ””°$+‹/sr(4[ˆÌ‚G ö 3ƒ‡AnÕþR§Nøüý7^qEE›o_‚G£ÔVºX%©Êb‘$&|ø!›nºIî°w¯ü8hS¦°åßA_ƒ‹„J%:‰ˆ€ððª<îåÅãO<ÁgŽŒoÇÆ2oÖ¬]‡ô¢tÖYËÚ£kYl=E¦j9†c¿Сßï" 8˜áCUL cdž2#-†9ì-+ãÔT>¯²ITTÿüúÎx”Í7sâð BbCó cγs2x:«•—ÓÓèèåÅÃmëpœ>Ï8ç–³MkŸ[Z Q‰°nDa3 œñç+6IbyN¯ìÛGêìÙðÜsU;¤¤ÀÖ­pç\äëËMaaL §óyàsšk±°4;›O33Ù[V†1t>ŽBˆAITf&'KK±têäv{ͱc\AŸþrMÞquÿÁ8pàÃ_,g÷ '"ß~›õr^èêB9$”J·Û,øpïÏz…/NÈjÿövaLyöùÏ#5nc°ø3íOÖYÃÚ£kIÉKqé£@A¿6ýÕi¿/Jeóê@¸ösèææ®œ ø¸=a–¤¦Î©øsm“$®Ú½› ¿å÷»vå6mjWii)ÞÞÞ(«ïô£GyÓ‘÷yeÜVã>‚ ¡[êFX Aƒ±HŸee1;#ƒ£ƒ\ÎyÂ׎qqðí·PZÊ`Oi)Ï?N???n gRX±žž®ÛUÃl6óËš5\wíµM2 À*Iü”ŸÏ§YYüœŸÂ$Ñá8\}Âótôô¤¯/f–¬ýËU5çH6wèÀgÉ_‘03¾^â™ÿ³wÞÑQUk~f2éR)iz ½‡Ž‰R¥÷x¹¢^®ˆÊ§"A@AA¤¥Šô @€ $ÒH%½Oùþ8é™$È$žg­³`öÙ³Ï>3“=¿y÷[€Yÿ÷DM™Rêù(OOfíÛÇ©O>Ñøž”J%ƒGæºì:)R˜›Øãá„X¾:ó‡NâôþÓù¹«ïÇÞçdà–q1ô"™ò’é×lmÜx0Ü1‰ĵsÖY wïÎæÃ‘ > ½c¿e&…cM îwôêí/2žŽD®–-éàëKdVŸÑÑԔΥ¤ê3QS<æIF¿DDÐÃÜ\Ï"""•BÐ"""“©T²áùs–††–çןšŠNDŠR|+¥£GÓåôiÒ&Oæ^négß”|SR˜L73³|1]¿Ÿ»ë׳ò·ßðpwG¯XQ—E¥RqóæM:wî\nß{iil‰ŠbGt4±YÙ4ˆ€žA`ÖR]\MLh×г\ßÜ¿}}IKÌ„c—ÕŽ'—(¹nü„Gi—q=ÐÿkõåÎáà¢E\¿q³\á—)‘ ËÝ@Ì”è ɵ¶ž»t…;w2iÒ$^‡o–|Ã%ÓKd6„ðƒ‡™›'ƒOž^b ]©.Ýí»ãîâNkwž]såä& ÿ9_Pí»¤n‡›Þðð7Ð … dºAæg9‹÷—¸†­ž{Z¶¤ŸŸÙJ%£ïßçVÇŽXjèÃ<ïɲóЦ”²+ """¢)¢€~MˆA„%© A„‡fX[Eµ1Ð'M¡`md$?†…U¨…£·ñ·:ës.Êf͈:z”ÛMš©«ËîØXvÇĘë«{59™«ÉÉÌ ¢·…㬭mmM®PÎÎÎæ÷#Gˆ÷ð`Åúõ|þÑGUrO;öîåÿ¾ÿž§·oç[W ó"'‡?cbØ…oJ uÀ%XB¿`0Ï’ÒÊØWÔˆ~HéÍ(Ðÿd‚ûA”R‡K™#H‰íxѦ“=q‚N r"+ ~ûmLeÓ±G(¬Ìn OHÚ)R§N⇠?‘3­ÀŠWÌhžå’ÅÞ½{Á˜‚Ê~€“…o4~ƒ¾ Ý‘>Àß§MÙø?.yèÜ’“!·ú8(û@Jæ™GOss–5n̬  B33™øð!ÇÛ´AZìý+¾¶\NJbo¬àš2ÞÆ†®fåE1ÖN´qm©nÄ BõˆA„åSs•K5pñâE¼½½‰ˆˆà‡~À¬‹°DX’šDÈÄÿß‹iÖ¬Y•ûùçŸsèС*¯2$ÉåüÁÊðpâsròÛ›ò…£#C h¶w/ zô€³gK'56–V¬`ñ‚,06f“~©©ìމawL O33Q'&òwb"3ƒ‚èkaÁ8Â÷ì!¬GrîÝcÃÝ»|:cF¥­Ð*•Š%›7çáÁ޽{™2v, øÚžJH`KT‡ââ¤+iô†ƒe<8èãjbBKctÕˆîèž‹»¤·‰!¸a0AvA$I’àèFx:Ò5¨ ·i¼÷ör9õ‘òkXÇûDÑ3·SïBOˆ¢NÍ^‡S¿X5Auîé @7N—=ò†Ë8ÉÝ ¼ÔŒ?ÁæK N¯7ho¼!ƒA:0zt!ý’|Ò°!W’“Ùé/Xðì œœŠô)¾¶ÌÎUõúR)‹5ªÜj0baIÄ BõˆA„å#V€E‹¡««Ë÷ßO@@v¥äq-ŽèŒ_;ñ˜:•½{ã~ñ"Ç·m«’1cbbhÚ¶-îÞ¬˜ÕD|N+ÃÃù%"‚$yAŠVÆÆ|éàÀ8t$T*•Æ»+:::è–²Ý~=9™]11ì%¼ðxr9’E‹På¦ “]¿Ž§DÂ{ï¿™Žæ2f2†eÇ©cûž=|p÷.iýûÓrÙ2öïÛÇÖèh¶EG•‘…}˜Ø êHe¸š˜àjb‚…†»%ìÜ÷ŒÐf‡x‡çuž£*^Eï\¸âÉÏËÝxóÍPäÉ€o¿… PèëóvÖl³ ¾€:G¡Nª0V‚ $ ÌÅÀ„¥¤ŸKÊJârØe|B}øcï„<)È]W7uyÛð ÌŒpò$<^²‹¾>ôî-fwwhݺdŸI“fqáBIÙ–­ìlo=:_êëT…‚.¾¾×ÁþÁZDDSDÝR>¢€~ ìììðóóô?˜ÀÀ@úýï<Ÿ>z7r~þü*±BOûôS¶[X0%1‘­+VTÁL+FTv6?†…±62’´B¹„Û›˜0ßÑ‘‘ÖÖh`w}iTÀ¥¤$vÅİ/6–è}û„ ݺtZ°@ÈöQHÌêJ$˜ÉdEDµ¹ŽŽÚ6S¾š:•°yó@*EzêJ}}¬ZõÁ%X‚óS0΂ÆÆ´71ÁYƒ ÇÂè;ès3á&£~ø9ÿ §ÔL¬«Ñk€‘ENɳ^òï¨6/µfjÿr,¾1X,…7îB‹bÁ{ àd[Hœ‹§§]»ŽÀØ2õÂy¢¸H`†þÉ>§ø£TåVL¼rnj%[d‚þm8¶Š-þ-Ù’¼|Àâ™úë+u Ñbã!#A½ë@ð¼`Š©)ôï/¼o¼ÎοDbXE) [±±Y3Æ=xÀ‹œF߿ϥöíÑ—JyñâuëÖåçðpB2…ß:9‰âYDD¤ÊW“b¤¥¥N“&MJä­ baIª:ˆP¡P0eæLæÏ™ÃŒ©S+=^–RIPF32x”ž.ü›‘ÁÀb³³…¼º––\V(hºoØÛ '•b–kͳ„š¶†ª±”®Y°€°áÃ!4”°áÃùlñâ*±B'&&ÒµOïÜ)±%œ‘Á’ÐP¶FE‘Sh3ª¯…ó ©3í+`Õï¿; 7¢M¡€¨(Á±¶kWê/^Ì¢Ù³I—HHV(H’ËIV(H.ãßL¥T*8w§ißÐä4Ô‰JŠ´~Kú''Ó³m[ç'Ñ“`ÜÒWôõ‹XK£¢¢ ~‰ô%©—.[ ñìRáØNhŸë"q»1ÿãŒTo c^~Ù»¨xΫ™’gùm” þ¿¡øoÈlc„#¼dŸ¢àÏ…09Š»–g{@Âgüï0w.hSá¾±66\INfex8¾))|üø1ë›5Ã}Ô(þ÷ý÷|Ÿ»‹ÒÌȈ÷ÊÈýOA ",‰D¨1ˆ°|j½€¾{÷.‹-âöíÛáææÆ… JôËÎÎfúôéüùçŸ( ,,,X¿~=cÆŒ©’yˆA„%©ê Â;w?b+vîdú¤IY¡å*!™™Eòãôted–•¥¾œòŽðÎ;EÛÆŽ…Í›á¿ÿ [©$N©$N‹(@b¢P¢yÌX²æÍã}ûˆôö¦Uƒ454¤©‘M ±70¨Åüåˉ5ŠùË—³:·¬öÃôt¾öŒ?cbPºÇ7êÖåÿéin^+T=ÙÙÙlo¡R)wîÌ·B«€°ÌÌùqFO22k`«§GÃèhšš’^Ü¥ÁÒ ss>ÑÑÁÐÑ1ßê©Î2šTØ"šÇîÝ‚˜7ù˜1œY·Ž3ï½WäRR).…u“Bÿ·)–"11‘£wî œ3‡£?þȸˆV%&r ..ÿÇfeÅ|GG:•R˜âu³býzžõï_Ð`b’/ž²;ubãÒ¥ÊÈ!6nÞEçY%OJ$$5hÀÝû÷iÛªU‰Óº–º·3Ƥ 2óÒ—Í‹aÒ$ ³}Sú*«ðÛG_ã¦+¡ñ;³ÑMÄëµ!í9<Ý•™ñĦÅ›Kس0² Šíd©3&Êt™e÷!+æ–¿{áå%üDÙ ^Ü‚“WÀàŒp2s ÐÁ,]2÷³¶ “HØ“[d%:;›…?ý„òljڿ?¦_çÎ }M.HÚŽ(žKbaa!Šg5ˆš¥|j½€öððÀÃCˆ¹zõ*™™%«f¥¤¤°uëV>úè#z÷ÒA-\¸7²fÍš|ýõ×_søðaâãã8p ­Zµb÷îݯïfDJeã΄öè énn|¹p!Ç]] ÊÊ"(#£¨X-ƒ:2MŒhRH˜æýßL&ÃcêT|GVûÜÄ·ßæÚï¿kœ‘#G¥"Y.çiTC³³‰*¾`98 —–†,9™ôB)3•JüÓÒð/Y¡s™¬ˆ ¾´ja¹¾Â¡Ã†á6>L›€T"aŒµ5_9:ÒÆØX£9¿²³³ùù×_1<ãðÒ…[´\Κ øô?ÿÑhÜí{öÐÐØƒ,õ–ÈlGG|®\ÉÐR)Æ­r]4ìËÞÊT(à»ï`Ñ"áÿØ=…þ p¨þ9’¿LÐKmOû›A4_µ òܼ-¢ë—_ÒµXÿ;w2yá'Ð>¾ì 2£Ó„Ne÷Q‹Ð2+–›Y¨¯¯Ï®–-°oJ;;áרQ°z5?N˜PÝÓ©…T“o‘˜˜ÈÞ½{ùæ›o5j½{÷æÝwßåçŸæâÅ‹¯äš×®]#55•ÖÅò.µmÛ–³…rÛΛ7 .‹6lÐøáááøùùPäHOO/ÒOl«x[¾õÙÍ BC!;›ø^½ðÚ³ÿ´42CB Ä `¢£C‹¸8FåÈmmÞœË:pÅÊŠð¸Ú¡Û[´àk''\_¼ E®r`` wT*aÿ»¸O{h(˜˜pG¥"00P£ûЕH°ÔÕåóçõæ›%ÇËÊ"{ÌFŸ?Ox÷îl16f•½=sìíjiI3##daaEæ’$—sóþ}þ å[Nß¿²I Eåà‘‘ÈÒÒ˜jgÇQss699ÏÕý^‚°›pì?ØÔ·/ÇÆçœ§gþQ¸Í{éRFxxht””VÍý ³D}ˆˆÈ?’Ý@úTxI¼TÂß?1bŒýí±j‰¾½~™×xö úô´B€¾ËY <¿›,0Ã? á!ð"÷x uwÕųÓÎŽ·¦éòå•E€LFú† ðå—jïÃÚÚÃd (î—Û– †ÉB_M_{ˆ À™:¿g‰¶ˆˆˆjÿl”Õf…ÃáÃ%åMLÐ71!ýî]­˜ŸØ&¶is[ž&9vì~~~$%%!R6Z# cccùâ‹/pttdüøñlÛ¶¤¤$êÕ«‡ŸŸß~û-nnn´oßž½{÷¢ÔТ¨ ‘‘‘´lÙ²H{›6mˆŽŽÎ¿–‘‘ù‡i¶»gèàíȯï¥ÉÅÓ´úû0õ}Vºœ³‰ÇȰÉ@"“”:^^Û–-q´k—. mu\–ˆ¡d&¤àø̽iF÷)ï1bæozÌüM9^Ƹ†ea¾}mÞÌ• ÌÌðš6¸AƒJ½Áƒóôö},ÎY¾Ó hÜt½ Ž—yzû>ƒÖèu©[·.¶¶ëhÝz:xæõëO*ÒÖºõîÞ½[ퟲÚÖüú+)&&‚õ9" ²LL˜³d‰VÌOÚ´f.ÚÒ&—Ë ÒŠ¹Tg[ž&Yµj»víâÉ“'ˆ”Vä>sæ ǧGÌž=›¨õkŒŠŠbÓ¦M¬Y³KKKî³,”‡»»;™™™%‚W¬XÁìÙ³‰§n¡dükÖ¬á'¦è IDAT£>âŋԩD6OOOBCC9wîÜKQ™5kV¥ƒ ­ß|“€yó]#ooV8;WIFä…ŠŠ”†L&£W/ *ÌS>ù„FFùÙ;ؾ¦Ê·Æäôt¶ÿü³ÆsMLLÄuÚ4ž}úi‰sN+Vp{ëÖU211‘o¿ýö¥|Ur‘ë#¹vø"FgÏÒ¼Ø{¨O…eºR]vaç͇”ö*'³€-õëÃñã a§OŸ2nÆ8ž?#6EPÒÖ¦Ö8¦9²{ýnœ+š[®–Ðgüxþžnغ&&wþòå„ ªö\èСE2rÔ*è“p&œ˜\»taßÍ›4/ð!– ÇŠ¿‘ ÄËÀ>(W<ߺ&À£GÂc++·t=kÃ>@©Tb¤kÄþ±û±}ÓšŸ=<ø&6Ví8?Ÿ4j.ýUÎÎÎ\;uGásÍ€^]{Ñ´iÓ2 Ôf|oÝâ¡™™ ž¡HÀiì°aÌ^ºï]»ªivÚƒ(†J"Åßß… i‰¨G+tÛ ä^ÐÕÕel^Æ‚* ~n~Ðû÷ïÓ±cÇüö{÷îaggW%_JzzzXXX`PÁªf"¥S8ó†Z$B{ô(’‘CÛøwn@_U’˜˜È_þþ(ª^Sº¸ð×Ñ£ü/1±FY¡_†Œ  ’¯% •JqéÔ‰€BVèºÜ@—$TÀv[[võë ?È R)*$lÜ$aé2 Ù9н§”öï¯e•ß÷ئúflº®æÁBÂÏ..$ÆÆ–°B'OÍÍiïë /ñH$š5kV%Õ/k³—.%vòdõ'MLxhf†ï­[¢ZD¤ °°°Ð8»Ñ?­ÐÅIJJÂØØ8¿ÀFRRû÷ï§nݺŒÈ­þV•tëÖ sssîÝ»—ߦR©¸{÷.ǯ’k4hÐww÷*KD`ÓðD_¿ÀU éR)ß­XÁ¿'O®ÒÂ8ÚÌWK—ò¬S'¡ðH)<ëÔ‰¯–.eÍ÷߿ƙ½^”éJâ¼â„<†¹¸6jľ‹i.—cD¦Ÿ‡gÏÐuu-u< ðïÜ#ŸKÂñy~C2|_°f|¬¾-6ÖJ}}>9~ü¥Ä³HQJXŸÕ Z¡ED4ÃÅÅv‰+å¢u:,, '''BBB°··'55gggÒÓÓÉÊÊbþüù,\¸PãñÒÓÓ9uê leggãååÀÀ111ÁÈȈ3fð믿æ*.[¶Œ„„þ£aЬòˆŠŠÂÏÏ;;;ìììªdÌšNe+¶kÖŒÝÿú^qqlÍ‹+\\p*nåoÓ†ÔÔTÌ ¥‚Óf*[-¬ž¥%3â㡜zZš7%%¥Hövbbb¨W¯^‘ö йsgµãÄŽCñ"ž<   B𔄠ð #üðRÛWñuÑA@'yQ ÀÓ®]iß½{¥ÇÏ ²²²ªôX5•›wîÐ:9Ö­ËoËHMÅÀظÈLFâ?`×¥,ÄJ„%+%***ÿµJÙh€¾uë-Z´À>×'pëÖ­èëëÄÉ“'ùæ›o*$ ãââðôô,Ò–÷øÆ4iҀŋ“™™ÉäÉ“INNÆÉɉÇãZ†Eª"}Xù÷ßÌrsc«·÷K¿6""""¯Q·”ÖY »téÂÇŒB¡ 44”7n°b…P’VGG…BAj®%çU •JE«E bwLLþÿÇÙØTãLDª’±žžŒûñGÆ>x 6YýF5k†ÔÍ ®^Í-ÿ*¤Äñ– ž--¡IpqAioÀ#”f*Rd÷‘YX³Æ¢òø8¬;wc×®ýùc6cÅŠöäi÷ÎáÏ?Áªaƒ·Á'T°úövèÍ‘‰G0×7/÷~fýô+r+¢V•õYDDDD¤úÐ: 4@£F044$''sssnܸÀíÛ·éÚµ+‰‰‰Uó,5ÇÓÓzõêň#þÑÛ­UMÛ7¸—–F[îtz™òÅ"ÚJiVh%0ØM±JP&&$´}‡¤:nàâR$@/33“•¿dOz{òºã…)ûxÎ l iÀE`% Ì BBv oìx¿(Á$íáâÁþqû1”j|?Óú%²E볈ˆˆ¶’WP%O³ˆèÒÑ: 4ByíÚµ$$$ðþûïç·ß¸qƒéÓ§×(ñœGçÎÅb1*Dø0={iiŒ³.{ ½&!úŒõð`œ¹9cÓÓQ"T°vö£ÈÏmÛ‚»;¸»“iß™¤E³n&Ð8‡„ô‚L8© •á€G¡^‰ÀEêÕêÙ ¡I¡ Ú>ˆGñBâçq­Æ±ýííèJËÎ]œWayƒÕSÙµ¥¶"®-%ƒ‹’gä›0aBuOEëÑʼ^æææÌ›7~ø¡HU­3f°víÚjœÙË]ÝSÐ:V¯^])wœÚê¾±¤XéáQQ‚³qß¾H6dÔóçìRÕÖçýffŒÝ°A(×|çüðÊî}ˆ;’XªxÖsÒã±Þ‹b­ºÏõê L €Àø@zmê•/žßëøŒú£Ââ }ÇŽ´/”g¾*ðññÉ$) ²kKmå½¶”B^¡HQDÍR>Zñó<%%SSÓ ='99¹Æ¤%ppp¨î)h•zÞ•+ ;™šÒØPó­tm§&íT$%%qp×®|ä²pnÑ‚>ýú•< û÷ÃÞ½BNïBY3Æ£%ƪT¬¶èH¡_/”Ó¦!-d]Œ?|È/¿üÂÞ½{‰-¥D®6rïÞ=–,YB¯^½^IIèwRS Ìõ­MÖ皆™™›–,áë2“}˜nØPÐûö Ç¥KEK—4h€¯‹ÿKà´]<[΀g,««ä‰ù9:èȯ Ô©S‡Ô;©¤ù§•zmË¡–d›dWè¾þ~ö7CÿJr–PÅpÉÀ%|Þóórž%"""R³ÉÛÕºwïmÚ´©îéh5Z! 'Mš„D"aÑ¢E|ùå—¸¹¹áââBãÆ©S§ÁÁÁK€±µÈÿ¹¦!‘H˜¹p!á|€g)[ær`UãÆ¼Ý»7¬X!ˆæ+WJŠf{{5 ƌឩ Cg»ó|x<¨àÇ› L„Ù(“»w™üádýzˆÇŠ»f`ÚÁ£Fd'j. £Ìžâ¾Ã yR‰”_ßú•÷:¾§ñóEDDDj*Í›7ÇÊÊŠk¹éaEJG+´ŽŽS¦LaÒ¤IìÞ½›}ûöqàÀ‚ƒƒQ(˜ššÒªU+:vìÈ–-[jd„Aœ÷«¤2>{rwº››ãP¼ò` §¦úŒš4‰QK–0ùþ}µ Ê}}&fe!qr*)šaôh3ºtÜÊq íÅó®Ï…>é_ž‡ô¼ºFuáZð5|Wøb+±U;/Y]uÝëVìfÚìã²ÃQTr%ºR]¶ÜÆøÖã+6ÆkD "TD¨žš¶¶¼Ä ¢XYYaee…A-û^}hU¡T*e„ ìß¿ŸÀÀ@RSS '99™+W®°fÍšûÇ/:ä—äe}®''ó$#¨]Ù7ò¨i>‰„‰óæ±ÃĤÄ99p8+‹QAAâ¹Q#!?ÜõëË—C×®ùâ 6-VˆñË%½Ä: l9äÒ†<¾öXý¤¤`=ʉžð„øxÈÕš@ ˜®àØ ¬{‚ÁBèü+¼ý*‰C™!^ã½´Z<ƒDXb¡zjÚÚò:ƒÕ#j–òÑÊ<е ±¢OÕ2'8˜ŸÂÂJ$„uëFý2|æE^*•ŠQM›²'(¨ˆz `ŒnÚ´ÀÒìêª~\²³³iêÑ”gnÏJíc“dƒÇUtžëP¯M=ìLì¨g*ükkl‹Õ@+,Ü„<Ð~~0lX"aaß‚#4X£#¡pÜé}]¸—#@WWŸ³ÿ>Mo‡Þ/ùjˆˆˆˆÔlDÝR>âþÖkâÊ•+¼ÿþû 2„!C†T÷tj,*`O®ÿs/ssQ íÚi<œžžúÊÒßW]¹.½öF’"Ai¤$"%‚ˆ”Èõøˆ5å™í3\ã]‘Fw`÷ÊödÅ:ƒä8mƒñ %m•¶ÀI}ZųˆˆÈ?’£GrôèQ®\¹B÷îÝ«{:Z( _]ºtaåÊ•¢O^%¹””DxVP;Ý7jgÏÂ’%pæ 7LFXXv˜˜0ñ§ŸT@<ü~ëwB$!‚ ®Wò|·ÇÝ0Í4Å0Éf›'#:5še9:9x·ð&5.ÿ8`äÖÐ9¨‡â­2‚ ­À¸Ž>ÿ9ºBó©-¸»»3pà@Þ}÷Ý꞊Ö#ª¹×„\.ò‹ñ2>yÙ7t$F×R­Õ>J%xy ÂùÆ‚v==$S¦0±ysv,XÀäÔT;:²Ò$‡~–ôŒþ7gžœžÀ„:'¦TÁYîLãèÆè=×£¯k_º´é€ ñéñ<íúKƒzüqî6qº·Á° C‡B• zeÏ!Í9GÏi<çêF "TD¨­^[ª 1ˆ°(2™ ™L†\.GGG§º§£ÕhUamFtÈ/IE}*ûr³oô³°ÀF¯5TCÑÊ@ŸœØ´ Z¶RÍå‰g˜=ž< 5g‡ÙbbÂÄyó , *ÖÞ\Kë_[ â°¯k϶µÛèr· f÷Ì t/êÒãzÌž˜áæìF—]òÇ Á±«#®mÇpú³eÄýt~ˆ§é±Öö9ÈW=¾ÂP“b;fð8¤”ÀD-D "TD¨­\[ª1ˆP=¢f)1ˆð5 :ãW gxçš5cz=5ûû"UKZ¬_?ýááíVVðñÇdþûßLÿl)¹^5Ä„zÿ <Þ)2Ôˆ}˜‰¡·!mÍÛÒ¤q“•Keæ2î»ÔgüT)‰‰BÛðá°};äum= 5÷Ýî—y»ÒÇR6{lfꤩ¾@""""µQ·”Vîo¥¥¥±lÙ2öìÙCpp0ÞÞÞtëÖÍ›7ÉW_}UÝS©vçºoèJ$Œ·¬_-ññðË/Âñ¢P¡{{˜3Þ}ŒŒÈLLäÂ…,"#z² ¸Í³ý µ%+ò´ k®¯aÞ™y¤åUÍÙ0l Ì–D"¡¿þtÔíHj‚ÿ$p ΊOÿ+E©²áÍŸ ÉŒGç–yý¥­Rý8@ýgõ<`°f¯‘ˆˆˆˆÈ?­tá;v,kÖ¬aÀ€XZZæ·7mÚ”eË–‘““S³{9|||ðôôÄËË«º§R#ÉQ©8ëï9¨n]êêê–ó ‘—", fÍ œ,XP ž[´€Í›!8>ùŒŒòŸ"‘è…Ž:@ÿbmæùýƒ‚黥/ÿ˜´œ4$Hø ÓøÿÇ¿ˆxP)Td…f‘x>QítårX{ÑœO¾7@©ccØ»¾û®¨xXóÃßi éêoÝØÏ˜Yãgagg§ñË%"""R›ðòòÂÓÓSt Ó­³@ûúúrìØ1nß¾««+gÏžÍ?סC’’’ÇÙÙ¹gYq:wî,n…£">gˆÏýá4ÞÆæUO­Z©l ÏñãÇÉ*ìSQ úúúxxxä]~øvîüóèÒ¾øBð‡ÐÀŸY@$E«JR~9ý ËÎ.C™¡¤QN#œ œùªËW´•´%ãHiéiä¤åt?2Ü©(T ¢R£h`Ú ¼ôt8r¹ë#pr‚C‡ m[õ³222âø¶ãŒ1–‹ÒíÓ…$Õ‘ÐðIC¦»Og·s4¼Gí@ "TD¨1ˆ°$baQFŒÁˆ#˜0aBuOEëѺÕ% €¦M›âš[lA*-0’çää “Éjdpˆè_’Õ«Wóí·ßbaaQnß<÷ }©”á…v%j#K–,©Ô­Ck×ÒòÈÊè <:KK!£†—WÑ2Ûƒ ¹_?Í/¬s kó¥4Õ5G_WŠ~š9úé0ȇžQ4Æ1rn}‹  Kƒ. ´ˆn€.)¤ä“™™ÉÞMÉÎn“ß–M·”»è&SÍVfsXu %íèÛW°<—§#7nÌÓ78qò'þ>ADD}ºôaØ×ÃprrÒü^µ„<+ш#ªy&ÚEEÖ–•][j#yA„+W®¬î©hÑÑÑ88”õ-"¢uÚÉɉàà`bbb°±±)" :„B¡ qãÆÕ8×Cü –DÓ+K©Ä+×Òöfݺ˜Õr«Re¿à¾\½šå·n13"¢Ô>3­¬ø2>^(¡‡T o¿ óæAÇŽ»¨ñì\N0 n{tUyî5 H ‚ Ÿ Yz ¡«´ýê §µ½#º¥8‘éèX#—T,”艼ûòuôHâ2~+W‚¦ ©TÊ›oò¦Ç›»G-DÎêÅzDñ\ ñó¢Q³”Ö)‘:àèèÈÌ™3Y¸p!‰¹\ÎX¼x1#GŽÄ¨ÿ¥HíçÄ‹$É匫åîUƒƒtíJèj­Ð¡qq8äþ(AO¦L¹s¡iÓ _/+'œúÎèoÙ™²Ø’bª‚Vé´"º’ödG•ºEÈ‚geUô(–`C-aˆ:€˜JDDDDäu£uÚÐÐ]»v1zôhš5k†L&ÃÝÝ´´4Ú·o/ækü’W<ÅHG‡!µÜ}£ªøïŠ,»v_ÔX¡—Ÿ ^gÌò87hP¢Ÿ¦˜)ƒéi劎²”¤ûz€©™Ÿ#Êw”…¬È©©Â¢ùõ2Ðá–@ 5,BDDDD¤– u„€»€€<Èýû÷ÑÑÑ¡mÛ¶Œ9²ÆVÆÑ$¨K›™÷Í7ÌþðClªÐ¬I OºBÁ‘øx†ZZb\CßÿÒ˜ý¯ñ"8¸H[Rz:æÅvYê6nÌO›6•? Jâpå ]]žŽ…N?$zz8|õ|ôÔ­[Ê@𑉛¼ PF‰l i9|0SHìWò(ëOD‰‚TÂ1Ɖ‹X“Iíú¼,b¡zÄ BõˆA„%ƒÕ“••…¾¾~uOC«ÑÚÕÅÐЉ'V÷4ªŒšDæS§ˆJNfËŠU6®&>GããIS¾³µÑ}ÃÁÑ‘7wì``¡ÌžÀ–B}Îèêâß¿¿úáêUá¸r®]ƒ¤$þ , ïÙ,37gîµkЬY¥çžz'•û¿¾M’sÈ@¡ÌA*Ü4¬m%H ¤H¤èê 5”’”)%s«à½K¾šÌõ OØuí<ж÷ L/(’öù¾ý}n4¾!þN¬ð'""""RkÐ:‚‹/®±ƒê¨‰A„…­ÏyT¥º¼@Ÿ¼â)ud2×B÷< >¤‡•g‚‚Í3@ÀP"–- Är÷YÖÿ]±‚Î'Or£ïYbf"×l$àX reŽ˜Y¶:-Ó©?F]ð:æÅ É ü”~†±†4ˆiÀ¢Y‹ûöØ ]/;;¸P¨ED…³…Ô¼‚JUD¨1ˆP=baIÄ BõˆA„å£u«KÏž=‰‹‹#**Š•H­¥mÔÄ Â"Öç<9íåU%Vè²}ärN%$ð¶µ5ºµm""B(›½mÜ¿Ï{À4` °Áz}ݺlݲÜÜÀÜü¥/åààÀþ'^Êúœ!Ï`ÕµUœÜtÇ'…òy¼hDg«¼Ù»~~Ó‡¿ÈÛuÞæïËBßQ}éѽÆÆÆjF.###.‚BR0Œtþúˋѣ‹·o_{_1ˆP=b¡zÄ Â’ˆA„êƒËG¢ÒB‡âåË—ãííÍÌ™3éÛ·/ºººå?I‹Éó%ª) —÷ùóܹ~ïΞ%þË/KvxöŒî»w3cøpFŽy%Ä]ilzþœéœn׎uêTù5^;iipà€ šÏ£HBd©”•NN´~öŒ …y㫯˜õÍ7Õ2U¹RΆ[Xè½Ç[Ž4‹Ì dgP“^2ø¢´@Ÿ}±±(rwÆkQö ÷aÃÛ¨ÿòóCOÍùlàœ©){,€[·Šž40€¡CÑìî¥7½7w.·lÁ·Š¬Ïܹs‡{ïѦEÚµkWB˜û„úðÅÙ/ð üi¥J)†ñ¶Áxîúu".FæíÜÞxCxŽD&ÁzŒ5FÍŠVL|Uˆ>êƒÕ#ª¦.6Ç IDATG ",‰¸¶¨G ",­K¬kooÁk·¾½jjRá÷ß~Kœ¯/Íš5CùÅ0oæätïOîÜáÛÈH ?ÿ¶nåƒçÏyPÜ-ACV¯^MjjÉL yÙ7êëëÓëøWW†-XÀF33µç6ÿŠˆ(Ï ôêë×CTìÙC†”*žAÈ Ý®[·*ùü¯^¿šVƒ[á¾Ügà¾ÜVƒ[±z½P›ðNô†ü1„Þ›{ç‹ç:²:¬Q¬aAý¸²k¾xîÐÞ|3÷¶t%ØL°ymâ }DŠâãã“H(R@ikË?%K–T÷´qmQOMÒ,Õ…Ö* Þzë-úõëÇìÙ³k|!Îø …‚ßÿ™LVc¬"o;Æq##Œ²²ˆ6 ƒìläÀøÖ­™zþ<#üýQ.††\ïØ‘:Up_‘YYØ_½ŠR¥â“† Y©…V±íÛ³£˜:˜ ìpq)S`òdhÔ¨Zæøñç³íé6’[•t71¹gBC†¶ D…ðço 3à£öñ^ø{ÈŸÈØ¶ ž?ú·k'd2”H@ª/Åf¢ ޝóvDDDDD^r¹¹\Î»ï¾‹ŽŽŽDXZg `Þ¼yX[[óÖ[oåWÆ©‰e°ó¸~ý:³fÍâĉÕ=y›{y€¯/ÙÙl&Ëd ËÉa¡³3AŒð ßí¢2ìE©…î„…Á¢Eü+&† ÅNmHø—»;\ºÃ×_W›x¾}ç6ûïîW+žRÛ¤€*Z…ŽD‡éí§øn ³ŸÏFñTÆŽâ¹uëBâÙ@Ší[Q<‹ˆˆˆÔRNœ8Á¬Y³¸~ýzuOEëÑJS¨Ëç®átïÞuëÖU÷44Æ?-g™™Ô½z9ð°ÏÏZ·æ«5k¸Ó¦ {cc9õâŸ?yÂòÆ+uÝ<÷ Gº•â*ñZIO²glÙçσR‰;0˜èYÀù6møÏñãÕ9Ó|–®YÊóVÏËîÔ ìýí9½ð4.†.Äìˆ!õY;wÄ<¶h#GæŠg#A<ë×}âDDDDj+C† aÈ!b)o Ð:íì쌗—WuO£Ê©IA„Gãã±—/#¶êë3¥{w$.À‹0a[&LàÑÌ™ÜÉÌäǰ0\ML˜lk«Ñ5Šú<ËÌäjn†‹j-Ý­R š÷î…””¢çÛµcz§Nlس‡SRØ`fÆô… «ìò• ô  †nåt2;ìpÑw!jK‘9üñ‡`dhÚF©tLt°j‹žºÐÉW˽{÷øë¯¿P*•$$$`iiùÚç Í¤åÆT´XMm'>>ž:uê -#åä?‘ØØX¬­­«{ZÅ?umiÓ¦ o½õV©çÅ ÂòÑ:][©iùyº}r2ïÆÇ³8íì̾sçàȘ1¢£1úóO¼îÝ£óªUÄI$¼Hs##:™–_T£xµ°=±±ä9Œ«ŽE>$DÈ×¼u+6¬Ê¦QÙja2Íþ¬UÆ‚xŽÊáÏ?…ÛhÜÆŽijÌ\†íT[t-«'Á××—o¾ù†öíÛ“““S+b"ª…B€ŽŽN5ÏD»?+ê_õüÓ^—àà`Þzë­2´X‰°|´B@_¾|™={ö0}útê֭˲eËÊì_KnÖ¤b|NN¾%xÈåË ºëë3ï›o„¼ÏÆAðÁ°oNþþì=›A?ýD¦RÉHnv숭^ÙËâïc^ñ”&††tÐ@€W ©©°¿`möö¬Ïyèé Y3<=ÁãDöŒéß}Çà‰ùö»ïªtJ• ÚhÛ¬-Wâ®@™ÍLÂMš5”Ìèöì)ø½àì ãǃŽÈêȰ›f‡Ì¢z—‰zõêq5×HDDDD¤rhâžQ“4Ku¡:::š«W¯òöÛoc`` ~YV3Ç_¼Èrè`í;ïà:n\A'++Á½á?à£èëçÇÊU«øè“OÏÊbÔýûœk׮̪}… ÊÈàV®«„¦î;7näâåö“±ø÷ßó-ݨTpá‚`iÞ·¯duÀNÑ<~<”±­ç>l'§LÁ½ ­ÏUÁû¼Ï¦±›Èž£ö¼YºãÏŽgÐÈ7Ø»Wˆyppê»Èd k¥‹íT[dfZ±DˆˆˆˆˆˆhZñí8räHôôôhÒ¤‰hmÒòÜ7l :Ðþw„h²âLœ(Ôuž>½¼ðsqaÃ[oq))‰>d}«V]3Ïú š èfmÚðèóÏù"w¾êHf÷è!ˆçà`ÁEcÛ¶…<êÕÒÎyz åµ5dÅÚµ÷}„&…2úØhr\rà(0!Ò1‹ F{{Nœ0&÷í¥aCÁCEWôlõ°b‹Ž‰è """""¢­‰°˜4i×®]«îi¼2jJ¡\¥â䋼õø1•J°6wêTú“êׇãÇaÝ:ÖlØ@O~å׋K}ZPPr¹(ȾÑÊØ˜ÖDuêÒ…§Í›“ ”r¬·´dV¿~àæ&ägþî»ñl`ãÆÁ±cBôÜÒ¥Ï¯Š€€€—z^Hb}¶ô!8!ZÁ¨‰£èp£Ž>ŽXÞ²¤íÙ¶L?6I½'М„Ô„õë ¿ôô@¿¾>¶ÓDñ,"""òO¦¦h–êDktm§¦ú$%‘˜+jß:tHh4Hˆ(+÷ÞCÏ×—ýýEÃØXfeeñ÷?ö≉‰EŽü‘ˆˆ®EEáŸëF1ÜÌ,?0Jfþô«J)cœ >>tèÐA£/^ÒÓÓ\ѵE¤ÊÉÊÊÂÇLJ””<<<*”û7==S§NѪU+š4iRj[i„„„àççG¿~ý077¯Ô}ˆˆ¼n´J@oÚ´I£~Õ% ç΋¥¥%§OŸæÃ?äÇ䫯¾ª–¹¼ ‚22LO`HžËCûö aa”âLêÐ;³,"‚»MšÀgŸquáÂ"5>n7i£† 0ºs‡¿ÿ !௘P&0rKЦð3‚hÎ[‚W™˜0óÈ!À±–r/æ¶ 6]p—Y<`1ózÍãž4$þÄ"U#žAø=aÔÜë1ÖHtDñ¬Í|÷Ýwœ;w.ÿ±ŽŽŽôìÙ“I“&U8ôàÁƒÉV󷤎]»vaggWfFŽÉæÍ›5J‘5þ|BBB42˜T5>>>ìÞ½› .‚©©)­[·æÓO?ÅCÃÝ6¹\Î¥K—ðõõÅ××—ÈÈHZ´hÁ¯¿þúRsÚ¶mÄÏÏÄÄDÚµkG÷îÝùâ‹/0+¥"ëÖ­[Y·n< Aƒ¸¹¹±dÉ’‚lCå••…§§'G%5Uدzþüy¹ïuabbb9r$‹/fÞ¼y¥¶•Ɖ'øàƒ¸}û6®®®_·¢„„„ä¿_þþþdgg³råÊWzM‘ÚV è;v”™Ø»º¹xñ">>>X[[3gΜ ùªÖ‡ü£…²Y Ù³Gø»{¥Æ\â⽌ N¼xÁÝþýù (ˆÛþ @ ðgŸ>ù}øùaÖ§@ff)#æ"‘I‹[µ‚Ö­ùÄÀ€ŸW¬àëÄDÁúÜ®ÿWÄóï¿ï$,,¶H[\\ VVE3‘ØÛ[óòߎºÍ mƒˆÏÞ³ÿÈìî³óÏ×'ƒ~eˆg€(ÃDlÆÚÔèHˆï¾[ɦM>Èd&eöËÌ|Ìùó[ʵˆ©ÃÇLJñã¿ÆÀ lËjVV NÄÓsb…¯Q<ÀÛÛ777 yñâ§Nbýúõ¬^½š‹/VªjX\\÷ïßÇÙÙ¹ÖoÝNž<™¨¨(úôéÀ äÌ™3œ>}šåË—3gΜrǧïÿ³wÞaM]}ÿ„½T–¢8À€ Š·Uë¨hÝ«¸gmÕ¾ÖºZíë¨ëž8yµâ¨u‹V7(\€"²÷ÉûGH$H@­÷ó5kÖ`nnθqã¸{÷.›6mÂ××—óçÏSIƒLF;vìàÀ,\¸I“&abbò¯ÛQ•3gÎ<¼{¿âããKyUe¡¡zÊ”€666Öøî¹¤yûö-ñññŠ2¨õë×ÇÏÏ©T*+.¢†‚C¾\@;feQ+\æO[(ÿgh‰Dì¯W·oó4-€±cYõâ3¯_ÇMG‡Ä:u°xôˆù¾¾ª©^4Pˆeê×—eË02Rti¬9s†__ÖXZ2mÕªZwI±fÍ1<˜û^ë|@¹8KýúKúVø-ºxv!.="–w\Kã´)üü3øùAL`9:…vâù&$WýG‹g€„„tBC——>¯XqY¡‚Ss#‹‰AZš»šž>¤¦†iMÙµk¶¶¶„‡‡3lØ0.^¼ÈÊ•+ùá‡4çÌ™3J{{{Ó§OƧÖjøOgÖ¬Y 0«\;k¡¡¡Ô­[—9sæ0f̵îfffìØ±ƒfÍšáè舉IÁ7pù1sæL:ÄäÉ“Y½z5:¹Š5?~œ¡C‡òå—_òôéSE¹ö;wî°víZ7nŒ¯¯/F9߃‡¦_¿~¬ZµJ#ãN`N¶¤iÓ¦ýëÝ'ÜÝÝ3f M›6å¿ÿý/¿þúki/©Ì#T"TO™ÐÅIff&!!!ä{QH$>|HXXMš4Qú‚MMMUºÓ××'33‰D¢ÑöiY¿“²³¹œsGÞóÑ#Yc… àâRÀYšaª£Ãц q¹s‡D±˜9?ýDãQ£ˆ°¶&þÚ5hÙ’–/R¾Je‘Ü L(ç³…ù>ÓV­bA·nDÛÛÓ¼E‹^wI £c¼¿x,Ÿ~pí•]=»‘”•RÖþø~Ñxä±’V¤ó95Uú<ËyL9ü0Ç©€>ekkk¦M›ÆÅ‹¹ví€Â*9|øð<ýƒƒƒ9xð ;wÎcÑT…T*åöíÛœ>}š'Ož’’B:uèׯ_ç?þ///¨W¯#FŒPˆ~Mæ<|ø0W¯^%,,Œzõê1`Àê©H/ùàÁN:ŵk×(W®ööö¸¹¹áàPðÍÔ7ß|“§ÍÆÆ†°gÏüýýi—kgL*TÐÈU¥ n߾͎;hÞ¼9yŽ÷êÕ‹ùóçóÝwß±lÙ2/^ Àùóç‘J¥ 8P!ž¾üòKLMMÙºu+ .Ì×zËæÍ›×̆œ\öõêÕãË/¿ÄËË‹¨¨(¦L™¢t^JJ ëÖ­£U«Vj_Ÿ¢’ X*œéÔ©“âï°°0öìÙý{÷(W®ÎÎÎ|ýõ×yÊrwûÀÔO‘²®YÊeÆþT¡B…b©EõêUš7o®ø‚1b„Ê~±±±´nÝš&Mš0fÌ*W®ÌÏ?ÿ¬8nccƒT*%!'îÁƒ4o޼о‡e•Ó±±dÉ«>,kìÜ9Oùê¢âhdÄ^GG´D"² éÕ¨ñ³gËÒÇ…‡³réR‡³gá·ß`Ì™x×P<ƒ,#Ç3‡Œõ¹0DD@ËþWølc—ñ¬Ƕñúè;ñ\‰ YFbbÿÇú!åñÃ|žÿ TˉHÊ©âyûömF­rÇë·ß~ã‡~ÐØÚ‰³³37näõëׄ……±|ùr\\\X¿~½Êsîß¿OëÖ­9|ø0ÑÑÑüúë¯4oÞ\£âX111téÒ…þýû³mÛ6"""X²d M›6eÇŽŠ~R©”¾}ûÒ AÖ­[GFF¡¡¡¬ZµŠ5kÖhôÜT¡•dlUƲ{÷n$IÿI“&Q¾|yvíÚ¥h“û»ºº*õÕÕÕ¥yóæÄÄÄðHnQARRÞÞÞ¼|ùí>x{{sóæM@‹´dÉ•çÍ;—¿þúKãçXXRRR8pà@žÇ¦M›˜;w.ÇWô={ö,NNN,Y²„¨¨(n߾͸qãhݺ5ŶF9eÆZ,ãJ$Ú·oÏ·ß~[à—ëĉ‰ˆˆ 44kkköîÝ˰aÃhÚ´©"°ÄÕÕ•mÛ¶ñÍ7ß°{÷nZµjU,k. þÌqß0‰øìÊYãG¾kïiaÁb[[þsõ*©&&P© ˆÑÎ8 ù8~£ÛU¸Ùü›ˆ4Œ ²n7ÐK‰6xï¤Bè0Z|.»Ïh]7ƒšO"Ñ×’²q£jòÇ”ãæ%¿ø2@l,tê$«´XXÒÓջ䗞žž íĉñôôdÇŽJÂ,-- OOO:tè ±xùòå¹|ù2mÚ´Q´EFFÒ§OfÏžM¿~ýòˆÍU«VáééɰaÃYQ 6mÚ0vìX"U³gÏæÜ¹slÛ¶ÁƒchhÈ«W¯1bß|ó ]»vÅÚÚš‡räÈ&Ož¬d¹‹ÅEþ?òêÕ+Ž;F½zõ¨[Bi.Ÿ>} @“&Mòícdd„½½=·nÝ"99…eÐÇÇGIDgff*Š‘½~ý:ß9üüüpwwg×®]eªò¯µµ5þþþJm©©©´mÛ–˜˜FŽ©hswwÇÀÀ€ëׯS'Çpß¾} :”9sæ:¨€@a)3èâÂÕÕ•+V0hР|-/QQQ9r„‘#Gbmm È*#Ö¬Y“M›6)ú-Z´ˆ={öP½zu?~\¨l e9ˆP"•r2§ú`·¨(´å&ÍbØöúÁƆªÞÞ² €¯_ƒ…º&&”ñÿâù½J„µÎÁÐs —‚:Œ¯¸—û‡gÎÀ&dP/,}­œ÷ )®ôx‚~hñЇTZ4Ÿà"ÙÙ² ŽÐÐÂ?"#AZF¼]:ÄÎ;YµjíÚµcݺu3s¦,€´U«V899±eˤ¹ý¿ÿýøøxƧñ\FFFJâdÖÙ3f’’‚¯Š˜kkk…xppp W¯^rþüù|çzùò%Û·o§mÛ¶Œ5 CC™ËRõêÕ™6m)))Š °¬¬,jÕª¥4†ŽŽµk×ÖøùÉÉÊÊbРA$$$°yóæEþÇäéÓ§ˆD"ªW¯^`?ùîç“'OèØ±#"‘ˆƒ*vDAöËw"^¿~]| /A$ C† ÁßߟƒâääÈ2ć‡3`À…x2d¶¶¶xzzòöíÛü†Ѐ²¬YÊ ÿz­ wîÜ!+++Ÿ]Æ •îÎíìì¸sçaaaœ;wN£Hg9gΜaΜ9,[¶Lé!ßF“Sm7“’ˆÚ¹¢¢èyñ¢¬±A–íÙóÑçõõõEª­-³>9žž$´oÏŒ\î2eåu)®¶àà—üý7Ìš²ÖeÀ0œ ­Á¨5Ù€ÁX¨s†ôÝl´}µÙÐf=§ ¤^=Y"’Ÿÿó3w×ÝE’.QÌñÌð:Uêü££ŽŽ>X¶:Jbƒ!|ñÅ·|õÕÅÃÜ<¦L½.ªÚbre†ù ÀÍM–¼°®]ËN¡Êï¾ûŽ‘#G2kÖ,üýýéÚµ+ÿý·’ˆ˜8q"/^¼P¬›7o¦bÅŠôÉ)X¤)¯^½â‡~à‹/¾ Q£F888(,ÛÏŸ?ÏÓ?·ªœÎ;èVðàÁ¤R)U«VeÆ ¬_¿Ö­[§šòó7nŒ££#³gϦgÏžxzz9£Bvv6ƒæêÕ«¬Y³†Ï>û¬Hã…råÊ!•JÕfïHÉ©Ò*OgçììÌ”)S ¤ZµjŒ7Ž–-[2lØ0š5k¦Ô÷ŸÎ¬Y³8zô(k×®UJ1(¿™øüóÏóœóùçŸ#‘H *±uþSÉý½+×$ݺucΜ9 —ü)3.¥IxNƉúõë+µ7lØ'N ‹•¢£uŠàܤI ”'Må{e¨ÝÜÜJ¼íDL ¸º¢]¡ÝrRÌÑ­[±Ìûݲe„»¹É¦L—/ÁÊŠ[W®LÍš5Kå5Ф­S§¡DEé’‘‘€§g ZZﮃÜm•*eqþü^¥sÓÒdîÝ7oº±r¥%Ñѹ&Ѫ¦=¡w0ØÈʨ‰;ÀoÄK™i³næ:†¹¼³îeEeÑ:³5ô”wV&ö‹uk u 1ndLÅ>Ô›5j(=~ü¸Ô_Sum—/_æcP®,] jâËTâã—/ËÞÃÒæÆT¯^ƒ|3 :”ï¾ûŽÍ›7Ó¹sgËÉñç{íæÆŒŸÆ{ëÖRy 4i‹‰Ñ%0pgž>ïãää@¥J:ÞÞ2W ™Þx7ž¶6JH6øŒ{ ¹µ%`™ Õo¶UÛ3qÑDÅá¬è,"vEPÓ¸&ïSÛR¶mÜÀK7K•ׯ(meÉ?³,P±bEµ….LLL6l[·n%**Š-[¶ ‰;vl¡æúïÿKRR.\ C‡Šö½{÷æ{Î7òm«Zµj¾çÙØØ279M²[T®\™Å‹³`Á®^½Ê¾}ûغu+ÑÑÑüñÇjÏ—J¥Œ7Ž={ö°`ÁfÏž­öœMÆ ñòòâĉù è'OžðäÉœœœò¬1"OP|÷îÝÑÑÑ)R¾s9:::¤¥¥åIÑúâÅ‹"YXNž<ÉÔ©SéÝ»7+V¬Ès\îjyãÆ z÷î­tL~½©sPþÞ•ÿ.ÿùoOmø1\8@ñI¾-$çÑ£Gèëë”ÜÔW®\ÁÝÝooïëc–‘Nª÷ïËá=ßÇÁŒŸæµÜúü>•*q+5õ£ùB—&ÑѲÊáVV2€#GäâY–ºÚÍ vî”ùÖ–«øº=RϹÑši‘þ,NÑ”#ÏÙ)ùû0Õ3¢bߊˆ´„lŸ"'N$++‹ 6°{÷n:vì¨äæ¡ AAAXZZ*‰g @*O±–›³gÏРAƒ|Ïkذ!ÚÚÚ;–7…cAhkkÓ¦M6lØÀ—_~ɉ'~À1yòd¶mÛÆÜ¹sùé§Ÿ 5çÇbòäÉXXX°jÕ*bsbPÞgÁ‚dgg3oÞ<µãùûûsêÔ)†Ž™™Y‘×U­Z5y¢å:t¨Èc†€€H“&MØ·oŸJŸtGGGàݵ•›sçΡ££ƒ½½}±¯õ߈··7îîî\‘'ÈÁÍ»TP< uëÖŠöû÷ï+Ž}(ÎÎÎe2*øÏÜÕs‚tèØ ±Õ« ÁÁÁÜJMU¶>¿~ •++Ls[¡ÿɼ~-{ȱ°€ž=¡OèÒrâ£HÈ…šb墑Y åØf“p^–]@'–‰ç¤ijƒ¿ªøIÜ7iR—Ö­A$*øÉŠD”+—77²&XZZÒ¤ÉfD"ŸûI$bêÔU¤9>6 6ä³Ï>cÉ’%ˆÅbÆ_è1j×®¯¯/üñ}ûödâäôéÓùžͺuë˜:u*7oÞäøñã8;;+*÷©ÂÚÚšéÓ§³råJ.\È?ü Hk𙙉··7ööö899q@‘ÔS IDATåÊbccÖV¥ÀóóóÃÈÈHmQ“éÓ§³aà À¸qãò”·´´TÃÙÙ™ÄÄÄ<[ÚaaaˆÅ²Ï®T*%##C1–‘‘‘Ú333–-[ÆØ±ciÑ¢»wïÆÅÅ---ÂÃÙ5k sçÎy|×?N«V­°´´$;;›+W®0tèP øñÇ œW­ZµbÓ¦Mxxx°lÙ2ŒŒŒ8tèø q5áÍ›7ôìÙsssŽ?®är–›°`Á¼¼¼5jM›6ÀÃð°0¦L™‚¹ù»ŒCIIIŠx yàeDD„âýªZµj±¤Ñý'âææ†››ƒ.í¥”y ¸¸¸P¥Jnß¾­ØæLOOçþýû…Þö̲Z‰P^}°¦–õåÛ¯Å}cÏÿþG¹˜œr¥z@åzõÐÎõÅõìÍâââ>È‚R°±‘YšÝÜdÆüü‚ÐjÖ­Ìí÷容 õÚ²~âx1;#ÈN,@<×5¢bÿŠˆ´? Ëó°a}6¬o±ÎÑ A|}÷ëÅÁĉñõõ¥R¥J ÿÆÂðÃ?àííÍW_}…ÚÚÚ„……±fÍÆŒ£òœyóæ±råJÖ®]‹©©)T®\™íÛ·«oñâÅÄÅű`ÁV­ZEݺuINN&44”´´4Nž<‰““Lœ8ìììH$Ü¿vìØ¡¶2¬Üáåå…——Wžã;vìPr#yõê•Ê EWWW¥´yþþþ ?åÞ½{k´Û8fÌ,--3f Ÿ}ö&&&˜šš†H$bÆŒ,]º4Ïy?ÿü3ׯ_Ç‚””ÒÓÓ±±±Á××WÉWº( :”7²yóf<==122"99™7*ÒÈ—.]",, ccc•.ãÆcÑ¢Eèéé±oß>ú÷ï³³3ŽŽŽÄÇÇóúõkºuë¦(:#gïÞ½Lœ8Q©-·@|ôè‘Ú<ŸB%BõüëtîÊFÁÁÁdee)ÊœŽ7ssstttøþûï™5kµkצI“&¬Y³‘H”çC÷!ëX¶l®®®yà—i çãd®=røƒ€þñûïùñûï?ú¸e‘ºuAÓø‹V5xù@Ù…ã}­“ vævDìŠ@œðžµ:†u ©8àÓÏŸ'N¤[·ny- BžUhäÈ‘j­kòb%ò, Ü b÷îݼzõŠ йsg¬¬¬ÐÖÖV8ìØ±WWW¦L™ÂŸþ‰¿¿?#GŽT™/zΜ9$縎É144dÛ¶mL˜0___Eùêš5kòÅ_(ªŽ=š (ªš™™±~ýzZ¶l©Q€÷ï¿ÿNfff¾Çßÿ~^³fÊþ+V¬ÈóäFx¸¹¹Ñ¦M®\¹‚¿¿?ñññ899ѲeK…«Âûìܹ“¿þú‹çÏŸS¡BœéСC¡üVÇŒ£rW@GG‡K—.±sçN©Y³&ݺuS¼þ 6Tôµ´´ÌsݨjË:°cÇÅëÕ²eK¥¢9ï“;SV‹-ÀÛÛ[©a=òÜDÉçÉ*Uª¨]ë§Â•+W¸råŠRŠDÕˆ¤ï;¬ýËxóæM¾©›<¨^X¿~=[¶l!44”-Z°råÊýö4ÅÝÝ„„–.]Š¥¥e¡þ 'ÆÄÐ3ÇïùÔÞ½tݺììà=_p»°Sm?''wüýÕ÷ÿßñl¹¶iãü?†•îUbSùM4¶É?Õ°–!•†TB¤óïÏ;wîdÁ‚y¶ÙÔ3dȼ½½yüø±`IP ßaQåVMtt4sçÎ¥B… eÒõ´¬ð¯·@W©REãHþÉ“'3yòäbYG… ÊÜ‘Ü}ÃXK‹ö¹Ò× ?iâ4&ý9‰i;!Y¶ ÷U†o qîNc·üų­•ÿûij@áIMMåúõë\»v0þ|A< hŒÜÈ'dáPÏ¿^@—ÊbU¹€þ<3}yšˆ\Éê‹›gÏžakk[¤¼Úÿd‚b‚èçÕÀ(Y”{å¯*SÕ·*Ï_='¾Nèëë—ö2Ê4Ÿ@œ~ÙÀÇǧL¥± HN&,GÔ÷”.00€"å?6ùú–E”ŠŸ‘ýûi¾¹¹B<^ësî͸ǭ“·88ù ãOåã•™”5‰½5÷2ÁmBžâ;rô«éc5Ô -=ác, £víÚÍ¡C‡%±4AžÆÎÇǧ´—Ræù´L¥H×®]Ë”/‘Üú,zÈ #´m«œc­˜Y½zu‰Íõ¡øø@x¸.àŽ®®¬¢]~qYÖÖyddg0í¯ilº½ -‘óÛÍçǶ?¢•“‚­Ëç]èòy~_ò;»"È|“ “~U}¬†Y¡¥/ˆgwèêê*‚½ ‹<&>uý‰"ÐÍtu© küŸU ýúTºCCøûoÐ À\Áó¸çô÷êÏÝ™¥¿’q%ööÝKçZóô•dHˆôŒ,P<ëUÑ£Ò°JhâY@@@@@ 4ô'ÈÛ¬,näTëêùêÕ»%èÿüO!%z÷y½™­[ 'ž?:̨££HÌ•‹oS£ úÀºœuž¾’ ‘{"Éx¿¿¼ž•VíÐ6Ì'±´€€€€€€@±#˜°JˆË—/—è“11Hr²ö”W³±‘ù%” Ïž=STò*‹H¥0bÈ+œÿ= ¢Ù¹™Ù™L;5~^ýHÌHD„ˆÙŸÍæÂ×TŠçì¤l"wG’ñ*ƒçÑÏUŽ©WI«Vh âY@@@@àã#÷¾|ùri/¥Ì#èÂÖÖ–;w©"ØÇFî¾QEW—¦òJ\¥à¾QÖƒ-‚?þýÞ½;¨(¦’ЄPÚìhÃÚëk074çØàc,ë¼ ­¼›>a„oWXž7\Ù§®¥®L< âY@@@@ xpsscçÎB,….%DYÉÅš%•r&§ú`÷ôtD©©²¥  Ërá‘#°p¡ìw{{Ø·´rn7ãââ˜>ž»wï’––FíÚµ±··/òJ‹3fðÛo¿)Ú¦L™‚±±1¿þúki-+_‚ƒƒ™1c£G¦W¯^%2gtt4ÉÉɘ™™k™mooo¼½½¹|ù2mÛ¶-¶yþ ” åò ",M‚RSy––@÷wºw/•õxxx°`ÁLMMKeþ÷™9.^”ý>h|÷òqß[¾Hœ$Rø?Py(%0…è£ÑH³ .[neßê}ˆt„òÜ…%>>Ê—/Oƒ ÐÓSá>󤥥qïÞ=Äb1NNN˜˜˜|ô9Þç›o¾áàÁƒJmúúú´nÝš¥K—Ò²eËBgooOFFþés¬6 (::š£Gjl¸rå !¹¿ƒJ™3g²gÏÞ¾}«h‰Dôïߟõë×cii©vŒøøxf̘ÁíÛ·yôèb±˜–-[âççWèõ¼zõН¿þ¥›š*Uª°yófzöì©ò¼¸¸8†Οþ©ÔþÕW_qèСB¯£4¹(ÿâÍŹsçÊÌÿ†÷IHHàèÑ£´/æÊ½W®\aÅŠܾ}›°°0–.]Êœ9sŠmN¹‘O“áOA@—eÁ!_n}è)ÿÒ­Z6,•õ”¥ ÂmÛ`Ý:ÙïM›ÂöíyûD&EªÏ[£)™)ÊmRˆ;G•µçšw1gÿ‚ý¯[@Fxx8ƒÆ"8=˜8Ó8ôÅú˜D›Ðû³Þ¬Z²ê£ìr¤¥¥1væX.?¼L’eÙZ٘ƚâTÙ‰=¿ï)V«œµk×R±bEbcc9zô(.\ C‡ܹs‡B¤¡Ü²e ÙÙÙŠ¿ïܹúuëèׯ=zôP꫉ ü'áííMóæÍ8p 6$((ˆmÛ¶áååE\\gΜQ;F||<ûöí£AƒŒ9’íª¾04ÀÏÏîÝ»“’’ÂâÅ‹éСfffܹs‡… Ò«W/.\Èüùó•Î‹ŽŽÆÅÅ…°°0-ZD§N044äþýû„‡‡i-e¥K—Ë ð?‰[·nqéÒ%š6mJÓ¦M9vìX‰Í]4KYGПríhhH­#GdBõA®^…I“d¿[Y·÷»Šæ/â^°çÞ<ïyò,édú –Öï¶‚%éÞ~KÚÓ´× m¤MÅþ1¨iðaOæ$88˜ŽÃ:âåem)¤K,›^nâfÏ›\ùó ÚÚEw‡IMM¥E·<ª÷IÇw»I$ñ*úÍ»5çÚñkÅ.6{õꥰOš4‰Y³f±jÕ*V¯^ÍÆ5gøðáJ›šš²nÝ:š5k¦²„¯T*%++«P‚F,ÐKVVºººjûedd ¯_ЇR™Ë—/+¹G4mÚ”~ýúQ³fMΞ=Ë‹/¨U«VcT¯^¤¤$Åë±{÷nç—“ÍøñãINNæâÅ‹|öÙgŠcŽŽŽôèуN:±hÑ"úöíKƒ Ç—/_ÎóçÏ9wîr€7iÒ¤ÐëȽžü>#š¾—š¾gR©©TŠ–Vþ‰>}ú¨§°ä÷<>ôZ-*R©”ììì|ç?~<Ó§OàÔ©S%* Ô#äþDH‹¹’ ³€öLM•EÉÁ'/ Ã o_ÈÌ==8|ÊUŒgóíÍ´ÙцÚkkó“ÏO<‹}À­‚Ç3 2cÌ1d¾ÍäÍ–7jųž••ÇVÄsJ¥ 7€×…xÎMfL*ðÓ²Ÿ>hžq3ÆñØá1’**\x,á™ó3†Œ×°ÊÎGdذaúúúØÙÙ±N¾µ£Ož<¡gÏžXYY¡¯¯OíÚµ™?>YYYJý=zÄìÙ³iÖ¬†††èêêâààÀŽ;ÔΡʷXWW—häZ¢­­ýÁÖÑ]»vqïÞ=FŒ¡$žå˜ššò믿’Íܹsíñññxxxо}{%ñ\XZ´hÁ”)S¸yó&®®®˜˜˜P«V-…µ;%%…1cÆ`ccƒ¡¡!­ZµâÙ³gyÆÉÊÊbþüù8::bhhˆ™™Ý»wWÙ7&&†~ýú)üx;uêDPPÊõuïÞAƒ)µ988°`Á‚<}9‚ƒƒŠ6 aâĉT¯^òåËÓ½{w>|À¡C‡puuÅÈȈʕ+³víZ_¿÷éÔ©*ï?K—.áêêJùòå111¡Y³fìߟwçÑPnÉ(“èBSÃââTl,byõÁë×eÚÚйs©¬'))‰“'Oâä䄽½}‘ƒ¡>„´4psƒÈH@;‹q+ÿbu¸'Ç/'#[ùýjdÕˆ]Fpfíν:‡¤z^!¥¦E+½V|ÙýKRƒR‰þ#IFÁ>ÓFõŒ°t³DKïݽìãÇ µÿ)sïÞ=‚‚Á(ÿ>i5ÓðüË“ÎC‹v­K%R|‚|Èî' xüä1ñññ%ê·œñ*wiÛ¶-óæÍãÚµk´jÕJ©ïºuë×Xt%&&rúôiˆƒƒÉÉÉ?~œ 6¬Rˆoذ¨¨(fÍš…©©)»víbêÔ©$''+‰@U\¹r…®]»¢§§GÿþýqqqÁÓÓ“Å‹sÿþ}Žä욥¤¤Ð¹sgÄb1cÆŒaîܹ¤¦¦r÷î]ÅëQäâ+·¥·8ùûï¿=zt¾}:wîL5”ªÂ’ššJÇŽyøð!ÇŽãùóçØÙÙ1`À `}úpïÞ=Å÷uvv6íÚµÃÏÏöíÛ3cÆ ðôô¤I“&}úðÅ_°yóf¦M›F½zõè\„ÿ‹C‡%111Ïz7nܨôžxyy1xð`X¾|9úúúlݺ•!C†ðìÙ3~üñÇBÏ]vgçSDÐ%„O©fá»o˜éèðÙ²F(á —/_2h ²ˆ ‰Ä´Ž)Æ ÆL<‰o¿ù¶D×2z4ÜŽ¸ ÝwcÐü1ÑðÎMœ*&UÒp#œFÐȪ&2zêh.^»H¤m$X1`bE‡jغm+ —ˆ»Å ŠÀ´ƒ)¦mó¾þË–-+õ€Ó ¾7|‰5‹UÛï¥ø%6v€¢tÞl¼5}ËíÛ·?È*XBCCqr æ˜1cX¸p!›7oVÐ!!!œ={–ñãÇcll¬ÑøVVVPë.R«V-|||xóæ UªTáÅ‹€Lœ5oÞ©TŠ¥¥%[·neñâÅxzzjü?æáÇ>|˜¾}û²K ÆÏ—_~©ôþW¬X‘µk×âç秸¶~ÿýw®]»Æo¿ý¦p5˜>}:uëÖå?ÿùžžžlÚ´‰'Ož°~ýz&åøÌ 0€òå˳xñâ<úc¡££Ã‹/î"¶¶¶ ·˜§OŸR»vm† B­ZµØ²eK‘ô¨Q£”þ–û©[YY±iÓ&@f­Ÿ3g†††øúú*n†Š““K—.eÔ¨QT­ZõCžò!ÏÂáããC×®]KmÿŽ¢k×®¥VÊ["•òW¬Ldt34D[¾ÍõÅ%ºÿ\»r­þ5^¹¾"sX&Q.Qw fáµ…ôQtŸ·½^{éôU'œº8Ñ⋌š:Ѝ¨(•}CBéúóÏì·p€±- …éZÑé1´áPN;Í«™¯XÑe…B<±ë~Î.?Ë2Çeô‹ìÇ2Çeœ]~–½¿ï%õÏTâ.,žµôµ°l¥R<‚xÈ—¯¿þšnݺѢE jÖ¬ÉßÿM‹-âÅÊÊŠ>}úàååEB» Õ-[¶ •J?~¼Æs‰D"D"R©”ÈÈHîܹƒŸŸÍš5PÚ.—Ó¤I% mmmºwïNRRRÁyÜ»wáÇ+ij¹¿ö… Ù6¾ŽŽgÏžUi,,AAAŒ=šªU«*¥Q+nÂÃÉDj…£••€" ƒ„‡‡sôèQlld•h¦mÛ¶JÖu===¾øâ ÒÒÒ8}úôÏÿ!ÈKy âY=‚úàZb"19>„=Ÿ?w ýŸ³²²øzÆ×¼jÿ T¸&Û'ãèî}»øzÈ×›’’Bïa½¹­s›øzñŠ+úfÌM.ö¹È¿û/ýÝú“˜‘È¡‡‡Ø°›Ë¡—‘"…œx/-‘l;0Üi8_9~…‰žú´d 6¤a®ì%â81Û"ÈŒÌ,ð<] ]* ®„®¥ú@õ|Öâ3ÌO˜S3¦À~5tj°k®"Í!•H>j8¯y]`¿ŠñⲸ±¶¶æ›o¾¡uëÖyÂĉñòòbïÞ½Lš4 ±XÌŽ;hÑ¢E‹±:V¯^ÍòåËUfvxùòež6U–»Î;óóÏ?+,§ªûË^¸p–-["‘Hf‰‘H¤ð•­X±"&Là÷ßçĉ´k׎2lØ0ŒŒ ðåQÁóçÏéÔ©ºººœ;w._ yqP½zu‚ƒƒ‰ŒŒ,0ô›7o"L.¸Y¼x1²í‘Þ½{ãææÆöíÛù믿òŠªÂÕÕUq¾;;; ï{)o‹‹S´¡¥¥EçΕÞ/©TJXXÑÑѤ§§c``À‹/°··§zõêJã6oÞœrÅXÈ+¿ç¡êZµ³³#44ôƒç5j¾¾¾xyy)¥˜”‹ó.]ºä9§K—.üöÛo~NÊ‚€þ»oh‹Dt;~\ÖX©’,_[ qÑç"OÍŸªÏrâijv×ÚB è ³&àcåCv•÷üS- äó&/ŸŒg¤'ç¢Ï‘&VæÓŠ®Ï´ÙÙy(ÕÊkV´@é/Ò‰ú_’´‚ý í ©øUE´ „ŸE£F¨™Z“˜Ô˜|ý  ƒ þÅpÚÛ¶/ò<ííÛs üÙÖùøAÇ€£‰c±û?ïÚµK­kûöíqttdóæÍLš4‰'NðæÍ/^\¨¹öìÙÃŒ3èÑ£+V¬ÀÑÑÍW@K¥Rž={†™™™ÂµD¾½ß±cÇ<â·Glß¾'Ožh´†‚|\ó;&ÏU••EZZfÌ[±333ÖôÜhkkçy…E"Éÿ»7¿ç¡îù• °oß>–.]J¿~ʵä±PEýœ”-]B”f¡\@·.Wó'd]º@ îùû iVïe£ˆLQr$ ˆÀq#fFf˜šafÿÏø°xN‡ž&Û%Q#‚·­ßr|ËqÈÙ5ÔN³";`0¢{Ã9äє͔”p-ø³ñH%éVp­€Y'3Ðà%‚5G$áµÙ‹öƒÛóÒõ%¼gÈÒ{¥G£ðF,ܶðƒæÙüÛfîv¹ËcíÇH¬Þûg µoÔf4ÇÇd„ L›67n°yófÊ—/Ÿ'€::„¾¾>›7oVªÒ'~SÅÙ³g™7o^ž6(Ø××ÑÑÝÍœ9S£õÙÙÙ±jÕ*V­ZÅõë×qsscÉ’% èW¯^Ñ¡C9þ¼ÒnRIñù石mÛ66lØoÅ·£G®ôœêÕ«‡H$R¹ o+‰@V]]]êÔ©ƒX,Ö¨°G­Zµ¸qã111J–þÀÀ@Þ¾}«Q%H•ÏûÒ¥K…[|1±gÏ.\ȨQ£T¾&rë÷Ù³g3fŒÒ1ùç¤f͚ſP ‚Õ#˜ÂJˆÈÈÈR™74=ÀëNÏ”ˆ—(áôub±8ïÕvxïf;[šÍãèÇ\ »ÆÉ§'Ù{/7mKµ¿«ahŒ¹¿96çmg1ŽË'.Phù¾ß8}ƒÁÒÁÔð©é=SÊÝ/GõKÕéÓ‹›Ý,SGFŒ‘‘?þø#§OŸfذaÊILLDWW7ÏöºÜ—U×®]#))I©íÌ™3èèèX¸Q£FT­Z•õë×+•,×”–-[âêêª(…]ááátìØ‘˜˜NŸ>MS5;qwîÜáÆ…^“:HëÖ­ñòòR™ÑäõëÆ IDAT×Ì™3}}}~ùåE{5øâ‹/È#&åã899}ôõª¢{÷î)2¤DÇŽ‘H$œ;wN©ýüùóÏgccÃåË—•¬´x{{k¾èbâï¿ÿfôèÑtìØ1ßœìNNN˜››sþüù<;8gΜAKK«Ì”Ï.-ÍòOB°@—¥UÕG©ú ¯¯ì--™ºéÒ¦ ›vn"Ý2ý]£ o¡gAljK#.-ޏô8bÓbIHOù-ç&(8€€ªfU±¾º“ƒdwÓ_}’)(óM&ÑG£ÉŒ(x«MÇT‡Jƒ*¡W¹pùb… ÂÂS­Z5.½T¬¥¼Ù³aO©”ò.,¦¦¦ <˜mÛ¶0nܸBÑ¡C.^¼Èøñã™2e ÚÚÚ¬_¿ž˜œïU©'­¬¬èÑ£¿üò ¦¦¦lݺÆŽ[ eÍÈȈ 6лwo\\\øñÇ©_¿>ÉÉÉ<}ú”}ûö1qâDzõêÅöíÛ9uêC† ÁÎÎŽ¬¬,|||8räM›6U[Rž£¸[·nœ¨< F/`Jÿ)yŽI¤2ˆM‹UëC:‡Øòx Ò »OH“ËóÛo2ñܨìÚU4ïI–„Ÿ¯%ªuÙ0°5 Ò€Jh ›<%‰©©)íÛ·/Ö9 •ƒÊ*ãÆcÛ¶m´lÙ²HÉï¾ûŽÀÀ@öïßÏþýûÖ±#GŽÐ¤I•¾¢ ,À××—: ‹ÑÒÒbÔ¨Qxxx¨¯W¯^øøø0qâD%—mmm\]]"²J•*ܸqƒÿýïŠ>¶¶¶Œ92OÉkUÈ ¥œ:uŠS§Nå9nkk«‘(Y½zµRÀÙëׯY¸Pæ*Ô»wotƒ `òäÉìܹSñ:éêêÒ¬Y3Ž?NóæÍóœ×ºukÎ;ÇðáÃùö[YúO‘HÄÈ‘#Yµj•Úy?&&&ܺu‹Y³fñóÏ?+9±µµU¬ ÀÜܜ˗/Ó·o_EÑ öïßÏwß}§Ñ|3fÌàîÝ»üñÇüõ×_0iÒ$4h'\I’œœ¬¸±õ¯Û!´÷ô%;þ0––pó&hXg@‰´à4bŽÇ Ž«í[®E9,ºYR;w²`ÁªÃ (óÇðÕW_±}ûvFŽYäq"##yõêööögKHMMåñãÇØÙÙ)ÃB||ÅØØ˜5j¨´òGFFòúõkLMMÕæRþ' •J !11GGGwOÞ¼yCDDvvv¥º"‹yöìÉÉÉX[[èÓüâÅ ²³³>Á…%""‚ððð¾ËT’H¥Rž?Nzzº"5cIáîî¨ÞíôóóÃÏÏ}ûöQ¯^=aG´]¸»»“‘‘¡²Tgqr4:·À@NGGÓ¥Ù__ÈÉõYˆ%bš­ræ^Š?DÁcD¦"2*¤¡#ÑA7L#š:¶C__ Ge7ÿ;Ü}C_K‹Î¹óÁ“€>÷âNL ²Ievw=Éðþ>?.\ŒTš¢tîüùóùöÛ1”/_>Wët*h˜¸¹~}[^¼˜ŠH¤:V/[‡z‰Õ©˜ji€ýü± ¢¡¡À? ‰DBff&ºººhkP¨)33©T*Dù—"hii)UªÌÈÈ@$¡§W´x±XŒX,Ö¨’lQ_gzzzh}”4Lï#ß%_»vmi/¥Ì#袤¶B亃©)Æ'OÊííÁÖö£ÏÈW^_!–ˆ1Ò5bfåtu©At´ìx•*°m›¼vK¹œÇ;6oÞüAó/\8… ó¶ÿÓ ¢âYà}:D`` âolllhÙ²%µjÕ*ôxK–,A,kÔwúô阪 J¸|ù2:t`ÇŽ¸»»«³K—.„„„¢Ñ>&™™™œ={BBB022ÂÁÁ±cÇbii©ñ8ÙÙÙ>'''ZµjE»ví <ïåË—œ8q‚7nššJ:uèÓ§ÎÎÎjç´··ÇÁÁS§N)µÙÚÚ¹ϼyóøõ×_yóæ •+W.ÒqæÌ¾øâ öïßÏ Aƒ>úø¹‰çÖ­[ܽ{—””ÜÜÜhܸq±ÎY–Ü7Ô#èSRN—éibWd}Åa}ŽHŽ Ç¾$f$¢%Ò¢å«ý|ÿŸw• € ÀÜü£O] BA”O‹ƒ{÷rÌÃ]5VÏT--þ»{7¶E¸‘¼sçÿ:u6µŒìlÆ.YBÇN =‡::ÄÁƒÑÓÓC$!‹ÉÎÎFGG‡©S§²råÊB·dÉsÓ»»»«Ðÿ$7nÌ£GÐÑÑ¡víÚ„……‘’’¯¿þÊÑ£GÕŠV€×¯_S·n]RSSm-[¶,’€Þ¸q#³fÍ"--:uê`ffÆÑ£GIKKcðàÁlذAå®Ü‰'>|8ñññØÚÚbhhȉ'¸s玒(.«ÄÇÇcffÆ´iÓX½:oF¦Òä—_~aÞ¼yH¥ï²4ÙÚÚ~RZ@=‚€þ!·>ô|ð23e²|wRR)))ùO§Òçh^&¼ üÕ߸xúKÌÌ`ýz<¸‹ÿPÊ`Aâǵ}{.ÎËÆW¯òí“|Ý´i‘Ä3€““ÄdzãÁ ÊÅòuíÚ¸´jU¤94%(([[[9zô(?ýô«V­ÂÙÙ¹P¹ôtåLoooúôéÃÒ¥K™3gÎÇ^v™ÂÉɉ ЧOtuuIMMåÀŒ=šAƒªÖ…AGG‡þýûÓ¬Y3š5kF‡Š´–åË—óý÷ßãììÌáÇ©^½: s…˜;w.+V¬àÙ³g\¿~Q®’ª~~~|ùå—4kÖŒÿýïŠk;==i-@©ì”EªU«Æ¬Y³hÚ´)Ïž=cþüù¥½$2ˆ  Kˆ’¨D(Ð Œ±ñò’5‚•ÜŒÿ#/ŠÐÒÒ#Y|4­'HõÓeë •im$d4 •u¾>žøÓSèÒ¶o‡ªU5›çÙ³gØÚÚ¢£óa—¡$CBÔ(ÒƒÕ[EÚ"*|V m+ Ò)›©é„ BÍ©Zµ*­[óìàAêäÓg“™ã~þ¹ÈshkkÓsêTŽÎ˜[.‹cn®kiѨŒŒJÆ ¨|ùò >œÄÄD¦L™ÂÑ£G4h7nÜ |ùò*¯Ÿääd©^½:U5üfggsýúuž?NFFvvv8;;ø<³²²ð÷÷çþýûØÛÛÓ¢E %?[u¼yó^¿~½½=ÎÎÎ*}j¥R)øùùahhHݺuiÞ¼¹Zߨýû÷+ýmddĨQ£ðôôÄÇLJ{÷îѼyóǰ²²RrµÊ-n5åÍ›7,^¼kkk.]º„¡¡¡â˜ŽŽË—/'99™7âééɈ#ÇçÏŸ'NœÀÊÊJÑn``@³fÍ(*wïÞE__Ÿzõê)µgffrýúuž={F“&MhÔ¨ÑÑѼxñ‚ `b’7è:==]qíÔ«WűÄÄDnݺ@DD~9žôõõiÒ¤I¡Ö|ãÆ $Õ®z¦¦¦y> >äÞ½{€l7BÕg%÷ký©ºÔ •Õ#袸«úÄfeq5§ZIO oáµk… èÈÌ„ˆˆP¡/tzr‰†´·pÈ kÁ©_02‚åËaҤ­ÙÃà |Ðö°8ILÔÞ(2#2Õö-é‚(EE",ß­\Éâ«WUZ¡Ó€«5k2ýݘÍеkézCÍšüþã4GQè”ã.òò¥l7è‡~àöíÛ„‡‡+ 2€uëÖñÃ?péÒ%tll,öööDGG+³³³©Q£žžž´mÛ6Ï9 ´iÓ†›7o¢§§Gzz:...x{{+ =Udgg³téR-ZDVVZZZH$Ø¿¿ÒöùêÕ«Y¶l‘‘‘“––†D"aòäÉxxx¨}nªhøöÎ;,Š«íÃ÷Ò‹€Q±€½$ Æ‚5ì…Ø±Cb71‰IÔ¨Q£&Ñ×å ÆÞ’X£ ¨A# Fl(EIDAš"‚ e)óý±îÆ•–ntîëÚK8sæœ3»#û›ç<ÅÖ___òË[âTM~þùgÒÓÓYµjU¡ÏJÎâŋٵkk×®UˆºÈÈHÎ;ÇG}¤xOA(“ˆ•#Fòމ‰aøðá¡§§‡T*¥k×®|ðÁÌŸ?Ÿ«W¯*‰c&NœH||¼¢mâĉüöÛo€Ì-ª_¿~>|˜Ã‡`mm]j+øûï¿_¤jÀ€ w–gÏž1cÆ >¬xÈ*((`„ lÙ²å•`v‘¤¤$ѺDÇÏ*¢²oÄ3))ä¿ðך™ QQ²eF³ar ²xÐ>b´á˜=Úk\zñ ²/ÁòˆçÜä\w%–(ž%Ú̘Qgj×^<ÃÛkñ(+ +´ŠcÛtt˜af&KH^Ž—æÔ© 56渊Ý l«Ðúü2'Nœ I“&Ìž=›ÔÔT~ÿýw¥~‚ °sçNZ¶l©RøªB"‘0gÎîÝ»Gvv6?æÐ¡CH$¦L™¢R´,^¼;;;ž>}ʳgÏØ³g|ôÑG%ηaÖ.]Êøñãñõõ%--]»v‘••ÅÈ‘#ó=|ø/¾ø‚Ö­[sïÞ=ÒÓÓÉÎÎæÚµk 0@­k{•ììlN:…‰‰‰Ìe§ ¸sç}ûö-²Oƒ hÑ¢aaaŠ´b÷^Tz÷ÝwY¾|9-[¶DOOÖ­[³~ýú"­±eeâĉ„……qøðaÒÓÓyúô)íÚµcùòåEž3vìXœœœ ãÆôïߟýû÷súôiºwï®ʳfÍâéÓ§<}úTa. < !!AéµpáBºvíªè÷é§ŸräȾÿþ{RSSIMMeåÊ•8p€O?ý´Ôó¾éˆâ¹dD ô‚Ü}Ã\[›ÎçÏÿ{  :--Z]‹b|‰æ¢óg΀¹y©§(7Ù±Ù<>ø˜üÌâ­EbA”·UVè,àŠTÊçÞÞ2Çx`0”¬Ð[7fsYŸ?~Œžž)));vŒŸ~ú €I“&ðÁP·n]¶oß®´ }þüyîß¿Ï?þ¨ö\¦¦¦¬x)Í©©)NNN$$$0oÞ<|}} V 6oÞ¬pÙpqqÁÃÃOOO‚‚‚ŠÜžOMMåûï¿§iÓ¦ìÙ³Ga!üè£ÈÉÉaΜ9]aa•ckk‹D"QX'»téB­ZµX±biiiŒ5J|W6oÞÌ–-[prr*sº² •J‘H$%Æ€Èåø¬¬,îß¿¯ä>±|ùr:tèÀ®]»˜={v¹|¡åÿº ÉÑÒÒ¢gÏžœ|9áÿK¼ÐÚ¾}{LLL®F•Å7˜8q";wfß¾} ·–€€ n#/3pà@‚ƒƒ ´H©]8JÁ”)Sx÷ÝwéÔ©Qr 5©Ì ÂËii<}‘Óu¨±ñ¿µ³Ë辑žj¸ƒ ºy庮ˆˆµsÑÊIHçñáâų–™u§×ýÏŠç»wïV÷þ“Ìß°õ/„Tp¥m[ú&%Att…½Æ'%q¨U+äwß–&M˜]…¾ÏëÖ­cÏž=ÿüsµ¯ïÀLŸ>>}úàîî^ªL!\_¿~½È>©©©„……amm­4¬_¿>€ÊL!ò*qqq²FKKK¥">rTµUiii <˜ÜÜ\þøãBqLMMÕ÷gdd¤R•øàM@Ð¥ <_”•é/ÐÝML¨)wßÐÑOÖ¥¥¶EC¸ZB©D ´¥–e_ŽºA„B@²g2©¾©ÅöÓoªO—:Õ^Š»¼ˆA„egþ† |geÅ•Fè[IåëÇOÊ!~nܸJ­Ï¥aöìÙ¤¤¤pôèQvìØQªàA9þþþ˜››3cÆ ¥ÔpîîîEžã­Âß\ÞÖ´iQ‰áwÞduuêÔQùRõ·ÂÎÎŽ¥K—J÷îÝ9pà€Z–³ßÿ)S¦Ð½{w<==«Ìïùe¦M›†––«W¯.2ðoÓ¦M<þœÙ³g+ÚÚ·oOÍš5¹téR¡óäÙ35jT!klÕª ús=ÿr¬MÐÓÓCKKKá’RVrss5jxxx¨t÷;w®Ð1y–޲Tô|“ƒKFÐÿqîgeú"7íPssðò’èÖ TäæT==CHœ þEœŸ kJM­²( B®ÀãÃÉ,ÞR]£m jO¨†ŽxK¿ÍÔ«W={VŠõYަ¦&|öY•[ŸKCÿþýiÒ¤ óçÏçÞ½{¥¶>˜˜˜ðäÉE°ÈÜA<<<Š<çÞ½{JÕÔÔTNž<‰••ƒŠ)èÔ²eKÈ–-[¸råJ¡ãñññ «jTT)/b>ä‚€T*-¶”œãÇ3aÂ:uêÄéÓ§Kü .\ÈܹsK·´´jÕŠÙ³gsçÎÆ§TÕ`×®]¬^½šÆ+¹éðá‡òèÑ#ŽÈóý#{ÜÝÝÑÓÓ+ä³\V† BóæÍ9~ü¸’Åyûöí$öêÕ‹¦M›âíí­pKÙ½táš6mZêL‘7.ˆPnß¾ÍÍ›7¹}û6999lݺUeߨØXÜÜܦU«VLŸ>]a ‰ŽŽføðá8::²råÊ*»†Ò T}0;ÂÂd¿”×ú–=‚oCÂ1èÔžáºp¹9<ÝŽPww ƒ”üÌ|xDÎÃâ-J&=L0u·ßDd|_DéãŠdüÔ©•êÏY^$ 3gÎdÁ‚eœ6mš¢´õðáÃÑÕÕÅÝÝ™aôèÑ 4ˆ‘#GR³fM<<<ˆå·ß~+ÑÊ»uëVúöíK=øàƒhݺ5Ïž=ãÎ;øúúrôèQêÕ«ÇÙ³g™7o  Y³fäççsåÊüýý™7o^‰neÓ¦M#//‡ªtƒX½z=ð“/ IDATµÂ—`ß¾}¤¦¦*2žÈqppPˆz©TÊ­[·E9úöí«V>êÿýïH¥R¶mÛÆÅ‹±··ÇÔÔ”ÀÀ@þþûoÚ·oÏÁƒ ]Ó¢E‹¸xñ"ãÇgݺu4oÞœcÇŽ‘——ǦM›*Ìšª¥¥Å¾}ûøàƒxï½÷°³³#55©TÊW_}źuëJ¬ÚX `éÒ¥XYYakkKݺuKµž={077çäÉ“…‚Û¶mË×_¶¶6nnnŒ9’:0jÔ(AàèÑ£äççãææ¦äÂãçç§(MKKdï¹Ü½nçÎtïÞ½Ì×-òfðÆ è¤¤$Ú¶mK5066&>>^¥€ŽŽŽæý÷ß§víÚŒ1‚ .н{w¼½½iß¾=ÖÖÖŠ@ Í H5QYùrÝT_Ÿ/o©•²|÷ˌۃÝ\¬BL.pOφ4¬kLó¦ÔÒ‰ä8::fåÚú,®a^jI¿%‘›\LPÌ™aÜéÍJ€/V",•-žAö7ÁÐаÒç‘Ó³gOôôôTV|+Š¡C‡²`Áµ‚6lˆ³³3¶¶¶Jç{zz²qãF|||x÷Ýwùþûï:t(€Ra“:uêàììÌ´iÓ˜9s&;vìÀÏÏ6mÚ°sçÎB¥®Hrr²R›µµ5·oßfýúõ\¾|™ƒbbbB³fÍØ¹s§ÂªÚ¿¾üòK®\¹Â‰'055¥ÿþ¬[·N-Q3vìØBÖÞ—©U«–ÒïNNN*û·k×N‘ýãÕB"êfsÐ××gëÖ­Œ1‚ãÇLxx8mÚ´aÚ´iÌž=[¥@­U«/^ä‡~àòåËáèèÈܹsé©fåÙÑ£GŠç=zt!ÿáÎ;ˆ»»;áááØÚÚ2dÈÅ÷êË[ýíÛ·ÇÙÙYeaUó}óÍ7Œ1ooonݺ…™™Y±k®W¯ÎÎÎ œ‹ìÿ²(0`7nÜ`Íš5Š]Ž!C†°hÑ"Z¼’­ÊÄĤÐgúêñ7±aÉHU‘ ÿa¤R)‘‘‘´hÑ‚µkײhÑ"•Á.sçÎÅÃÃÐÐPŒŒŒÈËËÃÞÞ++«"Só\¹r…Û·o³dÉ>ýôSZ¶l‰““S‰krqq!&&† .”ûú^&#?óË—‘ðyýúlüê+8q¬¬ œA$ñéñXo²&¯ ¦óE³ílØPA Á矮²¡4QJÒþ$òÓ‹Îñ,Ñ’PkT- Z½ž[èåÁÅÅEôƒ~ÁÞ½{ùöÛoK]L¾ýö[V¬XÁåË—• JˆˆTöööDEE•Û•C¤êqqqŠ·éÓ§ 6¿‹ŠásÕÑÑ¡U«VJA/ªøí·ßèÔ©FFF€l«ªwïÞüñÇ*Ó3,ÿfjj*óçÏGWW—g/Jg«Ce8äÿ™’‚ôEÉP[ + xjgàN™x˜IeìV© "̺ŸEâžÄbų†¾µ'×~#Å3ˆA„"å'00 60fÌQ<‹”‹ .°ÿ~’’’(((àÁƒ̘1¾þúëê^žH%!–Ì' ÕáÉ“'¤¦¦*Š È±µµ¥   Hk—ƒƒ_ýµâ5}útµçtwwgĈ¸¸¸(½^Íù«nÛøñãÙó¢m¬¥ÅûÿüƒKFwAI@—eŽ|!Ÿ;á8pç]ˆo¯ÐåYsIm!<Úÿˆ/Adr¤R¿¯ŽEdr$Z&ZÔù°zÖz•º±íõh“çQ{÷î¡««KûöíÑÑÑaýúõÕ½$‘ÿ8111Lš4‰:uê ¥¥… »wïfÁ‚|òÉ'Õ½<‘rðòß]¹&iÚ´)#FŒÀÏϯšW÷úóƹp¼Ìš5kTºp„„„`ggÇ?~¼¢=00öíÛãééɰaÃ*lêl—”–éóç³?)‰¬>bt­ZüþË/°v-hjBr2”£4êÉ{'q<è(ûÅsÍ3¦)b+‹gWŸ‘òg s7j[jSgR4ÿÛiêDÔGtá(iiixxxаaCzôèQå¹EÞLâââ áéÓ§XYYѺuëB¾â"ÿÔÑ$•¡[Þ4Þ¸ Bu©¼E/ÿ½2¾t*2ˆ099™S÷î‘%•BZC[¶yþçÎË%ž¶ÝÜ€DjŒð÷xzL.ïŠUµµ5éÒyvµxw=k=,Ç[¢¡÷æošˆA„"eÅÄÄDñÅ'"RQÔ«WOQQäí@ ",™7_¨ÀÊÊ €þùG©=$$DéxEâëë‹‹‹ Ç/÷X‹þ÷??øÆŒAòûï ’JáömÙÁrú??H{€W„,—´<¤†•âÿ àö“>(Q<´6 öäÚo…x±¡ˆˆˆˆHõpüøq\\\EyDŠæ­´@S¿~}îܹ£Ô‚¾¾~¥T$0`@…l…$''s."† @çØ14Nœø·C9ôŽ€/ª[̨]SÀ×ï}Möýâséu2Â|9H*~ ¯+â–™ˆˆˆˆHu0|øp†.îd©ÁÛaÒSÁÔ©S¹zõ*<d³þøãÆŽ[ª\«UI‚TÊÈåËyð’¶tÔ(ÉW­Zо}™ÇÏ+Ècw¬8ŠEVWH²¥vm(¦o™ÈOÏ'qO"ÙQÅ‹gÓ¾¦˜~»Ä³ˆˆˆˆˆˆÈëÏ) ;vìH:uøþE9ß:uêP§N~ùåEŸyóæñî»ïÒ©S'ÆŽKÛ¶mÑÓÓã»ï¾«”5ùùù•É…#0=•ÑÑt  ž——î߇—,äBãÆœ“HHèß$eW›'ÂN!ËzPà? ¨xësöƒlâ·Å#M”ÙG¢)Áb„&Ýßüdõ""""""¯ r1 Gɼ‘.Ë–-ãùóç…Ú;vì¨øÙÄÄ„sçÎáååEpp0#GŽdРA•VaÈÄÜ\­­ù¬‚Î?}Ê©'O8õä q/þþ;ŒS蜘ٳYôÕWì(§û†þ˜E;w–y}Û¶#¼HÂl! 44„¶mË<$¹É¹Äïˆ/$ž– \¦ÏZfZÔ™Zç­Ï ŠˆˆˆˆT‰‰‰¿¶±`¯o¤úu¤@"aëÁƒÜiÑ‚âUä…®«£ÃPss†YXÐ×ÔýåÈ“““9uý:‚­-ÄĨ_ÐÖæÔõë$''caQ:;´4_ÊÞà½ô²éÅßG[²”Ò* æj“y'“äÉäÛO·¡.–N–hŠRDDþ‹ÄÇÇãééIïÞ½iÑ¢E‰ý===IOOgâĉU°º¢ÉÈÈ@OOOåÎ`uPPP@NNúúê¹°––FÍš5‘”#þ¥$‚ƒƒ¹víãÆ£f9ë ”–­[·Ò¢E z÷î]l[iðññ!,,ŒY³fUÔ2• ÃÇÇGGÇJI‹[™DGGsíÚ5133«îå¼Ö¼5Þ¬­IëÚ•“·nAÝºŠæv5j0Ì‚aææ´72R™p">>ž‰ýú!¼È¢ 4¼d9›7I¿~ÄÇÇ—Z@ =ÆãÌÇLn5‹i/Òc—Ù}£R¼Sxv¥øüÎÆ1ëo&î…ˆTIIIXZZVª J¥dffV™˜øòË/9}ú´âw===¬­­éÖ­Ÿ|ò ¥¯M›6jw:þ|‰E4îÝ»ÇìٳٳgZúÇ$::ºZ´‡‡À××—ääd455iÒ¤ óçÏç£>BC£ä?F999?~œ€€ˆ§M›6:t¨ÔëÉÏÏgýúõ?~œ²²²hÖ¬]ºtáûï¿W)À&OžŒ¿¿?‘‘‘äç磣£C»víX¾|9ƒ *õJâÌ™3,Z´ˆ^½z)îùàà`‚ƒƒ=zt¥Z+gÏž³³³’XVÕVöíÛǾ}ûÔÐ)))xzzÒ©S'Z·n­ÖøW¯^eöìÙ´lÙ²RtHHÞÞÞBNN;wî¤{9|/;wîLçÎ ®À•¾™ˆºªÈÍ…=¬XÁÀqãp´´d¨¹9õÕ¨ôcggÇz;»Â&O†ÔTYåA77Yï2°õæV, -14yåó=J?V~F>>&;ºøu:¤µM£~ßú¢x~1ˆ°|LýòK&‰ÓÈ‘•6‡ëöíD'$°ùE¦ŸÊ&..ް°0&L˜€‘‘)))xyyáééÉŽ; ÄÈÈHíñÚ´iƒTúo&œ¸¸8üüü°³³£U«VJ}utt*ì:^æÍ›GFFC† ¡U«V„……áááÁôéÓ‰ŠŠRdo*Ž„„Æ@“&M¸ÿ~™Ðããã;v,—/_¦G,\¸333üýý9pà'Ožä—_~aðàÁJç9s&L˜€—/_ÆÇLJ!C†pôèQFVâ½/çøñã¬X±‚^½zUùv¿““öööU2WLL ~ø!7nT[@W6l`ß¾}bbbB||<2¶X‰°dD]U$'ÃóçèöîÍà€fMªö©/^dÁĉ´24T>)û7?Z·&ôùsÖîßOÏž=Õ;49”¿üÀ‡m?䪷ìËRKKæÂQrbsxtäùéùÅöÓ¶ÐÆÒÉ’¿ÿ‘o»~[å[‚¯;baÙ‰ˆˆ 07—˜;;bD¥X¡¥R)»¼¼È­QƒÔÔÔ*½¿ÿþ{lllÈÊÊbܸqxzz²víZV­Z¥ö8/§ô™òóócüøñ|ýõ×¹ä׎ 60xð`%7‰ 6РAV¯^ͼyóJÜų°°àìÙ³tèÐ333ôôôÊ´–¹sçrùòeÖ¬YÃÂ… íü1sæÌáƒ>`âĉDDD`nn®8þèÑ#¥{{þüùœ;wŽþýû³~ýú*ÐÕIY,ýo"sæÌ᫯¾¢uëÖ¬]»–E‹•{Ìììl²³³‰‹‹«”¢ro¢€®*ÒÒ`ï^²;vä›äÏŸ~b››Z[-={öÄÚÜœ·o£­ªCz:¹ééL´³+•x†S×I0£ý ¦|+koÛVD¨.Ï®?ãéŸOò…bû´2Àb¸ºlÚ´©Tk}[ÅsÙ™÷ý÷$ŒCF@G<<*Å íº};Ñ}ú µ°à› p+…p­HôõõùøãñôôäæÍ›,]º”š5kòå—_êÿÏ?ÿ°cÇF­Ößüü|¼¼¼8{ö,÷îÝãùóç4mÚ”1cÆ0äE%TUðË/¿LóæÍ™9s&:tPëš²³³qssãÆÄÅÅÑ¢E &MšTh«>++ ___¼½½¹víúúú4oÞggç-“£F*ÔfffÆ„ عs'!!!%ºÔ¨Qƒþýû«uMEáëë‹»»;}úôQÏrìííYµj3fÌ`åÊ•¸ºº*Ž©z0ìׯæææ¤¤¤”8wVV‹-¢gÏž…²P]¼xæÏŸ_¤ëή]»8sæ +W®ÄØX >lØ08þ<'OždñâÅøûûsòäIBBBpuu¥cÇŽ\¾|™S§N“'O°±±ÁÁÁAmšÏ?ÿœN:1aÂ¥ö³gϲÿ~bcc騱#}ôÑÑÑœ9s†eË–©ôé=pà§OŸ&..޶mÛ²|ùrLMMð÷÷W¼ïDGG`ccÃçŸ^â:åÈßï¢èÖ­c^JQÆÖ­[ AOOöíÛóùçŸ+Ö%§S§Nj¯A]|}}9sæ ÉÉÉ¢€.qó¼ªhÙ>ùìíÉ5ŠØÔT:—ÂÄ;eÙ2ö³E»ÏȈ)Ë–•jIYyYürKf‰êפõ óâ{Xmÿçi=&Å+¥Xñ,Ñ`ÚÏK'K4tÅÛN¤â‰ˆˆ P* Òø~çN¡øºÒ"•JÙuæ 9íÛ#X[óGX©©©:GioËÝ1?~Ì‚ ˆQl¼qãF~úé'¬­­ÕûñãÇ8::H:u°´´ÄÝÝ¡C‡òÍ7ߨ<çâÅ‹ôïߟ¸¸8š5kÆ©S§xÿý÷b«8bbbèСóçÏçÆÔ«WÇãààÀÚµkýòóóéׯŽŽŽ\»v¶mÛbee…{öìQëÚTñì™,f£ªRwýþûï|ñÅEöqvvÆÌÌLÑ·8Î;Ç“'O>|x‰}srrpuuåâÅ‹…ŽáêêÊãÇ‹<ÿÑ£GŠûþáÇDGGMZZ7nÜÀÕÕ•µk×2iÒ$bcc133#33=Äœ>}cccš4i‚Ó§OÇÑѱĵ¸ººòçŸ*µýøã 4ˆË—/Ó¢E îÞ½KïÞ½Ù¹s'®®®ŠÏ÷e–,Y¤I“HHHàáǸººÒ½{wòòòYi||< ó…–_gBB‚Zë”SPPÀÝ»w ½|}}quuåÂ… о'Nœ }ûöüöÛoXZZ¢¥¥ÅÚµkiÓ¦ wîÜ)Õ¼eaàÀlÚ´©RÄù›†h®²{÷&ñܹR3tÔ(Ƭ\‰³ +t.p¶Q#~WaY)Ž#ÿáiöSfu˜Å +RG@ç>ÉåÑáGä>Ê-¶Ÿ¦¡&µF×B¯QÙ¶9EDÔaÞ÷ß/·¦iiñ÷;ï ûÃh”Å™¿òOœ ¯W/Åïу³dýz6WRӒؽ{7¶¶¶€,¸jÛ¶mìܹ“•+W*ú¥§§sèÐ! Dƒ Ô»fÍš„‡‡Ó¤IE[nn.ŽŽŽ¬[·Ž3f›{÷îåìÙ³ ëlBB:t`Μ9„……¡­­r ù&ß¹sooo™¨4iK—.eÔ¨Q4iÒ„Û·osùòe¾þúkV¯^­4Frr²Z×ö*ÿüó'Ož¤cÇŽJ×[™„‡‡ðî»ïÙGGG‡æÍ›síÚ5ÒÒÒ”ü¬ÓÒÒØ²e ÙÙÙøùùqåÊ&OžÌW_}Uék_´h999¬X±‚íÛ·+ÜŠ^eÿþý:îíí­tÝ‚ °`ÁÖ¯_Ï™3gXÊ¢` ,Y²„:àããƒá‹íÓ#GŽàääTäyþù'÷îÝ£iÓ¦0þ|~üñGŽ=ʸqãèÓ§fff´k׎©S§–Êêü2†††…"ÓÒÒèÖ­¦¦¦Šq333™={6&&&øûû+v.]ºDÏž=™;w.çÏŸ/ÓD*ÑXE˜ùùÑÑÅ…Ž..˜ÎžMmcc®]»ÏŸCB„…ÁpþA*•rýúõBçØØØ(¹6Ô­[GGG¢¢¢ŠµBGDDàîîÎ!CâÀØØ˜?þ©TŠ»»»b]€ÊTo¥Í@ðüùsÆG^^;Ë‘K¿´Ü¿‰DBýúõ‹í'H‘ n9OŸ>eÑ¢E¬X±‚óçÏcff†³³s™ÞƒÊbذa*Åõ« ‰„O?ý .]ºTêy:Dvv6ãÇWˆgYº‹ îœ>}:M›6@CCCáFqûöíR¯¡4äåå1fÌÂÃÃñððPd®9qâ 8::*¹ÏôèÑ;;;.\¸Peßêféy›-ÐUD³çÏÙûü9×%„š5qIJ‚^½d€j2¸€Â üYëóí¤Û\{x €©í¦¢¥¡¥ÐM›BíÚªÏ R/¤’v9 JØ7êh„Ù@3$šª¹ÜÜÜøö[1ˆðUÄ ÂÒ£d}–£¥…N—. £U9ýUüûKƒóê#_UZ¡å® ºººX[[óᇲlÙ2¥TY³gÏfÊ”)œ>}Z±%¾}ûvêׯ_(›CIܺu WWW‚‚‚ˆ‹‹C*•*Üb"åAÌ/ѯ_?•m[·n-$_&44mu¿ĘŸŸD"QôiÕª;wfùò帻»3jÔ(œœœhÞ¼y©® d>ׯ ãÎ;üöÛoØ©ÊvTI˜šš"Ïž=+äÛú2ò‡³W…± ‚ ‘‘··7û÷ï§oß¾,[¶Œ+VTêÚÕ¥(Krll,®®®üõ×_<|øPáÚQPP ò¾* ù½5`À¥vMMMzõêʼn'Tž÷ªÿ·ÜW?11±Ôk( sæÌáܹsìÛ·O)n),, Pýÿ¨ÿþܺu‹ÈÈÈJÍÒ”œœLrr2QQQb6¨t‘ xgï§OKw²¾>ƒ±1Îùùìyð€/„÷žrZŸ5$Lo?A€+WdÇŠ²>ç?‘¢.ªøum æCͩѦøÔFb¡jDñ\:TYŸåHûõ#êÇ9>sf¹2rH¥RÚܼIž U¡Q#¼þø£J2rDEE¹].g̘1Ì›7íÛ·ãèèÈÍ›7 bùòåh–"Õ¥¿¿?=zô nݺôéӇɓ'Ó¨Q#øøãÉÎ.üw@Õ®¼­8÷ ¹`yôè‘Êü³ýû÷Wºn___~ûí7ÜÜÜX¶l™"ÿñêÕ«ÕÁR©”‘#GâëëËÎ;?~¼ZçUÍš5ãæÍ›DGG+ £££Ñ××/Òw½F >œAƒqéÒ%¶mÛÆ²eËJõYWªÒ¾%&&booONN `øðá4oÞ---Ƨò¾* ¹³ªœËÅåa®U«–Òïò‚:Åÿ*k×®eÇŽ,]º”)S¦(“û÷ÿèÉ“'•¶6í€úùù‘›[ü( « [ …†ummÑj×N!ˆ•^&&ªÛ_rÛ Œ¶³ãÃþ´±áh)­ÏÏsŸ³?d?ƒ› ¦qþþäº^•€Îyø"Eݳâ-æZ¦ZX:Y¢SçÍÊ+òú¢Òú,GK‹è6mÊ‘Ãuûv¢ŠÉÌPݾÐ/£§§‡‹‹ 7n$66–íÛ·£©©É´iÓJ5ÎO?ý„T*åÈ‘#JEr¿kUøùù Šó{±µU\pž<ÚÆŒLŸ>½Äµéêê2uêT¦NJdd$GŽaÕªU,\¸/yq©bÈÍÍÅÉÉ ///¶lÙÂG}Tâ9Í{ï½ÇÁƒùý÷ßi×®Ê>·nÝ"<<œnݺ•ø¨««Ë˜1cpssãï¿ÿ¦M›6Eö•çöVåzTÙjû÷ï'!!íÛ·+}ÖevhÔ¨.\(”ÂÏ××·Ìk­hŽ=Ê×_Í„ ”bäÈ/]ºTèáCîÚ¢n CYéÞ½;Ý»w] Õ@ô®Bö7mʤ›7aÏpu…U«`þ|˜9ƇÁƒeêÕÎllÀÌLe-m—åËÙmdÄn##\–//õ:„àYŽì‰}VY%¦—ýŸ_޹òž]FâžÄųAs¬fZ‰âY¤Êˆˆˆ 3Sö )•ª|¥÷èÁªmÛÊœ‘Cž÷9çwŠœC¨WÓÿ]­9^fÖ¬Y‚À¦M›8xð ƒ.Ñ×öU¢¢¢077§}ûöJír_dUøøø(ù^ƒ,;ÈcQ´iÓ===Ž9Rª5‚¬É¢E‹pttäÏ?ÿT™máeòóó™0aÇgÓ¦M•Vι$fÏžM½zõpss#66¶Ðñüü|–,Y‚ *Å–*nݺPb¦ÌÍÍñññ)4çñãÇÕšKî/ÏR¡.QQQ…Òw_•„<-ãÙ³g•Úãââ®?eÅÚÚMMÍR_ç«\¿~)S¦Ð­[·"BåÿGΩH2pîÜ9´µµ_»b.o3¢ºŠˆÐÔdÚܹŠ-¢ò0tÔ(F½ðq;VJë3üë¾ambÍ ¦²²¯ò¸ sshÒ €ç!YdÞÍ$3<AZ‚ð@Í^51yߤTÛ䨨ØTÈ{ò&!V"TŸÛÿüC;Ú““™™É’%KXµjÚÚÚxzzrúôiúôéSlZ, –.]Ê’%K˜3g?üðƒÂ&99™`oo½½=çÎ#**бc¢˜‹ IDATÇ*ú„††rîÜ9,,,9‰‹ÂÅÅ…£G2vìXìííeÁÜ/ѤI¥­}RSS =),¦r_dùXfff%úd²iÓ&ÆG‡زe Âüùó9þ̆ ˜9s&÷ïßgãÆ%>€È0`‰„mÛ¶add„¥¥%5jÔP âS…\®Y³†5kÖ`bb‚ŸŸ»wï.³ÛÉ€èܹ3»wï¦E‹ :”¤¤$E.븸¸2 2_u{{{NŸ>ÍàÁƒiÙ²%:::*sJE\\ŽŽŽÔ®]›C‡!‚’«Š––ZZZ888(æ:~ü8Ç'??Ÿ~ø¸¸8>ùäj¿ ôøñc…ϸü!ìîÝ»ŠÏßÎ΃2]·X‰P ‘JÇÙÙY¨e` äææVؘ'N=ZêónÄÝøo¾ûë;E{³ú¹B ž _¿—(D¯Œ¢–G©õz°æžY¦køì³Ï„§OŸ–éÜ7ggçê^ÂkÞ={kkëê^Fµãää$BTT”Ú縻» €Ð A!??¿Ø¾ ¬^½ZÑ#Ô«WO###ÁÈÈH077Ž;&ÂòåË}}||@X»v­`kk+èéé æææ ØÚÚ ÑÑÑJóõìÙ³Ðçš››+,^¼XÐÒÒ$‰P¿~}¡fÍš èèèçÏŸAvîÜ) a,,,3337n,\¸p¡Ä÷ÅÄÄDq¾ª×ž={”ú×®][ÐÕÕ-4޵µu‘c|ðÁ%®CŽŸŸŸ`cc#‚D"ôôô@ÐÖÖþ÷¿ÿúì<¨rN‰D"Œ3FHOOWkÞˆˆ¡aÆ èëë €Ð½{waÕªU )ú®^½Z„ÐÐP¥1>ýôSÁÒÒR±ùýSTA„ììl¡cÇŽŠÏÕÂÂBÐÖÖ~ÿýwÁÄĤÐ{ú›¨ª-99YpppP¬EGGGX°`°páBRRR}…¢äª±>,¼óÎ;бíííUž+gÏž= øøø‚ ^^^ÅÞs3gÎTœ.¼÷Þ{ ˜™™  Œ=ZHKKS9OQ¯—?×qvv.ñ{¦wïÞâwQ H¡‚+ ˆÂÅÅ…[ìùõWêÔ©C:uªm-Ó<§±+hZZD9GakÄÃË™¬[˜ƒè׺vUo,º:X:Y¢US´ ‹T{÷îåÛo¿UT{[¹uë ôìÙSeú6U\¿~Î;³bÅ –•hüèÑ#iÑ¢…Ÿd…$ÜÝ݉åÝwߥ[·nrñâEš6mªH–’’‚¿¿?¶¶¶Ô¬Y“¿þú‹[·nѺukúöí[È æïïOVV–ÊÊ©÷ïßÇßߟððp iÔ¨J–刈®\¹ÂÇ155¥ÿþjço>þ|±R¶¶¶J)Ä|||ÈËË+”áâÅ‹dee©£víÚEú5«";;›[·nLjj*mÚ´¡}ûö…‚ÜäÄÄÄð×_KAA5¢cÇŽ4kÖLí9A– ïôéÓŠÊ}=zô 11‘ÐÐPºuë†Ñ‹â]QQQ„……Ñ£GBfA¸u뉉‰Šû§¸þ s9qâ¡¡¡4nܘnݺѰaCE:¾—ß»3gÎP¯^=E®ó¢ÚäÄÇÇ‹úúúŒ9’ .(í „„„§2KHqc?|øþù“b ¡ÅÅÅB§N033#99YQ-T 6TrÍJ¥øøø(U"ìÒ¥K‘óÅËŸá˸¸¸ªÖILLTTnƒÚ‹FÐU€‹‹ wîÜa„ tîܹT+’´œ4ì–Ûa‘h£¦#Î œøûo8vLÖgêTPÇU²F»˜1G¢UöÌ""%! è²3lØ0þúë/ÂÃñ´´¬î刈T*‚ r! ÃÎÎŽ™Êîm¤8}íÚ5®]»Æhݺµ( ‹A4V­[·.s£rQٲɼ›ÉŸþIŸà>ô°û×OS^õWK ŠÉø€DK‚Ù 3ŒÚ]V\DD¤zHOOçÔ©S\½z•S§N±aÃQ<‹¼¬Y³†àà`Þÿ}jÖ¬Ihh(›7oFSS“~ø¡º—÷ŸAnäS•NRDQ@WUYÕ§ ·€ìH™hΠˤ K–Ó2(<3}3›6Vô— èzõ@£ˆ¼,šFš´0À¨ƒQ…eÙƒU#Š”•¸¸8&L˜@ƒ ˜;w.Ÿ~úiu/ID¤Jh×®gΜaÅŠ¤¥¥Q¯^=zöìÉÚµkËTdçmG ",Q¹TIII•:~AV™÷2É Í$+2 !WÙ3'öY,ž? }ÝÓRåäÀ#Y3¯¦iÕ6ׯ ¥­ Щ§S®Bª+ªF¬D(RVZ¶lYæt}""ÿeXdåC‘Ò“””TlîvQ@WææÄmŽCCW‰Ž ] 4t4èJmšºšHt%ª~q/iØügùdÞÍäyès²dC1Å“nÆË4%š´­ÓVÑ òïÛ† A×Jý–ú¶2D»–¶ª¡* ±¡jDñ,""""Rˆâ¹dD]ERÜÇå/©¡#Û- y©yj“•—ÅDzêR­jµÂ@û߈ø˜X èòPÀ×`Z_¼%DDDDDDDDŠCTKÿ1 ¤ -Ý9·o‘W Û¬: Ñ– ßDƒ–ì÷Öçš´±ÓÒ*y+KyWwï°Åo 7cŠÎYYÜŒ¿IŽV9Írè0£ 4Ärœ%:­kp%@Vù©:ЧEDD—§žýmâîݻս‘·???Ö¬YCPPPu/åµGÐU„®–.ý[ö§‰…zÉþ+-c-X?à`óƒîvû©ö¶2D¢-s¤¾yäÕD»w¯²e)pss###£ê'~ÍY³fMu/ADDDDä-¤eË– >\í‚Mo3¢ GÑÄ¢I•ˆgm m Z`ÐÒ+¾:öñ¦ñèké3¥Í¥¾~~ÿþ\Z "TD(""""RXXX`aa!¦RUQ@Wz õ0dFAN‚T  § øŸsY5û’¼’9ÃâßÌž?Âã®cß‹©ž©Ò©rmc#Ë-""""""""R2¢€®"´Ì´0¶7VÿA0XœÈ–hJÐo¦–±êqOð¤ù²ˆÃ™f*/Àå˲Ÿ«Ãú,"""""""ò_EÐUD©+JåŠÖ-››º€Àö€íØÕ¶£Ký.JÇCC!%Eösu h±¡jÄJ„"¯Arr²âw===¬­­155-æ¬7›ððp‚‚‚ˆÇÌÌŒFÑ©S§"«§ ‚@dd$AAAûLxúôiõLþãìì\ÝKxmسg`mm]Ý˨vœœœdN]J¯V­Z §Nª´y>>;vŒ)S¦Ãðáùy³rÒcÊ-¨111•2~Yøâ‹/8tècÆŒáìÙ³¤§§óèÑ#¼½½?~|¡Ô˜W¯^¥]»vüóÏ?¸¹¹Ezz:/^¤S§N|öÙgLš4©Bט””„““Ïž=ã—_~!%%…Û·o3iÒ$N:Å—_~©Ö8sçÎÅËË‹èèh’’’8räÝ»wç×_eéÒ¥jˆ¦¦&ÎÎÎ 4¨<—%ò!V",qïü $>=ž“÷N0Ávƺ…}¯åº[7H ùOQ]@nrù+}–Ú:Šô•Içα±±`äÈ‘4lØï¾ûŽ-[¶°k×.¥¾Zµjùþ\¿~€‰ »±É±µµ¥]»v#•JÑÑ)ÿƒ½½=666ìØ±ƒI“&ѬY3¤R)›6m"99™%K–”iܘ˜Ö¬YCff&ÎÎÎå^§ˆHQˆA„%#*—*⯿þbÖ¬Y :”¡C‡VÚ<„ÿAì3™5iV‡Y*ûÈ´žtèPiK)777¾ýö[ÑúÖ¬Y#úA‹‹ \»vMQŒ¨wïÞ|õÕWäååáååEëÁ ½zõ"%%…}ûöáááÁˆ#HLL$""‚/¿ü’Ï>ûL1®|€zõê)\"Z·nÍðáÕÖpôèQBBBøæ›oX´hÝ»w§~ýú…\)䤧§3kÖ,Ö®]«t-sçÎ¥  ¬¬¬4hï½÷sçÎå×_UXÃþùg¦L™Â’%KX¾|9ݺucÀ€̘1sss¥ùÂÃÃhÔ¨Q±ïg£Fàþýûòð*‘H¸qã .¤yóæ¼óÎ;ÄÅÅ¡««ËÁƒ7nœÚcÅÅÅáàà@VV1114lØzõêUîuŠˆ¼Ê©S§8uêýõýúõ«îå¼ÖˆA„UDŸ>}Ø´i¬Ôy¶ÝÜ€±®1ãÞUýGZ. ;v„ 0¶”1ˆP5¢x) Z¶lIݺuéÚµ+Œ1‚9s晙ɥK—°±±Qˆg9Æ àÌ™3Ô®]›FqæÌ®^½Z¦µøøør{èÑ£G‘©á† ¢ôûƒð÷÷g̘1 ñ,g̘1hjj*­±ÿþ}GGGî޽ˉ'ÔËÂÂBñPŸŸÏ‘#G˜0a?æôéÓ²^9ZZZhii•)˜ømCÐo;wP ªƒ®^…YQ@‹ˆüÇX¿~½" GyqvvfôèÑìÛ·'Nðÿ÷lÚ´‰/¿ü’uëÖ•x¾¾¾>999dee¡­­­tìéÓ§*Ï122*ÔWžvnĈt/âÒ«Áx/cll̬Y³>|8 4àäɓ俿¢­­M³fÍ™H·µµUy¾ „††¢££Sa©»Ž9‚ |úé§ «º††Æ £K—.œ9s†ÔÔÔRïÀijj2~üx6lØÀ¹sçHIIQø¤‹ˆˆT-¢€®"*;ˆ0¯ ]²4V]tÅÖRõ—…Ü}CC^1U9b¡jÄ ÂÒ£ßDëo¬«m~‰fõç‚lÚ´)‰D‘!ãe¼½½ÉËËSJ9†††Ì™3‡9sæðèÑ#FÍÏ?ÿÌÒ¥K166VˆÝüü|•óåååqñâE…‹@ZZš"xOZµjÈ„°ÜϹ,Ô©S‡æÍ›sçÎÒÓÓ133cèСüïÿÃÕÕ•±cǪ<ÏÃè¨(F¡aá]»²pëÖ-€B)úämW¯^åîÝ»…¬÷êÒ¦MHLL´H¥ –Œè]E$%UnŠ­a'HÈùÄ<ÿ èwß…êv?vss+•/àÛ‚:¹]E^A-Iµ½¨~ýŒ¾¾>½{÷&::š;wî(óôôþõ?~µØÈ2ytíÚ•ÌÌL…«GãÆ177çîÝ»…ú?€Ã‡+µ9rDåøEѸqcÚ¶mË®]»xøð¡Ê>¹¹²ßùùù*üüü¸sçuëÖUˆÊîÝ»ãääÄ•+W”åÜ»w¯¾ú ,X öšKBþ¼oß>¥öŒŒ Ž;@‹-íAAA:tHÉûÙ³g*ÇŽçÔ©S)É¡C‡.1""塲5Ë›€hú«"*»ª}ú(‚ KBCCƒ]»vѽ{wÞ{ï=æÏŸO›6mJ¥DFFrøðafΜ‰³³3¹¹¹ôïߟ֭[ãèèH‹-___Ξ= ÀªU«”Æß¸q#ÑÑÑ,\¸óçÏ3hÐ ,--¹yó&{öì!##ƒíÛ·«Ì””ĬYª _|ñ…J 3À¬Y³øùçŸÙ¼y³ÂºÉÁƒÉÈÈ`îܹ˜šš*úïÛ·WWW¼¼¼æ`ãÆ >222¸pá—.]âùóçìÛ·OÉOõܹsÌž=›ÿgï<⸺ü.Ý ( (X±"±×Ø»"ƽÄ[ˆ¦c”øi¬Qcì1MT[ì(ƆŠAE¥(ˆŠŠ€°Ôù~°îR,ê}ŸgwïÜrfîž¹÷”E‹É«ú‘‰Q¥Èûûû°jÕ*¼¼¼øâ‹/°±±Éþ?IðÎ!2æŒP ßÇñûÇá4íΊW®@bbÆçâ @ ‚Üaff–«,ÎÎÎ\¼x‘?þ˜‰'’ššŠ¹¹9nnnj;666ØØØ°`ÁP(4lØÉ“'3iÒ$5³ª… Ò°aCŽ?ξ}ûxï½÷4h›6mÂÉÉ ///vîÜIóæÍ9~ü8_}õ•F²råÊÉñª_ç½÷ÞãÆL›6ùóçË;S¥J•¢K—.4j”ÏÞÈÈ:IJeËdÓ8œYµj¨/ X[[ãëëËÂ… Ùµk³fÍ’MRÌÍ͹råŠüÂKKK^¾|)+š¯3|øð,h+++|||øôÓO9tèì0X±bE¾ÿþ{ÜÜÜÔê—-[KKKµ-s'''¬­­Y¶l™¼o``@£FøòË/5¢™”,YKKK(áááj×`iiÉ•+W¸rå ‘.\(ÐAÞQH’$éZˆ·•]_a­,ºwgñ¹ŒíÉ۟ܦVùZZë-Y³gg|‡+ÐàÍÆÎÎŽ)S¦èZ @ (r„K^·Å‹ˆˆP CTDEE1xÜ`‚Óƒybö„4) 1à:8ëÕgøW67‡ºu _V@ ‚·¡@çƒ'N°wï^9 Pnø¯ùÑÑÑ´tiIHËÈœ¥ð†Œ‚ßa¿,•z•ݲ%dã—S¤ˆL„Ú™@ KD&œyëœSRRøý÷ß™;w....rPzm\¾|ìììèÖ­GŽ‘Ï…„„àè舣£#sæÌ‘ËÏž=ˤI“8vìXžR¨†„„pûömµ#/tÃ>FH‹×”gæp¿Ù}>œð¡Ö¶Á½{Ÿ‹“ù†p"ÔŽp".N„9óÖ-ý={öŒÁƒãèèˆB¡àæÍ›ZëùûûÓ¡CºvíʲeËøóÏ?éÝ»7ÞÞÞtëÖ pb—.]bôèÑ8p€êÕ«çI®ë×ShÛöß`öII·Øºµ}ûöͱm\\7žÞ€ÚÙT*7oÜ$..Ž2eʨ*®öωP;‰P ºD8æÌ[§@W¨PØØXJ•*…‡‡‡Úêqf–,Y‚µµ5žžžЯ_?î޽˂ ´®ZÇÅÅѵkWÚ´iÃæÍq—«V­Êøñãs%—RY¥ò³L%Ú³[iãÚµkĘÇäX/Æ<†k׮Ѷm[µr•ml Z²Õ @ òÀ[g¡¯¯O©R¥²­#I»wïæ½÷ÞS³¿mÑ¢çÎãÑ£Gm Y»v-C† ¡aÆ4lØ0«ÐÉ@ üçH!%%…ôôtµZJ¥R£L__ƒÔ×ÞuR×rH¤hØ,)•J|}3úkÜ8C‰Ö6†(eűL$J‚Â!ó¼«T*Q*•ÄÄÄ §»dÍ[§@ç†'Ož T*©ûZ8Šúõë®ÑÆÄÄ„Áƒ«:tÈè1€Û?ÇF6nÜHpp°Z-777²-[¶PòQIõîŽÏÕ‹R/¥R²¤z½É“ݸ~=£?Uügmcè¢,88˜©S§ YŠSÙíÛ·‹,º.vx@P8džYñk IDATwÝÜÜpss£U«VŒ3†óçÏëXºâBz‹—xT&¯_âõë×iذ!»víbÀ€rùßÿ““^^^¸¸¸˜#GŽdëÖpàd¦R/öî%W6У¦Œâ—”_H³ÖþV¨©Ï0£al^µY­üØ1èÒ%ãóгg>. psscÞ¼y˜™ióŒ|w9r¤°ƒþ‡-[¶0oÞù$OJÙãǹrå W®\!22>ýôÓ\·÷÷÷§E‹¬]»– &äçRèØ±#öööœ>}:_í‹ žžžLœ8‘óçÏç˜ròäÉœ>}š˜˜œý‚þ /_¾äêÕ«\¹r…{÷îa``ÀªU« uÌâ@TTQQQ”.]Z×¢{ÞIºR¥JܺuK­< @í|Áœš“ÚÈÄ“Màп¢OÛ ( JÅ–¢]ƒv¬>²ZkÌF•]·nF@ðæqìØ1vîÜIåÊ•100 66–/^ðá‡ò믿j${Êooo”J¥ü=))‰˜˜J—.­±€ðùçŸç¨@¿I´hÑB^ù­_¿>wîÜáèÑ£¬Y³†;vпÿû ÃÞÞ^­¬Y³fyR ŒŒ°´´Ô0»ìííÕ”tccãwB åÂ… DEEå)Tï»È;©@—,Y’Úµkk„¸ ÀÜܼ¶ëaûœ?¶øoáNÌè˺/£§E†-FÕªU³l“š .d|.NáëAþðõõÅÞÞž´´4Μ9ƒ››;vì mÛ¶yZÅ|}å×ËË WWW>ÿüs>ûì3íÞz÷îMÿþýåhE’$qäÈzöìÉèÑ£éÑ£GŽJm©R¥pww§Q£F4jÔˆ:uêäYŽ÷Þ{¨¨¨|]ƒ ð™0aŽŽŽ4jԈѣGó÷ßëZ¤"¡yóæ4oÞ]‹Rìy+褤$$I"55@^i144D__€ñãÇ3wî\üüühܸ1¡¡¡ìÛ·áÇRöUòzÛ•©J枀ƒ™7ú#}£Û]» Ÿ‹£-2jGd"Ì;OSR¸«³ñÛššR¶Ÿc}}}Ú·oÏôéÓ5jÇg„ x{{S®\9Zµj¥ÑæÙ³gœ={–ºuëæÚT!&&†'Npÿþ}”J%ööötêÔ kkë,ÛÄÆÆâããC@@µjÕ¢K—.±é³# ???"""¨U«;vÔº–˜˜ˆ¯¯/.\ D‰T¯^víÚåhO¼råJµï …‚îݻӣG<ˆ¿¿?-s°w³°°øÏ ^¼x 6”W³Ÿ`~ûù¹RžΞý÷sqT W¯^-œµàáá!ì óÈËÔTüâât6~ã2eÈjS°¨ìŸU+™?ÿü3'Ož$22RCÙZ±bß~û­š-uv<}ú”J•*‘––F¥J•P*•<{ö SSS¶mÛFŸ>}4ÚDEEѬY3"""([¶,8::ràÀwö”J%³fÍâÇ bÅŠ|(¯ÜY[[3~üxâãã5Ú|ùå— 4ˆ˜˜>|ÈŸþɃ=ztŽã}ûí·üøã|úé§Å‘#G(W®C‡%û÷ï³hÑ"ºuëFLL ÷îÝãÉ“'„……ñÑGåêÚ^'66–ýû÷ciiI½zõòÕGA2{ölºtéÂÝ»w‰ŠŠbÁ‚„„„°páÂlÛ}ûí·Œ5Š)S¦°sçNLLL$‰À† ˆŠŠ"22’Ÿ~ú‰+W®0mÚ4~¶mÛ†?&&&F~Î>Ì78vìqqqx{{cjjÊŒ3HJJ’ë9s†Ñ£GÓªU+|}}yüø17nÜ wïÞÌž=›sçÎȽ6l˜ÃXu„„„P©R%¬­­iòOö°û÷ï3|øplmm çþýû>ãñãÏèÓç³\mc}ïû=Ï3>/ì¸=EîÿËTóSq\}yçÖ­[øûûsòäI&OžÌ?ü€žžž¼×µkWX¿~½Z»ƒ™ëÌ©eË–eܸqò¢‚¾¾>mÚ´aúôé<~ü6eʔ᫯¾’·¼»téBÏž=9sæL¶qeŸßÿ=ÕªUC¡PеkWÜÝÝyòä‰üRðäÉzô衶ʮڎÏãÇ'22’Õ«Wchh˜¯> ’š5k²xñbªU«†©©)sæÌÁÔÔTë=‡ŒÕÖqãÆ1oÞ<–-[&?ñ’åççǸqã3f ¦¦¦”*UŠñãÇÓ³gO~ÿýwÜ))),[¶LþÍÌ‘!99™U«VÑ©S'ôõõéÕ«={ö$**Š   ¹ž»»;úúúüôÓOòÿKݺuY±b …‚~ø¡@„„z÷îͳgÏðöö¦råŒÞ%K–’’œ9sä2sss¾ÿþ{€BÜ}¼­¼•&Å‘€€ªTñÀĤ5Jekþ÷?xÿýìÛ<ŠÄŠ‹+hS¥ =kä>ˆsPüó;#hà-¡çkÜøùçŸiÑ¢a«9~üxæÌ™#ûw¬_¿žÒ¥K3dHîw° ßdçÎ\»vˆˆ’““yõêwïÞըߡC {ÑÎ;³k×.9l›6HJJ¢M›6œ:uŠôôt$IB’$J•*…B¡¾›5k†­­-sæÌ!((ˆAƒѸqãçïïB¡P3‚Œ7 9 ׻ޝ¯/¾¾¾ÈÉåÚ taeeÅ!}‰Œ´`ãF8t! ¿ æûÌ'!%à УSÞVTæP|háD¨áD(ÈŠ;v`ee%Ƕ²²ÒPÇŒÃ×_Íúõëiܸ1<àðáÃŒ;6O&jOž<¡iÓ¦„‡‡ckkKíÚµ±³³“M7´ÅáÕæ|§*{ðàA–c…„„°fÍÖ¬Y£µŽª½B¡`ïÞ½xxx°|ùr–.]JµjÕ˜4i’¼Âš[¾ýö[¾ûî;FÍŠ+rÝ®°©U«–FY™2eHNNÖ(¿uë~~~ôèÑWWWóªìâsGFFª}Ï.³6ÙT;*ùÂÂÂHIIÁÏÏ/ËŒ½…a¾÷é§ŸâååŲeË4’¡…††R½zu­;Ñ-Z´ÀÛÛ›—/_æéùyqttÄÂÂBëËš@¡¹‰‰‰8::2w.lÞ éé°t)lØ ½~ðó`6\Í8Ù§VZÚæ- ŠJ65…b`Ò§áD¨áD˜wJëëS¯P å“"òÒoÞ¼¹FüáשP¡|ð;vì`éÒ¥lܸ‘ôôô<™o,X°€°°0Œ­û÷ï×°±VñzlýÌe*TÈVfÈxA}úÈ+ˆ£GÆÚÚš3fpçε6iiix{{Š<_|ñ÷îÝcæÌ™¦'QQQ²möåæÍ› 0€ºuë²sçÎ,ÿ?'Ož ÀºuëÔÒØ«² N™2¥@äyÓ¹qãžžž²Y• kÄ tadd„™™&&&Ô«—¡ØúúÂÚµ0w.dÞ}žsbÆúÆ|Óþ›<•9:P›6 ¼@ xã˜8q"Ÿ|ò eʔшϛ¦M›ÆîÝ»iÕª]ºtA__Ÿ'NðÉ'Ÿ¨%™È̘1c>|8-[¶ÄÌÌŒ?ÿü“„„¶lÙ’ã*äÊ•+yðàƒæûï¿§nݺÄÇÇs÷î]nÞ¼ÉÞ½{©V­gÏžeÒ¤I4mÚ”5j’’‚¯¯/|÷Ýw9ŽóÅ_pèÐ!µè*~þùg† &?|ø°V{ïÌù’’’¸té&&&ôéÓG-ô[Q1bÄLLLøè£èÞ½;ÀÌÌŒß~û!C†Ð AZ·n­­-QQQ\¿~¨¨($I*pYƇ¿¿?«V­ÂËË‹&Mš`ddDhh(—/_fòäÉ4kÖì?³zõjbccIMM¥W¯^ç,X@ëÖ­iÓ¦ 3fÌà‡~ N:4iÒ„Û·osíÚ5\\\4bI8ýû÷¶Ý’$Éÿ¿vvv/#o &&&˜™™åø"* t‘aaaA·nÝäï³gg(Ð/^ÀÆ0ujFùÉ“½w€‰M&bgš÷Î*ó ##ø'f±D8jG8 ^ÇÅÅ{{û<98µo߀!C†hU3S³fMÜÝÝÕ¢d´jÕŠ‹/²fÍÂÃé]»6»ví¢Y³f¤§§Ó:ÓöV•*UpwwgÀ€Lš4‰_ý•€€ÌرcqrrRoÈ!¼xñB­Ì‚3gΰ}ûvÎ;ÇÝ»w)Uª;vdÉ’%²3š*)ÆÅ‹‰ˆˆÀÌÌŒ/¿ü’N:嘬ÀÍÍ-[ûÎ×ÓrO™2EÎf›™Ñ£Gk\ƒŠÜÄø¯\¹2îîî8;;ËeU«VÅÝÝ]kjpЧ'NÄÜÜ\­lРA˜™™qêÔ)Ž;†««+íÚµ#00Ÿ~ú‰€€BCC©X±"&LPs¶300ÀÝÝ=K'ÂéÓ§kÍÚ§z~2›é( Ö¬YÃ!Cðòò"00¸¸8™8q¢š²ëì쌻»»^.;>øàµ{Öµk×lÿ.2g±\ºt)]ºtaÿþýáììÌÌ™3:t¨F»Þ½{k½V@ãž¿MT¯^êÕ«³uëV]‹RìQH…ñê)PcäÈ‘„‡‡sòäI¹L’2"pܹöö úúÐlC3.E\¢ŒQîO»EI‹<פ øùeØ?P¬úBÁÍÍM8jaäÈ‘Âú¶lÙ¼yó Õµ(o3gÎdùòå\¿~½X$ÅUD–ì~g:tè@•*UÄoQ6è"âuƒ|…fÌÈøšáT¸;p7—".0«å¬|)ϯ^¿Æçânÿ,2jGLX‚ü’žžÎ‹/ðòòbõêÕŒ;V(Ï Ï'œ tqëÖ-–/_Î… ä²áÃA’òKÓøâd†m^ÅR™ÑbF¾Æ¹pRS3>wZ ,AAA”+WWWWìììX°`®Eo.\`ùòåZCR Ô taeeE»víÔb¸š˜À?ŽÁ\IÛÌíèŒÌL_´ý‚ÒFÙÛ,f…ÊþY¡€|fµo(¶¶¶>|˜›7o”mìe@ x{{{Úµk‡•¶øº5„]D”*UІ j<”“'CɲJh73>n”÷S*T tíÚÉw¢XLªj¹\ S)nï¥J•¢[·nZÐ '¬¬¬hذ!¥t˜˜êMA(ÐEÄãǵ–—/NãWAÙ&Ôš‘~þÂǤ¥e˜pÀ›a¾±zõj9-°à_<<ò–¶] ‚‚$+Eð/B."²2ÈQÆXþ…éqîü1$ßcøûƒJ}â? 'Bí'B@ èáD˜3BÖ1‹Ï-&&éyÆ— ùu»QQùëKe¾oÆ ´@ Á›ˆP ‹ˆ€€<<<ðͤåFÅG±ââ œÍ߇ žY¦÷Î ª®+WΈ--@[|}}ñðð @×¢{„]DXYYÑ·o_µ só}æ“’Àš~rÔŒµk3â9ç•ý¦¬> 'Bí'B@ èGGGúöí+¢pä¡@‰‰‰8::ba‘‘%øy0®nÀ¥– Ímš3kVF]Uz,lúñ¦(ЉP;‰P ºÀÂÂGGÇlÓÝ 2 tñºAþ—§¾$%==… ;. O¨Q#ãüòåQ5rË›hÿ,œµ#œ@ K„aÎZ\}t•7v0Üi8u*dÄlÕÓû7½wHìÞû>U tٲРAAJ+Å“ØØX‰ŒŒÌµ9Xll,¡¡¡H’TÈÒe T*¹{÷.=ÊטiiiDDDȳgÏ AÂ7‹ãÇ3oÞ¼BÙ½ôòòzc²w3oÞûŒáÇóìÙ3Ö®]‹³³3Ož<)”qŸ}:»wïfÛ¶m|òÉ'ܽ{—   µú>¤yóæ,Z´gggÖ¯_ÏÞ½{™>}:7nÜ Y³f¬]»Vc¥R)ÏÛy%11‘ž={âíí͈#ؽ{7K—.¥råÊÌš5‹+V䪟çÏŸ3~üxÖ­[ǶmÛ?~<ÉÉÉŒ3†_~ù%W}lذ¥K—••III¤åÅ>°¹~ý:sæÌáŽê,´k×wwwJ—.]ˆ’ òûì¿SH‚BgĈRûöí%I’¤¦?7•˜‡TvQYéé«§Zë?}*I%JHHR¯^9÷¿woF]¤Ó§ RòÂeÚ´iÒ‹/t-F±cĈº¡Ø°yófÉÎÎN×bèœAƒI€"—¥§§KC‡•iæÌ™…2n`` H‹-*”þóC•*U$}}})((Hã\ZZš«VÖ¯_? V­Z¥Q?44TªQ£†dhh(ݾ}[휩©©T«V­|Éxüøq zöì©V$) ©^½zùêW’$éĉ 5oÞ|8ßã$;vì(VòŒ1B266Öµ¹âðáÃ íØ±#ÏmGŒ‘ãïLûöíÅoQèLsǧ½k{.™\G˜Õr%-´Öµ°È0åX·„À@¨];ë¾U憆дi!_H,_¾\×"K„aÞIL¼Ç“';t6¾µõ8ŒŒ,‹tL…BÁÇ̯¿þÊõë×åò¸¸8,XÀ_ýÅãÇ©W¯#FŒÀÅÅE­ý«W¯8xð ÇŽãÌ™3èééáààÀСC:t(¾¾¾Ì›7€_~ùE6ã¨U«ßÿ=’$±bÅ ¼¼¼xúô)Í›7gÆŒœ={–#GŽàåå%7þ|BBBX»v- .äìÙ³„……qûömŒŒŒHHHÀÃÃsçÎFµjÕèß¿?ãÆ“ûˆ'<<œFQCåq ===Ê”)#÷ññaÏž=´mÛ–O>ùD£¾K–,ÁÅÅwww5yÿ áIÛµk§V^£F *W®,ŸÏmÛ¶¥L™2ÄÅÅåª~µjÕò=– www^½zÅÿþ÷?,XÀÉ“'‰§M›6,\¸PÃ<==åË—óçŸrïÞ=ªU«F=˜2e zÿØþòË/¬\¹Èx6Ö­[@‡˜:uj–²üòË/ìÞ½›-[¶Èã®[·Ž#Gްk×.Ö­[‡··7ááá4hЭ÷`ÅŠìÙ³‡èèhš6mŠ»»{–cÆÇdzhÑ"Ο?Oxx8Õ«WgðàÁòNÀöíÛùã?˜:u*:tËSSS™8q"‰‰‰¬_¿ž’%KÅ‚ ¸víOŸ>¥víÚL˜0îÝ»kŒþüyþ÷¿ÿqãÆ jÖ¬ÉàÁƒåˆ^——^^^„‡‡ GÂ&EÄÓħ\Œ¼T,U‘éͧg[ÆŒ [fI‚œLæT t£F& Á»‡„$¥ê쀢qH{äädY)‰ŠŠÂÙÙ™~ø;;;úõë'Ûâ~þùçjmȇ~Hdd$ƒ ¢_¿~°uëVLLLäé2eÊ`ee…••åË——û=z4Ó§OGOOAƒ‘””DÇŽñôôdß¾}jãýõ×_x{{Ó·o_öíÛGñ–o3 IDAT:uhÞ¼9‘‘‘8;;óí·ßòâÅ zõêÅ;w?~}úP­Z5þüóÏs,ìܹ3¥K—æ·ß~S+?sæ >ÄÕÕ5ß}{zzGÿþýÿ«˜¹ÆÇÇooozôèÁš5k¨[·.)))¬]»–ž={ªÕMOO§k׮̜9…BÁ Aƒpss£[·n¤§§Ï”J677—Ÿ/SSÓle¹yó&ûöíC©TÊeþþþìÛ·ñãÇ3{ölÊ—/OÅŠùã?hÑ¢…†½ôÇŒ›››üÜ*•J:tè@pp°Æxaaa899±páBâââèÕ«·nÝbÔ¨Qj/e}ûö%00?ü¨Lé„¿üòK6lØ@—.]dåùâŋԫWµk×bddDçÎ9wî=zôа‘?räíÛ·ÇÏÏWWWjÔ¨ÁÌ™3Y¿~}¶÷)¿ôíÛ—-[¶ÐúM²Õº^1b„DC$¾F¢’“‹S®Ú¹ºf˜eKÒ£GÚë¼z%I††õfÍ*@¡‚b@nM8îJ!!_ëìHJÊâ´€Ðf‘””$›'Ì™3G’$I;v¬H{÷îU«÷þûïKzzzÒõë×%I’¤çÏŸK€4|øp±åÏÙ™p\¸pA¤¡C‡ª•¯_¿^"ãB­¼k×® õë×OJMMU;7lØ0 Ng²AKKK“¦NªQþÝwßÉý;99I_ýµtþüy­÷ÍÕÕU¤€€­çUôìÙS¤°°0¹ì¿˜pHRÆ{µjÕ${{{iÊ”)RïÞ½¥%JH#FŒž>Õn¾§gÏžIÓ¦M“Æ/Õ®][266–ÜÝÝ¥¸¸¸<Ë”_ŽfÍšI€4bÄ)!!A’$u¢Ì÷Æ  ¹»»«õ1}út 6lØ —åÇ„ÃÝÝ]¤G™~?þøc Z´h¡vo/^,ÒÚµkå2??? >øà)==]£îë&  …Ú5¦¦¦J&LÐ(¿víšdll,uèÐAJKK“Ž=*) µ¿³´´4ÉÉÉI*S¦Œ.—'&&J;w–J–,)=|øP®ëèè(•+WNízƒƒƒ%CCÃB5áÈMw±]T¤  9— fÉê%96Q%VÉ.½÷Å‹’’ñùM{aN„ÚN„‚¬˜?>nnn >KKKöìÙƒ Ÿ~ú)©©©lݺsssúôé#·122bèС¤§§ËæA%J”ÀÔÔ”˜˜RTÈ?˜˜˜äJOOOy…QÅÀ10ÈÚ:ð£>B___þÍöíÛiÛ¶-ï¿ÿ¾\®§§ÇøñãÔV³çÎË¥K—6l·oßæ›o¾¡E‹´jÕ ///y… 44›l¯Eu^U¿ hݺ5...„††òã?âííM¹rå:thž¶ßðòòb÷îÝR®\9êÕ«G©R¥ LÖÜ2yòdJü³Í©P(èÑ£MCÅÆ²€e6o5j”ÚùÂ`Ô¨Qj÷Vµ:žY>ÕËG}„B¡Ë?úè#þ"""øý÷ßéܹ³¼[ ¯¯Ï„ $‰ýû÷Ëå 6ä‡~àäÉ“LŸ>aÆQ³fMÖ¬Y#×ñññáúõëŒ5 [[[¹ÜÄÄ„1cÆÀÑ£.\¸ÀíÛ·éܹ³ZfÀjժѲe˼ߠ< œsFØ@™Rs¿ªùŠ5;×0}âtµ’×iÙZ´€óç3Ò{Ï ¯Ï™*ó …9ø›ÂêÕ«™7ožH¦òÂZ oooôõõ111¡Aƒ´jÕŠÙ³gcffFpp0)))tìØQ6éPѵkW9B…‰‰ £FbùòåXZZÒ§OúõëG¯^½4ÚfŽ{÷000PSzLMMiÖ¬çÎÓhc``@ÇŽÕÊ‘$‰„„ ¥ ÀÐÐPck½I“&lÛ¶åË—ó×_±}ûvöìÙƒ««+Ÿþ¹ŠÌÒ2Ã.=:::Ûy&::€²eËæ|á¹àùóçÔ¯_6oÞŒ««+¡¡¡òV¾»»{®3ŽÚØØÈq«ýýýÙ¸q#Æ ãܹsZ£‡ÖÖÖ4iÒD­¬aÆjQ`‚‚‚°±±ÁÑÑQ­nýúõ©T©’F””‚$ó‹#@:u022R“/88}}}Ú·o¯V×ÚÚšzõêq÷î]¹ìÖ­[¼|ù2×Ïæ¤I“8yò$+W®ÄÄÄ„£Gª½ì¨"ŽÜ¾}[£OUö?Õ"ŠªïÎ;kŒÝ¹sg|r¦+Ÿ<~üXØ@ç€X.*^3ëŠ3ËÕJcæôÞÚBªhGÇ çÃ7 ‘‰P;BydÅåË—‰ŠŠ"44.\ˆ¹¹9111€ö bVVVËu–-[†¯¯/:ubïÞ½¸¸¸PµjUvìÈ3¦R©ÄØØX^‘ÌŒJ¦×)_¾¼†’úøñcJ–,‰B¡Ð8†J«,VÊ•+G¯^½ðôô”m¢—-[&Û2׬Y€û÷ïg{-ªó9­Tç–={öÉôéÓ9r$¦¦¦899±råJX¿~½ÆÊN( œY¶l666ìØ±C¶/ ´½\¨^¶T÷[’$bcc³T¼ìììˆ-´$6Úì§ …Úx/_¾ÄÌÌLkÝ×åÎͳ™yeZ…Ja677§råÊjçT/kÆÆÆíJ”(Áˆ#¨S§Ž,«6¹²*+H„òœ3bZG¼2yETTuëÖͶ^ß¾P½:òe0i¨­ÓÒ2V§áÍ3ß #£JXZjnÁÚÆ¢DiàÔ©SçÎ;GRR’F4‚V­ZѪU+”J%‡bÖ¬Y̘1ƒ``` ¶Å­m¼cÇŽqáÂÚ´i#—§¤¤ä)n´*šF‡øúë¯sÝîu7nL:u¸uëÑÑÑT¨P-Z°råJ~ùåºt颵¿¿?þþþ´lÙ²À"?~€nݺ©•+ ºtéÂO?ýÄ•+W´*_9ahhH=X¿~=ÿ)ÙKA£P(pppàòåËÄÇÇ«ÅjŽåòåËT­ZU~®²{¾ {{{þúë/¨_¿¾\žššŠ¯jEêTÏf×®]³Ò‘™­[·²mÛ6 ÀÞ½{>|8¯UÕçÈ‘#éׯ_޲œ>}Zcº8ÅfW+Ð:Â<Æœ¹È¹]zï¿ÿU$#¡@ ÞeôõKR¢Duzzš«IE¹¹95jÔÀßß_^åRqäÈš5k¦µ­‰‰ ýúõcĈDEEqíÚ5 cÊÄÄ„h´Q…êRÙkª8{ölž²Ä9::R¡B~ýõ×í.%I’Wå^'44”   Ê–-++ƒ ¢qãÆìرC«I‰R©dÆŒH’”mè´¼¢ZÉöööV+OOO—#ƒd¶MLL$&&&W>!)))œ?y…2"²ÄÄÄèÜvµY³f¤¤¤h˜œ8q‚ÔÔTµgPeæQ¶ç9¡ziQ½ä¨¸|ù2±±±jeõêÕÃÜÜœmÛ¶åjµ?00É“'ÓºukvìØÁ¢E‹8tèK–üëóÔ¬Y3 s•Mò½÷ÞÃÐÐPCVmò Š¡@™çÅ$¨¤—‘Ö57Œù¯yF¦¿C2¿,¿‰ ´p"ÔŽp"ä—  IC‡%$$„äädþøãÖ®]‹½½=cÆŒÀÏÏ©S§rõêUIIIÁÏÏ­[·R²dIY±)Q¢­[·æðáÃxzzâïï/Ûˆöïßggg<<<øæ›o8sæ 6là£>¢zõê¹–ÙØØ˜eË–q÷î]úöíK@@iii(•JþþûoæÎË€ Ç&ÜÜÜ8}ú4< $$„Í›73lØ0RSS>|¸Ú çš5k(S¦ ;vdéҥܸqƒ¨¨(Ž9B»ví8uêãÇ×p†„Œø¿ª¸¸¯¯¿¤dfРA°bÅ Ö­[ÇÓ§O¹xñ"&LàÁƒ¼ÿþûj[ûsæÌÁÜÜ\M)òôôdêÔ©œ:uŠX±b:u" €‰'ª™ÏlÚ´ sss–-[¦&Kxx¸,³Ê¦÷üùórYV/$ù嫯¾ÂÐЙ3gâççGZZ/^döìÙòÕW_Éuœœ¨T©[¶laÿþýøûûk}Y+HÆŽ‹••‹/æôéÓ¤¥¥qçΦNª‘ݰT©RüïÿãÖ­[ôíÛ—›7o’––Fbb"þþþ¸»»Ë/‰‰‰ 8vìØ¾¾>3gΤW¯^Ì;WŽ£noo»»;dìØ±„……ɦ/ýõ'Näï¿ÿ råÊŒ5ŠË—/óý÷ßOLL Ÿ~ú)ÏŸ?/Ôû¤ë±7Ýÿx·1b„„óø©R›JRàíÀ<õñÕWšÙÌønm]B"¡vDè ™3ÐÆ.+~øáÉÈÈH$ 4h ݼyS®sñâEÉØØXghh() ÉÉÉI:xð ZGŽ‘ºté"•(QB#ÞóçÏ%y<[[[iëÖ­Ò°aäråÊ©õÓµkWÉÒÒ2K¹7mÚ$•+WNM@²°°Ž;&I’$¥¤¤HíÚµ“Cxe>ŒŒŒ¤ &HÉÉÉ}‡††J-[¶ÔhS²dIiåÊ•jáÌT˜ššjÔÏ|œ:u*Ûÿ‡­[·J+VÔhשS'éÁƒju§M›¦ÎmçÎò}Í|”(QBš:uª”””¤ÖÇÚµkµ†ܼys¶×qíÚµl¯C’2ÂØi é—U˜ÃHjÏ`ÅŠ¥C‡iô±yóf©U«Vr½‘#Gf+Kvaì2‡`Tall, 4H­ìÊ•+’š|nnnYf"üé§Ÿ$333g³bÅŠ’$Iÿ†Ü¿¿ZÛèèhÉÖÖVªR¥ŠôüùsI’2žão¿ýVþÌüÿlgg'ݹsGn'õéÓG$}}}I¡PHµk×–¶nÝ*2ê…$’5¿@fäÈ‘l½¼Ãú†T‹¬Æšo×Ðþýö97ÌÄÓ§P¥ (•Ыx{CåÊ À®]…$¼@ C¶lÙ¼yóŠt‹·8Ill,ÕªUÃÐÐ0ÇúQQQ\¹rEÎDèìì¬Ñ.11‘K—.&GÇPE­ÐFRR¡¡¡˜˜˜`gg§vNe>P±bE5j„B¡ÀÏÏO®óðáC”Je¶«Óqqq\¿~;wîP²dIªU«F£F4¢ÅÆÆâççÇÇINNÆÖÖ'''µP_Úxøð!×®]#22’åË—¡C‡Ô2Ç©¸{÷.iiiYöU¥J91FV¼zõ ???‚ƒƒ155¥N:²ƒXfžbýú©™¡‚7¡@o^½z¥øÀôîÝ›¹sçòÝwßéH²œyðà-[¶äåË—œ:uŠFéZ$ HÈN9V™öøúúÒºuk¡@gƒˆÂQD¸º¸æûALII!2r"ÐW.˼ q€WžC# A~™5k·nÝ¢E‹”)S†ÀÀ@<==±µµe–*g1ÅÖÖ–£G²sçN.^¼(h€ŒTÞ}ûöÕ÷Z ŽP ‹a¯Ipp0öööÙf-{¹}û¶F 8Ò§O¢¢¢8pàñññT­Zwww>ûì³ÀÙÙ™æÍ›3xð`þúë¯\·ùš'Bí'BMRSS ֵŎèèh¢££u-F±CÌ-Ús‹&bnÑŽÐYrF(Ðyä·ß~cÆŒlß¾¹sçæº0È×D8jG8jÏêÕ«u-F±Ã××___]‹Qìs‹vÄÜ¢‰˜[´#t–œy«èÔÔT”JeŽunß¾­ñ¶•ÀéÓ§9}ú´Ú[{¹rå°°°àÕ«W¹~›Ï»ð@ :Bè.ÙóÖ)ÐñññŒ5ŠúõëcbbB‰%²¬»|ùrÌĮ̀]»6¦¦¦Ìž=I’ˆ‹‹ÃÓÓOOO.^¼(·éÖ­sçÎeùòåÌ›7¯°/G @PÌxëh¥RÉÇéÞ½;...YÖûã?˜>}:K–,!>>ž;v°råJ–.] €¥¥%ëÖ­cݺuŒ1‚ôôtµÕìÀÀ@œœœ ýz²âöíÛnÏ–×>SSS9pà@öù_9räHŽ»…Ýgpp07nÜ(Ð>ÿ+^^ÙGp)Š>?žã¶zaÈ™J¥’#GŽè¼Ïœ®¹0äÌ1·hGÌ-Ús‹&bny7xëh Ž;ÆâÅ‹iÒ¤I–õV­ZEóæÍ™0a¥J•ÂÕÕ•>ø€ü‘ôôtú¯^½ÂÑÑ‘Ž;âììÌ–-[øüóÏs-WJJJ¾®'+Þ†¹˜˜˜wôy~ä^¾|™'ùrÛþ#—ššJLLLžÆË‰·áG.66–ØØØ<—bnÑŽ˜[´#æMÞ†¹¥ u–·…¤²Yx ñðð`Μ9¼~‰)))3zôh6lØ QÿîÝ»T¯^]£¿ÔÔTBCC©X±"e˖͵:tàüùó4nÜCCCµs–––ËßÃÃÃsUvõêULMM©V­ZžÛfU–’’‚¡¡¡@=§¶iii\ºt‰Ê•+gYOåˆP¥Jþ¼½½©U«vvvù–ùõ²'NдiSÊ”)S`÷%""‚Fadd”«¶$''£¯¯Ÿe½ëׯӰaCŒŒŒ4úÛ³g7þO2¿^öçŸÒ®]»ë/<<œÐÐPÚ¶m›ë¶W®\¡N:<}ú4Ëz—/_–ÃÖenÏ©S§prr*°û’œœÌÙ³giݺuÝ—û÷ïóøñcZ´h‘붪¤YÕ377'00¦M›j´½zõ*±pPP÷%<<œèèhêÖ­[`÷%¯ó•—.]¢E‹ù𝼽½iß¾=ÏŸ?/°ûréÒ%,,,¨\¹²Îæ«èèh’““qppÈ×|µgÏúõë÷Ÿÿö3—ùúúR¥JÎW7nÜ N:”(Q"ÏóUJJ §N¢wïÞv_’““ñ÷÷ÇÊÊJ§óUVó©ŠÌó•ÊÞ9::SSS®]»FÓ¦M9yò$í¼“ txx8vvvüðÃLŸ>].?xð ½zõÂÇÇGíí¿²aÃ~ûí7r‘éG ®Ñæ08dÈÆŽ«iÞ ÞÉD*Ïž=ÀÎÎN­ÜÞÞ^í|A1vìXñ @ ¼%¼u6й¡B… @ÆöEfîÝ»§v^ @ xwR¶²²BOO[·n©•P©R%]ˆ%@ xx'h:vì(+Ì*®_¿NÍš5©ZµªŽ$@ wÞJúèÑ£xyyÉ+Ì^^^xyy*×™6mW®\ÁÃÃÈÈH6oÞÌž={˜6mZ‘ÉNëÖ­±µµ¥OŸ>JçM¥K—.XXXàèè¨kQŠ ¿ýöõêÕÃÎÎŽž={râÄ ]‹T,ððð zõêØÛÛÓ§O.]º¤k‘гfÍB¡P¥kQtŽR©D__{{{ùx=í»ÊÕ«WiÙ²%•*UÂÑÑQü=zôŸôõõ Ü?êMÄÇLJnݺѠAúõëW¤±Ø‹Ò[HãÆ%KKKcëÖ­jõ6mÚ$Y[[K€T¾|yiÁ‚E*çG}$-\¸PJLL”Æ'}ùå—E:~qåÒ¥K’¿¿¿T«V-]‹Rl8uê”%I’$8qBªZµª”ššªc©tODD„|~ýõW©E‹:–¨øpá©ÿþ’©©©ôèÑ#]‹£s%KKK]‹Qìxúô©dmm-ýþûïRjjªôâÅ )99Y×b+¶mÛ&uëÖM×b œœœ¤£GJ’$I«V­’¨c‰tÇ[…ãòå˹ª7jÔ(FÅóçÏ)W®\!K¥‰¯¯/«V­ÂÄĄѣGóÅ_¹ Å‘&Mš¼ÛoµZh×®Úç'OžðèÑ#lllt'T1 ³¿‚ƒƒ¯^½Ò¡4Ҥ¤$¦OŸÎüA:ut-N±!99™íÛ·Ó¨Q#j×®­kqŠ7n¤iÓ¦4nܘ°°0a¨… 60eÊ]‹Q,HHHç]kkku,‘îx+è¼¢ å9""‚””ÌĮ̀[·.—/_&===½·Ò²FP@¬[·ŽV­Z½óʳŠÍ›7³qãF>|ÈáÇu-N±`þüùŒ9R8DgBOOŽ;rçÎ~þùg91…Á»ý3xûöm‚ƒƒ0`¥K—ÆÐÐȉXÞuîÞ½K`` }úôѵ(ÅOOO\]]±µµ%66–?þøC×"éŒw{æø„††„™™™œ!ìu^½zÅÙ³g £iÓ¦8;;ËçôõõIKK“¿«g…BQè²=ÂÏψˆ7nLãÆµÖ»|ù2Ç'))‰÷ߟöíÛ±¤EKBBׯ_çåË—4mÚ4˶۷osîÜ9,,,hÛ¶-æææu<ÈÚµkß ègÏžñ÷ß“””D·nÝ´ÖIOOçâÅ‹øûûS»vmZ¶l©ñÃÞ·o_Þ{ï=¶mÛÆ¤I“8uêTQˆ_(ÄÆÆrõêU‚‚‚°²²ÊòG;,, ooo>|ˆ³³3ýúõ“³œúûûsñâE,XP”¢*1·ñûï¿Ëß»té‚··7®®®….aQs‹™™%J”wn{÷îÍŸþIïÞ½‹ä šäädä¹e„ ZëÅÇdzgÏnܸA5èÛ·¯Ö¶›6mbèСoÅ ÅÕ[¾þúk\]]éÙ³'ëׯgÕªU,Y²¤(Ä/~èÚ†äMãèÑ£’………H€ôþûïk­.9::JæææR£F$}}}é“O>‘ÒÓÓå:U«V•¢££%I’$___©K—.Eq …ÂôéÓ%@R( }ýõ×ZëmÚ´I200ºví*õíÛW244”¾ùæzo… tÏž=%}}}ùy9uê”Öz‹/–ôôô¤ H–––’µµµtýúuµ:‡–êÔ©#=xð $/<ž={&ÙÛÛË÷$«iH©TJýúõ“ŒŒŒ¤&MšH¥J•’Zµj%½xñBkýÔÔT©L™2ÒãÇ SüBcïÞ½òßžž^–sË•+W$ ÉÉÉI:t¨T®\9©{÷îRbb¢$I’äéé)ÕªUK>ôôô¤êÕ«KEx5GAÏ-*&Ož,-^¼¸¤.| jnÙ³g4zôhùûܹs³½oÅ™'OžHFFFòßPVs˳gϤÆÿßÞÝEQÿqŸ ˆ€x â)‰ǃ a(6ü,RIsÒ-EÃ'F+' +ÅÑlšÌ§t4Ò |lòaRPC I"=ÀäéA¸A@îóûùÎ;ÈK¸Sïóšqd¿ûÝÝÏ®ûýî‡u÷»/‘‡‡Íž=›¼½½ÉËË‹ŠŠŠtê=|øÜÜÜ(//ÏÑwŸ®Ê[”J%õë×ÚÚÚˆˆ¨¢¢‚ììì„iKà ´‘.^¼H6l 3gÎPxxx‡'bdd$ 6ŒT*?~œD"8p@¨3gÎZ¶lÕÖÖÒ¬Y³:¼0<Μ9C¤V«ÉÆÆÆà¾¨T*²³³£¥K— eß}÷‰D"*,,Ê.^¼H?ÿü3I¥RÊÊÊ¢ââbSìB·Ø°FÕIDATaíÝ»—RSS;¼ÈåääÚ¾};µ´´Ðÿþ÷?ò÷÷êœ8q‚<==éÂ… ¤T*I©T>·/ú¨T*Z½z5;vŒ>úè£/r_ý5õéÓ‡ •––’««+ÅÇÇ uÎ;GÍÍÍÔÐÐ@[·n¥°°0“ìCw((( #GŽR©¤ˆˆˆû–±cÇRXXµ¶¶Qnn.õêÕ‹¾ÿþ{ƒõŸ÷—»ªoÉÏϧŒŒ *++£ÔÔT’H$TQQaªÝèr]շܽ{—<==©¸¸˜îܹCþþþ”ŸŸoªÝèR ”ššJ………´fÍšû–ÄÄDrrr¢²²2""R«ÕäííMï¼óŽN½Ã‡SHHH·ÇÝݺ2o‘Éd”žžNmmm´eËš8q¢)vá™Ä ôSèè"WZZJ=zôÐÕÃÇÇGç.sEEMžzÊó(++«Ã‹Üûï¿O666:£j¤¤¤:þ<=êð_~ùe?ÏëE®½µk×vx‘“Éd4~üx²÷Þ{…»­qqq4dÈò÷÷§E‹=÷wŠ´:ê[ €ÞS3fŒÁu½ûî»TWW×ašÜÓô-yyy4cÆ 3f }øá‡”••eª°»ÕÓö-Ú²àà`zõÕWi÷îݦ»ÛuÖ·¸»»Ó¤I“tÊâããÉÆÆ†ÔjµP¶uëVúõ×_»5NSû·¼%))I§üñ¼%##ƒ¢££iøðáûÂô¹ÿ?Ý ._¾ F£÷ö{@@Î;'L»¹¹áèÑ£¦Ïl®]»‘H¤s\$ ÜÜÜpíÚ5¡lÓ¦MæÏl.]º„¡C‡¢gÏžBY@@€0oôèÑHJJBRR’¹B4¹ÆÆFê=€;vàÖ­[ËåØºu«™"4íè4~~~:å8~ü¸ÁevîÜÙíq™›¶oi?²Æã}‹¿¿?öíÛg®ÍâIú˜5kfÍše–MM­V£¢¢o¿ý¶Ny@@š››QTT„ÀÀ@èðùé‘6o1Ô·´Ï[ÂÃÃnêðžI<ÜC7(//`ø"W]]––s„evååå8p ìììtÊ„cf‰ÊËËõÎ___XYYYìqé¬ µŸoi:;.µµµ;¤Tyy9¤R)ìííuʹoá¾åqÚý6tƒ«ý|KÃy‹ñ8î Q§åää"²Ø‹\[[›Á7™­­­uF$±4z犕•ìíí…sÉÒtÖ†ÚÏ·4Úv¢qCKÛ®,µµµµé€ûî[ôiχǯE–Þ†8o1'ÐÝÀÍÍ ô>råÊØÚÚÂÑÑÑa™»»;nÞ¼‰è”çååYôxµnnnzçŠR©Dmm­p.YšÎÚPûù–FÛN´ÇA+//zw`-÷-†qߢ¯³6Ô~¾¥á¼Åxœ@w©T ÀpÕγDÞÞÞÐh4: T­VãöíÛðöö6cdæ%•J;ìÌ-õ|qqqAŸ>}:<.4GXf§m'W¯^Õ)ÏË˳è6¤í[ „2î[¸o1ÄÉÉ NNNKÏž=1hÐ óffœ·èn‚Áƒ#++K(S«ÕÈËËCTT”#3¯èèhXYY!--M(Ó¾ø4gÎs…evQQQ¨ªªÂ­[·„²?þøööö˜4i’#3‘H„3f ''­­­Byff&ÂÃÃÑ¿3Fg>>|¸N*))Á•+W,º iû–'NeÜ·pßÒ‘Ù³g#;;*• ÐÚÚŠS§N!22Ò,_&~pÞò˜{çMMM %$$PBBI¥Ròðð¦ÛÀ!99™D"-Y²„öìÙCaaaäááñ\;Ú™´´4Šˆˆ ˆˆêÑ£ 2„"""hæÌ™:õ’’’¨W¯^´`ÁZ¼x1988Pll¬™¢î~)))”@QQQ€Þzë-JHHÐ𝱱‘üüüÈÏÏvíÚE«V­"+++Zµj•#ï^+W®¤„„ #BÊÌÌê\¾|™)22’RRR(&&†¬­­)==ÝŒ‘wŸÊÊJ¡ 9;;“X,¦Û<'--lmmiòäÉôé§Ÿ’——1B»õEÃ}‹aÜ·6gΊˆˆ ™LF„sçØ±cB’’œV¬XAcÇŽ%‰D"Œ9ÿ¢á¼¥{ð0vFjkkCee% 44„é‡ õbbb`gg‡äädœ:u ÁÁÁHIIyaŸ;srrBPP€ƒƒƒN½ÄÄDÈår=zõõõøöÛo1oÞ<“ÆjJ*•J8?´¿ÅWVV¢®®N¨ckk‹sçÎ!11›7o†D"Á¦M›°`Á³Äl wïÞÅÝ»w1`ÀDEE Ǩý‹M8sæ Ö­[‡/¿ü>>>8~üø ûéwkkkƒmlll„Ÿ'L˜€³gÏâÇÄ•+W0þ||ðÁèÛ·¯Iã5î[ ã¾Å0¹\777½6$‘H„Ÿ===‘™™‰mÛ¶A¡P ,, ;wî„L&3u¸&ÁyK÷™;ÆcŒ1Æžü 4cŒ1ÆcFàš1ÆcŒ1#pÍcŒ1Ƙ8fŒ1ÆcÌœ@3ÆcŒ1fN cŒ1Æ3'ÐŒ1fb …………æ£S•••8sæ ª«« ÎÏÎÎFqqq§ë¸wï222tÆšeŒ±'ÐŒ1‹ŠÐÐP\»vM§üöíÛ ÅŸþi²X/^Œo¾ùÆdÛ3†J¥ÂèÑ£1hÐ ÄÄÄàâÅ‹ëÍ™3ÉÉÉ®+;;ãÆCCCƒ°îäädܽ{·«ÃfŒ1“â/2Æ,Â… <úbÝÁƒ…ò¦¦&\¸pAç n–ìØ±c(**BUUÕSÝpèСX»v-lmmJ¥óæÍCVV\\\º"\Æ3 ¾Í³¯½ö:„¿þú«Óz*• ---:eÍÍÍP«ÕÂtKK‹t?|ø—/_Fss³Î2·nÝÂ?ÿüÓ鶪««‘ŸŸFÓaòòr( ½õ@]]Z[[eee¸zõj§Û#"!77WoÕj5ÒÒÒ —Ë¡Ñh R©:]—VUUrssõâóôôD\\¬­­¡ÑhP__hhh@]]Þ/-ÈÎÎÆ7:=Œ1fnœ@3Æ,Fdd$°lÙ²N빺º"55U§lëÖ­ÉdÂtjj*Äb1> ±XŒàà`8;;cß¾}(--…\.‡|||­·––LŸ> @`` ¤R)²³³uêäääÀÏÏ À¨Q£ ‹±qãF:b±[¶lAPPˆØØØ÷K¡PÀ××^^^x饗 ‹±sçNa¾L&Ã?ü€ßÿb±R©´ÓãôàÁL›6 îîî ‚··7òóó…ù'Ož„X,†J¥BII F˜0aÄb1Äb1 ¶¶ãƃX,Fdd$är9œ;Ý6cŒ™'ÐŒ1‹²víZœ:u §Nê’õ}õÕW8zô(*++1uêTÄÅÅaÆŒXºt)êêê°cÇìÝ»EEE:ËíÙ³ŽŽŽ¸yó& † ‚©S§¢©© À£;Ó&L@`` òóóQSSƒ/¿ü 8}ú´ÎºV¬Xèèhܹs¿ýö›Á8›ššðÆoÀÙÙ™™™¸}û6æÎ‹ùóçãܹs½8ƒˆˆÑ¿>Ö²~ýz¡´´999°³³Ã®]» Ö4hðüyVVˆDضm***PRR‚ªª*455éýÃcÏN cå•W^Áĉÿõ.ô“Z²d ÂÃÃáâ₸¸8ÔÖÖB.—cîܹpppÀüùóáììŒôôtåzöì‰Í›7ÃÃÃX³f *++ñË/¿¶lÙ‚`ãÆðó󃃃-Z„ìØ±Cg]¡¡¡øä“Oàââ‚~ýúŒóÀ(++úuë0f̸»»cóæÍpqqÁúõëÿÓ¾2Ÿþ9ÜÜÜ0räH̘1'Nœ0z=ùùùJ¥H$€^½záõ×_ÿO11Ƙ)ðK„Œ1‹³fÍŒ1‡‚\.ªuEDD?ûùù&Ož¬SG.—C¡Pè”3Fx¹F…¾}û wisssaooøøxh44 îÝ»‡¶¶6uMœ8ñ_ã,((€ƒƒBCC…2‘H„ñãÇãï¿ÿ~½Õõø~cõêÕhiiµµõ¯gáÂ…˜2e ÜÜÜðæ›o"66V'NÆ{Öpͳ8AAAˆŠŠBbb"öï߯7_$é•=þ–ÞríË´å¿×»woéž={ÂÊÊJ3ùÁƒpvvÖKާL™GGG2wwwƒ±µ÷ðáCX[[£GÝÿxìÝ»·ð¢±ßOíº}pìØ±(--ÅîÝ»±ÿ~„……aìØ±ÈÈÈ0øoÁcæÆ 4cÌ"%%%aذaؽ{·Þ’’’ÐØØˆ;wî`íÚµpttÄ´iÓ‰«V­ÂíÛ·ÑÔÔ„uëÖ¡¬¬ qqq]¶_ñôô„——Ž?®“D9r÷ïߦµÃçi_¨dŒ±g 'ÐŒ1‹õÙgŸ,Ÿ9s&ª««1xð`ôïß_|ñ/^Ü¥ÛŽŽŽFZZ ///œ8q{öì^”J¥8xð NŸ> ™L___¸»»cèСÂGaŒáè舟~ú çÏŸ‡<==±|ùr$&&"22²K÷­3K—.ÅÁƒagg'<ƲråJ¸¸¸ 002™ S¦LALLŒIãbŒ1cˆH;ŽcŒ½À®_¿WWWaìa­’’466ÂÓÓSç™ÞêêjdeeA"‘ $$ ¨©©··7€GwI•J%|}}…e4 õÖURRáQ í´³³3 ”J%F-ŒBÑ^cc#òòòP\\ '''é|Åïúõëððð€ƒƒÃ•J…‹/B­V#88Ô™¯T*ÑÖÖ†tºž¢¢"888èŒ×ÜÐЀ²²2 :"‘÷ïßGii)d2™Þ³×åå娯¯‡¯¯/4  ŠŠŠ ‘H0zôhØØØ<Ñþ0Ƙ9pÍcŒ1ƘøÆcŒ1ÆŒÀ 4cŒ1ÆcFàš1ÆcŒ1#pÍcŒ1Ƙ8fŒ1ÆcÌœ@3ÆcŒ1fN cŒ1Æ3'ÐŒ1ÆcŒhÆcŒ1ÆŒÀ 4cŒ1ÆcFàš1ÆcŒ1#pÍcŒ1Ƙþ‡'ˆv#%IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/Q8-1g-idx-optlevels.svg000066400000000000000000004360471416254111300250640ustar00rootroot00000000000000 PyTables-3.7.0/doc/source/usersguide/images/Q8-1g-idx-sorted.png000066400000000000000000003107121416254111300243220ustar00rootroot00000000000000‰PNG  IHDRÐß}™SsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìwXTG÷Ç¿»tX`)"( V{ÅJ,D%êõõ÷Úb4±Æ×K±€5ÔØ"REŒ ŠJ±—ÒaaÏïž½/—»»,Š¢É|žgæž9sfæÞñܹ3gDDD`0 ƒÁ`0!nh ƒÁ`0Œ÷ æ@3 ƒÁ`0u€9Ð ƒÁ`0 F`4ƒÁ`0 ƒQ˜Í`0 ƒÁ`Ôæ@3 ƒÁ`0u€9Ð ƒÁ`0 F`4ƒÁ`0 ƒQ˜Í`0 ƒÁ`Ôæ@34æÖ­[ؽ{7.\ˆ­[·âÊ•+¨¬¬lh³ÞG…ƒƒbccÚ•œ>}S¦L››\]]Úœ÷Š©S§²6û›Òºuk|úé§ËWTTàúõë8|ø0Ž;öÊåÊår\¹r»wïÆW_}…•+Wbß¾}ÈÊÊze Ihh(Zµj…ââb.íM>7C† A÷îÝ߈îúfÛ¶mpppÀ­[·¸´U«VÁÇÇçõÿä? í†6€ñî“““ƒI“&áÀ333äææ|||°k×.8::6¤‰õƾ}û°hÑ"ìÛ·žžž¼kEEE¸wïJJJÈ:õA\\ÀÀÀ2™ ‹Å Äwß}§±] MEE¾ùæôéÓ‡7¶¼ÉçæñãǯÔö A~~>îÝ»‡òòr.mذa˜7oöíۇѣG7 uŒ7›f¨%??^^^8xð æÎ‹û÷ï#''/^¼Àúõëqùòe´iÓ mj½››‹´´4¥Nrÿþý‘’’__ß°¬vŽ9mmm?~_}õfÏžÝÐ&1ï † ^ybàêÕ«ðôôÄ•+W°fÍÜ»wEEE(**µk×°téRDFFâÒ¥Kõlý›cÏž=ÈÊÊÂôéÓyékÖ¬Áõë×Ȫw'''ôïßË—/5´9Œz†9Ð µ,]ºÙÙÙX½z5V¬X[[[€……¦NŠððpäççcΜ9¼|(((諬¬D^^ÊÊÊ”–WXXˆK—.áæÍ›Éd*ó+ÞòKJJpñâEdgg#??………JõòòòxŸkRRRÂ9Î………ÈËËC^^JKKúúú°¶¶†žž—§¼¼yyyÜ'ºœœüùçŸÛår9‘™™©²|…wîÜÁùóç‘““£VVAEEòòò™™ ®j¾TVV"%%—.]BQQ‘R]EEEÈÏÏçþ¾wïΞ=«ñàÿâÅ œ?W¯^UÙ•••HMMEBB‚J™¢¢"¼|ù’ûûÎ;HIIQ*¯´­är9ï^+..ÆÅ‹ñäÉêR“û÷ï#..?VjG^^är¹àZqq1òòòPQQ¡Q9eeeøóÏ?qÿþ}ÿ»w«÷§¢Ï•=#ÅÅż¶«Ivv6âââT¶CͶ¿ÿ>bccQZZª²LÜuem ¬ŽÉÉɈ‰‰ÁíÛ·U¶MõgšˆpãÆ $%%©?€ª¶ILL|å—úÄÄD„……á‹/¾€‰‰IóËårLš4 ¥¥¥ˆ‰‰ÁÌ™3ѬY3ˆD"èêê¢uëÖ˜7oRSSѦM._ͱ±´´ ‚å¹¹¹¸pá233•>—Êž}Å8¥Ë”••iÔgr¹Ë—/GŸ>}Ë5¤R)7nÌK«9ŽÜ¹sþù§Êq¨êß””$''ktÉårܾ}.\ྈV'??_åìµb|¯Ù~2™ 7oÞT;6)xöì.^¼¨¶N0mÚ4ܼyáááµÔˆñÞA † ^¼xAºººdkkKeee*å|}} ]»vKóôô$OOOlbb" ï¿ÿž—ž““C#GŽ$‘HD™˜˜Phh(O.>>žÐ?þH“&M"@sæÌ¡þýû“‘‘åçç Ê=~ü8 uëÖ©¬Ç¤I“¸²«ÿæÌ™CDD{÷î%tâÄ .ÏÆ EFFRß¾}9ûMMM),,Œˆˆ6lØ@fffœ¾~ýúÑË—/åïÞ½›ÌÍÍye2„^¼x¡Òf"¢'N(µ{úôéœÌ–-[H"‘p×Äb1}úé§TTTÄÓÕ»wojܸ1%%%‘½½='_XX¨Ö†«W¯’——¯|}}}Ú°aOnÇŽdbbÂɈD" ¡‚‚žÜ AƒÈÔÔ”nÞ¼IÍ›7çä(--ÊËËiôèѤ««ËÕgùòå<)))€–/_NÓ¦MãîEäååñäGŒAzzz‚ºýñÇdggÇ«›effr2û÷ï'4mÚ4^Þììl277§V­ZQqq±Ú6$"Ú¶méëës帻»sÏLõþTôùÞ½{:TÕãØ±cÔ¤I^=üüü(;;›'W½íœœ8Ù„„‰D‚:*èÕ«™››SII‰Ú:Ι3‡w/ FÑÎ;²öööäççG§N" N¾I“&tñâE¥u¬þ¬ÙÚÚÒ•+WÈÔÔ” ¤Ö.exzz’©©iòüöÛo€ÆŽ[§|Š~^³f M›6»·ýþèÑ#êÕ«¯Ýš6mJüñ§C.—“••õë×§{æÌ™€FÍKÿä“OHOO¯Ö{3..ŽÐ–-[×”ÝoŠq$99™yczxx¸@Ç•+WxϘT*¥Ã‡S‡ÈÕÕU JR©”×#FŒ œœNæë¯¿&ôÓO?ñòÆÇÇ“ŽŽõïߟär9UTTÐÒ¥KIOO76MŸ>JKKyù hذaœœ¶¶6Mš4‰¾ûî;@‰‰‰{MLLhÀ€jZ˜ñ>Âh†Jbcc Mš4I­Ü÷ßOhÛ¶m\Z]h™LFdbbB“'O¦³gÏÒ¾}û8ÇüàÁƒœ¬Â¶µµ%___:vì%&&Rbb";vŒÐÆå2„ xlMîÞ½Kÿùϸÿ(âãã)>>žs0Ô9ÐvvvÔ­[7 §íÛ·“³³3Ѳe˨Y³f´|ùrŠŽŽ¦¡C‡Z¼x1¯ìM›6qÍ–-[èòåË4mÚ4222"___µíŸ——GñññäååEÖÖÖœÝYYYDTå” nݺѹsç(99™fÏžM(00§«wïÞdhhH666´råJºté>}Zí Trr2éèè íܹ“ÒÒÒ(11‘~úé'^_ìÙ³‡¯¯/ÅÆÆÒ7hþüù€zõêÅÓ9hÐ ÒÓÓ£f͚ф (66–-ZD‰„:tè@£G&Ú³gíß¿ŸZ·n-øÏKá@[[[SÛ¶méÂ… tçÎZ±b‰ÅbêÓ§¯LeŽÀ©S§H$‘——­[·ŽiáÂ…deeEÎÎμvùì³Ï>|˜ˆªîk244¤›7oªíC"¢#GŽêܹ3%$$PFF}óÍ7dccóÚôáÇ µk׎~øáJLL¤ùóç“……¹»»“L&´}Ó¦MiéÒ¥”@gΜ¡¢¢"êÞ½;™™™ œä;wîH$¢3fÔZÏiÓ¦ÑO?ýD—/_¦´´4úù矩}ûö$‰èòåË|˜²³³iÿþýdeeEæææzíÚµÜx¶}ûvºtéM™2… ©gÏžœ\ee%ùûû“¾¾>7¹“““CöööÔ´iSž “'O&‘HDǧƒÒÙ³gi̘1¤­­MS§Nå•_} ÏÈÈ „„êÚµ+Y[[«t ½½½ÉÁÁ¡Övf¼_0š¡’-[¶Z±b…Z¹ððp@ÿùϸ´º8ÐëÖ­#ôí·ßòdKKKÉÔÔ”\\\¸4…­¯¯/˜5©¬¬$GGGòòòâ¥?~ü˜´µµ5šR8ÄÕÿCR În×®OvÇŽÜ Eu穬¬ŒôõõÉÃÃKËÉÉ!sss²³³ãý§GD4wî\@ǯÕv???²··ç¥•••Q“&MÈÐÐP0“Ò§O@çÎãÒz÷îMhîܹµ–W½\JOOW)SQQAööö¤««+˜ÍpàÌÏ›7Op¯×…””‰DôÉ'ŸðÒ_Aª¿DMœ8‘Ð¥K—vWÿFô¿Ù×·å@ºzõªàZ^^åæær¿êÏ‚blÔÑÑ|¡Ú¼y³àþ&"JJJâœÉš²çÏŸ'"¢§OŸ’H$¢Ï?ÿœ×Géé逖.]Zk¦M›FèÁƒ‚kªh´lÙ2^úêÕ« íØ±ƒKSLZìÛ·'»aÃÀs Ÿ={F&&&äääD•••<ù3fŠŠŠâÒ>|H–––äææF………4xð`ÒÒÒ¢³gÏr27nÜ ---êÛ·¯ n¤­­Í7nÜ ‘HDýû÷çÉåää–––JzÔ¨Q$‰jý’Çx¿`k *Q¬+«m âúóçÏ_©œcÇŽÁÐÐ'Nä¥ëééaذaHOO¬§öóóƒ/M,cÒ¤IHLLÄŸþÉ¥oß¾u cUW‚‚‚xwéÒàîîwww.]WWí۷ǃ¸´‹/"''S¦L¶6?0Žbçö•+W^É®ôôt}TÚ1tèPÞߊv­™Þ©S'hii) Ö²eKØÙÙñÒzôèjCÞ½{·nÝÂØ±caffÆ»6dÈèëëóúE__û÷ïGYYzôèo¿ýÁÁÁ?~¼Ê2”””pr­¬¬”Úúª¤¦¦"##ãLJ±±1ïZPPttt”Þ_ÊîAƒ¡I“&øå—_¸4™L†mÛ¶¡K—.¼{]EEE8sæ víÚ…M›6!&&Mš4Á;w²‰}ûöå¥/úùsç`eežìë¶_]QŒ›¦¦¦‚k7†™™÷ ÈøøøÆ\Å}Ú«W/^º§§'¬­­ÇíÃèÖ­àÌ™3€¨¨(ˆD",X°úúú\ºâ_MÂÄݼyhÒ¤I­²Õ©9.*ë·ØØXˆD"În5ë qqqÈÏÏÇÔ©S!óÝece“&M†´´4´k×X¼x1o#ø‰'PYY)Ø }ô***píÚ5U÷ l333Ç~¨²\\\¸5ÞŒ¿Ìf¨ÄÉÉ @Õ¦#u(®+6Ö•Û·o£¸¸‰D¼ß¶mÛ”ÚPs°U0aÂèééá矶lÙ‚>ø;w~%û4¡¦n¥éŠkÕ73Þ¾}0wî\Aý[·n ¯&J¡[™ѳgO8‰:uÒHÿÝ»w!—Ëk«ÎEZM;LLL™¢]kÚ'‘H ‘H”nüQV¦â?ò»w華955°iÓ&A¿˜šš¢´´TÐ/®®®X¼x1ÒÒÒ`mmM›6©Ô_ņ0u¶¾*Šz¬]»VP Èd2A=ôôôеkW.mmmLœ8gÏžåô>|OŸ>ŤI“4²gïÞ½ppp@=0vìX¬\¹k×®ENNž={&÷òò¼,+9E"''þþþ‰D%K–ð®>}^ë ‡fÍšAOOIIIÛQ“ú°C‘‘‘‚4Å,œº2ýRPP q¿L™2©©©øí·ßðå—_b̘1¸ví7s®ŠfÍšA[[[­­ÕQ8¼ÉÉÉ9r$—^VV†˜˜¥õ(**ª—ûËÖÖDXX&L˜€ÈÈHLŸ>]íˈ‚sçΡ¼¼ .äÍ|¦¤¤ ==ý•O´ÓÕÕå–2Ôs¢££_I竈yóæaÍš5øì³Ï/†¯B‹-‹èèh <˜K—Édˆ‰‰D"á½dwëÖ Û·oGXXîÝ»Ç9òÝ»wÇüùó±uëVäääh|Êß| ÊÔô딦4oÞ±±±¸{÷.ïy¼rå òòòx!ò÷ree¥F÷²L&èQ£ “ÉpòäIŒ5 #FŒ@BB÷¡Щ§§W«N…}‘‘‘ðññá]Söœ*HOO‡……… Üãý†-á`¨D,cݺu "Œ3F°ŒâåË—F~~>-ZÄ›qvvFQQ¸´ÒÒRìß¿_PθqãPQQ (µ£®'QùøøÀÓÓk×®EDD‚‚‚kXU¡XrqóæÍ:•ù:tìØ...X³f ž>}*¸.“ÉÔÆ¯VGóæÍáííË—/sq…ªX¹G…ŽŽ_Ùv±XŒàà`DEE)}9RÌ(*>_¿~·Îµ²²GŽ––Ï1¨O?~Ì;°‚ˆpìØ1èèè¨-ÓÞÞþþþ Uú†ˆx±nwìØÐÐPÌž=ÇǾ}ûPPP€àààZãÚjkk#00™™™HNNæÒår9Ž?.oÞ¼9Äb±à…$""‚gPõ%©S§Nؼy³Ò5˜r¹\iÌvu|þùçxñâ‚‚‚@Dø×¿þ¥Q>ÅlßÇyé{öì©SùÊ>|8ŠŠŠŽÌÑ£G_[w]pttÄüùó‘¥ã—²™vu 6 pèÐ!^úéÓ§QXXÈ]W X²dÉèêêr{¼½½ajjʽLkê@+œLÅLj}òÑGöÓáDz¾¾¾pttĪU«ð×_ ®———ó¾À,X°/^Ä?þˆÞ½{cûöíHNNÆŒ38™¡C‡B"‘૯¾R_¼  €û‚Ñ·o_à÷ßç}ÕHKKã–—(#==]ãýŒ÷ˆ†Ù»ÈxŸX¿~=ééé‘¥¥%M˜0Ö­[GS§N%@_|ñ… ÏÕ«W ¨Š‡£FQttt­v+‹ÂATŽPWW—š5kF«W¯¦_~ù…”FÛPÄo­ OŸ>%[[[ÒÖÖ¦)S¦PXXmܸ‘ÆŽËÓOúúúÔ´iSZµjmÙ²…zöì©4âƒ"qM_”Å’­ëW…£cÇŽÔ¨Q#Z¾|9mݺ•úõëGhöìÙ¼üÊú%--,,,ÈÀÀ€fΜIaaa´iÓ&š9s&ÙÛÛÓöíÛ¹²ŒŒŒ¨}ûöT^^Îåÿé§Ÿ”†-TƵk×ÈÈȈ¬­­éÛo¿¥-[¶P¯^½¨sç΂(DDü1 oooZ½z5uëÖÌÍÍ©M›6‚zܸqƒÌÌÌÈÈȈfÏžM;vì 7ÒŒ3ÈÎÎŽÍCUÛWG.—s1¢»téRkÝ<|øLMMÉÜÜœ¦OŸN¡¡¡4~üx²³³#gggAÈ2EèšDGG®ý‰ªâ$[YY‘±±1}ýõ×J#FŒ 6mÚ¡¡¡ÆQ8¾þúk4h 4ˆLLLH[[›û{Ê”)é(++£I“&‘H$"kkkúøãiÙ²e´jÕ*úâ‹/ÈÖÖ–Äb1}õÕW\U1òôíÛ—‹åJ‹/&‰DBfff\ÈÊ긹¹q¡1«£ˆVR3¤:òòòÈÔÔT%…H}èš<~ü˜€ÿÅÖ'ªj+wwwÒÖÖ¦Y³fÑŽ;hòäÉdkkK‚{"22’ ¨Q£FôÕW_ÑîÝ»iýúõôé§Ÿ’……7Ÿ8q‚D"‘ ú’"¢È¯¿þÊ¥mÞ¼™Äb19;;ÓŠ+hÏž=ôÝwßQpp0Qnn.'»páB@={ö¤-[¶Ð·ß~KMš4¡N:)‘ššªQ4+Æûs ‘˜˜HAAAäììÌ;ìä—_~Q™'<<œÜÜÜH,“‹‹ }ýõ×”ššJžžž´k×.üÎ;©]»v$‘H¸ÿxHû÷ïçd®_¿NžžžôÛo¿©µ7??Ÿ‹ [W222hݺuÔ¿òôôä_9yò$yzzrᡈªMðôô¤ëׯót”••‘§§§ÒQsæÌ¡öíÛ Ò333iĈÜFFFÔ¦MZ°`=þ¼V»'L˜ 4QÕAþþþdjjJ:::äééÉ ]¦`òäÉÔ½{÷Z˪Inn.}úé§äèèH"‘ˆôôô¨cÇŽ¼— ¢ªþëÞ½;I¥RÒÑÑ!Ú´i“@ߌ3”:fQQQäééÉ U¥ K—.¡æÍ›“X,&}}}rww§ÿûß¼0ªÚ¾&ŠxÇÊ@Qǹs績hÔ¨RVVM˜0Ì“íÛ·/M˜0A ãÏ?ÿ$OOO.æ¶‚ììlêß¿?I$²´´¤áÇÓóçÏ÷†:&OžÌ…á¬ù«i_mDGGÓ°aÃÈÕÕ•´´´ÈÐÐZ·nM!!!‚êÆF¢ªp„_}õ¹¸¸X,&+++:t¨ D£‚o¾ù†<==éÇä¥ïرƒ<==/µ±`ÁÒ×׌EÊî7UãÈ‹/xcª‚ÜÜ\=z4™››“±±1õêÕ‹RSSi̘1JÛ<##ƒ† F¶¶¶€$ yyyÑÂ… )''‡ŠŠŠ¨gÏžÔ£GA踲²20`uíÚ•ç'$$P¯^½ÈÊÊŠ;Ì¥sçδjÕ*AxÑ 6‹‹ iii‘——mß¾víÚEžžž”ššÊ“ýâ‹/ÈÌÌLé_Œ÷æ@3êLii)?~œÔñ§ IDATtuuÉÍÍ­Ö“òj>µ!—ËÕÜ¡ ŠC)jž„÷¾P3fs}R×þ¨+šÚþ¦í¨î@×W™eeeÜéeo’êvæææ*u UÉkÂë>_DD;w& ‹ZOTÅ›ìÿꬼ+Tÿ2ñ.éÒ”/^D"œøYŸÈåò:÷Ý›+ëk {ùò%I$¾@1Þ?ØhFÑÓÓCß¾}±sçN¤§§£_¿~(**R)_3¶qmˆD"•4¡¤¤sçÎ…³³óýü&©Gª¨kÔMmÓv¼‰2ÑÞ4uµ³®ò¯ó|@xx8Ο?o¾ùF£ÍƒÊx“ýÿ6ÃÖiJõï’.M±°°À矎Ÿ~ú o¤ ‘HTç¾{ce}aÛ·o‡X,VcšñþÃhÆ+óÑGáÁƒØ·o_C› jI›6m`ii‰[·naíÚµ¯í(0Œÿ±iÓ&¸¸¸`èСðôôÔ8ö3ãïÁâÅ‹qúôéZ7Å2ª>|8nܸ!wÉø{ µxñâÅ mãýÅØØR©ôpT‰ÙÙÙ8p 6lØPïá–ïD±XŒ€€8884´9¯EEEüýý¹Øì E^^ 0kÖ,,_¾ü~)a¼{èèèÀÒÒòœá111Q[šñþ#"ªc<ƒÁ`0 ã [ÂÁ`0 ƒÁ`Ôæ@3 ƒÁ`0u€9Ð ƒÁ`0 F`4ƒÁ`0 ƒQ˜Í`0þÖ$%%áÁƒ m£HNNFffæ[+¯¸¸IIIøë¯¿4’ÏÈÈÀ­[·Þ°U £¾a4ƒñžðâÅ „„„`ãÆ*efϞɓ'¿E«^G!==ý–áåå…¥K—¾Ñ2¯Æ³gÏššúFã ûúúbæÌ™µÊ!55¯UÞõë×áåå…ß~ûM#ù‰'¢_¿~¯U&ƒÁxû0šÁxO(,,DXXbccUÊ8p{öìy‹V½³fÍBëÖ­Ú F±|ùr´lÙùùù m Î;‡–-[âØ±c m ƒÁx`4ƒÁ`0 ƒQÔäÎ`0Þ{Ž= ###tëÖ çÎãf°{õê…öíÛ äe2Ο?ÈÈHìííáççWWWž\nn.Ž?Žääd˜››£C‡ðóóãÉ”––"""îîîpqqÁ™3g'''4jÔÙÙÙËåøõ×_¹<¾¾¾hÚ´)÷÷íÛ·ƒôôt4oÞ={ö„³³³ÒºFFFâìÙ³077G@@<==5j£ôôt\½zþþþ°¶¶\‹‹ÃDZøó×®]C\\²²²àææ†¾}û¢I“&‚¼Ïž=ÃСCqýúuœ={wîÜÁ’%K ‘H@DHLLÄÉ“'ñòåK4mÚ>>>ðöö†H$üúë¯pttD‡xº333‘€nݺÁÊÊŠK///ÇùóçqæÌáïﯲݪSYY‰„„üñÇ(++ƒºvíŠV­Zñä ñûï¿#99FFFðööFïÞ½ú¶{{{#** /^„¥¥%ÜÜܸå;‡‚¡¡! ]»vprrâòggg#** ·nÝ‚­­-ºuë&°¥z[Ÿ9sFFFð÷÷GÛ¶mk­/¤¤¤pÏE||<—nkk‹.]º¨Znrúôi¤§§£¨¨ÎÎÎèß¿?ìììTêÍÉÉÁ©S§píÚ5¸»»càÀ033ÓÈ&¸zõ*Ο?û÷ïÃÝÝýúõãõ³‚—/_"22ñññ066†««+ºwïŽFi\ƒÁ¨#Ä`0Þ 233 1B¥Œ½½=™šš ÒüüühöìÙ€5jDH,Óúõëy² ccc‰DäääDnnndhhHööö<¹¸¸8²µµ%‘HD-Z´ 333@£G¦ââbNîñãÇ€¦NJݺu#---²³³£aÆQ—.]H__ŸPãÆ¹ß‰'¸üK—.%@Íš5#¤§§'°[&“QHH ©TJ-Z´ --- #4iÒ$µm›””DhΜ9‚kååådiiI:uâÒÊÊÊhÚ´i$‰x¶™ššÒþýûyù D¦¦¦´uëV‰DdeeEÆÆÆôøñcºvíY[[²±±!’J¥€rss9hܸqÛ¶oßN(::šKÛ³gI$Aºººªm"¢ÈÈH®|{{{rww'cccÁ=uýúujÑ¢ æÍ›s÷T¿~ý(''‡' €‚ƒƒiøðá$‹ÉÎÎŽüýýiÈ!dhhHÈÊÊŠëÿ°°0.ïÆ9;;;‰D¤¥¥EK–,á•!—ËiÆŒ€ŒÉÍÍ´´´hÆ djjJƒ R[ï 6p÷°©©)gKHH=}ú”D"éèè““ÙØØ’H$ôÛo¿ñtÅÇÇúꫯ¨E‹diiINNN\[ݸqƒ'ïçç'x¾JJJè³Ï>#$‰ÈÎÎŽ……;vŒ';sæLÒÒÒ"]]]jÕª9::’¶¶v­÷<ƒÁx=˜Í`¼'¼Ž­­­M]»v¥˜˜"ªúOÞÙÙ™$ ÏáiÚ´)988P^^—VZZJ§OŸæþ...&[[[jܸ1%&&Q•3wî\Àsn´¶¶6…„„P~~>U9DD#FŒ ===¥uÙ½{7 1cÆPrr2ݸqƒúöíKb±˜’’’8ÙÍ›7š1cUVVQbb"çèhâL´iÓ†š6mÊåWpèÐ!@›7oæÒ–-[FhÖ¬Yt÷î]®MÛ·oO‰„=zÄÉ4ˆÄb19::ÒŸþIDUNyEEõïߟtuuyNUee%9s†JKK¹´º8ÐVVVJûðÌ™3µ¶··7™››óì—ÉdtêÔ)ž}dbbBçÎãÒW­ZEhÊ”)<Šþ4hw¯)úúôé‚—gΜ!H—/_&"¢Û·oÓˆ#EEEq²äî™LFDDÔ¼ys‰Dµ:ÐDD'Nœ ´wï^Áµüü|Ú±c÷r(—ËéÒ¥KôÁ™™½xñ‚“U8ÐZZZ´|ùr.=66–ŒÉÛÛ›§[™=þ|@ .¤û÷ïQLL yxx¹¹9WÞíÛ·¹1¡¬¬ŒËÿüùsJHH¨µÎ ãÕa4ƒñžð:4Šç¥Ï™3‡pNuEEI$µú‰ˆ6mÚ¤t¶¶¼¼œ$ Syy9ýÏÖÒÒ¢¿þúK KݬY3211áœnÉÉÉ€þõ¯qiÎÎΤ££C<Ù™3gjì@¯]»–ðœE"¢ÀÀ@200 —/_Q•3eddDÍ›78ÛG%´lÙ2.mРA€V®\)(³S§NäææFr¹\­mš:Ð2™Œ$ =ºÖú*ÃÁÁüüüÔÊ„‡‡«´ÇÚÚš´µµéùóç<ÛPjjª@^Ý®];ÒÖÖæH>$±XL\ZçÎ eeeñdW¬XA^ÛVÅÖ­[ 8p€KS8Ðfff‚ûcÔ¨Q€"##¹´šô“'OHWW—<<<åíܹ“К5kˆˆèâÅ‹€6nܨ±Í £~`k Œ¶¶¶‚õ³¾¾¾øöÛo¹ÉZZZ:t(vïÞ   £{÷î066æå»qã gÏž¼tøûûã÷ßÇÝ»wyk¦;vìsssí}þü9²³³Ñ£GÄÅň —ËAU/ý°²²ÂÍ›7T…ËÈÈ€¯¯/$ OO=ðý÷ßkTfpp0þïÿþaaaèÕ«€ªu¯'NœÀG}@jj*ŠŠŠÐ©S'œ:uŠg[YYttt8ÛªÓ¿AÚèÑ£1uêT`ìØ±0`€Ò5®š¢­­¡C‡bÏž=¨¨¨À¨Q£”ö¡*>úè#¬Zµ  Ànj޽{ Öì^»v €°ÿªöÞµkÒÒÒ`iiÉ¥;99 ÖЫC&“!11žžžHNNÆõë×yýïààÀ xýúu´hÑööö<=—Y%%%صk®^½Š‡¢¼¼%%%€¬¬,¼ŸŸo½{÷îŵk×н{w¥å\¿~åååh×®Nœ8Á«·H$‚H$âêÞ¾}{ØÛÛcîܹ¸qãFŒhiiÕ[½ †r˜Í`¼'hkW=®2™L¥ŒL&ãäªãääÄmHS`dd$зjÕ*4mÚ?ÿü38‰D‚±cÇbÚ´iœ¤p¸;uê$(ÇÇÇ¿ÿþ;îÝ»Çs˜”ɪCáÄÄÄ ..N©Œâ  uöÔ¥\KKKôïßáááÈÏχ‰‰ vïÞ ™L†O>ùD`ÛþýûqðàA---¼xñ‚—&‘H”n|›8q"*++±nÝ:L˜0ÚÚÚTżyó`llŒüÇŽƒ¾¾>FމéÓ§£M›6€{÷î¨êëšøøø`×®]‚ƒkêÚÿ÷îÝCEE’’’0dÈ¥2R©@Õº‚‚¥rÞÞÞõâL>}úíÛ·ÇÇáää„–-[ÂÕÕ8{ö¬ÒØÑ5_XÿµÃýû÷U–¥¸¿vîÜ©4$¥žžwï‹D"9rÿýï±yófüøã°³³ÃgŸ}†I“&ÁÂÂâUªË`04€…±c0Þlll ££ƒÇ+½.—ËñìÙ38::¾r7ÆòåËñàÁœ8qC‡Ehh(zõêÅv¡˜!UÌDW'99‘,êêÄ(òÏœ9%%%J©©©œÍÕËVf¦„„„ ¤¤„;#,, öööèÖ­›À¶µkת´íÔ©S<½ªê¯¯¯éÓ§ãöíÛ¸pá¦NŠsçÎ! €ç„ŠÅb”—— ò+»7nŒ•+WâáÇ8vì† ‚ÐÐPôîÝD¤¶þR©_~ù%²³³qãÆ!<<þþþÜl«¢þêÚ»æ,z]û_ѧ!!!(--Uú{òä ÀÄÄúúúJíIKKCeeeÊVÆ’%Kß~û iiiˆˆˆÀºuë¨2²¯ õS†¢}·mÛ¦òþÚ¿?'ߺukìܹ=ÂŽ;ðÁàË/¿Ôèðƒñê0šÁxOÐÒÒ‚££#’““‘››+¸ž€ŠŠ ´lÙòµË200@Ÿ>}†E‹!;;§OŸ´hѸ¿"##¡­­­Q¸4hÔ¨d2Š‹‹yévvvJ¥ˆŠŠÒÈé³²²Bll¬Àɬicmôë×5BXXqíÚ5Œ;–7{ïîî±XŒÈÈÈ:éV‡––:uê„5kÖ`ÿþýË娵kwÝÆÆ—.]ä;tèJ†††èׯvìØE‹áÞ½{jᩎb9ΦM›°~ýz¼|ù‡îþRÖ¶Š´ºô?P5‹\cccØÛÛ#**Jé‹CuD"\\\””„çÏŸó®9sF#;ÔÙT-[±´´ÄàÁƒyéêÚ?**J¦hu__*NžåävìØçÏŸcÒ¤IœlmôìÙr¹\©3ºhÑ"\¹rsçÎEEEïÚíÛ·qùòeîï)S¦ ¸¸¿üò —öôéSìÝ»W#;èèè 88qqqX´hD"BBBx2˜:u*ÂÃñaߘ˜ÈÍŽ×Æ¸™]мŠ%6@ÕqäwîÜÁÙ³gTÅyÞ¹s'¯ €ªãÞO:%èCe:kRQQC‡ –)òêêê†[[[:tˆ·ö÷èÑ£ÈÈÈÀ°aÃÔÆF®ŽbuÍ{X¼x12331mÚ4”––ò®eggó–ö|öÙg "üðÃ\Z~~>vìØ¡‘@UZ"**JÐMš4Á‹/À¥Ý¸qGŽQ©ïÑ£G¼%>™™™8räìíí1`À•ù1nÜ8ìÙ³aaa‚ë—.]ÂíÛ·TÍr'%%ñ®Ëd2ܹsDÄõƒÁx4ÀÆÅ÷š‡’¯¯/I$züøqC›Ãø‡QYYI£GæâûúúRûöíÉÀÀ€´µµyÑ(â@×$::šÐöíÛ‰èQ>lll¨wïÞ4jÔ(.Öo`` /¢À¾}ûÈÐÐÌÍÍiàÀÔ¡C@:tà…ôRDáP_™ˆ¨  €‹!lccC®®®\„‚ÊÊJš0a800ÉÃÃD"­X±‚Ó“ŸŸO;v$ÔµkW "+++.´^]bâ*bBP‘¢°°H¨E‹4tèPêׯ¹¸¸"9(â@+ÃÔÔ”LLLÈßߟ>þøcjÓ¦ ‰Åbrwwç¢~%$$p1³½½½¹¸ÉŠØÞŠ()))\[öéÓ‡FÅÅ  Rí£¤¤„¥¥%õìÙ“‚ƒƒÉÍÍD"uîÜ™VïÔ©S$•JÉÄÄ„úõëG¾¾¾¤¥¥Eîîî‚HP±ƒ¨ªöYYY‘««+ýúë¯Üõ™3g’X,&kkkêß¿? 2„¼¼¼H,ÓôéÓ9¹ÒÒRêÑ£ Ž;ÒÈ‘#ÉÖÖ–>ÿüs211Ñ( q±—q³!ù.^¼HÚÚÚ¤­­M={ö¤>}ú±±1}óÍ7€-ZÄéPDáøôÓO©Q£FÔ½{w6lI¥R’H$tòäI^™ÊÂØåääPÏž= µlÙ’†N}úôáÚ*<<œˆþ…ÅÍ͆ Bƒ âž§ÿþ÷¿Õ™Á`¼"¢Z¾2x <óçÏG¯^½ššªôÔ2ãMsêÔ)œ>}ÐÕÕE‹-ÄmôªÎÚµk!•J3©YYY ÅàÁƒÑ¦MTTTàôéÓˆ‰‰Á;wPYY ???tëÖ ‚Mˆiiiصkï$BÅF8………X½z5ºté‚=z(­ !)) 111xùò%FŽ 777îzll,Nœ8ÔÔTˆÅbØÚÚ¢GèÕ«ôõõ9¹²²2üðøxñ",,,н{wañâÅhÛ¶­ÚY¿š|÷Ýw(((@=¸“è”qäÈÄÆÆ"-- °··GŸ>}àïïϵï¿þЬ¬,Ì;W_qZ`JJ òóóÑ¡CtïÞ:tl½ví~þùg}ú ´´Tà@?{ö ¶¶¶X°`-ZÄ¥7oÞ­[·FDD„@—ÂVÄe0 ƒÁ`ü3`S§®^½ ™Lwww^º‡‡.^¼¨4OZZšÆú·lÙ¢ôHVUk± ƒÁ`0ÞÙÙÙ‚´Ñ£Gcâĉ `ÍûÁß~ ´&>žwZmy>|ˆÌÌLµr—.]âtÖ”‹ŽŽ~m›k¦:uª^õeggs‡zhš÷Ê•+())Q+WýpŒêrŠkõÙ.ååå8sæL½¶ËÝ»w_§¼Š:«’+((àBX]îÎ;Üs[_õÈÎÎÆÕ«Wëµ]ê:^)ÆurêÆ«¸¸8nü©¯z\ºt wïÞmÐñJ1¶¨“S7^EGG¿¶ÍÊÆñ†¯c‹:9UãUõq§¾êQ^^ŽK—.5øx¥j}úk÷×ëê¤JgVVµiÓ¦NåÕFnn.ï0ކÒY[;ªÓ9wî\š;wnÊ« 6¶(‡-Êac‹¿ÃØâááAu*ïŸ[Âp±œÓÓÓaiiÉ¥§¤¤@OOR©ôµË(((ÀâÅ‹y÷_‡êMÔuÕ©­­]ëáêtúùùÁÔÔ´NeÖFŸ>}x‡k4„NgggÁÑÃuÑ©îàŽWEÝ!oKg=x_ÔE§©©)üüüêT^mèëë£OŸ> ®³¶vT§³C‡u*KØØ¢6¶(‡-BÞç±%&&111(((àùC %4´ÿ6Q5}îÜ9@›7oæ¥ûúúÖË{@@uêÔ‰233ë}ã}æMÌèüx3:ï;obFçïÀ›˜-þ;ÀÆå°±E[øäææRff&uêÔ‰Í@×[  cÇŽ°±±Á•+W¸´ÒÒR$''cèСõR†¾¾>êe6›Á`0 £¾‘J¥ppp¨÷¯-G´/^¼¸¡x“¼|ùk×®E\\¢££‘››‹’’ÄÅÅÁÝÝ‹ÅÐÒÒŠ+`hhˆ’’üûßÿÆýû÷ñË/¿ÀÌÌìµlˆŽŽÆÓ§Oadd°²²ªª½÷8;;ÃÊÊ b1{«Ž››ûtVmmm¸¸¸ÀÜܼ¡My§°´´D³fÍ`hhØÐ¦¼S°±E9llÂÆ>7nÜ@TTΟ?æÍ›¿‘%:þö£Kqq1"""333XYYqWßý‚T*… š4i‚… âUfË…°M„Êa›k‡­~K(6*ÖÕ•Áƒ§ 3³U­rúú‘ÈÊzµÅÿ ®Å¯¿æAKKªVN.Á¥K¡pppx¥rÔñòåK>ÿüs@FFŽ?޳gÏâØ±cøã?4ÖÕ¨Q#Á:®ØØX$%%aĈ°¶¶æÒÍÍÍëÅþw…ÜÜ\ÌŸ?mÛ¶Åœ9sààà€ØØX9rƒÆÁƒ1tèÐZõDEEaΜ9J¥°··GVVÖ+ÍdÇÆÆbôèÑxüø1† † &ÀÚÚ·nÝBll,‚ƒƒñ×_aêÔ©€1cÆ "">>>øüóÏáéé‰û÷ï#..ûöíÃæÍ›¡­]5|•••áåË—(//¯³]oŠo¿ý§N‚ƒƒ ðüùó7öÒù®“••…™3gbãÆðððhhsŸŸŠŠ |ñÅ€‡âøñã8þ<:„‹/BOOO/..IIIÐÒÒ–-[°`ÁˆÅÂ9¨ÂÂBnS ¸GѲeKTTTàÞ½{HMMů¿þŠU«Váÿþïÿðå—_òt•––Âßß7nÜÀܹsáéé‰`éÒ¥xñâ6nÜXÏ­Ãø§ˆˆÄÅÅ¡K—. mλ 1Þ8ãÆ£qãÆ½–LÇŽã Z..êËQǤI‹ȬµŒÆQffæ+—£ŽÆ“½½=/-??Ÿ?~üµôOŸ>P||ü+å_±b ””äÔÚ÷o‚ÂÂB:wîœ =&&†PË–-5ÒóðáCÊÈÈ ¹\Nñññ€¦OŸ^g[š6mJ¦¦¦têÔ)¥2;vì ;wQRR V­Z‘\.Ⱦxñ‚—¾qãF@Û·o¯Õ–ï¿ÿ¾Ný÷ª$''Ó_ýED¯Ͻï(î›7¾±2&MšD¨¤¤¤VYWWWjܸ1/­¢¢‚|||mÙ²Ei¾ñãÇ“ŽŽ-\¸Ðü¡TÎÏÏO`‹â¾Û»w¯@þüùóÔºuk@»víâ] %ôÝwßñÒHb±˜îÞ½«Ô†ÌÌLðÆÆiÆßMü–:l Ç[‚-ÈuŒ1nÜ8@bb"žÄüùóÑ«W/¥2cÆŒÁÇ \;Œ1Bi¹¯eOu=z„‘#GªüíÝ»—'áÂŒ;^^^Àþóäåå ô¶jÕ굿jŒ;Vé c||ûÐyßóŽ;÷}‡sÏûœó^¹r%TUUÑ»wo4kÖ .\@ÇŽ«õ_^¹r%–-[SSS8::âÔ©Sh×®D~žçÏŸ‡öîÝ %%%XZZ"00íÚµ8|óæ ìíí±lÙ2ÈÉÉaĈ°±±AHH"""$º¶¯!"dff¢yóæŒ Ä·&88²²²øí·ß$’××ל>}Z¤rÚÄÇÇ#00P@™Ú³gºté‚û÷ïÃÅÅzzzضmìì옘Øàc8yò¤È—¸”” ¼ ÆÆÆ"00Ë–-È#PPPMMMœ9sNNNÊè³gÏ`kk‹õë×C]]LJ¥¥%NŸ>-0agg‡mÛ¶AOO...xðàºwï.¤Ø?xððôôÄž={ ­­ p8ÈÊʤ¥¥!//yyyÈÈÈ0uwïÞ gggÀØØZZZðóóƒ­­-"##ú™3gƇœœôêÕ ™™™èÖ­âããë÷eHKK‘®i§NBQQ~ùåhii¡_¿~ j°ùiff†_~ù>|ÀÓ§O™òÈÈHÈÊÊ m3meeÅœ¯ l¡0l¡hØ B øÎðÿéÂH*'êøñ]8.^¼HDD{÷îøÌçÚµk€vìØ!²}QËéYYYTRR" ÷éÓ'ÒÕÕ¥fÍšQUUSÎwáÐ××§¬¬,¦üÑ£G€ú÷ï/оrá((( jÑ¢…@ýììl233#*++#"¢?ÿü“ÐÝ»wÚäñxT\\,òúımÛ6@K–,©uݺ¸pTUU‘¼¼<™››K\§¢¢‚Ú¶mKHNNކJ~~~”™™)R¾¡]8Þ¾}KúúúdllLDDôîÝ;RRR";;;*((`dïܹChРAÕ¶WW999‘¿ ýõ +W®0eüyillL LùñãÇ ÍŸ?Ÿ)›9s&IIIQtt´@»UUUÏÁ Aƒݹs‡)+(( ;;;RRR¢wïÞ1å#FŒ äêêJ¥¥¥íÖ䑘˜HrrrôÓO?Qaa¡@¹‚‚uîÜ™)‹%.—K½{÷¦ÊÊJ¦|Æ  Î.UUUôàÁ222"ôúõk¡:;w& æÙ "´sçN!ÙÚºpðÙ²e‹@›EEE€lll„d?~üH¨]»v"Ûb]8Xê ëÂ!ÖÍòÑ››‹yóæaÞ¼y:t(tttœœŒ=z0K™£F‚ŠŠ öíÛ'PwïÞ½PTTdÜ$A[[ eòòò?~<ÒÒÒðîÝ;¡:½{÷†¶¶6ó¹]»v°±±Á… jŒ¨?qâ233ñÛo¿ Ô×ÒÒÂèÑ£‘””ÄX ÔÕÕ@ÈÊÅáp ¨¨(ñõñyüø1fÏž kkk,X° ÖõëBzz:JKKa`` qiiiÜ¿‡‚µµ5NŸ> ÁÇǧA–ì«£°°ýúõCII .]ºŸï[qq1†Ž&Mš0ò]ºtA‹-pîÜ9dgg³qIʈ#`ffÆ|îׯ¸\.bcc™2MMMðx<¡´€\.—y>|ø€sçΡE‹èÒ¥ #Ó¤I >ÅÅÅ8vì˜Pÿ£G€WÛ·oGYYfΜ eee¦ÜÔÔîîî¸wïó½:t<£F‚””#ëééYk—ž?ÂÞÞöööÐÓÓCÇŽÁãñpíÚ5´hÑB@6!!÷î݃‡‡cMïÛ·/´µµáïï_«~k‚Ÿõ€¿šÁÏcaa!$«¡¡--­ožM†……¥zØ,D}³pHж6PWÝèÔ)àÁƒ†O](,,Äž={***hß¾=ŒI“&1(•””0vìXøùùáÝ»wÐ××Gff&Ο?1cÆ@UUUâþˆGŽÁîÝ»ñæÍ|øðA ‚>55Íš5¨Ó½{w¡vzö쉨¨($&&VëÿÊ÷A òµä+êqqqprr°aÃ0{ölôïßíÛ·ÇÀ1f̘:¥Љ‰AŸ>} ­­‹/2ŠÀ·F[[\.·Öy—åääàééÉø†ß¾}ûöíÃÞ½{qðàA>=zô€””nܸ###‰ê5òòòLú»ºbmm kkkL™2sçÎÅúõë±aÑÐú0eÊ\½zþþþBJZnn.€ÿÝã/á—ñe¾'jjÂ)(¥¤¤^õõõñöí[øùùÁßß³gÏÆ‚ 0dÈìܹuº^999¡MqdeeA^^^ pŽFŒ%%%ŸŸ+yyyæùøzLµQ UUUÀ|Žˆˆ€‹‹ ˆgÏž1/˜<GŽ””6oÞ,Ð߯Üßß›6m’¸ïêà[ž[µjàóJ¬¬¬È Æ¸¸8TUUIüðe=þ‹Ëg*++‘œœ,ägþ_…oä9rä÷ÊëÂÑH°ù ••:wîŒýû÷£ªª û÷­-œœœjÕÎÎ;ѬY3cÉ’%6lºuë&ÒuƒÏß çÚ¾sçÔ¨ ò—‡—-[†€€‘GŸ>}ù&Mš`Ú´iˆŒŒD||™Dáëë‹äädÌ™3oÞ¼©µõ¹¢¢ppppk(++«q î[·n |æñx¸uë455kü£Ö©S'¨6_M˜››cݺuÐÓÓ“HMMM…««+ŠŠŠpýúuX[W¿¡´´´ÞŠ€(V®\ 999Ì›7¯Z+mzz:ÂÂÂ|þƒö¥ÅôKøß{C*«§OŸÆÜ¹s1jÔ(,_¾\¤L›6mׯ_(¯¨¨À;w ®®.à{ÜâîÝ»Bé´kkkìØ±ÊÊÊ̼211ºº:îܹ#4'øßAûöí%jŸŸQTZºN:¡²²ÇÛŽ££#ág/22²A|gΜ ¬[·Ž™§|÷;wî ..Nè˜?>²³³qñâÅ:÷[XX¤§§cîܹñ£G¡—þ®–üó’ÂîD( »¡hØÅúpüCðövƒƒÃ!±rÚÚ’ýQÅ AÝÜ+'#£&r©ø{0dÈhkkcÓ¦Mµ>û2¶nÝW®\ÁÅ‹áææ†ââbÌœ9S`ké¯ Ç0vìX”——cñâÅÈÉÉÁªU«Rt}MÏž=1`Àlܸjjjðõõ…ªª*Š‹‹ñäÉøûûcÛ¶mhÒ¤ 6mÚuuu¸»»CKK 8yò$Þ¿/ÒûKrrràêꊔ”,_¾ éÚµ+4–’’¸¹¹ È•——3 ß;%%…‘iÕª•X—SSS¬\¹sæÌ­­-Ö®] GGGW­Z…eË–ÁÙÙ/^ÄòåË1mÚ4tìØ&&&HNNFhh(Ö­[‡ƒQ£FÕØ§¤¼xñcÇŽ… V¯^-duQVV†²²2† ;;;?~={öÄ AƒPTT„3f ¨¨6lžKIIÁË—/™ÿŸç ßm§{÷îbýÐÝÜܰaÃÌž=sæÌAJJ vìØ!Ò*]–.]Š–-[¢OŸ>PWWG^^üýýQTTÄX{eee±páBÌš5 “&M¦M› ¬¬ŒsçÎáøñã°³³Ã!C$êï£|þüytèÐæææPVVF³fÍ0iÒ$ìÝ»³fÍ‚ŒŒ †EEEäååáþýû8þ<“ ÙËË +W®Ä¶mÛйsgtêÔ ééé˜6mê´Cæ—HKKcÑ¢E7n6n܈3fàüùóptt¬vyôèÑX´h<ˆAƒ‰í#66·oßfv"LHHÀ¡C‡‘‘nݺ19³ùŒ?ëׯÇÔ©SѼysXYYáìٳعs'ìííFŒArrrÕ^K`` uìØ‘¤¥¥ õîÝ›9—ššJ}ûö%Äápž‡®]» ´óøñc200 ¤¨¨HhìØ±4a„:§±û’ŠŠ 233£&MšÐÚµk mݺµÆöºvíJRRRÌ=­)Ýׇ¦¦&uîÜ™.]ºTmûááᤩ©É<+ÈÁÁ¡Ú´ŽDl;–úÃê$âáU³VÊÒ`xyy¡¬¬LhW³¯ev‰-&&G ‚8–/_Ž%K– ,,L¬ÿszz:²³³Ñ²eKTpÙÙÙ¸~ý:ŠŠŠÐ¾}{ØÚÚ"??©©©ŒÅ øôôîÝ;´jÕ eeexðàRSSÑ¡CÆz÷%ÏŸ?‡†††Èå°ÄÄDDFF"55zzz°¶¶ºîÄÄD<~ü™™™hÙ²%ºté"Q »OŸ>áÕ«W5Ê´nÝš±‚–——#66***î•••"ƒ˜øÔ*óDUUâãã™ewSSSØÙÙ‰Ls÷êÕ+DGG#==JJJ044DÇŽ™ 2>?~ÄÛ·oahh(v÷¿ììl¤§§£U«V““CNNRSS«•×ÑÑÔª¬¬DDD¢¢¢ ¦¦†víÚ‰tÝà÷SÖÖÖmdSVV†[·n!)) mÚ´A»víPTT„ääd˜™™1)õ¾œ—_§‘‹‹‹ƒ”””@z¶¸¸8<{ö YYY°¶¶†‹‹Kµéçøs0//ÖÖÖpttZeIIIA~~>lmmk¼ž‚‚$%%¡I“&Bn8ÑÑÑˆŽŽFVVš5k‘îP¸wïÞ½{'''X[[#==>|€Ø”vü<þf$_Ãÿ––Fee¥ÐoÅ×¼ÿ™™™055…ŠŠ k>,_ÏEEE4kÖLât”………Gbb"Ú´i‡Wº’““abb‚¤¤$€g6ˆP6ˆP4#GŽ„œœÜ^'© Vn¼¼¼œœŒHKK‹üÃÉ*Ðu#??fff°µµòdaaaù/RíååÅþùм¼<,]º”õƒþÿTVV¢²²½{÷†±±1;_j€ "l$Þ¿éÓ§ ù¡²ÔØØXlݺîîîÈÏÏǶmÛ¾÷XXXX~hXeH6ˆPLŸ>]dþqAØ ÂFÂÙÙ™Ù„¥þ<|øK—.…­­-üýýkÌ0ÁÂÂÂÂÂÂ"žŸþ?ÿü³Ètž,‚° 4Ë?ooox{{ïa°°°°°°°üa]8‰²²²ï=–ÿ0üT”,ÿ£²²’Ù&žå°:‹xXº‘`wõaaaaaùž°; ÃîD(Vg«@7ì®>,,,,,ß6ˆP6ˆP4¬Î"Vfaaaaaaaaa©¬ÍÂÂÂÂÂÂÂÂÂR Xº‘`òYXXXX¾'l¡0l¡hXE<¬ÝH°ù,,,,,ß6ˆP6ˆP4¬Î"Vn$”••ñüùsddd|ï¡°°°°°üaƒ…aƒÉÈÈÀóçÏ¡¬¬ü½‡òÃÃn¤ÒHdddàöíÛprr‚®®î÷ÎKhh¨ÀÒ‘ŠŠ LLL §§Wë¶òóó&‘¬‚‚ºví*Vnß¾}ظq#®\¹±ò–––4hÖ¬Y#Ñ8š¨¨(DDD -- :::hÛ¶-Ú·o_ëvJJJ‰¼¼<ÃÒÒ²Nãá·‰¬¬,˜ššÂÎÎVVVB²•••¸zõ*âããQPP]]]´jÕ ŽŽŽ““cäÞ¾}‹˜˜ØØØÀÀÀ Æþ9‚Õ«WK|ÿêKFFbbbPQQggg¨ªª~ó>ÿ‹,]ºˆŒŒ„¬¬¬XùŠŠ „„„ !!………ÐÕÕEë֭Ѿ}{¹Å'&&áááHKKƒªª* áìì,ô»ÂüŸËåBGGÍ›7‡††Fý/’…¥HNNFxx8222Øy+bùæxzz’§§g½eøäååѵë×hùºåt>ˆÞ¿_ÿA~Eii)Ý¿ŸÖnZK‡¦øøøïC:::@èprr¢Çת­°°0‘m‰:ŒŒŒ$jsÍš5€^¾|)‘<‰ïkC’››K-[¶d®Ãá0ÿ4h}úôI¢vnÞ¼IVVV$%%ÅÔŸ6mZÆtæÌÒÖÖfÚ‘‘‘aþß³gOŠŽŽfdÃÃÃÉÔÔ”9¯¬¬Ìü_NNŽJKKÙÝ»wò÷÷;†Í›7×êþÕ• ¾¾¾À û¦}þ¨DFF’:uê›õáããC$š×÷îÝ####‘sK^^^@¶¼¼œ<<<˜çG^^^àYسg€|u¿/JJJäííMIII yÙÕ’””D­?–µÑIþ«°.DC9äï>°î¸w þHþÆÃq´#¦ÍŸ†ŠŠŠéãÖ[°ïe¾ûb^Ü}ú„k×®ÁÍÍ çÎìY³$j'33JJJ˜4iæÏŸ_çñ,Y²C† šš.^¼ˆ¬¬,”––"..~~~xñâ®_¿à³Oâ!CœœŒ={ö 11………ÈÍÍEpp0~þùgp8œ:¥1HII­­-þøã¸¹¹}ïá|WŠ‹‹ñâÅ |øðá{yyy:t(ÒÓÓ±ÿ~$%%¡°°999 BïÞ½ä/^Œ€€ <÷îÝC~~>>}ú„ÈÈH,X°@䊂óûuïÞ=9r8räZ¶l‰£G6Öå Á ÃІ "”€ï­Áÿðôô$WWW±2âÞöþXó© V!,…Ð!÷«õÞ·Þc ¹BZ]´‹…ûàLå•‹•••Õ»ŸêÐÑÑi îÞ½»ÄVÆš˜6mZµÖÀœœzúô)EEEUkÉúÚýñãGzþü9•——‹”G 說*Ч/^ˆýNsrr(,,Œââ⨢¢¢FY~Û<O¨<--dddH[[[l_÷è×ÖK222äàà@EEE"e>|ø@OžtýH Ÿ'MÂë–¯1cÑŒ:÷QW† HHH@FFdeeñË/¿ˆ”0adddðþý{‰Ú~ôèÚ´i ´iÓ666PVVÆ´iÓPRR"²NNNºví ---ØÛÛCEE»wï–¨?"¦M› ªªŠ-ZÀÎÎÊÊÊX´h‘Ð*–-[àèè---8;;ÃÒÒZZZ¸yóf}p¹\‘VZØØØ //•••·¾,^¼X¿~=”””DÊhjj¢M›6þý­¯¯ÿÍLJSí1oÞ|8.]º„Y³faĈøøñ#-Z„¡C‡âÔ©SÌKÞ÷däÈ‘ÐÓÓñcÇPVV†Í›7cÉ’%pqqA÷îÝÇŽâE‹0räHlذ&&&HOOúNŸ>aÆ¡C‡8zô(455ñ×_aãÆÈÈÈ@pp°€|UUzöì‰#F`çÎÈË˃••”••1yòdf.N}®]»àëë )))L˜0ÀçO777DEEaÆ èÛ·/âââ0mÚ4ðx<‰¾Ÿ¶mÛø lnnŽÁƒCEE¥ZùvíÚ!44S¦LÁo¿ý‰ú©ŽÑ£Gã÷ßGddd½ÚaaaùøÞ&ðÿ Dè:È•ð‡°[…Ða+Lu‡ÿ'^®y¯æÕ.Å×—¯]8rsséðá䨨H222KDD·nÝ"´uëVú»ví""²ýÚ,§wìØ‘¤¥¥©¤¤„)ã»p|½L[RRB dff&°ޝ\8^¼xARRRäîî.Ô_Ïž=IVV–Y.^ºt) ÄÄD±c•www@W¯^­uݺ¸p‡Ã!;;»ZõµnÝ:&¸KVV–œiëÖ­"ç]C»p‡Ã¡1cÆ0e¡¡¡€<<<dóòòHFF†LMM«u;¨« ‡œœœÈß„¿þú‹Ð•+W˜2þ¼ìÞ½»€lDD qãÆ1e¾¾¾$--]£{Gee%™šš’ŒŒ œ6l [·n1e#FŒ 4eÊ¡¶jrá8{ö, ™3g ”óx<244$mmmÆeéÔ©S€æÌ™# {ûöm&XO’ ÂåË—“¢¢"êììLÛ¶m£ââb!ÙÈÈH²²²bÚ755¥‰'2¿C_#ê·ákš4iBZZZbÇYØ B–úÂЇuáh$ê믬¤ |ëwyŸÄ‹É äåå¿Ù0Þ¾} ]]]èêê¢iÓ¦ðôôlÛ¶ ­Zµ¸ººÂÂÂ{÷î¨ëççôêÕ«Öý¾yó×®]ÃÑ£GqèÐ!¢²²)))B²?ýô“Àgtî܉‰‰xûömµ}\¾|UUU˜6mšÐ¹Ñ£G£¼¼ÏŸ?&­ÞòåË[ëëùšeË–!88“&MªÓ÷Srrr@DµNß6gΤ¥¥aãÆ°°°À£G0mÚ4bÉ’% ¢o2ÞÀÓÓ]ºtÁ˜òÐÐPúÞTUUáää„7oÞ 99ù›Œ©6|moÓ¦ TTTæp÷îÝQYY‰%K– 11Qd;©©©xóæ œÑ¤Is|÷‹[·n Õû:OçÏŸ‡Ã¯¯¯@9‡ÃÁÈ‘#‘ÍŒï¶ôõJFÇŽk•³vñâÅHKKÃúõëannŽˆˆüöÛo044ÄŠ+dmllëׯ3AÌ{÷î…••ÜÝÝñêÕ«Z]/ðÙe ??ÿ›Íáš`ƒ…aƒEÃЇU ‰úîêÓ«s/pߊ¹]Å@o«Þx?ó}Ž›ónB#SLÞG .¥)©ü<ꉒ’<<<àááyóæáàÁƒHHHÀ¤I“ä&Mš„˜˜ƽãáÇxñâ&L˜P«, øé§Ÿ`ff777Lš4 K—.e²Bˆºwüåð/éÙ³'€ÏŠxuÄÇÇúõëyyycâĉÀ(5]»vÅøñã+++´lÙk×®ENNí]t6mÚ„¥K—2Ëë…k|©¨UUU̘1ƒÉ½nÝ:ÈÊÊbùòåB/N Abb" ###œ;wN ŸpRR€ÿÝã/á+Õü{û=éÒ¥‹Àg)))èéé¡°°)ëß¿?F…;wÂÜÜÖÖÖØºu+ þ_Q—ë•’’‚««k­Æ"‚•••ÐóÀw⿘¼yó²²²pqqhCFFF¢î_¢®®ŽÙ³g#::™™™X½z5¸\.þøã>|XH¾G¸párssqùòe¸ººââÅ‹9rd­ú-//Gzz:LMM¿K&v'BaØEÃîD(ÖºQ­üêë?nì8l?²¯_Wë£Ü,¢vÚ ]åºmÔ¢ë¤ iÜÉ»TãrªþD |Ô©}IÑÐÐ(¨ÃËË ,ÀÞ½{ѱcGøùùAFFÞÞÞµêoüøñ¸uëöìÙƒ=zÀÄÄ\.ë֭üyóDZŠŠ‹‹…ÊŠŠŠ Æø/ÇŽ«ÖjÆ·²s8ìÛ·«V­Â¡C‡pöìYÌŸ?Ë—/Ç7бcG‰®o÷îݘ9s&ˆcÇŽÕÙº.p8´lÙÑÑÑ(,,²fJŠºº:æÌ™===üòË/†Oƒ3''}ûö‡ÃÁåË—üêÿÝSþ=þ~™¨ 8êCu¿1¥¥¥ÕÖ‘äÅVVVÇÇÚµkqèÐ!œ>}Ó§OÇâÅ‹ñ÷ßÃÖÖ¶N×+-- EEE±ý=^V+cggÇŒ»²²¥¥¥PPP9¦º ©©‰ùóçCKK 'NĹsç˜U¯¯áp8èÓ§ºwï}}}<{ö iiihÖ¬™D}ÅÆÆ‚Çã¡eË–uo}`ƒ…aƒEÃЇµ@ׂ>}ú yóæ033ƒ‡‡G£¾¡)((ààŸÑüFs ÿ«“e€î]Ìô˜)ôTüÐ"¼¸ï¾š<@í‘ú›ôÇàþƒëÕGC¡¦¦œaРA())©µU’Ãဈ„Ü"øÛ™Vǵkׂ¬¬,<|øíÚµ«1ûÇÀ¡¨¨ˆ… ŠÜø¦´´”É( ÊÊmff ¼|ùRìµ9r'ND=pæÌ™-ã………سgOƒ)g_2sæL˜ššbÆ 8uê”Ðy‡íÛ·3>DZ±±xúô©È¶øî'ööö 6¾_ý•Ùð‚ŸríkÜÝÝÁårqቔ”<þŽŽŽ[!%ÅÔÔOž{ö Àçà5MMMŸƒ]ÅVúøøàþýûh×®|||˜˜ˆ'N mÛ¶¸wï^¯yøðáàñxèÕ« ‘––†sçΡ¸¸cÇŽeä6n܈Ÿ~ú ...˜={6455ˆ›7oÂ××Wâ”n222èÖ­Ž=Šììl˜››ÃÂÂÞÞÞèÒ¥ ¦L™‚íÛ·#%%ÐÕÕEzz:ÂÃÃqÿþ}&¦ÀÕÕîîîØ·ox<zö쉄„lÞ¼¶¶¶¥†{úô)¼½½æVRRBCC}}}Ìœ9“‘÷òòÂôéÓáîîKKKÈÊÊâÎ;¸}û6ˆ7nr›ILLdä> )) ¨ªª‚——Ö­['Ñ÷ö-ˆ‹‹ƒ¥¥åwëÿG¤²²ÉÉÉ077ÿÞCù¡(++kp·´ß+ýÇ?___²°° ®]» ¥wªŽ†Ú‰ð¿€‹‹‹P*.ql۶К5kÄÊ®^½š,,,èùóçLÙ‡häÈ‘$//ORRRdkkK{÷gÏ’……=|ø‘Ý»w/YXXP\\ùøøŽŽq¹\²··HëÅÇ‚æÍ›'T~÷î]êܹ3©¨¨’‘‘!Z¸p!³«á_ýE...$//OHWW—ÆŒC'Nœ{/_¾$ ‹´´4F>--,,,hüøñídggרÆáÇŎ…Oqq1-X°€:tè@JJJ€´µµ©wïÞtýúuF.==–-[FNNNL*;.—KúúúäëëK)))í……={Vì>LôæÍ""æWwlܸ‘©ËãñhË–-Ô¢E âr¹¤¨¨H;w¦§OŸVÛOuGvv¶Ø±òx Kƒðe¡®®.ów†Oii)[Æ– ”ñw8å—ùøø@JJŠÕIj€ÍÏõ€p@ ÿ3BÕYYÙoÄÂÂÂÂÂ" QÁ–l[öuÿÿü¿ÎoÎ" ëÂ0;Ç}½»[tt4$Âßÿ ///Õ»-–†&((^^^øûï¿¿÷P~xX`òd~½ÕlTTTƒåÐlß¾=:ÄlGËÂÂÂÂÂÒ˜°A„°A„‚ 8‡Bûöí¿÷P~xX@»ví`ff&ðÆ•››‹èèhxxx4H™™™ Ò K]øz«z²üVgÏ¿Þúǘ5k€Ï媪*&`oÍš5ÐÓÓ‡ÃÁŠ+0vìX¨««ÃÁÁûöíCÓ¦M1qâÄGjj*¼¼¼0pà@Ö ÍÂÂÂÂÒè°a¨©©aË–-ß{? AAA Bjj* ¿÷p~hþõ ô—ôìÙ³Ús#GŽ„††üýýqàÀ899aþüùõÞÝŽ‹‹ ûãÅÂÂÂÂÂÂòÃÂ7òñ ,Õó¯W µ´´$V\ÝÜÜàææömÄòæÝ»wHMM…½½½D[ ‡‡‡C[[fff0:qddd 99YâûÇÒpdeeáÍ›7°¶¶–xw–•½ý£PRR‚ÒÒRHKKW»#"ËçdîyyyÌg˜˜˜`À€B݈#!!‹-’HV[[Û·o+wäÈÌŸ?/_¾„¥¥¥Xygggxzz6úê!<<§OŸFDDÒÓÓ¡¯¯GGGÌž=›É<#ŽòòrDFFâÉ“'xöìòòòзo_üòË/uשS§póæMDFF"++ ¦¦¦°··ÇäÉ“add$ Š]»v!>>ÐÕÕ…••\]]1räHp8À7°ÿ~øúú¢k×®5ö€ßÿ]âûWWrrrðäÉ?ÿü3~þùçZ×ÿ¿Ñ£ñáéS(ŠQ¾Óåäp5"BhÃIس};.nÛ-1–¹×¥¥8s÷®ÄJXm BNN£,¿}û999X´h–-[&±B ÅÅÅÌN’|²²²›› ### ¤AÃ\ÀBff&:vìyyy888ÀÉÉ ÷îÝÃýû÷±oß>„‡‡ÃÊÊJl;'Nœ€···@™®®n­èÌÌLL˜0ÁÁÁÐÓÓƒƒƒZµj…ØØXìܹ›7oƉ'0lØ0ÿÛÄHNNŽŽŽèܹ3Þ¾}‹àà`ìß¿C† œœ€Ï/JèÝ»·Xº±5j®^½*P6}úôötYYàããƒ={ö|ïáàÁƒ „••¤¥¥‘——‡””@§NpóæMæþIEE–,Y‚ììlܼyãLJŒŒŒÜÓ§O(pO²²²èêꂈ‚¬¬,€­­-V¬Xþýû µ·eË̘1VVVpssCxx8Ƈ˜˜lܸ±!¿šz±víZÖ•ð+øA„¬ôgBBBpñâEÜ¿¿F·WÄòÍñôô$OOÏzÉÜ ¥•êêD@µÇEÚ¾bEÇùñãGò66®±L€&öî]ç>Ä¡££CFFFÌ犊 ºzõ*©ªª—Ë¥'OžÔ«ýiÓ¦ «Sý5kÖzùò¥DòÄÞûoÁÇiñâÅ”••Å”ñx<Úºu+ Î;KÔNDDíØ±ƒÂÂÂèÖ­[€¦M›V«±ðx<êÔ©q¹\Z°`•—— œÏÍÍ¥_ý•¶lÙBDDÉÉÉÄårIKK‹òóód+**(88˜*++™²Ý»wò÷÷;–Í›7×êþÕ:~ü8ÅÅÅÑo¿ýV¯9×|úô‰Ï7냟$ùFŒAèýû÷LÙëׯ©{÷î€V­Z%²Þ™3g¹»»:sæŒH9Q¿¡¡¡€–,Y" [RRB$]]]@çß½{GÒÒÒÔ¡C*--%"¢ªª*0` G‰½Þ†&))‰PRRR£÷Íòï@½å¿kþ‡àÒ­¶›™¡ðñc4©Ffóæ˜=»Î}hhh ™›"÷î…m5;¼oÔÕÅŒF|S—––F¯^½àáá???ܽ{FFF†„êDFFâéÓ§pww‡¦¦¦DýDFFâÖ­[HJJ‚ŒŒ ,,,àáá&Mªû¶?o¼sõêU¤¦¦¢C‡2dˆÄî9UUU¸xñ"ž>}Šââb888`È!"ýrãããqõêUÄÄÄ iÓ¦ppp@=jô#ÕÐÐXއ___¬]»¨¬¬;ÞöíÛ3ù@ÃÃÃ%º¶¯9vìîß¿ &`ÕªUBçÕÔÔpàÀfGЈˆðxÎ;Wíy´mÛ–ùœ““ƒ   DEEAMM íÛ·Gß¾}…ê}€SÓ²¿8Þ¾}‹<{ö ššš°¶¶FÏž=¡¡¡! ÷ðáCܹsoß¾…¥¥%úöí ™¨¨(Û¶m#´xñb*//']]]rttÙFçÎIGGGÈÒI$ÚòHH]]Ú¶mKêêꀚ5kF‘‘‘õùèíÛ·“¢¢"µjÕŠ uíÚ•rrrä!ÂOŽŽŽ€š4iB€,--)..Ž‘+**bä´´´¨C‡dffF‡þúë/q_eµ˜››“††F­ë………ÕÉíääD\.—ÒÓÓ%’&4`À‰äëcŽ'¡CMMÐܹs™ºÿý7””Y[[“žžcí,((¨¶Ïº¬zTTTP¿~ý©ªª’££#µhÑ‚¤¤¤hÍš5ŒÇ£éÓ§‡Ã!555rpp 999RRR¢cÇŽ ´ÉŸ»~~~¤¨¨HM›6%Ú±c3‡•••ÉÈȈŒŒŒ¨]»vLÝ7oÞ““#Ó¼ys@æææ%ÐϳgÏÈÀÀ€¸\.YYY‘¾¾>™˜˜Ð‚ êe&ú¼"€~þùg¡:ïÞ½#)))úõ×_‰ˆè×_%)))z÷îlm,Ð_òÇÚ»w/SöÓO?zðàÐx™™™ØëmhX 4K}a-Ðâa7Ri$îÞ½[ï­¼]ºuà 33Š8·ŸËÅ„ '§z}û¢ù§OˆÑGc[Ÿ¿äĉ ÈÈÈ`üøñˆˆˆÀ‹/ä^¾|‰{÷îÁÛÛ[¤ï£(¬¬¬pïÞ=äääàñãÇÈÊÊÂÍ›7ñáÃøøøˆ¬³`Á„„„ 66)))صkîܹƒ+VÔØǃ‡‡^¿~€€dff"%%gΜÁû÷Ž?Žˆˆøùù!;;áááHHHÀ›7oÄaUÇÝ»w‘Ð AI‰‹‹ƒžžôõõ%’wqqŽŽ.\¸€nݺáÀHMMý&c377GFF†À‘’’kkk(((0Ö˲²2Œ5 xòä ¢¢¢ðîÝ;¬Y³ÁÁÁX¹reƒŽëòå˸té/^ŒÜÜ\<|ø¯_¿FZZš€n@@¶lÙ‚Ñ£G#++ OŸ>e¢Æ‡ääd¡¶ç΋³gÏ"33iii˜8q"^½z=z4’““‘œœŒGø`8fÌDGGãøñãÈÌÌDrr2‚‚‚››++Ÿˆàåå…’’DDD ::ééé˜0aÖ¬YSïï…oÉíӧй£G¢ªª žžžOOOTUUáèÑ£õî—³³3€Ï‡|Þ¿B1zzzÐÐÐ`Îÿ°; ÃîD(+ï»wï~ï¡üø|o þ¿€§§'¹ººŠ•‘ämï^h(­þÊ } í5ø-×öø§ëó7·úFªGGG‡š6mJW®\¡+W®ÐÎ;ÉÕÕ•©©)Qjj*IIIÑÿýßÿ ÔŸ6mq8JLLÙ~m¬^^^€²³³™2¾oРABò¤¨¨H%%%L¾²@Ÿ:uŠÐÌ™3…êO˜0Ptt4­_¿žPLLŒØ±JBvv6šššeOêbÎÈÈ Ô±cÇZõEcÇŽ%YYY@ÈÉɉNž<)àÿLÔð>УF"‡C§NbÊ8@è÷ß­ªª"MMM’——§¢¢"‘íÕÅ}ôèQ@7oÞ¬QÎÆÆ†PJJŠ@ùáÇ M:•)ãÏ]¡vjòæ¯LžuêÔjëDFF ).|욬»|?à˜˜‰rO÷èÑ=zô@zz:‚‚‚pøðaLš4 \.&L[ÿÓ§Oèß¿?~üˆ´´4‘ÊumsÄóçì“'OÄî¨ÉŸûüÍD¾DÔ8%EYYÆ CJJ fÏžU«V1ñ%%% ¡Ì3|±uëÖzo Á±ÿró蘘¹‘’’‚¢¢"vã–üTªªª %%õ½‡óCÃ6aaa˜>}:BBB¤½)ë×cèÿcïÌâ*Ûþ@•EdWPQQwp)T\Ss!­L+3{ˬ޲^3­\*Í\r·LQs+ÜýÄTÄdÖóý1ÎÈ03²èó»®sçÜçyî3sæážûÜK­Zt/…'²´L=›/xTÞçÒ “ɘ8q"ÇgÆ =z”ñãÇ—ê Jnn.—/_¦_¿~jƳ$I%–7 ÕØ÷ï¿ÿbddT¢‘áéé  ÑdCõêÕãwÞáðáÃÔ­[W¯Ä¨ììl DXX+V¬`äÈ‘¥š³¼øä“O4¼º…ÉÏÏ'>>^çXÉÉÉÔ©S§|”CQºmðàÁ4iÒ„Í›7k½ÜÝÝÅ{\”ÿýCCC•LycggÇ„  ÃÃÃCõÞáææ¦êêX˜ÐÐP ôþÂbllŒ¥¥%©©©ÇJsÏ*Å¢ŸI’Ø¿¿^º”ć~ˆ§§'óæÍS%çmÞ¼™´´4æÎËÝ»w5¶ ––ÆæÍ›Ë4÷áÇY¾|9M›6U ïéß¿? øR\åßÊãU‘D¨‰H"TgÏž=L:•cÇŽU¶*Ua@WÎÎÎ,Y²¤ÜjØvíÞ‡~ýxóxŸ•X[[ÓväÈ õ>—–×_Þxã d2o¼ñF©Î¯Q£öööìß¿_ÍxX¾|¹ê±½6öîÝ«–‰¿uëV®]»Æ˜1cJ¬ÏÐK)Ÿ““}ú0dÈvîÜI@@€ê1?(âxOž<‰ýõ;wîdÊ”)\½zUmœ«W¯òÕW_hxŸ–¤¤$úõ뇉‰ ;wîÔ¨;­dôèÑØÚÚ²~ýzµp”Õ«WsçÎÞxã µ¸\m¯QNNŽjŸ.:¤v«|çεy¦M›†$I|ûí·ª÷3--Å‹chhÈÔ©Sõz èÞ½;'OžT…c(úH¤°°0•¬²’ÁçŸ.YZZJ¯¼òŠôÒK/I†††RóæÍ¥øøxµùÐRúܹs’‡‡‡$“É$///iàÀ’ªö°²ÂÀ¸qã$™L&yzzJƒ–¼½½%ccc©fÍšÒ‰'J¼Î³gϪªW·ÎÐWfí} ïÞ½[â?üðƒÎ×\’$I.—K}ô‘d`` ’“““Ô¶m[©fÍš 5hÐ@:pà€$I’´eËÕø¶¶¶RçÎUµ²iðàÁR~~¾jì²TáX¹r¥HöööRëÖ­5¶ ¨Îýûï¿%KKK©víÚR¯^½¤6mÚH€äãã£V©¥ð<Åmºª LŸ>]¤¦M›J ºwï.Õ¬YS222’víÚ¥’ËËË“FŒ!R“&M¤W^yE²µµ•ŒŒŒ4Þ]]4W¯^-™˜˜H2™LjРԡCÕ±K—.I­Zµ’©uëÖÒÀ¥Î;«j¦'$$¨d÷íÛ'™››K’¿¿¿Ô¹sgÉÎÎNšÿüs:uê¤zʤmpssS{ÏLLLhР 6ÄÍÍ­Ä®¦}ûöåĉlÛ¶sçÎÑ£GüýýK°)ª2I*¦g³ ÜP¶õ-©„>2MòóóiÕªr¹œË—/W™rQ@PYܸqWWW®_¿Ž‹‹Ke«#¨†›D7"º‚ÈÌÌD.—“——WÙª<¤¤¤ÉG}ÄÅ‹™?¾0ž D¡&"‰P¼¼<är9™™™•­J•GÐÄ‘#Gʵ Ç‹ÎÆñòòbñâÅŒ7ŽÀÀÀÊVI ª4"‰P‘D¨Ž² Ç‘#G*[•*pÙU½{÷fÉ’%•­ÆsÃàÁƒiÓ¦ Í›7/¶r‚@ ž Çk":ª@@@€^Õ‚^t„-¨–ØÙÙ=“VÎ@ º!@ A)t‘]Ù*àF$j"’µ#lݺ‚HLL¬l@ð#’5I„Ú6‹n„]Aˆ¶˜@ ¨LD¡&"‰P;ÂfÑ0 +ˆëׯ³qãFΟ?_Ùª@ hpþüy6nÜÈõë×+[•*0 +ccc¬¬¬055­lU@ 4055ÅÊÊ ccãÊV¥Ê# è ÂÆÆ†¾}ûÒ¸qãÊVE / "‰P‘D¨NãÆéÛ·/666•­J•GЄÈ×ììlär¹j“$é©Ç’$Im¬’6}3Ž÷îÝËÛo¿­÷ûùöÛoóûï¿?õ5”I’¸}û6'NœàÆäçç?õXåÒŽ^’$nܸÁéÓ§yøða‰²r¹œèèhŽ?Nll,¹¹¹2ùùùÈår½®-44´Tï_y››[æ{ùyàðáüýöÛ¥z4œ››Kll,ÇŽãòåËddd”(ÿþ}N:EDD ZerrrJý¹^I„šˆ$Bí›E7€® žE@~TTT¹Y”‹/–Éè*- 6ÄÌÌLmkÚ´)3fÌ %%¥Tc8qBc¬â¶¦M›ê5æÙ³gYºt)ÉÉÉzÉ/]º”°°°Ré]dff2iÒ$êׯOýúõñööÆÕÕGGÇRý³¸xñ"ï¾û.>>>Ô®]333þóŸÿ<•Nqqq`nnŽ««+:t nݺ8;;óý÷ß«GIIILœ8 š7oŽ7ÆÊÊŠ>}ú¨ñË–-ÃÌÌŒ5kÖèÔáܹs¥zÿž–M›6ñÚk¯Ñ¬Y3LLL033ãĉÏtβ——Ç'Ÿ|BHHÈ3›ãÂ… ,]ºT¯Ìr¹œÏ>û kkk7nLçÎiÖ¬VVVtéÒ…øøx5ùýû÷ãåå…;v¤M›68::Ò°aC~úé'5ÙŽ;ª>÷¦¦¦˜››ãééIÿþýY´hÑsmT‹$BMD¡vD¡nD'ÂjJff&Ý_y…£û÷ëmü= £Þ}—·ÇŽåí  g6GQêÔ©Ã_|Àµkר²e sæÌ!,,Œ#GŽ`hh¨×8ÎÎΫíûûï¿9rä“'O¦Aƒªý–––åwU€G±téRüýýùôÓOiܸ1aaa¬Y³†÷Þ{&Ož¬sœ“'OòóÏ?ãî·÷S aìØ±äääðÞ{ïѱcGˆŒŒ$,,Œ?ü¦NJ~~>Æ ãÀÒ»woZ·nMBBÇŽcíÚµäååadTu—¯•+WrèÐ!Z·nMÓ¦M«ü£ó¼¼<¾ýö[&NœH```e«Ã”)SX¶l/¿ü2têÔ‰äädΞ=Ëš5kxðàêó{òäIüýý±°°`êÔ©ôèÑ bcc !""Bc|###¾ûî;ÒÒÒ¸vígÏžeÊ”)Ì;—Ÿþ™þýûWè5 ‚j†$xæŒ7N7n\™e 3#8X2üï¥>£G—M¹;tHªóöÛRÓ>}¤¼¼¼g6Oaìíí¥† ªí+((š7o.ÒÖ­[Ë4þûï¿/Ò±cÇžêüàà` .]º¤—}ؾ}»êÚ¬vþÙ³g2dMš4ÁÑё޽{³oß>­º¯[·Ž.]º`gg‡¯¯/›6m*ÕuK’„¯¯/fffÇe2™Ú=píÚ5€b=Æ5jÔÐ{îáÇóÍ7ßššúÔ¡JU™ªþ$¤2I„ÚyžC™Ê a@Wå?káBâýüãÄ„¯¾":3³\·Õûöq¥^=05%륗X°iS…ÆBE™lfjjŠƒƒ¹¹¹k$•åçç3{ölär9ŽŽŽz}ìØ1vìØA=øòË/:t(ñññ <˜eË–i=gêÔ©lÙ²…aÆñÖ[oqæÌ:wîÌñãÇuηjÕ*:uêDhh(Ý»wgܸqœ={–ž={²~ýz•Ü… èÒ¥ ëÖ­£S§N3fÌÂíÛ·ñññaÑ¢Exxx0{ölÆOAA§NRÉ¥¤¤Ð¦M-ZDûöíùì³Ï077gÊ”) >\mÌ›7orðàAÞ{ï=~ùåÚµkÇäÉ“qppॗ^ÀÉÉIe@wîÜYuîúõëéÔ©ûöí£[·nŒ?ž¨¨(úôéÃÊ•+ÕæY°`£G&77—?þoooÞ}÷]6oެ׵»¸¸ “ÉØ±c‡Öð‹¢(ï—ü¹\®×%1iÒ$¬¬¬´~¡­îˆ$BMD¡vD¡T®üÅ ­¡_AAÖëÓÅÌ™3%@š?~©Ï}šŽÜÜ\©F’»»{©æêÓ§H2™LêÚµ«ôÕW_IÑÑÑZeË;„ãüùó’………äåå%¥¥¥I’¤x%ooo);;[%{îÜ9 |}}‹ïiB8–/_.ÒöíÛ5Ž~ï?üðC V¯^­&3~üx vìØ¡Ú§¼w›4i"¥§§«É—¡ ÁiÚ´©”••¥ÚŸ-µm¥X¬E IDATÛV²±±Q…ù<|øPu~"""$@ï×á½÷ÞSÉ·jÕJš>}º®UöܹsRÍš5%@²´´”ÆŒ#-_¾\ëçZ’JáPÒ®]; RSSuêZ!‚²"B8t#<ÐÕŒÂÞg#FÀÆå7IT4i…š¾È»u«0/tRRAAAѵkW6lÈÝ»w1b=zô`ذaØØØhçMŸ®~ n],-,øÂاrhÔòù¼yľû®úN™Œ¸Ç±ÐϺ"GNNŽêÑ­……cÆŒaàÀ 4H%cbbÂ믿μyó¸vínnnܸqƒ½{÷2yòd5ãV¹¹¹Ì™3‡ß~ûøøxØÑ[·náâ⢶¯gÏžãôîݛӧO‹­­­Ö¹®^½ ÀüAHH’$!Iª0•èèhºtéÂÀqqqaÒ¤I|óÍ7 4ˆqãÆÑ®];½¯MɱcÇ ¤Y³flß¾ƒŠùîloo¡¡!÷îÝ+Õy2™ ???üüü¸wï‡â×_eÇŽìØ±CþRždff2`Àø€‹/²}ûv–,YÂ×_MHH§OŸ.U,´òËPáJ=Á‹@xx8áááDEEáééYÙêTi„]A888X¦î>Z½ÏI:”=?ÿÌ?k×>õø&ÙÕUÍû¬$«kWÌË›cÆ”9Aª$œœœôŠ}œ8q"óæÍcÙ²eóÛo¿QPP öU>ùä¾ÿþ{^{í5ºu놇‡¶¶¶¬[·ŽY³fiM¶³³³ÓØgoo(bêŠCip4nÜXk ²···ÊSgffÆÕ«Wù믿X¾|9K—.eÑ¢Ex{{³~ýz\]]õº¾3gÎðÊ+¯àääľ}û*´Ã”±±1nnnÄÅÅ!IÒSyÎíìì:t(C‡%88˜O?ý”Ÿþ¹\ è‚‚FÍ™3gضm^^^jÇ•5ª•ïqa”ûÒÒÒÊM€ãdzk×.–,YÂÚµkY¶l-[¶dåÊ•´oß¾D”íxµéTZ£PyÏ6jÔHÍ­¤cÇŽ4kÖ xrï—ôù(-–––ª/S[·neðàÁ,\¸PëçÜÀÀ€–-[Ò²eK¦OŸŽ»»;çÎãðáÃZzmH’DLL XYY=•ÎU•èèhÕ{%P Ì ]‚4kÖ >\ÙªTy„]Adee•iáÊÌÌdCXy3fh¨[—s\¾|¹Lu¡?ùáNœ¨ý`z¡õ¡Q£Fª$¦/¾ø‚åË—ãããSêoÍ«W¯¦I“&üþûïj_ JªªqàÀ5o (B$àIè…6”ïÍĉõÒÓÈȈW_}•W_}•äädV®\ɇ~È/¿üÂܹsužAïÞ½©[·.¡¡¡888è<§¼éÖ­+V¬`Æ Œ5ªLc >œO?ý”Û·o—“v >þøc¶nÝÊ¢E‹ð÷÷×8®|ªq ¡¡¡åþX&“áïï¿¿?éééüù矼ùæ›Ì;—?þøj×®MXX˜Æ—“‹/’@ß¾}õž«8”÷ì„ èСC‰ã(ïýЪU+µcÊÏGYÀÌÌL¯Ð CCC ÄüùóKu¿¬\¹’‡2dȲ¨Z%™3gŽh¦Re¡h¦¢ÀÆÆ²²²*[•*ˆ® ÊÚÕgÖÂ…Ä»ºÂÅ‹Ån‰Íš1éq’§áÀáÃ\ÌËƒØØbçȲ¶&ø—_*µ"Ga&MšDbb"o¼ñ L,Îø/†ÜÜ\’““quuU3žÓÓÓù÷ß‹=¯¨1››Ëprr¢aÆŞ§Œá~š¬ï:uêðÁàààP¢nJÎ;G¯^½¨]»6¡¡¡j! EQzažEæõ¬Y³077ç³Ï>ÓOŠXß½{÷ŠøâJ(mÙ²@k‰¾§eÉ’%ÌŸ?Ÿ÷ߟw‹†.=ÆÇÇ@ãu—Ëå>|GGÇ¿8••Úµkóúë¯Ó®];µ{ÏÛÛ›{÷îqîÜ95y¥žú†‹™˜˜Ð¤I­UOºwïŽL&ÓëžUVî(úùHOO׫B (<ù©©©ZíÙ³‡¬¬,š7o®ÚWÜ=•——§*E©ïý²ÿ~>þøcÌÍÍùþûïõ:§:!ŒgMD'BíˆN„ºèj‚gãÆÌ67‡"ñ¹jÔ­KM__ ž.ÆU’øÜϯä9ñcyøða±q¾I@@õë×gýúõXYY•*y5bÛ·oÏ¿ÿþËo¿ýÆÀIHHà£>ÂÜܹ\®Õ;wþüy¾þúkÞ~ûm222øä“OHKKcÖ¬Y%†·téÒ…±cÇò믿"“ɘ4i 6äÞ½{DDD°råJ6mÚ„……_~ù%¦¦¦ôïßnß¾ÍúõëIHHÐÙ%-!!___“òóóÕæCK'B¹\.}ùå—’‰‰‰ÚùR—.]TÝ×>ûì3ÉÈÈHMÆÑÑQ7nœªlXqœ={Ví]unRR’¨vÜÔÔTúé§ŸŠ§¸íîÝ»%ê¹`Áû£nݺҰaäÄÄD5Ù]»vIvvvj²íÚµÓèD©«‹æ¹s礱cÇJõêÕ“ÉÁÁAu,;;[š5k–dff¦6L&“¼½½Õʽ%''«Ê*7iÁ‚z•±»}û¶Ô«W/ÉØØXãusttÔ¸ç¾üòK•Î…7CCCiøðáÒ­[·Ôä[·n­ñ¹«_¿¾ÔµkWé믿ÖùÙªˆ2v‚²"lÝÈ$I‡»QPf‚‚‚ÈÎÎfÆ %Ê€xĦ,Wïö'Ÿ|·ß~ËùóçiÑ¢E‰²iiidddP·n]5aff&§OŸ&++‹6mÚ`ggGVV©©©X[[«Jredd––†­­-2™ŒK—.qëÖ-Õ9EIHHÀÌÌ KKKcYYYDGGsóæMlllhÚ´©F‚_FFçÏŸ'11‘Æãáá¡×k’——§³|—­­­Ê[žŸŸÏýû÷111¡N:*™‚‚‚+hXXX”ªâ À£G¸xñ"III¸¹¹Ñ¨Q#µ’~J>|È•+WHLL¤víÚ8;;Ó¸qc'Ê÷ÉÒÒ²D¯.(ÞçG©®]ynqÔ®][UÊNÉ;wˆŠŠÂÊÊŠ–-[j- §œ§8ìììt>%ÊÎÎæüùóܹs‡zõêáååUì9¹¹¹\ºt‰øøxš5k†›››ÆëTøÞÕ•\Üç0++‹Ë—/Gݺuqww×z߃¢zÇÕ«Wñôô¤Aƒª×ºèg¯8ÒÓÓ‰ŽŽ&!!###œiÒ¤I±ç^¹r…[·nñèÑ#iÔ¨‘Ö„Ù¨0Éd2¬­­KU¡£:pãÆ \]]¹~ýºZ!‘D¨‰H"ÔÎÈ‘#111yám’’tÄÍ›7UÉFÅÉ€0 K‹Ò°ìÓ§ýõWe«#•NqtPPøS„””fΜ)⠋гgOœÅýR"º‚ñtåËéÓ§Ù¼y3Û¶m#//yóæU¶J@P¥Æ&"‰P;ÂfѨÂ!¨–ÄÆÆ²mÛ6ZµjÅÖ­[õ®‰,@PV„ºdddpæÌêÔ©CË–-+[emd@ ‚ŠFÐzòàÁ¼¼¼èÚµ+éééÄÄĦwcŠâêÚ @Pˆ$BMD¡v²³³µ&w ž B8ôÄÒÒ’+W®°aÃvìØA¿~ýøé§Ÿô>ÿY4¨@_æÌ™SÙ*T9”ê›E7­'FFF=y¹ KåUù@ ¨LD¡&"‰P;ÂfÑ0 Ÿ‚Ó§O³qãFNŸ>]Ùª@ *˜çÞ€–Ëåœ>}šÓ§OsåÊ7nÌ´iÓ´Ê8p€µkתc|øá‡…ø/^¼Èˆ#غuk± @ Ê›{÷îqæÌ½dÛ¶m+ì”gÈso@‡„„0räHìíí‘Ëåxyyi5 ·mÛÆ°aÃ6l½zõbÕªU„„„päȬ­­EF`` kÖ¬¡]»v¥ÒCŸpÐÐPK5nuF’$niñº‡x]úò¢ß+™™™Z÷‹$BMª[áÑ£G:4ƒö%ÊœfófùSÛ"‰P7ϽíëëK||<õë×§oß¾Èår­r3fÌ ÿþ¬[·€1cÆàêêʲe˘>}:< GôîÝ›ÈÈH"##qvv¦_¿~zé¡+ ¿OŸ>XYY•îâª9ÄÛÛ[|H‹ðÏ?ÿЧOŸÊV£J‘ÍñãÇyùå—+[•*Ell,5ªdMªbm,,,ÔöÍ™3GÄAA™DXâ óó»’Ÿ¯Ë06-Ó‰‰‰"ZϽmkk«SæÌ™3\ºt‰I“&©öÙÛÛÓ²eKÖ­[ÇôéÓ‘Éd¼ÿþû€¢õ' òL냮qÔ¨QŒ5Jïñ@ ( ÂxÖD$jGϺeì€ëׯТE µýžžžªcÖÖÖ|òÉ'jÛðáÃõžcË–- 4ˆ   µ-::ZMNìûÄ>±OìûÄ>±OÛ¾Û·o£N­eŸºœ®9‚Û$7fРA„‡‡#(™$IRe+QQ(C88 ¶Ñ¢EL™2…»wïª5Fùᇘ6m=ÂÜÜü©ç Ä·@ OOHHƒè áaëVž:ZØ-ºhÀØØÐL¼ÈÈÈ Fež#--”””bc°_DbbbÈËË«l5ªE½E¢OLLLe«QåHJJ"))©²Õ¨rˆµE;bmѤº¬-ÁŸÂÂ…Ïv¹\NJJ iiiÏv¢ça@NNN\¸pAmTTÖÖÖ˜š–-àäɓ̜9SÃûý"³xñbÒÓÓ+[*‡è¦‰è¦ððpñ¨U bmÑŽX[4©ÊkËÕ«ðÃàë 660|8„…=Û98ÀÌ™39yòä³è9@„p ˆnÔ¨ÁÁÁLŸ>]µßÃÃ;;»2½âQˆ@ Ï'ú>Y.ÚѸ(¹¹pø0üý·b»zUS¦fÍËEGeóÜWáÐWWWzôèÁæÍ›yÿý÷155åĉ\¹r…3fT¶z@ ª )))¸»¿„©içå rèÞÝ‚µkÕ+~Ü¿»vÁΰw¯"T£(îîп?Àƒ0thy^àiyî èøøx:tè@rr2’$©ÃÃÃUÅÓ/^LïÞ½iѢ͛7çÀ 2„#F”‹áááøB5KàyÆØ¸'ññºJᥠ—Ï 2ò‰—ùäI((P—¬QºuSÌФɓc!!gÑ]çù,Ц4—ñxüBBB§k×®¥>ÿEâ¹7 ­­­‹­ñhoo¯ú½yóæœ9s†]»vÇ;#ŸŸå&Þ¡Cñ(¤111¸¸¸”øHëEDt Ó¤ºu «(” „666•¬IÕB¬-Úk‹&¹¶œ= À­[šÇll _?…ÁìçEzà¨èС«WËE?ŠœœlnÞ¼ (j7+›5W9KƒÒÉ7räÈRŸû¢ñܯ.µjÕÒÛ‹lgg§Šû)otu"|Y¼x13gÎ|á:0êBt Ó¤:v «” „â©–:bmÑŽX[4©ÈµåÚ5õ¿==sÿþЩè㯫W¯cÆŒ 55•ñïçôÍÓ¤ÖIÀò°%v`ù˱´´,“®¢¡n^¨$ÂÊ"((ˆ›7o2eÊš5k&<@ <¤¤¤Ð²åLnßÖÂah8“>}~T…f<­}š––F§W:q¹Íe lÔã? ’ p?ãÎñ]ÇŸÊˆŽŽŽ&::š… âìì,¾p•€(c'@ð”äçë'׿¿"apòä§7ž^Ÿò:—½4g€›®´½Â„©ž~^º‚pvv&00PxŸ@ ¨"Pc•JÞаìúfffr*Ƴ’›NÅÒh§Íš5#00P„oèÁs]UÈÎήlª"ÑG;"ÑG‘D¨‘D¨±¶hG¬-OØø×Fþ·è¤¥‘•Ÿ……dAË-Y¹`%ÖÖÖÅž—™ k×ÂâÅUè€ñ°Cã Îã}pä$œ\6–ZG ‰„ôâR⸑rƒ¸Ô8N?Áó;:ÏM­“Jdd$>>>¥ž6‹‰‰‰nÁ±ºT±±±DDDààà *£÷¢#}´#}4I„ÚI„Úk‹vªÓÚ’——§w—Íúõëëõå:??Ÿ¨¨(¾þþkB…’Ú=²Ô7‰kÉ×híך_¾ü…6mÚP¯^=Õ¹11ðóϰr%¤¤<ÓÄ ÒÈrüÆÜY¡ e@×Tp9 ›ÞÕô) ¸“vGeßH¹¡f,ǥđ_Äùï|.3 $$$‹‡‡Ç³›è9@$VAAA\¼x‘Q£Fáíí··we«$‚€üü|æ-žÇî°Ý¤f¥bifÉ+=^á?ïþÃòˆ)xF¤¤¤Ð¤Ép22FéÌ$ à ü¡ûËuJJ .»ñ¨Ùu¤ÞÚ…²Àpy}†ô̆ ؽ[ámÞ» [K..0i š‚GçVd‹‡š%Lþ·9»ñÒˆ¶*c9>5žÜ‚\zÆÜÀù9¹þ%Ÿç|ЙK_¢fÍ’”Òäøñã?~œõë×ãááQm¾pUÂ]Axxx0uêÔÊVC /ׯ_gÐøA\nxyk¹"ë©Nœ=Á†^غb+®®®zwçÎrrrtÊãääT͘˜x”¤C*…‚‚™z™e|É·ãÀ òÛ%qðÚi{oâÞC9ew²Y¸¹ËiÕ. ÇY\ÎÏâÓÿË ß&¡dãÀ3ؤHb#"K³6³ÆÅÊ…†– ?­ªý^Ç´C¯ eëý­ÅÆA$ÐÞ¹}©g@å䋈ˆ(õ¹/€à9£  €¡o %²S$˜:`rW9‘‘ }k(§öžÒ»aXïÞãyð@÷Ôºusáž§ÔüÙ!I¦rÝå\å$JG¡óQõóX 6P¶vÈGwS@PÄEÇ€}-{Z5Ô0’•×6®­s¨• WrÉÿÑ­¢5Œhƒû4hÊŠ+ôPJP„]Aˆ$BMD¢vD¢&"‰P;"‰P;bmKpÁ邺ñœ(o3¸àtKðÁäô³F gê”sp*¥¶eãÑ#¸pd²'¨ÿO.{/ÿK~­"•) P4õ+œ7h ãh—!ì†fFf˜™bVÃŒx)žlJþ/K’ñSÐOL?é)¯ò æææßyœ×§¼Î©‹§Hµ~ÜHå¡¢‘ÊÊÝ+177/Ó"‰P7/îêRÁ\¸pÑH¥"ÑG;Õ)ѧ¢I„ÚI„Úk ì ÝEvó"F]8PèVÉvÎfWè.½ èò@’ 5’’àÁÅÏ¿+&$À½{EϾÜšRØ|ù÷_hÙRËd†¹àºZü ÍBÀ졦÷98 ô-´ï–2|Ø>y‰ÊH632ì†&†šFåðÈáüùðOu#¼Ž7ñïå_¼@)177góÊÍdff© iݺõS…mFÙHåÂ… ´mÛ¶î~bŠºñ\mHËNžxØêW…béwK9íšë=¯C Íã&—Mç;î™ÔV®Y³æS—ª+ a³èFЄ²‘Š@ ÏkskÈE«A§"¢Dñòª—å9Š¡C©kV·Ìsß¹sæ”þ<##¨[llÀÒΞ…,ÃÑàó7tI-$yRoÀFHXO'Ÿz½¹‡£©r*-„ô|u£¹†ÌÏš~xÕðgKÄYRä‘Pë$Ô/ÒB0±…¤Ù©Ç?—D:uØ´h㦎#Æ%†Ü¹`<‡Ëø7ógÖ§³Jÿ‚Tʧä!!!•­J•GÐ@ ú0Ês› ¤VZ%œüd'Àä4d·©…c ÃÊŸº~/q“’šÆÕôxãù1–@Ð ØÀÙž™œˆ[¤vØÔÈ¿F~ k1Œþîý±0± %%…½ß]$%e?lœõ÷ƒG’«}£œ·…‡K¡À Ð߀èЮgþ=ÃòÕËÙt?)i)x6õdlðXÚµmWª±Õa@W"‰P‘裑D¨‰H"ÔŽH"Ô΋º¶ää2ûðlfžMNý88Ê|²ÂI„iàõÈ‹>ÿ鯍 Ä?RÔ$Þyu';¯î¤VZ h:€Qž£ðkìG ¥+[ÓÙภ<Á^‰¦ewßù§´lY¤KßS’nxz<(^ÀhùœxÀUa4¿Òø†µF€{æÆÅ%Ò™Aújˆ¾ —aä Z£pÙ§s^ɘššòÎ[ïðÎ[ï<ÕùU ‘D¨›ku©DË?άº#}´#’5I„ÚI„Úy×–“·O2aûÎß;€Ì€7?y“ˆß#8oqžŒÆŠ$Â~P+¦­ÒZ±õ÷­ØÛÛ3§×Çf}Ôzþ¼ø'³’‘›Á†óØp~ÖfÖ õJjD¨5zÿ^…j)דCÛ8ˆ†ã(M–¼‚<Ò¸›~—;iw¸›v—»éw¹žt\ë$Ý%çêƒùÅ:üúŸŸ pÐY.7÷>P¨Æ±d y±(Ì¡ w¦éÏ)‰‰‰Ï$fûyBt"¬‚‚‚¸yó&S¦LU8@Pndæfòß°ÿòãñ)5[ض`ùÀåtª× €ÕV³uïVîÜ¿ƒ“­ƒüí¢T IDAT1väX­ãåä²7f/ë£Ö³íò62s •}» \2„^ùZÏ ÄŽÆ™í¸xi é *£øNÚ #ùNÚîgÜGB‹’ü Ôñ<€!©CÙüûŸ:^Õ¹s!Wæݺµ¡wï—u VÊóI”² ÇÂ… qvvΜh@ ª!¡×CysÇ›\K¾€±¡13ºÎàÓnŸblh¬’;rl±sQjÔ À=€÷2r3‰á×cë9tûˆÊƒn%Ï}î{`/&ÿ3Ñnë‰umk2ÉD޼D9ãDcÆŽ£×˜&&&ü÷¿Ï_Gàéß~ ÀòᆱdM^,„]Aˆ*@ (R³SùÏ?ÿá·3¿©öuª×‰ßüFK;m‘ŸŽZ5já–þçg¼ò$°m µ–|RM²µ·˜–!æ¦ ŽæŽ8™;áXÛQëïµ014á[“oùúÄ×d6ÍÔ:ùàrÇßž¾e¼ÒêKRRûbbT¿—Õ -ªpè0 +‘D¨É‹šè£ ‘D¨‰H"ÔŽH"ÔNu_[222ˆ¥Q£FÔª¥^ cÛåmLÞ9™;iw¨Y£&³zÎâýNïc +9`¸´kËîÝ0t(dfØÐ°Nsâ¤# +á$IÑ®ú­—ÞÂÑÜÇÚ dsGj;JFÔÍô÷§³;p7Gï%שHÜE.؇۳hæ"ר4T÷µeú·ßrsÀÕïåå…I„º©ž«K5D$jò"&úèƒH"ÔD$jG$j§¬kKLL ·nÝÒK¶k×®åf¨Ÿ<}’·g¼ÍCÙC2,2¨õ¨Ö’5K‚—àÒÜ…)»§°éÂ&•¼¯«/¿öÿ·:nz_šµeíZxýuEÓCCXº"®¶añý#`W‰÷`X·a|Ýãk½æÑÅ?üûÓße÷Ý<´{H–IuÕÅ&ɆU?¬¢S‡Ne¿:¯-*ﳿ¢Ãá¾íÛËÅ "‰PDaˆs@P>|*ÿí”ܹV­õ\½úG¹8–­^Æ«¿ Á;A½ùI.X±$Ï. Eå +S+æõ™Ç„6Ê<¯6~ü¦MS´Ý65…  0îß¿O§Á¸î{]»Z×ý®œØr[[ÛrÕéÑ£Gœ:}ŠøÛñøtôÁÝÝ™¬$WøóÏ„>b…‡¸º*v\¿Îø‹ËÅ -ìÝ´@ …((€¬¬Q@Ɇ±µuD‰ÇõåÎ;Ìúm ¾ šk@j÷TêÃ ŽƒøÉÿ'k;–ËÜE™1ãIA ؾ^~\ŒÂÖÖ–o¦~ÃÇ?}Ì­N·Ô»hˡޱzÌùpN¹Ï ],^èXç¢$%%±»÷WWöíØQn^hAÉZ ‚Jdæ¼™Ül}³d¡nÐëa/¶¼ºå™èŸ'Âò势ííaÏðòR—9d$í[µçõi¯s'ýòšrL3MqªíÄÊe+iÒ¤É3ÑO ÎÄÙ³¹;P³ÆßÍødî\~›;·´z±ÐU¢\PN:tˆ   ‘ÙZˆ˜˜òòò*[*Gttte«PåÈËË#æq¦¹à IIIªDBÁªÛÚ²ïPè“ÅQb£®—ižâÖ¹\‘,¨4žÝÜàÈMãYI“&MßÎÕ}W9þËq®î»JøŽðjij=^ØL²\Ýè5õá¤}¼Þ÷s!¼1\œ©Pÿ!ܲ†‹Vðp:ä¼Å•+ABÆŠü±† Ÿü^·®>¯V4 ¾¶ØÙÁŠúœû|RÕ×–¸ÇÆsücâ %'Óp-æœë@«Þ½©W¯ÞSÏ+:êFX.„èD¨‰èD¨щP“êÜ-ìY":Çb Ç&$&B÷îêR2øø(Œæ¡CñË H¢ƒp0ˆº?€G2¸¦AªD[A|[HŸFNΓn… ¬Zφó¸õH½‹amãÚ6 ä5Ï×èåÖ ##òó!>^aÈ)Â)Èy ^‡„ `ùž@ ”U²²`Û6íW\«–ºa}ïž6©9À*µ=ŽÏ¦œtµ¡*¯-ñÙÙôˆˆ N.àÓ† ùÊÅ…ÛßÏì£Gù©˜n™óê×çÓï¿/ÓÜ¢¡n„]AˆQ“ª¸`U„ñ¬‰•••¸_´ çâмW”Uíd2èÜù‰Ñ¬ÍI÷ᇣ°qXǺÿ[Izï4P…6äAë$ îì£á©kŒŸð& #²éâ&.Þ¿¨6F ƒômÜ—Qž£Ðt5k¨‡˜*Œ]¥÷ø‰-Tð‚|Ír ãøÆ HKS?–‘/*¶âYUÒÁ’ªº¶Ü~l<_l<ììÌ7;Ö«Wãöí¹vëE›¸_Œ;w.“÷„Í¢€Ás±1|ý5Œ­Ýh.Œ§§¡çv“Þ'Mk±×§âš^ã¿»¦C¡r»2dtuîÊk­^c˜Ç0¬Í¬Ëõ\] ÉÉ CúÆ ˆ‹Sÿy㤤”ëÔ‚ änN=##‰ÍÊ`Zƒ|ëVÈT¾Ÿÿ\¹Â7ÀÏEÎW¿>Ÿ•Ñû,Ða@  Ú¢o¹g[[E§=}"Æö‡î'Î)®ÄN Í ` ÐZÙ·b”ç(F¶‰³eÅxîêÔQlmÚh?îé çÏWˆ*‚r$1'‡ž\ÉT$œN©_Ÿù…‹rß½ ¾¾Ô»t g¥i} 0)ï³@?„]Aˆ$BMD¡vD¡&U=ѧ²xQ“óóõŠþþýW›„faiø'ürìuwE±¬eÉ7àU¯˜–}z’›{[[Ý ”rsµ6kE{E Í$ª´¶Ü{ìyŽ~l/'ï³H"ÔèDXA8p@´ò.‚èD¨щP“êÖ-¬¢xÑ:&&Â7ß(B Òf@k×n%$ä N9øí·`ÌÌÌ8|Xámþë/E%––`k;•˜˜™ ‹ë àšnIR®ÖÄ9Ÿzõ>æüyõª?çïgwÌnv]ÝÅ‘›GÈ-x<ø à>С%ó¡ýÉöœÚsª´/C…àåDdä*r­[¡[Nðìx˜›KÏÈH";”&8:²¬iÓ'åÀ££žç;wO›óçpûömþשÿ=q¢ÜÂ7„Ý¢ñì\ BHÈAþúë ä^ÑNN3xø0›íÛÍøåˆŠR?îå“'Ãk¯Áرsm!4ú†%ƒ±R* :§AìØvììF¤ç¦s ú»cv³ûênâÅkÌmnlޝŸ/gž%.+Ì´ëX÷l]¾œúe©_ƒŠ¢ysKÌÌ‚tʹ¸ˆ¾Ý•Ir^½ ÏAüêîþÄxŽŠ‚^½žÔ%üì3˜5Ku~½zõ0ëÒEõ» â´@ *KÔ›žh’šjB³fê ‚&&ŠÒs“'+j7+™6mÇb‡r§r¡rs…h”‡‘_4†÷q[âöÄË\ˆæ6Íéפýšô£[ÃnÔ0¨Áå6—é7¾×Ú]ƒÂMJ$¨s¶ƒ" o@©®¼"Ù°aAe« ÐAj^}"#9ûøFmoÏò¦M1=6Ÿÿïÿ Oxø¸Ï7ßÀ§ŸjŒóQu£Rt!’5I„ÚI„šT¥DŸªÄóšDø8 PÔIž8&LPTÒ(JrÊ4IR7ž¢°ÑgùäµÈ%ñ¯ðTü]³FMz¸ôPÍ.V.ã6mÚ”S;N1î½q\:‰ŒZÔ× vNm¾˜ò#†Œ(Ÿ‹­@ÄÚ¢Ie­-òòèsî§ôigǪfÍžÏGB¿~šªøûûïჴŽõ,<Ï"‰P7"‰°‚5I„ÚI„šT•DŸªÆóœDè뫨´ Ÿ|¢ÝxEÅŒl‡"Š“h$Õ0âvï°ûµÝ<øøú›É&k5ž•X[[³cÝ®î¿Jøáœÿë<^¬–Æ3ˆµE•±¶¤åçÓ÷Ü9N>zÀ0[[Ö4oŽ¡Òx>püüƳL¦H(Æx~V›E7ÂõWAˆ®>šTÅîOU‘´¡IUíVÙT·N„›ªéÄÞ6oÖ¯f³©‰)mÒWSÎÙ™…ýb`Pz¿‘L&£QáZ¼Õ±¶hRÑkKF~>ýÎãØcãy°­-ë=<žÏ{÷*JÌde,_®èõ^Á›E7€‚ræÆüñǽd‡ï‹‹‹Ë³U¨’ÈÉðp…M°gœ;§ßy¥‰êêÕ¥‹YL¦}fñBXÕ°z*ãY (/2óóñŠ"üqXÆ@6zx`¤4ž·mƒáÃ##X½FެD%! h@ (g"""˜>ýð’ÉC¸»Gèe@‡‡‡óÞ{ ©Q£f‰r¹¹™,Z4…®]»ê­oy«0–÷ì°0õxægAÏ=qv'";Š Ù¬s®Ó'M¶Š%UP@@T÷X¨[—?<<¨¡4ž7mRô™ÏËSô߸Qá‰TY„]Aˆ$BMD¡vD¢&Õ3‰ÐðÖ!“ ÷hIIIDDŒ ‡m$=þY8‰0D•\¨‹ýû÷óöÛ 15­S¢œ\žÌ’%SðõõÕ8–ž®0”•^æØXÍó ¡Cxô.^ÔK5½144dù‚åtÙ‰¼>y`Z¡Å% ülý>hxùN\ k‹&ÏjmIMMÅÒRQ"P^PÀ€¨(ÂϯX[³¹E Œ•OD~ÿ]‘%›Ÿ¦¦Š‚çýú•«>¥E$êF<Ï*‡fÖ¬YLš4‰Gã—ôEäk"’µ#}4I„Åþx{:ÒÒÒˆ‰yóçW•¸ÅļNZÚ“®~‘‘0w.ôì uë€ðÓOêÆs½z0~¼Â±vÿ>;Í›—áRK`ííµäùåA(Ø„Ú`ºÓû£ö4Ù߄پ³Ù°t󙸚!ÖMžÅÚr.*Š!ã–³ <ž}ÉÉô±¶fKË–˜(ç¥ÿÏÞ}‡Equþ-½¨€bÁн×Ä^+¶ì%FŒ-1j,X¢±ÇˆIŒ%$Ʊë§Xc‰»Q±‚Ø•.½Í÷ÇÐYv¸ïóì#{wvç ëå0{Ï=«aøp9y65…ƒµž<ƒÈY²B\úˆ³gÏb``À¶mÛ˜;w.ÅŠËòsÅ‚üôDQ˜z¢Ð'½‚\Døü¹Ü×È(õ-kÌh®ˆðÜ9y‰æÑ£àã“þqCChÛVÞ<ÀÎêÕSLٲߢR½ÿÊVttæÝ Ýð½ÁÊË+¡(´Ð–ãCŽs÷Î]Ê”)CÙ²e³ü:…˜[ÒË‹¹e’³37õõ¹vó&stt8š°sG \ëÕÃ(1y^¾„H A(.N.Àû"¼å[n+ZT^¶ag''Ε+gïùÆÆÆgÐû¬úoW_]`jë©Ô)Y'×^[>Ä$ggÞ E‹âgfOžÐ¶Q#Ö¯‰nB·ŸÙ³“Ûq[ZÂñãrz!_ taaax{{S½zõ\ÝòH¦'ŠÕ…>éåÏ"¬™>6”·}ŒLKðΤ[Ù ®ˆðC½£µ`|A¾Ñ "Gò2ˆï¿‡iÓ@_?N• |B}˜yB¾‚WÅ¢ ßµû.é11·¨'æ–ôrsnI¼úLÑ¢ò@¿~˜­]ËáÏ?Ç41yvrJþ8§L8qê(ï?QD˜¹_DxëÖ- @5ÐÑÑ¡}ûöj‹ŽŽfèС˜™™Q«V-J”(Á®]»r-± ?=QD¨ž(ôI/?ÂO?eýø-ä­^¿øÆŽ…É“åOv, õ~ýÖ®…‰Õ=[}áÎàïϞɻ^\¹"ï”qð ¼;Öúõ°b…¼kk \KpœãÉ7Çyò˜á:êÖUNò ðÍ?ß%sÿÞãwŒõ’¯l‹¹E=1·¤—›sË$ggÞ|úiò@Ñ¢”*Åã{÷@’à믓“ç àìYE&Ï r–¬(𿞿|ù’°°0Ìž={2®®°yë]¨½Òl}Wù\¿…Û“<ÚBãüóðvÝ“/p ¬7®U»¦z}hÛ¶-£FbÅŠ¸¹¹åÉ9/_¾Lhh(õÒì»Ô ANœ8‘tÆŒœ>}š·oßrîÜ9Ö­[—åsx{{ãî§gª[xxêö³bLŒ‰±ü5öìØØÈ t\œ'ÆÆá,]jùsÃ8v¬kÖX°o_-Žm”tK;yr666Y:¯‰‰ •+¯£jUjÖüŒÚµ“n)Ç*W^‡‰‰I–¾???( ñ¤æÄ$|­”çÎ;Šø»_pfO>XÜi1eŠ”ÑZ,bLŒ™˜˜ÐëÓOy[£†ÜA0¥çÏ!*Šð‘#ñ*]š±µkãù矄§ùhH ßGbNrøðaÜÝÝ Nh7.dL1 ôÛ·oùöÛo©T©dÓ¦Mcee…»»;óæÍ£]»v4nܘ]»vŸvÆÿp¯^½ NšµHõë×çõë×Iç211ÁÜÜ<éV4ÍG5ïsïÞ=¶oߎ««kª[ÚŽa…iìáÇìÙ³G±(iÌÓÓS1±(e,66–‡*"–”c7úѰ!œ?/•)ãÊ~LžlA‹-hÑ¢Ož<¡J•*I÷ÓŽYXXdé¼]ºtáñヌÙœcÇ~àÞ½œ=û3gÏþœjìñãƒtéÒ%KßGpp0*PHýs$66Vqïmyzz*&¥Œ%Î-9y½+!!„[YAÚ¤óÜ9y¬hQš7çáêÕ¸º¹)îï ñkWWWV®\ÉöíÛyüø1B&$8~ü¸dbb"uêÔI:|ø°¥ö8iÑ¢ERÙ²e¥úõëgû<]»v•lllÒÿòË/ ùûû§wqq‘) ÛçJiذa’­­mŽ^£ úæ›o¤ÀÀ@m‡¡8Æ ÓvŠ(}óÍ7Ú#Ih¨$}ñ…$É•AòmÜ8IŠˆÐl{÷î•öîÝûÁÏðàTºGi‰y¼÷VºgiéÁƒ¹yöÅKñRëõ­%æ!é-ГÜ}Ý3æææ9>G\\Û·o§^½zé–ŠV¢ÐG=Q蓞’Н_—wÍðò’ï[ZŸB¯^šÅÞ>g«V­JœoHÈëÕ‘àÝÓw˜—Íù<˜뮯ãü ùRÿ7Í¿¡a醫”÷ŠÒˆ¹%½œÎ-uuy1u*HÏŸã5|8º)>!wS©¸2e S²³5ݹs‡;wî—.K…”"–p4hÐ Óä9%}}}ú÷ïŸkçOl÷z÷îÝTã·oߦL™2¨ÞW”E˜››cdd”ã×Aó$IÞªeËää¹C¸yS;ÉsN…Ç„3xÏ`üªùÁ•÷xÂë‡Óð†œzzJcñ¥ô&ì ÓO ¢YEæÛÎ×J‚Rœ$1òþ}â$ ]• Ç;w8ŸfyéïÖÖ|5?ÿ¼_ŒŒ077Ç ízn!E$Ði›êþŸþ‰««kžœ¯E‹˜™™qûöí¤1I’¸uëvvv¹rŽråÊaggW ABA÷úµÜ}ÏÉ ¢£AOOîÄ{ü8$üþ¯<|LËõ-Ù~g;Ô†¢RQ,Ý,!å&E‘Pú\iª­5áÕ»WtÚÔ‰Y'g›ákç…)Ǧ€KwLõM5z~APçoon$ì7>9"‚ïW­â÷ŸÕÓããAƒ0ɬǽ‚T«V ;;;Ê•+§íPOq ô‹/(^¼8>>>€¼ÉyåÊ•;v,Ìž=;[¯ž´8þÍ›7øùù%ÝOÜhßÄĄѣG³jÕ*8€··7'N$00±cÇæÊ÷åëë‹»»;¾¾¾¹òzAb¡Zb¡,e¡¦ýó4hÇŽÉ÷«T‘‹gÌ€\lVúAüüüÒeæÈÃ#|¼æcn½¾@‡Êxü¿Çýé(ŸxÂÇW>æã+ó‰÷'þñ0^;½XÛk-&ú&ÄKñüàöm7´åIГ¼ø–Ò9ñä›omÀ¡–½jd~¹_Ì-ꉹ%½[GD0÷‰ü JTó0 ¥©JÅ™„‰á÷ŠùjÖ¬\7¯‰\%´½;-WWW©nݺI÷]\\¤2eÊHþþþÒÖ­[¥êÕ«gëõž={&™™™©½yyy%+?^*V¬˜HÖÖÖÒ?ÿü“+ßÓ°aäêÕ«KË–-“.^¼˜+¯YˆBõDáFzÚ("ŒŒ”¤‰%I¥J.2D’BB4Æ{e§ˆ0^Š—]$éÌ×I*œrtŠ›¥ç{¼õ®j˜ôÜb‹‹I[ooÍIø™ŠŒ”ª¯¬.1©ÈE¤Á/²ô<1·¨'æ–ô>tnéäî.qê”Ä©SÒñ>’'ˆbŤ°={¤UªH§õô¤ŸgÎ̃ˆóÖÅ‹¥eË–IÕ«Wï—L¨$I’´Ä§ô÷ß³fÍš¤=Ÿ›5kFË–-Y±bÑÑј™™áååE… òäüñññ„„„äJá`"GGG@pB~áé) º»Ë÷‹…ß~ƒ¡CµׇzýGWGöxÈÝXMôMXÿÉzÖ˜­×‰Š‹bÚñi¬¼¼2i̱‘cž-«˜wzóÏÈëG—u]ÆÄjû™ ‚FmôõexÂÕüaG²ÑÙªUƒýû¡vm–Î7a¼C IDATšÅÎuë8õäI¾Z¾‘’È[2§ˆ]8RjÖ¬ãÇ'..ŽçÏŸsõêU–-“;Méêê—´ô"/èèèäjò,‚öÅÄİcÇÿÈÊå‚û÷k²lYc{4m Û¶AÕªyc^ñò÷Â~»=~T±¨ÂÞ{iPºA¶_ËP×v+èRµ Ã]‡ó6ü-Ý7ráŶõÙF«&¹÷}ÿû8Ÿs ‰UÆ7Ÿk¯-êMt4Sîß TP¿üö›\M¼k$ìâõÕ¬Y”¬Z5ß&ÏBÖ(î 4@•*U066&&&333®^½ À7hÞ¼9AAAùêéèèȹsçhÓ¦ ööö9ÞzJ„ì ¢zõáøùÉäÈ0À XŽJÓ¦ÁÂ… ¯¯ óÀþûûºw(!Q!ØU³ckŸ­XYäøµ}B}ø|ïçüûø_ t XÜq1“ZNB•ážxY×ᯜzz •—G^æã²çø5!§9Âö„Ý´¶-\ÈÀÚµaùr¹²¸H¬KÌYÄèŒ)®ˆäDyèС888°sçΤñ«W¯2bĈ|•<'jÚ´)7nÉs ¢ÐG=Qè“^nVì2¹uÀÊJÞaÃÙYÙÉsFE„sOÏÅ~»=!Q!¨P1³íL >”+É3€U+Ž =†s'gôuô‰Ž‹fʱ)tßÒ×a¯sôÚ›nnJÚ2ïë¦_g;ys‹zbnI/Ës‹$qxÅŠ¤ä¹û•+ ì×\\ Lò òÞò7n¤iÓ¦ÚEñ™@›™™1cÆ –,YBåÊ•“ÆwÊÈ^¿ÎÙ”‚ÈÅÅ%O—ãäWÎÎÎÚAqBCCqqqÑȹ¬¬àÖ-èØQ#§Ë77·¤z‘DÁQÁ|²íœY€„DQƒ¢ìî¿›E¡£ÊÝ)_…Šé­§s~ÄyªZÈk\Ž<Û¯!æõÄÜ’^–æ–ðpBæ+++ŠDF²ªY3øòK D¨"gÉœ"–p¼{÷Ž¢E‹fë9!!!+V,"Ê]b1¾ hWPPõêÍãåËÌ:ŽѧÏá›¶mYÙ»7+,,˜Ð0ãN˜È[2§ˆ+Ð{÷î¥~ýú¬_¿ž¨¨¨÷ëááÁرc©šÏ*znß¾³³3çÎÓv(‚ äS’$Ñc`œö;qµùU¼Z{áÕÚ‹«Í¯òÍÞohؽ!üåäù“šŸpuÔU$ÏE Šò·ÃßlrØDQƒ¢HH,½¸”Vë[%%ô™9÷üÞø€îÕ»‹äYЮ‹¡iS.GEáâà@sSSÆ5È~n~qîÜ9œS5–ÔSÄÂ!C† R©X´h3gΤ]»vT«VªU«baaÁ£GðòòâÎ;ü÷ßôíÛ—S§´ÓRöCU¬X{{{,--µŠ ùÔâå‹9mpšðÚá©PAX£00Õ5óœæ1Ûfv®óe×ÐCiU¡ƒvâê««\ó¹F“ÕMpéî°†Ã2|^L| cŽABÂD߄ߺÿ¦Á¨!M›`ôhbââõÃÄ«Tè«T¬«S•æÿ_iJ­Zµ°´´äòåËÚEñ‘@ëêê2tèP† ÂŽ;ؽ{7{öìáÑ£GÄÅÅQ´hQêÖ­ËG}ÄÆ©U«–¶CÎ6##£|w^zøð!ÖÖÖè ŒÜàéé)Þ+iÄÆÆòôéSªU«¦íP´&..޵ÿ[KxçÉsâ—‰uÕµ ÔáRÌj3K+És¢ªU9?â<ßüŽŸÎÿDht(Ž®Ž}x”?zþA1Ãbøúúrèè!N]:E« ø÷çžÿ=æØÌÁÚÜúƒÏ/æõ ûÜÈg]»R2Ř$I„FGSÔÐ0i,âÙ3Z¿yÃàGGGnW©ÀôŠ©gZ°ÛÈ[ZZbii‰QB±¤1EÍ.::: 4ˆAƒ‰¿¿èÉ.ä§çââ¼yóľÛi8;;‹ugi$ú,_þak“ýý!›¯ÇÃÃÐi ãž'ü™"'Š+‡‡‡õêÕÓXlêèë賤Ó:WéÌнCñ õeÛm\ò¾D¯Ð^ì;¾çž#•‘À¸xCÍ5™ÒrJŽÎ-æõ ûÜbaaAÙ%XpäV cAÀ< åÌâtî7nÌÂaò§&5MLø®R%M†«U¯_¿¦bÅŠÚCѱ:#FFF"yÄQåË—‹pjæp177ÿàäÙÝlm!“ò Å{ýú5a†a©k‘*y3 SÔ/ìªtâÖW·è^½;ON?a¥ÛJžÙ>Cª&A  Ðè Q¢ˆ‰ŠÉÑ9ÅÜ¢ž˜[Àiùr–ZY%Ý7'uòì55bôªUD*`Mê(:eÊU"gÉ\áy7hÙÅ‹ùòË/9xð ¶C„BcçNhÝZ.¤/tt¶£¯ŸñMOoJ]ÞØ AŠÏô¸âAÅi °"§’&%94øß·øÕC´ÌàÀ"àÝÈ›IßMÒh|BáQ³fM‚6Ä'ƒÇ—N;³nï^ÎFD0ªlYÚ’_È<È—_~ÉÅ‹µŠâ)j GAÖ¬Y3–/_.Öä ‚ÄÇÃìÙðÃò}]Ý"Œ9Ìû™cmÝ*¯Ãû fÅ͈ ‰‚H £å‰‘PV§,%K–ÌàíªTUïžkËy·óŒJ(lœ–/g©­-?û¤N£}€àªU)vàÓ: [ðcÂèÂÀÎÎŽN:1jÔ(m‡¢x"›ÓØØX±(? Qè£^a/ôQ';E„!!0d$~ØS¼8ìØ¡G§NvyeÞñ ÷£÷ŽÞøµðƒÃ@/@ŸÔE„1PÖ­,[ÿÚªµ83sêÒ)âKÅgz\H|ÑÑÑd}ÿè”ÄÜ¢ž˜[dIW¡}|( <ªKÍÍq:tˆñ”ÐÉÒ¥zuÌ ÑûHOO===bccÑÕÕÕv8Š&–phˆ’Ö$*…覞è–^V;>x-Z$'Ïuë•+ЩS˜‡î¾½K³µÍp{îP§Gª¬†ñ}c¸ÜãûÆÔ:]‹?î ZUåîTR¥BÈÂyCÉðƒ“gsKFÄÜ’ >'33–"¿]H¸úÜ¢%Jð¿·op°´¤·B?ÍÉk"gÉœ":t¢£PPEDD0rä·Y*г··á³Ïò,–£Gaà@ ’ïú)üý7d³É©¢zpˆA»ñ.úƒëfý'ë!NŸ9Í?gþ ›M7ÚÛ´Wü§\ÿý÷]æv!°Y`ÆÅ@+÷Vœ?(–qy :Zþˆj÷nF +ÀÉÊŠA'NðiP/£¢0ÓÓã^Ó¦”M±½]a"ò–Ì)òs‰°°0~úé'vîÜÉ£G8sæ -Z´`Æ ¼zõŠY³fi;DA€¨¨(NŸŽâÕ«Å™ ,˳úçŸaútyí³Jß}óç£Ø‚À¬Xzq)ÓŽO#^ŠG…Š…2«mÂܧv]í°ëš¿–¥|üñÇ43mÆÑÀ£`¡þ«ËVüú㯚 L(BCÁÞNœÀ©n]–úùñÙë×7lÈz]]^&\ XR¥J¡Mž…¬QäŽþýûóÛo¿Ñ±cGJ”(‘4^£F ~úé'bbr¶Å‘6œ;wGGG\]]µŠ ä*•Êy3¨÷ÝÌòäÜ‘‘ðÙg0uªœ<›šÂ®]°`AþMž£ã¢±NÇœˆ—â1Ñ7awÿÝÉÉs>·mõ6Ýn„áÓ4ÉIX¹Y1¡çš4n¢•Ø„ìí[y?Ë„ä[[j^¼ˆgåÊt,^ÛäW¯hkfÆè²eµ¬ö¸ººâèèȹsç´Šâ)î ôµk×8|ø07nÜ Q£FœH|³Mš4!88ooo*W®¬Å(³¯iÓ¦â£4D¡z…½Ð'666Ý䇯¯oª}áß¾…… Ësû¶¼æ×Úöí…íà–-‰Å‚nÏݨP¬ûí£q™ÆêOècii©±sÊ‚«G¯2kÑ,N\9A@DFºF”)V†e¿,£aƒ†9>‡˜[Ô+´s˳gе+Ü¿/ßïݶnCC^˜™ñ®ysf^¼ˆ”°×óÚš5µØÇS»ìíí±··Ojh'dLq³‹§§'5jÔ Q£F€Ü0QLL zzzù²8D,ÈOOt S¯°w ¥_¿ œ4&IáÄÆº¢¯/ÅÇCtt8’ä ,§}{ùÊs>Ê#Ó¹ûö.½¶öâIК—kŽë@WÊ)“ásѰÏ|>EÑÓÓcÉÜ%ÄÇǧšçsƒ˜[Ô+”s˽{Ð¥ ¼|)ß5 V­]]θ¹á]¡1oßòlǘ=›Y•*QÓÄD»1+€èD˜9Å%ÐÖÖÖ.D,] “ä—¯¢¢Xuô(îeÊÈÉ3€½=&«V¡/ˆY¤¸ÚØØ˜íÛ·Ó·o_jÖ¬‰žžvvv„……Ѹqã,í+‚ò- ¤ºŠìã#ßNJ}lV óC)Dtt4'Oäø¹ãtnÓ™¶@¾<ø%Ü7`ªoÊ&‡Mô®Ý[›á ‚bùøø°xÙ2Vþøãûܺ!&ôôx½q#§;wæ”—§ƒ‚¸+V€“SòsŒñ­X‘3nnØ´m›§ß‡P0(.¹àÎÓÓ“½{÷r÷î]tuuiРù¶3NTV6Ê-dD¡zù­Ð'::kÇuéÛ¶Á£Gàé™|óðk{‚ƒ“M¿;},ÉýÂòKW.1rêH•zDdéH~_÷;Öß[cdk„»®;  î´ŸFeeëõóc¡&ˆ¹E½ü6·¤å´x1Ç®_ç[¬¬¬Ô´r%~sçrºU+N5mÊéîݹ§RÉk¡ݾ ÕªÉWŸãâÀ×ʕÿG¦ÿò —DMTT†b¿÷RììbllÌàÁƒ3?0ŸE„é‰BõòS¡ÏµkÃe‰¾~òR´||ä„úÆ ˜;WÞ²5Yb¿°ü³¶õÑ£GôÜ——í_Êm·D–ŒÄ³¦'¸í¡yƒÌ‹3’_‹óš˜[ÔËOsKZ>>>œ}õ ¿/¿Äiñb¶¬\™ôX@L g‚ƒ9ýÏ?œ*R„;{ö ©ùËPG‡–ÅŠñðŸð?^Œˆ€½{aÜ806æa¹râ*4¢ˆ0+݉ðþýûxzz’6ÄüöÃ"qOÅ6mÚ$m#ùÝõëСCÁÁ¶€ Qy‚$EѾ½![¶džQ¯Þ<^¾ÌìØ úô™ÇîÝÊLª›viÊÿƒŒJ6" Ä©xŸ÷δXP »!&°µY3(_ž²..,^°wNq34u‰ŒŽÍŠÅÖÜ[ Z+Æåóçé³cþ}ûª?QDÍ׬áÒÞ½yúý(•««+®®®I9K~ý…KyzñâŬY³†§OŸª}\Á9†ÄQ(HnÝ’—d›¡RbéR>üýÏ)Lúùùá£òÉ8y0#3#BƒB1² ´ d$ñê3åËðÊÞžaóçÃW_¥:N?6–Ÿ=öIl­­iU¬&i–}:-Y‚ÿ°ai?âJÅË¢Ð^…N¼È—ØÊ[ȘâèË—/3sæL¦NÊ€¨Zµ*ªüÚRL  {÷ S'ð÷P±b…9‰Ÿ† ²›7oâ_Ì?Óã̸yó&;vÔ@T‚²bþ|ˆŒd»;ÞãÆ%?X¾<„‡£òó£lHÍ._fÌ´ÑÑÁtÿ~(U*Ã׬mmM¥“'ßrCC?ŽØÐNxÅ%ÐOŸ>¥L™2,^¼8ß ª#ŠÓ…>ê)¹ÐÇË :v”»üôy’QD¨–˜[ÔSòÜ’–……×NždêíÛüÞ¦MÒÕç$Ð}Ü8ê¾~Mg “ ìßÅŠ½÷57¥ÙÉ+66–§OŸR­Zþ*PÎk¢ˆ0sŠ›]Z·nŸŸ_º¶½ù("LOú¨§ÔBŸÇ¡C¹``Ñ¢Ô»@å.ìI\ÜÓ¤±ˆˆpr¥oßÔ…Åk¿ÐØ/ܽž{Ùyw'§žœ"NŠƒxàeæÏ-â_„Úµkð¹E¡zbnQO©sKF¾ùñG&ŒÍÛ/¿Lÿ`ùòܬ\ׯYbo/oñc”ý¥P¡¡¡¸¸¸ˆæ;iˆ"ÂÌ)²ˆðçŸæÌ™3L˜0öíÛ£¯¯Ÿù“,q-Q~š¸!¥gÏÀÆFþ`Θ?_»1i“„?{=’æ§§ˆOÕ"‘2EÊPñ~EnEÜ"²šú+Ñ&MpªåÄüo ñ_¤ ¼‡÷«WXIÜ´iêxù’ÎΜpw‡ô‰µˆ¼%sŠ» `ggÇòåËéÒ¥‹Úǘó Bõò¥|å91yž>=ÿ'Ï’$ñðáCÞ¼yCƒ (Z´h¦Ï ˆHºÒ|òÉÉtIsiÓÒô©Ó‡~uúÑ®R;T¨è5¤nwÜ©‰¥»[ŒÖº­™;}n|w‚P0tž5‹¸!C2> \9Ԯϛ7ï -yDq tPPM›6¥^½z >\ ‚ùúÊÉóãÇòýI“ÀÙY»1å„$I,Y¾„µÿ[K˜E†˜™QѸ"¯ü›ÊiZDàêéÊλ;9ñäDº¤¹”i)úÔîC¿ºý°©dƒŽ*u‹áƒ[²ö¯µ¬Ù¹†€ÈŠgtÿÑŒ&Z BFþðòÂóÂðñA7&†ª>>覸x RaX¾<º11¬Þ°y3gj1Z¡0R\}òäIttt8vìÚ'׈"ÂôD¡zJ)ôyûV.ôò’ï ¿ü¢Xr«Ð§×à^œÑ=ChçäÄBxö‚vCÚq`Õ*Õ¬”œ4?>AL|Lª×(iR’Þµ{Ó¿nl¬mÐU½ÿ£ãQÃF1jØ(ââârµ8Zª'æõ”2·dæJH}}aõjJ°üñc^ÆÄøx"€aMš°óرŸKª'Š3§“ù!šU¡BŒŒŒ066Öv(¹J¦çââBè{öâ,¬œp‰7 @Þª.±ûíÈ‘¦x]£ }rbÃæ œ=Ohu5ï9Sð¶õ¦Ýçí(í\š/ö}Á‘‡G’’gKKF4š?ÿ'þèù*wÈ4yNIWW7×w:wî\R!¡LÌ-ê)anÉŒOt4wï¾JÅÿÊ–eÐüù\ˆ'"á˜ÕŒ^´(WΗsKA$r–Ì)®ˆ0..Ž=z`kkËäÉ“ó}!È‹ñãââX»v-zzz⪈ hAAò•çë×åûŸ6€Žâ~ÝΞº~ÄõV×ßÐ À ¨"'͵è_·?¶•m³•, ‚}Qññ´wwçRHTªÄ˜~ýàÚ5þî1&2R¾ú|íšvƒ- bcc‰eÔ¨QèêêŠ"Â÷P\&÷üùsf̘ÁâÅ‹iݺuº$ÚÕÕU¡åÈ•+W˜8q"={ö¤gÏžÚGÔ ;»ääyà@øóÏüŸ<ÇÅÅ”ùå¡~`}–]Š­µ-z:Š›"¡ÀúÊË+)yþ²LÆŒ ‰r§#XsãçÚÕg!½#GŽpðàA®\¹BË–-µŽ¢)ò§ƒ‘‘Ÿ~ú©¶ÃÈU-[¶ä?þÐv‚¡°0èÞ._–ï÷î ÿ]0v‡’$ ‰,|ئ‚•:йJç¼J„$+¼½Ù°É|;ssV~ÿ=?.?Ø«¬^ÍèãÇYèäÄ·vvZŒ´`K¼È'ZygNq tåÊ•óåæÌˆ"ÂôD¡zÚ(ô‰ˆ€ž=áüyù~¯^°};(åŸ&§…>zzz˜é™ezœÑ#ºtW¿}¦‰"BõÄÜ¢žR‹O2åÑ#*±{Çô·l‘lÕ vì]]:ÙÙaËÅ~¢ˆP=QD˜¹|þÁlþ!ä§' }ÔÓt¡Od$|ú)œ>-ßïÚví%•äF¡OÛ6¨n¾gKÌX°~eMÇstME„ꉹE=%>Žˆ ÿ½{ÄI&ºº¸^»FÉÄ8ëÔ Ŧ¹èŠ"BõDÎ’9E^¸p;w2bÄŠ/ÎO?ýôÞãó[ËMÑÑGPªèhpp€Ã‡åû:À¡CÔWÑözîeÐîAD튂@ÚŸÁ‘Pö|Yvÿ´›–Íź?AЄи8Z^¿Î°0¶ùù1°$(_.\€ ´eá$ò–Ì)âó­×¯_séÒ%z÷î‘‘—.]ÒvH‚PàÅÆBÿþÉÉsÛ¶òÅž‚–$666ó OOÏ=ÿÝ»wex bР vízÀÇÃ?ÿ@‘"¹}ÞˆåáÇÙzÎìS³™|t2%ŒKprØI:T¡!M›6¥gžù:yöóóK*$’‰¹E½œÎ-¹e¯Ÿ ž> ¦ž[?û ÐPyËŸíÛ¡MÅò!sKa r–Ì)&.èÄ‚üôD¡z9-ôiÑ¢7õêÍËðV¡Â÷ð@ÌttØçä„YBÿV­’+š5Hª'r–Ì)¢ˆÀÜÜœ7boo¯íPrXŒ/hR£FŽÜ¼¹1Ó㌌yñb#i´ØøX]Ùr[Þ«Zñjzkskí&114½~Çè¨Tøãºoß.?8>Ì™£Ý…$"oÉœ"Ö@':pàO>ÖyŸ‰'æ}0j0nÜ8ÜÝÝéС?ÿü3F­âJ(4ªV¥@%Ï‘±‘ôÛÕƒ^hPºdž£´ii-G&Bœ$ÑÿÞ=GDðÃñãÉÉó—_ŠäYÈw•@ÿùçŸY:N[ ô´iÓ(Q¢Çç믿féҥ̚5K+±BN¤!Q!|²íÎ<;@« ­84øæFæZŽL€)q"0€A^^LOlÇÝ»7üö›#„£¨¡›7o¦GÚ#Cnnnœ;wŽ’%K2eÊ”l­' òÓÝÂ`íÚ-¼xñ6Õ˜Ÿß,-K¥«P¡$£F ÑdhŠò¾na~á~Øm¶ãšÏ5ºVíÊž{0Ñ/`[Ѝ!:ª'æõ´Õ‰pƒ¯/+¼½hÄú äÚµƒ-[@G{åX¢¡z¢aæ5»˜ššbn®Ì+Foß¾%((ˆ’%KP·n].]º„$I¨Tïén–@,ÈOÏÅÅ…yóæ)öß\V¬ØÏݻߦ,H5R·îâ èwïàêU¸x.]‚»wó&VmJ,ôIÛDÉ;Ä›ÎwÆÓOÞ] _~lî½]m„©q‰„±v$'ÄÜ¢ž³³³Æ×´^ á+//JÅÄà:f ÆQQP¿¾¼¦–—Af4·v¯_¿¦bÅŠÚCÑ•@ç¥èèhž>}Š‘‘Q†oŠøøxîÝ»‡··77¦té䵓ááá©~344$::šøøxtuu3=¿x#¦'&,ÐÓ3¥ÝŸÁqrOÏäd91aŽÏûXµÉÜÜ<ÝûÅËß‹Îwæy°\Á?ªÉ(þèù:ªÂ³¹HœÕs‹zšNž_EEÑû΢âãÑ—$v;9QáͨTIn”¢€_pÔÍ-‚ÈY²B1 ´™™úúú¹þº.\`„ ܾ}›èèhlll8}útºãèÞ½;×®]£téÒ¼|ù’ï¿ÿ>is¥J•$‰àà`ÌÌ̸{÷.üq–’gAÈ)__èÚ®\  Œ«QÞ¾…„¥†ù^LL ëþZÇѳG ¤QÝF|1ð ¤R]7wåMئµžÆ’NK´­ ‰¢âãq¸{ŸsE)× IDATèh~]±‚¶·nA‰rò\¶¬–#„œQLýìٳΊëׯC:uRׯ_ŸK—.%ݯ^½:ׯ_ÇÛÛ›ÿý—R¥J¥}© ;vŒ3fàììœêö<±S‚Â4æââÂ÷߯ˆX49öäÉsÜÜ`ÊððpRçœn¬^=2ęٳŸ3bÔ©*•ús/îO·nSèÓg^Ò­^½žéÆŠ÷WÔßKʱ1Ncxÿ"¢+À9 ¨ Å#ŠÓ­T7ÅĬ±ÄN„JˆEIc‰•‹’ÆwŽÊËs dê£G°u+eïßg÷¬YèÇÆÂÚµ8ß¾­õ¿ƒ´c);j;mŽ%æ$vvv̘1ƒ«W¯"dB*Dºví*ÙØØ¤_¿~½HwïÞM5>kÖ,IGGGЉ‰ÉÑy‡ &uìØQºqã†äáá‘ê–êX1¦Ü±Kõê “ªW·—êÔ"Õ«7,é–r¬C‡Áéž.IûöIRïÞ’¥e˜$—J “ÀC‚”cRº±† ‡)âï@Scu:Ô‘‡Ä,$楸¥S}¦’Öm\§˜˜Å˜+ìcÃÃ% 77‰S§$£uë¤ ÕªÉØâÅŠˆOŒe<–˜“:tHºqã†doo/ 6L2¦˜VÞš`ggGdddº"ÂåË—3iÒ$Þ¾}›j?UÆO```޶C-1 †¬¶ÈnØÐw÷ÈËý\]å¥aa©ÓÕccGBC³þš…ATT5ºÕà¹Íó÷ _Ä}Áúë5˜ éÌš3‡E ð..Žׯs/a¢Û¶p!Ož„ à=õG‚2‰¼%sb P¦L¼öªLäáá¡¡a®ì%zîÜ9quuÍñk Êæç:@éÒ0lìÝ›œ<›˜€½=lÜ(V­ªÕPÉÐÐcŒ3?0êרŸ÷ ‚ Ö¿§N±rûvŽŸ:ÅP¤äyÆÖ­rò<`,[¦å(…ìpuuÅÑÑ1iy!cŠÙ…C›Ê—/ÀÝ»wiÕªUÒøíÛ·“Ë©¦M›ŠßäÒ(¨ÝÂ^¾”o‰J”€ž=ÁÁºtã¹atô[,,RïA÷]ÝÔë룣Sw+,èt t Hl&\ûX<ù˜R¾¥èbÛEóÁ)ˆèD¨^A[r*§GöéCÌ‹$¶;HèO?aïäDøÒ¥”»|™7ëÖÉW6mÒj—Á¬S³··ÇÞÞžAƒi;ų ТE ¬¬¬¸ví£F 22’Û·o'ÝÏ)щ0½‚Ü-¬R%ùJ³½=´m+/×PgïÞ_ˆˆˆH56gÎ,HÝ‰ÐØ¸[^…ª(¡Ñ¡Œ98Úð/ðI‰E„vò]_:V옮ð·°Õ+ÈsKV ´³ÃÀÇrÏ>{F»J•’îÇKÑVVl?r$K¯ÙÆÖó©S±Œä_]] ææ„7m îîÔ,Q‚9 ]®œüÑ›ò;‚ŠN„ê‰N„™+ðk ƒƒƒYµj6l &&†Ñ£G0zôhŠ'ìýµ|ùr¦L™‚³³37fÅŠœ?žk×®Q¹råÅàèèÈíÛ·éׯmÚ´¡M›69û¦­Èêè5¹?óã„Ôn¾¾Iÿ]ýñò——RZO|[úBâ”ñPì~1…5âðöØššj/`AP°ÍkÖ 7iÃÃyC›FFF¸GF&-ÚnbBì²e|–ð313±±± ªW ÏžÑÌÊ U«ÀТ¢Ðýáî»ví¢~ýúâ“ó÷(ðW ÃÃÓÖ[XX$Ý4hPR=qâDôõõY»v-?üðÍš5ãìÙ³9NžU¬X{{{ñQk!`œ…å»Bj«¯­f⑉DÆFУzþšöQQLž3™wEq“â í=”QÃrç“!A(¨A¿eËèïé‰ð£©)ϺwçÇÇ™F<°³bEv‘ákDÇÇãΰ°¤Û¥•+)v÷.Òýûrò `hˆ‰µ5;.\ ÇÌ™"yÎÇjÕª…¥¥%—/_Öv(ŠWàh++«T{9¿Ï×_Í×_'q˜™™åhí™ Dï¢ß1úÀh¶ßÙ€žŽ?tü§VN¨P1l_·]ËQ Bþ£««KïI“Ø9iŸ†‡³¹lY¢ÆŽe³»;Ó<`Ÿ‰ ½'MBWW—ø˜p'8˜;¡¡Ü‰ˆàNl,^’Dºv4°?̘‘jøÝàÁ¬8s†7³gkì{rŸ¥¥%–––6ž’){u"ºú¤'º…©§©naÚvÃ÷­þ()y®P¬g‡Ÿej«©ròœ‚覞èD¨ž˜[dƒFŒ`gÅŠ,15åÙˆðü9OGŒ`TÍš,rpàØ«W|´nENž¤†‡½_½bNH;cb¸—&yÖ‹‹£ö³g´ûë/ *WN¾úœÈÐèÎ9íæ¦Ñï1§ÄÜ¢žÈY2'h 9}ú´ØÆ.Änaù…¦ò”ÄnaÙªÿVÑr]K< gž¸éNËò-ÕŸ²[˜,q½¢Z~›[òŠ®®.=Çcu¹rÄ4o[·Û¼9[¬¬¸3t(·oÏõjÕˆH‘ «$ k__z^¼ÈŒ­[Ùòý÷Ü1‚0;;î9:~ô(Ѩ=_Xß¾|›Ï¶­sKj‰ÛØ¥í—!¤Wà‹•@lHžÿ> :ŒB’bÐׇZµ@__ý±eËêsàÀZÆ—_„D…0rÿHvÝÛ€¾Ž>‹;-frËÉé®: ‚ssü‘…€Ô¬Yòàµkðä ¥{õ¢^Xõ"#©K=êêéQÄÄŠSÓä?MMù÷Ú5:D@Ÿ>ž¯øÿþÇŽÞ½édk›÷ßœgDÞ’¹¿ZrêÉèÛ$i-ÆÆàæ}¤í¨òŸë>×é¿«?PѬ";úî EùZŽL ¦ˆˆ¶üóÒܹ©øè#*ÿû/wÛµÃ8UÏÓ-ÂÈÜœ ¿ÿ€Ÿ–±±øééaie@\DÓæÌáz>[Ê!Ù%hAx°0øôSð÷—ï¯['’çŒÜ¿Ÿ) ¦àäMDL–&–ص¶cÖ”YüþßïL96…è¸h>©ù í7bad¡å¨¡àúqÅ žôè¡ö1ïÎùñ·ß˜ëä”å×»øï¿¤üÐúïU«89k-bèW_%«TâÓ$¡àk 5äìÙ³b tJ/ô‘$øüs¸}[¾?m œ÷çÍE„+׬¤Ã¨*ˆ›ÍnâÕÚ‹ /°øöbJ|\‚ñ{ƾŽ>K»,eßÀ}ÙJžE¡z¢ˆP=¥Ï-šÁêýû‰ÿøãäÁçÏ“¾ŒiÒ„Í'O¦käô>†††%݆OàG1|üøTã†i LÌ-©%®>{ö¬¶CQ<‘@kˆµµ57nÃRPz¡Ï‚°güu÷î°x±fΛߊ=ï{²dÇ^u|•Üz;ADÕ‚ÚÁ ¨dV ·/ܘÜrr¶Ï! }ÔE„ê)}nÑ„ÙsæàÓ»wêÁ­[SÝ}Ö©?þöÛŸCOO×ãÇóuËt1·¤fooÏÆ±¶¶Öv(Š'Š5@,ÆÏöî…>}ä«Ð5kÂåË ¶ÅT¯Ý§íp«ãFcp΀cßæ¹æ„BJЧ|÷î¼J¸_SG‡ÒÅŠ!IRºåUK•âÏ•+5¤ h"oÉ\þýµQòÈíÛòÒ I’“æýûEòü>¯ß½~oò ]1šK/‰Z4`ùμJhtÒÿÕ+vhbí™ 2b ‡ ¤àï/ ††‚Žlß5jh;*劊Š"’ÈÌ4ƒOä}@‚PÈÝ|ü˜oK” B@«{öÔrD‚P0‰ZCDazJ+ô‰…~ýämëœÁÎNóqä§"BCCCŠéËô8×:´kÚîƒÏ# }ÔE„ê)mnÑ”Èøx»»¥¯Ž$ñw‘"˜Kþÿ™ŸæMsKj¢ˆ0ëD­!¢ˆ0=¥úLž §NÉ_S§j'ŽüVDظFcT/ß¿mUyŸòtéØåƒÏ! }ÔE„ê)mnÑ”ÉÇŽq¯xq¾»v ›.©ÿÏå·¹EÄÜ’š("Ì:QD¨b1¾ò­_#GÊ_7içÎA6ú Za1atߨ³ËÏBÀ4ý1¦7M™m3›éßL×x|‚PXxñ‚OÉMŠZxyáÖ«z ÍM!»DÞ’9QD(z.Àرò×¥Kƒ««Hž³"$*„î[ºsþÕyèfÿšS9†ð áPxå—g„ݦM˜¦íp¡ÀòŽfĽ{`hHÑðp¶èë‹äYò˜H …BÍÛz÷†èh00€ÿý*TÐvTÊD×Í]¹òò ­ضt'ŽŸàÈÙ#¼|ù›f6|2çñQ  ä! p¼t‰· ÍK\Ž¡ÊŠÚ J ‘@kHTT”¶CPœ‡bmm­µMø#"ÀÞ^¿–ïÿö´n­•PRñôô¤V­ZÚ#Ctþ»3×}®пn¶ôÞ‚žŽÝ»u§{·î¹~ÎØØXž>}JµjÕrýµó³ÄBKKK-G¢,Úž[4iå³g`À™3|þõ×A+m¥Ï-Ú æõ¢¢¢òUGImE„rúôi± GÚ.ô1®]“¿7.y ´¶)¹Ðçmø[lÿ²MJž‡ÔÂÖ>[ÑÓÉÛDEú¨'ŠÕÓöÜ¢)·Ã˜þø1ß¼á¹óS”<·h‹˜[RKÜ…ãôéÓÚEñD¡ˆÅøÊ³d $ôÀÖŽƒBp±*G|C}鸩#÷ÞÞ`x£á¬ûd:*ñ{¸ hZd|¡Ð9|fΔ¿®\víÉsf^¾{‰ÍF›¤äyÌGcXÿéz‘< ‚–L{ôˆ; {]ÏØº•vÓ¦‰äY4Hüô OO<âã¡H¹MwBÓ.!σŸc³Á//Æ7Ϫž«Pñþ½ŸAȇýýùõåKšzz2ßÐZµÒrT‚P¸ˆZCDazšî$·é–kl6m‚zõ4vú,SR·°Çi·¡åýe§´œÂÊn+5ž<‹naê‰N„êäN„o¢£ùÂÀ"l]»½~ÈÒs•4·(…˜[Ô9KæD­!¯·z’h²Ð'> /ù"*óæƒƒFNmJ)ôyð›6< ~À·m¾åç.?k%Q裞("T¯ ¿Ÿ× ¿¬øõWªÍž )Úu¿Ræ%s‹z"gÉœ("Ô±_û¦N…Ÿr¿>}äuÏìô$~tü«#>¡>̵™Ë¼öó´” r./_2þÁúœ=Ëî›7åÍë!—‰¼%s¢tJCpww§L™2”)SFÛá*›7''Ï À_‰äù}n¿¹M§Mxö€E1³íL-G%…Ûݰ0¦&´ê.ççÇš5kàÊ-G%4¾¾¾øúú@ñâŵŽ¢‰%âëëËéÓ§yúô©¶C)T®^…Q£ä¯--aß>05ÕnLJvÃ÷¶m“’矻ü,’gAв¨øx{xŽ$ñ÷?Pü»ï lYm‡&0OŸ>åôéÓøúúj;ÅW 5¤jÕªLœ8QÛahÍÎ9x0õZÍwï)RÄ UŠ­Ð ÀÅeFFF9>§¯¯¼Î92RÞÝi÷nÈ]¥µÕ-ìê««tý»+‘¬ì¶’ñÍÆk<uD·0õD'Bõ Z'Âs+aM·ÓŽØš˜À˜1Ù~щ0=1·¤Ö¢E Z´hÁåË—µŠâŒÙ%(ì òwîü—ÿýo,²Øe02Õ˜•Õ"##³”@Oº˜þ¹‡J¥›î1I‚'O <àK–ìÇÆ¦h¿ ÍpvvÖøº³ /.ÐmK7B¢BP¡bUÏUŒù(û? óJb¡Ïòå˵Š¢$ÚÛÛk9eqqqaÞ¼y˜››ÐóîÙÃóç3?ÐÀ€/§Oÿàó¼Ï¯¿ýÆø¯¿æX@+¼½hòàßÿý·üÑÚ¬CÓÆÜ¢tbnQïõë×T¬XQÛa(šH 5D¼J)ЬIw„ŽŽI–_íÉ“×ܽûkš×LÏÔt"ÇÇeùuµMÓ?àÎ>;K­=EG¥ÃÚ^kù¢ñ!3æææâœ"qV/§ï=}}ü׬aÄ{vòˆfԩÌŋst.u®ü÷3/¦æG1,> 0‰ŠbëÂ…è;9A:ôº"yNOÌ-ꉜ%sb ´Pà™™i;å:ñäݶt#4:]•.Ùÿ¥¸äY4Í®W/žT«Fe V77ss¾É£má&/YBøÒ¥ Z¸ßèh–¹¸PÓÔ4¹ª Z%®@ Š'NÈ݌䛱qò׉7C쿦ØqC½#à°ÃÈØHôtôØÜ{3êÐvX‚ #,`íçŸ3.((ÝcQÀikkÆõê•+çúæóÏ ~ü¿ðpþ«QJ—& tiðò¢ÂÛ·\8x»]»²";“Ÿ yF$Ð"ºú¨ó°&åÛ0 úöÍü™*èÐÏO4QèsÀë}wö%:.}}vôÛC-…v–AúdDª—E„v½zÑ×ÚšQîî¤MY×+ÆÈ¹ssd µëÖÅzÇ좣iceEÔÂ…ò}ú`°t)7®]㪎OmmstQD˜ž˜[Ô‹ŠŠÂPü²ö^4QžÂ^D¨ž ðaÝÂ$ âòϲælÉ­na¡¡¡|5å+Z~Ú’Ú]kÓòÓ–|5å+¶\ÛBŸ}ˆŽ‹ÆP×=ö(:yÑ-,#¢¡z¹Õ‰päüù¬M³ïep:$»Þ½¡T)hÔºw‡#`öløýwpu…Ë—áÅ ˆ‰Éô<_LšÄŸ•*q^_Ÿ¶¶É{mššb\ªBCù³re¾˜4)GßèD˜ž˜[Ô9KæÄh )ì òCBÔ¦/Ü(U ¶m==y9GddÆîؑܚ» ÉBŸëî×0v<&¾I|ÒøeŸË¬¼©“„QY#\ºÒµjן/¯‰BõD¡z¹ò^9{»E‹èÆ(Hº ½yï $ Þ¾•o7ofü:*•¼ ½••¼o³š? Ê–¥Ü7ßÐa×.¢‡ IõôàÏ?g¸›ãGÂÀÀ Gß’("LOÌ-êöœ%+D-ä)I‚eËàäɬ¯¯Mš@Vv…ºw¯`&Ð9Åg>ãa‡‡ Ÿú1ÉJ‚~ ³GW×ü‘< ‚FyzÂôé°? 'Ëkut/_}®Z•qß}¯^ügâ×>>Pô—$e¢}ëVºÓù/Δ±cÙZ¡T­ &iv"21áQ›6Ôo×.o¾_A>ˆH 5äùó縺ºR«V­B³íéSpt„3g´Iá²dÅT.yN¢ªf*®ì»Bש"@î¼4o¬[—¼>¬HìþÏÞyÇ×tþqü}³ÙCb‡Qµ+jTmjïJ:h©¢-UÔ¨ªÆ(¥Š_U[{oEQbUk¦H¬ˆ•D¶È¾y~œìÜìqž÷ëu_9÷¹Ï9ç{n’ç~î÷|ǤI ص‹QW¯²ÚÒ’–,œ’…€ÐPÍâ:uûñc D­V³òí·ùêý÷‰¬PæÍƒšmÅ»¹1eñbÎlß^2×.‘¤póæMnÞ¼I@@€ôBçÐ¥Db>âà^$~ýUù,xöLynj ††_gJJHJ GOÏ‚Œ¡ø ùh^ð‚SÔDŸã'éÕ¤\ç¨k¨9þ÷qf0£Ðç)Md¢fd¡f ”Døü9,Z¤2’17or96V¸}ƒjÕHÈê}NÅÄ„[¶¶œ¿x‘Íšå}M9 “³#×ͼlš¥0H]Jèëë¿ñŠAA0jìߟ>öá‡0nÜ'ÄÆ†eš»xñbFŒ™YÆýóÝÆ»~}GîÝŸ©¸&ÔêàrÕÒ·¨Ý¢ã£!{sÆÌèÀ³¸g…>Gi#»…iFv"ÔL¾:ªÕ°f Ìš¥xŸSéÝ<< ^½´¡®½z1·R%¾š3§Hv…'%1íÞ=~~ò„d!¨fhˆÉÖ­DØØ »v-Ï££Ñ}ö #!ˆS©P›™QÁÔõóçLœ>¿ÿü³Ðç—³#ז̤Þ%_¶l™¶M)ó¨„Hù/–”îîîÀ‹ŸÀ±k—"–SœbTª¤|>uëßÏËéÅ‹iœGÌæäd®øûc’“'F’#Ñ Ñ¬ûoS&N!º]4Ùjoe$zøõàÀ¦¥fŸDRfØ¿_‰s¾q#}¬E X¸rˆ5.ª÷v]` “ïÝ#8%FZ_¥âÓjÕ˜Y£Ñ!!¤~'&&2¦];Vûù1ªfMVž<‰¾¾¥R©°··/´ I~yYtKQ(?n9I™%2>ùÖ¯O4V®kkåù˜Ï>ãüï¿3ûîÝã©§‡é”)R<{á÷X~~9¿^ù•ÈøH¨\šæ¼ù sÞñNi™(‘°}Ã.9’÷D##¦ÌŸ••UžSçL›Æ™µk©llœë¼cbø{ìæÌÉœ˜Q«–džóçÓ>Is|îÊ5øM¶©Í7ÇîcÙ¿ËøãÎ$‹ôRuµZÕBwŸ.¾Á¾ˆŠÙo0©‚U´ÔmÉà~²ë ¤t±sp@gÿ~Ækèî—J ðYãÆùÏMœÈýY“Ë—s`mÍšXuê¤$úòí~Æ ;ŠXNÏÕjæÜ¿Ï’HL9§½‹œœ‘‡ù½O?åÍ-[ø¡ˆuŸ%IÉ!t)Qž:~øá NœP¡£“s €ñqqGB‰3ìÜYI¬REó>c§MãÝ-[hŸòA—±¡§ž͇ yi½ÏqqqÌøn§/œ&$<[+[Ú6oË7S¿É“Ãú«ëYöï2|žúd:Æ›5ßd|Ëñô¬Ó“gnÏþÑpΜ#Ì1 l€P°ö·¦U…VlúeS)_aщ>š)oI„í;ub¥³3æ/’Óú"kk&.\˜ïcV¬XÛðY»–WRƲö8ý^¥â;??剑ŒS§æ¯^f!ØÂ__ââÐQ©S¹2skÖÄ2ù:}ºÈuŸ³"“³#×ÍÈN„y#t)Qžºú$$@`à, ¯¥ä’‰‰:8vlî³Ó¼Ð´W«YÌN9ËËì}ööñfà‡ñ}Å—Ä–‰°î¶¼ËåÇ—ùã­?Øþ¿íT¨\Ÿ.üÄšËk OÛ×Dß„¯Ž`|ËñÔ·«Ÿ6naaÁÍ8sö {þÜÃõ×iP·}†ôÁµ«6.³HÈDÍ”Ç$Â1ß}ÇŠÁƒ™–íµàB­ZLêÔ©@Çü|Á¦ž8Áš€€Lk‹`+U*>¾ýJ¨<—_\ãïÜá@hhÚXs33VÖ©CÓLÉÒycjjZÜæÉ$B ȵE3AAA²Œ]H]J¼¨ˆÍšÁ¦MP»v>&ÿý7c¯^å]µšö¤÷!ôšaš½‰À Nbb"ÃÆ ãÆ7 ÕÙ”¢…+'rÃö¯xè^Ñ$뤇iT·¨ÎÇÍ?æƒ×>ÀÚØ:Çã»¶q-—‚9+²[˜fÊ“pN¥}§N¬¬U‹˜°°l^èÆÆŒ©UK©™˜˜ïGÅÄDlŸ=Ãx…Ì=N¿¾kÛ–.…&MJäš’“YøàßÞ¿Ol²òj¥§Ç¼Zµ]©:¹ÄV—&RÉ*ݯÄÁÒØ¡q©Ø%‘™§OáÌåqú4\¹) ÄíQ¾8Ç@šz0FWWIèÓ×/𣢾>¶^^øÜ¿Ÿ ý}õê|·qc‘/%**Šï~ý•û!!,™=€À„>¿{—MÂóT¨ÀÊ:upµ°(ò9%IÙF è0räHŽ?ÎÓ§Oùï¿ÿ ”ŒQž’‹Dd$¬Z¥Ü.}ò$}ÜÌ F†‰1©Z•æ_}ÅFöÖ¨Áï/ÂÅ‹ÊmÛ+WàÁ6L)çñÃJœÈ JdLdö¦'!@Æœ0]¨lRù¥Ï2ÑG3EM"<~ü8¦iñ¶—¢£Yºu+;vÌyÒ½{ébùÌ™Ì_œ3¢RA½zŒ©Y“'O2):Zñ>7kƤ  u©|ÌÔæÍ™@,`Û¡+V,Ò1f-^LȨQìùãfFF²1:š¯üüˆLùB`ª«ËlGG&T­Š^ ×ЄL"ÌŽ\[4#“óF è°jÕ*LLLptt,ð¾å)‰ðé#_ìøŠÜ;q€*ê,‘‘‘J·¯‡±ûóÏéý»Aé¦2~<Œ¼2c§MÃiÉÆöí«TÞxã EDÿú+LŸÁÁpö¬ÒÜÀÝ]©ÓêàP2¬E*ZT„2‡pœ!-€„”y/12ÑG3EM"ìØ±#u«Vå^^9þ·ÇÃ7Î,ž““áêÕÌ‚ùñcÍÐׇ¦MÁÕÚ¶…6mÀÆFñB7oNÌÅ‹¬°¶fÌwßê2’Z‘cÎÚµˆªUù~Á‚"3**Š=/’yá´‚«W¯rñâE®^½J||<«V­Ò8÷Áƒ,_¾///êիǨQ£¨__©¥ëïïŸv[´wïÞÌ™3§Ô®¡,ÀX2…¯ˆÊöZø€ÚÞÞXçòw¦Vs§~}vŸ:U„+(¢££Ób{SQ«ÕQ¹råLã4n\ö’+Û´iCÃ:u˜ûï¿ä”ž µlY$ñ\\»¶hÁF‘žùxªTŒkÖ,sür1$åe¤¸Å3`mÉ…¨¨(¶_¼ˆ˜4)Ûk÷{ô`ÖâÅi9Ê 2‰0;2‰P32‰0ˆŒøøxáãã#Ôjµøî»ïDN—8nÜ8Q¥J%„"11Q¼öÚk¢gÏž9ûìÙ³båÊ•ÂÚÚZÌž=[lÙ²%_6¹¹¹‰:üb´ÌÞûÄ7X ¡i"D]]!¦NâÉ“"„ "<<¼`;…… 1~¼zzB€˜ ârû²>Ü ~ŽR`ãÕ‚Ùf#ê.­+úÕvN·9ƒý*W / Q€k¸|é’øªbÅß⫊ÅåK—Jð ORR’hYµªØ¤¯/6§<Öèë‹n::iÏ7ëë‹÷ŒŒÄúÿýOÛææÈiOOáac“ãïÀÃÆFœöô,Ò9vïÞ-vïÞ]ø$& ±x±ææâˆ3Ø÷£©©8´m[‘ìÓ…Z[²0væLÁªU‚'4>jöî-"##‹ÉâÒÁÍÍMÛ&”9ÂÃÃÅ„ ´mF™£C‡òï%^¸.êÕ«‡N±i6l E‹˜¥´WÕÓÓ£C‡ùD‰ß¾ΟWª–¤Ðäµ×¨Wœ¢B€€zõhòÚk%u‰EBWW—qÓ§£¯§ÇÄD†$&ò^b"““ÓžNL$ªfM†¾ÿ¾¶ÍÍ×víðrv&ZÃkÑ€—³3®íÚé}úô)|7ÂãÇ¡qcøì3ˆŠ¢+àiaA<)ÞgggºX$û´E¡Ö– DEE±öÂ…\ï¬ÝïÞY‹úÚ@&„eGv"ÔLyÔ,¥Ž¶|I’“:$$Dbúô陯ÿýwˆ‹/«nnnÂÌÌLôéÓG¸¹¹ezܸq#Ûܲ2Öê•~bFi^©85¬­ËŒ}nnnâÆªUb¶¥¥¸”Á{{#e{dŠ÷¹,½§ý†ô–“,³FsÄßþnnnâÚÒ¥¢¿J•É ÝDBFoz†kÓ8fl,Dݺ­re±ë­·ÄW*hœ—ê}.Žk‹ŠŠ‡‡:u?ÿüsÚó¬c—/_Î÷9’’’D á“Ãõn76ëW®Ì÷ñž={&ºté"Ö®]+Nœ8‘öÐ4ööÛoýw)Äߋӟ.šêêfû½550Ö­+Ú9 ;vâ„f~OkÖâðaqhß>ñ£¥¥hi` ~^±B;ö•±n“& ll; ã>}DµEµEGGáеkÚsÛêÕŰaÃÊ„ÍrLŽeÌ-E“899‰>}ú'''á&=йòB—±óðð`êÔ©d½Äk×®ñꫯ²iÓ&†fèvwùòeš6mʾ}ûèÕ«W±ÙQ.ËÁÄÅqϾ5µ¢¼lV[Zâ¼n]‹ñ½)"ž>e¼‹ ëÂÒ}®—ý:0ëøqí–…°Ø0Z¯iÍíÐÛ¨P±yÀf× ›6›¿$%Qè ì34$xêT>èÒîßÿÌ?ïßWZ#ç‚°ÈXd+ øÔƆµnnJ’fê£zõ|WNȈZ­¦eõê|þô)¹Õ\9ª«Ë‹ã6fL¾½aÕ* ?ûŒYªY` ‹ [¯_Ïw~BRR-ªW瓈ˆ\í<¡Rñ†‡ïòIþŒŒW†\¿×®¥ÿ H›2%o 5:¥R±¹øè#èØ1ÏŠ5ÅB|<|ÿ½R†-¥í4¦¦0c†’¤›Rnr@“&ì¸r¥äm*ƒ\‰Ž¦õùóÄ'%QÍȈ›4Á*—CCCTe¸yŠDRÊ¥n)e^¸$Âüš—i<õyA¶òKyJ"ã>¡V”ͪ÷fyäIÎ9:2®Åsq$úXÚÙá4t(—V®¤iJõ‰€e'NÀ›oÂ/¿@ÍšÅ`qáIP'Ðok?n‡Þ`nǹŠxþùg¥ÉLr2nÆÆ ±²¢öãǬ­^-Ó¦)M(ZµÒ|Мŵ¿?##Y|“a—%ÀÄÐPÈzëY_3‹êÔGÍšÊëÐÕÕeÜ´ièOžÌ€ʶ `[­Z 5*ç7(9Ywî¤=†Þ¾Í`µš€ðœ*}Ð}ÿ}¥"DíÚé 4^OO±_|ÕÔ©ôÉòŸÑÎýuê0ò£4Ûwï^f‘|ýºbkJ7ºœøØÀ€Ÿ’“™’2ï'àc!`ÇåQ»¶R²ñÝw¡€5…óDøÇŠHöõM:T Ê’˜ùÁ Pq¨°kKTRƒ¼½‰×ÑAÏЭS)%ÌïE@&fG&jF&æ-{ÀK”œB8"## >ÿüóLã«V­€øï¿ÿŠÕ777áàà ÜÜÜŠ–ðSZ¬]›v{÷­ÄÚ_Dqhß¾b=Mq$ú¤.FÔ¨!ˆ‹*•˜mf–~˼B!–.B­.–s†‘»G¦% ¾»ç]eðûïÓm43ÂÓS¬^¼XÔÖÕ«/.úI#"ÄȦMEhÊ9BAŒ´¶¢n]! rM2ÌôÐÕ¢fM!ÞzKˆ1c»÷îÂÛ[ˆØX‘˜˜(úÖ­+’sØ»±±ømùråý¿_ˆcÇ„X¹RˆÏ>¢W/!\\r´g=ˆ­ ÂAL‘ ¢ˆ¤œl­\Yˆví„5Jˆ „ؽ[±3.N$$$ˆ~uêähçn##±zÉ!=âÏ?…X´Hww!š6UÂcòó>Õ­+Ä€BÌž-ÄŽBܺ%DR’Ò²¥xâˆ!õë ñî»Ùid$Ä;ïqöl¾Åy&úú ѳgæó4l(ÄÉ“Eÿû*ÃvmxýzZ’à€€°L»È[òÙ‘I„™Ù½{w&Í"É™—2„ Zµj4l؃¦7Ž_ý•àà`LMs*>UpÊÕ­oo¥}vL ¡ØÐ„+¿S¤¤²ï¹øzÜ8z¬\ÉÒjÕøñìY,ç̓•+IkôÒ¦ ¬YS¨’{Eá›Sß0óÄL:8vàÏwþDÿ›yZËÚ‚-HLL¤««+‡Ïœ)–;!W._fW·n|ÌŒŠéwè’<˜êñõõ…»w•Ÿ©{÷Òoñç…JU«ò»±1¦÷î1 ‹7VýMMÙV½:z÷îAÞßl˜˜€“jgg:ÅöÐPt€múú$´hÁ]]ÅûûäIþާ£Õªñ‹‘¶¾¾ôQgîü(€ÆÆl16F?,—¢Ü©T« BƒÊ£aC¥û¦‘‘ÆégNžälÿþ´Ù¹SIŒˆ€µkáÿƒ72ïðê«JxLjJg?ÀËË‹=?ÿ¬üîò¸Ö>#GÒøÀÅÜz÷ËÒæÌ±c ®ó¢³âÑ#>¾s€^66ìmØ0×p‰äE¦\ém¡e_¢äVÆnÖ¬YÂÒÒRøûû !”o¡5kÖ,‘o\©ÁùežgÏo ˆdT¢+‡„½½¶Ê?áááÂÕÎNÌþøãôÁ“'…pvÎìåóð")©TlÚtmSšçÙe¹‹ âóÏÓí±·âêÕLû<þ¼XmÙ®¸bd»vùÛ!9Yñž<)Äš5BL›&Ä AB¼ö𽝉 ú¦xˆ3yŸAü–“ÇÖÈHˆúõ…èÓGˆÉ“…øùg!ŽâÁņÖ¯\)¶ 5ˆ~..")ãïîÙ3!._bëV!æÎÂÍMˆÖ­…°µÕx΄vV;wƒX­ÉF!Ú·bÜ8!V­R¼Ã…ú= iÙR iÙRó‹žžB œÝoj*ÄèÑB\¹"ÂÃÃEï*U„äúèmm-«TI?†J%Ä{ï \(»_.EE Ó''NˆêçΉЄm›$‘h•r£[´È ) ›5k&ìíí…©©©„½½½°··k×®M›!\]]EÅŠÅÀE5D½zõăŠÝžÔÌÖ2Â1thÚ‡îRó¯Ñ¿¿¶*K<<²ßº‰QD«®nº¨hÖ,›p-nΜ†ß f#ì؉»!w1”jCõêBܾ]¢6¡Ô…®^¡BñÕ}~úTˆsç„X¿^ˆY³„1BˆV­Äo¦¦b{ñ— ¢¯J%]\”PÏ>SB7ŽSB92ˆäÜHJJ}ëÖ›2TÞÈááBüû¯6(v&D³fbµ‘‘ØÅÎ~::"¡ysEh.^,ÄÑ£E®sž•Óžžy×} bÞ`±³3ŸV­ªe‹$’²E™Õ-eé.%Ê\'Â3g`Ú4eÛÑQ©G«Rñ÷ߤ•MnÛ¶dMðõõ%).n¥B½zÊû±d‰R89–.Ujûjhþ¿ßþGöuèô]'FE§ï:Qåõ*Ì\ªÔzn«ïÌÎ!éâyÀØ»7ßâùæÍ›ÅviÚ@OO¯ØÅsRR¾»èúúúåR<XZZâÔ£éèð—ŽN=zHñœÜÖ–e¦‰ç·mm_*ñ\Þ×–’ $Ö–2§YÊ R@—=""""[ûp­ ƒ+1•°};XYpú´2EO/çÒÅÅòåË‰ŽŽ.Ù“ä¥ÕñµkСƒ2æç:)m–£¢øbö|qð ü;ûùj$Ô‚ÈW#yÚó)"QP÷¸­ID×'åƒÊݶlɱ¶&<<<ŠùâÊ?ÑÑÑ,_¾\Ûf”)&ÌË766|ccĹsµmN™"§µå³gL¾wG##~+ãÍ¡Š¹¶dG®-™‰‹‹#""‚GiÛ”2 á(ÜÝÝ9þ<;w¦k×®tíÚU{Æ$'C—.pì˜òü§Ÿ”Îd)¸ºÂٳЬ\¸ %µðóÏJxKŠp¦jUz™šôÀ—ä š=[†j¨É¶ŒË–)Ýú$’àëqã˜%@žD$%ÑäâEüãâÐW©8Ó¤ -Ì͵m–DR¦8|ø0‡æÈ‘#´hÑB†pä‚Ð¥@™Š%š5Kiç 0dlÞœöR|ÜÚšþÀÉ ÎaºØ Ö©*ų¤T°´´”â9üððašxîck+ųD")2R@—ZÈø†WB8LL`Ç05Í4%µ|”Ž€.3I„y1r$øøpÒÁšÁÀæ¦mâæÚÕŠt:™è“™è£™BR„¡$ŒkË¿QQ|q÷.ðrÆ=gD®-Ù‘k‹f´®YÊR@—~~~ܼyS;vIIJÒ`ê¹W®„úõ³MKÐÎÎ`o_òf•©$Â<5×ãcW¶v…:Ù½Ðj`…9h ¯Ö}µHç’‰>Ù‘‰>š9sæ g2~ó•0ÒÝž”Ä`…À@G‡mõëcù×{–kKväÚ’™nÞ¼‰_jIŽÈèRÀÝÝk×®1pà@\]]q-íøˆÏ?‡Å‹•íQ£”¹,66®Žøí·Ò5±¬¢jV^XÉÌ3 ÇÀ®*,8”9ú=ß ìÃ9³á UªTÑžÑÉKLXX.]ºÐ¯S'ž ξÇÁÎÎL¡I®¤~)ß¾}; 6”1йðò~/e6lÈ—_~Yú'Þ½;]<7n¬T…Ѐ··"žAÆ?§rÜï8OàzðueÀ ¹6â±ÏcØ<å§ ‹â}^lϘ<|²Ï‰™¶p!!~ȶ={÷ó33úÚÚJñ,‘äƒT'Ÿ ÷ÉÂñ"s÷.¼û®²ma¡Ä=iœšñ.pI7P)ëøGøÓ[Þ\÷fšxv±uáðˆÃx-÷âÀ‚`ïÈ/ºJyº_tU`ïÈÞïö2öƒ±¹Z"‘” aaa¾qáìLxŸ>°c5Œøõ%Ž{–H$%ƒÐ¥D©äÇÇÃÀ©<ÿýwprÊqzj¨]»ä̓²—Dø<ñ9ÓOÇe¹ »nìÀÜМEquÌUº8u E³x_½Ç‘:.$Gê¸à}õ-šµ(;ä7ÿìÈDÍÈ$ÂÌL[¸€^½àÑ#¨YUP¿T®üRÇ=gD®-Ù‘k‹fdaÞH]J•î LJ+W”íÏ>ƒ>}ržênÓ¦ôú~”¥$ÂM×6Q÷ǺÌ;=xu<*T¼×ä=î|r‡Ï[޾NæN‚*•Š¡S§2°B†NŠªß4™è“™è£™D˜Ž_p0›¯]C89)¡k±±0p ÛV®Ô¶ie¹¶dG®-š)uÍR‘I„¥€»»;aaaÌ™3Jö„6À;ï(Û¯¿'O*½¹sàÁ¨^]Ù^°&O.YóÊ—Ÿ\fü¡ñœ}p6m¬uÕÖ,ë¶Œf•›åº¯‚wú÷gýÎÅ* %IþIHNfÅãÇ|9cñ®®Ùî´ÕXº”ËkÖ`mm­% %’òC`` Ìœ9kkk™D˜ Ò]Jâé鉿¿ÉžÈÇ>úHÙ¶µ…­[sÏðrÆ??yÊèý£i¾ºyšx®lV™õ}×söý³yŠgP¼Ðk·o—âY"ÑØL½ øôÊâ5†©ôêÅ´… Kß@‰¤âïï§§'Ú6¥Ì#ÃJ‰W^y…‰'–ìIž?‡”Ÿ::°q#ä#ókýÓÚNÃÔÀ4#dFWW·$ÿÂßÎ IDATÌ”H$¹p*"‚IwïráÙ3e`Ç%çC¢V-þñÑ#P§·;ºß³§ôB#×MȵE32‰0o¤€.%J< Õ*Ø´IÙîÔ fÍÊ×n‘‘p=¥Ìqi×.®$§OŸÒupWêt­Cûiíi4¬¯tx…ß6*Ý`î…ߣϖ>t^ߟ§>Ô³­ÇŸ#þdï½8Yå\DÈDŸìÈDͼ,I„ |tû6 /\HkŒRAW—5jàöÏ?èáøÃió™3©¾xqÚóûö±{Ë"""´|%ÚE®-Ù‘k‹fdaÞÈ$ÂRÀÝÝ ÐÁøjµšÄÄÄœ'xyAûö~åÊè^¹+æë؇A÷îéÛ]»ÊD­qóÖMº¿ß¿æ~`™áæ×ÍqT9r«Ñ-âÕÊ·i C f·ŸÍ¸ãÐÓ‘L‰¶ñõõÅÙÙYãkÏÕj=xÀ¢ˆNñ(ëªT¼ëàÀœš5©d`Pš¦J$/ EÕ-/RA”Î;ÇèÞ½yÃTCˆAr2AR§€Ÿ§OÇ5ŸâÒÃ7tt”‚å‰äädF|2¿¶~µ?Œ ¢FqõìU¸ :Î:¼ßä}¾}ó[ìLì´b¯D"ÉNŸ‘#Yôõ×t}ë­´1µ¬yò„Yþþ&$¤÷°±a~­ZÔ¯PA¦J$IR@—\]]iR§‹ÿý“æÄ£ªWÇulÁ:á¥6PiØÌÍ‹df©ãééÉMÓ›ÙÅsFZƒéSNÎ?Ék•^+5Û$IÞ>z”G3}Ù²4} 4”)÷îáóüyÚ¼¦ff,rr¢½¥eN‡’H$’REÆ@—W®\ÁÃãÐñŠc<Ù‘‰>š)ÏI„‡ÅÏÙ ïÚ•ñ‹s2%ÉÏJOENNÜlÑ‚aöö´ÒzYêrZ–kKväÚ’úô郱±±¶M)óÈ$ÂR ¸‚ñÏxzr®&‡…e_hnNë½{ , ,€)S”퇡J•"™WêìÛ¿’ðJB®óm„×YÓR")+$ K÷îÜ›8 •Á;ѯU‹ñ½{3½F ¬òh%‘HJ™D˜7Ò]Žpmßž+Y¼ÐÏ+..Ïž@èèXþÄ3€SS'¸ $ç<Çä¶ ƒº *5›$IÎüͧ¾¾Ø-[ƽïs={R÷Ø199Iñ,‘HÊzT;†J$Iº”(®®>®®®\Q«yNŠ÷ÙÉ©PÞç7 5D[º°‰>[½·ÒqmGžÆ<…Z0tÄP^9õ ¶—lQùª0¾nLÕ“Ui4’c;¡R4‚R»ÈDŸìÈDÍ”Õ$Â$!8Êoo*Ÿ;Ç__®¤Ä$«.]BïÕW3{ŸSïÚ•éË–ùü2‰P3rmÉŽ\[4#;æÐ¥D±uõ9}šÕj–ËMMùxÁ‚B&cÞ‘¶ta}æžÇÐC‰KŠCOGÿõü›&lâêñ«œ˜w‚5o­aÏè=xm÷â—¥¿`d”[»²‰LôÉŽLôÑLYK"ôyþœÉwïRíÜ9z]»ÆÎ§OIHVb¬œŒ™S³&õ'©GÍ04ÄÏÙ¹È^h™D¨¹¶dG®-š‘óF&–ÅŒÿùç°x1CU*hÒ„Í—.ê0#GÂúõ`m !!PÖ´‰É‰ŒÞ?šß½~ÀÜМí·ÓÙ©³v “H^r"’’ØÌÊôš©®.ììx×Á¶––üyô(ƒ×­#*' Oà ¸*C9$­!“óFfj”7àãW_…ï¿/ôaRVmÚ”}ñN¿­ýðô÷ †E þþõíêk×0‰Dˤ†oØÚÚÛ1÷:@ïnÝrœ“,GÃÃù-0½!!Ä%§gòª€7,-qwp` tuÓ^‹|öŒ¡ÕªÁÕ«¹Ú`Ô²%áááXYYíb$‰¤„ºSRˆ×,\XlÇœ™r;[“€¾Ãï¬ âQ–ÉFF¸98àfoO­jÈî×Áýú›­‰D¢-d t)Q,ù)Þg {{( ñÏw¢ÏÙgiõK«4ñ<à•xº{¾ðâY&údG&úd'$$„#>>ññ)¶DÂ}‡áç₟‹Kš':*)‰_ž<¡Í•+Ô=žïÒij‰®.#ìí9Ö¨~­Zñµ£cŽâ¹4‘I„š‘kKväÚ¢™D˜7R@—§Nâ£>â@F\PR÷ur‚Bv4„tmdÍšÞœ¢’[¢Ïæë›ys후Ä(Â`J›)l¸ c=í8—42Ñ';2Ñ';SæÏça­Z<¬U+Í]TfþôQ]»Õµ+Ÿ-[Æ;7nPéÜ9FݺÅß‘‘ió^·°`uݺúè#N:¥mSÊ<2‰°pwwG­V³zõjôôôÐ+L“€¨(°µ…ÄD˜0~ø¡Ðö4i^^ж-”Åÿ‘oN}ÃÌ3Ð×ÑgeÏ•¼ßä}-[%‘”BBBh:j&Pé‡ؼt)&ÖÖ$$'“ ñÉÉiÛYÆk¿yò$G®_'¡woå${ö(–š7 Š¡!#ííqwp Ž‰‰¶.]"‘” III$%%1jÔ(tuuea.ÈèRBWW·h%ÕŽQÄ3)|#**=G›á \¿~*W®¬Œ©µëþ[€¥‘%;íàÍšojÏP‰¤Œ•”Ä€Ù³ HºÀ“·ß¦ýôéðÁ…?ðúõ0}zúóž=Ñùö[öèÁ»¼ee…NYÏ8–H$E"Õɧ›!ùW¢) Ë ©áffðÆ…>̹sš0¯ ýøñc†<ÿh¢Ì¢0Œ3Ä4Ú”QCFqÀä§î+.ñš–5ùcøÔ³­WúFJ$eŒ¨¤$ö…†²ýéSûù‘pï >¡fM¥3Rd$XXèØz*º/’Ф "ãÝ1==*4kưû÷éòÊ+Åt%‰Dòb t)Q¤€üäd8xPÙîÜ2´ò.(©ñÏ::ðúë…7©0ܼu“®ïuå~«û`„–€|yìK’ƒ’¡3´®Úš½C÷bgbWº–nÞ¼‰KbÜ_D’’’ð÷÷ÇÙÙYÛ¦š‚–œ{¦V³/$DÍaaħ~óݶ  R¶Sã’-,`Ð :?Τٳ1P©0ÐÑIûi˜åyÆŸ:*M-Â+%$“ ]º0kéÒ\KÚ•E|}}qtt,\¸Ü Œ\[²ó"¬-%A||<†º…JÒ‘I„¥D‘ºúœ?OŸ*ÛE߀tÝ XZéPB­V3tìPî·KÏçe3¹Q2XÀë1¯sÜíøK+ž>3FÛ&”9^„DŸ©óç35d¿gj5›‚‚èsý:ÏžeÄì IÏ–ÏŸc ŽŽÊ×®)GGn=|Hµšv––´67§©™ +T Ž‰ ŽFFT64ÄV_s==ŒttÐQ©Øwè÷\\@“ØÔÓã^†Šå™D¨™D˜am) d'¼‘º”¨^½záwN ßÐÑîÝ }˜ÄDø÷_e»´Ã7ŽŸ8ÎmËÛñ mW cXx ˆ»‡‘^ùk¿]\xûøpÁÇom›R¦°´´dÞ¼yÚ6£Ð„„„pÔ×—£¾¾ÙJÎE«Õl¦oŠhž"šS›“XêéáîàÀ Ò÷äI¢û÷OßÙÕ5Ó?óýÞ½ùrÁ‚Ù6kÅ ¢ºtÉñõ¨.]˜µbEŽ©m~øá,KÓCPN aÙ±´´ä‡"$忨I³¼$Èû[åTݼ9T¬XèÃ\º±±Êvi ècgc“û$D$FœœŒŽÎËùÝn·ßãáÁ„o¿åØÆÚ6§ÌpëÖ-¦|ó {6lж)…bêüùôê•¶½Äá¡l æPXX¦N~zz¼mkË ;;Þ²²Â@G‡Þûç¨Sr©[{àŸ ÉW¨ÈÉ“'yrïN?ý”ë¼'÷îqòäIÚµk—«•H$’) Ë:Àÿ)Ûž¥' ÕBÍéû§ùóöŸ`“÷üd’_ZííãÃ5==¨Y“kzzxûøP_&o0aÞ<.†‡sëÖ-êÖ­«ms Dª÷YôèÀ¦]»Øpð qff™æYèéÑÛÆ†A+Ò9E4g$((ˆ{÷F„‡çz>UïÞåK@·k׎@oï^‘D"‘H¤€.% DøÇéÛÅ$ «W‡jÕò·ODD§NŸbýÖõ´nÙš7ßx“FåºO‚:c÷ޱëÆ.öÝÚÇÓ˜§ ‘YDgH"LÅBÏâ¥Mü™8oÁ@@Á}û2qÞ<Ž–SkqrëÖ-¼ÔjB{ô`¼y^»VÛ&å›;±±¸ÍœÉýï3@L¿~°u+|ðæYD³a._ëׯÏ×õëg+hbâË‚L"ÔŒL"ÌŽL"ÔŒL"Ì›—ÏÍWîܹÚ5kزe QQQÚ·Ðù©áU«BãÆ…;F gÏ*?óë}^»i-M{7eІAì8}œÏwO§ÓôNtÔ•àààLsŸ'>g‡Ï†í†ÝB;zlêÁš+kñ èÕң½  Î°S†$B³;fŒì3²ðX޹pý:ç@ÉìÜ´ ,-9#[/\жiZg¼yuéÇóŸܺuKÛ&åHlr2‡ÂÂçµÿý—:GŽpÎÏjÕJŸT«""X_¹2Á¯¿Îúzõèec“«xΉ3gÎp&ã­% “sB&fG&jF&æìD˜O"##iÓ¦  ..ŽuëÖqäÈ4hç¾îîî@!8bcÁÆFùùÑG°reÁ OáæM¨—RRyåJåp¹±nó:&®›HxËpð6‚ºãáðVèq"¡Áå;pŒ?ýþd×]¹{„ؤØLÇ0Ò3¢³SgúÕëG¯:½¸zá*C¿J`Û@ÈR‰Ïà­ƒ[ã¹Ï³Ð×X‰MNfå£GL3†„>È\%"V¯¦Õ7ßðI•* ¬Xý—¬‘…ÏÍ›¸ÎKxjƒ°0šïÜÉ™õë³…8h‹»±± ã`h(žÄfŒgþåèØ1³€T÷îñþ¬^¸°”­•H$’¼)´ny‰÷·ò‰¹¹9×®]C•"`ìììX¾|9«V­*¹“þõWzÖ_)Æ?‡‡‡3sùLÂß ø;ÀØ® RÏRh‡wmo*÷¯L²kæä'33º×îN¿zýè^»;¦¦i¯µ£=û—ìçýÉïdDˆy& &X…XÑ»eo¾_ñ}‘®±<‘œÌ/Ožðm@oßVêøf­`i ––üsíÿDE1éî]>ª\™+WƾµÀËW¢£ÙÄOS¦Ÿ²`mÍ…äd,6o¦}Æt¶¶æ-++T¨P¨ó\¿~=__‚3—œÌɈˆ4Ñ|'66ÛZ ÁµÈH³ˆgQ«G÷ïÏw²ŸD"‘HÊR@çUÏߣG°²²*Ù“¦†o+^¬"* ­¬ Ke6Žþu”‡5*qËÞFÐmˆ"ž;vQ¼Ð¯ø#ª ÄEåæ…± ½ëö¦_½~¼åô†º9ÇM5kÚ ¯¿¼¸{÷.§NŸ¢¶sm7nŒY–„ªÂ²aÓ&ú÷틱±q±¯¸I‚µ|sÿ>÷ãâ”ÁmÛ`ôhÍ; „þêÕ$NšÄ“„fùûóm@ƒìì_µ*Í‹é}+ Äű)8˜õAAø<®$К˜(´4ˆ¸_åp•*  ’o¥ˆéNVV8äã Æ®ýûùrÎnç#Læ^Š—ùPX'""ˆQ«³Í©ndD7kkºY[󦕟~ù%§ûöÍùz{õbêüùÒ -‘H$å)  Á_ýÅîÝ»¹téR¾÷)Taª€~óMEDTýúëŠοÎý…Ú^ ɤxŸ;+/¨TÐc$DøÂk&7ógÍ É ¬Û=üÿ)©T*œ>z4Ë,(6ñˤùó¹óè_Ož\,Ç,.’…`sp0³ýýñÍà±t &ÄΎȌ헔LO ¬*Vävš˜àMBr2‚‚ØD+só|…wlÞ²…¡C†”ÔåeB­VsîÜ9\ól™”ÄŽ§OYÄ©ˆ2Æ“©¶oG¼ûnêA!0ªT++L,,° & ¥¬ã“„Ö².0€WMMyËÊŠ·¬¬xÃÒc á_¯\I«+»öï§_†$?€ø ^æCaaÜŠÉ^‚Q_¥ÂÕ‚n66t·¶¦~/xHHþýѰ¡òûÔ€ÐÓãÀ¿ÿÉ -“5#“5#“³#“5#“óæ…[]Ù³gW®\ÁÛÛ›øøx>¬qî… ˜;w.^^^Ô«W‰'ÒµkWüüüè–Ò¾¶oß¾|÷Ýwœ>}š±cÇrôèQ¬­­óm—ŸŸ7oÞÌ4VµjULMM5ïàå)ÛE ßxòîÞU¶ó“@XÕ¡*ø û°ÌŠ»]{˜í ½gûŽÂ`RÄZ˜™ÑÜÌŒæææ473Ã2®ËW®põáC>73»vöÒ2á±|9¡îîl8r„/Ç+^hìzú”™þþŠW5'ccf;:òë?bnŽÕž=i¯=?r„ ;§=‚K—reçNNGF²ìáCö„„$ÿDEåÞqõÚ5>˜0úõëójÆ%~Ík6ndî÷ßãwù2ºººÙ^O‚C¡¡l bhh¶È +T Ëó笵´äiª÷96vï†qãˆéߟWöîåäªU ãhx8…‡ž”ÀÕèh®FGóýƒêèàjaÁ[VVt¶¶¦±©)»÷ïÇ¿~}¢ºtáëÅ‹é׫þqq åPXÇsð2W14¤›µ5Ýmlède…™†ëxüø1Ãßz qÿ~®ï•ê­·xüøq¡pjaŸ>} µÿ‹ÊòåË™={¶l¦’Óš…Ô$BÙL%3AAA²™J¼pI„T©RT*ÞÞÞhºD///Ú¶mK—.]6lþù'¿þú+û÷ïOÑY9þ<ÇçÀªEëîîÎÖ­×03˜6ïÃÚµýrþà›;fÌP¶ˆgå£Güüä ¡‰‰iã::ÙÂ;Þ6Œã]»ÒñðaþÚ´©°—š/Ôj5 ºw' U+–891zdzU•sQQl bkpp&›A ÁfoÏ;öö425¥›»;‡ûôɵ߼Ão¿á9mZÚÿb²\|öŒ#áá ç\d$‰þ÷íôõIœ1ƒˆiÓ@Oƒýû±«R…G¯½–m®žJÅëtO Íx5§/¼‰Dò!“óæ…ó@ÛÙÙE… ððð`êÔ©ç-Z´ˆJ•*±eËôôôèׯwîÜaîܹô³gÏèÒ¥ mÛ¶å·ß~ V­ZŒÎ)v5 qq ‰‹û2ÃÈžçéáIÎ ??þÉPêÐÁÀ€i5j0ºR¥LåÊÒ#\ÍÐyµj1ÓÑ‘MAA,{ôˆÿ4„w¼…·‘T¯Ž·‘W¯]+Q/ôš xýubÞxƒ%´ë×Í!!l ân–d;S]]úÚÚòŽƒoZZ¢“ò·Á³Zå!öÕ‰‰üë¯4­£RÑÂÜœææ|U£Ñj5'""8Æ‘ðð´0Œ§§OC£Fr—$¡[7Í™)º’Ýllè–Sm!Ã$‰D’…î“AWW— ydä !عs'o¿ýv¦¹Ö­[3oÞ<ž¾@ïó—K–ð|áB¾\²„3­[s>6–™÷ïs2""Í>cc¦8:2®JŒutˆ‹‹#¹¿Ë¬c$$ànoÏ{•*q*"‚=b÷ãǨõô”ðŽùó•š… õîÍЯ¿fêO?¡’âãÑO9žJlzR|<úúúèèè R©Ð3¼/*”; I d˜§bbøvíZb¾ú T*n·j…ËܹХKÚïMW¥¢£‰ #«V¥oÅŠTH Èxm–––œ9p È÷&ô²±¡—Ò½Ç72Ïèh&Ï™£xŸ3ü é6kÆà{÷˜èÝ;óX“&pãÄǾd ×îÜápX¿>yÂ7÷ï3~ÂÞþóOš^ºD¥¿ÿÆðÔ)l‡åµ½{éïíÍ'wîà@÷Ñ£Yù27cbˆJ‰¯8q"ë6n䮣#XXp»F ªwíJ»?þHÏzz4_»–£¶¶L®V--|%?¿·›7oêwþ†¥%Ûë×gȶmŒNNÆòÁ¥Ž¸……ò¾DGãcdÄ;2âÆ ÜÇcø±c õña°ƒ¼½6vìÿۻ󰦮ôàß°#P¶Xe#‚"E¡bkÝQÑÖÑJ«¸µZº´Ói­v”:¶µj‹cÕªUkëRZõWÅ +Š¢"ÜÙU} „œß1Ë&!‰ð~žÇ§ÉÉͽç¦É›—›óžƒI'OâÍ›7119¡Éɘ4oÆŸ8±B!B„BŒ 1þý÷1êøqŒ¸qÓ’0,) cÆC†@ O–%C†»v™™ð55Å×®®È8.۷ÿ´Tž<·äÜÄb1RSSÛôYXÿÏ¢êÈHú÷—_}–½_jGFòÁƒð15mÓ1ÔÝ–ŸŸüü|­è‹6µ¥¦¦B,kE_´©MV‡£ }Ñ–6Ylц¾h²mÑ¢EX´h1kÖ,ÄÅÅ4­Ã®O6„£á)&%%ÁÇÇ¿þú+&Mª—|ãÆ ôíÛQQQ?~¼ÊúŽ]»2œ©×…ß~k¤øç­·€C‡¤‰fn.І!'O²z´£G1cšÞ¾šI`µåK”—ê`3­À;.QUUCCC¥éü‚‚‚/„ä û 33iò˜š¢ËÎxwÒ$8yyÁǃŽ ut`Àã)þWG†õn³ª*L˜6 Ÿ|¢t,ÝÄD¼^P¯ðpd‹DȉS]‘H©p­¥LuuaohˆÜeËPúÑGÒKú"°n°|9Ltuí¢SÉ IDAT±°{w|ìàËçüé?<<\%ãÎ^{ûmÄL™"M eŠ‹-[€þ³ÍûW ‘HXµJáWý˜,íÖ +çÌiÓî‹ŠŠ°råÊ6úô=7–,©K ëyéØ1ü8x°ÒŒÚ,êYñ)*Z´hrPUléHT[:š×_ŽŽŽô~iB‡ÂѲ! í²ûÍ y>¦Ø>û׈êj :Zz{ôè6%Ï@ÝøgO:…]sF]8ˆòÞ}ª’°g¤ê­„ë×qÛÔTš<uÃ8 UáÎîÝØòl,tK­ˆŒDγ±Ï Õúø mýzü¾t©ÒŒ55È©®VH¬Þ~\]†]–ÕÖ"åâEÀÍMš<€¡!xÞÌÎÆ÷ûºêë·êREÀº!â¶±±bò ææàÛÚâGSSôòôc HÇ­3ÆäcØe÷{LRÿ>cø}ÿ~l:U þÀª Æ/X>sf›~´°°hóÜág3op%ÏfäxQPâÌ’!n” )SEléHòòò——ת cU§L ííí·nÝRh … «V€h2þë/ ´Tz»Ó×u ´§§òz mKOÄqW€ay*b†Noóñë[Çï¼Ãý ™n›š"áúõÏÈQYY‰}çÎAüñÇn“>l"6oVš‘ÃZ_Öúúðjâ¥ÆÇ‘Xo‹ˆ@ÁâÅ Û²q㉮aa-ê{{[¼v-MžÌùX~h(¾ùúk•ÍÈQ[[‹Y‡£êÓO•äñ1h¶ïÝ«0#‡&|¶nJ€?cÖwßÜ¿=Š *øìBÈ‹&==—.]B^^^«¦êíŒ:eÝ¥KôîÝ7oÞTh …°´´DOY1›Jy@:ö¹²Ù7 êÆ^<'±¸tIz»¹©ë²«ÊñAj& gˆË°³WOX©îçO¥«ÏÎÈÑœ ‘‘((/‡M3‰à–3gžk^h}††p¨7™ü©³g±U ¨»ú,chˆûÎÎ8uö¬ÂŒšpC(”μÑð곌¹¹JgäÍ¼ÑØ =²9f……i¬(¥¨¨¢FöÇicúõƒÎB:©€€ 11QÓ]Ñz2‰D`ŒAü¬èKV]ª¯¯/ÿï½÷°téR\½z~~~HOOÇï¿ÿŽiÓ¦µÓê;ª }É›xÙe ôàÁM&›-qý: [@­©š÷'jô¤‹9„à.¦¸~Ô¦c7t!>½JK!زEÞVYV#…qÕ†Œ¡°°°EË¤Ï ÃÛ'6ð¥Kg®hƒÏ¾ý…óæq>V8j>ûöÛ6'Ðm]-ìÃeËPee¥°8KCUŒáÃeËóûïÏ}@:‹Ìªo¾AÅĉu?wpx`hˆûöaλï>×qÚºZ˜……–¤Ú÷´6 •¹ÑJ„Üh%Be´¡"±X ±XŒŠŠ téÒEÓÝÑj2ºôêÕ ë­&»ò‰¹sçæÏŸøøxÀÃÃ)))4hV¯^ÝN½: Àgÿ²tSÜäκ%Uðòùóu·›J ?žÇžô زðŽk|HÄóúûûïãï￯ÐÖÖBUt­ÅN=‹û=z(_}–14Äý=Ú|º­«…múâ‹/ÎÒVeeeؼbEóöéÓ¦¡Q´Z7Z‰­DÈV"TF±EÑñãÇqôèQ\¸pÆ Ótw´Z‡œ…ãúõë‰DJí... s73Æ  ‘œœ 777øúú¶Ë‹ððpÄÇ ñðá$TT66@X0wî›pss«Ûpýz@6V75hãP’‰¥«;83óÎ>Fp¢Œ§ ^e.öõB€}ÛVì¨ú½ú*›šB§ÁRÙõIª«ñrYêÿõB!„¼@h%ÂæuÈ+о-,Bãñxðöö†··w;÷ð÷÷ÂÔ©ŸbùràÑ#é´ÅõsguÃ7‚6'Ïpá‚ô¿]}.­­EÈõË`:f€¤šQòÜJŠ !„tÒ…T4A(¢º:FFÒŸ\ÿûßÕe¼*¾‘’<~,½ÝX=þÚY”èHÇY÷|z_.àÞB!^ll,"""ä³’‘ÆQ­&¶¶¶˜:5aaÒŽÿû?éByrÇK§ÍT:}À@—qg+¤?@è=½„“o,€O½oÙ*PD‘lµ0R§þja¤Žl%B¢ˆb 7Š-Ê(¶( …­mÓí”@«Mee%–.åCGGºR÷W_ÕÛ@6|àlóñd ´¹9Чâc÷*+±è~ºôŽè1¾r²A‹m>fkmÞ¼eeej?®¶‹ˆˆÐt´Ž¬Ð‡(Š•’:[¸QlQF±EŸÏ‡@ @ee¥¦»¢õ:d¡¶i8Ò$ààAédéé€m×Zé²ÝOŸS¦?ÿÜæcº»÷î£FI¯vËTK$p¿x Å$|z±¿oóñ!„Ò1Paóè ´š¤¥¥aÿþýHNN†lá<‘Ø´ @\œ4yT2|ãÑ#iò (ßøàvâ³ä0É9„Ã#V¶ùx„Byñ%''cÿþýHKKÓtW´%Ðjb`` á•WêÛÈH æ·gÃ7tu¥—ŒÛHV‹¯¾ZwûX~>¶?)–Þ)¾=ýFàe“—A!„bdd 41]+‘¢ZMø|>FŽ)_íH6Ýsa!P²ïY=p  ‚µçeC" €¤·s««19ù: .ÅÝT„ ƵùXmA…>ܨÐGúp£"Bn[¸QlQF±E‘««+FŽI«›¶%ÐjòèÑ#…ûcǽzÎH‡uÞMi£ †ou+úùFF€„1LHº‚rèl²vâ‡ákTr¬¶ BnT裌 }¸Q!7Š-Ü(¶(£ØÂ­aÎB”Q¡46ëV@øþflÂߥ ÉÉ€§g›ŽU^.ÈC,>ùøòKà?éiX–.]Úœ—ó;οúÚ>Ó!„B:*"l]V“[·naÆ ¸té’¼mÚ4`¢tøF®¡s›“g¸t©n:é  àRI þž.m(€lºPòL!„%—.]† pëÖ-MwEëuÈ¥¼µ‘­­-† ¢09¹Qm9Kb‡D!ð:·í8²_sy<À+@ŒA7 ˆ xò+þóîm;!„B:$gggáÌ™3šîŠÖ£+ÐjbbbÅÕ}Nž„®X8Šå彟ƒ,îÝøøÑ-ä>»­÷ Ç|]í©¬¥BnT裌 }¸Q!7Š-Ü(¶(£Ø¢ÈÖÖ>>>011ÑtW´%ÐjÂ9 ÿÙêƒ"}SÄ`ˆòòÞ­T[+Âüi¹8”ÿlné'1X×'ž]Û>DD•¨Ð‡ú(£BnTDÈb 7Š-Ê(¶p£"ÂæQ¡pÆg °·òòP>,/þ  0k°mÛóçÚ5éÌp¬€ÞÎxˆyªòðê“]8÷îÿ^Ï„B!6®@kʵk@^ÀdrBC¥Í{öÈ›[-6€¾XyCš<³Z˜¦~ƒ=ãþGÉ3!„BˆŠP­&B¡u?·>¾ ‘/¬"_Þû9ÄÆ˜wèQ%mH߉ÿ/„£¹c›úN!„Ž/66 …šîŠÖ£ZMlmm @ m%Ð~~€ €Àg³ËEFJçsn­Ó•ùÀ„lé¢ëø›Y ¦zMm{çÛ úp£BeTèʹQláF±EÅE¡¡¡ŠN”@«Iee%tyÌÜ\ !Aú@½Õ?þXúßÂB`ûöÖíÿÂ] ß6ocM1l2·aKH¤ zÞ~¨Ð‡ú(£BnTDÈb 7Š-Ê(¶(âóù¨¬¬ÔtW´ªÒ`ümÛ€9s¤·¯]úõH$€@Ü»ôè!ý¯®nóû—0I¸kTà%ÿ ÇG­ÀðžÃU|&„Bé計°ytZdÃ7ìíåÉ3èèK–Ho§¥‡5¾‹ŒŒ dddþ“‘!Ož‘}ózøRòL!„ÒN(V7‘8uJz{Ì¥‡§Oºv•Þnja•ÅkÖ`ñš5ˆ-.ÆÊô4icÙ=˜$ŸÁú*X‘…B!„p¢ZMþ8ñƽ3©Û~¨«¬7þYÆØ˜?_zûêUàÜ9ÅÇÅb1¢¢¢p23Ñ™˜pâ$ @m%üL3Ý c=ãö=¡BnT裌 }¸Q!7Š-Ü(¶(£ØÂM$iº Zh5)4.ć#8ÿå?¥ FFÀopn;¾4‘€õëëÚoo ßý0iå(}wÊÞ†ü_~‘>˜ð-pb& hdzP-*ôáF…>ʨЇr£ØÂb‹2Š-Üh%ÂæQ¡„‡‡cב]€#°õ0§Èõõ]ÂõFŸ3o°e‹tšè›7#£4 Œ¬þYÀý à“ÕÒ 7lÞp¾ÿôîßFI‘‹<ù&„Bi©¨¨(DEE!66AAATDغ­.Ž@ŸAÒäþWœ¦þvY²DZTÈðÕW@Ø‚0d Îî¸ÓÔmøöÛÀÁ@H5Œ^žJÉ3!„BžKhh(vî܉   MwEëéiºIHJÝí=vE8óM0ÌíÌ¡§£==èëè×ÝÖÕ‡ó|=°¶¶VÿÉB!„t”@«‹¸.Ú÷mjp?ñ_Ã=Ñ.[¸QlQF±…›H$‚¡¡¡¦»¡Õh‡šè—YÒÛG݃|ôóê?{?øØú ÏË} à ÐÓ²'lº ‡qßÿ¯ì&¦ÀãÇ“(^}–±±^ê ãR£ê‹” }¸Q¡2*ôáFE„Ü(¶p£Ø¢Œb 7*"lªAxx8bNïBú³:pPœæ‰ä³Éòmª$ì{ô³³‘Ô0ð_7vÍ–.^~™û £ËŠOP,L¥«.„Byn´aó(ÓR“âBC‡%†Àí$sü+l222 kcƒïsr°5'ù55òíutfcƒöÝ:£éúµ@JŠô_#$zfÈÉÉ£££:N‰B!¤S¢ZM*Í^.ÂK"`sRÖéÀ¹WãÏ´4ˆëýÐÝÐó»uÃ;;XëëfÍzŒåË?Ö¬€ï}T;V*î<ÕÝK\±1bjkkÕyZ„B!%Ðj"ruÅ9!>>øï›o"I @’lEBææXؽ;&ðùÐãñžûÑG=ðí·=Ÿ?¶O6ÀÂøGKŠQZ <}h”ÌÀ¹[‹àâ¢æ“j#*ôáF…>ʨЇr£ØÂb‹2Š-ܨˆ°yTD¨."øûã¹s‘ô,€êè`š­-®õïX__LêÚU)y¤«~ðôvƽEظ< wÿ¼‹×ïO’`gõâ%Ïú4† }”Q¡7*"äF±…Åe[¸Qaó¨ˆP ÂÃñ+/xçàÈt™6 ÿôôÄ\{{¼l`Т}ʨЇr£ØÂb‹2Š-ܨˆ°yTD¨áááØuë0uª¼Íú—_ <|vvvÍ>?** &õ‡p4²%~ûMú !„BÈó "ÂæÑh5±IKÿÍÌä÷ysæ ¤¤¤E 4!„BÑ”@«IPp0>˜5KÓÝ „B!mDc ÕDÿٲܤúp£BeTèʹQláF±EÅnTDØB!D]¨ˆ°y4º6lØ€óçÏCOOÇGXXŒŒŒÚý¸vvvØ]ݨ&·«­­Ý‡íÞB!„ÎŒèVpppÀ²eË ‰°råJ”––bÑ¢Eí~ÜW^yqq{Ûý8„B!¤y4ºÞ|óMøúú" S¦LÁÅ‹[ü\¯Œ }¸Q¡2*ôáFE„Ü(¶p£Ø¢Œb 7ÊYšG t+íÛ·K–,Áž={°téÒ?ä+£BnT裌 }¸Q!7Š-Ü(¶(£ØÂr–æuèZ,£ªªªÙmîܹ£ô×VEEbbb£ðW»••ø|>ÊËË[ü×|FFFë;O!„¢!”»4­Ã%Ðeee˜1c¼¼¼`ddccãF·Ý°a,,,лwo˜››ãÿød“’”––bÿþýØ¿?._¾,ÎÈ‘#±téRlذ+W®lïÓ!„B!Z¦Ã%ÐUUUÈÊʨQ£0~üøF·;xð /^Œõë×£¬¬ ?ÿü36n܈¯¾ú `ccƒ-[¶`Ë–-˜>}:$‰ÂÕìÛ·o£oß¾í~>¹sçŽÊdzµvŸb±GUé>ÛêøñãÍþêÐÞûLMMErr²J÷ÙVQQMÏࢎ}ž:uªÙŸÕÛ£Ÿ©ªªÂñãÇ5¾ÏæÎ¹=úÙŠ-Ü(¶p£Ø¢ŒbKçÐáh>Ÿ“'Obݺu0`@£ÛmÚ´ ˜;w.LLL0a¼ùæ›øî»ï ‘H”¶///‡@ ÀСCáëë‹;wâ_ÿúW‹ûUSSó\çÓ˜Žð%WTT¤òBŸŽð%W\\ܪþµÄ‹þ%'‹QTTÔªã5§#|É•”” ¤¤¤UÇkÅn[¸QlQÖb‹ªs–ލC/¤Ï>û O±¦¦†††˜9s&¶mÛ¦´ý½{÷àêꪴ?±XŒôôt¼üòËx饗ZÜ×_qqqðóóSZÒ󮮠††òû-jKHH€¹¹9zöìÙêç6ÖVSS}}}8::¶è¹µµµˆG·nÝÝNVˆàè訴¿#GŽ W¯^prrzî>7l;}ú4üýýaff¦²×%;;ýû÷‡A‹ž›êêjèêê6º]RR|||``` ´¿Ã‡ÃÏϯM}nØvâÄ 2DeûËÈÈ@zz:Üâç^»v xòäI£Û]¹rAAAJÏ-++ÃÙ³gÑ·o_•½.ÕÕÕ8þ<‚‚‚Töºª««Ñ£GçŠW‡ÆÄ‰ÛüÙ¯ß GGGÆ«äädxxxÀØØ¸Õñª¦¦gÏžÅØ±cUöºTWW#11¶¶¶WÅS™úñJ6Þ9??æææ¸~ý:üýýqæÌn2ÎÈÈ€““¾þúk,^¼XÞ~ìØ1„„„àܹs ´¶Ú¶möíÛ§Ô. ü„B!šÂU08uêTÌž=[½y1tÊ…T NNN íÎÎÎ «ÊìÙ³éMH!„ÒAt¸1Ð-ѵkWÒŸ/ê»ÿ¾Âã„B!„4Ô)h[[[èèèàÖ­[ íB¡`oo¯‰nB!„@§L õôô0tèPyÂ,“””www¸¸¸h¨g„B!DÛuÈ:::QQQò+ÌQQQˆŠŠBzzº|›… âÚµkˆˆˆ@NN~üñG>| .T[?322Œ7NåS鼨†>Ÿ@ é®h}ûö¡OŸ>prr˜1cpúôiMwI+DDDÀÕÕÎÎÎ7nâãã5Ý%­òñǃÇã!//OÓ]Ѹªª*èêêÂÙÙYþ¯á ´UBB {{{ú.0zôhùû¤{÷îÐÕÕUy}Ô‹èܹs9r$¼½½1qâDµÎÅ®uXäççÇlll”þíÚµKa»;v0;;;€Y[[³/¾øB­ý|çwØš5kXee%›3g[¾|¹Z¯­âããYbb"ëÕ«—¦»¢5Ξ=ËòòòcŒ>}𹏏0±X¬á^i^vv¶üuØ»w/8p †{¤=.]ºÄÞzë-fnnÎrss5Ý«¬¬d666šî†Öyòä ³³³c`b±˜²êêjMwK«ìÞ½›9RÓÝÐ }ûöeÑÑÑŒ1Æ6mÚÄþö·¿i¸GšÓ!gá¸råJ‹¶›1cf̘§OŸÂÊʪ{¥,66›6m‚‘‘fΜ‰eË–©½ÚhÀ€û¯ZC† Q¸ýøñcäææ¢{÷îšë”¨_¯Ð£G”——k°7ÚC$añâÅ8xð <<<4Ý­Q]]={ö ÿþèÝ»·¦»£¶oßøùùááÇ4„‘ömÛð÷¿ÿ]ÓÝÐ ò¸kgg‡ÊÊJ ÷Hs:dÝZšHž³³³QSS €§§'®\¹‰D9²†¨È–-[Øé“g™üÛ·oGVVþüóOMwG+¬Zµ áááT]ŽŽ†Š»wïâ‡~/L¡§×¹¿ïܹƒÔÔTLš4 ¦¦¦Ð××ÇÑ£Gå ±tv÷îÝÃíÛ·1nÜ8MwE+ìß¿&L€ƒƒJJJpðàAMwIc:wähƒôôt¤¤¤ÀÂÂB¾BXCååå8þ<>|øúúÊÓÕÕEmm­ü¾,qæñxíÞ÷ö’››‹«W¯";;~~~ðóóãÜîÊ•+8uêD"‚ƒƒñÚk¯©¹§êUQQ¤¤$Ãßß¿Ñ?Øîܹƒ .€ÏçcðàÁ°´´T򾯱cˆŒŒìc  pãÆ ˆD"Œ9’s‰D‚Ë—/#11½{÷Æ Aƒ”¾ØCCCѯ_?ìÞ½|ðΞ=«Žî·‹’’$$$ %%¶¶¶~i?|øGŽAVV|}}1qâDù*§‰‰‰¸|ù2¾øâ uv½]©"¶àÀòûÇÇ‘#G0a„vï{QEl±°°€±±±ü—Û±cÇâĉ;v¬ZÎAÕª««! å±eîܹœÛ•••áðáÃHNN†››BCC9§°Ý±cÂÂÂ:ÄmÍ[`ÅŠ˜0aÆŒƒ­[·bÓ¦MX¿~½:º¯}4=†äEÍø|>À°àà`Îí222˜@ `–––¬ÿþLWW—-X°€I$ù6...,??Ÿ1ÆXll,>|¸:N¡],^¼˜`<`+V¬àÜnÇŽLOO1‚…††2}}}öùçŸ+mwûöí1z̘1LWWWþ~9{ö,çvëÖ­c:::ÌÛÛ›ÙØØ0;;;–””¤°ÍŸþÉ<<ëÛ·/ cVVVlÔ¨Q¬²²’1ÆØþýûY¯^½äÿttt˜««+»}û¶ÏFuT[dæÏŸÏÖ­[×N½nªŠ-‡f3gΔß_ºti“¯›6{üø1300†‹-ÌÏÏuëÖ½ûî»ÌÕÕ•¹¸¸°´´4…íÄb1³³³cB¡P ½o?ªÊ[rss™……«­­eŒ1–““ÃLLLä÷;J [)!!}ûí·ìܹs,88¸Ñ7bHHëÝ»7+..fŒ1vüøqÆãñØÁƒåÛL›6}öÙg¬°°M:µÑ/†Á¹sçXLL +))a†††œçR\\ÌLLLØ'Ÿ|"oûþûïÇc)))ò¶„„¶ÿ~æääÄâââXzzº:N¡]|ûí·ì—_~aûöíkôKîêÕ« Ûºu+cŒ±êêj6dÈÖ§Où6'Nœ`ìòåË,77—åææ¾°…>ÅÅÅì‹/¾`ÇŽcüq£_rëׯg]ºta‰‰‰Œ1Æ233™ [°`|›óçÏ3‘HÄÊÊÊXdd$ TË9´‡»wï²#GްÜÜ\6bĈFcKPP d555Œ1Æ’’’ÎO5!·IDAT˜žžÛ¶mçö/z¡ªbKrr2‹‰‰aYYYlß¾}ÌÚÚšåää¨ë4TNU±åÉ“'ÌÁÁ¥§§³G±>}ú°äädu†J•••±}ûö±””¶fÍšFc˲e˘••ËÊÊbŒ1VRRÂ\]]Ù;ï¼£°ÝüÁ ÐîýnoªÌ[ÜÝÝÙÉ“'Ymm-ûî»ïبQ£Ôq Z‰è6hìK.33“éèè(Íêáææ¦p•9''‡3FþÁ---mï.«Ec_r[·neX||¼¼-??Ÿ`K—.•·-X°€MžüðÃþJ‘Lc±åîÝ» €Ò•Ó>}ú°AƒqîkæÌ™¬¨¨¨=º©vm‰-B¡Mš4‰ 4ˆ}ôÑG,..N]ÝnWm-²6___öú믳ݻw«£Ûí®©ØbooÏF­Ð¶`ÁfhhÈJJJäm‘‘‘ì÷ßo×~ª[syËêÕ«Úæ-111lÊ”)¬oß¾lÖ¬Y&æ>Ýnܸ‰D¢Týîåå…óçÏËïÛÙÙáèÑ£êîžÆÜ¾}<Oáu±¶¶†nß¾-oÛ´i“&º§1ׯ_G¯^½ ««+oóòò’?6pà@¬^½«W¯ÖTÕ®¢¢)))Jc£½¼¼ðÃ?àÁƒððð@dd¤†z¨²Ùi<==Ú½¼¼püøqÎçlß¾½Ýû¥i²ØRf†±¥OŸ>øõ×_5ÕEhIl€©S§bêÔ©飺•”” ''aaa í^^^‰DHKKƒ··74:~º#’å-\±¥~ÞŒàà`uwO+Ñtí ;;÷—\~~>ª««5Ñ-ËÎΆ££#LLLÚ½¼¼ä¯Yg”­ô^Ð××ï´¯KSŸ¡úw6M½.………vJ©ììl899ÁÔÔT¡b Å–†dçÍu«þã å-­G t;¨¨¨ùu2VVV`ŒuÚ/¹ÚÚZÎJf…I:›ŠŠ ¥÷о¾>LMMåï¥Î¦©ÏPýÇ;ÙçD6ㆌìsÕY?GµµµJ¯ @±…b‹2Ùû¡áwQgÿ QÞÒz”@·;;;PZ äæÍ›066†¹¹¹&º¥qööö¸ÿ>ªªªÚ…Ba§ž¯ÖÎÎNé½’››‹ÂÂBù{©³iê3TÿñÎFö9‘½2B¡fffJW`; Š-Ü(¶(kê3TÿñΆò–Ö£º899àþ€ÊëŒ\]]!‘H> %%%xøð!\]]5Ø3Írrrj4˜wÖ÷K×®]Ñ¥K—F_GGGMtKãdŸ“[·n)´ …ÂNý’Å–»wïÊÛ(¶Plábee+++Î×EWWÎÎΚ阆QÞÒz”@·ƒ Gˆ‹‹“·•””@(bòäÉì™fM™2úúúˆŽŽ–·É Ÿ¦M›¦©niÜäÉ“ñøñc°“'Oj°çí'//OþâóùÌÒÒR~¿þâ9ÑÑÑÌØØ˜3†ýë_ÿb...¬_¿~ò¹[;Š-Ü(¶p›6m1bswwgäïcÇŽÉ·ÉÈÈ`=zô`}ûöeË—/gAAAÌÚÚZ>ç|GCyKû iìZ©¶¶yyy€€€ß‹Åòí¦OŸìܹ§OŸ†¯¯/öîÝÛaÇYYYÁÇÇäÿ333…í–-[=z¥¥¥Ø°af̘¡Ö¾ªSqq±üý!û+>//EEEòmŒqþüy,[¶ ›7o†µµ56mÚ„÷ß_#}V‡'OžàÉ“'èÞ½;&Ož,ê6yyyáܹsøòË/777?~¼Ã.ýn``ÀùCCCùíaÆ᯿þž={póæMÌž=óçÏÇK/½¤Öþª Ån[¸yxxÀÎÎNé3dmm-¿íàà€ .`Ë–-HLLD`` ¶oßwwwuwW-(oi<ÆÓt'!„ByQÐhB!„BZhB!„BZhB!„BZhB!„BZhB!„BZhB!„BZhBQ³ÄÄD¤¤¤hºMÊËËùs矟Ïùx||<ÒÓÓ›ÜÇÓ§O£0×,!„t”@B:…€€àöíÛ í>D@@.]º¤¶¾,Z´_ýµÚŽ×ÅÅÅ8p œ1}út$$$pn7mÚ4ìܹ³É}ÅÇÇãµ×^CYY™|ß;wîÄ“'OTÝmBQ+Z‰Ò)\¾|€tźC‡ÉÛ+++qùòe…Ü:³cÇŽ!-- ?nóꆽzõÂÚµkall ÈÍÍÅŒ3‡®]»ª¢»„¢tšÒi :‡Æ•+WšÜ®¸¸ÕÕÕ m"‘%%%òûÕÕÕò¤[,ãÆ‰D ÏyðàRSS›’““!‘HÝ&;;‰‰‰Jû€¢¢"ÔÔÔ²²²pëÖ­&ÇCZZ’’’”ᤤÑÑÑððð€D"Aqqq“û’yüø1’’’”úçàà€¹sçÂÀÀ‰¥¥¥€²²2)ýÑRQQøøxÜ»w¯É׃B4hBH§‚ÀÀ@|öÙgMngccƒ}ûö)´EFFÂÝÝ]~ß¾}°´´ÄüKKKøúú‚Ïçã×_Eff&<<<àææ777L™2EéÕÕÕx뭷н{wx{{ÃÉÉ ñññ Û\½zžžžèÞ½;üýýaii‰7*lcii‰ï¾û>>>pttĬY³=¯ÄÄD¸¸¸ÀÏÏ–––ؾ}»üqwwwìÚµ gÏž…¥¥%œœœš|ªªª0aÂØÛÛÃÇÇ®®®HNN–?~êÔ)XZZ¢¸¸ð÷÷ 6 –––°´´âµ×^ƒ¥¥%BBBàáá>Ÿßä± !D“(&„t*k×®ÅéÓ§qúôi•ìoݺu8zô(òòò0~üxÌ;“&MÂ'Ÿ|‚¢¢"üðÃøå—_––¦ð¼Ÿ~ú æææ¸ÿ>ѳgOŒ?•••¤W¦‡ ooo$''£  X¸p!Μ9£°¯åË—cÊ”)xôèþüóOÎ~VVVbìØ±àóù¸pá>|ˆððpÌž=çÏŸ -œ>}:FŒÆX³ÃZ¾ùæøøø 33W¯^…‰‰ vìØÁ¹­³³³|üy\\c`Œ¶lÙ‚œœdddàñãǨ¬¬Tú†B´ %ЄNåÕW_ŨQ£š½ ÝRK–,App0ºv특s碰°‡™™fÏž >Ÿ“'O*|mÚ׈#ä·===cÆŒQØÆÃɉ‰ mƒ ’×€¿¿?^zé%ùUÚ¤¤$˜ššbÁ‚H$`ŒA"‘àéÓ§¨­­UØ×¨Q£šíçÝ»waff†€€yÇÃo¼k×®µðl5OVPI!Ú†hBH§õïÿ›³ýí·ßF~~>zôè[[[¬\¹‹-Ré±§L™‚èèh8;;ÃÅÅ'NœÀO?ý$/trr¡C‡pæÌ¸»»C ÀÞÞ½zõ’/ ÓæææøùçŸqñâE¸¹¹ÁÁÁK—.ŲeË¢ÒskÊ'Ÿ|‚C‡ÁÄÄD>ŒåóÏ?G×®]áíí wwwŒ7Ó§OWk¿!¤5xL6!„t`wîÜ|îa™ŒŒ TTTÀÁÁAaLo~~>âââ`mm ¬¬ puu ½Jš›› @ ŽD"AJJŠÒ¾222`hh(Š!»Ïç󑘘ˆÜÜ\ 8P> E} …HOO‡••|||Vñ»sçºuë33³½ÅÅÅHHH@II |}}áèè¨ðxnn.jkkѽ{÷&÷“––333…ùšËÊÊ••…^½zÇ㡼¼™™™pwwW{ÒÒRH$$&&"-- ÖÖÖ8p [t>„¢ ”@B!„Ò 4„ƒB!„V šB!„V šB!„V šB!„V šB!„V šB!„V šB!„V šB!„V šB!„V šB!„V šB!„V šB!„Vø š §#IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/Q8-1g-idx-sorted.svg000066400000000000000000006666221416254111300243530ustar00rootroot00000000000000 image/svg+xml PyTables-3.7.0/doc/source/usersguide/images/Q8-1g-noidx.png000066400000000000000000002423411416254111300233630ustar00rootroot00000000000000‰PNG  IHDRÐß}™SsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwX×÷?ð7½I•& €""R#*ˆ¢Ø±—hbCM,шŠ-F½$±&¶äcO"ÑX° Æé‚R¥—óûÃßΗa—²Š æ¼ž‡çÑ»w9;{ï""0ÆcŒ1ÆêD±±`Œ1Æcìm 4cŒ1Æcràš1ÆcŒ19pÍcŒ1Ƙ8fŒ1ÆcLœ@3ÆcŒ1&N cŒ1Æ“'ÐŒ1ÆcŒÉhÆcŒ1ÆäÀ 4cŒ1Æcràš1ÆcŒ19pÍcŒ1Ƙ8fŒ1ÆcLœ@3ÆcŒ1&N cŒ1Æ“'ÐŒ1ÆcŒÉhÆcŒ1ÆäÀ 4cŒ1Æcràš1ÆcŒ19pÍcŒ1Ƙ8fŒ1ÆcLœ@3ÆcŒ1&N cŒ1Æ“'ÐŒ1ÆcŒÉhÆcŒ1ÆäÀ 4cŒ1Æcràš1ÆcŒ19pÍcŒ1Ƙ8fŒ1ÆcLœ@ÿGTTT ** QQQ¨¨¨hìpÜ¢E‹°}ûöÆ£F=† 0cÆ \¹r¥±ÃykÄÄÄ`ÆŒ¸zõjc‡ÂêÙéÓ§1cÆ dff6Êú‰qqq¸~ý:222%†ú²|ùrœ9s¦±Ã©¬¬ 3fÌÀÑ£Gë½í§OŸbÆŒ8wî\½·ý:¬Y³kÖ¬þ_VV†  <<¼£b²pýŽ‹GïÞ½¡­­¶mÛ¢mÛ¶ÐÖÖFŸ>}ÐØáÕ«¹sçâÈ‘#2_Û±cÇk99×—ÀÆÆ6l@xx8ÒÒÒ;¤·Frr26mÚ„û÷ï7v(¬ž…‡‡cÓ¦MÈÍÍ­µî¥K—0uêT¸»»CCC 8xðàK­÷ŸþA= ££ƒ–-[¢C‡022‚¾øâ ÄÅŽT»åÏ?ÿÄ‚ `jjÚ ë[¿~=¶lÙRçúeeeØ´i.^¼Xï±dffbÓ¦M¸víZ½·ý:ìÛ·ûöíþ¯¬¬Œ¨¨(LŸ>½£b²(7vìõ9yò$F…ââbLŸ>ݺu„††bãÆpqqÁáÇáããÓÈ‘ÖU«Va̘16l˜Ôk³gÏn°‹‡¼îß¿ãÇcêÔ©øþûï;ÆÞJ¿ýö¶mÛ[[[ØÙÙáÖ­[/ÕÎÚµk1þ|hhh`Ê”)prr‚‰‰ ¢££‰-[¶ ,, aaaõ¼¯Ï²eËн{w8::6ÈúvìØ===L™2¥Nõ•••±xñb¸»»¿æÈÞN3fÌ@×®]ñ矢W¯^ûÿ8~GåççcüøñPPPÀßÿ:¯õíÛC† A=0nÜ8DGGCCC££}ýfÏžÝØ!T+>>àééÙ¸0öûòË/±lÙ24iÒÛ¶mÃäÉ“ånãæÍ› „««+‚ƒƒaaa!¼Ö£G@`` öîÝ[oq¿n!!! ÅñãÇ;”j)++cÉ’%Æ«K—.hß¾=–.]Ê ô„»p¼£Ö¯_ÔÔTÌš5K”033ƒ­­-üýýñøñcQªû¾oß¾011ÁÚµk1eÊøûûËŒýÞ½{ðõõ­µ»CJJ ,X€Î;ÃÔÔæææðööƱcǤê>|¾¾¾HNNÆæÍ›ÑµkWãƒ>¨öÎíÎ;áááfÍšaÀ€ÕžªcnnŽ&MšÈµLUÓ§OGEE6oÞ,Jž+³µµÅòåËEe3gÎÄ„ ““#ܵ®ü‹Wtt4FŒXXX oß¾ µ±}ûvôîÝÏŸ?ʲ²²àëë‹þýû£¬¬L(ONN†¯¯/>\ë6­[·fffèÛ·¯¨<,, ¾¾¾ Ão¿ý†ž={ÂØØîîîÕó0`,--amm ???ܼySx½´´Txß%dz䯦ni’å*S‘lûîÝ»qóæM :fffpvvÆÚµkeŽå¹sçˆfÍš¡sçÎX¹reûæäÉ“èß¿?š7oŽV­ZaĈˆ^ÏÎÎÆ Aƒ0a”——‹–=vì˜ÌÏÍ®]»Ð³gO˜šš¢mÛ¶˜8q"ž={&µî‚‚̘1vvvhݺ5>ùä“÷ÑĉqåÊó&!öNzÿý÷ effV['55•P×®]…²­[· “ªïééImÚ´•…‡‡“)++S¿~ý(((ˆ,-- -Z´HT×ÙÙ™Z¶lI®®®Ô£GZ°`Mš4‰rssI[[›úöí+µÎøøxRTT¤Ï?ÿ¼ÚíHII¡ÀÀ@@H´`Á¡Ž‰‰ õêÕK´œšš¹»»“™™µoßžfΜIFFF€Ž?Nþþþd``@Ó¦M£Î;úàƒDmTTTPÿþý ÙØØÐüùó©OŸ>¤¨¨H-Z´ ¬¬¬jã&"Z¹r% <˜ÐàÁƒEqWTT ÒªU«è£>"rtt¤‚‚¡S§Nòóó####     úå—_j\ÿ²eËHAAlmm)00Ö®]K&L 333Q½~ýúêÛ·/­ZµŠÆŽKŠŠŠdggGyyyB½ .0`©©©ÑàÁƒéã?&UUU211¡;wîPëÖ­ÉÎÎŽfÍšE€V¯^-Z_¯^½H__Ÿ<<<ÈÇLJ֯_O¤¬¬L¶¶¶”““#µÎÝ»w‹Úøâ‹/™ššÒÌ™3iÔ¨Q¤ªªJºººôàÁ¡ÞÆ -_¾\(»ÿ>ijjRÇŽ©¤¤¤Æ}HD4fÌ@½{÷¦ 6иqãÈØØ˜ÜÝÝ©êi¶M›6Ô©S'©6ÂÂÂmݺUT>eÊ@4{öl>|8©¨¨¾¾>ÅÄÄÈÜ÷†††4qâD ¢ÐÊ•+ ]ºtIj½S§N%ú÷ßkÜÆ£G’¹¹9Mš4‰V¯^M“&M"sss@Û·oÕýæ›o >œÔÔÔèÃ?¤Q£F‘ŠŠ ©¨¨Ð£GDõ/^LÈÃÃ֭[GÓ¦M#}}}êÞ½; ¸¸¸c«Jr;pà@—),,$EEErss“k]DD:u¢æÍ›S§NÈËË‹‚‚‚hܸqDDtñâEÒÒÒ" ¤¯¾úŠÚ¶mKŠŠŠ¢ývøða@'NœÊ~ûí7@èòåËBù®]»]¼x±ÖØŒÉÇÇGªüèÑ£€† B***Ô·o_  &Mš:s挨þHYY™Z¶lI‹-¢ùóç“……©ªªÒüADD¥¥¥Hdff&œ‹k¼š>}ºP&¹>õìÙ“ôôô¨S§NôÅ_©©) ¯¿þZÔÆÕ«W©I“&dbbBAAA´|ùr²µµÎ]ß|ó¨þüùó ÓôéÓÉßߟÔÔÔH[[›nß¾-ÔÛ¶m …  e±±±¤««K...TTT$”:”PË–-iÞ¼yÔ¿RRR"sssJOOê•””››)((И1chãÆäççGíÚµ#+++rqq‘ÚG’óÃúõë«Ý¬aqýŽ200 ##£Zëikk“‰‰‰ðyèòòrrqq!---JNN•6ŒTTTèáÇB¹³³3 I“&Iµ@ŠŠŠ”˜˜(* "tçÎZ·3FækÕ%ÐhçÎBY^^©©©‘ŠŠ õìÙS”¤Ž=šÐÝ»w…²;wúꫯDm_ºt‰ÐŒ3j[r!;zô¨¨üçŸ&4mÚ4Qùúõë¥. ’ZEE…âããk]'уHEE…ºwï.ÚN"]Ž9Bhâĉ¢:[¶l!$”I’8555ŠÊ;&ÄW9Q-))!²°°µÝ«W/áâ^QQ!”ÿøãR_Îd%Ð’2*++ÊãââHII‰|}}Eëóóó#%%%ºt郃éééÕ)q»rå  ?üPTþÝwß ÉOeò$ЧOŸ&4yòd*//Ê£££IAAüüü¤¶YIII*NOOÙÊòóóIWW—ºwï^ëvæåå‰ö%Ñ‹Ïz«V­ÈÐÐJKK…rIݲeKQâpâÄ @³fÍÊâããIUU•<==Em?~\Ø ‘@ß¾}[HúåÕ©S'@cÇŽ•———“““©ªªŠ¶!77—lllHWW—222ˆèÅ{¤  @3gÎê}öÙgdccCÆÆÆ¢sŒ¿¿?ijjRqqqq¥§§ ÇOU’󎺺ºè MLLŒðeY"//LMMÉÐÐPô~¦¤¤®®.µlÙRKuÇxujJ  :Qqq15mÚ”š6m*j£[·n¤¤¤$:ïdgg _ò*'Ðaaa¤  @C‡sÉÉɤªªJ^^^¢¶GŽIŠŠŠtæÌ*..&777ÒÖÖ]ßöíÛGhÞ¼y¢e#""e’sgÕ’/ËÎÎÎRûèÙ³g@øbÆ'Ðï ÜÜÜj?„UµiÓ† “< ôÅ‹ }öÙgRuOž}DusrrHQQ‘éÉ“'¢× D(??_(“$ÐU ¬¬,RRR¢–-[J­³r=dÈj¿xõìÙ“”••Ew–³²²ÈÊÊŠÌÍÍiÔ¨QRïsM$½ªï_ZZ)((¼RÝ·o_RTT”ºcKô"aPWW¾`Höƒ»»»Ì8%wØ$ ÑîÝ» >|¸NÛ*Q^^NOŸ>¥ÔÔTáN^å»á’zþüù¢å HMMú÷ï/”­^½šÐ·ß~+ª[ZZJ::: –@ÿþûïÕ~éݾ};yzzŠþRSS…×% ôÙ³gEËEFFòöö–jsæÌ™€víÚ%”¹ºº’“““ðÿ¶mÛR@@9’ºuë&”›™™QÏž=kÝ&É9zݺuR¯IÎ;ƒ–zÍÚÚšZ·n-ü?88XøBZ•äó{þüy¡¬>hKKK©úÇ'ôìÙ3""JLL$2×ùé§ŸJ%Ð~ø! ëׯKÕ0`)((ˆÎG’/<&&&ôñÇÚ·oŸh¹Î;“ºººè †DÛ¶mEçI™Çµä3\ݵ»iÓ¦Ô¹sg™¯±†ÇƒßAÚÚÚÐÑÑÁÓ§Ok­ûôéShhh@MMMîõH¦ »~ý:üüüD¯Iúë=zôHTn`` s¤µ““<<<°sçN,\¸JJJ8vìRSSkíÇö*¼¼¼ ®®.*³··Ghh(z÷î-Uééé"BTTš4i‚±cÇJµ]TT„ÔÔTTTT@QQþáqqqÐ×ׇ›››¨ÜÄÄŽŽŽxðàÊËË¡¤¤$¼Vµ¿mMûì3 <¸Æý" EEE©A ÆÆÆpttÄ?ÿüS§vªÛMMM|ñÅR¯=yòEEExüø±¨¿nuÇÀäÉ“ñ¿ÿý{÷îÚÛ±cŒ¥>¿²TTT`Ó¦MرcbbbD}r}Û[µj%*«Ú®††Ú´i#|†}N%ƒô$”••áåå%³õë`nn¢Ø$TTT„óÄíÛ·…}_™¶¶6ºví**‹‰‰™3ùøø`Æ BèÞ½;Ö­[‡§OŸ¢¼¼<À×_ììlLž<ùùùHJJBJJJ¦5“ô÷733«¶Ž¬÷ÞÑÑ—.]þ/™¶OÖ±åãャG"&&FêóZªöÝ Ì •žžá’µþ=z`×®]¢²èèh¨ªªâ믿–ªÿèÑ#bccáàààÅ{{øðatîÜ?ýôÆÑ£G‹–{ðà´´´0~üx©6óó󑜜Œ’’¨ªª"66VVV°²²Õëܹ³Ôõ¨2ssównúÙ·'Ðï(;;;\»v ÐÔÔ”Y'++ YYY°µµ­S›Te•d`„¹¹9ŒŒŒ¤êà½÷Þ•5oÞ\”ðU6yòd|ôÑG8uêúõë‡íÛ·C___æ´tõEÖ #™¯IÊ%û¡  ………°°°@³fͤڑ”½l——SSS™Ëš››ãŸþAaa¡(Ϊ hM²²² ¡¡QmZ9###¨¨¨ÈŒãêÕ«xþü¹(®n¿Ö´¿«_êêê000¨vEEEÕËÎÎF“&MdN]hjj OOO© UQQ‘00Iž)óòò ¥¥]]]™±Ö5®ºý@íÛáíí-õ¾Tw xxxÀÙÙ;vìÀ_|»wï",, 2ßÛª/^ŒeË–aРA˜:u*`jjŠ_ý ,@ii©Ô2ÚÚÚRe ¢mÍËË ;É“$µ Ar”• |òÉ'øä“O#GŽÄ¡C‡¤ê˜™™AUUUT&™¿ZÖvHÊ*ÏqÝ£G¬]»çÎÎÞÞÞÈÍÍEii).]º$$‹|ðA­Ûdmm àÅ ÃêÈó5oÞ¼NÛQŸª‹ø¿ÏŒ¼ÇPmŸ«.]º@KKKT^TT$ $¬º\ii)rssaeeUãµ@²|^^Z¶l)UOMMMæ9O"))I¸‘Ã'Ðï¨nݺáêիعs'>ÿüs™uvïÞ ¢;m’dûÉ“'¢ºÅÅŸuë–èäЦMÀ!C0jÔ¨WŽyذa˜1cvìØ{{{œ9sŸþù;Åž––ÌÍÍ¡¯¯­[·Ö{ûVVV¸rå RSSEû½´´ÿý7LLL^iÖ„††âéÓ§066®1ŽóçÏ#>>^tǤ¢¢!!!ÐÕÕEÓ¦M_:Žê!""]ºt­óâÅ‹044¬qÛÛ´iƒ7n`ëÖ­ÂŶ&=z4š7o{{{,Y²ÞÞÞððð¨uYkkk\¾|‘‘‘¢_ $ïSUššš2’õ$Å6mÚàßÿŶmÛj£.¦L™‚I“&!$$¿ýö0qâÄ:-»gÏXXXààÁƒ¢DñîÝ»¯“$É»pá‚ÔÝPygâxÚÚÚppp@xx8¢££…óÛ«Ü‘?þ<>þøcÑk’'ãU¾kßµkW¨ªª ´««+ ```€V­Záܹsˆ…¾¾¾ÔÍ Y$ÉVå»Ü/Cò¹?þ¼ð<š¶£.Ÿ¹ú$9†.^¼(u½“u µiÓ.\ÀæÍ›«½¡SYVVF´oß+V¬@÷îÝáååàÅ/ÖÖÖÐÒÒªÓµÀÚÚׯ_—ºÁƒ””™7¤222••Å ô„§±{GÍ;zzzX±b…0Ïpe X³f ´´´DÐ-Z€ÔcOOž<)u‡ÁÝÝêêêØ¶m[½<\MM Ÿ~ú)Nž<‰Å‹ƒˆê|q^üìXµËÈëæíí7nS´Õ§N:Ξ=+*¿rå ž?^§ä®&’.;vì¨SU|íÚ5dee½r5©zñ»qã233¥º“Tåííììl™w «""Œ3ééé8xð ~þùg4kÖ £FªÓT„’XªÆ.š’L¢E‹ˆ‹‹Cbb¢¨ü—_~‘¹OŸ>Eppp­qÔŇ~lܸ?ÿü3zôè!óNXUD„ôôtØØØˆ’çââbœ8qâ•b’쿪ÇWRR¢££_©mymذeeeøüóÏe¾wòrrr‚–––ÌGhÿõ×_ :–555áîgÏâüùóèÞ½»ðZ÷îÝñ×_!$$^^^uúUËÀÀ¦¦¦¯œ@wèÐŠŠŠÕn‡ŠŠŠèËcCŸ‹mll`dd„©_rdÅìíí‚‚üüóÏujܸqHNNÆþýûñÓO?ÁÒÒ~ø¡èñîÞÞÞ¸sçN¦™ëܹ3JJJ**¯é ãÇíÚµ«S̬4JÏkÖ víÚEÊÊÊd``@[¶l¡»wïÒÝ»wiÛ¶mdhhH ´ÿ~Ñ2dffFFFF´yófЉ‰¡åË—S‹-ÈÀÀ@j»+VðbšµÈÈHÊÏϧ””ºpá}òÉ'tóæM¡®³³s­cbb„W]ºt‘k{çÌ™Cêêê´zõj:{ö¬hÊ®êŽ1BªI“&©Á…²yIF¡›ššÒÿþ÷?JMM¥¼¼<ºwï­_¿^4õQuªDøôéSÒÑÑ!333:}ú4åääÐåË—ÉÖÖ–”””èÞ½{B]É ByMUTT»»»03F\\=þœnܸAB½ÌÌL200 úã?(''‡ÂÃÃÉÞÞž)22R¨[Ý”rDD–––äéé)U.™Â¬ò€š^½z‘šš5kÖŒ~ùåÊÏϧ[·n‘‹‹ )((ˆÿÈZgQQÙÚÚ’ŽŽmÚ´‰©  €¢££é‡~ ñãÇ u׬YChÍš5BÙßÿMJJJ4hРZ÷cQQ5oÞœ (88˜ èÚµkdooOúúúRƒ%3·ôïߟÂÃÃéܹs4|øp211‘:¾òóóÉÚÚšôôôhóæÍ”””DùùùEÛ¶mÍ®PÓ¾¯ì³Ï>f7¨mšÃÊ$ƒk·mÛFYYYCC† !@§NêJVž.PÂÙÙYj°WÇŽI]]víÚEyyytÿþ}êÚµ«°ÿê2ˆ0!!vïÞM»wïze•ýÕD²,--iË–-JñññtýúuúöÛo…Y*ÏÔ©S'©s£Ä’%K„Ùâââ(--¾þúk©YT$–.]*¼?ýõ—P.™æ}÷ÝwuÚ"¢R³fÍD³¸UÞ‘,£««+*;v¬0ëÎãÇ)11‘¦N*sà¥dš½yóæÑŸþI.\:ŸVVÓ ÂÊç" YÇ—dv¢±cÇRbb"eddТE‹„ã³ò Â’’rpp ---Z·nÅÇÇSAA=|øvíÚ%šME2›NåY"""HEE…úôé# âMOO§¦M›’©©)íÝ»—RRRèùóçtïÞ=Ú°aÍ;WX>..ŽTUUÉÖ֖¨°°NŸ>M¤­­-ó:)™¹CÖÖ88~ÇEDDpâ•üµlÙ’BBBd.súôiÒÕÕꪪªÒÁƒeÎ]QQA›7oFËWþkݺµh:­º$ÐD/fI€ŒY0jMcÇŽ%333^L¥&ñºh¢sKæÝ®ü§¥¥%š²­:5]È®_¿N¶¶¶¢véôéÓ¢z/“@½˜I2¢½ò_Õ‘ï·nÝ¢¶mÛŠê4mÚ”Ž;&ªWŸ ´‰‰ ýõ×_¤¡¡!|©ÒÔÔ”š1¢ºu¦¤¤³qTþSQQ¦‚ —ºJH™Í›7×¼éÅ1`mmM„Xƒ‚‚„ù¡++--¥#FˆbêÔ©“ðV=¾’’’hÀ€RÛ¡ªªJS¦L©u?TuïÞ=@&&&ušãZâöíÛÔ¢E Q ýúõfòx•úñãÇôÞ{ï‰ößÇL .¬s-ùU÷wáÂ…:oë±cǨyóæRmH’Þ«W¯h¡U¿ IDATŠê×”@—––RPP)**ŠÚ5jeggKÕ¿|ù²pþª<½dFF†°oîß¿_çmùûï¿ež_äM óóói„ RûäóÏ?M{)‰uêÔ©ÔªU+¡^MÓkÖG]^^.Ìý^ù:÷×_I%ÐD/fÉ9r¤Ôö(++ ³DFF’ššuïÞ]ê ˆdö˜Ê3œDGG“———T›šššRÏEøóÏ?…릂‚)++Óª½Nº¸¸ˆžÙÀŸQ5wcï”'OžàÖ­[HNNÆœ9s ¤¤„ÐÐÐjûùeddàúõëPVVFçΡ¥¥…'Ož ¼¼\æ Œ‚‚DEE!&&:::hÕªZ·n-ª“’’ æáÀ‹ŸÂnß¾”””G$×$99åå倪¤¤$¨ªªŠf€HHH€¦¦¦T³gÏž!//–––¢¾|ÅÅÅHMMEÓ¦M¥¶ðàÁÀÌÌ ®®®uŠ¿  @è‡,kÀgYYîß¿‡ÂÊÊ Rí"-- FFFRƒ_ê"99wïÞE~~>¬¬¬àêê*õqYY¢¢¢æÍ›ÃÑÑQªzQQž233ѬY3899 þ$﵉‰‰Ô¶TTT 11ªªªµ³À‹ã#22Ïž=ƒ»»; 1vìXìÝ»WæÁÛ·o#&&íÚµƒ]Ç<~ü÷ïßGvv¶°:::uÞ•×ëââ‚yóæaÅŠµnWe………ˆˆˆ@NNÜÜÜ`nnŽüü|¤§§‹öaNN²²²`nn.5@1%% Rƒ­ÊÊÊðÏ?ÿ ))Ih[Ö±QÉç¨:¦¦¦rŸORRRðàÁdgg£U«V°±±‘¹o%³íÔ4è1##·nÝBQQœeÈþï¸SQQ‘j/)) åååR³7ÔFÒW·òÓk:ï<}úEEEB—¾ÊRRRpûöm(**ÂÙÙ¹Ö·©©©(..®ñ=”œ?utt„AtåååHJJ‚®®®Ôg¾¦ã+!!7oÞ„µµ5Q^^ŽÇC___æ@ß'OžàÞ½{xöìLMMáèè(¬/33¹¹¹2÷‘$fYï“äZðüùs˜™™ÁÅÅEæ¹=77×®]ð¢«\“&Md^'CBBàíí3gÎHÍVÃ'Ð/áÀؾ};~ûí·G̾©ÂÂÂàããCCC\¾|¹AGº×Fr¢X±bæÍ›×Øá°FR9~›Õ”@7–¾}û"44>”šR½›Îž= ܼy³Öi+Ù›ÇÏÏééé¸|ùrc‡Â*áY8ä”’’‚­[·âæÍ›())iìp^JçÎqæÌDDDàÎ;oDŒ>|666˜5kVc‡ÄØ;£¨¨+W®Ddd$Nž<‰uëÖqòü"™/33³±Car*++CûöíѧOŸÆ…UÁ ´œ¦L™‚µk×¢gÏžÊ+éܹs­34¤ÐÐPœ8q}ûöÅçŸ.5Ÿ*ûo111©ö'î·‰¡¡¡\ss¿.%%%سgÌḬ̀jÕªj§¶dï.¾)ñvRVVÆÂ… ; &wáþ}ûpýúulذzzzˆŠŠ’ë Œ1Æcìí÷Ÿ¹]RR‚øøx¨««Ë¼¼qÿþ}$''ÃÕÕUôgZZ6lØ€‹/6TÈŒ1Æcì ôÎ?HåÊ•+pssƒ¶¶6Ú´i#õ4(‰ÌÌLxxxÀÕÕãLJ©©)–/_.¼ŒôôtôíÛ^^^xþü9üÊÔ3ÆcŒ±·Ë;ß…#44ÁÁÁpssæM› ¦¦&šÊGbĈˆˆˆÀ•+W`ff†}ûöÁßß'OžDïÞ½QXXˆœœ¡~›6mжmÛÖ:½cŒ1Æ{w¼ó te¾¾¾(**’J Ÿ>} añâÅByË–-áää$ó1º’ZÖ3ëcŒ1ÆØ»‹oˆŒŒDii)ìííE厎Ž—¹LtttÛÿñDZÿ~©òêúb3ÆcŒ5”ÄÄD©²Ñ£GcüøñÍÛáï]’'ÿ´k×NTîèèˆôôt”••½Rûû÷ïGDDJKKE剉‰(..~©²ÈÈHïÔ×v”””àêÕ«~¾ªî|*!ë|•‘‘ÒÒRDFFʼñÇ*i„LJ7š^½z‘§§§Tù† ¥§§‹Ê¿ûî;@YYY¯´^ooortt|¥6ª:zô(=z´QÛ,,,¤I“&½t›.../WŒµ™>}ú+¿_¯Úæ©S§èÀ/Ýf«V­äН.ÆŒÓèmRjjêKµO...r­¯6YYY4}úôFo³¶ýXS›sçÎ¥¹sçʵ¾Úð¹E6>·ÈÆçiï¹ÅÑÑ‘¼½½åZß wᄹœ>|CCC¡üÁƒPSSƒžžÞ+¯£r»õÁÎή^Û{™6•••ѯ_¿—nÓÓÓºººr­³6¾¾¾PWWoÔ6mllPTTôÒmvéÒE®øêÂÏϯÑÛìÑ£š4iòRmêêêÂÓÓS®õÕF]]¾¾¾Þfmû±¦6;uê$׺ê‚Ï-²ñ¹E6>·H{Î-õ³¼‹8`aa¸wï<<<„ò;wiÞ…‹ÜëPß'­—iÓÆÆ¦ÞÛ|UoÊE®¾Û|ïÂEîuàs‹l|n‘Ï-ÒøÜòßÀ} ¸»»£Y³f¸qã†PVTT„;wî`ðàÁcŒ1Æ{Ó(-Y²dIcñ:åää`ãÆ Å… ••…ÂÂB„††ÂÞÞPTT„’’¾ùæhjj¢°°³fÍBRR~øáèëë¿R .\€ºº:†ZO[õn°±±±±1ù{\evvvüóYÊÊʰµµ…Ac‡òF144D‹- ©©ÙØ¡¼QøÜ"Ÿ[¤ñ¹E¶Ó§O£iÓ¦ zçþmóΟ] Œàà`èëëÃØØXøåÑÏ3fÌÀ·ß~‹}ûöaذa())Á¥K—`mm]/q„„„`ìØ±2ç”þ¯úþûïñüùóÆã³råÊÆáóüùs|ÿý÷Æ'44T4Òž½ÀçÙøÜ"Ï-bÁÁÁ;v¬ÌÎ1±ÿÔƒTËØ±c{öìiÔ8cŒ1ÆjÃyKíxáâÇä;IŒ1öùùùñOÒŒ±zñÎwáx[ðO±Œ1öúœ>}·nÝjì0cï¾Ý@ª>H–.]ºðÏ%Œ1öxyy5v.**ªÁ§ÿ{Ó•••!>>¾NSþ—CMM­±Ãx£ñè’––ÖØ!0ÆûãA„Òx¡lœ³ÔŽèÒ¢E‹Æ1ÆØÿÂ)MOO7nlì0Þ8œ³ÔŽè‡ƒâîÝ» cŒ1Ƙ”»wïâàÁƒˆ‹‹kìPÞxœ@7UUUèééA]]½±CaŒ1Æ“¢®®===¨ªª6v(ofŒ1ößÅ ô[£ ÒÒ–ÔZËÖ6þ¥×PZ ddÌ`Uc=“Úãxúúú¢Ÿóòòàää„3gÎàÔ©SèÝ»wÚ±´´”º³0cÆ Üºu 3fÌ€»»{½Æý&ÑÖÖÆßÿ.]ºe'NÄÅ‹áåå… Ô)îÙ³'bbbвeKDDD sçÎrÇÒ½{wtïÞ]îåcŒ±7wáh òv?`ÿG[[cÆŒܼyOž<ÁÈ‘#±wï^™õ÷ïß‘#G"55µNí§§§cýúõèÝ»7áææ†?ü5.·{÷nøùùÁÕÕãÆC|||·)66S§NE—.]ðÞ{ïa̘12§8ŒÇ–-[0xð`ØÙÙ¡sçΘ:u*bcckl_KKK”|8Ñ¡C|úé§ÕËÎÎF`` >øà¸¸¸à£>ÂÅ‹¥ÚûöÛoññÇ£¼¼kÖ¬AÏž=ѵkW¬X±B8÷={VØÏ~~~¸wôôtŒ9¿üò "##1qâD¸ººbÀ€2÷åºuë0nÜ8”••aÕªUèÓ§Ú¶m‹çÏŸ×yÿ×ð Bi<ˆP6ÎYjÇ ti¨ù@vöËý•”4Hˆ¯„ˆ`jjЇbáÂ…R‰aáÂ…xðàš5kV§6Ï;‡eË–AWW¾¾¾°°°À±cÇàááQm´lÙ2|õÕWhÙ²%:vìˆ#GŽÀÍÍ wîÜ©u}¿ÿþ;œ±cÇhiiÁη‚›››(ቅ‹‹ ¾úê+¨©©aĈpttÄéÓ§qõêÕ:m[UD„´´44oÞÊÊ óÔýû÷qèÐ!äææ e·o߯¡C‡0gÎ|òÉ'(++ƒ––:„Ž;Ö:àðÑ£Gèܹ3Nž<‰)S¦ÝCfÏž>}úà?þ€³³3ˆk×®…‹‹ ÒÓÓ…åSSSqèÐ!lݺ~~~ÈÊÊ‚‡‡”””••…C‡aïÞ½ðôôÄå˗ѦM„„„`øðá8pà€(–øøx¸¸¸`éÒ¥xòä Þÿ}„††bèС zéývèÐ!üòË/èÚµ+8;;;Ü»wsæÌ‘ꇞžžŽöíÛcùòåÐÕÕÅ|€{÷î¡_¿~Xºt©¨nII |||0nÜ8$''£GpssÃÍ›7E}dcccáììŒM›6ÁÔÔݺuCDD>øàlÚ´IÔæ•+WpèÐ!Œ3K–,¡¡!„   üùçŸèÓ§>|SSSüþûïxÿý÷Eïs~~>:„={ö W¯^(**BÏž=¡C‡bݺu¢uþý÷ß8räF…;wÂÔÔíÚµ½ô>×ñ Bi<ˆP6DXÄ^»1cÆP«V­h̘1tôèÑjëŒ3¦Ú6ÜÝÇ@uø«k=Y‹ ˆ«µž‰ÉbŠ‹‹{-ûÊÄÄ„,--Ee¹¹¹deeEè?þ "¢;vˆþ/ñ×_úþûïe¶?}út@aaaBÙÓ§O©  @T¯°°LMMÉ‚ÊËË…òo¾ù†™™=}úT(¿ví ˆÚ z_sssÉÄÄ„Z·n-Z>==ZµjEÖÖÖT\\LDDkÖ¬!téÒ%Q›”ŸŸ/sûjóí·ßZ¼x±Üˆ……š>}º\ËIöÙƒ„²ÀÀ@@ööö” ”oÞ¼™ÐªU«„²¸¸8QÌdddDmÛ¶¥øøx¡ÞÅ‹…ý]TT$”_¾|™Ð„ „²S§NRTT¤[·n‰â½yó& tâÄ ¡<))‰”””ÈÕÕUTРA¤  @W®\ÊŠ‹‹Éßߟ”””èöíÛBùˆ#HMM­NûMÃÒ¥K©¤¤„ˆˆ ¨M›6¤©©I………BÝÉ“'úùçŸE1x{{“’’Ý»wO(_ºt) +VH­3##Cø÷ðáà 9sF(ËËË#gggRWWíû#FêÕ«effQii)y{{“¢¢"Ð7„ú;wî$´fÍ¡Lò> ýû÷ å………Ô¡CÒÐРÔÔT¡|àÀÂ:%Ÿ™šxzz¾ÔqÏØÉÑ£GE9 «ßn ]ºtÁž={àççר¡¼ñ²²²0wî\Ì;C‡…‰‰ âããÑ£GôéÓ0zôhèèèà‡~-»cÇhjjÂßß¿Îë322‚†††¨L]]ãÇGrr²¨ë„¯¯/ŒŒŒ„ÿ»¹¹ÁÑÑÇŽ«ñîéþýû‘––†iÓ¦‰–744ć~ˆ¸¸8DFFxÑxñ3ze ÐÔÔ¬óöI\¿~_~ù%0þ|¹—>úè#Ñhïxq×Z–ãÇÃÛÛmÛ¶ÅåË—aii)¼¶~ýz(((`þüùPSSÊ=<<àêê*³@‡àìì,s]ÎÎÎÂñèСEEEøßÿþ'µü„ „ãVYY>>>¨¨¨€‡‡Þ{ï=¡ždƒ¬nKZZZ2dˆðuuu 6 ………RwÿÇ?ô±úáçç‡={öÈìÈÄxá;ÆÈxÙÜèÈàÊ•úçeäååaÛ¶mtèЃF@@€ÐWKK }ô¶oߎ””˜™™!-- ¿ÿþ;üýý¡««[çõ~úé'lݺ±±±ÈÈÈý œ˜˜ Ñ2|ðT;>>>¸sç=zT팒>ˆáááRɃ$QŠŠ‚»»;† †/¿ü @‡àççÿ—š^èÞ½{èÝ»7ŒŒŒðǼ1 GÕ/”044”ùóapp0–-[†!C†à§Ÿ~%ÉÀ‹dLWWëׯ—Z¶´´™™™ÈÌ̽7}ûö­slÀ‹/JáááÈÍÍ…®®®Ð7//Rõ•””êܧ[hkk‹Ê\]]¼ø‰ÕÎÎ?FII z÷î-$Ú’ä÷áÇ€'Ož ++ C† ¢bõ÷Oþý÷_|||dÆ$Õ_II ¾¾¾¢2É—“~ýú‰Ê›5k###™ï³§§§Ôñ)ù¼Uí«ª  €ž={V»Œ1öºpÝ@ªC¾¾>0cÆË-õf$Ð-Z´¨Ó€¼É“'cóæÍؽ{7‚‚‚°k×.”––bâĉr­oÅŠX°`zö쉩S§ÂÞÞFFF8~ü8Ö¯_ÃeMágmm @úŽqe’¹KJJ¤êijjbĈ011ðâËCBBvíÚ…;w"((‹-Bÿþý±eË–:÷ñþ÷ßÑ£G())áìÙ³¢»¶MOOzÊD%%%™ýX+**PQQ’’™ƒŸó­[·–;6B|Ož<^“µÞaÆU{‡».$wskŠ!++ d>åÔÌÌ êêêBgÏžUÛne’ú’cº2I™¤Ž„²²2´´´De’$]ÖZEEE™ï³¬uJ>oU?3:::¢_rXÍ¢¢¢`ggרa¼QÊÊÊÏO ®¢¸¸Xê&ãºp‡üú×®];tíÚ?þø#æÎ‹üNNNrOO·yófXXXàøñã¢;_Û·o¯v™ÐÐP¼ÿþû¢2Éì5%¨’„í«¯¾BÛ¶mkM[[Ó§OÇôéÓƒ~ø«W¯†³³3–,YRëòqqqèÞ½;ÊÊÊpá´iÓ¦ÖeÞTƒÆìÙ³ñé§ŸbÀ€8zô¨¨ë òòòdþÌÿºH.ºC‡ÅçŸÞ`ë­Lr¼…„„H½Ž¢¢"´lÙЪU+(**"..®Æ6%õCBB¤ü"YÏëú"&k–ÉS(ߤ/o£•+WòÃTª "䇩ˆ¥¥¥ñÃTjÁ} È«ˆåâký+/ÕÇ5'×aÕßamh“'OF||<æÌ™ƒØØX¹ï>—––âÉ“'puu%ÏÅÅÅ5>‚ûüùó¢ÿWTTàüùóhÚ´©Ì;h’¤»º)øjbccƒU«V¡Y³f8~üx­õáííçÏŸãÌ™3pp¨þA|©©©2û`W–™™ ooo$$$Ó«>}ZTÇÓÓS¸ƒ›kkkôêÕKT¯¤¤DH^$ý·„:mÛ¶mð»‚Æ ƒ¦¦&†Š=zàôéÓÐ××ÇW_}…ÇcôèÑØ²e zöì 555<}úgϞŽ{÷°|ùòzE__Ë–-ÃÌ™31jÔ(¬^½VVV(--Å¿ÿþ‹ýû÷£gÏžèÖ­[½®·ª¥K—ÂßßãÆÃ?ü€¦M›"$$›7oFóæÍ1a¡îºuëеkWôïß;wî„““ÊËËqýúu$%%aذaPRRÂüùó1sæL`Ó¦MÐÖÖÆñãDZoß>ØÙÙaôèѯe[4440uêTìØ±úúú8}ú4öîÝ  8𵬓1ÆäÅ ô[" @¾Äðeôêå…^½¼^ûzꓪª*Ƈ•+WbĈr ”غu+ˆþýûC]]EEEèÖ­V®\‰iÓ¦É\f÷îÝÀÔ©SQVV†òòrŒ7³gÏ®u}{öìÁìÙ³1þ|Ì›7:::ÂÉ’ŸØóðnÞ¼À‹A“………PQQAÏž=±aÆב˜˜( òZ´h‘Ì:qqqµ>Ž=33SêÉÁÁÁðbF‡/ÛéþôíÛüñ///œ9sÆÆÆ8{ö,Æ@II xþü91nܸ×Ë´iÓP^^ŽE‹áÈ‘#ÐÒÒBQQÊËËa`` šÉãu=z4±hÑ"CCC………°µµÅáÇ¡®®.ÔíÒ¥ >Œ)S¦ÀÅÅššš())AYY¾üòK 6 0uêT¤¤¤`íÚµøù矡®®ŽÂÂB¸ººâÀB_ìú&yZ¨¡¡¡h;~ýõ×Wz cŒÕ'’5ŠƒÕ«±cÇ¢¸¸¸Æ¾™cÇŽÀ?±Ý»w °··¯ó2K—.ÅâÅ‹VkÿçÇ#==¶¶¶¢©àÒÓÓqæÌ<þ:t€““rrr˜˜4iÒÀ‹A€)))hÛ¶-Š‹‹qåÊ$&&¢S§N2‹Ýºu 2{ôèþùç$&&¢;1ls IDATY³fpppÚîGáúõëHKKƒ­­-ºuëV§)ì ]c{{{¡ÛJII îß¿¡ÿ+ðb€¬©Æ$ÌÍÍkÄUyŸI¥¤¦¦"-- Rt¹wï444„8$±™ššÂÔÔTT799011U–——ãæÍ›¸ÿ>rssÑ¢E têÔIœ ¼˜5ãÑ£G°²²’ú5E²ïdm›äøqtt”J ÓÓÓqóæM<|øÚÚÚ°±±A§NDÛ—€œœ899ոπêÌÌL©ãR"11×®]CZZÚµk‡Ž;JMÑ(‘››‹ëׯãþýûÐÕÕ…«««Ìn>=Â7™™ GGGtìØQêW–ê¶«¦ý\õ}އµµ5/^Œ… âöíÛ¸ví¬­­Ñ¥K©ã>..ÏŸ?‡££c {ñÿxyyÁËË«NcÞU<ˆP"”mÔ¨QPSSûÏç$5Ẍ;W¯^Åœ9sàââ™uN å•““ƒV­ZÁÉÉIª_2cìíQ9~I.'Ð/®3|ËÎÎÆ’%K¸ôÿwëÖ-ܺu «W¯FÇŽùx©wáh -Z´y†½œû÷ïãÌ™3øõ×_‘““ƒo¿ý¶±CbŒ±7'CÒx¡˜žž¬¬¬xað, ÄÔÔ^^^µö9eu%K–@AA»wï®q† ÆØ›OQQººº¢þÚŒ±†eee///©îrLßfo¥O>ùŸ|òIc‡Á«'-Z´¨ñ!DŒ1ö&á;Ð ¤¡žDÈcŒÉ"™Š’ýŸ²²2©GÄ3ÎYê‚èÂO"dŒ1Ö˜V®\ÙØ!¼q$O"dbœ³ÔŽèÂòcŒ5&D(ÊÆ9Kí8fŒ1ÆcLœ@3ÆcŒ1&N wÈgŒ1Ö˜x¡4D(ç,µãºp‡|Æc‰JãA„²qÎR;N wÈgŒ1Ö˜x¡4D(ç,µã©4ÐÐPŒ;~~~ðóókìpÞX.\ýt¤££kkk4kÖLî¶rrrV§ºðôô¬µÞ?ü€uëÖáÔ©S°¶¶®µ¾ „o¾ù¦NqÔ·;wîàêÕ«HNN†‰‰ Ú·o:ÈÝNAAþùçdggÃÊÊ vvvu^VÞ}VÕãÇѽ{w|öÙgøì³Ïä^þM2kÖ,œ={·oß®—ö$ûöܹs077¯¶lýúõرcBCCahhXçö?~Œ ¬¬ ]»v…––V½ÄÍ{3#88¡¡¡èÒ¥Kc‡óFãºtéÒ¥Þ¾ýçääà굫 ‡“:uìTïÝ,..Æ7ðwÄßhfÜ <`ccS¯ëeÔ¨Q2:rwwÇ÷ßöíÛ×¹­ wïÞuªkii‰øøøZë={ö ÑÑÑuîÔÔÔ:Õ­OÙÙÙèÔ©>|PPP „ýû÷×é‘ÉçÏŸÇ´iÓ…òòrÀôéÓåºc#ï>«ª´´ÑÑÑÈÈÈx©åß$?Fttt½µ'Ù·¥¥¥5–=}úÑÑÑ(++«S»Ó¦MÃáÇEŸÅÈõʼn1öö‘Üä;vlc‡òÆãºÔW‡ü­;·bÍÞ5H3MCaTo©Âd“ u„µK×BEEå•×qþâyL]4©†©È1Ìb¤"Œ~6‚»±;voÚ }}ýzØ’ê™™™áܹs€˜˜:tûöíCÿþýqçÎ4mÚ´Ní888HÝÞ¸q#:„üíÚµÊÕÔÔêoÞEEE "|ÿý÷ðññ¥¥%.]º„uëÖáèÑ£˜={vúý¥¥¥AKK ÐÑÑy©;éü1¼¼¼`eeõ[Âä%Ùß/ó«DBB:tè€:àܹs¸téR=FÈKTT ª¢¬¬ ñññ rƒèmR\\üÎ]ë'Ð ¤>:ä/^¹¯mDn÷\¡¬Ä¤Im’°=q;bücpâЉWZÇŸgÿ„ÿ×þÈðÌ”^”U i6i8öìºèŠÈs‘PUU}¥õÔDEEE8ÉÛÙÙ¡_¿~HIIÁùóçqüøñ:3nÒ¤ ÜÝÝEe’;õíÚµ“z-++ ñññPQQMîÐfff")) öööry©¨¨@ll, `ggWã>ÍÊÊBtt4ôõõѪU+(+×üÑ566Ftt4„2ØÛÛÃÚÚ‡þìwXTÇ×€ßAšt°`CTD±W"(Š]ìcÀDco1–DcIL0ÆèÏ’Øbb‰½kb {C±£ ,J/;ß|»qÝ¥5™÷yöyعgfν»œ{î9gre@÷éÓ‡>}úœ/ÚÉÉ ''§lež?NXX...˜™™åyŽ×INNææÍ›˜™™Q±bEuÐEjj*!!!”-[;;;ciii\»v {{ûÒ¨¨(>|ˆ‹‹ >ü›õ~úô)ÿý7Õ«W×yÝîØ±Cý÷½{÷¤ý/aæÌ™2ú5TI„2Z“èèh2‰°˜(è…xçî~Þÿ3/j½Ðy<¥| §2N±qÛÆ|Ï‘˜˜ÈÈé#5ŒçWÖ‚[UoñéäOó=G~éÑ£é‘ŽŠŠÂÐÐ?üP§ìÀ100ÈuèÄÙ³g©[·.VVVÔ­[wwwLMM5j‰‰‰:ûÄÄÄТE lllðððÀÜÜœE‹åj>!sæÌÁÂÂj×®©©)“'OÖxì™ó† bccC“&M¨V­666j}Vèééé4Ë”)ƒ»»;±±±¹~œ_PfΜ‰B¡Ð(¡5qâDu[«V­°¶¶¦^½zXYYåúÙ®]»(UªžžžÄÄÄ™¡+}úôÁÔÔ*W®Œ½½½†A°oß> k×®eèСXXXP¯^=–-[Æ¥K—P(üïÿ㫯¾ÂÒÒ’:uêàääD—.]xñBû;xàÀ*T¨€££# 4ÀÒÒ’N:ñäÉ“|­Y`` …"ËWlll–}Uë­+$)**ŠFakkKíÚµ±´´dåÊ•ùÒQòî!gmd¡n¤ñœ3Òýް|írþ®ñw¶2ÏksæÌÑêÓ»woÚ¶mËÿþ÷?âããùòË/:t(fff|ðÁÙÎ7jÔ(.\H§Nð÷÷ÇÚÚš¥K—DJJ ßÿ=þù'cÆŒ¡sçÎìÞ½WWW"##9pàzzù»÷}òä W¯^¥N:9z±‹ƒ.]ºP¾|y6mÚÄÓ§Oùßÿþǘ1cðôôÌ6Þ}Ñ¢EŒ1‚îÝ»³jÕ*ŒŒŒHKK£Y³f}:ÏŸ×Ø±cñ÷÷×h‹ŽŽfРAXZZæêéˆ.:uêD÷îÝY¶l111|þùçbiiI—.]ò5¦D"‘üyóÿA%¹âôÅÓàžƒ>Ü~z›!»‡äo’S@åœÅb2bHHH(–ŒüØØXvîÜÉÖ­[100 eË– 2„M›6±zõjFŽ©–_³f |òÉ'¹ž£cÇŽtìØQýÞÍÍ ???BCCY°`ß|ó†a™žÜeË–©ßïÝ»kkk¦M›Æû￟¥ÁtåÊ~úé':vì¨ámÓ¦ mÚ´aþüù|öÙgØÛÛ«oæÎK¥J•¨X±"M›6Íõ¹½ÎÇLZZ_ýu¾Ç(Lj֬ɖ-[Ôï+UªD›6mX·nNZÁçŸÎwß}ÇgŸ}ƬY³Ôžöùóçsýúu~üñG† €——½zõÂÎÎŽ‰'ª×TETT!!!±~—.] ""‚'Ož¨CJÚ´iƒ k×®UЩ©©Œ5 Ž;¦þN4iÒ„òåË3vìX6oÞL¯^½ò´.îîÿó…OJJÂËË ===vîÜ™oºFÞ¶Ý»wcggǤI“¤-‘H$y@†pM"4-e EýĽ$”³˜0È÷?ð܃ƒØÙÙdHÕ«WÀÛÛWWW–.]ªÑwÉ’%T¬X‘6mÚäyÞ{÷îñǰzõjV¬XAùòåIOOçÁƒZ²­ZµÒxollŒ§§'wïÞ%"""Ë9öìÙCFF£FÒ:Ö·o_RSSÕœª¬ÞW_}Åõë×ó|>¯3}út~ÿýwœ¯õ) T¡9*š5k†Î5OIIáý÷ßgöìÙ,\¸ï¿ÿ^#LeÇŽXZZª¯fff´k׎ . T*5޵nÝ:ËD___xl}}}¼½½‰ŒŒ$55€îݻǀ´n(ýýýÑÓÓãìÙ³¹X‰¬BðÁpîÜ9Ö¬YCÆ ó=–Æ{+++êիǵk×äÆ ÿäN„ÚÈu#w"Ìé.& úÏ©gvß²²2k¡hëÖ–_Çþš¯9®‡\§ç×=‰qÎ&D ¥õK£¯ŸMœG)Uª”úñµªtÛ¶mµB2̘1c8yò$M›6åôéÓ\¾|™o¿ý6Ǥ±W‰ŠŠ¢oß¾:tlmm‰2?»×3×UžðWiݺ5üñ÷îÝÃÙÙYç\·oß C‡ZÇTeæîÞ½ dÐ `õêÕ¬\¹>úè#>ù䬬¬r}~YxÚ´iôîÝ›ü1O}‹’×C*LLL°²²âåË—Z²sæÌ!55•9sæ0lØ0­ã¡¡¡ÄÅÅé¬Ò’žžNFF‘‘‘êÚÈ@¶7¯ë™qJ¥’ÄÄD `Þ¼y,X°@K^©TæªL‹-4hƱ×+7¼Ê¡C‡èܹ³FÛÔñʺ¨Q£iTyzzæJG;;;ÆÏøñã ÁÃÃŋçÊ€^¾|9Æ £C‡lذá­HÌ/•*UbÆŒ´jÕ oooöï߯±«b5¸|ù2­[·.Ò'$¯¢ú<Ú¶m[¨cïß¿ŸáÇӾ}{æÏŸ_(c:tH˳®ºnó³K¤D"‘üWy{\Q’iÖ¤Á›ƒió° ®']q:ãD•“Uhr¥ »fïbôàÑžÃÁÁK/ñA‰p;åF™Óe¨p¢§U4÷ïß'888Ë~üñ‡F¸ÇsúôiêׯŸmõ???LLL˜4i’VÉ:Ȭ_¬ŠÓÕå-¬\¹2VVVêÐìXµjŸ|ò >>>lÙ²%[ãË—/Y¼x1{öìÉqÜ7Iùòåù믿ptt¤U«V?þOE˜¾}ûËwß}§³¯®õ,(5kÖ¤V­Z,X°@gܶ"ËRˆÙB¯^½¨Y³&6l(´‚}ûöiÄß»wk×®ñÞ{ïå9,H"‘Hþ˼»î¨wŒÂ ÈwrrbÿÆý@¦ÑfccSèäMLLXùcfmؘ˜LMMßÚÇ» 4 ^½zœ?žJ•*i%I儞ž;vdÓ¦MôèÑ???ž={Æ÷ßOãÆ9uê”ÎxjSSSZ·nMÿþýIHHP—ºûöÛo³¯B… Ìš5‹#FP·n] €³³3?æÒ¥K¬_¿ž{÷îaiiɈ#¸ÿ>íÛ·§B… DEEñûï¿óäÉ&L˜í>> 0€-[¶0iÒ$Ο?O»ví°²²"<<œC‡‘˜˜¨ö¶%J”àçŸÆÛÛ›:uê0räHjÖ¬Ibb"·oßfýúõ|ÿý÷øùùåiÜþýûóâÅ š7o®sÛ1cÆäk—0¥RIÇŽyÿý÷‰eöìÙ1sæL ¹Í›7³yófNŸ> d–ÖS%U®^½ºPv=•/r'BmäN„º‘;æŒ4 ‹‰¢Èp}Ç´¢ ¸½R...y6Ö8þ<Ì1yÐÞÞWWW²t‹-¢D‰lÛ¶íÛ·ãææÆÔ©S±±±áóÏ?×µ¶¶ÆÕÕ•Õ«W3wî\ÆÏ“'O¨U«üñÞÞÞ󹺺jy¤‡ F­Zµ˜4iS¦LáÅ‹ê݇ª®æÐ¦M~üñG¾üòK’““qppÀÇLJµkתwÌŠôôt\\\¸yó¦NU% ÈÜýÑÕÕ•råÊiȤ¥¥©«‚¨Îþ)õöª±ª5{õÇØÎÎWWW!%...z¨t³±±Q·ÙÚÚrøðaüýýùì³ÏX±bìÙ³‡… ²téRvíÚEjj*¥J•¢Q£F 8PÝßÔÔWWWÌÍ͵æ/Y²$®®®:¯}ÕõóªG¸Aƒ\»vqãÆ±téR"##Q(”-[–víÚQ§Nµl™2eÔk˜¶¶¶¸ººòçŸê<>lØ0ŒŒŒÔkûª1««MµÞ[¶laòäÉ|úé§ÄÄÄàááÁÚµkiÒ¤‰ÆøQQQêÏØÈÈWWWur+ 3/@òö#“µ‘I„º‘I„9£ò—°È äøñã4oÞ???Þ(ÕöÔòÇ-ïøûû³}ûvüðÃ\ë’×5{ÈÈHš4iÂÔ©S™6mZžû¿MLž<™íÛ·çêfÂÇÇ///¶oß^dú(•JnܸÁùóç9þUªTyÓª¼U¤¤¤`ddô¦Õx«‘t1QЀü¡}ûòôÂLJdÿ‘=22bÿ™3èéåýáÂâ Ø5>6%Kf+w+9™-ý•k#,/lß¾˜˜µ±ALL “'Ofúôé¹6ˆ¸té’FÛãÇyþü9ÎÎΔ|å<Ë”)S8'ð–MÓ¦M)Y²$uêÔ¡qãÆ;vŒ'N°lÙ2‚ƒƒqssËqœµk×Ò¿6‡<ÐÖÖÖ¸ººÊã·+W®P§N¶ØØXi@ÿK™9s¦Œƒ~ U¡ŒƒÖ$::ZÆAç€4 ‹‰‚^ˆïÈÑnݘôüy–2» ›2%_Æ3@¯¾}93g¿„„d)óø²mÛ"1žU899© ßôôt:D¯^½˜:u*íÛ·§nݺ¹§víÚZe›FͼyóX¿~=7.tÝß ùòË/1b¶¶¶@¦WzÁ‚Œ5Š!C†ð×_å8Ž›› .¤^½z$%%ѲeË<ë2pà@˜ç~’¢ÇÖÖ–Y³fQ¯^=jÔ¨Q¤ßkÉ›GÏÚÈ$BÝHã9g¤ýŽÐÜË‹•+óòÜ9̲ù¹\9Ö—ï9¬¬¬(ëëË•¥K©%„N™ø´lJ”(A›6mð÷÷gÉ’%üõ×_8;;óûï¿S»vm-ïdzÕ.\¸@§N°¶¶ÎÕ>>˜ššf9¾••_}õ•F›B¡`È!Ìœ9“3gΞžž£¾ 4 Aƒ@fø@$%%‰´´´,e²çØáÃâ›Ò¥…­×n±àë¯ ¬ë³gÏD ³³Î9¢@|Ò¶mçÈ{{{áìì¬Õ>þ|ˆ/¿üR¤¦¦ ѰaCcxzz {{{‘ššªulÔ¨Q§NR·mذA¢téÒ¢^½z¢téÒeË–W®\Ñè$±`Áabb"ªW¯.Ê—//Ñ¢E £!h}®·oß 6€033eÊ”€¨V­š¸qã†Z.>>^-gcc#5j$*W®, …X·n]NK™%UªTVVVyîwêÔ)ˆQ£F婟jÍBCCÕm&L€Xµj•066ÖÖÖÂØØX¢qãÆß“°°0ˆ©S§ªÛRRRDŸ>}„¾¾¾X¸p¡º}çÎÂÆÆF¢|ùòÂÄÄD¢G"%%E-·wï^ˆ   áìì,ÌÌÌDåʕŌ3ÄÅ‹ &Mš$<<âñãÇ\¼x‘»wïâèèH`` =*}$ïr'BmäN„šìÛ·Ñ£GsêÔ©7­ÊÛÏ›¶àÿ ooïers·wìðañík^è= èðç÷õ D€ïóÀbðLÙÛÛ ;;;±wï^±wï^ñã? oooˆJ•*‰—/_ !„úúúbèСýG% …¸{÷®Îñuy ³BåU|ò䉺MåMíÚµ«–|™2e„‰‰‰HLLT·ñšzÓ¦McÇŽÕê?pà@ˆ!„³fÍ€¸víZŽºæ†'Ožˆ2eÊKKK™çþEá3fŒ†ìåË—  ¤n{Õ.ÜÜÜ„8sæŒFß>úHâÏ?ÿÔhW*•¢lÙ²¢|ùòê6•ºR¥JZúª<ÐeÊ”J¥R㘋‹‹°°°P¿òä‰0225kÖÔgË–-sæÌQ·Äý:W¯^æææ¢N:êï…JG…B!¢¢¢4äçÍ›'1nÜ8ãIô¿éQÔæùóçyþMû/àíí-¯—1ÐÅDaä7÷òba•*ÄŸ=‹*ögÖ6kùL|+ üµk\~øÚÿß6ÛÞž±óæÊø9ñøñcÚµk§~¯¯¯ŸŸ³fÍRÇý–+WŽ:°f;ÿþ{LLLHNNfÕªUøøøP©R¥<Íyÿþ}~ýõWnß¾Mtt4DEE©ÙØØhÈ{{{káããÃÊ•+¹wï^–..\¸@Ù²eÙºu+B„(•J,--¸vínnnôìÙ“ &àëëˇ~H÷îÝs@ù:/^¼ }ûö<~ü˜íÛ·ãàà¯q ›=zh¼wwwÇÜÜ\§—4$$„&Mš`jjJpp0+VÔ8~öìY¬¬¬xùò¥zm•J%B\\\8r䉉‰˜˜˜¨ûtèÐ!Kݺuë†B¡ÐhkÑ¢?ÿü3 ”*UŠ+W®’’BݺuÙ¶m›z>!IIIèëësóæÍü,M¶DEEÑ¡CÌÍÍÙµk—ú{‘””Ä;wpwwÇÞÞ^£*üúõë…®äÝ@&j#“u#“sFÐï ÃgÍba·nL|þœ½xþ9Fy(ï–FÇÄðiݺ¬xð€( ÎÃWW×B#+Ù³gYº|ùò:“݆ ÂÎ;Ù°aýû÷gãÆ<þ<˰‹¬Ø»w/:uÂÔÔ”êÕ«S£F lmm¹zõ*7oÞ$>>^«*±îU5jÄÊ•+yøða–ôƒ˜6mš–q™?æ111T¨P?þøƒ~ø   ¾ýö[ÜÝÝ6l¹. —@‡¸xñ"ëׯkÂ7­…B±±1iiiZ² ..Ž©S§jÏwïÞ%55U«ìž sssž={¦a@g £ë&¬T©RjýT›älÙ²…;vhÉ›ššê¼~ Bbb":u"&&†ãÇãää¤>BçyU¯^=Ë›‰D"‘ä i@¿ƒ¼ê…^^¾Cf 6å|}}©T©K—.¥ÿþ,Y²ºté’§ù>ýôS ¹yó¦†×nĈYö¹r劖‘¢ŠÇÎλ«2v®]»–«ÚÓ>>>øøøðèÑ#¶oßÎÊ•+Iyñâ…–gZ"‘H$yG&…½áðY³èQª^yðDæ•Ñß~Ë^£÷9/(  Dpp0ëÖ­ãäÉ“|ôÑG¹.%™žÄ›7oÒ¾}{ ÃBÁ¶mÛ²ìwèÐ!­¶?ÿü“%JP¹rå,û¹»»°ÿþ\ë™åƆ Ʊcǰ¶¶fõêÕ9öIII¡k×®>|˜_~ù…>}úäiη ===~þùg† Æ”)SøüóÏ5Ž»»»sõêU"""ŠM§š5k°k×®b™ï³Ï>cûöíÌ›7Oç“333Ê–-˹sç4vô„Ìë jժŢ«äíC&j#“u#wOÎi@݉ðuš{yáо=‹Àû¬ÂÊÊŠº}ú«÷9¯ôïß### €B¡`À€yêo``€½½=$..Nݾ|ùòluïß¿_#–tÛ¶mÜ»w~ýúe[ŸÙßߟ5j0~üxNž<©q,55• 6¨ëñþñÇÜ¿_C&,,Œ¸¸80]¤¦¦Ò½{wöïßϼyóèÝ»7ÉÉÉ/ñJ­ïÈÈH7n̘1c´ÆRɧ¦¦™5¬UmÙêQØ( .\ÈgŸ}ÆÌ™35j”ú<¦L™@¿~ýˆŒŒÔè­3Ä¢ 8;;3`À6mÚÄüùó5Ö2k7–ÑòÓO?1wî\F­®<¢‹±cÇ’‘‘Á?ü n‹eéÒ¥h=YIII!99YýS©Tª?_]¡4’w—™3g¾iÞ:T;J4)l›å߈ á(&Š" ùºuèëëú¸¯òå¬YE>GA°µµ¥{÷î¬]»___±±91iÒ$FŒ““^^^DEEɸqãøþûïuö>|8M›6¥iÓ¦$$$pâÄ ªW¯®µyÉë²~ýzüýýiÞ¼9µkׯÙÙ™ÇJll,±±±”,Y’µkײjÕ*jÖ¬‰‹‹ ÿý7.\ÀÐÐ0Çy®_¿ÎîÝ»ÌЇ‘#GjÉ„……Q¡B Óˆ:}ú´:‘QETT”Öît .TÿÃQtÅ*qô«¯¾"11‘%K–àááÁ²eË9r$...xxx`eeExx8ׯ_§Y³fyïÉ ³gÏ&**ŠQ£F1wî\ÜÜÜHLLäÎ;DDD°}ûvªU«VàyæÏŸÀÁƒu†8;v 333† ÆÑ£G™1c›7o¦B… œ;wŽ/^0oÞ<\\\4úÙÛÛkÜ<þùçŸ ÏþEÈÏR™D¨™D˜3Ò€~‡)ö¸çÉ“'çyÎ&Mš°víÚ\%vêÔ ‡áÇSµjU6oÞL||<>>>tïÞ¸¸8¬¬¬4’ɼ¼¼ â“O>Á××—]»vñàÁ‚‚‚4hº¯Š   u؆ www.^¼Èo¿ýÆåË— ÇÍÍ=zйsgõn}3fÌÀÓÓ“sçΧ§'Ó¦MÃÓÓ3G´““AAAÙÊ”.]Zãï   ­Ä933³lÇiÞ¼y¶sÀ?kfgg§nk×®–––:wzœ}:ÕªUãÁƒ\»v wwwñññaýúõ\¿~¸¸8š7oÎØ±c騱£º¯««+AAAZ1ÄðÏÚé:7Õõ£20,,,øý÷ßÙ³gÇçÖ­[XYYѼysÚµk§+ÿþûïS¿~ý× 2M_½NÇŽ˳gϲ”WíBh``À¶mÛØ´i“z'¡C‡Ò­[7ç;mÚ´,w!|ýÚ•H$I& ñú3GI¡d÷Ÿ‰6ÔªU‹äädnÞ¼™§øg‰DòßÁËË ///¦M›ö¦U‘HÞz¤M’32º˜ù…Kll,—/_fܸq\¿~~øAωD’ 2‰P™D¨i³äŒ4 ‹ _¸¬_¿.\H@@~~~oZ%‰D"y«‘I„ÚÈ$BÝH›%g¤Ë®˜ù…K·nݨS§Žzs‰D"‘d|¯L"Ô´YrFÐ’w;;;D3‰D"‘H$’âB†pH$‰D"‘H$y@ÐÅ„ È—H$É›D&j#“u#m–œ‘t1!ò%‰Dò&‘I„Ú¼ÓI„’ï'C#–6KÎÈèbBäK$‰äM"“µy—““Ó‰Z…¾™>¥j–ÂÔÝC'ÃB[Ú,9# h‰D"‘H$’wŒÄk‰d¼ÌàÅ©¼8õkL=L±ð´xÃÚýû‘!‰D"‘H$ï®'h5§=K#åo¿\Hº˜ù‰D"y“È$BmÞÕ$ÂäûÉdÄg臂"„àÑ£Gœ>}šû÷ï“‘¡û77(•J’““IOO/D ß6mÚÄàÁƒIJJzÓªH$E†L"Ôæ]M"Œ¿¯³]a ÀÄÕ¤ÀãK›%g¤]LE@þÕ«W }Ì×¹~ýzŒ®¼âì쌱±±ÆËÕÕ•Ï?ÿœØØØÌ‰'Ð××ÏÕ8åË—'((H£m×®]œ8q‚¡C‡R®\9u»…Å¿+áâÅ‹,Y²„:ðÅ_P¥J>ÌêÕ«1bzzz :4ÇqΜ9ÃO?ýDÕªUiܸñ¹H$É?$ÝMB™¤Ôy¬TÍ‚‡oHr‡4 ßQfÌŸOÜG1rÆ ö¯^]$s9vŒpWWþ·nûõ˵áZPÌÍÍ=z´úý¼yópssãôéÓüþûïøùùåj'''&Nœ¨Ñʼn'èׯ7.T½ß&,,,¸qãU«VU·ùúúÒ£G4hÀ?ü+ºK—.ÄÅÅaffFpp0Mš4)Jµ%‰D’‰!º½Ïz%õ0®b\ÌÚüw‘!ÅÄ_ýE`` Û·o/ðX‰‰‰¬?z”Œ–-¹,7oÞ, µÿÃ<ïÒ…ð¦MYVDFznP(påÊ¢¢¢ðööÎò±ÛO?ý„··7‘‘‘¹ÿÁƒ|úé§Ô©S[[[œœœðôôdÛ¶mYöB0}út4h€££#:tàòå˹>§ .н{wªT©‚““¾¾¾=zT§Ü”)ShÚ´)–––T¬X??¿ÃwŒ5Œgõë×§V­Z„……å*–¹téÒ˜™™åú¼t±téR¼¼¼xúô)3fÌ Y³f˜››sþüyÒÒÒÔí¶¶¶ÔªU‹1cÆ aIß¾}qvv¦zõê|òÉ'¼xñ‚Ž;2yòdµ\xx8^^^¬]»6K}bbb²ÕûêÕ« <˜š5kbeeEùòåñññáÈ‘#Z²sæÌÁËË‹ÄÄD&OžLãÆ133Ë69iíÚµxyyÎÿþ÷?š7oŽ­­-žžž;vLgŸ-[¶Ðºuk©^½:~ø!>Ô){éÒ%zöìIµjÕ(]º4îîîŒ=Z+Îû—_~ÁÛÛ;;;ÜÝÝ4hÏž=Óyu=8@ûöíqtt¤mÛ¶ìܹ€'Ož0lØ0jÔ¨³³3#GŽÔ ‰™9s&^^^$''3zôhjÕªEÙ²eéÙ³'ááá²wîÜÁËˋ͛7sàÀüüüppp`„ Y®©${d¡6ïZ¡2MI íßF“ê&(J,|cûöíò×_hœÿÒ€.&*T¨ÀŠ+rí=ÍŽóçáë @t×®|<}:7 õµêÀn•)%K’ôÞ{ÌÛ°¡Xc¡_Geì”,YÒÒÒ "--MC.##ƒo¿ý–ääds5ö©S§øý÷ßñööfêÔ©ôèуˆˆºuëÆ²eËtö=z4[·n¥gÏž|òÉ'\¸p¦M›œã|+V¬ Q£F:t///¸xñ"-[¶Ô0ø®]»F³fÍX³f 5"((ˆ~ýúñìÙ3BCCsun¯“žžÎýû÷©\¹2%JϨ{÷îqôèQ>þøcÖ®]K“&M©©©Ùê½{÷nNŸ>M‡øúë¯iÓ¦ !!!x{{³k×. Ù[·nqôèQúôéî]»hÑ¢Ÿ|ò %K–ÌrüððpŽ=ÊÈ‘#™4i...´lÙ’àà`Z¶l©õO}ÆŒôèÑƒØØX>ýôS:uêÄÎ;©U«–Ö ÕŠ+hРgÏž¥{÷î|óÍ7´mÛ–­[·§–>|8ü1J¥’‰'Ò²eKV­ZE­Zµ4ÖNµž+W®¤sçÎ(•JºvíÊÉ“'éÝ»7çΣS§NìÝ»—:P²dI,X /ãÆ Ž=ŠŸŸçÏŸ'00¾}û²oß>4h 1g||ÿ?q=z°ï§Ÿøã·ß 4Ç‘cÇx^±¢†÷YERóæÌ›5«È+r899© èì4h³gÏfÙ²eñóÏ?£T*ùä“Oò4ßĉ™3g}ûöÅÓÓ“5j`kkËš5k˜1c†NO£V›½½=ixe…ª–u•*Utz*7nL•ÿ¿ 266æöíÛlÙ²…åË—³dÉ,X@ãÆY»v-+VÌÕù]¸pvíÚáääݱ±ÉU¿ÂæÕ²€:QÍÖÖVç¹T­ZcãÌŒòŒŒ „ê÷¯bdd”'úõ¤¬èß¿?›7oføðáÔ­[—êÕ«ciiɼyóX¼x±ÎëâõsÌ ¯zºU( =_¾| üs½ŠÊØT%]ª®?SSÓlç}ùò%FFF”.]:Ë1Uóæ¤kVí {½uGVßY¶ð¸qãÕªU{Ój¼U¨òBª‚ó©¨IÑ<¨o®Qy#ÇòKJJŠÖC‰&Ò€.& º«Obb"ë&ýóÏu X[sE©äæÍ›ª =qî\b Ò}°½Ð¹¡råÊ´iÓ†_ý•)S¦°|ùrš4i‚»»{žÆYµj...¬\¹RãÆ »ªGŽá½÷ÞÓh;xð ðOè….TŸÍ Aƒr¥g‰%èÝ»7½{÷æùóçüú믌;–E‹1kÖ¬û_ºt‰Ö­[cmmÍ¡C‡4<{oš *`hhˆ““SŽ˜˜˜`ccÃáÇQ*•tUÛëòé%~3gÎä¨ÛË—/Ù²e ÞÞÞ,X°@㘪zHq¢úç~èÐ!Þÿ}cþù'€ú)‰JöÎ;xxxd;æñãÇ9qâ„ÖÕ˜EeT>>˜ššrèÐ!Ê”)“¥¬Ê SœÛ·êëëóÞ{ïqàÀx߬ðõõåùóçZ•NöîÝ«%[¦LJ”(Á4Ú?~œe‰¸Wyúô)B\\\4Ú#""8{ölŽý ›æÍ›h}îñññœ8qGGGµ­ºÆ~üñÇ|Nhh(5kÖ,² †^Ÿ3**Š«W¯Ò°aC­Iá!gmÞ•¯'‚î½SŠ$|CÏ9#=ÐïîUªð­™d÷øÙÚ“V­´¤bÅŠøúú²oß>µLRR+W® ,, Ȭ¼xñbõùäÕãÿ* ,ÀÃÃ6mÚðÝwßѬY3 ¹ÿ>¿ÿþ;fffŒ?€éÓ§³cÇüüü˜Hìç IDAT}ÊøñãIMMeÆŒê OOOz÷îÍòåË122bРA”)S†°°06nÜÈ„ °²²" €~øùóçSµjUÚµkÇÇþÿùß}÷]‘ÏÞ½{Y¼x1½zõ"::š!C†0uêÔ"›S"y—É*|£„U u“1o¦øÇ‹Â,c÷oGW»œ˜>}ºÄˆ#r”ÕUÆîÒ¥K¢|ùòê’Y€èÑ£‡Xºt©ÄáÇղªÒcüñ‡¨T©’FŸI“&i”B»Œ™%ë¦N*ŒŒŒ4úëéé‰fÍš©Ë~Mš4I”(QBCÆÑÑQˆ/^d{ž¯–Ëꦖ €ðõõÕ'222Û1æÎ›ãšë*÷*gÏžõêÕÓÛÖÖV,_¾\C6$$D4mÚTsssÑ¡CñèÑ#ahh(  !&jÖ¬©±¾&LÈu»Ã‡  FŒ!¾ýö[­õS•±KJJÊq=T¨®¥ÐÐP­c®®®¢Q£FmOŸ>:uÒЧdÉ’âÇÔꟘ˜(ÆŒ#ôôô4äíííELLŒZ.""BxzzjÈXXXˆµk×jŒ—]YÀF WWW­öE‹i}×TeìΟ?/¬­­ÕsˆÅ‹kôW]ù¹Ærƒ,c'yWI‘.¦i—® ›&bÆä<@>6IÎ(„ÈeF$ß’’’ºu법ùˆMåÌ‹w{âĉ|÷Ýw„„„àææ–­ìË—/IHHÀÚÚZãQqbb"çÎ#))‰:uê`ggGRRqqqXYY©K‡%$$ðòåKlmmQ(„††òðáCuŸ×‰ŠŠÂØØXç£ð¤¤$nܸAxx8666¸ººj%ø%$$Btt4UªTQW6ȉôôt*º°µµU{Ë322xòä‰VR™R©ÌÖÛjnnžcÅ“øøxâãã±³³ËöÉÈÇ %==2eÊàææ–¥7?%%u¥Ž*UªðÍ7ßðÅ_hÈ¥¦¦rùòebbbhÔ¨–––:õQ]öööžìØØX.\¸€‚ºuëRºtik@¥ß‹/HLLÌSŒ¹®qT<}ú===¬¬¬´ú=zôˆ,--©Y³¦Î>qqq„„„ðìÙ3Ê–-‹‡‡‡ÎÏàÁƒ\»v {{{ÜÜÜ´6€Q]fffZóÅÄÄ T*µ®]Õ÷çÕïZ`` +W®DAzz:W®\!&&†úõëcii©Ñ_u çæË ^^^xyy1mÚ´õ®"“µy’_¿ fŸîSË +ƒmá‡=õéÓ##£ÿ¼M’Ò€. çСCÙÊ€4 óŠÊ°lÓ¦ [¶lyÓêHŠ˜§OŸjjôë× 6péÒ¥…“HŠžW èâFЙë/ÿÇhË´iÓÞê8èÈŸ#Iy¨]ÉËÐÞ§!NE2gË–-)_¾¼¼^²AÆ@2 ¿p9wî›7ofÇŽ¤§§3{öì7­’¤¨S§nnnÔ¨QƒÔÔTŽ9µk×=z´4ž%’Æ6o{aÚó4Æ3mígi³äŒ¬Â!y'¹{÷.;vì V­ZlÛ¶-×5‘%ï6S¦LÁÄÄ„ãÇsöìYêÖ­ËÎ;™;wî›VM’  TfS"ù¯‘Uò ÈÍSÞ4Ò-y'QÕF–ü·8p |ÓjHòIPPAAAoZ ‰ä!1$Qg»Q#J”–&Ü›Dz ‹‰Â؉P"‘H$’ürãÆ7­Â[Gzz:wîÜyÓjè$õI*©Ñ©:™Ô,xbmvH›%g¤]Lç‰D"‘¼ÎÌ™3ß´ oªßF²ò>£ÈÜ}°(‘6KÎHº˜ù‰D"y“È$BmÞæ$¬âŸK:—Dß,ë » i³äŒ4 %‰D"‘HÞ"R#SI{–¦ó˜L|;´D"‘H$É[DüÕxÝô T i@¿ Hº˜ù‰D"y“È$BmÞÆ$B!‰×tÇ?W2FϤèM7i³äŒ4 ‹ /‘H$’7‰L"ÔæmL"LH%=.]ç±RîÅã}–6KÎHº˜ù‰D"y“È$BmÞÆ$¬’%˜T+Úòu*¤Í’3² ·ä­bÕªU$&þóèÊÜÜœŠ+R·n]ŒŒŒÞ fo†¤¤$6nÜÈÍ›7‰‰‰ÁÚÚšªU«âííå\xx8'OžäÒ¥K( <<>ž .póæM222øè£044ÌÏ)I$I±#”‚„kº hccôŒ¤ßómAÐ’·ŠñãÇë|täììÌâÅ‹iÛ¶m‘Ì»jÕ*ÆÏž={¨[·n‘Ì‘WNœ8A=ˆŠŠBOOŠ+©¾ÁˆŠŠÂÞÞ^-/„`îܹ|ñŤ¤¤`ccÀÓ§O)Y²$ßÿ=Çט#""‚!C†0hР|ÐBúöí˺uëP(TªT‰°°0”J%:t`ÇŽèëg_néĉx{{`aaAFFñññLž<™?ü+V P(rÔÅÇLJÇ£T*ÕmþþþÒ€–H$ï ÉaÉd$dè<&«o¼]È[™bBäçžråÊñüùsž?ÎÙ³gùì³Ïxøð!þþþüý÷ßE2gbb"ÑÑѤ¦êÞõ©¸INN¦W¯^<~ü˜uëÖñìÙ3îܹC||<—.]b̘1hô1bcÇŽ¥aÆܼy“'OžðäÉ®_¿Ž‡‡#FŒ`ܸq…ªçš5kX·n­Zµ"<<œ;wîðèÑ#ºtéÂîÝ»Y´hQŽcT®\™íÛ·Cll,ÏŸ?çÔ©S4lØU«VñË/¿äJkkk† ÆŠ+hذaAOM"ù×!“µyÛ’³ ß0T`\Õ¸Øô6KÎHt1QXù)SHz3FžB_i¢Ýý@OOKKKêׯOýúõ e÷îÝìÙ³‡¨eccc¹|ù2J¥’Zµjammå¸>äüù󘙙Q½zuÌ0†gÏžÉýû÷LohéÒ¥Õý¸páúúúÔ¯_CCC=z„¾¾>j¹`bb‚­­-IIIœ;w…BAóæÍÕ2/_¾äÚµk<þ777­pŒK—.ñ÷ßÓ³gOüýýÕí …‚Úµk3gÎ-ùE‹ѰaC<¨a\W¯^£GÒ°aCæÍ›ÇÀ©ZµjöB.9yò$ùä@"âààÀ_|ÁŽ;8yò¤–×ûuÊ•+G¹råÔïK”(AãÆùòË/éÔ©'Nœàã?ÎQ— 6¨ÿ^·n]~NG"ùW3sæLýª$·!ZdCuWß(U­zÅç󌎎–qÐ9 èb¢°.ÄĉÄ+”±òŠ^I½b1 uѱcGvïÞMXX7;eÊfÍš¥ñÈ~ĈÌš5‹’%KªÛ.\ÈÂ… ¹yó¦Æ˜sæÌa̘1Lš4‰%K–Э[7õñ &¨³Ö·lÙB`` ññ™µ9Y¿~=ãÇÇÁÁ#Gލû¹ººâç燧§'cÆŒ!--úõësöìYÒÓÓ™1cß|ó ééÿdY÷èуeË–©oââ2?ãœÂT̘1¥RÉÔ©Sµ<Ó†††L™2…îÝ»óÝwß±|ùò\›nnn@æ Á«¨Þ«ŽçU¨Š»»{¾ÇH$ÿ gmÞ¦$¤;I(“•:wø†4žsF†pHÞ þøãàŸ/õ¤I“˜9s&äÖ­[Ü»w±cDz`ÁFŽ©îwíÚ5FŽI¹rå8uêñññ„‡‡³~ýz*UªÀôéÓ™6mëׯ'44”ÐÐPÆŽ À•+WèÙ³'•+WæìÙ³ÄÅűqãF†ÊÇuê{ðàA,XÀ¶mÛ SèãÆcúôéôéÓ‡}ûöqýúuÆŽËž={8p º:u066fëÖ­|ñÅ„††f»>W¯^¥D‰øúúf)Ó¾}{ —/_Îv¬¼Ð­[7¬¬¬ âĉdddpæÌ¦OŸŽ¹¹9½zõÊó˜OŸ>åÿØ;ï0§ÊìnêôJqè]ª¢6@) (eUD]À† ‚²bY± ‚X@¤éºþYtqDd]š8" "E©Ò™˜Ê$™ôûû#3™’›If&$x?ÏsŸ$÷Þ¼9InN¾÷ÜsλråJ^yåRRRª5†@ Ô6¼¥o¨"UD´ŒPÜ&B@ š£G2}útÖ®]KTTýúõãܹsÌ;—¶mÛ²hÑ"Z·nMóæÍyçw¸é¦›øä“O8|ø0àв,3yòdnºé&¢££iܸ1#FŒ`È!Ô¯_ß]Œ×´iSÚ¶mKÛ¶m©[·.o¾ù&²,³`Án¸áâââ}ºÜk9Ž?NãÆ+XGDDpÕUWyDâkBJJ ‡¦AƒtïÞèèhn¼ñF´Z-‡¢uëÖ~UR,X·n]î»ï>Z´hÁáÇiذaÀì‚pD¶É*RÜÕ. Ií»Z\„€"!ßNž<‰$IH’DëÖ­ùÇ?þABB‹/¦yóæìÞ½‹ÅBß¾}=žÛ·o_Û·o wïÞhµZ^ýuÖ¬YS­ïaçÎÄÇÇ{´T«,Ú_.ç`óæÍØívFE~~~¹eРAå¢Ã÷Þ{/GeÍš5 2³ÙÌ{ï½G‹-x衇(*r9[³ÙŒÍf#:Ú÷%¾èèhŒFcÀŠ%F#ï¼óëׯ§uëÖŒ=šöíÛ“žžÎÌ™3Ý©(þЭ[7fÏžÍßÿþw:tèÀÿû_yäÑÐ_ ¢ˆÐ“p)"42á´†GúÍâB@ !ü'!!3f0cÆ æÏŸÏºuë8vì÷Üs€»È¯wïÞÏ-YWâëÔ©Ã|ÀéÓ§2d <øàƒüôÓO~Ù"Ë2§N¢{÷îÑÝ:¸[ÅU¤gÏžh4åK JÒ0~øaË-%ÑðS§N•{ŽJ¥âÎ;ï$--sçÎñÅ_Ю];>ýôSÞ|óMÀ%ŠSRR8yò¤Ï÷rúôiêÕ«°Ön‹-bæÌ™LŸ>dz`Á8ÀÂ… yÿý÷yçwü«}ûöLœ8‘Y³f±ÿ~æÎËÊ•+yÿý÷b«@p¥#f"ô$\f"ô–¾¡ŽQÑ,øéB³øF‰@%ä«£Õhëx‰ƒ`5pçùçŸ÷º½$Òªô/Yë^÷øã3fÌÖ®]ËW_}Å—_~É¿ÿýofÏžÍĉ+µE’$9räˆÇ¶œœrssŸå9[TIaãW_}åõx(ÛÍ£"†¿þõ¯$&&Ò§O6lØÀ´iÓhݺ5ééé;vŒ–-[*>ÿ÷ßÇb±pÍ5×x}ª²råJ¢¢¢ÊuFøÛßþÆ”)SXµj•ÛÆª2zôh^yåV­Zå>YÕGzE„N³“¢£ÊéÑ¢‘TÁOßE„¾º–wsq7Ç…ÚŒRRü·aÃüñrÛ6lØà‘{«Ñh:t(C‡eáÂ…\}õÕ|ôÑGnéꯩ”ÚЦM¶mÛÆ‰'hÖ¬™{ýÆËuñEûöíWë½»îºËïçU¤U«Våºx¤¦¦’žžÎŒ3øøãŸ7cÆ ÷¾"##µZím/Y—‘‘Qí±£¢¢ˆˆˆÀf³ÕÔL@ [LMÈvYq›˜<%|)‚ZÇÍ7ßLóæÍÙ¼y3.\p¯/((àÛo¿¥~ýúîüè³gÏâp”ŸÕI§ÓKvv¶{]É,|{÷îõx½‘>wî\÷º¢¢">úè£*Ù}×]w‘’’ÂÔ©S˽v çÏŸwç5ïß¿ŸU«VyØðöÛoÐ¥K÷ºÔÔTºvíÊâÅ‹/G¾÷Þ{¬X±‚›o¾Ù :wîLaaa¹ÌkÖ¬áüùó\{íµåÖ/Z´ˆW_}µœø?xð Çût:,Z´ˆsçÎy̘––Æ«¯¾êµŠ@ Ô&¼¥oh4艙Tîo¿ý6Û·oG¯×Ó¿î¿ÿ~¿sIEB~àÐétÌž=›{o¼‘Q£F¡ÑhX²d ,^¼˜˜W¿êþóŸ|üñÇ 6Œ–-[b4ùî»ï8pàÿøÇ?Üc¶hÑ‚–-[2eʾþúkRRRúè#î½÷^Ú¶mKjj*-[¶$++‹ß~ûuëÖqèÐ!š5kÆ™3g1bMš4áæ›o¦eË–œ={–ï¾ûŽS§NQ¿~}wë=p¥š¬ZµŠ#FðÔSO±lÙ2wÑãöíÛùùçŸéܹ3ÿùÏí]³f×£²=®+2cÆ 6nÜÈ#<ÂâÅ‹¹ãŽ;øþûïÙ²e Æ#zÑ¢EüöÛo<ÿüóî¨õ«¯¾ÊÖ­[éÑ£5âÏ?ÿdË–-äææÒ AÞ}÷Ýrc¤¥¥±dÉ àž¼\' kÖ¬JO„ŒF£!&&†µk×z}Á•ÀÁƒiÛ¶m¨Í+ìv;'Nœp_Ù 6£ƒ¢?+IßBÓ}Ãb± ×ëCòÚµ! «@Ó¦MéÛ·/F£‘©S§b6›yâ‰'üz®HÈ÷’®¾2dÛ¶mcêÔ©,X°§Óɵ×^Ë?ÿùÏrËðçŸò¿ÿýsçÎÑ AúõëÇ„ ¸óÎ;Ëùã?²iÓ&6nÜȹsçÜ“¨Õj¾üòK–.]Jzz:’$ñꫯòðÓœœìÑm£_¿~tîÜYÑîÁƒ³oß>^yåÖ­[ÇñãÇ©W¯íÚµcöìÙîÙo¹å>ùäÖ­[ǯ¿þÊ×_MDDMš4áþûïgâĉîÖ{%4kÖŒü‘÷Þ{Í›7³råJòòò°Z­ôìÙ“M›6y¤ZÄÅÅUÚMÄ;vdïÞ½LŸ>íÛ·3sæL6lȈ#xñÅ騱c¹ýo½õV®ºêªr™©©©h4~þùgÖ¬YCTT­ZµbРA<õÔSåfƒ×Ä*ýû÷wO:S‚V«uç™WœÊ»ìÄ:Á•Š˜‰Ð“PÏDhüÝ^2£:zÖÒ 1¡o$Y–•o•²hÑ"¶mÛÆÒ¥K}î[’sZ™ãògAxñË/¿ÐµkWÞ|óM^xá…P›£ˆÅbaàÀlÞ¼™ýë_Œ=:Ô& !¡W¯^ôêÕ«Ü• Ôdþ_&æ“fõÚ:ZŽ]|¡I|#r «È§Ÿ~ÊĉY½z5Ï=÷\¨Í‰Å‹sþüy÷ã}ûö1jÔ(¢££ÃZ”êõzÒÒÒ¸þúëyì±Çøê«¯Bm’@ {ó)Oñ ¢x°6pE h«Õês £ÑÈ¡C‡¼vWHJJ"))‰üü|÷lw‚ËŸ·Þz‹úõ드œL||<×\s YYY|úé§©áFll,;vì ;;›>}ú„Ú@ P\<è%@èðç²Ïþí·ß˜6m¿üò 'Ožä¶ÛnS,вZ­Œ3†+Vàp8HHHà£>òèX0hÐ  D=˜4iÇ÷ËQDX»Ù¶m?þø#§OŸÆétÒ¦Mºvíꑇ®hµÚZc«@ ¸4ˆ"BOBYDè­û†î*]Èæ{(Aúæ²@gffât:=z4:uòºß3Ï<ÃÚµkùî»ï¸xñ"ãÇç¾ûîcçÎØl¶rÑëx´èª QDX»ILLdðàÁŒ;–qãÆÑ·o_!HA­BÌDèI¨f"´åذf(_‡è³Ð,¾¹ì#Ðýû÷wwضmf³g¾Qaa!K–,aüøñîÓ¦Mã_ÿúóçϧ[·n\¸p[n¹…V­ZqáÂ’““™?¾ßvˆjV@ „QæI¨f"ô}F -4‹o.û´?üôÓO –[×\s ›6m Aƒ9r„… ²uëV6oÞL»víü~Ý»w³téRÒÒÒÊ-JjàÒ äwÅ:±.ØëŒûJôúƒëÉ3å o¤G“  º}%šä…^`éÒ¥œ:u Aå œ;w(j¹„N:‘••å.(Ôjµ´nÝšØØØ Û( öc=oÅ–mSÜÑg\Q}  €Ùlö("œ={6Ï<ó 999$%%¹×ÏŸ?ŸñãÇ“››ë1™CUHMMÅb±°bÅŠJ÷q‰M .¢´("T"E„yó(ØZà¹A‚ÆÏ6F£öÜdFމ^¯š¤.ûh¨[·.Ç/' ;°îþ$ä[­VòóóküZ@ (Ýnµ !GÌDèI(f"ô–ÿÑ,",Ä3ˆ™ýAh\ùÍàê¬Ñ¥K÷ú}ûöqÕUWd.zÄ+VT¥Aõ¹ãŽ;BmBHâÙ“`šO›±ç+ŸÌÅtŠ š¾âÙ7B@7Ýtñññì۷ϽN–eöîÝË!C‚bÃ믿ÎĉƒòZ@p%rÕUW…ÚÁŽi¿Iq½¤–ˆjdk5á²Ð&“‰õë×pþüy¬V+iii€+CTT=ö , gÏž\wÝu¼ýöÛäååñä“OÅÎ&Mšˆ3>@ .Wd0PN߈l‰*Rôu¨M\ößVNNO<ñO<ñçÎ#;;Ûý833ӽߌ3=z4>ø 7fÍš5¬Y³†Î;ÄŽôôtRSSÝâ]Gy‰ ʈBOD¡2¢ˆPá[”¾Å“`ø§Í‰ñ rþsTÛ($MÍçš4B³øFè áÏL„W¢ÐGQèã‰("TF*#|‹2·x ßb>bF¶*—›…K÷ŠÍâQDRSS9~ü8cÇŽ¥cÇŽtìØ1Ô& @ çWžÇô‡g ‡:JMãÉÃ*”¹ÿ~öïßÏÂ… iÞ¼¹("¬„0úÚ.ot: DDD„Ú@ AÀiqRt¤Hq[Tû¨°Sa$$$ ÓéBmJØsÙÏD.4lØ0 }¥@ „7¦ƒ&d»ò…þpì¾ÑªU+ZµjÅgŸ}jSž0;÷¹| ùžˆBeD¡'¢ˆPQD¨Œð-ÊßâÉ¥ö-Þ&OQǪ‰h¾W¤…fñÐAB$ä{" }”…>žˆ"BeD¡2·(#|‹'—Ò·8LŠŽ)§oDwˆ†ðk¾áFh߈" fôàÊ¢ð—BrÖæ(nKy$}#}-ò¡[|#"Ð@ Æ[ú†&QÖâYàB@ @ G¡óI³â¶p,T! ƒ„HÈ÷Dú(# }<E„ʈ"Be„oQFøO.•o10‚—ÙÚ  …fñÐAbË–-¤¦¦’––jSÂQ裌(ôñD*#Š•¾Eá[<¹T¾Å¸O9}C[W‹®~øöXNKK#55•-[¶„Ú”°G‘Œ/Á•=ÏΙ÷Ï(nK¸=„Û‚lQÕºÅ7"-@ ¼BíHßø‡Ð@ ›€Ö7УMÖÙÁ¥Bè !ò=…>ʈBOD¡2¢ˆPá[”¾Å“@ûÛÖ,«â¶Ú}šÅ7B@ 1«'¢ÐGQèã‰("TF*#|‹2·xhßbØç帓 ªCTÀ^çR#4‹oDaÉø@ \þœ™{{®çÕ}=)£SB`QõºÅ7"-@PC¬ç¬Šâ ¦SL­\j„€@ ¨!†½Êé’J"ª}íIßø‡ÐAâàÁƒ,^¼˜={ö„Ú”°Aú(# }<E„ʈ"Be„oQFøOå[ìyv )TÜÑ<u´Ñ/cZ IDATºÆ¯ öìÙÃâŋűâB@ «ÕJ³fÍHHÿêÁBú(# }<E„ʈ"Be„oQFøOå[rÿ›‹lW.)‹êX{¢Ï 4kÖ «U¹“ˆ QDD2¾@ —'¦ƒ&Îv^q›*BE碎¬è„nñˆ@ @ TÙ&“ûß\¯Ûú$Ô:ñ,ð! @ ªAþ÷ùØ ”óíõ õÄÝd‹ÁBè !fõñDú(#Š7<E„ʈ"Be„oQFøOjâ[lÙ6.n¿¨¼Q‚¤AI ÕÀ¸"4‹o„€bVOD¡2¢ÐÇQD¨Œ("TFøe„oñ¤&¾%ç›d‡rYì ±èèkbZHšÅ7¢ˆ0ˆd|@ . û dÿGùê:ZM碊¨½1J¡[|S{¿]@ ‚ ã´8É[Ÿçu{b¿ÄZ-žþ¡ µ¾8~ü8ßÿ=û÷ïgÿþý8:tè@ÇŽ¹õÖ[i×®]¨M@p…ÿ]>ŽB‡â¶ˆfÄ\+¦í¾ÛS¤ýû÷óÀкukžxâ Ö­[‡V«%::š7òÔSOÑ¡C† ÂÎ;Cm®ODB¾'¢ÐGQèã‰("TF*#|‹2·xRUßbÍ´R¸SyÆAI-‘<(9P¦…¡Y|–ú7Þ k×®èõz~þùgL&¿ÿþ;_ý5iiiìß¿“Éľ}ûhذ!½{÷f„ ¡6»RDB¾'¢ÐGQèã‰("TF*#|‹2·xRß"˲«pЩ\:wSÚºÚ@š2„fñMXnÚ´‰Ž;R¿~}¿öÏËËcûöí 8ð[V=RSSÙºu+Ý»wgèС :4Ô& @ ¨†_ d¥|ÅG¯¡Á¸¨ta—ô›´´4ÒÒÒÜšEz',ô冨f öâ,rræƒ38MNÅíuGÔ%º]t­ºtÝâ›°?Ur8äçç—[wâÄ æÍ›W+rŸ@ Ônò6æyÏ‘­#/+ñ,ð°Ð+V¬ [·nîÇ?þø#-Z´`òäÉÜrË-¬_¿>„ÖùHÈ÷Dú(# }<E„ʈ"Be„oQFøOüñ-–3 w{)ÔH$¼< Ë"4‹oÂ^@ïÚµ‹~ýú¹Ï™3‡Aƒ‘ŸŸÏc=Æ_|BëüG$ä{" }”…>žˆ"BeD¡2·(#|‹'>}‹ìšq/É®ñ=âÑ$†¶#ðÙ³g9{öl@ÇšÅ7aŸ=fÌ4hÀ´iÓ((( N:¬ZµŠaÆ‘žžÎ¨Q£8vìX¨Í¬‘K$J"¢MØ·ßÅî,$g]Žâ6m²–c i¤ [Užë `÷·ßlL¡[|öènݺñ믿ðå—_Mÿþý$IDA­àÑI“xtÒ¤P›!¨%˜ÍæP›pÅã08ÈÛì}ÆÁ¤I!ÏgÏže_n.ûrs…TNØ è~ýúñí·ßÒ«W/&MšÄ£>JTT¿üò ­Zµ ±…@P9v»å›6±|Ó&‘›+ð‰Ýn§IÛ¶âX 1¹ësqš• £;DÙ2Rq›ÕjeÁ‚—Ò47wŽƒ=5{j*wޔ׏{ݼys>̵×^ËÔ©Syùå—ÝÛŽ9ÂC=BëüG$ä{" }”…>žÔö"ÂG'MÂ6t(¶¡C…E„ÊÔvßòè¤I\¨W/àW,„oñÄ›o1Ÿ0cÜkT|ŽJ¯"±¢×1GO˜À„×^ÃjµÌN%Ξ=˾¼Ó¦MÃd2tÌ`# }<1 Ìž=;ÔfT »ÝÎòÍ›‘ûöEîÛ—å›7LÜmܸ‘7d¬P±wïÞ€Y›‹K®Vðæ›¿b1øÎ;6V ?ÿüsÀǬŒ@Ÿ)Ê™Üu¹^Ÿß+Mœr-ƒÕjåóôtœ<ÂèÎì”e²¬V~5X—“ÃǼ~âcfÈþý´9û¨Qîýí£F, -4‹oÂR@*·‹©Œ‹/^KÇùå"„êât:q:•/-] 6mÚð1Ÿzê)6žÉdâµùóy`ìØ€ .EÑF°Û=:ò²wï^Öýøc@ÇX·n]ÀÇ,Á&Ëün4ÒwìX¬C‡‚$$a:”O>ÉéDx–}ý5˾þ:Ö–²gÏöìÙÐ1+ãÖAƒøðã:æœ9sê[€  òG'MÂ6lh4؆ XzÙòåËÎfÙòå\Лo¿=h¹·{öìá†^½:¦ÑhäTff¹uw\Äz^Ù‡éê눿1Þëx£'LÀ6|8òwðyzº¢/”óV+{Š…ñ¿22˜vò$cfèþýtÛµ‹FÛ·£OOçªmÛ¸þ—_´o:Ä+'N°èÜ9Ö<ˆÉl†¶mKnÛ6`Qè&MšÔxŒË°Ð'NdÀ€~ýéoÚ´‰!C†pÿý÷Á²êsðäÉ€Ž7é…˜ô ³2ýíoL›5+ cv¹ãŽ€:óÆŽÅ1v,ëvïZú‰'Ÿ ø˜·øâðáà :”Çt\oìÚµ‹ëºwè˜#ž}–SQQ¤§§lÌ—§McØèÑ5§D(~á¯8Á½Ðá矉NO§ÃŽlÙºî¸Ã½¿|ÇlúášlÝJò?Ò{Ï&=ÊâÌLö Xý<16 ¬ß»—õ{÷TÜ 7ŽaãÆl¼Êøðã1víÊßçÌ è¸#~8à´NÝ»ôø+K‘ÓÉ~£‘/23ùtÓ&äâãE¾ã>Ý´‰…§NñMN¿ äØlÕzñ3gÂüù®Ûq×èÑ8~˜»ð;ò‡aãÆ±¯   'xwÍW;v¸E§ã¢ƒ‚ï ”w– iP’WåT}–ûôq,N·1cx²X߸{7·oG÷ý÷Ô߶늅ñ#‡1õøq;ÇWÙÙü\XÈY‹»—&i‰ sç‚ÂçÈ(´ r²]AAsçÎeΜ9Ô­[—nݺѪU+Z¶l‰J¥âСC>|˜_~ù…ŒŒ ž|òI&OžLݺuCmº"©©©,Ù¹“Éý+oO›VãñœN'Ñ;`Ü¿•êÒžM›5‹WþøƒˆŸ~ÂôûïsÙòå<´lqg΀K¸&“‰ÄoÄ:w.üðCãË%K`©w¾X½š{RSù|ñbî>< cΚ=›ççÌaæÄ‰< ÈSÛž=9Ô£Wÿð/‘(KÓoäŒÙÌÎO>¡K—.5/==Þo½…cÔ(®zë-2tÉ8ª];ÌÝ»óR“&L+S[á ›,sÄdâ€ÉÄïF#ŒF˜L1™°ys£óçÃÕW—Ðl܇‚PÕJm£¢¸6&†Î11\[¼ÔÕjËí7øø¦C8ÀÚÿÛ¿7^ {öìá†âãî—Ù³éܹsǬŒ¸Ž)œ5 ÕgŸ± gOä‘™••EƒnÝèÒ±#;¿ù&VºŽÁÛ'O¦žÓIÆ/¿TkŒ"§“£EE-*âˆÉäº-~|Öbqµž?Ú´¾}KŸ¸a>\îX‰P©h¨×Ó¨’¥¾NGIˆeË—óPZò“O"-XÀÒ¡Cy°†A§³gÏÒ|Ø0l³f¡}î9Žù% 6¬Ñ˜•Qrl:ÆŒ¡ÙÂ…À)÷{xøa®_³†]ÿûV]Àø»rîsÌu1$Þ•Ì‹…?ÍfŽñ§ÙÌŸEE7›Ù3}:ævíJï² cǼyàGûʆz=): tºÒûz= t:÷ýœŒ š Žý­·ÇÑL™Â‰Õ«kô}ˆ6v¾ K]Baa! .dóæÍìß¿ß}†X¿~}:vìH=?~<ÉÉá= Pjj*KNž$"+‹¢Ч§Láƒâ(ÄSZ-ï{ùЍöí)š3iõj^kÑ‚—Ÿ{®Æc&\s ãÇ#mØÀÒaêíÌMùv;æ6m gOe´&ðÛ÷ßÓ0.ޏKÔw7ñÚkÉöYÞ}—¼ß~ Ș1;b7Žèùó1ìß_ãñ>L‡Ñ£±¿ñšüƒŸ|B›6m`©2»víâÆ¿ÿǘ14™;—“?ýTã1Sºv%sòd°ÛQ/_Îæ)SèÙ³gÆ|yÚ4¦gd Fä„ ˜þøÃ½­ZB¹•$Ñ,"‚QQ´ˆ`öСØçÍs¥o”E–ÑŒσŸξ¢"˜}DžSt:· ¾Z’xô®»°¿ñÚüƒÜíÛ‰‰‰©ÞRLó[oåDq T DŠ7>üøcžLOÇ9z4˜LÄ>÷pÌw4ˆŸ»tAóÍ7œY·Žúõë×xÌ”n ó¹çPÿßÿ±ù…¼&‡ƒcfså"Ù<õ”KD—=^dÙ%ž?øÔj¿mÖJ ŠÅôοý ÛŒ—‰‰D¾ø" 6l@+IèT*´’äõ¾N’Ð*Ü¿}à@ö :Ànz©p›¡~íµ€œàuéߟ݃CRÚ÷ßçðìU°Áu²“g·“g³‘g·“o·s^mç?Ãàfe?`·Ãøñ°paùïoãF´üA«)SH)ÂÞDr¤ŸÁ°ë àסC˧o”åàA®KK«Q_è‘#G¢×ë…€®„°îèËsÏ=ÇsÅ‚-//§Óö‚Y‘½{1ׯOëAƒxhÁbÔj÷[æ~ÅÇê ¼N§“¾þyî\>š0Ù3f\’(´C–y}Ö,Ì·Ý:òðá¼9q"ã&MB%I¨p ©øVUáÖ[wÌeË—s±];X¿ùî»÷æ›ô>Üí¨òJ–bçUn}™uyv»ër·Å¿þ %—­$ Û=÷Ðþoƒ¿ÿ$‘¨Ñ¤Õ’¤p›èe}‚Fãñù—ðÅêÕ\lÓš4áb›6|±zu£Ð³fÏÆtë­ðÕW˜n½•Y³g×( m—e?òöâË|öÑ£ÿœQÓ¦qÆbñXÎZ­©?6Yæ¤ÙÌÉo¾q‰ÜØX—8þyŠÚ·gÔüùЧOõ>”ìl—/¾B‡ì^¼˜ˆÕ«ÑÔ­‹Z’PjIBUæ¾{©ðXUö±Â¾ÆÃ‡9©ÕºMǘ1tì1/^ì~nÙÿ5¥ÿEe·©ËìcÊÊbOn.4o‹ãü[*Ï?4ŸøÛîTL£Úv VørU’D#½óìÙœ¿ûnÏß{Ÿ>H«W³§sgt:]õ>ï2œ={–½™™P¯äz)t¬W½™™œ={¶ÊQè´´4ÒÒÒØ²e‹{Î 2a¾\HMMeIf&LžìŠ0,\è÷s#Tªr‚:{Á2á®»\;¬YC‹‹i÷ôÓØeÙ½8ÊÜW| åWÜÇQrX<ñÌ %?ü•+]·#FøeEa-æ'ž@~ë-—C×çѦMõùÛoÃ7º¢Ï%Ȳ+ðÞ{ ×WkX ˆ÷òçýÉðᘧO‡¸8¸x‘È—^â­o¿Å)Ë8AñVö²¾ävÎwb›=Ûe¯Å‚fÒ$îûϰ k±ø° ÷z/Ûå3g\Ÿíôé¥o꥗\—‹ÿˆÀõÝhŠÿt4Å÷Ë lÿ}„üÊ+®ÏžE÷Î;4Y´È-–MGÕ¾„§Ÿ†—_†:uJ×ÍšÀ5×TýKX¶Ìu<ß{¯ë±Íæ:V>üÐcW„²v»˜ë®Ã2w®²€eô&`øõW¯³f•Ö‡rrpLžìº,\–ñã]¿…HåÞ´>yöY˜4©ôØ8sfφ‘HÖ­ƒŒŒÒ“^“ÉeƒŸþQâ4’Ëü.™8‘Üáá8ÅMõ쳌]¸ˆäd·_sTðŽ þÏQaÛ=„õ¥—\‚\'/úuü%iµn\ñ6©LJŽÝn'¦sg,| |¼È2ú§žÂ°gâ±"¬VN+ˆëÏî¹ûÌ™Pöê„ÁS¦¸uuxñExàR pàüûßðæ›Õ³2*›¯¾ >Õ ¢Â{¸îW¸ö pë­P<×D EW©ùóîHZDEÑ<"µDFÒ<"‚¦È6±;c­xõ iãFîÏÎfÙ¢EÕ³µ +V¬àå 8}a.ŽÜ\°Ù\¢pûöòœòçøñüùÀÞÿ¨«Ëʕлw©x6 &Lð[@—DJù¦MЩS©x—|þyE­–$4K­¶Üã›WOžÄ>yrù'JŒA§ý‹¡o½EnqÄ:×fsÝ/¾Í·ÛKO* ùÅûüYvÃ?¸òZKZ*ÆÅQtõÕLøäèÑïÏŃÿüÇõܱ¯×cïуe À_ÿZ½1çÌŠEŽcƸֿóŽ{•S–±âGºŽç²¥Q#׺£G+)*IB/ID¨TèË.•¬ûbíZleŽA¢¢ºv¥Åwß¡4ˆœâß©·«2P`·SPòûÌ˃ÂB·xpŽÅüÉ“áõ×½Ú_){÷BJJ©x×ohÚ4W`—HVÈ­*ˆäÊxtÒ¤ÒN-JHÖââÿ÷Áž›z:õt:º”ñ­Ë–/çß;–Ï11H:1ëÄ ÞsVYÆ&ËØŠOÈ+»áÜ9¦Úl8ÊŠg€PÙl<¦ÓY¯pŸ°8ŠÿeNPÊnsàòCJÛ.:ÄÞ¸8ä²Ç&À# 3‡æ ¸Ÿ[Œ(ÏYfܲÛ.`±ÙÜâ9î"tÜtîLôÎt>Üý“¨ÕÐäñèS¼b,î¼áíû“ûôáóqãøÄj­qzäÈ‘ÕÅ‚À"t°4ˆˆ§Ÿ&óã18Š#s† ‹Òºßy‡c–/FÐh`à@ê/]JË ÊEkº¼òý÷Ø*VÇëtH½{3`ãFú>ñr%Õ²·%Ñ×ÙiiXfÌ(?fl,R§NL8tˆ!#F”Še†X¦Òƒa?ŒýÞ{•Wúüsž¯_ß={eEJþ„•ĵûq…mV®ÄY1ÊòÐC®ˆ†ŸºbdÞ¼q£KØ–eøpT“&qõßþ†^¥BW,@tÅbD'IåoËl7œ<Ɉœ-Z”³E T<+I$6oî3ú¦x«°=÷À~NJB®x¹ð‘Gˆ~÷]ƬZElqzR¬ZM¬FãõqIêRÊ3ϸ¢d©S¹aCþe±xä¢ÚŠÓ Šq]ätòѬY|Ö§rEa3læ X€À¸q㼊âš`0ظo<ö˜çÆ[nÁ°b[®¾Úk.´½øó©¸ô{á2”ì3†”ùóùï† ^…±¦Š'î~ü1+o¸Á#Ê ßwçŸ{Ž‹¯½æ^Wèpc³‘k³‘Sæ·XqÝ–×^ÃP10ѱ#,YBôÅ‹h“’ÜWMJRÊ^IQÚ¶ë“O\Ñç²Ô­‹ªQ#qoß¾$ Æâ›õëÑGG#U’³*;|cT.nóÆø·ÞB.{ªìx>È/½Äd?ƒ9%tIMÅáå9ÎÔTv¾öZ@s¡›¬Ôí¨Q#œqq¬Ô骜 Ý¥v—y7íµ¨[—"›’Dlññwc\¥âÙn·óÙ×_#?ñlÝêu?k³f<2q"Kƒ4C¡àÒ#t°(i=¤Ñ`éߟ7^{­J9œN'ÑÛ¶¹£å8‚ øáÚk– =mÖ,ì½z•>#Æ÷'²®¸€É_–-_޵C‡ÒhÈÙ³pÕU V#?ø ‹_z‰9U˜Çd2±n÷nïÑ|IÂzÏ=<0v¬×Ž®Êç†æ>_ó‹Õ«Ѿ}iô¹„¸8TíÛóÉ… 6ÌkN¸Rnø¬Ù³y¾gOä’èó©SФ èõÈ=zúãUÎ…n›šê*ÎRÀ9z4k^~9 9š>ú(²Ò¤ bNHà¡‚‚*uäHOOçB½z¥Ñg‡23¡X ;FbijÏztäÐJÚb!®ÄÀ/¿t×”¢+úåiÓüêÈ*î{üqle£OÅí¶â]}im#Grßã{íÈ¡‘$w:X {öìá|ddùès q>2ùèQÚ¨#ÇßçÌÁé­%fTÆë¯çÃ?vwä(9±jVÉï3++‹ÆFciJAßBj*í?ü°Ê¹Ðéééônк;9GâÕ©Syü/©Ò˜Þ8èP@Æ)˲åË),ëo¡Ô·ÄÄPء˖/÷»ˆÛ=ó]Åès :°oÉ’jåÞ*±gÏNëõîß}EcÆ0lܸ*»V|Mÿts(b]¯áìÜ™_}Åc>ˆ:FMBïÊû‰geeñ÷‡r¥ UƵ×^Òî@c±XÐW3ýñJAä@ÔÔT–ìÝëÊǰۉxúiŠð{Œ’ÎrIîs¤5kÚ‘#ºcGL³g».o+½ÞªU¼Ö¬Y•:r$\{-Ó§—:ôyó\â·ø±ê£X2x°ßÎüžQ£øâäI¤nð¾“,#-Y‚ñÔ)"üȾHêÜ™¼iÓʧ ”PXHâË/“[Å¥1;b|÷ÝÒô™3])- ÑÏ>[¥Že;ox#9Ü7¦NUÞáìÙ*wäHéÚ•Ì)SJ´ÁàJ}?Þ½úí·«Ô‘ÃÝy£$÷¹"6›GGŽpÂ`0tóÍØÊ^Â/‰x•é»­}ê©*uäh~ë­œxòI¯"…³gi¶`A@:r|øñnjݸ¹²¾Á&1/¾Ha:u4ˆŸ,v|‹vÊN¯YS¥Ž)]»’ùÜsŠª~ü›øfÍ0THßú éÆKw2ˆÙ¿Ÿ‚'ü³KÿþìîÝÛ»€WGŽÍ›…öylê×_ç—÷Þó; Ý¥vßutè€Ö.1ø3‡¶ýݺ•úÕ† L¼ï>Z¤¶ ºStßGm¤wïÞ4iÒDä@W‚ÐA 55•O¿ùMË–îuŽÓ§yoÊ&ø1ÕgIßgóܹÞ{IÚíDL˜¾ÐÓfÍbêŽ0t¨÷l6ô3f`>zÔ¯1—-_ÎÃk×âTºô\‚Á@üK/‘ïgK¸´´4NøéøGŽYãvV_¬^͈•+qV2Ó¡jáBVŽáwGŽY³góüÁƒÈ•ä´I+V0³m[¿£ÐW÷èÁá>} búFYþü“6›6qè‡ü³2šÞx#§Š;oxCýúëüôöÛ~E¡Ož®üH~Èî+jÜ6,õÉ'Y·c‡Ïýô²LúêÕ4oÞÜç¾YYY4¾ë.l• ëºu~G¡Ý½Ç+ËaÏÎh_ò@³mÛ6¿f«U©TÜrË-~Ù­_?Îù1ëoƒ¸8vÖpöÓ“'OÒªKÔeþ7•p\¼È­Í›³ÅYE322hئ ªâβ›Ó>ßÓoÉÄÖs0wÿ%( ­%ˆ"BßÔ }êÔ)^yå¾ûî;Ο?ïžeîÙgŸ¥gÏž 2$ÄVNMÄwß}—)gºqãJ÷sœ>Í[Ï<óÏ>[­×)áùW^a£“èd™•~Hcv<ðÈ#üwçNßc:üôÍ74mÚÔ/[ƒIb«Vtê„ï}W¹ €ø}ûÈóóÄ"ª~}¬Mš(¦Ê¸±ZÑ:…ÉÏ™Õßw™……>÷»*6–µŸ}æ×˜Þ8~ü8mºvõëOî–¦Mù¾}IkÂÉ“'9v,V?òuûtéÂ[Õ-:«e|ûí·,ùâ Ÿ­×$àá»ïfÀ€Á0«Jô8ôS§PùðCò¶mœ;|دéwßÍw¢òq [ÎÊâ K%ðø(U IDATÄjµ’‘‘€å ÓÿLH²‚OPA½Gë‘Ò6%ȆB@û&ìt^^:u"&&†Áƒ3oÞ<Ìf3Ó§OçÇdgž¡Dˆ—ëׯÇn·ûÜO£ÑЯ_¿ X$Ο?OQQ‘_û¦¤¤¤¯ öâ4;Éù&ã>ï™ñ=âIì“D«Â¡[|öE„Ÿ|ò 6›½{÷’ŸŸÏ¼2í§ºtéÂG}BëüÇb±„Ú„°ãèÑ£4kÖÌkÜp#X¢øàÁƒ´õ6ÃÔŠÝnçĉ´ªnÏ×Ë”ììlêTlõwQ¯l‹¹bj›o Wºo):^DNZö‚Ò@ˆÃéàLþš&¹®"h4$ô¬¼pðJ@ú&ðÓטƒ2tèPt:Gn¯,Ë †YV5²ü¼ü~%1oÞ¼Zóý“™3g†Ú„°Ã`0”;y¸Øºu+[+iu¥"|‹2Wªo‘í2¹ësÉZšUN<­F–ì,íÒ”ô—$$m€çT¨…Íâ›°Oá˜>}:+W®dïÞ½äææÒ Aw ǘ1c8pà;ü(J %âRˆ@ ÁÇvÞÆ…ÿ\Àšeõ¹oT»(êð¼¢q%"t‹oÂ>}×]wqèÐ!¦OŸN^^ÌŸ?ŸÏ?ÿœx Ä @ 'dY¦`{ç>:çSŠÈ–‘HêÚ—*|‹2W‚o1î7’óMÎ"=°%ˆëGìí±œÅ³:VMýê“ô—$Œf£ð- Íâ›°/",ÁápðóÏ?“™™Yn}:uè^fÛpD$㇢#EämÌ#ªcñ7Æ#éj_äL &²S¦pg!ùßåã´T.:Tz‘WGÝ>šÈV‘HñûªMزm+"ºc4êhu¨Í (æf²¿Ìöè°¡DT»(’ïLFuy}Fèß„ýõ-³ÙÌc=ÆÚµkÝE„eéÕ«ß}÷],«‡³ÙŒF£—ŒÓâ$÷Û\ ¿º"NÖ,+…; ‰»5ŽØ®±¨´µæB‹@4,§-ä|“ƒ5ÓwwpýÎŒ{÷]bºM$Qí£ˆj%Ät#[eò¿Ïç⎋È™¼õyD^Il—X"[Dº¦™¬¥È™üÍùl+À×tš*½ŠÄ‰Ä^ãj)v»»ÝŽÃá@­'•öJîý÷ßç³Ï>cÑ¢EôèÑãi~m£;wîdâĉ <˜Áƒ‡ÚœËó 3ÙiÙØóËGFyëó¸¸ý" =ˆ¹>FüÉ €Óä$wC.†=Ÿ¢Ãë'Æ}FŒûŒH:‰¨6Q®œéÖ‘â„5Œ0ì3·>G¡Ã½NvȘ~7aúÝ„&ACÌu1Ä^‹:®v‰%ëy+Ù«³ý:Ô7ÖSwx]4‰µC/„’o¿ý–µkײsçNn¾ùæP›Ö„} ÇØ±cÉÉÉaÕªU¡6¥Ú¤¦¦b±XX±bE¨M +jRèã´9Éß”ÏÅŸ.ú%4ñâ{ÆÓ9&ì‹¢®„BŸª"Š•©J¡,ËvÈÛ˜ç»Àªš¨t*"[G¦ÛD…lBŠ+½ˆÐše%w].æ“ærëe£e–ûK*‰ÈV‘Ä\CT›¨°®Ž’e™ÂŸ ÉÛ˜ç;/_‰½‰ë‡¤R>…oQfäÈ‘èõz‘ÂQ aü3qѽ{wöïßj3jŒHÈ÷¤º…>–³2>ÌàâÿÄ3€½ÀNÎ×9œwò3|Ï/çBŸê"Š•ñ·ˆÐ’a!ó_™ä|íGw¨öI¦ÓêÄxÀÈ…Ï/pjÖ).¬º€q¿ÙÜßÛðÊ+"tšäþ7—Œ3<Ä3À­ Ÿ';eL‡Mœÿì<§ß;MÞÆÔ0€¦­£%¡WQ¢¤ðŽH ÕÅiv’ÿ]>…?úuÒ¨Ò«H¸=Øb1Ÿ0cüÝHÑÁ"&‡ÏçV†¤uE8K"Ó*}Íb7N³{¾ÝµØKïçÛq8p˜¨"TèRtèRtèèÑ¥èÐ$i.Ëß»,ËöÈߘÃX³ïÊÍ"ˆ¹>†èvÑAM“e§Á‰-׆=×î¾-ú³È¯ÀØbIê/¦ä® B·ø&ì¯oéõzî»ï>î»ï>Þ{ï=í·Ýv[¶l ¾a‚ b=o%ûËl¬þ<ù–mãÂЦkI¸=èvÑW  {‹ó_ þ ªèŽÑ$õOBëÊ…lId«Hìª500b:hª–@“m2¦?L˜þ0!iʈ髕ŴÃèÀQàÀžoÇ–osß/Y|u —È67c>^‰½Eµåœ…Üu¹XÎx®÷ç—™K̵1Ä^‹¶ž6`ãÛ/ÚË ä²·²­ê±=uŒšä»’]i(Á%&ìôÿþ÷?î¹çî¼óNzôèAݺuËmOII ‘e‚  ÃÅíÉÛìG¾›äŠU„J1’lÏ·WI¨W•NEbÿDb»T¯=ð-ʈ™}öºqãÆ=z4ÔfÔ‘ïɼyóxõÕWIHHðØV¸«ÜÿåúU|sm IIBQ QÇ©IœLÜ­q|_€q¯Ñ¯PË ™K3‰hAÂí D4 ¾ž9s¦È;«@I¡Ïœ9sBmJXQR@دm?rÿ›ë×DQm¢HüK"ÚÄ\Ž/ΑhAÒ_’0Ÿr‰iÓ&ìý³ãR±dç&öšH\D\µžï´(‹jm}-Úäò‹&I´ü`ói3¹ërýNeÓ$hHêŸDT;WJÃÌÉ.ߢŽRs<ñ7Çc9m¡pW!ÆÆ*Eþ'…» ]ð¡o¨§Îð:h“« ߢLVVMš4 µaMØž={–=z°xñbzöìjsª…HÆ÷G¡ƒì5Ù)ò¹¯:ZMòÉDµõïf˱‘¿%ã~c•zßF¶Œ$¡wú†âLÜœf'Ö,+£]=šä0‰Ú]¦Øóìäþ7Óaÿ¢¸šx IIòë7S]dYÆzÆêÊ™þÃä·¨¯µH®ÏU“¤)ÕÉtÉ:Ô êJ;@ø‹Ãà oc†ßüëÝ-i$âo'®{œß}¹Kz{î* X­É¥BRIÄ÷ˆ'á¶„ZÐK¬v"t‹oÂ>½{÷n’““¹í¶ÛhÒ¤‰GŽŽ;òÆo„È:A 1î7’óm¶¢ÚE‘<Øÿ|7m²–º­K|x ¶`üÃ?!]t¬ˆ¢cED¶‰$±w"º«t~½ÞåŽì±çرfYÝ‹í¼ÍC,I:É•_š¢w_×ÔÑDT\i8ŠØsìØrlØrlØs옙üÊ#•Ôq7Ç‘p[Â%ïL IúÆzôõ$öOÄz¶Œ˜Î°˜V&Nƒ&¡x‰wݪb\)ÖsV¬VlÙ¶K׺RÆ]Ühþ³|º™¤–Ð$jÜ‘ê²âZ«ò}ré„‹;/’¿%§Ù¿t¨«£HTåICTz±7Ä{C,– †ÝŒûŒ~¿î¥B©B›TüùßêèÑÖ P1£@PMÂ^@4l؆ †ÚŒ°BvÊ qYD÷&¹ßäb<`ô¹¯*BEòÀd¢¯©^× ]=uï­K\FùßåStØw¤ èpEGŠˆnM|¯xtõ®!í(t”ÊY6— ñ#·Q¶ÊXNY°œ*­´ºúÅŠ…µ¶ž6ì'¸ ²]vå‹ä¡l˱U»=YD3WñŸ®nðYI’Ð7Ò£o¤'©–³WÎôïFìy¾Å´¤‘Ü¢X“ A¯F› E¯v=Žõ/Â+Ûd¬™V,ç,X3\·ölû%ï/;dlÙ®ßKE$­T. ¤D\k“µ¨£Ô˜O˜ÉY—ƒí¼9Êš$ ÉIH!´>E~ž¤~I1ì6`>å_QjuPEyŠdm²m’U¤1 “°Oá¸HMM¥ÈPIJ–![eœV'²M.½ïëVi_»Œ¤–PG«QE«PG«ÝKÉcU”ªÜºp›b÷èÑ£Ô³Õ£à›¿ªù#[FRgH€N9k9cq écþ i$W˯„^ 5ʽóF¨ŠV'¶ó6lçmå󥚵®,’ZB[OëÔºÚúZ÷1{YúÈ®‰}lÙž"Ù^`¯RŠQž)€Ä¨Dmê5‰ý‰¹&&P–Ë9—˜67»üT±(.IŽö#J«€?3Ê6K¦[†Í-¬m.a¤º ¨"T~G~%­DBÏânŽó™‹]ßb½`Űۀá7NSÕ}‚:F]^ —¹-[¿l.+ß@ÄL„¾:¤¦¦røûÃ,xyHít’¢ÐVáQªÀ_fwº"2²Ãu0îÁqŒk?Îg¡J§"¡oq]«Wäæ“fò7çûÕñ£I%}m4±×Å"i$dIF’$$Uñ}•„$•ÞGÂ}ëí¾¤’H•zi– ¶\›;í¢D(Ûóª&Þ.5’JB[G‹.EGQloýû-f½8 ì.ñã´¹N$e[ñb/^W|¿Òõ%·ÙëwâÏ÷U¥çHŽBWK¯@µïZp=à*",ýà ¶k,‰½C*LBÉĉ½(W†ÓæÄ–U*¨­ç¬X/Xk¾öÇ ¶ Åbù¼õ’õÙ„}C=Ƀ“Ñ¥\9)F—šQmÍ´–»bŒmÞÐÖÕ’<0™ˆæ¡k· ®Üoëy«ëªA¢&ì®r ª("ôMXæ@9r„;vàp8ÈÌÌdÇŽ^÷µZûZ¸Ö"»r“&G•û„I#‘p{ñ·Ä»"xA"¢E)-R06‘ÿ]~ȪÑÝ'Å:ÞA€¦ç a;ÜQEªHìSÜ 7<}µ•VåÎã.‡Ó% Ëæ«»ÅõÅKs5G¥W‘Ð+¸ã¢ûDIÊ xdee±k×.¿öíÒ¥ õë׿Ä]¹„å‘?yòdºté‚^¯gĈŒ1"Ô& ‚ˆ.EGÝau7el5ˆjEdëHL]BÚßBž+ ISœfQ_‡®¾+oYW_‡¤—\—Â3ŠsK3]ÑïPEì.'$­äº,^§´eZd«H1[°Q¹ ÷4IÂ=§Í‰#Ï¡(®«U *AÌ51$öMD#¾ç+™9sæðÖ[G‘¤v•î'Ë0eÊ̘1#H–]y„¥€>~ü8Æ #//¯Ê9láŠÕ!"å©8[˜¤’ˆë^Üj+ :2H’Dt»h¢ÛFc<`$K¾b5} 9–}Œ–uZ^òש*š Úz¥bYW_ç*¢ô «µ“2¶ ¥¢Úšáêèá´úN0u8œÉ?CÓ¤¦z;a¤’P'¨Ëõ.¹¯ŽS» ë²³³±c'&:< C…?E„—•V…ªžJ1à4;=:­”ˆk§Åó· »JGÒÀ$"šÔ<]CÌrêIm,"”åå¡>öJ~ªökˆ™}–úr$Û]~…ÊU§Ò©tRµn6'£‡ÑÓ¨|?œs]ËΦ­£¥Î°:á9aIqרöQ÷Éÿ>߯\ÕeáÖ…!Í—WéU.q\O‹¶¾}}=Úz5¯”—Ô®žÐº«tPÒÎ]vMrS"¨-¬™ž?ŒV#Kv.aꀩ5²!ÜPǪËÏjW,”5‰¿N"Kf":Ôןé•Ee³œ†U„ ]ºžyêƒ+j]"°5 b® XA·˜åÔ1¡2b&Bß$ZÝØŠF•Šà Mýê´:=Dµ7Áí49ƒÚÂiꀩ AÜq$ôIûI%Ó9†èNÑö(H/¸$³¬KÔ–Aø`¾tóÙªHX è_|Ñç>·Ýv[­еI’PG©QG©¡n¨­ñÍ}÷½À™3ƒ€ÊO²$i }ûö¥iÓ‡uþŸ½3o¢Zÿøg’¦{éB)P¶" ,‚¢‚¬²(¢pYE•ÛŠ"¸¡àuA½Š?¯Šz½ Ed‘Ë&PP‘½ìЖ¥tM“œßÓ´Y&™IÚ¢çó<ód29yçd’œùž÷œ÷= ¥T‘©$’? ………T*ÕqšÀ¤I“8}ú´n¹¤¤$ÆŽ[ 5ªzl6_ýµ¡²)))´l©ßƒ?räwÆdºR§d1Mš~ÿý†Îpð ìØá¾>løí’‹LµЇ"66Öo™ª  ”¢¢ªÍ+\©h ÃaBˆÿ70“é§rÙ¯*þê>V«•9sæ¸s8dee‘””äv¼eË–têÔ©2«W­ÈÊRc+«¸&Ջж--ZÜÄ‘#µÿ™€„ØÂé8qÂPJÖÐÐPêÖ­kÈæ3Ï|ŠÕúœÇÑc@=›¯Ð/½ô*'Nè‹òºu“xñE}˜?Ž9BJJGL¦«uJÓ¸q{ölеyìØ1úõ{¸Íå¨8$”¢ ¶’‘±QצÃán¤¸ø#’ç¸paæ+V+üö›·X>^÷ô D¨OµVŸ±±±Õ¶w(‹­")©‹Û±©SãöÛo¯¢ù榛îá»ï2ÐO4šÉþýëËg²:úhqíµý9r$[·\ƒ5ùñÇåå>Ï¥èsâÄ 7î†ÉÔÀo9!l´m›ÈæÍ‹ Ù5j:B¤º-@ˆ5(Ê`·cõëçðaý›¨+Ž!<<œððŠe<øòË/yòÉw •}óÍéÓ§n9­ü¯[¶l cÇŽnÇæ?~F^Þ ŒÜ<Œs.^¼˜ï¿ÿÞíXDD¯¿þºWÙþóŸDFF®oeñû﹬Õ-—›«/†œ\yå-?î-ì:¸=ONŽc×®¯ ÙŒŒlFqñºå,–?ÈÏßk¬¢>°Z­ØlKĆ?Îqøp 7¹Ö8z} ÷uSÏáp¬2d/##ƒË.ë‚¢tÔ)YLƒ™<˜n¸®ZlÚ´‰ß~ëܬSòk6mÚdH@kçuN*[l*Ðü¯Çóx -O*–ËÉŠ›9vlàdÑl~ˆ .Ð={ÞËÑ£m×NOC-ò*ÿ‚V*W0™øAhè<Ó¬ ½ÛdŠn1PîÍ §r‰*'ˆðäIˆ×‰GЉ6mT±|õÕж-´jÎ>ýøñ•# ¥xÖ§Z h³ÙLllli®Ó?fÜxã‹„L›6×_?èͱÚÅÓO猪‰ÓÇŒù/ÇŽÝDù-§(“=:S÷&—Ÿ¯{Áá€üš6…ºu¡N²­n]µÁ2†‚ûw诜1(¦ À÷J›N ¹q6ÄfûB·”ÅRÑ›ñ¥…¢ÜdHèËó”¤(sаÅÊÈÿzñ‰E¯sƇŽív3Ç¿tmšÍú£G’?B€Ý®Þ((0þÞÂBX´H½×¸?ºîgg«¶`÷XC'9¹L$;s“& /}Ž» ”‘\Lª¥€nݺµááÖK‡tÔá³è{{¼bˆ÷U÷g_ôç+/ó:vòdÙP×Ï?«{öo´„€… }¿é.¨µöëÔ1v.IùBÆÌϯêšHþJäç«mÌ©Se99ÆÞëpÀ!¾·ðpÿ¯"À>ùD{þ¼:À×¾Q±XPaa]¶EE¹?wnþ¦¸b³Á´iª“£¸X}tÝËÊ‚'Ô! N¹,êÂu;Þ¸€6m‚ÌLõ3ÄÇ«7“áÔ¶ûP=tî?ÃåËáØ±2Ñ|ê”ñϤ…¢¨=ý¬,ЊåÌχÔ-Øíê›Ö ÈX&½ÝÀÅ"NŸV=-®[Q‘þ1çóÌLã7ªS§ qcõfY\¬ÞdûÅÅÞÞwlÀ!À}µ°S§ C¨QCÝbcËö]·¢"<¦ø¿.gΔÕÑYÏ@öÿÝØ¹@-»p¡:ÏѹY,îÏCBÔïÊçMîA„{öÀ§ŸB^^™ÇLkÇãõœ1CýozÖ+ͨ_DX°@í¸††ª×ÃbÑÞ×uÞm‹ðϪŸÝU,çæ¿Zõüá‡ò¿?P¬V¸çžŠXÐn[¬Võ7æLEl—Q\ ?¬_.X˜wÙí¶ÅfsYÇ'2Rý]8`¬}‰‡'Ÿ,o=UzõêE¯^½*fÄdàÀ 6ì¢ãÏ‚Е†· œ9SÝ@ý3:EhBBÙ~|Î}­ç¡¡jÝÓÓ Óèhxè!µ­7™T‘ëÜw}~þ<¼þºgۮݶԪ¥:BœBÙùèéÉÈP§]øw\zÈ B}¤€®4üÿíñã;Ëš5êVU¡ŠÔsçÔ–þñ¸a±”E_uUÙ£k¶®Ù³¯_\œºée‰³ÙT磌È=ºâŽâb8{VÝ|3ËëˆÝóçWìÜÁÄlVGMBCõ>K;}¨ IDATpûíeD_[n.Lœèy“Óô‰ŒT;+ÎN¢kG±¢ßÕ¥ñé`f³Úi‹ŒT·¨(u˜Úh›Ó²¥ÌäÙa d;uªâCëÆÐn[âãÕѨڵխN÷Gç~»vê(˜&“*Êý…l8çÞúÛ 2&´ÃÂ`×®2‘ìoDë“O´ô,¯r!!ðê«úçõwcÄÓ®æ+vÇf D˜ÆÆªÂ؈Í7Þð<ªÝ¶„†Â•zaGb¤xÖG è*dÀèÝ»LPyngΨ.n;*ªl8;ÐÇž=UÁ¨‡Ù K–¨º³¾Î:»>ž9£SiM&X±nºÉèt‡€h¿%„|HHˆ:Ú5åê#eaa°zµ¾7'/O R1r]EýZÃâžÃý+W³i±ÀÛo«7»ðpµÞÎ}­ç®Çœ7Ã@nr Æ:=Æn†  ¢å>2” êÝ»U‘bD¼ÅÆÂË/»_×@÷ÿû_uʃx@uñ›ž£ }dÜ«„¨-׊G×—ÓVå!ÄYô3X÷ZEDDý#Bèåñ\{í_Ø¥ý'D èJ£ü+ÊS…VEÄså¢Dh”1cF2fÌÈ`V¨šP9A„ÕuÔÀ5½Ÿ8Ôw9Vè¯? ÚA„’е-½{·cÅŠÁºå,;1Æó`úDê‚FÊcâÄ»8}Ú=@ ;;ÛË™”t—a›Çy`6Ôx f@k‡úÓËÚ ;gÂáFóbI¹ŠElììv½ÜíЩӵ†lÆÇÇÓ¾=—-ò"„ÀjÍ',Ì}ˆ _¿¿²™””Ĺs¿*{1°Z­,Y²€AƒjlèV¹¡>R@Wв“éÎÒçB º)+™Zµ"ÉÌŒ¢øÏUípì­àRêï ®þti¬Dèpd`6ßa¨\Åðô)?;À2¢¾¼ øï€ÞÒ¸Æ %$ä[Ý„°Ñ°¡1‘W³fMúö­…Ýþ^é±âb+{÷î eËöne{÷6~óWE¸žG±ü]OåÅÿ‚,Bü~~wW<ó¿:SYÝèQÆ­[·&>~"êoN¯¬Ÿè^ƒX,6Ìæ‡ÐËó,ÄzÔ @£Æ]€¾ <Ôp±iÜÓúÁ‡ü‚DÛ¶‰†2úÜ–ç~gÚ;|ðùìþi7ͯiÎ}wÜǘ‡ÿ>ß~{$O¿ö络׎šÀØ{¸ogâ3çù!11‘ììí¥ÏÓÓÓùlÑgìÍØË€›p×]w¼Šm5زÅÝ+îÜ9&L˜ÀäÉ•³˜J°ÈÊÊ¢]¯v-:Š=Y 1¿d¦~X}¾_ó=‰‰ëTË B}¤€®$†½‘iÓ¦¹ó\fÖ?½œSÆdðÇöí+ƒbÇ›•€j< ÑÇ ÊÖég3÷ƒ£GùsÉY­VV¯^MóæÍivƯ³²v`óŽÊñ"N…Ãq³¹¿Æ+ý½Ê¥A…S§®'Wù{|¸F”x‰~„Ÿ&c>I´hEíÚ÷œÕ­[×ðÒÂF‰ŽŽfùòrDˆú!&&†Úµ÷P\¬¿xLïÞúK*ëñÄOз¯Þ0±Js½ˆÖüç]ëQÖØÜœ»îº‹»î ¤R1~üq uL0¼¢ê=÷ÜÌŠÏi¼â~Ìb±ëĪÂÈ÷åáÈ‘#\wóud&g"®Ð~9÷ /zœWþû ?|ýC@+×^(¼À…” îâÙ•fs(‡Ü¢à:uŠënºŽã¡Çq\°æË5<>ñq^xøžúù Ù‹‹»$ÅsƒkPxK!$—·aãÐñC4¸¦‡¾?Dí ¤Y’âYE£ÙQ%å%-- €Y³f•ëýëׯçÅE$½ôÒôèÑ£\繘,^¼˜Õ«WóÁ²°ßb‡Æ./ZAùR¡¡£!Ãú x)ïÝ»wÓu`WΙÏáHr \P0Ÿ1s÷­wóáÔƒÿaJøå—_ظq#ݺu£M›6í<å!åê2ZdhÏù.Û{ûÜ_éõ’H$úX­V~úé'®¹æš É'^‘Hv¯lÐ èË„škj’õG–Æ‹Ú$·NæDŸà¯y.€:_ÖáÐöC„™+6Àf³×,޼[òÀs1+;˜¾21¦ç&MœT¡ó‹­[·2dÔÎäŸÁ®Ø 'œ›;ÝÌç}Ôó¤\BF‡ ¨ë£ÀIh´¥‡v*÷9*ª[þ H] ¤¥¥1wñ\â“ãù×?þŨûGUÈÞæÍ›Y¸D]ZoÈ !têÔ)ÕÔäï£ÿÎòµË)¤³ÃLbt"Kg-¥mÛ¶ÛJ¼"‘ì>Ù>gl˜¾01÷©¹ »Ýx÷•_®dà#± °¹Û lSh’Ý„½[÷\W¼öæk¼4å%lul8ê:00r2„y‘ñOŽ/·Ý;vðÉgŸ°é§Mt½¦+w½›«¯|ºÅä©“yâó'pôôrBùZaò=“ó`Ňå=9sæ äÚkÍK¬Jl6[§!éSXXÈŒ3XøUÉö–!<ðÀ„aõ$kIî³`Ì{ÌÍÍeòÛ“YºNâØs ?öx€#e³}ûv7nðp¼/¾ÿþ{¾Xù—5¾ŒAƒQ£F ý7ùaÏž=Lyw k¶®¡~R}†õƈ#‚RWO–.[ÊßÇý¨BìqvÌçÌ„ç…3ç?s8 °l}òQ¦îžŠ¸Î÷-^ùAᾦ÷1î¹qœÊ=Åé¼ÓœÊ+yôx~:ï4¹ÓsÁÈGÿQ–(jFÖ¤fDMñá±(¨¿]ní·qßú `ùØÂÁoR¯^½€®Ãáàí·ßæó¯>çdæIº]×Ç|¬\÷6€ûÆÜÇœ5s°õµËàž²C¡Æ/5سeIIIå²íÊ™3gH꜄}¨ÿVó|3§ÓO“HD³ R@ë#t%––ÆìgC0­5ÑÂÔ‚]›wlçûï¿çÆÛo$¯NŽËTqd:`"êdk¬¥]»vA«óéÓ§iÖ¾®º ý9ÏCÈ!Ü{ë½ÌxÛ`ž.à£>â¾ïCtsù¹A½NÛ6ˆù4†œýÆæ*Z­Vâ/'X>øÐ"ÊF…§»>Ík/U0ÿV ý†õãËŒ/qÜä×`M»úÝÞœ|3«¬ Èfaa!]níÂŽÓ;°·¶ƒ(óN3W']Múªô€ÄV½6õ8~ëq]/Q½Uõ8úóÑ€êêg^x†·?~[ "Z`Ê4Q;¢6éËÓiTÎÜa6›ñÏgÙúedgfsíU×òÜãÏÑÍhš æÍŸÇƒÏ=HR€ÃìÀRláÊÆW²aù†€F>Œ°pñB†?>œ¢ÖEˆêo_9¢¶3Œ'ÌÁå[+øö¿ßΪ­«°šK´=”[¯¿•s”ËÞÇŸ|ÌÈgGRtu8Gô@ØŽ0Þõ}†ß=Üïû娱ctèÓÓE§±ÇÙQò,ùÆ¥Žã_/ü«\6/YÌ=ßCQí" »‚ù°™¶õÛòÍÊoÊÕa6b Ób»Æ¦zûrA9 {(–u ו[liñÈSðîÊwqôs€kf‘B0-71²÷H¦ÿgºa{I-’È”é>Ý< ÷xÓ"àŒ‰b€™À}€¿Œ6ø¸ßpUÝ0+f"¨Y“=ÿÞƒ#M'÷ä¯ðäeOòÆDãóÖ7oÞLïa½É»2ÑX¨‹ZÁü­™›ÛÜÌ— ¾ ¨Îk×®¥÷c½±ßîCÔž†ú›êsä—#~í8„ƒ¬ü,2ó39wšÌ¼ÌÒ΋óØ/«~aßÉ}ÐÁ”- 3†ÌàþûË÷E 6Œ°°0) ý t%––ÆìofCªú\Ùªöúßç}Ã6víÚEÛ>m±Ýns‹QÉ…!üºæW.¿üò Ô9åê2:fx›•`žofí»k} ™üâ|Ž_8^º½8æEö4ÙãžDá+ ;nâW™£Ðí…nDZ"½¶ˆ·çæmà³ÝŸ!Úùù ; òãHòäx¼IOO§û¨îØïðÝó7/4³nê:n¸áÃv›\ׄÍ”yY–R¶>Æî’é?hO·(°°ïÌ>öÙÇÞì½ì=³—ýû½úéµLš¸ñ_7Ò(®b¹=Ö‹©GˆÉ¸g¶UçVìÛã‡ûÍ5 ,Ë,¬ž½:à©E_}õ·ºü¶ùê<¿Ö`þÞLûÚíIÿ*S )j€ÞÈÆ#ÕiD®NÛƒ¾6œŸ×þ´ÿPzz:Ýïí®ÞT=G²‹Àü¹™5ï¯ èº8j]Q‹³ÍϪžEç4ëæª71~w<™dt]¾þúkú<ÚG­§gÜp1˜˜Y3=°zúcóæÍt¿«;Å}‹ÁÕ!çÓ&Í‹šóë·¿dóýßçÁ7Ä>¨äZ»¶-{!akÙ{³²9lÄ0æšïÞéwrÂ…“ùkfP<ô»wï¦õßZc»Ç¦-N„|ÂÎå;KçÌgdsøüáÒ-ã\FÙþù N¾sÒ[»¶-NJ¼Å®˜‰‘‰ÔŽªMíèÚ$E%Q;ª6 þ¹€£mºÍ»õâ4ú¥£þ=Šìül² ²ÉÎÏ&+?«tÿláYBG ½É3³.†{ß¼—&ñMh’Є¦ MI‰KÑœB’““CR›$І©žâB`p‹úº²Yá¶äÛXð‘ñÎh|³xÎÝvνãã²JáwýƒÆk ã̼L² ²õ¯Ë€h￘²MaZ¿iŒ=Úðçp¥gÏž4lØP h?H] ¤¥¥1ûçÙn —e¶…Â}…†ot‰ÍK¦?øŠ9 ñ+ã9³·âkí®X±‚¯ Äq«Ÿ?rD/Œæ•O^qÊÎí|‘G£O€¾è'ܘ Â]Üøâsà|ŠüRæBÄß#ˆ‹ <$œˆ",eûá!áDX"t÷_¾ÿeN÷< ñ~Îušlj¾öøðö´·÷ù8=|_kÓzOöy’ëû_ÏÞ콪X>£>Ë9†Àã//‘Y1““¬)®!êÝâÕ¿Ê?WüÓ÷”‘|ˆþ<šóûÏþ½Ÿ>}ší`jÕ¼))Ûz[zäíŸûé\Òþ“†½ŸÎÅ9¨¹²&Y{ŒÏ õGLJ ¹w䂯4ò!fA 9Œg‡èÞ¿;£6úÒÞ Ýòº±aùÃ6£G“wgžï›>DÍ"÷PÅÃ5×PÏç㺘֚x©ïK†ƒÃJÑð"ŸañÊ…!qC ÏEÝ»w/-ûµÄ6ÌOÐp\¹÷JvnÚiȦ?®ìz%¿¶ùÕ½CáI&DoŒ¦Áè>˜¼b§À@î#ežØ!bnï/{¿T,׎ªMbd"&Åû¿š‘‘A³îÍ(N-ö)ô-³-ìݰ×﨓C88WxÎMT{ í¬¼,–<¿qŽD9l<$™õkÔwÕMâ›ðdÚ“ºæïùÃ@Èü~^ø3-[¶D ¸Pts…ç8[xV},8ë¶ÿêý¯bÿ»ŽÓâ0°࿘  $E%ogË{[wø¿.æfޝ;^îi#r ‡>2 GaK¶Ñá¹$_•L¨9´t s{j¥ðL!g-g}‹g€x8qž +&™‰Ía£ØQ¬>Ú‹ í;ß³cú-uzÁëÈ屯3ö“P:m*0Ñ¥Yò‹ó½¶‚âìÂ¥‘²ãí1Ó E8 ¬.â³øÏq°ÿÔ~L/™ˆ°DhzÏÇ#B"X1yŽAþ¯µãz¯¿û:¨~bd"qäÒºqåÄ׌çŠúWq.ƒ“¹'Ý„¸]Ø9’s„#9GHG;[­ÈZ4ŠkÄöw¶ûf„¼yL™2…1cŒÍ¹îÔ·Ö¿i‹gÑVð¿…ÿcÇŽ†ç‰?ôχ°ós“‹ƒ3ÉgxïÃ÷¸mèm;ŠÝþ®ûþþ?ÅöbŽ:F~|¾oñ êu©‘Çä•“ixYC,fKéÞb²¸µ³Q,øö÷oá?6›Ã·s¿e÷©Ý(! V»Õm+²¹=?|à0ñ~=gDBAB/.z‘ä”dBL!˜3!¦ÒÍlòxîãõùÎ'¿…ÿëâèáàµé¯qçÈ;qGéfwØËöEÙþÌI3)jë[<ˆ«Ëf-cþ¯óKíØ…ÝçþÂÉ ±]­“q§ü¾ñwR—¦bµ[)¶»]Ûb‡Çs×]Ë82p“ÿÓ‘¹çsù=ëwí¯ÉIÃØ†4ŒmH£ØF¬]¿–‡¸l{r:µéÄÝ­ïÖ9yÉGnÔˆñ#ÇóêœWÕ¹¾®£¡9`^næ…‡^вeRL$D$á~nôøhòìyþ;‡ ùÊdÎ[λu*ÂQê‘_h}YùÓø϶«lt|¤#!·„p¾ð¼û}Ç“"ô²2ªÄâ¶>K\xµ"k‘•DRTµ¢\öKŽ;%F&bVÊ.BÝéu9™yR;8 j9jeεÄ7R@W¢–àû¾÷9w×­¨kèàhäà¥w_ªøbP§1f#(‚ÐÈPêF×%9&Ùk«W£É1Éäß–O‡!°µðsS:-´dcÚFŸE¬vk© ~åÂ+L;8MÍAê‡k㺣 ¸€[…¶BÃûÅ—µ» ®d ¥õôKþç*Sòº‹>MŒL¤YB3š&4¥YÍ’Ç’çqáqdÜ‘A³žÍ(îÇK´ÆÂöõÛKotV»•#9GÈ8—AÆùŒÒGçÐð‘œ#XíV73™ù™dæ•̱ÔY¼G4Œ},Ïç=¯Ûa 5‡r0û ï›C ¶66D«á­| YWÁ›K®n‡K4Œž<šÑGÊ7ìYÊfÀÀ´oG#cß F‧l~²l l´x¬´0`s3` [•£¡ƒÿ›ñÆêéÏ®:eLoÎçòw.7öŸû}ñ XC­ ýl¨±»Þ¨#f:ØCíÌù~ޱ3˜QÀd21°ÅÀR‘Ü0¶!âÔÇZ‘î˜Ü¹Ôº²…õ µ÷ž!œ¥»ôW+tå¥g_¢ïþô¾«7ÄìvÌfb”V¶:¨ÁÃÏ=ôÏ/{^9Ñ"¢~âÈÁ#˜L&Nåbÿ™ý8{€ýg÷³ÿÌ~öŸUŸŸÌ= VüwjÔ…œs 9-ÌáfìV¤Ì„2uÜTjEÕÂb2âýÑfÛêm4íÔë «÷ý/,‹-lÞ´¹Üö%ƺ²ðÔG¡UïVXêX4=C® Â#Sx­x‰1³bÆb¶b Áb²¸í‡˜B°˜-^ûûìãTæ)ÿo ´(”cÏ£fdÍÒ¨iŸ$°Ø»qnÙœB× Âóþu8[vmñÎÇ‹O½ÈÌ3±^iõyRv)Üxͼ~Óëþëç»°—Šék—]Ëáó‡ý_—ó›À#Ý¡ ¸ Ì{n+(õ¢»>ßãØ£Îyó˜7ìè# ÂÁ7#¿¡YÍfĆùÿb5jÄ‚7?yû;¸NÑÌó23Oß÷´›—(ÔªuÆ7Ñ´)œ¸p¢t~¥S`<}¯Ì_yO#ñ$ V¬ráݱt ®“áê°ª ‡¶âÐÞCú6­ó9çDV”8 Ó@¹ èvJ9‹÷oÏÙ?sýßÇ–”5BR@W®ØáYáìú—~& ûl6u;ÖÅÖÙÿ¢ù™Œô âãâK…²®°Õ`k«­t¾¯3ö¦~zÕÙP;F+g”9ïΡ0µ%/ÁÖÑ¿WƒrX!fo –l¨1OJJâù‘Ï3á³ 8ú;¼͇!懾ÜXDµ+fÅL”%Š(K3ߘɭãnÅ~›Ÿ µfæMšÇÍÝo6dÿš×°ýÔv÷éé¸úœ‚æšs]òu†ëýÚ‹¯Ñ÷ƾ 1€\%GŒÓÑ"še.£K—.†m:Ï9ªÐ¡¾{øwÄËê©Îãpý5×Ó©C'ÍŽbé»Ú‘ÜR¸Å]”[m”úpjÔ¬ÁåÉ—ûí :Ÿ:ïS÷§'¡Ëµ]Úg¨ßN¦§mÏ}{±Ö[cóê9»c>bæ§¹?¡„(nCüZÃÿGÚáÙŸÅqµ‹Z<\òè2'ÚtÈÄÄÿ›HƒÆ ¼„§Ç_Ø„¡z†d„phá!Ì¡flvaÇæ°•nv‡Çs¯tô#–_®;Êbµ0ëÎY˜Séf6™Ëö•²ýùyóùèI.¿—mx(‡ÛÂÙ>f{©-§ ­ýOê~£Ÿ=Ѝï§ch…hÍÁÇ*¾€UÊÓ)Œœ:G_?±›L¼øø‹Ù½}ÐíôèÚƒzppíA 2 ˆ¨AãÚYÿÓú ¯V\4ñìä—¿ðüËÏóÖ‡oa‹*Éð“­føùîÛï Ÿ?ÊEë¤ÖDŠH._(ëœi´-Ê>…‰£&rÏÍþæK•ñͲohÒ¥ ÅwkvÔ•_nhvCP½óÍš5ãäï'ÉÈÈ`ñâÅ <¸Ü<‘+ê#ƒ+Ï BÓ&f>:“{ÿ~¯a-:¶`w«Ý¾‡p3 Åîü¶ù·ŠW¸¡ï lŠÚ­4^,Ë'ú"¾øí·ßxwæ»|½åk’“’¹ëow1räÈr×uÚ{Ó÷ê8Šãh .¤bÚo¢idS¾[ý]ÐòËÜ4è&Öe¯CôîÞ-(n¨qVl0l/##ƒf76£ø?Ó-æZØ»¶|×ÚÉÅôuïß¡¥õ3d¤gP·®ÎäÃâ›Åsîös~½Æ¦ÿ™ølÌgÜ~ûí†l¶êÜŠßZý~.ƒùc3»¾Øexu@t¾¥3›6Ã> ì™ØòµÿQWbšÄ{{®ï9˽ š û¬ö§Ò­o76ÕØ„h®}+P~Wè^ÐuËÖ¶é‹cÇŽ‘Ò9[ªÍ÷´…½Ð9»3é«ü/ƒî$??Ÿš-jRxO¡Ï©ÊV…û›ÝPêÍ„f œíç;öÄô…‰ÙcgsÏ]ÆD–­:·â÷¤ßÕ´¡(?+\~ìrvo5¶ºåŸ•`ä˜_¼t1wüëßÁÄg iM§~324ãbwÉbî~ìn ¯/T§\F§Á¼ÍL›˜6ü°þ‡€³U%2ˆPŸKçÛ¼ÔÀQù8„‘]F$ž¶®ÞJÄêÐÊf¶"ÿɶ¯·¥ªVl ÍÉ6˜–› µþ…Ànÿ8œOþýI¹]Ë–-ùïþË[þ`ý²õÏzˆœ½9,z|#¢FðJ‡Wø}ñïìÞº;¨âàKþÇ]Ÿ tV(¦u&øLëL„Î å‰.O$žAnñ̈g0jV‡Œ]Éó§fžñL…½ ÓK´né:¢·EÃqí ¬P{×XÃâ`Þ”y˜¿ô3±: ꜫcX<lZ±‰ÐU¡n<®(é ·u½-(â`ãIø>ågïž‘ò³B¶6~á{¾¿«f¯Â¼Ø¬zÍ<±‚y±™U³ËC¾vÙZ·'¢ü¨QÏw$ò¿%ÿ Ȧ/êիǸáã0­2yO»8 ‘#ë‘‘‘Ìú÷,ÌŸkü‡P½u2ê$ž¾Yò á‹ÂáÇ VU<÷hØ#hâ`ç¦ô2õ"dnÊO å'…¹!ôRzñÛ–à8G.e*ìÅj¸ˆh)ó­ñ IDAT!B®!W„ˆˆÆbø¨áå¶ç^~A„¦„ åVEp?‚¡Óu&QÿÊúâèѣ岹eËQã²Â|•Y(}aêj–Ë,âowþ­Üõ¼pá‚hÓµˆ¸€{úßÈ#‚²¨G0—÷ǹsçøôÓOY¶fÍ5cè¡ÏÍ×"++‹éÓ§Ó©S'zö섚ªäææ^ôeÍ/6»wïÚËŸ›ÍÆ¡C‡hÚ´iUW¥Z!W"ÔG èJ --dzn]Åçþ™xüñÇ™0aBЧY\ꤥ¥ÉF˃sçÎ1aÂ&Ož\ÕU©V,]ª¦ 8Ðsy¹¿6²mÑF¶-ÞȶE¹¡>R@Wr2¾D"‘H$’K©[ô‘³Ú%‰D"‘H$’Z"‘H$‰D" ) +‰¢¢¢ª®Bµcß¾}Ølþpø+²{÷_;׫6›}ûöUu5ªYYYdeeUu5ª²mÑF¶-ÞȶE©Yô‘º’8u*°¤ì¦L™Bn®FâÖ¿8'N¬ê*T;rss™2eJUW£Ú‘žžNzº±GþJȶEÙ¶x#Ûm¤fÑGVr2¾D"‘H$’K©[ô‘h‰D"‘H$‰$¤€–H$‰D"‘H@ èJBNÈ÷Fúh#}¼‘>ÚÈ BmdÛ¢l[¼‘m‹6R³è#t%!'ä{#}´‘>ÞÈ@md¡6²mÑF¶-ÞȶE©Yô‘A„•€œŒ/‘H$‰äRAê}¤Z"‘H$‰D" €ª®À¥‚ÕjeêÔ©¤§§Ï€èׯ_UWK"‘H$‰DRÉH´Arss9qâO=õÆ cüøñ¬]»Öðûå„|od 62ÐÇè£ "ÔF¶-ÚȶÅÙ¶h#5‹>R@$!!7Þxƒë¯¿žo¼‘ÔÔTæÍ›gøýrB¾72ÐGèã ôÑFj#ÛmdÛâl[´‘šEDX„ôìÙ“#F0|øpÝòr2¾D"‘H$’K©[ôùKy ­V+V«Õo™¼¼<þøã‡Ï2Ï>û,uêÔ1$ž%‰D"‘H$.þôúçŸfÈ!¤¤¤ÆÍ7߬YÎjµ2|øpbcciÞ¼95kÖdÁ‚^å&L˜À®]»˜3gÎÅ®ºD"‘H$‰¤ò§Ð'OžÄáp0bÄZ·ní³Ü¸qãøâ‹/X¿~=999<òÈ# :”mÛ¶•–™0aÛ¶mcÑ¢EX,–€ê!'ä{#}´‘>ÞÈ@md¡6²mÑF¶-ÞȶE©YôùÓ èÞ½{³xñb^xá’““5Ë\¸pÙ³g3zôhºvíJLL /¿ü2µk×fêÔ©€Ú ¿ôÒKìÞ½›6mÚмysFŽi¸rB¾72ÐGèã ôÑFj#ÛmdÛâl[´‘šEŸ?½€6ÂÖ­[ÉÍÍåÊ+¯t;Þ¦M›ÒTuMš4¡  €ß~û;v°cǦM›føYYYÌ™3‡¥K—ºmžÞ£¿Ò±É“'³aÆjQ—êtlÖ¬YÕ¦.ÕåX\\“'O®u©NÇÈÀ«E]ªÓ±É“'W-êRŽ9ªC]ªË1gÛRêR•ÇœšdüøñrŠªA¤€Ž?@Ë–-ÝŽ·nÝšS§Náp8P…ððp·-Ði‰D"‘H$’KŸ¿T»[n¹…ÂÂB6lØàv|Ò¤IŒ7ŽììlJO:•Gy„3gÎ_îóÊt0‰D"‘H.¤nÑGz ZµjpðàA·ãû÷ïÇb±WásÈ ùÞÈ@md 72ÐGD¨l[´‘m‹7²mÑFj}¤€†ÒàÂ_ýÕíøÎ;©S§Š¢TørB¾72ÐGèã ôÑFj#ÛmdÛâl[´‘šE9…ÈÏÏ'99™‘#Gòæ›oêjƒuêÔaÀ€̘1£Bç•C!‰D"‘H*ÊéÓ§9yò¤¡²uêÔ!))©\瑺EŸª®ÀÅ&??Ÿ¯¿þPxV«•¥K—pÓM7Mdd$<ðÓ¦Mã†n mÛ¶¼ù曜={–‡z¨*«/‘H$‰DÀ_|ÁË÷ßÏ­&ÿV9üsæLFŒQI5ûëñ§÷@9r„víÚi¾–žžNÓ¦M°ÛíŒ;–Ù³g“““CJJ ï¾û.·ÜrK…ë––Fzz:]ºt)M;%‘H$‰D(íjÔ`í… ÔðñzpcL ßçälÛ™ÎΩY¤Ú7z(‡ƒœœœ :IKK£¨¨ˆyóæÍæŸ}ûö‘’’BHÈŸ~ $ vïÞMóæÍ«ºÕ ›ÍÆ¡C‡J;¼gabbbפz!ÛmdÛâͥض|ðÞ{|è!þåph¾þ¼ÉDãiÓ¸oÔ¨rŸcذa„……IíDèÉd ªxv"'ä{#}´‘>ÞÈ@md¡6²mÑF¶-ÞT´m9rä©poß¾ºÛ[¯¾”:ß7j«£¢Ðò/ç«£¢*$žAj#Ht% 'ãK$‰DRµœ;wŽÍ›7*[«V-ŸÓ?=iÊŒâbü-­6¨ýðbI¢‚€),„ƒáÀ8p€–-ãàºuüËCÂÃû R·AŽoI$‰D"Ñåã?6”8,,ŒáÇWè\GŽáii˜Š‹uË^Õ£ϼô’!»÷ö뇞´üA¢o¾™Ï¿úÊÍ¡=Æ®·ÞâAþÈbà±ðp¶úÏBÀñãn"¹t;xNœPË”pÐx JçB;½ÏßWPà¸áC6ãââèqà tݰ^~ÊÝ`±0÷ÓO ×õÅ7ߤý”)Ü_X¨é…~_QèóÈ#›ë-Œû‡©^f£DG3:6–7Ž/õB¿a21º¼nIÀH]I|ñé§t,IŸjtô¿ÿÍý÷߯ûÞÕ«WóBjª¡´5ÿ7{v…®{ú÷gïºu„ê, “©(¬úùgC7-›…a&“Û/›Ï<ò?­Z¥[ÎƇ«Vºqú£Oûö?®[."9™/·m3d³u:Ô¼pÁíä;Dº|×ÈŽ‰a§ÁNTÓ¸8èÞ8GD°ïÜ9C6}qìØ1ÞxåÁ,®´¼új=Z·ÜÁƒéß®Ñ.+É  Èáp4hÕ«,X k3##ƒ>W_MœN=íBТW/>Z´Hצ?fΜÉ{O?YG˜Ú…Q¯¿n¨xçw˜ñôÓÔr¹Å%ö]¿ëL‡ƒ^1cÆèڼꪫxë§Ÿø@çºÜg2qÕUWéÚÓãÖÛo§ÆÂ…¼åç|g€¿ÅÇþ¿jÙܤPvƒ ÔfÚ˜1,^½š~êyœ˜Xañ 0`ÈZ̘AšŸßË,Eá÷!C Û¼cøpO™Âý.6w®!„3…ƒÜ/&ÎËŠ¾}yÙÏuyØdbâܹ†múã®'Ÿd뫯ò¨ëb~ˆˆ`ê¿þeØæ´E‹X§½J<Û6àà !\$wîLBB‚1ƒv;ääÐwøpÞŸ9“‡<êZŒúÝm›=þýoÃõÄl† à²ËÔ­qc÷ýZµT/t<< ›BÑ)1QœòQÏS :%&úñýÒ>"Bù8ß;Š"^yî¹€mÞÙ½»øÚGÛÒ5$DdϘ!Ä»ï ñÚkB<óŒ>(İaBôé#DçÎB´j%DýúBÄĸէ«G§‚˜àëw” Äu× qÇB<ý´ï½'Äš5Bìß/Dq±¡Ï2sútñœÉ$ž3™ÄÌéÓ¾žìܹSÌ›7O´jÕJ¤¦¦VØÞŸ) +ÔÔTqS‰Ø âkwßxc@6æ|ø¡xΨ}Îds>ü0huîP³¦ÏFR€xÐdË—/¯r›×GG‹$°¨“g}TÌðñßœ¡(âÙGõoÀn¢¨Hˆ¼‚ º[,,<~< Î………t‹å;«Õkί:„†²ñüyÂÃÃÝ_,*‚‚uŽeaaÙ~ÉcŸ{ïeâáôñ°ù ðL:|ùÌ3àp¨›eûzÏ}¼Öiút¾(*òúnÇ* ]oº‰Á—]føš8é4k–o›=z08%Å[ò8|u]Õ×:®XÁâ¢"êzØ|@Qܾ=·8¿;²Êßë.¯9„ ã–-lv8ÜþGv “¢°¥];5ï¬ëûœhí èøóÏ|ëp¸Í×´…- b²Ù ¸l6÷­¸ØÝ® %à6ú40øVóãz`¸Í…þ/px¶œ6‡¢â¹Î…¾X î¿!‹bcÕ-.®lßÇóöÆñ­ÕŠ˜ª(d=ñDù3oäƒ÷Þ¨pæ Wd}¤€®\ô×Àœ¤$ævì¨6PÅÅ`µÚÿ¸ €?¬V×x­ýĉ¬·Z‰ò°ùŠB­>}x°s粃¾n:_;–“CÚäɬ±Û½>[/“‰Y£FQ/"B½1Ûí꦳_hµÒmÅ ¾s8ÜD‘:˜LlìÖpE1|ÓwÞŒÛÿø#ß áuãì`2±­woL‹zspþn<÷³Xxzölš¯]˽×Ät²XØ2{6&‡Ãû&m³©×BãØg¿ü¶•+ùÇwqèÊwii†D„뱇óõºuü×Ãf&0 ,ŒÍwß ÎùÄZ¾6dáB>q‚×e=0µNà.0ý Ó’mô¶mô8~Ü«Ã5X_£ÓÝÅra¡O!äd?0Xîq¼?0 hâ÷ݳUMr9– ôŒ%«›_•Øárì0ØRN›þx¸UØ9ùؼ^N›Ï€\ŽÍ2€WÊió+`0ÕåØÃ¨×ºâk÷zó*ªÐ Ìo[ÆS¨7c×𴙨7ã7ÊiÓÔßæfpóž}„úyÊ{óï|‰ú{?„è3hêA*€e@-—c7£ €`’ 6xï,MO¸…@7Ôß\vɱš¨×j#îã}zô^œ¡‚?ãQ¯¿aœ g×ußãydzgùBj–¼u,Ð56–ÁžÞóèxú4_ÁYÔ¶åI kBƒ##Ëêæº9ëä絎»w³Øf+õB? nÚ”[bc½;PZ›¿×=^s×­+õBÛN&[n¾“¯Î›¯ý’G‡t\¹’oö¡þ‡:›Íl:TíD»vŽ]7cÇŽeIn.I”xŸcbøvÖ,o'G¶ëÛ´aSa!¡À… ÿøϾöšz¾r2´GÒ6l )0ÂbaéɓƃýÐ>"‚¡EE\¨ïóÅB®D¨Е@ZZ‡gÏæiàãÈH榤¨ Qhh™7Ok_ãØÇ¿ÿÎ+W–¦­y^Q¸¢†_ye™gÓÕ«gäÑÏkþ™e6[©Çø!àÖzõè]îëÑqß>–Ùí¼ L@£·Ö®M¿ðð2­«×Íõ¹ÖkBÐUl:½Ð/¡/>XîZúæ†ê±pÒ ˜J=§ßé-7¸_¨(tÛ¹“ï„à^TáÙAQØØ¡áfs@7|×›qûU«ø®dø¶Ôû|à eöZ›¯×tš Oï™U¬n!À%O×%$„Ï„`[a!/ þV^ú˜L|W»vYy‚BãØŠ‚Vgfâ\{,`2±¹~}ï¡uÏÇr¼6$'‡‡m¶R/ôz`ªÅÂÂøxß‚ÁSxzl£¢ÇÙ³8cä‹€õÉÉLïÜYõ¶9=n®:Çögeñx¿~¬(ñÈõ aòwßÑä²Ë ‹â@X¼p!ßÜy'“²¿ÅűÅ`:5=›8Œî‚ͯ¾úŠÅ}û2Ãá(ͼ±%3³B6ýñôèÑ\þÞ{ÜG‰÷yÔ¨€§?yòܘ14œ2…-BÐQQ8üÈ#¼òÎ;²ùÕW_±¢o_¦:-O ©/nmÙRl±àfÞðE‡¸8‘â±rdɨT›%9‚™yÃv»]´ … Ú)Ø×n·‹vaa"D» wJL¿ü̾h!Þ,gæ _ÜÙ½»¸ÌdÙÙÙA³)„K–, ª½Ê&55Uê t%àÌÂhæ _83r;ó†/œÙ3Ê“%£2m¶‹Š@¼Ä̾pÍÈhæ _8E‘-ˆ‚È™‘£€àfÞðÅS£F‰÷Q3oëæïÌÈQžÌ¾X¾t©xÈd§@tLHŠM83r”'ó†/œ®`u¶„P3rô }ƒœyË,w+Šè¤ïõbÙ\µj•h2=ó†/ž5Jt§|™7|ñ죊N Ÿy#V­Z%âá¢w*œ¼òÜs¢ahhPmfgg‹G|0¨6ÿ H­Е@jjª¨m2Åûì¤CÝûìdùòåbˆÉOñÅ´9mÒ$ñEQ½Ï•@¿k®oÉûìdÔwˆ!ADB¨^èn”¼ÏzØívq]X˜x7È7ÿëãâDš¢Åûì¤cB‚¸CQ.ª÷Ù‰Ó  ï³g‡+X-'·¶lY)Þg'-‚æ)¾˜6[×­[iBÑn·‹+’“ƒÚáµÛí¢eÆAïD?7~|Píé±råÊJ=ß_) õ‘ºHMMÑ"555hÃ:s>ü°R¼ÏNZÖª4O±“¦ AæJ‰ˆ¸èÞg'G5Ìæ xŸˆÚ11ADv»]\ä›±?ž5J4 êùæÎ™#R‚Üa\¾t©hU«VPmúã¶Îƒæ}v’:`€H0 ¨6÷íÛW)Þg'‡ºÍo¾ùF\ˆÂ(gÏž ª==ŠŠŠ‚ns×®]A·y©S\\,öîÝ[ÕÕ¨6,Y²D¤¦¦ŠÆK­ƒ\Ê»’h{ÝuAŒ?ü^Ï„a—u;wRÛ5p+ô¸í6ºwïT›ŸoÜH»ví‚jÓõêÕã[¶P¯^½ Ù ç¦þý½óøV“ÉÄ΃1äU^^›6–;õ|}ûõãëÖÍ@¿hßA+£÷ÅaúÒ¥A·Ù÷n³I“`'­óOƒ ‚nsÑ¢E´nÝš¸¸¸ Ù ¦-#„††ê 7ß|S…y››Ë”)S˜ÞÈ@md 72ÐGD¨l[´‘m‹7²mÑFj}¤€®$äª>ÞÈ@md 72ÐGD¨l[´‘m‹7²mÑFj}da% 'ãK$‰D"¹TºEé–H$‰D"‘H@ h‰D"‘H$‰$¤€®$ä„|od 62ÐÇè£ "ÔF¶-ÚȶÅÙ¶h#5‹>R@W6l --¥K—VuUª 2ÐGèã ôÑFj#ÛmdÛâl[ÜYºt)iiilذ¡ª«Rí‘A„•€œŒ/‘H$‰äRAê}¤Z"‘H$‰D" ) %‰D"‘H$’º’ò½‘>ÚÈ@od 62ˆPÙ¶h#ÛodÛ¢Ô,úH]IÈU}¼‘>ÚÈ@od 62ˆPÙ¶h#ÛodÛ¢Ô,úÈ ÂJ@NÆ—H$‰Dr© u‹>Ò-‘H$‰D"‘€Ð‰D"‘H$IH]IÈ ùÞÈ@md 72ÐGD¨l[´‘m‹7²mÑFj}¤€®$ä„|od 62ÐÇè£ "•†ÿÉ IDATÔF¶-ÚȶÅÙ¶h#5‹>2ˆ°“ñ%‰D"‘\*HÝ¢ô@K$‰D"‘H$ ´D"‘H$‰DR@Wß|ó iii,]º´ª«Rm>ÚÈ@od 62ˆPÙ¶h#ÛodÛâÎÒ¥KIKKã›o¾©êªT{¤€®$RRR˜5küÿöî>*ª:ÿø›ç ypAD@PD…M­I]]MK-mÓÕMk[ÏÖÒ¶©«Öv¶-mm3]³tKÍÚÔ#¢¦æ‰Š(J%ò$¨ €ÈÃóýýÑáþïÆQ¸˜÷ëOÌw¾sç3·¹Ÿùpù~îèJ«ÁF96ú¨±ÑGŽM„rÌ-rÌ-jÌ-¦Æ 6ÀÏÏOïPZ=6j€‹ñ‰ˆˆ¨­`ÝbÏ@Y€4‘X@k„ßê£ÆF96ú¨±ÑGŽM„rÌ-rÌ-jÌ-r¬YÌc­~«}äØè£ÆF96Ê1·È1·¨1·È±f1M„àb|"""j+X·˜Ç3ÐDDDDD`MDDDDdÐá‚|56úȱÑG>rl"”cn‘cnQcn‘cÍb hpA¾}äØè£ÆF96Ê1·È1·¨1·È±f1M„àb|"""j+X·˜Ç3ÐDDDDD`MDDDDdÐá‚|56úȱÑG>rl"”cn‘cnQcn‘cÍb hpA¾}äØè£ÆF96Ê1·È1·¨1·È±f1M„˜5kêëë±víZØÛÛÃÞÞ^ˆˆLÔÕÕ¡®®Ï=÷ìììØDØžÖHJJ -Z„ÄÄD½C!"""RILLÄ¢E‹’’¢w(­Ï@k€—ƒ!""¢¶‚u‹y<­.ÈWc£}ÔØè#Ç&B9æ9æ5æ9Ö,æ±€Ö䫱ÑGŽ>jlô‘c¡s‹s‹s‹kó¸„CüSµ¬[Ìãh""""" °€&""""² hpA¾}äØè£ÆF96Ê1·È1·¨1·È±f1´F¸ _>rlôQc£›å˜[ä˜[Ô˜[äX³˜Ç&B p1>µ¬[Ìãh""""" °€&""""² hpA¾}äØè£ÆF96Ê1·È1·¨1·È±f1´F¸ _>rlôQc£›å˜[ä˜[Ô˜[äX³˜Ç&B p1>µ¬[Ìãh""""" °€&""""² hpA¾}äØè£ÆF96Ê1·È1·¨1·È±f1´F¸ _>rlôQc£›å˜[ä˜[Ô˜[äX³˜Ç&B p1>µ¬[Ìãh""""" °€&""""² hpA¾}äØè£ÆF96Ê1·È1·¨1·È±f1´F¸ _>rlôQc£›å˜[ä˜[Ô˜[äX³˜Ç&B p1>µ¬[Ìãh9r³fÍÂW_}¥w(DDDD*_}õf͚ſlÝžÖ“#""¢¶‚u‹y<­.ÈWc£}ÔØè#Ç&B9æ9æ5æ9Ö,æ±€Ö䫱ÑGŽ>jlô‘c¡s‹s‹s‹kó¸„CüSµ¬[Ìãh""""" °€&""""² hpA¾}äØè£ÆF96Ê1·È1·¨1·È±f1´F¸ _>rlôQc£›å˜[ä˜[Ô˜[äX³˜Ç&B p1>µ¬[Ìãh""""" °€&""""² hpA¾}äØè£ÆF96Ê1·È1·¨1·È±f1´F¸ _>rlôQc£›å˜[ä˜[Ô˜[äX³˜Ç&B p1>µ¬[Ìãh""""" °€&""""² hpA¾}äØè£ÆF96Ê1·È1·¨1·È±f1´F¸ _>rlôQc£›å˜[ä˜[Ô˜[äX³˜Ç&B p1>µ¬[Ìãh""""" °€&""""² hpA¾}äØè£ÆF96Ê1·È1·¨1·È±f1´F¸ _>rlôQc£›å˜[ä˜[Ô˜[äX³˜Ç&B p1>µ¬[Ìãh""""" °€&""""² hpA¾}äØè£ÆF96Ê1·È1·¨1·È±f1´F¸ _>rlôQc£›å˜[ä˜[Ô˜[äX³˜Ç&B p1>µ¬[Ìãh""""" °€&""""² hpA¾}äØè£ÆF96Ê1·È1·¨1·È±f1´F¸ _>rlôQc£›å˜[ä˜[Ô˜[äX³˜Ç&B p1>µ¬[Ìãh lÙ²3fÌÀ°aÃpýúu½Ã!""""°€¶@MM &Mš„ôôt ½Ã!""""°€¶ÀÓO?øøxØÛÛ[üX.ÈWc£}ÔØè#Ç&B9æ9æ5æ9Ö,æ±€Ö䫱ÑGŽ>jlô‘c¡s‹s‹s‹k󬦀6 ÈÊÊBnnn£sŒF#222˜˜Ø¬ož¦ž“ˆˆˆ¨µaíÒ´v_@;v ‘‘‘pqqAPPf̘!wýúu <˜3gºv튿ýíoGKDDDD­]»/ F#†Š?þÑÑÑΛ7oŠŠŠ““ƒüü||úé§HHHÀîÝ»5Œöî?¾Ù׳YºÍºº:ìܹ³Y·y¿Q]]­ë6üñGddd4ë6ï×W_}¥û6÷íÛgöÏê-gcª««‘˜˜¨û6ͽ斈³)Ì-rÌ-rÌ-jÌ-Ö¡Ýб±±xûí·1uêT¸ººJç\½z_~ù%žyæx{{¦OŸŽž={âßÿþ·2ïå—_†ŸŸlllÑ£Gßuµµµ÷÷BîÐ>äJKK›½Ñ§=|È•••YßÝhëruuu(--µèùÌirååå(//·èùÌan‘cn‘cnQk¹¥¹k–öȪ¾HeäÈ‘¨®®ÆÁƒMÆ1jÔ(lÙ²“'OVÆŸxâ ?~EEE÷õ¼>ú(’““ “ûºté'''åvnnî]¥¦¦ÂÕÕ½zõ²ø±ÕÖÖÂÁÁ=zô¸«ÇÖ××#%%ݺukt^ÃZò=z¨¶·cÇÁ××÷žc¾slÿþýˆŠŠ‚‹‹K³í—‚‚ 0ŽŽŽwõØ‚‚ ØÙÙ5:ïÌ™3èß¿?UÛÛ¾};"##ï+æ;ÇöìÙƒ¡C‡6ÛörssqéÒ%<òÈ#wýØS§N!$$×®]ktÞ‰'«zlEE8€ðððfÛ/ƒ‡Flll³í—‹/âÊ•+4hÐ]?öÈ‘#ˆmtž››233¥zljj*ÀÓÓ³ÙöKnn.Š‹‹ÚlûÅÒ|eoo”” 4èžòÕŽ;”ëõ7×~III§§'ºuë¦[¾*..†Á`@Ïž=ï)_mß¾'N¼ïcÿö±#GŽ Gºæ«ŒŒ „„„ C‡ç«ÚÚZ8pãÆk¶ýb0––†®]»êš¯˧ nÏW ë‹‹‹áêêŠÓ§O#** ß|ó HŽ4€õë×cöìÙ8wîBBB”ñ„„¬X±555÷téº}ô6oÞ¬oHüDDDDz‘5 N›6 sæÌÑ!š¶áÞ«Âv¤áO ¿øÅ/LÆ»ví £ÑˆŠŠ têÔéž·?gξ ‰ˆˆˆÚ‰v¿úntíÚ••e2ž™™ ''§û*ž‰ˆˆˆ¨}a  {÷î€sçΙŒ§§§+÷, ÑÑÑðòò©S§”±êêj¤§§câĉ:FFDDDD­M»/ ËÊʰråJ¬\¹ÙÙÙÈÍÍUn_¿~`ooÅ‹cíÚµøûßÿŽ}ûöaòäɰ±±Á¼yóZ,¶ÜÜ\ÄÆÆÂÇÇñññÍ~)¶jĈðôôDpp°Þ¡´›7oFß¾}áëë‹1cÆ`ÿþýz‡Ô*¬\¹ðóóC||æÏŸßÂý¿#GŽ`ÕªUxàðì³Ï"!!A³çnÍhÝ¿ÕJ :Ôäç«W¯¢°°Ðê×è7|ñôìÙ·nÝÒ1šÖ£¦¦/¾ø"¶mÛfriNkg0ðé§ŸbÀ€èÓ§Þá´ ëÖ­CTT"##‘““½Cju>úè#üþ÷¿×;ŒV¡²²RÉ»^^^¨ªªÒ9"ý´ûºµ*((@mm­r…ÐÐPœ8qF£¶¶í~e ݇>ø111V_<7øÏþƒuëÖ!??»wïÖ;œVaÉ’%˜5k–É/ÖÎÖÖ=ö.\¸€µk×*_Lq?×øoΟ?ü“'OFÇŽáàà€;w*_Äbí~øádff">>^ïPZ…Ï>û &L€ÊË˱mÛ6½CÒugŽûpéÒ%dee¡S§NÊ7„ÝéÖ­[8|ø0rrr…ˆˆå>;;;Ô××+· g›½¥âäÉ“(((@dd$"##¥óNœ8}ûö¡¦¦C† Á°aÃ4ŽT[•••8sæ ÊÊÊwwwé¼óçÏãèÑ£ðôôÄ#<777Õœ]»vaÍš5íb tII Ξ=‹ššŒ9R:Çh4âøñãHKKCŸ>}0xð`ÕûøñãñðÃcãÆøÝï~‡h~‹(//Gjj*²²²Ðµk×F?´srr°cÇäçç#""'NT¾å4-- ÇDzeË´ ½E5GnqttÄÖ­[•Û#FŒÀŽ;0a„¿¥4GnéÔ©:tè€'NƇ={ö`ܸq𼆿f0žž®ä–¹sçJçUTT`ûöíÈÈÈ@ïÞ½1~üxtîÜY5oýúõ˜>}z»ø…â~ëxýõ×1aÂŒ3~ø!V­Z…·ß~[‹ð[½×´5IIIÂÓÓSÄ!C¤órssEpp°pss vvvbÁ‚Âh4*süýýEqq±Bˆ#GŽˆ#FhñZÄ‹/¾(@¼þúëÒyëׯööö"..NŒ?^888ˆ7ÞxC5/33³]¬3fŒ°³³SÞ/Î{ë­·„­­­ ]ºt^^^âÌ™3&svïÞ-BBBD^^ž‘·œ’’áçç§ì“ÆÒPuuµ˜8q¢pttÎÎÎ"&&FܸqC:¿®®N¸¸¸ˆ+W®´dø-æË/¿TŽ[[ÛFsË©S§„§§§Ó§OîîîbÔ¨Q¢ªªJ!ÄgŸ}&‚‚‚”¶¶¶" @dffjøjšOsç–óçÏo½õV EÝòš+·lß¾]<ûì³ÊíW_}µÉýÖš]½zU8::*ÇPc¹¥¤¤DDFFŠnݺ‰§Ÿ~Z‘m2¯®®Nxyyµx?TKk®º¥°°PtêÔIÔ×× !~î1svvVn[ÐJMMï¾û®8tè2dH£oıcÇŠ>}úˆ²²2!„‰‰‰ÂÆÆFlÛ¶M™3cÆ ñÊ+¯ˆ7nˆiÓ¦5úÁÐ:tHúè#éü¶ÞDØ\¹%##C¬ÜöòòÂÎ;µO7™™™°±±1Ù/ðòòBff¦2¶jÕ*=ÂÓÍéÓ§;;;e¬_¿~Ê}ƒ ÂÒ¥K±téR½BÔ\ee%²²²Tk£ûõ뇵k×ââÅ‹ Áš5ktŠP W§ 5ïׯ¥Y·n]‹Ç¥·†Ürû•5îÌ-}ûöÅ–-[ô Qw“[`Ú´i˜6mš.1j­¼¼—/_ÆôéÓMÆûõ뇚šdgg#,, ]?Ý5Ô-²Ür{Ý2dÈ 2DëðZ%^î¡ÈÃ`0è–î УG8;;›Œ÷ë×OÙgÖ¨  @õ^ †ƒƒƒÕî—¦Ž¡Ûï·6Mí—7nXí%¥ àë닎;šŒ3·0·Ü©áuËNpÝ~¿µaÝb9Ð- ²²”KÔ5pww‡Âj?äêëë¥ÌŽŽŽ&W$±6•••ª÷Šƒƒ:v쨼—¬MSÇÐí÷[›†ã¤áŠ Ž+k=ŽêëëUû`nanQkx?ÜùYdíÇë˱€n^^^ ú2sçΡC‡puuÕ#,Ýy{{ã§Ÿ~BuuµÉxzzºU_¯ÖËËKõ^),,Ä7”÷’µiêºý~kÓpœ4ì‡ééépqqQµÌ-rÌ-jMC·ßomX·XŽt ðõõ ?@î³F0&hyy9rrr cdúòõõm4™[ëû¥sçÎxðÁÝ/=zôÐ#,Ý5'ßÿ½ÉxzzºUC ¹åÂ… Ês s‹Œ»»;ÜÝÝ¥ûÅÎÎ~~~ú¦3Ö-–c݈ž={"99Y+//Gzz:¦L™¢cdúš:u*””¤Œ54>͘1C¯°t7eÊ\½z/^TÆŽ9‚Ž;bôèÑ:F¦Lž<'OžDmm­2~ôèQ 2]»vÕ1:ý„……!<<ÜäÊÍÍŹsç¬újÈ-{öìQƘ[˜[óôÓO#%%eee€ÚÚZìß¿cÇŽmôËhÚ;Ö-÷@ïË€´5%%%báÂ…báÂ…Â××WtëÖM¹}û8lذAØØØˆ—^zI|òÉ'"&&FtëÖ­M_w´)III"..NÄÅÅ [[[Ñ«W/'ž|òI“yK—.öööâù矋-...böìÙ:EÝò6mÚ$.\(¦L™"ˆ_ÿú×báÂ…&—櫬¬¡¡¡"44T¬_¿^,Y²D888ˆ%K–èyËzã7ÄÂ… ELLŒ CGUæœ={V¸ººŠ±cÇŠM›6‰™3g GGG±wï^#o9EEEÊ1äéé)ÜÜܔ۷yNRR’èСƒ3fŒøóŸÿ,üýýÅÃ?¬\»µ½an‘cn‘›1c†ˆ‹‹€òÞÙµk—2'77WôìÙS„‡‡‹×^{MÄÆÆ åšóí ë––ÁËØY¨¾¾EEE€èèhPn×ÕÕ)ófΜ ggglذû÷ïGDD6mÚÔn×¹»»£ÿþ ü\\\Læ%$$ $$;wîÄÍ›7ñÏþÏ<󌦱j©¬¬Ly4ü_TT„ÒÒReN‡pøða$$$`õêÕðððÀªU«ðüóÏë³®]»†k×®¡{÷î˜2eвnolêׯ:„7ß|+W®DïÞ½‘˜˜Øn¿úÝÑÑQz €“““òóðáÃñí·ßâÓO?Źsç0gÎÌŸ?=ô¦ñj…¹E޹E.$$^^^ªcÈÃÃCùÙÇÇGÅ|€´´4ÄÄÄ`ݺu Ô:\M°ni6B¡wDDDDDm×@Y€4‘X@Y€4‘X@Y€4‘X@i,-- YYYz‡Ñ¤¢¢":tÅÅÅÒûSRRpéÒ¥&·qýúuù为ºâ§Ÿ~BZZzõê…'žxUUU~>3=|øp„……!##%%%X¹r%.\ˆo¾ùÆd[¯½ö¦NŠ+W®`÷îÝÒ8«ªª0nÜ8xzzâèÑ£ÈÉÉÁ¬Y³0gÎ>|Àσ3gÎD\\„f—µ¼óÎ;èß¿?òòòpòäI8;;cýúõÒ¹~~~Êúóääd! „|ðÁ¸|ù2rssqõêUTUU©~!"jMX@‘UùÕ¯~…Q£F™= }·^zé% 2;wÆÜ¹sqãÆ „„„`Ö¬YpqqÁœ9sàé鉽{÷š<ÎÎΫW¯F·n݆åË—£¨¨_~ù%àý÷ßGuu5Þ{ï=„††ÂÅÅ/¼ðˆµkךl+::úӟйsgtêÔIç¶mÛŸŸ7ß|ƒ†··7V¯^Î;ãwÞ¹§×Þ«W/¼þúëðòò€0yòdìÙ³ÇâídddÀ××{{{Œ9òžb""Ò›‰Èê,_¾?ü0¶oߎûÚV\\œòshh(`̘1&sBBB––f26xð`¥¹¢¢¢ðÐC)giÏœ9ƒŽ;bÁ‚0BÀh4âúõ먯¯7ÙÖ¨Q£ÌÆyḸ¸ ::Z³±±Áã?ŽS§NÝå«5uç댈ˆÀ²eË`0öTzƒæIDATàèèx×Û™7oâããáåå…ñãÇcöìÙ&qµ6, ‰ÈêôïßS¦LABB¶nݪºßÆÆF5vgÃ]gggÕãnk¿³)î0¹mggåšÉÕÕÕðôôTÇñññpuu5óöö–Æv»ºº:8::ÂÖÖô<ð€Ò„h©;_gö-mŒE^^6n܈­[·"&&±±±8xð ôÿ‘ÞX@‘UZºt)úôéƒ7ªî U]Í"))©YŸÿÛo¿Emm-§OŸFII‰Ò¨†}ûöaÔ¨QèÒ¥Ë}?_ïÞ½QRR‚Ó§OcÀ€ÊøÞ½{~ßÛ¿ ·ìj"ÎÎΘ7oæÍ›‡“'ObàÀØ·o†®IlDD–àh"²J˜={6V­Z¥ºoôèÑøú믑——‡ÚÚZlݺß~ûm³>ÿ­[·°téRTVVâÊ•+X±b\]]1aÂ?/kpttÄÿøGüðÃÊã~úé';vÌâç›4i<<<°dÉäää ªª o¾ù&òóó1wîÜf{]Mñññ¿¿?MŠè;vàÖ­[Êí†Ëç54Tµ6, ‰Èjýå/‘Ž?ùä“(..FÏž=ѵkWüõ¯Å¢E‹šõ¹§NФ¤$øùùÁßß{öìÁ'Ÿ|¢4úúúâ‹/¾À7ß|ƒÀÀ@ÃÛÛAAAÊ—ÂXÂÕÕÿýïqìØ1ôîÝ>>>xõÕW‘€±cÇ6ëkkÊâÅ‹ñÅ_ÀÙÙYYÆòÆo sçÎ C`` âãã1sæLMã""²„h¸ŽQ;vþüytéÒE¹öpƒÜÜ\TVVÂÇÇÇdMoqq1’““ááᢢ¢%%%ðóYÒÂÂB+1ÈÊÊRm+77NNNÊRŒ†ÛžžžHKKCaa! ¤\…âv•••HOOÇ¥K—àîîŽþýû›|‹ßùóçÑ­[7¸¸¸ÜÕ~(++Cjj*ÊËË=z˜Ü_XXˆúúztïÞ½ÉídggÃÅÅÅäzÍÈÏÏGPPlllpëÖ-äåå!00Pµöº  7oÞDpp0ŒF#ÒÒÒ  4NNNwõzˆˆôÀšˆˆˆˆÈ\ÂADDDDdÐDDDDD`MDDDDdÐDDDDD`MDDDDdÐDDDDD`MDDDDdÐDDDDD`MDDDDdÐDDDDD`MDDDDdÐDDDDDø?¹š\_¹l§>IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/Q8-1g-noidx.svg000066400000000000000000004220331416254111300233740ustar00rootroot00000000000000 PyTables-3.7.0/doc/source/usersguide/images/compressed-recordsize-shuffle.png000066400000000000000000001546121416254111300274170ustar00rootroot00000000000000‰PNG  IHDR@°AàÚ²sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwX××ðï²ÀÒ» E)¡ª(EÄ‚%J,hl=ÖÄü"±$Fc×Hì¢&&64± ±ÄŠ%¢ "UºˆT=ï¼;a`A² ÷ó<<:wÚ™;»3ggîÜa†a¦‘“u Ã0 Ã0M%@ Ã0 ô8,b†a¦Åa Ã0 Ã0-K€†a†iqXÄ0 Ã0L‹Ã †a†aZ–1 Ã0 Óâ°ˆa†a˜‡%@ Ã0 ô8,b†a¦Åa Ã0 Ã0-K€†a†iqXÄ0 Ã0L‹Ã †a†aZ–1 Ã0 Óâ°ˆa†a˜‡%@ Ã0 ô8,b†a¦Åa Ã0 Ã0-K€†a†iqXÄ0 Ã0L‹Ã †a†aZ–1 Ã0 Óâ°è=¢¢"Y‡ñÎJNNÆ«W¯š|½ÏŸ?Gfff“¯÷}•ŸŸgÏžÕjÚ’’ÄÇÇËd¿6"B||<Š‹‹eJ½#>>D$ëPÞ)xñⅬè"55©©©²ãÄ 6~üxx{{cذa˜:u*þ÷¿ÿáâÅ‹Uׯ_‡§§'߸Ìââb˜››#$$¤±Â~gœ9sOž<©ó|Ý»wÇÁƒ!¢šÍœ9³gÏnòõ¾¯áèèX«içÎ //¯IƇ5kÖÔzú»wïÂÓÓÑÑÑõ^·Daa!ÌÍÍqûöí[¦,ܹsæææ(((x«ùCBBàé鉸¸¸ެnvíÚ…uëÖU;þñãǘ?>zöì kkküý÷ß5.ï³Ï>ÃÒ¥KëÓ•+WpïÞ½z-£²˜››ãúõë ºÜæ€%@ ìÌ™3hjj"44^^^èÕ«¦¦[[[ˆD"Y…úNúòË/qöìYY‡ÁÈØßÿ]»v!00zzzõ^ž…… k=½ªª*lmm¡¤¤Tïu3|²>ö]¸p+W®Ä—_~‰ððp©Óœ8q®®®ÈÈÈÀ„ °hÑ"èèè4zlkÖ¬ÁÞ½{t™C‡Å¢E‹0vìØ÷þêcC“—uÍч~ˆ3fpÃOŸ>…§§'†ŠÓ§OC^^vvvX²d ZµjÅMW\\Œ°°0dggÃÌÌ ÖÖÖÕ®C,#11šššÐÖÖ®vºgÏž!22jjjèСÔÕÕYYY())žžÂÂÂðüùs¸¸¸ð–UTT„Ç#==ÊÊʰ··—zHKKCDD444`ooÏ;°ååå!44EEEptt¬ñd–‘‘’’dee!>>жm[ÈÉÉáÙ³gˆ‹‹C~~>ŒŒŒÐ©S§j—SqÛE"ôõõ¹:‹ˆˆ@||<,--akkËM›››‹W¯^ÁØØÑÑÑxöìzôèyùÚ}EJJJŠÜÜ\¸ººrõœšš EEEèêêVÙV99¹j룤¤‘‘‘HII¢¢"Úµkƒcxõê^¾| @JJ ttt¸“xjj*îß¿---899U9àÑ£GÈËËC‡xŸÍ’’’l‡¡¡!ìíí! ¹qiii‰DÐÖÖFqq1ž?LŸ>½J2SXXˆÛ·o£¨¨®®®ÈÍÍE«V­ ¬¬ KKK,Y²FFFÊoݽxñmÛ¶E||<ž>}ŠîÝ»óꈋÄÄD¼~ýæææ5~‡¥‘V—jjjPUU¼xñ÷î݃H$‚““Wǯ_¿FDD233aggcccnœX,ÆÃ‡‘””„N:ñÆ•”” 99&&&——Ç‹/  ¡¥¥Å­÷Î;PUUEiii­¶%//aaa(,,„••Ú¶m °¶¶Æ’%Kкuk@RR’ÔeJ¾û@yýß¿ùùùpppà¾Ó™™™ÈÏχ©©éãºrå ª=nfggc„ X½z5¾øâ‹ZmkEÅÅÅ EAA\]]¹}—’’%%¥*Çд´4(**B    ¯^½âކ††ÜgìåË—¸wïˆNNNܾ‘ëUUUÑ¡Chhhpã–.]Š`Ïž=˜>}z·©Ù"¦AéêêÒÖ­[«”ß½{—Ðõë׉ˆ($$„Pvv6ݾ}›Œ©U«VdmmMrrrôÉ'ŸQQQ K—.QYY?ž¬¬¬())©ÚX|}}IAAìììHKK‹”””($$„ˆˆ¾øâ ²±±¡Ž;’ªª* ÒÓÓ£;wîpó÷éÓ‡”••ÉÆÆ†ŒŒŒHEE…8À‹Å4þ|’““#}}}RQQ!züø1?~œttt¨M›6dbbBêêêtêÔ©jã;v,ÉÉÉ‘²²2ijj’¦¦&eddÐõë× ’‰D"rss£ÜÜ\n^sssúù矹áU«V‘ŽŽýûï¿DD”@îî¬L:u"@@Ÿþ97ýúõëI__Ÿ¼½½I$‘ššÒÅ‹«—ˆhäÈ‘äàà@VVV¤¦¦F¨mÛ¶MDDóçÏ'kkk‹ÅÜ$\V´bÅ  …Ô¶m[’——§Î;Sbb"7ÞÝݦL™B&L EEE@b±˜zôèA_}õ7Ý­[·¨M›6$//O–––¤¡¡A€Îœ9CDDaaa€’““‰ˆèСC$‰è³Ï>#ÒÐÐ ]]]:vì·Ìýû÷“@  ¶mÛ’ …B6l•––Q~~> +W®T»íýõ #GŽ©©) +VQùgT$‘ ikk“±±1…††ò¶É‚„B!™˜˜@  9sæQjj*uëÖäääÈÌÌŒ-Y²„›7""‚ÐîÝ»ÉÞÞžÐĉ‰ˆhïÞ½¤¢¢BêêêdjjJªªª€òòòªÝŽS§N‘¶¶6µiÓ†[ߢE‹ˆˆèßÿ%”‘‘ADDÖÖÖÜ÷]SS“D" îû}úôijÕªQÛ¶mIUU•Ž9Â[߸qãHNN®Úx¤ñöö¦Ï>û¬Jùwß}Wå;[^^^Ô¥K255厖––ôìÙ3""úüóÏÉÙÙ™7Onn.©©©ÑüAß|ó ÉËË“¢¢"W’ãõ¾}ûHMM,,,ÈÀÀ€tuuéòåËÜr¾øâ îX¯­­MJJJtãÆ Þº¾ýö[êÒ¥K¶©¹c P«.""ÒÑÑ¡5kÖQÕ¨gÏž\ÂCD”™™I‡""~$‹iòäÉdmmÍœ¥ùçŸïyþüy.9ùâ‹/ÈÀÀ€N:E¥¥¥A;w&OOOnúÐÐP***â†W¬XÁ;yíØ±ƒ”””èØ±cTZZJ¥¥¥´gÏŠŒŒ¤ØØXRVV¦C‡‘X,&±XL«V­¢Ö­[SqqqµqÛØØÐÆye™™™¼“aFFñ¦«˜­]»–tuuyÛÞ»wo5j½|ù’ˆˆž}:ÙØØPJJJqH2?ÿü3/‰‘øâ‹/èÃ?ä•mܸ‘äääxÓgddÐñãÇiãÆ4yòd^Ì...äåå%uýË—/'KKKzüø1EFFRdd$wB ¯6ni Qù—þîÝ»´sçNúá‡èƒ> Y³fqã% ЦM›HOO;ñ•'!èĉ\,‘‘‘äììÌý ^¿~=™››óÖ@zzzÕÆJTž3†W¶lÙ2ÒÔÔä† DC‡冻téB‹/®q¹DDÙÙÙôçŸÒæÍ›iÞ¼y ÊA­"É ÊÜÜÜhÒ¤I¼m÷óó£6mÚц HNNŽ233«ÌûôéS.騨uëÖÜÕITXXÈ›fâĉÜAZâàÁƒoL€æÌ™C#FŒ¨R>~üx²³³ã•íØ±ƒpuwwwÞ‰W¢b\åDDDo¼¤¢¢Â›>((ˆxß—ââbºzõ*mß¾V®\IÚÚÚ´iÓ&"ª[ôúõk^ù¨Q£ÈËË‹·wìØAòòòôúõk:~üx•<EEE$''Ç]Ñ“puu%ooo"ú/ŠŒŒäMãççGªªª¼-W¯^}cdkkKü±ÔiÕ%@%%%Ô³gOúøã¹«/?ýôµiÓ†"""¸í¾qã ‚*û¯®ªK€œœœÈÑÑ‘Nž}úÔ¯4ÙÙÙ\ۛʱH‹C2NÒ¶¢ºÏ·D\\ÔÕÕáââRçØ*RSSCII ^¿~ ‘H„Ë—/cĈhÕªœÑºuk…Â7î3i*oCLL ’’’0jÔ(^yûöíñòåKÄÄÄ@WWU–±X,µî‚ƒƒk\oll,ºwï^ã÷DšM›6aæÌ™066†¹¹9F ÔØxÁ‚HKKéS§¸8bbbðòåKŒ3†7mÇŽQXXX§˜êâÃ?ÄÇ °±±ÁÎ;áììŒû÷ï£K—.µ^N§N°yóffΜ‰ñãÇcݺu¸ÿ>âãã1uêÔ—ƒ¸¸¸*û¾S§NÈÍÍE§NðÍ7ß`Ñ¢E˜7oïX/iC”7ìWWWgÝvTÀ &ræÌäååÁÓÓSêx'''ܾ}ááá Á–-[0lØ0Þ#³ººº˜:u*>ûì3\»v –––5®sÕªUX¸p!BBBpöìY|úé§PSSàAƒ¤N¡PCCC„……aÍš5¸zõ*wà¼{÷.öíÛÇM¯¯¯_ícÂúúúèС®^½ZcŒÒP¥.¾ýö[XZZâÏ?ÿ„‚‚H]oQQ–/_ޝ¿þNNNèÑ£ œ:uŠkˆÙ˜$ Â% Tûõë ìØ±‰‰‰2dHqddd`áÂ…8|ø0—`¦¤¤`Û¶mo¾¾>|||ðÕW_U;>..eee¼ŸFÚ111\ÂOž@€Ž;bêÔ©X²d bccy¿çÍ›‡íÛ·£GøðÑœœ\í:ããã¹»——6mÚKKËj–ÒÒRœ‹<< ç%AoÚ‡eeeˆˆˆ€¼¼<\\\0oÞ|8Í;—ŒIKK‹k;SZZJmÛ¶%GGGš;w. 0€tuu ·Îääd244$]]]úòË/iΜ9ÔªU+®¡¦©««ÓÌ™3iÅŠ4bÄRVV枊¨.n Z¼x1Íœ9“²³³i÷îݤ¢¢B>>>4mÚ4²°° MMMúâ‹/¸ù*?æëëK­ZµâÚ4?~œ”””hðàÁ´råJòõõ% Z½z5IotüøqRVV®6V¢ò6@ÚÚÚôÅ_Ðÿþ÷?²²²"--­*murrrHUU•:vìXãò$\\\ÈÖÖ–æÌ™CÆ #@aaaÕγmÛ6222ªRž™™I–––dmmMK—.¥¯¾úŠ<==y±Œ=š„B! :”¾ûî;rqq¡Ù³gQù(B¡ú÷ïOóæÍ#---êÚµ+÷t“¤ PåöÏž=#mmmÒÕÕ¥éÓ§S·nÝHWW÷m€BBBH^^žÒÒÒxå………dooO­Zµ¢E‹Q÷îÝIAAkÈKTÞHò´QE•ŸûñÇINNŽtttHSS“ºvíJ(((ˆˆj×Hòt¢äi¥¯¾úŠtuuiÊ”)4qâD211!Z¿~=Õ­ ¤Q¬DQQ¹ºº’‘‘-Z´ˆ¾ýö[0`ikksÓ,^¼˜Pß¾}iÅŠÔ»wo2d•?!‰ÈÝÝ–,YB­[·&+++.vI ICy IƒqUUUš2e õïߟôôôjlTTTDB¡†N«W¯¦ùó瓞ž÷Ä\å6@fffdhhH¾¾¾¼¿ŠOO©¨¨ÐôéÓiÅŠ4jÔ(RSSãÅZ—§À6mÚD‹/&+++êС-^¼˜¸ñ¥¥¥äêêJvvvôã?Òˆ#H$ÑÚµkk\®——éééѬY³héÒ¥dffF­Zµ¢ØØXÞtiii¤¨¨HU–ñÏ?ÿ‚‚Íš5‹æÏŸO$‹iÀ€¤££CóæÍ£ï¾û޼½½IQQ‘ hß¾}¤¯¯O3fÌ µk×Ò˜1cHWW—÷”,Qù1®rº–Nè'­aS/æææ000@§Nàíí-[¶à“O>áM# ®®OOOÈËË£OŸ>ÈÉÉÁÓ§OQ\\Œ‰'Â××€ûóôô„––„B!† †üü|¼~ý666Ub°±±¡¡!’““‹¶mÛÂßߟëKæÌ™30`Ñ»woøûûsË’““È#PXXˆØØXxzzbÍš5ÐÑÑA¯^½   uuuLž<ÊÊÊHNN†¢¢"æÌ™ƒž={BNNÆ ƒ££#ž={†ØØX˜™™ÁÏÏöööÕ¶Ó°··‡³³3|,_¾°··‡••7¯››×vdÀ€PPP@FFaggdee!** ÊÊÊ;v,Æ‘H@¸¹¹ñö‘žžw+M@€=z ¬¬ =‚««+öìÙË „B!¶oߎ%K–ÀÉÉ©¦ÀÛÛB¡?FçαeËhhh W¯^\¿"ÒÃÝÝW¦¢¢___¨ªª"66YYYpuu…ŸŸ×îgøðá°³³Cnn.233ѯ_?̘1***ppp@ïÞ½‘’’‚ŒŒ |úé§Øºu+wE÷¹¨Øæ@CCÇGQQ²³³1|øp,Z´­[·æÚîHcdd„€€Þ yyyL˜0 ˆˆˆ€µµ56mÚ„Þ½{óæwrr‚……¯L ÀÁÁ+÷ðð€ºté‚™3gbÞ¼yøá‡°xñbèëëC @UUžžž\û+ƒ*WL$ûD(¢[·n077ǃ`hhˆ 6ÀÒÒ]ºtáÚòÉÉÉ¡W¯^ÐÔÔ”ºí:::ðôôä}Gäåå1iÒ$!>>)))°··Ç?üÀÝšìÛ·/ÜÝÝ‘ŸŸÔÔT¸¹¹añâÅÐÐÐÀ|€!C† 33 ðööÆîÝ»¹þªTTTàéé n½ŠŠŠ;v,ÊÊÊðüùsôéÓ«W¯æöwå[¦’X{õê…çÏŸãÉ“'X´hïvŒššwyùò%œ¡¦¦ÆûëÒ¥ ÔÔÔ0xð`¸¹¹!)) 111hÓ¦ ¾ùæ8;;óêÈÎÎ={ö”Z¯Ý¿EEE°··G»ví //tèÐÛGãÆP~Û_GG?üð|||j\®@ @ïÞ½QPP€Ç£GØ»wo•+.[¶lÁÊ•+Ѿ}{Þ8SSSôêÕ (++CŸ>} ¡¡ØØØ !! °²²Â÷ßÏõ5ellŒääd®~üýýy·¬‰Ó¦MÈ#ªm†Ñ ˆØ ]Z¢3f ::šwYœi<˜7o’’’ ¬¬,ëpÞyû÷ïǬY³)µAt}%''ÃÈȈ;nܸkÖ¬A\\ëiT[·nÅêÕ«_ëŽVëkçÎX°`âããkì8·¥iöm€ÊÊʘ˜ˆœœ©ãÅb1RRRØ‹ý˜FµnÝ:L™2…%?µ4~üxî*Íëׯ|ù UJ:í IDAT.„®®.\\\`ee…åË—c÷îÝ,ùa•X,Ɔ 0}úô&K~BCC±`ÁüüóÏ,ù©¤Y_š;w.vìØ]]]äää gÏžøí·ß¸Ë¾Gޝ¯/455‘——‡€€|ôÑG2Žºi„……!//]»v•u(Í^aa!‚‚‚Э[·y¯UK‘••…+W® sçμî BFFîܹƒŒŒ ÁÅÅ¥IÞõÄ´l¯^½Â… ¸æ MáÆ(((@ß¾}›d}ï“f8pýúõƒ^½z̘1³fÍBNNLLLpéÒ%¸¸¸ ((cÆŒAjj*ûÈ0 Ã0Í\³¾6nÜ8®Ñ¯ŠŠ ÊÊʸigÏž…­­-×Zÿþý¡¦¦VåQ`†a†ašŸÑâ7ß|ƒk×®aÔ¨Q8p €ò7çV~s°¹¹9¯¿•ÈÈHDFF6i¬ Ã0 #KJJJðòò’u®E$@rrr011ÁÕ«W‘––äääT¹Õ¥¬¬Œ—/_rÇÆáÇakkÛè1¦¥¥AKK JJJo5||<ÌÌÌlÚš¦©nœ´òÊe¥¥¥Ü>xúôé{µn¯_¿Fffæ[?UT—}”››‹ÒÒÒÛ•4Tgdd@MM÷øråéšªŽ«‹±¶êºÞ´®¬¬,ÈËËóz–¨nI˜¨ØFCÚç¶òºŸ={}}ý&¹…^Ÿ:®ëü²<^TÞGÈÊÊjð6aÒHûÔE]ê¸6ǖƪãÊeÅÅÅ EZZZ­b¯É® ¢¦7iÒ$®ó¼ü‘{ D×®]iÏž=Üð²eËhÙ²eMÛÞ½{ßø¶ášÔ%ÎÚL[Ó4Õ“V^¹,;;›6lØÀ+8pàãi©©©´}ûö·ž¿.ûèæÍ›töìÙ§i¨:þí·ßèñãÇ5N×Tu,mÝuQ×}ô¦u={–nÞ¼)u\uûèÒ¥K\§£Ò>·•×ýùçŸSDDÄcnõ=.½/Ç‹Êû($$„ë ³±IûÔE]ê¸6ǖƪãÊe©©©d``Pc,ÍE‹J€.\HcÇŽ%"¢£G’µµ5o¼®®.ýóÏ?ÜpS&@/^¼¨ñMÃo’ššÚ ÓÖ4Mu㤕W.+--­ò親㒒’j{ήºì£üüü*=±VÖPuœ]¥æÊÓ5UK[w]Ôu½i]¹¹¹”ŸŸ/u\uûèÕ«W\éÒ>·•×½}ûözm{]Ôw=ïËñ¢ò>Š‹‹£½{÷¾1ž† ísPu©ãÚ[«Ž+—µ¤¨Ù6‚~ýú5|||„ØØX=z»víˆ#ƒ BNN¶nÝŠôôt¬^½ÚÚÚo|7NcÑÑÑ©ó—+ªØëgCL[Ó4Õ“V^¹L(ÖéÅ• I^^^êÏk«.ûHEE…ën¡: UÇZZZUn»ÔåóÐÐê³îºî£7­K]]wk°¢êö‘¤'⊤}nß×:®ëü²<^Ôæ{ÔX¤}ê¢.u\›cKcÕñ›–Ýœ5Û6@’·š/]ºÉÉÉ066ÆÆáíí ‰D¸páæÍ›‡5kÖ cÇŽ ªö Lã6l˜¬ChöX7¾>}ú°7m72===™ýHešŸfýôÓO5NÓ¡C7QDLunܸŽ;Ê:ŒfÕqã{ðàtttÞúAæÍrssñèÑ#|ðÁ²…išuGˆõ%yO,{_,ü_qñâEY‡Á0ï¡PȽìUš´´4888´ˆ§Àší †aZ®{÷îáÛo¿­ò¦x†iÉÊÊÊpäÈ‘ –„%@ŒÌ¥¥¥µØFxM¥%Ö±““öíÛ'ë0æQTT„#GŽÈ:ŒwF³} ŒyÊ:„fÕ1Ã0 K€™›6mš¬ChöX3 Ãð±ˆa†a˜‡%@ŒÌµ„§ dÕ1Ã0 K€™cíS«c†a>–12ÇÚ§4>VÇ Ã0|,b†a¦Åa #s¬}JãcuÌ0 ÃÇ FæXû”ÆÇê˜a†%@ŒÌ±ö)Õ1Ã0 K€†a†iqXÄÈkŸÒøX3 Ãð±è Š‹E(*’uÍkŸÒøX3 Ãð±è Ö­›€YGѼ±ö)ÕñûïÇ„··7¶oßÎ+OHH€··7òòòdYó3bÄlÞ¼YÖa0L^Ö¼ëJJáïL*ëH†ijb1ðúuÓ¬KI©æñ·o߯­[·ð÷ßcذa000äææâäÉ“xÝT¶–––\ý2Í»T ¡¡À;²Ž¢ùbíS«ã·sê ¬Ü4µ¹Õ>lØ0˜ššbåÊ•oœ699gΜAXXÄbq­¶·¤¤áááFbb"oáÑ£G8sæ xãÊÊÊׯ_#33AAAxòä 7>)) çÏŸGAAo¾—/_"%%D„‡âìÙ³ÈÈÈàM“––†ììl@qq1’’’¸qb±>Ä_ý…ÈÈÈ*ÛSXXˆ[·n!88OŸ>­2þÑ£G ½{÷PZZʕϞ=^^^¼ióóóqõêU\¾|¯^½’º…K—.¡¬¬¬Êú˜w 1ÕZ¶lDѤI²Ž¦ùÚ¾}»¬ChöZZŸ8q‚† ÒË!îÐØ……5Ç2lØ0š5k8q‚)66–ˆˆÂ½xñ‚›vÉ’%$ÈÌÌŒäää¨[·n”ššZãòÃÂÂÈÎÎŽäää¨M›6€&L˜@DDYYYÔ¯_?dnnNhÆŒTVVFDD)))€>ýôSRVV&mmm@~~~4sæL …¤¥¥EšššÀ­såÊ•dddDݺu#%%%’——'555:þ<7M÷îÝiâĉ4iÒ$RTT$ôúõkzöìyxx’’uêÔ‰Mªp ¾víµnÝš ÈÒÒ’ääähܸqDD”““Cîî¢BíÛ·'%%%jÕª•””‘³³3­\¹’[ÖÅ‹ÉÀÀ€455IGG‡tttèôéÓÜø~øŒiРA$‰HUU•ŒéêÕ«5ïÔ&VXXH"‘¨ÆiRSSÉÀÀ ‰"’-v¨–^¾”uÍkŸÒøX7ÞÞÞprr²eˤŽ ÄÚµkñçŸ"..QQQ(,,ļyóª]fII F DGG#11ñññptt,]º‰‰‰Gll,.\¸€½{÷bÿþý¼åˆÅb$$$ ++ ?þø#üüüPVVÆ]Å™2e ~ùåÞ<¯^½Â´iÓ››‹§OŸÂÝݳfÍqÓüúë¯066FHHDnn.¾ÿþ{©u“——‡1cÆà“O>Á³gϘ˜ˆI“&ÁÇÇ/^¼à¦ËÊÊÂàÁƒ‘››‹—/_ÂÑÑ{÷î­¶ÎÙc P-€5†f˜F(D¢¦ùj×êÕ«qèÐ!<|ø°Ê¸€€8;;s'{KKKŒ;üñŠª¹ÏvíÚ5<~ü ,€……ÀÔÔ³gÏ8pÇGûöí½{÷F÷îÝPé 8gδjÕ Ð¿À’%K §§Ç•…„„ðâhÛ¶-|||   SSSL:QQQ¼[pÓ§OÇòåËáè舎;"11ÁÁÁ=z4RSS…ÒÒR8::âêÕ«555$$$ .. ¯¯Q£Fqã^½z…ÿýDL™2òòU›Å!==óæÍƒºº:TUU1þ|äääpÉ–¤¾¦N EEE…Bx{{#((Hj}3ï–ÕA¥.LaíS«ã·3xpyÛœ¦ø‰jWÏž=Ñ¿|ýõ×UÆÅÄÄÀÃÃWæáá±XŒøøx©Ë‹‰‰H$ª2¤§§#//Oê2cbbªQQQ±J™ššÊÊÊjl°-¹êT±­œÿT%YïòåË1jÔ(¸¢ÿ¯Èï¿ÿ¹¹¹°°°€‰‰ fÏžÔÔTÀ¸qãðÑGÁÓÓšššŠ‹‹ñìÙ³*ã´µµ! ¥.³ºåÕGXX€ò+CÕÑ×ל8q÷ïßçý­X±àêêŠÐÐP„††béÒ¥Ƙ1cººº8|ø0°{÷n@=x·´$ôôôðâÅ ¼¬Ð¢°°III²ýLÓa ÐØÙ=æ ûûË(fŒµOi|¬Ž›Œ5 Ë—/ç•÷èÑÿüóï*Ë™3g`ggWí »sçΉD8qâ¯<::ŠŠŠpuuÅùóç¹òÒÒR£G ¸EåOš8q¶¶¶hÓ¦MµÓÙØØÀÐÐ;vìà•—––r·¼ÂÃÃ!ààà€éÓ§cþüùÜ“`=‚X,F›6m0bÄìܹ%%%Užnʯt.^¼È•]¼xÅÅÅèÞ½{Cl6##¬ 7èÜù.>ìÀ = lÚèêÊ0(†a+V¬€¯ìË/¿Ä¡C‡`ff†Ï>û —/_Fhh(N:UírŒŒŒàçç‡ àäÉ“èׯnܸÂÂB\ºt ?ýô>üðC8;;ÃËË €‚‚–.]ZïmˆŠŠÂÇ kkkœ>'NDLL ÜÜܰuëVtéÒ…›££#>øà^™­­->ýôSdgg#22"‘cÆŒÁgŸ}‘H„¾}û";;ÑÑÑ())¯¯/&Nœ@777…B$&&"55Ý»wÇš5k¸öC@ùU1SSS@¿~ý`gg‡¸¸8ˆÅbÌŸ?¿Jû+SSÓ*q [·n5ïØ&TZZŠÕ«Wã›o¾©vš¼¼<øûûcÁ‚M™l¨â³† $7ÔÒòCÅ'H­¬€¨¨º=µÁTÏßߟݢid-­Ž±oß>Ööé·jÕ*üöÛoRŸfc^QQ´´´ª}(ÿ±äààÐ"œ`m€já³ÏÊ{j•ˆŽ*Üfê©%˜e…Õ1Ã0 K€jA[9’_Ɖg†©öÒQF–XTK¾¾üáÀ@ =]6±47-áR«¬±:fÞEÖÖÖèÝ»·¬Ã`Z(–Õ’»;бãÃ%%ÀÿwÁÔk§ÑøX3 Ãð±¨*7£Ø¹¨åK–™°ö)ÕqóvñâE,Z´ˆ~üø1îÞ½Ë ¯]»¬Ó2 1a„{;®lÖ¬Y¸yóf•ò’’ܺu«Nëg˜ÆÆ :ðñTUÿŽØ«^†‘µÈÈH;vŒ^¾|9¦L™Â Õ9yýú5öïßçÏŸ×zžÃ‡KM˜öïß77·:­ŸaK€ê@C;–_Æz†®?Ö>¥ñ±:nYüýýqáÂY‡_ýFFFøüóÏe ÃTÁz‚®£iÓÊo}Iœ> $%&&²‹é}ÈnÑ42VÇoçAúl¿³½IÖµuÀVÈËI?$ß¹sÛ¶m«Rîáá!5¹8sæ RRR0þ|®¬¬¬ ?ÿü3Nž<‰¢¢"Œ?“'O®Ulb±3gÎÄ!C¸·¼åæÍš5 ‹-ªÒ#5 4]»vÅùóçá[ùI†‘1–Õ‘“àâܹS>\VìÚ°þ´ß;17>VÇo'.;¿üÛ4}^lôÚXm¤¥¥n8%%k×®…³³³Ôéoß¾Çó ;w¢W¯^èׯÎ;‡)S¦@]]#+÷ñ!…œœ ±bÅ ^ôûï¿ãìÙ³UÞÉ%¡¡¡ îå¥ ó.a Ð[ðõý/Ê ÿý eÃ0Í—••æÎ  ¼mNÏž=1räHÌš5«ÖË;v,öïßX°`¼½½±iÓ¦Z%@@yíææ†ÇsW{vïÞI“&AAA¡Ž[Ä0²ÇÚ½…1cMÍÿ†““?ÿ”]<ï;Ö>¥ñ±:n>fÏž¼¼<ìÙ³§NóIÞÑ%!IfjËÕÕØýÿýDDDàÖ­[¬}óÞbW€Þ‚Š 0n°uëe¿üx{Ë.¦÷kŸÒøX¿‡ÖØ>°iÚ)ß|e×®]øý÷ßqçΨV|$õ-¨ªª¢´´´NóøúúâÛo¿Å÷ß={ö _¿~077¯W #+,zK¾¾ü((ˆ‹ر î؉¹ñ±:~;fZf˜Öùݨ»Û·ocöìÙ8zô(,--ë½¼ . ]»vušÇÇÇ .Ä‘#G€Ÿa˜÷ »ö–:t<<þ&ªiÈ0 S/ééé6l¦L™'''¤¥¥!-- YYYµ^F||<²³³QPP€Ý»wãÂ… ˜1cFâPWW‡æÌ™‘H„AƒÕ8}YYŠŠŠPRR ümä5½‰œašK€ê¡òê={Ê_‘ÁÔ kŸÒøX¿ßþøã$''cË–-044äþ¼ëpß=<<FFFÐÒÒ´iÓðå—_âÓO?­s,Ó¦MË/0eÊßðäÇ–-[ ¬¬Ì5´VVV†²²2^¾|Yçõ2LCÉ:ˆw•ßÿ?ÛîWÍ3îEEåýÿ¼xñ_Ùï¿W}súè#  ~ýúѼyój½\ÉùhÛ¶mõޱ® I$qÃ’ïKAAeggS|||øpDGGcÇŽèÚµ+233qïÞ=¬[·Ïž=ƒAƒÅ~óæM$$$ 33òòå§Ê“'OB,#>>ž›îàÁƒuZ®µµ5¶mÛ† &`ذahݺuƒÅ\W’ï‹ä%ZZZHKKƒ@ YLM©Y'@7nÜÀ¨Q£°fÍŒ3†7N,C®B£¢¢"dddÔë $S¦ß}÷_™¿?x SUZZšL-«ã·ôêUÓõl*W;*>>—/_®Rnii)õ!yyyÞñM"<<ýõŠŠŠ0|øpØÛÛ×241<777X[[såeee8xð zõê…¶mÛV™OOOýúõƒžž¶lÙR«uå·fÌÌÌ0~üx®,##ÁÁÁØ»w/w<755…³³3&MšÄ½pUâõë×øã?ðøñc|üñÇpuuåÆ=xðIII¼&Ožúè#XXX OŸ>õZï”)å‰ÄÝ»À¿ÿÖk‘Í룦ñ±:~¿¥¤¤ 00ûûã?0qâÄj÷믿þŠuëÖñÊNž<‰¾}ûâúõëØ²e ÜÜÜp÷îÝZ­_NN‡ÂÒ¥KyåçÎÃôéÓ¡®®.u¾víÚa„ è]¹·Ø7øý÷ß1qâDÞ•]]]àúõëU’¡P%%%n˜ˆàì쌭[·âÑ£GðôôÄ·ß~Ë?u꾫øKÀ?ÿüƒ/¿ü€ððp¤¥¥quþäÉDEE!118yò¤ÔØ= GGGœ;wX²d‰Ô®UÆ?þø£NõÂ4¬f› …B,Y²îîîRÇwîÜŸ|ò ¢¢¢pùòe 2—.]zãÛßÄĤüVXE¿üR¯E6{¬šÆÇêøýÖµkW^dbb{{û*'ñšX[[ãéÓ§øë¯¿€:`åÊ•µžÚ´i8yò$¯QôîÝ»1räHhkk×i{j’ššŠ'OžTI„B!,X€}ûöÁÂÂ>>>øé§Ÿ.u9[·nEHH±~ýú:%}úôÁ¨Q£`kkËÕùÈ‘#ñÑGÁÅÅ8zôh•ù²²²àëë‹-[¶àüùóضmîÝ»‡ààà*¹û÷ïøøxÞí4¦i5Û[`JJJÕ¾Å(oÛ3£‘îíûúüú+ðÓO€†F£¬Ža˜ÆÒ·/ðøqÓ¬K$ªÕd;vìÀÑ£GqçÎ:½ÔÔÎÎŽ»R£®®Ž¾}ûbïÞ½µžðàÁhÕª0þ|ddd௿þ•+Wj½ŒÚ´A‘vKmÁ‚øä“O°yófܹs§N¢E‹0vìXìܹÊÊÊ@€ž={ró9::"** /^¼€®®nƒÆ[Ñ­[·››‹ÜÜ\øWx3¶ÂÂÂзo_®ÌÄÄB¡ÉÉÉ033k´˜˜ê5ÛH–ú÷ÌÌIbŸŸ<|ñ…,£zw±ö)Õñ[RSlme'$$sçÎűcÇðÁÔkYfffÈÊʪõôòòò˜ùK—.ÅG}Tïåݸqƒ÷Äkm|þùçˆŽŽÆµk×°gÏøV~bÐÑÑAAArrrxåb)Äåääàãã###ܾ}»Öë‰DRûª/ ÃÏϯJÿq^^^¼i%Û¨¯¯ßàq0µÃ F2iRù+2$€›7eÏ»ŒµOi|¬Žßo%%%>|8\]QBË IDAT]ñõ×_¿õ2$¢££Tí“ê´iÓ ÀÌ™3ñìÙ3|úé§oË›Ö!//¨JOßݼy}ôBCC¹²ÒÒRìܹ)))uz€¥wïÞˆŽŽæzHOKK«¶Qs]¸ººÂÄÄ .äu&Š{÷îñ¦ŠŠ‚œœ,,,ê½^æí°¨‘ÞÞü² ·„†aj- ×®]ÿÿþ ;;;ØÚÚÂÖÖ¶N È®]»Ð©S'xxxÀÆÆ¶¶¶X¸pac™6mÌ+×ÓÓCTTœœœ ­­ ggghjjböìÙXµjú÷ï_ëmpvv†ÚµkGGGØØØàÕ«Wµž¿:ªªª8t肃ƒaaa~ýúÁÂÂ...HMMåM{ôèQtéÒ¥A3u# "’uï*I#êšS×äâE â%% %`Ÿw>Ö>¥ñµ´: ľ}ûšÍ­¿ääd©ï1ÔÒÒ‚ƒƒRRRðìÙ3®¯›˜˜äçç£cÇŽÊû½QSSCll,"##amm?üPj_Aeee¸zõ*œœœ Qá ±XŒ«W¯ÂÎήÊí›7nàƒ>àúSKJJÂÓ§O«,»{÷î5>q{àÀÌ›7ÑÑÑU„°°0<}ú066†‹‹ ï³™™‰GñAçååáîÝ»ððð€B…Kó—/_Fbb"úôé999$$$pmšâãã‘ GGGnúèèhó:Ñ ƒªª*¯MVaa!BBBðôéSèééÁÝÝc~~>lll°lÙ2|þùçÕÖCC+**‚––VR¦¥¥ÁÁÁ¡E¼?%@5¨oDTÞ~òÉ“ÿÊ6læÎ­l͉¿¿?»EÓÈZZ7·¨¥)++ƒƒƒ<<v ¬ ¬1tm´¤³¬°:fÞ'B¡¿þú+°{÷nY‡Ó :„-[¶à÷ßoÒ䇩Š%@lÂ~÷‘‘€”í†a˜ ìííQPP€ÉÍì]B>>>(,,DçÎeJ‹Ç F¦« ŒÁ/cWøZÂ¥VYcuÌ0 ÃÇ &P¹«ŒãÇŒ ÙÄò.bí4«c†a>–5nÝ€öíÿ~ý¨CôÍkŸÒøX¿ÿ†Žõë×7Úòïß¿33³Z÷Lœ 333©ï⊉‰ÁÎ;:D†iP,j"•Ï?;v”?%Æ0 S©©©UzGnHÅÅÅHHHÚã²4b± (..æÊ ŽY³f5j²Æ0 %@MdÜ8@Eå¿áØX 8Xvñ¼KXû”ÆÇê¸å*((h²u­\¹ƒÆMÖí=ó`/Cm"ššÀèÑÀž=ÿ•ùûýúÉ.¦wE`` »EÓÈX¿+99XTéÅ–¶.(ÖÐ1aEÛ¶mÃÚµk«”ÏŸ?³fÍBII ¾ýö[üòË/ÈÎΆµµ56mÚTå}TÕÉÎÎF—.]°aà 4迯\ÁäÉ“«}ïÖªU«°jÕ*¬^½û÷ï¯ÕºFVXÔ„¦Mã'@þYÞ3´‘‘ìbz°sãcuüv²JKq«‰ÞÖ]»OåFŒnøðáÃØ¶m÷>¬ï¾ûX·n<==ñóÏ?càÀ åz‡®‰¶¶6:vìˆíÛ·ó ;v S§NÐÖÖÆ‹/ê1ü{Ø-°&äâ89ý7\Z 4³>¾†iúúúppp€ƒƒ¶nÝŠ}ûö¡]»v(++Æ 0yòdLœ8æææX»v-,,,°aÆZ¯cÚ´i8wî’’’9998vìK¦™fƒ%@M¬ò#ñ;wee²‰åÿØ»ïøš¯ÿã¯l2$H$1“ØÄ޳¨Ú»´¨Ú´tQ£¶ZýR£Ã¦Aù¡F”{ošH+d/‘½îïËÍý$AÂýäæ&çÙÇç‘|Îçs?÷ä”xßsÞçœâBä§ÈO´qÉKÿþý™6m”{X%''Ó¹sgɽ;wææÍ›~v×®]qttäÏçSV·nÝJÕªU µëº gb¬ˆ½ÿ>|ñ¼Øx8$€Þ½µ[/mù)òmüfÜ­¬¸ Þm+#“æÿ¼Íðáé]»6 ,P•¿Øì3÷~OÉÉÉ’@_GOOñãdzbÅ æÌ™Ãúõë?~ýôS† ÂÍ›7ùâ‹/8yòdŸ¹hÑ"æÎ«‘ú š% bbÀ€¼ Б‘н»òkI&òSä'ÚøÍ(²d§fÉñ:ÀÇÇG¶Ÿ5**Š7’žž^ ûÓÒÒØ¸q#ÑÑÑ’r…BÁ÷ßϰaà õþ£F",,Œ®]»ªÊŽ;FíÚµiß¾=³gϦwïÞ8::âââ¢ZÌÏßß_#~PP®®®ÄÅÅÑ¢E ôôô8þ 6°eËnݺÅèÑ£™7o^¾uÏÊÊbÙ²etïÞ777f̘ÁÓ§O%÷ 2V‰Õn‹Cë×Ã;ïHËTÎ+‰D~ŠüD—,¶¶¶¸ººª===6mÚ¤ÚÈtëÖ­ôíÛ &NœˆMš4!**ª@Ï/_¾<üú믒ò5kÖ¤ Hr«X±"/½žŸ¨¨(.\¸À‡¹–ÆwssŸ~ú‰ÐÐÐW>#88˜>ø€úõëÓ³gOfÏž-éµ9}ú4gΜ‘¼æÊ•+>|{{{,--177ÇÕÕªW¯Ž¹¹9åË—ÇÕÕ•ºuëæûÞƒæÏ?ÿ¤wïÞLž<™‹/Ò·oß<÷½ÿþû’UÐ>1V Á®]ÊEÿû/§ÜÓªT)y»Æ‹üù‰6~3zzè›ÑçD½‚ßÚ¢E Z´h(‡Š–/_Î×_Í»ï¾ ÀÌ™3™0a‚*€=z4¶¶¶üïÿcÑ¢Ez‰'2uêT–-[†¹¹9lÚ´©À¯/¨«W¯R¦LUŽÍ eË–eÙ²eÌž=›ªU«âàà@Ó¦M8p Æ CO/§Áìììðóó£lÙ²€²WfïÞ½|ùå—ªC»ví¨S§fffLŸ>]U^­Z5š5k&)SwôèQöíÛGpp0Õ«W yóæ4hЀëׯӰaCÕ½mÚ´aîܹdee©†ùíP1en@ëÖpÿ~Nù‚Ê hÒ$íÕMJ‹Š½+Ò!µƒ¶«ñRYYY >œzõêñý÷߯ãÇéܹ³ê>===:uêÄ•+W üì!C†0cÆ ¶mÛÆÇÌþýûÉÈÈ`È!ý¢¢¢TÁCn}ôï¿ÿ>»víÂ××—+W®0bÄ–-[ÆùóçU„™™™*ø¨Q£­g~.]º„……Ež!?CCCîܹ# €ªW¯Nvv6T®\Yöº ¯' bÌÖ‚6m@}¢ÅÔ©`oýúi¯nš޶«Q¢‰6.™fÏžMpp0W¯^Eÿù: Tʵª wîÜ)ð³Ë–-ˇ~ÈÚµkùøãY¿~=£F¢Œú¢e••…±±ñK¯—)S†#F0bÄ@93¬k×®œ>}šN:åûšüzX …f*¬æÙ³gX[[ç™ñ6lØ0UÝ FFFd–ÔdN$r€Š¹Úµaÿ~05Í)ËΆáÃáÜ9íÕK“D~ŠüD—<ÿý7üñ{öì‘äÜ899addÄ©S§$÷;vì¥y,/3qâD._¾Œ··7‡’e(ÕÚÚšGå) ####OyݺuÑÓÓ{m^º *¨CMª_¿>tëÖ~ýúIŽ*UªHî DŠé€V­`ûvPÿP“š }úÀ­[Ú«—¦ˆüù‰6.YnܸÁ˜1cøá‡¨T©ááᄇ‡“€‘‘ü1žžž9r„gÏž±dÉ™ä®N£Fpæ ”+§•*½5‘Ÿ"¿ÒÖÆ^^^xzz–ú¡¿§OŸJíÚµUSä‹£uëÖ1g΂‚‚°²²’\S(DFFOõêÕ%ÉÎ……B¡È“¥ )))„„„P¹reÌÍÍ%×Μ9C—.] ÄÉÉIãï]P©©©XYY©VÑÎOxx8®®®¥bí0Ѥc¾ý6ïz@þþп?pûb§´ÿ#UD—N–––Ô¯_¿X? œ¦oggÇ_|‘çšžž¶¶¶Ô­[÷­‚P&‚Ëü€2i¼N:y‚Ÿääd&MšÄ„ ´üy‰H­\ ½{KËŽ‡Q£@ûóD~ŠüD Å™¡¡!Û·oÇÇLJýû÷k»:õÃ?ààà ZuZ(>Š÷Ç!_°mté/æ”oÛ•+Ã/¿h¯n‚ o¢^½z\»vMÛÕиüQÛU^Bôé(SSØ·êÔ‘–/Y¢ûì3jÔ¨Áüù󉌌ä¢ú´*àR•*œŽ×Fõ mäHøé'iYh¨rµèØXíÔ© D~ŠüDë¾Þ½{³ä-f8¬[·ŽíÛ·ê5~~~,Z´ˆ)S¦0kÖ,¼½½_ûšÓ§O¿võgu>ÄÁÁ!ßø€€6oÞ\˜* B•šið)))ܾ}›êÕ«pûöm4h º®¯¯³³3AAA´nÝZUî]»6–¡¡tȵ:iq5s&ø ÀÏ„‚*Ñ=@ê&OžL“&MèÞ½; \ÝT}‹uÀ‚¨¨(é §Og÷âÅÌùö[<<<òl‹QÏ—-ƒAƒT%€rçøáÃáÛoµ_?q.΋êüm%'òàÁü"9ŠÌ×+###ßÒ_eÇŽü¦þÉHMzz:™™Ò÷/S¦ —.]âüùó¬X±‚3gÎ0}út~ýõW õÞ…5kÖ,ú÷ﯯ¯¬ïSÚ½øû²ÿ~<<<èØ±#‹/.ôŸ-]U*öûúë¯Ù»w/'OžT-ƒ¾`Á|||عs§ê>wwwÞÿ}&L˜(ÿpÌïØ€íÎÎ ‘i u9¤¥Á;ïÀéÓÒòI“`Å íÔéeJÛ>UÚPÚÚXS{EG{qãF ÕêÕ:tHA_ÿå]´Íš5ÃÅÅ…¸¸8Õ„Ž1cÆðÛo¿ahhȬY³Ø–ÏúË–-£_¿~Œ7;;;¾ÿþûçï×777|}}9uêLš4‰E‹¡¯Ÿÿgã;v0tèPBBB¨Zµj¾÷:tˆ=z P(xüø1íÛ·gãÆtèÐAuÏþýû™9s&W¯^%44”ZµjqçÎjÕª%yÖ¬Y³8uê.\xuã "ö“*Ñ=@ÙÙÙ|úé§9r„Ó§OKö€±··'$$Drÿ£G¨R¥J¾ÏÚž»g¨˜31½{Am”Pn£±`vêô2"?E~¢K†ýû÷ãîîιsçøâ‹/ظq#6l`Ê”)xyy©ŽvíÚ‘™™©ÒŒŒTMxüø1›6m¢OŸ>œ?ž©S§ò믿æD½àííM5^ú{2·ªU«âääÄêÕ«%å«V­¢uëÖo½·— ¼›”••ÅÀÉÎÎæäÉ“y6¨ëÓ§Ó¦MÃ××WWWΜ9C||<]ºtÉ÷ycbHÌÊÂÜÀ (ª¯VVÊ…[·†ÇsÊçÎ…*U`ôh­UMBä§ÈO´ñ›15­‡ƒÃ·Eò^¯ÊÿyaÊ”)|þù瀲GèúõëlÞ¼™ñãÇS­Z5ªU«ÀÁƒÙ½{7'NœÀÖÖö¥Ï›9s&S§NU=ïìÙ³lÛ¶÷ß?Ͻÿüó›6mbëÖ­è©O7}‰'òá‡Gùòå ÅÛÛ;Ï„A(j%6zöì{÷=/Ô­[—ÀÀ@¬­­Y½z5:t J•*DEE±iÓ¦—~"IÉÎf_L Ãuh  jU8xÚ·õüÉqãÀÖV9M^„ü) mWã¥Z´hÁÊ•+%eÁÁÁ¼ÿþû,_¾œV­Zêy­[·æÐ¡CyÊ/\¸À°aÃøæ›o2dH¡žÙ¯_?,--ù믿˜6mžžž¸ººÒ¼yóB=G4­ÄYYY¡P(òª{FŽILL ÿþû/ôνÅz.Û##å®¶,\\ÀËK9,öBf&  W¯j¯^/”†±fmm\2ÅÆÆJ>à%%%Ñ¿̸qã ý¼¨¨(,--%eÇŽ£{÷îÌ›7o¾ù¦ÐÏ422â£>bíÚµ( 6lؠʳm*±PAáää„A†¶¼ccIÈ,øLâÄÝ6oõÜÆ¤$èÕ ‚ƒµW/ù)EA´qÉtôèQš6mª:ÿè£033ã÷ß/ô³ GŽ¡eË–ª²9B‹/æë¯¿~ãzŽ?ž›7oò¿ÿý¨¨¨|‡Ø¡¨•Ø!0M±LMåéóÅsÒ²³ÙïS/Ά°0øôÓœ²ÈHèÞΟmî‰üù‰6.Ž;F‹-044äÏ?ÿ$00ÿû¿ÿ`éÒ¥üý÷ß,Z´ˆÝ»w«^Ó¨Q#œó}Þ®]»°³³#==U«V‘’’ÂÌ™38uê£FRåR®ZµJõ:gggɬ®×qpp {÷îÌ™3‡ &`fföÊûýüü ãÞ½{ÄÇÇãíí¹¹9íÚµ+ð{ Âëˆè5DFrþùâ‰;"#u6øäeBô¢E9eÁÁÊž “'á5¿—AÐ’fÍš‘™™É÷ßOpp0 4àÔ©Sª]###éСûö퓼îã?ÆÙÙlll$×,,,øã? £I“&lܸQµ\‚‘‘îîîDGGç™Ö»wï—@*TÀÝÝ=OùgŸ}Frr2“'O–”—-[wwwIþåÞ½{9þ|7g[[[.\H5D$hT©XèMyxxjaÁÚfÍTeÆúúD´iƒ•¡îÆŽ |ðlÙ"-ïÙþùŠúG+mkÔhCikcM­T’9991mÚ4f̘¡íªED¬$Uês€^§ò³g8©}2IÏÎÆ+:Z‹5z{zzðçŸÐµ«´üàAåì°¢&þ‘’ŸhcA)À\ÝÆ;tt6˜:##ؽ\]¥åžžÊu‚Š’ÈO‘Ÿhc!·1cÆH¨¡´PäÞãh\±%`¯ e¯Oî›,P®-BÉ5oÞ¼|su¡´P417§¶Ú0X†BÁ{ÁÎN¹ZtÅŠÒò©S•k…Ò0Ö¬m¢A¤DT@¹{vèØÞ`¯R·.ìÛê‹`ggÃ!ðí·ž.ïû‹üù‰6AP Í‹#º ƒ½Ðº5lÛêëAfdÀwßAóæò®-òSä'ÚXAJ@ÔÐÌŒz¦¦ªóL…‚Ý%¨ OX±"oùõëàæ³fAZZÑ×KA4M@…»¨$ ƒ½0~¼2÷ÇÞ^Zž•?ÿ¬œ5vá‚fßSä§ÈO´±îÛ·o¾¾¾Zy﨨(RRR tohh(žžž~ö³gÏðôôäÙ³goX;©ÔÔT6n܈ŸŸ_žkYYYDDD™Ï–F×®]ãÀ©CFF^^^¬X±‚ÐÐPU½vìØÁªU«ˆ‰‰aëÖ­¥K—âèè¨*»{÷.ýúõÃÔÔ;;;LLL¨U«Ÿ}ö™êž;vðÃ?ê½òsÿþ}ªT©Âìٳٻw/·o߯××—Ê•+óã?²sçNBBB˜4i—/_.ðsMMMéÙ³'/^|ë: Jº»œ±8›™ÑÀÌŒ›IId)슎fRåÊZ®™æ•/¯\hèPe¯ÐãÇ9ײ³aéReâôúõPˆ-ò%òSä'ÚøÍüûᅩ1¢HÞ+""ÙžÿÃ?`ª6Œÿ:é ïJ IDATé餥¥±{÷n\]]¹~ý:3fÌ _¿~Ü»w===ÙêºqãFvïÞÍÁƒi¦¶ÿëœ;wŽõë×ãããC¹r刧sçÎ899±{÷n7ñ8sæ Ë—/gÉ’%­ûîÝ»iÔ¨GU•}ýõ×¼óÎ;y¶)Œ÷Þ{I“&1vìXnܸ!kû—"z oooš6mJŸ>}å0Ø7÷ï«®oŒ,‘Ð ={*{ƒ¾øÖ®•^»{:v„É“aáB07×JA6<}ú´HÞ« »]½z•ƒ¢P(:t(uëÖàüùóܾ};Ïý:tÀÉÉ  Ôf9ìÚµ‹úõëÏáÇ166føð᪞sssÉP—.]øòË/ùðÃyüø1ÕªU{m]£¢¢ø÷ßéÛ·/åË—W•GDDpðàA†šïëèß¿?™™™ÄÆÆ¨]æÏŸÏ˜1cpqqQ•9s†>L½zõ¨Zµ*íÚµcÚ´iyžömÛˆ‰‰aäÈ‘899©®=z”råÊѲeKUÙ¹sçÈÌÌÄÝÝ .°e˪T©‚§§'VVVXZZ²k×.š7oާ§'vvvôèÑ#ßú_¸pcÇŽ‘œœL‡òÜ÷Ýwß±~ýz¼¼¼èß¿ÛEÈŸ{K—.±rårÕyîa°3OŸ^‡ÁÔ•+kÖÀÑ£yMT(à? aCåõ7!òSä'Ú¸dØ´i½{÷æòåË,^¼˜V­Z€^^^ªã÷ßg̘1°|ùr6mÚ¤zÖ—_~I¯^½ÑÑÑ;vŒ>úHR^¿~}€|ógÌs}j ÆÕÕ///NŸ>MíÚµÙ¿¿êúÒ¥Kù믿$¯Ù°aüñ¾¾¾¯C‡Ü¿Ÿ}ûöñðáCLLLøõ×_ó½711‘M›61tèÐ<ÃË3fÌÖ­[§*ËÎÎÆÓÓSãògΜÁÒÒ777Iy­Zµ:t(“&M¢aÆŒ7ŽU«V©’“Õ™˜˜pâÄ Ž9ÂÑ£G9r$;wî,p&Mš„««+}úôÁËË‹ß~û™3gR³fMFމ—— ,Èóº‹/òÓO?qåʶnÝÊæÍ›Ù²e .ÌÓÖ½{wNœ8Qà: /' ÈÌÌfÏž]ªóܽ@ÛKÀÞ`ef¿ý§NAíÚy¯¯[ @a&Sˆüù‰6~3½zõ"..®HŽ2eʼ¶> 6TõšT¨PŽ;æ›H;zôh¬¬¬X¾|yžkê\]]166ÀÚÚšvíÚqåÊ•<÷edd0hÐ RRR^ûÌÜÆOPPçÎàСCÄÇÇk<·*<<‡Ü]ÔÏmÛ¶ .мys®]»Æ§Ÿ~J­Zµøî»ï$÷U­Z•Zµj©Î›4i¢ª·œ>ðõ×`˜«O1<ú÷‡áÃáeûÇŠüù‰6.Ô‡=n޼ɉ'4h \føðá,]º”6mÚèy‰‰‰€rš··7—.]bäÈ‘€2hnÛ¶-œ;wŽÊo1ÛµOŸ>XYY1pà@\]]iÒ¤É?ëeœœœxøð!©©©’òß~û‰'òXm-¤¤$.\ˆ©©)­[·.ð{tîÜ™‹/ªþ?øùùqA+ÃöíÛ—àà`V¯^-)ß»w/ѹ~qQ«V-1 ^DT6ç$æÞõbB!¥xŸøñG¸t 5Ê{}Û6eoP~“D~ŠüD— ‹-¢Y³f´jÕ :tèÀäÉ“˜9s&qqq,[¶Œzõ꩎U«V½ôyß}÷uëÖ¥V­ZôìÙ“ñãÇ«ª+W®àïõêÕÃÎÎNu|ñÅ…ª·¡¡!cÇŽÅÏϯ@çÍ›‡›››7oæÆ¸¹¹1lذW¾¦M›6²wï^Iy5عs'ÕªU£råʸººbeeÅ®]»Øºuk¡zÊúöíKDDŽŽŽ8;;óî»ïz˜1? 4`ÅŠ|ùå—¸¸¸ÐµkWlmmyÿý÷IÏ5ËxçÎtïÞý­ßS=EAŸÐ²cÇŽ‘ššJ‡4>vü2ÌŸ?_un`ááX[+ƒŸ–×®qEmùöÅ5kòyÖÆ(é22”ÁЂÊïsëß_¹ç˜]Ñ×M(¼¼¼ðôô,Q=_×®]£bÅŠLƒ èÔ©“ª'àúõëÄÄÄäy““Õ«WçÆ˜˜˜Pûùì'''&MšD“&M £I“&’õsbbbòä½P¹reÉP™º¸¸8üüüèØ±£¤<>>___ÜÜÜ$ ß©©©\¼x‘V­Z©¼ˆÌÕ«nffF‹-^ÙFS¦LÁÏϳgÏJÊ333¹rå >$!!GGGZ·n-™ÍvïÞ=ž>}*é %$$„V­Z©ÊÒÓÓùçŸÐ××§k×®„……‘••…³³3þþþ˜™™Q³fMÕk®]»† Õ«WW•={–ºuëb£6šÃ¥K—Të,µoß^RÇ€€5jÄÿýG£ü>i¾Fjj*VVVyzÉÔ…‡‡ãêêZ:†Í:bÏž=жmÛ*ÌÍÍnnnŠÙ³g+Ž=ªHII‘í=¿ýö[E•ª( çøá£T×?z¤àÄ ÕÑòÚ5ÙꢋüüŠfÍ åjAÒ£B…bÓ&å}aaaÚ­h)PÚÚxÏž=о}ûj»Å𣣣bÉ’%Ú®†F=~üXann®øóÏ?µ]ËÎÎV¸»»+ ôÆÏHIIQ˜˜˜¼òž°°0…­­í¿‡.Ñ™!°~ýúqöìYbbbX²d ¦¦¦ 6LöD°Z-¤Ý›ïø[õý`ÔGa/'$ðà‘uiÓ¨\¼¨ì Ê=A.6V9Kì½÷ÀÓ³ä|J/®JROˆ ¼L•*UX¹r%S¦L)Ô>[º`úôéÜ»wï•CšBáèÌ@JJ d×®]œ8q‚:0dÈBmðW$íãïÅÿ©Ê àÁ#ªVv ÍÿqAm忟œøJ­›SPºu >úHåfi ½{C½zÊÙdõëC­Z ¡u¡”*‰C`‚ð¶Ä˜”Îô­]»<==éß¿?wïÞeÛ¶m²?/Ô6ë}Õœó¬,øeeN_îdè¥p:|AÔ¯¯œ.ÿË/Êéóêž>…¿þ‚¹saà@e²´©©2 ê×O9»lÓ&¸|¹ð«L ‚ B~t&rssãÃ?$00Ý»wãíí-Ëz ¹aDÃÒÄæSÞWP(”kKä»öìÁEP/]¤¯Ÿ}þþ¹wÏûI#3‚‚`ï^åF«£FA«VÊÞ¢*U kW˜6M¹ÙñãϪö‚šÒðiN¡0t&jذ!+V¬ 00I“&qêÔ)êׯÏ!CdïÇJ—K÷÷ÉæêÍ5T11¡­¥¥äú¶R¼&PAÔª'O*·Ô03(Ü0Eh(;¿ÿ®Ü—¬KePdi©\“hôhøùgeðtû¶2˜*íÄP ‚”Nm…qçÎΞ=Ë™3g8{ö,ÉÉÉE²ÔàΣ©Tåc"Ÿ(—DÏÊ‚ ¿£Å"åPØJ•8ûô©êþQQÌ©QCözé2==eð2t(øúN$0Õqë„…þ™ Êuˆ.]’–+ƒ®úõÁÉ ªWWîjÿâ(ྎ:M¬$‚ ¥3мyóXºt)íÛ·§k×®|òÉ'4nܸÈVÃlÓ£%^ësVü¼r"ŒÔ´PʘTf ÓïÞ%ûy>¹b"ÉÉÔ35-’ºé2å.ò¹7¬NH OPÁÁù¯-ô*éé <òcm­ „¥‘£#Ô¨¡ÌGAJ €¦L™Â7ß|£‘U7ßĬÉKñZï¦:÷õ…ÏÎÀNÿ‡½±1í--9¯º¾#2’oÞpßœÒ&<<»\«"–+-[*u™™Ê (w`¨L¦~ÑÑÊãêÕü¯Wª”7@zñ}P€M¼µ.¿6A(Ít&²³³#66–5kÖpçά¬¬èÙ³']»v-’÷oÕ´•ª•%2D™àœ•'öïa`' ÇÐJ•$Ðö¨(——W‡h ¡n]åÑ·¯ôZxxÞ (0BB”Ë/¾©ÈHå‘ß²"zz`k›pô"@26~ó÷֔´± Bi 3ÐãÇiÑ¢U«VÅÝÝû÷ï3xð`¾øâ æÌ™S$uè=`ë—oQ_:FpèßÔ¬<„ÖÖL»s‡¬çÿÒ$%q3)‰Ê,_á4õ³òȵ?IIÊeAApï/èµÂ>ãÅ¡§'ß×ß¿îÈ]§·¹?÷ ù­òº{Þä5…}î˾›ûÔÛ\ýüUåoRVEFFÒ­[+""îk»*²Ó™ cccöï߯¯¯jXË–-±Ìµ ³¦¹ls!¦e ßU~ªkРµªñàn "ñ?ŸÈý6{q´ëËI´#2R@¥˜¡aNÂóË$&æÌ4{Ý­ù%¹eg+·É¥¡¨T¢\9ùwX(t&úù矉‹‹cáÂ…EšØhdMè7רøî;ª²Q#>bþüùªó“'Á­Ï|íúÒßÚšI·o“ñ¼cínJ ÿ={FS ‹"«³®)mù)¹™›+'§×ß«P@LLÁ¦œµ‘ÂÒÛÆE#°D‡~­ê t 9@ÂÛÓ™¿©*T ((H+ïûŸiOÒ0©bÀàÁƒ%ãKFf,Œ*е|yª÷EE‰èŠsPq£§§Ìé±¶gç×ߟ–QQ°j•½{O$- RSÉ÷ë«®æžôte/UV–²ç§ô8tD|¢«€ÈÞžÎä%&&âîîÎáÇ‹,ÉÐÃÃŽó;à8Ã’Kš¨®Õs®GЭœ€ìóϡׇSéÔè76†‡3:0PuͱLî¹å¬"-¥É‹@H=(Êï{MÜ—ßµ‡BQ4__uä®ÓÛÜ›û7w~¿É_wÏ›¼¦°Ï}Ù÷osß‹ãE;ä>ò+Ó²Ò&;;›´´é<}ú«¶«";éúå—_¸qãÕ«WϳF¼Ú Ìr ÛN_€ç‰qÆ “ô8mzn~£Ÿµ5Æúú¤?ÿø{?5•+ÏžÑBô ¥Ð‹„d±~‘ áᑸºîJ~¤3“T'OžŒŸŸ×®]ãâÅ‹’£(¤>-KÜ¡hÕù!Ò$1__Ðö”G‘ÞXÒ½|yÉõí‘‘ERO]$V땟hcùÅÄÄù¶‹B ¯”žžN¬Zz ¼b¥¦¦ªþ°ÛØØP¯^½|¢úýªïqVKÂÈΆ3gàJз ©TIòÚ¿£¢(…½©âåå¥í*”x¢åwâÄ ž¾é޼BDGGsöìYmWC(!ŠuO½zõ6lGŽ![Ë•1 ɈΙ˛»èäI°È¸Jff}*VÄDm¸G©©\JH(ªªê‘-?ÑÆò4hXQf•+W‹ S¬ ;;;îß¿OÏž=ùþûïqrrâÛo¿åAA÷ Ѐ2„©¾ÏÎÖ'|yNróàÁƒ%÷úúBrB6ççSÎО¹–kÃ`‚ ‚P<ëÀÌÌŒQ£FqúôiŽ9BFFíÛ·§k×®lݺ•ÔÔÔ='<<œ»wïæ)÷ññÉ“S“ëcavJrتÕ÷ÎÎÎ4hÐ@uþb,:ÂÃ`%òSä'ÚX~"H~"HФb©«]»6?þø#<`ÆŒìÚµ‹5j0eÊ|}}ó}Mff&ÁÁÁŒ=šßÿ=ÏõÞ½{óÁ0qâDÕqùòeÕõG­bÑ#gè-9Ú”§gr6<ÍÝ tâT4ˆåIô zW¬HYµa°'iiœ9yˆüù‰6–ŸÈ’ŸÈ4I§  èÕ«»víâÆÔ¬Y“ß~û-ß{=ÊØ±c¹qãÆKŸ÷çŸâëë«:Þ}÷]Õ5ÿNTп,¹?lþ5Õ÷¹ó€üü”U^ œ‹¹ïæÊ Ø!†Áòù)òm,?‘$?‘$h’Î@AAA,_¾€ääd¾þúk>ÿüs²³³ùì³ÏX¿~}¾¯ëÑ£'OždРAoô¾)eÊ`ßᙤ,ê$d>Uvuׯ_?Ï0ØéÓ`–~‘¬¬$†æÛEvi\]KAŠ €þüóOUnÎÊ•+9t膆†|ðÁoýì~ýúaoo»»;ÇŽ“\»xñ"×ûÙr“Óø>ÿ/+ËÈ•w8yòd¾³ÁÊègs>på®_ÇÌÀ@u-ìòeΨu“Ÿ%ñ<&&&ÏïâT¿’p~ôèQI¶ëSRÎoß¾ÍÉ“'Y¶lçÏŸ×úŒë¢¢3ÐÇi×®;wîdΜ9üüóÏø@¹'XÊì߸ô“ ª½0€fW›bѬœêuêyF3f@Ÿ>P¯Ñ9ÎgÕfàÍ›ªk•Œ mݽœg ‚ ‚¶…‡‡ãêêZ*fŽêLÐÇ̼yó¨W¯ƒ ¢Zµj\½z•ôôtìììÞ虹»ùRSS‰ŒŒÄÖÖ6Ͻe?Ny}?IYØü«ªïó80›w+VÄ\m,2=SE°™ ‚ ùÓ™¨aÆpàÀ<==ål°={ö ÷Šž”»wïâééI@@xzzrÿþ}@Ùeݹsg~ùå6lØ@Ïž=qrr¢K—.ydm}«(IQ¤w&YÉYÀˇÁʦŘtúX[K®o’>«4+ Ÿ4´M´±üÄ:@òë š¤3@FFçÏŸgÑ¢E¤¥¥÷Ê×$&&òàÁÚ´iC›6mxðàjœ¾yóæ 8   Nžþ#Ï0ØÞèhÒKɲ゠‚P\èLôçŸräÈêׯÏÀéÒ¥  <˜¬¬¬¢­Œ¾>ö#,%E±f¤>Hòö½«dð„&©`d¤ºŸ™ÉáR> &òSä'ÚX~"H~"HÐ$ €‚‚‚xò䉤LOOË—/¿v- 9XÌ‚¹^°zmû^¹2tî< ??ˆUn¢qîæLˆa0 ‘Ÿ"?ÑÆò9@ò9@‚&¾þíºzõ*GåâÅ‹K>ÉÞ½{— *`+ (U«bßà>wnÔT…oKÀa‚:uêШQ#üýýå0Ø™3з/%cH½ ¬ S½îŸ˜Ò²³1Ñ×™xT£D~ŠüDËoРAÚ®B‰'r€M*öÿâFEEáëëKdd$=Â××___üüü¨S§»wïÖZÝl¿n‰>iªó´dSbw?òö8¡üja˜A¹˜Íب ƒ%dfrPtë ‚ B‘Ñ™• ¯]»†¥¥%µjÕ*²÷̳tnYYܲø‘ˆ”öª"ëÆ‰¸ø¾ÇíÛ·©[·®ª\Ovî„  ,«§ªeuhÎtúá•*±ÕÙYŽ£Ø ã m…‚m,¿˜˜,--14,öë:+==ÄÄÄ¢]ú¤”+ACÁÁÁܼy3ÏîZe`@åÁ¦’¢SÒÃÓ©S§7V•«Ï³3¡Ÿù3ÉëöÅÄRœ~¶"$òSä'ÚX~"H~"HÐ$ €¬¬¬˜={65kÖdñâÅZI|Î¥ÇL Q+ú„/øxù¢ˆz@ÊÃï°56V]KÌÊâ@LŒÜÕ-–D~ŠüDËO¬$?‘$h’Î@ï¼ó7oÞdýúõ\¸p5j0~üx®_¿®ÝŠ9:b_玤(lS4(òN‡÷÷WÎÐOòfµ´·´ÏA„¢¢3Ð ;wf×®]¬[·Ž-[¶Ð¨Q#:vìÈîÝ»‹~= çìfº¢GÎú) æÄŠ víÚ¸ººªÊÕ‡Á, ÓiŸé-yο±±$iégЯPòå IDAT¦Ò0Ö¬m¢å'Ö’ŸXHÐ$ €’’’X³f ®®®Lž<™iÓ¦Ȉ#˜;w.ëׯ×J½Œ>èµÉUIYèó Rs÷½˜ þ+öjÃ`ÉYYü[ ‡ÁD~ŠüDËOäÉOä š¤3³À¶lÙÂäÉ“©]»6S§Neذa”)SFu]¡PðôéS¬¬¬4öž¯¦&vðÿðßÙRu®¯ŸEë¨<ˆy@:uTåzzð÷ßP±"dÿÚžbIDNò³»•G7ÆPOOS?† ‚ ˆ˜V •/_žC‡qõêUF- ~@¹*´&ƒŸÂ*ÿC?Ê¡:ÏÎ6 ⾯Ӝãÿ<çT|<¢Q© ‚ è( €Þ}÷]ÜÜ܈‹‹ãÇdÀ€¬ZµJÛÕRÑ«[;‡IYØ:åjÏ/› P!éšY˜K®oŽˆ`úÝ»²Ô³8* Ÿ4´M´±üDüD IÅ>züø1Í›7ÇÞÞžU«V1sæLŽ?Nƒ X¼x1K–,ÑvUì?wFœá¬¤˜r$œŽÎ“tý:¼Hõ)o˜Ê/–ÿQ³lYÉ=¿>~Ìüä®r± òSä'ÚX~"H~"HФbŸôùçŸsçξüòK.\ˆ=ÂÐÐk×®1lØ0îܹóú½Âä–Æu‹?ˆÉhª*²ï˜LÝïÒ´iS|||TåŸ|ýû+¿ͪMë¶þ´óñ!4-MòÈ_k×fZ•*oóc‚ Bˆ bäÞ½{Œ;–öíÛóÉ'Ÿàèè¨ZjÞÅÅ…×<¡™˜`ß=CRyƬgYyzÔ‡Á*éßÁ:;ŠCQAm0€OïÜᯈAAМb¥¦¦bii €¥¥%Fj‚‰‰ i¹zL´­â‚÷0&gŒ:+˘Èå×ó䩃êÁñ_ábfÆ¿ bf` ºOŒ d ž_>ih›hcù‰ ù‰ A“Š}¤P(8~ü8Û¶mãèÑ£DFF²mÛ6ÕQÜè5j€]éêÔa+R³fMš4i¢*S(àÔ©œ{2ŸîÀ­\9ö¸¸`¬Ÿó¿&S¡`ðÍ›œŽ—·òZ"òSä'ÚX~"H~"HФbŸ4eÊÎ;÷Ê{|}}eyïBç=—ò¿-\šYå®_Jͯ¹òûáU|ýõת²† á×_s^WÑi «ÀΨ(†­ö¿§œ¡!']]ib.5&‚ šPšr€Š}¤Mo‘œŒ¯åzâ3ªŠªôLAÿ·:ÔªUKU¦¾("À“ìzŒè|Ku}]Xã‚‚$¶12âl“&Ô1•îB/‚ o«4@Å~L'™šbß)YRqTÇ*Ž4mš3C,÷0˜­^I©9è>¶·çg''És¢22èæïÏãb–ûô6JÃ_4mm,?‘$?‘$h’€dbóÃ;ñLuž™Q†¨Uy’¡ÏùÞPOÁñë3%׿ª^¯ªW—”=JM¥›ŸÑÒgºJä§ÈO´±üDüD IbìÞxì¹»¶?ð8²êܪFåŽ7¦fÍšª²ÜÃ`©Ù4mrœJå;Hž5.(ˆuaa’²æwuÅBmÖ˜ ‚ ¼)1&h„ý¤j’óø‡å±O¯D³fÍTe œ8™sOý,||ºò4áŠäµ«ëÔa¤ìê³gô¹~ÔìlAA(8ÉÈìó”3¸-) ›{>Ï¢ˆÿ—nƒa¢ŸÁ¥ÿÚ“˜˜3»M_O-õëÓ­|yɽ'ãã@–w䕆OÚ&ÚX~"H~"HÐ$ÉÉÂû6Ò¿¬áû34`¤ìá­TöK{wŒIãâµ¶$&úç”éë³ÇÅ…VåÊIîÝÍØ  ÝA^ä§ÈO´±üDüD I"’Y¥ï;c@Šê<=ÍËS™¹†ÁTˆý”ÃQÒÀÆP‘ÌåÿÚ‘”tCUff`À† i`f&¹wcx8Ÿéèò'NÔvJ<ÑÆò4h_$ó ²¨\¹2}úôÑv5„B@23poI¥ >’²ÐÿÝÌ3ìàÞƒŒê|™e$åúÙϸú_’’TeŒŒ8ܨŽe¤÷.{ü˜ï>ÔðO ‚ %€Š€ýX;ÉyÜòômÓ[RvþüyÌSÍéÝú0û䳺Yq\óé@rr ª¬²‰ G7ÆÎØXrï7÷ïóÇ“'þ ä%òSä'ÚX~"H~"HÐ$r³`®ÿ@u®@ã•a4oÞ<§L¡`Ïž=´¯Þžþb¿tÆ;Ù™1üçÓ‘ä䜤êšeËr¨Q#¬ %÷~r÷.;"#eùYä òSä'ÚX~"H~"HÐ$++ì›GHŠÂ÷¤2h 4zÇŽ m0ŒJÕr0ׇöÌŒ|};‘’’“ëÓÈÜœý bª¶P¶BÁÈ[·8¤#Ÿ”D~ŠüDËOäÉOä š$ "bëÑ}ÒUçi)¦t3q“ÜsîÜ9ž/vøUÛ™<³˜È!iÜDzzèó 螪¬­¥%»4ÀH/góÕ …‚7or!!A†ŸFAt›€ŠˆaÏvØXúIÊŒÖÅI†Á²³³Ù¹s§êü·wçFv/Žä ‚ÒÒãç׉ÔÔª²*°©~}ôÕ‚ ä¬,zùûs=)I³?Œ†‰üù‰6–ŸÈ’ŸÈ4I@EÈ~¤•ä<6 z ”ýúë¯ÄÇÇ` gÀ¶AÛ9ü´)Çr¥ô¤¦>Â×·©©TeÃ*UâÚµ%÷ÅefÒÝÏ{))W"?E~¢å'r€ä'r€MP²š?€²z93´èÓáŽt·÷»wï2|øp²ŸooafdÆþØæÀÉ(éóRSàç׉´´UÙÄÊ•ùÁÑQr_Xz:Ýüý KO§8ù)òm,?‘$?‘$h’€ŠRÅŠØ7z,)2ø×”Ñ£GKʼ½½™93gWx[3[ö8À÷Ës&ZúÈ””{øúv&--'°šS£ŸU“îCv/%…î~~ĉ.zAAPQ³û¦%zä!©‰üôîZ¶l)¹oñâÅüõ×_ªóúÖõÙ9t ƒŒ9#}fJÊ]üü:“žž3w~qÍšŒ¶“®?t=)‰^þþ$geið'z{"?E~¢å'r€ä'r€MP3îß‘Šfþ’²Ø…ìÙ³{{{Iù¸qã¸r%gWx÷î¬ëëÉü›z\È%'߯׷3ééÊèô€uuëÒÏÚZrß…„ܼIz1ÚA^ä§ÈO´±üDüD I"*jzzT"ÝÇ+ÚÇ cöìÙƒ‰‰‰ª<55•þýû«¦Æ wÎwä››p)סää@üüºž®Ì˜6ÐÓc›³3sí (6–É.&;È‹üù‰6–ŸÈ’ŸÈ4I@ZP~A_Lôr2š³„ÿp™V­Z±zõjɽOž[¶lA_?çS\\}úô!Am“Sggö ÝCÆÌº7síúìÙüý»“™©¼ÐÄÜœ\\(«öÜ,…‚÷oÝâX\®„¢"P>ih›hcù‰ ù‰ A“J|äêꊇ‡nnny®]»væÍ›cdd€¹¹9...’µwä¤W£ ¶5¥ÁÖ£_£ÈˆÍP¿÷Þ{üðÃ’{nݺň#TÛettèȆ¾HÉÒã+¸õLú^ —ð÷ïAV–òB++v4h€¡Úæ©iÙÙô»qƒËE¼ƒ¼ÈO‘Ÿhcù‰ ù‰ A“J|ô*ááá’!&þŸ½3¢HÿðÓsgr_„„CADoXQQ§š_2`»"¿À´‚}—}Gm|ôÌ™3ùûßÿÎM7ݤ¨·víZžzê)Åùn9ãFgÆì‚¿ì„ýUðî»uÇTVþȃÆå2pU|<׬]‹TFŋ¹r÷nöÖ¬ ßÖ¯×_¾þ5íq½PÌ×¶qGñ§+æ'L˜À믿ÞaüéŠùE‹)æ ¶?]%¿víZfΜɨQ£˜5k‡ƒP d‚ ~øa^{í5¯mæÌ™ìÝ»—åË—{m—\r ãÇgêÔ©ÞGÃM›âr±?öEò«.T˜3¦E‘ñÚpoÞb±pÁð믿*êýç?ÿaÒ¤I Û½ŸßË[¿¾E¤fŸ §E(/sgœñjµç±øy¹¹Lm0ì×C¯g˰a>O  ë"‚ C„ÄÄDJJ”kJÒ½{÷ösB­æ´¯® B:¬0gÍ­ ôóº`XX«W¯¦[·nŠzwÝuÛ·+{‘æ›Ïؾc©rÂc;ápµò’å囨½û*Ün õèÁÌŒ E\› ¶oç“vŒ…´`#Ú8ðˆ À#b€mIH  1cÆðóÏ?{EPNN‡ââ‹/nW?Tç`Ðó*4˜¼6‰}wbͶzm={ödÕªUÞÇôÁÓ3tíµ×RXXèµiTVL\Á°¤a^tÄGe²{÷5¸ÝžóÏÈÈàÏ©©Š:96öìaÌÎì3›Ûò%+ñ)G´qà1@GĵŽ*—‹l«•&ß–—óiq1Kòó™}ü8O=ʃróÞ½\±k#ý•þ?ÿÌàƒ±\rI°]oºüØæÍ›yûí·ùùçŸ8çœsxàøÃþÀƒ>ÈÆ=z4ÿýï¹ùæ›yöÙgÏ䇒Ëf°û¿£ ^TNdº…aû/G¥¯Óªo¿ý6“'OV{Á°qãF…8Ê«ÊcäÛ#9^yœ-¼:2”“Awƒ¯A¥Ò#wìÛÇû'Nøø¦‘$þœšÊŒôt¢4Ÿr@ ´nY¦ÚíÆäryS™ÃA™ÓéMåN§­~Þu’?ïQ‹SñþûmüŠ:]^ðûï¿+l§Ÿ~ºb(iË–-ìß¿Ÿ!C†0bį½½V+G{=OVÁ…9å:5ýV)c„¦Nʼyó¶É“'³hÑ"…mOÑÎ_|>¶ bkDPºº‚Áƒ?E¥Òã”e¦=Ê«ÇãðóÑHÒéx©wonKJRO A¨bo TK MSÉìríõ$hÈG޲kÀÊg*ì^O!é¡~Þ¼Óéä²Ë.#33SQoÞ¼y<øàƒ Û7G¿aìcq¸ÄéൡÐ3LyÝøø«4èT*OÒïf3Ób}#ãíçFE1ï´ÓÞ`‘Õ“¡  €¤¤¤S> qDž’’¢££ÑˆÒ€a·Û1™L~çu;²Ì »|»|›|»»r§Ó#\š+þn;3Qo¿MÅÛ€#PC8–ͶIبë¥R© ÿåD ­{l¿¤¤„³Ï>›£GëVÕh4¬_¿žÑ£G+Îùþ®÷¹ýÓÛˆ¯A© DPBÂ5 ´IÒzmŸóè¡C³ZiˆJ’˜œœÌ ½z¯Õú”·” (…´=¢ÏÊ•+=z4ñññÁv¥Ë’——ǶmÛÂ7EµËå55¦ v¿Ø)q8èŠ?„‘j5qZ-±'iµÄÕÛÕh<ùzuœee\<|xH<8!PKTÞ÷*;›º»É°˜jÎ:v9šè:ÛîÝ»9ï¼ó0™ê¨ãããÙºu+½zõRœó¹MÏñ̨‡¹C!¹ÁSî ×1hÐr$©î·›—²³y);k½Ék‰Ójy.#ƒûRRPKb`L –R‡Ã¯©í¹©µUq©-0ªÕDÔK15"¥¡hQˆœš|ŒF£˜è¶¥„ÒcðB5A0²LÞ§9ð›2(ál;ƒº¬~œ4Ÿ~ú)7ÜpõßÊ3Î8ƒ~øˆå$@÷¬¹‡ÅÛÐMïé j(‚'púé+DÀ1«•Gbuq±_—‡FDðúi§qAt´ßr@ÐqqʲÏpOáŸú1,õˬn7.YÆ'x·v¿¥6·,ãj¡­ÚíÆîçF,˜¨%I!TšJá-­§R¡  ¥@ È ¬Œ}iorÂtžÂÜû±HÒf¥°=ûì³Ì˜1Ca»îºëøä“Oêý9ÝNÆ}4Žõ‡×Ð]s‡y¶õéÖm~ˆ$©}Üúº´”i‡±¿‘GãoíÞ—ûô!¹ÞiM!âShãÀÓž1@Õ.N§OPm“â¥!cë`¢Â/N'X,б‡õ‘€DŽ$Žäz)N«m‘X1¨ºÎŒ2B € €×ÿ¶óëù;©vgxmnÎüb 1W&{m²,3qâD>ùäÅñ3fÌðñ¿Ê^Å…K.d牀§赡ž¡ú$&N¤ÿ…h4±>~9d™W繬,L~º™#ÕjžÎÈàáÔT´ÍÜňø”À#Ú8ð*è¸ÍÆöª*v˜Lì0™Øn2ùÉ JJ`ÿ~8ï¼æë:•Š$?Â&Y¯Wغët'5\ÔHt `yùC~ùk N½6ÞÂYGþˆ>¥NµTWWsÞyç±k×.¯M’$V¬XÁ 7Ü 8gnU.#ßINe)OOPBƒN­6Þ½ÿIròŸÀσïy69r„üÌÐßhäß}ûrY?µ!t5œ²Ìïf³Bèì0™( ‘u™ZC¤ZM²^OrCqÓ@ØœÊáŠ@ ã €â±ÏóÛ×çS_„Dg˜z`’¶ÎvìØ1Î>ûlŠëÅ鄇‡óÃ?0dÈÅ9wîæ‚%Pió¬þžs‡JÄé|?‘‘gsÚióˆŠ:ǯßWT0õàAvÖ Æ®Ïø„^íÛ— ±¶˜@€ÉåbW¡ó[uµß‡ Ú µ$)âSÂUªFcWƱ„ÕÄ«¨ñ<ªª9_klÞýflzI¨öš´ B €Ž%€°Û9’ñ"Ùù£æÔ }W(—îøöÛo¹ôÒKëedd°uëVu7ÙÀ•^‰Ãí¹Ëìs†ªIÐù{zB"9ùnz÷þ'Zm¢O©K–Y—ÇÓGRægM$ƒJÅ_ÓÒøkZaõÆÌE|Jàmx‹*°ÛBg‡ÉÄ!‹÷)|õj%‰x­ÖGˆ´TÀøËw†8–@Í$¨C ÐÁ Ëfg¿5”;+ì§¿™L·ûû+lóçÏ÷™qÔ¨Qü÷¿ÿõù‚^ºs)w®¾Ó›×«`jÿx®J2!»m>~h41ôêõ))ø ’.v8xòÈøý’Ï0˜Ó·/×Õˆ1ŸxDž+V~î¹ÕéÃX'ìöS:o´FÃЈoÁéááÍÆÖuEZ; õ$:ž°²‘_&”a£®'G­¶sÖö‘ψRÔ½ï¾û|–Ƙ2e o¼ñ†ÏyŸùîf~;Sa›~&Ï IÄ\¹Á¯/grÚi¯}¡ßòmUUç½û³»ygÇ; [”>м‹ÞòX,‡üúÓ½û-ôéó :]²O™ ¼[PÀߎ¡ÐϰV’˜–šÊ-Ý»“¦×'‚»ÛÍ!‹E!pjO[%«%‰þF£Bì ˆ A|þAF Ðq²Lî™Ïpp÷(…¹Û,œþ¿+¶¢¢"FŒAvv¶×¦ÕjÙ¸q#^¨ì¹q¸Œûpÿ=ò_ŸK^ÓïJ^<ûLJóÿËUíS®VG’‘ñRS§)–Ò¨¥ÂédƱc¼‘›‹³áG®´jÆôÃÕjÒ ÒôzÒj¶õó©z½x\õ$1@þÉ·Û}Î~³™cVkëWÒ®¬„ðph kT«®;gDD(âà-CÄ!€@@••ìM]@a•ò©¬¾OI}IiÛ±cçŸ>æz&&&²mÛ6ÒÒÒ”§µUrý²ëÙxt£Ï%ãÂâX0ö9i¾£°p¹_·ŒÆœvÚ¿‰ã·ü·êjþ|ð ™ååuÆ5k …cú*I"E§ó+Žj÷£Åb”>„r Ùåâ`mONƒ6]*á»ïH8ç†÷衈×é”}»""(ð$:¸\[wóëÈíT»ëDŒ$¹úÕ¢/»|ùr&M𤰠:”-[¶`lƒ##3ë|žøï˜¾³=O<}"¯\t3…ÙOQ]½Ç¯o‰‰7ЧÏ †4¿åË yìðarl¾AÖ§J”FCz½¤úâ(Í` ‡Nr?H6·µ$u¸Þ3»ÛÙíÆâvcq¹Z½o©=¾‘ýj—‹B»½MºÔ«Tô £¿ÑHÿÚmMŠâ[ÐÉHt|`ž½œ_ÀEˆÑª9ëØt Ö·xê©§xá…¶o¼‘eË–ù=÷¡ÒCܹúN¶ßâSÖ=¼;‹®~“ááY;6§Ó7ÐY­6’–öwzöü *•Þ§¼ÚåbNN?VTm³‘mµ¶Ëâ…I"U¯'£F¥ ¤×ô&¥ ôÔëÑwÂá ȶZÙg6ó{MªÝ¯U+„4’¤Øo,©¡éòy•$am¡ˆ9•GÁM½ž~ Nÿ°02 †ЂÐA Ð9@Ѹ—Øó嶘ޜyà$uݵ,ËŒ?ž5kÖ(ê>ÿüóLŸ>Ýï¹Ý²›Ù?ÎæéožÆæòí­¹ãÌ;˜5f:Å9/PPðø¹× ëCß¾¯•ßkÔO)s:ɶZɲZ½¢(«f›m³‘o³µéݼ?$ I§«G RºÁ@d'b³¹Ý°X<§ºÚ+vö[,˜õâ¬u„«Õ~EN?£‘ˆV¾Çí¹X¨"b€@ ó g¼Äñ¼ æ´]ô^v‰ÂVUUÅÈ‘#Ù»w¯×&I«W¯nr\}oÑ^nÿôv~Éÿŧ,5*•%×.á <øUU¿ú=G|ü8úöKXX…½5ñ)v·››l›Í+’²¬V¯@ʶZ±´Ãlº±M“)± žæ)u8½8µÛ£Vkë{NZgÕÕPIiz½BàÔî÷Ðëý,ðrrj-0A"(ð$:‘äì\vžö9åöõ­ ^”DÂ䊺‡æœsΡ´´Ôk‹ŒŒäÇdРA^Ãévòâ÷/òü¦ç½3G×çþ÷óÊ¥/SYüGNÇá(ñ©£RééÙóqÒÒžD­Ìü?EG£½HYV+Eí°¶’Q­öi§ žá¶zù”š8¤†ÃVõ‡¯üMp²¨% |ŸÀ 2•Š0•Š0µc;±fiï¶AykëF©ÕrHS B €Î%€ìŸ~Ƕ닱SwªQ[9k×¹„­¨»aÃÆŽ‹«ÞIŸ>}øù矛í^Þ^°Û?½ß ó)ëÛ›wÇ¿ËÈäA=:üüEȲoŒÁFŸ>³ILœÐÚ—yʘ].²jz޲¬VŽÕlkmùv{ÀcS´’D²^O±ÃÑø°ÕI£Ñ0Àhd€ÑÈÀší£‘ÞaaÞh—,ã’eœÍ$4_ÇÍ-ËaÓŒP1Ô¬%:B €Î'€*ZÈŽ7ú"S¿WÎðœkP…)ï‚çÎËÃ?¬°3†uëÖ¡n&þÁæ²1#s³~˜…KVþ€«$|˜Å¯ÞÁ IDATþøË^|ˆÊÊýž'6v QQÿ W/ÿ³I»ÛÍñ1t¬ž0ªJ96[P{R$ Í`ðŠ›ú‚§»Nç÷1Pà1@GÄ!€@ç@9C_àÐÎó¶¤s«ðÃÕ>uï¾ûnÞyG9ûó´iÓxíµ×Zt­s~äŽOïà`éAŸ² YzÝRÎNAAÁ{9òWìö>õ>ÿ\Ím·]HDÄP""†1”ðð~'Tì¸d™<»½®ç¨A/R¶ÕÚ&«zT*N c`x¸Bèô kõjØ¡<P{!b€ˆ Ás"PcñÚܲ†=WÿŠ£H9ŸORR«W¯ÆPoÕi»ÝÎõ×_ÏZ|ÍÑ£Ù=e7÷ ¿Ç§l牜ýÖÙüsó?1pæ™9ýôeèõ=©÷0Z3ÈX,‡(*ZÁÑ£ÓÙµëJ~ø!…~Hb×®±9òw —a6ðxÝ‘ˆÓjÑnâ'¾Ì‚MII NgÓ=›‚SÃn·+ž^N!€º0ÆiãéÕ>…ÍjbßȵÈneÇ߈#xûí·¶‚‚.ºè"vïÞÝâkFê"yëê·øò–/I‰LQ”Ù]vžÜø$ç/>Ÿý%ûéÖíFÎ=7‹ÜÜg4h9ééOwE«{tìö”–~Mvö¿Ø»÷&~þ¹?›7G±}ûù<ø ùùoSUµ ·ÛÚªóv%V¯^lº<™™™TTTÛ.Mqq1›7o¶‚.‚k‚Î<æÅéäP¯YääŒT˜3n²“ññe>Õÿú׿òòË/+lqqq|õÕWœsÎ9>õ›¢ÌZÆC_>ÄG»?ò) Ó„ñâ%/2mä4$?SÑÙí'0™v`2m÷n-–C§Ô³#I †^h4‘¨TFÔjc›m;jÀ¶@ ´†P¨ º„äœvôYC…½_}+CÞN$îOƒuÝn7<ð‹-RØ###Y»v-]tQ«¯¿jß*î_{?Eæ"Ÿ²‹Ó/æñïÐ+¦W³çq¹L˜L»¢¨ºú7Üî¶_PµµH’•*¬Õâ)2r8±±—!I¢3V ·ìÆâ´`v˜1;ÌXžýZ›Åa!¯(—÷>͉½ÅÁv7àÔ]EØÖlá—k ±ëµiÕfÎÚ}.†±>õ{ì1æÌ™£°………±jÕ*ÆŽÛêëVrÿÚûùô÷O}ÊÂmáŒ>ŽŒ˜ EJNǨmz¶hYvb6ïÃdÚAUU­0ÚÓYÖjƒ…ÁNròd’“ÿ„N˜¹zÄ<@'Tçr¸”ZJqËnܲY–=[ä6ÏÛì6lfñqñèÔ:´j-:µÎ³¯ª·_cת‚ß3ë–Ý8Üì.;—‡ÛáݶÖfqZ°8ª°9*±;*q¸ªp8«pºL¸]f\.3²lAv[d[M²£ÂŽ'Z• ƒô*«ÙÔ(l:,øÒÀ²—-Í¿¸NŽ@MЕ@ù´%ìüwr½Ð¯È¸b†å]JïÛ1sæLžyæ…M§ÓññÇsýõן”ìú€©_M¥ÜZ^gÜŒð_¿[x7aÔdµf)zŠL¦X­Ù'ås{!I®!%å~bcÇ@›­R%æj:û<@&»‰RK)eÖ2J-¥ŠTfñc«©g²›ÚÏÉ* èßòCü £ÆSÃ2À+Dì.»B”4fk(bT¸ SƒQ FgÛ0ßTYý¼Aêv˜ãM!€]Mþ2‡·+cy’Ï+¥ÿÿ‚föìÙ<þøã ›Z­æwÞá¶Ûn;)r«r¹gÍ=¬;´î¤Ž¯O¢1±q“N¸6Ü[×é,ÃfËÅå2ãv›Ûlëv[Úü©³°°Þ$'ßKRÒ]ètÝÚôÜ‚®‹KvQn-oV´ø7þÖ÷ TxD…¦F\h$ÏV-yzCÂ[ LêçÃ5Ê2m'œÊK A—@˜ÍìI~‹¢Ê3æÓ5$=ßC.\È”)Sp×›ÝX’$Þxã xà“vå­_ßâѯ èdCÔ3º'ÉɤD¦™LrD2¡ù5ƒÛmmµp2›÷S\üi“1L’¤%1ñ:’“ï#6v4mÙ+$è¸8ÝNʬe”˜K(µ”Rb)ñ –Z›?qSa­ð™Œ´³¡x$ê!A_·­­(µbÅŸxñWøòåÍF–½Ql7Ž@MÐ%àܾŸ_Gü‚Ù]÷˜ºJ²3|C"þ˜î÷˜>ø€»îºËgž“—_~™¿üå/'íK±¹˜Í{7S©­äXù1EÊ©Ìi—»ÒXC¬W¥D¦(Rm>92™0MX›_Ûá(¡ `)ùù‹0›÷7Y7,ì4RRî%)éN´Ú„V]GÄ1@nÙííui(fê ™†¶ [çyœÞ¨5©‹D%©PI*$Iòl‘üæµ*ˆÕº‰Ñ:‰Õ¸ˆÑ9‰Ñ8‰Ö:ˆÑ8‰Ò؉Ö8ÐJ¾½ªN'X,Ùñ'„2.´Èè‘U:d H*’*¬æ‹pÔªp´š´ÚH´êHôÚhôÚh Ú˜zb„×ÕWQ©<ÛÂÂJ† O…V´žÍ°þ zí ¿þÙŠ Oï‡[Ö±çª_9+»;šß‘[o½•ððpnºé&ìv»×þÄOPUUųÏ>{R¾$(ØVà7>Å%»È«ÊóFÇÊ‘UžEvEv›¤2keÖ2öíi²^Œ!¦QT¿G©¹Àíúhµñôìù(={>Jyù·äå-¤¸xn·Ý§®ÅrÇÿÂÑ£O‘˜xÉÉ÷Ó²§òV¯^-b€Ú·ì¦Ø\L©€Õ'(0xöM'غa+º>:*Õ•^1Sn-ï=2цhâÂâ¼)Ö«È7L±ažr½Z_s»½»=›-›-»=·Á~ÇÉ?]TQû÷ÃyçµÍën$IZ‰Z‰FéݯËG5Q¦Ì«Tá¨TþÝ–“A÷ˆn”—}K~þBŠŠ>mòÇE¥2˜8‘””ûˆŽ>¿_EÇB–ÝTÛŠ¨°äa¶a²Piɡ’O•µ³­‹½»£§« Üf-„©e… ©Mб¹ý £Ú}‹[ªpPE QÇ ×ÅaÐ%®ïFdXÑa)Ä¢‰Õë‰Öi‰Ò© W«Ð«\­/Nge‡.M,z} z}tºÞ}&®ž@ñ/\ZS.¢€”$B@r~Û3>£ÒÞ×k“p3äxbï<³Ñã¶mÛÆå—_î³.ÏÿýßÿñÞ{ïµj.”`ƧÈÈ”˜K<¢È”¯Gõóù¦üvïM2h ¤E§‘“AÿØîœQHªjj׉& LJÊ}tï~M4Ð1c€ì.;&»©.ÙŠ©¶b¶a±cw–a·—ápVàrUâvU\ä6#É´ØÐ`G§r`P¹0¨ÜÔrÐ~Î*+!<Ôê 9ÐIP©ôèt1£×§Ôˆ›úûž2='v»“ÉD\\\< „¡!€lkbÛÕù8ˆñÚ´ê*Fì9}ÿÆç4ùí·ß¸ôÒK}þQ®¹æ–/_Ž^¯oäH%eŽšKI£©6Ÿ_•͸™©%à¬X¸:΋÷<ÑÒjµ‘ÄÄI¤¤ÜÇGmoq×ÎkqX°8-XVϾÄÅ^…ÕY噌ÍiÂê0awVcsVãp™±;«qº,8¸]&p›ÀmF…5V4ØÑKt*'FµL¸Âk†:F¿áÉóÝw0lDEÛ“`!¡Ó%ú4Ê}­öäçIÊËËcÛ¶m\sÍ5mè· >B €Ð@e¾Ï®W{('IŒ9ÁàŸ/GZL£Ç<•0]\1ãÙj6·ÛÞ¤@j.ïrU"Iú&ITƒ`\ÿeµB.4å­ÀB €Ð@˜-ü–²„âŠA>E‰ýOÐЦør©  €K/½”ß~ûMa>|8_ý5 Ï[ÓãS:Uö*¯0ªEõÅ’¿Åeãupe²§W¨{½ÈÒRèèa6· »¬Á.ëp¢Ç-pcDVQ©#P©¢Ðh£ÐibÑiãÓŦKÀ¨ïF¤¡;‘aID‚¶þS¨®Öžˆ ÀJHü§ ê0†1`ÓXöüa=eÖŠ¢¢ýÝ©Hÿ†~³’H˜v¶Ï¡III|ûí·Œ;–mÛ¶yí¿þú+]t6l %%Åç8sÔ4F¤.’ÁÝ3¸Û`¿åf‡Ù+Œê÷ ¬Èâ‘ߎ‘®;ÁU)2çÆÃæÍpªanYÂ…„[ö˜!Kjd4 i­gDÒ"©ÂQÕ h4Ñèµqèu±„i1‰0tàM¨)B­ŽB’:wôpfff§^ ¬3P\\,b€m†èj‚몥²ŠÜ± 9üã¸ñ dN~‚¾Æ£‰õ-«¬¬äª«®âûï¿WØ{÷îÍÆÉÈÈ”ׂXVÏ[É6L•›PIZt#Zµ½&&ƒ6½&‚0m$M$a:O^¥Ò!I:ÅV “]ŸPê¨ BVÕ`^ô%¿?”G¥£¯O™A_΀E}ˆ¹ý ßãÌf®»î:Ö¯_¯°§¦¦²aÃú÷oÅRÎ@ h7BIuö'OÄxï• Ë½Ž^C¶"¡\Ìj‹aÇŵ·Ù¥<ÎhäóÏ?güøñ {NN]t»víRØCá-؈6<%%%>kå Ú»Ýî3÷˜@p²$h)1žôᬗ* Wg7,%ç»¶%~JÕšŠNÇŠ+¸å–[öÂÂBFÅÏ?ÿìµ­^½:Pî jmx233©¨è<‹˜vFŠ‹‹Ù¼ys°ÝtÄX„úXCÜÙù»äCŽ:‹†óíJ¸H¿¦ŠôO®Eª7;ŸÛíæ`Ñ¢EŠú‘‘‘|þùç\|ñÅíâ»@ šG ~P¥%Óûàã ý{>a*å?‡Œšckbø5a9Õ›²êŽQ©X¸p!>ú¨¢~UUW\q_}õU»ø.A}BZ=ýôÓ EW6Oô‹73bÏHRzüâSVUÑ_FàøíŸ#»ë:gϞ͌3u- ãÇgñâÅ÷9Ô …»¹`#b€ˆ´%!-€7ß|3ÞôñÇÛ­Nz@ýŽ?Ê)ÇÐK%Š2·¬åðû‘ìLúÖu?¼3gÎdÖ¬YŠºv»{ï½—;^x?þ˜Ÿ~ú‰¢"ßIþ'ˆ <"(ðˆ A[Ò1@ûÛß(//gÁ‚~ËE PËpnÛÏÁ+Öq¢Øwõxµd¡ïC*’ÿ}¹×¶páB¦L™‚Ûínò¼ôîÝÛ›zõê¥Øoéb«@ h"(„øæ›o¸ñÆ™2e ›6mò)¯¨¨ ¼¼\‘ê#òåhFôg`ÁC ºå 6ò岋ý¯ëÙö!öC¥”——sß}÷±téÒf— 0™LìÚµ‹Õ«W3gΦNʸqã8p aaa¤¦¦rÞyçqçwòì³ÏòÁ°~ýzÅ?nGh‘y‘ùŽœ·X,”——“••Eee%¡Ò/ÒèòË/çé§Ÿæ†n 66–+®¸‚•+W*ê¬[·Žwß}W‘ê#ò5yµšÄ&óÛƒÛ‰Úã-_Ç:JŽ÷`kÿ˜wÓSÜzë­,_¾µúä–?e™ÜÜ\~üñG–.]ÊŒ3¸í¶Û¸üòËINN&<<œÁƒsÁ0mÚ4æÎËçŸÎŒ3øî»ïذa_~ù%?þ8+W®ä£>béÒ¥Üwß}ÌŸ?Ÿ¹sçòÊ+¯pë­·2sæL¦OŸÎO<Áµ×^˃>ÈäÉ“¹óÎ;¹ä’K˜8q"ãÇçÊ+¯äø_|1çwgŸ}6ƒ âôÓO§oß¾¤§§“žžNBBÑÑÑF†ÊŠ+p¹\mû~4È׊Âóyé‚ù’’–,YÒaüéŠùÅ‹+b€‚íOWÉÿøã¼ûî»<üðÃ,[¶Ìû}ÔÕ é!°†LŸ>={öxã%ÄØIâpý[Z› £Oq÷¹œö͵h’#8~ü8¯¼ò äÈ‘#ŠTYYçƒC¯^½˜6múÓŸˆˆˆhóó/X°@¬·`V®\)Ö 0yyyb-°JC`BÕcæÌ™ìÚµ‹U«Vyóõ·‚Öa]ó3¿ÿßÊÍý|ÊôšrúÏîNÜŸÏmôø’’QT›Ž?Þ%ïRbbb¸÷Þ{ùóŸÿL=‚íŽ@ 1„ zè!ÆOß¾}Ù½{7wÞy'‹-â†n„j,Vr®x›#ßõÃΧ8å¬\úl¸uŒ¡U§u:dgg7*ÊÊÊÚê­VˤI“xôÑG6lX°Ý!B(  ¦£P»8ÑÑÑüå/¡  €ž={òÚk¯yÅ 3úíCĽ÷ûî=J•-CQœ÷KuÿAc# ?3šð‘IÎï…Õäi5÷‰0”——ûF&“ N‡V«mtÛTÙÉnýÙrrr˜;w.K—.Åb±(üw8|ðÁ|ðÁüñä±ÇãŠ+®@’NnEö‚‚’’’NêXAË())!::ºÙà~ÁÉc·Û1™LÄÅÅÛA ¤{€šCôµ-ryYcÞ!ë—AÈÔ?¯a ×P7¦¯Â†QKxT)áIVÂû¨1Ç0,)#ÒÒ {w8I1ÐÑ(..æÍ7ßdÞ¼y6ZoàÀ<òÈ#ÜvÛm ­ë11@GÄxB©H &(0T½þ5¿?ZLµ³u1.jlÉ"œc5Ç ïf&¼—„¡ ¤§y„Qzº'¥¦‚ÎwÈ­#c³Ùøàƒ˜3g{÷îm´^·nݘ2e S¦L!11±=]!€€@Ä]PÊÑ?~HξÁ> «¶5VŒ#¼Vq”pU6†$•¯0ªr»ÁåòlkSkò5û²SÆe·UÂe«Kn›Ê³o—pÛÕ¸ì*Or¨=y‡—SMÄé’æŽE×;ÖûzdYfݺuÌž=›76úº ·ß~;<ò 8¥6HPƒ@Ç4ÿ¿üd7ÆÜHÌaTWÅátû>:2¨±N–GqŒpŽ¡§7z\¼ÉM®›g¿åeî6£“p’8°„”gG3¡¢lÇŽÌ™3‡ÿüç?8ÿÇKãÆã±ÇcÔ¨Q~눠À#b€ˆ Çç±ÇãÆoTü‹ À#b€ˆ `ÇiêÁÐõQ©œt»TKÊŒaD[÷DœÉdbñâÅÌ;—£G6z|jj*Ó¦McòäÉDGG·‡Ë  ¨³`Ï·S½·šê=Õ˜÷š½[G©ÿ!£¶EF­“Qk]¨t.Ô:7jŒJïöìëñìëeÔz•^F&¡6€3·‚‚oÃ0»’›¼BDš“”'ÐýödÔ‘ž§ç\.«V­böìÙüôÓOÉŸþô'~øaÒÓÓÛô• ‚®‡@@ ö"Pñ)ö02ï1{{Žœ¥NTFêp5j£UxÍ~¸ºÎÞ`¿©2UØ).§g±R6}yoR\u&r1Ej½›î·$‘òPÃê–ËØ²e ³gÏæ³Ï>Ãívû?V­fРAÄÅÅKll¬bß_>&&æ¤×jëj¸Ýn ÉÉÉ!77×ï6//˜˜ïšo SZZáááÁ~)x„Bµ">p»±/]CÁÌŸÉË>+M÷ E #ej:Ýnêæa‡æÕW_åÝwߥººú”]’$‰¨¨¨‰¥úù˜˜ z½•ªã¯·l³ÙÈËËkRÜäççãt:OùZ Š£ôôt?Ô `Ó¦MüñÄåráv»[œN¦~Gúy”$ £ÑHDD„"…‡‡®¦ýH$ò÷›)ûëJò~L „ói\Dh"%’îêAÊý)zžž+--eÁ‚Ì›7üüüörÛ¿ z½ÞoªIm™žS§ÓQ^^Þ¤¸)..jÕ'""Â+†ü¥äääN!*[‹ÛíæÄ‰äææ6™BiäÖ¢Õj}ÄQcb©©d±X¸öÚk9qâD°_RÀ¨ „•°=;ŸüeUä;Çb£éIc.Ž!åþ®O@¥Sa·Ûù裘3g»wïn'§D«ÕÒ³gOÒÓÓIHHÀh4zSxx¸"ß›^¯¸Ïf³Ù¯˜©¡¹¹¹´I› mˆŠŠ¢¢¢"Øn!€š@ öAÌQÓ 'N ¿þ¥ÿþ™¼ªÑ”rN““Gjµ$ßLò½É„õC–e¶mÛ†Z­¦¬¬Œ²²2JKK½ûå+++;Ô@°‰ŽŽ¦G¦°°0T*999dee‘MVV–7åää4:—S°P©T­Pþì*•м¼<¿B§©i“P@bÆ.AÐY½zµˆjŠîÝ‘ž–ø¿W¿d Ö—ÿB~ÎPò¹;¾Á Ž"Ù/e“ýr6q—Å‘r Ûò¶ñÀ”ZuY·ÛMyyy‹ÄRý|EE6› ›ÍÖ)”Z­&))I!fRSS}Ns̵ó56+·Ûí&//O!Šê§ììì6‰Ýj n·“É„Édj×ëž,:t„†#*THHŠmí_C»¿zÍÕ‘jþÚ I% $µäÙW+m¨ÀŠ‹lÁì6cqY0;͘fªոܮvóµ« z€š@ô :$.¬\‰üòlŠ #k(c84ñe­ï¡'jdÚD-ºn:´‰Z´Ý´èëöµñZÏmâp8¼b(X)**ªÉž›¤¤¤ÇÕÈnG‘{¾Ý“ ìØòmžýl’V…&Ñ€&V‹6V‹&FãI± ¶1¿m]RRÒ¨@ÊÊÊ¢¤¤¤MßŸŽ‚„D4Ñ$ÔûK$Ñ'Id°]í°8p`iðgÅêcköO²ÐÓГŸÌO¯ÑU¨ „tx23á•W°|µ‹<®¢€+pprJ* MœF!’¼bÉpÒÄkYAŽ='#×›Ôäà ”XE×½&¹ÁíÓ°L-ntÈèjöµØ<û­·éjÖì87C$ ] AÐ1@mÀàÁð^xnsçÒmáÓ˜+cÉãj ¸ŒÍlæÚvý¤Úá G‘ö¶é©Û™¶y¬|;ÛÆ0¢ˆj¾ò)"»¥&áëhºŽ-F´‰h²^ pa @!rj“!Ò„*-ÒÒ gOH;“¼È ØY\Ì5gœ&“'UW×í·$ßÁÑkq£i Š´õ„RCUßV——e¾åMåe¯ ÄXˆ!0A§¥²-‚¹sqçQÉ騉ÁQ“ìD{÷=ùœDµðG5t‘p£¥%è)EGI] ³ I‹§“Èšƒúy-T~lT81¯8šÂ’\z‘ÒS뉜zÛ@­açp4.ХРO}§Ó³­MMå[[·õ¶ÈHÈè˜Á?+:ÎüX"4þ‚P#* ¦MCõñÇÄ|ù%”–Bi6”íôìWT@½û 'Ñ~Å‘áÝä$ vt5‚F!lTåè¢è»kÑ¥èѦG#%w‡ädHêÉ@R’'oôLD‰Û UUžö-/÷lûÇ¡¢WqÎbÎRÎ2ÎJG•ŒÓ¬Æi×ã¬MJñäÙwøù{Zƒ ;aäFŽGàDU–ä"¬·}¿h¤ô4Hë =G{ÄM÷î¬ µZˆõ¤Ž€,׉¢æÄÓ©&»½ÉrÉáÀn2q|ëÖ`·J»  舠¢ÕÂí·SpÙe¾mìv{~”KK¡¬ ©´mYÚš<õ·¥G”6‹pƒØâ(¶¦;_¨kî*k÷UÞÔ|¾5uÕ~“Q¡ÁŒŽâaS#nô&tñ OÒ¡ëiD“W'd’z×l“ [·ÿP—””F£ñô^DG{~ìA]“üJ§ÓpªÍç"—U!»Ü ÕôØI¨êíר¥zû~ëÔvøÕ¯§R#I¾çmX‡°0ÏkM»RS!@,vɵÀ$ÉóªÕÛÊ Ø0th°Ýh„xü¶±JqqžÔZ¬V¨K>‚Éßr’ÔtÞÇæIÆ|*çÁó㜔ÉgÕ‰œÈ¶œ:33“Ñ£G·Íz^ ÄÇ{’$:RÈlûQ\\̶m۸暶g„&"¨ D @ B‰PZ µk à @ ´!€A'î4‚hãÀSRR"ô 0v»ÒÒÒ`»!è"$:«W¯¶ ]ÑÆ'333$ &ÅÅÅlÞ¼9Ønº"¨ D @ B $@Ð…HtBáN#؈6<"(ðˆ A[" èˆø”À#Ú8ðˆ À#b€m‰ˆj$‚PBÄ @ ta„P¸Ó6¢ˆ <"HЖ$:">%ðˆ6<"(ðˆ A["b€š@Ä  ”1@@ ]!€A'î4‚hãÀ#b€ˆ´%B ‚ŽˆO <¢ˆ <"HЖˆ &1@@ %D @ AF AÐ …;`#Ú8ðˆ À#b€m‰@‚ #âShãÀ#b€ˆ´%"¨ D @ B $@Ð…HtBáN#؈6<"(ðˆ A[" èˆø”À#Ú8ðˆ À#b€m‰ˆj$‚PBÄ @ ta„P¸Ó6¢ˆ <"HЖ„¼ÊËËcÅŠlذ‹ÅlwB’÷Þ{/Ø.tyDž5kÖPRRl7º4ùùù¬[·.Ønºš`;L6mÚÄĉ3f ÇÇb±°qãF¢¢¢‚íZHa6›ƒíB—G´qà±ÙlˆÊÀ"Ë2v»=Ønº!Ý4}útfΜɇ~Èwß}‡^¯çÃ? Š/¥¥¥§ôÝš!Ž–ÔmªNceþì m.—‹¢¢¢f¯œNç)Ý¡·æ=2›ÍTUU5Y§­Ú¸¼¼›ÍÖâsšS¹vkߣæ®UUUÕ¨økì=2™L˜L&…Íßç¶³¶qkæ÷EKþ…¿ÏAkhM·ä»%PmÜܹ»2!+€, ›7oæšk®@’$ÆÇúõëƒâÏš5kÈËË;éã,XЦu›ªÓX™?{C[UU•È<•/™ÖP\\ÌŠ+NúøÖ¼G»víbË–-MÖi«6^·nGm²^{µ±¿k·†Ö¾GÍ]kË–-ìÚµËoYcïѶmÛØ¶m›ÂæïsÛðÚÕÕÕít*mÜÚãƒù}Ñð=r8TWW7ëO[àïsÐZÓÆ-ùn T7wî®LÈ>ìØ1zõê…ÍfC§ÓðÎ;ï°páBþ÷¿ÿžÇßÿóŸÿ0`À€€ûSPP@LL ƒá¤Ž?vìmV·©:•ù³7´9N HMMõÚvìØÁСC[äû©`·Û)..&%%夎oÍ{TYY‰Óé$..®Ñ:mÕÆ………DDD`4­×^mܘ-¥µïQs×*--E£ÑøÖnì=*// &&Ækó÷¹mxíßÿž={Þ"ßO…SiãÖÌïQee%ôëׯE¾Ÿ þ>­¡5mÜ’ï–@µqC›Ífcûöí!Ñ+²1@V«µZíµ©ÕjE7äM7ÝÔn?@ tNöF¼³²(>>ðtm×*ü²²2ºuëæ­3`À€véý@JHH --ÿýïŒ;€_~ù…áÇ·èø'NðÌ3Ï——Çé§ŸÎÌ™3½Ci‚¶¥´´”Y³fñâ‹/Û•.É_|ÁçŸîÍ?õÔSŠ¡AÛ°iÓ&>üðC4 o¼ñF°Ýér|óÍ7,_¾Ü›ïß¿?<òH=êšlذÏ>ûŒÈÈHxàzöìl—Nšxýõ×yýõ×™>}:yyyÌš5‹mÛ¶Ñ«W¯fÍÉÉ¡¨¨ˆ3Î8ƒéÓ§Ó·o_&OžÜ^‡6›Ûo¿Ý»w³wïÞ`»Ó%yæ™gÈÈÈàâ‹/ %%Eˆù6&33“þóŸÌ™3‡Þ½{+âµmCee¥w’Ä}ûö±bÅ –,Yd¯ºeeeLœ8‘õë׳gÏ^|ñE>þøã`»uÒ„ìS`S§NåùçŸgÓ¦M”––rÎ9ç Õj}êÍŸ?Ÿ³Ï>›aÆñüóÏ#Ë2©©© 6Œ­[·rìØ1qdYæÆoôû´Ã¼yó¼mü /xçS™:u*O<ñ„˜›©üðÃ<þøã>ö’’n½õVú÷ïÏe—]ÆO?ýä-ËÉÉáØ±cBü´ƒrÇwøØÍf3÷ß?dôèÑÞIûæÎË7Þȯ¿þJnnn{»Ûi¹óÎ;9pà€}É’%Œ9’!C†ðÔSOáv»‰ŠŠ"##ƒŒŒ Þ~ûmžzê© xÜùxñÅY»v­ýÛo¿å’K.aÀ€Ü}÷ÝTVV¢×ë)))aãÆ”••µ¨³ #ÒàÆodðàÁüðì[·Îg6è•+WòÒK/±`ÁÞÿ}–-[ÆÜ¹sp»Ý¬Zµ ­V«¦ø2{ölÎ?ÿ|V¬XáÓÆË–-cÖ¬Y,\¸÷Þ{>úˆyóæñÜsÏqõÕWsÖYgÉëÎENN“'Ofüøñìܹӧ|„ DGGóõ×_sÓM7q饗RVVÆèÑ£‰ŽŽfýúõ\xá…b¢¹&¨ªªbÊ”)\rÉ%üüóÏ>å÷Ýw¥¥¥|ñÅL›6‰'rðàA²²²p:DFFrûí·‹ÞÌf˜?>£FbéÒ¥>S8¬[·Ž'Ÿ|’×^{eË–±~ýzÅðø·ß~KJJ ½{÷no·;k×®å†nàé§Ÿö¹)ÍÍÍåꫯæàË/¿Än·sûí·c05jk×®eòäÉ\qÅAò¾ò/¿ü"gffÊFÞ¿¿¢lܸqòœ9s¼ù÷ß_:t¨¢ÎçŸ.?úè£íâkgeÛ¶mrff¦¬R©äC‡)ÊÆŽ+¿öÚkÞüÒ¥Kå³Î:K¾çž{äI“&É“&M’ãããå¿þõ¯íív§¢²²RÎÌÌ”üqy̘1в£GÊZ­V®ªªòÚÎ=÷\ù­·ÞRÔ›0a‚üÛo¿µ‹¿›Í&gffÊ/½ô’<`ÀE™Éd’ ƒâó=qâDyÆŒòE]$›ÍfY–eùõ×_——,YÒ®~w6vìØ!gffÊQQQò/¿ü¢(›4i’üÌ3Ïxó«W¯–ûöí+˲,»ÝnyÔ¨Qò‰'ÚÕßÎÈáÇåÌÌLyĈòÂ… e¯¼òŠ|õÕW{ó999²F£‘?ûì3yêÔ©²,ËrII‰K’äSvàÀþüç?{ógœqû÷ïgË–-8NÒÓÓY±bcÆŒi7;#µ½8þÚøàÁƒ<öØcÞüàÁƒùý÷ß“9’ýë_w´ɨQ£8tè;vìP”ùäN?ýtÌf3GŽéôÝÚD§Ó1jÔ(ï4õÉÎΠOŸ>^ÛàÁƒÙ·oW^y%sçÎ妛nbÍš5¼ÿþûíæsgäÌ3Ï@£ñý‰:xð 'NôæÌáÇq:,[¶Œ‹/¾Xñ4¯À?½{÷¦wïÞ~ç*;pàƒöæ{ôèADDz½ž­[·’››Ë¡C‡u:#B5CQQ‘"`122‹ÅBtt4+W®¤°°Ë/¿œ›o¾9ˆ^vn mEuu5‹…°°0î¹çž`¹×%(**ò¶e-QQQ¡×ë™?>F£‘eË–‰Ý“¤áwx¾/ŠŠŠxì±ÇxóÍ7Y°`sæÌ¡{÷îAò²óãïûB–eŠ‹‹±ÙlŠ›)ÁÉQTTäótWTT‡ƒùóç³`Ázöìɼyó‚äaÛ P3FEL„ÅbA£Ñ0hРN¯~; ááá>m¬Õj“q tj4lcðìFEEqÕUWqÕUWɳ®CÃï ð|–£¢¢Ðh4L:5Hžu-ü}_€çúî»ï–[]Ц¾/† ưaÂäYÛòAÐÍ‘œœÌñãǽùììl’““ýåNmœ’’"Ú¸ INN&??—Ëåµ?~œ=zÑ«®Err2ÕÕÕ”••ymÙÙÙ¢Ûß111¢ç² INN&''Ç›¯ý\wµùÁ„j†‰'òî»ïâv»xï½÷˜0aB½êZˆ6<#FŒ ..λÐhVV›6mâú믲g]‡=z0räHïÜ3¥¥¥¬]»V|–Û˜‰'òÞ{ïáp8ñ}&L˜ÀÚµk9qâ|ðgœqF×{².ØQØk¯½VNOO—¹Gò™gžé-«®®–¯¾új911QNMM•/ºè"¹¸¸8ˆÞvN®¾újE6Ì[f2™äqãÆyÛøâ‹/–KJJ‚èmçdçÎrzzº/ 9==]ž={¶·üûï¿—»uë&÷ë×OŽŽŽ–_}õÕ zÛ9),,”ÓÓÓånݺÉZ­VNOO—üqoùo¿ý&gddÈ}úô‘cccå'žx"ˆÞv^n¾ùf9==]V©Trrr²÷)/Y–e«Õ*ßxãr||¼œ––&ŸsÎ9r~~~½íœüóŸÿ”ÓÓÓeƒÁ ÇÇÇËéééò¾}û¼å3fÌ###å~ýúÉiii>OãuBz&h@ ¡‰@r$ äH AÈ!@ ‚C @Ð&;vÌ;•A°ÈÎΦ¸¸8¨>‚Î@A$;;›±cÇòÐC)ìsçÎeåÊ•m~=§ÓI¯^½“¶'YYYœ}öÙŒ=Z̸,Z„@AÄd2±mÛ6V­ZÅ7ß|ãµïÙ³‡£GѳÀ°|ùrÎ9ç>ÌÇlwA'@  ‹¢×ëùÇ?þÁßÿþw¿å;wîä­·ÞRØžzê)ÊËËxÿý÷ùî»ïø×¿þŤI“˜7o²,³dÉn¹å,X@uuµâøßÿ‡~˜›o¾™¯¾úJQ¶iÓ&&OžÌ¤I“X¼x1µSmݺ•¥K—’••Åœ9sX¶l™_+**xê©§˜0aO>ù¤×Ï-[¶°xñbŽ=Êßþö7víÚåsìÞ½{yóÍ7)((àßÿþ·w¶æœœyä&NœÈ¿þõ/l6ÿûßÿX¼x±÷ø5kÖ°aÃoþµ×^ãÈ‘#dffrß}÷qË-·ðÜsÏygÏ!€‚.Ì=÷ÜCii)«V­ò)Û·o}ô‘Â6kÖ,***øä“O˜0a‹…+¯¼’Y³f1`À~øáÆŽˇ~ÈìÙ³Ç?òÈ#ôëט˜®¿þz¶nÝ À_|ÁÝwß͈#˜8q"óçÏç•W^Ÿó>ŒŸ/‰Á`ÙÙYÜn7äææX·nÝí‰MMM:tˆááa>}úÄôô4{÷îeff†cÇŽqÿþ}óÑ †a‹ÅÌö±XŒ+W®ðõëWp»Ý¥¬¬ €p8Œ×ëeýúõ”••ÑÕÕÅÜÜÜ’k‘Õ£ ‘ßœa´µµqæÌJKKÍ÷ÓÒÒøöíÛ²úù·×ÇÉÍÍÀårÑÕÕÅþýû—^ø_¸\®oQQQ±¢þú\XÇ077Çøø8.— ˜`hhˆ{÷îñöí[š››q:œ={ÇÈÇã ÒØØˆÍfãÔ©S+®KD~ ‰XÀÁƒÙ¼yó¢µ@Á`‘‘>~üÈ÷ïß¹yó&³³³ÿë: ‹¢ß¼yC__ÕÕÕTWWÓÚÚj~þùógnß¾½ä~+**ˆÇãô÷÷ó ª‡‡‡©¬¬\q­‡¦¿¿Ÿ×¯_“J¥èîîÆn·S\\l^³··—ŒŒ òòò())att”gÏž …xôèccclݺ•h4ÊŽ;–*Edõ(‰XD{{û¢][yyyœ8q‚M›6‘ÍÇMy­Dee%ìܹ“h4j®;êèè ;;ŸÏ‡ßïÇãñ,ÚUõ_¶lÙBgg'ápŸÏGUU×®]Ãï÷¯¸Öòòr.\¸ÀîÝ»ñù|\ºt‰žžs¨°°ôôtêëëù¯ÚÚZü~¿9}÷áÃvíÚEAA………dffrúôé×$"¿Ž‘ZØ‹*"""bËQËQËQËQËQËQËQËQËQËQËQËQËQËùj8¾ý6ì¤IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/compressed-recordsize-shuffle.svg000066400000000000000000001004111416254111300274160ustar00rootroot00000000000000 Disk space taken by a record (original record size: 16 bytes) 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 0 5 10 15 20 25 30 Bytes/row No compression zlib lvl1 zlib lvl1 (Shuffle) lzo lvl1 lzo lvl1 (Shuffle) bzip2 lvl1 bzip2 lvl1 (Shuffle) PyTables-3.7.0/doc/source/usersguide/images/compressed-recordsize-zlib.png000066400000000000000000001175131416254111300267220ustar00rootroot00000000000000‰PNG  IHDR@°AàÚ²sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwX××ðïîÒ»T)R”"*JØ"±$hìÆM°F Q1š`4F£±ÅhL"v‰kŒhì%Æ.± "Ui"*½Ãyÿðe»Šóyž}tîÜ™9{gwö0s玈ˆŒ1Æcˆ¸¾`Œ1Æ{Ý8bŒ1ÆX£Ã cŒ1ÆN€cŒ1ÖèpÄcŒ±F‡ ÆcŒ5:œ1Æc¬Ñáˆ1Æc'@Œ1Ækt8bŒ1ÆX£Ã cŒ1ÆN€cŒ1ÖèpÄcŒ±F‡ ÆcŒ5:œ1Æc¬Ñáˆ1Æc'@Œ1Ækt8bŒ1ÆX£Ã cŒ1ÆN€cŒ1ÖèpÄcŒ±F‡ ÆcŒ5:œ1Æc¬Ñáˆ1Æc'@ÿqqqÈÏϯï0ÞX‰‰‰ÈÊÊzíÛ}úô)ÒÒÒ^ûvÿ«rrrðøñc¹ê!..®^ö«2âââPPPPß¡ÔJAAâââ@DõÊ%55Ïž=«ï0*INNFrrr}‡ñF⨎3þþþ4h>úè#|þùç8}út¥ƒÅ_ý___nݺ???tïÞ]ꀡ££ƒ–-[B]]½¾B}#}òÉ' ­ï0X=;yò$6mÚ„ƒÂØØ¸ÖëkÞ¼9ÌÍÍ宯­­–-[BCC£ÖÛfÒêûØwêÔ)|õÕWøä“OpçΙu8///¤¦¦bܸq˜3g •Ûòå˱eË–:]çÀ1gÎŒ9ò?ö±®©Ôw QïÞ½1uêTa:** ¾¾¾8p þøã¨¨¨ÀÙÙAAA011êàöíÛxñâlmmáèèXå6JKKñèÑ#èëë£I“&UÖ{üø1""" ££ƒ6mÚ@WWðüùsÁØØ·o߯ӧOáéé)µ®üü|Ü¿Ož<¦¦&\\\dRRR===¸¸¸Hز³³qëÖ-äççÃÍÍ­Ú³ÔÔTáùó爋‹X[[C,ãñãLjENN,,,Ю]»*×Sþ½«««ÃÔÔTh³ððpÄÅÅÁÞÞ-[¶êfff"++ –––xøð!?~ŒnݺAEE¾¯HQQnݺ…ÌÌLxyy 휜œ 555Uz¯b±¸Êö(**BDD’’’ ¦¦†V­ZÁÌ̬Ú²²²‘‘+++@RR …ñääd„……ÁÀÀîîî•~€rssqïÞ=dgg£M›6RŸÍ¢¢"üóÏ?xöìÜÝÝ¥æåååáÉ“'°µµ|¸ÊxGŽIb±˜455I__Ÿôõõ)55•þúë/@æææäììLêêêÔ±cGÊÌÌ–µ³³£ 6ÓK–,!CCCºqãÅÇÇ“··7ijjR»víH$ч~(Ô_µj™šš’¿¿?©««“ŽŽ™››ÓéÓ§«Œ—ˆhèСäêêJ¤££CÈÚÚš>|HDDäèèH¥¥¥Â2ùùùdllL›6mªr½£G&555rpp kkkRUU¥•+WVËúõëÉÜÜœ~þùg211!´}ûv*--¥Ù³g“X,&ÒÖÖ&gggŠ– %SSSRWW§¦M›’D"¡o¿ý–ˆˆüðCòððZ&33“ttthÏž=´`ÁRQQ!555¡-ÊŽ×[·n%jÞ¼9™™™‘‘‘={VXÏ”)S„c}“&MHCCƒ.]º$µ­/¾ø‚:tè Ð{jè8ªcU%@DD†††´|ùr"ªœùøø QZZíÚµ‹ˆ¤ ÒÒRš0a9:: gYÎ;G¤þù§œL™2…ÌÌÌèðáÃT\\LáááÔ¾}{òõõêߺu‹òóó…éÅ‹KýxýôÓO¤¡¡Aûöí£ââb*..¦Í›7SDDÅÄĦ¦&íÚµ‹JKK©´´”–,YBM›6¥‚‚‚*ãvrr¢5kÖH•¥¥¥Iý¦¦¦’………T½ò Њ+ÈÈÈHê½÷èу† FDDI***Â^ÙËúõë)??ŸJJJhРA4jÔ¨*c%z™ÙØØÐÉ“'©¤¤„®_¿N­Zµ¢aÆQtt4‰Åb:qℰ̶mÛÈÈȈrss«\ï;w¤~`6mÚD‰„rrrª\fýúõ€ú÷ïOþù'=xð€RSS)$$„ŒéÁƒDôò‡¸W¯^Â{KJJ"===š>}:=}ú”ˆ^þH•}þ:wîL}úô¡ÄÄD*..¦M›6‘H$¢ëׯÑ¿ PÇŽéСCI túôi@k×®ÞëªU«jL€V­ZE®®®•ÊOž<)$¥¥¥K;w¦þýû u¼½½ICCƒ‚ƒƒéæÍ›FD$•egg“ 8PHžž>ž}š&OžLNNN”””Tme™ 6H%1e¦L™B½{÷–*[³f ‰Åb©ú©©©´ÿ~Z³f M˜0A*fOOOòóó“¹ýE‹‘½½=Ý¿Ÿ"""(""BøA¼sçN•qËJ€ˆ^~é¯_¿N?ÿü3-]º”Z´hAÓ§Oæ—%@k×®%cccá‡èe€8 ÄAÂ_Á«V­";;;©mnß¾Œ«Œ•èe4bÄ©²àà`Ò×צû÷ïO¦;tè@sçέv½DD/^¼ ßÿ¾ûî;š5k¨tP+¯ì PE;v¤>ø@ê½/\¸š5kFDD«W¯&±XLiii•–ŠŠ’Žòš6m*œ](K€òòò¤êŒ?^8H—Ù¹sg ÐŒ3hÈ!•ÊÇŒCÎÎÎRe?ýôêÞÞÞR?¼eÊ'@'Nœ¨ôCDD5žÒÒÒ’ªüøqRUU•ú¾Ð… è‡~ ¯¾úŠš4iBk×®%"Å ÂÂB©òaÆ‘ŸŸŸÔ>üé§ŸHEE… iÿþý•þà)“ŸŸOb±X8£WÆËË‹üýý‰èß(""BªÎÂ… I[[[ê– .Ô˜µlÙ’Þy癤U•‘½óÎ;ÂÙ—o¿ý–š5kFáááÂû¾té‰D¢JûOQU%@îîîäææF‡¢ˆˆ:wî¹»»SË–-«ýãÍÏϤÊfÍš%|ψ^~Ëo³eË–´dÉaZV4cÆ jß¾½Ô¾/ÛßIIIFèûï¿—y¬/“])Qjì¸ÐkRZZŠû÷ïcâĉ2ç/Z´ppp€¹¹9Þ}÷]ÌŸ?_èÏŸþ9þúë/;v¬Æîîîøì³Ïˆ™3gÂÍÍ 3gÎİaà ‰d.ãææ†ÒÒR$''ÃÆÆAAAXµj|||`oo’’þŽŽÆÐ¡Ce®+::iii>|¸Ty»ví““SmìÝ»wï¾û.Š‹‹Ñ±cGXZZB, q”Ù¹s'.]º„Õ«WKõŠŽŽ,\¸°ÒºÕÔÔªÜ®ŽŽŽÂ±/ßcFF²³³¡££ƒiÓ¦¡_¿~HHH@rr2nܸ½{÷V»Ž•+W"((ÞÞÞprrúT|ÏÉÚ·ÑÑÑHJJÂ7¤ÊËú`DGG£]»v•ú)•Í€.]ºH•wîÜY˜WÕ¶cbbгgÏjã•åÅ‹Bß›Š±ÈŠ£l^Yߊª>ßebcc¡«« OOO…c+OGGEEE(,,„ºº:Ξ=‹!C†ÀÄÄhÚ´)$IûL–Šï!:: 6l˜TyëÖ­‘‘‘èèhÁÕÕµÒºâââPZZ*³íNœ8Qívcbbеk×j¿'²¬]»Ó¦Mƒ¥¥%ììì0|øp|úé§Õv$þôÓO‘’’‚Ç qDGG####FŒªÛ¶m[äåå)“"z÷îwÞyàä䄟þ C‡ä^O»víðÝw߈ ‰0mÚ4Œ3+W®DXXâââðÑGU»ŽèèhÄÆÆVÚ÷íÚµCff&Úµk‡ `Μ9˜5k–Ô±¾¬ð²c¿®®.ÛQ'@¯ÉÑ£G‘ ___™óÝÝÝqõêUܹs—/_ƺuë0hÐ ©[fŒŒðÑGaìØ±¸xñ"ìíí«Ýæ’%K0{öl\¾|¡¡¡xÿý÷¡££ƒþýûˬçÎH$˜››ãöíÛX¾|9.\¸ 8¯_¿Ž­[· õMMM«¼MØÔÔmÚ´Á… ªQª0dÀ_|{{{üþûïPUU™ÛÍÏÏÇ¢E‹ðÙgŸÁÝÝݺub€Ã‡ 1•©¬CxYÕ·Þz Í›7ÇO?ý„GáÝwß­6ŽÔÔTÌž=¿þú«`&%%aýúõ¯©©)F…yóæU9?66%%%RŠ´£££…„ "##Ѿ}ûj·«¥¥õJãTu666®”t• ™P¾SvMš4i‚¬¬,<}ú´RgÚÚ˜1c „7 ?àüñG¬ÛÔÔظqc•ó_¼x´´´JëËïC///¡<22²Æv{Õ}øÖ[o!<<·nÝÂÅ‹±fÍÄÆÆâ—_~‘YçÎØ¼y3®\¹"Õy×ÔÔööö•’we²··G||¼TYÙwùùóç ­+<<æææÂçaàÀ044Ä–-[péÒ% >¼Òþªxü355E—.]pðàÁ*·³xñbâÊ•+ Ř1c ¥¥…wß}W¨SPP€ìììJ§3¾ þ58~ü8Fމ¹sçVyWÈ;w ‰Ð¶m[|ôÑG BLLŒÔ_³fÍÂ?ü€nݺ¡wïÞHLL¬r›qqq‡ÝÏÏk×®…½½}• Kqq1:„nݺA]]Ož<îV+SqÌ¢Î;ãØ±cR4¦§§#-- >>>¸rå þùç©e«?ÄÉÉ OŸ>•*{òä œœœ„ä§  ©©©•–8q">ÿüsbÀ€Âx-[¶„™™~üñG©úEEEu> áØ±cR‰®H$ÂÔ©Sñã?â×_­qÜ ÔÔT\\\„2yÆ‹ªŠ¶oß^i Ͳä¡sçÎHOOÇ™3g¤æGEEÁÙÙÆÆÆøóÏ?…ò„„ܹs]»v­v»mÛ¶Å¥K—¤Î¢Uüa‘ÅÒÒRæ~éÚµk¥õ…††ÂÚÚZ¡ÄÖËË ªªªØ°aòóóADØ·o_­ö{òä Ú´i#üØ¥§§#33³Vë,ãããƒýû÷WJ ËöaÇŽ!‰pèÐ!©ù>„‘‘œ¥öa^^Ξ=+×>¼sçŽTTÓ>,))Axx8TTTàéé‰Y³fa„ U{nݺ…€€lÛ¶MêxSö¾ÃÂÂpåÊ©ò””©2?Žï¾û®Ú¸äÕ£GüùçŸRǘÇC]]½Æ¤¿¼ÒÒR?~\êX ¢¢‚I“&aõêÕ8|øp¥c¬ãŸNœ8˜˜©ò²ß‡²ÁB ЧO¬Y³•Ú;>>DôZþüϨ¿«o “‘‘yxxÐСC©wïÞdccCªªª4sæL©zûYZZ’ŸŸ-^¼˜>ûì3²µµ:LW¼ ¬  €zõêEÎÎÎ2ûm…„„™™M›6V¬XAÇ'ÊÊÊ"¢—}€ÔÕÕiðàÁ4sæL²´´$¡ïLqq1Y[[“››Íœ9“úöíKFFF@Øfbb"™››“‘‘}òÉ'4cÆ 211:jŽ5ŠtuuiÚ´i´xñb2dijj wET·žžÍ;—¦M›F/^¼ ÒÒÒ¢Q£FѤI“¨y󿤝¯OS¦L–«xX@@™˜˜}öïßO4`Àúꫯ( €š7oNË–-#"Ù}€öïßOšššUÆJô²P“&MhÊ”)ôù矓ƒƒTê«“žžNÚÚÚÔ¶mÛj×WÆÓÓ“Z¶lI3fÌ Aƒ‘™™ Û·oW¹ÌúõëÉ¢RyZZÙÛÛ“££#ÍŸ?ŸæÍ›G¾¾¾R± >œ$ 8¾üòKòôô¤?þ˜ˆ^Þ"‘H¨OŸ>4kÖ,200 N: w7•õªØÿàñãÇÔ¤I222¢É“'S—.]ÈÈȨÆ>@—/_&JII‘*ÏËË#211¡9sæP×®]IUUUèÈKô²PÙÝFåU¼ ì›o¾!±XL†††¤¯¯O:u"tüøq"’¯PÙ݉ew+Í›7ŒŒŒhâĉ4~üx²²²"---Zµj)Ö¨¬Sl™üü|òòò" š3g}ñÅÔ·o_jÒ¤‰Pgîܹ€zõêE‹/¦=zлï¾KD/o€PWW'ooo ¢¦M›’ƒƒƒ{Y ²ŽòeÊ:ŒkkkÓĉ©OŸ>dll\m üü|’H$4xð`Z¶l’±±±pÇ\Å>@¶¶¶dnnNR¯òwOiiiÑäÉ“iñâÅ4lØ0ÒÑÑ‘ŠU‘»ÀÖ®]KsçÎ%jÓ¦ Í;—¶oß.Ì/..&///rvv¦o¾ù†† Bêêê´bÅŠj×ëççGÆÆÆ4}útš?>ÙÚÚ’‰‰ ÅÄÄHÕKII!555êܹs¥uœ;wŽTUUiúôéHáááTZZJ}ûö%CCCš5k}ùå—äïïOjjj”››K[·n%SSSš:u*­X±‚FŒAFFFRwɽ<ÆUìCרIÊêÁjÅÎÎfffh×®üýý±nÝ:¼÷Þ{RuD"tuuáëë ôìÙéé鈊ŠBAAÆ€€ˆD"áåëë H$ 4999(,,„““S¥œœœ`nnŽÄÄDÄÄÄÀÚÚ7nÆ’9zô(D"úöí‹G¡Gظq£°.±XŒ!C† //111ðõõÅòåËahhˆîÝ»CUUººº˜0a455‘˜˜555̘1>>>‹Å4hÜÜÜðøñcÄÄÄÀÖÖ .„‹‹K•ý4\\\àááþùšššèÕ«Ú·o777ܽ{ZZZX´h:wî 888ËvìØQè;Ò·o_¨ªª"55nnnpvvƨQ£ðüùsåûèééaðàÁÈÏÏÇ‹/0xð`Ì™3M›6úîÈbaaíÛ·C$I¡PQQÁ¸qã ªªŠððp8::bíÚµèÑ£‡ÔòîîîhÞ¼¹T™H$‚«««PÞ¹sgŒ5 :tÀ´iÓ0kÖ,,]ºsçÎ…©©)D"´µµáëë+ô¿233«tƤlŸH$téÒvvvøçŸ`nnŽÕ«WÃÞÞ:túò‰ÅbtïÞúúú2ß»H$‚¡¡!|}}¥¾#***øàƒ`aa¸¸8$%%ÁÅÅK—..MöêÕ ÞÞÞÈÉÉArr2:v숹sçBOO-Z´À»ï¾‹´´4ÄÇÇÃßß!!!ÂxU"‘ZZZðõõ…–––°]555Œ9%%%xúô)zöì‰eË– û»â%Ó²X»wOŸ"22"‘sæÌ‘º£££#G222àáá©W‡ ££ƒ cÇŽHHH@tt4š5k† ÀÃÃCªœáãã#³]Ë C~~>\\\ЪU+¨¨¨ÀÌÌ mÚ´öÑèÑ£¼¼ìohhˆ¥K—bÔ¨QÕ®W$¡GÈÍÍÅýû÷Ñ­[7lÙ²¥Ò‘H„uëÖ᫯¾BëÖ­¥æÙØØ {÷îGII zöì ===Œ5 NNNˆG||<ðõ×_ cMYZZ"11QhŸ7J]²&"Lš4 C† ©²Fc$"âº4FS§NÅÇ¥N‹3åÙ¾};fÍš…„„hjjÖw8o¼mÛ¶aúô鈈ˆÙ!º¶aaa!ü€®Y³Ë—/Gll,ÎΔêûï¿Ç²eË'÷@«µõóÏ?ãÓO?E\\\µç66 ¾PII =z„ôôt™óKKK‘””ÄöcJ»_ IDATµråJLœ8‘“93F8KSXXXçëŸ={6ŒŒŒàéé ,Z´!!!œü0¥*--ÅêÕ«1yòä×–üܺu Ÿ~ú)6lØÀÉO ú ÐÌ™3ñÓO?ÁÈÈéééðññÁ/¿ü"œöÝ»w/ ¯¯ììllß¾o¿ýv=Gýzܾ}ÙÙÙèÔ©S}‡ÒàåååáøñãèÒ¥K<ת±xþü9Ο?öíÛK QRSSqíÚ5¤¦¦Âžžž¯åYO¬qËÊÊ©S§„î ¯Ã¥K—››‹^½z½–íý—4èhÇŽxë­·`ff†¬¬,xzzbêÔ©˜>}:ÒÓÓaee…3gÎÀÓÓÇLj#œœÌ2Æc \ƒ¾6zôh¡Ó¯––JJJ„i¡¡¡hÙ²¥0ZŸ>} ££SéV`ÆcŒ59ØÎÎNj¼•ˆˆDDD¼ÖXcŒ±ú¤¡¡??¿úCéE$‹aee… . %%VVVHOO¯t©KSSÂô¯¿þŠ_ý-[¶TzŒ)))000€††Æ+-[[Û:«[]ªæÉ*¯XV\\,ìƒ2QQQ5Žj] ‘––öÊw)²233Q\\\m¿’ºjãÔÔTèèèHݾ\±Þëjãªb”—¢û¨¦m=þ***R£ —©j•Ý0Q¾†¬ÏmÅm?~ü¦¦¦¯åzmÚXÑåëóxQqåææâùóçuÞ'LYŸE(ÒÆò[”ÕÆË pëÖ-¤¤¤ÈûZý Aôú}ðÁÂàyß|óð À2:u¢Í›7 ÓÁÁÁüZbÛ²eKO®Ž"qÊS·º:UÍ“U^±ìÅ‹´zõj©²~ýúÕO]HNN¦~øá•—Wdýý÷ßZmºjã_~ù…îß¿_m½×ÕÆ²¶­E÷QMÛ ¥¿ÿþ[漪öÑ™3g„AGËÈúÜVÜö‡~Hááá5Æ\j{\ú¯/*î£Ë—/ t*›¬Ï"icyŽ-ÊjãŠeÉÉÉdffVm, E£J€fÏžM#GŽ$"¢ß~û¥æѹsç„é×™={ö¬Ú' ×$99¹NëVW§ªy²Ê+–Wz ôëj㢢¢*GΖ‡"û(''§ÒH¬ÕU¿xñ¢Ò(Ìë½®6–µmE(ºjÚVff&åääÈœWÕ>ÊÊÊFL/#ës[qÛ?üðC­Þ»"j»ÿÊñ¢â>Š¥-[¶ÔO]õ9P„"m,ϱEYm\±¬1%@ ¶taa!F…ãÇ#&&¿ýö6mÚ„!C†ú÷ïôôt|ÿý÷xòä –-[†&MšÔøle144Tø‰Ëå•õ³.êVW§ªy²Ê+–I$…\Y—TTTd>ñ\^Šì#---a¸…ªÔUTºì¢Èç¡®Õfۊ¶¥««+ui°¼ªöQÙHÄåÉúÜþWÛXÑåëóx!Ï÷HYd}¡HËslQV״Áö*{ªùüù󑘘KKK¬Y³þþþuuuœ:u ³fÍÂòåËѶm[?~¼ÊG40å4hP}‡Ðàq+_Ïž=ùIÛJfll\o¤²†§A'@ß~ûmµuÚ´iƒ'N¼¦ˆXU.]º„¶mÛÖw ·±òýóÏ?044|åXÍ233qïÞ=´hÑ¢¾Ca @ƒ±¶ÊžËÏ‹eŒ•¹qãîܹSßa0öJ$‰ð°WYRRRàêêÚ(îk°g€cLöîÝ‹'NÀÅÅ¥¾CaL!%%%Ø»woµ Pc «w)))¶ÞëÂm\·† ‚   úƒ1…äççcïÞ½õÆ£ÁÞÆþ;`ÇŽxòä lmm±cǤ¦¦¢_¿~øì³Ï ¿¼è1wî\tïÞM›6ÅÞ½{‘››‹Õ«WöìÙƒÿýïHHH@ëÖ­±hÑ"ØØØrrr°zõjœ?yyypppÀ‡~(´Ãž={ðË/¿ %%æææ0`ÆX¶l „®]» °téRœ>}êêêxçw0mÚ4ˆD"À®]»˜˜wwwlܸééé;v,† 55µšw,«_cõŽû§(·1«Î’%KpîÜ9œ8qBæüÈÈH¸»»ãñãǘ>}:Š‹‹áëë[mÂãÆÃœ9sЬY3Œ;éééXµj`óæÍxï½÷`hhˆ€€\¹rnnnB¤´´4lÛ¶ ½zõ‚¶¶6FŒ .ÀÛÛ½{÷†™™ „+V`Ù²eÂ6/_¾ŒyóæaÙ²eðöö†­­-.\ˆàà`¡Î¾}û0gμýöÛˆˆˆÀ³gÏ_~ù%áááyóæ!33;wFNN`ìØ±Øµk†މ'B$aëÖ­€mÛ¶aôèÑèÔ©>ùä´jÕ +W®¶¹{÷nDEE Ó~~~ ¿¿?:vìˆàà`ó¯\¹‚àà`|òÉ'ððð@×®]1iÒ$ìy§ bU ¦àààúƒ1ö™;w.-]ºô•—ÿá"@ù/3³šc騱£ð^>øàjß¾=•––Ò±cÇH]]]¨7bÄj×®Ô²ƒ ¢:T¹îË—/úý÷ߥʟ={F%%%djjJ³gÏÊ‹‹‹I__Ÿ,X@DDׯ_'&ÔÙµk ääd¡låÊ•äææ&LO™2…¼½½©´´T( $###aºE‹4pà@ÊÎÎÊHMMŽ=*”åç瓎Žíرƒˆˆ hÕªURï§  €ˆˆFMÝ»w—9ˆÈÜÜœ6oÞLDDGŽ!t÷î]aþ¾}ûH$уˆˆhúôéäîîNEEEB©S§ÒàÁƒéUåååIíWY’““ÉLžOÀ—Àcì5êÜøÿ«-J¥­­Xý/¿üØ·otuu¥æ]»v ýû÷—*ëÑ£fÍš…ââb¨¨Tþ)¹rå 444УG©rCCCÄÆÆ"55Ý»wÊ% |||põêU©úFFFÂÿMMMÆÆÆB™••"##¥–ÑÑÑ.%@çαråJ¤¦¦ ëèܹ3´Ë5RXXŠ‹‹±uëVlÛ¶M(×ÐÐÀÇ}ûöEpp0"""йsgôë×Oˆïí·ßÆûï¿ wïÞèÕ«ZµjU©]€—íijjŠÖ­[ e¾¾¾‰D¸~ý:Rmkccƒ3gÎÈ\'S'@¬Þqÿåã6~s¸¸¼|½i¬¬¬0uêT,X°@¸LU&++ fffRe&&&(**B~~¾ÌνÙÙÙ000€––V¥yYYY sáááUÆX>©)#‘Hª~SÿOSSPRRReÌÌLH$Œ1Bª|øðáBB²cÇìØ±{öìA`` ¦M›†7bäÈ‘1bš7oŽuëÖaݺu˜9s&&L˜€Ÿþ¹Ò¶²²²„D¬Œ®®.444„¶‘Ež÷ÊäÇ}€X½ãþ)ÊÇmÌä1oÞ<¤¤¤`Ë–-R厎Ž8{ö¬TÙ©S§`aaQåM-[¶DJJ îÞ½[iž½½=Äb±Ìu:99Õê=ÈòçŸÂÈÈæææUÖiÕªŠŠŠ`oo©WÙ™±XŒ±cÇâ?þ@RR† †åË— ëðòòÂÎ;ñðáCìÙ³›6mÂãÇ+mËÑÑ÷îÝCZZšPö÷ß#77W)ïŸÉÆ P 22 ˜XßQ4l<<<°qãF/û½÷Þ{(((€ŠŠ ôõõ±}ûv™ý£ pèÐ!¼ÿþûضmJJJàææ†}ûöñe®×HDDTßA¼©.\ˆ/¿\++àÑ#@ÆågV¸Šòq×    ((¨¾Cyí [[[©Ä5ÉÌÌDFF,,,*ýÀ§§§#992“EM:>ÄÑ£G‘œœ 333…ÇÍ)**B\\ŒŒŒ`hh(5/77 ÐÓÓ«ô*..FBBˆVVVPUU­q[111PWW‡¥¥¥B1¾Šüü| ??¿Ê:)))puumÏäK`rHHx9z+S|ÜÆ¬.¨««£uëÖ %? §§‡fÍšÉ<»a``ggç:I~ÊSQQA³fÍ^iÐ@UUU888TJ~€—g¶eþA¡¢¢[[[ØÙÙÉ•ü@óæÍ_KòÃ*ãHN»w×w ÷OQ>ncÖXXYY¡E‹õûà>@rúí7`Í@Ì)#cŒ½±æÍ›Wß!°ÿþ9—Sr2pñb}GÑ05†kÍõÛ˜1Ƥq¤¾ ¦Ü?Eù¸™¢ÒÓÓ«Mœ“’’‘‘¡Ð:Ë:W7 aEqqqÕvÚeìUq¤€}û€ÒÒúŽ¢ááþ)ÊÇmÌõí·ßbÈ!ÂôÌ™31wî\aºW¯^ذaƒBëŒŠŠ‚å\­¨¨vvv¸$ã.”›7obÔ¨Q mŸ±ò¸P ÔÔ QXøò.‚'O€sç€r¯aŒ±FÁÚÚú•kááá8sæ Ö¯_¯ð(ÆÊ㨎Žp÷î¿îÙ½› ºÆcÔ(·ñ›#«0 ÏrŸ)};±ÍôšUGVž=«‡¾¾>š4iR©|ôèÑ˸ $''ׯ_‡D"A‡äN’ÊÆÌiÚ´)444„òÒÒRglèС:t(6lØ€%K–Ô¸ƪÂ}€j`o=½§Ÿ>NŸ®¿x"þaV>ncVѤI“žž.¼fΜ kkkÌœ9SîuäääàäÉ“ÈÌÌDhh(bbb°nÝ:¹–UUUÅ|€” AHH†*ó,cu‰ H$Åx÷]é²={ê'ÆS†Ã‡cÅŠØ¿?Œå^Îßß_xn–ŸŸ|}}qìØ1¹—ÿè£#<>55GŽᄽ| LC‡;vü;}àðÃ@ÜÞhqÿåã6~sèªëÂFßFéÛ1Ñ6‘«Þƒ0zôhlذµÚf»vípýúu¹ëÛØØÀÏÏ›6mB÷îݱ}ûv8;;ÃÛÛ»Vq0&þ —Ã[o@ÙÑŸ=Nžüüê7®†‚û§(·ñ›c”Ë(Œry3nßÎÊÊÂÀ1zôhŒ7®Öë Wø¹V“&MÂСCñüùs„„„àã?®uŒÉƒ/ÉAM ð÷—.ãË`u‡˜•Û˜UDD7nŒŒŒ°zõêZ¯¯¨¨§N‚¯¯¯BËõíÛ&&&˜2e ðþûï×:ÆäÁg€ä4t(°uë¿Ó?þÈùÀ_Æ{£üüóÏØ¿?üýý1}út¡¼}ûö˜8q¢\ëøá‡ðüùsÀŽ; ««‹9sæ(‡D"Á‡~ˆ/¾ø}ôtuu«­ŠsçÎáÖ­[ÈÌÌDPPÔÔÔ°hÑ"…¶Ë'@rêÕ 04þÿ»Ž/€?ÿúõ«ß¸|ÜÆ¬¢-Z 88¸R¹ GROU2dTËýÅ7uêTèëë#,, ±±±˜4ifÍšUí-ð&&&®TçÃ?DIII¥‘% ‚ƒƒagg'”©¨¨@CCÞÞÞB_¡7a€Föß#¢ò÷2) .”úwâD $äßùcÆÛ¶½þ¸š7ò%%ã6®;AAA000»†±ÿ‚üü|Tûlµ””¸ºº6Š(s  *=}èPPP?±4$üì|ÜÆŒ1& ôè”"##8~¼þâaŒ1ÆØ«áH**À AÒe|7Xí5†S­õÛ˜1Ƥq¤ a䧪¹œÊäpðàÁú¡Áã6fŒ1iœ)ÈÇ05ýw:+  ­¿x|ÜÆLQ+W®Ä!C„é­[·b×®]ÂtïÞ½±aÃ…Ö [[[$&&ÊU¿¸¸¶¶¶¸té’Pv÷î]|ûí·˜6mfΜ‰ÿýï(**R(ÆN€&‘ï½']Æ—Ác Í‹/¤.ž>}çΦ‘‘‘¡Ð: ’’¹êâãã…»–JKKáââ‚Ý»w£¤¤ÏŸ?ǤI“¤5ÆäÅ㽂aÃ^> ¬Ìï¿yy€¦fýÅô_ÆcÔ(·1«­íÛ·W9¯°°b±*J~@¢X,Æ¥K—¤ž¶{÷n >œ?ãLa|ètí ˜›ÿ;“üñGýÅó_ÇýS”Ûø ²k`k«ü—§gµalÙ²¶¶¶•^ß|óÌúÓ§OÇìÙ³¥Ê¢££Ñ·o_èééAWW3fÌ@ii©\Í››‹V­Zá—_~‘*¿yó&š7oޤ¤$™ËU|Pj||<,--ab"ßÃ_+Ãg€^X  ¬[÷oÙîÝ/˘â¸Šòq¿A²²€øxåo§†»3 777aúСCX¶lz÷î-³~jj*´µµ¥Ê:„ `ñâÅØ»w/Ö¬YŒ3¦Æð´´´àåå… 6`ĈBù¦M›Ð¢E XXXTÛ·çÿûΞ=‹¿ÿþ7n„D"©q›Œ•Çg€^QÅA}y&ˆ1Æþ Œáêê WWWhhh`ÕªUøñÇáîî.÷:>ùä̘1X¶lºuë†;wʽü¤I“pñâEDDDòòòðË/¿È•°oÚ´ ÇŽƒ¡¡¡ðøÆÁ Ð+êܰ´üw:78r¤þâù/ã1j”Û˜U%++ ÄØ±cå:sS: &&Fîú^^^puuŦM›ûö탦¦&Þ}÷Ý—=}ú4=z„÷ß}úôABBÂ+ÇÍ'N€^‘HT¼ñ`÷îú‰å¿Žû§(·ñdÔ( 6Vù¯k×j …ˆ0f̘˜˜`åÊ•µ~kÏŸ?¯ñiîMš4 Û·oGaa!BBB0aÂ…:S>¹¹¹R·Ê3&îT C‡kÖü;úòò¾‚ßÿFû§(·ñDW÷9H,Y²×®]Ã7¤žôþªNœ8nݺ)´Ì¨Q£0{öl¬Zµ /^Ķjž0]XXXéÉï‘‘‘333Åf'@µÐ±#`m >>B'gWWWx{{#$$ñññ044DŸ>}°xñb¥AÄQ}ñ¦Z¸p¡Ô¿²\½ xyý;­®¤¦zzÊ­!Ù¸q#_¢Q2n㺅Ït0Vßòóóa`` Œ¬-KJJ \]]ÅÜ º–:tììþ.(¸¿©bø‡Yù¸cL'@u âÝ`ül0ÆcìÍÆ P6LzúÄ =½~bù/j §Zë·1cŒIkÐ ÐÚµkÑ¥KØÛÛcìØ±øóÏ?¥æ=~~~R¯Ó§O+¼ww E‹§ j}ãÁcÔ(·1cŒIkРЃ0wî\=z;v„¿¿?ž={&Ì?sæ |||0iÒ$áåèèøJÛªøh ¾ &?|ÜÆLQû÷ïÇ×_-L‡……áÎ;ÂôüùóqDÁáï“““1nÜ8©ãpuJJJ0nÜ8Ü¿¿Ò¼ââbܾ}¿þú+®É1è#c5èhÆ 0`1yòd899aß¾}Ruºví áeeeõJÛªxìäIàùóWœ1Æê×Í›7*LϘ1sçΦ8 •É#==Û¶mCŽœN,--ŶmÛœœ,U¾gÏ¡[·nÆ’%KŠƒ1 ”——‡ÈÈÈJƒl>|÷î݃££#|}}!‰^iýíÚŽŽÀÿJŠâb`ÿ~`âÄÚFÞðñ5ÊÇmÌjëÀ¯||¬KÛ¶môiÓ‚¡ÿ꽺§Æ3V•}¨¼)S¦ÀÍÍ }úôÊÞzë-dggãÊ•+=z4úôéƒÂÂB©å¶nÝŠ… J½Ê+?ýò,пÓ{öTCˆ§+O—ïŸò&ÄÓ§ËÚøM‰§¡L¿Š3éé¡ô׌¨¨jã8qâÆWéµwï^™õwïÞýû÷K•`ùòåèÞ½;zõêUi~urssñÁàÊ•+Rå 7n\¥³>ÀËGa|þùçX´h‘ü¨“Çx4FeŸç#GŽ`áÂ…ðõõÅ·ß~ÛhÊF1â¼yópèÐ!œ={¦¦¦2뤧§ÃÞÞ;wŸùB,ïî]ÀÅåßi‰HIŒk=cìMRÛ7&%arÙ©b%2SSCJ5´ ÃÙ³g…鈈üøãØ»w/Œ àܹs¸pá`ذaÐÖÖÆæÍ›­ZµBTTúõë‡.]º`ïÞ½¸zõ*Ξ=[åóÀîß¿V­Z!>>ÖÖÖèÓ§ôõõ±§\§ÉÅ‹c÷îݸ{÷.ŠŠŠ ¦¦†S§N¡G8yò$Þzë-ܾ}×®]Cjj*ðÎ;ïðHÐrà¥5èOLii)fÍš…¿þú çÏŸ‡q5™ˆ,--‘––öÊÛkÓhÕ 9]RìÛ¼ò*cL)\]]áêê ÈÌÌD‡ðñÇcðàÁr¯#00K—.þïåå…uëÖÉý@ÔI“&aøðáxúô)LLL@DزeK¥Gb”‰‡X,Fÿþýáîî‰D‚eË–ÁÁÁgÏž…–––ܱ3Ö`/•””`РAˆÅÙ³g+%?QQQ8þ¼0Ѝ¨(téÒ¥VÛå»Á×þÒ¨oÜÆ¬*D„Ñ£GÃÌÌ +Ë?ØPzžùÓ±cG„—ý(‡ÀØØÛ·oœ>}Ož<Á˜1cdÖOMM…™™"##±ÿ~ìÝ»7nÜ@DDÖ¯_¯PìŒ5Ø3@YYY8tè@WWW(wrrBDD²²²0dÈäååÁÀÀ¹¹¹Ø¼y3lmmkµÝ¡CòWÌΞ<ÌÌjµÚíàÁƒ|›¶’q¿9º4aÏ IDAT`KË–Jߎ¦X¾¿o¿úê+ܼy7nܨõe$---Ë]_EE'NĦM›ˆ >úúú2ë[XX 33jjjBYóæÍáææ&õ@WÆäÑ` T×½ÉÍÍ )))HNNF~~>lll ‘Hj½]gç—ý€Êî-» 6eJ­WÝ`ñ³òq¿9œ´´àô†\ªùã?°téÒjûG*âÔ©ShÕª•BËLœ8K–,Áï¿ÿŽH™¯ÈÑÑÙÙÙˆ‰‰AóæÍ¼<ÛïÞ=xzzÖ*vÖø4ØK`ò‰D°°°@óæÍë$ù)×Ácoº¨¨(¼ÿþûX°`¬­­‘’’‚””dddȽŽÈÈHdgg#==ß}÷nܸiÓ¦)G³fÍЯ_?Œ?­Zµª6‘ñööF×®]1iÒ$ddd ==K—.EFFF•—Í«J£N€”¥btá ãŽNöÿ¸Šòq³Š6oÞŒôôtÌŸ?æææÂ+@»6.\¸CCC˜˜˜`îܹX³f zöì©p,xöì™\g*·mÛ†ÜÜ\ÁÌÌ ëÖ­Ãÿþ÷?´mÛVáí²Æ­QÜÿª½ ¾<77 ,ìßéᆭO¯›¸š7ò%%ã6®;µ½ ¾¡IOOÇãÇáàà ׶ݲ³Uöööuz¿!ãÛà¥ñ %©xh÷îú‰ã¿€˜•Û˜)‹\\\^kòM›6…““'?ì•q¤$ K—€„„ú‰…1ÆcÒ8R’-§‰€ß~«¿xÞdáTk}ã6fŒ1iœ)QÅ'Äóe0ÙÊ? Œ)·1cŒIãH‰† ‘ž¾rxô¨~by“qÿåã6fŠºuëBCC«œðàAÜ)ðLNغu+rrräªODغu«Ì£fffVÛ™—±šp¤D¶¶@‡ÿNU>^¡88R:¾ V3|ÜÆ¬¶‚ƒƒëýÖÿèèh¼ýöÛðõõE|| ãM1dðé§/ÏþÀµk@l,`gW¿q½Iø9UÊÇmüæÈ}‹Ì¿3•¾±¦¦Ãª~¼Åýû÷qåÊ•Jå...ð(Çÿ“H$2o9¿rå Ž;‰D‚#F E‹rÅ———‡Ý»w£W¯^°²²ʳ³³ñÛo¿¡ÿþ•ž ¶mÛ6ˆD"¬X±êêê€M›6ÁÉÉ —.]B§NäÚ6c'@J׬àíýò6ø2{ösçÖ_LoþaV>nã7Gú™tDNŽTúvÔÌÔªM€¢££¥:ÇgffâÌ™3X¹r¥ÌhÆ ÐÖÖFçÎ…²Ÿ~ú ëÖ­ƒ‡‡Μ9ƒÕ«WãÚµkÂsºª£©©‰U«VáÖ­[X»v­Pþ믿bþüù5jT¥eѶm[!ù^>LWW<àˆ)„/½ül0ÆØ›¦ÿþ8xð <ˆ@GG¾¾¾øøãå^‡——bbbðûï¿#..Mš4Á·ß~+÷òعs§Tgæ|ðÁPUU­TßÙÙwîÜAB…AÕŒŒŒ*•1VN€^ƒÁƒ‘èßé›7¨¨ú‹çMÃýS”Û˜UgÑ¢E Þ={ ¢"ÿ…víÚ #@£k×®¸zõªÜË=Ø¿? <<W¯^ÅG}$³~@@lllàé鉀€ÀÙÙ=‚™™™ÜÛe àK`¯…¥%Ð¥Ëˇ¢þ{wUuÿü5û–Lv[Øw° ¢(¨ Å¥Š¥Š”**îKEV¬µ¥V[­Ô¯ÚBq¡b­­XlÑ"(¸€ ‚,ʾ‚!ûžÌþûc É„$$0gî,ŸçãqÉ=³ÜÃ!„ÏÜû¾ç÷Ö[ðË_j×§h"ùõdŒ£‡½¿ì²•ǘҶ_ïË–-ã©§žâÓO?%++ë´ŽÙ³gO>þøã6?ßétrÝu×ñòË/3mÚ4.\È%—\BÏB’ÉÉÉ|ùå—¼öÚkìØ±ƒŽ;rÛm·qÎ9ç´ø!Z"P„\s@-‘ÿ˜Õ“1Ž©ãRI—ªu7ؽ{7Ó§OgÞ¼yœuÖY§ý~k×®¥oß¾ízÍí·ßÎÈ‘#Ù¾};¯½ö/½ôR«Ï·Ûí!+Ö¿ñƤ§§3f̘Sê³H\r ,B&O}£ÑÞ¼vîÔ®?BˆÄVUUÅĉ™6m7Þxã)½‡Ûí®ÿ~Ë–-|öÙg\}õÕíz#F0bÄ®»î:, W^ye›_ûÅ_0kÖ,fÏžÍfk×q…(B²³á‚ BÛ$ $ùõdŒESÿ÷ÿǶmÛøàƒ0`@ývÿý÷·ù=üqFŽÉÈ‘#ùÁ~Àå—_ÎÌ™3ÛÝ—Ûn»Í›73sæÌ“®î~ýõ×sá…2pà@ÆÇìÙ³™5kV»)„\‹ k®Æ—Çßz }T³îD ɧ¨'c,šš>}:çŸþ í:o¿ùæ›™ ><¤Ýh4²zõj† VßvóÍ7³mÛ6úõëǰaÃÈÈÈhóŸYˆÆtÀñ)úDSsæÌ ùzº  sgðùÚ¶n…AƒÂòöBˆ˜={6©©©šÏŠ,D{ÕÕÕ‘ššÚêjùùù 6,!ÎË%°êК~@’Ë`B!DäIaMדHò)‘ c,„¡¤Š°I“ ñihMÆX=É©' NRilÂp:ökj‚EQMv}Š4ɧ¨'c¬žd€Ô“ ')€4–”/¼Ú¶mÜs6ýÑ‚LЧžŒ±z“'O&##CënĵÎ;sÕUWiÝ '¤ŠS§Â-·„¶ýíoðhÓ!„"ÞI%ž{† m»ãعS›þD’äSÔ“1VO2@êIH„“@QÂf ÎÔx¡ªªàÊño“G’OQOÆX=É©' NRE‘`޼ж͛aÖ,mú)’OQOÆX=É©' NRE™nn-X¸³D !„*H…æÍ ž jì–[‚ ¦Æ#ɧ¨'c¬žd€Ô“ ')€¢Ã<ãc³5´UTó@n·výREò)êÉ«' õ$$ÂI  (5thðΰÆ6l€Ô¦?*I>E=cõ$¤žd€D8IÅn¹%8GPcÏ?òa^!„8=RE¹_„>}BÛnº Ô¦?*H>E=cõ$¤žd€D8Iå’“ƒy ‹¥¡­´4xfÈãÑ®_á$ùõdŒÕ“ z’á$P >æÎ mûòKøå/µéO¸I>E=cõ$¤žd€D8I#î¾&M mûÓŸàý÷µéBˤŠ! B û\=äåiÖ¥°|Šz2ÆêIH=ɉp’(†¤¦Â›o‚ÉÔÐV\ ×]>Ÿvý:]’OQOÆX=É©' NRŘ³Ï†'Ÿ mûì3øÍo´éO8H>E=cõ$¤žd€D8IƒfÍ‚+¯ mûÃ`åJmú#„BÄ)€bN‹A×® m~?LŸ±õ|Šz2ÆêIH=ɉp’(FedÀâÅ`46´ÀO,†b‰äSÔ“1VO2@êIH„“@1lôhxì±Ð¶U«àñǵéÏ©’|Šz2ÆêIH=ɉp’(ÆÍž —\ÚöØcðñÇštG!„ˆ RÅ8^{ :ujhóù‚— µëW{H>E=cõ$¤žd€D8I:t€×_}£¿Í#Gàg? N–í$Ÿ¢žŒ±z’RO2@"œâºzî¹ç3f }úôá†n`ÅŠ!ïܹ“ &Я_?&MšÄÁ^b}Ü8øõ¯CÛ>øžzJ›þ´‡äSÔ“1VO2@êIH„S\@;wî䡇âý÷ßgÔ¨QLœ8‘ââb<]tãÆcåÊ• <˜K.¹„@,œ2iÁ£ ¡¦mk×jÓ!„"ZÅu4þüú3uz}ðRX‡ m^op©Œh¾l.ùõdŒÕ“ z’á×PcµµµìÚµ‹œœvíÚÅàÁƒë×ëõ 4ˆ;wjÕŰèÔ)ŠÖéÚrsaÆ ÍºtR’OQOÆX=É©' N SÝyç >œK/½€ÂÂBìv{Ès’““)lrëÔ¢E‹˜3gNÈÖX4î_rIðöøc-ü÷¿ðÌ3ÑÑ¿¦ûó)ÑПxÜ?>ÆÑÒŸxÜŸŸI“&±ÿ~>þøc233C¿êª«øî»ïØ´iŸ}öeee\|ñÅZtW‰®]ƒ‹¦6Îíß3gjÖ¥fI>E=cõ$¤žd€D8Åí%°²²2ÒÒÒNhïß¿?;vìàÿøwÞy']ºt¡°°W^y… &Ô?7–/5öÀÁüOcù Üu—6ýBé˜ñäO‰}ôuuu\pÁ$''Ÿôù©©©'Ógúôé\{íµäææÒ½{w C¸ºUž|2˜ýY·®¡íà¼ó`øpíú%„Bh%f.UVVò‡?üÎ;sî¹çòÈ#ÔE§Ãd2Ñ«W¯¸-~ ~~óMHMmhs¹àšk ²R»~—Ÿ4´&c¬žd€Ô“ §˜)€&NœÈš5k(..æ™gžÁn·3uêTRÿ¯.ZÔ£,\Ú¶g»áMS’OQOÆX=É©' N1•ª­­åÿûK–,aõêÕ\pÁ\sÍ5Lš4IÉñâ%ÔØ=÷ó?½ø"Ür‹6ýB=)3g€^zé%²²²X´h?ùÉOسg‹/VVüÄ«¹sOÌýüüçðí·ÚôG!„ÐBÌ@£Fâúë¯gÇŽ¼óÎ;,_¾œÚÚZ­»s,xë-hœ#¯­ 檫µéS"|ÒКŒ±z’RO2@"œb¦:t(óçÏgÇŽÜqÇ|òÉ' 8k®¹Fë®Åœ>}‚—½Û±C»Ûâ%Ÿ¢žŒ±z’RO2@"œbæ6x€Ý»w³fÍ>ûì3Ö¬YCMM ºÆ³ü‰6›:V­‚—^jh{õU7n¸!²}i¼˜PCÆX½É“'kÝ…¸×¹sçµÀ„81sèÑGeøðá¼õÖ[ <˜·Þz‹£Gòæ›ojݵ˜õÜs0thhÛ]wÏ !„ñ,f  »îº‹ÒÒRþ÷¿ÿñÀ0lØ09ûsšl¶`Èáhh«®æ"¯’|Šz2ÆêIH=ɉpŠ™(;;›ÊÊJž|òIn¾ùfxà>üðC­»ó €yóBÛ¾ý6xgX¤H>E=cõ$¤žd€D8ÅLtøðaÌ’%KHKKãÀL™2…'žxBë®Å¼n81÷óÒKðÆ‘9¾äSÔ“1VoòäÉdddhݸ& N1‚ž;w.&LàÅF·/mݺ•3Ï<“x«Õªaïbß¼yðÕW¡ùŸ[n£¦LÑ®_B!„ 1s(//‹/¾8¤mРAdffòý÷ßkÔ«øápó@6[CÛñ<Ð/~>ŸºcK>E=cõ$¤žd€D8ÅL4bÄæÏŸOQQ^¯—E‹áóùèÞ½»Æ½‹C‡ÂóÏŸØþôÓpé¥plèÃNò)êÉ«' õ$$Â)fÖs»ÝLš4‰÷ߟAƒ‘››‹Édâ7Þ`üøñJŽkµÅ_þ÷ßOh{N¼óŒ¡M¿„B¨•HkÅLÈl6³lÙ26mÚÄîÝ»IMMåì³Ï&%%Eë®Å»ï†aÂٟÆÿ‚1c௅34ëžBqÚbæØSO=ÅìÙ³6lS¦LaüøñRü(4f lØçÚ^W7Þœ0±é¢S•Ÿ4´&c¬žd€Ô“ §˜)€ÒÓÓ)((к ¥sgøøc¸óΛ?ÆŽ…päÏ%Ÿ¢žŒ±z’RO2@"œb&TUUÅ…^ÈŠ+"6×F¢f€š³hÜqGð PcÙÙðöÛ0z´&ÝBF‰”Š™3@úÓŸøî»ïÈÉÉ!555dê͘k×BÓîòóƒ ¨6MZ!„ˆf1‚¾óÎ;¹öÚk59vEiÎ4)´Î<3˜ š:¯BâñƒÓëÖÁ /@{ç¤ÌÏÏ';;;¼!dŒÕ+..&%%£1f~­Æ·ÛMUUéééZwEĨ>TWWWxËÊÊbÀ€Ín* Zp>›þo¥Çˆ%°|9<ôЉýýïÁKa¶ï=%Ÿ¢žŒ±z’RO2@"œ¢º*++cÀ€L:••+Wâ÷û#Þ‡G d~×%âÇf<ùd0û“”úØ7ßç Z¹²íï'ëT©'c¬ž¬¦ž¬&Â)ª  ììlöïßÏå—_Îï~÷;zõêÅo~ó8Ñ~t=dæãw>Šè1cÁÕW/{õïÚ^\ —_O=¥M¿„Bˆ“‰êÀáppà 7ðé§Ÿ²råJ<çŸ>?üáùç?ÿI]ÓÛ’ÙüÊ¡ˆ'Ö ,‚&N m÷ù`öl˜<ªªZD¸Û@k2ÆêÉ<@êÉ<@"œ¢¾j¬oß¾üþ÷¿çÀÌš5‹%K–н{wîºë.6mÚ¤ôØéßvVúþ±Ìé .‘ñøã oòµd œsìÚÕòë%Ÿ¢žŒ±z’RO2@"œbf –òÚk¯±uëV.\Ö÷ž3gc;¶~_ÿïLÖcÄ›>€iÓ é‡4§^{ äò½BD/™( íܹ“çž{€šš~øaxàü~?÷ߨ‹Ÿæ|ó·ýÊë.½Ö¯®%ÖXEEð2Ù£‚Yv!„"DÌ@¯¼ò EEEüõ¯åƒ>Àh4ò³Ÿý,b}ÈøVîk‹ž=áóÏaúôÐö@ x™ìÊ+¡´´¡=>ihMÆX=É©' N1SE=cõ$¤žd€D8ÅLÔ¡CvìØÁÑ£GÙ´igŸ}6TTT(;îÞ5!û_¿¼WÙ±âÑ=÷ÀªUÁ5ÃÛ»Î=ÞxC樉cõd õd N1SÍœ9“G}”0yòdºuëÆúõëq»ÝJ§ø?Ò+ôö¥Ô-”+^\BãÜsCÛkj‚éûï·[›¾ !„HL1S :”mÛ¶ñþûï³hÑ" ÿþ÷¿Ñét­¿ø4Ôö?r¬û~+_.—S°íÕ¹3|òIpEù¦þïÿòq:ƒ3Hß|3üùϰf TVF¾ŸñJ2@êIH=ɉpŠ™ÀãñðùçŸóôÓOàr¹(mœ¦UÀ˜ná`¯Ú¶¯^Ø©ô˜ñÊd‚ùóá•Wš.˜º—+¸ŒÆßþ÷Þ çŸ))ЧL™O<ï½yyZõ>¶IH=É©' N13ÐÆ?~<_|1›7ofÇŽìÙ³‡óÎ;£G*9 4gÎÒ¿ÍáŒwzÕ·ìUÇ {/ ûñÉ7ßÀ¤Ií_8 ++x›ý°a0|xðk¿~Á5Ê„Bœ:™( ÍŸ?ŸÙ³góæ›oÖ·õéÓ³Ù¬ü/êœ[„\ËÙoåë¾PzÌxwæ™Á\дiÐ¥³ [}úéàë ‚ääàŒÓ·ß À—_3FB!DsŒZw ­***0`@³Y,¥Ç>çÒóxµçrºï ^·Ñà‹ùÛyñ¹'y¥hMF¼þzð‡Ñ˜ÍưiSösgpM±¶¨­ ®I¶n]C›^<3ÔølÑСÐ)sìùùùJoÁ PJJ FcÌüZ9n·›ªª*ÒÓӵ1shôèѼøâ‹!sþ¼üòˤ¦¦FäCÙ¡áçæü_T‘¥K—’™ ãÇË¢­[ƒ!课‚^†§Ï=޶¿¯ß;vÀâÅÁ…Y/½4Æv:ƒóýô§ðØcðæ›Á‚KátRš“ z’RO2@"œb&äóùøÙÏ~ÆòåË©««#;;›ÚÚZÞyçÎmzu˜ÏÍ™3‡/Þ_ƒëІ;<:HþÐÆY£äØ¢y~?ìÞz¦hãF8zôôß[§ ^Žëß?xæ¨ÿ†ï{ô8q¡W!„ˆ7‰”Š™sµƒþóŸlß¾mÛ¶‘ššÊÈ‘#q:9þ¹?¼ ¶¿á2ØÚùßIaz}Caríµ íùù¡ѦM°gOûÖ àðáàöÑG¡Y,Á;Òš+Ždî;!„ˆ=1S½òÊ+tíÚ•ñãÇ3pà@S¦LañâÅ"p Pé#tßßp7˜ssgåÇLáȧdgÃe—·ãª«ƒËm/Š6o^;•‰Ã]®àe¹­[O|,##´ :þ}ïÞÁÂ)HH=É©' N1ó/uçÎ'Üê®ÓéX·n¥¥¥dff*ïÃÙ·öÁý߆S =öÚØøÉ× ¿p¤òcdz¥K—*YªÁá憚^!Íχ]»‚!ëãÛ®]°oœÊ}úhÚŸ3oêÿ¿ û=÷ØÙ¼ö~0úLí:ãb!Ÿ’’gŸÜ  7·ùâèСàã§Êå‚ýûƒÛÉúÖRqtü{ȧ[·èãX' õ$$Â)fþ¥îÝ»‹ÅB¯^½Ðkx?òÇñZ÷èv0xL€Ož—ètÄr>E§ƒœœà6~|ècuuÁ3DÍGeeáëCyypÛ¾½µg-Ål¾› å›Õ Fcpi’Æ[¼“ z’á3Pjj*³fÍâ¾ûîã®»îâæ›o&--M“¾É£ÛÁ†»Á’6ÉÝ`§#V‹Ÿ“±Zƒ3OzâcÁ¼Q^9râ–—,jÂçvÜnp»Ãý¾m§×7_ -··öX[Ûõú7®õýSk›Ì믇7ß·ôXÓå›[1Ï9Ý×´ôý©_èÿVãÉ©S¬$$N_ÔÏtÜ믿ÎwÞIß¾}¹ûî»™:u*V«µþñ@ @yy9©©©a;fsó÷Ñ›+0L5×ïûõÐñ‹tŸ}FØŽ/DþM·æÚ[j'r:¢¼ü)­»¡\ÌœJKKãƒ>`Ô¨QÍ>®ÓéÂZüœÌÅ×^Âë® Kn°ÒûaÕ³ëüO)€„hN¢Ü &bOk…R¢9zô(çœó@  ¨ñ£ý€ÒÒRþú׿²~ýz.¹äM/ΣKnÃe0ÛFÉXœ É©'c¬žÌ¤žªy€‡ž½H·X@B\³&êo<|ø0gu:ubÁ‚<ôÐC¬ZµŠÁƒ3wî\žyæÍú6xzèíï½v%±sã6z»–.]ªu➌±z«W¯¦\«9DQQk֬Ѻ"ND}è`÷îÝ<øàƒ<ùä“lܸ‘C‡a4Ù°aS§Ne÷îÝJŽÝZè¸v]IçE=cõ$¤žd€D8E}è®»îbíÚÖ ŠM›6)9v[2@Ï^ºˆa+zÔïïTÉ-['(é“B¡J"e€¢þ~Íyóæ…å}Ö­[GII —]vYHûüù󩨨i›0aƒnó{÷Ÿš +ö{îJf÷·;è;tÀiõY!„jDý%°Óuøða.\ÈôéÓY¾|ù ÿþ÷¿ç›o¾!??¿~«««k×1.¿ñJŽvj¸öoôÊe°öH„OZ“1VO2@êIH„SÜ@EEEäææ¶:qÖ½÷Þ˳Ï>[¿1¢ÝÇù¾ÉÝ`¦ Y-'77—;wÒ­[7ìvû)§Ï”4XÙ°ßk§“;öÒc@ïSz¿D" ¡ª'c¬ÞäÉ“µîBÜëܹ3W]% ¡Šðˆû3@'“œœÌï~÷;.¿ür²²²xâ‰'B_´hsæÌ Ù;¾å-WQíe‹€àe°÷žZÝâóe_öe_öe_ö£aÙ²eÌ™3‡±cÇ2wî\<‰ êï —ûgŸ}¶Åç|ýõ×\pÁ¬^½šQ£FÕÿp4ý¡iɳ?|•au¯ßß9¤œÛ¾ýñ)÷9QÈ:UêÉ«'k©§j-0Ñ ‘îKø3@9’°sçÎSz}ï))¡û;R8´s8º×$Ÿ¢žŒ±z’RO2@"œº*..¹>77—Ý»w·ëøÆ&Ü6‘‚Ž «è½°ì«N»ŸñNò)êÉ«7yòd222´îF\“ §¸/€Ö¬YÃŒ3X±b+V¬`ÆŒ|õÕWlÞ¼™œœ&L˜Àõ×_ψ#˜9s&guÖ)ïû!GBöõ_Ë/D!„"ÚÄýÅê>}ú0cÆ f̘QßÖ³gO.ºè"Ö®]ËæÍ›©««cÖ¬Y >ü´Ž×ý'vø¨a¿÷ŽTrw [ß§õ¾ñLò)êÉ«' õ$$Â)îÏegg3vìØ­C‡†ÛÌ´iӸ馛N»ø˜x×Õvh¸ fòÀÿøái¿o<“|Šz2ÆêIH=ɉpŠûH yƒ›\[—©QObƒäSÔ“1VO2@êIH„“@ t»Ê²ßkG*ùóZx¶B!"M ®¾o EY —ÁÌnø÷ïO\‡L%Â|Z“1VOÖSOÖá$"yƒ¿mX'¡½–H>E=cõ$¤žd€D8I¤H—+Í!û½¶¥’øH ÏNl’OQOÆX=É©' NR)2iÖdŠ3.ƒYÜ:–>ñ¾†=B!ÄqR)¢×ë9<$ô2˜ÿ«4zÝ$Ÿ¢žŒ±z’RO2@"œ¤R(ûŠÐ ÑzoË àˆüGÔ”äSÔ“1VO2@êIH„S¬*Ú»|S~¿Ÿw³>&­¤¡ÎÜy×>nûËMaèB^²¼ ½^On“Ë`¾/RZx¶B!"E Å:\nÙïµ-ƒâ£Eõ&:%Â' ­É«' õ$$ÂI Å®ùÅ5”¦7\e´ÖÁ’ÇßÕ°GÑGò)êÉ«' õ$$ÂI Åôz=‡š¬ æ‘Ë`!dŽõdŒÕ“y€Ô“y€D8IY—êBö{mË ´@Nã !„Z‘(¦<8™²Tý¾­VÇÛË`ÇI>E=cõ$¤žd€D8I&³™Cƒ†´¹×$kÔ›è#ùõdŒÕ“ z’á$P„d\êÙïµ-“²bù$’O‰cõ$¤žd€D8I!×<4…òÔ†»Ál5°ä÷ÿѰGB!Dâ’(BLf3…æ0j?shÔ›è"ùõdŒÕ“ z’á$P¥ýåØk[¥eõ&zH>E=cõ$¤žd€D8ÉZ`­8ݵÀšrÕÕñAÇÏqV4ÔûþßnzzFXÞ_!„8²˜PÂbµrpHAH[ͧrL!„ˆ4)€",åbOÈ~Ï­™TWViÔ›èŸ4´&c¬žd€Ô“ ')€"lÊCWSál¸êè¨ÖñÖãïhØ#íI>E=cõ$¤žd€D8Ia6‡ƒƒB'E¬úØ¢Qo¢ƒÌQ£žŒ±z2z2')€4|‘+d¿çöŽ L!„ˆ$)€4pí/§PÕh%Œ¤Jø×÷2˜äSÔ“1VO2@êIH„“@°9ìz7XåêĽ &ùõdŒÕ“ z’á$F’ÆÕ†ì÷ØÚÚêz£-ɧ¨'c¬žd€Ô“ ')€42å—WS•Ôp7Xr¥Žýa‰†=B!‡@q$'±PaH[Ù*“F½Ñ–äSÔ“1VO2@êIH„“@r\zÉ«ÇÖŽ¸êê4êv$Ÿ¢žŒ±z’RO2@"œd-°V„{-°¦*JËø¬ÛFÕºú¶Ü_çò³ßþLÉñ„BˆÖÈZ`""œi©TÒVº21/ƒ !„‘$ÆlT‡ìú:›¿”XaèDø¤¡5cõ$¤žd€D8I¤±«ù1) W!^¨ü½ƒ¢òÂV^_$Ÿ¢žŒ±z’RO2@"œ¤ÒXJZG§ iË9`eá´iԣȓ9jÔ“1VOæROæá$P¸eþ vžúÉqø‡ƒX°`žF=B!â›@Qâ‡/  ÚÑp)ÌìÓ3=ØV°MÃ^E†äSÔ“1VO2@êIH„“@Q¢÷¨þ]º>XïÝßü.uÞøžHò)êÉ«' õ$$ÂIæj…êy€š³pð{ôÞæ¨ß¯³ÁÇ¿_Æ“÷ÍX„B$&™HhæÂù}¨³6ì[k¡ç‹çóîÎwµë”Bg¤Š2}.ìOєҶþÛSøä_r¸â°F½R+>ihMÆX=É©' NRE¡Ÿ.šÈÁ¾®¶‹V]Â}óîÅðiÔ+u$Ÿ¢žŒ±z’RO2@"œ$Ô -2@Çm}o y“J0»µQLñó[ùõ…¿Žx„BÄ?É Í ¾â ò\Ú¶%ƒ¼ç޲æ|B!N‡@QlÚkW×ÃÒö£Õ“¹ÿå{(­+máU±'>ihMÆX=É©' NRE1£ÅÄÀ?¤â56´¥”é˜öÞƒÌüÏLí:f’OQOÆX=É©' NRE¹3§Ž$÷òг=þéLÒÛI,X¿@£^…—¬S¥žŒ±z²˜z²˜')€bÀuÿ¸”ü®¡w]µêzžü÷|WðF½B!b—@1Àê´Óý7Vº†¶Œbw}ô;¦¾=•Zo­v ɧ¨'c¬žd€Ô“ ')€bĹ3G³|h¾à¬u=¸¶?³–ÏÒ¨Wá!ùõdŒÕ“ z’á$óµBËy€šSYPÎò3Ö“uÔPßv4ÛË7]ÍËÓ_aÒÀIöN!D¬“y€DTJîBÖlCÈ¥°ŽùFüäÌüÏL•Ò®sB!D ‘(ÆŒ½o,û.¨i;ç‹þœ·í~úÎOcr©ŒDø¤¡5cõ$¤žd€D8Iƒ&ýýyLÞɧ¨'c¬žd€Ô“ ')€bPZN&öŸ‡~Òìœgâ¡5Oñø§óÉÁO4êÙ©‘9jÔ“1VOæROæá$PŒºìÑKØsneHÛykÏ`ì¡1Lg:%µršX!„hIÂ@ëÖ­cùòå'´×ÔÔð§?ý‰[o½•矷ÛÝÌ«£ÓU‹Î¢"¥á&>£nøèa Êò¹éÝ›4ìYûH>E=cõ$¤žd€D8Å}tøða.\ÈôéÓ›-€.»ì2>ÿüsFÍ{ï½ÇäÉ“5èå©éЯºÛëBÚrXùkÇ»;ßeÞ×ó4êYûH>E=cõ$¤žd€D8Oþ”ØVTTDnn.ééé'<öÕW_±cÇ>úè#L&W_}5™™™ìÞ½›¾}ûjÐÛö›ðäå,\ù½¿qÔ·]øÙ(Võ>‹ÿ·âÿq~ÎùœÑñ {xr’OQOÆX½Xúð«$$Â)îÏ 6Œ9sæ0jÔ¨Û°agu&“ €¤¤$† Â×_énž–K_BURÃ¥0³f®šƒÇãfêÛS©ñÔhØ;!„"úÄ}Ôšüü|œNgH[zzzH^bÑ¢EÌ™3'dk,ö»ïNÝ UÁþ²€Þ{Üÿ坨þöv~¾üçQÕߦûÇ;úûÇÇ8ZúûÅÅÅüú×¿ŽšþÄãþ£>’Òº?ñ²¿lÙ2æÌ™Ãرc™;w.‡D0KaÜwß}<ûì³õmsæÌaÛ¶m¼õÖ[õm_|1'Näž{î©ÿáhúC­^ú?z~g«ß¯³Áìïfs‡­¼9ùM®|†½kÙ‚ äb2Æê½ýöÛŒ7Nn…WèÈ‘#¬_¿^.ƒ)$Ka$ˆ¬¬,Š‹‹CÚ èØ±£F=:=þµ7µ¶†zÖZ w®þ=·þ÷V”Шg­“ÿ˜Õ“1VOæRO2@"œºúáȺuëꋠdzgÏ.¼ðB{vjzéGÙÔÐe2úmwrïú_Pî*çº%×áõËmºB!DÜ@kÖ¬aÆŒ¬X±‚+V0cÆ ¾úê+ú÷ïÏõ×_ÏèÑ£¹ãŽ;;v,>ø`Ìž˜úòUìï i»dõe (íׇ¿ä7ÿF£žµ,NµjMÆX=™H=™H„SÜ@}úôaƌ̟?Ÿùóç3cÆ zöìYÿø¼yóX¸p!#GŽdñâÅ<öXì­¥Õ˜N¯ãìg»à27\ sTë¸{Õ“<¹æIVí_¥U÷š%sÔ¨'c¬žÌ¤žÌ$Â)aBЧ"ÖBÐýãºwéº8%¤í‰ÿâÅaóé”Ô‰-wl!Óž©Qï„BD# A‹˜7uÑÈíº¬Ç«'Ó½¢ ßW}ÏŒ¥3´é˜B¤ŠSF‹‰3þ˜‰·Ñ\ßÎr³>ž À{»ß㹯žÓ¨w¡ᓆÖdŒÕ“ z’á$PûÁÕgrøÊ²Ð¶o²ùÙÖübå/ؘ¿Qƒž…’|Šz2ÆêIH=ɉp’ P+b9tœ»ÚÅÛ?¥s®©¾­$ÃÏ7]C£˜~ýøæ¶op˜­¼‹BˆD  7Ì ½›„¿Ñßtz±ž?y€]Å»¸ûý»5êB¡ )€À97žËKB/…ø:‡);ƒKc,Ú´ˆ7¾{C‹®’O‰cõ$¤žd€D8I” ®}íbŽf7ürÖ`òª[I« .{û²ÛÙWºO“¾I>E=cõ$¤žd€D8I” ™Étþ•…€®¡­ÃQ~ú4® ®[räW–uªÔ“1VOÖSOÖá$P9ÿ®óÙ76t­°Q_ôãÊ}W°.o¿Zõ+-º&„BD”@ fÊëP’é¯ß×`ÚG?Çî±ðôÚ§Y¹oeDû$ùõdŒÕ“ z’á$P‚qvJ%ù~H[§<}öG¸þß×SP]±>I>E=cõ$¤žd€D8I”€Æ?üCöœz)ì¼Ï‡rÑ¡±äWåsÃÒ™)¢$Ÿ¢žŒ±z’RO2@"œ¤JP_=‡²Ô†3AF/Ìøh6Ÿ€å{–óÌÏhÕ=!„B))€TfŸŽ˜î ½ã«ÛA ~þxýþÃ>Ì •÷Eò)êÉ«' õ$$ÂI  vÅ㗲笪¶ó?ɹGÎÀã÷0ó?3¹÷÷âõ«ûÅ.ùõdŒÕ“ z’á$kµ"Ö;™#[±aÌ^’+&Ú×§†[§MÀ§o¸DvQÏ‹xkÊ[dØ$ã „ñJÖ £ó9xnª iëµÇ΃ë~Ò¶jÿ*Î~él¾+ø.’ÝB!”H0éÙ+ØwFmHÛ…a|Õ¸¶}¥û8wá¹,ÝÞË)‰ðICk2ÆêIH=ɉp’HpÑ ý¨µ7ì[ëàº÷ä’? y^•»ŠIoNâ±O Ûmò’OQOÆX=É©' N’jE"d€{ó–ÿÐñegH[a;'~Å£9áùW¼šWò*“#R]B¡d€DBºæ… XÒ–U``Ì‹çñÂ;ï2¢hXÈcK¶/aôÂÑ(;Á^ !„§O QO§×qÞŸs(ÎòŸðX¿-Nžxñæ|ú,Iž†ke›nfäK#ùäà'§|ÜDø¤¡5cõ$¤žd€D8I$Bô»x—|7‚}?*Åk }ÌâÖqáªðòÂw™¶í§õíE5EŒÿûxþºþ¯§tLɧ¨'c¬žd€Ô“ Pù|P\ »wúuðÁðÆØ-â ç䯒jE¢e€šÚúÞÖý"žÛlÍ>þÝŠxñü_ómæöú¶ÛFÜÆŸôgLzS¤º)„‰)€Š ()ÒÒàÖÖï++ƒ¯o§Óópz6,ÊÓàYÆ“?E$ªÁWœÁà+ÎàÝ_¼¡•´’ІC6gòÇóY3ú+ž9oµ†:^ØðÛ ·±äÚ%dÙ³4ê¹BœŸÊË¡¬,ø½×{âÖÞöSyMãv'ØŸÆELYøOŒ,4§ÜšÁ÷)=)H:“²®¨2vÀ¥ËÀïKAïNÂ\kÃ^e"¥\¾Ç§Š8:H$NêÇü³ÊxûÆOèþa _ÃcÖ:øáGç0xûø÷/ñ¯ÿâ³CŸ1òÅ‘,º”aÙÃZ~ãcòóóÉÎÎVø'2Æê“’’‚Ñ(¿VUq»ÝTUU‘žžÞú«« ˜òòÐïÛòµªªõ÷nƒïÓºS”C‰­ ælêô™xý©àubª³c«2“\aÀ\ÔGÁIpKtò/U´‰³S*7-ÿ1›ßù†MÒ}‡%äñNGLܹøNÆ ¿šùçÿŠìaôßF³hâ"¦ šÒê{/]º”Ûo¿]e÷žŒ±z«W¯fܸqddÈr1aQSEE![ÑÞ½¬ß¾«:vl½ˆ‰ò0ºO§§ÚšJ¥9…jK Õf'u&'µÆ$ê \^½ÞŽ+>¬à·¢ó&cp9°V[Hª4a«Jƒ›×9÷2}bă%ÔŠDϵæûÞÃðª”2Ý ÕÚ|2æsžõ.ƒ›_]ð+÷:N|®"¸\'3­nÅÅP[{ò÷UÈ«7RiI¡ÊšFµÙIÙI1 —ÉY_¤xtv¼z;>lø°âX,èüt>z¯ƒÇ€ÉcÄäÖcvë1×o(‰f>û–|õ­»¡œœ§dÒ³WPr_!ÿžù9=W§ÐhÝTlµ:.[9š¡ÛÿË’± xœÇùöè·¼6é5’ÍÉÚuZqújkƒJIIðkqqháÒ\A£ø’’Go£Ò’J…5•jK*ÕæjÉÔÛÜz½/6üÇŠüt>3z¯ ƒ×„ÑmÀä6`qé±Ôé0»Úc[#Àql‹%n3T¦ë¨ÉÔãÎ2@G†&¬,$w²ÖÅFVW;zS%kGO¤¢Eé=²¸ùóá¯Ùúërö„^ërØÌ½ÿ¸— ΜÌ_.x„sKÎåÝëÞ¥wZïçI>E=cõb.ä÷ôNj˜ÆMk[;ÏÌøtj-™T™“ƒgQLIÔ™’©3Øq¸ô6<½ ¯Î†WgÅOðLŠ?`F°€ÏˆÎoÂï àñxIó¦6*V~k¦X±Ûâ•_åiP}¬¨ñw0¢ï`Â’m&©³•ÔÎV2»ÚÉîb'#ÓÚ¦÷ÌÏ×öÌ[$ÅÈ¿TÍF\7’3¯ ðöÝ˰¾žDrEèéÝaßtæù¯ðñ˜O]v¯_÷O.îyqýã’OQOÆX=M3@55Í+­5-ÞA¤£Æ˜L™½åÖ *-éT™‡PãHÁ•’Š[ïăÀNÀgCçµ`ðš1x=zŒ^F·‹[Ùºà:¶5aì'6·¨˜bv²›~œwJÃ-:p[t¸¬Ü6›¯M‡Ï®Ão×°ëÁ®»ÀÁaÀè0`Ë2“ÒÅFFÙ]ídu´b0$F^GɵB2@íW´ç(Kg~IïOS‚¿øšÈÍqñöØù\|çÅÜ{ν‘ï ±¤¦rsOÜ„£GŠ™º†%l¼:+¥ŽŽ”Y3¨°¤SeN£Ú”†Ë˜ŠKŸŒG—Œ/`'à·¡óZ1x­Ý&,uF¬µzl5ºfÿí&š€\Vp+PÜ6ðÚ‚Šß®Çotv=z‡½CÑaÀ”lÄœdÄâ0`I2`K6a?¶9’$%›HN6£‹ÒP"­&g€DXeöéÈÌÌW¯|ÁîßUÒu¿9äñn‡,Ü÷Ú,6n?Ì=3ïâO7ÿfƒ¹…w"޹\pøpóNnnð±’p„e·n ×ÚäSëI•pÑ»£xvÍß8ó‡ƒéøgM8ƒƒÏÆ 3h×ñ8$ 0òû!?¿¡9trsYýå—Œs»Éøþûàå)¿ŸzJlÝ8˜ÖŸG/*LgSÇ•’†Õ˜Œ³Ô„µNGÁp2µþ³ã²ê¨q¨KÒávêñ&ëñ9õà4€S!Õˆ)ň%Í„-Õ„-ň5É„ÕnÀ–dždÄ‘d‘dÄn2 Ó©”#Gް~ýVÎ7.Ÿ‹O Uîªúb¥þûFLÓ¶ÆíMÛ\¾f‚Y€³{bL“( VH(¼Ö,øŒƒ¨£Ë¡–× s›áû®5TfÅ—q€¤nùdw’yöz EGGÇöX$¤ââú¢æ„íСà™c‹Eú1‘—Ò{ý^Šk‹)¬.¤ º€ÂšB « ë¿TP\[ŒÏïÃð €?àÙж¶<§¹çt:ƒ³ÁŒÅhÁb°4ûÕl0·øX[_¯C‡Ûç®ß\>WÈ~s›ËÛ†ç´á}Žoþ@Û–º'ç§NÊ?Šÿ…}å ˆ˜1·ŸÏ¨=¼qËÉx'{õ‰Ï1»¡û>;ìë ôNÆÎx˜IDAT ÖÛ»T±%éüÖo19¶`ìîB7xÉgŽ¢{ÿ³é›Þ—$sRdÿ@"öTVžXÐ4-rÝæíÖÛ8”6#ξ[Ï£:y2¾Þ™kSH*·â,7 +Ê!™àn”fê¨ÈÖáêjB×ÍŒ5Ç‚-ÓŒ=ÍŒ#ÍLrº™ä4éRŒ­œqñø=ÕÕ1Õìª.¤°°Â mÇ/­-%€|NñG QF‹‰Ÿý}¹³ðßÛ60èë J(!–×ö±Õ@ÝIÀÀcÛ5T¤(ZSN™í ïóäzWSž^Di¯ÎøÀ6ì,ºõA¿Œ~ôLí‰QŸØ?êqŸòûƒ™š#GNÜn(tÊC?ÕVYR9˜6ï“ûRb9—Ú>ð{30W'ã,·‘\®ksþ¦‚ 80о˷. ”tÔQÝÙ€·« c7 öV2zØéÜ;‰½$[[þùuûÜÇÎÆæ`q!…‡Z>SSXSHY]Y»úU|€°iÝ‘ø`Ôq˜8Ì’ÌI8LL˜ØUµKë®E„\k…\Sï_zƒý}9ãô?¡c®“ôâSŸÓ¢$ÓOi‡̦½t©Þ@NéÇä%²½ƒž‚ú÷Á8x(é=’“’C÷”îä¤äi–ä…: ,ˆÝ Pqqó…Mã-?¿ÙõŸŠ’:q(­?ùŽÞ”Yr¨#ÜéXª“I-1㨠_tö>a8Ãq6Yf²"J;é©ëlÀßÍŒ¥›…äž6²z:èÖ×IN¦Fglü?%µ%­>JAuÕ­jøþøvüLM…«"l†¨W úkÝ‘È1è ˜ fì&;N‹3X¨˜8LŽ6}߸¸iú½Åpâ4‘‰t¼@­(òªâ›¥[9¼¶ã.#Ùû,8+Ní?©€ ²½Tfb3ì¦gÅ×üàȧ¨`oìI‡½é›i¦2§#þ^=±õìG×´îtOí^_$uqvÁ¤o9·$NQEÅÉ ›#G‚·‹7£Úâä`Z?ò“{SbíN•!¯/ƒ+[•ÔÖºf_6~=wÐQÑI»› ]W¶+)½ìd÷tÓ'™N©6\Þº š¦íE5EøÑsŽIo"ÓžI–#‹ŽdÙ}m´ŸjME¯ÓŸ°étºÛh¦­Ï ¸|.\^Wý×㹚Æmáx,@ >Kd6˜ÛµÏµë5ÍG¯‹ìD‡R @  h±{G9Û×RôYºÍudïÖa«9µ¢È¯‡â /Ui5ø¬ÅXuGHwí#§b;½ 7cð×p 5XíI‡½i°/CGe·Žzö sFOrRr‚ÅQ£"ÉiIŒ»&NÊç fhòóO^ØT7;Æ«7’›Ö—¼”¾ÙºSeê‚+îT,ÕRÊÌ'Ì8®‚×%tTvÖãéfBŸcÆÑÃFzoz'‘”]K¹+ŸÜò\ò*ó8Zu´ÙB§Ò]©¼¯meÐÈ´gžPÄ´´ŸjM•…ŒH"@‰ŒQádù”¾Rè; föÀçó³}K;×R¶®óÆZ²wù1yN~,½² d:'Á õhŠ€B”fø©H«Ág+!Íõ=ãí§ËwÛé]¸ ‡ûKòœ_ÖFŸ;ƒ´7 ;9IïØ#äÒÚñ)'%‡NI"÷I®¢ ‚™˜‚(( ß>² †àÝKnwðëél-½G?Oå§t'7gŽžT˜»QKo¦ÚdåfÒJ èeoR n*xLP’­£ª‹_WÆî’zØHïmÇÞÍ‹)¥sõj*r9\q˜Ã‡É-Ïåð¾ÃämÎÃís7¼Y `"¼2^§'Ã–Ñæ‚&Ý–³Ûí¦ªªŠôô–3ƒB´•@Bsí£Æ`Ð3dx:C†7üt»ýl^WȾÏK¨üºûfÙ{}!«ÔŸŒ.éEzÒ‹’€$ 8‡2`P– <­¯½³?Ÿó¾?Àä];è[¸‰”Úb [Ø›¶¥þÒÚ²cÅÑÞt(M6ÑÕÙ5ä¬QNJ³Ò7½/ZîXMÍ M«ß7sÉh)‰P@§§ÌšF^Z¿àÄ~ÖªõÙø|ëœØ*m¤•1»sJõªÚu6(í¨£¶‹ŽSŽ…äžV,]üè²+¨µçQPu¬¨9^àTärdÃÜëÜ'?@c€„eÊå$sèèèüšüÚ¸íxA“aˈøe­±~ýz®ºê*­»"â€\k…\‹m••n6^ÈÁ/J©ÛPMÒæ::(Yç¨"%@YFGFC>)žýt®ÚIï¢ÍdT¥ÒÒP Õ_ZK£:TC7·•þ¾4z¸íd×Ȩò‘\V‹©¤ ]uMXúèÓé©¶¦†¬Ê]kN9¶*w.ƒÞŽGoã³áÓÙð̰˜Ñùƒ«rë}&ô^F¯ƒGÉ­Ç|láK‹;rg¼&¥YP­§®“o' “Ÿ@¶ ]V5Œ2\Ö" ª Èmt'¯"¿ § ÃH¯Ó×_vj\À/lš¶ÙŒr›“І\"$'›siÆ\Ú¥¾­¦ÆËžíeäí¬¢dgu{ëÐïs‘|ÈGZ¾ÿ”'­s–ëp–ÛÞŸÛ N ð-P™,Ž\IåGé[vóóvѽd+:Ôš’¨5%á2Úq6ØØg°áI¶ãI±áÕ[ñé¬x1ãÓYðc!0CÀ3Ÿ߈ÞgÄà3¢÷0zô<:Œ^=f·³[¼DX{lkÂ|l‹=”¦AIe™.Ê2«)I+¡8%ŸBÇAŽX¶‘§ÛF•·™lMá±-’ÌItsv£«³+]]ÉNÊnölM¦=3aÎÒ+¤š‹ä5v»‘3Fdrƈo}÷xüìÝYΡíﬢfO-ºnì½dæ0zNíÔQr¥ŽäÊãÅQ6ðÜÀ'%±žÒÑšw²¹–"­2Žf¹(H¯¦0­‚‚”BŽ&¦0)—ûŠ,»ñê[¹…+7I%›“é–ÒPÜ4.tŽ·§XRꟲ˜PB2@"œä_ªÐ\´¬Se2é0$CNœîÎçós`o%·•S¸«Šê=µö¹°ô’q8€¥.º¯$¯a W™ÜD 2=dÔR^NAJ1Îï)H:D}¶¸ ÚÎ]ã´8[,jŽ··÷ξիW3nÜ8222õZHH„“d€Z! ÑV¹‡ª9°­œ£;*©Ü]ƒŸ ËAé‡|Í.ù¡…€.¸ÖZ­ÕË ÎâÇeñQgöâ2û¨3{p™=Ô™ÝÔ™]¸L.êŒuÁ¯¦\ÆZêŒÕõ›ËXI¡ —¡’:Cy𫱊€ŠU tèp˜8-NR,)8-Î-ÅšBš5­¾°9^è$›U,Z!Dì“ ¢]ºå8è–ã€ËN|ìè÷5ìÝVNþŽJ*öÔàÝ[‡ù€‡”zæ¢Ñ¡#ÉœtB±rBÓLQÓø¹ÉædÉÖ!N‰@Bsñ¾NUÇNv:v²ÃÅÂú¾^¿—ýeûÙU¼«Ñ–Ï®â]äUä….`YEðÎþSd3Úê§Ö·›ì!ÓìÛMöï“ÌI$›“[-j’ÌIqW¸HH=ɉp’©BsÑ’Š5F½‘¾é}é›Þ—+ú^òX§†=%{ê £ÿ½ñ?†ŽR¼œ¬iü}¬NœI’RO2@"œ$Ô É !„H$‰”НsÐB!„m Ð\"|ÒКŒ±zÅÅÅx½^­»×Ün7%%%ZwCÄ )€„æ–.]ªu➌±z«W¯¦¼¼\ënĵ¢¢"Ö¬Y£u7Dœ P+$$„"‘HH!„"ŽI$4—Ÿ4´&c¬žd€Ô“ §„.€rssùòË/C¶;vhÝ­„ó÷¿ÿ]ë.Ä=cõþóŸÿP\\¬u7âÚ÷ßÏòå˵ =â¼yóxùå—éÚµk}ÛèÑ£™7ož†½J<555Zw!îÉ«çr¹H¥Z@·Û­u7DœHè3@“'OfÓ¦Mõ›VÅOIIÉiýÃnÏ%޶<·µç´ôXsíMÛ|>………'=¾ ^¯÷´>¡·ç都¦†ÊÊÖ×Þ ×—••ár¹ÚüުαÛûwt²cUVV¶XüµôwTUUEUUUH[s?·±:Æí}½–¿/ÚòïH•æ~Ú£=cÜ–ß-ªÆødïϾŠÿùÏ8räÈ)¿~Á‚a}nkÏié±æÚ›¶UVVòú믇´Î/™ö(**â_ÿú×)¿¾=G[¶laíÚµ­>'\c¼|ùröïßßêó"5ÆÍ»=Úûwt²c­]»–-[¶4ûXKGëׯgýúõ!mÍýÜ6=vuuuÄ2@§3Æí}½–¿/šþy<ª««OÚŸphîç =Ú3ÆmùÝ¢jŒOöÞñ,¡oƒŸ={6ùË_°Ûídff2mÚ4~ùË_¢×ëÂ9sæ°xñb  ¼/ùùù¤¦¦bµZOéõ Ga{nkÏié±æÚ›¶y½^òóóC.;nÚ´‰aƵ©ï§ÃívSTTDçÎOéõíù;ª¨¨Àëõ¶ºhc¸Æ¸  €¤¤$ìv{‹Ï‹Ô·ÔǶjïßÑÉŽURR‚ÑhÄétžðXKGeee¤¦¦Ö·5÷sÛôØ;vì [·n8Ž6õýtœÎ·÷õZþ¾húwTQQA~~>ýúõkSßOGs?íÑž1nËïUcÜ´Íår±qãÆ„8+”ÐÐÁƒÑëõ˜Íf¶lÙÂ-·ÜÂm·ÝÆÃ? ¡I(Z!D"±Z­\vÙeZwC¹„.€šúÿíÝyPSWûð/›P„²(‚* ZT¬TePJÀŠc§ê¨£•ZFJÝp@êVÔ*L…Z‹(q[K*®-ê¸!’°(K-"XQ@Êòüþp¼¿7 }‹¾Hò|þ✓›|9f.7çžlÞ¼¹¹¹8}ú´ª£0Æcì Òè»ÀÚÛÛ…» ¼¼}ûöíÔ±ÕÕÕˆ‹‹Ão¿ý|öÙgèÕ«×›ŠªÑêêꘘˆ7ª:JtâÄ äää혘…zX×ÈËËà««Ëwš¾¹¹¹8tèÐ2dV¬X¡ÂD=Ó™3g ccc,Z´ Pu¤×¦ÑW€ÜÝÝáïïGGGܺu {öìÁùóç;µV¢²²555>|8¢££áèèˆ tCjÍÒÜÜŒyóæáÖ­[¸s玪ãôHqqq°··‡ ÿþ\Ìw1©TŠM›6!))  ëµXר¯¯6I,,,Dff&RSSUœªgyüø1$ ~øá`ãÆ8xð ªc½6¾ ,.. 8}ú4tttàãã ¥Ç%''ÃÃÃnnnX¿~=ˆ¶¶¶pssC~~>îÝ»'üñ`ˆÒáÝ;wîæxÆ Â~*K—.ŪU«:\ÄÊ:vñâEDFF*õ×ÖÖbîܹ2dÞ}÷]üòË/ÂXee%îÝ»ÇÅO'Éår|øá‡JýX¸p!†Љ' ›ömÛ¶ !!!¸víªªªº;®Ú ‡L&SêOMM…——FŒ˜˜´··C$ÁÞÞöööØ·obbbTXýlܸÇWêÿùçŸ1yòd8;;cþüù¨¯¯‡¾¾>jkkñÓO?áñãÇ8p  w!b”””DãÆ#$“ÉÆ233ÉÎÎŽ®\¹B·nÝ"WWWúâ‹/ˆˆ¨­­"##),,ŒŠ‹‹U]m$&&ÒØ±c €Ò\}÷Ýw$‹éêÕ«tóæMrqq¡íÛ·ÓçŸNÇŽ#"¢1cƨ"¶Z©¨¨ ˆˆ²´´$???¥q___Z¼x1•••QJJ S]]={–vìØAkÖ¬!OOOjnnVAzõP__O‹-¢³³³ÒøÜ¹sI"‘PII =z”ŒŒŒH&“ѨQ£h×®]”••E^^^TPP ‚ôêãË/¿$@W¯^U;uêõíÛ—.]ºDwîÜ!Z·n0.•JiÉ’%ÝYíäääPPPikkÓž={Æ*++ÉÈȈ233©¤¤„ÂÂÂ(00ÚÚÚhùòå´lÙ2rrr¢¼¼<¥ï\ÑÕ«WI*•’®®.)ŒMŸ>’’’„vzz:5Já1999ôñÇwKVuuåÊ’J¥¤­­­TùûûÓÖ­[…öþýûiôèÑA¡¡¡Jæææôé§ŸvwlµR__OR©”"##•  ²²2ÒÓÓ£§OŸ }cÇŽ¥½{÷*<.88˜nß¾Ý-yÕQss3I¥RÚ¼y³RôìÙ3200PxK$Š¥ &Pcc#íØ±ƒRSS»5·º¹~ý:I¥R‰DJPhh(ÅÅÅ íï¿ÿž‰ˆ¨½½|}}©ººº[󪣒’’J¥äîî®T%$$P@@€Ð®¬¬$]]]ÊÎΦ¥K—Qmm-1¢[3w5^ýÒÛo¿ ÐÒÒR“ÉdX¶l™Ð>|8ŠŠŠpá´¶¶B,#33~~~Ý–W=@Çs,—˱råJ¡íêꊻwï*lBæåå…øøø7TÃ××ÅÅŸ~ýºÂ˜\.‡X,†‘‘‘Ðçêꊢ¢"9r...hllDii©ú_Ö~ƒzõê___<þ\i¬¼¼0hÐ ¡ÏÕÕ………˜6m¶mÛ†Ù³gãØ±cHOOï¶ÌêhäÈ‘]]å?Qr¹‰Dh»ºº¢¤¤­­­ÈÈÈ€¬¬¬º-«ºrpp€ƒƒC‡{•Éd2¸ºº mA__ùùù¨ªªBqq±ÂcÔ@ÿ ¦¦FaÁ¢±±1ššš`bb‚Çãáǘ:u*æÌ™£Â”êíáÇ s,‰ÐÐЀ¦¦&¼õÖ[€ˆˆUÅëjjj„¹|I$¡¦¦úúúHNN†¡¡!222xîkúë¹xq¾¨©©ÁÊ•+±k×.ìÞ½III¾Û”)ëè|ADxôèš››þ3Å^OMMÒÝ]"‘---HNNÆîÝ»1`ÀìܹSE »@ÿÀÐÐPá;Zššš ««‹aÆ©}õûoÑ»wo¥9ÖÓÓSØ•  ÿÍ_çx±`W$aÆŒ˜1c†Š’õ=W/ÞË"‘ºººXºt©Š’õ,/€ çÏŸ¯ªX=Ê;_¸¹¹ÁÍÍMEɺ–FßÖÖÖÖ¨¨¨Úååå°¶¶îð£öz:šãþýûów!kkk}„/½ÿ>òòò¤âd=‡ ¼¼¼„½gêêêpüøq~/w1‰D‚´´4´´´àóÅ›ŒãÇ£ººðÍ7ß`øðápppPq².¦êUØÿ$‹ ÙØØÐÈ‘#…±††  KKK²µµ¥ &УGT˜V=(̱›››0öìÙ3š>}º0Ç>>>T[[«Â´êéÆ$‹ÉÜÜœ H,Ó–-[„ñsçΑ••999‘‰‰‰°뼇’X,&+++ÒÓÓ#±XL‘‘‘ÂøíÛ·ÉÞÞž Dfff´jÕ*¦U_sæÌ!±XLÚÚÚdmm-ÜåEDôüùs !sss²³³#OOOzðà Óª§M›6‘X,&277'±XL………Âxll,“““ÙÙÙ)Ý×hôNÐŒ1ÆÓLücŒ1Æ4@Œ1ÆÓ8\1ÆcLãpÄcŒ1Ãc¬KÜ»wOØÊ@UÊËËñèÑ#•f`Œ©.€ëÊËËáïï%K–(ôoÛ¶ ‡îò×kmmÅÀ6ìN÷ï߇‡‡&NœÈ;.3Æ:… Æz gÏžáÊ•+ÈÊÊBnn®Ð_PP€²²2&{3:OOO”””ààÁƒªŽÃS\1ÖCéëëcíÚµX³fM‡ã7nÜÀÞ½{úbbbðÇÒÓÓqöìYÄÇÇ#44;wî!55aaaؽ{7Ž¿{÷.–/_Ž9sæàÔ©S cyyyX°`BCC‘’’‚—[åççcÿþý¸ÿ>’’’‘‘ÑaÞ'Ož &&ÁÁÁˆŠŠr^¸p)))(++ÃêÕ«qóæM¥cïܹƒ]»vá÷ßÇöíÛ…Ýš+++±bÅ H$ÄÇÇ£¹¹pùòe¤¤¤Ç;v gΜÚ[·nEii)@*•â£>BXXÖ­['ìžËûw㈱,""uuuÈÊÊR+,,Ä·ß~«Ð—˜˜ˆ'OžŽ9‚àà`455aÚ´iHLL„³³3.^¼8p[¶lQ8~ÅŠprr‚©©)‚‚‚ŸŸ8qâæÏŸwwwH$$''#!!À‹B,** 3gÎDii©ðúÿ©µµÞÞÞ(((ÀìÙ³!“É0~üx´´´ÀÌÌ †††°²²‚³³3D"‘Òñ2™ k×®…¿¿? ñäÉÔÕÕÁÓÓOŸ>…D"ÁéÓ§…¯ÑÒÒBtt´p|tt4Ö­[hnnFTTÌÌÌpîÜ9ÁÛÛ|𪫫ñ믿vú߇1¦:ümðŒõ`ºººX¿~=bbbøÊÇÇÅÅañâÅ^•••Ø·oÀÄÄ X»v­ðø“'OÂÂÂPSSƒÔÔTxxx 666l@hh(ÀÀÀÑÑÑXµj@,ãìÙ³ÐÓÓë0Gvv6ªªªpýúuhkkãý÷߇¥¥%Ž=ŠØÙÙaìØ±ÿÛßÅÒÒ—/_†`óæÍèß¿¿ðûLž<–––¸víÜÝÝñüùsÈårÌÍÍQZZŠêêjÃÅÅfffÉd‹Å˜9s&ŒŒŒ0cÆŒWžcƘjð Æz¸`ÿþý¯|¬ŽŽŽð³‘‘´µÿÿ”all¬tÇ•–––𳇇ªªª¼ø*** ÎÎÎpvvFdd$z÷î-<ÖÜÜüo‹Ëå?~¼ðúZZZðöö†\.ïôïbbb"?/ŸÓÇÇG!ðaà —Ë¡££___œ?‡FHHfÍš…¬¬,\¸p“&MÂÒÒÖÖÖ˜4iÒÒÒÐÖÖÖéLŒ1Õá+@ŒõpZZZØ´i,X___¡_WWþùç+=ÏkÿUQQ¬¬¬¦¦¦HKKÃ;ï¼ÓùàÿÁÔÔTiñvYYüüü^ëù^>çËu<ÐÖÖ†ŠŠ ˜ššxqEèüùó¸yó&rrrPRR‚ØØXaáÂ… üøã(**Â¥K—mmmÌ;÷µs1ƺ_bLL:ƒ RX 4~üxܽ{µµµhooGFFZZZþ§×y¹(º  'OžD@@ 6lÆqäÈ‘N?¯ŸŸŠŠŠ •J¼XP]XXˆ)S¦¼vÖ÷Þ{R©·oß!==:::3fŒðšÙÙÙ044D¿~ý0nÜ8ãâÅ‹ðööäææ¢¬¬ C† Axx8FŒñJE%cLu¸bLCÄÇÇ+ܵկ_?Ìž=èÓ§Μ9£ð‘×ë˜2e Œ‘#G"<<\Xw”€>}úÀÖÖNNN°°°P¸«êŸ8::bß¾} „­­-¦OŸŽ¯¾ú NNN¯uòäÉøä“O0zôhØÚÚbõêÕ8xð phèСÐ××Gpp0€W¼fÍš'''áã»`Ô¨Q Disk space taken by a record (original record size: 16 bytes) 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 5 10 15 20 25 30 Bytes/row No compression zlib lvl1 zlib lvl3 zlib lvl6 zlib lvl9 PyTables-3.7.0/doc/source/usersguide/images/compressed-recordsize.png000066400000000000000000001221351416254111300257600ustar00rootroot00000000000000‰PNG  IHDR@°AàÚ²sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwX××ðïÒ{¤ˆ€1""v°EbŒ"Q±K‚õ§Æ†Q£ÆÆ{7–ØbÃÄÞbŒ5ÁŠX(*]D¤Ãyÿàed`EYVá|ž‡GçN;sgÙ=윹#!"cŒ1ÆX-¢¤ècŒ1ƪ'@Œ1Æ«u8bŒ1ÆX­Ã cŒ1ÆjN€cŒ1VëpÄcŒ±Z‡ ÆcŒÕ:œ1Æc¬Öáˆ1Æcµ'@Œ1Æ«u8bŒ1ÆX­Ã cŒ1ÆjN€cŒ1VëpÄcŒ±Z‡ ÆcŒÕ:œ1Æc¬Öáˆ1Æcµ'@Œ1Æ«u8bŒ1ÆX­Ã cŒ1ÆjN€cŒ1VëpÄcŒ±Z‡ ÆcŒÕ:œ1Æc¬Öá謬,E‡ñÞŠEZZZµï÷Ù³gHNN®öý~¨^¿~§OŸÊ´lnn.¢££r^åˆììlE‡òN²³³ "Rt(¤$<þ\Ña”øøxE‡ñ^⨊ <~~~ð÷÷ÇW_}…Y³fáܹs¥Þ,þþûoøøøàÉ“'n3;;¶¶¶¸r劼Â~o;v <¨ôzmÛ¶ÅÎ;åQùÆŽ‹ÿýïÕ¾ßÕáÃ‡áææ&Ó²&L€¯¯o•$ ƒ ¢E‹d^þúõëðññÁÇßyßE233akk‹«W¯VÙ6áÚµk°µµEFFÆ[­åÊøøø **ªŠ#«œM›6aÉ’%eοwï&Mšooo8::âÌ™3ånoÈ!˜1cÆ;ÅtñâEü÷ßï´’®\¹[[[üý÷ßUºÝš€ *vìØ1€§§'ôõõ ___´oß^ô†¡££ƒ† B]]]Q¡¾—¾þúk?~\Ña0;sæ 6mڄÇÃÄÄä·gggsss™—×ÖÖFÆ ¡¡¡ñÎûfbŠ~ï;{ö,¾ÿþ{|ýõ׸}û¶Ôe:///$%%á‹/¾ÀÔ©Sadd$÷Ø-Z„­[·Vé6{ö쉩S§¢ÿþü·UMEÑÔD;wƘ1c„éGÁÇÇ={öÄü8;;#((uêÔ–ËÎÎÆ­[·ðâÅ ØØØÀÑѱÌ}àÉ“'Ð×ׇ¡¡a™Ë=}úáááÐÑÑAãÆ¡«« HIIAnn.LLLpëÖ-<{ö žžž¢meeeáÞ½{HLL„¦¦&\\\¤¾ $$$ ,, zzzpqq½±¥§§#44YYYpss+÷Ã,)) ¹¹¹HIIAtt4ÀÚÚJJJxúô)¢¢¢ðúõkXXXÀÕÕµÌí?vuuu˜šš }†èèhØÛÛ£aƲ¯^½BZZ,--ñðáC<}úíÚµƒŠŠl¿"¹¹¹ Å«W¯àåå%ôs||<ÔÔÔ`ll\êX•””ÊìÜÜ\„‡‡#..jjjhÔ¨ÌÌÌÊ!-- /_¾„•• ..FFF‡x||Ô´iSrpp @ÖÖÖôðáC""š4i9::RAA°NVV™˜˜Ð¦M›ÊÜî AƒHMMÈÚÚšTUUiÉ’%寲zõj277§7R:umß¾ hÊ”)¤¤¤D...¤­­MÎÎÎôøñcaÝãÇ“©©)©««SݺuIYY™~úé'""ºÿ>}ôÑG¤¦¦FVVV¤¦¦F?ÿü³h]´oß>²±±!4gÎ""úá‡HEE…ŒŒŒÈ‚´µµÉØØ¸Üã¸sçú°¸y󿑲²2Y[[“ŠŠ 5kÖŒžùûûӀʌ•¨0ª_¿>9s†òóóéúõëÔ¨Q#  "¢ˆˆRRR¢Ó§O ëüòË/dllLen÷öíÛ¢˜M›6‘²²2½~ýºÌuV¯^M¨[·ntêÔ)ºÿ>%%%ÑæÍ›ÉÄÄ„îß¿OD…Ä:uŽ-..ŽôôôhܸqôìÙ3"*ü*zýµnÝšºtéB±±±”——G›6m"‰DBׯ_'¢7 P‹-èÈ‘#ôàÁЉ‰¡sçÎZ±b…p¬K—.­0Zºt)5mÚ´Tû™3g„ä   €¢¢¢¨uëÖÔ­[7a™–-[’††Íž=›þûï?ºqã‘(JOO§úõëSÏž=…ä)11‘”›)))Ñ‚ „sH:uöýäÉÑïäƒHMM~ÿýw¡ßeM€ZµjEÁÁÁB_ž>}šÔÔÔ„Äììl¢Â$:tHˆ%<<œ<<<„¿‚—.]J¶¶¶¢}nß¾LLLÊŒ•¨0êׯŸ¨möìÙ¤¯¯/LwëÖzöì)L7oÞœ¦M›Vîv‰ˆ^¼xAG¥Ÿþ™&NœHJ½©Wô PI-Z´ aƉŽ}Μ9T¯^=""Z¶l)))Qrrr©u=z$$ÅÕ­[Wøv¡(ÊÌÌ-3tèPáMºÈÎ;+L€ÆO½{÷.Õ>xð`rvvµmذoê-[¶}ð)ž>}ºÔUø –––hù“'O’ªªªè÷%;;›þúë/Z»v-}ÿý÷dhhH+V¬ ¢Ê%@999¢ö€€òõõà 6ŠŠ åääÐÁƒKýÁS$++‹”””„oôŠxyy‘ŸŸ½I€ÂÃÃEËÌ™3‡´µµE´üõ×_&@ 6¤îÝ»Ký#­¬(77—¼½½©{÷î·/?ýôÕ«W„ã¾|ù2I$’R篲ÊJ€ÜÝÝÉÍÍŽ9Báááô矒»»;5lذÜ?Þ|}})00PÔ6qâDá÷Œ¨ðuX|Ÿ 6¤ùóç ÓÒ ñãÇS³fÍDç¾è|ÇÅÅÑ7­ZµJê{}‘ôôôR‰RmÇ5@Õ¤  ÷îÝÈ#¤Îÿî»ï˜››£G˜1c†PϳfÍÂßÿ'NTXÐéîîŽo¾ù“&M„ àææ† & ‰Dê:nnn(((@||<êׯ   ,]ºÞÞÞ°··G~~>ÿFDD OŸ>R·äädôíÛWÔîêêŠ×¯_—{IwïÞE=——‡-ZÀÒÒJJJBEvî܉˗/cÙ²e¢¡ˆˆÀœ9sJm[MM­ÌýêèèT:V ð_¾|‰ôôtèèè`ìØ±øôÓOƒøøxüûï¿Ø¿¹ÛX²d ‚‚‚вeK899 u%¹$iç6""qqqø÷ßEíE5puu-U§T4Ú´i#joݺµ0¯¬}GFF¢cÇŽåÆ+Í‹/„Ú›’±H‹£h^QmEY¯ï"QQQÐÕÕ…§§g¥c+NGG¹¹¹ÈÉɺº:.\¸€Þ½{£N:ððð@ݺu¡¬¬\á9“¦ä1DDD &&¢ö>ú/_¾DDDŒÑ´iÓRÛŠŽŽFAAÔ¾;}út¹ûŒŒDÛ¶mËý=‘fÅŠ;v,,--akk‹¾}ûbòäÉåOž< ∈ˆÀË—/ѯ_?ѲMš4Afff¥bªŒÎ;£{÷î'''lܸ¸qãš7o.óv\]]ñóÏ?ƒˆ ‘H0vìX <K–,Á7¯¾úªÜmDDD **ªÔ¹wuuÅ«W¯àêꊙ3gbêÔ©˜8q¢è½¾¨† (,ì×ÕÕåa;Šá¨š;v éééðññ‘:ßÝÝW¯^ÅíÛ·qåʬ\¹þþþ¢[fñÕW_aÈ!¸téìííËÝçüùó1eÊ\¹rÇÇÀ¡££ƒnݺI]þöíÛPVV†¹¹9nݺ…E‹᯿þÞ8¯_¿ŽmÛ¶ Ë›šš–y›°©©)7nŒ¿þú«Ü¥¡C|ûí·°··ÇÑ£G¡ªª R÷›••…ï¾ûß|ó ÜÝÝÑ®];! 1婨 ¼¨@õã?†6lØ€'Ož G寑””„)S¦`Ïž=B‚‡Õ«W¿U<¦¦¦0`¦OŸ^æü¨¨(äçç‹ ŠEÚBÂ<@³fÍÊݯ––Ö[?RÖ›´‰‰I©¤«hÈ„âEÙ144DZZž={Vª˜ö]Œ?þþþX·nðþÇTɶMMMáááuëÖ•9ÿÅ‹HNN.UX_üzyy í<¨°ßÞö~üñÇ Chh(.]º„åË—#** »wï–ºüÎ;±eË„„„ˆŠwMMMaoo_*y—'{{{<~üXÔVô»œ’’R©m………ÁÜÜ\x=ôìÙFFFغu+._¾Œ¾}û–:_%ßÿLMMѦM>|¸ÌýÌ›7“&MBHHŽ?ŽÁƒCKK =zô–ÉÎÎFzzz©âéÚŒoƒ¯'OžDÿþý1mÚ´2ï ¹}û6$ š4i‚¯¾ú AAAˆŒŒýõ8qâD¬]»íÚµCçÎ[æ>£££…»¯¯/V¬X{{û2–¼¼<9ríÚµƒºº:…»ÕŠ”³¨uëÖ8qâ„h€ÆÔÔT$''ÃÛÛ!!!¸yó¦hØØØrÇqrr³gÏDm‰‰‰prr’Ÿììl$%%•ZwĈ˜5k&Mš„Ï>ûLO£aÆ033ÃúõëEËçææVùX$D„'Nˆ]‰D‚1cÆ`ýúõسgO…ã%%%ˆàââ"´É2^TY¼½½±}ûöRi%­[·Fjj*Ο?/šÿèÑ#8;;ÃÄħNÚcbbpûöm´mÛ¶Üý6iÒ—/_}‹VòƒEKKK©ç¥mÛ¶¥¶wüøqX[[W*±õòò‚ªª*Ö¬Yƒ¬¬,8ðÎû%&&¢qãÆÂ‡]jj*^½zõNÛ,âí탖J ‹Îa‹- ‘HpäÈÑü‡ÂØØÎÎ΢s˜™™‰ .Ètoß¾-J‚*:‡ùùù ƒŠŠ <==1qâD >¼Ì÷žÐÐPâ—_~½ß÷7"jOHH yòäIüüóÏåÆ%«:àÔ©S¢÷˜àà`¨««W˜ôWPP€“'OŠÞ TTT0räH,[¶ ÁÁÁ¥Þ ¤½ÿy{{ãôéÓˆŒŒµ}> j``€.]º`ùòåppp(Õß?UË Å]}«™ŒÉÃÃúôéC;w¦úõ듪ª*M˜0A´\É KKKòõõ¥yóæÑ7ß|C666BÁtÉ»À²³³©S§Näìì,µnƒˆhóæÍdffFcÇŽ¥Å‹Sß¾}©N:”––FD…5@êêêÔ«W/š0aYZZ’P;“——GÖÖÖäææF&L ®]»’±±1öKææædllL_ý5?žêÔ©#j0€tuuiìØ±4oÞ<êÝ»7ijj wE”·žžM›6ÆŽK/^¼ Í›7“–– 0€FŽIvvv¤¯¯O£GÖ+yX`` Õ©SG¨i8xð ihhÐgŸ}Fßÿ=’-\¸ˆ¤×>>000€²²2üýýñúõkäääÀÉÉ©T NNN077Gll,"##ammuëÖ cÉ;v ‰]»vÅ“'OСC¬[·NØ–’’z÷îÌÌLDFFÂÇÇ‹-‚‘‘Ú·oUUUèêêbøðáÐÔÔDll,ÔÔÔ0~üxx{{CII þþþpssÃÓ§O Ì™3...eÖi¸¸¸ÀÃÃ7oÞ„¦¦&:uê„fÍšÁÍÍ wîÜ––¾ûî;´nÝ...pppÖmÑ¢…P;ÒµkW¨ªª")) nnnpvvÆ€’’‚û÷ïCSSýû÷ÇàÁƒ¡®®‰D+++´hÑBtŽLLL„KiÒH$´k×ùùù¸{÷.¼¼¼°eËQ\ ¬¬Œµk×"((îîîå½|~~~PVVƽ{÷ЬY3¬\¹zzzhß¾½0®ˆ4–––hÙ²¥¨MKK ÐÖÖFdd$RRRàåå…9sæu?½zõ‚³³3^½z…ääd|üñÇ3f ´´´Ð´iStèÐqqqHJJÂÀ±jÕ*á9Âë¢xÍžžzõê…¬¬,¼xñ½zõÂÔ©SQ·n]¡vG lß¾‰Dô …ŠŠ ¾øâ ¨ªª",, ŽŽŽX±b:tè ZßÝÝvvv¢6‰D‚¦M› í­[·Æ€мysŒ;'NÄ‚ 0mÚ4˜ššB"‘@[[>>>Bý•™™Y©oLŠÎ‰²²2Ú´i[[[ܼyæææX¶lìííѼys¡–OII íÛ·‡¾¾¾Ôc—H$022‚èwDEEÆ ƒ……¢££,X°@¸4Ù©S'´lÙ¯_¿F||ž9ØÖÖV4ÞJxx8ÂÃë5VÆcL‘444àëë«è0ä®V$@JJJ°²²Â_ý…„„XYY!55µÔ¥.MMM¼|ùR˜Þ³göìÙƒ† Ê=Æ„„@CCã­ÖŽŽ†M•-[Þ2eÍ“Ö^²-//O8E=zTá¨ÖU!''ÉÉÉo}WQeÎÑ«W¯——Wn]IUõqRRtttD·/—\®ºú¸¬eUÙsTѾRRR ¢¢"]¸HYç¨è†‰â5Ò^·%÷ýôéS˜ššVË%ôwéãÊ®¯È÷‹’ç(##)))U^&´×AeT¦eyo‘W—lËÎÎFhh(dŠýƒ¦¸!ˆªß°aÄÁó~üñGáA€EZµjE[¶l¦gÏžM³gÏ®–ضnÝZáÓ†ËS™8eY¶¼eÊš'­½dÛ‹/hÙ²e¢¶O?ý´ÂxªB||<­]»ö­×¯Ì9úçŸèøñãå.SU}¼{÷nºwï^¹ËUWKÛweTöU´¯ãÇÓ?ÿü#u^Yçèüùó £E¤½nKîûË/¿¤°°° c® ïú¾ô¡¼_”.ÙOfffåÆRSÔªhÊ”)Ô¿""úí·ßÈÑÑQ4ßØØ˜þüóOaº: çÏŸ—û¤áŠÄÇÇWé²å-SÖ}ºš"be¹|ù2š4i¢è0j4îcù»yó&ŒŒŒÞúFV±W¯^áîÝ»hР¢Ca5@ñ]='–ŸË+òï¿ÿâöíÛŠƒ±·¢¬¬,<ìUš„„4mÚ´VÜVc¿bŒ1yØ¿?NŸ> E‡ÂX¥äççcÿþýå&@µ '@Lájm^uá>®Z½{÷FPP¢Ã`¬R²²²°ÿ~E‡ñÞ¨±w±ÇáÇBÇ}Ìcbœ1…9r¤¢C¨ñ¸cLŒ ÆcŒÕ:œ1…« w(÷1cŒ‰qÄŽëSäû˜1ÆÄ8b Çõ)òÇ}Ìcbœ1Æc¬Öáˆ)×§È÷1“fïÞ½X¾|9nܸ!jüø1V­Z¥ ¨j¦¹sçâÒ¥KŠƒà S8®O‘?îc&ÍòåË1}út 6 ÅŸŠŽÉ“'+0²šgïÞ½ St¬ š)×§È÷ñûcûv`êTùïÇÔ¸u«âå† †-[¶`ß¾}¨pùׯ_C[[»ÒñäææBUUU꼌Œ hii•»~ff&455EmYYYå>|–ˆ••Uj=Y¤§§CGGG꼂‚äçç—y<ÙÙÙPWWµ••üdggCYY**åKÛ&{7ü cŒU£Œ 1Qþ?II²ÅS¯^=Œ;³fÍB^^^™ËíÚµ õë×‡ŽŽÌÌÌðÓO?¡¢gi'%%! úúúÐÐÐ@Æ ±cÇ@NN&Ož CCCèèè Q£F8s挰î;w`ccƒ-[¶ Q£FÐÒÒ‚þý÷_lÚ´IˆÅÏÏO”\|ûí·èÙ³'ÆŽ+l»S§NH*Ö!>>>X¿~=V­Z…Ö­[£yóæ “žÑ£GÃÀÀººº¨_¿>Ž=*¬÷àÁtêÔ ZZZPWW‡¶nÝ  0A=z4 ¡¡¡###Œ1BX×ËËKôЇÂÇÇ:::ÐÕÕÅ矎gÏž óç΋Ï?ÿ3g΄‰‰ ŒŒŒ0|øpÑq°wà S8®O‘?îcVžéÓ§#)) ›7o–:ÿôéÓ:t(†ŽÇcæÌ™˜?>Ö­[Wæ6 еkW}úééé ¿¹êÔ©ttt‚ßÿ±±±èÙ³§è8>ŒçÏŸãèÑ£8vìΜ9ƒ_ýUöËÊG¬L³gϦٳg+:ŒoíÚµŠ¡Æã>®:Ó¦M£ ¼õúk×òÿ13«8–-ZÇ2þ|²°° ŒŒ :qâ©«« ËuîÜ™¼½½Eë>œlmmËÜöÑ£G ]¾|¹Ô¼¬¬,RSS£ùóç‹ÚëÕ«GDDtýúu@Ož<æ:tˆPnn®Ð¶~ýz²±±¦GM;wm÷»ï¾#uuuÊËË#"¢ ÐäÉ“EËܽ{—Э[·„¶‚‚²°° uëÖQݺuiúôéR÷Ë/¿$ÊÊÊ’:ßÜÜœ¶lÙBDDÛ¶m##Ì?w¿ÆGíÛ·mcúôéÔ¡C©Û—Eff¦è¼JOf²¼xjþˆ)×§È÷ñûcÈ >^þ?·oW.®ñãÇ£  +W®,5ïîÝ»èСƒ¨­}ûöˆŽŽFFF†ÔíݹsÆÆÆðòò*5ïÑ£GÈÉÉ‘ºÍ’µ2‰Dø¿®®n©m‰.IÓªU+dgg#66Vh«[·®h™»wïB"‘ sçΨ[·.êÖ­ sss$''ãñãÇ€I“&á§Ÿ~‚““† †S§N ë:OŸ>…¥¥%üýý±víZdggKçîÝ»ptt„¥¥¥ÐÖ²eKhhhˆŽ¿ø±€±±1›[…¸š1ƪ‘¦fáÏûF[[³fÍÂÌ™3±víZÑ””ÄkKÛfEÅÀ%•L¤)ºL¥¯¯_æ2ªªªPQQÁ;wJÅPTh=yòdøùùáСC¸té>ýôSLž< ,@Ë–-={ö $$3gÎÄš5kp[J&*­?srr——WîñËr¬Lvü S8þ‹Fþ¸™,¾üòKañâÅ¢vWWWœ;wNÔvúôi8;;—ygR“&M––†þùGÔž ;;;èêꊶYPP€sçΡiÓ¦Ut4oœ:u NNNå&@M›6E^^®]»Ѳ²2ˆ999°··Ç”)SpäÈÌš5 ÁÁÁ “9Œ17nÄ™3gpçÎDFF–Ú—««+ž¸¸8lܸß|ó -Z ðòϪU«-*V~[wîÜÁêÕ«aee…Ý»wãàÁƒØ½{w¹ëØØØ`äÈ‘2dæÎ WWWDFFâСCøì³Ïзo_8;;còäÉhÖ¬ž={†ß~û Æ ôíÛ 6DçΡ¦¦†7ÂÛÛvvv¥öõù矣qãÆèÓ§¦L™‚””,^¼Ý»w‡««ë;?“ '@xùÒ±±@±Kµ¬Šñ³üq3iÜÝÝamm-jëÛ·/Ž9‚””¡ÍÓÓgΜÁ¬Y³Ð¿ØÙÙaÇŽèÝ»w¹Ûÿã?0wî\ìÛ·6l@£F0aÂ@PPttt°iÓ&¬_¿ êbtuuáíí-JÄ áíí-ºT§N´mÛV´_===œ9s·nÝ‚µµ5öïß=zó½¼¼P¯^½Rñ®Zµ nnnøõ×_ñý÷ßÃØØ­Zµ‚··7ÔÔÔ0lØ0lß¾óçχžžºuë†1cÆ(¬Z½z5öìÙ"‚‡‡6mÚ$l»U«VBÝ‘²²2.\¸€I“&aêÔ©PWW‡¿¿?¾ÿþ{ay‡R—ÃêÕ«OOÏrûœÉNBTÁ@µØœ9s0wî|ý5°d‰¢£aŒ½‚‚‚```€   E‡Â¤3f >|(*Pf…²²²```Pªþ¨¸„„4mÚ´V\6ç ìÛWxc)“Úð‹¦hÜÇŒ1&Æ bb€Ë—EÍÅÏ©’?îcV[tèÐA4  ceá íÝ ´n­è(j&®O‘?îcV[|þùçŠ} ø ýöPP è(cŒ1V8’Q|Eþ¸Ye…††âøñãeÎ?|ø°ÔAþÊóòåKlÛ¶ ¯_¿–iy"¶mÛ_©ý0& N€*aï^EGP3q}Šüq³Ê:pà~øáaÚÛÛ]»v¦¿ùæüþûï•Úf\\†ŠçÏŸË´|^^†Š{÷÷Ûo¿Ic‡1Yq P%8¬\ (qÚX¥¸>Eþ¸Ù»š={v¥S!GÅöíÛqüøñrGvf¬"Š5¿çÔÔr“£HLþühß^ÁA1Æ>X·“nãläY¹ïG[M_ºYæü{÷î!$$¤T»‹‹ <<|‡‚ŽŽ|||ð¿ÿýOæmxyy!22GEtt4 ñÓO?ɼ~`` vîÜ)™xóæÍ6l˜ðÄxÆä…  ØÛ?‚žÞ›égÏ€EfïˆëSäû˜•ç»ï¾Ã7°oß¾JÕù¸ººBCC`bb‚¶mÛâêÕ«2¯?hÐ dggãàÁƒ€°°0\½z_}õU倱·À—À* ¬œ‡=€;Þ´íÛt˜c®&fM0Þk¼Ü÷£§®WñB~ÿýwüøã¸xñ"êÔ©óNû´µµÅ… d^^OOýúõæM›Ð¿lÞ¼ü1lmmß)ÆdÁ úô'@‡k×ïÁ 5×§È÷ñû£U½VhU¯•¢Ã<|øÄêիѬY³wÞÞßÿ ‡J­3räHxzzâÞ½{رc6nÜøÎq0& ¾&ƒ? ÞL?œ9£¸xj®O‘?îcVRzz:üüüп :ô­¶‘““#üÿÖ­[øë¯¿*ý(Š¢¢æ~ýúA]]ݺu{«X«,N€d ¦øù‰ÛöíSL,5×§È÷1+iÙ²e ÃÉ“'ѰaCáç믿–yßÿ=<==áéé WWW|òÉ'1bD¥c ÄÍ›71bÄ©·Ú·zõj´hÑ‹/ƳgÏТE øøøTzŸŒñEõélÛöfúða`ýz€oT`Œ}ˆˆ¶mÛ–j755 >½zõÚgÏž-JN~ù嘚šâöíÛˆŠŠÂâÅ‹+LDêׯóçÏÃÌ̬T,öööpssµ«¨¨àüùóhÚ´©ÐöÉ'Ÿà£>-WQÒĘ4œÉ¨S'ÀÈHI)œ~ñ8u øôSÅÆUp}Šüq³’lmmË-6.9¯Q£F¢iOOO…I¬´´´¤&IRÛ%I©v;;;~«| LFªª@Ïžâ6¾ V5¸>Eþ¸cLŒ JèÓG<}ä­˜Xj®O‘?îcÆã¨:tLLÞL¿| œ<©¸xcŒ1öv8ªÀß_ÜÆ—ÁÞ]BB‚¢C¨ñ¸™4±±±HKK“Ûö322ðäÉ™—ÏÍÍEtt4òùyC¬pTIâéà` ØclØ[àúùã>fÒ´iÓ»ví’Ûö‹n±—Uxx8lmm‘˜˜XjÞ‘#GT•á±ZŽ Jòöþÿ.Q@Zpü¸ââ© ¸>Eþ¸Ù‡êÊ•+X²d qíÚ5E‡Ãj¾ ¾’”•Ï?/|F‘}ûJß!ÆcR=<}*ÿý¨ª%ÆË)OJJ ^½zUªÝÄÄ::: /Qݺu ÉÉÉpssÆ ’Eff&Q¯^=Ѹ=ÙÙÙˆG½zõ¤®‚Û·oÃÈÈHæ}1& N€ÞB@€8:zÈÌ45Ó‡ŒÇ¨‘?îã÷ÈþýÀ¨Qòß™P‰Ú¯… bÆ Ât~~>ÒÓÓ±mÛ6 2=‚¿¿?ÂÃÃaff†ÄÄD,Z´&Liû©©©pppÀþýûáWlhýU«VaõêÕxôè‘ÔõÆ/|pìÿþ÷?ܽ{Wæãa¬"| ì-´m ˜›¿™~ýøãÅÅó¡ãúùã>fY´hRSS…ŸÏ>û žžžèÛ·/€Â‘¡ÍÌÌ…èèhlذ_ý5®^½*ÓöÍÍÍѽ{wlÚ´IÔ¾yóf|ùå—PRâ#V½ø÷””€b#ÄöîUL,5×§È÷1«ŒeË–áìÙ³8xð ÔÕÕ‹/bøðá°´´„²²2¾øâ XXX`ÇŽ2owäÈ‘8qâbbbÿüó=z„aÆÉëP+_{K}ú+W¾™>v¬ð› mmÅÅÄû˜˜®®Õ³Ÿ·pþüy|óÍ78q⬬¬ ï+®uëÖÂE@@.\ooo¡½N: ¡¢¤>|ˆÆ˼}‰D‚ÀÀ@¬Y³ãÇÇÞ½{\uÀX%ð%°·$‘½{‹Ûø2ØÛáúùã>fÉÊÊ‚¿¿?ºté"qrr‚©©)N:%´ÅÇÇãÆRŸ(_ž¡C‡".._}õ,--Ѿ}û*‰Ÿ±Êâo€ÞAŸ>Àòåo¦/HWWq1}ˆ¸>Eþ¸YEfÍš…ëׯ£Q£F¢×K×®]ѽ{w,Y²_|ñ®]»lݺÍ›7ÇСC+µôêÕ ¿þú+–.]ZáòÛ·oGXX._¾ŒgÏž!((–––7n\¥‘±â8z-ZÖÖ@ÑHïYY…·Ä÷ï¯Ø¸cL&L@³fÍžžž˜={v©etÿÿ/ºÂÎλwïF\\¾ýö[Œ=**eŒ8;;cÆŒ¥Ú§OŸ|ñÅ¢v333Ìž=[Ø'¨ªªBCCݺuÚÔÕÕ+uœŒI#!"Rtï«9sæˆþ•fòd`É’7ÓÝ»>%žÉŽëSäû¸êÁÀÀ€ËÀ>8YYY000@V9ÏoJHH@Ó¦MkÅó¹èõé#ž>y2˜*+×§È÷1cŒ‰qôŽš7lmßLggüYS9\Ÿ"ÜÇŒ1&Æ P(y7ؾ}Љƒ1Æc²á¨ ”ûçôi 5U1±|ˆjõfEã>fŒ1±­X±mÚ´½½=† "à ___ÑϹsç*½ww Aƒ7Ó99À¡Cï}íÁõ)òÇ}Ìcb5ú6øû÷ïcÚ´iprrÂÙ³gáç燧OŸÂØØ@áïcÆŒ³³³°Ž££ã[í«O`Á‚7Óûö•£Öâúùã>fŒ1±­Y³Fø¿££#6lØ€૯¾ÚÛ¶m[êù6o# @œ9¤¤FFï¼iÆcŒU±} ¬¸ÌÌL}‰§NBÛ¶m±hÑ¢r×KKKCLLŒÌûÉÊÊÂãÇEÔ¦¥¥!$$#FŒÀÎ;eÞVMVôzþý÷ß1gÎøøøà§Ÿ~Bnn®b«&µb$èéÓ§ãÈ‘#¸páLMM¥.“šš {{{ìܹ¾¾¾d º¸;w—7ÓÊÊ@B`bò.Ñ3ÆÞ'ï:ôº¸8Œz𠊣*ÍLM ­Z•»Œ¹¹9fÏž‘#G";;***PVV®’ýgeeAMM JJoþÎNMMEll,>úè#¡­_¿~8~ü8R˹uvëÖ­˜1câââdÚ÷_ý…víÚáõë×ÐÒÒ $$ÉÉÉèÒ¥ 8ð–Göáâ‘ Åjô7@?~‹/–™ü€,--‘œœüÖûkÜhÔèÍt~>P Çc»wï¢C‡ÐÕÕ…žžfΜ)Ì:t(lllJýüóÏ?€=z`åÊ•ÂòNNN˜;w.Z·n èëëã‡~戒ðòòBZZšÌß:„††ÂÆÆááá¢öÍ›7£U«Ve–2ìÝ»ÑÑÑð÷÷—i?¬æ«± P~~>üýý… .À¤Ä×0=ÂÅ‹…éãÇãÑ£Gï\]òÑ<(bÅjÃ_ŠÆ}ÌÊrðàAôéÓ—/_ƈ#°páB=z@áâ>,ü899A[[.ÿÿUw\\^¾|)lëñãÇØ¶m†ŽþùÄŒ3páÂ…2÷òäIxzzBUUU¦xÝÜÜ ¥¥…Í›7‹ÚW¯^Ž;B"‘T²XmUcïKKKÑÿ*iñ' ;99!<<iiièÝ»7233a``€ŒŒ lÙ²666ï´ß>}€âWÌþüHLÌÌÞi³5ÚáÇù6m9ã>~4ÑÖÆx++¹ïGOÆKY³fÍ^Íš5ÃåË—±sçN|öÙg°³³–Ûºu+BBBpõêUèè蔹½… "àÿG‡õôôÄñãDZ{÷nøøø”ZvãÆ8}ú4Ξ=[‰#1þ|üðÃPUUEhh(nÞ¼Éã]±J©± A¹wu¹¹¹!!!ñññÈÊÊBýúõ«äÚ·³saÐíÛ…ÓE—ÁF~çM×XüÁ,ÜÇïVúúh¥¯¯è0ÊÔ¼ys\½zUÔvýúuŒ3{÷î­ôXi­ZµBddd©ö£GbôèÑX³f ¼½½+µÍÁƒ µ½zõÂæÍ›Ñµk×Rwù2Vž{ L‰°³³«²Â?€/ƒ1Æ>\)))¢oÍŸ={L:Ÿ}öY¥·—””~ºd IDATQÛ¾}û€õë׋Æe“•¡¡!°iÓ&deeá×_å$ŸUZ­N€ä¥dô×_€ w¤ÖZ\Ÿ"ÜÇLD„³gÏÂÝÝ——‡>}úÀÕÕ³gÏ®ôö222péÒ%Ñ>K–,Á°aðk×. 6ì­c9r$NŸ>eË–AOOŸ|òÉ[o‹ÕN5ö˜"9:M›7nN¿ýŒ§Ø¸ÞW\Ÿ"ÜǬ,ÁÁÁ°µµEff&Ö¯_ÌÌL|ýõ×€o¾ùÿüóV¬X½{÷ ëxyyÁÖÖVêö¶mÛeee<þ«W¯F:u0jÔ(Àž={0yòd 6 ‰‰‰X·n°^³fÍЬY3™ãöòòB“&M0kÖ,Ì;Wt»½4W¯^EJJ âââ••…'NÀØØžžž2ï“Õ,œÉIŸ>o Ø»— ²ð³üq3iZµj###L›6 111pwwÇ•+W`aa pÜž-Z`÷îÝ¢õôõõakk Ô¯__4OSS‹/FJJ Z´h…  EÓÚÚÚðööFDD"""Dëihh”™™››£eË–¥Úƒ‚‚°nÝ: >\Ôn``oooQR´k×.ܼy ¢¢‚… ÂÝÝ Z¬V „ø¶*;bq€½ý›i‰xò¨†›?crô®!Ödøå—_„»ÀØû…Bã 9iÐððx3MTxŒ•V~Ñû˜1ÆÄ8’£’»„Ίá±;äû˜U‡qãÆUú6yÆ… 9êÝ[<RxŒ‰q}Šüq³ê°xñb¸¹¹): Æd ÙØÅîþ°¿ÂÂaŒ1ÆØÿã»Àä, (>¨êÞ½À¤IŠ‹ç}”€ºuë*:Œû¸jýù矊±JËËËStïN€ä¬wo`òäÂoàÚ5 * (cZ‰Ç¨‘?îãªSôL«ÔÔTQûƒ`mm DU;¤§§#11 4Pt(¬)S¦(:„÷'@rV¯в%pùò›¶}û€iÓÓû†?˜åû¸êøúúÂ××WÑa0ÆÞ×U~6cŒ1ö~á¨ôêU8b‘ÿþ=R\<ï£Fþ¸åïùóç\c!g999HIIQt¬†à¨XZmÚˆÛø[ 7xŒùã>–¿óçÏãåË—Š£FKNNÆ¥K—«!8ª&|¬l\Ÿ"ÜÇò׫W/+:ŒÍÂÂÝ»wWt¬†à¨šôêXñÍ›ÀýûŠ‹‡1Æ«Í8ª&uëíÚ‰Ûø[ B\Ÿ"ÜÇòÇ5@òÇ5@¬*qTø2˜t\Ÿ"ÜÇòÇ5@òÇ5@¬*IˆŠ†èc%Í™3Gôï»JJ,,€üü7mwïUÉæcŒ±w’€¦M›ÖŠoù jdj üÿ ²þˆ1Æ«~œU³€ñ4'@\ŸR¸åk€äk€XU⨚ùû*Å@rïpû¶ââyp}ŠüqË×É×±ªÄ P536:t·ÕöoxŒùã>–?Hþx V•8R¾ ÆcŒ)'@ г' ªúfúÁàÆ ÅÅ£h\Ÿ"ÜÇòÇ5@òÇ5@¬*q¤††@çÎâ¶Úü-×§È÷±üq üq «J<P9ªz â~ùøâ‹7Ó ðâcŒ)ÄäÎÏPS{3üû¯ââaŒ1ÆjN€D_èÒEÜV[/ƒÕ†¿4ûXþ¸Hþ¸ˆU%N€ˆŸ VˆëSäûXþ¸Hþ¸ˆU%®*‡E=9ÆêIH=É O’ÈEGkg|"#]meeZÈjõ]¿T‘|ŠzrŒÕ“ z’ž$Ÿ:T»3ÌÝÆpÏ=¾éJ’OQOޱz’RO2@“¤òc×_¯äîùçA>Ì !„ÇG ?÷ê«Ð·¯¾íφ|Ó$Ÿ¢žcõ$¤žd€„'IäçbcµE=9ÆêIH=É O’(tî ï¼F·ÿÍC‡àÊ+µÁýäSÔ“c¬žd€Ô“ ð¤ .€þûßÿrúé§Ó·o_®¾új¾üòKÝöÌÌL¦OŸÎI'ÄÅ_Ìžb}âDxøa}Û_ÀSOù¦?í!ùõä«' õ$$<)¨  ÌÌLî½÷^>ûì3FÍ…^Haa!6›³Î:‹‰'²jÕ*Ì”)SpÂ)“<ôV5n[³Æ7ýB!üUP@óæÍk8ÃsÓM7Ñ¿–-[ÀªU«ˆŠŠâ®»î¢W¯^üóŸÿ$//µk×ê^£{v6lÞì‹î·›Ñ¨] ëÜÙÕVW§M•áÏ—Í%Ÿ¢žcõ$¤žd€„'u䮺ºš;wÒ³gOvîÜÉàÁƒ¶F Dff¦îy×-X  ³ ºvÕBу«-+ fÏöY—ŽJò)êÉ1VO2@êIHxRÈ@7ß|3ÇgêÔ©äçç¥Û'66–üF·NMÒ>ù„´‡&--´´4Ýv\Ÿ2E»=þH ÿû<÷œô¯ñº{>ÅúŒëõÇØ_úŒë3fÌà…^ð›þãú«¯¾ªËùº?Á²¾bÅ ÒÒÒ˜0asçÎÅf³ Î@½´Ñý÷ßÏÇÌ·ß~Kç#ׇ{ì16mÚÄÒ¥Kö?~<—]v7Üp }s¤ýóŸÚÆ~€ÓO÷zßU]L˜ ÏÿX,°zµ6¡ªBÑXnn.©©©!qÙ<¨Ï9n¿ývV­ZÅ÷ßßPütíÚ•¬¬,Ýþ¤[·nÍ¿ØÊ•*»êqf³6U†ûM)6Ìš%%¾ëWsBáÍ×ä«' õ$$<)h »ÝÎÅ_̾}ûøöÛoéÔ©“nûùçŸÏ¶mÛØ|$àüÃ?PRR¤I“šÁ+€@›,uÑ"}hß>¸î:Ÿu©Y’OQOޱz’RO2@“‚öXII :thÒÞ¿222xûí·¹ùæ›éÖ­ùùù,\¸éÓ§7ì«»f0hS­»ßb î¾[Ëÿ¸{ñE¸åßôG!„’K`~è믿æÓO?¥¼¼¼Mû'$$àt:›,õÅÀW\Aaa!Ÿ~ú)‡Ö?M8Ðh Å@ñä“Ms?wß ›6ù¦?B!„¯LT^^ÎOiøšcõ$¤žd€„'Ltá…²zõj yî¹çˆŠŠbÖ¬Y$¸ÿUWíË/cr­fœp,X oÛ½ŽÜðæS’OQOޱz’RO2@“*T]]ÍçŸβeËHOOçÌ3ÏdæÌ™\|ñÅJÞ/--¿?ñaV««qýz5JÉûyÃm·iùw¯¾ ×_ï›þ!„ð’òC¯½öIII,Z´ˆ‹.ºˆÝ»w³dÉeÅO½}½{ëô2X½¹saøp}Ûí·ÃÖ­¾éBá S=š«®ºŠŒŒ >úè#V®\Iuuµò÷ÝÝ·¯¾!À  ðpøàˆuµUWky ÊJßô)>iøšcõ$¤žd€„'L4tèPæÍ›GFF7Ýtß}÷dæÌ™Jß·I´v­ÿ$ØN}ûj—½Üedøî¶xɧ¨'ÇX=É©' áISìÚµ‹7ß|“… òùçŸSUU…Á}”?Šओ\ v;|õ•Ò÷ô†Y³šæ~Þ|S[¼Í}.0¡†cõf̘A¢ûÐëÂãRRRts q<¦z衇>|8|ðƒæƒ>àðáüÿþûêß|Ú4ýz€_«÷ßÿÂСú¶[nÑÎ !„Á,`  [n¹…ââb>ÿüsî¾ûnRSS•Ÿýi¤Pd¤–ŠŽvµUVjy /Ä«H>E=9ÆêIH=É O ˜(99™òòrž|òI®½öZî¾ûn¾òÖ¥¨  "µž“Û¶yç½0^zIß¶u«vg˜·H>E=9ÆêIH=É O ˜(;;›Áƒ³lÙ2:tèÀþýû¹ôÒKyì±ÇÔ¿yd$œy¦¾-HÎ\}µ¶¸{í5xï=_äSÔ“c¬žd€Ô“ ð¤€)€æÎËôéÓY¿~=sçÎeÙ²e¬^½šGy丧Ãh“ ½ V綾´³A>üÐ7ýB!T ˜(''‡I“&éÚ D§Nøý÷ßÕw qôþ8Gèh-éj«ÏýíoÚÍoªH>E=9ÆêIH=É O ˜häȑ̛7‚‚êêêX´hv»^½z©ïÀÀг§kÝj…ôtõïëEC‡ÂóÏ7mæ˜:Žz“|ŠzrŒÕ“ z’ž0Ð]wÝEll,;wfÈ!$&&rÏ=÷°xñbŒF/ý3‚ü2Àu×Á /h3È»ûúk96nôü{J>E=9ÆêIH=É O2ûºmÆŠ+ؼy3»ví"!!SO=•øøxïubÚ4ýÊAXÜz+¤¦Â¥—‚û•“ƒáôÓáå—aölŸuO!„8nsè©§žâ¾ûî#55•K/½”É“'{·ø˜4 Ìn5ãž=°{·wûà%§Ÿ®í;Vß^S×\£ ˜h³yæ½$Ÿ¢žcõ$¤žd€„'LÔ±cGòòò|Û‰¸¸¦AžHIo¿…›onºmÞ}úðüƒýû÷{µc8 nw5L™îƒ/nßÙÙ^í“?¸äX·ú÷×·Â9çÀSOù¦_B!ÄÑøuÍÕW_Í÷ßϪU«°ÙlœqÆœ}öÙ¼ûî»Þ™xõ`¦k%1FÒïbgê ¨A^¨o·Ûá¾û`Æ ¨¨hý5$Ÿ¢žcõ$¤žd€„'ù}ä®_¿~<þøãìß¿Ÿ;3eË–Ñ«W/n¹å6oÞ¬ô½·ÖÅPë~ìœsô;„hÚðH}ÿú—þÄÀ²epÚi°sgËÏ—|ŠzrŒÕ“ z’ždp:N_wâxäçç³xñb¶oß΂ <úÚiiiüs„†õWNèÄ_N¢­¬] cƸvŽ×f 5Ìì"J|ñ\v4þ‹ƒ\¾Bÿ•››KjjjHœ5˜3@™™™ü÷¿ÿ ªªŠû￟»ï¾‡ÃÁ]wÝåñâ§9óf¸VN9:vt­—–jEQˆ›:6lÐF pWV¦]&{è!ðQ–]!„h0ÐÂ… )((àå—_æ‹/¾Àl6så•Wz­¿ÖÅ`­ÿëm2ÁäÉúBø2˜»Þ½µQ¢¯¸Bßîtj—ÉÎ;Š‹]í¡ðIÃ×ä«' õ$$<)`  púé§°téRxàžzê)rrr¨¬¬T÷ÆÖ’†/íÆÞÎv; â·Ã·&2R»äõüóÚtî>ÿ\;öë¯ÚºäSÔ“c¬žd€Ô“ ð¤€)€:wîLFF‡fóæÍœzê©”••QVV¦ì}c õáêyûs­Lªßù—_À×¶ú™Ûnƒo¾Ñž °g¡zï=£Æä«'ã©'ã O ˜èºë®ã¡‡bÀ€̘1ƒ=z°aìV+ÉÿºzÐÀ|}A³¹.[}n¼kW6̵Ñé„/¿TÖ—@uúéÚî™qÐf¹ì2¸ë.°Z}Ó7!„¡)`  ¡C‡òÛo¿ñÙgŸ±hÑ"L&ÿ÷ÿ‡Á`Pö¾c‹#¡Îu†ÉnŒäÝl·1ä2X›¤¤ÀwßÁM75Ýöïç§ }íµð °z5”—{¿ŸÁJ2@êIH=É O ˜Àf³ñã?òÌ3ÏP[[K±{šVg,«õƒØÌÛï6*tãèË/µ3A¢ ‹æÍƒ… !"Â}Ërjkµ+ˆo¼sæÀgh# ôí —^ =Ÿ~ 99¾ê}`“ z’RO2@“f M›61yòd&MšÄ–-[ÈÈÈ`÷îÝŒ;–Ç+9 ”––ÀïSzðªõĆv³£šê‰Ó0 ÚT艉úÓë×7)Zèüò \|qû'NHJÒn³OM…áõǓNÒnÌBqìd ?4oÞ<î»ï>Þÿý†¶¾}û¦ü?êÞA“¡ÎUàÔ#y?çÈY!‹Î:Kÿ¹ vT#Fh¹ Ë.ƒnÝÚ÷Üü|m²ÕgžÑž?hÄÆj#Nßx#ÌŸ¯ ÉTU¥¦ïB!_À [\VVÆ€šÝ®ô½û$ô$©êCòãF6´½¸o+—w?2 è´iðñÇ®'¬\ >¨´OÁ 1ÞyGûÄa6'³ilÞìZ23µ9ÅÚ¢ºZ›“lÝ:W›Ñ¨r?[4t¨–]5¹¹¹JoZ(>>sˆ¯’Õj¥¢¢‚ŽîƒÐ qŒæ иqãxõÕWucþ¼þúë$$$xå‡áüÄxÝúk$öú«‡s@k×BI ¢m–/_N§NÚ¸’÷Ü£EÛ·kWþ^yE OÑÑm]‡22`ÉmbÖ©Sµ0v\œ6Ñå—Ã#Àûïk—Êá¤|M2@êIH=É O ˜ ÝnçÊ+¯dåÊ•ÔÔÔœœLuu5}ôcß_í!õ ´´4víã¤M¿Éõø½~)Ìêv’¶2`€vÊ¢Þ‡jS¡ r8`×.ý™¢M›àðáãmƒA»׿¿væ¨××'œÐt¢W!„6¡” ˜sµ&“‰wß}—;vðÛo¿‘À)§œB\\œWÞ¿_ÇÞ$V~Da£Ë` дiúhåJ)€0]…ÉÿèjÏÍÕD›7ÃîÝí›wÌé„ìlmùúký¶ðp펴æŠ#ûN!OÀ@ .¤{÷îLž<™àt:¹ôÒKY²d &/Üt^‡XÞtˤ¬³F`w:1 Ztd²V@‚Ðíà‰|Jr²ö_à~5²²R›n£¾(Ú²E»$v,‡×Öj—å¶ooº-1Q_Õ}â‰Záä$¤žd€Ô“ ð¤€ùIÍÌÌlr«»Á``ݺuÓ©S'å}¸ðÙ¼¹)LQØŒÑ,ÿ}/—¤œãÇkƒÛÔÔh;çäÀ¶m0dˆò~ºåË—+™ª!:ZË 5¾Bš› ;wj'ìê—;aï^8–qì µÉ_üQßn4j—ÎFýû·ÿηã¥ê —ôôt&Nœ(Óa(TPPÀ† d: á~ŸÚ°a_}õ+W®$))‰‘#]— vïÞÍÆÙ´i“’÷vÏÕKüß¿)ŠÞ°~†¥ˆïÇ]¬­L›_|ázgž¿þUIß„çÙlZÔ\q䉌‘»èh­(êÑC f§¤hE‘û£üBx›d€üH~~>›7o&//êêê†K]ƒáÇóÀxµ?ç&D³Øí2ØÏ5a8œNŒõ—ÁÜ  •+¥ ‹ë Íôéúm¥¥ú‚¨þë]»´[ðÛ«²R»,×ZíÞ´8j®PjÏqB!4~¨ÞƉ§oß¾^{ÏæÎýV´›Á›v)²¡íÿôäÂä>ZÀäH> €°0(*’¿PGÈù§²²š/Žôά(ññ-Gõ_C.=zæ1’RO2@êÉ ?´gÏÂÃÃéÓ§FÞ<¨c_:T­ 86µ¡í?»7iЀЫ—k~«ÒÓá¼ó|ÔÛÀÈùƒzöÔ–É“õÛjj´3DÍGž&ª´T[vìhm¯å„…ÝHd$Ê—ˆ0›µ©IÜ—`' õ$$<)`  „„î¼óNî¸ãn¹å®½öZ:tèà“¾œÉ»n·WÿT†0€vì•W\W®”è(µø9šˆmäé¡C›nËËÓòF99pèPÓ%'G+j<çF¬V­&÷ÕX}Fcó…‘ÉÔr{kÛÚÚn46] †Ö×­mï¼£_O|ÝÒ¶ÆÓ67¢'ö9Þç´ôõ±ìg4¦pÚiç“—ç:îCÓ¥¹ö–ÚDè ˜hÊ”)lß¾o¾ù†—^z‰Gy„Y³fqÛm·1´¹¿0 Ý;hïþºŒÚ=ÎVS,+ïcz—ÞÍ@B4Ò¹³¶´¦ªªiQÔ\±(sž9Z&„¿i©P ENg""®ðu7¼"`  zgugu|ð×\s ¯½öãÇgΜ9\pÁ^èäNý‰¯üœÒF—Á¦wé “&iiZ›MÛ°g6"Ÿ³K&3@*EEiß6GûÖ))9z¡tøp.µµrŒÕ*â À_«Ä TžÍ9®¼^[ç ^""Bàš5ö“ZYYÉ;ï¼Ã¼yóÈÎÎæ¶Ûnãšk®áûï¿çÁ¤  €¿üå/^éË´¸Þw ¸®©6i—ÁbcaÜ8øö[ׯ•+áÖ[½Ò¯@È  -ƒµ¼Ïüù˹ᆩ®¦a©ª¢Õõ¶ìÓÜsjj´ñ”ìvýüÒ‰€d€Ô)6’Ç/`î{çw¸ùæ›éׯ·Þz+³fÍ"""¢a»Ó餴´”„„½gswÕÛœŸÁð­.ƒ|6°7çtéO=¥Í¾YïÜsaÅ õKˆ@Ô¸ ª_š+–Zk?–ç8Ú%¸ú¥ñz[ÛÚ²Oý{¶÷ëö>§ñoîæ~“mŸcyN{_·¥¯g¿ú¥þø7^ško©M4w/¥¥OùºÊÌ :ðÅ_0zôèf· ?G“š4€¸Ê/(‹ÖÐöï=¿hдiú(=]›KÁ_æEÂBån0xZ+”BÍáÇ9í´·)€üÆþðŠ‹‹yùå—Ù°aS¦Lñ饓)qá,uûù¡úÈ-ÆA×®ðûïÚzUüðœ}¶÷;$¤žcõd õTäzõ"=<Ü „Ä5küþ&ÀììlFE×®]™?>÷Þ{/ß|ó ƒfîܹ_‚­³ IDAT<÷Üs>ë۽χ붖S<«ò³´•©Sõ;ËÝ`-Z¾|¹¯»ôä«—žžN©¯Æ¬^½Ú×ÝAÂï3@wß}7»víâž{îáÉ'ŸdÓ¦M>€øøx,Köððpjkk}Õ5îp8ëÖ«Í ¤dCÇŽpê©úÝ'J Bᓆ¯É1V¯°°ººº£ï(Ž™Õj¥¨¨È×ÝAÂï §ÓÉ7ß|Ã’%Køê«¯ÈËËcÉ’% ‹¯–<„˜Ê ]Û3»Ök_L›¦ßYr@Í’|ŠzrŒÕ“ z’žä÷ [n¹…5kÖ´ºÏæÍ›•¼w[2@¤Ïãƒkº¨ºb*Ͼ~þÜoÛ‡‚m²"!„ÂÏ„RÈïÿ¿ôÒKyuëÖQTTÄ´FgeæÍ›GYY™®múôé <¸Í¯}ï€Óù$# Úá¬2wà»ÂÆŸr $&Ba¡¶ci)¬] §Ÿ~|ÿ!„B¿¿v¼²³³Y°`W\q+›¹õøãóË/¿››Û°ÔÔÔ´ë=Æv=™è ýe°¹»Ök!èÉ“õ;Ëe°&Bᓆ¯É1VO2@êIHxRÐ@deeµ:pÖœ9søÏþÓ°Œ9²Ýï31Z(Ó+ŽŒ±.9 £’|ŠzrŒÕ“ z’žä÷—ÀŽWjj*©©©”””´¸OVV™™™ôèу¨¨¨czŸ{ŒaEf´aD+Íù±(—±S§jCŒÖG­~ùòò sçczŸ`$¡ª'ÇX½3føº A/%%…óÏ—‰P…gý £‰åÑGåœsÎ!))‰Ç{L·}Ñ¢E¤¥¥éwõëg¦ 'ª2-jØöô®ŸI›?_›£~§¾ü²Éóe]Öe]Öe]Ö}±¾bÅ ÒÒÒ˜0asçÎÅf³ üþ.0O¹ãŽ;øÏþÓâ>ëׯçÌ3Ï$==Ñ£G7|s4þ¦iÉÒçñ¹ÛÝ`1u…”Ÿ} Ü?<ù¤kÇË/‡·ßn÷¿!XÉlÜxLïŒ$Ÿ¢žcõf̘Abb¢¯»Ô$$<)è  Õ«W3{öl¾üòK¾üòKfÏžÍÏ?ÿ À–-[èÙ³'Ó§O窫®bäÈ‘\wÝuŒ5ê˜ßïŒ(‡n}U¹ ,˜4I¿£Ü &„BøLÐ_¬îÛ·/³gÏföìÙ m½{÷ଳÎbÍš5lÙ²…ššî¼óN†~\ï÷×~§ñåžr@›µÜ܉%yŒœ6 Ü/C¬\ >x\ï,$Ÿ¢žcõ$¤žd€„'ýOjrrr«¿ø|Ì—¼š3¹Ç("¶Ì§&f@CÛÜky¯qè§Ÿ ¨H›45Ä-_¾\.Ñ(&ÇX½ôôt&Nœ(—Á*((`Æ rLxDÐ_ó…Ó#íºõ•e5Ы pE8ºÛáC™üaVOޱz’RO2@“¤RàÎ~£×]^%æÎl//’Q¡…B?!èyá;umOd¬iZ}ñ…ÜÌSå rŒÕ“¹ÀÔ“¹À„'I¤ÈØýHšŸ—VÃøñéjÌÍ…-[¼Ü3ÿ#cÔ¨'ÇX=H=Hx’@ŠÜÙw$î—ÁŠÌɨ«‚ ô;Êe0ɧxcõ$¤žd€„'I¤Èy'Œ&¼r®í‰Œ%$„Bø)€1``tx­®íÓ⊦Ð?By¹{æ$Ÿ¢žcõ$¤žd€„'I¤Ðí}Rq¿ VhéÂînáÈ@ŒØlðõ×Þ|ŠzrŒÕ“ z’ž$BöKXå^·ïX-—Á‘|ŠzrŒÕ“ z’ž$B œ^£kû_q¹@B!„I¤Øœ>Ãtë–dö=E› µÞ‘áåžùɧ¨'ÇX=É©' áIR)vIŸqXª];ð œ~º~Ç> $ùõä«' õ$$E=9ÆêIH=É O’È‹®ì7su¶«Á`âß©=õ;}õ¼ô’w;æc’OQOޱz’RO2@“¤ò"“ÁÄ0S‰®í­p ¬ßñ®»à矽Ø3!„"´Häe7ö [Ï1'S°ä]ˆ‰q5Z­pé¥PPàåÞù†äSÔ“c¬žd€Ô“ ð$)€¼lv¿ ˜jr\ 3OØsáõ×õ;feÁe—ÃáÝú€äSÔ“c¬žd€Ô“ ð$)€¼Ìl4s²Aÿ æÃ¼<øãaÎýΫVAZš÷:ç#’OQOޱz’RO2@“¤òzê/ƒe™“)´VÃܹ0fŒ~çý >ûÌ‹½B!‚Ÿ@>pmÿ‰˜j~w5Ì<¹,øðCHJrms:áŠ+`ÿ~¯÷Ó[$Ÿ¢žcõ$¤žd€„'Iäf£™!ÆB]Ûû‡üêÖ Þ{O?Btq1̘µµ^ì¥÷H>E=9ÆêIH=É O’ÈG®ïq’ný ± Ŷ#ΤIðÈ#ú'lÜ·Ýæ¥Þy—äSÔ“c¬žd€Ô“ ð$)€|ä/ý'b¬u]–p-<“éöÉæþûaútý“^{ -òN…Bˆ &XŒ£çç½ÜC®ƒÞz úôÑ?ñæ›aË/ôÐ{$Ÿ¢žcõ$¤žd€„'IäC×öè«[?`ìBYÕÕK—BD„«­º.¹‚(k ùõä«' õ$$¼éÜ`{öÀUWiwˆɧ¨'ÇX=É©' áIRùP˜)ŒäéÚÞÈÍ£IYóç?õ×êÛ>ùžzJiÿ„Bˆ`%]ÓMŸñÉ1'sݯ?4Ýñŵ³Aî|ÒÓöÎ;$Ÿ¢žcõ$¤žd€„'Iäc· œ„¥b—®í¢:Þ<´_¿cD,[¦å‚êÙí0k:D “|ŠzrŒÕ“ z’ž$E˜ÂYÐ7¬îw„¸vçn6WTèwîÝ/Öî«——3g‚Íæ•þª ùõä«' õ$$E=9ÆêIH=É O’ P+|‘r÷Ôš§¸oÇ:è{›®ýÜÄD>2£ûx@pÊ)à>vP÷îðË/”ä¥ !„d’~áoãþÆÙáeðû§ºöO ¹ß>ýÎÀ‚ú¶ìl¸ì2h|½Bâ¤òc ,¾h1I9ï@éVݶ§äíÇõO˜9n¿]ßöÕWððÊ{z|Bᓆ¯É1VO2@êIHx’@~.9&™7/\€aû? V?süõ™™¬++Ó?á™g`ìX}ÛãÊŠ{zì$Ÿ¢žcõ$¤žd€„'I¨¾Î¹»ûË»ynë'0üE0†7´§„‡³~ÄRÂ]määÀˆÚݺi·Â›L®¶âb¸ä¨©ñ^§…B?%P€3…±dÆbJ7ÂźmëË˹.3Sÿ„³Î‚ýKß¶iÜz«âž¶_(|Òð59ÆêIH=É O’(€ôëØ—þðì_?è¶½sø0O<¨½÷ÂùçëÛ,€7ÞPÛÑv’|ŠzrŒÕ“ z’ž$ VøSÈÝ]Á;Û—Áˆ— ºOC»Ñ`à“!C871ѵsI Œ{ö¸Ú""´yÄRS½Øk!„þN2@¯½|ÞËœß ¶=6×'N‡ÓÉe;v°£ªÊµsB,]ª=õjj´øàƒ†¶I“&qá…rÛm·5|s4þ¦ñw³—ÏæÍ-o‚1†¿1®[á Àÿ Â:¹žðâ‹pÛmúùãµËa^0þ|n¼ñF¯¼W¨’c¬ÞÒ¥K™8q"‰îSÑ:tè6làüÆs ‘K`!"))‰ÂÂB][^^]ºtñQ<ãÅ?¼H¿ŽýÀQ Û[qÃ6'pÅŽl«¬t=áÖ[µ[áݽÿ><¯`Qùìžcõf̘!Åb)))Rü éèì³Ïfݺu EPvv6»wïfüøñ>îÙñ‰ ‹aÉŒ%„™Â 6¶=¤›.£Ânçü­[)t?ÍùÚk0hþ…îº ÒÒ´±‚„B„„P) ̾î€j«W¯æõ×_gݺuÌž=››nº‰ÓN;þýûsÕUW1nÜ8&NœÈªU«¸çž{þ Àˆ®#xòì'¹ë‹» l;ì|úÿ­aû¾šflßΪaÃ0  Ë–Á©§By¹¶“Ýÿü§6RôÛoCŸ>-¼ÛñÉÍÍ%99YÉk cõ ‰Çlú_«>cµZ©¨¨ cÇŽ¾îJàp:µ‰¯óó¡ Àµ¸¯»}Ý%?Ÿ+}Ýg/ ú Pnn.º¶Aƒѹsç†õ5kÖ™™ÉÉ'ŸÌ¨Q£Ú5Tω“óÞ=Ïv}¦5ô½º]¢Û禔æt’«áõüOão‹ØXxá¸új÷Sò)êÉ1VO2@êI¨ª:j£[/,l÷Yü{ãâx*Æ´ úèxz_•ϰ—‡ñ{Åï€N~ :ŒÒíóòI'qcJŠ«áË/aölø]?ª43gÂ+¯@B‚Ò~ !DЩ®†²2(-Õ?6×Öx[i©VÐTWý}ŽS¨@r®6È%E%ñÖEo1eñœ8à·G`Ä<ˆtÿ3g×.FE1¾¾¨™2~ýUèãõ/øÁڨыC€g¥„¢Ml¶c+Z·Èíå¡’ú”3@­†3@õîÿú~ž\ý¤¶ÕF¼ ¦¨†í,ÖIïˆý_yE CWUéÛFøÛßà‘Gtã ɧ¨'ÇX=É©çñ PM deÁÁƒú%+K[ŠŠ´ÂÅ}*¡@”:ARRÓÇFm‡†~zHÜ/?©!âщ’¾/Ÿs~†ªƒÚ™ ¡OP?]FÍÆ[·òãˆÄÔO—pà 0a\vüò‹«Ýá€'Ÿ„U«´¥ÝsDí´|ùrɧ(&ÇX½ôôtÉ)VPPÐö ù¹M ÷õü|õö4‹¥ÍÅ II˜Ø®©Î(|êÉ VÓ €}%ûHŸJYm™ÖÐcô¹A·Ï…:ñÑ!n³ˆa³ÁCÁ3Ïh¿XÜEEÁ¿ÿ ù‹²¾ !„NYYëÅMv¶ÿ]r ƒ¸8m‰×?¶¥­cGíQ¡PQÎ…Þ ½yå¼WøÓ²#ƒf-è>ÐerÃ>Ë xxß>íÝ[ÿd‹E;ã3m\y¥öË¥^U•v¦èóϵñ„ÜG™B6§S›§>ÇÒÜR]­Ýid·kšûºµmmÝÏn×îj:xPËÖx‹Éä*HÚZ¼4·­qÄ@ø”@!fÖY¬Ú»Š76½¡5ìœ Q= v@Ã>ÿ:p€¡ÑÑÌt* Á„ Z@ú†´[æÝ-_?ÿ o¾ “'7}n $Ÿ¢žcõü.TW×zÑRV¦ùÕ–}üäB¨<6 É]»BϞУ‡öèþu—.Zñí©w~ÄO~R…7=Îó¬9¸†ÌÂLpXaÛƒF½ŠÓâúµrMf&ý¢¢Óô:tÐî[´æÌq œÚ­óS§ÂwÀO@xøQû#ùõä«ç• PQ¬]«}Ð(,<úY™ SlÚ< PÇŽÍ6õ))à/«ð:ɵ"Ø2@î6çnfô룩µ×j ±0‡ÁõË 9,Œ·dR‡-¿Ðž=pÅÚ/åÆN>Y HìáÞ œNÈÈ€t-™™~s6Æç""š4‹9sÓn’A/59•§'?Íí+o×Ê3pd> þÞ°O®ÕÊä-[¸»GëÝ›0c33Äœx"üð<ú(<ö˜~ÄÑ_…Q£àé§›Î6/„Ы¬„uë\ÅÎO?AqñÑŸç ÑÑú Lã%:Z»œd4jmýúXž ½ziw8 q¤ asN›Ãª½«X±s…Öpx•Šî1«a'07+‹¯‹‹ywÐ DE5}!³Y›3lÊílÐþý®m55Úe²Ï?‡… µkêH>E=9Æêµ;´¿þìί¿zvâa£bbZ/\š ö6^bcµÂÃÈ\`“¤ q /XȰùÃ8T~HkØû*1&5ÝfRçvª}SE#7nä¹Oä÷i3Ü[¶À-·h“§ºûüsí’Ø‚pÞyºM’OQOޱz­f€¬Vm-÷‚§¹©fŽ&"FŽ„1c {÷Ö š˜04Ð" µk !ŽB2@­æ »ôýéœýÖÙ8œ®ñ}Fú3y=ÿÂÞf‚”çwêÄ‚þýéÔÚàZï½7ÝÔü­ª7ß sçBd¤'º/„ÿ9|X_ìlܵµíäd;Vûp1v,Œ¡%#„"’!eâ ¹ÿôûyì‡ÇÚÖþöuJfò4ÞjôƒðIA'—•±hÀ¦´t*úOÒ~a_y¥–r7o¤§kéÔTOÿs„ð®ÒRíòÕ–-ÚÝY?þ{÷¶ÿuL&2ÄUìŒ ÇãBxŒœjE¨œ¨sÔ1~Ñx~Ìú±¡Íb´ðòy/Óm:7îÜII]î9àŽîÝy¢O› Hƒ6ˆÙO@Zš6.‰»°0xì1r/¿œä®]=û:’ò§vïv;õËñ´ãSe|<Œí*vN;MËÛˆIH½P:ÔÂ_-jÌF3ï^ò.  m6‡ë>¹Ž5ÿÅÆ©œ™ {Žøwv6§ýò ¿UV6ÿÂF#<ð€ö©¸_?ý6«åg+Wj·Ô7.’„G,_¾Ü×],åå°f ¼ü2Üx£–¹‰Õæ¼›1C»ëñ“OŠ€t Õ±‰ûö…«®‚ùóµ"ª¨Hû¾øa8ûl)~Ú   €Õ«Wûº"HÈ V„Ò zK[ÊÌgâDÿmqVï³X2ã}^˯äû÷ëÒ‘F#Ïœx"·tëÖò‹WVÂí·kAè–˜ÍÚiÿ¾}µ¥_?×ã 'È e³œNØ·Ïu6§þìξ}Ç7ÞNx¸6DýÙ±c¡¹‘Õ…ð3¡tH  V„bðÁö¸æãk¨²UéÚûtèÃdz>¦*¢'—ïØÁîfÒç&&òFÿþtn-¨ùÑGpýõÚ'àö0›µñ?Ü‹¢úB©wïvÍx,BPe%lݪ¿|µu«~$óca2ig†† s=#GJXY¤P*€äã´hbæà™œ”x,¹€ƒ¥Ú÷ïeÌ‚1,¾h1›FMgή],lôCòia!'oØÀÂ8§¥ëô_¬å®¾¾þš\ M锺:í2Ùž=M·™LúâȽ@êÝ;äÿMÈéÔ&ß­¬l~©¨hº¾s§VììÙsü£('&jÃ9œ|²Vðœ|²6ÒyD„ÿÍ„$$©ïÚ¥ýñÞµËõµŠâ¨>sT_¹G=zh!î@P[ ۷æMÚ²y³vÖ£¢Â×=ó:鋜ú³:m˜ÈWˆ`JP~ôžb1ZxùÜ—Öes>ŸƒÍakضlÇ2vífù¬å|=lOgeñð¾}ØÜŠ”m••œºq#OõéÃmÝ»sÔqi##µ±P† iº­ºZ+„ê #÷éСc+Žêê\¯ÑXD„6×Y}Aä^$ùò¶ý’­¸q/v~ûMî ;³ hz «¥‘Í…AMεBÎé}à{f|0ƒüª|]{§¨N,¹”ñ½Æ³¡¼œËwì`gUU“çOëØ‘EÐ¥QÇ#ù”ÆÅ‘{t¬ÅQkbbôE‘û£'ó ÙÙZã^ììÛ×î—isÎÊßÚÄ›11ÚcýÒÚz—.Z±3p Ò³:’RO2@ê…Ò )€Z!PSKrÁ’ Øœ»Y×n1Zøï9ÿå¦Q7Qi·sÇîݼÞÌ\GI o Àyn9‰ùóç«§ªªÊuY­~Ù¹S{TñCÞ±cóÅQ¿~-õâp@ffÓb§ àøú’˜©©Ì7¸qôh°Ù\K]~ÝSmv»v&¯=EJãõ–¶EDßñPH2@êIH=)€ PKªlU\óñ5|°ýƒ&Ûny/üá,F åçsýÎÙlMö»¹[7æžx"‘¾§b IDATÎØ”—7_íÜÙþÛôÛ"9YÀÎÎÖŠ­[µBíxôêÇkKjªöØ£‡gú-„ ¡TɹZÑnQ–(ÞŸñ>ú ã¡ô‡t“¨¾²ñ~Ëÿe\ÆÅIIœÇU|S\¬{y99|[R»2,&ÆÛÿ—ØXm‚É#šn+*ÒEî_ëØ1¹¹Úòý÷Gß·%f³v9§¾ÈIMÕ–Žý5…"ÄÈ VÈ £[±s—t9eµeºö^ñ½X>k9©É©8¹YY<¸oV‡C·_¸ÑÈßccyhøð£¤ýInnÓ¢¨>wÔÌ‘Ç,:Z˯¸;C‡¶;Ë4ãù1É©' õä mtÞIç±öºµ\ðÞì*rÝIu ôãÞÇ¢ qé K¹§G&%$pÙŽdº]ê©u8øÇâżT]͘¸8ÆÅÇ36.Ž‘±±-ä’“µåŒ3ôíN§vY«¹³F{÷j™–tîÜôVß¾¹ùòåjsV‚ôôtÉ)VPP  á1r¨r¨íJjJ˜µt_ìù¢É¶Ï|G&>‚Uv;wîÙ뇵úzaF##bbϸ¸8ÆÆÇ“è£9Ûí°¿«(Ú½[+¢ê‹_ÞZ/„„Ö )€Z!PûØvî]u/Ïþôl“mô¿€Å/&6L» êã‚®Í̤°µ3"ôŽˆ`ì‘3DcããÉP΄¯…RäÇ×D 1LÌ2—·.z‹³þvå3?fÌëcØS¬ÍãuA§Nl=åf$%‘ÐÆ@ñ¾šÞ9|˜[víbø† tX½šÉ[¶ðýûù¢¨ˆR°E¡ðËÌ× ©“ïA¥¬V+E*îÎ!I áqWž|%ß_ó=Ýbõó&mÏßΩ¯Ê×û¾ kXÌã99ì8õTôïϵ]»20*ªMèr»¯Š‹ydÿ~¦ýú+׬áäõë¹qçNÞÊÍmv¶úPµ|ùr_w!襧§SZZêënµ‚‚V¯^íënˆ !—ÀZ!—ÀŽOnE.½k³×êÚMÏM}Ž9§Íiñ¹ÅuuüTZÊeeüXZʺòr*íöv÷¡sX˜.\=$:šX“ £\:Bˆ&Bé˜Ü&”IŽIæÛÙßrÓŠ›X¸yaC»Ýiçö•·³åð^>÷eÂLMÃÍÌfþ˜ÈŽÜQcw:ÙRQÑPýXVÆšš£ö!Ïjåã‚>n4¢r”ÉDŒÛÛh½µö–ö•¢J!‡@B©pS8o\ðÃ’‡q÷wcwºÎâ¼±é väï`þ„ùœ|âÉ­¾ŽÉ``Dl,#bc¹µ›viíPm­® ú¥¼\7kkªìvªìvòŽýŸÖıUC££95.΃½hžŒ¤žŒ¤žŒäYuŽ:*¬TÚ*µGk%sbiÿÙö@$?©Â+n?ív†tÂÌgRTí 1þ”ýî›ÀCw?DÿÄþôïÔŸNÀd0õ5SÂÙ‘”ÄŒ¤$j6”—³æHAôSi)ùí¸ËìxkQuv‡<Ø«ã”ô d oq€Ô Åq€êuXíVjíµTÙª¨°V4+ _)`*j+)¶ÕPl­¡´ÎJ©ÝFy]•v'•UN¨q¨q°bÂn S$˜£´G“öyÞX_ÿ³½B2@­ çí-ÞËK.`[Þ¶÷ 3…ѧCú'öç¤Ä“8)ñ$úwÒ¾îÝ¥]ï·«ººá ÑšÒRöÕÔPm·ãßô§ÇÇó@¯^L“O·ÂÔ9ê(¬.$¿2Ÿ¼Ê<ò«òɯÌox̫̣°º»ÃŽÃéÀ‰‡Ó¡[œN}[[öin?§Ó‰Á` ÜN˜)Œps8á¦ðfÃLa-nkëó °Ú­ K­½V·ÞÜR[׆}½N­ÃN &j±`5„a5X°©3„ã4Gƒ9ÌÑ Šk‰Ò0FÏLñÙËT?ý¾G^ËŸÉ áU}:ôá§kâÊÿ»’åÍß™dµ[É(È £ £É¶¸ð8­ jTõëØ˜°¦sŠõ‹Œ¤_d$W»]þq8T9TØíÍ.•íl¯°Û©t8šLóÑ^«KK9ç×_˽zqA§N5=ˆ6‡‚ª‚†"&¯2OWÐ4n+®.Æé—üˆ)Lõ…Ê‘b¥áë0Çk–ˆˆnº±}SÛÏHx]LX ýñ#Ò¾MãÑïÅYá„6·ZV[ƆCØphC“m)±)Íž5êгÑõ­n428žds:ÛU8m,/ç«F“Äl(/ç¢mÛͽz13)é¸Ö’RÏW «ÝÚìY™–ÚJjJ¼Ú?²V ò(ûÌÚb¬´¸µYÀ`j¦­™ýMZÒjqM°(c¨ ^r ¬r L½5Ykxî…çH<#‘ÌÂLvî$·Â³·_ZŒzwèMÿÄþôíØ—^ ½èß“^ñÚc§¨N}¿öXWVÆ¿à……-îsRT÷÷ìÉ]º`>ÆBhþüù’RléÒ¥É9œŠª‹8\y˜¼Ê<ò*ó8\áúº~©?SÓx"â `0CDˆHÖ/•°/ÆœÒzAÂÂpnpipe4mÔ>ðÅšÍÄ™-$XÂH°„ÓÑA‚%¼á¦ŒX³™“‰Úâbþ8a‡÷íóõ?E9)€Z!o”Õ–±³p'; w6EõK…µÂãïe‰ÒD=ã{ꊤnqݰ-_w[**xìÀ–àháGò„ˆîíÙ“k’“ ÷ç‰bE5u5-4Û ª twKúšÅh¡ST'’¢“èÝ™¤¨#në   Æ&‹Á`hÚ†»ÓÀa;²98dsc³“cµ“c³“m­#Ïfoñç ™ bŒâM&âÌ&Ìf:XÂè`±`¶`6¤H‰mt7©{m4zälq¨Œ$P+¤ò?‡Ê5E™®âh_É>êj¦!0Œ¤Ä¦´Z$Å…{æVöŒª*?p€÷òò¨káG3%<œ¿öèÁ ]»åáËx¢íœ8É«Ì#»,›¬Ò,rÊs8\q¸ÙB§ÜÚ¶é^¼Ád0Ñ)ªS“"¦¥õ„ˆ íL£YÖÖ²¿¦¦Ùåw«5¨ œH£‘x³Y+TŽ<ºo2é×íc2ùMÞO Hä-žÈ§Ø6öïÕFª.©5'><¾É¥µúõžñ=éÓ£¡ígmöVWóäÁƒ¼yøp‹áê$‹…;{ôà–”⎒;‘ PûÔ7Y¥YZS¦=Ö;ÙeÙä”ç`µ[µK.æ°FB˜å`÷Þ4,Fƒ‘ÄÈÄ64#;¶» qåuu×ÕQÒÌ’o³y§À©«ƒêjˆmu7³Á@˜ÑH˜Á€Åýë#aF£Öîþu£ý-‘FcËE[qDgd¥€@Þ¢:Ÿâ~ImÉ~–ä`éA”à`éA%—Õ³-tëÞ¤H˜4~ûÑ9ºs³ÏË®­å™¬,^;tˆê ¡³™9Ý»s{·nt´4©N2@.Nœ®8LvY6û˲Ø]ú;{Ë“UUÌ¡ªRrk+(²ÖRg ׇ^MÑnØhW0Öxd$óテáÃ!.œu`+ƒºò#eúuݶr°•jG §˜°:Gw¦Ktí1F{to«/h#ÛT\WØíº¢¥¥b¦ØfkÒVj÷î%)£Á@JX½""8Ám‰-)áðÖ­L9ï¼ ‹Ñè7gS‘@(TUé ¢ƒ¥9Pr ¡ípÅaå·LJÇ7ܽæ¾ôKìGlX,‡­VžËÎf^N-̉c2qs·nÜÕ½;]šN/ŒêœvU–U]BNU¿×Vr¸¶ŠüÚ mVŠmu×Ù(®³QnwPå4`Åâ[ÅÏn?6 £ÅB'KÍf:Z,t4›I<òØÑíÑêp½˜©/`êêZ¼¤ê Fƒnaa …MãB§GxxPU $¡T…v\^ cdG:Fv$59µÙíµöZ²Ë²uE‘{‘”U–EMÝÑç%kMim)ë­gý¡õM¶%Ç$7Dí8Qƒù¬*‚r»þZ…ÝÎÓòBv6×§¤pOt÷¯?ðõìN;åµå”Õ–QZ[JaM‡ªËù½¦‚çïù”pS8'v8‘;œØìöú¼Hã3Gîg“ «[¾Íýhr+rÉ­Èåûß»MQº_Œ±ûLìf}¢Úáàùìlæ:Äìädîíу¨ÒÒc>ÆNœTÙª¨´VRi«Ô}]i­¤ÜVM©µŠRke¶jÊlµZk)ª«£¤ÎN©ÃA…Ã@•ÓD!ŒZCvS˜cµÅwäLLÄ‘åÚo(?ÿ-e6´ ke%uQQ9-^® dQG‚¼Í-I‹®Èé~ÌC6´Fæžäç¿ZD(ôyª èÝ….Ñ]8%å”f÷©´U6{ymWá.v¦é€ˆ­²Wá<ð6ö¬!å|èñGÓ?cu8xõÐ!^?”C¯pê%㩪³Q娣Ú^GÝN­ÃAÓÕáÄêtbs€Í` Îi vŒ8ÆZ±hÆ0·õhšŒbiÂ,~ÎXœV" bMF:˜-$†…Ó%<†Ná‘ aW÷»xâ`ëïÄ[ºt)GŽ$11‘j‡ƒ"›¢ºº†ÇB›M×Vè¶­Èf£ÐfSZ8µVÀmñ— o(Î&Ô‘ P+$$¼¥°ºP7ÞQý²»h7U¶ª£¿€Á]ÿ=ÿáí›/-à9j1ÕUbvTî´(ƒƒƒ8“Ž–0R"âèݑޱIôïJJd|ø*þt!¦Æ­p*l¥€*©«#üÈJm]ä’“h É !¼*12‘1ÝÇ0¦û]»'9e9ÍGº±œ68ô1ü¾ºLž—AdwüKŽÁéÀì¬!Âi#Ñ;±Fˆ7ép$ô›d §sDÉáQt‹Œ£{t<="ˆ4ϯ±£‘”ðpRü4»%D0 žß"`ù{È— è×îqÝ9«÷YºmuŽ:ö•ìk¦8ÚJκ«qvž=¯€èÞPTŠr˜Ž,f§‹¢ NbMZÓÑl&1,œ¤°pº„G‘K׈ô;:˜ÍGÇ(øj.°P" áIò“*|.Ð3@¾b6šé×±ý:öãÜ~çê¶UÙªØ]´›ÌÂ|\Á×ß­!é¼3 7‰4™µÅh"Êl!ÚF´)Œhs±–m1‡I\Xñ–H"Œ&ÂFm17 ;òõñ½,ÒÓÓ=2˜h™d€„'I¨’BJB)äûX¿B!„—I$|.>iøšcõ ©«S3!¯ÐX­VŠŠŠ|Ý $¤>·|ùr_w!èÉ1V/==ÒÒR_w#¨°zõj_wC ɵB2@B!B‰d€„B!‚˜@ÂçBᓆ¯É1VO2@êIHxRH@YYY¬]»V·dddøº[!ç­·Þòu‚žcõ>ùä }Ò[qt¿ÿþ;+W®ôu7Dé_zé%^ýuºwwM0nÜ8^zé%ö*ôTUµa®+q\ä«W[[‹D*Õr:X­V_wC‰>0cÆ 6oÞܰøªø)**:®ìö\âh˾­íÓÒ¶æÚ·Ùívòóóúþ*ÔÕÕ×'ôöüUUUQ^^Þê>ž:Æ%%%ÔÖÖ¶ùµU;ž÷nïÿÑÑÞ«¼¼¼Å⯥ÿ£ŠŠ ***tmÍ}ßê1nïó}ùû¢-?Gª4÷}Ðí9ÆmùÝ¢êíµƒYÈ@þâ“O>áСCÇüüùóç{tßÖöii[síÛÊËËyçwtmÇóK¦= øðÃùùíù?úõ×_Y³fM«ûxê¯\¹’}ûöµºŸ·ŽqsïÝíý?:Ú{­Y³†_ýµÙm-ýmذ 6èÚšû¾müÞ•••^ËÏ1nïó}ùû¢ñÿ‘Íf£²²ò¨ýñ„æ¾Ú£=Ǹ-¿[T㣽v0 éÛàï»ï>^|ñE¢¢¢èÔ©—]vÿûß1µº0--%K–0`Àå}ÉÍÍ%áÿÛ»Ó ¦Î· à$e ‚²DDTT¨€hµ b¥*ƒZVë80.£ŽVj)uÃA©[Q«0j-®u[kên‹Z7dߡʪ¢€(p¿ÏûÁICîß'Îóœ“sñ$s¸9ˈÅâVmŸŸŸ;;»6[÷ŸÖy]_sí¯¶544àþýû —oܸ—eÏž=CEE¬¬¬Zµý›¼GÕÕÕhhhøÇ/ml«1.++ƒ¡¡!ôõõ_»^{ñë2¶Ô›¾Gÿ¶¯ªª*ˆD"H$¥¾×½G>˜˜˜mÍ}n_Ýwzz:lmma``ТìoãmÆøM·WåñâÕ÷¨ºº÷ï߇££c‹²¿æ>oâMƸ%Ç–w5Ư¶Õ××㯿þÒˆ³B]@[[:u­[·‚Y³faÙ²e^Ðø¦hÆcšD,Ã××WÕ1Þ9.€^µvíZœ9s§NRuÆcŒ½CýXSS“p¹ ѵk×m[ZZЍ¨(üý÷ßèÛ·/¾úê+têÔé]EÕhUUUX·nV¯^­ê(ÒñãÇqôèQa9""BáRkçÎÃîÝ»!‰øIÓwàÌ™3Ø»w¯°Ü»wo,Z´H…‰:¦Ó§OãðáÃ022œ9s`kk«êH­¦Ñg€ÜÜÜàëë ܾ}Û·oÇ… Zt¯Dqq1ÊËËÑ¿„‡‡ÃÁÁ!!!íZ³Ô××cúôé¸}û6îÞ½«ê8RTTìììàåå°²²âb¾Éår¬Y³±±±°··W¸_‹µêêja’Ä´´4ìÛ· *NÕ±í°eËaŒW­Z%̧2þ|,Y²¤Ù›XYó.]º„ÐÐP¥öÊÊJL›6 ½{÷ÆG}„?þøCè+..F~~>?-”••…Ï>ûL©½¶¶³gÏFŸ>}0bÄaÒ¾7"00ׯ_GIII{ÇU[3fÌ@ff¦R{BB<==1`ÀDDD ©© ‰vvv°³³ÃÎ;¡‚ÄêgõêÕ8vì˜Rûï¿ÿŽQ£FÁÉÉ 3gÎDuu5ôôôPYY‰ß~û <@=T¸ £ØØX:t( ÌÌL…¾}ûöQ÷îÝéêÕ«tûömrvv¦o¾ù†ˆˆ)44”¦NJÙÙÙªˆ®6Ö­[GC† !JcõÓO?‘T*¥k׮ѭ[·¨oß¾´iÓ&úúë¯éÈ‘#DD4xð`UÄV+EEELæææäãã£ÔïííMsçÎ¥¼¼<Š'###ªªª¢³gÏÒæÍ›iÙ²eäááAõõõ*H¯ª««iΜ9dkkKNNNJýÓ¦M#™LF999tèÐ!244¤ÌÌLrqq¡­[·ÒÁƒÉÓÓ“RSSU^}|ûí·äååEèÚµk }'Ož¤®]»ÒåË—éîÝ»äîîN+V¬úår9Í›7¯½#«£GÒ¤I“H[[›¶oß®ÐW\\L†††´oß>ÊÉÉ¡©S§’¿¿?566ÒÂ… iÁ‚äèèHçÎSQú¶Á]»vär9‰D"ÊÈÈPè7nÅÆÆ ËIIIäâ⢰ÎÑ£GéóÏ?o—¬êêêÕ«$—ËI[[[©òõõ¥ 6Ë»ví¢AƒQpp0QPP™™™Ñ—_~ÙÞ±ÕJuu5Éår U*€òòòHWW—?~,´ 2„vìØ¡°^@@ݹs§]òª£úúz’Ëå´víZ¥èÉ“'$‹>ß2™Œ"##éÃ?¤ÚÚZ""Ú¼y3%$$´knusãÆ ’Ëå$‘H”     ŠŠŠ–þùgrpp "¢¦¦&òöö¦ÒÒÒvÍ«ŽrrrH.—“›››RC~~~Ârqq1‰D":|ø0ÍŸ?Ÿˆˆ*++iÀ€íš¹­iôMÐ/½ÿþû---¥¾ÌÌL,X°@Xîß¿?222pñâE444@*•bß¾}ðññi·¼êhРAš㬬,,^¼XXvvvFzzºÂ$džžžˆŽŽ~÷AÕ˜‘‘¼½½‘7n(ôeeeA*•ÂÐÐPhsvvFFF8€¾}û¢¶¶¹¹¹êZûêÔ©¼½½ñôéS¥¾ÂÂB@Ïž=…6ggg¤¥¥aìØ±Ø¸q#&OžŒ#GŽ ))©Ý2«£D"å?QYYYÉd²³³3rrrÐÐЀäädxyyÁ¢ݲª+{{{ØÛÛ7;WYff&œ…ekkkBOO)))())Avv¶Â:êˆ  Q^^®pâ‘‘êêê`llŒýû÷£¬¬ cÆŒÁ”)ST˜R½•••)Œ±D"AMM êêêðÞ{ƒU¯C(//Æò%‰D‚òòrèéé!..úúúHNNæt[éÕcðâxQ^^ŽÅ‹cë֭ضmbcc[ü´)SÖÜñ‚ˆPQQúúz…¦Xë”——+=Ý%‘HðüùsÄÅÅaÛ¶m°µµÅ–-[T”°mpô/ôõõ¾£¥®®"‘ýúõSûê÷¿ÂÀÀ@iŒuuufEåèí¼:ÆÀ‹v% ÆñãÇ«(YÇñê±xñY–H$‰D˜?¾Š’u,Í/€ gΜ©ªXÊ?/\]]áêꪢdmK£Ÿk KKK Ë………°´´löRkæÆØÊʊǸ YZZâÞ½{hllÚŠŠŠ`mm­ÂT‹¥¥%jjjðàÁ¡­°°Ç¸5w¼011á3—mÈÒÒÅÅÅÂòËÏuG›Œ  !“Éðý÷ߣ©© ˜˜ˆ€€§êXxŒß=777tîÜYø¢Ñ‚‚œ;w“&MRq²ŽÃÚÚžžžÂÜ3UUU8vì–Û˜L&Cbb"ž?€ïB@@Ž;†ÒÒRÀ?ü€þýûÃÞÞ^ÅÉÚ˜ªïÂþ/ð÷÷'©TJÈÚÚš(ôÕÔÔŸŸ™››“ }øá‡TQQ¡Â´êÉÏÏOaŒ]]]…¾'OžÐ¸qã„1öòò¢ÊÊJ¦UO7oÞ$©TJfff$‹I*•Òúõë…þóçÏ“……9::’±±±0k¹²²2’J¥daaAººº$•J)44Tè¿sçÙÙÙQÏž=ÉÔÔ”–,Y¢Â´êkÊ”)$•JI[[›,--…§¼ˆˆž>}JdffFÝ»w'ºwïž Óª§5kÖT*%±XLfff$•J)--M茌$###rtt¤îÝ»+=×hôLÐŒ1ÆÓL| Œ1Æc‡ ÆcŒi.€cŒ1¦q¸bŒ1ƘÆáˆ1Ö&òóó…© T¥°°*ÍÀS\1ÖÂ××óæÍSh߸q#öïßßæûkhh@=&lOpwwLj#xÆeÆX‹pÄXôäÉ\½zÄ™3g„öÔÔTäåå©0Ù»±wï^xxx ''{öìQuƘàˆ±JOOË—/DzeËší¿yó&vìØ¡Ð‡’’’pöìYDGG#(([¶l!!!S§NŶmÛPSS£°}zz:.\ˆ)S¦àäÉ“ }çÎCHH‚‚‚—S¥¤¤`×®](((@ll,’““›ÍûèÑ#DDD aaaB΋/">>yyyXºt)nݺ¥´íÝ»w±uëVÜ¿›6mfk...Æ¢E‹ “ÉúúzÀ•+W/läÈœ>}ZXÞ°arssr¹³fÍÂÔ©S±bÅ aö\ÆØ@Œu`ÁÁÁ¨ªªÂÁƒ•úÒÒÒðã?*´­[·=8p¨««Ãرc±nÝ:899áÒ¥KðõõÅîÝ»±~ýz…í-ZGGG˜˜˜`Ò¤IHII?~3g΄››d2âââàE!† & 77WØÿÿjhhÀðáÑššŠÉ“'#33Æ ÃóçÏajj }}}XXXÀÉÉ ‰DiûÌÌL,_¾¾¾¾HKKãGPUU<~ü2™ §N¾DKK áááÂöáááX±b ¾¾aaa055Åùóç1iÒ$ >Ÿ~ú)JKKñ矶øýaŒ©** sçÎð¢ˆ(..ÆÎ;ÆÆÆˆ‰‰ÁòåË…õOœ8.]ºÊËË‘wwwDFFbÕªU ˆÅb„‡‡cÉ’%©TгgÏBWW·Ù‡FII nܸmmm|òÉ'077Ç¡ŒèÖžIDATC‡ˆîÝ»cÈ!˜1cÆksss\¹rb±°víZXYY ¿Ï¨Q£`nnŽëׯÃÍÍ OŸ>EVVˆfffÈÍÍEii)²³³Ñ·o_˜šš"33R©&L€¡¡!ÆÿÆcÌS >ÄX±XŒ]»v½ñ¶:::Âφ††ÐÖþÿC†‘‘‘ÒWZZZÂÏîîî())ðâTXXœœœàää„ÐÐPëš™™½¶ø€¬¬, 6LØ¿––†ެ¬¬ÿ.ÆÆÆBñóò5½¼¼2ôë×YYYÐÑÑ··7.\¸€ýû÷#00'NÄÁƒqñâEŒ9àïïsssXZZbäÈ‘HLLDccc‹31ÆT‡Ï1ÖÁiiiaÍš5 ···Ð.‰ðìÙ³7zZ~UFF,,,&&&HLLÄ|ÐòàÿÃÄÄDéæí¼¼<øøø´êõ^¾æËûx ±±EEE011ðâŒÐ… pëÖ-=z999ˆŒŒ„¡¡!fÏž èÒ¥ ~ýõWdddàòåˈˆˆ€¶¶6¦M›Öê\Œ±öÁg€ÓcÆŒAÏž=î6lÒÓÓQYY‰¦¦&$''ãùóçoµŸ—7E§¦¦âĉðóóøùùaÕªUBmm-8Ðâ×õññAFFär9€7T§¥¥aôèÑ­ÎúñÇC.—ãÎ; "$%%AGGƒöyøðaèëë£[·n:t(²³³qéÒ% >pæÌäåå¡wïÞ˜1c ðFE%cLu¸bLCDGG+<µÕ­[7Lž<öööèܹ3NŸ>­pÉ«5F^½zaàÀ˜1c†pßQLL :wî 8::¢K—. OUýìܹþþþ°±±Á¸qãðÝwßÁÑѱÕYG…/¾øƒ ‚ –.]Š={ög€úôé===xqÆkâĉptt.ßÝ»w...èÕ«úôéLŸ>½Õ™cíG‹^>‹ÊcŒ1¦!ø cŒ1Æ4@Œ1ÆÓ8\1ÆcLãpÄcŒ1ÃcŒ1Æ4@Œ1ÆÓ8\1ÆcLãpÄcŒ1ÃcŒ1Æ4@Œ1ÆÓ8\1ÆcLãpÄcŒ1ÃcŒ1Æ4@Œ1ÆÓ8ÿ%Gž~p¡ÝéIEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/compressed-recordsize.svg000066400000000000000000000713241416254111300257760ustar00rootroot00000000000000 Disk space taken by a record (original record size: 16 bytes) 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 5 10 15 20 25 30 Bytes/row No compression zlib lvl1 lzo lvl1 bzip2 lvl1 PyTables-3.7.0/doc/source/usersguide/images/compressed-select-cache-shuffle-only.svg000066400000000000000000000761331416254111300305610ustar00rootroot00000000000000 Selecting with small (16 bytes) record size (file in cache) 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 0 2 4 6 8 10 12 14 16 MRows/s No compression zlib lvl1 (Shuffle) lzo lvl1 (Shuffle) bzip2 lvl1 (Shuffle) PyTables-3.7.0/doc/source/usersguide/images/compressed-select-cache-shuffle.png000066400000000000000000001617631416254111300275730ustar00rootroot00000000000000‰PNG  IHDR@°AàÚ²sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwTTGûðï.°tPŽŠˆ€`CQ”"{W4v£¢Æ‚-ÆX‚%¶$þ$FQ_»&H,‰¾1@ÔˆØPA±+ "½ Hçùý±/7\véMa>çx2/^¼ÀìٳѣGtéÒÆ ÃæÍ›ñæÍ›*cõêÕX°`AÔ'''ÞÞÞxÿþ=¯|ýúõøòË/ëäuiòäÉØ¹s'·|þüy^ÇGŸ>}¸f5sãÆ ´k×ÅÅÅ€“'OÂÞÞ¾Jû.\¸Æ CAAĺ˗/cÉ’%å˜ˆuëÖaðàÁ055Å*<×–-[0a„*Õ«<=‚¿¿­ŽQVtt4:wGÖéqKVVÚµk‡û÷ï×hÿääd8::âòåËu\3Innn2d/X;}ú4:t耞={bÀ€øã?àèèˆÜÜ\€§§'† V«óž>}ŽŽŽ(**ªÕqêÓ´iÓ°~ýúZãáÇ066Æ•+Wê¨VÍ €>"7nÜ€••bcc1cÆ ,^¼¦¦¦Ø¿?<==¥N)))øüóÏÍ+744DÛ¶m¥Niß¾=tuu¹eWWWܸq£kÔ8|}}qâÄ œ?\ùýû÷áááÙ³g#00Pê¾ÁÁÁèÞ½;‚‚‚0zôh¬_¿íÚµ«÷:{yyaóæÍuzL+++üç?ÿÁÂ… %îáæHVVæææPWW¯×ó\¾|‡ÆùóçѪU+®|Æ øòË/‘™™‰ eË–077‡PXwŠ444`nn@PgÇü <7nÄÔ©S‘ÝØÕù$É6v˜ýðð´´„ïûuëV„……ñ¶ÍËËãGœœŒ.]ºÀÐаÂcãùó爌Œ„‰‰ ÌÍÍ%¶‰ÇóçÏ¡¦¦†Î;C^^žkn‰…²²2D"ôõõ1aÂòö•——‡ŠŠ DFFºuëÆ rñ“§OŸB ÀÂÂZZZܺ‰'âàÁƒØ±cG…¯©¨¨ÑÑÑÐ×ׇH$Bjj*w­²²²ððáC|øðÝ»wçýa/yíaaaxûö-Úµk'qíÃÃÊ:ÀÔÔ”·.** ZZZPRRBff&rss¹×{÷î¡°°–––^ÿùùùxüø1RRRжm[îóÞ¢E ¬^½š»~ ÈÉÉ‘Ø_OOòòòÄ÷èãÇKKK‰Ï4k׮Œ%K`ffÆ]›ÀÀ@¼xñöööˆŽŽ†––zõêcccˆD¢ †°°0¢[·nnÛ§O˜™™qŸ³´´4äååAWW·ÜÏayˆaaaˆŠŠBÛ¶m¹×oÞ¼ATTòòòЦMtìØQbÿÜÜ\<{ö °°°¸o ððáCÈÈÈ {÷îûâéÓ§x÷î,,,`llÌ[¿bÅ ;v ûöíÃÊ•++}=LÄ|4ÆO666•nwçÎj×®ihh©©)ÉÊÊÒîÝ»¹õsæÌ¡qãÆqËQQQÔ§ORTT¤®]»’@  ¹sçrë‹‹‹iùòå$ I[[›”””HCCƒ^¾|Iººº€TUUI]]lmm‰ˆhÁ‚4räHî}ûö¥þýû“‰‰ ©««“¬¬,õêÕ‹²²²¸mîß¿OFFF$##C&&&¤ªªJB¡þøãr_«ƒƒ-Z´ˆ[Þ±c©ªªRNN%$$ &""[[[Z¿~= 2„)))‘ºº:©««SAAýüóϤ©©IãÆ#‘HDªªª¤««K—/_®ðº:tˆ”””ÈØØ˜ H ÐO?ýDDDÞÞÞ$''GÓ§O'‘HD***$‰èĉ4`À’——'%%%222¢®®$''G&&&dddD²²²´uëVný¥K—ÑÞ½{ÉØØ¸Âz†„„@  7oÞ”»ÍÚµkÉÚÚZ¢üìÙ³$//Oïß¿¯ðe­X±‚Ú·oOÝ»w'%%% …¤¡¡A÷îÝ#""jÙ²%}øð·Ÿ……¹»»Ó/¿üBòòò$++˽WÇŽ#"¢«W¯’¾¾>ikk“±±1)((p눈víÚE dbbBººº$##Ã[ODôÛo¿qïy¢££ íÛ·zôèAhüøñDDtáÂÒÔÔ$CCCjݺ5©¨¨Ðùóç¹}K>c¨M›6$ ÉÉɉˆˆrssiÊ”)€Œ M˜0²³³¹ýåäähË–-äììL€ºwïNDD×®]#]]]îQSS#tëÖ­r_ÇãÇ©mÛ¶¤©©Ifff$##Cƒ ""¢ÂÂBÀÝëcÇŽå®·ºº:)))ñŽÿèÑ#277'uuu²°° ¡PH[¶l)÷ÜDDOžôÍ7ßpÇxöì/Øøõ×_I pï{M ;vPÏž=+ܦ¼híÚµdddDŸþ9éèè’’õë×îÞ½[áñV¬XAššštîÜ9*(( —/_’0—üÙÚÚrßyyydjjJ+V¬à^ç•+WH(Ò³gÏÊ=ÿÎ;yAL‰@ñññ\YeМ9s¨oß¾”˜˜HDâ<:::´gÏžrÏ/-RSS£“'OR~~>åå命½=-_¾¼Üc :”zôèÁý`IIIá‚¿˜˜zûö-·í›7oHEE…~ûí7""JMM%---úâ‹/¸ûûéÓ§Üý?pà@266¦«W¯Rqq1%%%‘¾¾>ýòË/D$~ŸºvíJ ,àå;wî âÕ3,,ŒÐ»wï*|=Œ$–ô>|8‚‚‚`dd„7bРAÐÑÑÁâÅ‹‘œœ ðóóCZZFŒׯ_#44æææ‰D¸{÷®Ä1_¿~«W¯bÒ¤Iˆ‹‹Chh(Š‹‹ÑµkW\¿~pðàA899a̘1‘‘ŒŒ ¾øâ ÞãÞª1búõë@ØÙÙáï¿ÿ!<<«V­âr‡ZµjUéchggg>>˜;w.×䦡¡5kÖôõõ¡««‹ÀÀ@8p^^^PVVFDDq狤¤$¬_¿ž»¿---1kÖ,““ZµjÞ÷å“'O0qâDDGG#44-Z´@»ví$rÛµk™Z}g4W,è#cmmßÿD„ˆˆøøø`ãÆ(..ÆÞ½{¢¢"Þ 'ÿJëõPòܰaƒÄº’v÷ˆˆLš4©Î_‹ŠŠ —œ÷æÍ(((ÀÖÖ¶ZÇèÕ«”••qëÖ-„‡‡c̘1øüóÏaccƒÜÜ\ÖIÝK×U999ìÛ·+V¬ÀáÇajjŠ™3gbñâÅPVV–º¼¼<ˆˆW¦¬¬Œ>pË{÷îŲeËгgOtìØ‘Ëy©M–´´4Ôxÿ.]º`êÔ©Üò©S§ ®®ŽK—.IÜw)É{÷î:v숅 ¢S§N ‚žžΟ?_áR@|oæææbÊ”)¼rKKKäççCEE{öìÁ7ß|ƒÝ»w£cÇŽ˜;w.æÍ›Çå‚•ÐÓÓã~HT¤lòlDDRSS%î³®]»r¹_€8X/ï5¸¹¹ñÊìììpèÐ!AFFFêy_¿~ ===©¹%Y·næÍ› èèè`øðáX»vm…‰ìG…——‚‚‚¸{°ä»cþüù¼m+ Xñý§¯¯_­:Kóöí[bïÞ½8tèoµµu­Ž]Ùç½äµ—÷žbܸqPUUEÏž=¡«« ¡PÈ}n#""`ll\¥|©ÒuJIIáö÷šššÄ=YYYhiiUéÞføXô‘011Á’%Kðúõk\¸p€ø ²²2>|X¥ã”übüïÿ‹6mÚ”»Í«W¯*|8¦M›†¶mÛâùóçøë¯¿¸àáââ ¼~ý/^DHHïØÿüóAµ›``РA˜2e 0cÆ èêêâÉ“'¸xñ"ž={###xxx`æÌ™¸~ý:ÆŽ‹'Ož $$¯^½Âwß}‡Þ½{ÃÜÜ&LÀ¹s瀓'OVx^[[[ôïßC† Á„ PTTT¥ûc„ 044„½½= áííeË–AQQQ¢iuîܹHLLDll,¯©«äýÞ±c,X€«W¯ÂÒÒáááðõõÅîÝ»Ëø²ÿþpwwGBB‚İÕµoß> <0`’““áïï>}úT:8gmÈÊÊb÷îݘ2e 1~üx¼xñ·nÝÂÛ·oñÅ_`ÇŽxõê._¾Ì{Bjmmyóæa„ :t(z÷î ???˜˜˜p9VÑÐÐÀ®]»°páBܾ}]»vÅëׯáçç‡Í›7óš¢ÿúë/´iÓíÛ·¯KѤÉl–Â4Š9sæ S§N …HLL„ŠŠ F#GŽð­ÛÛÛcРA\R³–––.]ŠBFFfffܘ!;vÄ”)S¸DBEEELž<Ó§O‡¼¼<ÔÔÔ0gÎ(**"&&"‘nnnppp€P(ÄÀ¡¡¡'Ož }ûö\³…©©):uê@Ü´ceeÅû tìØ‘ËaèÝ»7¦M›†ž={bÁ‚X½z5¶lÙ‚åË—W˜3 ­­ YYYLŸ>ûB-#füøñ¼|—’z”ä;X[[ÃÂÂ>D‹-0`ÀJŒ]’ˆ(M‹-`mm„„„††BAAÛ·oçþ  hkkó’oôõõyƒ@ @›6m¸†Q£FA$áùóçèÖ­<==¡ªª GGG¨©©qãÐ8::rÍgMG¥éëëãèÑ£‰DðÃÂÂ’’‚öíÛÃÚÚ²²²PUUåoÔ¨QÐÕÕEpp0Š‹‹±lÙ2¬]»¶Üó•°¶¶†šš>|KKKìß¿½zõâm# qâÄ L™2 à­SVVƸqã””ØÛÛCWWŸ}öƒW¯^A__«V­‚ôôôйsgÄÇÇ#,, ªªªðððƒjíÚµ055ÅäÉ“+| òòòprr‚ªª*¯|ôèѰ¶¶Ftt4"""жm[|ûí·èÖ­ºté‚#F   oß¾…‰‰ 6mÚmmmhhh`Ú´iÈÎÎFhh(ìììpäȉ?X¼ñ‹ñFŠŠŠˆ‹‹CçÎqàÀ¨¨¨ÀÑѱÜÁ œœ••…ððpdggcòäÉX²d „B!w9::¢eË–HOOG·nÝ ªª î_çΡ«« Œ9’»ï[¶l‰ùóçcäÈ‘å6‹éëëãøñã‘‘Aß¾}yëTTTàääÄ÷G[[›{VöóahhˆÙ³gãÇ aøðáæÞ•î„Q¢}ûöãìTÔ[ZZbôèÑ(,,äÆvÚ¼y3tuuѧO˜ššâñãÇhÕª~üñGXZZ¢G\'áÇ£[·nÈÊÊBBB°lÙ2.Ǫk×®É⥿·»wïŽqãÆqÉöêêê˜={6Æ999nŸE‹aèС?~™*h”¾gL³ôîÝ;®;7‘§§'iiiñÆ bêÆÁƒ©E‹¼.ǃ’®ï±±± vΛ7o’P(ä¿ÄÔ¯£G’ššZƒ¾ÏÍѯ¿þJŠŠŠ\W{¦zDu”!Ú¤¤¤@(–;J.S;³fÍÂùóçѾ}{dff"!!'NœÀÈ‘#»jMNqq1 ðññáýblLÇ‡šš¼¼¼ä|qqq°³³Ã¤I“°uëÖ9'#ÎÓ:t(òòòàççWéHÏLõ=þýúõömÛàêêÚØÕù$5›héÒ¥^yqq1¶lÙ‚ƒ"++ ݺu«U^S¾äädÜ»w ÐÓÓCÏž=¡©©ÙØÕj²’““qóæMôêÕ«Nº%×áÏ?ÿ„••Z·nÝ ç|òä Þ¼yƒaÆUškÆÔ­’Æž={ÖjXFº»wï"-- ƒnìª|²š|tñâE,X°ïÞ½ƒ›››D´`Á<}úëÖ­ÃÀQPPP­.§ Ã0 Ã|zš|TBÚ ÈÈHtèÐKÚd†a¦yhÖÝà`hhOOO<|øªªªX´h7ÆË—/ñòåËF®%Ã0 Ã4…fÑ´Ö¬ ¨¨((**bذapssÃ;w0qâDüý÷ßèÛ·/¼½½áíí ssóz¯K||LuÞ£÷ïߣ°°„À¼ IDATånSW׸dÈ„Ò#Æ–Ý®¡®qyu¬ªê¾G•+55²²²PSS“XWÞ{”žž¼‘u¥Ý·eÏ mmíiB¯Í5®îþù}Qö=úðáRSS«5š|MI»ª£:׸*ß-õuË–ååå!$$ñññUªû'­±ºŸ54777rssã•mÛ¶FÍ+sqq¡Õ«W‘»»;¹»»7HýŽ=JoÞ¼©ñþÕ©gU¶­h›òÖI+/[–––F»víâ• 6¬ÒúÔ…¸¸8Ú·o_÷¯Î{H¾¾¾nSW×øÔ©SôâÅ‹ ·k¨k,íÜÕQÝ÷¨²sùúúr³°—UÞ{äïïOþþþ¼2i÷mÙsÏ;—ž?^iëBm¿—>•ïÑ;whÉ’%•Ö§.H»ª£:׸*ß-õuË–ÅÅÅ‘ŽŽN…ui*šutúôi277ç•999q7CC@)))”——Wãý«3DU¶­h›òÖI+/[VXXH‰‰‰¼²†ºÆ”œœ\ãý«óeggÓû÷ï+ܦ®®qZZåææV¸]C]ci箎ê¾G•ëýû÷”-u]yïQff&effòʤݷeϽoß¾¥¶çùT¾/ʾGaao臎ÒÝ»•V©Ö¤ÝÕQk\•ï–úºÆeËšSÔä›À²²²œœŒ÷ïß?êÓÒÒ‚²²2FŒÕ«WcÇŽøâ‹/àçç‡{÷îaïÞ½ ^ÏŠšJª¢²š«»mEÛ”·NZyÙ2‰ÑnЬ¬l­ºÝWç=ªÊˆuu¥=¢¯ÎýP×jsîê¾G•«ìˆÎ¥•÷•ŒÔ[š´ûöS½ÆÕÝ¿!¾/Š‹„ 6VAA@L  ÄÆ*ýïqYJ @ÈÊyy€°g³”vTGu®qU¾[êë;¹²c7eM>ºyó&¶oßÎ-Ïœ9ß~û-ú÷ïøøø`ÕªUسgŒáããÃMÝÀ4Œ±cÇ6vš-n–júÄSçÄÆ=z4Ôy™¦ˆ@µäåå…Û·oⱫÂ0˜˜ÈÈÈ4«¨¹HH6m êš€¾þ¿ÿ øËß}ü÷¿ÿnWwçfš'ÕÉ“'s#M3 øùùIL;ÓÔ¥¤¤@]]²²Mók53øñG`çN ;»êû©ª–Ô””éé• Ÿ$Æ*@ ÄÆÖüµ0 À †a˜:áïï''§&7Áo~>°¿ø LR’ôm:wœ¥?Á©eg*ŽxlÌdÁF²'@L­±ˆa¦4µ "ÀË X¿xóFú6FFÀæÍÀäÉõÛ%(Ixþ7ˆ@Lm±ˆa†áñóV¯=’¾¾U+`Ý:`Á@$j˜:•…51µÅ †a˜:Ðr€îÝ>þþÒ×++Ë—_}%ÎíiHâ'@ÿæ±'@LmÕóCK†a˜æÁßß] &Lll¤?rrÀÂ…â®ï›65|ð”κ @Ü­¸¸áëÁ4ŸîO†a˜È§˜lÜ> J®€‰Å ÐíÛ7|ýJSU”•õ‘-Î**f:‹SØ †a˜f&#C<ˆ¡‰ pà€ôàç³Ï€à`àÔ©Æ~J”ù™5ƒ1µÁ †a˜:’’‚Bi‘ÄG$/ø¿ÿ4[·>Hncm \¹üý7н{Ã×±"zzùR¹e–ÍÔ €†aêÀÇœT\ ?˜š+Vˆ'(-«Cñ´÷î‰ÇôùµlùoÀž1µÃr€†aêÀÇštñ"ðÍ7âIK¥ÑÕÜÝ9s€½[ûöÿްˆ©üvg†aj"0øúkàÆ éëÕÔ€U«€eË%¥†­[M•ÍbM`Lm°ˆa¦|,ã½x¬Yœ?/}½¼¼¸Kûš5â H?%ZZÿް'@Lí° †a˜:ÐØ9@11âf¬Î¥?B!0c†xÌŸ;?½à”•ù9@ì S,b$LŸ>£GÆ2ÏÎ}||àææÖHµjzbccáè舀€€Æ® SÆß(¡¦§‹›º:tçST$¹Íðáâi-ŽÚ´ið*Ö™NXSwXØG¢°PúXuM(¬|îÈÉÉaùòå âÊ_¿~«W¯Ös ›999˜››Cµ1†Õe>y¹¹ÀîÝÀöí@Zšômlm;€¾}¶nõ¥ì|`ññâI[‚Æ©óicO€>+WŠŠõÿÏÞ¾jõùꫯðäÉœ={¶Òm_¼x¿þú oÊ›2ZЬ¬,ܹsHMMå­ËËËÃÝ»wqùòe‰uÙÙÙxûö-q@æç燄„nýóçÏqýúu—#?!!©©©ÈËËC`` üýý‘ÍÛ&22999€÷ïß#))‰[—››‹»wïÂ××±Rž»§¤¤ ¼ú”¼ž   \ºt ¡¡¡\y«V­°zõjXZZò¶OLLÄßÿû÷ @b]Éx3ÁÁÁ¸ÿ¾D]˜ÆÑPãGŽˆŸø|ýµôàÇÂBÜ vëVÓ ~@Q1 ÿ~'¥>¦ S=Ä”ËÝÝÜÝÝ+ÜÆÍÍvíÚUës¹¹‰ËÔï?›Ê뢩©IgÏž¥¥K—’™™ÑÏ?ÿL:uâ¶KOO§¡C‡j×® WWWnûòœ={–Z¶lIJJJ¤­­M²²²ôŸÿü‡ˆˆ=zD&&&¤¨¨Hzzz¤¨¨HGŽáö=uê‰D"š>}:‰D"RQQ!yyy:yò$9;;“¼¼<)))‘‘‘=|øÛoàÀdccCmÚ´!UUU@&&&EDDÅÅÅ€vìØA$¨OŸ>DDtýúujݺ5iii‘‰‰ ‰D"®¾DD{÷î%EEEjß¾=ééé‘P(¤Ñýû÷ÉÐд´´ÈÔÔ”„B!5Šˆˆ²²²ݸqƒ;–‡‡‰D"244$‘HD:u¢°°0nýàÁƒ©_¿~daaAjjj$++KVVV”’’RùÛ€|}}iРA]uæÌJNN®×sääY[—ÿùnÝšèÈ¢J>‚Ÿ¬˜˜ÒÕ½À{Í¥>æLˆ‹‹#Æ®Fƒ`O€˜r­]»±±±8vì˜Ôõ6l@XX?~Œ×¯_ãÚµkðööÆÁƒË=fDD¦NŠyóæ!&& €¦¦&Š‹‹1uêTtéÒQQQˆŠŠÂ–-[àêêÊ{rRPP€Î;###8p æÎ‹1cÆ ==éééÐÓÓÃéÓ§yçNOO‡——222pûömà»ï¾ãm³k×.Lž<Ïž=ÃÑ£G‘žžŽñãÇcÑ¢EˆÅ«W¯pøða¸¹¹!!!YYYX²d vïÞððpÄÆÆâÑ£GèС`ÕªUèÞ½;Ф¤$L˜0Aêµ Â²e˰ÿ~DFFâõë×ÐÓÓÃìÙ³yÛEGGc÷îÝHOOG||<’’’pñâÅr¯9Ó0"hñbñôeih?ü Npž5 ‘©×j4}}}˜˜Œä•±Dh¦¦Xô‘“wOmˆUÕªU+¬\¹6l@nn®Äú'N`̘1èܹ3ÀÁÁŽŽŽ8qâD¹Ç|ˆôôt@§NàääPQQAll,˜2eJ¹×FOO³fÍ‚ŒŒ 0gÎܸqƒ×¼8hÐ 8;;C @SSNNNøûï¿Ë½æLÓpò$pè¿LII<Èáë×âft…Æ©[Cbó1u…@‰~'5Ö÷¿êv8Z¾|9òóó±wï^^yzz:RSSaggÇ+·³³CDDD¹Ç‹ˆˆ@Ÿ>} $e䵈ˆØÚÚre²²²°±±©ð˜òòò "^™ŠŠ >H›è¨”îÝ»#&&†W&ò?(((À´iÓ0qâDLœ8Ó§O‡¹¹9    OOOœ={ššš°°°ÀÎ;¹\¢ 6@(ÂÌÌ zzz˜7o—Ã$íõK»ž%ëÊ£¬¬,‘ÏÄ4¼úÌzþX°€_fl,~â³u+ ®^/§ýèäççCCƒŸÈ ¦¦XÄTHEEë֭ömÛxcœ¨ªªB$áõë×¼íÃÂÂЪU«r§­­W¯^I]תU+‘D2õ«W¯*jÉÉÉÈɹÉ+cM`LM±ˆ©Ô¼y󠦦OOO®LFFvvv¼&¤¢¢"\¾|öt5³³³Ã»wï\*‘¡¸¸°²²‚ŠŠ ï˜Ïž=ÃÛ·o+]ºtÁêÕ«1vìXL:íڵË/àããƒ?ÿüjjj°±±‹‹ ,--…sçÎáÎ;€#FÀÌÌ }ûöE^^N:…¯¾ú rrrÈÏÏçkÆŒ8tèÌÌÌ0kÖ,<~üþþþ8zô(šCr#áÐ!qîOi“&I6‡5'l>0¦®ÈlذaCcWâcU’DëèèXî6~~~hÑ¢z÷îÝ0•j }ûö…––·Ü©S'î)GÏž=ˆ¹¸¸ ##áááøì³ÏpôèQèèèTxìÉ“'ÃØØiiiÈÈÈÀˆ#0wî\(((ÀÆÆ¶¶¶x÷îÒÓÓ1gÎìܹ“—›£££#ñDÈÀÀ@â=hÛ¶-¬­­ˆŒ-,,йsg$&&bÔ¨Q8xð ÷ÿIÍÑÑQ¢¹ÉÙÙýû÷GLL ^½z]]]¬\¹ÐÖÖ†••âââeeeüøãÜy¹ë“““ƒ3f`Á‚ … …ptt„ºº:„B!¦OŸ%%%¼|ùmÚ´ÁÎ;1|øp®.]ºt©©)¯ÌÔÔ:uªðº7¤ððpܹsS§Nmìª4˜””ˆD"‰<²šzô7Ž?@ª™™xv÷Ê3mªòóó‘””#G¹2ñ„®LÝÈÊÊÂþýû±råÊÆ®J½PÙìQ†SV#.]ºFFFXºtiÃTŠ©‘Ï>û :tHæfꇟŸ<<<àççרUi0gÏž…““S4ƒef=z¥Óå»wÅs}5W±±±¸v-S¦üÛ &‰;x°Ñ ëF||<ºuë†øøøÆ®J½c9@ Ã0u .s€æÌá?°woó~ñSçÉ“Gòºûçç))W'æÓÅr€˜faÆ PQQiìj0L¥öîÊŒá‰3Ä2bzz@é΢qqë(ÉT{Ä4 ¶¶¶èÒ¥KcWƒiÂêb à``ùr~Y§N@©˜ÍZ~~>RSSY"4S'XÄ0 Sj;Pz:àâ"nÒ)¡¢"ïGʸ¡ÍRrr2nÞ¼ÉFƒfê €˜jÙ·o>Ì-"<<œ[ž={6oŒŸª Áܹs«¼}ll,fΜ)1S< þþ¼ì ) Ój›4s&¿YØPÿÒ××ÇÈ‘#¡¯Ï/gSÍ"úõ×_±gÏžr×geeaÒ¤Il>¥*¸vínݺÅ-7?ÿü3·|âÄ DFFVë˜ÑÑÑÎVVzz:Ž?.1ýCqq1–/_Ž/¿ü²Zçg˜Æ¶s'pá¿lÞ<`ò䯩ÏÇŽ51u¡I@ÁÁÁ˜6m\]]ËK©¨¨...øë¯¿ÝÀ5üô=zôHbFõưlÙ2hhhT+b˜ºTÓ À@`õj~™•ðÓOuT±&¤$ˆ=bêB“îfhhˆÙ³g#''Gb²Ì‹-B§N$FåmhgžŸÁ?¯ÿ©÷ó·4Æ*»Uå®ÿå—_påʉò/¾øBêt{öìA¯^½0lØ0®,##kÖ¬A@@Z¶l‰o¾ùFb’ÏòDEEÁÝÝ6l€‘‘W~ÿþ}ìÛ·7GikÖ¬››6oÞ\áÄ¡ S_üýý«=PJ 0q"°CuuqÞ¼|=Tò—œœŒàà`èéñ§Ã`O€˜šhÒ®®.tuuqþüy©ëøá$&&ÂÓÓ³Ò9¡êÛ­··pàþz?M…ºuëÆ-߸qþù'-Z$uû?ÿü ¼hñâŘ0aF…#GŽÀÙÙ¡¡¡hÛ¶m¥õkÓ¦ nܸƒbË–-\ùO?ý„ÌÌLˆÊWKK ZZZPSS«ô SÆ_­í‰€iÓ€²žÚ·¯ÃŠ5!%9@OŸòËÙ ¦&štXEΜ9ƒsçÎá—_~á¦AæØ±cذaï_iÕMøýØ999aéÒ¥Xºt)F€€ìÚµ‹›Ú¡*öìÙƒãÇcÕªUxþü9TTTpà@Õ‚;@€yóæáرc(** ~¢töìYÌŸ?¿F¯‰iqâD(++ãÈ‘#€îÝ»c„ øæ›orrr8uêï×ð!C   €?þøCê¹þûßÿb„ ÈËË$%%ÁÐÐgϞň#°ÿ~üðÇ@ ÀóçÏaii‰·oߢuëÖ¼c-[¶ !!!ܶ­maÛÚ¶QÎ]ÖîÝ»áçç‡û÷ïC¡ô¤;5P\\ Ì™3§ZûÍŸ?ÎÎÎðõõÅãÇñ×_Õª Sߪ’T\,îÚ^:gE Žª"×ì•äYZ6RŘOR“Î:{ö,ŒŒŒpäÈ9rFFFøóÏ?»Zµëׯcåʕؽ{7D"âã㬬¬*ãÙ³gÈÏÏG\\Ö®]‹¬¬¬j t055Å´iÓ0fÌhkkW¸}AArssQXXˆââbäææ6zÏ>†)kãFàêU~ÙªUÀðáSŸOYÙ®ð¬'S]Mú Ðøñã«Ü3CZ×ïæh÷îÝ(((Àä2#°­[·›7o®Ò1¼¼¼¸mÕÔÔàíí “j×eÞ¼yXºti•’Ÿ]\\x½ýѵkW<|ø°Úçe˜š¨,èòe ìYýúI–1å+ĦÃ`j«I@Lõ={¶Âõ¿ýöoùÁƒ¼å’Þ‰‰‰HJJ‚™™Y¥I¡#GŽä Ksssƒ›››D¹………ĸNå%X3LC©((6˜2EÜVBK ðöª3ÍüOÙ ÒØ ¦ºØG©ÚÚÚ•6[1LSRÞÓæÂB`Ò$ )éß2¡øõWÉf¦b¥s€ØhÐLm5é †a˜Æ¶n¸{{Ù²F{õ“ÇšÀ˜ÚbÃ0L6Ø_ßÏßÎÙpwoÀŠ5!%s, š©=1 ÃÔdddpËoßÓ§‹§¼(¡§xy‰›À˜êKNNÆÍ›7°'@Lí± †a˜:P:¨ pqþ÷° ##Nzf©q5W:¨U+ñÀ‘%³6ääéé@‹XAæ“Â~‡0 ÃÔ±¯¾îÞå—mÞ ØÛ7N}š"ÐÕå—±§@Lu°ˆa¦”äýþ;ðS™iý†V¯nœz5%¥s€Ö ÆÔ €˜j À­[·Ê]âÄ D•Ÿ¾ ¢££qòäÉ*oŸ‘‘cÇŽ!;;»Zç)Ovv6Ž?ާOŸJ¬+**BBB‚Dr+Ü»wÿýwÔ!??¿ÿþ;<==‘@ÅùóçáïïÀÀ@¤¤¤àÁƒ8þ}:/^ UUUnÛAƒÁ =\oæÎÄñ½x yy…u¦> IDATqÞºz£V«I*›ÄšÀ˜Ú`ÐÇbÍ`þüú?¢b•7;w.dee±oß¾jÂÚÚÂÿ ukdd„îÝ»ãÞ½{UÞÞ¼yøé§Ÿ kkk\¼xyyy˜8qbµêQ™øøx^sUiüñnÞ¼‰ƒ"((ÇŽÊ+°nÝ:¬.ÕŸ¹mÛ¶\ðVVVÕêÑVS¾¾¾022•+WpåÊ@VVrss†=zðê"‚€uEªS‡¥ø~ø(uù™zÄž1µÁ …¶öG5D¬‡‡.]º„û÷ïC^^¾VÇ222BRé©°+ѱcGØÛÛãàÁƒ°¶¶ÆáÇ1}út())Õªeegg£eË–å®ïÛ·/—P••…õë×cíÚµ˜9s¦Ä“ òòò(**ªÓzJ@ Ñ4éææÆË "''§Î¯asöîÀ@—{{u,ZľVëKÙ --@V(¥";xÿ(ó`©Ø'•‘píÚ5|óÍ7ðõõEëÖ­k}¼Û·ocÈ!ÕÚgþüù˜7oV®\ ???½Â}ìííQPPÀçÌxyyÁÀÀèÖ­Z´h¿þú ÞÞÞUîÒ£GFTTŒannŽQ£F•Û“­:ºu놟þ‹-BçÎ1`ÀhkkcÆŒÝÙ³g1hРZŸ“‹‹“ìÞng,^Ü8õiîÊæ±Dh¦ªXÃóõ×_Kt À% »»»CFF†+?xð / øçŸ`llŒÀÀ@$&&¢wïÞ¼„\iìììx]ÇK×eÈ!°µµ•¨‹¿¿?´KåLMŸ>]¢™­l—õ²1eÊìÞ½£GæÊ]\\0fÌ!** YYYh×®lmmyÍH‹-’˜Žcüøñ°³³ã–Û·o˜˜\¸prrr8p ¢££yÉÈP/ÓeèèÑ£Ð+óÍ~éÒ%XZZrË ,ÀøñãqïÞ=ÄÄÄ`åÊ•èׯ”••¹m>|ˆ;wîàСC^ ¦êæÏÒÒþ]VTŽÒÒR ®®.u\,¦n”ÍX"4S Ä”ËÝÝÜÝÝ+ÜÆÍÍvíÚÕ0bê\dd$)**’——WcW¥ÎQïÞ½iÊ”) ~n___4hPƒŸ·¾ýú+Àÿ÷ãâugΜ¡ää䯭`C.\à•mÜÈ?–/o¤Ê5qqq¤££ÓØÕhì§ Ó¬µmÛ»wïÆ¼yó`nn.1&ЧìË/¿DBB|Ë&«05’,YÂ/ëÝûßæ°êæ¹1Õ'-ˆ=bjŠ@L³7gΜjMÆú©Ø¿cW¡IùòK %åßeyyqÓ—eR6*–ÍÔûè2 ÃTâôià÷ßùe6;þ»œ’’ÂõöcêG~~>RSSye, š©)1 ÃT 9øß¬,kk૯øeþþþÈÈÈh¸Š5CeçXSs,bx addTîœuáÀÿÏÞ™‡Çx}qü“É.HDlA{KmU[µüPªT)jíbkKÑ¢j-Š"U‘¢EQ%-ÕØšXk‰µö¥–X²/²O’™ß#“y³Î$3™™ä~žgžÉ½ïûÞ÷ä&3sæÜï=G§Äˆyæ âÀú2M ÈÁGf"sظ46C* PÅŠ"!ÉMT¥Šôo Ål˜À, >$99Ù`ãÇÅÅI’ DRR>”ôÅÆÆrìØ1FÅž={ôm¢@Àž=°c‡´ïË/¡qcãØ#ȉL–³Šˆ ´A8@½‘‘Ajjj±ÝoàÀ >œÐÐÐb»§ t &Hûš5Z¸„Èðä¦!„± ÌDXùø1ÛÃà ~ŸÆlhÐ@ëó‡αcÇrôïܹ“V­ZÁøñãù믿HKK£]»vøúúR¿~}­Æ?|ø0cÇŽåŸþ¡J•*êþo¾ù†£GæÈÒ¬y s1@[&MRm}ÏÄÚZµô•WžÃÀÀ@:wî,–Á HöZ`™!´ 0ÈDx’ÂÍÊŠ&Âܹsy®a×gŸ}Fhh¨:#ñ€HMMå?þÀÙÙ™9sæÐµkWnݺ…½½}ãwìØ‘ääd6oÞÌ´iÓU1Òµk×òé§Ÿæ— `ß>زEÚ7c4mš÷5"áÉM¤ê—¶EH  b L/µkׯÛÛooo‚ƒƒ fÏž=888pæÌŽ?ÎÂ… éÚµ+Í›7ç‡~ $$„Ù…y`mmÍû￟ŸŸºïðáÃDFF2lØ0CýZAžÄÆÂ¸qÒ¾&Mà‹/Œc `DHP„$Њ3gÎ0qâD¶nÝJݺu¸ví¶¶¶’ÚW®®®4hЀk×®i=ö˜1c¸{÷®z©ÍÏÏÁƒãää¤ß_B Ђ©S¥ VVª¥/këü¯ Ó—Hl…±f"L©QƒÁÙ·2€rÙ÷îjAXX `æÌ™ôìÙSÝommMFFiiiØÙÙ©û“’’°.èÓBwwwzô误/^^^øûûçÈõ!*gG“Ï>ƒêùBTä¥K`‚ÂPj ³gÏM=Ô})))üöÛoœ={€>}úбcG>¼õ…»îN„©žžÎ AƒhÙ²%³fÍ’kÚ´)ééé;vŒ^½zpóæMBBBhšŸX"ÆÏÛo¿M:uðòòâå—_ÖÛï hÃóç0v¬´¯Q#˜3G»ë…Èðä¥K`‚ÂPâ ÇsàÀ–,YBÏž=%ÐÕ«WY¶lC† ÁÚÚš)S¦Ð¹sg¾ûî;#ZlZL›6³gϲjÕ*‰®§M›6¼ôÒKôë׉'òèÑ#œœœøæ›oðòòbÀ€:ݧgÏž¸¸¸0þ|||| <ÿĉ$$$‰L&# €jÕªéìx ™|ú)„„dµ--UÑ [[ãÙ$ІïEFF‚³³sŽcM›6åâÅ‹êvÛ¶méÖ­K–,ÑjSIÄ‚Ž;R¡B@•´°uëÖlÛ¶Mrž³³3üòË/Ìš5‹+V’’BçÎY¾|9VyíÜÜÜhÙ²¥¤ÏÒÒ’Y³fñ믿òÎ;ïHŽUªT‰W_}UÒçëëËÇqpp 11‘ŋӥKá ÅáÃàë+í›:ZµÒ~Œ¨¨(óýß ¹\NBBBŽ÷ó*UT  U;.’’ L#)0Jü+5sSlllŽcÙ—ºbcc‘Ëå¥ú ÌÒÒ’   u[swVnØÙÙ±|ùr–/_®õ=ÌàÁƒsô7Žqٷ߯¾ú*’¾Í›7k}? ?`Ìi_ƒ0ožnã áÉKdi •*Ió6={uê³³Bì{\.gÉ’%Œ3FâmÚ´‰¹sçJš³¥ùýõbŠíéÓáÁu2üø#ØÙé6ÞÀY½zu±Û_šÚëׯ—8?šÇUË`YígÏŒo¯¹´÷íÛÇܹséÔ©Ë–-#--Ò€…R©TÛˆâ`òäɬ\¹2Ç1¥RÉСC áàÁƒêå¯ÌŽìÿ4ÙÇõððP/TlW®\™#rgj= ;ƒæ»àäɰb…ñlŽ^½`ÿþ¬öŽ0hñì1WBCCñöö.e†J}(==aÆÆ_ýUjµ?Ai#) Þ_êüÔ­ n<‘Èðä•„Z ;¥ÚŠå7Þ@¡P°ÿ~Ê–-kl“A11s&Ü»—Õ¶°?¿Â g‰‹‹Óq‚\‰ŒŒÌ3G˜H†(Еï8q‚#FpðàA<Ȉ#8sæ [·n%((ˆ§OŸÒ½{w:uêD§NøùçŸlµ@ 0$'OB6¹~:~Ì ´É+꘴-r ¢Äowª[·.#FŒ`Ĉê¾ZµjЯ_?uQOM2 ‚’Gr2Œ•µe V-X¼Øx6 ŠŽˆ t¥Ä;@U«V¥jÕª¹«^½:Õ«W/f‹1ùòK¸};«ma6€ƒCÑÆy€ O^y€@dƒèN‰_è†B¡`Ĉ\¿~Ý`÷Ø¿?³gÏÖúüóçÏçšT™¾ïß¿¯ÓýÿùçFѬ½ÏÄÄÄpäÈvìØÁñãljŒŒ”_¾|9?ýô“N÷Ê ¥RI`` £FbèСÄÄÄ‘‘A@@Çgذa<}ú”#Fh½#99™?þX,ßÀéÓ9wx ]º}l¡2<ùi€„Z +Â2”éJ) Ã?äŠ|íP(lÞ¼™g|÷¸zõ*¿ÿþ»Öç?|ø-[¶äèOIIaÈ!,Y²Dë±¢££4huêÔÁÃÃCÝ¿råJÜÜÜèÝ»73gΤsçÎT­Z•÷Þ{O}ΡC‡øçŸ´¾W^lذ!C†P¹reêÕ«‡R©ä›o¾aìØ±¸»»ãááAbb"›7oÖúÕÞÞžÿýïŒ3†Ó§OÙÆ’HjjÎ¥/77XºT?ã áÉOT¥Š*š—IL ¤¤“a³DÄjM„{ŸÞãñªÇ¿OùÖåi~º¹ÁïchúöíËáÇIJJÊUÇ•‹-ÂÅÅ…Ï?ÿ\ÝwðàA¦L™ÂêÕ«3f ¶¶¶¤¦¦rðàAöìÙ£wÛ÷ìÙÃĉ™9s¦¤ï‹/¾`ì‹jœÙ£SÚðúë¯óþûï3eʽ8j%¹sáÆ iŸ¯/”+gszÆÚZ• :<<«ïÙ3•¾K È á òeùòåüûï¿9úg̘Aƒ P*•¬Y³†½{÷ªk͘1;-+ÛŸ>}šõë׳víZI¦}ûöqüøñ<£;ßÿ=r¹\"n/ˆçÏŸãããÃÖ­[±´´T÷Ÿ:u '''>üðC,^|…´µµ¥OŸ>ôéÓ'Ç8[·nå—_~A&“1zôhúô飾nêÔ©ôë×Û‰¾úê+š7oNŸ>}øúë¯ "%%…Û·oÓ¦Mîß¿Opp0...œ:uŠ®]»Ò¾}û÷eþüùœ>}úôéäI“ɲ¹sæÌÁÕÕ•'Nä:Fi%88g¤çý÷¡[7ýÝCh€ O~ P逄$б&È—Úµk«ë©y{{óßÿñǨs&M˜09sæÐªU+úõëÇÆéÑ£‡Öã{zz²}ûvvíÚ%éŸ7ožäƒ=;®®®xxx蔸òСCØÛÛÓ«W/Iÿ믿Nll,‹/&)))ß16oÞÌ?ü@—.]¨[·.ýû÷çܹsêã;wîäÎ;’köîÝË•+W¨_¿>VVVT«V oooÜÝÝiذ!5jÔÀÛÛ›5jä¸ïóçÏiÖ¬7nÜà£>¢gÏž¬\¹’¯¾úJr^åÊ•éÚµ+üñ‡ÖóRÒ‘ËaäHÈÈÈê«^t(_§BdxòÓB tC|U1,¬-ÙÞÕõýû÷Wÿ|úôifΜÉîÝ»©^½:wîÜÁ××—;w2`Àzôè——ûöí£wïÞŽïääÄàÁƒÙ°aÆ àßÿåüùóìØ±C'[ âܹs´jÕ*GÜW^y…‰'2oÞ<æÌ™ƒ§§'-[¶dìØ±´iÓFrnïÞ½Ù¹s§º©gj¥eÙð·ß~›I“&Ñ­[7‰¾hôèÑôíÛWíœe_[ºt)666ìÛ·Omrpp`Ö¬Y9œ víÚqøða­ì) ÌŸW¯JûÖ¯GGýÞgàÀúPƒü4@ªãÒ¶B òC8@&B¥u¨³ÔtK‡……1`ÀfÍš¥Žð£T*éÔ©“ú¼FQ¥JÎ;§•ª*ðmÚ´áöíÛÔ¯_???ºuë¦÷|L¸¹¹åzlÕªU̘1ƒ={öpåÊŽ?ÎÆ™2e ß~û­ú¼ì"W777ƒî˜ËäÌ™3XXX0dÈu_LL 111DGGK–ÜÜÜJEm¸t -’ö ={Ça¹€º  A¤¥¥1pà@Z·n-îÆÇÇcggGùòå%çWªT‰øøx­Çoݺ5ÞÞÞlذ¯¿þš-[¶àçç§7û3IOOÇ!Ÿd/U«Ve„ êöìÙ³Y¶lóæÍ˳LJnzCÔާaÆ âØ±c,X°€ÄÄDär9çÎcôèÑ9νxñ"M›6-ò=Í™ÈÈœ¥-† ¾} wO‘Èðèª A~H/ß~û-)))têÔ‰jÕª©;wîD&“±k×.ÂÂÂðòò¢víÚñÇPAǯÙeË–eèÐ¡ÄÆÆæú¡ž¦M›booÏÁƒÙ°aöööju^ôèуÇsôèQIÿÀQ(¼ù書¹¹Q¥JLÇŽub9\]]©[·.111Ô®][§1r£yóæìÙ³‡Ÿ~ú‰råÊáàà@ûöís,5ÆÇdzwïÞ;ÝJ+V€æ†¾J•r?”2ž=‚âÇÕ4÷!´ /DH ”RSUZM>üœœŠçþQQQ¤k&è¹\Nttt¾çˆ@[„$J›6I¿í—)S¦ßý…Èð¤á ´G, ³'=–,‘ö£Úþ^\”fíUq¡Häh‹ˆ ³gÛ6ÐÔ´ÛØÀgŸÏñ ¶H ˜5 E΂§ï½Õ«¯Bdx´Ñ‰@[„$˜˜ˆ‡‡§N*ÔõéééÌ;—3gÎh}MFF‡fÞ¼yL˜0¹sçrõêÕ¯[ºt©N%7vîÜI›6mr=#C´À<ؽnÞÌj[ZÂ矿Bdx„H O„ÈDˆŽàùóÓ¿­m ªU˻ԄR©äáÇ$''jüŒŒ ¶oßN:uhݺµV×üõ×_ 0€®]»âîîÎŽ;øúë¯Ù¹s' È󺘘²•&$$"é‹ŽŽæÂ… Œ5ŠáÇӱcG­Ç˜ JÛC†€1rr á)L ÈHHKkk&0K„d"DGðøñ*ƒß§|ùÖù:@ÙINNÆÎÎNR>?lmm¹©ùu<III”)SFÒ÷ÒK/q÷î]jÖ¬©îkÙ²%‹/Î×Ò=zô <<¼À°ºÀ4Ù¿.^Ìj[XÀŒƳG`|lmÁÙ2_ÒJ%„†‚ÆÛ‹@ˆ%0A;vŒ-ZP¶lY*T¨€¯¯¯úX§NðððÈñȌƼôÒKìÛ·€gÏžáááÁ²eËhÒ¤ eË–ÅÅÅ…­[·ªÇsss“8?­[·ÖÉ)Ù½{7 4ÈQô‹/¾È7éáÙ³gyðàÍš5Óú^ÓaÁi»hÔÈ8¶ áÑFbL ÂäÊÎ;™:u*'Ož¤W¯^Œ7Ž7nðý÷ßãïï¿¿?»wï¦lÙ²Ô¯_ŸÊ•+ðèÑ#•&èáÇlݺ•Ù³gsüøq:tèÀˆ#xøða®÷V*•ýôS.]ºÄ¥K—Ô×½öÚkÔ«WOk»ßzë-&OžÌ²eËØ¶m[ç’ššJll,ÿý÷¸¹¹ÑÈX[‰²p¡jks&/¿ Eð™õ†Ðm5@b L  ÂH°´´¤cÇŽØØØ0aÂbbbèС»wïVkiär9^^^lذAr­——...´oß^½#,¥RÉ—_~IZZ½zõbáÂ…êÜB...¼úꫜ9s&GéºuëæéÕ®][½Û,;;;fΜɑ#Gèß¿¿äXµjÕhÛ¶­¤oÍš5DEEQ¥JÂÃÃY¼x1ÿûßÿ„d¢Ü¾ ¿þ*íÚAvĘ@,”JÍïRMæÎ+yÎÉ“'ãááÁäÉ“‹Ç(3#$$777Ο?¯Þ&(ù°råJô:î¨Q°qcV»qcÕ’˜–y: JTTŽŽŽXY‰ï•†B.—“PঋädÐÌ·*“Aj*ˆ?MÁ„††âíí­S–}s¥Ôh€BCC¹{÷n®ÇΟ?϶mÛ¸råJ1[%´åáCزEÚ7s¦i8? 4@Ŷ {{pÒÈ*¢P@X˜ ˜%%ÞJOOçÞ½{Œ1‚5kÖä8>uêTÌ¡C‡èÝ»7óæÍ3‚•%—råÊ1iÒ¤Kb®|óª¦S&uë AƳ';¤bÅŠÆ96À IDAT6£D£­„ZP0%> xøða/^ÌÝ»wñôô”»ÿ>ëׯçÞ½{T©R…{÷îáååŇ~(ÞÈô„““+W®4¶3'4~üQÚ÷ùçªÊïAn¸ºÂ‹äõ€p€9)ñ =z”ë#GŽðòË/S¥JêÔ©C:u *f+A~,_))Yíš5áŦD“Aä2<Úæ!„L‰w€ò#44”J•*IúªV­Ê3¯ ;vì`åÊ•’‡&B7$äMö×KaÚÑѰnº€iÓÀÚZ?ãë«È’%KLÆž’Ø^´h‘D”ßùª­ðYígÏŒo¿©¶ÿþûoV®\Iÿþýñõõ-=޼²”0iÒ$å¤I“$}³fÍRdÓ¦M„岿6%%…ððp”¹¤ÙúóÏ?¹pá‚^lˆeãÆøùù©ç-22___6oÞLtt4›6m"ES@“qqqlÞ¼™šêQ#âã11Yí `ÂãÙ“BdxŠ¢"hAvJ¼t÷î]6mÚÄõë×¹~ý:›6mâþýûtìØ†ÊŽ;|ÈÞ½{iÓ¦ »víÒ»í›7ofúôéœ8q‚cÇŽQ¶lY~üñG–-[FPPÇŽ+Ô¸ .¤J•*Ìœ9SÏ놯/„‡gµË–U9@¦ŠÈdxtÉ”½Xx8¼ö @)Èäíí··wžÇË•+Ç,(&”œœ\,ßãããµ:O©T²wï^N:EåÊ•9r¤z½x÷îÝ<þ<Ç5ƒ ¢L™2( lllHLLä×_¥W¯^;vŒsçÎQ½zuF…ƒƒ¯¿þºÄ1{ûí·9tèÿý·Ö¿×¿ÿþËÕ«W2dˆ¤ÿòåËü÷ß9ê‚eÒ´iSêÕ«§SÚ÷ÄÄD–-[ÆêÕ«±Ï£ ŠH¹¸¸àãã£.‡àææF·nÝHHHÈ1Î7øý÷ßqpp`ذa’õù­[·Ò¾}{ÜÝÝÕ}»wï¦Q£Fxzz²gÏŽ9Bƒ Ø´ižžžÜ½{—Ó§OÓ½{w6mÚDÓ¦Ms]ËW*•øûûsöìYèÛ·/Mš4Q—Éd|ûí·¼öÚkÌ™3‡jÙ¿Jr9,]*í›0 ¨€ ¨)SÊ—‡Ì·ªŒ •d„g‰Râ#@‚Â1vìX>ùä.^¼È§Ÿ~Ê«¯¾Š\.TÉ%ýýýÕ¹sçòÑG‘šš À| .jÍÈ‘#iݺ5³gÏæüùóLš4‰Ž;ªµW¹ñôéÓ) ò###ƒwÞy‡àà`IÿÇœo^§Aƒ1bÄîuèÐ!2220`€¤¿Q£F„‡‡çº|X¶lYI{÷îÝtéÒ…Ó§Oãëë‹««+êããÆËñ»L›6#GŽª¥¶””Ž?Ž¿¿?—.]bÿþý(•JŽ9‚¿¿®:¹\Î믿΄ xþü9ǧuëÖ9´TíÛ·§zõêüþûïZÏ‹>ùé'xü8«mgŸ|bS´Fh€ . Uáù# A®Œ;–Û·oÀÕ«W¹~ý:Û¶mÀÇÇGíüÌŸ?Ÿ¨¨(üüü¨P¡BžãM›6k×®qèÐ!Nž<ÉùóçÙ»wo®çÞ¸qƒÀÀ@Þÿ}­íõöö¦uëÖ’ õwîÜáĉŒ7Nëq´!((ˆ.]º`gg'é8p uêÔÁÛÛ›N:1eʶmÛFRRRŽ1<<<¸uëþþþ\¼xWWWöíÛ§µ kÖ¬ÁÚÚš3fàïïÏøñãY¿~= Ê•âïïÏ;3㺕+WrëÖ-=zÄÚµk9pà&LÈ5 Ú½{w£$ÍȀŋ¥}£Gƒ†ÔÊ$ ã‹„Z?Â2-ZDLLŒÁ‡ÖÊž—_~YýsÆ ñôôäìÙ³’sbccéß¿?ãÆãí·ßÎw¼V­Z©nÛ¶-UªTáܹs9Î{úô)=zô [·nŒ3F+[3?~<¿üò‹ÚáðóóãÕW_Õ»¨=44ýåË—çúõëüòË/¸¸¸ðçŸòî»ïR¯^=üýý%ç6oÞœòåË`iiI“&M8yò¤^íÌýû÷ãééÉ?þȺuëX·n)))\½z…B!9×ÝÝÇša˜bbǸw/«mm Ÿ}VìfèŒÐ]4@ „Ђü)ñ s!3ƒ©âáá! =+ †Š››[Žì·ÚP«V­¡ìÐÐPºwïN£Fرc‡Î¹(Þ~ûm¦L™ÂŽ;6l?ýôË—/×Ù¶‚HLLÌ3Ú%“Éxûí·ÕáãÇ=z4Ÿ|ò ýúõËsL[[Û|—õÅ“'O¨V­7oÞT÷Y[[3aÂ222ɲ¾9;;çªõ2$J%,\(í6 ÜÜŠÕ A A, òC8@‚Q(œ>}š  XæÎ˵kׯRÇŠ”‰‰‰\¸pA¥¼ïܹC=hÑ¢[¶lQ‹¨uÁÞÞžádzaÃ*V¬HZZZŽ>pvvæÉ“'9ú …ĨQ£C‡eøðáDEEi!°µµÕ:w.Ô®]›ZµjiU öÉ“'’¤ ÅÁï¿ÃµkYm™LUôÔˆŠŠÂÑÑQ-€è¹\NBBÎZªáEHb L+iii€jÇÐöíÛ‰‰‰áÍ7ßà÷ßgùòåìÞ½­ÆËÜ•’’Š+ÉdjçääÉ“´iÓ†7ß|“íÛ·ÊùÉdüøñœ:uŠ/¿ü’‘#Gi¬¼¨]»6·nÝÊÑ?räH¾ùæÉޝððpV¯^MÓ¦MuZéÒ¥ GU·ýýýsM¸¨+ä矖$b”ËåüüóÏ9νuëõêÕ+ò=uaÁi{Ð (f Ð]5@"$ÈñUE+  eË–<}ú”{÷î1oÞ}zž×tïÞyóæñäɪW¯®î¯Y³&³gÏfƌԩS nß¾M“&MtÎ@ýî»ï2xð`NžmÚ´‘,ùøøäÐÍž=[²”Ø·o_žûì3Ο?O\\õë×§]»v{~úé'Ê–-K=ò }’=úÓ§hü*Îd)‹%0&ÊÜŠ •ÎEó97&OžŒ‡‡G±d-6GBBBpssãüùó%ªÆÚÞ½{8p —/_ÆÓÓÓØæè•ÈÈH<==Õù C@@+W®ÔºVÛñãСƒ´ïÌÐØdÏûóÊ+б£ql”<„Zfã­[·???@%Â}÷Ýw™?>£G6²e °\»Ù’dcµ‰ …¨fxt­B-ȳq€®^½ª}úúú²`ÁΜ9ÃÉ“'‹%ƒ®@ Ð?‹©²?gÒ¼9¼ñ†ñì) BdxtÕˆ òÆl ;;;RSSIIIáìÙ³êm»J¥’§Â¥ÌŽ{÷`ûviŸ9åýÉŽ¨fx £ /ÌÆêׯS¦LQçriܸ1Ož<áñãÇT­ZÕØæ Y¼XUù=“† A‡œ—Vˆ%0A^˜Ô»woÖ®]Ë믿Îö_/]ºÄâÅ‹±¶¶6²u@?†ìI¦gÌPe6W„ÈðF$"@‚¼0émð±±±œ:uŠîÝ»ciiIÿþý%Ç{õêe$ËAQXºäò¬víÚ0dˆñìÑtîÜY,ƒÈÈH‚ƒƒuZ A^˜´°|ùrÆÇðáÃ9r$uëÖ5¶I9ˆ/Y3m‰‰‰ÉóXx8øúJû¦OWeé5gD ã Ph¨JxoÎÑF~0é·'''Ž9Âýû÷Ù¸q#¯½öŒ5Š·Þz‹2eÊÛDÊ—/ÏÚµkY»v­±MLŠ.]ºäÚ¿b$'gµ«W‡#ŠÇ&Aé£|ypp€ÄDU;- ¢¢ÀÅŸv ŒYÕS(>|˜ü‘¿ÿþ›~ýú1jÔ(Ú´icûiS LPtBCC…ÝÀ˜ÊÇÆ‚›ÄÇgõ­X%¡”ž¨fxt­–I½zp÷nVûòexé%=WBµÀL™LF·nÝØ¾};7oÞ¤I“&L˜0///~üñGc›'($þÙ3á ôŽ©ÌñêÕRç§R%;Öxöè‘Èð&!´ wÌÊÒÄÙÙ™?þ˜‹/òóÏ?“¬S˜ãÇ7¶ %S˜ã„XµJÚ7y2˜ÀJ¶^y€ Oa4@ªë¤m!„€9@·nÝbÕ‹wϤ¤$f̘Á'Ÿ|BXXÍ›7/ÖÊÕ@wÖ­Si/2qtñ²"$È ³q€6nÜHdd$ßÿ=ÀÊÊŠaÆÙ2AQ) kÍÆÆØsœ’Ë—Kû>úHå•D ÃS˜<@3$ ˜‘ôðáCÚ·oÀ®]»˜5kK–,áÉ“'$fÊûf‰©èSJ2ÆžãTm?ÎÄÁ¡dŸ5 ã/ X€9@•+WææÍ›„……qéÒ%ZµjÀóçÏyþü¹‘­SЧ”tŒ9ÇiiðÍ7Ò¾qãJÞ6d¡2<…Õ‰%0An˜4zôh¾üòK<==8p 5kÖ$88¹\nÛ{AîlÝ fµmmá“OŒg ô!DЂÜ0›„Mš4áúõë„„„¨£?–––ìÙ³ ‘ÒÓ¬1•5%cͱB‹IûFŽÌùTy€ Oaóå– Z 0›Б#G¸|ù27ÆÒÒ€f͚Ѯ];#[&(*ÆÖ§”Œ5Ç¿þ ·ogµ­¬Te/J"Bdx «rr{û¬vj*BK-(a˜Ï¢E‹puu¥mÛ¶Ìš5‹#GŽ’’blÓEDh€ 1æX©„… ¥}ï¼ÅnJ± 4@†§° BhANÌÆêׯ'Nœ **Šo¿ý–2eÊ0xð`œœœŒmš@ È…}ûàÊ•¬¶L3fÏAéFl…dÇl €äädöíÛÇš5kX»v-¯½öÛ¶m+Ò˜ üöÛoœ>>ܼy“ &pôèQ6lÈ Aƒ 5žL&C¡P¨îS*•8–¤ÜüøçÐxY%?ú#0}Ę ;fãܹs‡Í›7³qãFþúë/’’’Š”hçÎøúúÒ¬Y3ÆÀ¤I“$çœ>}š   ÉCÑ.z[SŸb ö”ÄvæÇý¤ÑŸ :w†¶m w?SiGEEqäÈ“±§$¶>,Ñér½*”Õ~öÌø¿©´oß¾MPP+W®äÔ©S¥Fk6З_~I³fÍØ¹s'^^^ìܹ“°°0vìØQè1÷íÛGïÞ½Y´hœûŒŸþ™x§5k„ÈðrŽW­‚¤¤¬¶«+”Æ•d¡2.\¸Àœ9sصk÷îÝû¤ç×/ á1ÔÇÇÃÚµÒ¾©SÁÆÆ ·3i„ÈðU$–Àš˜(oooµÓsóæMzõêŽ{÷Œl• ( á1Ôÿ½j+q&ÎÎð"©z©Ch€ OQ5@"$ÐÄä#@ …‚™3gÒ¬Y3FŽÉÍ›78rämÛ¶-R5x@PxRSaÅ ißGAٲƱG (¡hbòŸŸ;vì`âĉԪU‹·Þz‹Õ«WÓ·o_¾ýö[–.]jlEDh€ !æxãFÐÖÁ&NÔûmÌ¡2<úÖ‰%°ÒÉ;@ÿý7_~ù%#GŽdöìÙ¼òÊ+|õÕWìß¿Ÿ‘#GÛ< ãï9ÎÈ€ìß=ÆŒ‘–(m á)ªÈÙYªOKJ‚çÏõ`˜À,1y(..ŽªU«ªÛõêÕcôèÑtèÐÁˆV ô‰Ð}ÏñÎðßYmkkøä½ÞÂì8p K³X Uda'€ˆ•fL^­P(8zô(±/”–W®\!66–íÛ·«ÏýôSjˆí&% ¡2<ú˜ãì;¿Z·†Î‹4d‰Bh€ >4@b L‰…R©TÛmHMMeóæÍ¬ZµŠ6mÚ0sæLêÔ©cÐ{Î;Wò,”VîÞU Ь>èÛ×x6 …!"*WÎj—-«*ê+PŠ··w©ÈÐoò‰3±µµ¥oß¾ôèу-[¶°oß>c›$”¾ùFêüxyAµ¨QpqQ%îÌ$!Aõ”>Ì aâĉ4jÔ¹\ÎíÛ·™4i’±Íè‰ÒðMÃØeŽŸ>…Í›¥}Ó§«²ê ²µÀ OQkÈ-ÈÂä  ðòË/S¾|ynÞ¼ÉêÕ«qww7¶Y="vñž¢Ìñ·ß‚\žÕvw‡!Cô`T CÔ3УQ% ¡2<úÒ‰Ì` LPò ãë§¥ÁòåÒ¾ ÀÑQF•0D-0Ã#4@}""@ ?ÿ gµíì`ÊãÙ#èá @8@@h€ .s¬PÀ’%Ò¾‘#¡J=U ã/ X€X˜¢˜áÑeŽwï†Û·³Ú––ª­ï‚üµÀ >jTª¤Jå鯦>Wu9Yx*)RH}¤zVÊ•XØX ³•©žm´.Ì5ˆ¬.ÅŠÙÔ3¢˜ 4Ò¢\¸Õ:¶l1ž=AQI‹L#åá Çæa )Rس.•òÉ)T!'äR XXçï •iP††[bae8O©4Õ @ æàA©ócaŸn<{‚‚P¦)I}¬ŠØh:9™QœÔG©d$eä¸îe#ØZÊ4%i˜ûñ„Ë Ø×µ§Ö‚ZÅkX E8@£JÕìÅyzEÛ9^´HÚîÝ76Q%Œ¨¨(±i²õ‚R¡$-2´ð4äárÒÂÒH|–HÌÃìÂíÔŽŽü™¥¢ô,d¡ý)…d:4ª‡<\.y–üžFZTÊ ã.?YÈ,°qµ‘88Ù«ò9?ê¬|áÑ6ƒ IDATÚ®¬¶)l…·°²ÀÒÊÊHûíkÛ.žoCÔ}Q{£x²ú Õ?ŸY…¥Ô;@ÇgòäÉܾ}™LÆåË—ñðð0¶Y¥ ¡2<ÍñâÅ ¹´Y3èÑ£ +A˜¤H i19š´ð4äªÈLZ„ÆÏ‘Æwh4±*o…uekl*Û`]Ù  Ò+§SµaÕ,§†m¡vE™[2ÄZ‹jKü…xuß½ÏîáØÁ‘²MËÑ2óÅ„^©ÅÏÑ£G4hsæÌáwÞ¡\¹rÆ6©T"4@†'¿9~ð¶o—ö‰èî§(ùn2òP¹ÔyÉ-R™†2Ýt™LâÐô,³•æê}úô)ÁÁÁ4ý_Ó"Û’=¢©;@2¶7"¸y°z)Q‘ªàúàë´8ßË2–F¶Ðü(Õy€:uêDÛ¶mY”]üð‘HPøðCðñÉj׫7o‚Lä‰7äarbÇs0†èƒÑÈCM$oÌ+g+­+GÓùÎýì™Ô rr‚˜ãÙ£-¡?…ró½›’¾j£«ÑÀ·~Æ/Ey€Jí[œ\.çøñã$%%Ñ­[7^yå¾üòKäréK\\±±±’‡&¢-ÚæÜ¾};–”œÁ´iYαí+­mEª‚˜Ã1\œt‘`ï`NU;Åwop÷§»ç'ÉõEm'ʱ®dC#œ::a×ÏŽêTÇcŽõÖÔ£ÆÆxzóòÕ—y%ü^Šx‰W"^áåk/ãèë®Ô[]÷/ÝqçŠUg+_qľž=VŽV&3¿•+ƒ¥e¬Æ1 3ûòjW^•*CUui2ÿ~Ï6<#|gx¡ÇONN&66–‡òüùsJK\¤Ô:@?F¡PàééÉŠ+Xºt)ûöícJ¶ŠlÚ´IòÐD´‹ÞÖü¦a ö”Ävæg?þÁ›HIÉj;:nbøpãÛkŽí¨¨(~”z“:]Ÿx5‘•#Vr¥ÇNT8Áå×/ã÷ —Ô»\_Pû€Å,‡¦“'¼O¨š¹Ô[[Kï]R94×TÍíe·y%ü…CäÍ鎧©·¶s=¨þauöÅîé“^XW²fóO› ýû¦íçç'©V”ñ,-ÁÁAz|ÍýÚk¨výïëc_Ç^ò÷¾=ö6Vl(Ôxÿüó›6mbòäÉìØ±ƒŒŒ¢íÖ3JíØãÇ©Y³& 888°wï^>øàBBTJ{±V<¬[·Nh€ LnsîîªçL–/‡©S‹Ù¸®]»tÒÉó–µbÅú4Uë{YU°¢lÓ²XWz±¼TÉ:ן­œ­°•œS™ ¢ÖË${Ù—'à•Wô2´Á‰?Ï…W. LËúwlçˆ÷Qï"•Ê(MK`¦³ [̸ººbggÇ7hÙ²%ØÚÚÙ²Ò‡p~ Onsìã#u~œaìØb4ª„QP Eª‚¸“qjOÂ¥¬ÈNAXXYP¾Myœ»9S¡[ʵ,‡…eÉql´ÅÕÕUoÎj<©dêBhMʽ\ŽÚ jsoÚ=u_Ü©8Ì}@­ù¢T†6”ZH&“1qâD>ùä¶lÙBHHk×®å½÷Þ3¶iÁII•+¥} eÅnZ½’x=QíðÄ˵&U^Ø×µW;0žèCÑÄŒ!õ‰ËZNVTèR Ý*àÜÍ»Zv´Ö<‘Ëå$$$àì쬗ñ\][TJ…Ê)”¥÷0•”BRS OKc“§'M^È$L ðüÉ“à—‚‘‡«Äñ’RE©Œü(µ m âAh€ æ§§«¶º?xu|òdX±Â8¶™;I7“ˆø-‚?îÀó¾'å•嵺ÎÂÒ‚ò­Ë«žr­Jç²–.èªJW*y’šJHæ#%…G?߉K%Á2-ß1*Y[ó··7MÕ ¢Dså+’%U—ÿ¹ÐøwÝ+—& p€òA8@‚’È–-0lXVÛÆî݃5Œg“¹‘øo"¿E±+‚Äk‰Z_g_Û^íð8uq2©¼8憓Ëyô"Z£é䄤¦ò(5•P¹…>â*Y[èí— ;A÷>½GÈòI_½Õõ¨þ‘n¥2J“$^}A)B©T•½ÐäÝw…ó£ ñâ‰ØAäo‘$ÝNÒê«òV8uqRkyìëØØÊâ']©$>#¹B\©4øsdZ!))<‘Ë‘+Åò;F¤¥ÑåòeþnÚÔd Ú‹j«Jeœ×(•ñé‹R/ q_nH`t„ÈðdÎñÞ½ªÂ§™Èd0}ºñì2i”ðüìs"vEñ[)÷Sò=ý9Ï)kY§—³žò­ËiK²)¡î''s51Qò¸•œ\lŽééœ ú,[$—A„-„ÛbgÇgïÙRÓÖ– ü ¡Š—Ëérù2M›ÒÈ k U©Œf¹”Ê¥2rC8@£#jžÌ9Î^õåÍ7¡~}ãØdŠ(JžŸzáôìŽ 5¤`³ÌFF…®¸ëq—VSZQµ®ù;óORSÕεÏד’H4v‚¼¸8¸u ÚµÓêtK ªÙØPÓÖ7;;jÚÚJ~®fiKÍr6dúo©À3 3Š­LÆw«ÇS;AÞÞ4,S&ç Œ}]{êûÔçÆð꾤IÜ|—ëõS*£$!4@ù 4@‚’DPtî,í;š77Š9&ƒ2CIܱ8•Ó³'ù³‚ëlÉìd8ww¦ÒÀJTìSÑlµ•:@ßÕ«‡X£á…Éåt¹t‰@ooƒGKñxÅcõŽ˜ü°v¶¦b_•ÓS¡kd6º/­T ¬(d(•œ‹çHL ‡cbøçùsRõ°óÊF&£½½$šãåà@m;;dhgŒ¾k©Æ”¶ó«¶¶~}”À: 'è™F$¨¾‰9A5?yQ*ã€F©Œ…¨ðš(•ÂJ<ß|šŸ•^^Чñì1$ ¹‚§ëžòpþÃu=Ö•¬qéç¢rzºT0¹íê7’’8üÂá9K\„É–Ôµ·Ï±tUÏÞ¾@‘pI'{(?ÈðyáýÝ z±EÞ¤œ ðÜìIpÓ`äa¢TFv„$0:Bd UͯŸTs<}:”¸Ï<%„oçþ÷Iþ/9ÏÓlªÚPéÍJTX ÇŽzÕBUô$5•Ã11‰‰áHl,OSµ¯#–‰àag'‰æ4vpÀ³Llõ 66ú®9#@Dµ¾á­×8ùij*/_&ÈÛ›zö¦“ðÒ¦Š ž?yr¥GV©ŒÔ'©Üu«P¥2J¡Ò?ÇŽÁ¤IšâN•ÈÆ 1ž]† æp ÿMÿø ñ¹·©jCåA•©4°å_)…Ì0ÞŸ® Øôt‚bcÕQž[IÚe—Τ†­mŽ¥«FeÊà`i>»Ót¥84@ùE€2±ÖÕ¯B©dƒÆOSSÕËauMÈ rîæLÍOk²4«TF䑃;s?¾v-|ðAñÚd(.&poú=bÅäzÜÊÉ ·Ïݨ1±2{ãG?R NÅÅ©¢<±±œ×I°\ÕÆ†×*TP=œœŒ.H.)„„€›[VÛÅ""´»V Œ¹u ¿l^Su[[“s‚”iJ.´»@|pÖ™ŒægšKJeˆ<@À¬HJ‚%K`éRU¥€ìØÛ«£1cŠß6}“r?…û_Ü'ì—0rÛÛ-³•Qý£ê¸ÍtÃÚÙx…RÉ…„µpùD\):—Ë[YÑÑÑ‘®/œS­AeîT­ªZÎôE£¢ - ¬µø×±|4@ ü¨á=шÕ1'HR*#þE©Œ7†Ü Ep “ø’PÜH`t„¨hüò L›û% Ó¦…Ò¢…yÏqZdç?äé÷OQÈs:2 ª¼[¯=°s+þèHTTáÖÖ%$p8&†À˜2*ÛÊd´-_ž×*T k… ¼\®–%N¬U4 ¡²¶–F}”J …š5µ»Þð­_¥RÉF¨ÉãÔT:]ºÄQooj›ˆd_çE©ŒaY¥2¯'rgÒRY*C8@£#4@…ãüy•ÎçäÉÜ{{êUС¬[çO‹æ9ÇI<þö1!KCòÌããÜÙÚKj{Õë뉉ÅÆª´<¿ÿNLãÆP¾¼V×Ê,,ð.[VáqrâU''½d6.ÉB*!´æ²×Ó§Ú;@ ú[nx Ú”‹dBNP•w«s(†ÐŸ²ì|æû çnª,ç¥ á ŒŽp~t#, f΄M›¤ÛÛ3©T ,€÷ßWU{óœceº’g~ÏxðÕƒ<ës•kYŽ:ßÔÁ©³ásš(k‰‰}áð%"Mc«½:ëÙÛ«#<œpÖfE Æy€@%„¾|9«­:;2 ü4@ü¤á…dF‚š5£–‰è¶ê­­GÜ?q$ßÑ(•1FU*ƒâIêmH 0ärUDgþ|xþ<çqkkøøc˜=‹ß>}±;‚û3ï“t+÷Qöu쩵°•ߪ¬Zƒ0Jàj6‡'2­àšašá²y m6è‚YX°±A”J%ÿgïÌ㛪Ò?üdëšîiÙ·"-; *²8:¢‚+ˆ:¢è€Û ãÏÑQgÔÜ7dTÅeTT\ "ÈeiYÚBÛtß›&Mr\š&MÒ6mÒt9O?÷sï=çÞ›7§ÉÍ÷žóž÷]e—`ì´]OPgA*­œ*cï{mÃÉ ©2z­éÚCåž Àï –ùòKxàÈÌt]ùåðïC²›aü®ÒÆå[Ê9ùðI*u¡ð€€øú=ÞÄ»Qh¼«|$à`uµ,v**Ø\^N‰'‚§²’ðˆ¦DG Çeá  ð,tK( V †¬¶A9uu6Çèþ@… cÀs8ñ€]ªŒm¨_î9² ç¼SA§Eø¹çȸÿ~øö[×õÉɰd \qEó×éìm\“^ÃÉGNR²¡Äe½*TEŸúÐçÁ>¨Â¼çFœ<›ÊËÙRQA©‡=<¡*FD0%"ëæÍÌ¿àzÅÆzÅ>3¾òjK, æP*¼V}`'‚²ëêl=AAõ¹¿eß—Qº±1UFé«¥DÅDùѪŽC ßéÌ?Ìþ¢¼-’ãö¸šH!u-\غ麵gŒœúç) Þ/@²:ÏiW¨èèèÿ¯þ$´Ï9Á*I°sZþ¹ ‚'L¥bRDS"#™Éø°°ÆT·ÞÚ.û-ã+ O£A·{ôaÔÐÔÏß"È>U†¾1UFª©gDˆH èDX,ðöÛðøãP\ì\¯TÊÎÍÏ<#;;wUÌef²ŸË&wi.Ö:×±qâfÅ1ðÙiûì™ýÕÕüÔ xÊË=š–¡Vs‘à«ÕŠ©éÝo÷5 R(X9l’$ñQa¡­<ëlOÐæÑ£ýîÀ9+Ïaÿeûmqµ‚$ÿ÷NuB üNWñOñ5›6ÉÓÚp]ÑE²ô˜1ž_»³´±µÎJîÒ\²ŸËÆ\æZŒDN‰dà  ŸÐºéäM1Z­|\XÈÒÜ\vW¹NáŽ(µšÉgÅΔˆFkµ­ÎŠÞÞ\`‚–ñ•·œ ]¡R(XuÎ9X5.DЦN ‚¢.¢ïßú’óbŽ_íèhDÐ ßY¿~½¿Mð+YY0kL›æZüôí kÖÈù½Ú"~Àÿm,Y%ô+ô캃p)~BSC±a£7n“ø9c4òè©SôÙ¾yG¶JüÄj4\Ç«ƒ³oüxJ&Mb}j*÷÷îÍØ°°V‹sUTTxl· õ³uëV¯_·©*.v=ôÜVT œs7ÄÇ;”Ÿ:+‚N·!ñ­·ðôÂÎ ó·ŠÈÖ "˜À—ÔÔÀsÏÁË/C]s}Hˆœ¹ýo“SYtUJ¾*áä#'©9Tã²>°O ž@­ mJTúsEKÏœáÅŘ[¸Å0ÅnH+%4ÔW³è]ŒØX9 FgÎ@’—ó„š%‰›fm“dcƒƒÙ4z4}½û‚b8i`×è]¼¥x‹U«üjKG új?°nüßÿAn®ëúo„_ô,mg£rG%':Iù–r—õê(5ýþÞ¤…I(ƒ<ëŒ6X­|XPÀÒÜ\öWW»=N©PpEt4WÆÄ0%2’sBB>éðaÖÙ‰ “ƒÍ1º·EPðÀ`^H û±l¿ÙБ$ð;Å?¥#°Zå(Î/¼àº~ìXxí5¸ðBï¾nG¶qmF-§þqŠ¢O]§ÔVÉÉJûý£ê(ÏnAÙuu¼‘—Ç;ùùÍÆè‰R«ù£NÇŸ’’:lº±ðò=¾òyìàÁÆ}o9B7E­PðÑYô©:a0Ø|‚ü)‚¯ '㉠¿½~G"|€~Çßþ)EU\skñ“_ýæ}ñÓÆVƒ•Ì…™ü–ò›Kñ£P*èu[/&dL`ÐKƒ<?iåå\wèƒvìà…œ·âgDh(o%'sæüóyiРµ"|€|¯|€À7SáÝ¡V(øxøp®k2•óÄÙž ÜNàÔ*¿ÓYcÔx“S§`æLHOw,×hä™_?ÞêšmÂ×ml8a ýútª÷»ŽŠ¹"†Ï$tDë##×Z,¬>;Ìu¨ÆµÿȦWÇÆ²0)‰©‘¾Ï æŽY³fùíµ{ ¾Š¾› ïŽ4'=ÿÙż8n×”ägŸ îŽ@Ù¼®¿ÞÑ¿ä8>Ÿ~*OoïÊ^ÌÑÛŽb®pž6~^8_HäÔÖ “Suuü'7—ÿæçSÞÌTœ†:÷$&ú}± ëÓÑ@£P°&%…ÒÓYßD5ø% ä3ĘÀïèí2'w7Þ|.½ÔYüŒ)wu”øñEK‰“œäе‡œÄOð `R>Ia쎱­??”•qÕÁƒ Þ±ƒ—OŸv+~Fkµü÷ì0×svñSRR‚Ù›s§N˜L&JKK[>° tä˜=…‚ORR¸ºI •Lƒiû÷“'†Ã|†@¿Ó}€Ìf9MÅÝwCSw•k¯…_~~ý:Îo·±©ÐÄþK÷“óBŽ-zl 7'0~ÿxâf·ªºÚbáõÜ\†ïÜÉ¥û÷óeI VSÙÕ 7ÄÇóó˜1ì?ž;t:‚”ëö%|€|/}€üÑÔ€F¡à“áùª‰ʨ­eÚþýä›L^M³$‘g4²»ªŠ %%¼“ŸÏSÙÙ<¢×c5Êë¯×q€šAÄ´…²2˜=~üѹî±ÇàÉ'¡+gS¨ØVÁácÌs|2U(´dIjyîðqƒe¹¹¬Ðë©h¦×$N£áÎÄDîILþŸ’•4îët× Ô€Éjåúôt64é2N !môht-çë±XÈ7™Ð›Lä›LäÛvåÅõõ.6Âÿû_*V‰8@=‚%K– ×ëyñÅýmŠ ‹sô¨ìì|ü¸cyp0¼÷̙㻼řWÎpâo'ÌŽ7ÎÀ>¤¬Mi6‚³|[ZÊÒÜ\¾))iÚqäÀø°0&%1'>žÀNÖÓ#èž4í*(só©TgC€Rɧ))\—žÎWv"èXm-ïÛÇ{ÆQi6»4 B§Êbé8ƒ»8=^­\¹’E‹1vìX›Òcé.q€¾ùnº šŽ‚ôî ë×øqþ± Ú߯–* Gÿx”¢µÎÓÛ£.bø‡ÃÑĺNK_i6³B¯ç?yydÔÖº} BÁ¬¸8þÒ»7}9%ÎGˆ8@¾Ç—q€!*JîÁ9fWa¡³0ò5J%Ÿ¥¤pmz:_Û‰ £µµœ¿gOÇÓÍéÑViii,Y²„'Ÿ|ÒߦôhºƒÐ’%0c†³ø™8Qvvö§øöµqÍávŸ·ÛYü( ßcý¹q¤Kñs¬¶–…™™ôÞ¾ûŽw+~zð¯þýÉ>ÿ|>>¼KŠ>@/}€ÀŽÐMiA—û@è5 R(Ð06,Œ+bbø£NÇcýúñlBû÷ûìu;=öQåðáÃÜ}÷Ýlܸ‘Í›7ûÛœMWŽd2Á]wÁŠÎusçÂÛoËO–þ¦­m\øQ!ÇÃRãØ­®ŽRsΪsˆ¹2Æ¡Ü*I|}v˜ëûÒÒf‡¹&„‡ó—¤$fÅÅÐ †¹D ßãË8@ ÷öØÇêêHGè¦*•ü/5•É[±“ÖÏ| V*Ñ¢  W@º³K¯€[¹. €8Æe²_½^Ï«vYë»3]ÿ®Ó ¸þúëyÿý÷`ïõæ‚+V°hÑ"‡Å±ßs÷ aðàEMÄÏ"”J9×Ê•ðÜsÇ^Oö¥z‰Ì…™€½ŸdçïSáרÒÀ+¢ÜÅÈ‚î×õá²/¾àÃáÃI=š£çÇ#?ýDíäÉœ˜0­cƺv-K‡ áýúq‡NÇŽ×^c´VKB@J…Âöz6l`Ñ¢EL:•Å‹SßLš™îDœöàƒrôèQf̘ÀöíÛÙ¾};ýë_¹ãŽ;8ëißðáhú!x—®è´o\}5ää8–‡‡Ã‡•WúÇ.wxÒÆÆ3FÒg§Sùk¥Sn¾Ž!ˆ  l-f3<ÉÛyyn{|¹'1‘;u:â[1“¥+"|€|/}€yÄ1UÍ¢Eð¯ùä¥\RZ*ÇûÏÜ'Jn`ÀxåpÛ!f6C]¼ ­Þ®**böòåll¼¬Ò#¿©S§NE­V“••ÈãÊuuudeeaµZýk\dýúõ]jìÓOá¶Û iv†Aƒà‹/`øpÿØÕ­mã²Ê8|óaꋟ•ÁJ†þg(½nwQŸñçÌL·qJ.Œˆ`aR×ÇÅ¡îÊsÿ[AZZÓ¦M#&&¦åƒm¢¸¸˜]»vùl¬©׆À$©QdÔÖ:­Ïdøæ3;7Õ¢2¸Z‚1ÒdDAÔÉÛ§êººŽ¬0I1uh,MMƒr†£º¨ž§ôÈ ¦¬X±‚+V°iÓ&‡rÑ$hÊ“OÊO…M¿5_ kׂ}}‹ÙÏf“õÏ,$«ã› Lʺ´£µ¶²<£‘?gf:ä0j@©P07!ûz÷fŒVëT/tVÖ®…n€^èÆQ¦+âá…g…ŠñÒìºa»®Îù¦Ñ‰y8<œz€Cì<¥¶n¿>ùĹîÞ{áÕW¡«Ž|˜ËÌ™{„’¯œ»¼c¯ŠeØûÃPGÊoNÞÎËã¡“']0L åää.;“KЃ0™ä€]GÊ˱cLÿí(å#‚³?þ»yþ4RàKºè-ۻ̛7yóæùÛŒKg÷:sFö÷i‚C£×^“Ó]tvܵqÕž*Òg¥SwªÎ¡\¡R0àéô}¸/œ¹Ê¨­eAF[ÊË® Tòhß¾<Ò·o·˜ÑÕ„ïi“Pq±ƒÈ±mŸ:%G:´#ÌËöú• ‚‚ä%8¸ÕÛ5 û>üÐßÖwâ›*ð;Ùè×_åÜ]Ms‰ÆÄÀºu0uª_ÌòWmœÿN>™ 3±Ö9ú½Ä0üãáDN““˜.Ç~ IDATÖK/æäðTv6F>rDDðNr2ç„„øî t:¥ÕÚèâ¡3¬GÛV+DD@d¤ãå\ÖPáq·©[ ³Nžt9ÇŽ9g"öF©%Á¶m7¬£B˜Lßa!(Cƒ!$D%ök;±"ñMZ‹ÿLn‰ìd ø¬—P(5Üÿ'xüqðd$ºJ¯gÿÚµ¾kˆN„ðjáÔ³Yµ ,€¦É˜SRdgçýcW{±ÖYɸ7ý{Îâ#.ˆ`ø'à L’ƒí¬¬dþ±clêñ „©T<7p ÷&%ѽݛ; åå°s'ìØ@uuËâÄI4½ŠVÛzÁÔ°m4:‹œãdz·ƒR¢9F2y$råì‚¢‚ÅH3k)8„Í;ƒy}E_§É‚Çê"êŒR ×\÷ß“&µÍÖÊJy¦Ú²e®ýž“’àå—[Ÿ†G¯×3zôhôMŸúº!¢H h‚Õ ÿ»˧)3gÊñ9ºh_¹á¤ôëÓ©ÞWíT×û¾Þ zi ‚‹…ÇNâµÜ\— gÄÄðúСôé Q»#õõ²ÈÙ±£qÉÈèRŽ´­¢ºZ^Μéø×V©ä¹äÉÉ0l˜¼$'3iþ0¶eÄÙÛów3¦u—4`õjyzúáÃî ƒ;î€ûîsLÀÚÂÃáßÿ†?þþügh×77n¼Qž^¿lY眥ê/„øÎäTU7ß 68×=ò<óŒüÔÖÕÐëõ¨wª9zÛQÌ厉*­Šäÿ&C<KK¹;#ƒìº:§ëÄðêàÁÜß!vw%Ú唕å(vöì‘{p˜€jÀ£É–áá6qc/t2\Ĥ ì d4îçç·,€ôz9vÏòå²Ë‘;úõƒ¿üæÏ—Íò&©©°i|ô<ø s´45J~ýE‹ºîCœ7Hàw:‹ÐÉ“rOÓ'·  xçøÃücW{‘,ïÜþ“¾DÓH…!ç„úY*!ÃB(®¯çÿŽ烂—×¹­W/– D´ÆuÒÓžN«}€**äqö‚ÇW© 9‹+ÇWc[µ]U%g-/w^»*++óxˆ®Ø8ER*¡o_×BÇÃl¦žÄÚ·Oîíùè£æßÊùçÃ_ÿ*ûú:»üM7É÷±'Ÿ”m³4›å¼…}$÷pßr‹oméì$ð;AülÚ³f9ûK&&Âÿþçç³ÚŒd•¨Ü^IɆŠ×3騳ƒAüñ$¿ŒJ«buA÷?N± ?ŠAA¼™œÌ¥QQaz—Åe.0³t;G¶m(K­†‘#aÂùß²0éìC”ƒ{ÑäbX^ÎU*•,lì…Î!òûõMõRÓžI’{ˆÿýo¹WÅj5\½ìß3a‚WLk5Z­,pî¸.„~p¬ÏÏ—s¾õ–<,6rdÇÚ×YHÐãY¾\¾I4u <÷\X¿Þù‰°³b®0Súm)%J(ýº”ú×N¡ ‚Á/&iaYuuÜ} oK“-ª îëÝ›§ú÷'Ä×­Ý…œG±³{·ü#ßúö•9–qã¼ö#ßi>ë\܉¾dîz€jjàý÷å^•ÌL÷çGFÊ“'.„>}|ggk6 ¾ÿ^ž±ú׿ÂéÓŽõ?ÿ cÇŸþ$÷EDøÇN!ÀïøËÈl–_ݹàÝwåéÎŒ!Ó ÷ò|YLÅÖ ¤z×= ¥”M4I¤¬MA;1ŒWΜá±S§¨i `”VË;ÉÉŒïnދղµKC$ßV,%ååD”•µí¦ãÇ; ‡nz¾ÎÎÍžž.ûÿ½õ–ÜåŽAƒäûÉí·{6í¼#˜5 ®¸Böa\¼Øq¸Îb‘ã™}ü±œí²ËüggG#ÀïøÃ¨´fφŸ~r,W(ä›ÄßÿÞ¡æ´É,Q±µ‚’ %”|YBmFm«ÎÛ¦ÜÆí3n'ùdŽ™¿g¿UU9¤Tòx¿~<Ô·o×ÍÝ• [¶È·Û·Ëãš BŇY®Ó€i@‹Q€”J9–BƒÐ™8QžšÓ½ë;_çç -[äÅS¦ÈÃ\3gvîaHˆ|o›7Ojß|ãX_X(‹·ñãc°Z»jNÏq€šAÄêž9"߬Nœp,×jå)î>¼·¶‰úÒzJ¿)¥äËJ¿-ušÅåu¤šèË¢‰™CôåÑX"U<•ÍK99Ô»øÚOŽŒäí¡CÚÕ9Ò(x¶lqîç÷7:]£Ð™0Aîéél]ÇË.EÍ¡ÑÈquî¿_BêŠ|þ9üßÿÉ›þ.â€t3D Gñõ×òðVe¥cù€rpÃÔTÿØÕ”šÃ5¶^žÊí•H–Ö=§„$‡3#†˜1DLŠ@¡–{q6——sç® 2j{Œ"Ôj^8‰‰? ¡Å"O½i3Èm©M\}µ<ÜõüóòЗcÔ…N'ð B üNGù-^ ?,:´gÊÙI06Öç&¸Åj²R±¹‚â/‹)ÙPâ”›Ë ‚È‹"m¢'xH0I"Ã`à›Ò"öVU±»ºš´'\øM\ˆ !±³Î2åèÇ ‚ç—_äéÖmE¡hŒØÛ°4Ýwµ´â˜’º:"FÝÝ•;á*Çè±H6Lî-¹õÖîå‡$Ǻí6ùý}ñ…¿-êX„øŽðzôQxöYçò;ï”§ú#´©ÐDé×ggm}WŠ¥ÊÙÙš8 1—Ë‚'è’p«Œ|U]ÍÞêÓìÛSÍÁšj›:6oÝê0¶×+ €¥C†0+.ŽNEUlÛÖ8œõÛoιHZB£‘‡™.ºH^FŽ”Õr)ùÈ·)mÝ:¦%&Ó~!;árïNe%\z©, .¿Üg›NÁ€òØ×_ßÿlé,éÓ|ŽðjáÔ=øüs9™ý']­–ãxüùÏk‹d‘(ù²„ÜÿäRöc™S`Bw„Ž%øŠ(ô³g¨™}µ5ì«®æ˜Áà2UEsüQ§ã¥Aƒˆê YË‹Š‡²¶lýû2t·Hh¨<ätÑE0y²¼-Dˆ ,_.”zbÚˆœœÆŒ™KIÉwþ6Åçt‚; @à;Nœ»wí5BT¬] ¿û]ÇÙQ_\OþóÉ{#ºì–‡·JL†pbj›'XÙf Ïx6_RVÛlÌÛÉÉL‹ŒlÛ¼ANŽ£ÃòÑ£ž_#:ZÎÙÐÃ3nœÇYÅ‚æè±YýF@€„FsÀßftâ®!ð;¾ò2äH¬ej5|ù%\x¡×_Î%U»«È]–KáÇ…Xë¬Í[§$}’šoϳðóh uAv K=ŠÖh­ÕÚ–Þ55\4`@ÇOm?rı‡''Çók$%5ŠÉ“å)äp<¢]¹À­¢#|€=ñMø_ùÝ{¯<¢bÏ‹/ú^üXMVŠÖ‘»,—Ê_+›=öøPÛΗØ~>d µ")<ËÐ?(ˆÑZ-cìOß&—/_δŽx¬ÍÌ„¯¾’ÅÎÖ­ò—§ Ò(v.ºô¾> Õ¹Àm¦£|€=áÔ Â¨ëòÎ;r8z{fÍ’‡¾|…1×HÞò<òßÎÇTà^Èá§‹á×Bf ñFìÑ(  uèÙ­Õéï“ >ûL•Û\r$W(•²“rCÏE¢‚ ½^ÏèÑ£Ñëõþ6Åçˆ A·cÏ9=C‡Ê©-|Aùærr—åR¼¾ÉìþyBß >¿¾ºªZÈ0®V3*4”1aa6¡“B@g 5{ì¼ý¶œ ©µ±xghMž,wÇõ´D S ÀïxÓ¨¬Lîé±êŸ~*§[ò– »,—šƒ5n“°{œÜÛ³ý|y¿)½m"§ak@p°WC‘y­F¹1ßz 6onùøÐP8ÿüÆá¬ ºí -áä{„À›ˆoªÀïxËH’`î\8uʱüÍ7½áÙpÜ@îë¹èßÓ7›’¢66N‡õ×Ài€GkµÌ×é¸!>ž¸BÔî6>rDîíY¹’ƒ„Lž,Çš<ÆŒé13´„ï>@o"|€šAøu-žy{̱ìž{\g{÷ J¾)!wY.¥K›Ý“ÝOîíùî÷`hÒÑ®VsS|< t:Æu…,ëuurˆì·Þ’gq5GLŒo`Á9l®@ è’  ‹ñãðÏ:–{.¼òJÛ¯i.7“ÿn>y¯ça8ap{œU ¿\Ÿ]{Ç8×_ÁŽââQ©ÚnPGqø°,zV­‚ÒÒæ:U§}ÝuÐYÓi „øöú§äæÊ Nís|ÅÄÈž_¯ú@µ»çƒB,µî£WDÈÍŸ_ …ñŽu± ·öêÅ|Žs:AvõÛØ`§È½õ–œŠ¢9ââ{{†õ®¡]áä{„À›ˆoªÀï´Ç?¥¾fÏv 7£TÂêÕзoë¯#™%Š>“c÷Tü\Ñì±Ç’åa®Ÿ.†z;÷pITóu:®‰íT3¶Ü¶ñ¡C½=ååî/ PÀ´iroϵ׶MYvs:ƒ„„U²b±Z°H–V¯ÛrŽÅzö¼³Û  ÛîÖm=¦¼¸œücùL¿r:½Ã{Ó;¼7ñ¡ñ(zHörwHàwÚãœûàƒ°}»cÙãÃôé­;ßT`"ÿ­|ò–çaÌsnÙ¬†MSåa®#ç8Ö%r{¯^üQ§£“„‡66à“Odïð¦×”øx˜7Oîí<ا6vufÍšåµkד[•Ë™Ê3äVž]Ûíë«õ˜,&'A"µ6¹\gùÇËmÛ¥†¤ð$› rµôÒöB¥èÃÏ‚E A—eÍxí5DzßÿÞÙÈ•Û+É]–KѺ"¬&÷)*Šcá‹«àË™Pn—BK­PpeL t:¦GG£ê„©œ8p@žÉµj•c~¦(r¢´;ï„k®‘³« ¼‚ÙjF_­w4öûyUyÔ™[Î'©·Ö“UžEVy–ÛcT º0[”–DRx¥ø¬÷$„ø¶ø9óç;–õé~(¹ÂZg¥ð£Br—åRµ§ªÙëï%sm»ÇÁÁÁüQ§c^¯^ôê Ã@GŽÀÆè?ø€^»w7lBÜ~»ÜÛÓEÒOtê­õdçgcÖ˜Ñ׺8új=V©ùœp‚f°&ÀÃPRÉ™Ê3œ©<ãö âCã„ј!\;ìZúFx0žÞÅ)¬-¤>¡ÞßftB üާ>@ÕÕr’Ój»\¡²¯;÷‹âÏ‹9vÇ1êKݱðý¥²ð9i÷û¤Tr]\óu:¦FFvnoƒª*øé'øæظ²³X¸la….½Tîí¹êªÙÛSg®£ÂXAE]…Ëu¥±Òm]E\o0à0Ðð³Ï»J¡B¥T9­• ¥Û:O×®®Õà‡£P(P hqÝÚcÛvmI-'ÓO¢ ° ÌjSµÛ¶ð ‰‚š j Øßø°pÿÆû™Üo2·Œ¼…Ù)³‰작˗gñ/‹yoï{ÅvΡ|o#Àïxê´`ܱaÏ’%raWœ^rš“;‰duí‘›$Ïäúær¨Ö6– e¾NÇÜ^½ˆêÌ3{l<[·ÊžáMpjaî¸CîFëß¿#¬ì$$ôÕzÛHVyúj}³âÆdñ<­K†{ç2¡šP’“lC3öëÞá½I O", ÌIœ(Çñ¾£¨0VØzÙ–qÔÐWbh!xg3HHlÎÞÌæìÍ,üf!3†Î`îȹ\>äòn1d¶'/l{u‡×Ùz'ƒH èt,] ìXvóÍð§?9+™%2ÿœIÞ›yÎu ØyžÜÛ³ó¼ÆZ•ŠÏ+„©Ð±— 0þþœìÜ<-2’‰‰\K`g VhµÂo¿É‚ç›oämk+g …„È))Dãõ˜­fr*rÈ®Èv)pÎTžÁ"¹®í âBâèÙß¶$hœD‹½à Õt2ñ+è”HHlÉÞÂê«YwxåuÍEŽO4}ðtny W%_EºãýjªLU,ßµœoÿ7ùÕùn›Òo Lz„郧÷¨\`=Z]pÁœþù$''óã?²yófrrr8;½Y ÎÁcɉNí¹ï>ç<_…Ÿrô¶£Xë…ÃÑaðè3P«déàÁÜ™˜H§  @îÝÙ¸Qîíi)˺=çœ# žéÓå¬ë~ÀXg®ã`áAöæïeOþöäïá`áAŸÆ°ið¡±8ý"û5nGô#DãÿÔ#‚îÑbdCÆVXÍ×™_·8,ά᳸eä-Lí?Õç‘« j xõ×Wyc×n…šW»š‡/|˜‰½'ÚÊ…ê!˜L&›Ø2dO=õ7Þx# PGÑœÐW_ÁÌ™²L\›69ÎØÎ~&›SŸrÊÔ¾e2<ûˆ äÓ”&ú˱¹ öìq\²²Z~X˜œ°AôôëçÑË·7ßZµ©š}ú}6¡³W¿—ÃE‡½>\¥@A/m¯fŽ?ž¤[ƒÈæ{:c.°²º2>Iÿ„ÕV³-g[‹Ñ¸û„÷áæ7sËÈ[HOõª-'ËNòÒ//±bß ·"¥†?Œü]øçÄžãTß“Pþ¦Ú‹«ÕJee%ÖÖ;¼†»8@YY0w®£ø‰—³84ˆ«ÉJÆèßwþ²~t¼½ÎŒàÓ””Ž \xú´³ØÉsž‰Ö"#GÊCZÓ§Ã…¶+F'±–J ¥6‘Ó x2K2½’fA©P’–H¿ˆ~.EN߈¾ªºfVùÎ ¬»S\\Üá>@-Å]ãîâ®qw‘UžÅ?`õÕ->êòøÓ•§yaÛ ¼°íF÷Í-#oáæ7£ÓêÚlÃ>ý>^ØökÓ׺bÖhY0v=ÿ¯ôïÝæ×êNtÿ³|ùr4 W_}µCùŠ+X´h‘ÃbØoÿ¾ýsC½Ñ(;,+k<^¥‚éÓ‘”$ï×—ÖsïÐ{ÄÏ V`VÃKƒ·î„qŸ}ʦѣmâÇ«öK‹î»OŽÀø÷¿Ãe—±(4TÎÂzÍ5ðä“,Ú°ÁAü8^­É~d$‹†‡wß•§¶ïßÏ¢  Ù¿ç¬øi«½ mÜ´þÁ<ÈW™_ñÔ–§¸nÍuDN$æÅ.]u)}ÿ/û˜Œ’ŒFñ³©Ép±ÏE}/bîȹL>5™ÿ^õ_~¸õŽÿå8ÿ°üƒÓ÷Ÿfë[Y}ÝjÔ[ÔÌ;ŸK^Êè!<÷ÔsmzaÖ¬Y,]º´ÓØÓ÷ßzë-ñão{šî¯xe^ô(Gþt„]wîbâñ‰$„&4°Éápö}¼¿{ÞKzóûU¿çÚ{®uìØÒëÍû¿yL_=1oŽáãCcIk"~6AlH,ON{’ìÿË&|{¸ƒøi¸Þ† X´hS§NeñâÅÔ»ˆ%ÖéÑC` |þùçÌ›7o¿ý–óÎ;ÏVÞðáhú¡øž;ï”ÓVÙóÌ3ðÈÛ†L®<€!ÓàpLµþù$§ä?C†ðG]ÛŸª°ZáØ1Ç^½{›Ï©Õj5Œ#{x_~¹ÉчSðO•Ÿrð×Ù“¿‡‚f—¹ oD_ÆêÆ2¦×ÆêÆ2V7–İNâc%t,’…NþÀê«ùß‘ÿQS_Óìñ!š®v sGÎåÒA—:%qµJV>?ö9Ïo}ž¹;Ý^§_D?¸àþ8æùʼn!°ĺuëøË_þÂ7ß|ã ~GSÿ”+œÅÏŒr' @ù–rÒ¯MwJk‘—(Ïô² dsJ ÚêïS_‡;Šýû¡¦ùW³ˆò\þ†eÄŸ9/KHì×ïç§S?‘–•ÆÏé?S¡i»XS*” Žl9czaŒn 1Áb¸§áä{:£PK¨*.t— ºŒš5¬?ºžÕVóý‰ï]WÕÖ×òáÁùðà‡$„&pcêÌ5—ñ#X}`5/n{‘c%Çܾ^j|*_ø07¦ÞˆZ)>‹ÍÑ£[gÉ’%¼þúëüðà îÅ8ö°÷OÙ¿î½×±~ÀX¹RN[¥_©'cA†S÷C©ðØÓ0¢oëRRHh­¿O]œJÂ^ì<(Áµ•ÐP5ÊQì îó<[éEé¤Jã§S?±9{3¥†ÒÆÊýÀøÖ]G­T“—ÂÝ›ØÝk4ÚmË'÷`„ïéŒ>@žª å#þÀFü‚š>>ô1«¬v”° ¦€Ww¼Ê«;^%X,çœs侓xdÒ#\1ä ŸÏ2ë.ôØ!°ŠŠ "##IMMu¸aõíÛ—•+Wb¬£©¨€ñãáøñƲ  ض ÆŽSÿ°š~@VyV«ÏS `ÆÐ<<éa.ìs¡WlC`=€ÐÐPÒÒÒœÊCBD 1ož£ø9÷×èáVßt”Â5Î?ð+æÁÇ·+ysèPnw7Í»¸^~–-sL!ï)ññ"§Aô Øòy^âTù)ÒN¥‘–•FÚ©4r«Z™ì,áá¶¡«†¡¬a±Ãœ| AÇ2,vO_ü4O]üÛr¶±úÀjÖ^ëØ‹k‡Z©æ¦Ô›xxÒäĥt°µÝ‡+€Ôj5S§Nõ·ä'Ž•+{±~½cù¼ypëU&ö]|ˆÊí•uõxáa8vE ?§¦rnX˜ó…KJ`ñâ¶ ŸÞ½{uÆŽÅ6ý¬ƒ8SyÆ&v~:õÙν_ÍÍ”~S˜6`#BF0%uŠè÷!ÂÈ÷tE OP `RßILê;‰×.¯3¿fõÕlÈØ€Ñb$DÂü±óyàüèÑ×ßævyÄ7Uàw^zi=¯¾ê£fÔ(X¼°†Ýb<åЫ"Bö÷‰žÉîáÉoêïã‰ðQ(ä^{¡3f ÄÅyã­yDAMCOfi¦G燆3¹ßd¦õŸÆ´Ó•0 ¥BŠ[¾|9SS§úÀjAÂÈ÷tu OPpͰk¸fØ5”וóýÉï¹xÀÅbâé±>@­Aøùžü|YoØçúŒˆ€m¯–Qz_:– Gÿœœ¾ðÈó0k|KFmïïSR"u-]ê^øètpñÅŽb'"Âï¬e k ù9çg›è9\tØ£óC5¡Lê;‰i¦1­ÿ4Æ%ŽÃY ] ÀÇÔÖÂîÝòÔö¦‰Î?¼9¢ù™`vÔæ{ÇÀ3O+yyìPn³÷÷iðIL”_lÁyJº©6Usºò499œ®8ÍéÊÓNëÚúZ®¤â‚>0­ÿ4.p1ç&‹FéÛ™e@Ð]Hàs$ 22à×_aÇy}ð ýä+=Ð ÿ=÷$!oœvºÆ×WÀÚGùvt*ãü}Z+|yDެè%ác´ÝŠš†uK™¢[C€*€ I¸xÀÅL0‰½'¶yvV{s ZFøùžîî$èXÄ7UàuJJ`çÎFÁ³c”7«ÖÈþ{„¿;ÔH 9¥…þîHv¦¤§Ñ4 ŸeË ªÊõ%uö— IDATºFáãA°A³ÕL^U^³â¦-ÓÍ[ƒZ©æÜÄsmCZö½`u°W®íI.0AÛ>@¾§'ù |@‚vQ_//lèÙÙ±2=óÝ%†Ûy)h/Š{qŒðÌ£0ö¦Þ¬4uYYcOsÂçá‡á®»œ„„DAu[q“S‘ƒ¾Zï6™ ·Ñh;Ìæ´|Qß‹|lPˆß3kÖ,›ÐíILLâGà5„xDNŽãPÖž=r0eOQ(`ȸbh5—m;@P™É¡¾4žx^Éß®Jæ{¬yáÓ«—,|î¾ÛAøX%+›³7³æÐ>=ò)ŵŮÏ÷2ª@úDô¡Ox·ëȠȱE Î$pKu5ìÚå(xÚ:1 O”™Ë†Õrn\ ƒÔ5hËj¨;V´¡žRJ ¢qLÿä@X¶$÷Æ÷eìk¯µ,|zH>Áòp‘„Ķœm¬I_ÃÚôµíNøÙµRMbXb³â&>4Þ«¯Ù^„ï>@¾Gø ¼‰ø¦ö`JKåièyyòÒ°Ÿ/'>OO—“ {Bõ V×rAR £"kI°V¢7 .ª‡íÇÙg´ÙÊV®BîÖÞ16¿¨å»´¯ˆû²{á“ÐØãsVøìÈÝÁšCkø$ý£$7 T(IMh¶÷F§ÕÙâët„ï>@¾Gø ¼‰@Ý{aãJà4l·'ßgõô£†Ô’SÅjâ+ „T˜Á dŸ]ZAƒøùßµ ¹*‡/¯ú ª 7™ËäŸ{îà`öäïaÍ6Yô´&‡Nx`8ƒ¢¹8IáIÝrj¹?¾Gøùá$ð&Bu!ÊÊ\÷ÖØ—åç·Í'ÇQ˜èO-ýTÕô©d º†þÆ:Âjí…KÎ.mÀ¬†Ó}à›V®9¹„›oÿÊõññ6ás°úk¶?Úô5/=îúx;"#¸fØ5ÌIÃ%/é–G ž!P'Àl†S§ ;Û½Àñ¶°Q#‰‰(LDQO”ºŽ¨à:¢4F¢”F¬Fú× ¯;;fÜŒFµ†¡“Õ_^N÷k’™È€B’6ÿÄ?7üDê©SÎ'ÆÅÁC‘1ç>:ù9kÞÏ‘â#-¾ž6@ËUÉW1'e— ¾¬Çg7>@¾Gøùá$ð&â›ÚAH’,d22—ÌL8yRžNÞ^‚±‰‰hê‰Ò‰ ª%:ÀH¤ÚH&¢­f"ëÍDÕ™ «k’ÅL»Ží2j9]Ev?Yè”öM¯:bÔ =™É¸½{¹ù‡ƒ ÌÏGs6âr µé…ââ(ùó¼{~«O¬âÀ»kñµC4!\9äJæ¤ÎáŠ!Wx-†Nw@øùáä{„À›ˆ\`ÍЖ\`%%²¨i*tއšÏ^_ìT…‰(µ‰¨Z¢DiêˆR˜ˆ’ê‰2›‰4š‰®µXßqÿÊzMcNnuý%‚b«ée9ÍÐÌ£$ïÚEòÁƒD¹sbvƒ5&šŸçLä±aùl-ÝÛâñAê ¦žÎœ”9ÌLžI¨&´ïH "˜ YjjENS±SZÚºk„QO/EñÚjBj‰¨%Ji"Êb&Êd&Ê`!²ÆŠªa–¨ôÕ;r½Ð)¨DêcAYN¢éɇ1uûvú§CÙNmˆ å½ßÅðpòiª¾†fÚ1@Àïýž9)s¸*ù*ÂÃÛõÚ@F’$¬V«m±X,­ÚW*•„……†RÙµfH z.Bµ@FF2/¿ì(vr[˜a­B"VQGBX5 ÚZâjè¥0o6’`¨'¡ÒJI yØÉ COž") "B8XUQ êbXb”("%4¡&‚Jè[uŒQ÷3{ëVÂ~òN<«FMQïhNèÙmäU›Rj¨ pßE¦VªùÝ€ß1'u×»VôîìdµZ1˜L&—‹B¡ €ÀÀ@mÛh4Þsˆ÷Ĩ¾¾žêêjªªªlkûí–ꪫ«1›Í­(­ÝoB¡@«ÕNDDááán·›« E¡P8]¿½>@f³ÙöY1ÛM×F£‹Å1*¼«öiZæ«cÄ©ÅbñéRSSƒÉ䘶»"P |ôÑM|ô‘cYˆÂLBxµ,p‚jIPˆ·I0Ö“Pe!¦ÒŠÒŠÜcÓ½6fµ,fÊ¢ < ‘æ+R¨ Uˆ‘@M5¡Ê ÂÍ…ÄTçÒ«°€yyÄŸ>CØž"ÙQÉ›ö¨ÉO çX¼Šß¢jù-¢†Ãqp<ÚŒEi—Okà|¾J¡bJÿ)ÌI™Ãuç\GlH¬WíëIxÓH’$ 555TWWSSSã°][[ë @š'®Ooú#å ͉#ûukÊNœ8ARRf³¹E!clO ŠNŒ$I¶÷™ÛÒ“b3(•J› ²H†ªª*âãã[/®ê¬ž6ë¡„‡÷Œ^uáÔ ‹-âØ§×ÓK2o2‘Pc&¡ÜJhmÇ5Y],hJ£ÁnŨ­ÇT‹BSMåÕV›KTm.qÕzâÊˉ//'²ºEýkë՜օr(NbgD5‡b­¤ÇÃÉ(°:?Ä5‹“úNbNêf ŸEBh‚oŒîA”––RTTd(®DKk·qÛº/áááT¸‹ÅÖ=@-pס6¸iÚ(Œ‡²X u¡ÕHåHåJ¥„Öa* ¦6—¾Œ«¨ æpE»ýlÚ‹1HÍÉ^ì®g_´‰ôx8Y‘f¬Š¶Y(˜Ð{sRæ0;e6IaI^´º{bµZ),,$??¿ÙE¯×wÛÞ÷Q((•JÛ¢R©ÜîÛo[,[ÏÇ‚®‚@>@RÈ=6%±jÃë0U R—lÍ'Òtš^Õ'I.Éâw¹Efyaþ»°*Ô«©VQ¬¤,ÐÊ‘0#éq‡ãd¡“aFR˜ÛüªE "96™ä˜dÛ:ÚͰþüønº.&“ ½^ߢ°),,l×ÐOwB©Tà0e¿H’ävh¤Þñ'Ú€Z­&,, ­VÛªuÓ2­V‹F£iQ¤4'Z\í»ò»ñ„†!°ŠŠ *++©¬¬lÓv§Sf[‰Z­öh¨³©?—«öiZæ«cÄ©J¥òéRUUÅ¿þõ¯æ²› P0@qœDe¤Sh5hJ ¤ˆú3ÄÖž¢wyŠò )é¸'ïê(‚Š ¨ô|»*PêÏ.í£—¶—ƒÀiXˆ€J¡r:~ùòå »Û»Èl6STTDAAÃâêÆÚçCOÏiÍ1f³Ù©§´´´K²ˆPKÑÆÓè*3èWr‚ˆÜh»Ÿ%!PÜ6S–þ^«ƒ3Ä¥ÐñtZzksM&………N¢ÆÕRRRÒ%„DGM||<ÁÁÁ.K[¶»Ë‰B¡°ý€ :*•Ѝ¨(¢¢¢ümŠ #P ܳk^›Ï•Pya¯…ü0÷Û¦¶?Äú  z‡÷v8ɱÉôè‹ït——––¶JØ”••yéu} 111$&&¢ÓéÐét¶mû2N'~ÜÀBµ« Ceá’¯=+d\lëµPï#a¨ $XL:ˆ`µ¼RÙÊÚ[ƀȄhBÜÚ`2™¨¨¨ ¼¼Üiíª¬i]ee¥˜–Ú¥RIlll«„MkbØX­&Ìæ òósˆ,HRãâ›}+ÐS¦qiÜo®N:{¾ëº¦õ®¯cmRgOs~î¶÷ÝS^n@«Õ RIH’ùìbq³6Ÿm+×u ÛÇ8'Û¢k÷ö¢T*‰‰‰!!!øø8â㣉‹‹ ,,Ø…saÓ³›H-žãÚ©±åý°° "ˆ×JLL … «µ«Õpv©Ãj=År«µŽêjÇŽlÇX,ÇËkù_|"}’oټƌBÅ/””À±cpÁþ¶¤{3rdÏø Ô‹†fPZ”„жZË€ÚPB”!ý $)µUZR£²ªP˜(­J¤z K½…ºº:ŒF#uuu”KÉ7æ;”FêëëDˆðaqD£QFll(11AÄÄ­!:ZITDFZ‰Œ4i$,Ì€$Uc±A’ùÛôVQR"/¾@ˆß3eŠ¿-èþÄÄñ#ðBµÄëòÊŠ•ª³ï T* U¦$2R"*ÊJT”Dd$DEAt4g…¼­ÕZP(Êò¯í§ÙÍ@ è"$h3j5hµòÚ¸íjßUYp°„B!QZj¥©}- PhP©‚)+ 66Ù§ qñÍ~ƒÏBûýJ¼ã×bOs¡ Üm;î»;§¬¬–ððÔj …úl›¨›lËk¹\×¹>ÎUüúžûDµ¶Î3­F?0«—¶e2ûm“ÉdójÞ‡ÉÕgÄÕ箥ãÞ£ÕΫ]™ó¾ýñŽ>qí?_FþL7»ÛÎíëËß#Mê¯WWgD¯ßBO@ NŒB*•¼(•Ûö‹§åǫÕâ^Ìhµà­ÉE[·úvˆF© D¥Ò¢RiQ*ƒhÎÁ\ûó´tŽó¾«ë8ï+•(•Á(•Ag×ò¶Jå\¦T·²¼±¾ÁqrùòåÌœé\`×ìܹŽiÓ¦ãoSº-yyy;¶‹±cŘ®¯Ðëõ<8Úßft"X3,Z´ˆ÷ß[™«:WëÖÔ©ÕŽ"¤AY»$Je€M¬¨TavÛ®öËÔjçsŸ–@Ðz½žÑ£G£×ëýmŠÏ¿ -ðÞ{þ¶ ­47µeÎÝÀrïDÝè°¯P6[ßÜ~ÓsÄ‹BÑòÔn@ Ú‹@-“s“'OC¡ÐØÁkÖJ¥Æe¹ûz×ǸÏš*öûàj(¦³£×ëéÕK8ù¹{ùÛŒnMII Nù£ÞÃd2Q]]M´pxÿMÍËËcÛ¶mDEEqá…ìPòäÅÜzë"ÿ×CX¹r%=ô¿ÍèÖˆ6ö=_|ñW\q þ6¥Û’ŸŸÏ¶mÛ¸ùæ›ýmŠ УЖ-[˜={6—\r §OŸÆ`0ðã?¶;‘ŸÀ3jkkýmB·G´±ï1"v—iH^+xƒî‘Ѱ<úè£,Z´ˆ>ø€Í›7È|à[JKKÛõÅöÄa­5Ç6wŒ»:WåMË, EEE-¾¾/0›Í”´#Ò 'ÿ£ÚÚZªªšå­6.//Çh4¶úÚ¾¦=¯íéÿ¨¥×ªªªr+þÜýª««©®®v(sõ¹íªmìéùþ¼_´æ{ä+\}<Á“6nͽÅWmÜÒµ»3=V ¶nÝÊUgç_+ ®¼òJ¾ûî;¿ØóÅ_——׿ó—/_îÕc›;Æ]«ò¦eUUUN"³=7O(..fíÚµm>ß“ÿÑضm[³Çx«7nÜÈ©S§š=®£ÚØÕk{‚§ÿ£–^kÛ¶m8pÀe»ÿÑ®]»Øµk—C™«ÏmÓ×®©©Ál6·ÆìvÓž6öô|Þ/šþêëë©©©iÑoàêsà ž´qkî-¾jã–®Ýé±Óà³²²0`F£‘€€Þ{ï=Þ|óM~ýõW@žÿñÇ3lØ0ŸÛ£×뉌Œ$((¨MçgeeÑ¿¯ÛÜ1îê\•7-3›Íèõzz÷îm+Û·o£Gû>î„Éd¢¸¸˜ÄÄÄ6ïÉÿ¨²²³Ùܬ³¦·Ú¸°°­VKHHˆÛã:ªÝÙØZ<ýµôZ¥¥¥¨Õj—ÃÚîþGåår¤ñÈÈH[™«ÏmÓ×>zô(}úô!44´U¶·‡ö´±§çûó~ÑôTYY‰^¯gèС­²½=¸úx‚'mÜš{‹¯Ú¸i™ÑhdïÞ½=¢W¨ÇúÕÕÕ R5¦kW©TÝ7Þxc‡ýh@ÐhëƒxW£Ç  †h­UUU6…_VVF||¼í˜aÆuHï@ ‚Ž¥Ç  ØØXúöí˯¿þÊôéÓؽ{7cÇŽmÕù<ñÄäåå1|øp-ZdJx—ÒÒR/^̳Ï>ëoSº%_}õ_~ù¥mÿ±Çsêx‡-[¶ðÁ V«ùÏþãosº?ýôŸ|ò‰m?99™ûï¿ßuO~øá>ÿüs¸çž{èÓ§¿Mj3=Ö`éÒ¥,]º”G}”¼¼</^Ì®]»0`@‹çž9s†¢¢"FŒÁ£>ÊàÁƒY°`AXݳ0Üzë­ËG}äo³ÚL°páBž~úi¶lÙBii)çws*†×_sÏ=—1cÆðôÓO#I½{÷f̘1üöÛodeeÙ~<î‘$‰n¸Áål‡eË–ÙÚø™gž±ÅSY¸p!=ôˆÍä¿üò >ø SyII ·Ür ÉÉÉüþ÷¿gÇŽ¶º3gΕ•%ÄO+ÉÌÌä¶Ûns*¯­­åî»ïæœsÎaÚ´ilܸ€W_}•n¸={ö››ÛÑævYæÍ›GFF†Sù»ï¾Ëĉ9r$=öV«•ððpú÷ïOÿþýyçwxì±Çü`q×ãÙgŸeÆ Nå›6mâw¿ûÆ ãŽ;î ²²’ÀÀ@JJJøñÇ)++kUgAg¦G €n¸ÔÔT~ùå6n܈Á`p¨_·n/¼ðË—/gÕªU¬Y³†W_}«ÕÊgŸ}†F£qp¦8óòË/sá…²víZ§6^³f ‹/æÍ7ßdåÊ•|øá‡,[¶Œ§žzŠ™3g2nÜ8?Yݵ8sæ ,àšk®aÿþýNõ³fÍ"""‚o¿ý–o¼‘K/½”²²2¦M›FDDß}÷]t‘4× UUUÜ{ï½üîw¿cçÎNõwÝu¥¥¥|õÕWÜwß}Ìž=›ÌÌL²³³1›Í„……që­·ŠÞÌxýõ×™:u*ï¿ÿ¾S‡7òüƒW^y…5kÖðÝwß9 oÚ´‰ÄÄDØÑfw)6lØÀõ×_Ïã?îôPš››ËÌ™3¹çž{øúë¯1™LÜzë­1uêT6lØÀ‚ ¸üòËýd½—ÒîÝ»¥´´4I­VKÇŽs¨»òÊ+¥%K–ØöW­Z%=Úá˜/¿üRúë_ÿÚ!¶vUvíÚ%¥¥¥IJ¥R:~ü¸CÝôéÓ¥W^yŶÿþûïKãÆ“æÏŸ/Í™3Gš3gŽ#=üðÃmv—¢²²RJKK“|ðAé’K.q¨;uꔤÑh¤ªª*[ÙùçŸ/½ýöÛÇÍš5K:tèP‡ØÛ1RZZšô /HÆ s¨«®®–‚‚‚>ß³gÏ–þõ¯I“'O–jkk%I’¤¥K—Jï¾ûn‡ÚÝÕØ·oŸ”––&…‡‡K»wïv¨›3gŽôÄOØöׯ_/ mÛÏÉÉA§Ó¹Ê´ Wmœ˜˜(ÚØ‹èt:òóó±X,¶²Ó§O“””äG«º:ŽššÊÊÊle999¢½Œ«ûEdd¤è¹ô":Ž3gÎØö>×Ý->˜@-0{ölV¬XÕj`åʕ̚5ËÏVu/DûžñãÇmK4šÍ–-[¸îºëülY÷!))‰‰'ÚbÏ”––²aÃñYö2³gÏfåÊ•Ô××â~á f͚ņ (((`õêÕŒ1¢ûͬó·vgàꫯ–úõë'RRR’4jÔ([]MM4sæL)..NêÝ»·4yòd©¸¸ØÖvMþ¿½{ ‰ªÛÃþŒ&^R qWæd]HÊA³"ßL¼¤£i„DRD,LE  ³QQ*¨”jXTD‘”—$#22˜•’dWµu>ˆû¼sì=Ç<ïiŽÎóû4³×¬½ÿ{yX{­5:ßñÊ•+å¶¡¡!áïï/ÇÞÞÞ¢¿¿_ÕNOuuuB’$¡R©„R©’$‰¬¬,¹½¢¢BØÛÛ µZ-¬¬¬ÄÉ“'õXíôôöí[!I’°··&&&B’$'·¿xñBÌ›7O¸¸¸qèÐ!=V;}EFF I’„‘‘‘ptt”Wy !Äׯ_…V«*•J8;; OOOÑÛÛ«Çj§§ôôt!I’P*•B¥R I’DCCƒÜž’’",--…Z­ÎÎÎVãͽ4&>#"""ƒÃDDDD‡ˆˆˆˆ  "ú[¼zõJÞÊ@_ºººðþý{½Ö@DÓÑ ÔÕÕ???ìß¿_çøéÓ§Q\\ü·_oddóçÏ×Ùðwêì섇‡Ö¯_Ï—‰hR€ˆf ¡¡!}ú¤Ó¿±±@dd$nß¾­ÓöðáCÄÄÄ <<999ß‚¬¶¶yyyèììĉ'PTTôÓz‘””„ÐÐP$$$ÈuVUU!''ˆÇóçÏ'ô}ùò%Ο?7oÞàÌ™3ònÍÝÝ݈EXX222ðíÛ7@MM rrräþׯ_ǽ{÷ä÷§NB{{; ¼¼{öìATTRSSåÝs‰èÿÑ ¶{÷n  ´´tB[CC®\¹¢sìøñã””” 44_¾|Á–-[püøq¸¹¹¡ºº~~~¸|ù2²²²túÇÆÆB­VÃÚÚ!!!¨­­ܼy»ví‚»»;ÂÂÂpîÜ9dff b  B{{»|ý?F£A}}="""ÐÜÜ /// ÃÆÆfff°··‡››fÏž=¡ss3’““áç燆† b``žžžøøñ#ÂÂÂpçÎù§A åþ‰‰‰HMM|ûö °±±AEEBBB Ñh°}ûvôõõáñãÇ“þû‘þð×à‰f°Y³fáèÑ£HJJB``à/÷?räöíÛ`,Dtww#;;`ee…ÌÌL$''ËŸ¿uëlmmïÞ½Cnn.<<<’’‚´´4„‡‡”J%qèÐ!€$IxðàLLL~ZGYYzzzðìÙ3aÛ¶m°³³Ãµk× ÕjáììŒ5kÖ ::ú/ïÅÎÎ555P*•€cÇŽaîܹòýlذvvvxúô)ÜÝÝñõëW´´´@•J…öövôõõ¡µµK–, š››!I‚‚‚î’ŽŒIDAT`aa­[·þòwLDúÁ ¢N«ÕB©T"//ï—û˯-,,`dôÏ–––V\) ùµ‡‡zzzŒ=‚JHH€››ÜÜÜsssù³*•ê/ô´´ÀËËK¾¾B¡€F£AKKˤïÅÊÊJ?ãçôööÖ©aéÒ¥hii±±1|||PYY‰ââbhµZ£´´UUUðõõÂÎÎŽŽŽðõõE~~>FGG']éG€ˆf8…BôôtÄÄÄÀÇÇG>>kÖ,|ÿþý—ÎóïÞÿ«¦¦&ØÛÛ¬­­‘ŸŸuëÖM¾ð?±¶¶ž0y»££7nœÒùÆÏ9>FGGñúõkX[[ª¬¬ÄóçÏqãÆ ´µµ!%%Ø»w/ÀÖÖwïÞESS=z„¤¤$aÇŽS®‹ˆ~Ž€Í›7ÃÅÅEg.——Ñßß?~ ¨¨ÃÃÃÿÕuÆ'E×××ãÖ­[ --Mnÿüù3JJJ&}Þ7¢©© åååÆ&T744`Ó¦MS®õ?þ@yy9^¼x! `llŒÕ«WË×,++ƒ™™°víZ´¶¶¢ººpÿþ}ttt`Ñ¢EˆŽŽÆ²eË~)T‘þ0ˆŒŒ U[ˆˆˆÀ‚ 0gÎÜ»wOç‘×TlÚ´ ®®®X¾|9¢££åyG™™™˜3gœœœ V«akk«³ªê?Y¸p!²³³'''øûûãâÅ‹P«ÕS®uÆ 8xð V­Z'''ÄÇÇ£°°PZ¼x1LMM `lÄ+88jµZ~|×ÛÛ‹+VÀÕÕ‹/†¹¹9vîÜ9嚈è÷Qˆñµ¨DDDD‚#@DDDdp€ˆˆˆÈà0‘Áa""""ƒÃDDDD‡ˆˆˆˆ  """28 @DDDdp€ˆˆˆÈà0‘Áa""""ƒÃDDDD‡ˆˆˆˆ Î?UæÂ´DIEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/compressed-select-cache-shuffle.svg000066400000000000000000001035071416254111300275760ustar00rootroot00000000000000 Selecting with small (16 bytes) record size (file in cache) 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 0 2 4 6 8 10 12 14 16 MRows/s No compression zlib lvl1 zlib lvl1 (Shuffle) lzo lvl1 lzo lvl1 (Shuffle) bzip2 lvl1 bzip2 lvl1 (Shuffle) PyTables-3.7.0/doc/source/usersguide/images/compressed-select-cache-zlib.png000066400000000000000000001251561416254111300270730ustar00rootroot00000000000000‰PNG  IHDR@°AàÚ²sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwXS×ðo ì2EA– p  ¨µîjQê¬G«¨UœUk­­Öݺwkq ¢Õ–¨‚'C– {¯óû#?®\’@ŠÂù<Ï=wÜÜ„7÷¼ç!„€¢(Š¢(ª‘“u(Š¢(Š¢š €(Š¢(ŠjqhDQEQT‹C Š¢(Š¢ZQEQÕâЈ¢(Š¢¨‡@EQEµ84¢(Š¢(ªÅ¡EQEQ- €(Š¢(ŠjqhDQEQT‹C Š¢(Š¢ZQEQÕâЈ¢(Š¢¨‡@EQEµ84¢(Š¢(ªÅ¡EQEQ- €(Š¢(ŠjqhDQEQT‹C Š¢(Š¢ZQEQÕâЈ¢(Š¢¨‡@EQEµ84¢(Š¢(ªÅ¡P3Äçóñþý{YW㣕››‹¤¤$YWC"EEExûö-³œ››‹wïÞI´oii)Þ¾}‹ÜÜÜFªÝ™™™HMMmôóH‹Çã!!!AÖÕh0„¼}ûÅÅŲ®J­jºÿž>}Šääd¡òììl¤¤¤4Eõd*-- õ:FjjªÈkHIŽ@™gÏžaúôéèÞ½;ºté‚¡C‡bݺuxóæÄÇÀìÙ³¤>………8uêrrrXå+W®ÄW_}Õ çhHÞÞÞØºu+³|éÒ%VGE¯^½š¸fusïÞ=˜››£¢¢ðË/¿ÀÕÕU¢}çÌ™ƒ¡C‡¢´´Th]HHæÏŸ/vßôôt|ûí·øüóÏaee…}ûöÕx® 6ÀËËK¢z‰ÐÐÐz£ºÄÄDtî܇nÐãÊJ^^ÌÍÍñßÿÕi‡~ýú!$$¤k&Ìß߃fkgΜA‡àää„àâÅ‹èׯŠŠŠ{öìÁСCëuÞ3gΠ_¿~(//¯×q“V®\Y¯cDEE¡}ûö¸yófÕªå¡ÐGäÞ½{ppp@rr2&OžŒyóæÁÊÊ ?ýôöìÙ#“:ñù||ñÅHLLd•›˜˜ÀÔÔT&uª‰…… ™åY³fáÞ½{2¬‘l\½zÇŽÃ¥K— ««Ë”ÿ÷ßؾ};¦OŸŽ°°0‘ûFFF¢[·nˆˆˆÀ¨Q£°råJ˜››7zOœ8uëÖ5è1°ÿ~Ì™3Gèn‰¸\.lll ¥¥Õ¨ç ÁÁƒqéÒ%´nÝš)_½z5¾úê+äææâÁƒÐÑÑ ääîO‘®®.lllÀáp옣Ï?ÿkÖ¬Á¤I“ŸŸ/ëê|’¸²®õÁ–-[бcG³>¼ß}÷âââXÛ#::<]ºt‰‰IÇ®¨¨@LL Þ¾} KKKØØØm“ššŠ˜˜hjj¢sçÎPRRbš[’““¡¦¦EEEÃËË eee¬}•”” ®®Ž@YY]»v:GQQ"""ŸŸgggäçç£U«VPUUY襤$hjjBSS“Ù?33FFFÌ6 hÕªÔÔÔ0gÎ(++3u*//Çcž™™™±ŽÿòåKÄÇÇÃÍÍ \ní‡ÌÌL<~ü°±±a‚­‚‚¼ÿ¦¦¦xýú5âââààà@LL x<úôéÃú²/--Ell,’““ÁårakkËzmuõí·ßbáÂ…èС«<22QQQÐÑѹ_EE&OžŒQ£Fa÷îÝRŸ·¢¢=BZZœœœ˜àëýû÷¨¨¨`®G%>Ÿ²²2¨©©!;;›Õä׺uk¨««<‰ŒŠŠBvv6ìííYA. x²ñäÉp8ØÙÙAOOY7~üxüüóÏØ¼ys¯©¼¼‰‰‰066†¢¢"222Àáp˜k•——‡¨¨( [·n¬?앯=.. 077ºö/_¾Dll,:tè+++ÖºøøxèééAUU¹¹¹(**b^C~~>ÂÃÃQVV†Ž;Öxý+•””àÑ£Gàóù055e>ïÚÚÚ`®_ZZ …ö722‚’’Á=úèÑ#¤¦¦¢cÇŽBŸ!QV¬XùóçÃÚÚš¹6aaaxöì\]]‘˜˜===8;;£}ûöPTT¬ñxqqqˆ‹‹ƒ‰‰ ìííkܶW¯^°¶¶f>g™™™(..†¡¡¡ØÏ¡8„ÄÅÅ!>>¦¦¦Ìë€7oÞ >>ÅÅÅh×®lmm…ö/**ÂÓ§O‘ ;;;¡û¶´´QQQ——G·nÝ„ö/++Ó'O””;;;´oßžµ~ñâÅ8räöîÝ‹¯¿þºÖ×CUC¨†§§'éÑ£G­ÛýóÏ?ÄÜÜœèêê+++ÂårÉÎ;™õ3fÌ cÇŽe–ãããI¯^½ˆŠŠ éÚµ+áp8dæÌ™ÌúŠŠ ²hÑ""''Gôõõ‰ªª*ÑÕÕ%ÏŸ?'†††ÑÐÐ ZZZ¤wïÞ„BfÏžMFŒÁ£OŸ>ÄÃÃXZZ---Âår‰³³3ÉËËc¶ùï¿ÿˆ™™‘——'–––DCCƒÈÉÉ‘‹/Š}­nnndîܹÌòæÍ›‰††),,$„’––FÈÈHB!½{÷&+W®$„2xð`Âápˆªª*ÑÒÒ"ZZZ¤´´”ìÚµ‹´jÕŠŒ;–(** bhhHBBBj¼î ªªª¤}ûö¤M›6„Ãá;vB9uêQPP _~ù%QTT$êêêDQQ‘;vŒ 0€())UUUbffF>|ÈsÖ¬YDAAXZZ333ÂårÉwß}Ǭ¿qã@ÊËË !„‘öíÛ×Xχ‡CÞ¼y#v›+VGGG¡òsçÎ%%%’““Sã9ª[¼x1±°° ݺu#ªªªDNNŽèêê’ððpB!Û·o':::¤  €µŸ $¿þú+QRR"\.—y¯Ž9B!äöíÛÄØØ˜èëë“öíÛeeef!„lÛ¶(++KKKbhhHäååYë !äôéÓÌû/Nbb"@öîÝKºwïNOOOB!—/_&­Zµ"&&&¤mÛ¶D]]\ºt‰Ù·ò3€´k׎ÈÉÉwwwB!EEEdâĉiß¾=@¼¼¼H~~>³¿‚‚Ù°aéß¿?áp8¤[·n„BîܹC ™{DSS“ ýõ—Ø×ñèÑ#bjjJZµjE¬­­‰¼¼<4h!„²²2€¹×ÇŒÃ\o---¢ªªÊ:~tt4±±±!ZZZÄÎÎŽÈÉÉ‘ 6ˆ=7!„<~ü˜ /_¾dʲ²²ˆºº:@455‰––9qâ9{ö,QWWg¶ûî»ïˆƒƒ³œ™™IFŒA¸\.qpp òòòdðàÁ¤¤¤DìùýõWÒªU+fyéÒ¥ÄÒÒ’xxx¢¢¢BÌÌÌȃj|ïÞ½#®®®iÛ¶-‘““#½zõ"„>'ˆ‰‰ ±±±!\.— 2„U¯;w À|_¬ZµŠBÈÀ‰««+±±±!ššš„Ëå’nݺ‘ŒŒ fÿgÏž‘.]ºuuuÒ©S'ÂápÈòåË…ê¹yófÒ±cÇ_ % €>"¿ÿþ;@È÷ßO®_¿Îú@BHnn.111!kÖ¬a>lçÎ#ŠŠŠ$>>ž"yxxñãÇ“ììlB!qqq„Ëå’àà`B!ûöí#ÊÊÊäÂ… ¤¬¬Œ”••‘ƒ’çÏŸ3ž}úôôtBˆàÙ½{·Øó‹ €455É/¿üBJJJHqq1quu%‹-{ B2déÞ½;óƒ…Ïç3Áß»wïHBB³í›7oˆºº:9}ú4!„ŒŒ ¢§§G¦M›ÆÜßOž}ûö‡Ã\\\pýúu@DD^¾|‰%K–0¹C­[·®õ1tÿþýñøñcdgg#)) X´hΜ9 ƒ»»»Ô9úúú˜7o”••!''‡Ñ£G3uENN***xôèÓ{ÃÖÖd¶QTTÄ7ß|ÃsÀ€022bšåпܹs‡ÙÇÎÎÆîÝ» B^¿~-Õë©*>>^¨ùER (//Ç!Cpûömܽ{ÚÚÚ>>µîçïשּׁ,9rðçŸâÍ›7ðôôD||ŸÙŸÃáÝ7šššB?ö¸\.ôôô$º·)6}¤8,--1þ|¼~ý—/_ ø¨¦¦†¨¨(‰ŽSù‹ñ·ß~C»víÄnóâÅ‹Sý¹´tttPTT„”””Z¶«âr¹puuŽ{÷†U«VÁÖÖíڵÕ+W‰Ÿþ¹Që^i„ =z4"##q÷î]lݺøþûï%>FÕ?rÙÙÙð÷÷ÇÁƒ1yòd‚?;vì¨W=544êüehii‰³gϲÊ ¬¬,õ¸%111Àü1ìØ±#ÜÝݱgϘ˜˜ [·nèÑ£kŸê¾>ŒŒŒj¼ß§OŸooo„‡‡#44ÈÏÏg~­WâñxÐÖÖ–ê5TÖÁÖÖVl¯¹èèhÀ‹/DÞÛ­[·fþ VŠ‹‹ƒŽŽüˆ¢ªªŠŒŒ 3 É’èÔ©þúë/<}úÿüó‚‚‚0tèPæý¨.""sæÌaº¨WÒ×ׇœœ"##%ê P©>÷_U•Ià{÷îE¿~ýê}}ú4>|ˆ?ÿüIII@ëÖ­™.´}úôÁ¤I“0tèPÌ™3III8uê¦L™Âä°ˆóàÁLŸ>fff8þdûÖ­[àp8R7ÁÀ Aƒ0qâD¸¹¹aòäÉ044ÄãÇqåÊ<}úfffؾ};¦L™‚?ÿücÆŒÁãÇñðáC¼xñëׯGÏž=acc///œ?iiiøå—_jÿüs¸¹¹aÀ€àñx E¯^½jœ³>¸\.vî܉‰'",, žžžxöìþúë/$$$`Ú´iؼy3^¼x‡ƒÖRGGGøúúÂËË C† AÏž=qíÚ5XZZ29V5ÑÕÕŶmÛ0gÎüý÷ßèÚµ+^¿~k×®aݺu¬¦è?þøíÚµƒ……Ec\ŠfM~µ¨äJ&f̘N:ANNéééPWWǨQ£pèÐ!Ö£uWWW 4ˆIjÖÓÓ 0pà@ÈË˃ÃáÀÚÚš3ÄÖÖ'Nd UTTàíí/¿üJJJÐÔÔÄŒ3 ¢¢‚wïÞAQQþþþpssƒœœ]]]<~üL³…••:uê@дãààÀúr8ØÚÚ29 ={ö„œœœ0{öl`Æ X´hQ9úúúàr¹øòË/™/ÔÊ1b<==Yù.•õ¨Ìwptt„¢¢¢ ­­€ÃáÀÄÄDh4èÊDDQ´µµáè舴´4ÄÆÆBYY›6mbþ r8èëë³’o9ŒY‡ÃA»v혆‘#GBQQ111°··Çž={ ¡¡~ýúASS“‡¦_¿~LóY›6m„šŽª266ÆáÇ¡¨¨(ô?..|>ptt—Ë…††ëx#GŽ„¡¡!"##QQQ… bÅŠbÏWÉÑÑšššˆŠŠBÇŽñÓO?ÁÙÙ™µœœŽ;†‰'bÀ€¬ujjj;v,Áçóáêê CCC|öÙgpssûwïðâÅ cÉ’%pqq‘‘:wîŒÔÔTÄÅÅACCÛ·oƒjÅŠ°²²‚··w¯AII îîîÐÐÐ`•5 ŽŽŽHLLÄ«W¯`jjŠU«VÁÞÞ]ºtÁðáÃQZZŠ„„XZZbíÚµÐ×ׇ®®.|||ŸŸØØX¸¸¸àСCB°ÜÜÜXã‚1ŒTTT’’‚Î;cß¾}PWWG¿~ýÄfèî¼<¼|ùùùùðööÆüùó!''ÇÜCýúõƒŽŽ²²²`oo ¨««3ÿ:wî CCCôèÑ#FŒ`î{øùùaĈb›ÅŒqôèQÈËË£OŸ>¬uêêêpwwgû£¯¯Ï< «þù011ÁôéÓQPP€ØØXB0lذsïªv¨daa!4ÎN›6mj ê;vìˆQ£F¡¬¬ŒÛiݺu044D¯^½`ee…G¡uëÖøá‡бcGtïÞéä1lØ0ØÛÛ#//iiipssÃÂ… ™«®]» %‹WýÞîÖ­ÆŽË$Ûkiiaúôéðôô„‚‚³Ïܹs1dÈ¡¿”dÒ÷Œj‘’’’˜îÜ„²gÏ¢§§Ç+ˆj?ÿü3ÑÖÖfu9þTv}ONNn²sÞ¿ŸÈÉɱÆ_¢×álj¦¦f“¾Ï-ÑñãÇ‰ŠŠ ÓÕž’‡Êmø|>äääÄŽ’KÕÏÔ©SqéÒ%XXX 77iii8vìFŒ!ëª5;4h 88˜õ‹Q–† MMMœ8q¢IΗ’’L˜0ß}÷]“œ“äé 2ÅÅŸvíZ­#=SÒ‹‰‰Aß¾}±qãFÌš5KÖÕù$µ˜hÁ‚€íÛ·³Ê+**°aÃüüóÏÈË˃½½}½ò:(ñx<ÂÃÑ––###899¡U«V²®V³Åãñpÿþ}8;;7H·äú"„à÷߇ƒƒÚ¶mÛ$ç|üø1Þ¼yƒ¡C‡ÖškF5¬ÊF''§z Ë@‰öï¿ÿ"33Ÿþ¹¬«òÉjöЕ+W0{öl$%%Áßß_(š={6ž>***:t(üýýñÏ?ÿ`üøñ¸~ý:úôéƒS§NáÔ©S°±±iôº¤¦¦B[[[hø~I½}ûVâa×%Ù¶¿áÛ6 IDAT¦mÄ­U^½¬¬¬ ©©©¬ný/_¾9ÚjC+))Ç«s>Œ4ïQNNÊÊÊ ««+v›†ºÆ•C&T1¶úvMuÅÕQRÒ¾Gµ+##\.šššBëĽGYYYÀYWÔ}[ý܉‰‰Ð××o’&ôú\ci÷—å÷Eõ÷¨  R&_W¢îiHs%ùni¬k\½¬¸¸>DjjªDuÿ¤ÉªûYSó÷÷'þþþ¬²7’Q£F±ÊÆG!„’ÀÀÀ&©ßáÇɛ7o꼿4õ”dÛš¶·NTyõ²ÌÌL²mÛ6VÙСCk­OCHII!{÷î­óþÒ¼GaaaäêÕ«5nÓP×øäÉ“äÙ³g5n×T×XÔ¹¥!í{TÛ¹®^½ÊÌÂ^¸÷(44”„††²ÊDÝ·ÕÏ=sæLSkB}¿—>•ï‹êïÑ?ÿüCæÏŸ_k}‚¨û@Ò\cI¾[ëW/KII!5Ö¥¹hÑЙ3gˆ «ÌÝݹš2âóù¤¸¸¸ÎûK3„$ÛÖ´¸u¢Ê«—•••‘ôôtVYS]ãÒÒRÂãñ꼿4ïQ~~>ÉÉÉ©q›†ºÆ™™™¤¨¨¨Æíšê‹:·4¤}j;WNNÉÏϹNÜ{”››KrssYe¢îÛêçÞ»wo“ÇRßó|*ßÕߣ¸¸7dË–Ãäßk­R½‰º¤!Í5–以±®qõ²–5û&°¼¼<ðx<äää<êÓÓÓƒšš†Ž€€lÞ¼Ó¦Mõk׎   &¯gMM%’¨m†fi·­iqëD•W/“——í¶©p¹Üzu»—æ=’dĆºÆ¢ÑKs?4´úœ[Ú÷¨¶sUѹ*qïQåH½U‰ºo?Õk,íþMñ}QQ¤¥ÉɆˆˆÞ½’“ädÕÿÿ/(ãóB.(.äq6KQ÷4¤¹Æ’|·4ÖwrmÇnΚ}tÿþ}lÚ´‰Yž2e V­Z(++#88K–,ÁîݻѾ}{3S7PMc̘1²®B³G¯qãëß¿?‘[>ÿCSÌT j’“ÁOµiÊÄh  /ÊÊû5rå©f­Ù@ŸþyÙìÖÖÖ¸|ùrÖˆªîï¿ÿF—.]d]f^ãÆ ]]Ý:wdøT=yDE‰lRROjN€§,œL ª~š}ÔØÂÃÃ#ëj|Ò”••%š!™ª;q×XQQ±Ö B)Éxzzʺ MêÕ+`Å àÌA³TÓ0 ˜:'9èÞ½©ÎK5G4ª§'Nàï¿ÿ†¬«BQR)))App0 €(©¤¥k×?ÿ ”–6Üq[µŒ?ükÓ†½¼~=ðÛo¶OIi¸sS- €€··73Ò4E}*²²²ê5– ÅÆçó¡¥¥.·y~­ææ?ülÝ äçK¾Ÿ††ø ¦²ÌȨmø$Á0V%òè"9¹î¯…¢QE5ˆÐÐP¸»»7» ~KJ€Ÿ~äÑ'@T}5òCKŠ¢¨–!44ÙÙÙ²®FÄÅ^^@¢ƒ`ÎA×÷µk›>øªæÝ èVQÑôõ šO÷§ EQÔGäSÌJIÖ¬ÊÊ„×s8Àøñ‚h ‹¦¯_U€šš1òó9@åå@z:ÐBgq }DQÕÂdg 1´´öíü|ö œ<)ûà§Rõ‘Ÿi3U4¢(Šj|>e¢"‰Hq1ðã‚€æ»ï€‚ám›7ë×nÝš¾Ž512*Á,ÓDhª>hD1bcc%”Ç'OžÈ¨VÍOEEîܹƒwïÞɺ*Tú˜s€**€£G++`ñbÁ¥Õuè ˜Ö"<\0¦ÏÇHGçC@ŸQõC ŠáååÌ›7U~ëÖ-¸¹¹É¨VÍOqq1ÜÝÝqþüyYW…j@žžžå ˆW®]»S¦ Âë ½{˜A"4‡ÓäU”˜…Ňq€QõC“ eìî]àâÅÆ?®.°jUíÛ5 ÇÇ’%KЩS§Æ¯X ¤¢¢‚””hjjʺ*T3,] Ü»'z½¦&°d °p! ªÚ´u««ê9@´ ŒªÉØÃ‡ÀŽSSÉ `ÅŠ¸|ù²Øíž `Ô¨Qxùò%22I–ß|ó V­Z,Y²)))èÛ·/Š‹‹™×sñâELš4 S§NEqq1Ž? ‚ŸŸúõ뇅 ¢C‡øñÇ™sž8qoþ?¿@EEÜÝÝqâÄ Œ7]»vÅÒ¥K±hÑ"fû¿þú Ë–-êU«Ð³gOôèÑS¦LÁ•+W${S©F'ë wï€3ór‰ ~ää€É“cþlÝúé? ¦ÆÎ¢O€¨z!”X$00°ÆmüýýɶmÛê|ŽmÛ̺ӸÿLMk¯KçÎÉÎ; !„Œ?ž¸ººB9þ<ÑÕÕe¶1béÝ»7kßÏ>ûŒxxxˆ=öÍ›7 Ê*çóù¤°°hkk“Õ«W3åEEEDYY™lÚ´‰BÈÝ»w òòåKf›}ûöEEEÂçó™²µk×’>}ú0Ë“'O& `Ó××—´k׎Y644$ÞÞÞ¤°°){ñâ‘——'þù'S–““CÉÅ‹Iyy9QRR"û÷ïg»¸¸˜BȘ1cÈðáÃE®#„MMMrúôiB!§OŸ&È›7o˜õG%\.—$$$B™6méÛ·/)//g½¶É“'“ºÊÌÌ$ZZZuÞŸú8df²d !**â?ÿÆòø±¬kZ11ì×eb"ë5?)))ÄÀÀ@ÖÕhôÙ¹Œ¹¹Û¶5þy¤M7Y·nìììpíÚ5¡uááá˜1c«ÌÃÃ6l{¼ÿýšššèÓ§«\WW?FVV<<<˜r%%%ôîÝáááBÛWÒ××*kÓ¦ ^¼xÁÚG½ÚtÔ...Ø·oòó󡦦èÛ·/”••™mþûï?ÈÉÉ!((AAAL¹ªª*^¼x999 4K–,ATTzõê…aÆA[[0xð`øùùaôèÑèß¿?>ûì3XYY‰¼6ááá077‡™™Sæáá²²2<|ømÛ¶hkkC®ÊŒ“¦¦¦"ߪe(*vî6m23EoÓ»7°y3Píc÷ɪ>Xjª ú˜·© €dÌÁAðïcÓ¡CLŸ>Ë—/ÇòåËYërssa``À*ÓÓÓCAAÊÊÊDæ@äååA___äºÜÜ\yÌÊæ(QäDL?ÍårAÿ HB9Iâäää@YY&L`•O˜0¶¶¶€ .àСC8þ]tðÓ¶-pèðèQó ~€ÇãA[û>«Œ&BSuE J,###ÌŸ?_h¼š¯¾ú !!!8~ü8 qêÔ)cΜ9b5jÔ(bþüùxðà***pýúuüøãhݺ5Ƈ½{÷âþýûÈÌÌÄêÕ«ÁçóáççWïב€çÏŸ#33»víÂÙ³g±xñâ÷éÕ«œáããÃ4©%$$`ëÖ­ AFF¦L™‚ØØXBPPP>ŸÏ4ñ-X°·nÝBII JJJšš [[[Vs]¥I“&AEEHIIATT¾ÿþ{8;;3=ʨ_SŒ4ož`zŠêtu-[ ÎS§òòZ ™166†¥åVM„¦êŠ@T–.]ÊäµTš:u*æÏŸ)S¦@KK “&MÂôéÓk €455ñÛo¿!>>Ý»w‡²²2¼½½™§0AAA011Aß¾}a``€;wâ×_m±ˆRRRлwoèêêbÉ’%ðóóÃW_}Uã>rrr8þ< `ggEEE˜ššâðáÃhÕª”••ñúõkØØØ@]]ºººÈËËÃ’%KÊÊÊ6lÔÔÔ ¡¡;wb÷îÝ"›íôõõqéÒ%\ºt íÚµƒƒƒŠ‹‹qêÔ)¦«>Eýò pà»LUX¶ xýøúk J[³Eç£ ‡Ð$±V¯^Íú_” ÀÌÌ ,hšJ}DŠŠŠðêÕ+˜››CUŠ‘Ô²³³‘›› ccc¡€ ##éééèСäàgì”)S³gÏ"99FFFPPPê%%%ˆGëÖ­¡££ÃZ———‡wïÞA[[[(§´´IIIàp8011©57„‚W¯^A]]†M0ÅuVVÌÌÌ••Õèçj 3(&pvòó?”µoüù'ЦMƒŸî£URR‚ùóó°o߇'©ë× &v¥Fjj*ìí푚š*ëª4:šMÕ™²²2:vì(õ~ZZZÐÒÒ¹NWWWd3Q}q¹\´k×®Nû***¢C‡"ש««39AÕ)((ÀÜÜ\âóp8XZZÖ©Ž”ì…††ÂÝݽÁ›ÁòóSTT ~””€³g[Vðr€ #Qu: ÚFÕm£šµvíÚI„PT]5VÐìÙ‚'@UmÛöñÍÔÞŒ1`;ˆ6QuEŸQÍÚÚµke]Šª³¹?UM˜ ŠZ*:ÕPè Š¢¨ÀçóQVVÖ`Ç‹ŽôúªÊÚøùç;Å'§¤¤ªªì±Áè ª®hDI$33iiib×'%%Õ8hŸ(%%%xûö-***$Ú¾¢¢oß¾EII‰T硨¦Ðãåæ ò~ŠŠ>”©¨ò~ª lÞ¢ðx<¼}˨r4hŠ’ €(‰¬]»“'Of–}}}Y½ã\\\pøða©ŽùèÑ#˜››#SÜ8þÕäääÀÜÜ>Z÷÷ß MÏAQM©!s€f̪Íè‚  ÁD§-™±±1¼½G°ºû—”|¾ìêD}ºhU'fffhݺµ¬«Gáîݻعs'3‡j‚‚€3gØe“' 8¤ŒŒ€7o>,§¤Á×õ‰¡Œåç £Pü|W …+Ç…‰¦‰ØõÙÙÙ"ŸÄèèèˆì²>}út±ózEFFBII NNN¹SRR‚äädCQQ‘)/++CRRŒ«Ï‚øÑÑÑøï¿ÿ ¥¥…œœ‰ÎEQ¡!ÆŠŒ-b—uêìÙSÏÊ5%%%ÈË˃‘‘.+JN¦OÇ(éÑHÆ=<„…×6úyLµLñvÁ[±ë8€uëÖ1ËÈÍÍÅÆ ´ý´iÓ`nnŽ]»v1eW®\ÁêÕ«QVV†ÜÜ\ôéÓׯ_—hÄòòr888`ݺu˜;w.S~þüyÌš5 ÉbºzøøøÀÇÇ[¶lÁÏ-9;”’¹úŽ”•Œ'hÒ©¤®.Èû‘bœÑfÇã!22FF´+>·nÝj Q)êc¶u+pù2»Ì×ðö–M}>vt, ª!4ë&°ÈÈHìØ±.\À¬Y³DnS^^ŽqãÆáÞ½{0`@×ÐTÒ„©–i£Ÿ§¦üŸª=z„™3gâÈ‘#ušæ¢ª®]»â?þx{[[[¸ººâÀprrÂáÇѳgOt¦ûÔ' ®9@aa@õVf`ÇŽ¬\3Q™dlÌž.‡>¢ê¢Y@&&&˜>}: !nÎ×¹sç¢S§N2šÃ4Ls˜&“sW—™™‰Ñ£Gcîܹðôô¬÷ñbbbÐFÊÉŠüüüàëë‹~ø‡Æúõëë]Šj uÉâóñãªã'ji ò~””¡’Ÿ8q9@ô Uͺ ÌÐÐýúõƒ‰‰è§[¶lAzz:6oÞÜÄ5ûøTTTà‹/¾€……6lØPïãâÎ;èׯŸTû;ÊÊʘ9s&òòòàååUïºPTS6ˆÀÇHLd—<XX4påš šD5¤fÕäìÙ³8þ<~ýõ×Ç9räV¯^ÍúWUddd#×´ilß¾ׯ_‡¶¶6æÌ™???øùùá—êÕrŒ€€,Z´íÚµCÛ¶máïï/U=1uêTœ>}S¦LrÕÏD¸|ù2 ‡€€úÔH Õ§n¨~ÓåÆ[Þ¸¸z•½¾GÕ;öã¨ßǼü!H°\},õûÔ–+{ðöë×?üðJKKÑpˆ¸¶¡fdÁ‚ +Ã××€;vÀÑÑÞÞÞŸôôtÙVLFÌ3¢ 4ˆµì]­{Je8qâD‰Ïill,2¸lÛ¶­Èreee¡ò!C†`È!Ÿ“¢‹¤9@iiÀ_°ƒ]]àôiüÔ¦2hĈ02âã?¬KI¡%­\¹’µ‰1cư滢(Š’”$**]Û«æ¬p8ÀÑ£€iãwýäUæÕ³ã*ÕÂ4ë sçÎÁÌÌ ‡¡C‡`ff†ßÿ]ÖÕ¢(ª…Z³¸}›]¶d 0l˜lêó)«žM{‚QÒjÖO€<==%îÎ}óæÍF® EQÍYm9@!!@õüü¾}…Ë(ñªæU ‘ö£¤Õ¬ŸQE5•ÐÐPdgg‹\—œ Lœ(h«¤§œ:H3MýÇÃýû÷ÐÑ ©ú£%‘uëÖ±ò£öïß³gÏ2Ë...8räˆTÇ|ôè‘T½rrr`ff†¨¨(¦,** ßÿ=æÌ™ƒ… âÌ™3(¯š]JQMDÜ8@eeÀ„ Àû÷Êää€ãÇ…›q¨šUÍ¢cQõE J"HKKc–oܸ¿þú‹YNJJBNNŽTÇ,))A||¼ÄKEEâããQ\\ ÈÍÍ…ƒƒ.\¸BÒÓÓ1uêTšÈN}T¾ýVн½zÙÀ²©OsA›À¨ú¢_©:9wîœØuÅÅÅàr¹——oÔ:())!""ŽŽŽLÙÁƒ1sæLìÙ³šššz~ŠªJTÐßÏÞ® 0°‰+×LTÍ¢IÐT}Ñ'@²vè`fÖøÿúô©±AAA033ú·k×.‘ÛO›6MhLžçÏŸcÀ€ÐÐЀ††ÄÎÁV]VV¬¬¬ðÛo¿±Êïß¿: ##ChEEEVð °°°€†††D票†R=(!øòKÁ”•ŒŒ€'M`”ôjÊ¢O€(iÑ'@²–“ÃÌBF<==áââÂ,Ÿ³Ìºu€««lêÓq8€¡!{2Ù”Q’£b)–ŒŒ Œ=þþþ[uVÆ:pvvÆëׯ%ÞÞÃÃ8tèÁS(###|öÙgµîûÏ?ÿ )) C‡ÅÀÁãñê\oŠª >Ÿ²²2\¸ìØÁ^7d ›z5'%%%¬æpÚ FÕ}$kÓ¦£F5þy$l¤¢¢_|ñ¬¬¬dFõŒŒ ©rq8|}}±cǬZµ ‡¬Y³¤jÎ?~<.\ˆððp:GÕ¤BCCanîŽiÓØ]áÛ¶Ž<± ê§ê\` šªÉš¦¦àßG`ùòåxñâ"##!×Yš7oÞ„———TûL™2+V¬À–-[+W®ˆÝ¶¸¸JJJ¬²¸¸8`šó(ª© î‰Þ½ªc!*(gεÌJI¨j@ŸQõC péÒ%lÞ¼¸qãSnaa'''‰ŽñÇÀÊÊ %%%Ø¿?²³³ñõ×_KUV­ZÁËË +V¬€——ôkÈýí·ß°}ûvLš4 VVVxùò%6nÜwwwtëÖMªóRT}-XhDtwssCXX˜ò¡C‡ÂÉÉ –––PPP`Ê;wî £*?¿zöì MMM,[¶ ‰‰‰°··GXXÚ¶m+öœpsscæÍ›‡ÄÄDÌŸ?ŸUÎåráææÆ$9;;;ÃÞÞûöíCBBôôô0f̬^½šö£šÔÉ“ÀO?ñh¡òkuÔ(`áB™V«Ù©:@ŸQõÃ!’ÔÒUŽsS}¼›ª,X333,X° i*EQ $++Kª©H(ÑâãÎÜÜsÜ´Bûö‚§AZZ²®]󒜜ÌÊ †ý°ÞÕ¸{WF•k&RSSaooÔÔTYW¥ÑÑ'@EQõ0s&› ‚q€””y?4øixÕs€hU´EI‚@EQu0kû­’R(~ü1›Ž÷ÓˆªÎæT«>â} DIŠ@”DNž<‰­[·2Ëÿý÷bbb˜åÅ‹ãæÍ›RóÍ›7˜2e òóó%Ú¾  S¦L9ºtii)>|ˆ“'OâáÇRÕƒ¢¤uô(põ*»lófOtïö9• IDATNüiLÕs€eìmhDIŠ@”DÂÃÃR%ÙÁ××Ìò¹sçX‘$ø|>Ž=Š¢¢"‰¶/))ÁÑ£Gñþý{Vù±cÇ ££ƒþýû#00[¶l‘ª%”áîí..À¼y²©OKW=ˆ&BS’¢½À¨:¹~ý:äååe] ìÙ³+V¬ÀñãÇ1räH‚§AÕXüü€ÌÌË**À¡C@f&ZZZàJ0í U7ÕÇh"4Uwô“*c722p"=½ÑÏÓZA?XXˆ]åÊœ;wN¨|̘1BœÁS}}}Lœ8‘)+**Âúõë%%%øûûchÕA:j••…… ÂßßöööLù«W¯°~ýzlݺUhzŽüü|¬Y³›7of‚B+RTC9qøí7vÙºu€•pî\(ÜÝÝÑŠÎ{ÑhªÏЮðTÝÑHÆb p´ œ2UV®1200`ÑÑÑ8zô(Æ/rû[·nÁÜÜœ­\¹#GŽÄðáÃqòäIŒ1áááè.AŸ`mmmÄÅÅaçÎÌlð°ÿ~ÄÄÄ@WWWhÀ¾dddÀÕÕǃ­­-†úQ<¢š—´4 ÚàäèÙóCs˜§§gÓWª…•DŸQuEs€(€““,X€ ÀÇÇwîÜÁ²eË0xð`‰±jÕ*œ9s_ý5þûï?ØØØ`÷îÝïïçç‡3gÎ çÿ]kÊÊÊpìØ1øùù‰Ü>>>ƒFpp0ÂÃÃáããwwwK|^Š’ÄW_|þ‡e%%AÓWÌLÕM‚¦êŠ~t)–òòrL˜0666X·nTûjhh°–{öì)Ub´——”””pòäI‚ÉU‹ŠŠ0a‘ۧ§§ÃÔÔ/^¼Àùóçqþüyüû￈ˆˆ`=E¢¨ú:s¸p]¶z5`kûa™Ï磬¬¬IëÕÒT IÐTÝÑ&0ûLG‡mlý<ê6 -_¾¯_¿Fdd¤PδTUU¥úƒ ¬¬Œ)S¦àÀðõõÅÁƒñå—_BEEEäöÆÆÆÈÍÍe%ÚØØÀÎΪOËMQuÄãAhpCGGà›oØe¡¡4¨±‰Ê¢M`T]ÑHÆìÔÔ`§¦&ëjΞ=‹   „……AGG§ÞÇ»uë–Dù?UùúúbÛ¶m¸zõ*®^½Šèèh±ÛZYY!-- iii0øÿhhEEEˆ‹‹ÃðáÃëUwŠª4w.PuäEEàða úo šÔøDåÞ‹òrÁrn.—¨«Ë ‚Ô'…6Q€'Ož`êÔ©X¿~=ôôôššŠÔÔT&G±±±(((ŸÏÇ–-[ðüùsÌ™3GªzXYYÁÝÝ>>>èÝ»7ìììÄnÛ¿tïÞ3gÎD^^222°nÝ:”••ÁÛÛ[ªóR”(/§O³ËV®:u’M}(arr€¾>»Œ>¢$A € ·U~~>.\###æßÂê#¾ÕàÆÐÒÒ‚¡¡!V¯^ýû÷£gÏžR×ÅÏÏ|>_lòs%999?~)))ÐÑÑ¡¡!Ž9‚3gÎÀÊÊJêóRTUÀìÙì2 @ôö4¨ñ‰Êh"4U7´ Œìܹ;wî»~Û¶m¬å+W®°–ãããHNNF‡ ¤¤Tã9A*÷òòY®­­-Tnmmˆˆ$''#??õÎ]¢(ð÷t}¯¤  hú7Î!Íj|¢r€šMÕ €¨¥««Ë¥µ©Wÿ HQõpå ðë¯ì²eË€®]ÅïCs€Ÿ¨ A9{™>¢$A*SEU‘•øú²Ë:w¾ýV6õ¡jGŸQuA Š¢¨*-bÿårM_µÍ°Bs€Ÿ¸ Úžª QEýßµk‚`§ªo¾$Í!44ÙÙÙS1 € èþýûBå´ Œª‹…‡‡ãÚµk¬²¢¢"?~þþþð÷÷ÇÍ›7éLâbDDD $$DìúsçÎáÙ³gR“ÏçãÈÿØ»ïø&Ëýÿã¯$Ý›¦,A(Z@6ĉGPÑÂ7¨¸Šƒáèñˆ€‚âÀƒ"žªÈ£.dXQ @Ë,ÐÒ½Û´I~Üt¤‹´ÍÝ;Éýy>y´×4¹¼lÃ'×ý¾¯+>Þám+Ìf3ñññdddÔº/''G¶¿Í’—÷Ýg¬W/xñEÇ~>66VÐ*«/$§ÀDSx|”’’ÂÒ¥K¹ýöÛk@ÿý7óçϧC‡téÒ…'žx‚iÓ¦iÔS×¶|ùr,XPÙ¾ôÒK?~|e{Ú´i Hu9rä“'O¦  À¡Ç1yòd’““+mܸ‘^½zN`` 7Üp§äÝO4ÁSOÁ‰Um“I™ :ÇÅŒÂÈ h ¿ ,##ƒ'NÔyeÒ%—\ÂüQÙ2dW_}5óæÍ«wû¡xå•WÒx©Õ}ûö1fÌ}ôQ6nÜHaa!·Þz+÷Üs6lдo½üð,YbìÉ'aà@ÇŸ#33“ÐÐP»­Y„s™Íf j½Ÿ·i£,ˆhµ*íÜ\(*‚€ :)܆Çÿ¥ÆÄÄCNNN­û¼k¤srr0›Í-úV¸¯üùª¿Ž)ÈDTlT½÷ïÙ³§Îý³úõëÇÅ_\ûùL&Luì/¶e˾ÿþ{|}}¹í¶Ûèܹ³CýËËËcÍš5Œ=š¶mÛVÏÉÉaíÚµŒ7®ÖÏ|ðÁ3gΜʾ,^¼˜þýû³gÏž:û-DM0eŠý±=ॗ÷<²úê[Èd‚¨(ûu›NŸ†nÝZ¸ƒÂ­xü)0G™ÍfæÍ›Ç”)Sì £øøxâââìnÕ%$$4ëu³¿Ë&qr¢ê·ä§’ìÇÁƒY»vmåíÃ?dòäÉlß¾½ÎÇ¿ñÆ,«±PÊo¼Áĉùý÷ßyùå—8p 'Ožth‚ƒƒù÷¿ÿÍÛo¿mw¼bükî4pòäIúõëgWˆ]rÉ%xyyqàÀ‡^Wïj^µTó÷[ígŸ…£G+`4‡‚Ÿ_ãž/66¶Öï¯+ü÷yRûý÷ß·+~ªß¯œ«jŸ>­}Ý¥½nÝ:âââ¸ì²Ë˜?¾n²°[]Kîz ©S§°páÂZ÷Ùl6n»í6Nœ8Áwß}Wyú«â—£æ/MÍçíÒ¥Kåó7VÊÂ’žHjÒÏ6†_g?ul[ ‹ÅÂ5×\ƒ¯¯/_}õF£‘'žx‚ýû÷Wæ¨ÆŒC×®]+ßð;wîÌW\Á{g··7iiiôë×Ûn»W_}µÎ×IHH`À€dddÁ¼yóxóÍ79qâDeQÓ§O&NœÈ¬Y³ÈÉÉ¡U«Vlß¾AƒÇ¢E‹Ø»w/­Ïnd³ÙhÓ¦ 3fÌhÔ6z”““C—.]êœÕ‹Ÿ~‚Q£ ú»àÔ©Pcásá®»Ö¯¯j¯\ &h×w•ššJLL ©©©ZwEuºŸ*//çŽ;î -- 6Hö˜>}:ÇŽãÓO?mÔ¶111•³gmÚ´aèСìܹÓ៿ûî»ÉÌÌä믿”+÷8À=÷ÜSçã{ì1ÂÃÃéׯ>ø S¦L¡GdddTî/D}ŠŠàž{싟 .€Ù³›ö|²úê[$-Ïã3@ ÉÉÉaüøñDEE±~ýúsî]¥†€^´½«í¹ØLÞ‘çXÅí¬•+W²xñb¶mÛFXXX³^³k×®$&&:üø¨¨(nºé&>øà®¿þz–.]Ê 7Ü`— ª.<<œ„„>þøc’’’h×®S¦LaðàÁtíÚµY}žoæL¨vA!,]Úôà¬d€ÔW_d1DÑx_mÙ²…>ø r&bÒ¤I<øàƒ 4ˆO?ý”Í›73lØ0®¹æšÊŸ¹çž{¸ãŽ;Z¤áW‡~uËïU—¿þú‹{?üèèèf?߯¿þJ÷îÝõ3<ðW\q‡bÅŠ¬Y³¦ÁÇóðÃW¶—,YBÇŽ¹ôÒK›Ôg¡[·B¸? #F4ý9e/0õÕ·rŸ}[VÃçâñÐ\À¤I“˜4iR届Ùo¼‘Þ½{×ú=Îdgg3nÜ8zè!&4ñÄyõàÜo¿ýÆŽ;ˆoÔsŒ9’îÝ»3~üxÚ´iÃå—_îðÏnÞ¼™éÓ§3wîÜZWø Q¡¸î¾»ê’i€®]aî\íú$šOf€Dcy|Ô¶mÛzO¡tèÐ:´p\ÓܹsINNæ‹/¾àË/¿¬<ËË/¿ìÐs̘1ƒÏ?ÿ³ÙÌüÁí·ßέ·ÞÚè¾<ðÀL:•ùóçc0|lll,éé餦¦rüøq.\È”š×4 QÍóÏÃÁƒUmƒ>ø›÷¼²úê[d5hÑxò—*˜2e ×^{m­ãíξ«<òÈ#VŸ3gŽ]`|åÊ•´oßž?þøƒ'NÃðáÃ|Í=z°iÓ&BCCíŽß{ï½\rÉ%µNc±iÓ&zõêUyì ))‰ž={rÉ%—ЪU+Çÿ£…îlß^û ¯ûîƒFL4ÖK2@êk($!hÑXº¹ ¾)Zâ2x!´¢·ËàKK¡o_¨¾e]§Nð÷ßPÇ2SÂÍ”•)Û–Tÿ­¸XYÏI8N.ƒBg_ü€²ý…?žÁÛ[Y º:™ ‘Háñàµ×ìÝs\}µó^CÖR_Cë¡EãH$„ðhf3Lž KÕ±`Áç¾Î¦M›ÈÍÍuî“ ;lÙ²¥Þû%-CBÐBöòËJΧº÷߇Ùûf“u€Ô×Ð:@Êýöm™ ‘ !„ÇÚ½æÌ±?vçðÏjÓ¡.9&Cf€œà‹/¾àhÕvÒB¸…ÒÒR­» ª²2åÔWõXN»vPÇ~ÈN!멯¡u€@Vƒ#©Í4nÜ8ºté¢u7ÜÚ¯¿þÊСCµî†G«oŒçÔœñ sç*3@Õýç? ÖRQ²úZdH4ެÔGÖB¸žŒ èÜYÙñ½Â-·ÀòåÚõI¨oÇ<¸ª ý¥]Ü‘¬$„nì7싟¨¨Ú›Ÿ Ï#3@¢1¤šÓÃ' ­éiŒssaÑ"ûcÏ> jŸ™’u€Ôw®u€Ú¶Uöv«™©,ƒ D]¤š[»v­Ö]ðxzãwÞQŠ  ðÀê¿®¬¤¾s­äãS»Ð•Y Q)€„æh‰tN/c\XXû*¯©S›¿Ó»#bcc%­²s­rL8N !„Çxÿ}%]!$yD»þˆ–'— GI$4§§|ŠVô0Æ¥¥0¾ý±‡†°°–y}É©ï\  á8)€„æô”OÑŠÆ8>ÞþÓ~@<ñD˽¾d€Ôw® H$' ! Íé%Ÿ¢%Oãòr˜7ÏþØ”)Êåï-EöSŸ# 9&%3@B··|99RÕöñ§ŸÖ®?B;2$%Мò)Zóä1¶Zkoxz×]СCËöC2@ês$$3@ÂQR Íé!Ÿ¢5Oã5k 1±ªm2Áôé-ßÉ©O2@™d/°È^`B¸¾~ýà?ªÚ·ßŸ|¢]„¶JKÁϯªm0(Ǽ½µë“;‘½À„ ¬_o_ü 0c†výÚóõ…ððª¶Í:ø·\4@Bszø¤¡5OãÙ³íÛãÆA¯^ÚôE2@ês$rL8F ¡9OΧ¸ OãÍ›á×_íÍš¥IWɵG2@P;-¨‹¬$4çékÔ¸Oã—_¶o­ä´"ë©Ï‘u€ ö \ &ê"3@B·³clÜhLËÙáZdH8B ¡9Oͧ¸OãšÙŸ#`øpmúRA2@êkjHf€D]¤šóÄ|Š«ñ¤1Þ³Ö­³?öÜsÚô¥:É©ÏÑ „ …#$$4ç‰ùWãIcüÊ+Ê¥Í €«®Ò®?$¤>G3@r L8Bf€„nãàAøüsûc’ý5É)0á)€„æ<-ŸâŠo½õàâ‹/æÑGÅ××@N ¡¡·Þ‚ǯj)‹!†‡k×'á>ŠŠ 0°ªm2Ai©,p.z:æñëÅÄÄSïýÁÁÁÌ’Í„„p)f3¼öšý±”âG8. BB /Oi[,pæLí™!¡_ L¸>=|ÒК»ñÇCJJUÛϦMÓ®?Ž ú“Ù^4L ¡9w̧¸wc‹EÙô´º{땺,É©¯1 ´h˜ÇŸ®Ïö©ruî4Æ+WBrrUÛÛž~Z»þ8ª®Õæ…s5f/0 ´h˜Ì !\†Í¯¼bìŽ; S'mú#Ü›œ ‘HhÎÝò)îÈ]Æøÿƒ½{«ÚF£²é©; ú›’ Ñ)€„æÜ)Ÿâ®ÜeŒg϶oO˜Ý»kÓ—Æ’ ú›’ ÑÉ Í¹S>Å]¹Ãû-$$Tµ ˜9S»þ4–d€Ô×Ü @¢:™B¸„š³?cÇBŸ>ÚôEx†š3@r LT'М»äSÜ™«ñ/¿(·êÜm}RÉ©¯¹ ´4°ZÜ)ᶤšs—|Š;sõ1®9ûså•0p 6}i*É©¯±   åV¡¼ÒÓUè˜pKR ͹C>Åݹò'$(ùŸêž{N›¾4Gll,Zwã56¤üŒ}[r@¢‚Û@999lݺ›ÍÆ·ß~ËW_}¥q¯„ÍUsÝŸaÃ`äHmú"<¡E}ܦZ¼x1K—.`õêÕÜ~ûí¼üòËÜ{ï½÷L4—«çS<«ŽñÞ½Pó윻e*HH}Í¡Eýܦúûï¿+§>—,YÂìٳٱc[·nÅb±hÜ;Ñ®žOñ®:Æsæ(«?Wè×®½V»þ4‡d€Ôר È ¨ŸÛ@~~~”––RRRÂÎ;:t( œ;%%½[så|ЧpÅ1NN†+ì¹Óº?5IH}MÉI$êã6Ð7ÞÈO<Á?ÿùO.ºè"¢££9yò$)))´mÛVëî !iî\eç÷ ]7ݤ]„g’S`¢>nS3†E‹qÕUW±âìÇÆÝ»w3wî\¼½½5îhWͧxWã”øøcûc3f(«?»+É©¯) ™õqé(''‡õë×Wf|ÆÇŒ3ètvkèë®»ŽGyDË. 'pÕ|Š'qµ1~í50›«Ú矷ܢ]œA2@êkJHf€D}\ºX°`]ºtaÖ¬Y$%%iÝ¡W̧xWã3g`ÉûcÏ> ^n¾3¡d€ÔçŒ Pjª}ð^è—K@aaalܸ‘Ÿþ“ÉÄW\ÁÈ‘#ùè£(**Òº{Bˆ&xã (.®jwè“&iÖááBB 0°ª]V™™ÚõG¸—.€*tíÚ•—^z‰#GŽ0kÖ,6lØ@—.]¸ï¾ûؾ}»ÖÝÍäjùOä*cœ“‹Ù{ê)ðñѦ?Î$ õ5%µgä4˜7)€*F®¾újV¬XAbb"}úôáÁ¤wïÞ|øá‡ZwO4‘«åS<‘«ŒñÛoC~~U;* î»O»þ8“d€Ô×” HZÔÍ­  êÂÃÃyôÑGùã?øä“O(®>§.ÜŠ+åS<•+ŒqA¼ù¦ý±©S! @›þ8›d€Ô×” òsöm™àFÐxóì»gQQ3fÌ`Ú´i¤¥¥Ñ¯_?~øa{(„hÈâÅöÙ‹ÐP?[ÑdHÔÅm  ÿþ÷¿dddðŸÿü‡o¿ý///î¸ã{&šËUò)žLë1.) ì=òˆRy É©¯© Ù^ÔÅm  cÇŽ1|øpV­ZŬY³˜7o'Ož¤°°PãÞ‰æp•|Š'ÓzŒ?üP¹ü¸B` rúË“HH}ÎÊÉ)0nTµnÝšÄÄDÒÒÒØ½{7 //¼¼<{'šÃò)žNË1.+ƒW_µ?vÿý©MÔ" õ55$§ÀD]ܦº÷Þ{yþùçéÙ³'±±±tìØ‘„„Ìf³ì&„ ûôS8v¬ªíë Ó¦iס?‚uq›µWûôéþ}û8qâDåìÉdâ‹/¾ÀàÎ RSS¥ˆU™VclµÂœ9öÇ&O®ý’'ÈÌÌ$44/w_ÒÚ…™Íf oÔÏÕµ´n3´qãFþüóO¢££1™LôíÛ—¡C‡jÜ3Ñ\ZçSô@«1þüs8x°ªíå¥l{á‰$¤¾¦f€ÂÂÀß¿ª]Z MÈR ã6P~~>sæÌ¡}ûö 2„Y³f±qãFJJJ´îšh&É©O‹1¶Ùà•WìÝz+téÒâ]i’R_S3@ AhQ›Û@7Þx#[¶l!33“×_€€&NœHXX˜Ö]BÔaÝ:س§ªm4ÂŒÚõGè›\ /jr› ¸¸˜uëÖñÎ;ï°hÑ"®¸â –/_Þ¬ç,((`õêÕlݺ«Õê¤žŠÆÐz=ÐbŒg϶oßtôìÙâÝh1²úšºÈ ¨Ím  %K–E||<ãÆ#))‰+VpÓM75ù9—/_Î¥—^ÊÎ;Y´hçwiiiNìµp„d€Ô×Òc¼q#ìØalÖ¬íB‹“ úšš¹^Ôæ6ÐàÁƒ¹óÎ;ILLdÍš5|óÍ7ÍÚÿËjµò /ðöÛo3oÞ<–/_ÎW\Á[o½åÄ^ GHH}-=Æ5g®»bbZ´ -N2@êkNHN‰šÜ¦êÓ§ï¾û.‰‰‰<øàƒüôÓO\tÑEL˜0¡IÏg4±Z­lÚ´©ò˜Íf#Ô“ÖæBÛ¶Aµ?+Àóg„ë“S`¢&·)€:ÄG}Äÿû_6lØ@QQQ³Öúì³ÏX²d }ûöåþûïàñÇ·{ÌöíÛÙ¼y³Ý­:i7¿]=Ÿâ ýñÄvÅ·ÄëÙÏþlfÔ(2D½×s•vff&7nt™þxbû‡~°Ë5æç• ªöéÓÚÿ÷¸JûàÁƒlÞ¼™… ò믿ê&ë6ÐóÏ?Oß¾}ùì³ÏèÝ»7Ÿ}öiii¬\¹²ÉϹnÝ:ÆŒÜ9sHOOgëÖ­üõ×_Nìµp„d€Ô×Rcœ”_mL/³?›6m’} U–››ë´ Ì ƒÍf³iÝ G¤¦¦···SžïÔ©StéÒ…ôôôÊÓ^Ó§Og÷îÝ|óÍ7ÄÅÅÙ}B4lüxXµªª=hlß®]„¨•Õ#Z~~ÐŒ©ÇJMM%&&FWçºÍšímÛ¶%++‹÷ߟC‡Ƶ×^Ë•W^Ù¤ç³Z­Frss+  víÚñí·ß:Jïb IDAT³ÛBèFb"¬YcL/³?Âõ…‡+ûЕ–*í’ÈÉQV‰úä6§ÀRRRèÝ»7«W¯¦U«V=z”ñãÇ3»æå&:ï¼ó¸ù曹뮻X»v-ï½÷¯½öO<ñ„“{.ÎEŸ4´ÖccÇŽå·ß~cþüù¬^½š-[¶ðÒK/5y;Œ%K–píµ×òùçŸó×_±bÅ î¼óN'÷\œ‹d€Ô§ö?5×$9ô´O±¬¤¾æ¬r)¼°ç6§ÀNž>‹ÅBçÎ5îú•žK—Ú{æeï/!\‰¬-ªs›·¨'Ÿ|’àà`Z·nMtt4<ýôÓ|òÉ'åÖ­IH}jŽñ›oBQQU»}{Ðã™dÉ©¯¹ 9&ªs›ÊÁÇLJuëÖ±k×.^|ñEV­ZErr21ž¾¾¾HH}jq~>,ZdìÉ'ÁÇG•—si’R_s3@r LTç6  111•EObb"×]wÉÉÉ÷J4‡d€Ô§Öÿç?Ê¥ÄÂÃáì¢êº# õ57$3@¢:—Ÿ²Z­Ìœ9“¾}û2yòdظq#C† iÖnðBˆ¦+-…7Þ°?öÈ#¤M„8ɉê\¾Zºt)+W®ä±Ç£k×®Œ?ž·ß~›n¸×_×^{Më.Šf’ úÔãÿþª?m` <ö˜Ó_ÆmHH}ÎÎÉ)0}sùèÇäùçŸgòäɼð 6Œýë_¬_¿žÉ“'kÝ=á’RŸ³ÇØbšŸ=¦L±ßj@o$¤¾æf€ÂÃíóiEE—焎 ·äòPnn.mÛ¶­lwïÞ{ï½—#FhØ+áL’RŸ³Çø³Ïàð᪶·7L›æÔ—p;±±±Dè¹lÍÍ PíŸ@fôÌåCÐV«•Ÿ~ú‰œ³IË={ö““Ê+*3qâD­º'„.Íkß¾ã8ïgd€ä˜¨`°Ùl6­;áˆÒÒR>úè#Þ|óMÌÌ™3éÖ­›ª¯g÷U½JJR‚ÎVkÕ±µkᆴë“M‘ž­[Wµƒ‚”M}…"55•˜˜]¬Ðïò !Vðõõå†n`ôèÑ,[¶ŒuëÖiÝ%!tãÕW틟޽¡™YT!4©,ÜY¡ @¹ ýq‹èĉ<öØcôêÕ ³ÙÌÁƒyüñǵî–p=|ÒÐZsÆøÔ)øè#ûcÏ>«¬ª+ªÈ^`êkî^` «A‹*._Íž=›Bbb"o¿ý6;wÖº[‰ä*>õ5gŒ_ÌæªvçÎpË-N蔇‘½ÀÔ×ܽÀ*HH€@¿üò iiiÌž=›Ö­[c0jÝ„{“´úš:ÆYYðÞ{öÇž~äB§Úd/0õ9c H(\þmì›o¾Ñº BèÖ;ïØç#Z·†»ïÖ®?B8ƒ¬-À f€„ç“ úš2Æ……ðÖ[öǦN'uÊÃHH}ÎÈÌ …@Bs’R_SÆxÉÈ̬j‡„ÀC9±SF2@êsVHf€¸Á)0áù$¤¾ÆŽqY,X`ìÁ!4Ô‰ò0²˜ú$$œIf€„µ|ò ¤¤Tµýüà‰'´ëÎ$)€„ ú3ÆV+Ì›glòdhÓÆÉò0’RŸ³2@r L€@ÂHH}ã5kààÁª¶É¤\ú.& õ9+e¿”C^5ûi…›‘HhN2@êkÌÏ™cßž8ºvur‡<¬¤>ge€ŒÆÚ3š2 ¤?R !*}÷ìÚUÕ6`útíú#„Z$$¤š“ úãš³?cÆ@t´ ò@’RŸ³2@P;$þH$4' õ92ÆÛ·ÃæÍöÇfÌP§?žH2@êsVjÏÉ)0ý‘u€„æ$¤>GƸæìÏÈ‘0dˆJò@²úœ•9&d¨RII 'OžÔºBhbï^øê+ûc’ýžL.…º/€~ùåú÷ïOTT½zõâèÑ£ZwIw$¤¾sñܹ`³Uµûö…Ñ£U‘ úœ™’ ¡ëè§Ÿ~"66–{'N••EÇŽµî–îHH} ñÑ£°b…ý1™ýi<ɩϙ A ƒÍVýsŸ¾\vÙe 2„95ÃgÅÅÅÙ}Â=ü0¼ûnU»{wHLTÖJÂS>m_……Av¶výq©©©ÄÄÄèbf^·oqf³™_~ù…¢¢"®¾új† ÆóÏ?Ùl¶{\nn.999v·ê¤-mwn<˜Ã‡Ú=‚gž©*~´îŸ´¥­V»uk0™rªÝii®Ó¿–l“““ñcÇÈËËC/ó"º-€RRR°Z­ôìÙ“7Þxƒ×^{uëÖñD¿ùæâããínÕI»ùíêŸ4\¡?žØ®ãš÷?ôP<%%UíÐÐxî¼Sûþºc;33“í«I—êŸ'´—.]j—jÎó™Lhÿ;ï8·¿îÒÞ¶mñññL:••+Wb±XÐÝžKII¡cÇŽÀW_}ÅC=ĉ'9ÖR/^,—«¬®1ÎͅΕ¯,€'ŸláÎyˆU«V1jÔ(ÙCE§N"!!Ái—Â÷ïo¿òù–-0l˜SžÚmÉ)0hß¾=~~~ìß¿¿òXzz:¾¾¾öJŸ¤øQ_]cüî»öÅOx8Üw_ vÊÃÈ^`êsæ:@ÊóÙ·%­/º-€ŒF#=öÓ¦Mãĉüúë¯,Z´ˆ»îºKë® ¡º’X¸ÐþØ£BP6ýB ²´¾é¶ø×¿þÅСC¹òÊ+¹÷Þ{‰eæÌ™ZwKwô0Õªµšc¼t)œ9SÕ T Ñt²úœ¹È ÞéºòóócΜ98p€}ûö1cÆ L&“ÖÝÒYH}ÕǸ¼æÏ·¿Ê³7Í#ë©Ï™ë,†¨w²˜Ðœd€ÔW}ŒW¬P?¬àãÓ¦µ|Ÿ<ì¦>gg€ä˜¾ézH½±Ù”m/ª»ýv8ï ƒfÝòX’R_Kd€ä˜~H$4' çûùgèßî»ÊÊ”1îÒn¹EËžy.É©¯%2@2¤’š“ ó?O? Ÿ}VóeŒŸ~¼ä¯^’RŸ Aë—¼ ኊ`ÞÉ)0¡9ɧԖ“qq°hÔ5©ªœêzôQÇ®ì’1VŸ¬¤>µ2@‚Ö')€„p!‹²}ÅóÏCFFíûF%={¶vB4ŸÌ铜š“|Šbófè×|°îâçÿ€„å ¯Æ?2Æê“ úÔÊIZŸ¤šÓ{>åèQˆ…Q£`ÏžÚ÷w꤬åóóÏзoÓ^CïcÜ$¤¾–ÊedÔ}êYxƒÍV±¸¨)..Îî«ÎTXsæ(Wn•”Ô¾? @Ù¹ýé§•­,„ꉌT¶Á¨’:h×­¤¦¦£‹YcÉ ¡U«`êT8y²îû'N„W_…Ž[¶_BèUûööЩSú,€ôDN Íéá“F«¦O‡ñãë.~úõƒ-[”ý½œYüèiŒµ" õ©• Bë‘@BszɧäçÃ7*»¶×Ô¦ |ðüö æü×ÖËkI2@êS+r)¼É)0¡9=¬QsäŒ {÷Ú÷ö†ÇW.{ Qïõõ0ÆZ“u€Ô§Ö:@ 3@z$*ûé'¸ùfû|(—²¯^­\Þ.„Ж@ú#§À„æ<9ŸòÞ{pÕUµ‹Ÿ‹/VNwµTñãÉcì*$¤>53@r L¤šóÄ|Jy¹²MÅ@Y™ý}ãÆÁ¯¿BçÎ-×OcW# õ©™’ ý‘S`Bsž–OÉÎV®òÚ¸±ö}Ï=/½CËöÉÓÆØIH}jf€dGxý‘ àõ×_ç™gžÑºÂ$&ÂÀµ‹X±þýï–/~„çVs(-MÙ›Ox.Ý@ü1qqqìܹSë®è–§äS6l€Áƒ!)ÉþøyçÁ/¿ÀÿýŸ6ýÏcW& õ©™òõ…V­ªÚV+œ9£ÊK ¡ëhÓ¦M¼þúë¼ôÒKZwE×þæÌqþJ[ÚÒn¸žnß~ÿ}múSVŸ~ íÛÇ1r$¬]«|ÀúòË8zõ‚¸8eeg½Þºu눋‹ã²Ë.cþüù”Õ¼tÕCér7ø§žzŠÄÄDÆŒÀ¶mÛØ¶mO>ù$wß}7>>>€ìßRRSSië Ó$°{·òiìøqûã!!°|9\w6ýª;ޱ»ÉÌÌ$44//¹¸V-f³™‚‚ÂÃÃUyþéÓí·ª‰‹ƒ_Tå¥ê”•¥¬¶hQý%WèÚ.gOˆéi7x]Î]vÙeDGGsôèQŽ=JFF%%%=zkÍóBuî–OY½†¯]ütëÛ¶¹^ñî7ÆîH2@êké PK;pzHÙyæÌs? œ »áåý¦æ…Â1ºœª)>>žøøx6oÞlw\f€DM/½¤|*¬ùWsùåðùç ÒS!D øüs˜0¡ª=v,|ù¥z¯·q#¼ñ¬__û=¥‚Á 9#FÀk¯AzzíÇøúÂSO)ÅS@@óú$3@B;EEÊeì/¾XûꡇàÛo¥øÂݵDº´âãá’KàÊ+áë¯ë.~•÷–ÄDøê+xúi8x~L¦ÚÏ9{6\t‘2C-#0iÒ¤Z³?¢å¸ú'”eÏ®Ï>³?îí ÿùr¾ÞÕc®>Æž@ÖRŸšëº§ÀÒÓ•äΕ+G÷ì©ûqçsç‰Ê{Ë…VÝï¼ 0lXíŸ=~bcáê«•Ój¢aR ͹r>eûv0ví²?ß}§ìõå\yŒ=…d€Ô§v¨®Õ › Ý»¦LN”ä´´ºwé¥Ê•_GŽÀ³ÏÚ/ÊXSLŒ²¸êGA›6µïÿþ{èÓGyž‚‚æõß“I¨’Ò·O>QÞ¸JKí÷î­äÎ?_›~ !ÔÓªääTµSSë.2b³)§ÅßxCù T£n¼žxB¹°¢)òò”Âêw”M˜kêÐ,p|%zÉ ¡cV«òÉéÎ;k?cÇ*WzIñ#„gª9 Ô˜Õ ‹‹aÉˆŽ†k¯­¿ø †ÇW®Þª¸ª´©BB”Bë?`äÈÚ÷Ÿ< '*jìÛ×ô×ñDR ͹Ò'ü|åÒÒW_­}ßôéÊ‚dÁÁ-߯ær¥1öT’RŸÚ hZ:5ž^9Íuß}õ;+³1))Ê>çX‡·Q¢£••ç—/¯eØ´I ^O›¦¼Ï )€„ p•|ÊáÃÊÞ]ëÖÙ÷óƒeË`ÎeÊÚ¹Ê{2É©Oí 4.½{7Lš¤6/¿ u?nÈåûädxòIeÖF-·Ü¢ Ÿ~Z¹P£ºòreßÂ=”÷4½sÓ·sáI\aŸªÍ›aàÀÚŸÜÚ·‡Ÿ~‚ÛnÓ¤[Nã cìéd/0õ©½œû˜Í¦\–~ùåз¯D6›k?——’»Ù¾~ýU¹:«æåëj Rf±÷ìQ.µ¯éôie¯Â#ê¿M¤º·x1\udfÚ0@ÙÉ}à@mú%„hyõÍ»ï*³'×_¯œRªKX˜2ûrø0¬Xƒ©Û߆ôì©\öùçÊ*Ó5ýò ôë§ä‘ô8y)МVù”òreQ±¬}õÄ-·(»0×u.ÝIH}’RŸ ½{•ü_ÇŽÊûÅ¡Cuÿ\·nðÖ[Êú=¯¾ZwÁ¡•ØXeAÅ™3áìV—•,¥ß^¨,Ш§ëÂ¥šÓ"Ÿ’•×\£|¢«Î`€W^Q‚„~~-Þ-ÕHH}’RŸ ŸV6HÍήûñ»µ<>ªœ~rEÊjÑÿ­\¡VÓ™3Ê×_Õªeíe È:@žiÿ~åröädûãAAÊBd*G „.,) ºwoø1ÞÞJ¾ç‰'”SHîèÿƒ©SáèÑÚ÷…„r$ìÜ©¬£á©$¤>É©¯%2@µ/SïÙS¹XâÄ å4’§dýü .N¹úUþ¤šk‰|ʬYÊ•5÷õ¹ï>å*‰ÈHÕ» )É©O2@êk‰ T8W]¥ìÖ¾oÜ?øû«þÒšèÚU9%öõ×е«Eëî´É5@2@žáÿƒqãì¯nðòR–äíú%„pM‹+käôê¥uOZÞñãiôí{™™ lbæ!¼´î€jJN†»î²/~ZµRÖŸâ íú%„p]z^7ÔÇdž··>VG”S`BsjåSŠ‹áæ›íøòòRVqÕ[ñ# õIH}-‘ú!МZù”‡‚?ÿ´?öê«0l˜*/çÒ$¤>É©¯¥2@B$Ôɹ¯>€)SìÅÆ*§¾„BÔ-55•˜˜]ÌË ð8»v)+²Vwá…ðá‡ÚôG!„ë‘HhΙŸ4²³•™ž’’ªc°z5;íeÜŽ>ÍiM2@ê“ p&)€„朕O±ÙàŽ;àÈûãï½çÙ‹:B2@ê“ ú$$œI2@ {™=ž{Îþ؃ÖÞðT!DÝ$$„›Ù¸^xÁþØ€°p¡6ýBáÚd!D¡¹ÔÔTÚ¶mÛäŸ?yn¹Å~›‹ˆXµ ||œÐAÐÜ1ç–™™Ihh(^^ò¶ª³ÙLAAáááM~[™ò¼rÊsËÉË.#;³„ܬRò³Íg—QœS†9§Œò< F£.‚èÚ!Ÿ(¼#½ñŽòÆ;ÒŸ(Œþ2‡àÎä/UhníÚµ<ÐÄ¥WËÊ`üxHO¯:f4²eЩ““:èš3ÆÂ1›6mbÔ¨QDDDhÖ6¬6+«‹ÍâðצüŒÅzöçÎ~oÃFE¢¢âûú¾:ôÊ×rÀ 6‹œôNï;Íè!£ickC¤%’àÒ`Ì9åäf›)È*¥$§œ’œ2̹åXrË!ÏŠ!Ï‚W¾ï|+Þ¥u|öV•’ëoS€©² ªùµfÁäåW+/ F}ì´î¤škÎ?ÌO=Û¶Ù{þy=º™ò0Rü¨/66ÖiÏ•Q”ÁÉü“¤ä¥p2Oùš–žFÞ±<Ì)flglPfÀ`ó‹l& V#F››òÕdóÂhóÂh3Uk•¶Õt¶mªv3b´*÷›¬¦j5b:ûÕh5`´ÏÞoÀ`3`²zc´ú`´rö~ƒòÕ F›“µ¢m¨|ŒÉjÀ`«øÞxö{ì¾7Ø.Š(â8Çë¼ÏçìMm–" –cJŽ•œûÁ€ÁhÀ+«Þ©®Jf™Ô#p[+WÂ[oÙ»úêÚY !\E¹µœÔ‚T¥°©VàœI=CáñBJO–cÈ¢U^¢òÛ•ATN³»Tä­u÷E3Ù¬6ÊÒË(K/£h‘C?c 4UF!CBhý­  2‘ÔlR Í5%Ÿ²?Ü{¯ý±Žaùr嘰' u•YË8vúåÞå¤Ù8§2É;iÁ’æ‹Of‘ùmˆÊ‹"*'Œ¨ì èžM@±Iëÿ·PN9Å\ëD•ã,&( TnEVÊ|˱x›±y•‚±/ ñ±àcÍçXëv膡7¡¹–¡¹U7¯XöÉRhÁR¨Ì2å'äsòí“øvð%j|­'´&dpˆS‹¡3Eg(kSæ¼'taR Í56ŸRP lrZPPuÌÇGÙæBÃø…K“ PýJÊKÈ-Í%·$·Î¯y¥yä–ä’]šGnAÅ96Jó X MPà…±ÈŸr’NpaP çGTv0²ú“9ßz2'âܬFû[†!—ý†D‡¥ ¨z!c£ÜÇŒÍTŒÑX\YÄø•çX–Cpi¡%™„¦Ñ¶ “ˆ¼<"Òóðv`áJ³—?ôïÏš#xwØ02BC+ï * Va–á¹.,òæüBo"ó YNYz–‹SÆ¥ôd)) SHY˜‚_'?¢ÆG5!Š!M~Τ¬$æÿ:Ÿÿþñ_ü"ýœÒOW'ë5@ÖrM·Ü+VØ{çxøamú#\‡ ©©Í9ÊÑœ£$åçxa9…¥åX(Ë3P^hÄPà¡È ï"_üK(õ'°$€À?Kü(ö!°Ø‹ÀboŠŒ (“sþýj«²Â!=J¹ØÀ`Ãf°aÀŠÍ`ƒ °b0ذaÁ€rÌh;û=VŒX ›#V 6 ,•1Ù,låmÊ±Š¶Éªó²Y0ZË1Ù,xYË1ZË0Ú,•}áìÍvöµ+ŸmW>ë9_÷?MÞååJ“—GDn.yy„Ö?nÈó…\?È­ãkŽ_Ý÷zÃ5Épï.èž©<—ÅhäçK.aõˆ¬>œ“‘‘çüÿf‡„psTã‚#èP`RNƒe”aN7W~_qj¬úñò¬rlVÇÿ‰öëâGë ­‰šEpÇfÉvÞż­óXµoV›r)mÈÏ!änôüE=¥j€@®çí·á±ÇìÝz+|ú©6ý-Ëj³rºà4GsŽòwÆq’Že‘~¢˜âT†t/²ƒˆÈ !2'ˆˆl_"²Œç·Ì©Šæ(÷‚ÌÈŒ´QRJi`6ï¼LYø[Ó);ETÁ1ÚfgÒ6+‹¶&>~~`2)ç|›úµ9?[ý«Á ¬Caµ*K²W|_WÛÑcõ=Æd‚ ƒÐІoaaD±¥¤2L^qj²zÀ<%/…´Â´Ê :ƒ .; SvÁMûÁ÷ìï’Í``ÇE±zÄÖŒÁávíúúܫΠIDATÄÍQQÜI¯ÀÀk³Ú(ÏRfŠ‹8óù2¿Êth&É¿›?Q”ÓdA1AµîÿñÈÌÝ2—ï_ë>)€„@-ÄÑ|Êöí0b„ré{…Þ½aÇ8Çûˆî¹KÈb³p<ï»’|(ôE”œ²BºÿL?B³üˆÈö'2Ë›VÙÚÎÈÔ”GbÂ>Ïcö¬+ùg «o&Sþ¶3„”"¢ø8móŽÓ.3ƒ°êçuÛ´ .€îÝ•[Å÷\ Û휱P}Ê­åœ.8]Y%e%ñùÞÏù#õÊDŽÔߡWºýÏÿÙ­[e1´·K‡^³G@@e1ÔßÁÿ§Öb+™ë39³ò Y_ga):÷AÀ…DMˆ"r|$ßyÇÜ-sI8•PïãC%ç—‡úãΤj€@-cñâÅç̧¤§C¿~’Ru,8v=Uî pdŒ[BzFH!ùP*éÇ (:eƘfÀ?Ó‹L"2½‰È2âí¢Ìr“¥þVÌ>eX|Ê)÷.S) …[éÔSÁ…MÁÚçÇËRÏ?R­[×]ätï®Û"§!§N"!!믿¾Å^soú^–íYÆò¿–s<·ê²û¡'”BhÂ^¨ñûz cGÖŒÁšüƒ„=zÎ~~Œ‹Œäæ¨(††„`4œ;Ùl)´¹îl1´! kIí¬šŽEcsïÍl꽉cQÇìÍ%Ü}?ÿÿ/RO{þVº.€¶mÛÆ×_Mjj*Æ cìØ±DV;§+k°Záškà‡ì¯\ &hÓ'Q›Õbcÿ® öì8Aæ±<ŠO•bL³áŸa$4ÓDD† ?Ç–Kq~ߌPâg¡ÌÏJ™OåÞfl^f¬^% Å ÊÕ?¾Våêÿò\ÌÙ•åR’EHI&þå¹17þÅ£¢ì ›ê…NHÓC«¢eÙ°ñó±ŸY¶g«ö­"§D™! -[ÿRN‘õ=]û玷n]Y míÓ«…Mn<[ ÃË‘b(ßBÆ—¤–NÖ·YXKÏ] i}„ͽ7SpuSb§0ú‚ÑºÚ L×ÐСC2d=zô`ãÆüôÓO?~Ÿ³û'Häž{NÙ贺Ǘ}¾´d³Øøë÷4~û1‰œ„B÷éxÄßÒ–]œ¤(ÐJQP åþEL9ø2 *O£Uñ Úæ%²è8ÞäbB¥K±¡U+W¾vêT»È©vÕð ¥–RÖ\Dz=ËXh=f‹R÷?¥B·þÁuüÊ¥µjÅÚáÃY=b›ûö¥ÌtîåZyy162’›"#é@ÉD°ÉDÉTï,ѩӧXýæjÊÖ–qqÒÅxYÎ}ÁwP/_¢øü¿ù¬ÊÊ:çϸ;]@f³¹²ØèÞ½;ÿþ÷¿™8q" PKi(Ÿòõ×0v¬’}¬0t(lÞ Þ².œÃš“²YlìI8ÍŽIäí*$l¯‘ŽG|T-vŠl䇘1cõÎÅÇM€õ a¥§ˆ,ûLŠŸÆrt ›ÅÆ®ÇùíÇdòwÓjŸWe±s!þÍêG‰?ä…–SXŒÕ·£)_[:!e©D§]ÞÚ寻¨Y(×Ë ‚‚”¼LÅ×êß)30õ0ááNËÚ¸Â^`ž.##£Å3@çÒʯ÷÷¿ŸûûßÏÑœ£|ú×§,Û³Œ¥>‰,í}Ò”Bèö=Ъ¸ÚÏåçsçwßqçwßQàïφAƒX}ã¬ïÓ‡|Vsµ… … v'«Lí!ª}e3ÿ<ØÐ6Ü!y0âgµ bv+[ŽÔ%³[¿&…»‘5sÏZ¼x1ÞÞÞÜpà vÇãã㉋‹³»U'íæ·«ÿÃ\qi©²ØavvÕãM&=:Ž\«ÿîЮãê÷Û,6¦Üý‹þý=ónZÇ’‹¾áNßÉä=BÏçŒ XÈ–Äÿg7ÓO¼Ýó×l¿Ï>yì›Ì±¾;y·ÍL,.&¢ýtúúDzÝ|%Êoá.ëãL6ÍáHÎ&¶ßÊ?£Ï0èŠ(>ì„÷ô'”sžo½EÜ 7(+\~ó lÙBÜÀáÃpæ 7kdgÃñã°oq×^«„ÅÖ®…O>!®M˜;ž}¦L!îï¿áòË¡o_èÜ™¸ œ6Þ±±±¼ýöÛN{>i×n¿ÿþûvÅÖý©ÙŽ_ϬÌbÿÃûI¸/ÁIƒ9s~»ÚOƒK.‚Ÿ;Wûù³_ƒŠ‹¿y3=§N%}ôh¾|ùe&>ŒÿGaÿñÍjç­‰gݘ¶n^  ŽçÏKàWÃ6â‰g*S‰'žK&z ëS`þ÷¿ÿ1iÒ$¾ýö[Xy¼â—»æ/¹Pß}÷Á’%öÇfφ™3µé»³Ylüúk"»6§ðR"}ètØ_sÓOceDÚHoŸÁÿaÆD:µ:EïÞç:`¸2³R׌Œ—®'…Yl~8üËö,ã‹ý_PXVH eÅ»þ„¨ú×p¤ÜdbóM7±fâD¶wè@®ÅBÙL®ÅBi#O™5$"Fþ—m†è¿aù¥[X²ó9§=¿«Ò}´jÕ*{ì1Ö¬YÃàÁƒíeÔ̧ÄÇÃäÉö3¾üRYoMœ[iQ9ëþ·‹ƒOšàEøþRÚš›ž›ÈŒ°q¦C¤F""OÓ«#ƒÿýû+á_“ ú\1Ô…e…¬M\˲=Ëø>ù{LånHTN‘]yøìÙõ1™ Úr £‘ üýɰÿZßñ€ ɧ U+òƒ‚(ðó#ßd¢ÀfÃb³™í>ZÞ¯Þj 3žA×Ð믿λï¾Ë—_~I¯^½jÝ/P˨¾FÍŸÂ!P\í\y×®ðûïJlCÔ-+½„ _þÅÉé„ï2Ò%ÙÏnõã/ù’ëq,7‘iíó±¦f8@çÈ4úõîˆÿ€Ápé¥RìÔcÕªU’R™ë©%­0¯`Ùže$œJ k¶2+4y7´ËwÒ‹tî }úØßzô¨7DYlµrøôi.>œ´#GœÔ ×¥Û(77—°°0¢££íÞ°:uêÄÇ HÔÒrs•_“’ªŽùùÁÖ­Ê"ˆ¢Ê¡#yüòÕ²~Ì ÍŸÞœwÌ«áOõÈ ·‘Ö¡k` aƃtL£t'|ûPfv:w>÷“!š%1#‘e{–ñé_Ÿ’’u”ë*³B£“ÀäÀu9åa!x]c_èDG7i)=­¤Û¹ÚÀÀ@6mÚTëx@@€½“&Ù? ìý¥÷â§Üfcן™$¬O¦ds6íÿö¡íi#çç7⪬ìpHk_€%èa¦ƒt8ÃØèóðî)ô¿:k¿R´zÔ3²'/_þ2ÿ¾üßl=¾•e{–qgßÏ HËâî?àž]Ð)J½`_ìmmÀë’¾ ùç}t>¯êW†‡é¶òòòâ²Ë.ÓºåÇÇ·eíZûã“&Á½÷jÒ%Me›Ëعí 6Ǻ5Îû|i•ÊIZ?‡Ÿ'µ…œ¶éøì'$ôcœwß¾pé5Ðù>µº¯[’RŸ»g€ÎÅ€á†3¼ÓpÞºö-ÖZϲ=Ëèy`m3KɈ `ò¥÷2mÈ4:…Ê©èæ’¿T¡¹×^[Ë›oÚÏ>\r ¼û®Fja‡óŠHø)” 'ðþÍÌùû½,„‹ðuè9l8u^)E' öÛO.ÙŒy)†aà÷¿ÿ>Þ.°˜'“u€ÔçŠë©ÅÇäÃ=oäÆž7’S’Ã÷‡¿çò®—á/¿_Î¢Û #$¤¾Ó§•%YÒÒªŽ…†BB‚²“€§È·X8–]ÄñÃùd$’w(—¹„ì1Ðí ŸF.h\î© )9BHÈzõ2sá5×`6\Y-R!š@2@B¨¬¨H¹²kÆ ûâ”ËàÝ©ø±gÌfަpêpÙGŠÈ=R€ùp>'-Ÿ1yÆ@XU׎¯ãQìo#³}"$â½qÙu7á5à>»í„B8F ¡:› „íÛaÇåë_AyåeÚ©€²Ð3ÏÀ7jÕÓº•Ùl¤”pìp>g’ ÈÎȧì;’Ë×ïýIÈWe\¸ßÄ 4&Ÿc5A~ˆ™Ò BlÞ™øq†Ð²"Š曆Wg/ŒÝ;ãwQ4!õÅpA?8ÿ|e¯7  õIH}’Î$ ¸k¨zaSWSñ}i©s^¯sçªBgÐ e늖¨ Ÿ.àïÿ‰ÏÿJèµÛtν°Ê¼!½CÞ^éXOVzŒöùÉ´6ÃÖÅc·óñëñ‚îЭ›r-~§NÊ.ÌB¡’.);»îÙšêÇNŸnZ&ÇQÁÁ0`€ýìN›6ê½^M©™Å¬Zºëêzíò¦o94”籘àt—,¢ ›š¹šÀK‡bìyÑÙçjåk»v-Ö!„®A P^GŽÀ±cõ8j6u1¡woûb§W/å¸3+Ÿ’oæ³vS´2›Þ;}‰6x×ûx›NuÎ#Äg ƒò¿à²«.ÇtS,\öºr~N‡$¤>É©O2@™ä/µ…ØlJ!sð ýíÐ!8|X¹œ¼¥……)—ŸWÜÚµ«ú¾CèÓ‚‚ÔïG]ù”Â’r>[þ'™ËÎÐk‡?=Šà\ûbîXˆ_àú—}Í?n‰×Íãað<å’3“ ú$¤>É g’ Pš’ÊÌTŠšš…NRªÓÏš‚ƒë/lªßüý[¦?Ž2[¬¬^¹‡“ñ)ôØDpþ¹&­} „íâbÓwt7ŸØÿS*7!„& Ѡª"§f±“•¥ÞëÔ.bê*nZbÖÆY,V_®ÝCÒÒ£\°-„vÙÚÑð@Fë2ÌQÑ#ðgß<¿ñ·@×™-Ôc!„ž@  s8x° Ø;'O:ÿuÚ·‡®].n\hY™FËÌ+e÷Îc$m9ôÿÛ»ó¨¦Îôàß„„Ä!¬•E‚¨ˆŠÛ¸Œm]PqêÆOkÙªý9ŽGO—§ckmG…ã~\ª£cqªÌµ–¢NUt¬cM¥bª­Z)²(( @ BBÈûûƒzFÍ„4Éó9‡s¸ï½7÷É{ÃåɽÏ}/js5” áRÑ ÞÁjUƒö¯é«Üõ¨÷Í…Üý[ÌŒ Iäà¹wz(zëG5@æG5@æG5@Ä”è/µ‡½ŠC‡LóZ-ãá·ü<ú}ÀÀÉÉ4Û°$΀[7Ê‘­È†òæCŠùp*ï÷ Ü«øp`À@80~³ÇqÿƒÖ×ôÕ® U}nã9Ÿ«˜öê¸Ì{ pý}½ÛB5@æG5@æG5@Ä”¨¨ HLLèÒ:ÎÎÆ‰ÍãÉŽM|ia@i^5~<—âÊ¡+dVˆáZ!†§R‡æg{ùz'†ò¾Åp ¸Éÿ; îs£­f°AB±vTDÚ%µ ÓV¢c CÊ´ÔWh“™‡‚Kw¡.Ð_.‚³²<*á¨ÄÀ4ßt$ ÷û–¡W¿Lý](zÏžšäµ !„¶PÔþý 0cF£39r¹éÇÂéŒFƒƒuj-ªkêQS«A½ªõ*54* t5ÐÕ6¡Y­Gs}3Xkx åÁAˇƒ–¾ŽNŽ¡ŽG­„:µ<ˆ´<ð{ü…/LóÀÎ&!PåÕµ¬MRxn*¸úé4D ß¡0sJ,Ý®nFTd~Td~TDL‰þR;°`Á'Ý~P£×£LS‡’2ÊÊâaI5Ôejh•Zj `5<ðëùÔ !Ô:üœ”8@¨u€£ŽG¢Æ–ÄD܈´0zäƒøçŸ½~þ± ¨ô2 Æ½W˜k5ÄÏÕÁw°C^Œ)£&ÂAÒú¯¤¤$O¥äÇœ¨Èü¨Èü¨ˆ˜Õµ#!!M|>~ÿþûPÖ4 ´¨•%JÔܯ…F©®ZC-¨ã_/€ AGbâHøpQ’tøœ*k¢rgPyi¡–=„ÞUw-Üû9`à8 ;.ît¦B¬ÕΘ'â§µßB $ ;™l‰^4H€Jo=jÜjÑ(«󬆓¼£<0úùÐÇox ³6„B¬%@=´t­é…€ÖÑ€&GôŽ :¡za3š…ÍÐ ›`èa6¡YØ&l6 õ€¨7ƒ/j†C/€'„Î|8Jp” ÑK&†Ä]7)|=žC¸ßH„³¿ªO1?êcó£ ó£ bJô—ÚCÅhÅzèÄzèE:4;jaiÁDZ8ˆup4AЋA(àè̃ØYˆ^²^pqÃÙÓ/Ä^2ˆÜÜÁ“8µ äðô'¡[ ªO1?êcó£ ó£ bJTÔŽ„„„%†š€FÒ ­¸ :‘z‘ÇF4‹´àýœÀšÑKʃ“»2/ ¼ýdp ôK__ð<ÜZ ¢;!„üBQ á|ÿ»Ï°ø½·á*h9ëB!„«gÑl¬Km@oÈ ¢äÇŒìᛆ¥Q›_UUôz½¥Ã°i:*s>qšØJ€ˆÅ}ñÅ–ÁæQ›ŸB¡@MM¥Ã°i•••¸pႥà 6‚j€ÚñhÄî„H!„X{ª¢3@„B±;”‹³‡o–F}l~Td~TDLÉî û÷ï#==gÏž…F£±t8v)%%ÅÒ!Ø<êcó;~ü8ªªª,†M{ðàNŸ>mé0ˆ°ëÛà333…ððpC£Ñ૯¾‚T*µthv¥¡¡ÁÒ!Ø<êcóÓjµ ’JóbŒA§ÓY: b#ìú P\\pðàAœ?"‘´H,*•ê™þ°»r‰£3˶·ÌÓæµÕþd[ss3”Je‡Û7½^ÿLßл² V«Û]ÆT}üðáChµÚN¿¶¹=˶»º:Ú–Z­~jò÷´}TWW‡ºº:£¶¶>·ÖÚÇ]]ß’Ç‹Îü™K[Ÿƒ®èJwæØb®>îèµm™Ý&@.\à†Tçñx˜5kΜ9c‘xŽ?Žû÷ïw{ý¤¤$“.ÛÞ2O›×Vû“mjµºU’ù,™®¨¬¬Dzzz·×ïÊ>ºqã²²²Ú]ÆT}|úôi¶»\Oõq[Û¶•••…7n´9ïiûèÊ•+¸råŠQ[[ŸÛ'·]__ßc5@ÏÒÇ]]ß’Ç‹'÷QSSêëë;ŒÇÚútEWú¸3ÇsõqG¯mËìö6ø¢¢"ôíÛZ­ŽŽŽ€üãØ»w/.]º åö÷Ï>û !!!f§¬¬ 2™ b±¸[ë!00Ðd˶·ÌÓæµÕþd›^¯GYYüýý¹¶k×®aĈŠýYèt:TVVÂ××·[ëweÕÖÖB¯×·ûÐFSõqEEœ!‘Hžº\OõñÓb쬮¶¥R© Ú¼¬ý´}ôðaËe2×ÖÖçöÉmߺu }úô““ùM}–>îêú–<^<¹jkkQVV†àààNÅþ,ÚútEWú¸3Çsõñ“mZ­?üðƒ]œ²Û ÆÆF€Ãcupp0: Ûcÿ4!„_‚î~·6v›=zb³Z­æ2üêêjx{{sË„„„ôÈÙB!„ô,»M€<==€K—.aúô逫W¯âW¿úU§Ö///Gbb"îß¿Áƒ#!!»”FLK¥Raë֭ذaƒ¥C±I'OžÄ‰'¸éøøx£K=Ä4233qðàAüío³t86çܹsøüóϹébùòåŒÈ6={ÇŽƒ‹‹ Þ|óMôéÓÇÒ!u›ÝÖÀ®]»°k×.ÄÅÅáþýûغu+®\¹‚¾}ûv¸nII ”J%†Џ¸8ôïßK—.í¨í‹V«ÅÂ… ñã?â§Ÿ~²t86)11˜4iÀ××—’yS(ظq#¶oߎ   £z-bµµµÜ ‰999HOOGrr²…£²-ÕÕÕˆŠŠÂ™3g 6àСC–«Ûìö.0X¶lÖ­[‡ÌÌL¨T*Œ;B¡°Õr{öìÁ˜1c0räH¬[·Œ1øûûcäÈ‘¸|ù2ŠŠŠ¸äécˆŽŽnón‡Ý»ws}¼~ýzn<•eË–aåÊ•46S\¼x+V¬hÕ^UU…×^{ Äo~óü÷¿ÿåæ•”” ¨¨ˆ’ŸNÊÏÏÇoûÛVí xã70hÐ Lž<™´oçÎˆŽŽÆ÷ßÒÒÒž×j-Z´yyy­Ú“““1nÜ8 6 ñññ0 J¥ D`` öíÛ‡øøx Dl}6lØ€ŒŒŒVí_ý5¦NŠ,^¼µµµ‰D¨ªªÂW_}…êêêN,ø%³ë¢££Š‹/âôéÓ­Fƒ>|ø06oÞŒ¤¤$¤¦¦"-- ;wî =zB¡Ð¨˜š´¶mÛ6¼øâ‹HOOoÕÇiiiغu+öîÝ‹””|úé§Ø½{7Ö®]‹ˆˆŒ5ÊBQ[—’’,]ºsçÎÅõë×[ÍŒŒ„««+¾üòKÄÆÆbÚ´i¨®®ÆäÉ“áêêŠ3gÎ`„ 4Ð\;Ôj5Þzë-L:ß}÷]«ù¯¿þ:T*Nž<‰·ß~QQQÈÏÏÇÝ»w¡×ëáââ‚… ÒÙÌìÙ³aaa8pà@«!NŸ>U«VaÇŽHKKÙ3gŒ.ýõ×ðõõEPPPO‡mU222ðÊ+¯à/ùK«/¥¥¥¥ˆˆˆÀ›o¾‰S§NA§ÓaáÂ…‹Å CFF–.]Š3fX(za„]½z•) &Xnn®Ñ¼Y³f±íÛ·sÓ©©©lĈFËœ8q‚½óÎ;=«µºrå S(ŒÏ糂‚£yÓ§Og;vìà¦8ÀFÅ–,YÂbbbXLL óðð`ï¿ÿ~O‡mUjkk™B¡`+V¬`áááFó ™P(djµšk{þùçÙÇl´\dd$»yófÄk´Z-S(lóæÍ,$$Äh^]]‹ÅFŸï¨¨(¶fÍ6qâDÖÐÐÀcl×®],99¹Gã¶6×®]c …‚I¥RvõêU£y111,11‘›þâ‹/XÿþýcŒ ÆÊËË{4^ktûöm¦P(ØèÑ£ÙÞ½{æmÙ²…EDDpÓ%%%L °cÇŽ±eË–1Æ«ªªbÆ ëјMÍn‹ ÷¨ð™Ç㵚———‡?þñÜôСC‘››‹¬¬,èõzÈår¤§§#<<¼ÇâµFÎâ´ÕÇùùùx÷Ýw¹éÐÐPܺuËh²qãÆaÓ¦MæÔй¸¸ ,, ¸víšÑ¼üü|Èår8;;sm¡¡¡ÈÍÍÅ‘#G0xð`444àÎ;VZÛœÆ £ñ¸{÷îúõëǵ…††"''3gÎÄÎ;‹ãÇ#55µÇb¶FÇ­ÿEåçç#**Š› ÅíÛ·¡×ë‘––†I“&ÝÍKÚ„   6Ç*ËËËChh(7íççgggˆD"\¾|¥¥¥(((0ZÆQÔ¥RiT°èââFWWW>|x饗0"3Å# ¯IDATþ| FiÝ***ŒúX*•¢¾¾½zõ,Y²ÄRáÙ¥RÉõå#R©J¥"‘{öìD"AZZèvÓ“Ç  åx¡T*ñî»ïâ£>BRR¶oߎçž{ÎBQZ¿¶ŽŒ1TVVB«Õ}™"Ý£T*[ÝÝ%•JÑÔÔ„={ö )) }úôÁîÝ»-¡iPÔ‰DbT¡Ñh 0dÈ«Ï~)œœœZõ±P(4Œ‹ gód-»R©³gÏÆìÙ³-™íxòX´|–¥R)–-[f¡ÈlK[Ç  åôâÅ‹-–Miïx1räHŒ9ÒB‘™–ÝAwÄÇÇÅÅÅÜô½{÷àããÓæ¥Ò=mõ±¯¯/õ± ùøøàÁƒhnnæÚŠ‹‹áççgÁ¨l‹êëëQ]]͵ݻwúØÄÚ:^Èd2:siB>>>())á¦}®mm|0J€:…þóŸ0 €””DFFZ8*ÛB}l~£G†»»;÷ Ñ»wï"33óæÍ³pd¶ÃÏÏãÆãÆžQ©TÈÈȠϲ‰EEE!%%MMMèxa‘‘‘ÈÈÈ@yy9à“O>ÁСCmïÎ:KWaÿÌ™3‡Éår€ùùù±áÇsóêëëYDDóòòbþþþlâĉ¬²²Ò‚ÑZ§ˆˆ£>9r$7¯®®ŽÍš5‹ëãI“&±ªª* Fk®_¿Îär9óðð`b±˜Éår¶mÛ6nþ7ß|ü½½Ypp0suue~ø¡£µNL.—3ooo& ™\.g+V¬àæß¼y“²~ýú1777¶råJ Fk½æÏŸÏär9ãóùÌÇLJ»Ë‹1ÆYtt4óðð`lìØ±ìÁƒŒÖ:mܸ‘Éår&‹™‡‡“Ëå,''‡›¿fÍæâ₃ƒY@@@«»ñl]M!„ûD—À!„bw("„BˆÝ¡ˆB!v‡ B!„ØJ€!&QTTÄ e`)÷îÝCee¥Ec „XJ€±A÷îÝÃôéÓñ‡?üÁ¨}çÎ8|ø°É·§×ëÑ·o_£A{ÒÝ»w1fÌLž<™F\&„t %@„Ø ºº:\¹rGŹsç¸öììlZ02óøüóÏ1vìXܾ}‡²t8„+@ !6J$aõêÕøóŸÿÜæüëׯãã?6j‹ÇÇ©©©8þ<6mÚ„˜˜ìÞ½Œ1$''cÁ‚HJJB}}½Ñú·nÝŸþô'ÌŸ?ÿþ÷¿æeffbéÒ¥ˆ‰‰Áþýûñh²Ë—/ãÀ¸{÷.¶oߎ´´´6ã­©©A||<"##±jÕ*.ά¬,ìß¿………øàƒpãÆVëþôÓOøè£PVV†¿þõ¯ÜhÍ%%%X¾|9¢¢¢°iÓ&hµZÀ¥K—°ÿ~nýãÇãìÙ³ÜôŽ;pç΀B¡À믿Ž `íÚµÜ蹄_6J€±aK–,J¥ÂÑ£G[ÍËÉÉÁ§Ÿ~jÔ¶uëVÔÔÔŽ9‚ÈÈHh4Ìœ9[·nEHH.^¼ˆéÓ§ãàÁƒØ¶m›ÑúË—/Gpp0d2æÍ›‡Ë—/Nž<‰Å‹côèÑˆŠŠÂž={°eË-‰ØªU«0wî\ܹs‡Ûþãôz=&L˜€ììlÄÆÆ"//ãÇGSSÜÜÜ ‘HàííH¥ÒVëçååaõêÕ˜>}:rrrPSS•J…±cÇB­V#** _~ù%÷h‡¸¸8ný¸¸8¬]» Õj±jÕ*¸¹¹á›o¾Á¼yó0a¼úê«(//Çwß}×éýC±zcðððÀ;wP^^Ž‚‚ <nnnÈË˃\.ÇܹsáììŒÙ³gw¹ !–Ag€±qÑÑÑ‹Å8pà@—×uppà~wvvŸÿÿ‡ —Vw\ñx<î÷1cÆ ´´@Ë%¨U«V!$$!!!X±bœœœ¸e=<<žšü@~~>ÆÏmŸÇãa„ ÈÏÏïô{quuå’ŸG¯9iÒ$£† ‚üü|888 ,, .\ÀáÇ—_~GEVV¦L™˜3g¼¼¼àããƒ)S¦ %%ÍÍÍŽ‰b9tˆÇãñ°qãF,]ºaaa\»@ €N§ëÒë´7ý¤ÜÜ\x{{d2RRRðâ‹/v>ðÇÈd²VÅÛ………ïÖë=zÍGu<ÐÜÜŒââbÈd2-g„.\¸€7nàĉ¸}û6Ö¬Yggg¼ñÆOOOüç?ÿAnn.¾ýö[ÄÇǃÏçãµ×^ëv\„žAg€±/½ôúõëgT 4~üxܺu UUU0 HKKCSSÓ3mçQQtvv6N:…ˆˆ@DDÖ¯_ÏÍohhÀ‘#G:ýºáááÈÍÍ…B¡ÐRP““ƒiÓ¦u;Ö3f@¡PàæÍ›`Œ!55øõ¯ÍmóرcH$èÝ»7^xáàâÅ‹˜0aàܹs(,,ÄÀ±hÑ" 6¬KI%!Är("ÄNlÚ´Éè®­Þ½{#66AAApwwÇÙ³g.yuÇ´iÓ0`À >‹-âꎶlÙwwwøûû#88žžžFwUu¤ÿþØ·oæÌ™Ìš5 ÿûßÜíX§NŠ÷Þ{£F‚¿¿?>øà:tˆ;4hÐ ˆD"DFFh9ãõòË/#88˜»|÷àÁŒ1 À Aƒàää„… v;&BHÏá±G÷¢B!„Ø :D!„»C !„Bì%@„B±;”B!ÄîPD!„»C !„Bì%@„B±;”B!ÄîPD!„»C !„Bì%@„B±;”B!ÄîPD!„»C !„BìÎÿþ˜8¦7IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/compressed-select-cache-zlib.svg000066400000000000000000000777301416254111300271120ustar00rootroot00000000000000 Selecting with small (16 bytes) record size (file in cache) 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 0 2 4 6 8 10 12 14 16 MRows/s No compression zlib lvl1 zlib lvl3 zlib lvl6 zlib lvl9 PyTables-3.7.0/doc/source/usersguide/images/compressed-select-cache.png000066400000000000000000001264211416254111300261310ustar00rootroot00000000000000‰PNG  IHDR@°AàÚ²sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwXÇÿð÷ÇÑ‹ UDDQ@ª(M»(v£X£•h4Æ€±DÍϯ%Ø"öÞ‰ÑXˆJì¢ (MêѤ3¿?.¬,wÀQOa^Ïã7»;;··w÷¹™ÏÎr!EQEQ-WÚ  (Š¢(Šjj4¢(Š¢(ªÅ¡EQEQ- €(Š¢(ŠjqhDQEQT‹C Š¢(Š¢ZQEQÕâЈ¢(Š¢¨‡@EQEµ84¢(Š¢(ªÅ¡EQEQ- €(Š¢(ŠjqhDQEQT‹C Š¢(Š¢ZQEQÕâЈ¢(Š¢¨‡@EQEµ84¢(Š¢(ªÅ¡EQEQ- €(Š¢(ŠjqhDQEQT‹C Š¢(Š¢ZQEQÕâЈ¢(Š¢¨‡@ÍPzz:ÒÒҤ݌ÏVNN¤Ý ‰ &&†yœ““ƒ÷ïßK´mqq1bbb““ÓH­û$33ÉÉɾŸÚˆ‹‹“v3 !111(,,”vSjTÝù÷òåK$&&Š”ggg#))©)š'U)))ÈÈȨWÉÉÉb!%9}f^½zoooôèÑݺuƒ‡‡V­Z…wïÞI\ÇÒ¥K1gΜiO~~>Ž?Ž>°ÊW¬Xo¾ù¦AöÑÆ72ƒ‚‚X8pŽŽŽMܲº¹}û6Ú·o²²2À¡C‡àìì,ѶsçÎ…‡‡Š‹‹E–]»v ß~ûm•Û¦¦¦âÇÄ€`jjŠ]»vU»¯5kÖ`ôèѵ«*OŸ>EHHH½ê¨,>>Ø·o_ƒÖ+-¹¹¹hß¾=þý÷ß:m/àêêŠk×®5pËDùøø`àÀ¬`íäÉ“èØ±#lmmÑ·o_œ;w®®®(((lß¾õÚïÉ“'áêêŠÒÒÒzÕÓ˜&Mš„+VÔ«Ž'OžÀØØׯ_o Vµ<4úŒÜ¾}VVVHLLÄ”)S0þ|˜ššbçÎØ¾}»TÚ”žžŽqãÆ!>>žUn``CCC©´©::t€®®.óxæÌ™¸}û¶[$—/_ÆÁƒ ¦üßÿÅæÍ›áííÐÐP±Û†……ÁÚÚ>ÄðáñbÅ ´oß¾ÑÛ|ôèQ¬ZµªAë´²²Âï¿ÿ޹s犜Ã-ǃ™™ÔÔÔu?×®]Þ={„Ö­[3åþþþøæ›o““ƒG¡U«V033—Ûp_E033‡Ãi°:?G ÀÊ•+1qâDäååI»9_$ž´@}ò믿ÂÜÜÁÁÁ¬7ïÚµkÉZ·°°OŸ>…@ @·nÝ```PmÝeeeGLL LLL`ff&²Nrr2ÂÃ᪪ ÈÉÉ1Ã-‰‰‰PRRŸÏ‡¾¾>F’’Ö¶rrrPVVÆ£G //îÝ»‹ì£  >D^^ììì——MMM(**ŠmwBBTUU¡ªªÊlŸ™™ ===f¸¸8hjjBII sçÎ…¼¼<Ó¦ÒÒR¦ÈÈȈUTTbccáââ¯æ·Cff&ž?޲²2˜™™1ÁÖÇ‘––CCC¼}û‘‘‘°²²‚ŽŽ <<½zõb}Ø#""‰‰‰àñxèܹ3ë¹ÕÕ?þˆ… ¢cÇŽ¬ò°°0žžžÄÞÞ¾ÆõþùçÒ¾}{¢¡¡ALMM Ç#[·ne–OŸ>Œ5ŠyK‰‚‚éÞ½;áp8dÆŒÌò²²2²hÑ"Âår‰¶¶6QTT$äõë×DWW— ***DMMôìÙ“BÈœ9sÈСC™:zõêEÜÝ݉‰‰ QSS#<ØÙÙ‘ÜÜ\fÿý—bbbBTTT—Ë%çΫò¹º¸¸yóæ1ׯ_OTTTH~~>!„””€„……BéÙ³'Y±b!„‡C‰ššQSS#ÅÅÅä·ß~#šššdÔ¨Q„Ï碫«K®]»Víq $ŠŠŠÄØØ˜´iÓ†p8²eËB!Ç'²²²dòäÉ„ÏçeeeÂçóÉÁƒIß¾}‰œœQTT$FFFäñãÇL3gÎ$²²²ÄÄÄ„GÖ®]Ë,¿zõ*@JKK !„lÛ¶WÛÎLJCÞ½{Wå:Ë—/'666"å§OŸ&rrräÇÕî£2___Ò¡CbmmM —Ë%äÁƒ„B6oÞLZµjE>~üÈÚ®K—.ÄÏÏ>|˜ÈÉÉǼVû÷ï'„rãÆ ¢¯¯O´µµ‰±±1‘——g–BȦM›ˆ¼¼<111!ºººDFF†µœBNœ8Á¼þU‰'ÈŽ;H=âééI!ä?þ šššÄÀÀ€´mÛ–(++“   fÛò÷Ò®];Âår‰››!„‚‚2a€dôèÑ$//Ù^VV–¬Y³†ôéÓ‡p8bmmM!äæÍ›DWW—9GTUU r÷îÝ*ŸÇ³gψ¡¡!ÑÔÔ$:u"222¤ÿþ„BJJJæ\9r$s¼ÕÔÔˆ¢¢"«þ§OŸ333¢¦¦FºtéB¸\.Y³fM•û&„çÏŸ$**Š)ËÊÊ"ÊÊÊQUU%jjjäèÑ£äÔ©SDYY™YoíÚµÄÊÊŠyœ™™I†Jx<±²²"222dàÀ¤¨¨¨Êý>|˜hjj2¿ÿþ{bbbBÜÝ݉‚‚QPP FFFäÑ£GÕ>÷ïßggg€´mÛ–p¹\âèèH¾ObffFx<4h«]7oÞ$DVV–ù¼øé§Ÿ!„ôë×8;;333¢ªªJx<±¶¶&Ìö¯^½"ݺu#ÊÊʤk×®„Ãá~øA¤ëׯ'æææÕ>J<}F.\¸@+++²aÃò矲ބ’““C ÈÊ•+™7ÛéÓ§ ŸÏ'±±±„ÑÈÝÝxyy‘ììlB!‘‘‘„Çã‘àà`B!»ví"òòòäìÙ³¤¤¤„”””={öׯ_3_ /^¼`µC\djjJþþûoRVVF’““‰––9sæ !„üü|bbbBLbbb!„¤¥¥>Ÿ_môóÏ?“îÝ»3íì숒’óåD444˜¡bD!ZZZäàÁƒ¬:ûí7ÂçóÉÖ­[I~~>)--%cÆŒ!cÆŒ©²DNNŽh†‡‡“«W¯B„‡Ã!6l`ê2d‘““#$??ŸGGG²lÙ2¦Ž—/_²‚#GŽ‡Ã¼îu €Ö¯_Olmm«]§ªhùòåÄÈȈŒ7ŽèèèEEEÒ»worÿþýjëóõõ%šššäÌ™3¤¸¸˜¼~ýš8991sùàÞ½{™mnܸAdeeIRR!„%K–0AC9@@455ÉæÍ›III !Dˆ*++@@ÒÓÓ —Ëe½Æ?&·nÝbÕóñãGÂãñÈ;wª|åçz‡È¡C‡ÈëׯItt4‰‰‰!ŠŠŠäàÁƒ¤´´””••‘õë×---&www'äÙ³g„BRSSÉ/¿üB!dݺuD[[›„††Byøð!iÓ¦ ñ÷÷gö-++K´µµÉöíÛÉË—/É«W¯HNNÑÓÓ#^^^$>>žBHTTT‡‡ð" "ÊO娢¢¢"Ò³gOæ³£°°˜šš___æy^¿~p¹\òòåË*÷¿qãFVSîñãÇINNfÊj €¦OŸNzõêERSS !Â<:::$  Êý‹ €TUUÉ¡C‡HQQ),,$ÎÎÎdÑ¢EUÖA!ƒ "=zô`~°¤§§3Áßû÷ïI\\³î»w²29qâ!„ŒŒ ¢¥¥E¦M›Æœß/^¼`Îÿ~ýúcccrãÆ RVVFÒÒÒˆ¾¾>9|ø0!Dø:uïÞÌ™3‡ ”ÿùç€<|øÕÎÈÈH€$$$Tû|(Q4è32xð`<|øFFFX¹r%ú÷ïÌŸ?påÊdffbÈ!xûö-"""`ff>Ÿû÷ï‹Ôùöí[ܸqcÇŽERR"""PVV†îÝ»ãï¿ÿìÞ½nnn1bddd ##ƒiÓ¦±º{%1dÈôîÝ:::prrŸþ xøð!¢¢¢°dÉ&w¨uëÖ5vC÷éÓÏŸ?Gvv6‡E‹áäÉ“€ÐÐP¸¹¹Õ:‡@[[óçχ¼¼<¸\.FŒÁ´U.— <{öŒ¹z£sçÎèׯ³ŸÏÇâÅ‹™:ûöí ===fXNVV}úôÁÍ›7™mºt邲²2# OŸ>!oß¾­Õó©(66VdøERqqq(--Å AƒpãÆ Üºu êêê8p`WŸuîÜ#GŽÇC§N0uêTÜ»w999PSSäI“X¹lÛ¶mÃèÑ£E†*:{ö, o߾̒µµ5 ðøñcðù|ðù|>>ÈÊÊÂþýûÿý7Þ½{OOOÄÆÆ"""mÚ´––îÞ½[e=õ9ÿ**..Æþýûáåå…ŒŒ DDD 33ŽŽŽÌg—¤ºt邉'BVV|>C† ©öýž˜˜ˆàà`̘1ƒrÓÐÐÀ?üÐ×ׇ®®.BCC±k×.=zJJJˆŽŽ ¼ø"-- +V¬`ÎosssL:•ÙÇ€àææ‡ƒÖ­[ÃÅÅ…õyùüùsxyy!>>PWWGûöíErÛ·o™z}f´T4è3cccƒ³gÏ‚‚èèhcåÊ•(++ömÛÒÒRÖ &ÿŠ»ê¡ü éïï/²¬|Ü=::cÇŽmð碬¬Ì$ç½{÷òòòèÙ³g­ê°³³ƒ’’îÞ½‹¨¨(Œ1ãÆƒ½½= Ú m¯ØVqdee±cÇøúúbÏž=055Å×_ùóçCIIIì6rrr „°Ê”””ðñãGæñ¶mÛ°páBØÚÚ¢sçÎLÎK}®`ÉÌÌD›6mê¼}·nÝ0qâDæñ±cÇ ¦¦†«W¯ŠœwÕ)ÏKHH@çÎ1wî\tíÚ>„žž‚‚‚ªý"„çfAA&L˜À*777GQQ”••€eË–aëÖ­èܹ3f̘Y³f1¹`åôôô˜Õ©œ<ŒŒ ‘ó¬{÷îLî Ö«z>>>¬2'''¢´´222b÷ûöí[èéé‰Í-©Î?þˆY³f¡K—.ÐÑÑÁàÁƒ±|ùòjÙ÷íÛ‡£GâáÇÌ9XþÙ1{ölÖºÕ¬€ðüÓ×ׯU›Å‰‹‹CII ¶mÛ†ÀÀ@Ö2›zÕ]Óû½ü¹Wõš†††bÔ¨QPQQ­­-tuuÁår™÷mtt4Œ%Ê—ªØ¦ôôtf{‡#rÞ¨ªªŠüØãñxÐÒÒ’èܦØhô™âp8011Á·ß~‹·oßâ?þ ü¨¤¤„'OžHTOù/ÆóçÏ£]»vU®óæÍ›jë©üE^[­ZµBAA’’’jLØ®ˆÇãÁÙÙ·oßFhh(~úé'tîÜíÚµÃÅ‹†Ý»w7jÛË;#FŒ@XXnݺ…7"##6l¸ŽŠ_rÙÙÙðññÁž={0eÊÂ/-[¶Ô«***uþ0411Á©S§Xe²²²——¯õ¼%áááÀ|š››ÃÍÍ Û·o‡¬­­aooÏÚ¦òk¥­­ ==½jÏwoooŒ?<@HHüüü——ÇüZ/' ®®^«çPÞ†Î;WyÕÜÓ§OoÞ¼{n·nÝšùB-‰V­Z1Á8ŠŠŠÈÈÈ@aa!“,‰®]»âîÝ»xùò%þùçlÛ¶ ÌëQÙÇ1wî\æõrÚÚÚàr¹ “èrõ9ÿ**Oß±c\]]ë]_m”n¾yó&&&"Ë}}}ѧO8p€ HnݺÅÚþýû÷øøñc•xÔ´BîÝ»Wãö„dddÔéÜnéèØgÄ××Wd¾ŸÒÒR\½z•ŽrrrBvv6Μ9ÃZ/;;©©©"uš™™AGGGd—ââbf_NNN¸rå 3 ü"ÐÑÑššZ½'V´±±œœ¶oßÎ\uòLj£¦²>}ú ((pqqŒ3þþþÐÐй¢¦¢N:5Ȥ>|@\\äääàää„~øÇ¯1p¬NZZJKK™a 2i_›6mj5oTEîîîxõêkž™¿þú yyyprrªU]—/_†••ë’ëyóæáøñãØµk—È>×®]ÃÌ™3ñý÷ßãçŸFpp0 0eÊ”––âèÑ£¸xñ"ììì0hÐ Ì;žžž€Ÿþ¹ÖÏMOOëÖ­ƒ¯¯/vîÜ BLMMÁápªý ó?-Z„™3g2ëŽ3~~~LÏIU† †uëÖ!##uº´N صkWŒ3ˆÇ±cÇpãÆ:Õ{[œ1vìX 8‰‰‰5 IÂÝÝ«W¯†@ ¹Tûĉxüø1þþûo$$$`éÒ¥hݺ5s m¯^½0qâDxxx`îܹHHHÀñãÇñõ×_39,Uyôè¼½½add„3gÎ ""W®\a­3lØ0hii¡¨¨cÆŒa-ááá°´´ÄöíÛ¡¢¢WWW¨ªª2óиºº2ÃgmÚ´:ªH__ûöíŸÏùÂŒŒDzz::tèðx<¨¨¨°ê6ltuu†²²2,\¸Ë—/¯rålll ªªŠ'OžÀÜÜ;wî„k.—‹ƒb„ èÛ·/k™’’F…øøx¤§§ÃÙÙºººøê«¯àââ‚÷ïßãÍ›7Ð××Ç’%Kàää===XXX 99‘‘‘PQQÁæÍ›Eæ Z¾|9LMM1~üøjŸƒœœÜÜÜ ¢¢Â*>|8lllèèhâ§Ÿ~‚¥¥%8ºuë†!C† ¸¸qqq011ÁÏ?ÿ mmmhhh`Ò¤IÈËËCDDœœœ°wï^‘/,ÖüE€p#$%%Á»v킲²2\]]«œÌÐÍÍ ¹¹¹ˆŠŠB^^Æo¿ý\.—9‡\]]ѪU+deeÁÒÒ***PVVfþ,,, «« {{{ :”9ï[µj…Ù³gcèСU‹éëëãÀ‘‘A¯^½XË”••áææÆš÷G[[›é «üþ000€··7>~üˆˆˆB0xðàjsï*^„Q®C‡"óì´iÓ¦Ú ÞÜÜÇGII 3·ÓªU« «« GGG˜ššâÙ³ghݺ5þïÿþæææèÑ£s‘ÇàÁƒaii‰ÜÜ\¤¤¤ÀÅÅ .dr¬ºwï.’,^ñsÛÚÚ£Fb’íÕÔÔàíí OOOÈÊÊ2ÛÌ›7ƒ ùñKI@*מQ-RBBs97!„lß¾hii±æ ¢ÆîÝ»‰ºº:ë’ãÏAù¥ï‰‰‰M¶Ï;wî.—Ëš‰j\ûöí#ªªªMú:·DGŽ! Ì¥öTípi  Ñf ==\.·ÊYr©ú™:u*‚‚‚СCäää %%ÄСC¥Ý´f§¬¬ ýû÷³~1JÓàÁƒ¡ªªŠ£G6Éþ’’’àä䄱cÇbíÚµM²OJ˜§3hÐ âÊ•+5ÎôLÕ^xx8z÷î_~ù3gΔvs¾H-&Z°``óæÍ¬ò²²2¬Y³»wïFnn.,--ë•×AUM àÁƒHIIžžlmm¡©©)íf5[wîÜ]ƒ\–\_„\¸pVVVhÛ¶m“ìóùóçx÷î<<2331`Ài7å‹Õì ‹/bΜ9HHH€H4gμxñ?þø#úõë‡âââZ]rJQEQÔ—§Ù@åÄõÅÄÄ cÇŽLÒ&EQEQ-C‹¾ þÖ­[000ÀöíÛñäɨ¨¨`Þ¼yÌ|¯_¿Æëׯ¥ÜJŠ¢(Šj:òòò-bh­E@±±±PPP€‡‡|||ðÏ?ÿÀËË þù'zõê…ãÇãøñã033kô¶$''C]]]dú~IÅÄÄH<íº$ëV·NUËÄ•W.+))Arr2ë²þ¨á&â IDAT¨(±³­6´¢¢"‚:çÃÔæ5úðáJJJ ¡¡Qå: uŒË§L¨8clåõšêWÕFIÕö5ªi_àñxPUUYVÕk”••¬™uÅ·•÷mmí&B¯Ï1®íöÒü¼¨ü}üøµšM¾®ÄµQ›c,ÉgKcãÊe………xüø1’““%jûMZ—Ÿ55âããÃ*ûå—_ÈðáÃYecÆŒ!K—.%„âççGüüüš¤}ûöí#ïÞ½«óöµi§$ëV·NUËÄ•W.ËÌÌ$›6mb•yxxÔØž†””DvìØQçík󅆆’Ë—/W»NCãcÇŽ‘W¯^U»^Scqû®Ú¾F5íëòåËÌ]Ø+«ê5 !!!!¬2qçmå}Ϙ1ƒ„‡‡×Øæ†PßÏ¥/åó¢òkôÏ?ÿo¿ý¶Æö4qçAmÔæKòÙÒXǸrYRRÑÑÑ©¶-ÍE‹€Nž[ëW.kIP³ËÍÍ…@ À‡»ú´´´ ¤¤„!C†`éÒ¥X¿~=¦M›†+W®àÁƒضm[“·³º¡IÔt‡æÚ®[Ý:U-W^¹LFFFd¶Û¦ÂãñêuÙ}m^#In€ØPÇX\}m·†VŸ}×ö5ªi_•gt®¨ª×¨|¦ÞŠÄ·_ê1®íöMñyQV¤¤‰‰ºxøxÿHLÿûWX–žðx@a!ÀmÄ»YŠ;j£6ÇX’Ï–ÆúL®©îæ¬Ù@wîÜÁºuë˜Ç_ý5~úé'¸»»C^^ÁÁÁX²d `llŒàà`æÖ TÓ9r¤´›ÐìÑcÜøúôéCïÈ-Fzú§¦<˜©Ô$& ƒŸJ·)«Bk½QR"ÜFO¯‘O5kÍ>0`@µÙì:uÂüÑ„-¢*»wïºuë&íf4kô7¾§OŸBCC£Î2|©^¼ž<Ø$% {jÎ/t@b" €¨úiöPc{ðàÂÃå݌/š¼¼¼DwH¦ê®ªcÌçók¼A(%OOOi7¡IEGË—'O ‡¥š†>á­s=šj¿TsD z:zô(îÝ»‡.]ºH»)U+EEE¦U+))ÀÏ?»wÅÅ W¯¦& ¯ÿé¯MöãÕ«óç?­Ÿ”Ôpû¦Z&5€ñãÇ33MSÔ—"++«^sÉPléééPSS×â{pêy1C87¦@€¡´ˆª7QE5€æ–Dpô(°bðîøuŒŒ€U«€ñã÷’t <áùS €¨ú¢EQÅrå °t)ðô©øå­[?þÌ™ðùMÓ¦ÊwG¡C`T}ш¢(ª4‡ „OHˆøåJJÀ¢EÀâÅÂÜž¦$ìú”D{€¨újäNKŠ¢¨–!$$ÙÙÙÒnFDF£GööâƒYY`î\á¥ï?ÿÜôÁP1èáÕheeMߪùørªPE}F¾Ä ¤$`åJ`Ï ¤Dt9‡xy  ;thúöU¤¢()é#/O˜TZ ¤¦-ô.T =@EQ-Lv¶pC`×.ñÁÏW_aaÀ±cÒ~ÊUžù™ƒQõA ŠqøðalÞ¼/_¾d•GEEa×®]RjUóSRRÜ¿_ÚM¡Pzz:JÄEŸ‘ÂBàÿ4k×?Š®cc\¿üù'`mÝôm¬Žž^€ æ1M„¦êƒ@cÆ X¶lfÏžÍ*öì~øá)µªù)))ÁñãÇñæÍi7…j@ŸsPYpà`j øú oPZYÇŽÂÛZXeÕÞˆ’‚‚‚‘íjBÁÇ«|žeee(--û| °°r¦¹•——ÇëׯŮ[PPYYYÈÈÈTÛ¦ÊuRÒ÷¹æ]¼,[&¼i©8ºº€Ÿ0}:ð¹_À֡çy€QõC{€¤,/Ox5CcÿU5ƒke&&&˜>}:~øá”Us‰Åž={```eeeèëë#  Æº1bĨ¨¨@AAæææ8uêa@3þ|¨««CYYݺuÃíÛ·™mïß¿###8p¦¦¦PTT„©©)^¼x€€´iÓªªª=z4«gå»ï¾Ãرc1cÆ ¨©©AEEƒ Bff&³Ž<ˆÿýïppp€««+ ;;ÞÞÞPWW‡ŠŠ :t耫W¯2Û½xñ®®®PPP€¼¼<:vìˆcÇŽý÷ºæÁÛÛjjj——GëÖ­1þ|f[ \ºt‰yüòåK899AEEªªª7n22>uõ/Y²&LÀâÅ‹¡¡¡---Ìž=›õ<(ª¢ÐPÀÙ2D|ð£ª*LnŽŽfÏþüƒ@4ˆQõA JÄO?ý„¨¨(æË¼²sçÎaþüù˜7oâââàëë‹eË–áСCUÖY\\Œ~ýú!99‡«W¯0kÖ,MMM$ÑŸÀŸ OOO©Ü5+ øþ{aóž=Âùq*Ÿ¢¢"BÀápDê ¶Îª‹ˆÔ ÊýW”šš (Ws›jYYY())‰MV.O¢öóóƒ——‚‚‚pûömôíÛ+W®ÄòåËáææ†èèh?~÷ïßÇwß}‡ÀÀ@üóÏ?"õ‰;ž(++«ö¶ ’¹zõ*,--Á¯æ.ŽVVVÈÉÉÁóçÏ¡®®Îúãr¹(--Eqq1ÌḬ̀téR\ºt ¾¾¾8þ<a£¦¦†Y³faïÞ½¸xñ"îß¿/¶wÊÒÒ¯^½Brr2SvýúuÂãF}š*¨´Ø»WØãóý÷⃟.]€  àîÝæü€‚Bäå?õÜ–”H~EUF{€¨*)((ÀÏÏ3g΄††S¾bÅ |õÕW˜1c¼¼¼púôi\¿~AAAUÖ5dÈôèÑžžž˜7olmmŒ¼¼ß~ûm½ŸÇ£G°sçNèèèàðáÃ8þ<.\¸Pí6æææ˜4i¼¼¼°råJ˜››#** §N”)SàææGGGøúúÂÊÊ III ¢E‹C‡…½½=ÜÝÝÁår±}ûvxxx@[[[d_&LÀš5k0|øp|÷Ýwxÿþ=þïÿþãÆCÇŽëýü©¦77·F+(z÷ÎÐ,NÛ¶Â[[Lž Ô0“ÂI @]= ÉÉìa01o+Šª €(† XeS§NÅåË—Y¹=...¸xñ"V®\É|IŸ9sƒ ª²n.—‹ëׯÃÏχÂöíÛaaaÁ «V­B«V­pàÀÀÎÎ<@ëÖ­jjjpqqa ‰ijjÂÅÅ…µ899±ÊÔÔÔŒ/^ÀØØ.\À€˜å={ö„~å¾uûöíÃŽ;pøðaDEE¡uëÖèÝ»7 ªªŠ±cÇ"00 PWWǨQ£0mÚ4ÀôéÓñûï¿ãàÁƒàp8°··Ç¶mÛ˜º{õêÅC|>wîܯ¯/,XeeeLœ8+W®dÖïÔ©t+ÝôÈÐÐ=zô¨ò˜SM«)æš?_|ð£¡!œëgÞ< š©°¾xúúú01Š ¥HLºw—^›¨/‡TÎ"¥þþþ¬ÅY°`ŒŒŒ°`Á‚¦iU+_ý5²³³qîÜ9i7峓••###deeI»)”öìT¤¨øø‡ÂÔÔ¤Ó®¦6f ðßôa„‰ßÿýî @rr2,--YCòÍí¢(ŠjéééPSS«6q½®ÂÃ9sØeÆÆÀßmÚ4øî>[EEEÐÐÈðiHž^ FÕM‚¦šµ¯¾ú C† ‘v3¨ ±î–—Œ-ü·œœœ°¤%?€0(?ÿ«ŒÎMÕí¢šµŠ³)STcj¬ 9s„=@mÚôùÝ©½)èëë£oß¡8xðSí¢êŠöQE}¦…¹?+:Ö’ÐûQ …@”D>|ˆk×®U¹üôéÓxõêU­êLOOÇþýûY÷ȪNQQöïß@P«ýPTShèy€ž>^õUQ§N€˜ÉÙ[Œ¢¢"(*f°ÊhUW4¢$rôèQlܸ‘ylccƒÑ£G3}}}« Äy÷î¦NŠÜÜ\‰Öÿøñ#¦NŠèèh‘e{› Šj* ™”“#Ìû©8A¸‚‚0ï§š Ì›=@€˜vPùlÐU[4ˆª“µk×V{+‰¦rúôi;v ÁÁÁhÛ¶­´›Cµ` ™4}:ðæ »lÛ6 ¥Çøúúú?~(¼½?‡EE@z:ðß”a%1IÙãäǸs«Ñ÷£*§ŠiVUO–ñìÙ3|ø€³gÏbÀ€¬ ÿ²²²„#FˆÝÎÀÀC† ǹÍE}‰¶mNžd—M™L*ö|Žôô€wï>=NJ¢U{4’²[1·°ðÏ…¾C5Ãj ÈÈHÖ­,222pûömìÚµKl´iÓ&´oßööö¬²ââbXZZâÆزe =z„6\««¢¢‚U«VáÍ›7X³f S¾ÿ~lÞ¼“'OƇD¶spp€ƒƒÒÒÒhDIUCÌü79:£kW`ûöz6®™(**Bnn.ôô4XPb"í£jæQ„Ý÷AAA ™3gÀçó1hÐ LŸ>]â:ÜÜÜðîÝ;\¼xï޽ǹ‘jU8fΜ‰}ûö±n»±gÏ̘1ƒÞùœúìÕ7(+K8ËqQѧ2eeaÞ¢b4°¸sçŽÈ•`4šª ú­B‰Xºt)bccqäÈ‘Z–––̽ºtttгgO†ŠÊ·î£U-bìÈ‘#ÈÌÌļyóÄ.ÏÍÍÅôéÓ1uêTôïß¿IÛf¥g{ŸF߆‚FÍ+8qâvî܉ÐÐP¨««×kŸíÛ·Çëׯ%^_KK #GŽD`` †Š={ö`ذa"7¥¨æfãFà?Øe³ftOñè\@TChÖPXX¶lÙ‚³gÏbæÌ™b×)--Ř1cpûömôíÛ·‰[¸ºÀÅÐ¥æ›ÀóçÏáíí½{÷¢k×®õ®ïÞ½{èØ±c­¶™={6úôéƒ7oÞàøñã8{öl½ÛAQM¡®9@¡¡ÀÒ¥ì2++@ÂÑã¥<H_ŸýƒŽöQuѬ x{{#??UÝô~Þ¼yèÚµ+Š*¼·@™™™1b¾ùæŒ3¦Nu3ÿøð!îß¿ýû÷תtìØ£G†ŽŽÜÝÝëÔŠjj!!!pss«Õ0Xz:àåTœ?QMM˜÷#'×ü „……AOo(«œöQuѬs€tuuáêê ±ËýõW¤¦¦býúõMܲÏϺuësçÎÁÌÌŒùûñÇ%®cÙ²eppp€µµ5ììì0vìØ:Ý‹köìÙxúô)fÍš‡Síº¿þú+€øøx888`РAµÞ'EÕWms€&MâãÙå{ö:4pãš šD5(ÒøøøVÙÉ“'‰½½=ùøñ#!„>}úÝ»w³Öñóó#†††ÄÏÏõW‘““Ù´iS£¶¿)¼y󆄄„ˆü½~ýšBHTTyúô)³þ³gÏÈ›7o˜Ç¡¡¡$66–‘ß~ûܾ}»Æ}~øð„„„ââbVynn. !999¬òââbB>|øÀ”EFFŠ´ùîÝ»u:-Mff&QRRb•U>¿éãÆ{¼f !€†BÂ?{ûϧ}Ÿóc ü˜ ŸœÜçÕ¾/íñ… ˆŸŸqqq!¾¾¾DCCƒ´Bšÿ$â ,lÞ¼™)Ó××ǬY³ ££زe lll0~üx 8àïïÏú·ªºŒŒ˜}PÔ—"++ FFFÈÊÊ’vSš…ÚäݺôéT˜ñööÀíÛÀRRb”çihh@NŽ=e@z: !ÙµT5’““aii‰äädi7¥Ñ5ë!°êÌ™3ùùùˆ‰‰ALL òóó‘žžŽÔÔTi7¢¨/¤ó¥¤ãƱƒ àÄ üÔ¤| @ôJ0: FÕV³N‚®ÎŠ+XÃÂÂ0räHL™2EJ-¢(êK&ɽÀÊÊ„—¶Wü²æp€ ïÓ¢•çÂ(6öÓ²¤$ÀÜ\J £¾HͺèôéÓ022ÂÞ½{±wï^áÂ… ÒnEQ-ÔÊ•Àì²%K€Áƒ¥Óž/YåDhz%U[ͺÈÓÓSâ;4_¿~½‘[óù‹‡††”””¥þ¼¼Eff&¬­­Ñºuk‰ëÏËËCZZšÈðV~~>RRR`XEFè½{÷ŽV­ZI¼/Š’¦^Þ^¹¬_?é´§¹ C`T}ÑHÚŽ.lüý11¯þÓO?áÈ‘#Ìã’’äååáôéÓ5jÂÃÃ1räHÄÄÄ@SSزeK•÷\«,%%&&&¸zõ*ël6lÀ©S§ðâÅ ±Û-Y²àíí´Š?©)JÊÄå]ºlØÀ^¯OÀϯ‰×LTÌ¢IÐT}Ñ J¬mÛ¶!++ YYYÈÌÌDß¾}áììŒaƂɓ'£cÇŽˆ‰‰All,6n܈o¾ù¦ÊÀ¥2cccôë×LYYYöíÛ‡Y³f5ÖÓ¢¨FS9(.˜¹ß¾} èÕ«³—Ë…££#³LC† AëÖ­qèÐ!,\¸{öìÁرc¡¦¦V§¶RÔçbñbàþ}vÙªU€³³tÚÓq8€®.ûf²II€ººôÚD}Yh$m&ÿ>CQQQ˜8q"~ûí7ØÙÙ1ååÉÎÑÑÑèÑ£SÉÊç© ÇÃôéӈɓ'#((·+g‹RÔ¢<èüy¶la/4XºT:íjN*æÂa°ÊPçÎRjõÅ¡C`”Xyyy1b¼¼¼àííÍZfaauuu\½z•)‹ŽŽÆ›7oлwïZígúô鈈ˆÀܹsann[[Ûi?E5µ<}šiÓØåmÛ {,¨ú©˜ÐÙ ©ú¡=@”Xß}÷ÂÃÃakk‹Ù³g3å#FŒ@ÿþý±~ýz|óÍ7¸{÷.LLL°oß>¸ººbìØ±µÚOÛ¶mááá'Nà÷߯qýÀÀ@DEEááÇÈÏÏÇÒ¥KѾ}{š8MIÝ!žèÙ¨8¢¬,pò$ð_jUOs€šMÕ €(ÆâÅ‹Ñý¿|¤^½zAGGGdò9€fΜ SSSœ:u ©©©X»v-fÍšUí-+ºuëÆ\Æ^‘¿¿?¬­­1nÜ8V¹üüü //Ï”ñù|ÈËËcäÈ‘¬2Š’¶ €GØeë×ÒiOK@{€¨ú ÅX¼x1óÿ ä%¹ººÂÕÕUâú»uë†nݺ‰”[YYÁÊÊJ¤ÜÀÀþþþ¬2z…õ9:v ع3€Ê?V‡oš)¾Zq9@Ñ ª6hEQT=ÄÆÂØÂñ/cc`ÿ~)6ª™ªœD ª>hEQT=̘ï#¼˜œœ0ï‡ÎæÐð*çÑ!0ª>hEQT×®±Ë~ý¨0;ÕˆhU4¢(Šªƒ„À×·bI:œK0ož´ZÔü!£ÂôÚZZ@…[¯!/øðA £¾H4¢(Šªƒ™3Ù_¶rr!øßÿ²é|?¨r— T¾X•öQ’¢EQT-8\¾Ì.[¿Þ=zÐ Så a{Q’¢EQT-$%‰^ÞîäÌŸ/ö´t•ó€h"4%)QŒ®]»âܹsuÞ~ýúõ¸~ýºÄëBp÷î]¬Y³sæÌÁŠ+p¿ò$Å8pàÜÜÜ$ÞÏ­[·Ð¾}{±ËîܹƒK—.I\EÍž df~z¬ ìÝ df¦£¤¤Dz k*ç4šª;z¼”=ÎÍÅ­¬¬FߪŒ ¦Uþ¤¨$..¹¹¹uÞÇÙ³gÁãñ$¾!êãÇáìì www˜ššââÅ‹X½z50wîÜ*·ËÉÉABB‚ÄíÊÏÏGLL «,;;/^¼€··7ìííááá!q}TËuô(pþ<»lÕ*ÀÔ8}:ÔÍ IDATnnnФ÷½h4aaaôRxªAÐHÊneeaaTT£ïÇP^¾Æ¨¢‚‚ðù|p¹’wV×{“ŸŸyyyÖ­2Ú¶m‹W¯^ÁÔÔ”)2dÖ®][mÔ&L˜€/^ -- öööº/ªyHI¾ý–]æàði8ÌÓÓ³éՈˢ=@T]Ñ!0ŠåÉ“'èÝ»7TTT ¦¦†5kÖ0˼¼¼`dd$ò÷ôéS@ß¾}±wï^@ii)ŒŒŒ°fÍØÙÙAEEêêêØ¼y3SŸ––+ø{{{dV_¨ÁÝ»wѾ}{ÄÅűÊûí7ôéÓ§Êí.^¼ˆ˜˜ 0@â}Q-Û7ßééŸËÉ ‡¾jñj4 šª+úÖ¥XNŸ>)S¦àÞ½{˜4iV¬XÀš5kÄüµkךššèÔ© 11Ùn…‹`Þ¼y¸wïF…… ",,¬ÊýÿùçŸèÙ³§Äíutt!ûöíc•૯¾ªÍS§¨*< œ=Ë.ó÷:wþô8=æ56Ir€è%):&eVÊÊð10hôýhð${©W¯^I“&lmm‚ÇÃÍÍ &&&ÌzxõêÂÂÂXwk¯ìÿû °³³Ã… pìØ1ØØØˆ¬»yófÜ¿wïÞ•øyq¹\̘1¿ÿþ;V¬X.—‹Û·o#&&Ó¦M“¸ŠªŠ@‘É ml€ ÷„„РÆ&.ˆQuE )sQW‡‹ºº´›Q%;;;¼}û–Uvûöm,^¼/^„¡¡a­êstt©Nœ8Å‹cÿþý°µµ­UÞÞÞX¹r%®^½Š`Ïž=9r$´´´jUE‰3o–öé1ŸìÛÈȰף9@O\ŽŽðµ(->ÎÉrsee)4ú¢Ð!0ªZPQQa¿ÿ£GƪU«ªÍ±©Jjj*Ô*Ý%òÀ˜:u*:„ &ÔºN]]] 6 ÈÉÉÁ©S§0{öìZ×CQ•;œ8Á.[±èÚU:í¡Dq¹€¶6»ŒöQ’ U¥ââbܼyÖÖÖ„ãï£F‚««+¾ûî»Z×—™™‰°°0ÖUW?ÿü3æÏŸ3gÎ`ìØ±unëìÙ³qþüylÙ²íÚµƒ‹‹Kë¢(ÈÈæÌa—YYK—Š_Ÿæ5>q9@M„¦ê†Q,gÏž…®®.>|ø€;v€ÏçcþSÜ.X°ÏŸ?ÇŒ3püøqf'''´mÛVl}(**BRR¶mÛ###&7'00~~~˜3gbcc±sçNf;GGGtïÞ]âv»»»ÃÈÈþþþظqc뇆†";;ÉÉÉPPPÀ•+W ­­Í{åã#¼ô½œ¬¬p諪t:šÔøÄå4šªQŒ^½zAKK ‹-Brr2lmmÊ| çääÀÖÖ‡bm§««‹¶mÛÂÖÖV$âóùX»v-rrrгgO¬_¿rrrUUU¸¸¸ <<ááá¬íZµjUeÔ¦M‘¹{8~øáÌ.[¶ ¨.&§9@O\°œý˜öQ’àBˆ´ñ¹ò÷÷gý+΂ `dd„ 4M£¾¥¥¥àñx¸pásõyÉÊÊ‚‘‘²š`&ò/IV`nÎîE°°þýWØ D}~üý•+?=þî;à×_¥Öœ/Zrr2,--‘œœ,í¦4:šDQUÁ¢Eìà‡Ç}ÕüРÆWU½žª Q‚ÃáÀÇÇÆÆÆÒn EIìÊa°SÑâÅ@5oš”jxwîÜ)§C`T]´˜  ##ƒu냂‚œ9s< ¼•‹‹ di?w½q¹\Öm/(ês÷á0s&»¬KÀÏO²íiPã«*ˆ&ASuÑì{€°gÏLœ8ÿÏÞ™ÇÇt½ü3“}‘‘)J%4¨ÖöE”V‹ßmiµEmQj­Jµj§¶R¾ÚTUÅÒê¯ZÚJEÑ*J‘„$2!‘EdÏÌïc2sgIf2÷ι÷y¿^óšœsïÜû82“Ï<çsž³ÿ~αÿýË–-Chh(Z¶l‰)S¦`êÔ©"EJ„˜L›ddèÚ..,tϳO80”"êƒä3@¹¹¹ÈÈÈ@@@€Ñ±|§NªiwïÞ=ö/^ ///‹ï‘˜˜ˆ²²2^â%{A¿³:6mâö½óððÖ_#//þþþpµpÛÂz***P\\lôyÞ´)+ˆ¨V³va!PRx{‹$á4Hþèèh“+] §º PQQaÕXÿþýáééI+ilàŸþ±ªæa=æÆxÊ”)"DãXãÇsûÚ¶æÏ·î:THxÌÕrqš4áÖmÊÎZµ²s€„s¡‘ “&MÒLš4ÉìñòòrMß¾}5o¾ùfMß¼yó4-Z´ÐÌ›7óЇÚÔ¦¶s·ßxC£´y¥R£9rÄq⣶eíNØÿŸöÿòÐ!ÇŠÏ‘Û?üðƒfÞ¼yš>}úh¦Nª ÐÈÙÔÒÖé1eÌÕh4xþùç‘‘‘_~ù¥fúË’:@A8/11ìO¦–É“•+Å‹‰¨ƒ?ý¤k'$#Fˆ³Bu€dDUUÆŒƒœœìÛ·Ï*ïÁrx£‰ ±1%%À+¯pÅOëÖÀ‚õ»Õsu€2BÖ#kTPP€'žxjµ?ýô|}}ÅI–ìÙ³Gì$±1³g—/ëÚ °ysý³THxÌÕ¨"a=’@‡ÆØ±cñË/¿à—_~ÁرcqüøqÀ×_äädܸq?þ8úö틾}ûíuEËk¯½&v’‡Æ˜Ë‘#Àš5ܾ‰Þ½ëÍØØX2@ Œ¹:@ì·Mµ€ˆºü*°Ö­[cìØ±;vlM_DD`èСˆŒŒ4zö8AÒ£´xùeÝ’iˆˆ-/&Âv(DX‹äPpp0‚ƒƒM Ehh¨#" Q©Tfÿ~ 1Ö1w.pñ¢®­PÿûàãcÛu©ð˜«P5hÂz$?F8>äOcƱcÆ+¼&Lúõ³ýÚäžÚ<@d‚&¬E6Ëàë-ƒ'éP^tê\¸ ëkÞø÷_ Añâ"ø¡²’m[¢ÿ­´ðô/&g„–ÁAHŒ¸8®øØö$~¤›«­eˆÚ DˆŽ¾iˆÜÇøÄ `éRnß+¯=Æß=¨ðÔV #4a$€Ñ!ŠðÈyŒ+*€—^ª«u}¡¡ÀòåüÞ‡<@ÂS› #4a´\ªQ#b>}6nüýù½Oll,¿$Œ¨­;ÎmSˆ¨ Ê!YNŸ.äö½ððä“âÄC MÖ@ˆ¹ûSìǸ²’M}éÛrš5Lì‡Ì 䞺<@T š°@„èÈÙŸb/ä8Æ‹± >ë× s?ò µ ʵA BtäìO±rãÜ\ã­-ž}xúiáîI á±ÖD ¢6(D„äX¹()ѵ›41Þü””"¬!:rô§Ø9qa!°n·oÆ @èÚÉ$1³?„cA ÂH¢#UŠ#!µ16ôþôî ôê%N,ZÈ$<õõQˆ0 Bt¤èOq4¤4ÆgÎ{÷rûÞ{OœXô!ðXê"4a ä"DGŠþGCJcüñÇli³–®]Ä‹G y€„ÇRM–@ ‚ œ†‹;¹}äý! ¡)0ÂH¢#5Š#"•1^´ˆíý¥%* ° !`È$<õõݺÐ aˆlJ¥BZZšÉcÿý7¶mÛ†3gÎØ9*–?ÅQ‘Â_»lÝÊí›=›UvÈ$<–z€¼¼€† umµÈÉ00Â)‘¼ªªªÂåË—1vìX¬]»Öèø;#Q£Fá×_ÅàÁƒ1þ|¢”7Rò§8*Rã%K€ÊJ]»uk`Äñâ1$66b‡!i,õd„&êFò&è`Ñ¢EHKKC»ví8Ç®^½Š7âòåËhÚ´)._¾ŒÈÈHLœ8‘>ÈÂP©€Ï?çöÍœÉv~'S„„.èÚ$€C$Ÿ8p ’““M®ÐHLLD×®]Ñ´iS@«V­ÐªU+$''Û9Jy#Š#ãìc¼|9PV¦k‡‡/¼ ^<¦ ðXêÈMÔäPm¨T*4iÒ„ÓŒl½¯ øä“O8}¨m{[ߟâñH±­cG‰ÇšöíÛÀ† 5=€éÓ77LjOKRR/^ì0ñH±½páBލ¶óÙRx];;[üøµýÛo¿á“O>Á°aðiÓ&ùyL˜4i’fÒ¤Iœ¾9sæhFÅé0`€fùòåF£™7ožfòäÉšüü|ÎCjS›Ú¶ß_£a•4 _Ó´©FSRâ8ñQÛ1Û+W²ßíïÎøñŽŸ#µKJJ4ùùùšôôtMjjª&((H#~I1é2yòd\¼råJ$&&b¯^YÙ®]»bÒ¤I=z4âââ æ™ ûrçТŸ¯ë[¼˜e€¢6vìFŽÔµ~øA¼xœ•J…èèh§Ÿ6·YOuîÜ'Nœ@uu5 ¬¬ çÎCg1·”–!rx£‰³Žñ§ŸrÅO£FÀ믋OmHxlñ‘ š0Dò(-- ñññ8þ<Ο?øøx\½zЧO´lÙÏ?ÿ<0jÔ(ÄÄÄ }ûö"G-/¤P£ÆÑqÆ1.-V¬àö½ý6Р8ñÔÕKëd‚&êFòS`§OŸ6úðETTàÎ;X½z5RSSѱcG¼õÖ[ðððš#Y½˜4I×öõeÅÄ‹‰pJJ]ÛÅ(/§Ò u!§)0É×ŠŽŽFtt´Ùã 4ÀÚLˆ ŠŠ `éRnß믓ø!,ÇÛðóŠŠX»º¸yÓ83DÈÉO޾iˆ³ñ–-@f¦®íé L*^<–@ á±ÆЮðDí"DÇý)Ά3qu5ÛôTŸqã€{õJò 5 €ŒÐDíH~ Œp|¤°O•£ãLcœ\¾¬k»¹ï¾+^<–bªÚ<Á/Ö욨Êá0h4ÀÇsûÆŒš7'¹¡)0¢6H¢ãlþgÄYÆøûïsçtm¥’mzê Hx¬õQˆ¨ @„è8“?ÅYq–1^°€Û1hÓFœX¬…<@Âc­ˆ2@Dmˆgò§8+Î0Æ?ÿ œ8¡k+ÀìÙâÅc-ä[=@$€}(D„C`˜ý2èÐAœXi`˜¢)0B@„è8‹?Å™qô1>tˆ=ôq¶ú¤ä[=@99€ZÍsP„ÓBˆgñ§83Ž>Ɔٟþý‡'–úB á±ÖäëËZªª€[·ŒpJH¢ã þgÇ‘ÇøÄ æÿÑç½÷ĉÅbcc(v’ÆZ{ ·M> B‹Ó ‚‚9r ÑhðóÏ?ã‡~9*‚ lŰîOÏž@Ÿ>âÄBH2Bæp´aÃlÞ¼°{÷nŒ=}ôÆ'rd„­8º?E 8êŸ;ÎÎ9›÷G y€„ÇZ@FhÂ<@*×xOȇ@ 6Dbb"~ÿýw¸¸¸àÑGEŸ>}ðå—_¢¤¤Dìð‚¨+W¥¥ºvh(0v¬háÇÏðñѵ++¼<ñâ!‡@Z"""0þ|\½zsæÌÁ¾}ûвeKL˜0ÇŽ;<ÂFÍŸ"EeŒ €uë¸}Ó¦îîâÄÃ'äžúx€ã,Mƒ€“ -J¥=ö¶oߎ””tèЯ¿þ:"##ñù矋QOÍŸ"EeŒ×¬îÜѵ›4&L/>!ðÔÇš0S }ðÖ[oáÔ©Søê«¯PªŸS'œ Gò§HGãâb`Õ*nßäÉ€··8ñð y€„§> ö:n›2@àD(55«î}z–””`Ö¬Y˜:u*rrrйsgLœ8Qä ‚¨ ¸Þ €Þ¶„=  a §@_|ñrssëׯÇÏ?ÿ WWWŒ3FäÈ[qŠ”{ŒËÊ€å˹}o¾ÉDT ðÔ×D;¦ptíÚ5ôêÕ °k×.Ì™3‹/FVVîÞ½+rt„-8Š?Eʈ=ÆŸΖkññaÓ_R‚<@Â׈¦ÀÀ‰PPPRRR““ƒÓ§Oãᇡ¨¨Häè[pŠÔsŒ++%K¸}¯¾ 4n,Nþ˜Û÷Üs@Ë–vÅ.HxêëÈMã4hèС8|ø0òòò°bÅ x{{cÔ¨Qhذ¡Ø¡a‚½{3gtm¥˜5K¼xyCKá CœF@ii)öîÝ‹µk×bݺuxôÑG±mÛ6›®Y\\ŒÝ»wãÈ‘#P«Õú(V¯^ÍcÔ„%Hxì=ƆٟAƒ€èh»†`wÈ$<¶x€h Œ0ÄiP‡ðé§Ÿ"%%¯¿þ:<ˆx#FŒ¨×õ”J%Ôj5’’’jú4 ü¥T›Ÿ DàèQ@ïm@úÙÂñ¡)0§@péÒ%|ùå—øâ‹/°oß>”””ØThÇŽØ´i:uê„W_}0iÒ$Î9ÇŽCrr2硵moëûS!)¶µclûq³?Ɉ‰ºwî~ŽÒÎËËCbb¢ÃÄ#Åö8 k^Ï2@ºvv¶øÿGi_¼xÉÉÉøä“OðÇÈÆë4hîܹèÔ©vìØÈÈHìØ±999HHH¨÷5÷îÝ‹ÁƒcáÂ…¸uëŽ9‚³gÏò5a ä{qZðãÜ>¹d’’’h_B),,äÍD B¡Ñh4ba *• pssãåz7nÜ@Ë–-qëÖ­ši¯™3gâôéÓØ¿? ..ŽóLDí<ó °k—®ýÈ#À±câÅCZnßô-Zžž€ 6RÉ¢R©-‹Õ¹NS³=88·o߯ÆqéÒ%4lØO<ñú÷ï_¯ë©Õj(•JÖ fÍšáçŸæ3l‚ ))À·ßrûä’ý!Ÿ€¶]y9k—•¬J4!Oœf ,33‘‘‘ؽ{75j„ôôt<óÌ3X`¸ÜÄBÂÂÂðßÿþ/¾ø"öìÙƒÏ>û K—.Å”)SxŽœ¨ 9|Ó{Œñ¢Elï/-;ƒ ~[‡ê -u€š#¸8Z¶l† ‚¿þú Ë–-ÃîÝ»qøðaÌŸ?¿ÞÛalÚ´ O<ñvî܉³gÏbûöíxá…xŽœ¨ ò Ðc|ý:`X“töl@NûS á±¥@Ká .N3–••…ØØXN_ûöíѸqcdgg#""Âêkz{{cúôé|…HÔª$NãÒ]#zRRR0hÐ \¾|Yä¨[ ð5Æë׳¥ÄZ€{EÕey€„ÇVe€}>¤V«1{öltêÔ /½ôRRR‰‰‰èÞ½»M»ÁQÊË•+¹}o¾ øúŠAÔy€}^mÞ¼ xûí·gžykÖ¬ÁÓO?+V`éÒ¥b‡HØy€„Gˆ1þâ @ÿ²>>ÀÛoó~§<@Â÷ˆ¦Àäà  ß~û sçÎÅK/½„÷ß={öÄ|€Ÿ~ú /½ô’Øá<@ áá{Œ««ÃïãÇs·ä[=@\ZI PTÄC`„Sâ𨰰ÁÁÁ5í6mÚ`ܸqèÝ»·ˆQ|B áá{Œwì®\ѵÝÜ€©Sy½…Ó‹@9+@;`«H¡ôþœ ,œqx´Z­ÆÁƒQpÏiyæÌ`ûöí5çŒ5J¬ðB–,ZÄm„…‰ AXCH«\®%;h×N¼xñpxÔªU+ìÛ·ûöíãô/Òû&äܨT*N–à>ÇøÇ3gtm¥’>”;yyyð÷÷‡««Ã¬:-(..F@@@½¯AFhB‹Ã¿S×!$Çž={hL`øã… ¹íáömy¹´S“””„˜˜šÜÜ\œ8q‚×¥ð4&_^Ò‡Äðð5ƇGŽpûfÎäåÒNÕgGô IDAT[=@e€/€æÏŸ“'OÖz­""û`˜ý0èÒEœX¢>P5hB‹Ã  ãÇãèÑ£xùå—Ѹqc±Ã!€<@ÂÃÇŸ> Xñ0k–M—”ä><@T šÐâðïÔmÛ¶aýúõØ´i† ‚iÓ¦!Œ–›H ò cl¸òë‘G€˜›.))È$<|x€h ŒÐ¢Ðh4±ƒ°„òòr|ùå—Xµjºuë†Ù³g£U«V‚Þ3..ŽóLr%-Õj]ßž=ÀÓO‹AÔ‡[·€  ]Û×—mêK0T*¢££eQ¡ßá !jñððÀÓO?bëÖ­Ø»w¯Ø!„lX²„+~"#½¨! ³ÂZŠ‹ÙƒN!€222ðöÛo£}ûö¨¨¨ÀÅ‹1iÒ$±Ã"xBß4ÄÆ–1¾qøòKnߌ¬ª.¡ƒö[÷¨4¡ÃáЂ еkWøùù!%%kÖ¬A‹-Ä‹àZÅ'<¶ŒñŠ@E…®Ý¢ðì³<%1h/0á±u/0-ä"'@‡BNN,X€   ( £áÜZxê;Æ·oŸ}Æí{÷]€:C{ u€@Ãá?Æöïß/v![Ö®åú#‚‚€—_/‚àªMN"¤y€„§>c|÷.°z5·oòdÀË‹§ $y€„‡@ ‚Aˆò O}ÆxÓ& /O×öóÞxƒÇ $y€„‡/e€À ¦ÀéC á±vŒ++å˹}¯¿øûó”Ä ½À„‡<@ŸPˆ #¾ú ÈÌÔµ==)SÄ‹‡ ø„"ò 5c¬V‹sû^z hÚ”ç $y€„‡/M  ðX3Æß~ \¼¨k»¸°¥ïDíHxøò5iÂ-åPT””Ø|YÂÉ Dˆy€„Çš1^¸Û5 ˆˆà9 Bu€„‡/RiœÑ¤,ü DD ¿üœ<©k+ÀÌ™âÅCBA> ‚!:äKÇØ0û3x0%@@„<@Â×0ö‘’$€Ñ!ðX2ÆÇŽÉÉܾY³„‰GŠHxøòÆ š“Tˆò %cl˜ýéÓèÞ] €$Õ¾<@M”ª¡¬¬ YYYb‡A¢pîðÃÜ>òþR†–²@‡B—.]ФI´oßéééb‡$;È$çªI‡ŠO íÍ›7s<@¶\ÏÅðñá_»–ßx¥}ôèQÄÇÇcòäÉHHH@uu5ä€l§À233Žââbøøø~øá¼ñÆÈÈÈ@S`öbÆ ´^`Lqa!Т{Ö²|9ðÎ;vN"ìÚµ 111´†€Ü¸q'Nœàm)|—.ÜÊç‡={òri§…¦Àd@HH<==qáÂ…š¾[·nÁÃÃCÄ¨ä ‰á15ÆŸ~Ê?À„ v JbÐ^`ÂÃg v=n›ŒÐòB¶H©Tâí·ßÆÔ©S‘‘‘?þøë֭Ë/¾(vh!8eeÀ'ŸpûÞz ðõ'‚ª-od] úƒ>À|€þýûÃÅÅcÆŒÁôéÓÅKv¨T*‹†¤1ãÍ››7uÇ}|˜"êO^^üýýáê*ëUA©¨¨@qq1x¹^Hà…R„!Íq!¿d…@ƽÇÍ›@|<С/÷# Y¿S===±páB³Ëà û°gÏšý1®ª–-ã? ÙÛHJJ"ÀäææZ窪²²t‚&#¸~½æç™—2‡\Ýù?Þ{èóè£Ào¿Ñ®ÀD¶&hK 4!E¶nƌѵÝÝË—°0ñb"«Ñh€œŽ 1:* VÛ~¯&M€¤$ 2Òök98r2AË:DrC£aÛ^è3z4‰Âªª€;w€Š ûÍcÊþðOll¬p¯®þú HLŽÊËm¿®»;ûíŸÍ‰Œî»-t0øÞ Œ]“Û®u?°uëX&hÃî ´™ ZQàT"‰³d ·\d$0dˆxñrá;rw®µ ukã©«6mê4 Kà P­H¡`»k4Ü%òZ””D"ȉ÷o>áHÔj¶ç×W_€ ã3Øç8Á/6{€²²˜ØILdúìÌ©P-[O]µk§·¶ÛyʤOîP°i0ظ‘ûBm&¨MÞâ#„ƒ!:äâŸß&MÒ7w2PË–À³ÏŠ—”±ÚTPÀþXj³<©©ÖÝ0,Ìxêª}{¶³­D±‡¨Ö …‚Mƒ©ÕÀÿþ§ë×A­[ó#! $€Ñ!ñÃׯï¾ ìØax„ñ»ïÊ~ÆC0êô••ü¡Ëòüý·u†åà`¶1§ö!²!Y „ðYl‚6D¡` †¥Zµde±$‚ú($ PR,^ ,] ”–÷òbâgüxûÇ&[Ôj¶‚Ö¸|ø0A–âçôéôïÏíA%ÁÁLËh«äå••€››/V(€M›Ø‹?ÿ\ן•¥Ëµj%DØ"D‡<@¶ñÍ7Àôé@f¦éã#FÓ§«Ð¥ ±äååÁÿæM¸j§µ’’¬«¨ìátïÎÄNÿþ@׮̼LÔ „ÈÍ”¾u‹µ5¶º=<Ü 苠/¾Ðõgf²LÐÁƒlUáp8Þ:GBvìÙ³G윒¿ÿzõž{δø‰ŽfŸ½ À_Ñ ÂùólUЈHjÓ…íÛo¼|ûmÝâG©d¥¸§O~þ™Ÿ”¼÷+ÔDâLjÜÜ\>|˜÷ëZm„6D©d^ ±c¹ýZtåŠ ÑBA BtÈd99ÀìÙ@|ŠŠŒ»¹o½¼ÿ>Û.‰°†UWÖ<¹¹Ö]ƒŒËNA½Ð†(•lL£ÑÖŸ`hEPr2‰ ‚!:䪛~¦Ne»˜â‰'€•+ÍWã§1¶8{V'vdŽX ÉàïçW2. † ÀÊjÐu¡T²ô¬Flݪë¿~]gŒnÙÒ†|Aˆªdž €)S˜EÄmÛ+VO>YûuhŒM Ñgΰ?HÉɬxÒíÛÖ]ÃÇèÙèÓIj5bÆC MÁ¢PÏZ@µ¡T_~É~ǾþZ×íš.D"HtH¢C˜)(ââØÖCUUÆÇýýÙT×[oY¶\—ÆÌ›¡/x²^ð4hÀœç}ú°ÇCÕVp'0âBy€l6A›B_mÛ¦ë¿vM— jÑ‚‡õ…A8ÕÕlEíܹ¦í&J%37/XÀÌÎDüóðÛo:ÁcͲt€)ÍÿüG'x:w¦ÕY„÷ fˆÖhX½ -éé:c4ùÂDƒ!:äOa$'³í+Μ1}ü?ÿa&èN¬¿¶¬Æ¸¼ؾX³†Õ °†F€Þ½u‚':Úâ]ÑmÞ Œ¨¡<@¼™ Máâ Ñj5«I¡E+‚’“I‰Õ"DGîu€ÒÓØX–7%~š7gŸ›¿ÿ^?ñÈdŒ339sX»±c-?Ç3eyú43=ïÙÃŒW;[,~¶X¡-;¶u"T C”›kzê¹Þ¸¸0/ЈÜþ«W™ÊÈàñf„¥(4mp¸¸8Î3AðÉÝ»ÀÂ…Àòå¦wHðöf;·¿û.ÛÊ‚0áC,ÛóÝwuÿÕ Òewúôa«´ ûÄI84sýef¡¡<ߤªŠU.ݹ“Ûß},dqùiáP©TˆŽŽ†J¥;Á¡\-AˆÀ®]ÀäÉlË SŒ,Y⟇ŽIi)3–®Yí`gˆRÉ–È ÄÏØ/F© á  7@®®ì÷V£aZ®\Ñ£ÃÂx¾)aš#DGß4´¨ÕÀÌ™À3Ϙ?;³=3¿ù†_ñ#™1¾v `x80nœyñÓ¨0mpù2+¢ôÚk‚‹Ÿ¼¼GèCˆ æàA–í6LZ4iìÞÍ–·z\½Ê*@nÞÌ*Bš#0?xýuZFLØŒÝÀ¾%$°ÕaúYÚ´4'È05EðM¢#Š >û 0ÀXütìȦ»ì%~œbŒžz hÝš-3'~¢£™8ÊÌdËèDüHx„ôÙu L77`Ç–ÅÔçÒ%&‚ìˆü DˆŽÓûSLPUÅfd^{ ¨¬ä6 øãûVÁwØ1..>ýhßž)Å~`NqC\]Ù·äC‡€S§€—_<=ío-Hx„ô‰’Ò¢A†Ó{/2$D0UUL\ýý7°w/3!~ø!üfÎDLEÿ÷s@¨P-P ¢>äç³U^‰‰ÆÇÞ{˜?ŸJÏ - X»–íš]›hhÒ˜0Ms‘‚ôt "B×nÖL„äKE›/ß»—Ûß¶-[`¨ÒLq÷.L*{6÷sn®é/føùa± Ä1^9o+rÚ ^– ¾}û"** éééHOOGnn.ÊÊÊžžµ™e„p8›h÷n W/cñÓªpô¨ã‰@À1þö[¶ÉèÆ¦ÅOÏž¬Üÿµk¬€DÅ@ {`o½¦ÀRS7Þ`{üΞmÙV`W¯²/aƒ¯:%,C– CâããäädN?e€CæÏgß ß5ýú;w}1u[RRXôwße )'N4®MT^,XÀ’±»wó³T!`ìØ±FÙÂ~8ú7ÌL¶g׎Ü~77`ýz6_ïè¶^ÆøâE¶Kõ«¯¯÷ww>ø8yÒi—±Û í&¸Ê˹ý‘‘Ìpß}âÄeWþü78{ÖøXƒl7ö7Þ ÍÍÉШPP k«T¦EFmhw}Y¹’}Q2‡R  L™ÂVÔ‡¢"&¬Ö®eû› ,_nù6<ä"£V³oN/¼`,~† a+½$/~îÞeŸÊÝ»›?ƒ³¯¶'’ø!$…aÈšjÐ¥¥¬DT+âlNü4hÀÖ¤¥éV•Ö??&´Núô1>ž•ŒÅjœ?_ÿûH@„è8Ò7;wØÒRSûâΜÉöôjÐÀþqÙŠUc¼?Ks}ò‰qþ?(øææÊ ç7H'‡<@Â#´¨ŸZ¥æÎeÓ\&˜-Z°lLf&{{ÕQ‡×*¢¢€ädV‡ÌÐ˰Z‰>LJû¥j!DˆŽ£x€®\aþݽ{¹ýžžÀÖ­l¶ÇDåx§À¢1ÎÍFf_]¯]3>þâ‹À… ìë$ay€„Gh`úôi`ìX&l>úˆ½…Lѽ;[bù2ðÎ;,k#Ï>Ë Ðï¾ËjèSUÅö-lÛ–}¦É'ý8'¤„#ì–œ <ü°ñ7·ààAàùçE ‹7êã­[ÙÚ¯¿6>Árùññ2Yë_?bcc(v’Fè½À€º§À4–í×UzøòK¶' !®®ÌwsìðÇlu–áòu¡ðõeYì3gØR{C²³Y©®Þ½Í¯F“$€Ù³a0`—ÇíïÚ•íäþðÃâÄeÒÓÙ§¡á×WöuõßÙ„ 0—º{øôS–=yê)6¥dІ YöåÊVý‘G„·6Úµc+Âvî4=c}èÛ ~Ò$ãÊr€!:by€ªª˜‡÷õ×WO<û,[ñmj.Ý1cµš™¢¢ØrC|}u]¾ÜöÒ²2<@Â#†èÜ9æÿ gŸ—.™~]«VÀêÕ¬~Ï’%Že‘‹egÏ6®TQ]Íâ¾ÿ~–ä•Óºp@„èˆáº}xüqöN…øøcf$ôô´{X‚Áã3g˜ÙiÊöµVOOVóçÄ V˜„°ò  ßg¤æç›>¿O¶8ââEà­·Øô“#âíÍÞÚÿþËl~†Ü¼É 4>õT ÔjyLuS Z :@Òä¶œýòen¿¯/³Àl1²2àÃÙ†B••ÆÇ{÷fkxõKÏ„ÌHKÚ´©ý77æï™2…M!9#ßLžÌfÁ ñó›ŽÂBKa%e€YñÓO,ùa(~""X}ÉŠŸƒÙ´ÖÇ‹à³Ï˜œÄ!s §Àô dÓHéé¬Pª³Š€•û¸pQ4ÎvË£¶ BtìåZ¶Œe~ŠŠ¸ý}ú°‚ÇQQv Cxª«Ù'Û7ßÓ§> UL ËÑ2t([ú6a4´ò =<@>>ÆËÔÛµc‹%22Ø4’T¼žž@\ûì—¿Z DˆŽ=<@sæ°•†uý&L`«$7<a()Žgœ×^cé-??¶[ûsϱé®ß~ÃÙîà`¶4ä»ï¤ói.2ä{x€Ý[bÀ¶[ûùól`//Áo- lJìLjˆj±Ã±äªòIƒï¿† ã®npueåãß|S¼¸¬æÖ-VyíÔ)ö|ú4«xfín¯¼Â„Qm»-„ŒÙ°YâÚ·;ûsýz:uƒ¼¼Z61“®b@Brù2+`¬/~5bÉG/®ZÑhX}±sê”u›™¢U+frމá'N‚(P›U4ÜÝ5ps“GuD@„è¨T*ó~ÝÒRà¿ÿåørueU\{öäývõ£¢‚ÑÏêüó±QÉZ€è蚇*, ÁÿùBòòòàïïWcÁ¨¨¨@qq1¨"9ÁôN%DgÏž=‚l‡ñÆLKè³d‰ˆŸìlVqlÇ&~L-I·†–-™ÐéÔI'zš7眲güF™AIJJBLL m‡! ¹¹¹8qâ„àÛaò€<@µ@ çåÿÆçöÅÆ²©/Q¨®öícýø£qéiKpsc¦½Ì¢£Yí}‚ P©TˆŽŽ­B¿=¡ !9NždYõ¹ÿ~àóÏEæêU`óf–ñÉʲüu~~¬n~V'2Ò¸Ž=AQ/H¢Ã§(?ŸezÊÊt}ÞÞÀîÝ@ƒ¼Ü¢n**ØòòÿýHL¬{s°0ÈÑ žˆ^ëòå³"tHxÈDð Õ"D‡¯:@ ÛÔüêUnÿgŸÙ©Èá¹s¬6~H0jpà€yñ ¬]Ë6àÉÈ`Îì?†î»÷¢„bì·&7¨ðØ«!ÈT är.,Þ{Û÷úëÆžòÊÝ»@BËö=Zû¹~~l›ùñã.] Š ¢~ˆ œŒÄDàý÷¹}]»Ÿ|"Ð ÿú‹ÕÔÙ¾¸s§ös{ô`¢gÄ6GAˆ Btlõ§de±ÄŠ~AäÀ@`×.ž=ÃùùÀÖ­,Ûs¦ŽBa/¼Œ<ðAÔò y€„‡<@ŸÐ;•[êUVÏ<Ãv‰Ð¢T2bP §~h4l'õM›€o¿åº« Q(€þý™è:Ô¡Vl ÎEŸ IDATUk‰Ðáu€4Ð@­Q£Z]jMµÅÏõyMµúÞëîý¬ZG…ögsÏõ=§ ·Ù©Ù8h ÂüÂæ† Ÿ (d²{9Á/$€ѱåó´iÆÖ›¹sm J¥bK×7oÒÒj?74xé%¶ÇVË–6ÞXHüOll,o×Ê-ÉEÖ,de"«èÞ³^[U¬BEu…‘ Ñ@–Î Û7Ôüì¦tC¨_h 2õö †‹ÂEĈ G„á´$$«Wsû{ÌØ d1Ö+tu bÞžúp%ê¦J]U±ÊHÐè·oܹ²ªZ2‡Ju%Ò Ò‘^nö… š5hfV …6E¨_(Ü”nö œ@„èÔÇŸrá›iÒ'<ضMYÅÕ«¬Jâ_Ô]¬°uk–é;p"O y€„¥R]‰kÙ×PåVU‰y£*VA­Q×}AÂ4Õ*xYù2M52‹2‘Y”iöò â£6m0¬Ý04÷çc>Ý9¸Yr•MmÜžÇI DˆŽµþ”âb¶Éiq±®ÏÝmsa±ýšb…žž¬>ϸq@ß¾¼×è±ä2OYU Ë QXVhò¹¨¼Èì±Â2v¼´ª8 %‘ú¹(\à¢t1zV*”fYûlêZZŽB¡€Š:Ÿ-=@ÍÏ%y%¸rî ŠY\Qlv,¬A rîæ çnþÎþ»¦Êþ)èÝ¢7Fwg"Ÿ¿‡?/÷s4Òn§aÙËðÅ©/àÙØSìpìÕªªä˜<û,[}®ÏÚµÀĉ¼øüy&z¶lòòj?·C&zÆŒ5ªw¼„ýÐ@U±ªfJ$½ ªbU­â¦¢ºBì°ðqóA¨_hÍÔŒþs˜_BýBÑÀ½‘8Q*äWÛ¶°¼°&˦}hÅ‘6—WZÇ{ÝB<]=1øþÁÓq žhó„$¦ÌNfŸÄâ#‹±ëü®šì¤ßï~(L”~QOÊNÅš5Æâç¹ç,?ÿüê"ÖU¬Ð×—Uq?xøa›b%øG­Q#»8›#p®\«ùùzáu”W—‹¦Y´Ó,æD¶OªY!ð÷ð‡´oÒÞì9¥U¥F^+ýéÉÌ¢LäÜÍ©sz²¬ª »Îï®ó»èˆ‘#0æÁ1èÖï–àüvõ7,:¼¿^ùUìPDƒ!:–úSަNåöEF7ÖñÂíÛ™o§¤Äü9<ÂDÏÈ‘LI gñUkªqãÎ ³'£(Ã!36. øVû"°Q ÂrµúÏÍ4“DÖ@,ê[ÈËÕ ­Z£u@k³çT©«]œ]#ˆÒn§aç¹8¥:eòü¼Ò<¬?±ëO¬G«F­0ºãh<ßñy´ hcUlöD­Qã»”ï°èð"œ¸qÂìyŠ ç›æ¯4V 4f6lØP§?åÖ- sg SÏÃØ ðçŸ@»vf^T] Ìœ ,[fúx@›Þ7ÎN›…‰‡%clªÔUȺ“U«À©RײúN\•®ðóðc™Oÿz=ûºûb×®]¢×’:7nÜÀ‰'ðÔSOÙížçnÃÖ3[±íì6\/¼^çù„>‚1ŽÁÈÈ‘hìÝØÖMEu¶ü³KÿXŠ‹yÍž÷`ÓñjÔ«øà™ Ê–þV²@GÅ?þ•J…ž={bÈ!hÜX÷ KÈ1P«Çg{‹ê“Àv—0I^žnCRCbbX¶gøpÀÃ÷xåL•º × ¯ãZá5“'³(ÕšjAchâÝ-¶¬y4õmj$Zô› ñÒ@ ~¿ö;¶žÙŠ]çw¡ ¬ ÖóÝ”nØz Fw§Ú>OWû‹ïTÜÁ†°òèJdg›=¯O‹>˜Ùk&¶(«½Àd-€zôèîÝ»£mÛ¶HLLÄÁƒqýúu¸ß«àKÈ1xï=¶Ñ©>“&Õ²Ï×éÓÀ°a@z:·ßÝ™ˆ&L"LÙQVU†³7ÏâTö)œÌ>‰“Ù'qöæYAkØh=4ú§EúŸý[ÀÛö[#„¥¼º{/îÅÖ3[ñÓ¥Ÿêœ–õóðClûXŒî8}[ö¼ruÎݬ:¶ ëO¬7+ÔPàévOcFÏèÖ­¦ŸL¨¨¨¨;ЦM|øá‡5j@ö¢6Ê?C†pW©÷è$'n¦¬Û¶±)­ÒRnH°{7Э›‰I[=@ÅÅ8­:]#tN©Náü­ó¼OW) @°op­GŒoÒ–@{ #î–_–çv`ë™­8rýHÕ¸ÃýÂñ\‡ç0ºãhDñ;õ~%ÿ –þ±ñ§ãÍ~qSºáùŽÏczÏéx ±ñ>…r@²~§ê‹µZ¢¢"¨ÕT¤ÌÞ˜«Q“žÎ,:úâ'(رÄø©®¦OV¬0¾ALü8 X(¬©t»ôvÈÑ žKy—xÙfA©P"¤AZø·0)ršû7‡‡‹sNK:Â^`R'77×î ºhäÙ¯vy¯vyééøúìרzf+RrSLžŸQ”ÅGcñ‘ňŽÆèŽ£ñ\‡çÐÌ·Y½c8­:ÅGcç¹f§˜}Ý}1¾óx¼Óý„ù…Õû^RB~E#̰aø¹¹áé§ŸæôÇÇÇ#..ŽóЇڶ·õÿ0k——³b‡ùùºó]\€ãjp½Ü\¶ÆŠà^ˆ{è!–.º'~áß+F[;Ɔǧ͞†/ýˆÿÆ£áÀ†\ˆ_ Àô_§cûÚí¸˜wQ'~’ÁÅD;È'ÿiþŒé8½¯öÆæ§6ãÀ övfWÏFÆ” ~ù0¶ß ×ß]1®ó8 ¸oÚ´ÁÂÚe<„hÇÆÆbÍš5Û7n䈱ã1lÇ9ÿ™ƒ /àÄ„è–Ö M}šêNH朎ÓÛOcÚ/Ó¶" }õ†½>ŒSر®û<·D§Ï:aû¿ÛQd ~’ÆÞ1?f>®M¾¿£~ñ£½ÞÞ½{‡¾}ûbÙ²e¨¬”G%hYOiùþûï1vìXüüóÏxX¯ö‹ö—Ãð—Žž Øìú,XÌžmpâ©SÌïsí·ßÃX·Ž-'W ®rü:'³O"çnŽM×lîß›uF§àNèܬ3:7ëŒ!Žø!쇡?%>ÞXü  Ìšeð¯¿f«¹ ý>¡¡lÊë‘G‰×Ð@ƒTÿà·«¿!)= ‡ÎB¡[ý+»*J´h]#r:wB§fèEÓ=ZÈ$<Žèª … oõ8oõ8=){°õÌVüzùW“ÓU%•%Øvv¶Ý†¦>M1*jÆ<8‚:`ë™­Xrd RóRÍÞ/*( 3zÎÀ¨¨QpUÒïbmÈztV¬XO?ý@ûö櫈¢ïOùçà7¸Ç#"ØÎ5[pUU1¿ÏÊ•ÆëÕ Øµ hÚÔø˜Ä9wë’®&á·«¿áൃ¸]z[wðYvW¥+"›D¢S³N5b':8¾îÒ+É'äGôYƒ›žïð<žïð€¯¾^}ÕØï|û-е«=Bµ+™E™5bç·«¿áZᵺ_¤G€Wú´èƒ˜ˆtðî€>Q}(5. ägôYƒ ôjÞ ½š÷Âê'Vã§K?aë™­Ø{q/Ê«ËáíæqÇaj÷©hîß\ìpz§¢³té¬ZÅ­QóàƒÀ§Ÿ‚ù}¦MV­2~aïÞÀά8ȹ›ÃÉð\º}ɪ×ûyø¡w‹Þˆiƒ˜ˆ<ØôA(¬Òņ Ð7ª¯QZÈ$<Îî²ww m7CÛ EAY~½ò+úEô£…<"[%Hx²³N€½ÕØþþÀ‰@kÿ[Ìlü·ÞbEøÛöÍ»7qèú¡ÑsþÖy«^ïãæƒ^Í{!&"1-cÐ%¤ Mgaä"))þþ›-mÏ1(E´.üè7 ÈÈàôô6l^|Ñn±Ö‡âŠbdeàzáudf £(Ã蹤²Äªkzºz¢GxÄ´ŒA¿ˆ~èÚnJy¾ ‚ ø†!8 pñ"pìpü8{>{–Ín1TX éÓ¡E[€^¯e{Ù„‡ß}tébÏð(¯.7+j´Ïuím î.îx$ôô‹è‡˜ˆt ëVïÕY¶îFÔ y€„Gê ¾Ð;•à¼<àÏ?u‚çøq  V=°Àkè×» ‹J§/®6>¥o_¶ X“&Â}*unܹQ«¸©ÏrsKpUº¢kHך)­žÍ{ÂËÕ‹—k[³Q?È$^^ÀÆÀèÑõŽW­QãൃHø7»/ìFnIn½¯e .÷G¸_¸Ù熞 í Aa Â,ÅÅl5–¾à©ï€€¶5×#0ÑóÈýùh˜“ > Õ{ïÁµ¼œû‚-X}Ÿš*ˆ–£G®A¹ì<·Óæ ? qUº"¤AH­â&ÈDZ–æ“HxÈ$<ä"ø„Þ©2æöm¶ ýÆ öÐþœ ¤¦çÎjµõ×uscu|zt­Ä£WðPƒT„ÜIe= ¬Knݪ9Ÿ9€ôè×HH7¶ê¾Ç³Ž#áßì8·Ãê*ÉZ” %šú4­5{ÓÌ·YM}g<@ÂC á!Á'$€$ˆ¾°1%p´?&]êK§°[xªm*z6NE¤K ‚ S¡¼” lº¢¿ÔË,œ?Ëï¼,Y¸XVÏædöI$œc¢Ç’=tü<üЪQ+³'Ô/T’KËIüy€„‡<@Ÿr"òóMgkôû²³ëçÉ© ”£5Ò핊˜TtòNEDE*üU©Pfæ™6ÞÀË øßÿ€çž«óÔ³7Ï"áß$œK@Úí´:Ï÷÷ðÇÐvC12j$úß×_’‡ ‚°@@Upõ*píšy#”°Ñâá&šà‚paÈD;E*òMA[¤¢ñÝkP¨«R—y¸¡«+pß}@Û¶P…‡#øõר(³§§ä¦ á\þMÀ…Ü u^Þ×ÝOµ} ##GâñÖË~wsò y€„‡<@ŸÐ;ÕNh4LÈ\¼È}\º\¹Â–“ó‰JÑ·j©çf.7ÑTy Õ7áQm¢*±ÀiÒhÛÖøqß}Ì,`φ xÍ„ø¹œ¹&Ós&çL·òvóÆ 6ƒ02j$žló$o5t¤y€„‡<@ÂC ‚Oh/°Z¨Ï^`yyLÔ ´4àîÝúÇâòZÅŒþsâ|4Åõ¿™ÕÁy­[›:Yu©k…×°ãÜ$ü›€¿³ÿ®ó|OWO l=##GbHÛ!ðqó©ï¿‚ BöÐ^`D­Ü½«9†bçöm˯£„ÁP! ™hkE–Yaã‡"Ë/,”¤ 1-rZ¶”õ_•u' ;ÏíD¹Ï<Mÿww<Öê1ŒŒ‰§Ú>?¿zß› ‚'$€êàâŶX¾œ+v²,Xa­„M‘ƒ0d"5Ïú?‡à\Q÷*)»àéɦ«‚‚tÏ-[êDÎý÷ Øt‹‚²¤æ¥âbÞE¤æ¦"5/)¹)8wå4¾µ‹W¥+x#£FbX»aTDÐJÈ$<äò|BïÔ:øæ›gñÍ7Ü>4&Å¾È Á ¸gc5¸¹±::AA\QcîgÅ–*u®ä_Aj^jÈÑ>›ÝC+ÀCÆÝ. ôiÙ##GbøÃÑØÛºº@„ò y€„‡<@Ÿ¨âââ÷Ac#‘Š,¸£Â¾Á(•LÐfiÌýܰ!ÛoB nÞ½iRä\É¿‚*uý³Z (Ыy/ŒŒ‰Øö±hêӔǨ ‚ ˆÚ Qü%ÜÅ &XÂÃÙ#8ؼ¨ ´ÉgSʪÊpéö%#‘s1ï" ÊjÝÞÝ*Pà‘°G02r$ž‰|¡ By»6AA˜‚èÄöÆýÙÃúú4jeUe5ÒªRö\YjÔ§ßoM_nI.®^‡ZS½0ÌàîâŽVZ¡mã¶hضæ9 :íZ¶ãí>„1äò y€>¡wj}iÐÀXÜ>¼½°LJ~Y>òKóq»ô6û¹øòOßk—æ#¿,%•%&EŒa_Eµ§ß¬$Ø7˜#p´Ï"à¢0ÞâbÆ h÷ !!ðHxÈDð y€j!..C¿ÿÑC† :,wšø£ IÜ ô@®k¥±¨¹'dôEÍíÒÛ(«°„³Hx¹z¡M`“B‡–¥A8'ä"jèóôT»¯ÀÝì»@¶ØÑØó 38m·EsÿæP@8“5AA  :(RAÌÕìæðpñ€—›<]=áåÊž=]=kúlíoàÑ #àíæ-ø¿…ü)ÂQ¡V£T­Æõìl¡@µF£{ÐVk4PÐhXI˚ǽvmÇ4÷^oî˜Ñqçª Žéc(Ùz+%µgÁkJ àæë ‹ ª4TÝSÏU÷ÆÊì1ƒsLJûRó0hsŽ›8WYË1ÀŽ×rýë»(p¹wM¡~FU*îÞ…WÆ&ÿ¯kû1õ{W×yÚ£òÞ³‹ÞÏÚ˜ŒÚúçóüz@WçV;y£ÑïÓ;fÔgð£×Þ;–_T„ê¦òX}Kȸ)ÝÐг!¼ÐÈ«y62ù³¯»¯E"ÅÃÕCRÙ¡ý)ej5Š««q§ºåjcS·áIÃYaSsÄF¯1uN×Ñ(W«QvO¤hŸK««u?ÛÐ_¦VC­áÿþ ß„°<têøÑ°`äå©©@bG"iü:v;»@ÈB” %z64/¼îµkùÙ×ÝWìð}ñ£+ú;†íªªºÏÑû¹šln$~ìAŸ>bG }Iü¼A¨&aâfÄÁßÓß©².æ¦ Ó¥µMSh³ej5çg“m¦Öãµ]£TOôT‘X!‚ ì  :8Þ«U¨Ôä£J£Aå½ù÷šgµšÓ¶äÃgí9ææ³a¢ÏT0=ãðܾÍj&¼ã¦PÀËÅîùùpoÜ.Ðù*\îy ønk= |øJøðµèSÛt§¦¶ó,xMI~>¼ýüàæê ×{ãázïábø ˜=fꜬ,³¾*Çç#š¦!–RBfR*š™¹Ib¦hj*Â"O‚À¢<º²r(ósÙåùaÜÝÏë̽sw>܆3wî4j£ÑöIòÁ$À`%@-:åìŒS99|‡¡ßΜéÒ[4¦Ìanl ‘@ÐìW@}k“ë´°¬ís´µé!à9¢'ÿs_?‰õ¹Æõ­(º¾aàdTTùúj‰’t–„óçi .VXXˆÔôtü÷ÅùEoaä¿ÿòF· y€š±zõj„{yñ†AéñT²bñäÿ¦–µ®#jÔ µ$4„B4Ñ<@Dç5÷h+´”i» ÜãÉÕ„†¦—Œš¯of¹qÛ†äÅ„’B!Ý€ xæåaÒ„ 0yê¼IãÿíåMÔ7µNS÷³¡¥LÛ2 ýV̳®¨¨½h .Es-u=zX×£w‘Îdð?©………HIIµµ5Ƈçž{N­~rVVñaˆ‰‰ÁŠ+øC¯Qw½Ã‡cúôéøL"LJ;wî %%sçÎå;¢ :JNN†T*…··7nß¾ÚÚZüþûïÓDfݪ¦¦†ïôõq×S*•“_’ÎÅž< FHgðŸBCC±zõjìß¿§OŸ†©©)öïßÏK, …¢C?Øm°Öšu›[§©:måË=z„’’’·ßT*ÊÊÊÚݾ-û¨¦¦•••Í®ÓY}|ïÞ=(•ÊVvWëȶۺZÚVeee“É_Sû¨ªª UUUjeÚŽ[]íã¶¶çó|ÑšŸ£®¢í8h‹¶ôqkÎ-]ÕÇ-}¶>3ب¶¶gΜÁŸ<~mdd„3fàĉ¼Äsøða¶»}TTT§®ÛÜ:MÕi+o\VYY©‘dvä$Ó¥¥¥ˆowû¶ì£«W¯"%%¥Ùu:«?Žìììf×ë®>Ö¶í¶hë>ji[)))¸zõªÖº¦öQjj*RSSÕÊ´··]]] •JÕš°;¬#}ÜÖö|ž/º:TWW·OgÐv´E[ú¸5ç–®êã–>[Ÿìcð9990`”J%zôèøæ›o°{÷nœ;wÀãÇàøá¸ººvy¾{÷.ÌÍÍaffÖäzÝÕÇMÅØZmÝG-mK¡P@(j½­ÝÔ>ºwïÀÊÊŠ+ÓvÜ6ÞöÍ›7ѯ_?ôìÙ³U±wDGú¸­íù<_4ÞG(**‚‹‹K«bïmÇA[´¥[snéª>n\¦T*ñÏ?ÿÄU!ƒôàÁ€±±1Wfll¬v200°Û~iB!Ï‚öþ!®k 6j˜­µ²²’ËðËËËaggÇ­ãêêÚ-W!„Ò½ 6êÝ»7ú÷ïsçÎÁÇÇpñâE¼ØÊ)Ö‹‹‹ŽÂÂB :«W¯æn¥‘Î¥P(õë×óŠ^:zô(Ž9Â-‡……©Ýê!#99û÷ï‡P(Ä—_~Éw8zçÔ©SøñǹåÁƒcÙ²e^·n7ŸÊÒ¥K±bÅ š›© Îž=‹åË—k”—••áí·ßÆàÁƒñꫯâï¿ÿæêòóó‘““CÉO+Éår¼óÎ;å555X´h† ‚I“&áøñã€íÛ·Ãßß—.]BAAAw‡«³æÍ›‡ŒŒ òèèhŒ;ÇGXXêëë!‹áää'''ìÙ³aaa¤R)är9rss¡R©`aa   ºšÙ‚ÈÈHxyyaß¾}S8?~!!!ضm —ãÜ RIDATâââpâÄ µÛãüñàììÜÝa딤¤$¼ñÆøßÿþ§ñGiAA|}}±xñb;v >DPPD"¼¼¼””„àà`¼öÚk–Ëåøè£¸e777ܼySm²±cÇbãÆ]¨³°°€——233qùòeµ:¹\‰Dsss®ÌÍÍ ééé8x𠆊ššdeeéüeí®Ô£GxyyqÓh<-//0pà@®ÌÍÍ iii˜>}:¶oߎÀÀ@>|±±±Ý³.1bh}±¬\.‡T*å–ÝÜÜpëÖ-¨T*ÄÅÅaâĉjOóíœáìì¬u®²ŒŒ ¸¹¹qË}ûö…¹¹9LMMqá 33Sm]D P JJJÔ,ZXX ¶¶–––HHHÀÝ»w1mÚ4z9_ܽ{W­Åb1ª««Q[[˽œvÁ‚|…§JJJ4^ô+‹QRRSSSDFFÂÌÌ qqq4@·Ÿ+€Çç‹’’|ôÑGصk¢¢¢°uëVzajh;_0ÆPZZ ¥R©öÇiŸ’’§»Äb1êêꉨ¨(ôë×;wîä)ÂÎA P ÌÌÌÔÆDÔÖÖB(â…^Ðùì÷YѳgO>611Q›Œ‹ ŽiÜÇÀã»b±3gÎÄÌ™3yŠL4>We±X ¡Pˆ¥K—ò™~Ñv¾ÿ‚ž?>_a长Îîîîpwwç)²Îeðƒ [booÛ·osËyyy°··×z+‡´¶>vpp >îDööö¸sç=zĕݾ}}ûöå1*ýbooêêj”——seyyyÔÇLÛùÂÊÊŠ®\v"{{{äççsË Çµ¾ÍF P ¤R)¾ýö[Ô××bbbàççÇsTú…ú¸ë=½zõâ^4š››‹äädÌ™3‡çÈôGß¾}1vìXnî…B¤¤$:–;™T*ELL êêêÐù¢+øùù!)) ÅÅÅ€ï¾ûÆ Ó¿'ëø…ý,˜5k“H$ ëÛ·/1bWW]]Í|}}™­­-sttd&L`¥¥¥0è™  !„b˜è!„B %@„B18”B!ÄàPD!„ƒC !¤SäääpSð%//¥¥¥¼Æ@Ñ ”¢‡òòòàããƒwß}W­|ûöíHHHèôí©T* 0@mÀî”›› Lš4‰f\&„´ %@„衪ª*¤¦¦"11§Nâʯ_¿Žììl#ë?þø#ÆŒƒ[·náÀ|‡CÑ”¢§LMM±råJ|öÙgZë¯\¹‚¯¿þZ­,,, ÷îÝÄÆÆâôéÓØ¸q#°sçN0Æ·Þz QQQ¨®®VkóæM|ðÁ˜;w.~ù嵺ääd# {÷îEÃd.\À¾}û››‹­[·"..Nk¼÷ïßGXXüüüÂÅ™’’‚½{÷";;Ÿ~ú)®^½ªÑöÆصkŠŠŠðÅ_p³5çççcÙ²eJ¥Ø¸q#”J%àܹsØ»w/×þðáÃ8yò$·¼mÛ6deed2.\ˆ·Þz kÖ¬áfÏ%„<Û("D-X° …‰‰‰uiiiøþûïÕÊ"""pÿþ}ÀÁƒáçç‡ÚÚZLŸ>puuÅÙ³gáããƒýû÷cË–-jí—-[XYYaΜ9¸páàèÑ£˜?>F ©TŠÈÈHlÞ¼ÀãD,$$³gÏFVV·ý§©T*xzzâúõë DFFƺº:X[[ÃÌÌ vvvpuu…X,ÖhŸ‘‘•+WÂÇÇiii¸ÿ> ÆŒƒÊÊJH¥Rüúë¯Ü«AŒŒŒʵ Åš5kJ¥!!!°¶¶ÆŸþ‰9sæÀÓÓo¾ù&Š‹‹qþüùVïBèmð„è1¡Pˆµk×",, ³fÍjsûððp,Y²Àã$"??{öìXZZbóæÍX¹r%·þ±cÇлwo@II ¢££áááU«VaݺuˆD"„††bÅŠ‰D‚Ó§OÃÄÄDk‡BAA._¾ @€7Þx¶¶¶øé§Ÿàïïþýûãå—_Ƽyóšü^lmmqîÜ9ˆD"À¦M›àààÀ}?S¦L­­-.]º„Ñ£GãÁƒËå`ŒÁÆÆYYY(..Fff&† kkkddd@"‘`öìÙ077ÇÌ™3ÛÜÇ„~Ð Bôœ¿¿?D"öíÛ׿¶ÆÆÆÜ׿ææþÿ”aaa¡ñÄ•‘‘÷µ‡‡ <¾WWW¸ººbùòåèÙ³'·®M“ÉÈårŒ?žÛ¾‘‘<==!—Ë[ý½XZZrÉOÃgNœ8Q-†^xr¹ÆÆÆðòò™3g¼þúëHLLDJJ &Ož ˜5klmmaooÉ“'#&&=juL„þÐ Bôœ‘‘6lØ€àà`xyyqåB¡>lÓç4·ÜXzz:ìììVVVˆ‰‰Á¸qãZøS¬¬¬4ogggÃÛÛ»]Ÿ×ð™ ãxàÑ£G¸}û6¬¬¬<¾"tæÌ\½zGŽÁ­[·°jÕ*˜››cÑ¢E€Þ½{ã·ß~Czz:þúë/„……A àí·ßnw\„îAW€1Ó¦MÃÀÕÆ?7oÞDYYêë뇺ººm§aPôõë×qìØ1øúú|}}±nÝ:®¾¦¦lõçz{{#==2™ ÀãÕiii˜:uj»c}íµ× “ÉpíÚ50Æ ccc¼ôÒKÜ6:333ôéÓ¯¼ò 233qöìYxzzN:…ììl <óæÍÃðáÃÛ”TBøC !bãÆjOmõéÓpvvF¯^½pòäIµ[^í1uêT<ÿüó1bæÍ›Ç;Ú¼y3zõêGGG¸¸¸ wïÞjOUµdРAسgfÍšGGG̘1_}õ\\\Úë”)SðñÇcÔ¨QpttħŸ~ŠpW€† SSSøùùx|Åëõ×_‡‹‹ wûîÎ;9r$žþy 2={öDPPP»c"„t#Öð,*!„Bˆ +@„B18”B!ÄàPD!„ƒC !„B %@„B18”B!ÄàPD!„ƒC !„B %@„B18”B!ÄàPD!„ƒC !„B %@„B18”B!ÄàüÄ›ÄJ" »IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/compressed-select-cache.svg000066400000000000000000000760651416254111300261540ustar00rootroot00000000000000 Selecting with small (16 bytes) record size (file in cache) 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 0 2 4 6 8 10 12 14 16 MRows/s No compression zlib lvl1 lzo lvl1 bzip2 lvl1 PyTables-3.7.0/doc/source/usersguide/images/compressed-select-nocache-shuffle-only.png000066400000000000000000001520351416254111300310770ustar00rootroot00000000000000‰PNG  IHDR@°AàÚ²sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝgXg×ðÿ²°€ô.E¥)R"¢5бcìX#FQñÑhbõ±c¢‰±ÄÞE-&j"6éÒ{ïå¼x˜×e©º€Êý».>pÏÌ=ggvgÏΜ¹‡GD†a†a˜6D¢µ`†a†ii,b†a¦Ía Ã0 Ã0mK€†a†isXÄ0 Ã0L›Ã †a†aÚ–1 Ã0 Óæ°ˆa†a˜6‡%@ Ã0 ô9,b†a¦Ía Ã0 Ã0mK€†a†isXÄ0 Ã0L›Ã †a†aÚ–1 Ã0 Óæ°ˆa†a˜6‡%@ Ã0 ô9,b†a¦Ía Ã0 Ã0mK€†a†isXÄ0 Ã0L›Ã †a†aÚ–1 Ã0 Óæ°ˆa†a˜6‡%@¨ŠŠ ÄÄÄ ¬¬¬µCù`ÅÅÅ!??¿µÃh”ääddggsÿÇÅÅ¡   Q˦¥¥!..®¹BãbbbP\\ÜìëjŠòòrÄÄÄ 77·µC›¬¬,¤¤¤´vR×û/##!!!())™‹¢¢¢–¯ÍÈËËC||ü{õ‘››‹˜˜”——‹)ªK€šAYYöìÙƒ¢k×®pttÄ—_~‰“'O‚ˆÕGrr2 &–˜>|ˆ{÷î µEFFÂÙÙÿþû¯XÖ!.×®]ƒ³³3222¸xñ¢È|ÖÖÖøý÷ß[:¼w2jÔ(lÙ²…û¿G8}útƒË½~ý;wÆ¥K—j>yòäz÷ßéÓ§1uêTXYYÁÊÊ uÎ[PPüý÷ß ÆU—ŠŠ ;v iiiïÜGM|>«V­BïÞ½4~èÖ­[‡I“&½óò?þø#&L˜ ƈj CCCœ;wŽkËÏχ‡‡tuuѳgOœ={îîîØ³g7±±1®_¿Þìñ5$$$¤Qq$$$ÀÙÙþùg Dõn~ýõWôîÝû½ú(**Bÿþý±dÉ1Eõqc ˜UVVbÔ¨QðóóC÷îݱbÅ 4ðôôDaaa«Äµwï^øûû µÉÈÈÀÔÔrrr­S]”””`jj IIIU ‘··w+GÕ:¼¼¼àéé‰Ù³gsm………8rä¼¼¼ð믿֚lTVVbΜ9˜6môôô°lÙ2Œ?¾Ùã---ŸqãðêÕ+±õÉãñðÓO?ARRË–-[¿³öíÛ£K—.;/// >óæÍãÚ._¾Œàà`DEE¡  #FŒ€‘‘455›=ž¦:uêV®\Ùà|¦¦¦PPPh¨Z––Μ9ƒ]»váÆ­N«“lí>5OŸ>Å… ¸_Eo{ô褥¥…Úâãã 555ØØØ@JJªÞþ333ñøñcHIIÁÆÆFä[ZZŠW¯^!-- ¦¦¦ÐÓÓCnn.òòòPXXˆ˜˜€¦¦&´µµáãã@qq1’““ѱcG$%%áÅ‹°··¯5AЉ‰ÁóçÏ¡«« ###äååqýÔ”ŸŸÌÌLèëësmÉÉÉPRR‚¬¬,p1êêêÂÒÒ>>>PRRBqq1222¸Ë  ¬¬ eee®¯‚‚A__õn¿j/^¼@BB455annÎ%[‰‰‰““ƒ@ À¿ÿþ‹víÚÁÚÚ’’’ÈÉÉAPPºuëmmm¡þRSSŽœœhhh Gx¿ßçÏŸGHHˆÈÙ¯¼¼<\»v <¯Îe?Žß~û Ïž=CçΛ¼îôôtŸ›FDxþü9’’’о}{˜››sÛPJJ »ví‚««+–/_]]Ý:cÏÈÈ@EE455QQQÄÄDèèèp늈ˆ@hh(tuuaee%²³²²ðâÅ ðù|˜›› }Æ ðèÑ#‘Èç/''ÐÑÑ!11€ððp¼zõªI‰KBB^½zYYYtïÞJJJ€/¾ø‚»TYRR‚¤¤$‘eeee¡¥¥ÅýŸ””„gÏžAQQ=zô9Õtùòe<{ö gΜáÚ233qâÄ ØÚÚ¢´´‰‰‰ÐÕÕÅ¢E‹ //_oyyyxòä JJJ`cc55µz燒’$%%mmmÖÚï£GÀçóaccï ‘’’îØ¡ªª EEE‘>ÔÕÕáããÃ}®«·©¾¾>RSS ;;»F'HÉÉÉxùò%annÎm댌 ¼~ýYYYPUUE=DŽùD„ððpÄÄÄ@__¦¦¦"ýGDD 66}ûö­õ;#::/_¾„––lll„ŽEæææøú믱f͸ºº6êõ|²ˆ«/^º|ùr½ó•——Ó¼yóHBB‚,--IVV–,--)99™ˆˆâãã =þœ[æ¿ÿý/ÉÈȱ±1©ªª’¶¶6qÓ=zDFFF$!!A:t G_}õmß¾III‘’’)))Ñùóç)%%…Ðãljˆè?þ 4}út’’’"eeeRTT¤ýû÷s먬¬¤ùóçÒÒÒ" ’““#KKË:_ë­[·ˆÇãQZZ•””’’­_¿ž›gΜ9ôÅ_ѵk×UTTÐ;wHVV–$$$¸ØÿóŸÿ‘šš1‚444HUU•ø|>1¢Þíž™™I¶¶¶$''Gfff$##CTQQADD¦¦¦4hÐ ÒÑÑ!âñx4xð`Úµk)((ŠŠ IIIÑܹs¹> ©««S·nÝH^^žŒ)>>ž›§OŸ>äëëËý¯¡¡A¿þúk½±º»»Ó—_~Yçô²²2@/^™Ö½{wZ¾|y½ý×”——GÈÝݤ¥¥I^^žÐ¤I“ˆ¨j¿iiiÑÆ…–;~ü8)((PNN™™™’——'%%%255%"¢¢¢"úòË/IBB‚¬¬¬H PïÞ½);;›ˆˆ’’’È‚¨[·n$¨K—."1víÚ•6mÚTïë˜6m 0€¾ýö[’““#ôâÅ ÊÉÉ¡‘#GŸÏ'kkk’””¤þýûSqq1·lõgLYY™”••IVV–®\¹BDUŸMMMRVV&UUURSS㦭_¿žLMMiëÖ­¤¢¢BèüùóTZZJ^^^€tttHUU•ääähÀ€õ¾Ž ””uíÚ•TUUIZZšnß¾MDD+W®${{{""ú÷ß¹ÏFõŸ„„×ee%-[¶Œø|>™››“œœ™˜˜Pttt½ë5jMœ8Q¨ÍÇLJ$%%I ’’YXX‘™™ýøãÜ|€Î;ÇýòäIRQQ!}}}ÒÕÕ%EEEºtéR½ë×ÓÓ£aÆQûöíIEE…ø|> ŸÏm—;wÖº®ê÷þ_ýEDD÷îÝ#4mÚ4’’’"’——§;vÔsee%-Z´ˆ$$$HSS“ÚµkGªªªÊ}^UTT¨k×®¤¤¤D:t ÐÐPnùäädêׯ :pïU"¢;vššyxx@ jß¾=]»v[¾°°&Mš$ô9ëÓ§åääÅJ(""¢Þ×ó©c P3èÑ£ÉÊÊÒ¬Y³è—_~¡/^p_°Õ¶nÝJ:t ¨¨(""ÊÍÍ%;;;š={6‰&@wîÜ!)))îZZZJÓ§O§^½zQÕLçÎiäÈ‘CDD‘‘‘Ü~Ú´iäáá!C] Ð7ß|Ã}`|}}ÉÜÜœ[& €¤¤¤èĉT^^N•••´xñâz ââb’••¥3gÎÑåË—INNެ¬¬¸y¬¬¬Èßߟˆ„ "¢;wRçÎEúUSS#{{{ !"¢gÏž‘„„½|ù²ÎXV¯^M:t ‚‚"ªúRß»w/UVVQUdmmMÁÁÁDTõ倹õ\¼x‘åååqÛþÙ³gÜ: ÈÊÊŠ¼½½¹¶¦&@$//_ï—D] PII ñx<š0aYXX¬¬,éèè•——×Ù_õ—Àرc)>>žJKKiÏž=€;È®\¹’:uê$ô~îÛ·/Í›7ˆªÀèÎ;B}¯^½šºvíJqqqDD”••%”¤y{{“©©)•––QÕ6Ý·oŸHŒß~û-õïß¿Î×@Tõ^çñx4gκwï…„„PAAÍ;—lmm¹©©©¤§§G›7o&¢ªÏÚ±cRee%]¼x‘nݺE¹¹¹¤©©IóæÍ£ÜÜ\ÊÏϧE‹‘ŠŠ effQU€FM7oÞ¤ÐÐPÊÈÈ Í›7“’’:uŠ***¨¢¢‚¼¼¼êM€>|HèÞ½{\Û­[·¸÷åÛ PM.\ YYYzøð!DQQnܸ¨¨(,[¶ ;vtîÜ_}õU“ã÷õõåN5Š»,G…££#<<<ÀçóÁãñ ¡¡QoÒÒÒpppÀÝ»wTæ®^½áááGAAž?Žþýû79Ö)S¦ [·n áêÕ«uÎ///ÜÜ\<~üD@€™3g ]ñôô„¹¹9 gÏžPQQÁ´iÓ¸õ¸¹¹¡´´”+*—‘‘¹¹9BBBpðàAìØ±|>‘‘‘M~=Õ222ŸŸ##£&/"‚´´4vïÞ§OŸâûï¿ÇöíÛñí·ß6¸üôéÓ¡«« )))Ìš5 zzz Ìž=ñññ¸rå àùóç¸{÷.¾þúëzû À¨Q£PPP€°°0¤¤¤ÀÞÞž{¿ËËË#;;Ož<PµMg̘!Ò‘‘¢¢¢| ƒÆ®]»`gg‡nݺAFF?ýô<==‘°°0dff¢OŸ>\ û÷ïG§N0wî\ÈÊÊ‚ÇãaÈ!pvvF`` RSS±páB(((@NN‹/FVV.\¸À­·k×®8~ü8\\\`bbUUUüúë¯pssÃÈ‘#!!! ¨««×õå¤p—ºœ¹÷e]ÂÃÃ1qâDìܹŸ}ö·í‡  C\\\½ÇÈÎÎFnnî;½ÿj:tè:vìKKK¼~ýáááèÓ§’’’Qﲓ&M‚……ÀÄÄfffÜçûüùóÈÎÎÆâŋѮ];(**báÂ…HNNƵk×Þ;nX±bTTTT ###ëý\ÀÅÅ#FŒŸÏŸÏÇÔ©Sabb‡ž={"<<‡ÂöíÛQQQÁõ—™™ÉÝ´P½ï”••…j˜4440þ|ÈÈÈ€ÇãaĈBÇ»€€xxx ??aaaHMMz¿­±Ÿ¥O«jíÚµÃÂ… ±páBäåå!(([¶l»»;о}{DFF¢  @äYW2QýÁ3fŒP»……²³³ EEEôìÙS¬¯¥ú@\}÷Mtt4¦OŸÞä~\]]qêÔ)TTTàÂ… xòä ‚‚‚pâÄ ØÙÙA]]Ý»wK¼õÝ)T}×”££#áììŒ+V W¯^u.#---t÷ž¤¤$¤¥¥¹‚ö„„Œ1±±±ppp@‡ !!QïW ÉÊÊ€:ëªcΜ9ÜÔØØ!!!8rä¾ÿþû&õciiÉÝ~«««‹#F`×®]2dvîÜ 777×¹|QQqôèQ¡dW6gÎÃÖÖÊÊÊèß¿?V®\ KKK¡ùµµµ‘žžÞ`Ì5ëzQ\\Œ}ûöá×_šVýYgMDdd$´µµ…ê©ttt``` ô…X[]VTTT“ˆ˜ššÂÏϾ¾¾X¶l,--1þ|L˜0¡ÎÚ²üü| >ãÇÇ”)S„bóæ —\Vkß¾}ëÇûïíõ§¤¤`ìØ±Bí–––M¾«ïíÏwdd$ŒŒŒ„꜌ŒŒ ¡¡ñ^?>ê[7€zcŽŒŒyÕ2220zôh<}ú}ûöEÇŽÁãñ¸ãDuÌM©Ëy{{äçç#55‡ÆÙ³g…æ«­6²±Ÿ¥OK€š™‚‚\\\`ee555\½z“'O†¦¦&/^,twE}455abb‚Ÿþ¹Îé¹¹¹HNN)ЭF¼¿>***ïô«ÁÕÕ+W®Ä•+W`ll mmmŒ=ëׯÇC¿~ýê]^±U æñãÇñæÍüóÏ?øå—_àèèˆäädî—^c¼ýEçï"DEEq˜ÞÞÞç8«‹-ÓÓÓ›|—^ÇŽ!%%…ØØX.ª–Õ_lMñòåK >œûÞ¼ypqqÁ“'OðÛo¿áøñã"˼½¿dee¡¨¨??¿:oÝÖÕÕÅùóç…þù‡;ã™’’n¾ôôt¡øÆRSSƒ„„¶mÛ77·ZçÑÔÔ¬sŸ©««#-- yyyܾ),,DBBBƒg@ÛµkWk‘rCV­ZoooüóÏ?¸zõ*¦M›iiixzzÖ:ÿ—_~ eeelÛ¶Mäu=ºQwCU{ûýW]xý®455aii‰Û·o¿W?5©««#>>%%%\‘qff&222„ö‰¸ŽQß{hÿþýGdd$w¬Y·n7Ä@õ]táááïôcPNNrrrX·nÈåÚ¤§§ ݠбK`bvéÒ%‘iÕŸ{{{Ü¿_è3VRR‚7oÞÀÞÞ•••¸yó&7íÆ(++ƒƒƒC½ë¶°°ÀÍ›7¹_ú••• h÷æÍäææBII ÄæÍ›affVç—ëÆqïÞ=œôôôê<†7„ÇãÁÑÑ"I_mûú]?KŸvHÌ2220eÊøùùÁÖÖ:::xøð!þý÷_Ì;—ÈjãÆ°³³ƒµµ5FŒ²²2üý÷ߨ¬¬¬u º¹sçâÔ©S°²²Âĉ!''‡   ܹsyyy000ÀòåËñÕW_áĉèׯþüóOHJJâòåËpwwDzeË0cÆ ¨©©ÁÝÝýÆY¾|9nܸKKKèëë###&&&B·3×FBB...8sæ F êKé‹/¾Àï¿ÿ^oäää‡áÇÃÊÊ fffú…S› 6àùóçŸ#GŽ`Ö¬YÜ6¯‹w™pðàÁ¸yó&RRR`ccSç2÷îÝÃ×_ OOO 88ÑÑѵ^J{ôèV¬XÞ½{ÃÏÏkWWWǺuëàç燫W¯Â‚;{tÿþ}¤¦¦r‰gMprrÂùóçß©6ïmÆ èQ£ààà€É“'CCCÁÁÁ¸té"##¹¡0šªG˜1c† ‚ & ¸¸ÇÇÂ… ¹K²ƒ ¤I“0vìXÂÎÎC† y¯×SŸµk×âòåËÐÓÓÃäÉ“QQQ#GŽàâÅ‹˜:u*üýý1lØ0tîÜwïÞEtt4wæñxرc<==OOO„‡‡ãúõë5|ëÖ­prrB¯^½0dÈäççãÎ;ÐÕÕJ¬ñäÉìß¿¿Y¶ÃÇ‚¿fÍš5­ħÄÒÒPVVFqq1ÒÓÓaaaÿüç?˜3g7Ÿ‚‚fÏž @€ÈÈHäææÂÞÞ¾¾¾PQQǃ@ €³³3äääÀçóñå—_B__111HHH€™™6nÜÈ]òrqqƒƒ ‘””„ž={bùòåPRR‚šš¾øâ DDD   ÎÎÎPWW‡””\\\ //eee8;;s ǃŒŒ œ!++ yyyL:ýúõÀðý÷ß#33……… ´§££kkk¡j§N`hh(tPâñxPQQ³³3x<$$$àéé‰ÌÌLÄÅÅÁÎÎŽûbtpp¹ä÷ÙgŸq…à5õî݈EJJ úöí‹ï¿ÿž;…ÎãñЫW/¡_FÕ¿(ß®™àñx\QºœŠŠŠ ,Z´#GŽ„¡¡!W_Âãñ`mm-ô…noo/T¿PSii)¶nÝŠ¹sç ýª/..Æ£G %%ggghjjBRRfff\ššš˜0a¢¢¢‚ž={â—_~©u•·ñù| 6 !!!ÈÉÉÁôéÓ±eË‘º“øøx\¼x‡OÆÉÉ ººº†®®.¡ªªŠY³fqcœ¡_¿~X¶láààÀõ”žž777øùù ½îW¯^aÅŠرcGƒƒî™ššŠ kkkcúôé())ÁëׯQ^^ŽÁƒcÁ‚““ƒ¤¤$¦M› ¤¦¦¢´´'NÄèÑ£!%%777˜ššr—€/^ ¡utìØQ¤ÏÀÀ®®®ÈÉÉAyy9,XtéÒ¥ÎÄÛÐÐúúúHHH@DDttt°wï^nü#X[[#77jjjèÒ¥ äåå¹?555ôîݲ²²˜5k…ŒŒ ôêÕ ~~~õŽÅS^^ŽÍ›7cîܹÜXFÕÌÍÍEê¾zõê…:pñU>ÀÃÃVVVˆ‹‹CTT àçç33³:dzâñx°³³ó©æçhèС000@DD–/_.4hª´´4<<<””„ÔÔT888Ô9†ŸÏ‡‹‹ ”””Àãñ¸Áê×þö1¹®11}útÈÊÊ"!!ÞÞÞprrBûöí1dÈ®þsæÌ™˜2e :uê„=z¨zߎ5 xóæ ôõõ±~ýzhkkƒÇãAWWvvvBÛCMM NNNª’Þ™3g¢²²ááá(..Fÿþý±téR¡ñvìØ¸¸8¬[·®Ö×ÑVð¨%/2½’’äæærר³²²`gg‡iÓ¦aéÒ¥­ݧ¥¬¬ &&&3f 6lØÐÚáqrr‚……þûßÿ¶Ø:yyùF=B„yåååèÚµ+†ŽM›6µv8Œ˜$%%ÁÔÔ;vìx¯Ç±| XÄ4Ill,:wî ¨ªª",, $2Š IDATvvv8wî\ƒ#Ë2Mwûöm|þùç8þ<ØÚá¨z®œ­­-^½zÅÕ´5·ï¿ÿ›7oÆ“'Oꚯ»wïbÀ€8sæŒP­óq*..ÆçŸyyy\¸p¡ÞÑäÛ‚6›¥¤¤Ôz™¤cÇŽb{駨²²OŸ>EXX*++ѵk×zk˜÷÷×_¡´´´Á;åZJhh(âââÞ¹§©ÊÊÊpùòeqc11-çï¿ÿFqq1{lÂ'àÍ›7xüø1œßénÊOM›M€ˆ999Bm‡Æï¿ÿþA?˜a†a˜÷×f šŠ‹‹abb‚|0¿´†a†iì6øÿÙ¾};LLL„’ŸÐÐP„††¶bT Ã0 Ó²dddðù矷vÍŽ%@¨WeãÆ¸~ýºPû±cÇpìØ1˜šš6{ ÉÉÉPVVõ¶)bbbЩS'±Í[ßyõCµ`Á¡'êV[½z5­^½ºEb8pàEGG¿óòM‰³1óÖ7O]Ójk¯Ù–••Å=õ½Ú!CŒG’’’h÷îÝï¼|SöÑýû÷éÊ•+õÎ#®m|ôèQzõêU½óµÔ6®mÝMÑÔ}Ôк®\¹B÷ï߯uZ]ûèÖ­[tëÖ-¡¶ÚÞ·5×=cÆ zùòeƒ1‹Ãû—>–ãEÍ}ôàÁš?~ƒñˆCmhÊ6ṉ¥¹¶qͶ¤¤$ÒÒÒª7–OE›O€"##©]»v&2­% ŒŒ *))yçå“’’Ä:o}óÔ5­¶öšmåå唚š*ÔÖRÛ¸¬¬ŒÒÓÓßyù¦ì£‚‚ÊÍÍ­wqm㬬,*..®w¾–ÚÆµ­»)šºZWnn.Ô:­®}”——GyyyBmµ½ok®{÷îÝïõÚ›â}×ó±/jî£èèh:pà@ƒñˆCmhÊ6ṉ¥¹¶qͶ¶”µùK`ß~û-&L˜PïÓ¬[B}—J£¾';¿Ë¼õÍS×´ÚÚk¶ñùüÙ\$%%ëù¶!MÙGo_Žª‹¸¶qm§è›ò~·÷YwS÷QCëª~¨gmêÚGµò[ÛûöcÝÆM]¾5ù5—ºF{n¬¦lãÆ[šk7Ô÷§¬M'@ HKKÃæÍ›[;”6mäÈ‘­Â'mãæçêêÊÆVifêêêpttlí0˜OD›~¼®®.nܸÁF–mey@'ó~Ø6n~Ïž=CAAAk‡ñIËÍÍEHHHk‡Á|"Ø8@õ¨~Nl}Ï‹ý÷ßñòåË– ˆa>"ººº-6Z4Ã0⑜œ ++«6qX›¾&GŽÁ½{÷Øý ó–„„ðù|–1 óÁb Œ? ,hí0惈­[·¶v-*##JJJ”d‡ÕæRZZŠüüü÷¾i„a€6^Ä0 #.·nÝy¾ #^éééøë¯¿Z; æÁ~ª0 ȇ‡Gk‡ðÉÓÑÑÁ°aÃZ; æÁÎ1 Ã0 Óæ°ˆaF 222P^^ÞÚa|ÒJKK‘™™ÙÚa0Ÿ–1 È«j~¬ˆ'VÄ0 #¬¨ù± FœØ †a†aÚ–1 È«j~¬ˆ'–1"¼¼¼0|øpܽ{W¨ýòåËðöön¥¨>=‰‰‰pvvÆ;wZ;F X Póc5@Œ8± Dl,”ÔüëQPÌÌêŸçòåË’’¢E‹ððáC®=** 7oÞlæÛ)))˜ššBAA¡µCaÄ€Õ5?VĈK€>þþÀ¶mÍ¿[[àÁƒ†ç[ºt)¾ýö[œ¢¢¢ðúõkX[[CKK ðòåK¤§§ÃÁÁÿ‚3%%RRR““ÃãÇQ\\Œ^½zANNŽ›'&&ZZZ••Enn.JJJ ¡¡(..ƳgÏ™™ KKKèè轞ŒŒ ¼xñ`jjÊÅSýz‚ƒƒ‘••…Ž;ÂÄÄ ®®hkk õ•ššŠ'Ož@]]’’šÆç󡤤„§OŸ‚Çã¡GÚæ Ã0Ì[ˆ©ÓêÕ«iõêÕõÎãííMþþþï½.oo" ùÿlmŽEMMNž¸÷ú§&))‰´´´Z;ŒÁj€˜:­X±‰‰‰8xð`­Ó׬Yƒ×¯_#88QQQ¸}û6Ž;†€€€:ûŒŒŒÄĉ1kÖ,$$$ %%wîÜšš*++1qâDXXX 66±±±X¿~=fΜ‰°°0®²²2˜››#''9990`f̘#F ;;ÙÙÙÐÖÖÆñãÇ…Ö#GŽ ''÷îÝCYYÖ­['4¿¿?Æ8pÙÙÙðððÀ¼y󘘈ððpüôÓOðööFJJ òóó1þ|lß¾HLLijgÏ`ddøæ›o`ccƒÔÔT„……!-- £G®uÛ<|ø .Äž={ƒ¨¨(hkkcÚ´iBóÅÅÅaûöíÈÎÎFrr2ÒÒÒpñâÅ:·9Ó2X Póc5@Œ8±K`ˆNª.O5·îÝ?¯ºº:–,Y‚5kÖ`„ "ÓýõWL›6 æææ'''8;;ã×_ÅìÙ³kíóСC___´k×ЧOÀ£GðüùslÙ²…»ôôõ×_cÕªU8zô(Ö¬Y––Æ’%K¸>û÷ïÌ;—këׯnݺ%´nWWWØÛÛìììðÅ_ˆ$»wïÆðáÃ…^cII  „ÈÈH€……*++WWWÈÈÈàéÓ§ÈÎΆ²²2º¿µ‘åå呀ׯ_ÃØØªªªµnËêm£­­)S¦tuu1}útŒ3ÑÑÑÜåE777¸ººÔÔÔàââ‚«W¯ÂËË«Ö~™–Áj€š«bĉ%@ˆ ªþ>4‹-ÂÎ;±sçNHKKsíÙÙÙÈÌÌäŠjöööؼysýEFFÂÎÎŽK~jNãñx\B’’’°µµå’ÚHKKƒˆ„ÚäååQXXXïk³±±ÁÞ½{…ÚÞ®ªŽ©¬¬ “&Mj755EYYddd°k×.|óÍ7ؽ{7LLL0mÚ4|õÕW••Åš5k0kÖ,˜˜˜ }ûö6lV¬X}}ýZ_mÛ³zZ]õUrrrHj‰ z†a˜O»ÆÔK^^+W®Ä† „Nï+((@  **Jhþׯ_C]]½Îþ455^ë4uuu¢££…ÚÃÃÃëíó]£C‡õΣ©© 555<}úTä¯úL‘——bbbpûömŒ7ë×¯ÇÆVVVøçŸŒµk×âÁƒBg˜Þ¦®®^ëö¬žÆ|ØØ8@ÍĈK€˜Íš5 ŠŠŠØµkׯçóaook×®qm¸~ý:úöí[g_öööˆGPP×VYY‰ÈÈHX[[C^^^¨Ï¼yó¦Þ>ßEaa!®\¹‚Ô;Ÿ££#pùòe¡öÌÌL¤§§#++ ‘‘££#|}}1dÈ.É{þü9ÀÜÜ3fÌÀŠ+ƒ²²2‘uõíÛOž,T°víZŒ7®IëªMII ¶lÙ hjjâï¿ÿF^^6lØSSShkkãåË—011Áõë×Õ'aÚ´i|ˆ>}ú@^^ÊÊÊØ¼ys£× îa¢ÕN:KKK‘ˆVÛ»w/bbb0uêÔF¯«¼¼;wîÄ‚ Àçó¹öêKk5Ÿ7&//¥K— µ•””À××êêêPUUÅÔ©S‘ššÊMŸ0a6lØ ´Ì‚ °hÑ"À’%Kðûï¿ãСCèÔ©† ‚©S§âúõëØ¶m:uê„I“&ÕDD ÈÉÉÁÞÞž{ }µ¢K—.øù矽]¦M‹Ž† ÂÃ={Z;"¦™°ˆ2kÖ,œ={–ûsuuEqq1ìíík?11QäÖß'N`ÆŒ¸wïÆŒƒ%K–ˆ$4u100€®®.öíÛ'Ô¾gÏ888@ ÆÓÑ÷ïßGaa!F%ÔþùçŸCAAžžž8sæ ëìãÁƒHKKÃ… pùòeܸq¿ýö7=%%E¤f!==iii€ùóç£{÷î>|8Ξ= øúúB__“'OÆÙ³gk}]^^œœœ`jjŠ/^àÕ«WÐÓÓøqãPóñ~cÆŒÁ¥K—šºy˜&b5@ͯYk€ŠŠ€Õ«nÝ€sçþ¿ýäI #£yÖÉ´*v ìÑU£+ܺ¸µÈz꣫« ]]]À7päÈ\»vkkŒ~ø»ЫW/üñÇ8tèÈÓÚë2{ölÌž=Û¶mƒ‚‚bbbpãÆ lÙ²¥Ñ14Æ«W¯Ð¹sg(((µËÊÊâòåËX³f FŽ hß¾=Æ+V=ˆ±oß¾ØóÖ/ĉ'âÂ… Üž†èëëC^^ZZZ°²²âÚedd ££#Ôö¶ýû÷ƒÏçÃßߟ{øæÊ•+aaa˜˜póš››ãÇlT<Ì»c5@ͯÙj€NŸ-j«E,)~ù¥j:óIa ÐbVY˜ÕcVk‡Á‰ÅرcñÃ?ÀÑÑñ½úêÙ³'¢¢¢=¿‡‡,X€£GbæÌ™8pàììì`nnþ^qÔ”žžŽ:Ô:ÍÁÁüñ¢££Œ`ÿþý8~ü8bcc¹º&'´œººz‹¬¨ù‰½èÕ«ª:Ÿ?þ¨¾}ûXô b—ÀÅÅÅ9r$Œ¯¿þú½ûËÌÌ9ËRLž<¨¬¬Ä0{öì÷Ž£&@€¢¢¢zç100€»»;6l؀˗/#>>¾Þ‘hk&DPYYùÞ±Ö$%%{{{„†† ýeee‰\®,**‚„„¤¤¤ÄÃ|”òò€%KKËÚ“mmỿ€۷[,<¦e°ˆ1kVÕ™¨½{÷¾w_ÅÅÅøûï¿Ñ£G&ÇðèÑ#lÚ´ =zô{ÇR“¦¦&Þ¼y#Ò~ïÞ=ÄÅʼn´§¤¤¨ºDÖX;w9#T\\ÜÄHEY[[ãÑ£G¨¨¨€²²2÷§¨¨(’„½yóšššB…ÞŒø± æ÷Þ5@DÀ¡C€±1°y3PV&<]Jª*1 «*„~›Ž‡Ì‡¥Í'@D„ .À××3fÌ@vvvk‡ÔªvïÞC‡ÁÓÓgϞűcÇpìØ1<þ¼Ñ}?~7nÜÀñãÇáææEEÅ&ŸÁ166†‹‹ V¬XÉ“'7xéæÉ“' DLL 233ˆ{÷îÕ»ŒÞ¼y#ryîöíÛ066ÆÌ™3€³gÏÂÇdzgÏÆ AƒðÙgŸ5úu 6 çÎþ}ûpìØ1 6 §OŸnôòu™ìY`Í~öä àèxyµ]¦06m€Y5JNŸþwóóihÓ5@¹¹¹3f ž={///£¢¢¢µÃjUÉÉÉèÛ·¯ÈhÎ^^^077G·n݄΀|öÙgÐ××çþwrr‚ŠŠ æÏŸÔÔTØÙÙáþýûPRRªsêêê"·œU·ˆWTTˆ$OrrrprrsçôéÓ¸{÷.@UU7n„¡¡!úôéSçzŒŒÐµkWüòË/ðóóãÚ§L™iii\½zwîÜA~~> °hÑ"Ì;—;Ãbhh(r¶EOO½zõâþ8p üüü°uëVHIIaÊ”)øì³Ï¸Âe°²²BçÎ…úéÙ³§H}Rß¾}¡®® êòÝýû÷ñÝwßaãÆHHH€¾¾>FŒ!T„[ZZŠ'NÀßß¿ÎíÀˆ«j~ïT”™ ¬\Yu§¶ËÑ;[¶ÿ»áãâUÝ T Šxð Pc( æ#Fm˜¯¯/¹¸¸Pyyy­ÓW¯^M«W¯®·oooò÷÷o†è˜–ðÛo¿‘¢¢"%&&¶v(Íbýúõ¤¯¯O¥¥¥-ºÞ+W®››[‹®“a„TTíÙC¤¦FTuñKøOF†hÕ*¢ÂºûØ´IxCC¢ÊÊ–{ ­ ))‰´´´Z;ŒѦ/íܹÓ§OÇ?ÿüƒëׯ·ùË_mѸqã`ccƒ™3g6K±rkzòä Ö¯_mÛ¶±èÀj€š_£k€îßzöfÏ®} wwàåKÀϨ¯¦ïË/…‹¡#"€›7›7óaj³ PFF233±qãFøûûcÓ¦MèÔ©“ÈõåƒbÍš5Bo jÁ¨q“ÀáÇahhˆÿý·µÃ"B`` ¶lÙ‚á5‹9[PÍÏ˧üÿ­[·°bÅŠ&žOñÿåË— £Eæ_²˜<°·?†ðT`špå pö,``ÐðúwìÞ(u T ÝÚÛC\ÿ_¼xkÖ¬³³3~üñG”Õ,ÿDñˆj ÛF„……ÁÔÔ%%%ÜèÂk׮Ņ ððáCÿÿæ¨m$Þj ,@§N°`Á‚æ™a>غu+[;¦-(/¶o¯:£“›+:]^¾ªhá¦?ÜôÎÀÙùÿÿ—’âãMÍ÷ ùC•œœ ++«ϬµµÙ3@Õ#§½UÕß«W/ÄÄÄ´RD Ã0L“ݸQ5žÏâŵ'?ãÆ¡¡À²eïödw''ÀÄäÿÿ/+«z@*óÑk³ ¼¼<,--qþüy®íæÍ›°´´lŨ†ùX± æ'Tôæ 0z4пU=OMææUƒ94áQ>µªyK|@@UY4óQkÓ·ÁïÞ½îîî B\\BBBØ){†aÞ {XóKOOGÐýûöê°aPX(:“²2°v-ðÕW€¸ÿœ<X¾¼ê¹`\¿ (žþ™VѦ ;;;<þ7oÞŸÏÇÀ¡¬¬ÜÚa1 óbã5?G0ì›oªšx<`êÔªÄHCC¼+VU<<€Ã‡ÿ¿mß>–}äÚì%°jZZZ7n<==Yòó?111by\C]rss‘˜˜Øèù k}dÅ»*++CLL òòòš´\JJÊû Ã_Cll,ÂÃÃ…n¿ŠŠBdd$ÊËËÓèK*¥¥¥ˆ‰‰A~~¾Øâc˜Ê·ßÆ՞üôì ¢4óÑhó #¬¼¼øûï¿›m{öìÁ Aƒ=`` ºvíZë´]»vá¿ÿýo“Ö?þ| <¥¥¥\[ff&V¬XGGGtíÚ®®®ðöözœÆäÉ“Ens~/_¾„““Œaff†¸¸8<|ø½{÷F·nÝ`bb‚ׯ_ÃÀÀ‘‘‘ê“ÏçcÑ¢EpvvFIõiz¦E± fT^üôJýÑШJzþùxköfáètë&+†þ¨µéK`”;€“'›=ݺ»v5ÿzšÙµk×ðàÁüðØ8qb£—»zõ*8€àà`®V#-- ½{÷†œœF…Î;#,, øë¯¿ðèÑ#±Æ¾ÿ~¨ªª"-- ²²²àóùðõõE·nݸ˱M9CT%@‡†¹¹9Ö®]‹õë׋5f¦a¬¨ýñšŠtA†ñùÀœ9ÀþSUóÓRfÎÞò$ ðñ$ع„K€>UãM4·&^ÚJIIAQQ‘H»¶¶6÷,®¢¢"ѽZFFž}úÀÜÜû÷ïçÚ qìØ1‘§À¿¯«W¯¢G"O^÷ôôŸÏ‡™™V®\‰Ó§O×Yt;v ;;ÉÉÉHKKÃ¥K—ÃüMMMìÚµ ÙÙÙØ¿??~ >ŸßÿÙÙÙøá‡D–»qã–-[†;wî 44)))èß¿?æÎ+4ǃ»»;®^½Úè˜ñ`5@ÍäìY  ªj€tu¾}[/žšÅÐ/M¼dÍ|Ø%°ży@K<³IQñõööFNN÷åúçŸ"&&§N‚ŠŠ €ª37ëׯÇÉ“'1þüFõ;{öl¬Zµ 7n„´´4Nœ8999¸»»¿s¬µ‰…‘‘‘H»šš"""°iÓ& 55àææ†M›6 erssC¿~ý¸åœqõêULš4I¬±Ö´ÿ~¸¸¸@YYaaaüöÛo(--åçFFFBÅÛLË`5@Íä­[ÏÓõîau\"n}úÝ»/^Týÿ¿møú¶^LÌ;a ЇÂаêïõóÏ?ãèÑ£xøð!Ww 999XYYqó)((ÀÜܼÑw/ÀĉñÍ7ßàìÙ³3f ~úé'L›6 ’’â}{feeA[[»Öi:::ð÷÷‡¿¿?RRRp÷î]øùùaôèÑ ­³O999¤¤¤ˆ5ÎÚDFF"..cÆŒjïÞ½;rrr ñÖ­¿ÚÚÚBxaZ«jiiU56ÿ£`ØêÕ­OµY³€¯¿þÿÿ÷ïV¬`ÅЖ1 zøð!æÍ›‡'NAQWWGAA’““¡££¨¨¨@TTFŒÑèþ1nÜ8ÀÚÚ÷îÝÃo¿ý&öס¨¨ˆŒŒŒçÓÒÒ‚‡‡ÜÝÝ ƒF­ƒÇã5Ë“”555amm½¨7ÈÈÈàÎÈ1ÌGíøñª3,ÕÌÍ«þZÛĉUÏ«‰úÍ›ª§ÌÒºq1MÂÒU¦^©©©9r$|||0¤Æ‡ÛÖÖׯ_çÚîÝ»‡œœômâ5úÙ³gãæÍ›ðõõÅçÿÇÞy‡GU5qøM%é%ôÞBB³Ð»tR0Q‘ÀÏŠ¨(ÒK@Z ôŽ¥÷ŽTé$Pôì÷Ça³{7m“ìæn9ïóìgö–Ù“À=ç73íÚ¥šý•J”(Á7’ÙýõWþN!oëÖ­äÎ[‘–õêÕãÚµkI㇞9‡õhÖ¬kÖ¬I¶²såÊ•dÇÞ¸qÃ,ó'I©2ú•—ØÞ½MZŒ4ÓäÏ/ÅÐ6€\’¤ÉàÁƒ çÞ½{ Q²¯¯/5bìØ±øùù±eËòçÏÏ’%KèÝ»7M›6ÍÐ}¼¼¼ðòò"((ˆ7¦{ü´iÓxøð!—/_&44”/¿ü’:uêЧOŸTÏiÑ¢“&M",,L±etîÜ9FŒAݺu©[·.9rä`ÿþý\¹r…ùóç+ô5éÑ¥Küýýyþü9111lß¾—)õ+Ê Ÿ|ò ÁÁÁÔ©S‡~ýú‘3gNŽ?ÎÁƒyòä‰âØÍ›7§™A'1RdbþûÒoÓ†ãû÷Ó¹sgõüÒâï‹éÆ[¶ÀÝ»Pª”j.I2† €$ ™8qbR¦Tûöí©[·n²ã´)ã&L nݺlܸ‘¨¨(f̘¯¯oš÷xë­·È›‚û§Ÿ~âŸþáwÞQØ«W¯ž¬sŽ9psscÀ€I6—4ïÛ°aCJ•*ÅÂ… ùâ‹/’ìsæÌ¡_¿~ìÙ³‡»wïIÏž=ñññQdŒõë×/ÙÖRÇŽyñ*CDµèÒ¥KóçŸâîîÎÁƒÙ¹s§¢’õ矮ÐM˜GýÚDùóçgâĉ.\WWW8ÀÊ•+9tè÷ïß§~ýúÉ 9r„+W®Ð½{÷4çBbz¤ÈÄ,[¦7iB‰zõ°€ÐGШÔ® gÏŠqB‚Шê–Äx4Fm',•€W¿ÈiüB1‚råÊ1B¿:¨ÄbY°`Ÿ}ö—.]ÂÝÝ]mwLŠF£áÍ7ߤZµj,\¸PU_¶mÛÆ/¿ü¶mÛTõCbÅT¯ú sæÀàÁêù“üú¥(J•ÅMÕ…^BCCñðð ÔúœI Ä®8p 6ÄÇÇGÑ Ì;v,wïÞåÇTÛ»Dj€LȉÊàÇÕ¼½‰µ –~ý wnÝøî]È@]0‰ºÈHbW888°lÙ2† fS©âQQQ4lØÝ»w'm›I²—={ö¡¶¶ø™wÞgÿþýêø”yóBïÞJ›C[ 2’Ø… ¢k×®Êî²træÌI—.]R,ô(ɼ½½¥Ú$&ŠJÛ«†Ç%J”° ´>†•¡·miñ‹G@‰D"±vï†Ý8_>Ë®¯S¿>è'Š$&Š.ñ‹G@‰Db¤ÈDnõènn–§Òb¸ 4¾²€£Ä"‘D"‘˜©2ÑѰfÒ¦×ùÝâ4@Zúö…W-‚±‚eD=3‰ºÈH"‘HL€Ô™€áÙ3ݸdIhÖ,ih‘ €×^Ã"¬R mñÈH¢ &&___.^¼˜©óÙ»w/÷îÝËÐy‘‘‘üóÏ?¬\¹’½{÷“î9«W¯æ»ï¾3úûöíãÓO?Mñ½[·nqçΣ¯°k×.üüü’ÕËgÇŽqàÀd•š§L™ÂêÕ«3t¯”HLLdûöí¼÷Þ{ôïߟèèhâããY¿~=>>> 4ˆÛ·oãëëË3ý‡JDDD0tèP‚ƒƒ³ìŸD’a {öéc= F ·Ávì€ZïH,+ùͲ}îÅÄp:2Ò쯫QQiúG`` !ú"Ä KóæÍ3ô€ß·oEŠ¡cÇŽLš4‰-ZP¹reΟ?Ÿæy'OždëÖ­FßçêÕ«%³GFFÒ­[7f̘aôµBBBèÝ»7uëÖUTœ2e eÊ”¡[·n|ùå—4iÒ„¢E‹òÉ'Ÿ$³yófNœ8aô½Rã§Ÿ~ÂÏÏÒ¥KS¾|y4 cÇŽeäÈ‘T®\™’%Kòøñc‰Jçç®%_¾|´jÕŠþýû§;ÿ%R”E?Túèmk€¼¼ÄK‹F#ÅÐŽl…a!üxç¿Þ½köû4Ì›—Þžf»¾››!!!)¶ºHܹs³hÑ"zö쉣£#!!!4hЀO?ýTÑhÕ4oޜÇM›6mŒ>o„ Ô¨QƒôªÀ1aÂéÓ§...DGG³iÓ&öìÙcrß×®]˘1cøðöo¾ù†w_5j<}út†¯Û£GÖ¬YèQ£2`Ú;²XYµ ô‹“Ö¨-cÂÃÃ9~ü¸enƒX2D7^¸&M‚tÚôHÔA®IR$<<œ‘#GòÆoÐ¥KN:•ôÞèÑ£ñõõMöÒnõLž<9iõàñãÇøúúrèÐ!† F£FèÑ£‡b‹ÍÓÓ“^½záøj©»xñâtíÚ•«W¯íïîÝ»ñ÷÷'!!Aa bÒ¤I©ž·dÉþý÷ßû¥Fhh(‹-Jv݃R¶lY Ô—ÌÍÍ ooïd«K†ùóçÓ®];zôèÁöíÛïðÁ?~\a=z4»ví`̘1=z” 6àëëËâÅ‹:t(W¯^eÙ²eøúú²ÆPLúа°0†Îo¼A«V­˜5kV²c&OžÌ¶mÛä*P ,b˜ýe°ú¬ÒÒ§Ði …õëÕóG’&2’¤ˆŸŸÏž=£K—.œ;wަM›ˆæ¤I¯cÇŽ±oß>Ü^¥ª.[¶Œ¯ö¾_¼xA`` íÛ·'11‘N:qìØ17nÌóçÏS½ÿ±cǨZµªÑþV«V °eË…}ܸqI~¥D©R¥(W®9rä0ú^›7o¦dÉ’É:Þ·nÝš[·nñûï¿§«aš>}:+W®¤]»v+VŒvíÚqåÊ•¤÷ÿüóOnݺ¥8'88˜K¯ÚT¯^J•*…‡‡¥J•Jj¸Z¶lY<<<(^¼x²û†……Q»vmîܹÃgŸ}FË–-™4i?ÿü³â¸Š+Ò°aC6lØ`ô¼H$™æÖ-ÐÏîrp™UÖFž<É7)†¶X䘅P2Gêè§Qš‰*¯º¸§ÇâÅ‹éÖ­ ¾æÏŸŸ… 2jÔ(E·÷Õ«WsëÖ-<˜Ô!>%Ö­[G³WÙC† ¡hÑ¢¬\¹??¿dÇsüøqvìØaôç*Q¢:ubÞ¼ytêÔ Ú¢[·n1pà@£¯c ÇŽãÍ7ßÄÁÁAaoÛ¶-~~~Œ9’‘#GR£F êׯÏСC“­0ùøø0WOpüøqÖ¯_ϨQ£ŒòaÀ€ 2„N:Ñ¥K4hÀðáÃy÷Ýwyë­·€ä[`“'O¦T©RÉV‡~ûí·dñ7ß|“ÇåDh€ò厳³üo5Ã,_.43ZÞz Ê•KvXll,‘‘‘,X0û|Ë(þþ ¿ªºk\¿+ªç“$Eä¿T aTéÒŒ*]Zm7’Ð×𸹹áááÁ¿ÿþ«8æÂ… 8¹sçR»vm£¯W¤HªT©’b¦Ù¾}ûðññICg:vìHHHÅ‹gþüùtïÞ¢E‹fè:é–bË gggæÎË„ X¿~=gÏžåï¿ÿfÞ¼yLœ8‘‰'&kد«L™2™Î¼ËGŽ!::šÞzý‹þøcV­Z•lKÌÄÇÇãêêšêû¥K—æã?„ÖgĈLŸ>¯¾úJ`è“ÒªFÿ±‰xþü9^^^x{{+ìÇOÒ`iqqqQüÌ%ic8§#9sôµf..ðJÄoˆÅk€´øûë €E‹`ÊÑÕ^b1ÈH’.ÑÑÑ}ú0qâDZ·nÍ¥K—’^  ÄÝ»w7nþ†…ÉR &&†èèh‰O*&˜µjÕâäÉ“Éìü1ï¼ó[¶láþýû„„„Ä7ß|C·nÝ2TàwÞaéÒ¥„……qõêUFŒÁÍ›7>?5†κuë˜>}:ÑÑÑÄÄÄpðàAE*½–S§NQ§N,ßÓ^u€2F#ô?ú¼êüž]HŸ\¹’)†¶8d$I‘™3gâææFùòåYµjkÖ¬IZ ˜6máááÔ®]›âÅ‹'½Ž9’êõfÏžM¾|ùhР×®]cÓ¦Mä΀³gÏ’ÀèÑ£©^½ºâµ1ƒýtŠ)B÷îÝyùòeRÀ–åÊ•#gΜ=z”Ÿ~ú‰œ9s&m_¥Æ;ï¼Ã¹sç’ Œ}||xúô):u¢dÉ’”(Q‚÷Þ{®]»²`Á‚ }"##)Z´(5kÖÄÑÑÑ$«1M›6eùòåüüóÏäÊ•‹†2eÊ$ÓÖd„ÿþûâÅ‹§™a—YÂÃɈˆ L™2ɶæFÅ®]»R\é2†mÛ¶ñË/¿°Í°²¯D¢ÏàÁ0ožnüÞ{B/c+¼ñègR~þ9üø£zþAhh(ÉZüØ"rH’*îîîT¯^=ÓÁ! °‰àà›o¾aãÆ)®P¹¸¸P¶lYÊ•+—¥à B… f ~@d¢U¬X1YðsâÄ ¦OŸÎ÷2kEbNbbÀ°eN*Ù_V‹á*ТEâsK,IÌŠ‹‹ uêÔ!W®\j»bRjÕªÅôéÓùá‡2Ý7͉ˆˆ`ìØ±L˜0Ö­[«íŽU!5@dËxúT7.^Ò)}a5 -½zAþüºqx8¤R¡]’ýÈHbVÜÝÝ9}ú4ÕªUSÛ“3dÈöíÛgS™RùòåcÛ¶m™*C`ïH P1ÌþêÝÒYm¶* @Μп¿Ò&ÅЃ €$‰ÄÈ^` "6mRÚŒØþ²š:@úè7G!ü~ÕÒF¢.2’H$Iö²zµR Sµ*xy©ç9yýuÑÚCŸ9sÔñE¢@B4›6m² żDb,Úf¸ö„ì–Œl}aˆUôK 8p@7 „o¾45KÌü—šE:uê„»»»ÚnX5GŽ¡aÆj»aÓd÷×­[— *dÛý,Ù ÌHîÝÛ@úYE/°”èÙ†‡'OÄøñc± –FÑG‰ù±ëèÎ;Ü»wOaËŸ?†»-[¶¤eË–¦vM"‘X²˜‘,_ú}ßxŒ –­Rb¥gÀøõWmöl©Œ]@3fÌ`Þ¼y”*U*ÉöÖ[o1cÆ ½’H$&“Û_V¿¿2Ú¿.^„5ÔóÉα{´··7§OŸNzÉà'û‘ú)ó#çØüÈ:@Fpñ"è·qvµrŒÄêêéS½:4n¬´É”xU±ûH¢>ëÖ­SÛ›Gαù‘u€ŒàÏ?•ã6m pa£O·º:@†V†^¼¢¢ÔñE" ?ÿü“¢E‹R£F ¾þúkõ÷¦]»v±nÝ:ÅK9Îúøƒ>°(lq¬cKñÇÇÞÞÞìÛ·Ïbü±¸±Fúùó•ïè-Ó»ÞÑ£G ‹ú|ÆŒ]]AO$¿îéS RÝ¿3gΰnÝ:ÆŒömÛHHHÀ°k ÐСCùè£puuåìÙ³ <'''ÆŒ“tŒ»»;åÊ•Kõ†ïɱ˱Ëq ã(÷ð¡nœ'åzö´ÿ²c\¹²hø:mšƒØ{ï=Uý+T¨†˜˜ .Œƒƒö€ì¯Ç÷ßÏîݻپ};`\7xIÖ •¥ÌŒœcó#ë¥ÃС0k–nܯ,Y’¡KXm }._ÃLã³g¡V-uü1@vƒ· ·»nß¾M±bÅTòÆ~‘úó#çØüH PÄÅÁªUJ[&²¿¬^¢êu³fJ›C«‚]¯Õ«WvíÚQ©R%Î;Çìٳٿ?€\’H$“°q#è×ï)ZîßO·ù©Í²bôé£çË'æ#W.õ|z…\²&MšÄ‹/ؾ};NNNœ:u*)ø‘H$‰‰ÈDçw›¦{weö[D„Š$ÙŠ]oVwèÐ:¨í†Ý#õ)æGαù‘ Txþ6lPÚ2YüÐ&4@®®àë S§êl³gàAª¹dØõ Ä2úó#çØüH P*¬Y£¬uS¹24h©KÙ„HË! Ÿmuô¨²H¤ÄìÈH¢:úu€$æAαùñöö–PSÂpû«oßL_Êj{¥DåÊм¹Ò&ÅÐÙŠ €$‰DbBCa÷n¥Í^zƒaeè¥KáÅ u|±Cd$Q{È6P9ÇæGöK+@¿ªpƒbå#“Xu/°”èÖMdÄiyþ–-SÏ;C@Õ‘úó#çØüH P ˜¸ó»Mi€\\`à@¥Mnƒev](=d ‰D"É$†œàÞ=Åf•\¿.VÅôÅ'N€§§*îÈ:@‰D"‘díœV­dð“+йÑG®e 2’¨Ž=|ÓP9ÇæGj€ 0Üþê×/Ë—´9 C1ô²eB$1+2’¨ŽÔ§˜9ÇæGj€ô8|XlíhÉ• ºvÍòemN¤¥sgÐ/T)ÅÐÙ€ €$ª#kÔ˜9ÇæGÖÒÃpõ§KÈ“'Ë—µ©:@úH1´*ÈH"‘H$¦#>V®TÚdíŸôYññP¦ „„èl3fÀ‡f› ²D"‘H$eýzeðS¡‚ ~2‚³3¼ÿ¾Ò&·Á̆ €$ªcß4ÔFαù‘ àÏ?•ã,t~O ›ÖiñóG½GóÙ³¢¬€ÄäÈH¢:RŸb~ä›»×…… ´>&Îþ²y @ٲЮÒ&WÌ‚ €$ª#kÔ˜9ÇæÇîë­\)4,Z¼¼”½ÀL€ÍÖ2ÄP ½r%<}ªŽ/6Œ €$‰D’uLÜùÝ®éÐJ–Ô£¢`Éõü±Qd$Q©O1?rŽÍ]k€®_WêT¡wo“߯.4@NNR ÈH¢:RŸb~ä›»Öö­jÑŠ7ùmìB¤ÅÏOBZ.\€ÔóÇ‘Du¤>ÅüÈ96?v­2Cç÷”° @éÒо½Ò&WLŠ €$‰D’yŽ‡Ë—uãœ9¡{wõü±% ÅЫVÁ£GêøbƒÈH¢:RŸb~ä›»Õ®þt꯽f–[ÙHKûöb%HKt44m W¯ªç“ ! ‰êH}Šù‘sl~ìR” úUécÆì/»ÒÐùù)m.@ƒ°y³:>Ù²XÈ^`‰D’;w*;˜,¡¡à⢞O¶FX´j%*Bëãà0~¼ø»‰½À$‰D"I ”ãwß•Á©)RJ^V@£‰¡kWÙ1>“ÈH¢:öðMCmä›»Ó=|ÁÁJ›™‹ÚHK®\°|9LªLذAl‰ýû¯:¾Y12’¨ŽÔ§˜9ÇæÇî4@ B\œn\¥ ¼ý¶Yoiw CFŽ„; pa¥ýòehØÖ®UÇ/+E@Õ‘5jÌœcócWu€4˜3Gi2Äì·µ«:@©Ñ¢…(=àé©´?=zÀر˜¨ŽoV† €$‰D’1vî„ÿþÓsä__Õܱ;Ê–U¡û÷WÚ5øæÑKìÉu|³"d$Q©O1?rŽÍ]i€ +{{C6¬~Ù­(%ÜÜ`ñb˜>œ•ïmÛõëùsêøf%ÈH¢:RŸb~䛻х„á­>†‹Í„Ýk€RbØ0ص ŠSÚ¯_‡F’×i’$! ‰êH}Šù‘sl~ìF´`è¯tÕ¨gË­¥(š4º  ”ö—/¡OøüsQ´R¢@@‰D"1ŽÄD˜;WiËñ³ÄJ•‚þ÷ßOþÞO?‰‚•ááÙï—# ‰êH}Šù‘sl~ìB´};ܺ¥çÌ dÛí¥(rä€yó`Ö,puU¾·{7Ô«'Oªã›" ‰êH}Šù‘sl~ìBd(~~÷](P Ûn/5@Fâï{÷BñâJû­[ðÖ[B<-‘½ÀÒBö“H$’WÜ»'Ò¯õµ$Âo¨ç“$mBCE†ÞÉßûøc˜6-YëÙ L"‘H$}æÍS?µkËàÇÒqw‡={`èÐäïýþ;´l d¿_‚ €€iÓ¦ñÅ_¨í†Ýbß4ÔFαù±i PB‚%»¥ IDAT€ôɦÔw}¤(¸¸Àˆì½9”ïíÛ^^päˆ:¾©ŒÝ@‹/& €£GªíŠÝ"õ)æGαù±i Ж-p÷®nœ;7ôë—ínH P8P<¥K+í÷î‰4zÃì>;À® ={ö0mÚ4&Ož¬¶+v¬Qc~ä››®d(~îÝòæÍv7d ,R¿¾¨Ô´©Ò+Ê ‚ƒ~ƒ[Ç& §OŸrà•ÈK£Ñ°}ûv6nܘæ9/^äƒ>`íÚµäÏŸ?;Ü”H$ëãömغUiSaûKb"Š…¿þ‚áÓ¿7w.»u#—äFÙD4kÖ,æÏŸ@pp0ýúõã믿ÆÏÏ/ÅãcРA¸¾* ¥ýe1ü%’˜–Y³fÉ-3#çØü¬^½šæÍ›ÛÖ6X|¼H}¿_g›=[µêÏ÷ïßçøñãrÌ”œ:Ý»ÃÍ›I¦Ñyóò½­êÙô°‰ 777bbbˆŽŽæèÑ£¼ù曀Ø»¯ÿ÷Íš5ãõ×_çæÍ›Ü¼y“ððp¢££¹yó&‰¯¾éH²ù`6?rŽÍ··7 ÚPð°q£2øyí5èÛW5w¤È Ô­+tA­Z©íI¶ã¬¶¦ k×® 2„Ù³gS½zu^ýuîÝ»ÇÝ»wqwwOv|ÇŽ“V@ls½xñ‚ï¾û.;Ý–H$6Ä_Á?Šn6ƒ¡ø¹o_È“G_$æ£P!ض ÆŒ¿Äv‚M¬uìØ‘3fкukV¬XÀéÓ§ùî»ïp1¨r)±ŒÊ+‰²qr‚~àéœ9ØK˜Õ@OŸ>eË–-$¼ªLÚ­[7ÆŒC™2eèСü±Q×òõõeïÞ½ærU’²Fù‘slá³ÏD¡Ýøø=@S¦¨í•‰˜;ô%¢ €‡‡zþ ëeÑ:±ÆÍMm7²«A?}ú”=zpåÊ ÀÀ©T©’Iï!EЉ$5^¼;B6$oÏhÖ,Û]2qq¢`ž~›„ùóaÐ õ|’d‰Má/Ãyøâ!a/Â{–ô÷‡/&C"B[FÄ^ÛA[­(þüìÚµ‹7n°páBZ¶lI¹rå4h={ö$W®\j»(‘Hl”û÷¡cG‘@“S¦Xy´~½2øÉ—O?”X š½|”j cä<ŽzŒãÖ;ò:e‘K5°ÚHKùòå™1ž/šâK?Èyõ˜DÌZÎ Viqtt¤M›6´iӆdztéR†Jll,#GŽd\ºµXÖ­['Ó´ÍŒœcÓ°q£ØöŠŒTÚ½¼ 2r—/7D*ü”)É ([×®Á®]J›…T~·Ê:@£§Ôh_!‘!œ—/_2eÊbccùâ‹/ðôôÄÓÓSm%‰•’Ÿ|ü‘ü½‘#á‡ÀÑQô—lÜX4ÜÖòõ×BNc5ÄÆÂ¢EJ›…¬þd'‰šDÜ9ÀõÇ×S\© åYÌ3U}trp¢P®BIKZAMÑÜE)³8¤{ÝÐÐPÒ?ΰ‰háÂ…Iõ~fΜÉöíÛiݺ5ýû÷g‡Íå°]¤>ÅüÈ9ÎϟûïŠqú8;Ìʎ=⫯òѾ½î¿Õ ą̀S'›Î*kÖ@X˜n\ €˜ ÁÜ «¯²èô"–œYÂgwÌr´pvt¦Xîb¸çqOñ¥ÔÌYG«­dcØDtëÖ-|}}ÑgìØ±ôèуš5kòâÅ rçέ®ƒ’4‘úó#ç8ãܾ-ÄÎçÎ)íùòÁªUкµÒ¾gÏš7oN£F…8|Xgÿúkq¼U`(~~ï=° š0æÐ=‹yÆÊ +Ytzï4Ùuõ)˜³ ")ž§xŠN¡\…ŒZ¥‘˜›€Š-Ê¥K—ðððàôéÓ4hЀgÏžñìÙ3Y8òÁl~ägŒcÇ sg0, ]®lÞ 5j$?ÇÛÛ€ñã¡C=8.^Lù‹âòe0,kaÛ_¦ê–¨Id×],:½ˆµÿ®%*>*Ã×Èå’+Õ•ýW±ÜÅpurͲÏÓcŸŸo½õx{{SºtiŽ?Nll¬\ö—H$bÍè×¢ ž‰ =OÑ¢iŸÿÎ;"+ìÄ 1ÖhÄ*вeæñ×dÌ™£7iÕª©ã‹™ÈÈW~·üt¬Ò‘2ùʤؼæúZ6y-16ÕªU‹‹/rçΤÕ'''Ö®]‹ƒƒ\N´t¤>ÅüÈ96Ž~€/¿Tv€èÕKhƒÓÚ zôèùòåÃÙÙ™qã [7Ý{+WB@T©b¯M@L *m¸j˜ PF¶¸œœh]±5¾¾t©Ú7gËÙþ“˜›PPíÚµ‹3gÎðúë¯ãää@ݺuyÓj«Ù²O•ù‘sœ6ññ0x0Œ<ø;–/O_ ³gÏ""Dû€.] vmÝ{‰‰ðÍ7&vÚ”¬Z鯅 C÷îêù“ ÆöKÔ$²ó¿ø¬ñÁ}ª;C6I3ø©Z¨*ß¶ü–[ŸÞb«ÏVzÕì%ƒ;À&ê­[·Ž©S§&A-Z´ E‹¼õÖ[¸eAÀ'ëI$¶ÏÓ§àí¼öŸ««Øzï½Ì]wÕ*e•³3\¹åËgÞW³Ñ¸1蟮¬”£GÃGÁ«^ÔªqåÑϲøÌbî>»›æ±ùrä£×ë½è1F¥dÇ-¡¡¡xxxj(€³Alb ¬k×®tíÚ•ØØXNœ8ÁîÝ»éÝ»7ÏŸ?'::Zm÷$‰…rã†,ÿû¯Ò^° Ð5mšùk÷èÕ«ë®ß~›\j£:/*ƒe~:L*¶ÝÜ`Ò$3ø—1]2j‹ËÑÁ‘VZáëáK·jÝä*c@TT[·n%88˜={öвeKÞµ ú’Ô‘úó#ç89‡‰­*ý²7•+æM×ëèk€@G;VªµŠ,±Ò¥³è¼)1L}oÑBL‚,Y" Gƒh?a¼R!˜­(üÊâª\°2¾¾ ¨3€RyK™ÏA‰Ua ¹sçR¤H-ZD·nݸví+V¬ »îaK’#õ)æGα’+ÄsÞ0øiÒDF™+ëk€´ôî­Œ%bcáûï3á°¹ˆŠ‚Å‹•6#Sß·n…Aƒtš©{÷D‰sräê>øãÊþR–6KÚ°ìܲTƒŸ¼9òâçéÇA¸2ì _5þJ?6¡:wî3gÎ䯿þ¢^½zx{{Ó¾}{ræÌ™¥ëJ Db{|ýµX©0üŸ¯˜7OhLÉÂ…"PÐâæÿýÅ‹›ö>™"0^‘ X1¸s^UÖO£GEùâ…ÒÞ¾=lÙbZï?¿ÏŠó+Xzn)'CN¦y¬£ƒ#-Ê·À××îÕ»“Ó9kÏ{Dj€¬ŒZµjñǘ˜ÈX½z5Ÿ}ö 4 ((Hm÷$‰+2½ <„veüxóÜ·˜<nÞãèh¡/ž6Í<÷ˆÛ_¦ü\¹"tS†ÁÀöípë”-›5·"b"¾̲sËØss‰šÄ4¯T°ïÕyuP&ŸÊJl‰Õ`ÀÕ«WÙ¿?ûöícÿþý¼|ùRÖ²¤>ÅüØû?~,êòüóÒîæ&Vhz÷Îú= 5@ZœaÌåÎÒìÙ¢ÞPzEÍÊÙ³b¿O‹âçhÛÂÃS~?1æÎ«l%&!†-W·°ôìR6_ÝLt| , @,^s}ž5{âëáKã23~C‰Ýc ñãÇS·n]‚‚‚¨Y³&AAAÅüØó_½*ª8?EŠÀîݦ ~ e __(¥'?yù~úÉ4÷Í4†«?mÚ¤™£íÚéV²´Ô«§ÏŸ/2ÞŒ!Q“ÈÞ›{¼q0îSÝé¾²;Áÿ§üNQNxÄx°¸ÛbB?e~çù2ø‘d›Ð…††R¨P¡¤Žð¦Bj€$ëæŸÄÊÏãÇJ{õêB°›5y~ÿ† ÓóäÛEfjlž6/^@‰ðì™Î¶f²|µ11båçï¿•öÁƒE|‰Ê-±Õ«E€Ô8óà KÏ.eùùåéÖë¨_¢>>µ}èU³îyìw%3; +ÃÝÝÇ3gή^½Jþüùiß¾=­ZµRÛ5‰D¢+Vˆ"†±±J{Ë–â?öúãç'ªA‡„ˆqd$üü3L™’½~brôƒŸ% S§MLŸäÁO—.0s¦H{ïÛWl}i™5+ytóéM–[ÆÒsK¹v1]+¬DßZ}ñ©åC•B–ÚCDbÍØÄØÝ»w©Y³&ÁÁÁ(P€›7oÒ³gOþ÷¿ÿ©íšÄìá›†ÚØÛÿýwÊÁŸlÛfžàçÑ£Għ±÷ãæ& ,ëóÛobk)Û1Üþzÿ}!VJ?íõyûmCikþfÎïÚׯã¨GÌ<>“·¼M…_+0v÷Ø4ƒŸ¢¹‹2¬Á0ûæê°«Lj6IüÄÆÆòØp9ϸt Μ+qÓa+@S§N¥S§NÌÑ+±záÂ<==9rd–ÚaHÌϺuëøÀ/Úö4Ç×®‰6VúÁƒƒ¨¿3j”ùî»gÏš7oN¡B…R=æƒà»ïtõ‡""`útóe ¥È©Spì˜nìè("ؽ³´AÚãÇðÇ¢¡¨Ù‰Œ„¥K•¶*?¯_C‡*m Šà§dIˆKŒãÂà œ 9ÉÑ{GY{i-_<„ÚißþRo$‰™ ç*œÅcû,X ôj鑘(¶¯_‡uv(WN¹Z¤ Œòä1›ÛVƒM@Ÿ}öû÷ï§hÑ¢Ô¨Qƒ;wîàââÂòåËqt´ ™“Mcï5j²{˜ã?„={”6ŸìÛ^J­!yòˆŸ t¶Ÿ~bfÿ¶¾l™‚´”. ï¼£8dÿ~Q !á•Á%ŠeÎ2蛓L»v’SûNqîá9b V©P­p5|jùзV_*¨%÷µ½À ª’:—½\»Çgíhø{ㆲM‰ƒƒøÑë¯i_ö„M@®®®lÚ´‰Ó§O'e5hЀXC¤Ä"±'}ŠZØúO*ÚXèóÖ[¢&MvaŒHË'Ÿˆ G+€ ;SŸ~jf'gÍRŽýüÝKžyNûNíqŠ¿zþ—Ǧþ›ûãÇaÕ*Ñ6«Øº("BÌ“~@_²¤²ÈdFÈ‘j×/}ââD²€~`tñ¢°Ù“rĪ Ý»w3~üx|}}¸ÿ>“&MbË–-4iÒD]ç$FcëúKÀÖæøåKèÜY—¬eútÑÒJ 2¢Ò2r¤(†øò¥ß¹‹¥Û“ þ{ò_²`'üeòN¥ž…Üz¶›ùaGÅ.šè a5(—ÓO{{âUÂwr»ä6ú3åÌ ˆŸ…–Ù³MÙºhèPÑE‹ƒƒXA3u¼çâ"Ð5k*íññpøp8ݺí5í -«€"""ßj+W®ŒŸŸŸ ~¬ [z0[*¶4Ç ôë÷ªèžÇ'OÝÎNŒ©dH‘"" ýçŸu¶ï¾ƒAƒR.Ìœ Iàϳ²èô"N…œ""Ƹ2ÒþÇ•ã¹^à✃eÅŠIÇô6U«g‰DbŒk×*m:X@wõL2j”¨¶ýªúâaøjaϲs˘ü÷d®=¾–¡ë7º µèƉÎNô¶„•Møþ[eiQ`/+Áˆ-ÈÆ…žEËìÙbµK’œ›7Åv¨>uë&BKL‹U@+VdëÖ­l5Èãûî»ï’þ. ËÇõ)–†­ÌñÂ…JÍ }ÃòåŠlnUȨHKñâBàúûï:Û7߈-#ÇD–[Æ”¦påÑ•Ô/¢GÞyñp÷H(wúv  +ìØµ»ÿnÉ÷ß(Ï+_^¤DçÍ›!÷SÅß_-Y"~vY©ud‹ „!ä×ïM›3§(Ù”Õ@T’6V͘1Cm$&ÀÖô)–ˆ-Ìñß'/Z\¬˜¨|›mm$Ò 3 -£GÜ9:êÕk‰ Ÿ·‚¿â&sùÑåTÏ+˜³`R £}U*X m:×Ó§°^9iWógÄåuŠUžM#{{‹mÉGÄ8"B4P4(ó×´E Ðÿþ*mÓ¦AµjêøcOØl S ëI$–ÁիШ‘²ÇWΜ°w¯²´5ãïsæ&BÍ h:Ф\yÐÕÉ?O?>mô)• VJû¢Ó§+Ê G•¬D°+ÄÄêòÝóä´ë™–>ÿ\¹5Ù ¨Ï$>,¶ õSÒ;uÍfÕ"44BCCÕs"›°ê É“'sòäÉ4Y§ß1N"‘XOž@ÇŽÉœ.Zd;Á µû¬ÂÁušÂS<ÆÕÉ•AuñUã¯(·´qž3G1ü&|ˆ"øqqà`ó? ²Ù¦MÂu€£Gáôi¡5²wž?[_úÁ»»èÿ%ɬ::rä‡bРA.,ëY+¶¢O±d¬uŽSkp:y²¨7cIdF¤ACðÅ`&ý=‰óÏC ÿ¹8ºàëáËØ&c)›/Í÷ïUï^çàÊìߤ±ƒƒÐT™³l@•*м9ìÞ­³Íš•¼#‡±Ø’hØ0Q©Y‹6¨—²ìê eË–1sæLæÎK§NøüóÏ)UªTú'¾âüùólذk×®‘/_>zõêE£FÌè±$%lAŸbéXëš¼Ái¿~¢U€¥‘  kþ]体8÷ð\Ê%:ÙÌè7ŽÁÊgܡٳÃ`MwÂЕÇþñG±anüý•вe¢w[fº‘ÛŠhåJQßGŸáám[uü±WlBC`` ¿þú+5⫯¾¢bÅ”ª|)iݺ5•+WÆËˋ۷oóÃ?°sçNÞ~ûm@j€$5ùñGøâ ¥íí·á¯¿D…[kDƒ†u—Ö1iï$Î<8“â1'4gúÃ?ãàqEêÕm¼2ÄãÇ¢„°6·hÎöÒ õe¦NÍä‡È qqPª<|¨³Ížm\±G[äΑ¹¨­” P«–ø[ÂïµÔY9rä K—.\¾|™ßÿÚµk3\Oø—›7oÆU/Ïpÿþý=z4)’H$ê°~=|ù¥ÒV¡‚¨ÿc ‰Ì°þòzöp:ôtŠï;98Ñ·V_ú–O‡)•“Ú!?.ÒÓÛ·ÏÀÍÁÏeª&?ýú‰à2»pq™_zÕI˜5Ë> ÄDQÞ@?øqse¬õ÷ÚšqTÛ¬rçÎ>ùäjÔ¨All,W®\1*ø’‚Ÿ˜˜¶lÙÂÙ³géÑ£‡9Ý•¤€=|ÓPkšã´œfJqýºrùÁLtví2ÒŸ½{E ðWgGãF ïQ¿>xyàâ’ÎùfW¨­[ë>ˆU Œ^o̘1ìß¿ßìþšc|ü8Œ§|¿R¥† Sß¿M›6@³f͘:u*qqqØ+¦mÛ¶ Í—1$$$h®^½ª©^½ºfÑ¢EIö‰'j&Nœh&ï%‰>/^h4žžHšÖ½fÎÌÀEîß×h&OÖhJ—V^ÄÃÃl~²ùÊfMý9õ5âË!ÀAÓ3¨§æüÃói^§kWåGhÚÔ¸û_¬Ó[qâúi*WÖh>ÌúgË kÖ(?O®\ÍÓ§êú”]DFj4Uª(?Ñ¢Mh¨Úž%'$$DS¬X1µÝȬzhÛ¶mh4š4_ÆàèèH¥J•èÞ½;›7o6³×‰ÄmƒSò^#F@ºÉk ìÜ)òåË” „ÒTŸÓ§áß” š‚DM"Û®m£Ñ¼FtXÖc÷“ïï8à@÷êÝ93ô A=ƒ¨Y$í}ÃL·¿ÿV¶–H‰ aT<³Fi+øÛ·‹jÏjÒ©”(¡¿|)zžÙ#F$/å°`¨d.Q«€²Bbb"—/ëJÌk4Ž?N… TôÊ>±&}еbésüå—Éœvì˜NƒÓðp¡æ­RE³Y³FYUÎ¥KMâ+ÀOþcå…•|¾ãsš.jJ¾ïòÑ~n{ŽÜI¹Ìq—ª]8é’àwƒ©U´–Q÷ðò‚wÞQÚÒÒmÝ '?Y„+±I¶k2y×[”ÏD½©qv†÷ßWÚ ¤Jéb µkaÞ<¥í£D_‰ºØDXfˆ‹‹£víÚxzzR¹reN:E||<ÚËW ÂZkÔX–<Ç !¨>Ú§Ž)}EÛ·OH‚ƒ!&&õ »¸ˆl-Ë—Ã×_gØ¿ÐÈPŽÝ?Ʊ{Ç’þ|õ(ù7r€^³ÏNU:Ð,Ïâž¾/Àøñ°e‹n¼s§h%Ѱ¡ò¸£GáÝžN%*+?;äOu ªºìç'z_iîçΉ>Xo¾iÜùÖVèþ}!×§Fì+Pb40 IDATA I›¨”Yž~ʉ½w4)à¹ûìn†?×;•ßaR³IÔ+‘õ­[‹ÚGZ:tqZ._õ‘ê„ïâ/Z%Ù\sâôà>äÏŸeLIÇŽ ¯40 yQ@[@£¿Ûú?»9D[§Žz~¥‡¬d'(P€Ž;ªí†Db—\½*d;úÁOΜ¢dRðsü¸z–/¢‘ÔÈ• z÷Oýú:{—.¢ô°–eË’ ¨ø(N…œR¬î\}t ™ûNèžÇÆe3òÍ‘4,Ù0ýŒdÂåCtóf¡•òô+ mÛŠÝ@”ûIN>½-.øñ#Ò€‚‚à—_ @õ|2Ó¦)nß~kÙÁ½a×Ä2°Ö>UÖ„¥Íqj N¡~0o¹|NœHûB5jˆ'jÿþ)?ì}|PÔ²@>mÅ‘'8ÿð<ñ‰ih†Ò _Ž|x•ð¢AÉÔ/QŸú%ë“+.W†{CãÆÐ´©²~Ì”)¢oTûöpëã]1hüìïoR?LEûö"ÀÕêÔ££ÅÏ}Ĉôϵ–^`§OÃW_)mmÚ÷%Ù‡ €$ªcÉú[Á’æ8µ§³?>GÏ¿gƒßxö,õ ¸ºŠ *¢àeÜK<½Ahd("ðàÅñ÷¸{|“Ç™ü‘"ÐÉùø9·‚çsº’ñþº9»ááîAýõEÀS²>U UÁe5ÁÕ«WÝ ,£Œ¯ €Ö¯‡fÍàìY1Ä\Ð[J«S'¹PÈBprZ ‰u¶Ù³ ¬A}ûB¬N‹NáÂ"`Íî”’´±k PzH Dbzüü`þ|ñ÷ÄГUL(:‹Ê¤yÞ³RE8ÚÁƒMKqÍ)BèDÆF¦zÞŒÍð¡^Vú’:0 [ÊÇ:98Q³hͤUú%êS«X-\]R>!yóMQÑ4\§"幡3þñ‡-”û÷¡lYeÒÞÞ½b¥ËÚùè#1ýú¬['vc­©’H$3ðÍQÌ~@e÷ãøÇþ‰ï³¿(ÿRéTï«Â¬z°³B‡p%åcScY-eÔí_ÈÙ¢\ bŠÔ/Y?i+˳¸'¹\r¥~1?>yZ<@v(ƒŸÜ¹EQ% ¦D ±ªWžÙ³­?Ú´)yðãïo=Á½! ‰êXš>ÅÉ®9>r’!'’oEE>àáã´¾øŒ¿ B‹àÆÚóݼ0× æyÂýײæÓÁÒp3?”{Õ€2O,.ú¥¤`NÓiI=zd –öí¡^=¡ ×çó×fÃs=Cß¾ðZ'-ð÷W@ÁÁBÌV¿7KÖ=x š¾êSµªCK,ITÇ’ô)¶JvÌñš×Ð'¸± ± {Ù§0ø$¼ÜSß©"ѶW«=›+CBÊ´º:¹R4wQÜó¸S,w1Šå)–ôg¹üå(ôü/˜ö{Òñµw‡OLûݳgÙ4@ZÆW®&x¡UØFåA*~6¤M(_n¼Z¼Š-ÛFJýKÕi4àë aa:›««Ðßç²ÌE R”&R$‘ÇÒsKñ]ç›”Uå¨w®ÂÇ¡ýU1N‡¹aA]˜ã7ôR¡s8å hî¢ËS,ÅÀFßVÀ-êóç¡–^f  \IH F¤¿Ÿ>-ÒÆ/õûš¢¿×àå•|‰È‚ùö[e¶T¥JBombáéÓaøp¥íûïá‹/Ôñ'+H D"‘ɼ“óðßäO¢&‘âÏáýS0ø”‰Hû¼s5 q¨CîµjHáüÅùÎ °ÉïfÂ6¯¿.ÊKkÓ¦ââ`õj2Ät÷ÈÄ*lX—HÑþs•XÙJê A"L[ êÚ5Ñõ¾U«´Ï³$Ο‡Ñ£•¶-Ò^É’X2’¨ŽÔ™sÍñô#Ó±m4Ô»û@Ž4Jë<¡ç<ߣÉRjU«†q]±LDß¾ºDo0@æÖiéÖ v쀷ŸoƒÛ·uoäÍ }ú˜õÞ¦¦X1èÚV­ÒÙfÏN=²4 PLŒøµŠŽÖÙ „Å‹­o˱Ûf¨Ëaݺué$Éæ˜ãï|ÏðmÃÑ ¡ðK^™zðs˜Fø²ˆ÷ÛÝãíc?Cµj&÷']úôQ>•öíKÞ5> ìÙ³‡ˆˆt–½L€ƒÃ«òG³f)ßðñ`V†¡diýz±;™áááìß¿ßüNÉèÑ¢Ÿ™>sæ@É’êø#É2’¨Ž@›SÏñ„=øò¯/¡ïY¾:ù–W$y˜?u9ÅâTí÷ Ê™rƒÓì LÑ4K‹F+V˜ìòÞÞÞf@+¸{WÙ%¬FülH‹P¹²n'ä¦D‰%,F½}»Ðþè3h¨Ñ)±d$‘H2Äç;>gÊ?S’Æ_ï†Vÿ)™åô%¸ÏÌâ4¸»ÃÆÝ·¯r¼t©:~d•yó !A7nÔÈj›L98$߉œ;W×1Þ Y_ú)D•+'ˆ$– €$ªcÙjcŠ9Ö áÃÍòÓ¡Ÿ’l]/Á—;ÿ¸´äã„_yŽˆvræÛeÊdÙ…¬Ó³§ÈÓræ \¼h’K?zôˆøøÌõË ºRÚZ¬tõG‹¯¯è”®åæM¡s2$66–Çú äTâý÷•ÛtÎÎ"–¶ÂH»F@Õ‘ ó“Õ9NÐ$0pÝ@fŸ™d«ò¯sP4 u)M¸$àˆo÷‹AƒYº½é(TH´O×ÇD«@Ù¥bóf±¦%~èÕËü÷5#… 'ß:2”8eh€fÍ«™úLšõë«ã$óÈH¢:Rd~²2Çq‰qô îKà™À$[îXØäÌkѺè'Î1ã‚ GWÊwòdx÷ÝLßÚ<nƒ-_n’Ëf›höl帱Ìfå.bmÚ÷î)mjk€.]‚Ï>SÚš4/¿TÇIÖD"I•˜„¼ƒ¼ º¤°/ß’“ª”Û=%þÆ1t_ƒûõƒqã²Å͌ѥ‹r¯âÆ”»ŒZ"—.Á¶mJ›•oiiÒªW×SÚéS“ØX;GEélùóÃ’%¨'ì—d ùc“¨ŽÔ™ŸÌÌñ˸—t^Þ™ —7(ì_Ÿ.H§ÓQ ÛB‡AÌepÒø7„N×"É•KŸÑÇÛ`Ù¢úúk¥:øí·¡fMóÞ31ŒåæÎUj½ÕÔ§N)m³fYˆ¶M’)d$Q©2?ãç±Ïi¿´=;®+•¨ýž”fì¦g Ûg/>ÔÌH)AAJQ«Åáã£Aƒ³k€®]Kž¶o¸cå  ÜÍ3ÌöWK´{7Lª´ `õÒ+»G@Õ‘ ó“‘9~ý„Ö‹[óÏ­ö–®Õ\­"œ Ñ%>˜hÜpr’šR¥Lã·ÙhÝZDjZÂÂ`çÎ,]Òì o¾Q.‡Ôª•|%ËÊ)P@$êé£/†VCôø±vôSÞ+T€ßOý‰u  ‰D’DØË0Z¶àȽ# {£b^l[ÿŽuí®5ŽôŒ_Î-Ê&Ù¦L–-³ÍÝÌãìœüI»l™:¾ÃÍ›ðçŸJÛØ±6ÙoÁ0Vß¶ nÝRÇ5ŠôÅØNNâG¡zM+I–‘Du¤Èü3Ç!‘!4]ؔӡ§ö·J¿Åß§ëâ|ä˜Â>N3…´NwêdeÙ0†Û`ëÖÁË—™¾œY5@߯ë PµjòÎFxã ±¸¥%1Q§'Ën Ðüù¬´M˜ |”X?2’¨ŽÔ™ŸôæøvÄmš,l¿áÿ*ì-Ê·`—Ã{¸ÎV*š79uá[Æ$+T°Âo¾ åÊ鯑‘°aCª‡§‡Ù4@÷î%ï 1v¬M§Š¡çÏ;¯Ù©ºz†WÚÞ|SL½Ä6°ÝA«Aj€ÌOZs|íñ5/l̵Ç×ö•;°µæ·äøHù¸éRŸ„ÅhÑNΜâ[rþü¦÷ÛìÖÊÂ6˜Ù4@?ü r°µT¬˜Üo£e¥‚›f—(>^,¾x¡³åÍ+’œÌ~{I6! ‰Ä޹v‘& ›p;â¶ÂÞ£zÖ¶]€kÏÞŠÂ'ÑN¹é·–gäM²ýñxxd›Ë¦Å0ضM¨^-…D.¸>cÆØüS8o^èÝ[i3¬ÿhjž=ƒµkÅêSÅŠpL¹ãËŒÊC‰õ# ‰êH ùIiŽO‡ž¦é¢¦„D†(ìýj÷ceå¸ô÷Eõ˜0 èêÎ ,ú8Y-5kBíÚºq\¬Z•©K™E4uª²ò^™2"%É0\´Ü¹._6H£“'Er]“&¢KJ÷î0gÜV~ o_QØSb[ÈH¢:Rd~ çøÈ½#4lNøËp…}ˆ×»â4ùkغUñÞ¯#Xîk¹—üö›ù|Î6 ÅЙÜ3¹èÑ#˜9Si=ZÙÌÕ†©W<=uc~û-k °0±5`¸»‹ßá±caß¾ÔË@•-+V9%¶‡ƒF£_Ý@¢O@@€âO‰ÄøçÖ?t\Ö‘ç±Ïöá ‡óK»_D³ÍN…O¹4¡IÜ.âq `A8qÂF¶îÜO9íçupy×¥K«ë׸qð¿ÿéÆ%JÀÿYx…IÓ2gŽR]´¨øq¹ºw~B>,v6·m¿³Æ>ñÜÜ qcÑÏ®Q£Œûn­„††âááa+órH"±#v\ßA»?Û% ~¾jü•~®_kýzO‰p×t‹ J ~D›~@:ëÆÉ¤fš§O“/¯eWÁˆ­'ýz; NZܽ+Òæ{ö]æß~[t9~<ýà§JøäQ}úñcرþ‚{ÃYm$’ÐÐPÜÝÝÕvæ åhÄQÞ]õ.1 1Š÷¾nñ5c5pºwßW$8ºÐ%v(–d?Ú·Ï6׳‡¾}á½Ê×K—Â_dè="_¾|8;›à¿ÕéÓ…*WKÑ¢6Óô4#äÉ#v(uÕ cùý÷Hzõ*˜tLLŒØÂÒ®ò\¸`üõ_{ Z´€víÄËf‚z‰QÈ ‰êH ù7s=‚z$ ~~nû³~@<`ÏžU¼?"qy3iܦ Lœhvw³Ÿž=•Úš³g3ö$Å„ çÏá—_”¶‘#•M²ìeÜ'4@Û·‹²ÄvlëÖðÓOéÿÈDÆâ—_ÂÞ½Bfµn\ËàÇþ+@Õ‘u€ÌË‚S Xä´ˆ„D])GGfv˜É¯!ÂðûïÉZ-¬pîÇïñ'Ë”ú`›¬¿W° XظQg[¶L©ÁIoooÓø2cŠ¡›‡òþ†÷IÐè‚''u]¤ ~LÖUüRŽ: ŠŸ“4vu…Õ«ÅÃÄfI©(bv爼| Ó¦)mŸ~*ö‚옌ìþ99‰ŠÍ“'‹ éáCñ£Ôf~I$Zä Du¤ÈôœxžÞ«{s!ìÕž@$\]XÖcÞ5^­V„†‚··¢ÏÔ —ütŒ & ݖ˯¿BýúÙøÔ sghDFŠñÍ›pèxšI4@³f‰\m-ùòÁ°a™¿žÐ«—ˆÑŸ>Eü2T¼_²¤XáiÛVl‡YeUrI¶#W€$ª#5@¦eÖñY4˜Û@ü\7g7Öö^« ~âãáÝwEŸ-ôŠû“ëTL2õ(M’+tíª´-]jôéYÖEG‹Â‡ú &‚ ;'W.mýÇp`?9r@«VbºÎSf~ÉàGb,rH¢:RdžD?áýõï³öRò<áêíª³Â{µ‹éU=5J¤Ïè1™ l¦CÒ¸V-ý ;ÀÇG©…ZµJ,±ª“e ÐüùÊ`4O±ý%à£ÀÁ¡mÚt¦Y3I$YA@‰ °ïö>|‚}¸óìN²÷ü<ýøµÝ¯ärÑ{b¬X‘,ÓhwŽöLŠ™4ΛW49µ«M«VP¤ˆn*,Lô`0wÞl,|ÿ½Òöá‡B-DÃä8‰$+È-0‰êØCÅQs‘ I`Òß“h¾¨y²à'_Ž|¬ô^ÉÜNsyöH¯¦Ìùóàç§8ö¾[¼c–’¨÷_B` T®lV÷-gg±-¨‘Û`Yê(JkÉ™S¤¾KÄÆš®˜D" ‰êH Pæ¸ûì.-[°7@‘åШT#þßÞ‡EYµÿÌ0ì (² ¢"*.ä’– ¦–š ¨Yf™i¥½™þZÔ\ÞҲ̷rI37L Å}É,EÍ-W$7Ded`†y~x˜f˜¹?×5<çYæÌ ÷œsŸsb§Ç"¢ûg®¼Çyyl²ÃÂBå±2¡ FïF.š)Ë>þX5ÆlT_lß>6:«uÎ’˯¾â—M›Æ&?$*))AJJб«aÖ(HwûîìC×µ]qúái^¹…ÀŸõý ¿ù7üœý”åÓ§OgCº'Mxç¼-_‹XtSn‡„°²ÍVŸ>@ëÖ•Û……Àþý5ž׺̰m˜X¹-³ü,¢ÂÓÓ£F2v5ˆ‰0ëhâĉpttD¯^½àçç‡ÿþ÷¿Æ®!ZË‹ñÞá÷0&j r¤ü®{{ý–Z ¡…šô¾¥KxEëEï!“”Ûžž,=ÈÒÒ Õo:ÔÍ d …j´9e ûAB ʬ !C† ##iii8rä¾úê+ÄÇÇ»Zf‡r€jçVæ-ô\ß?]þIeßðvÃqýÝëÔzÚsÓ£¢€ xe±6}ð¾¬2«T(vîZ¶¬~¶ªýñ[7A‹:åíÜ Ü½[¹-Ÿ|¢Û5Ìå}2ëhòäÉp.Ÿ4¢cÇŽ ÄñãÇ\+óC9@5[wezüÜ7žÜà•[YZaÅK+pèÕChaÛBýÉbß”)¬µ¡\žuKŒ”î‚ •ë_}û-ðüó©~ÓÓ±#еkå¶LƆÄk¡sDZeÊ«š4‰­9BÔ¢ ¢OfU•ššŠ¸¸8ôéÓ‡W~óæMÄÆÆòUÑvý·«æ5†ú4¦í¿/þ°a˜~h:¤r)P¥±¬K;lî½³zÏ‚õ×ûùg`Ø0L/OzŽ °btqRáU^Â>}ø¡ñ_o£Úž8¼½Û·k=>,, ÉÉÉ÷«lï݋ت«wZZ"¶Zæy£º`ûÉ“'¼ c×ÇT¶SRR‹¨¨(ܼyŠ*–L@$ F…wÞyÁÁÁ¼}éééHJJâ=ª¢mÚ6ÔöÙä³»n,vßÞ]yÀSöeR×I¸:í*l$6êÏ?xá$M›ܾ]¹À'X†S , dsð5ôëkôÛãÇ#I ¨Ü>sI—.éïú_~ ÞÞW_ERµ.´Fu?hÛd·³³³‘””„ØØX$''ƒkè5ðŒDÀ™Ë+Õ ¨¨£G†««+¶mÛË*ÙŸ‹-â}%†Akñ)8–ü½‹O.VÞî`借Fþ„‰'ª?ùúu`þ|àÐ!^q:w¬#0º8JYnoÏŒìØQÏ/ÂT„„§NUnýµÆÖ;t ­Ü¶°nÝÚ·¯_}M\ii)$ \h‚HƒIOOG·nÝÌ"7Ó¬[€²²²0xð`´mÛÛ·oç?¤áPP¥Ô‚T¼°å,ˆY üôðìkÓ¯©~îÞÆ‚ƒU‚Øà‚ëL,ÞÀ+_¿ž‚­t ¦SPõÜŸðp ~jr€ˆ>™m PII :vìˆN:á£>R–ÛÚÚ¢W¯^¨ˆ4¬ýñû1eÿdKù£`ös³±tÐRˆ,Dü“=/f3 —ñ&¥±Êc fnçç·}ð[æŠh‘“xx°¥**üû/T÷kþù'ðâ‹•Ûk¹ëܹî×$DOÌ©ÈlדÉdðññA~~>/ÀiÕª"##W1bvŠåŘslV_Z­²ÏÍÎ ‘c"ñR›—ø;ÒÓÙü1ëÖñÿ9WÕ»7ðå—Ø“73Çòw=÷œêÂãD `èPþüIÛ·×o¦È/¾ào¿ü2?„Ù@ööö8yò¤±«A`Þ9@·³nc|ôxÄeÄ©ì{±Í‹ˆ‰–vU&æÉ;ùøñGÍË3tíÊþɆ†"6˜<¨Ìb+,ìÜɦœ!µðê«ühÇ`ÉÖrSE­r€Nþþ›_6¾+kÚ(ˆè“Yç‘ÆÁ\s€Ö_]?÷P ~D",¼ G_;ZüH$,¨iÝš%⪠~Ú·gÓ8_»„†âÌ–Ã[P°, 6ÃóŽ€——A_ši5Še‹WHJÎS9¬V9@Õ[†žy¦þu4”DôÉl[€Hãank%>MÄ~ÿÞ=¨²Ï¿™?vŒÝ^^, ÅÅÀš5,èÉÌTA__`áB6‰^y"ÿáÃ,¯V*­8ˆÝã/¿^xAÏ/ÈÔÙØcÆ[·V–mß®2kdXX˜öëœ?TŸhõóÏõTIó@k}¢ BˆT.Å‚˜踺£ÚàgBÐ\›v?2ËïiÛ˜=[}ðãî¬ZÅF€½ù¦2øÙ¾¥•T?ÌÂ…À§Ÿâ•™ê£Ávîd«¸ë¢zëÏàÁ,O‹b£3‡ÑÑ·¢¸*_œþÅòbÞ>;‘6Þ„íc·ÃQdüú+С0}:šªz1WW`Ù2àÁàý÷++å®Õ«×^ãÿo€/¾H f¬‡ÁƒYòT…¬,àØ1Þ!Z×»røýw~µþèŒÖ#úD1:Sκ™yƒ"!|W8å=RÙÿ¼Ïó¸2í &w› ìÝ té¼þ:pÿ¾êÅY3΃Àdz®™*¾ø˜1ƒ-1UA(d#ä›77Ý{Ü „B¶^HUÛ¶ñ6µæUŸ÷§ö :¡ ¢Of;PmÐ<@¤®òJò°0f!V_Z ¹BµUÀÓÁß ù†Mjxì0opù²ú‹ÙØ°Èæ“OXëO5Ìš¥:§µ5Årx‰\¸T]+ÐÎÈÈ`_µ‰‹ºuãG¦þÉZ•idh BHpà°éÚ&|vü3<)|¢²ßÊÒ ³zÏÂüþóa108}ZýŬ¬€©SYpäá¡ö¹˜2¨>u•ƒ¹RÏD*õî øû³8(,d7yÂíç-YÂ~z÷¦à‡F€ºÀˆÑ™Ê'‹©Ñû—Þ˜r`ŠÚàgXÛa¸ñÞ |Ý{>ì'¾ ôë§>ø±´dIÍññ,ÉYCðS\ Œ«ü4oÄÄðƒS¹ÇFW=Ø©Ò ¦6èÎ :š_F¹?uF9@DŸ("F×Ôs€ž>Á”SÐû—Þ¸˜zQe›fmp`™xí$b6|ºú?E€e+GD7o7~~Ÿ3?_u‚bðöfóìuïÎ/oê÷¸Ñ˜Xm¶cÇ€l¶t‰Ú %K…¢rû™gØÜ?¤N(ˆèåiA9@D¹BŽUWaÑÉEÈ+QM~µÙan¿¹˜ýÜlˆ-Ål˜1cXÞHu#G²DÙ®]k|ÞÌLü\½Ê/`©%­ZÕõ‘Z bc+·×¬Þ}Wõ¸û÷Ùä”U×hÛ³‡ýÒH™SµR'O ÛÚn˜õÇ,µÁϸNãpgÆÌí7—?Û¶ª?Ý»³ÀèàÁZ?Éɬç¬zð œ9CÁOƒ¨Þ ¤i…ø¥KùÁOçÎl‚&BH£@1º¦ôIãQÞ#„ï Ç ÈA¸™ySeg·Î89ù$~ û ÞŽÞ,ùuÞ<69OI ÿàˆÖ_UËÉðîÜa½gññüòþý“'-4ŸÛ”îq£7~<°³g‡ù9@ògŽØïAµõÈn(ˆè@ÄèšB~J±¼ÿ=õ_® Dô-Õügkgü8ìG\›~ |°Â¢" ,LýÊá ²u»ªÍå£É•+¬å'9™_>bpô(›"H›¦p› ooþ>ìØÁÏZ¶ŒÍæ]¡}{¶6 ©Ê"úDÃà‰Ñ5öµÀöÝÙ‡þø‰OUöY,0%x –ZŠæ¶Í+w¤¤° x®]ãŸ`clÚŒWëç?y’]Š-jZiâD`óf6G_Mû=nr&Nd+»Wؾaqå‹Ú¦¥±$öªæÍ,èóf}ÑZ`DŸè/’ îdÝÁK¿¾„1QcÔ?½½{ãâÔ‹ø9ôg~ðséЫ—jðãáÁþiêüìß ¦ü¼ÿ>ëa©MðC ,Œ· þý—=à›oøÝmÚ¨®%F1: €ˆÑ5¶ü”‚Òüߟÿ‡.?uÁ±ûÇTö»Û»cóË›qnÊ9t÷¨6Þ<*ŠMnøø1¿<8¸xèÙ³Öõز…ÍóSÌ_: ŸΦÒ%¤±Ýã&¯Y3™V‘½aäiiÀÏ?óýì3åBµ¤~(ˆè@ÄèK~ ‘×#Ñw- IDAT°2ËÏ-‡L!ãíYˆ0»ÏlÄψÇ]߀U"Ž-b ²Õ—aå6DËÛ»Öuùþ{6bÕADð¿ÿÿý¯î¯­±Üc“R­U'fûvä-]Êÿù·jLšÔÀ3]”Dô‰æÒ‚æ2W_ÅŒ#3p>å¼ÚýCü‡àÇa?"°y êN©˜<عSußܹl~šk>ÿ\uíLKK`Ãà7j}bhR)в%¿ÒÒ’µ®^ ¼÷^Ã×:2§y€(ƒ€˜µ¬¢,Ì;1¿\ý N¡²ßÏÙ+^Z1&¯{ü=šåýT%³ˆ¥úœ1Z(ÀÌ™l^½ê—ŠŠbOC6©aÕµHª?žžl¡6BH£D]`ÄèŒñI£Œ+ÃêK«°2?_ùY%ø±Ú`qÈbÜ~ÿ¶æàçêU–ÓS=øiÙ’-Æ¥Cð#“±©‚ª?Àï¿×?ø1‡OsFQ¥,o%°ÿû?½½¡ ¢O£kèü”ÓOã™uÏ`Æ‘È-ÎUÙ?¶ÃXÜžq ,€µÐZýEöìaó¤¦òË»taÉÎ}úÔº>R)› xÇ~¹«+pâ›@º¾(È@ÜÜ1”s‚»¹Ó¦«V&‹r€ˆ>QDŒ®¡æ¨IÉOÁ„Ý0`óÄeÄ©ìïØ¢#þšô¢#¢áëä«ùBK–°aÐEEüòQ£Ø¬À:¬G‘—¼ø"p俼bQÓ=j})­h ±´TNkÀµ¢|öìZOrIjæ"úD1 â pU ~»ñ›Ê>'±V¼´×§_Ç Öƒ4_¤¤„õSÍŸÏF}UõñÇÀÞ½€½}­ë”‘ÁFÌWÿ@Û®+ëС֗"ÆT}ŽJ|&¤  ˆ¡óSöÇïGØÎ0Ê yåðf·7?3³zÏ‚ÐB˘€Œ $„-jZ••›ÙyÙ2fúMJúö®_ç—wëÆ‚_- PuA9@Ô»7àï_™ôá‡:¤ö(ˆè@Äè ™Ÿ²ïÎ>„ï W™Ó§§gOœû<6ŽÞˆ–v-µ_$.ŽÍì|ῼysàøq6^×®±EMïÝã—÷íË–½(O)Ñ+Ê2°ñãY£#ðÁƮɢ ¢O£3T~ÊÞ;{±+‚üX,ðÃÐðÏÔð¬×³5_äÀ­µ ~6«ó”)샩 S ~`Ù2àçŸoðPPC  ã ¢Oø-»ax{{cÊ”)J¥ t(ã˜>}zåFv6 PΟGú±=»~“dšÏÕÈÎèÕ xî9öèÝ›­éUlÔü¥Kür`ëV6PcÇ»ÇÄ ÂÂÂŒ]“çéé‰Q£F»ÄD˜}äîîwwwú„l,dz¼s瀻w•»tê°ñó« vž{èÒ…åùÔÓÍ›ÀˆÀÇür77àÀ6 ŒBHÓcö]`5Ù¼y3-ZÄ{TEÛuÜŽÆÃ¢¶m‘þÎ;ÀæÍÀÝ»à õÛb1Ч0{6ED°e×mÛ°(3V?õ©ïŸÝ»/ªü,BÇŽlÔü³Ï6¢ûYÃvÅˆŽÆRSÜÎÎÎÆ‚ M}LqûóÏ?çÍdìú˜Êö¡C‡°hÑ"„„„`ùòåÉêÒìÞôÐ0ør~ø!àûï¿W–UürTÿ¥!õpö,°t)[g«ÜZÚ:hÒí®Ox¼4–>Ý»³ È€~ù…­†Q}¤×àÁl¤—ŽK„ÝÚµk©ÌÀ¢££1pà@¸ºº»*&+-- —/_¦n0¢að„èÛÑ£,ðùûo•]Uÿ-—Yq-s>ìqÙW„o߉ƨö ó†ÇqÀgŸ±Äæê¦LÖ®mÜÉΚPðcx”dx”Dô© ¾•“&C¡öì¾ú ¸zUóqÏ<ƒK½}ðYá\ðâPhÅŠ­,­ЀЩ®T LšÄZxªXìöé§ R B! Àì èèhÌ™3GÙ¯¼oß>¬\¹¡¡ óO×$ÉdÀ¶mÀ×_ññšëߘ;ß âðÉùOÀ¡²7ÖÊÒ »#vcdÀȨ0ðä 0jËí©ÊÚˆŒÂÃÕŸ×TÐ<@†×æRpÊ”qœò!WSV±-WSÆ+Wsïœò2 ÛÛ£A_ÍDôÉì °°0jºÖ©”%Ï,_OŸòË;t`kzµnÝ Õ Eee¸UT¤ rþ-ÿš¦Ë,硃"S+‘`Uj*N俢¸Z#S(`ˆàŽ6 oëV\¹q1û R™™,èY½Zu…õ VV,±æ“Oì&ÆO—~Âúÿ­GVQ–Ê¡bK1öŽß‹am‡¸âÌÆÀôé¬Ç®ª^v¤Žd‡ŒÒRd”–"½üÁû^&CFi)<¬¬àne±VV•ÛåeͰÛJÆqˆ¯èT<H¥ùgflOårÏÍÅñÜ\eEÚÉ9{23±25ghvqƒ¡ˆè.9™us­_Ϻ½Ô±³Þy˜=§ä÷°òŸO±ïÎ>”qeªÇJ±“ûÆïÃжC [w°‘^óç³ÄæêÞ|“-v*¼ ª©ä•q2ËMAMEYŽLVë€áNQ‘Öýb ‹Ê€HK°äfe¡@ öÕs€‡ÅÅ*ÎÝ¢"È Ôð. `YõÀR à—ëXÆ+WSö¤´W YËùcê™zГÒRüüø1Ö¦¥!µ‰·ü5‘Ú»{—%6ÿú«j³I…fÍ€™3!}w*¶¦ÁªÃðï“5^R:åtÂúÿ¬GooÃ'Û“'³µPyõ_~ÉR“L‘1s€8Ù‚šŠ€¦¢,S&ƒÂ½ò% ãaq±Öã,´‰ÔK‰G¢YÏžx`e………¸UT„¢25¿ŽDÚÛÚ¢³‚ª<š‹D*ÁŽúЬá<*.ƉW ”}E> 2oÜ0¹ ‹ùùX™šŠ™™(U(t:×R €•@+ ˆ-,*¿/ÿj%°òªßW;¾j™¬¨ ôJÊÒ‚r€Ê]»Æ†²ïÞ͆¶«ãî|ôN†•·6cãµÈ-ÎU,gkg¼ÙíM¼ßë}´iÖÆ@çËÌFfKUemÍ&¢7®Aªa’¤ ®àB~>nòZj2JK!§·™ ´¶±á:íì`k ‘†V§¦ ¹¤„]‘H𤴴Î×s‰ð†»;ÞõôDÛ&ÜmVªP`gy7×Åü|­Ç†8;c¦—z9:ª4–zþÝ ‰ €3gX?Ñï¿k>¦ukpÿN„øâ‡kkqxã§Ppš?Á¹aF¯x­Ëk°Ù ÒªJJ€]»€ TGz5oìßÏ–#µw_*Å…ü|åãºDb°nM\D"´´²BËò¯îVVl»¼ÌÝÊ «´>=.)Q~_ñêø©»¾Ü­¬øŽ½=:ÚÚÂNë×56>b1|Äb¼Ü¼¹²,¥<(º\‡ ([&Êädü/9/¹¸à=//ŒpqE SKJ°6- ??~¬õ5ÛZZâõ–-1ÃË Av ó^in("|r9[¦âÛoY¤I§N(žó!6Jñã•q'êŽÆC-–83zÍÀ@¿*û •Ÿò蛹ù—_XëOuíÛ³—êï¯÷§ntês ÊÊp±J°óO~~­»5tU=¨á4Nù~7++½µŒäÉå•RÕ`©Z ”¥­‹.?Ÿå¾U bœ„B^kNÅ÷®¦–d¦#o±Þb1F« Š*¢+Ȩ Èå,ïÐÁ€£998š“?kkL÷ôÄ4o¤÷öL^V¦¦bOf¦ÖÑ666xÏÓoyxÀ¹)N;ß„ÐÝ%̬è×_Œ ÍÇ=û,Òf¼o\ï`ÓõÙȨ¹é¶¹msL}f*Þíù.|}4§ïü”¿þbÓ4¥`„„°Iª›5ÓÛÓ6jµ½Ç€Û……¼Ö›EEõÊË©ÔT `x-6jtá$ÂI(D{[[­ÇÉ9O4G—¯]ƒoŸ>è^þ‰=ÈÎ>^³Î”¨ ŠR+‚¢ò€è܃ȽyS¥É6©¸Ÿ>x€…IIˆhÑï{yáYGdž~ *¤ ¶gd`Uj*b%Ç ¼èâ‚™^^Ö„Z³š:ÊÒÂäs€rr€;Xàsù²ÖC¹A/àŸ7a‘à4ŽÝ?Æ›µ¹ºîÝ1£× Œk¡µž+­^~>°e °f pGscà­·€Ÿ~2½‘^u‘#“ñ‚‹È«¾l-ØÚ¢·£#žup@ke N #5Äô”*Ø•…5µþŒƒÞ÷ôÄ„–-acaÑ@5dcMZ~yü9ZZK…BLvwÇûžž¨!øn(”DLWYðÇ,è9p€%Èh"@6r8¢_n‡Ï%qÿÁ<‡Š,D놙ÏÎDï>ú¯·7o²Öž­[-°àè¼ñðþû¬ëËÉ9ÿVkݹ[ÃðpMœ„Bôrp@''eÐãB%10+ LpsÃ77ÄI$X–†m(TÓÔ{µ Sâã1çþ}¼éáÑ IÓÇss±25³³µ¶šv°µÅû^^xÃÝö&˜÷ÕTPd.nßfAÏÖ­ÀãÇÚup@î¨!XõœËžFaòa‡zØ{`Zi˜Ö}Üíë–c¢k~Š\ìÛ¬Zœ:¥ýØ  ô¼ö`o_§ê5I2ŽÃ­ÂB\“HpM"Á?IIøW,®Ó°l Ê[w*ììŒ>亱i k™ºªóu±·Çº€|ãï-ééX“–†x5}®\ÎKš~ßË ÃõØÍ$)+ÃÖòn®[……³0ÒÕ3½¼0Ø\úÞ9úK5eOŸ¿ýÆŸê«|V' °o/œñÅ $œ|²P“8\á9Ÿç0³×LŒí8"‹ú}ò¯m~Jz:›{qÝ: 5UóqB!ðÊ+,ðéß¿^Uk$ee¸^è\“Hp­ 7‹Šøó‰9ÂV{­…"/Øééèú”Z#C­F*©[ ÌI(ÄÞÞøÀÛÇss±:5²³UÖFÓwÒt‚TŠÕ©©Øœž®µÛØE$Âww¼çå?ë†I µC9@Z4É …øóOôìÛÇfþÓ¢¸•'N…øá+ÿ4œB’Öc­…Ö˜43zÍÀ3Ïè¯Î58{–µöìÞ­yþEðð`“OO›Æ¾¯ª¬|ù¹®"\…B¸”O$×”<)-U:±å_ŠŠê¼„‚H @W{{^ÀCK¦.¥¤ëÒÒ°þñcÕ‘dUˆ-,tJš®¢V¦¤àhNŽÖ¿»®öö˜éå…WƒT”Dšžøx–©½y€ÜÖç{{ᛀLn™N¦õøVN­ðnw1µûT¸Ú4̧ۢ"`Û6–ßsýºöcû÷g­=cưÄf©BòùA¿‰Ê\/°$ÄŠ€ÈU$R>š«)«Øn¨¹ZH¥¼×pM"©÷˜Þb1/Øéîàë&ôæLHmx‹Åø¢uk,ðóCtf¦Æ¤é…[32°5#Ý𞆤é<¹›ÒÓ±:5÷4-ÿ¶ É+-Z`†—ú99éýuý¢¨)ËÏgk:lÚ¤:½quâÚ;ãÇŽø-°…V÷k¼|‹x©ÙL ô…ærK¤&RÀÕÐg#AÕ {÷ØH®M›TWh¯ÊÎxýuàÕwe(ñaÁÁä{¬ûç®TªÒü­öÆ–'—ãõ[X¨ š´m»…sä‡ÛEE¸VPÀkÝ©ëh¬ VVèfo`øaxÛ¶ð¢aÙC9@†§ëZ`"€—4½&- ¿jHš¾R%iú­ò¤é…«RS©áœ nVVxÇÃÓ==éo¬ ¡¿Ô¦F¡Nœ`ÂÞ½š#-—æj…õK±¥+‡Äf𗦔‰€‡€øQ@ü(Üê‹5Ö¨9ÔÚpqá?\]k.S7¡éÞ½ûàã3«W³jcbx èåÚIp¨D‚µ9%@Žö—¥o% ÒJJtjhV-(r°´D‚TŠ……(©ÇLÄÚÚØ ØÞž<å_[ZY)Y»v-¼:uªósšQá©Ëª­.ööX€eåIÓ?¥¥©]$7W.ÇwÉÉX‘œ\c×r/GGÌôòBD‹°¢–Ô&‡r€´hT9@÷î±¼žÈH¶»E"vwä°©pÒà´¥¹; ÃYÐso(PlØf[++ÕÀ(.®Ú–à[´•í$´“@(L\¿‘"ÚØØ ¿¬ Ù2²e²&·F•ØÂAvv®èt±·§¡´„Ô¶¤iM*ò†fz{£§ƒƒkØð(ˆD¤Å@¾}'\nFË-ËR”;Ó Ø ìêÈ¡@[+lnëòVžÑÀÃ~€¢á~ JKÙh.åß–­ð/^fÁÚI¿B@TÙ"ÂÐeá^kH°½=<Õ4MçËåÈ–Ë‘U)r¹ÊvÅ1ÚšÂõ©™Pˆnõwp@7{{t°µmr‰Û„4Vƒš5àfÍj•4í%ã]OOLõð€[•ÖUÒtQdd2pü8[¶áÁ 5…ƒ_b  7c,vÃÚ'ªKv¶t¶tîiêç@j/eמñv @Ë–€·wåÃÍ ((²³Ù„Ñ99üïuÎÅp€W1à/ÚH€6…ì{÷b 7‡5Õ•…:ÚÚV:èjgÇZæa8 …p ÑZ‡á©% …Ú )KCà”-“!W.×:1Z+kk•€ÍWCf µÞ©D9@†§kPmUMšÞ™‰ÕU’¦û;;c†—Æ4oN>L ý¥AEгk©ž“øãÞÀ¼-ðÅC­çKEÀÞ@ÖÚs¢5 P÷7)³ †àî(´Ì‰V.î,¸ ||øÁŽ——îËBVCÕƒ£´<9 ‘,’à‰ƒyÍ QìQN¬¡åäÌ™ZÍQã(¢«²5$¸|í†î{[XÀS,VÛ¢¤‰‚ã𴢩<(Ê‘Éà-£›½½ÁgQÖ÷zkDå^}r€jC$`¼›Æ»¹áFù¤†´»é¢ -ô™$“±E:+‚žÜ\à\Å(@(â\­ñç}€MÝ€¨ _Íÿ^[Î AV#1À}†¶‚v~¶ðð`‚‚ã •"®°×%ÄI$ˆ+,ÄÃæª O±X%©×߯†f&„¢ ¢AÏÎÀþý€4·ƒp_á Fâ¼ }¾Hu"»›»wÕ|°ìØ¢#Fµ…QíGáY¯ga!0LkH®\Ž8‰„:……ˆ“Hp£°Pen]Y °±AתÝ?hAëJB1 €ôL&c1W´ôˆŸf`$abþ¬1§Š…À¾@ôüÙ†ßÅe)°DßV}•AO[—¶z­¶L†ÅŸ/•*[tâ$$×s>€Âêbo.vvʯììûä å§ååž¡r€ˆy¢¿T=(-­ zöïZ=C(â(¢.BP‹… 8ðKfþ-xZ%ÿÕÁÊCÛŨö£0¼Ýp¸ØÔý_ªP ©¸¤R$VùZñ}F8YYX ÐÖVèt-ÿê®aäå§Ýcã Ã3t1/”¤…¶ ªAÏïûKÑíéI„â Bq°Æ$æ E"à/à`{àp;àq•)%|}Ú>£ÛFˆ_¬,k7ìRÁqH))Áƒò &Q*U~ÿ@*EFii×RÇÃÊ ]Ë碩xmm!¢Ñ„ÒäPQ«´8vŒ=göe¡oþŒÂ¬Ä18  V×Hq€ãþ¬»  »Ç3mŠQíG!Ø=Xã5²e2µ­7‰ÅÅxX\ ™bZk t²³ãu_u±·¯óJÊ„Bˆ1QTƒ²2K:Ä™ã÷ÝÆÀ‚x± çaš€9pѵò ®•¯R.€AnAØz BüB0Àw€²kKªPàvQ‘JëME°“_Ï5¢´q áomÖ66¼n¬X¨U‡òS î±áQáQÑ'úK­Á¥e=á*›……‚hÃÕnÉLM][ZtÂû~!Øz zz÷C¶ÀñEEø·¨»“2Xü‰R)ÒõÜMU•H €¯µ5Z[[Ã߯Fìø——z>u(?Åðèåå}¢ --Z„E‹×êXu][Í;¢{ëáhåÙ¶Ží‘&·@¼TŠø¢"¤êaT•&-­¬T›Š`Ç[,Ö¸*9!„óF9@¤FU»¶ö9âßÖ>pwë÷=ÐÙÞy–NH,‘áŽB<ðT¿Lv––h­¥Ç–Æ$„B´¢¨–JD"ÜðõBtwòÁ _p®íÀÙú l„Vzù¥P÷KÞb1?À)ÿ¾µµ5ZšØB|”Ÿbxt r€ r€ˆ>Ñ_j ~· 7ü|íâN 1X ð³¶F{[[ØØ ½­-Ú”;­¬­Íj9å§Ýcã ã ¢O”¤Å¢E‹°8$¤^×p‰ÐÞÖí˃œŠ€§­Mƒ/âI!„hC9@D'b ´µ±Q¶ä(66FUE!„í(Ò—XÌkÍ (ÿÞÏÚšFVÕå§Ýcã ã ¢OÔSƒ±·náj(è×)}úà¥GßRIDATx×®X€ÿx{c˜‹ üml(ø©§}ûö» &î±áÅÄÄ //ÏØÕ0iYYY8s挱«ALåi¡m-0B!ÄÔ˜SµB!ÄìPDŒÎ>iÝcÃËÎΆ܀ëô–”““cìjaöPZZvíÚ…¿þú R©ÔØÕ1K‘‘‘Æ®‚É£{lx@vv¶±«aÒ?~Œ£G»ÄD˜õp…Ó§O#<<ƒFrr2¤R)Ž?GGGcWͬ» &î±á•””€R* ‹ã8”––»ÄD˜u мyó°hÑ"lÛ¶ §N‚X,ƶmÛŒR—œœœzýaëÒÅQ›cµ£iŸºòêeeeeÈÌ̬ñù A.—×ëº.?£¢¢"h=F_÷øéÓ§(©¶¸®1»¼êóܺþŒjz®‚‚ÁŸ¦Ÿ‘D"D"á•©û½mª÷X×óù~Q›¿#CQ÷{  ]îqmÞ[ ukº¶)3ÛH*•âÌ™3Ê)ÕFŒcÇŽ¥>@ZZZÏ_»v­^ÕvŒ¦}êÊ«—¨™õy“ÑEVVvíÚUçóuùÅÅÅáìÙ³ZÑ×=>zô(µ×P÷XÝsëBןQMÏuöìYÄÅũݧégtùòe\¾|™W¦î÷¶ús6XP}î±®çóý¢úÏH&“¡°°°Æú胺ß]èrkóÞb¨{\ÓµM™ÙƒOJJBëÖ­QRR«òÅE7mÚ„uëÖáÂ… Øð÷ß~û ¯Ozz:œamm]§ó“’’àçç§·cµ£iŸºòêer¹éééðööV–ÅÆÆ¢[·nµª{}”––"++ žžžu:_—ŸQ~~>är¹Ö ÛôuŸ¯«‡èÇéÓ§±mÛ6…B¬^½ÚØÕ19'NœÀÎ;•Ûí۷ǬY³ŒX#Óô×_aÿþýpppÀ»ï¾ cW©ÎÌ6V®\‰•+WbÞ¼yHKKÃòåËqùòe´nݺÆsSRR™™‰Î;cÞ¼yhÛ¶-¦NÚµ6/%%%˜4iþý÷_ܺuËØÕ1I‹/†ŸŸ ðôô¤`^ÏbbbðÕW_aÅŠð÷÷çåkýÈÏÏWN’xûömìÚµ 7n4r­LKnn.ÂÃÃqìØ1ܼyK—.ÅŽ;Œ]­:3ÛQ`0sæL|ùå—8}ú4rrrЫW/ˆD"•ãÖ¬Yƒž={"88_~ù%8Žƒ··7‚ƒƒqéÒ%$%%)ÿyÍ8ŽCDD„ÚÑ«V­RÞã%K–(çS™9s&>þøcš›IçÎÜ9sTʳ³³ñÚk¯¡}ûöxñÅñÏ?ÿ(÷¥¤¤ ))‰‚ŸZJHHÀo¼¡R^TT„éÓ§£C‡8p rÒ¾~ø¸zõ*RSSººMÖäÉ“q÷î]•ò7¢wïÞèÒ¥ æÏŸ…BGGGøùùÁÏÏ¿üò æÏŸo„7=K—.Å¡C‡TÊOž<‰Aƒ!00o½õòóó!‹‘ãÇ#77·V™Y@   œ;wGU™ :::Ë–-ÃÚµk±uëVDEEá‡~( ìÙ³"‘ˆ—LMT}÷Ýwxþùç±k×.•{…åË—cݺuˆŒŒÄöíÛ±jÕ*|ñÅ E÷îÝTë¦%%%S§NÅË/¿Œëׯ«ì ƒ““þøãŒ?C† Ann.''';v ýúõ£‰æ´(((À{gAƒáâÅ‹*û§M›†œœ>|ÿùÏŽ„„<|ør¹˜4iµfÖ`Íš5 Á–-[T¦p8zô(æÎ‹ï¿ÿQQQ8vì¯{üäÉ“ðôô„¿¿CW»I9tèÆŽ‹Ï?ÿ\åCijj*BCCñî»ïâÈ‘#(--ŤI“`mm:tS§NŰaÃŒT{=áwåÊ.&&† …\||ú¨AêÚT]¾|™‹‰‰á,,,¸{÷îñö :”ûþûï•Û[¶láºwïνýöÛܸqã¸qãÆq®®®Ü'Ÿ|ÒÐÕnRòó󹘘nΜ9ÜàÁƒyû9‘HÄ(Ëúôéí_¿žw\XXwãÆ©oSTRRÂÅÄÄpË–-ãyû$ gmmÍûýç.\Èõïߟ+**â8ŽãV®\Émܸ±AëÝÔÄÆÆr111œ££#wåÊÞ¾qãÆq‹/VnïÛ·kÛ¶-Çq§P(¸.##£AëÛÝ¿Ÿ‹‰‰ázôèÁ­[·Ž·ïÛo¿åBCC•Û)))œP(äöïßÏÍœ9“ã8ŽËÎÎæºtéÒ uÖ7³M‚®ª"ñY ¨ì»{÷.>øàåvçγgÏB.—Ã××»víÂàÁƒ¬¾MQE+Žº{œ€Ù³g+·ƒ‚‚pçÎÞ$d½{÷Æ×_møŠ6a Á½{÷ËÛ—___ØÛÛ+Ë‚‚‚Ý»w£cÇŽ(**ƒš|³¶!YYY!$$D9FU=´iÓFY„Û·ocøðáøá‡0~üx8p[·nm°:7E]»v…ªÿ¢®Ü Âýû÷!—Ë…ðFóõüýýáïï¯v®²»wï"((H¹íåå{{{ˆÅb\ºt ©©©¸wïˆ dffò •Jáää„èèh º&HNNVn?zôj»rHݨ»ÇžžžtõÈÃÃ?FYY™²,99^^^F¬•iñðð@aa!rss•e=¢{¬gêÞ/œ©åR<<<’’¢Ü®ø½6µùÁ(ªAxx86oÞ …BˆŒŒDXX˜‘keZè^=àââ¢\hôáÇ8}ú4^yå#×Ìtxyy¡wïÞʹgrrrpèÐ!ú]Ö³ððpDFFB&“ ÷ C áC‡‘‘øõ×_ѹsgÓYgì,ìÆ`ôèÑœ¯¯/€óòòâºvíªÜWXXÈ…††r-Z´à¼½½¹þýûsYYYF¬mÓÊ»ÇÁÁÁÊ}‰„1b„ò0€ËÎÎ6bm›¦ëׯs¾¾¾œ««+gmmÍùúúrß}÷rÿßÿ͹¹¹qœ““÷¿ÿýψµmšž¹\ŽÖ­[ó&lH>DÏž=1pà@šq™R+b‚$ ._¾Œ={öàĉÊò›7o"11ш53Œ;w¢W¯^¸ÿ>vìØaìêBš €1Qb± ,ÀgŸ}¦vÿõë×±~ýz^ÙüùóñôéSÀÖ­[qêÔ)|ýõ×7nV­ZŽã°qãFLœ8k×®Eaa!ïü;wîàÃ?Ä«¯¾Šßÿ·ïôéÓ˜:u*Ƈ 6 b ²K—.aË–-xøð!V¬X¨¨(µõÍËËÃüùó†¹sç*ëyöìYlذ‰‰‰øôÓO§rî­[·ðÓO?!==?þø£r¶æ””Ìš5 áááøúë¯QRR¸pá6lØ <ÿÀøë¯¿”Ûßÿ=ñâÅxï½÷° "%%¿üò ÀÉÉ ß~û-,X <þÈ‘#hÞ¼9 337nDÏž=±páB,Y²ãÆX[[cÞ¼yøøã¾¾¾8uêD"‘Úzìß¿©©©ˆ………ÆŽ‹-Z`ïÞ½ˆˆˆ@«V­Ð§OLžçþýûakk www<÷Üs¸wïÎ;‡~ýúNœ8ÄÄD´oß“'OF—.]t * !ÆC!fâ믿æÚrwwÇøñãáïïüõ×_¼.¯º2dÚµk‡®]»bòäÉʼ£o¿ý...ðööF@@š7oÎUU“¶mÛâ—_~ÁèÑ£áíí#FàçŸF@@@ë:hÐ üßÿýºwïooo|úé§Ø±c‡²¨C‡‹Å ÀZ¼ÆŒƒ€€e÷ÝãÇÑ­[7´k×:t€&MšTç:BŽ€«‹J!„b&¨ˆB!f‡ B!„˜ €!„bv("„BˆÙ¡ˆB!f‡ B!„˜ €!„bv("„BˆÙ¡ˆB!f‡ B!„˜ €!„bv("„BˆÙ¡ˆB!f‡ B!„˜ÿh‡×fñ~IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/compressed-select-nocache-shuffle-only.svg000066400000000000000000000744651416254111300311240ustar00rootroot00000000000000 Selecting with small (16 bytes) record size (file not in cache) 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 0 1 2 3 4 5 6 7 MRows/s No compression zlib lvl1 (Shuffle) lzo lvl1 (Shuffle) bzip2 lvl1 (Shuffle) PyTables-3.7.0/doc/source/usersguide/images/compressed-select-nocache-shuffle.svg000066400000000000000000001020411416254111300301230ustar00rootroot00000000000000 Selecting with small (16 bytes) record size (file not in cache) 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 0 1 2 3 4 5 6 7 MRows/s No compression zlib lvl1 zlib lvl1 (Shuffle) lzo lvl1 lzo lvl1 (Shuffle) bzip2 lvl1 bzip2 lvl1 (Shuffle) PyTables-3.7.0/doc/source/usersguide/images/compressed-select-nocache.png000066400000000000000000001652541416254111300264750ustar00rootroot00000000000000‰PNG  IHDR@°AàÚ²sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìwXÇ×Ç¿—K•Þ«60HÓˆ""`/±£˜hì½ÄKÔ ¿è›bìHbÔhÔ{Á†½¢   "EzïçýcÃz—[èa>ÏsØÙ)ggÛÙ™3爈ˆÀ`0 ƒÑ„Pjh ƒÁ`0ê¦1 ƒÁhr0ˆÁ`0 F“ƒ)@ ƒÁ`0šLb0 ƒÑä` ƒÁ`0Œ&S€ ƒÁ`49˜Ä`0 £ÉÁ ƒÁ`0M¦1 ƒÁhr0ˆÁ`0 F“ƒ)@ ƒÁ`0šLb0 ƒÑä` ƒÁ`0Œ&S€ ƒÁ`49˜Ä`0 £ÉÁ ƒÁ`0M¦1 ƒÁhr0ˆÁ`0 F“ƒ)@ ƒÁ`0šLb0 ƒÑä` ƒÁ`0Œ&S€ ƒÁ`49˜Ä`0 £ÉÁ ÿ(%%%ˆŽŽFQQQC‹òŸ%66ÙÙÙ -F¥HHH@zz:¿‹œœœJ•}óæ bccëJ4"Btt4òóóë¼­ªP\\Œèèhdff6´(µFZZZŒJ!ïúKIIAhh( ¤öÅÄÄ //¯>Äk2deeáÕ«W5ª#33ÑÑÑ(..®%©Þm˜Táûï¿GŸ>}Ю];xzzb„ 8zô(ˆ¨Ru$$$ÀÖÖáááµ"Ó­[·píÚ5AZTT¼½½qóæÍZi£¶¸xñ"¼½½‘’’ˆŒŒÄ™3g¤ò¹ººâСCõ-^µ1b6lØÀowìØÇ¯°Ü³gÏвeKœ={VæþñãÇ+<ÇǤI“àââ”””ÈÍ›““[[[üý÷ßÊ%’’ú¨Îå-,,ć~ˆ§OŸÖZ"‘»v킲²2–.]Zkõ¾Ë˜™™¡U«VuÞθqã0tèPÌ™3‡O;wî>|ˆçÏŸ#''Æ C›6m`bbRçòT•cÇŽaåÊ•æSUU…½½=´µµëAª†ÃÔÔ'Nœ@PP._¾ÜÐâ48Ê -@cãþýû8}ú4ÿU$É;w ¦¦&H{õê>|CCCtèÐ*** ëOMMÅÝ»w¡¢¢‚:Hݰ………xúô)Þ¼y{{{XYY!33YYYÈÍÍEtt4ÀÄÄæææX¶l,,,ùùùHHH€µµ5âããñøñcxxxÈT¢££ñèÑ#XZZ¢M›6ÈÊÊâë)Ovv6RSSÑ¢E >-!!ºººÐÐÐ^FKKK8;;cÙ²eÐÕÕE~~>RRRøiÐÓÓƒžž_WNNnß¾-ZÀÖÖVaÿ•ñøñcÄÅÅÁÄÄŽŽŽ¼²õúõkhjjBUU7oÞD³fÍàêê eeedddàöíÛxï½÷`nn.¨/)) ÈÈÈ€±±1:vì%¥š}_#44Tjô+++ /^„H$’[öðáÃØ¿?"£G3338::ò}¨¢¢‚   ôìÙË—/‡¥¥¥\ÙSRRPRR”””àõë×°°°àÛŠŒŒDXX,--áââ"Õiiixüø1Äb1÷XNNîܹ"’ºÿ222““ ^¿~ ccc¨ªª"""ðôéÓ*).qqqxúô)444о}{èêê>øà~ª²  ñññRe544`jjÊoÇÇÇãÁƒÐÑÑAÇŽ¥žEå9wî„»»{¥¤„„!""ÑÑÑhÑ¢ìíí¥êŒŒDLL ºwï.óñâÅ ùä@¦¦¦dllLšššäìì,÷XCBBH$Ñ›7oˆˆ¨  €tuuiíÚµ|ž™3gÒ|@DD/^$TRRBW®\! RRRâeÿâ‹/ˆˆÈÐІ FÆÆÆd``@b±˜† ¦°ßSSS©sçΤ©©I¤®®NÆÆÆTRRBDDöööÔ¿²°° }}}‰D4`À "mmmÒ××'š={6_çëׯ Ñ{ï½GZZZÔ¶m[zõ꟧k×®´jÕ*~ÛØØ˜öîÝ«PÖ!C†Ð„ äî/**"tæÌ©}íÛ·§åË—+¬¿iiiQ`` B™KKKiáÂ…¤¤¤D&&&Ô¬Y3200 °°0þ~Õ××§víÚ‘®®.5oÞœÂÂÂøò Ô£G@Í›7ç¯U"¢ÀÀ@244$___RUU%mmm233£‹/òåssséã?Üg]»v¥ŒŒ œaaa€"##Oc‡)@u@ÇŽICCƒ¦OŸN?ýô=~ü˜Á–±iÓ&jÞ¼9=þœˆˆ233ÉÝÝf̘ADÒ Ð•+WHEE…¿A iÊ”)äææFDÜ ¦eË–4|øpŠŽŽ&"¢¨¨(þ†Ÿúè#¾üðáÃÉÉɉ?wiiiü_`` ©ªªÒæÍ›)//JKKÉÏÏFŽÉ—_±b988ð_©©©Ô®];Á³§ŒvíÚÉU› ̨øóÏ?±víZ\¾|&L@ûöíѾ}{ÁTÆŽ;0tèP"<<¯_¿F·nÝäáíܹÝ»w‡‘‘ÂÃÃñüùsxxxàÖ­[ÈËËÃåË—ñüùs,]ºÖÖÖ€–-[bÖ¬YU–ÕªUü0ñˆ#øi øå—_àéé ___ˆÅbˆD"+¬OMM ݺuÃÕ«Wp†¹þþþˆˆˆ@DDrrrðèÑ#ôêիʲNœ8ï½÷ÀÉÉ mÚ´Á… äæ×ÒÒBff&îÞ½ "‚ªª*¦M›&˜5j:u‚¾¾>&OžÌ·Ó·o_òFåêêêpttDhh(öìÙƒÀÀ@ˆÅbDEEUùxÊHIIAvv6Ú´iS岯^½AMM ß}÷îß¿¯¿þ[¶lÁgŸ}Vaù)S¦ÀÒÒ***˜>}:¬¬¬ð믿f̘W¯^áüùó€GáêÕ«˜;w®Â:wìØ#F ''áááHLL„‡‡½kii!==÷îÝÀõéÔ©S¥êiÓ¦ ž?^á1 0AAApwwÇ{ï½uuuìÚµ £FBzz:ÂÃÃ‘ššŠ®]»ò2ìܹ666˜={6444 ‰0pà@x{{ã×_ERR,XmmmhjjbÑ¢EHKKÃéÓ§ùvÛµk‡Ã‡ÃÇÇvvv000ÀÞ½{Ñ·o_ >JJJPRR‚‘‘‘Bù˦“þùç~ªËÛÛ›¿.å±cÇbÛ¶mxÿý÷ù¾_rr²À¾²BII ›7oFß¾}eæ111‘{ÎŒŒŒðæÍdeeñç&77qqqŽ€6kÖL¦‘rE|þùç˜7onܸ .`òäÉPSSèQ£dæŸ0aôôô°yóf©ã9rd¥VC•!yý•^W8;;ã?þ¨Q=å122«W¯PPPÀ§¦¦"%%EpNjëÙQ]C;wîDDD¢¢¢øgÍš5kxe«è"""ªõ1¨©© MMM¬Y³FêCYÉÉÉ‚ M6VËœ={{öìAii© ýüùó "ØÙÙ¼¼¼ðã?J9:|öì™Ìz½¼¼pêÔ))çieù;wî eee)Í¿ìf´³³«ß,ݺuÃo¿ý†‡+å;£lÅ̲eËàëë 8p """ðóÏ?+üê±³³CjjªTŸV‡ÐÐPZ´h‘#Gbûöí(,,ÄË—/«]gbb"lmmyE¥´´”_ U]ŒŒŒ ªªŠ/^T¹¬X,F÷îݱoß>Azpp0ºvíZ¥ºÂÃÃñâÅ x{{óiÝ»w‡ƒƒ† ôïߟߧ¡¡-ZH]kÝ»wÇŽ;¤ÎaÙõûøñcÜ´í‡~ˆ-[¶ ;;[Jq([SU444àææ†~øAj_Ù=âááëׯ ‚¼|ù(--Åï¿ÿÎï»|ù2ŠŠŠÐ­[7…m;99á÷ßç¿ôKKK+th÷òåKdffBWW}úôÁúõëáàà ÷åúÕW_áÚµk8zô¨Ôª ///ìÛ·OÊ1¡¼g À)ŒêêêÕºþÊãåå…k×®áÑ£G‚ôØØØ9KôôôD^^þúë/>íÂ… ‰Düu^[ϽÊâáá_ýUàL4-- ÉÉÉHLL„•••àCKÒÁd‹-`ee%÷^"‘žžžØ±c‡”Ò'ë\W÷^jL° Z&%%'ND@@:wî ܺu 7oÞÄìÙ³yGV_}õÜÝÝáêêŠaÆ¡¨¨ÿý7JKKe:¢›={6Ž;Œ;ššš¸}û6®\¹‚¬¬,ØÚÚbùòå˜5kŽ9‚=zàÏ?ÿ„²²2Î;‡!C†`éÒ¥˜:u* 1dÈjùY¾|9._¾ ggg´hÑ)))°³³,g–…’’|||pâÄ Œ1÷RúàƒpèÐ!… ——D"† 888Tê G_~ù%=z„@[[gϞŀ¤¦ZªÂ¤I“лwoøúú—/_F|||œ–©¨¨ÀÓÓÁÁÁåà®±uëÖñÊÄž={põêUŒ=...€Í›7ÃÍÍ #FŒÀûï¿'N ,,Lʦ,>ýôS 4qqqøå—_пÞ†¤Œ9sæ`úô騴i“Ô´ÏàÁƒ±dÉ}––†¸¸¸`Ïž=˜3göíÛ‡–-[bìØ±000ÀáÇñùçŸcüøñ˜7oF???ˆÅb8pÓ§Oçû\Ë–-ã§  €ßÿ‰‰‰èСƒÜ2×®]Ãܹs1jÔ(ØÚÚâáÇxñâ…Ì©´;wî`ÅŠèÒ¥ øt###¬Y³¸p᜜œøÑ£ëׯ#))‰W<Ë£¤¤///WË6O’ÁƒcĈèÖ­Æccc<|øgÏžETTï £ªtìØS§NÅÀ1fÌäççãðáÃX°`?%Û¿|üñÇ=z4Z·n www 8°FÇ£ˆÿýï8w¬0~üx”””àÀ8sæ &Mš„7bðàÁhÙ²%®^½Š/^ðÏa‘H„ÀÀ@Œ5 ·o߯¨Q£ß~û­Ò^Ã7mÚ///¸¹¹aàÀÈÎÎÆ•+W`ii)P¬^¿~{÷îaçÎuÒï âÕ«W¯nh!ÎÎÎðõõ…žžòó󑜜 '''|ñŘ9s&ŸO[[3fÌ€ªª*¢¢¢™™ ¬Zµ úúú‰DPUU…··7455!‹1a´hÑÑÑш‹‹ƒƒƒ¾úê+~ÊËÇÇݺuCnn.âããÑ©S',_¾ººº044Ä|€ÈÈHäääÀÛÛFFFPQQ´´´ ‰ §§ooo^¡‰DPWW‡··7444 ¥¥…I“&¡GèÝ»7¾þúk¤¦¦"77·BG{puu4hlmm UUU,_¾\à4UMM ¾¾¾ˆGRRºuë&ׇ”X,†tuu!‰xÁ²c—|&Ëóy¤££ƒ)S¦@CCqqqPUUżyóàåå333 8·ÿœ6m&NœtìØwÝŽ1%%%xùò%Z´hµk×ÂÜÜ"‘–––pwwô‡¡¡!¼¼¼pJï´iÓPZZŠˆˆäçç£W¯^øôÓOþ‹5kÖÈ<ަ‚ˆês‚”ñÎSPP€ÌÌL~Ž=-- îîî˜ýôÓ–®qQTT;;;øùùáË/¿lhqxyyÁÉÉ [·n­·6ûôé--­J…aÔœââb´k×C‡źuëZF-{{{×à IDATÖ(Kc€)@à‚ýikk Œ-²‰‰‰AË–-akk „‡‡ÃÝݧNªÐ³,£êüñÇèׯ‚ƒƒÑ§OŸ†W®sçÎxúô)oÓV×|ýõ×X¿~=îÝ»§Ð 4£v¹zõ*z÷î'Nl½ï&ùùùèׯ´´´púôi…Þä›R*))Á°aߟ¯ÐDll,† ‚ÄÄDäää`ìØ±Ø²eKC4fJKKqÿþ}„‡‡£´´íÚµShËÀ¨9ýõ +\)W_„……!66¶Ú¶8U¥¨¨çÎC›6mx_LŒúãï¿ÿF~~> ›Ðxùò%îÞ½ ooïj­¦ll4JhöìÙ¸téÌÍÍ.½9r$š7oŽ 6 ##;vÄ7ß|ƒáÇן° ƒÁ`0êF7Ô±~ýz$$$T5º¸¸ÁÁÁ˜6m.ù¨Q£˜}ƒÁ`0M€Fµ þرc8rä~ÿýw™ÎÙ$)[å!¹ZÈÖÖVàS",, aaau&/ƒÁ`0ÿ5ÔÕÕѯ_¿†£Îi4 Ð7°råJ„„„ Y³fæOOOᮆ†222øíƒâàÁƒ°··¯}Ë‘==½jbGGGÃÆÆ¦Öò*Ê#oŸ¬ôòiÅÅÅHHHx ŒŒ¬pyvmPXXˆäääj»÷¯Ê9ÊÌÌDqq1 äæ©­>NJJ‚–––àº/Ÿ¯¾úXžŒ•¥ªç¨¢¶RSS¡¬¬,X\†¼sTöl´‘uÝ–o;66&&&õ² &}\Õò ù¼(Žrss‘ššZ/Œe]U¡*}\™gK]õqù´‚‚Ü»w •’ý¦žƒ¯ÖóæÍ£N:‘ŸŸùùù‘››“ŸŸù[’¸¸8@ÙÙÙ|ZPPõèуßö÷÷'ÿúŸvïÞM/^¼¨vùªÈY™¼ŠòÈÛ'+½|ZZZõ½ŒV(OmOß}÷]µËWå]¿~Ο?¯0Omõñ/¿üBOŸ>U˜¯¾úXVÛU¡ªç¨¢¶ÎŸ?Oׯ_—¹OÞ9 ¡Aš¬ë¶|ÛS§N¥'OžT(smPÓçÒ»ò¼(Žþùçúä“O*”§6uT…ªôqež-uÕÇåÓâããÉÔÔT¡,…F£=}ú”Ο?Ïÿ.\HŽŽŽtþüy***’Ê_XXHÚÚÚtóæM>mÞ¼y4uêT~»> ””*((¨vùøøøZÍ«(¼}²Ò˧SRR’ ­¾ú¸¨¨ˆ’““«]¾*ç(''‡233æ©­>NKK£üü|…ùê«eµ]ªzŽ*j+33Sæ‘üs”••EYYY‚4Y×mù¶¿ûî»{U¨i;ïÊó¢ü9zñâíÞ½»ByjY×AU¨JWæÙRW}\>­))@f ÌÞÞ^0U•€;wîæ1;tè???,]º***7nV¯^   ¼~ýû÷ï—ŠV]_(š*© Š";W'¯¢<òöÉJ/Ÿ&‹+ YW(+++ô|[U9G•™†­­>–5D_•ë¡¶©IÛU=GµUÔSòΑ,/¿²®Ûwµ«Z¾!Ÿ•¹ê yÞž+KUú¸2Ï–ºêãŠênÌ4¨®{Ž~ýµ¡¥¨w؃Á`T“ýûS§„i99¤`¼3ääåL8’”š†jÐ4Ž’Á`0j™øxà“O$SRh’ÓõBaa!R¹á6Fmñ¿ÿ±±o·Åbü£¢ÒpòÔ#Lb0Œj0m–&™ÂÙÀ… !Qã‡ÙÕ2Ož7 ’r&MBb0 †,öîΜ)Ÿê € ãñð!ðúu}KՀܽËYƒ×1̨–™=(*z»maì%KNžz†)@ ƒQ^¿æÍ¦ùøå"ïàâÅú“©A9tèÖ `^õß-~þøãaÚ†  Æ@{—` ƒgÿþýØ´iBCCé‘‘‘ؾ}{IÕø(..ÆêÕ«qãÆ†…Q ¦NÒÓßnki?þx{¿µš€°r%0z4—lß^ç˨™ P-‘‘,^,LëÕ ðóky¦1x¾ùæ,_¾3f̤?|øŸ}öYIÕø(..ÆÁƒÑТ0ªÈîÝÀ¹s´uë@_ÿ­ üöPZZ¯âÕÙÙÀðáÀÚµoÓ’’€ƒë´YfTK¬Z%œ²TUNž‚ùj`vìà®Åº¦ysàÖ­ŠóM›6 AAA8{ö,XaþœœhjjVYž¢¢"¨ÈXi@DÈËËC³fÍ–ÏË˃††† -??êêòQòóó¥ÊU!77Wîq–––¢¤¤Dæñ@AAÔÔÔømuuu„……ÉÌ›ŸŸˆÅb…2•¯“Q÷¼z,X LëÕ ˜>ûùr_|û-•Åm§¦r÷\çÎõ+g Ì:•gëV`üø:kšÙÕ÷ïKOW.^ ØÙ5Œ< j`rr¸ Šuý{ó¦rò´nÝS¦LÁgŸ}†RŸ¯»ví‚••´´´`aaÀJ|=¼~ýÆ ƒ¶¶6444ààà€#GŽàš¹sçBOOZZZprrÂÕ«Wù²7nÜ€ ~úé'´mÛÍš5CÛ¶mñøñcÂÒÒ:::9r¤`deñâÅ=z4¦N ]]]hkkcÀ€H“X¾ãææ†½{÷bÆ èÒ¥ ¼½½˜üðCÁPÿ’%K0fÌ|úé§000€±±1f̘!8FÝ2u*7sP†¶6°s' qÛ**@Â2n쯿€Nd+?pç‹û_†ˆ ZWRò6ÍÆ†›Êl‚0ˆ!Å矎ÈÈHþe^ž'N`îܹ˜3g^¾|‰E‹aùò娷oŸÜ:‹ŠŠÐ»wo$$$`ß¾}xúô)¦OŸŽû÷ï.\ˆsçÎaûöíxöìºvíŠ>}ú &&7âƒ;wbË–-¸{÷.,,,àêêŠàà`üøã¸~ý:"##ñý÷ßóí&''ãäÉ“044Ä¥K—°iÓ&ܾ}[0¥‹Ù³gãÊ•+;v,ÆðóóëW¯páÂ$$$`ܸqðõõERRÀ××&&&Gdd$–/_ŽÄÄDÀêÕ«ñÛo¿á?þ@bb"~þùgHØG¼|ù999€¬¬,ôèÑfff¸sçŽ=ŠððpAÒ””:tEEE8þŸ»»; 8PPÖÏÏåÖýË/¿zøð¡Ô¾ôôt‹Å´yóf>­´´” iÑ¢EDDtåÊ@©©©|ž}ûö‘ššš ®õë×SûöíùíñãÇÓСCy–.]Jºººü¶™™­^½ZçÆ¤¤¤D‘‘‘|ZQQéëëÓþýû©¤¤„ttthÍš52÷Ã?$OOO*,,”¹_GG‡:DDD¤¤¤$8¶àà`AMš4‰ $¨cîܹ4xð`™õW†´´4A?0dóò%‘ŽŽð~êÓG:ß‘#GèÎdA>±˜Hâ´¾›-X ûÁ2hÑ‘#Â4¢×¯ëD”¸¸8:uêTÔÝèIN&24”>åˆ'SSÓ°þa#@ Ì´iœGÙºþUÆþG’%K– -- ;vìÚ÷äÉøøøÒ|||ŽÉ¡U BCCaeeGGG©}eå$ë‰Dðòò“'Oäʨ­­ "¤ð#4òpwwGFF†àKÒÔÔTJ^ððð€™™ÌÌÌ`ee…ììlÄÄÄ@II .„¿¿?0eÊü!±¤têÔ©xòä ¬¬¬àëë‹;w¢HÒ߆Ož<³³3ôõõù´îÝ»C,+<~CCCÄÇÇ+E‚IJÔKÿ899ÕòÑ3*KLŒ´«”~ý€É“eçOIIAqq1úö¦¿“v@—.qË×Ê+?ffÀ•+BåàV~éè¼ÝNL®u±˜ P5()fÍâ&½Ê°³“¾¸› lˆ! øûûcÚ´i000àÓW­Z…>}ú`êÔ©ðóóÃÑ£GqéÒ%œ>>pwwÇ¢E‹àêêŠøøxœ¬Ê=Ü&NœˆóçÏ l{¼¼¼pæÌð/écÇŽaÀ€rëVRRÂ¥K—àïï}ûö!((ŽŽŽ¼ÂðÅ_@__?ýôáææ†›7oÂÈÈ «« ///Á”˜¡¡!¼¼¼혚šÂÃÃC¦««‹sçÎáñãÇhÙ²%NŸ>~ýúñû»ví )™wïÞï¾ûû÷ïGdd$ŒŒŒàéé‰.]º@GG£GÆÎ;ñêÕ+èééaĈ˜4i`Ê”)øá‡°wï^ˆD"tîÜÛ¶mãëîÖ­¯ ©ªªâ¯¿þ¢E‹0þ|hiiaìØ±àóÛÙÙÁÌÌL Ÿµµ5:vì(·ÏÕgûviÛM›KKùe|}}ùÿ==…å/\x ¢"n*뇤÷ÅyT4_?{6§ô”4ܼÉýÜÜjMDfTE¥ÍÍ­æc@Då­H<«W¯ü•Åüùóaccƒùóç×PŒ*1aÂdddàĉ -ÊŽôôtØØØ ]2®ÑÑ€£#ç츌e?•Ïúõ†^½8ÏÐÿYRR8cæ+W„é"°z5÷•3½- ¡¿€±cî1uÌÇû÷¿ÝÖÖÂÃss¹Eàââ"˜’o¬0 ƒÁø"`Ò$¡ò£§'{P¤úè#Þ.‡ÁPÄóçÀ’%´Aƒ¸Y„Êàëë CCC~[ÂÌ Àp5Ø7ßC†-½N!ºu ¨Î L‘ˆ³#’äûï¹r-Àl€*ɦM€¤1‘ˆ3†® Æ`Sƒ)@ £ÉS6õõot€AÍ/ÊOƒýgâ‚ãÆqŽ ËÇû?ž+ç´"–¤'’>¶âã²¥þˆ$PàÎI×® #Ϧ1*Å­[·ð›+ΣGâéÓ§Uª3%%{öìÄÈRDaa!öìÙƒäää*µÃ`TD` ´ Ì–-U3—´8ÃgÉîgÏ8ë%!ðö–6LVRÖ­öìáV‘_~^½úwCG‡S°$©¥ø`̨,X ­ÉýuÃÉó†)@ŒJqàÀ¬_¿žß~ÿý÷1R½í¢E‹*H²xñâ&NœˆlI‹SäææbâĉˆŠŠ’Ú÷ÓO?É ³Á`TDT°l™0mèP`̘ªÕ#iúúÒ+Àtèî].’{yoä::ÀéÓÕvŒW\Ì-ûûo‰Ä¹s…†Ó×®q‘âk³ª€_•mûòKà_w" !LbT‹ÿû¿ÿ Z =z#FŒÀŒ3*=’Ä`”AÄÍH®Ð24¬^(«ò6@ÀÈèÈÎ9?Ló/­Zׯ |xUÄÞ½\Ô doÏ IR £@ÌHÒFÎ;S§6Œ<ïÌbs/á®D_©8c ÑQÓÁ$WùÆÀ>ÄÝ»w¥Ò;tè 3ƒX,†X†AÝ_ý…ß~û jjj3f ¬­­+%_ff&Ž?Ž~ýú þ¥§§ãäÉ“6l˜ÌrVVV4h”••¥Âl0±e ·D]’ÀÀ*›ÀÈ¥o_ÀßÿíöåËœ=°‚wµ gò¿ÿ•3Ôàã=ÊM‘T“ÂB®j€ä0w®ÐùÑÁƒÜ4›±qµÛc(àë¯ÈÈ·Ûb1·¬±². š Lj`®D_Á‚ u?’b­k­Pzöì™ ”Ejj*®^½ŠíÛ·ËT€6nÜ[[[tîÜYVTTüþûïØ¼y3îÞ½ KEîsÿE[[_|ñ"""°víZ>}Ïž=Ø´iƇLÉÜÿÒ¥KtéÒoÞ¼a £JDDŸ}&L>œs”[RRR ««+`Û©7¢T„;+‹péÞ½šBW…Ü\ÎG–òÌ™œö§ Øneرƒ‹™p¦'ššÿî8hÙòíû‚®@ùN¯………ÈÎ΄æa€ëã/¿¦Íœ ¸º6Œ<ïl Œ€¾?yò$Nž<‰cÇŽAUU À”)S*]‡^¼x3gÎàÅ‹PVV– ¤*‘H„iÓ¦a÷îÝ‚°»víÂÔ©SYäsF­RZ*=õedÄ}0W—ò6@g[\~&¨^ì€bcnݤ•ee.TPP•Ÿ¼<@â[ÅÅÀ””¸ð’|÷tŒ±*Àl€ä0w.ŸÿvÛÔX³¦áäyG`o†Ë–-CLL ~þùç*)...|¬.SSStíÚ7oÞ¬tùI“&!%%gÏžܼyááá˜,/ü6ƒQM6m*g³N/«¶ÒȲÀèúunè©üˆ¨×xyyÕ$(ˆ[á.Iù>ŤICBàlM®f$ƒ“'sç„ië×sÑ{ iTS`{÷îåWêêêb̘1X¬`eÃîÝ»qèÐ!AÚûï¿5õ¨9»š»b^çyuÞŽF册:„ï¿ÿׯ_‡žž^Ú´µµEXXX¥ócøðáØ¹s'Œ]»vaÈ!RA@Œšðì°r¥0mäH.Þg]PÞн{@RRÍ”-¹üô0}úÛPôe´kÇ­ôjÕªVšÉΖ½²ZJÒÓãâI:TÚ²˨¹¹À¼rïoïª/al¢4*èÞ½{˜2e :v숗/_bÔ¨Q°´´Ä‡~(3xx8`ÆŒ|šimY?V/k/xY{Uœ±xôè&OžŒüík!tõµk×ЦM›*•™1czö쉈ˆ®æ’xf$AX7Õ%ÉÂ…Üh£R4*¨ŒK—.aÉ’%PVV†ŸŸŸÂ¼aaa ÂáÇe®2j*|õÕWˆŠŠÂ‰'`ooÏÿV–Ÿ+PÀòåËÑ¥KtèÐnnn=z4>úè£*Ë2cÆ ÿÇ)""‚ß¾~ý:ÅÄÄÐÉ“'iëÖ­tõêÕ ÛÌÌ̤***¤gggSHHeee Ò‹ŠŠ($$„233ù´gÏžIÉü÷ßW«šiii¤©©)H+}7–íâb¢Î‰âT"SS¢%Kê§ý‚"MMaû·o×°þß' â+È 21!ú÷¨íãùôSÒÖ–lÒŸZ´n#£|I ‘ '_Ù﫯þ3×Ç;·}àÀÛó]ö;~¼Úõ>}šüýýÉËË‹-ZDÔh” Qii)õîÝ›*]fܸq4Fâî•¥ð”gÞ¼yBb4=ÒÒÒHWW·¡Å¨¾þZ '@tâDí¶‘œœ,¥ÌKòÁÂö×®­Acß}G¤¬,}P..D115¨X1‹ ›35%:}Z˜fc#§ð7ß3Z[sši((( ”””Ç;MF‘¹¹°/ ¨µêãããÉÔÔ´Öêû/Ó(§ÀίLçÎñXjBZ>íÛ·g6ŒFÆ“'ÀçŸ Ó>úˆ‹÷U›(²jÑhÞ<ÎÉ]y:ÇýÅM…Ôññœ«I–/çÌ{$í¾££×¯eT0y2 ¡ñv;&®’ ÌÜÅ,é@]½Ö‚Í65T\\Œ;v ÷_Ïfñññ8~ü8ºvíÊç™={6>Ìo8pyÿ.IJJÂÞ½{ѳgÏúœÁ`Ô%%ܪ/ÉUáffuó¾PdH+@ׯsFÃUâøqnyyV­âÂZHúÜ©eþïÿ„«ç¬¬€3€fÍa^™v@Ò˳«x"š¼ Ѓ\¬I–/çƒÁ¨¾ù¸uK˜¶}{Â_U›V­„nxŠ‹¹Ø`•&'GÚ狆pè«c>½| üðƒ0måJ@MûßÃC¸O¦HC‡„ÈX6Æ çÄRÂS>Z·æÜ0ªE£ñ¤¢¢‚Û·o#;;qqq°°°€¶¶¶ Ï«r‘ˆ¬`eeÍ:üzzˆ…AõCNNRSSѼyóJå/((@||<¬­­+\ Æ`”'4”‹*ÉÇs+Æëy~€$é×O8tá 'ί4«W £¹+)qÁFËkuÀ_pO˰µåœ<—ááÁù*C®ääxyW$@oÝ*ô¤€&íh÷n鈳oµPF•i4#@ehiiÁÎÎNJù‘‡®®.ìììš¼ò;v¬Sǃ'Nœ@‡*ÿÞ½{°µµ•éžàСC(ÿvc0þ…HzêËÂBø’®m*²¤—ÃWÚèñc.~‡$³fÕ‹ò ìÙ#Ló÷F´//ÆýûÜ€•LÊí߯x]½MÖ(5Uz¤Ç×Wú‚bT‰F§17ýõ¾ùæÌž=wîÜihqÿQ‚ƒÛ·…i?üèë×]›Ù€碧Œ˜Î?Q…Ìš%4z63«·`—¦í줽X[Xp£Berà HŽçæ»vUJ–&i”œ ÌŸÏý-CK ظ±ádj$4š)°w–7o€¸¸ºoGUxï½JgOIIAVV–Tº±±1?ZVXXˆ -- :t€QÜéæääàÍ›7RÓ[yyyHLL„µµµÌr×®]Ó'O _—o2Æ;OyÝ`ôh`àÀ†‘E--n´$$ämÚ… €½½‚B?ýÄ…¤ž‚]>yÂù,”$ ‹¥ózx/^¼ÝþûoNá“B,æºåËߦmÛ,XPëNß9ˆ€ðp®óÊ~ÏžIçó÷ç¬Ð5‚)@ ÍÏ?s7~]cmÍ­O­$Ÿþ9~þùg~»¸¸9998zô(FŒ'Ož`øðáˆŽŽ†¡¡!RSS±yófL›6­Rõ'&&¢uëÖ¸xñ"zõêŧóÍ78räˆ\÷K–,Lž<od#b4y.\ŽþˆDõã ·26@g$©ýú«´m3OZðé§Â´=¸uüõ€¿?Û« ''ùAc=<¸Ù¬2äÚÀÔ©œ&•ŸÏm¿xÁy³4H¡<Î(?Ÿ³Ò/Sv®_RR Snõ¡óÑïлw­F9ir4qu›!mÛ¶!==éééHKKC¯^½Ð½{w 2D„qãÆ¡M›6ˆŽŽFLL Ö¯_Y³fUÚïRË–-Ñ»woìܹ“O+--ÅîÝ»1}úôº:,F üèÏðáUü¬6•±¤—Ã_¹òVâ³Ï„QGUU¥ñÔ÷îÇŽ Ó-6+oTU&††@ù Õ²–÷—ã·JLä\,^Ì…0ÑÕºwçFÃΜ©Pù€éÅÛ°s2|}¹v>>ÀºuœÑ?£j0ˆQ!kÖ¬Á;wpäÈ(++#44wîÜÁôéÓaffeee̘1ºººØ/ù X3fÌÀ‰'xç“—/_Æ›7o0nܸº:F#çÏ?9_€’¬XQ?mWÆàFQÌÍßnçåqrKqó¦ôÚóÅ‹+˜/«=>ÿ\¯¬S'`Èùù„³r¼”ËC_º<}ªP¦wʈˆ3^ÿá`üxnɺ™|nýzàŸ„KëäP1îÁ˜ƒa8?ÑßWTüñ°d о=`cÃÍ.ž9ÙV1æÀccÀÙ¹îÛ±°¨V±³gÏâË/¿ÄüÀóçÏݺuãó)))ÁÝÝßW ###ìÛ· ,À®]»0zôhèÖƒm£qR~ôgÀÀÕµadQD߾•U.}úHd(-å¼=K¡ØØpÎwêþá^¢’Tds­¤Ä jH®lûûoÀÑQNWW [7¡ÆXo#\µNn.§´JNgUru› KÀçS=ðÑæÎȆV¥ŠÆÄß}ÇýÔÕ9p¿Ö­«.Jc‡)@ ͘1ÒÞQÿ#DFFbìØ±Øºu+ÜÜÜøô2c稨(tìØ‘Oöì™Àž§"”••1eÊìܹãÆÃÉ“'qµ¼±'ƒQInÞäÜâHROú€ÊÛÒ Ð¯¿rƒ㦼Îã~ááò«ÍÏçë 8³6m¸…p³nÌ}›cÈ!''Æ ƒŸŸ&Ož,Øçèè===\¼x‘O‹ŠŠBDD<==«ÔΔ)SŽÙ³gÃÁÁ:uªùMò#>>܈D}QY €3^•\ðô䉄ÄiÍmÈàƒjGÐ ¸r…›’¤²+î+íºŒáÃKË·ÛÙÙÒN‡$h0 ÒR. EP÷ÁjcÃÉ=jç\êöíÊ)?**Ü\âüùÀ‘#Ü à/8ëñ™3¹ùÑ/Œþáš,£lñ\¯^À† œû„¨(N/îߟñQDDçJªOÎkÈÿ³wÞaQ]鈅¢bE±+jì±k”{IŒÝØbÅšK°aù4Æ+ö^±`T,` ÖE@E…JG¤s¿?Æ-sw]Ø»÷î2¿çÙ'Îì-g'ËîÙ9ï9§/©?ùömá—ÅÐa;@ µÌ™3OŸ>E«V­ðÒO¸þýû£gÏžX½z5&OžŒÀÀ@Ô®]»wïFçÎ1dÈ­îãää9rÛøz5ìØ±aaaøûï¿‘––†yóæ¡FL8]ÌyüX5d£ÏÝ€h€4¥lY eKºVÎ… À¸q :eGÊÆF#°®à¯Û矓MhÓ†4F•ù¯^‘’<#ðææä‹_ù¦›7Ó§«U[ëUtå )?pó&ñF´nÜRxÊÕU±»Óªùÿ©¿ýF{÷¦}E€´›:•<ÒÒHv¡lwH¹$ŸÔTR+KÖ‹¶Q#²3äáQ¼ÚŠ1ˆ!gîܹhòIÔ¾}{T¨PAåY   & nݺ8vìÞ½{‡+V`âĉù¶¬øì³ÏäiìÊ,^¼Í›7Ç7¼¬ªU«ÂËË ÖJ?m,--ammPsŒâ·7-Øuu%ÙâRÆÝv€üü€qµüIi e~úI°ï|.\P‘/[¦ùù²Æ¨ÊenÞ$E‹ódÂrYÙî°0àüyò,qq¤Œ8ߣքڵépVƒ…êÑ–(õíP@($:*ÓûDO.s†nÜ ‚é¼xò„<þ÷? L™ 03뤵Í ÇÈ///ÎËË+ßc<==¹_~ùE?1:$!!³µµÛŒ"Ìq¦¦G\ òðõÕ¿±±±\VV–ÆÇÒ6—·Ëä¸úõéIŽËÌÐjš–-éÛ÷î­ý5<=ék̘¡ÁI#GÒ'¹»«=,##ƒ‹‹‹ÓÞ(M¹qƒãªV¥mÉëaiÉq®®7gÇ:Åq11:3ã—_è[Õ¬Éq¹¹…¿^r2Ç<ÉqãÆq\•*¿´2e¾×Ùk‘2LÄ`0 š+èd©fÍÄ©ú¬ á"傿ãתöÅøõWºé–€œ>­Z@réRí¯£µ !/eüüÔV@L”›KÞH;Ó g•)WŽtÒ]½šl“%'“í­5kH{OY²º€ßv„Bm$É)]š4Ýݾ¼¼‡ÉËusS_Õ»¸ÀB` Ã`yõJµUƒ¾êþðÑF/žî݉¶:^c!xJã#H³à8iSfÐ ÎÒ¾ôàÉÏWúÒ¢‰[Þº¥0hË•h€bbÈZóSbÓøñäEÕ­«ÛûæÁÕ«´li |û­nïѤ yüø# ·ùù‘PÙ… tÝMc‡í1 ƒeõj:ù¦A’Xd(ÈšyoÄtØ@©r°v­Þì8røçÅØÔ”tª( •+“$)ÙÙÀ;œÈ/Œèã¨éG¨S._&^ßù11!ÞÁõëÀ˜1zs~Uñó€¤\œPØÛ“^y{÷’Äsçâ`nþ¨àæ1 ƒ$2ؽ›ž›?¿h¡‚¢‡l-ëÀôì ôÆôÁŸô+Vè4¤’99ÀâÅôܰaÄ™,,… ƒ D—ÈNN&`•ÈÌÌD|||á “‘“C¶¼zô ßúÊ8:’­+H–šy÷8uŠž+Hü¬KLMfͲ`añP7æ1 ƒdͺ“@Íšªí¥ô‰¶ ¨Z6 [-hýK|­V€Ë:ìÛGÔ37'MP‹B¡ ÕoûÍ›©ô>h€"#IŠà²eªÍʺv%Åw4©ú(»wÓïé ô-–0ˆÁ`ïÞA§2óæ‰+èÔ´Åò娒.æÂl¥«$ HV–ªÐyÌ V­¢]W«Æ¨ÊLœHD/2ž?§ÂSEÖ;GB^üækffd!þú‹ôëŽSmýÆÊ› s€ †ÁñË/t³ÇªUI¿Iƒ"8XE糓°í^‹|ÀµÂt Ö’2fføVY`¨†7oÞàÇù“'Ož„¹¹¹Æ Q=oeEÂhÇKNXãëKwß(]:T<{Š,&2ÍJ•‚gÕª‚ßÇAÂ^Ë—/LjO[Ï­Zµ‚¿¿?öïß.]º víÚòã6oÞŒgÏž!((ˆêÖÎgݺuèÕ« uëÖ8sæ :„–-[ª»~ýzܹsZì•›ššbüøñضm-ZSSSܸqáááøV×õ㢲}»ê®_/+&j€8ŽlW)Ç:ªW';B Ýá•…°~~@ïÞº·sÝ:Õ]´Y³t€„Á”ïÐ^™ï¾#q:Yó§O{ú4‚¬­UÃ`iiÄæ×FHç#G ××CðÅÏÇ¥J‰cKqƒ9@"ÓÉÎ$ö‹D™Ö­[ã¥rn&€7n`îܹðõõEõêÕµºž«««ÊõàÈ‘#˜;w.|||ЪU+­®9vìX,Y²/^„»»;vî܉ ¼õãz%3Su×bòdº™¨Ø¨Ú¾]µ'ÄÆò&Y={Ò_†Êb]OJ(3w.²®KÜÜèv^Zé€*U"!«Ã‡åS•DŸÓ§éãž=#!³'OT¯1|8‰›JÔ£xõФ¿+ÃÂ_úƒ…ÀùÒJ1†ÈÈH <Ë–-ËWc“ïÞ½ƒ-ïÓvÏž=3f öíÛ‡aÆåqfÞT¬X}ûöÅŽ;’’‚cÇŽá;ö)bTìÙC‡ J”n×BÞ¿'½¥”éÓ‡<>Ñ­ÝøýÅ yö·ÎøßÿHL†££°»hy5FÕ~°3gåŒN eKUçÇÆ†§÷퓬ó‡Ê"]]Ï>Ïžâs€y’••…«W¯¢yóæH Ž¢sçΘ3gŽÖ×KHH@PP•uµtéRL›6 'NœÀ!C mëwß}‡?ÿü6l@µjÕЉÕ7rr€U«è¹ñãõÖ*KcòÕ}ÿ=w²±QIg+]šhw•Ñå.PLŒª.xÞ<¢Õ uQeÙíÑ®éÿ‰ÌÜ\į[Gâ#G’²Õ|ªQ#w3¦H¶ MVñÑ”a¿Ûô 1(Nž<‰Š+"99[·n…¥¥%¦}ú6cÆ üóÏ??~<+mK»¹¹Á)êi;vì@ff&¢¢¢°eË8;;˵9;v쀗—&Mš„ׯ_ã7¥ýWWW4Ñ¢J×®]áììŒÅ‹ãg¾RV ·nÝBRR¢££Q¢D \¸pŽŽŽrg!¤+äZZ’°ÔÈS ÒÔ ýž=k×c??’ž® V®¤}…*Uô“AçæFoÚ;kqiӀѣ±‚vïFŸ‹éf2Æ'17)ÔE(€S§hM›ƒ‰ä1ôs€rÚ·oòåËcÖ¬YˆŽŽF«V­pëÖ-ùzJJ Zµj…}¼tÔŠ+ÂÉÉ ­ZµRq„,--±bÅ ¤¤¤ ]»vX½z5¬¬¬eÊ”A§NðôéS<}ú”:ÏÞÞ>O¨J•**µ{LLL0þ|ìÝ»#Gޤžspp@gÞ'îîÝ» dggcÕªUpsscÄÈÍU­W3z4i}!5Ôj€²³‰XI¹qƒ@;¨îît1Â+Wˆþ©¨U®#"TŶ ùä/è 77àÀÅX+@J"Ï ¼Êú|ø êü”)üþ;9Ö@àÿÿ5J?ÿ?JpŒ<ñòòâ¼¼¼ò=ÆÓÓ“ûå—_ôc‘ÍàΜ9#¶)ŒüÊ•¢›1q"}MggŽËÌ,úu5áÑ#úÞvväujÅ‚ªë({´hÁqaa‚Ø.ÁÁª/#8Xl«QQQ\… Ä6C/0 ƒÁ,Ju8ß|Ô¬)Ž-¡¢Šˆ/¦><ßø‰ À/_UTÐË—ªZ“Ÿ~¢×BÂoŒš˜¨acTe¾û07G&ª ÐôéÀÍ›Eo_¯g~ÿwé|*§ÆÐ#Ìb‚‰‰ <==QSªßV ÉãëKúTÊ05UM¤’þþþT!PÌœI—s¶µUéþ®Žž=鱟_ÑìZºTQJ eqxQbA15Ú¶¥ç´ƒU­ ôïX©pú4ÑûˆÝWKÒÓU%aLü,F¥zòä þüóO„……ÁÖÖ_ý5ÚòÿòxüùçŸðó󃽽=FŽ©ÒšQ8LMM©¶ †¶ðw ò©Bi€.\ U‡•ñö¦Û;äAd'H&zôˆ´Ð¢•Ÿœà``ÿ~znñbÀÌLûk77Ú‘ T-ô\ Ó¦¡ò±cèÓ¶-)lX­šNmÔGÒ *ýû‹gOqƨv€fΜ‰ˆˆ¸¹¹¡L™2èÒ¥ ò<~Û¶m˜1cš~ªÚ®];•®á Cÿ\¾ ܾMÏ-X Ž-Z“žLJϵh¡qÊUùò¤›ƒ2…Ýòò¢ O7j|ýuá®UŠÔUF‡dÇçúuƒu~Uñó·ßê/É 1ª ³gÏÂRi;4 wïÞEûöíÕ¿víZlܸQÞª!44»wïÆ’%K´ºïåË—‘žž^xà ò{vùrzìá!ÙNrâââ`kk óU«è †¦¦ä[ÏTóß›îîÀ½{бŸŸ<\c?Ž£ç–.ÕÊ Ñ¦ iŒ*“H½|IúÁjÛ}>ó»ïð!%º7R<~ ܺ¥›š&ˆgOqǨ ™ó“‘‘Ë—/ãñãÇòîä|’““J5ÞtuuÅE~]òèÞ½;¬­­‘˜˜XxË9=ÒªæC{òZã™3gŠ`MþW¯ÒsŸÚeItqvFY~ÕÆ‰Iµb-pw§C€ýEJhã¼,ZDgß·h!^¨¥dI IÚ© $.´!66AAAª½À þîOÏžt¡H†ž; MÌÌÌ8+++îСCyóüùs—••%ŸÛ³g׬Y3ùØËË‹«^½º<^]Z<³1ëvììÅuë&-ûòתEå6{•,Éq Z_/+‹ãlmÉë—]îÎÍÏ¿{W±~²óÏŸw}<=i{fΔÀÿ/=ŽSR8ÎÒÒ‹J}2Dö9s†óòòâ:uêÄÍž=›sppàŠFéåääp¡¡¡\ƒ 8µÇ„††r¸ŒŒ ùÜ®]»¸Ö­[ËÇ^Ôb0º#(Hµ>Š.êàè~Ñ"€ãöî-ôå  /µd‰æçöèAŸëæVh3tÆÑ£´MJµÅ‚ß§_Õª—-¶Uª°:@Ž©©)j×®àìÙ³j‘u ‹‹“Ͻ{÷4ÈÒ`è–èèh±M0z eù™_íÚ‘)’çÃÄMŸªX§NÀˆ…¾daÓáoÜPí0Î×T‰ºÆ¨iiÚ]#33ñññ(AøµÆ×6ƒÆh ÜÜ\-¶ F!¬ñ¿ÿ’Ò.Ê‚öàåÿ¨(È«YX¿þZ¤Kº»Óã;wHÁ‚à¯Y·nZöÞ~cÔ¬,-£‚h€òËì•*ÿ Ü¿¯››ãƉgƒ`4"謬,|öÙghÞ¼9êÔ©ƒ ;;û•Š`Ì›7£GF«V­«V­ÂСCˆ¨¨(ÄÇÇcÔ¨Qb½„bËw¬ ˜àÂ{{Ó¢ÝæÍ/¾Ï‰‰6nÕ lÖ,ÀÅ¥H—­V ¨_ŸÔòH:û¥K€º–c2þú‹d‰+³lY‘ÌÐ)ê£vê¤ùù•+W6H4_üÜ»7qâb4••¢££ˆÈÈH|õÕWèÑ£•ìØ1TTÊ»ìÖ­îܹƒ7nÀÁÁ]ºtæ3ÅšÐPR Nƒ©ûóü¹"¿ mÖúI'—vwW8@©¯˜Ÿ´h=öð\]ubŠN(rcT$) 8|˜ž3€ß#Å£q€ÒA\VÓG®j> œáÌòE%::šrLºGêk¼j]°ÏÅÅ€ªã¾zˆ` À¼}{@G?¤ÜÝå‚êùé€Îœ!a2&&ÒÚýTu@·n‘]?ÍÎÏÌÌć ªÐÞ½ÀÇŠq­ZÀ矋gCÑh€†‹!èS )¯ñ›7À¾}ôÜüùš)ŠÎ'È  5tvéŽkkÅ8"B}#QŽSÝý0hÖLg¦è„F€2eã„àéSÍÏ7D ?ü5a‚½·æ1DÇô)†Ž”×xõjºYg­ZÀ!âÙ£5Ÿ AÊ:u€J”PÕȨÛ:~œô “ajJª>K SSÕœ6a0CÓݸA;x––À˜1âÙàaƒÁ¨(€_¬}Þ<Kæ÷Ô¡¨¦Ã_¸@ssIÏ/e¾ù¦ÈlÁÐI_0¿û3p éõÆÌbˆŽ¡Ô¨1d¤ºÆ?ÿLz‡Êpr .SI” èÜâ§Ã߸A×Ï9pxöL167'ߥJQ Cª œ8AÏIx#‘š¨¦ IDAT¶X †èHYŸb,HqãâT!ÿ½uÆÎÌ"#|Ò™šê¼Syƒô%ÓÓ½Ò²³~ïæQ£€Úµuj‚N‘5F•ñâiŒª †¤Ú½ÈÈPŒ]\ˆ¦‹!˜Ä)ëSŒ)®ñ/¿©©Šq… Xî̓Â' PåÊDè¡còª ½{7ÝxÞÒRgø‚!kŒªÌÍ›šk( ޶m£ç&NÇFÞ0ˆÁ`è¤$`ófznöl:ãÉ øþ’£ãð— ~ì²»ÀOs?^çP‚`ì: Ë—°0ÅØÆ9R<{êaCt¤ªO1&¤¶Æ›7'H†ƒ0i’xö%(@¶@5źu£ÃFÏŸ“Roß*æJ”0œâ‘…u€ EÄí~ý5`g'Ž-Œ¼aCt¤¨O16¤´Æ©©tq?ðôJ•Çž"¡äùHªTIÛØÚmÛÒsëÖÑãÉ“n¯søÐýûš5F5 PTðÇôœ#Ð 0ˆ!¤¨O16¤´Æ¿ýF2dd”)LŸ.ž=EB)~€²æžóu@Ê”*EÊ UªÕ«+ÆYY¤ahA‚hçNº3J³f@ëÖâÙÃÈæ1 ½‘žNRß•™<Ù€ÃzÒª: efÌÊ•ìÖ‚`Œ: Ü\`ûvzNB¿=<˜Ä©éSŒ©¬ñ®]$D ÃÆ†4N7Xø ªU»Uóæê;;" 74Ú·§Çš8@R×;Ge”. *ž=ŒüaCt¤¤O1V¤°ÆYY¤í…2&peÜÔTàÝ;ùÐßÌ I¥K v;SS GÕù9s s¿tó&IÏ©k€øâçáà TÛVL`Ct¤¤O1V¤°ÆûöÑ¿Ž­¬È—·ÁÂk1ÈÙeöæø: r刀Ü)LcT)k€^¿Ο§ç$ðgÇÈæ1 ÁÉÉV­¢çF&bXƒEú={ÒÄçÍ3܆¢6F•Û·Ëkb ¯í³Ïij‡Q0ÌbˆŽTô)ÆŒØk|ô(ª››?ü ž=:çÅUª„låô¨PhÚ”ü»re" 7d´BKU”M²¿”a»?Ò‡9@ Ñ‘‚>ÅØs9X±‚ž:T/& ùçä I¹º£@ȲÁ, Å m ©j€NŸ¦û™98_}%ž= Í`Ct¤ O1vÄ\ãÓ§'OcSSRÅØàáí êÝeË–ü¶={’:×7M ê£ÆÄä}¼T5@|ñóèÑØÖ¥Â ƒ!(ÞÞôxÐ  ^=qlÑ)| @m0ø´k¬]+HÏU½£®1ª¡é€BC+Wè9ÖøÔ0`CtÄÖ§ÄZã €{÷è9£ØýT5@vv‚k€À‚8‘Æ‚6a0)j€¶m£Ó÷»vêÖφæ0ˆ!:L$|}é9£ÚýÔ:@ƒú÷Ç#€ïݺE2«”Û~ÒÑ}üHzÛ)Ã>Æ ¶Ä`0t ¿ês‹ŠêÅFƒH5€Œ•ÆéƨññÀ³gâÙS‡ÑI€NNÀ—_Šg£p0ˆ!:L$<úZãà`àÄ zÎh2¿”Q#‚f Âcj ´mKÏ© ƒIEĘ™‰c £ð0ˆ!:L$<úZã+èŽØ aZpt4ÉÓ–agØÙ1 PÑD$ н{@PblnN²¿†Ó1D‡i€„Gküò% (3¾ªŽÃàÉ#žÕ*íÛÓcu4@¿ÿNûô*WÇFÑ`;@ C'üü3 ª]øúkñì 5hFÑiÓ†#……ïÞ‰g:’“ƒé9öûÍpaCt˜Hx„^ãøxÀLJž›?ßHuy8@LT4J–š6¥çø»@bk€öïRSãZµHO†abTPZZ.]º„ýû÷ãåË—ÿöí[ܾ}›zëÁR†2L$Á4@Â#äge›7ÓsÓ¦q¨Q’G <Ó77`ãFŘºyøçÅØÊ 3FS:¨>¢:@FFªV­ téÒ%Ïs „ßøn=ƒÁИ£GÈHŸT)#o À4@‚¡®1jz:`m-Ž=Êð¿&ʕdžn0ª˜Ìù+++888 44TD‹šÀ4@Â#äÿò =3†d†%99ÀÛ·ôܧ ¦*:üƨ™™tcT±4@ññÀ±côÛ¸6|ŒÊR& oÞ¼‡‡G¾Çíß¿ŽŽŽpqqÁòåË‘«\ÄÀ‘#G°~ýzê¡ }¬¬O‘‚=Æ8–­±®¯?mÚzÜ»§›˜¬ÇìA¯I7T_hã¥KIÌO6.]°±@4@«W¯––½8¦wÖSa°•+WR }ÙçãCv¢2vq:tÆzéb|åʬ_¿ýû÷ÇöíÛ‹#Ï!ÏŸ?çÊ•+Çíܹ3ßãÂÃù7oÞpÑÑÑÜÅ‹¹êÕ«s+V¬?ïååÅ͘1ƒKHH ʰ1籇GGº6‘‡‡GÇyzrÜš5’°Oçc__Nù'´l)-ûŒ`¼e‹ò'p½z‰o_ݺ {ŽÛ¸Q\{t=þøñ#—À…‡‡sÏŸ?ç¹â€ ÇqœØN˜. AÏž=1gÎL™2E«sW¯^+W®ÀÏϰxñbê¿ CÁ‹@ݺtåçÀ3ñh7¤iþuü¸xÆ …­|ýæÕÂ0Œ"ñèïàÄÆŠ—muå Э›blcü÷`k+Ž=B¦M› i‚Q…ÀnÞ¼‰nݺaÅŠjŸ/^ 66V>懻޼yƒ *n'ƒ¦8ü¡‰k¼aíü´h´{´•J¹}[ç÷“ù ™H7¨kŒ*«N"†ˆ/~2ÄxŸâ†Ñ8@©©©èÚµ+\\\ðüùs,^¼‹/Æf¥üÜN:Q1ÐÖ­[cáÂ…ðññÁìÙ³±gÏÌš5K ó‹5¬ðèzÝ»é¹9SÓ9Ì‘‘@D„Nï) òéÏêé†ü£ê»PL ÀÿÓaâgãÁh 333Ì›7®®®y3kÖ,tíÚU>^²d RSSáçç333=(¹åªÞ¿O·‡7t^¿¦UßU«ò!ÓéŽR¥€&Mè¹›7õ«⋟‡' [Æs€¢Ã4@£«5 îÞUŒML€y®×èI?ÒÍ“ Z`0 nQÓ—("8{–žcSÆs€ †Æðwzõœ©Ùý‘aLa0ÖL¯ˆ¥JM~ú‰t=‘Ñ®IÏgÌbˆÓ .ÖøÕ+Õ”à…ýžçÎå}’1;@J)ðÓé¾tïœ,œ(%XµŠüoå—x`»?Æ s€¢Ã4@£‹5Þ¸‘þUÜ´)Ðú*o÷‡ßÕ˜ ÞÓé–ªUU£^¹¢{ PR°lq|~ü‘TVÆÁ«VѽŒòåÉsááÀ…ůç×­ˆ[8vY•² >Ø`Ct˜HxвÆ'O’82J”&q¿Ò½0œœH—HÀ8 þîOõꤢL¤{øQãâbqäˆv ÈHÀÓ“lØýü3©ÎÀ§bE`íZâøüðPºtÑì6TÖÞ\+¶ zÅhZa0 ¦ž¢¬1?õ}ì°tØìàm Íœ©( ÈïdiŒšð× ckû!dQ/^”ÍT†••f ×¯Õ«I?¯Œ õÇT©|ÿ=0~Ë—/ǸqãD¶Œ¡k˜Hx »ÆëÖÑã/zæ¢â~Þ6ù¤I¤w ++â)cè»@Ô˜H(h(ׯ«×…„¶,õêÑ~–KÀöí@XyÛZY a±áñþã{ø<ôÛ ½#YèÉ“'òtÇíÛ·ÃÛÛwîÜA`` r”‹‘0 ¦žÂ¬ñ›7À‰ôÜÊ6§é¦§ÖÖä'4cÓiОi€„nŒ‹/(=úÓ§À°a@ƒÀž=êÛÏÕ©CІ„ãÆQ-ܶÜÝ‚´ì4ùØ$ÓDDkô‡d kkkddd ==wïÞE»vípØÊ™Ó OaÖ˜_ø°qcà³ ¼Â‡#G*¨žÌw€nßÖúþ’BC «¤{èÆ¨•ôA` ðø1ðÕW@£FÀÁƒtŸZ û÷Ïž‘Ý!s¦zU!-; [þÞB͙Dž’ì«ìׯ&L˜€ßÿ 4@£F‰ˆˆT¬XQló £æÃFPfµÇu`•ÒNŽ©)É+Vßzð€Ä$ õ§7ӉЛpÿ¾b|ø«V­‚…¡~ˆ2ÔÂ4@£íïÚEˆ«Pèùp5}P¿~$¶ ŽÚµåÝôtàÑ#­l ññ¤¶Œ’%IÁL$ P&€xDFªw~š5#e="»CÌùÉŸ\.ënÑB¿Ñ GÅD -©·Gbb"Î;'×øôïß?þø#ªU«ðððÀÔ©SÅ4‘!L$<Ú¬qn®jG‹ÅƒžÀÔï<=ùý÷ù_¨ukzl¨: w˜H8P,Õ:@­ZþIv‰ú÷'ÙŠŒ‚9| añaò±µ¹5¾mô­ˆéI9@ðóÏ?ÃÙÙ ,@XXXÁ'0 ¦mÖø?H*± kk`ôû5ôOîŽUÃ\|ŒE­¡Ä4@ÂQµ*I]—i€d´kœ?ܽ ôî-–u†ËšÀ5Ôxd“‘(W¢œHÖèI9@vvv¸|ù2®_¿333tëÖ :už={ðQ]ùNƒ¡sø©ïSûEÀúÔ!z² ÝÀx "ò3ÀÔ¤À3„§cGúß—.‘š@îîâÙdȼ ÀHÅߤ©‰)f»ÎÑ"ý#)HF5°téR¼zõ ,ÀùóçáììŒ &à¶¡g“0T` áÑtƒ‚€^„až¯éiÆÀ—_|±Ö­éXDh( ‘’Bà ¦–N€nÝ2qæL<®]ºuÛ"ÃfÍMz÷§O½>¨[¶®HÖˆƒ$ ¦¦¦èÑ£>Œàà`4nÜ“&MBÆ ±k×.±Ícè¦Mט¿û3 k"ÊžTÓôT‘…½=-’æ8«04˜H|û-°wo,rsµëÆP%86gžÓæ´Ë#£Óˆ‘´¤Œƒƒ¦M›†`ß¾}HKK+ø$†AÀ4@£ÉGDÇŽÑs«ªo¥ÛfW­ ªùAÄ4@’ÀÔ¨\¹²¼@.£ðü|ëgpPhú\«ºÂÍÉ-Ÿ3ŒÉ:@ÏŸ?džO©(?~Ä?þˆÙ³g#&&Í›7Ç”)SD¶Á0.6o¦«è6m:çyMOgÌЮ–¡;@§QhÃPˆIÁ¾Gû¨¹¹nsE²F\$ëíÞ½±±¤8ÓÖ­[áççsssŒ1Bd˺†i€„§ 5NM¶m£ç6¶Ü (ŸggL˜ Ý ÝŠŽ&5Œd88eʨ=”i€„'33ññê{14cÓMÈÈQtˆ­ãP}ëõÑ"ñ¬ôúõk´oßpüøq,X°«W¯Fdd$RSSE¶Ž¡K˜Hx ZcZŸìX.íoóšž~÷Pº´v7nÒ„äÑˈ‹#-¸ -*@3 ðÄÆÆ"€¯ÒˆÎøzN¦f¥bkÐVjn–ë,˜šHÖɾjGGG#&&>DëOEÕ’““‘¬\••að0 ðä·Æ¹¹ÀúõôÜú.À$4D1aexzjc RžWCÚÒ"ži€„G_ ø´xô>Ø™9™‚ßKŸì¼¿ñiŠ´ò6å1ªé(-É:@ãÆÃ¢E‹P¿~} 4NNN Bff&ëÆ`è__@¹æ¨¥%0è%¯ééˆ@aÿî 9 Æz€Kþxþ·LjS#ËG_ˆ.¿Üþ…š›Úz*J˜—É"ñ‘¬Ô¸qc<}úç΃ÀÌÌ §N‚ «snT0 ðä·Æ¿ÐŸ‰XÒí:,î)ÕÛ21É»é©&rAD- ¦}i€N<=8úïQL??]ðûéƒãO#<1\>.a^“[MÏ YèòåËxôè5j333@³fÍЮ];‘-c覞¼ÖøÁàêUznòÞîOß¾@½z…¿¹ºÎð™Z` I¡ PrF2.½¼$où{ –^[*è=õ¿íŘfcPΦø´½P‡d ””¬\¹•+W†««+,X€Ë—/#]9#ƒa0 ð䵯üÝŸ±mÿE™€sô¤&m/òÃÙptTŒ33‰dhá1 ðèCt6ô,•%^W½ð[Ðo‚ÞWHüÃýq/êž|ljbŠY®³D´HHÖêׯ‡uëÖÁÆÆC† ئ1FÁÿ‡Ós‹Kòšž¶o¸ºýf†¨ÊÉÞ¾UŒMLX°b€,üÅgʹ)8þô¸ž­Ñ koÒ @-ûZ"Y#$ë@ZZ|}}±yóflÙ²ݺuÃÁƒó=þÒ¥KØ¿?^*·³Î‡ÿþûÇŽÃ¥K—Xui‘` áQ·Æ[¶Ð-¾:ÕŠ@Õ뼿¯¢îþÈ0D("‚® Y±"Òσi€„Gh PZv·Wû\.—‹á'‡ãÊ«+‚Ý_þ}ÿ/·ү©8¶½P‡d íÛ·£|ùòðññAÿþý†Ã‡cÀ€yžÓ¶m[üðÃ8wî:wîŒntË»~ý:š5k†Ó§OcéÒ¥èØ±#K±¦þüüþ;}̆ëiÈÅèÕK7¢Ä°ûÃ4@Â#´èBØ|Ìú(Û[ÛÃÜÔ\>ÎÈÉ@¿Ãýp?ê¾`6èšµ7×Rm/:Të€6UÚäsFñA²PÛ¶m1räHãäÉ“¸páB;4‡½{÷pðàA„††âñãÇð÷÷Ïóø `ñâÅ8pà®]»+++8p@×/…QL$<ü5Þ»—Ô$”QÓ!ŸÝæ•‚ž3G³¦§šÐª}­/h¤ˆ–)ðL$ÜÚä7˜|PjzZ¹20l˜înjk Ô¯OÏI}ˆÕ*VdædÂ7Ä—šÐ`†6k{Кw©ïÐc_D}ˆÒ§‰Z³ñÎFª˜cýrõÑ«®ŽvuÉ:@Š={ö`÷îÝ8þ<>~ü¨q  €€¼yójŸ‰‰”/_^>W±bEDEÑoèÛ·oãêÕ«ÔC6.úXYŸ"{Œq,[ã«W¯âÜ9àùsù(e‘îÿnø4úÄŒ€¥¥níiÓÔ³wîHf}ÔŽ_½¢í­Q#ßããââpùòeñì-ãK—.Q ]^ÿò«ËHz®aV,UÙ/ˆ¦k–ë,üàö®8÷Uâ+´ÿ©=’2爽>Êã”Ìl>º™z¾—e/j7Kv|HH®^½Šõë×ãæÍ›ÈÍ5Žâ!YhÑ¢EhÖ¬Ž=І âèÑ£ˆ‰‰Á‘#G <7$$ýû÷Ç–-[P¥JµÇÈÒée5†dÿÎ4”ú$FÓ òóSß7´ÜÓwJ"i[[`âDÝahµÜò÷÷g} &))I0 ?üÕ¯~?ê÷ªî«ðEí/¨c^&¼D‘ž-½ò,ÛïmGj–âýX±TEô¨ÕCD‹¤‡ Ç)ç¼J‡èèh”-[Z‚ž={bΜ9˜2eJžÇ½ÿŽŽŽHHH§ÖoذçÏŸÇ… ‹/¦þË`:“þ¤2L‘‹Î.° ®˜üþ{`õjÝßüáCº/˜½=ÑIµ²{•*¤V€Œ—/YÌHÉárPqmEÄ~Œ•Ï]qŸ×ü\å¸GàÏçRó}êõÁɯOÂÌÄ R ;757ÔÄÛdEï®Þ˜ßa~çFGG£iÓ¦Å";W²;@+VDJJ V­Z…±cÇböìÙ¸téR¾çܼyݺuÊ+Ô:?/^¼@l,yƒ—+WÕªUÃíÛŠ’ÿ÷îÝCóæÍuûB ÁßýYÐèPÎ¥%  A£F€bœä¡ÑŒ @9nf89‰gCP®…_£œ‡èâÜEå833tªu æÿ|þ'&œ™ ¸šräß#”óSÒ¢$&µš$¢EÒD²PDD6lˆ'NÀÞÞááá{öìÁÊ•+qöìYŒ?^/¯¡ 8üÒ›èèhÄćÑó3³xm/†*UÆss E zNªa°×¯é‚NNÄþ|`u€„G¨:@'Ÿ¤Æ}êõ¡Òß•±6·ÆŸßü‰ÆŽ©ù]váÇË?êܶÂÀo{1¶ùXØ[Û‹dt‘¬´víZôîÝÿý7Ö®]‹'N K—.UÛÃÌÌ óæÍƒk>UkgÍš…®]»ÊÇÓ¦MÃòåËqýúuÄÇÇ# 5Ø·Þa á9}ú4¶l!2†T¹û缦§sN‘5”z@ZÖX } D NŸ¢æ4È»ÞØYÛÁo„œíœ©ùU«T:®ë›¿^þ…G1äc33Ìl;SD‹¤Kþ?iD$22ƒ ¢æ\\\P®\9DEE©8*ÖÖÖjufÍRí}òÕW_å›ZÏVHxFþÕªÑs+íÿD*Môî­šª®k ÕÒà‡ÿóŠ¡{„¨tëí-ü—¢Ðz•¶,­‘X¸R©J¸8â"Úïjw©ïäó³ýf£¼My ÿl¸NíÔþîÏà†ƒU5A²;@-Z´À¯¿þ*×ìdggÃÇÇ999¨^½ºÈÖ1†Å¾}Àû÷ŠqÛ2OáüïYú ]µ½È¾ôè ÅǬP±þò¨ë+3+έãPç‡GiËÒò9Æü1&Ï–Bò(æþzù5ÇÚ^äd Y³f¡téÒpttD£FP¶lYÌ;ûö탩©­€fà IDATdÍf¦ŽÖ®¥×xsu^ÓÓví77áqr¢5FYYÒì _ˆi€„G Љgtú{Aá/>Í+5Çé!§aif)ŸËÎÍÆ £ƒp;âv>gê~ÓÓ.Î]ТR‹<ŽfHÖ“°´´„¯¯/îß¿///?~/^¼@Ó¦MÅ6¡c˜HX.^BBk\Ý<ÍŸñZ¾èc÷G†!„Á á1 ðèZt?ê>ÂÃåãæ%ðe/µ¾N×]q`À˜š(¾R?f}„ÇA<}ÿT¦ÈÛä·8üä05ÇÚ^äd M›6ÅàÁƒñùçŸ#** mùÅÔÓ Ëºu Xã͵×Ã$[©éiýú€€ý•Tà;@·õû+Y#ÂÃ鱆 Ö LXt­⇿zÖ% u­A.ƒ°ùKºòr|Z¶0µ@ﺽ‹|]¯N^˜Üj25 Tk ]ò{ÐïHÉTü]W.]Cä^Æ„ä +W®`Ñ¢E3f ~úé'¸¹¹aÉ’%8wîÆŒ#¶y ` a8p¸rE6: +d`tÒú OORýYŸ”*4lHÏIi¨`L$<ºÔñwºÕì;k;\{Ó›ðUCº¼ÊÈ;xd ²r³ò8«pdædbÃúïÚ³'%Êf¨Gru€’’’P±bEù¸N:7n:vì(¢U !a Ý“˜̦vÀ¿ÃêF;`ýD©½C™2€Xkߦ ‰Ïɸs‡Ô!’…t€„¨”Ëå"úC4Þ$½¡¯“^ãUÂ+¬w_®5º|!#A— ¾þ§¨á/eLML±¯ÿ>Ä}ŒÃåW—åó~/ü0úôhì°ŸêÊ^þsP¥ŽÑÄ–436B$çåææâÚµkHLL<~ü‰‰‰8|X¡n2dˆXæ1Áüù@LŒblS‚ÃätŠ,&L ßÅ M`ÇÅØv€ ÇÌ*Îò#"9"߃3!gŠ•¤+^&¼ÄÃè‡ò±™‰úÕï§Ó{XšYâôÓèìÓ÷¢îÉçþsåmÊc½ûú"߃‡ŸoýLÍo1¶V"ý]’s€jÕª…óçÏãüyºˆÔªU«äÿfqMíú1ŠÆÝ»Àï<ó†/öÀ⤞šžj?ìï¿I]")t†/D@4@¶¶¶0ÿÔ3,—ËEÔ‡¨|œø´¢éY|C|ñKOq[/è“ÌÌL|øðEº?üÕ¡z”·)_¤kª£”e)œ~íwµGH\ˆ|~à p,é¨Qwöü¸vOÞ=‘ÍMÍ1£­ˆ×†ä -[¶ˆmCÏœ>}š…ÁtDN‰jåæ*æêײ,£:¨RE¿Æ)Ó°!Ñ}ø@ÆII@p0Рx6É(ÄPàÛ@¬Û¹f5Í“ƒ7Io™©s½Ÿ°ø0<{Žzeë z©‹   "‡Á„ ñ)oS~Ãýà¶Ë U-¸²Ž%1®ù¸B_›ßöbH£!p*ãTèë7$ç1ŠÌùÑ›7«V>89ͦ¿TLè£éiA˜š’l°«WswžŽ!LJ Â$x•±¤#ªÙV“?ü_ùSÍ.φœE=×âáéB™‰;Š« Lu€ÀÙΆ_@ÇÝ‘˜ž(ŸÿÎ÷;”³)W¨ðÛ½¨{ð÷§æXÛ íœ´téRÜ¿?ßcXÖƒ¡Êÿ‹Ñs#FÍþú=éḸèϰ¼hÓFÕ=Z,kqq@ŠR™€Ò¥Šn ÚŠˆäˆBÝÎÊÌ N¶N¨n[rr”ÖæÖ*÷›|V‘fíâ‹Y®ªžê9ùì$8(ÚÀ´©ÚUJ ¿Úر1Î|s=öõ@Zv ‡ËÁ7'¾Á…áЩz'­®Ço{ñyÍÏѤBÙ[œtçÎܺu ß~û-Ê•+'¶9 =À4@ºaÆ ú»ÛÞX?î ÐÙÑä+¬Ï¶ù!Å‚ˆüÝgç|ÿù+o¬$ƒ¬A)oS^îÈT·SurK:j äQǃßxsIIÅBøª Љ§º-~¨ í«µÇ‘ÁG0àÈyÕæôìtô=Ôׯ\ÓØ O DZQs¬í…öHÎ:xð ¶nÝŠíÛ·£wïÞ˜3gªV­*¶Y a ¢ãç£?±jàà=à8œÆ§fmÛ:ˆ`¡øBèþ>~llıÐ:üµáμÿøž ÂRuKáØˆc¨i_N¶N(a^Bç&V³­†ÆŽñÏ;RF ;7~a~*ugŒ‘¢j€Þ|€7t¡ êÂ4é]·7¶÷ÞŽ1(êÚ%e$Á}¿;¿ DMûš^ã—Û¿ ‡Ë‘›Th‚Ïk~.ˆ½ÆŒä !ÚÚÚbÞ¼yxúô)\\\гgOŒ;/^¼Û4†@0ç§h¤§S¦ÐsmÛã+ùÊû`ÈWxáB½Ú–/•*‘îð2²³Âß‚£…”ž@‡!\€YÝfÁ½¶;ê–­+ˆó#£WÝ^ÔØ7ÄW°{I‰¢j€NŸ¦‡¦›jäpèšÑMGcu÷ÕÔ\ô‡hôØ×1©1yœEHHOÀÎû;©9¦ý)’s€dXYY¡oß¾pwwÇþýûáë[<þÀ mY±Pþ}`fü¾9 &sx½€zô ú)!µÎðZ¤À¯ \C ZJ8è­ÿß:v¹\nG3dˆþâó½Û÷*ï— /à¾ßÉÉyž·õï­TK §2NÒˆ•†) ’t€Þ¾}‹éÓ§ÃÅÅ™™™ §§§Øf1‚õ+<ÏŸ«é’˜>øìÚ& DQw$ÚÌLÖ^ZHÍÒpè]ê;l¼³‘š›Úh*lÌô¾k[µ-Ê–Pˆ³c?ÆâN¤4TS”^`‰é‰¸òê 5§ïðŸ5=Ö`d“‘ÔÜÃè‡è{¸/2r2TŽÏÈÉPyßÍh;榒S³’s€¼½½ÑªU+”)SÁÁÁØ´iªW¯.¶Y aY}…gòd 3S1®ZXæ ,]Jwº}{Õþ[RÀ@ 7VP¿Â+–ªˆº)uõÖ ÌÔÄ_Ôù‚š+a°¢ô;r†ªËT¿\}¸”7Ò&ØÙg§Š°ýjøU =1TeWoߣ}TˆÌÖÊã[Œ×‹­Æˆä 7n &&ÞÞÞptt„‰‰‰Êƒa\0 Pá ›6lJ®ZD ʰ·Çw'è­ÉТ`®ôëõÍ **ïã…„ã€×¯é95Y`o“ßⷠߨ¹`Ø×ÃP¶€”y]Ru@EÑI)ü¥Œ¹©9Ž>ŠvNí¨ù“ÏNbÒÙIò±º¶[NDiËÒz±Ó‘œtáÂp—ïƒÁ(î¨6;¾üPç`ûvú /¯kÙˆ† Ш='Ö.PTQ”Ë([–Ôâ±ôÚR*Ç<ÆÛä·z·ÃHÍJ…ß ?jNìð—266ðꋆåé]Úm÷¶a‘?)îåâ‹àØ`ùs–f–ðlä!EAr£øÁ4@ÚÃovZ¢©3H? õëS¦H{¥Ó üŸ‡>ÔœWg/XšY"..ÙÙÙHcgm7'7jÎØw «:zéÙ çÖÙÎÍ+5×¥iEÆÞÚ~#üPͶ5¿üúrlº»I¥íÅÐÆCQ¹te}šht0ˆ!:L¤êš.\ÔxtZ5&¶n`n.í5–JAD  ¯«^òvP·l]¹ˆÕßß_o ü0ØÙ³z½¿¾)¬Hªá/>UJWÁÅQΆ.ìyÞ7ÞÜM`ÂRßus€¢Ã4@š£®ÙiƒÀ\ÏL`ïÑÝø‚e%½Æü   úê‹Ràÿy÷?9LÍ-í²f&f€AƒéU¨:@W^]‘·Y0F £JÏNÇÙPÚ1”Rø‹O½²õpnØ9”²,%ŸSnÝîµÝUÂe íaƒa@lÙ¢ÚìtëVÀbËzº¹¹4ÓÞÕQ¿>`«ÔÆ!%xúTÿv°´èÊ"*+§I…&¢W_®_®>jÙ×’Ó²Ópùåe-’½ü 2?ÈÇ•KW†«“«ˆL«Ê­pòë“°0µPû›œ8\¿Nü)íÐküïûÑ~W{¼J$a¤ûQ÷ÑÙ§s­å8:ÒU—srH6˜¾È#ì\è9Ü|{“zjy×åj/!–(>U¡µÑår¹8L‡~ )üÅgË—[0±ÅD±Í0*˜ÄÉëSD$$D}³Ó&õÒ¹¼L gOµ×rƒþ B§ÝõnañäÝtÜÝÉš]HÌ0˜4 ¯,¤¦{×í¶Uyu‹>!–¾¬ó%%–½yï?¾Å!ÑFð&€rÀËX•A÷šÝ…2MpLMLamn-¶Fs€¢Ã4@y£®Ùé’% ;=Ê»ÀÏ?óO—#Ô_{} ]÷tE\ZœÚçCâBÐqwGùÎP¾ˆYQtüéq<ˆVÔ0Iž»?€x €4cmY¹¥|œËåâ\è9Qlm4@'Ÿ¤Æ½êö‚¥™¥f1 æ1åàAà2¯¤Ë† @©”(`åJú‰)S€zõôgH¶‘û~w¤d¦PóuêPãW‰¯ÐqwG„Ä…äA íå:WÇOþ?Qs_7úŸUøL6iIqJ‡×¾dÈá/†0­4cÆ Ì˜1£Àã-ZkkkêQØnÃŒÂÁ4@ª$&³fÑsÀ€ À>( »¡\9Òð4t½ÆGþ=‚þ‡ûSý•à§N?áÙÔgþÙpj>"9wwÄ“wOò¾h³fd'KFd$yèžt17”jÿ üö=·hѧ$©3N©/P£FÀ„ ^SWk¼ææLôHµ„051ÅŽ>;0Ëu5·­÷6Lk=:?.-]÷tÅ­ˆ[êo F,=PÚ!Ë53Å-(„Û–f–ðê”ÿ ® %cƒiª:ñŒNgá/†:ŒÎ* W®\ÁW_}…É“'ã:/­8)) ‰‰‰ÔC6fc]ŽãâyÍNÑ Á§>§‡#10>Ù2ÀLQDHû^YˆïÏ|O=o‘iCal³±*Ç›ÀK]—â·'¤II豯®†_U½_ãÆô˜÷k_×÷úµÜ©Lak‚Ù'c:0±ÅDT³­&Üýu8î\±35þó]Ylûô1~óañad"8°²ð ì“â8-- ‰‰‰xýú5’““ÁqtóUc¥Ø;@={öÄ¢E‹0pà@ØÛÛã‹/¾ÀñãÇåÏ_¸p>>>ÔC6.úXYŸ"{ÄïƒûT„ȇ4;ÍN~øÔÑ}úÀ‡W¿&¯ëËÖ¸0öqà0íü4xßð*ž+a^cLÆP AÕ¿ªû*,L|:ÿCæ|yàKÌ[;>þÍzD(dý•Â_>Âl÷³üÇ :. ÏãzqqqØÅ«Ê­ï÷OäõHX™YÉÇ/ü_Pâs±ßßEïܹ“Ò©;ž =º×ìŽ2Ve$a¿TÇ·nÝ‚f̘#GŽ G¹(¨1Ã)žžžœ§§§ÖçÍŸ?ŸëÛ·/ÇqçååÅyyyéØ2Ÿ­[·Šm‚$ˆŒä¸2e8ŽlGǨQŸž\²„~ÂÒ’ãBB4¾va×8;7›yj$‡Å eV–á®…_ÓêZÿ üŸÊu,—Yr§ƒO+ÊÍå8{{úµ>|X(Û5æ×_©ûíh®°ï‡¿~Ðø2ÇŽãbcc4T3zîëI­ñº[ëÄ6IgDFFrüñG¾Ç4úµõúwÞß©'댃¨¨(®B… b›¡Šý ˜š²eÑ'LDà7;up֬ɄâWCœ6 ¨C§›çGaÖ8#'ƒŽÂÞG{©ùr6åpeÔt¬ÞQ«ëÍm7›¾ØDìËÌÉÄ £ƒpäß#dÂÄhÝš>QhOýꓞÜÖÊß»}¯æõˆ­’aÌU¡ Ò…Ä…P™†f&fè[¿¯>Lc F÷Mÿáć‡#99ÉÉÉGjjªüùæÍ›cµÒ—ÉÔ©SqéÒ%„‡‡ãÌ™3Ø´i† &†éŒbLžÍN˘7øøQñDùòD- ©Y©èu°—J+*¥«àú˜ëhQ©E¡®;µõTlﳦ&ŠžìÜl =1>}È„¾ "ò O}dg·› ‡ÂÞ[øBè¯o 9#9£ ¾ø¹“s'ÍûÑ1ŠFç`ôèÑxùò%^¾|‰Ñ£GãŽÒh­ZµP®\9ùØÖÖsçÎ…««+–-[†õë×cà@–1 OŠ{ ¼šŽòå€NÉÆ²e€­­V÷ÐfÓñùÞÏU: ×´¯‰ßÞ@ƒr ´º7Ÿ±ÍÆboÿ½TSÇ\.ßþñ-¶mÕ&Ï ·#»\3ÛÎÔê2b×’Qî\Ê»ÈÇY¹Yð{á'¢Eº£ :@¬ø!CŒÎrwwÇÕ«W©G×®ŠÎÍÇŽÃØ±cåcoooäÀaòÙÉøÕ„×õÙ3 %B‘ûò5~eüØþG”²,¥ÕuÄ®¤Œ±†Áò«ô:é5‚þS¼wL`‚þ úëË4†btÃð(Πàÿ£ç<=‰ŸƒTw?Ö¯§ÒÞ5E“5~“ôvwÀ£˜GÔ|«Ê­pmÌ5ªÈž.Ø` N 9¥ÒàqÊŸ_E)ô”›KŠ# AJ L©Á鿀Y¥*˜Üj²Ö—’ŠPu€Î‡ž§j7*ùi€ø»?®N®:Ï2Œ æ1"2y2‘¡;9‹ƒh~æÑ)âèßèÒE;BâBÐ~W{•~];ãò¨Ë‚é(<êxàÌ7gTªôž/Ë sûï1]W)ÜXÐi¡ÁwÝnçÔöÖöòñûïq7ò®ˆ  1´…9@ Ñ)® <›–Q@+÷Á²´ü”V8ò[ãG1Ðawªw@œ“óÃΣ´eéBßWº×ìŽ Ã/P÷¹S…w@Ðñ³ôšÆ8Ú`ló±y?RÑ$û‰ßûÊÂ`yi€¢?DãæÛ›ÔÜ€ôeÃ@aCtŠ£()IµÙi¯^d“oÞk×ÒOΘÔªUèû嵯7ßÞDgŸÎx—úŽšÒhˆÚð”Pt¨Ö—F^’ïZÜ©Ê;@(86¯\¥æ*6v¥tIÚ % `œ: ¼4@§‚OQ!¾æ•šÃÙÎY–1 æ1D§8j€æÏ§›ÚØ›6}üð–¦x²BÒµ¨[ã¿^þ…ûz 1.?¡Åp ÐŽ@ai]¥5®Œº‚r6åð°"a®ôdt4r^¿ÊóÜÂð“ÿO¨ž@ëbê¶èYèëII Ê™vb!"9"Ÿ3¤O^ þbæ1zæúõ|š‡ÓO._”)£SNŸB¯ƒ½š•JÍÏm7¿÷úªÓ£OšVlŠk£¯¡¬m%<¬H?·aƒjúÂò0ú!Ž?=Ž´ï“ºÉr“%àêäJÍÃ.Ÿ¸´8\ ¿JͱðC˜Äâ¤úøøöÛÿ³wÞáQm¾7ÙTÒ©!„Þ{G¥‹HP”ªE^Ô`ED|”(觯J€ "¨HD té„ JiÞÏ÷Ç!›œ-©»Ùìfîëʵ;sfΙœlv;ó›ç)œìZ¶„wßEÞî>eвCûör‡rRø¯9³†‘¿Ž$+7KÑfN¿9üïñÿiw­pZÖlɾ—öq±Ò{$=°õÃÈÌÍ4гäÌÜ3 -D9Peò壽 ¶-|›™Fbôy€6_ÙLN^Á}oY³%Ík4¯è¡ ,!€f§*y€>ø@óG¥’gƒìì€Õ«á„V œo¾#¤fɿNjŽ-b¦ äJÉU¨Xôä"f®>xé€ey$ºv•oÚClÂÂÙ>äžh¤ Xx5á*½VõâZâ5í3ðËù_8wï\ÁyT6ôÌ­«ld¥ °Î¨ÐÚË_Ï6*­]„˜kõ:$çö*ÌäÉЧÏÃÂÊ•pú´²¶½Ï?2ŸC>VÔUϭήvÑ´zÓr»Âñð€¦…Æ,I8ž:Ëæç73´ÙPEÓÈ‘ôZÕ‹Ëq—õ9y9Ì ™¥¨Óf .·µ¼CVê.ÇJ\ IDAT]#ôÑÛG‰K‹3ÓhÊN¾(+7KGĉå/AiH`v¬ÑTìÒWRÌT.É0j<úh¹®ûýÉï™ú·2ɘ‡£.–NkŒ£G±·µg㨌n5ZqèNòz÷æìݳšºàÐ` ¦áÔ6j‚zÏ‚ÈHåy­ÔP×µ.½;jÊyR…ÿeÆ•|ÐîˆÝ<È,¸×5kò¨oùþwU!€fÇ=@}aae• V¬(díùì3e20GGø_ù¢0¯;·ŽÉÛ&+ê\ì]Ø>v;s¦Ï1ÐËèÞ]Y~˜Um£fÝðu¼ØN¹dx/õ}W÷åøãdæfòé¾OÇÿÓá?4Jw‚ÌB¥kÖ|¸.Yv*«(kØŸïÒ^þz¦ù3мgAIH 02ÿþ«»«=0úöEx8u*|ýµ²Á»ï‚Ÿ_™¯¹éò&&lš Èˆí¨vdËó[è^¯{=-=3@ùتlYõÌ*;+EtBzÖ `òÖÉÜLº©©wT;òQï B+±ªûòÑöí¼¶Óh¹Õ*’\)—?¯ü©¨Ë_‚² ÀìX“(#^zI¹ôåïÿprgË9ñ×üù[Š‚ºuåed経Þ8Z‘ÉÎÆŽßGÿ®Ù1eÑ÷¸m[pr*(ÇÆ*Œ K–ðN÷wÝ’2“XºJQ7¹ód|\}L"€*³ ‹OjW«­)?È|ÀÁ¨ƒEô¨|dee±íÌ6…ÉÃу~ ú™qTKE Ù±&ÐGÁ•+e• Ö~y—‡ÃÓOÃÍ›º¾ü²ÌË/¢ðìúg‰MmU¶ü<âgžlü¤¦Î¢ï±Z ;*ë Íå3ï‰y|ø˜áøI.ö.|ðØC¡iT™=@ ÅÁM+ê,m,..Že,SÔ=Õô)ìlìÌ4"%#ÀìX‹èȘ7¯ lC??ò-¼Ü~ÿ]·Cƒ°};ŒW¦ë¿sœ€uŠX(*T¬ºR'"®Åßã"–Á óY¿Ï˜ÓO¿ßiJ÷)Ôt~HñÆ åÁrÆ‚ÊïhjÙ> ïºÞ„º‡*êÄò— ¬$í¥¯vœá”CF|SÞñUµ¦O‡óçaР2]ïܽsz‚~ð]åÍíUJ(€f<6ƒ¯*=VžŽžLë9­ ¢ z€6ˆ½­½¦¦Ø!WÙ9rëw’ïhÊÕìªéÄ„JŠ@³cÑþ”‡Ìš—/ƒ3i|Å{œ 3í2é6ìÖ Nž”—½œËt­°ø0_ó8 é Šú¯ÿŠÉ'ëícñ÷X[:Ù† ¼S{LeqÀbM`¼éLÇÝÁ½ Aô¸Ú»Ò˯—¢Î’f~=û+¤”7Œ£ÚÑ|X4B ÌŽEûS'#¾þóhÅ4æ¢FëƒÐÍ ¾û–M½e$òA$Ö ànê]Eýǽ?VÎphaé÷??¨]`à%3ÎÏjrçɬºWÞêöVÁœ¸U(«ªM¹vàåSÙ=@ùXrrÔßOü…2žˆå/AyH`v,ÙŸ’™ ïá§ÜÑl#nè61.]’3 –#ÊstJ4ýW÷Wlëy¶ã“>ŸÙגﱆR,ƒåóRû—8ùêIœí ͶEEéî³·×í\J,Áºñ€öGî')3É@ëÊéèSDIQÐL.;Ø:蘺‚Ò @PV$‰¿ž^Ê–ðæŒâWÝã¾¾òÖ÷ äÙr—Ç€5tr]½ÚéU¿‹Õ¢ñÈ‘u+¼õ¨²þŸ|{5¦Yõfšrv^6;¯í4ãˆJÆï—” 6ˆ«½«™F#°„˜‹ô§œ?OJ»GxvçdÜÑZö°µ•ƒ^¼C†èï_ d>à‰µOp1ö¢¢~\Ûq,X\¢sXä=Ö¦ 3@zÑÞf$d  |,-9ª„Äú ë!H, Ê‹@³cQþ”ôt9haÇŽ¸œûWçpn‡Îpü¸l *gj€ÔìT¯Ì©èSŠúa-†üL06ª’ý [Ô=6DçÎÊ%ÄðpHH0ÜÞÚ3@FØ–ãÝíð…ÿ¥ˆ"^ÙØ¶•« W! ˆ’—¿žjú”¹‡%°p„˜‹ñ§ìÜ ­[Ã_èì@JÆ•+“¿ÁöÄQèÐÁ(—ËÌÍdèÏC9|ó°¢~PãAü<üçRå>²˜{\nnТ…²î˜žvÅa¢%0Kñ<æû˜bW\lZ,Çï7㈊fîá¹òW ™<ûéåäeÖ1 ,!€‚â¸wÆŒ'ž€ë×uÿÉP挹H³Åo—Ëä\˜ì¼lFþ:’Ý»õ½ýzóûèß±\ªÆX«â É>ÑX?§².ƒ¿sœý‘û5e*¦ö˜jÆ ¬!€f§ÒúS$ –/‡æÍáçŸuߢÏòoÖßÄŒ%õŒvÙ<)ñ¿gKØE}WŸ®l³'µ“ž†©´÷¸´TbdI °fö ÷~œ–5[šo@«A Ù©”þ”‹¡W/xåHLTÊÆ…¼EK.²‰gøáyuÆHHLÚ¾¾ò®=#`I €Î5èæ£”ÛÂ+WPÄùGæ“+ÄlêÔ´3&Í0ãˆÖ„Õ  uëÖñí·ßÛ.;;›©S§ÒªU+}ôQ~ýUO<AÕ`÷nhÓæÌ¬,å±jÕ˜[g.¥ã§ õëË›½ŒÅ‡»?dѱEŠ:v½°‹ZÕjïB–Œ­-t꤬+Í2˜‰¶À[*•y,1#‘•§W*êÞíù®™F#°F¬N8q‚ñãÇóÊ+¯píÚµbÛ¿ÿþûœ?žM›6Ä«¯¾ÊѲÆ” ³ûSbcá…`Àý³ |=ñïżK.³Ë—oéëóŸóÿOQW×µ.»_ØM=·òû‹Ì~‰v@ÄÒü¿šh ÝÊ—(sEýç?ò¦0c°ðèBfìQNë×t®É®vÑг¡Q®a5 (ŸÚ„;À,Ͳ·¬¾[}M9-;={Ì8"™¬Ü,]¨¨›Ò} ÷îsPÏÒ´@P¬NÕ©S‡>}úP¯^ñßšãããIHH U«Všº6mÚpåÊSQ …Yü)W®@Ÿ>0q¢ì#)Œ Lž —.‘õô&LógæS¯Ì›gœa¬<½’);¦(ê<=Ø9~'-j´0ЫôXtPh¨œ”­$˜PYš(í ˆ•aì§s?­)»;¸3©ã$êÖ­ËÓO?mÆ‘ ¬ «@¥!66gç‚D‰®®®šz€àà`‚‚‚?…e +ôA»v°oÊ£T«:‹ƒ»;?ĹsŠŠ¥¯òŒgý…õLš: ‰‚™Jûöl»öuÚ—ûüV[öñÑìÜ Ù¯Z²þ‡•A%ƒv+ã,UŠß¯‚ËŠe°½J#´¹Æ÷õ¿_kÆðjçWqµw­÷ËË[·n%((ˆ>}ú0wî\²µ½Z+*©¸u" eÊù[õ7ß|c°Mdd$þþþ¤§§ãèèÀòåËY³f м8´_4ãC:uL¡½{!0PžýÑÆÉ fÍ‚wßµ€Ó§¡kWåìÏĉ°bEù†q5á*[¶0ýŸéääœÜQíÈ_cÿ¢¯ßò]@v+ŠáÃá÷BÉ1,€·Þ*¾Ÿ——2¬AL Ô®m¸})ˆÇÝÝõÃ×¥ž“Nõ/«“ž“®©;x†¶µÛše<;®îàÉuOjÊv6vDL‰ÀÇÕ‡¬¬,RRRðòQ MELL íÛ··.ß ªô P­Zµ°µµåæÍ›šº¨¨(|ŒDP2LêO¹–-ƒ= o_ýâç‰'àÂxÿ}øÉÎÆhK_Q¢ æÅM/â;ß—&‹š0õï© ñcgcÇo£~3‰ø+óAÙ|@(ų³ÑÄX¦ÀIíD¿ýuæÜ¯|<×ú9|\å÷丸8á*'€^ýuÍVw'''Xñð+}ZZ7ndĈæb•Ãèþ”Ü\ؾF–Ìpäˆn»Úµá§Ÿ`Ç/ÈgŸÁÙ³ÊæßîîKtJ4?û‰I›'Ñha#ü¾ñã¥?_bÍ™5ÜLº©ÓÞVeËOÃbp“Á¥ù-K…Uy€ lH{ ¼w€åz€ òl‡ ÕIÿ2­ç4ÍsáËš«-7ndÚ´iš­’›6mbÑ¢E<õ”œ9øÏ?ÿÄÓÓ“Q£FòY@@ëׯ'99™áÇóì³Ïšmü‚rpá¬^ k×Bt´áv*Lš_~ žºÁCCáóÏ•u/½O>©ÓIî½±—ˆöDìáJ|ÉMô*T¬º’-…è.;Ë1rÉ»v ââ F Ã}L¸ÞÒ h…&}ŽÜ:B\Z5œ‹¸Ÿ&@ãýyÈã 7ÛRœÀú±:4bĈ"gpnÝR†zoР/^äæÍ›xxxàêZ¶T‚²S.J|¼œ§+8Nž,º­ <þ¸éù‘Gô6ÉΖÅNa ÌŸ_PNÌHdä~öDì!$"„ó÷Î+ŒÌÅá¨v¤G½ôkÐ н^÷â;•«óU«­Z)§éŽ…€Ã}LœÕR=@õÝêÓ®v;ÎÜ=Èùè¶_ÝÎø¶ã+l ·’n±þ¼2LáÙ@x€FÅòþSMDýúõ‹o$0 ›6m*ÝMv¶¼Ä Û¶éFmÖ¦ysxñE?¾Ø¼OŸ®ÙP¤aÁÒdÝ;@È1y†'4&”<)¯Äõ·µ§«OWú5èG_ÿ¾ô¨ß[‡÷7¥¾Ç–@·n•J…„„зo_‹] h @ /ƒU¤ZptÙyß<ÚÖnËÀFÊ<3qqqœ8qB,ƒ Œ‚@³SâæÐPy‰kÝ:9zsQxxÀsÏÉNfm¿ˆΜ‘½?Ø¥AýÃÐ`5»†ðÜéäœ,y„_µšNÞdÁÓ /Ôg;çâ;š«? G„^¾¼ \œÈÄÈÒ½ƒCšáók¿_ý›œ¼Ô6¦ÿ˜HÊLâû“ß+êÞí¡›öBx€ÆD AåæÞ=Yð뺒µ±µ•3“N˜C‡‚CÉfY2s39y„±_„=nÔ; ¶ò¬R,@1“=6*Ú×i¯™áyÌï±2gm”}™á%IöxéÃÄÈÒéæÓÎ5ˆK‹àAæF¤“_{ù©å$e&iÊu]ëò|›çM~]AÕF ÙÑñ§deÁ–-òlÏöíʽèúhÙR=ãÆÉ»¾Š!'/‡ãwŽk<<‡o–c 4/ÙxU¨h]«µf†§·_o<=JÖÙLXä&®®œ,—ï߇°0hÖL{'BµdÈB~p“Á¬9S hkØV“  œ¼Y ¨{»ÛÛØÙØé´ 1±ÌÿTU¡ñ§?.‹žŸ†âzy,quéRì5¢S¢ùåü/ì¼¶“ƒQIKBóÍ53<}üûTøî˜òb• ùo¿§PGõ  ØXHM-({xÈ?FÄÒ=@MtÐÜs‹èQ~~½ð«"<„‹½ ¯tzEo[á!€æåΓ’ä=/ÝV­–ƒN˜O? ööE6ÏÌÍdó•͇ó÷Õ¿É•rK<¬îÐHžáéëß—:.–={buâ'ŸnÝtÐ /è¶«€å/K÷<Ñø Ô6jMÎ+ñW¸šp•Æ^MvMíÀ‡“:N28£*<@c"À<:ß|üQËÅ­[,q• rïñ;Ç  æçs?“˜‘Xl{îûÁ¾Ñ—U³ú2áY±+Ð"(i@D¨D¸;¸ó˜ïc„ÜÑÔm ÛÊ”îSŠèUvöDìátÌiMYm£6Ùµm„TÙÙð믲ð9qBSè̯T¯cÆÈ§cÇbO“ÃÚ³k  æBì…bÛ×u­K¾ü½¬/9á}!±! OL°Â8˜Vé]tö,ddÀÃÜ~*`ÈÒ=@ù4 P máÛL&J´gF´Ÿ»ŸÁöÂ$0&U.†À ÄÅÉûËýüäYœBâ@“¥J­–—¶~û îÜ… ‹?Y¹Yl¼¸‘!? ¡Þ¼z¼÷Ï{EŠŸöuÚ3ÿ‰ù\~ã2‘oÝ&fñZrŽýG#~¼½emfX].°|êÔ_ß‚rv6œ:¥Û®¥æÓF;-ƾûHÎJ6úu.Ä^`ÇÕŠ:íÀ‡Úˆ\`cbÙ_U•›óçeE±nü­ÜÍšÁ«¯ÂرP«V±§=}’U§WñóùŸIH/Ú,]Ó¹&cÛŽeBû ´«ÝNSÿù纣—-Ó›Ã*°Zȳ@QQå£G¡gOeá*1ͪ7£±Wc®&\ ;/›×v2¼Åp£^gÞ¿óÔûø÷¡“w§"ûÀ˜$0.’$Ggþæؽ»è¶”)0háØ-¹›zW³ÄuþÞù"ÛªmÔ4 `Bû 4 ÐÙN{á|ò‰²Ïøñð0]œÀÒèÞ6l((ëó™x ¼µ1¤é¾9R0º5l«QPþ’uaŠ›ýŒ@ã’"+\¸Âà ·sv–ÕÆÛoËq\0ìOÉÊÍbKØ‚CƒÙqu‡fgŠ!ÚÖnË„öÛf,µªéŸIÊÍ•mE…³gx{Âz›[ Vë‚âÐyy©¬3 ÚZ<@ o‡/,€þ ÿ EQ))‹Ž-"+·àŸ°E n2¸Ø~Â$0&Â$(7nÀ´iP¯¼ù¦añS¿>|ñܼ K—jÄèúSNÇœæíoã3χ¿Ž`kØVƒâÇÓѓ׺¼Æ±—q&ð ïtÇ øøßÿt,H,]j½K_ùX­dŸXaÑqãܽ[P¾sG©xk×–…¸‘±@oÿÞ¸9¸iÊ÷Rïqìö1£œ;5;•%Ç—(ê¦ö˜Z"q%<@cbù_UæáàAy™kÓ¦¢·±÷è!/s ¦ü*D`` ÷Rï±öìZVŸYÍÙ»E§¼°UÙ2°Ñ@&´ŸÀÐæCKœXôâEÝ¥¯qãdßµµcÕ ''hÛVi~>v¬`M³‚¶À[‹ÀÎÆŽ²ñâFMݶ°mtó)Y^½¢Xyz¥"Cšáèí‚Ù´­a[ &*-ŽQFj• S{L-Õ9„H`LÄ  €ØX9Ãú A†Å«+¼õ„…ÁæÍ%?«BWÑjq+]ñt©Û…CqéõK¼ÿÈûå? òìÎ[o銟V­`×®ª³ôUeP©týfùË`ÂTfš(Ê»#v“‘c8¨iQhÏþ<Óüy6*óØ‚ò"@æçŸåoËë×ë?Þ°!ÌŸ·nÉÎáF%{ãŠzÅ µƒ˜øçDîgÜWsT;òå€/ùcÈô¬ßÓÀJÇþýЮ¬Í´yåye¤U+£\Ê¢ˆ‰‰1÷LO÷îÊòÑ£rjŒ[· êll”©3ŒH||<99Eù-u:(Œ-W”_~Êø0++‹„„¢£¿ %E ªÎ;òtɘ1rÎ.múô‘·º‡‡ËË]nnºmô !±ôÄRZ/nÍß×þÖ9Þ³~OBC™þÈt¶ü©;+TZrså-îýú)?ïÜÝåÍkË–É;¦«"Vïý£¢ä@ˆùøøÈ»M€µy€òÑžÚ¶µÔçÐN{ѳ~OzÔëQêóÀ˜T•Y±BžõÑ7]Òµ«¼Õ=$DH6%©\O¼NÿÕý™¼m²NEg;g¾ô ^:@³êÍ€òûSnÝ’…OPnH¢îÝ!4TÞ•_•±zȯÙœ?/ç=)Œ —¿FŒAõê%7ôZ MÊ'€bÓbY}fµ¢®¬i/„H`L„ªŠÜ¸?“&ö7V''øê+8|¸ÈLìúÈ“òXptm–´Ñ;MÞÇ¿ç&Ÿãínoc£2ÎKoófyÉkÿ~e½Jÿý/8`²¸w‚ÊFÍšòRm>99°q£²ðÿ”š à¨vÔ”£DqîÞ¹÷ÿîØw ßPc¯Æ mV…¶` *-BU%$IÞÕºµìÖ¦W/yßø´i`k[ªS‡Å‡ÑkU/¦ì˜BZvšâ˜«½+K–°çÅ=4ôl¨Ó·,þ”ÌLÙäJl, ,oHÓÖqmÚȳB“&•érU’*ãÃ> /Y³ Gýx9也q_ïÆ€¯­(i:„æ5š—{ Â$0&BYùùæÎÕèã[¶ÀºuPŠ7çÔTÈÈÉàý]ïÓ㇜¿§+ªÆ´ÃÅ×/2¢¥q“@îÞ mÛÊö$m&O†cÇ E £^R`M˜IY;¶*[žlü¤¢Nß2XÄý~¿ô»¢®¬S"%“’¯¿.§«×=>i’ìõ2D÷ØCòòämå?ÿ,›ˆ u’K‹Ã¸LkÏÿý\I)ªj:Öeý3›Y7lÕÊÿ7ߟ’“~‚¶eÅÓ~û /GG='I•ñ4n¬_ì›4¹Ü IDATXY»Jæšÿï|Å{F—º]èå×Ë(× 1aâ,•;åìž‘‘ºÇ4 2ýû+ª³³áâE8u NŸ–Ïœ‘u”†—¡çèô=¹ªL*oaׯÆÞx>ÿœt›jœ9R tNŸ–­A™™zÎi› Íÿ€.KÀ¯þë>ð…-ßÃÕ'4U‰‰òÏéÓú»xz‚ŸŸaäî.·«Q#öíå_Mû×ùà9Ç—ˆè\>ª”øy¬‚ЈÆ] ®Œx:zÒ³~OöGäžÙ¶•·»½ À’ãKHÍ.ˆSáïáÏð–Ãv}á«úXùþûïùâ‹/øàƒˆŒŒ¤gÏžœ8qÉ ¶oßÎ_ýE¿~ý4užžž4Ú2°i“°0:ZçPZýfl~f%Ûzrª«¼¬¥mÒÁ#:}V€Ë]ým$œ|þùdº–j¸ùI;h¡æòòf´K—ty{ÃÚµr’S ÔèóY(.;›ù·n±þÞ=r$9¿º”ÿ˜ÿ#ä]×®ÓW¦m4í$‰\ÿ ð—àad÷)é*¦ìÝ‹JE^^7èõÜCÊ㎭c£RašGU¡çšº¶Q•á÷,k`«R¡V©¶ [—ÿ¼}tŽkõ)\_¸®0Ráç’dø˜çÚýòŸ%''“SE(Z•š;w. .dÈCÏKxx8«V­â“O>1اgÏž|óÍ75IJ o¾)oa×"W¥æ+iŸÜœEÆ¢˜cTyÐä/輚l—ËðskÈ{MWàÕ¬7zÊ9Tó¢¢tc+––û÷óg}b€:šú'Ÿ„Õ«å]ûãC:uŠoh-tí ]ºÈñœåõX¿ âããqwwGm‚éʘ¬,¾¾y“%wîZì7S£"_ü&O’@e«h–%A–1Ç›“#ïxu-Ý—1Aép+eJ$KÅjPRRáááôìÙSS×£GvîÜYl¿‹/R£F jU² ‘‘»à'Z.{ç´8ãghÇDi%§(AÖv—è¸Û.ËÉuÕã*D¿ý˜Üy2C›ÅÎÆNoI‚»w•¢¨ðOddiÒ& ;;9éÔ©"¢³±±TP®$‘ž—GF^^Ácn®¦\Ô±ŒõëIÏË£†ãkׯÇÄc5…èVf&ÿ‹Šbyt4y†¿¬T<§· ½Ï eÅjvåïrq+ä¼õòò*r÷‹Z­æèÑ£ > @bb¢¢Mpp0AAAŠŸÂ«œ¿ë‘G‚0Z{Þæ¬ÿÓl?V!~‚€,ìù˜OéÂqN±Y뷒ϧRÉ›aüÛO Í§£P¿ç ýf’{RKüì•<=y§û;¼‘ö»_ØÍˆ–#°³±38^• êÔ;‚xî99ÛÆÒ¥Ð½{—/CZš¼R7iR¿ü_|иqÍ›ƒ“&PH£Fpø0$')Ä©îwU+ç‹cŸÿÃY³ˆÊÈàLJ {ïßç¹éÓ Ž‰aÉ;Ì»y“þï¼ÃG¼wío„‡ÓñÍ7{éÃΟgðÙ³4 ¤Ç©S´?q‚æÇŽá1iµÆíÀìöíCýÒK¸8@ÍC‡ðý÷_šNžL»'èvê}BCyrêTž=ž1/2ñòe^Ÿ1ƒw¯]cfDs"#ùzöl>¸~¿#GzþÝìÈè֣˕¤°,žAÓ{¼ƒ“ Ƙ••EJJ ^^^Å7”‰˜˜6è‹+g…Xrww§sçÎlÛ¶Is$ìܹ“¡C‡jÚ¬]»–f͚ѥK"""hðpgˆ$IìÝ»—V­ZéžÜˆäåÉ;Ø?üîÝ“ëlÈã ¾ås>¤©:}®úôæì[?ðâ“ù¢…rKxJV kÎýÄÒK9c`?úCZÕlÅä.“ßv»ïßgwb"R¾ž6o Ü>ìhcƒ£ Nmlp²µUÔé<:ž+IlˆåŒ"Ø•L®$±%>ž-ññÔ+4+T¿Œ³BeñNIaNd$ÄÆêìÐɧ¡“øúòb:ØUsœƒ­7zœM—usÌ jûLN. Ð#|Æ ú±G'OO˜?^|QSu5á*ËN.#84˜8=Ûáó±UÙÐ4€É'óDã'Píû³i©r1jJÉùÔTy†'1‘}TŠ|Sv*ÝÜÜèš“C{__!ã©Vcof?‰¹8š”Ä÷ÑÑürïiEÌ ÙäÏ y{P½ºÁÙ˜¢<@ÿ$&2'2’ýÚáÎ ÑÉÕ•™~~ ­QÃBþs¡ó÷9]0¥ýã³?2®í8“]Ox€L˜”›?ÿ”ãÙ\¿.—Ûq†9ÌdºÉ9ïøqò’W:ääå°%l KŽ/a×õ]ŠoYÚx»x3©ã$^îô2õÝê›à·1-¤$"#ƒÝ‰‰ìILdÏýûÜÍÊ*q_ÐÎÅ…þžžô÷ôä1ww\lmYºt)ãzß2ÝÜÜèææÆ¼FXw÷.Ë¢£9«gV(O’Ø϶øx|˜X§“¼½ñÕÊʫϴ5>ž9‘‘M2¼LÝÃÍüýyÒ?Ô‡4¢@õÝê3ºõh“^Ox€ÆDÌAYf€.]‚·ß†þ‘Ë͸Â'Ìb¿¢Ò'bT*6 fφ-¸|›å'—óé¸|ÛàuT¨è× y¦ù3¨m„–µTîee±ç¡iywb"¥ ±ÝÄɉþžžôóô¤Ÿ‡Õíô¯ÏѤ$–ݹÃúØØbg…=œ¢5+$ÄÆ2'2’ÓzU>}<<˜éçGÿÊœ~§NÜ9A—å²°ž;p.ïöx×Ì#”1$(5÷ïËI;¿ûNŽÖîG$³ø„Xƒ-ÞH ‚Ï>CêØ×v²tý‡l¹²…\É𯗓/¶{‘ÀÎ4­n8Ñ« r‘˜“ÃÌLîdeiofdpðÁΧ¦1¿§K]úyxhfyŒåOÌ ÍoÜXã24+ôW|<ÅÇS×ÁÿԩÄ:u8šœÌg‘‘»ïô0ÐË‹üüx4?°Ó©n'ê¸Ô!=;—;¾lîá¥B r’—+VÀŒrÊ®:Ä0ƒÏx…ï±ÇÀÒE¯^ðÙgD·oÄÊÓ+Y±`÷#мN7ŸnLî2™Ñ­Fã¨.AÎ/ !.;›ó7oÒ¬^=jØÙUšm¾%åANw²²ˆÖ7…£³²ÊeTöT«éSHð4/dì/)ÂgU:ÜÕj^÷ñáuŽ$%ñ}³Bw23™Éìsç Z5°µÕsFxªzufúùÑÕ­ò„ (/*T4 ÀËÉ«BBkÀ˜T”·µŸ> ^$ð%_òßâLšþ;#ÍžÍöFy|r.ÛB¶‘“gØÐZÍ®cÛŽerçÉ´¯ÓÞD¿…éÉ“$"33¹”šÊå´4.¥¥iã³³1j<ÔjjÚÙQÓÞ^~Ìÿ1P6U@¸”Ü\£OàµLRVœllxÔÝ]#x:º¸”{7ðY•înnt8+ôãÝ»|ççôÍðœ>-‡c/$pTÀðš5™áçG{—Št2¬Å0ÚÕiW!× 1 "0äºuKÞÖþóÏàJ2ï0Ÿwù7 [µ"ù®ÞVœZÁͤ›E^·u­ÖLî<™qmÇUª€…Å‘‘—G˜–À¹œ–Æ•´4“%rt±µU¢ň&[Ð+f¢µÊɘq[­RÑÕÍþgyz¸¹UÙY–¿ù³B÷î‘®çµm«R1ºV-føúÒ²Z53ŒP (Â$ÐKFÌ+g,—ÒÒ™Æw¼Ï—ÔÀÀÖôF8õêÓ̪{…í×&“{Õð‡ª£Ú‘á-†Ø9G}5Ño`²³"'ÿùŒ ƒaüMEJn.)¹¹¥6W466xÛÛS×ÁA~´·ÇÛÁvÕªÑËÃË&‚ÊI77zö =œR«TŒ¯]›üühâT±¹õA騄üñ¼û.ÜŠÈf?0“9ÔåŽÞ¶¹ÞuØö\GÞô>MTÚ|¸jø¼­j¶âåN/3¾íx¼œ*Ϻ¶DedèÌæ\JM%6?y™pW«©•œL’»;ñÙÙ—\ÓÉÆo-QS×Þo­ç^fÞ™%<@¦ÁC­æ Þðñaç4®Qƒ†VºÔU 1¨bck1`ìÝË8Ö2‹Oh€~Ãr¦—«yóN£pÒlÿÂÈIíĨV£x¥Ó+ô¬ßÓ„£/žè¬,ÂÓÒOO/øIKãZF†Qý-Þöö´¨VÎδpv¦ùÃǺ,]º”ÀÀ@$à~N±YYÄfg—MlþOV–¢œÿÜg[[½¢&'ÿ¹G_šá2=I'NàÞ·/d2„H`L„¨‚‚‚˜3û#žÍûƒOù˜\ÒÛ.£š µcN§RŠÈ¤Ñ®v;^îô2ãÚŽÃÝ¡â¶ÀÞËÊÒ8áéé\MO'ňÂF¥¢££Fä<î& i¹¹z…‘!Á”•—§3;£Y–*$nL5^@ ¨Ì@Ãñ¼.t@–õL{[æwÍåd’蔩·‹½ ϵ~Ž—;¾LWŸ®&g|v¶ŽÀÉÿ)Mê„’àhcCÓ|‘ShF§™³3lÞu¶µÅ×ÖV'*¯@ E!P1è?Y¶°¬3|öX.w ÌvwòîÄ+^áù6Ïãjïj”±$æä(ÄÍÕB‚'ÑÈ"dCs=³9 š¤QøSL¸Ç¦§¨\`ã <@c"þSKA® ·‡O{C”ž,77Æ´Ã+^¡CeºFRNŽÞåªðôt9fŽ‘q±µ¥±“Mœœhâì,?:9ÑØÉ‰ÚöE¬çáO1=â›}¹ÀÆEx€ÆDx€Š ((ˆ O>ARÁúV0«/„éyoë^¯;/w|™Ñ­GSÍ®d1?ò$‰°ôtN&'s29™S))\JKã^)_–”jEŽFè;u*Hä ò#<@ [šÁÌ~p¶¶²ÞÃуñmÇór§—iS«M‘çÐ;'SR8œlÔ`{N66²ÀqvV ''êŠ\Q@ (¨ž~^Y~Ô÷Q^éô #ZŽÀI­èÌ”bÇÑÆ†FZ³8ùbÇÇÁËÊ¢U€ð§˜qMð™áñŸZª;Uç…v/ðr§—iQ£…¦ÞbÇÁƆ†ŽŽ ?N¾'§¾££ÅŠœ¢þÓ#î±é Ó#<@c"<@EÄyÕyÖÍ\‡½ÑÅŽ‡ZMGWW:¹¸ÐÉÕ•.®®øy‡•@ %Ex€n7ÊÀs—Ê-v<óÅN!ÁÓHä À,T Gêׇû÷KÕÇËÎN#ròˆ@}þÓ#î±é Ó#<@cR±a{­vv ôòâ__6¶jED÷îÄ?ò;Ûµãÿ6dDÍšBüæM›Ì=«GÜcÓƒÌ= «&..Žƒš{+Ax€Š ((ˆOúôÑ”kÚÙ)fu:¹¸ˆ @ °„H ¡Wd$ïÐÉÕ•ú"žŽ@ VX+†¾ÕªU+ÑØËCyîqiû›óýBûo”””DLL M›6-ÑØËƒ¾×Ai(Í=.É{‹©î±v]ff&§OŸ®³BVãÊÈÈÀÖÖVSgkkkpZ±$íŸ{î¹ ûР2PÖ/â–†Õ êÕ«òTu¾bOLL¤V­Zenß¼yó ™ý@P±XªQ£¾¾¾9r„AƒpòäI:vì¨isíÚ5ÜÝÝ©Q£F‰ÚÅÝ»wùä“O¸sç-[¶$((H³”&0. Ì;—Ï?ÿÜÜC±J¶mÛÆ–-[4å™3g*–zÆaÿþý¬[·µZÍwß}gîáX{öìá×_Õ”›5kÆ;ï¼cÆY'»víâÏ?ÿÄÕÕ•É“'S¿~}s©ìHVÄÂ… ¥&MšHÁÁÁÒçŸ.yyyIׯ_×÷ññ‘f̘QâöEqóæMéÔ©SRvv¶4}útéûï¿7úï#¤ŒŒ iÔ¨QR‹-Ì=«%((H –"""¤ˆˆ)33ÓÜC²:öìÙ#=þøãÒ¹sç¤ÔÔTsÇ*yðàæ5ü×_I/½ô’¹‡du$$$Hýû÷—rss¥³gÏJÏ=÷œ¹‡T.¬fÀ›o¾Éœ9sØ¿? |˜iÓ¦éÔÇÇÇ3nÜ8š5kÆÀ9zô¨æØ­[·¸qãuëÖ3™% <<œ_|Q§>--ÀÀ@Z´hAß¾}5Aû,XÀ¨Q£8uê·oß®èáZ,&L ,,L§~åÊ•tïÞ¶mÛ2sæLòòòpssÃßß~øáfΜi†[Ÿþ9[·nÕ©ß»w/ýû÷§yóæLœ8‘¤¤$ˆg÷îÝ$&&*>_-3 °JÁ¼yó¤ž={J€¦8¶aÃÉ××W:qâ„tîÜ9©uëÖÒüùó%I’¤ÜÜ\iÚ´iÒØ±c¥«W¯šcèÃܹs¥=zH€Î½úå—_$???éäÉ“ÒÙ³g¥–-[J .”>ýôSióæÍ’$IR·nÝÌ1l‹âæÍ›Ò¤I“¤š5kJ Ð9Þ§Oéµ×^“"""¤+VH®®®RBB‚´oß>iÑ¢EÒ| uíÚUÌARR’4yòd©~ýúRóæÍuŽ7N9r¤tíÚ5é?þ\\\¤°°0©}ûöÒ’%K¤ßÿ]êÞ½»táÂ3ŒÞrøî»ï¤Þ½{K€tòäIűíÛ·Kµk×–þý÷_éâÅ‹R—.]¤Ù³gkއ„„Ho¼ñFEÙâØ²e‹4lØ0ÉÆÆFZ¶l™âØ­[·$iÆ Òµkפ±cÇJC‡•rss¥)S¦Ho½õ–Ô´iSiÿþýf½qH’¤“'OJ!!!’Z­–®\¹¢8 Í›7OSþñÇ¥öíÛ+ÚlÙ²Eš:uj…ŒÕR9qâ„"ÙØØè AƒIß|ó¦¼zõj©S§NÒ¤I“¤Ñ£GK£G–ªW¯.½ÿþû=l‹"))I ‘¦M›¦#€"""$;;;)99YS×£GiùòåŠv#FŒÎŸ?_!ãµD233¥éË/¿Ô@)))’£££âõ=räHiÖ¬YR¯^½¤´´4I’$iÑ¢EÒÊ•++tÜ–Fhh¨"¹¹¹é Ñ£GKŸ|ò‰¦¼iÓ&©qãÆ’$IR^^žÔ§OéîÝ»:^KäÚµkRHHˆÔ¹sgôÕW_IO=õ”¦|ëÖ-I­VKþù§ôæ›oJ’$IñññRÛ¶m+tÌÆÆjLÐå!ßø¬R©tŽ………ñÖ[oiÊmÚ´áÊ•+:tˆœœüüüذa ¨°ñZ":uôßãððpÞ}÷]M¹uëÖ\¾|Y„¬{÷î|ñŦ¨ãêêJŸ>}¸zõ*¡¡¡Šcáááøùùáâ⢩kݺ5W®\á·ß~£eË–¤¥¥qýúuËŸÖ6!öööôéÓGF£0QQQ4jÔHS׺uk.]ºÄàÁƒY°`Ï=÷›7oæÇ¬°1["íÚµ@­Öýˆ gäÈ‘šrëÖ­¹ví999¬_¿žÞ½{Üý+( aÆ4lØPo¬²°°0Z·n­)ûøøàâ₃ƒÇçöíÛ\½zUÑÆ¨bccæ\]]IOOÇÝÝ7rïÞ=žxâ ÆŒº ÕIDATcÆQZ6÷îÝSÜc777RSSIOOÇÉÉ €I“&™kxVAll¬æ^æãææFll,,^¼gggÖ¯_¯ø[JŽö{Èï±±±¼ûî»,Y²„¥K—2oÞúÞ/$I"..ŽÌÌLÅ—)AÙˆÕÙÝåææFvv6‹/féÒ¥Ô¯_Ÿo¿ýÖL#4Bƒ³³³"8bzz:jµšV­ZY¼ú­,T«VMçÛÙÙ)‚q T>´ï1Ȇ]777† Â!CÌ42ëAû½äײ››jµš7ß|ÓL#³.ô½_€ü=qâDs ˪(êý¢C‡tèÐÁL#3.Vµ Ìx{{sóæMM9** ooo½K9‚²¡ï×­[WÜc#âííMtt4¹¹¹šº›7oâããcÆQYÞÞÞ¤¦¦’˜˜¨©‹ŠŠ÷ØÈè{¿ððð3—FÄÛÛ›[·niÊù¯kk‹&P1Œ9’àà`òòòX³f #FŒ0ó¨¬ qMOçÎñòòÒ$ŒŒdÿþý 6ÌÌ#³|||èÞ½;+W®äž[·n¯e#3räHÖ¬YCvv6 Þ/LÁˆ#غu+wïÞ`íÚµ´iÓ†† šydFÆÜ.ìÊÀСC%??? |||¤víÚiŽ¥¦¦JO=õ”T³fM©^½zR¯^½¤¸¸83ŽÖ2yê©§÷¸C‡šc)))R@@€æ÷îÝ[Š7ãh-“3gÎH~~~RõêÕ%GGGÉÏÏOúúë¯5Ç8 ÕªUKjÚ´©äîî® ç (9÷îÝ“üüü¤ZµjIvvv’ŸŸŸ4mÚ4ÍñóçÏKþþþR£F$OOOiúôéf­å2fÌÉÏÏO²±±‘¼½½5»¼$© 8jõêÕ%___©k×®Rtt´Gk™üßÿýŸäçç'9::JÕ«W—üüü¤K—.iŽÏš5Kruu•š6m*ùúúêìÆ³¬&¼@ AIK`@ ªB @ ¨r$ Ê!@ ‚*‡@À(ܸqCÊÀ\DEEgÖ1Ë@ À ‰ŠŠbРA¼ñÆŠú °qãF£_/''‡ (‚V$‘‘‘téÒ…¾}ûŠˆË D$X!)))œ8q‚ßÿ={öhê/\¸@DD„Gf~ýõWºvíʵk×øùçŸÍ=@`$X)|üñÇ|ðÁzŸ9s†åË—+êfΜÉýû÷øñÇÙ·o_|ñ£GæÛo¿E’$V®\ÉØ±cYºt)©©©Šþ—/_fÊ”)Œ3†íÛ·+Žíß¿Ÿ—_~™Ñ£G³bÅ òC?~œÕ«WɼyóX¿~½Þñ>xð€™3g2bÄ>üðCÍ8:ÄŠ+ˆˆˆà¿ÿý/gÏžÕé{ñâE–,YBLL .ÔDk¾uëï¼ó#GŽä‹/¾ 33€#GްbÅ MÿÍ›7³k×.Mù›o¾áúõë„„„ðꫯ2vìXfÏž­‰ž+*7B V̤I“HHHà÷ß×9véÒ%~úé'EÝܹsyðà¿ýö#FŒ ==Áƒ3wî\š7oÎáÇ4hëÖ­ã믿VôçwhÚ´) 6ŒãǰmÛ6&NœHçÎ9r$‹/櫯¾d!öá‡òÌ3ÏpýúuÍõ “““Ãc=Æ… xî¹ç ãÑG%;;OOOœ©U«Í›7ÇÍÍM§XXü1ƒ âÒ¥KüðC<==9pàÆ ã±ÇãùçŸçîÝ»;v¬Ä@`>D6xÀŠQ«ÕÌ™3‡™3g2tèÐR÷ÿä“Oxíµ×YDܺu‹~øwww¾úê+>þøcMû¿þú‹5jËÊ•+éÒ¥ ³fÍâ³Ï>côèÑ8::2cÆ ¦OŸ€ŸŸûöíÃÎÎNï8þüóOnß¾Mhh(666 >œš5kòÇ0jÔ(|}}éÑ£&L0ø»Ô¬Y“#GŽàèèÀ—_~Iݺu5¿Oÿþý©Y³&§N¢sçÎdddŽ$IT¯^ëׯs÷î]®^½JË–-ñôô$,, ???žyæ\\\2dH©ï±@ 0bH °rF…££#«W¯.u_[[[Ísll Þ2\]]uv\©T*Íó.]ºpûöÿ·oÇ,­daÇÿ1‚`CŒa*51APÁBÐCL‘""i?€‚ˆ`% ~±Š…*ˆ`£˜&j/Š£¤ÅÊÖB‰n!6»ì®7 ÷ÂÎóëfÎy§{xÏ{~¾Ž –—— ‡Ã„Ãaðx<ö[ZZþ6üX–E4µ÷w¹\Äb1,Ëúö·477Ûá§²æÈÈHU ===X–…Ûí&S,ÙßßgjjŠl6Ëáá!$ 2™ ­­­´··“H$Èçó”Ëåo×$"¿Ž:@"ÿs.—‹õõuæææˆÇãöûúúzÞÞÞ~hzþ3Ó4ikkÀëõ’Ïçþ~áàõzÿ2¼]*•H&“5­WY³2ÇP.—yzzÂëõ_¡b±Èõõ5ÇÇÇ<>>²ººJSSóóóøý~NOO1M“««+VVV¨««cff¦æºDäçPHÄÆÆÆèì쬚ŠF£ÜßßóòòÂÇÇ{{{¼¿¿ÿ§}*CÑ···œœœN§H§Ó¬­­Ù¿¿¾¾rppðíu“É$¦iR(€¯ê»»;R©T͵ŽS(¸¹¹áóó“Ün7ƒƒƒöžGGG466âááËËKb±ççç”J%º»»ÉårôööþP¨‘_GHÄ!666ªnm¦§§éèèÀçóqvvVuäU‹T*E0¤¯¯\.gÏmnnâóù0 ƒP(„ßﯺUõoºººØÞÞ&“É`lmm …j®utt”ÅÅE0 ƒ¥¥%vwwíP$¡¡¡ÉÉIà«ã•Íf …BöñÝóó3ýýýƒA"‘‡ÙÙÙšk‘ŸÇõY¹‹*"""âꉈˆˆã(‰ˆˆˆã(‰ˆˆˆã(‰ˆˆˆã(‰ˆˆˆã(‰ˆˆˆã(‰ˆˆˆã(‰ˆˆˆã(‰ˆˆˆã(‰ˆˆˆã(‰ˆˆˆã(‰ˆˆˆã(‰ˆˆˆã(‰ˆˆˆãüëëó~âIEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/compressed-select-nocache.svg000066400000000000000000000761021416254111300265010ustar00rootroot00000000000000 Selecting with small (16 bytes) record size (file not in cache) 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 MRows/s No compression zlib lvl1 lzo lvl1 bzip2 lvl1 PyTables-3.7.0/doc/source/usersguide/images/compressed-writing-shuffle-only.svg000066400000000000000000000730161416254111300277210ustar00rootroot00000000000000 Writing with small (16 bytes) record size 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 0.0 0.5 1.0 1.5 2.0 2.5 3.0 MRows/s No compression zlib lvl1 (Shuffle) lzo lvl1 (Shuffle) bzip2 lvl1 (Shuffle) PyTables-3.7.0/doc/source/usersguide/images/compressed-writing-shuffle.png000066400000000000000000001600631416254111300267260ustar00rootroot00000000000000‰PNG  IHDR@°AàÚ²sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwXW÷ðïÒ« RD ˆ  MÔX[Ôˆ% F¢1v_ˆ11úþŒ½E- &Ñ$ŠFˆ± ‚¢¢ ‚ éíüþØ— KwAá|žg˜;wæÞ-fÎÜ+""cŒ1ÆX3"ר`Œ1Ækh1Æc¬Ùáˆ1ÆcÍ@Œ1Ækv8bŒ1ÆX³ÃcŒ1Æš€cŒ1ÖìpÄcŒ±f‡ ÆcŒ5;1Æc¬Ùáˆ1ÆcÍ@Œ1Ækv8bŒ1ÆX³ÃcŒ1Æš€cŒ1ÖìpÄcŒ±f‡ ÆcŒ5;1Æc¬Ùáˆ1ÆcÍ@Œ1Ækv8bŒ1ÆX³ÃcŒ1Æš€cŒ1ÖìpÄcŒ±f‡ ÆÞ2D„èèhäåå5vWj%66ÙÙÙþí{~~~­¶GBB‚,»(,,Dtt4ŠŠŠdÞV]äåå!::999Ý©yõêRSS»ŒÕˆ &sßÿ=,X Q–˜˜777H”ûùùaþüùÕîoùòåøì³Ï„åÀÀ@Ü»wO¢NHHñâÅ‹7ì½t8pƒ –ïܹƒ   ‰:™™™hß¾=®]»ÖÀ½«Ÿ÷ßþþþ€¬¬,´oß¡¡¡5n„:àöíÛÖ`ذaU¾~D„}ûöaÒ¤IèÖ­† Rm[hß¾ý½222pôèQäææÖ{å‰D"LŸ>#FŒ@II‰ÔöÛ˜<==±bÅŠzo¿`Á,^¼XŠ=b¬r1™SSSömÛ••%”áÔ©S8~ü¸D݃ÖøßpÛ¶mall,,¯^½GŽ‘¨£©© ())IáH®®.ÌÍÍ…åÇã›o¾iÄ5ŽÜÜ\Lž<«V­Â‡~(”'%%áàÁƒ5jþøã‰÷L©œœ¸¹¹ÁÇÇݺuÃÊ•+k €¤!66&LêÙ eeeœŠ‹‹###Œ?^øo955yyyÈÈÈ@tt4 M›6èÒ¥ –.] ===â3iiih×®ž>}ŠgÏž¡ÿþPTT¬ÐßàñãÇèÔ©Z·n¢¢"a?奥¥!//†††BYll, ¡  Páy888 W¯^BŸ222„Ë €8@*+==·nÝB÷îÝa``Pã±...ƽ{÷ðêÕ+´mÛ–––‰D€˜˜èé顨¨7nÜ€ºuë‘H„¤¤$ܾ}}ûö…¶¶¶Ä>cccñìÙ3dggÃÈÈ=zô¨±5Ùµk±hÑ"‰òÄÄDBYY¹Êm7n܈;wî ,, -[¶¬sÛqqq¸{÷.ÌÌÌ`aa@%&&¢]»v——êæççãåË—h×®âââ/^¼@aa!TTTкuk¡ndd$?~ ccã Ǩ°°wïÞEJJ LLL„v eË–X·n–,Y‚Y³fAUUµÊ¾'&&BQQ:::(((^g())ÁƒðìÙ3tèÐ]ºt©tûˆˆhhh GÇ9-- ¡¡¡ÐÐЀ••TTT„u•}Û´iyyyƒnݺÕê5€¨¨(DEEAKK =zôž÷äÉ“…:ÙÙÙHJJª°­¦¦&Zµj%,GGG#<<èÕ«—ÄkÈX•ˆ±`iiIK—.–»téB¿ýö)((ÐóçωˆèÚµk€ˆˆhêÔ©4hÐ Zºt)©©©zðàÍœ9“ÜÝ݉ˆhÁ‚¤  @ÊÊʤ¥¥EZZZtïÞ=ºrå  ŒŒ ""Ú½{7iiiÑøñãIQQ‘455I__ŸÎœ9#ô)77—FM¨]»v¤­­MêêêB[•Ù»w/µlÙ’JJJˆˆ(..ŽD"=zT¨3pà@š?>íܹ“LMM‰ˆèÀ¤¬¬L Bß>Lééé€&MšDêêꤣ£CŠŠŠ´`Á‚jqTTuîÜ™´µµ©K—.¤  @}ûöÖ+**ÒØ±cIKK‹tttyyy‘¯¯/)))‘ŽŽ©ªªÒºuë„mBBBR—.]HYY™lmméõë×B:pàeffúûï¿«ík·nÝ諯¾ªrýÇ…×»¬ÜÜ\jÙ²%íÝ»·Úý—wçÎ@cÆŒ!yyyRWW'äããCDD/_¾$EEE:qâ„Ävk×®¥Ž;RAAijjjÑ¢iiiÑàÁƒ‰ˆ(99™>üðCRTT¤^½z‘œœ9’ŠŠŠˆˆ(,,ŒLMM©U«VdnnNòòò4pàÀ Ï«E‹ôóÏ?Wû<iÊ”)4cÆ RRR"”››K/^¼ {{{RQQ¡ž={’H$¢©S§ Û•””Ð’%KHNNŽôôôH]]´µµ),,Œˆˆ~üñGRWW§Ö­[“šš™ššRhh¨°ýôéÓiàÀ´lÙ2á³Niii4pà@@&&&¤¥¥Eªªª4kÖ¬*ŸCaa!=š”””¨k×®¤©©IšššEDD&L "":vì˜ðÙ(}öŸ››K$''GVVV¤¤¤D666Âçž±êpÄÄgŸ}FÖÖÖDD”˜˜H***”››Kýúõ£C‡Ѻuë¨[·nÂ6S§N%999š3g]½z•ÂÃÃ)''G""ÿQX²d‰D{•@ ôÝwßQnn.•””ФI“ÈÍÍMØféÒ¥¤§§Gþù'‰¿¨ÝÝÝ« €ž={FèÞ½{DD´uëVRWW§Ñ£G‘ø––>}šˆ$ "¢… ’«««Ä>K Q£F Áá©S§HII‰233«ìËÇLÖÖÖB0öúõkúᇄõŠŠŠ4pà@ŠŽŽ&""???@£GÚÙ¼y3µmÛVØ&))‰ž>^"H6l˜DÀ“––F¬ÐÇñãÇ“——WµÏÃÑÑ‘”••iåÊ•JwïÞ¥ââb|8ˆ¨BÂtYHIIÂ5440mÚ4‰:3fÌ€‰‰ —#½½½…vŒ/^ ** €ø’œ©©)nݺ…½{÷â‡~€ªªª°¾>ž?999tèС^Û–öë§Ÿ~Â;w0wî\|þùçÂ¥ÓêÌ›7-[¶„ºº:–-[‘H„?ÿüðÙgŸ!00<üþûïHMMÅôéÓ«Ü_nn.~úé'Œ?ÉÉɈŒŒDff&úõë‡K—.¿ Â~µµµáááQa_:uÂÓ§Ok|^^^øê«¯Ð»woôèÑñññÀG}„„„DFF¢°°½{÷ú°gÏØÛÛc̘1PPP€¼¼<<<<`ii‰cÇŽA$ÁÛÛJJJhÕªæÎ‹ÈÈH\¿~]hwÈ!ؾ};lll`ii 555>|'N„³³3@II©Â%Ôò444ŸŸ7n ¨¨"‘£FB›6mªÝîÒ¥KX²d Ž;&äîÙ³£GFvv6"##‘˜˜;;;áy3VÎb ÂÑÑD„Ë—/#88Xœœœ0{ölãòå˘5k–Äv¥ù+² ¡¡œœJJJ[!« „„„`ܸq ùsç°ÿ~üþûïxýú5Þÿ}´hÑâú*‰ ®®.Ün^™Ï?ÿ=‚••tuu1xð`¬\¹R"麬Òü"Ê444@HD¿ÿ>Fމââbôë×FFF““Cqqq½ŸKZZôôô„©úX´h‘ÿcnnŽ«W¯âèÑ£ðôô¬õ>ÔÔÔЩS'áÎ0ôîÝ;wîÄ–-[°mÛ6L™2¥Ú×îéÓ§ "lÞ¼;vìXgjj X¹r%<==aii  :+W®Dûöí%ê"99¹Æ~—ÿL”£kÖ¬©°®4'**ªÊ»+£¢¢Ð§O‰| ¾}ûBAAQQQ°³³«´Ý¤¤$¼~ýºÎŸWWWÌ™3'N„¼¼<¬­­ñÅ_`èСUn‡qãÆaÍš5B°•““ƒ„„øùùáôéÓõK=cÕáˆ5ˆ-Zàý÷ßGPP‚ƒƒ±eË€ž?ŽÓ§O#''§^gJÉ?âõ!//MMÍZý^ž‹‹ >ÿüsœ:u Æ ƒ¢¢"ÆŽ‹'N@[[»Æ?oÚ÷R;vÄÅ‹…ÿÜwíÚgggÄÆÆBN®~'{W­Z…Î;ãôéÓBÂxddäõSSS©©©())©s¿:vì@œÐ]6ZCC=ªÓ¾rssñìÙ3 esçÎ…··7¦L™‚¿þú [·n­°]Ù×K__°oß>ôëׯÒvºv튿ÿþááá¸~ý:¶oߎ!C†g„J%''×xö¤2¥}8yò¤p|*«óøñãJ×éêêV8£÷ìÙ³j“ÿq /_¾¬S•””°uëVüç?ÿÁµk×ð믿bذa¸uëÞ{ï½ õóóóáîî'''|þùçB¹ªª*444°zõjL˜0¡N}` àÛàY0`~þùg} ¥¥Uçýš››Wz§H]}ðÁøù矅 èÉ“'øçŸjÜÎÙÙñññظq#ÆŒ7nΞ=‹¿þú«ÚHZ}€°°0aŸøöÛo‘œœŒ´´´zï311Bð“——‡W¯^½Q?ŒŒPXX(ÜUUÆÆÆèС>,”•””à?þÎTÔV`` п¡l„ PTT„»»;  q·–©©)”••%^/===téÒ»wï–ØwII ž}:|||Wa<¡èèh‰áj«S§NhÓ¦M…> ïe;;;œ;wN¢ÍŒŒ $%%ÁÞÞ/_¾Äýû÷…ugÏž…ªªj¥I)uuu˜™™ —ñû#11±Úþ>~üùùùÐÓÓÃðáÃñý÷ߣeË–UhsçÎE^^öíÛ'Q.‰Ð¿ìÙ³§ÂJu †YóÄg€XƒqqqÁš5k`oo/‘Ëãèèˆo¾ùË—/¯×~GމQ£F ÿ=Ï;·^ûùöÛo1xð`têÔ FFFÈÎΆA·Ôêéé¡GˆŽŽ†««+ }ûö°´´Dxx8lllªÜvøðá˜5k¦L™### 0 Ú?:Õñôô„‚‚œ!''‡'NÀËË«Æ<¦êLŸ>ÞÞÞHNN†ºº:Ο?ÔÔTX[[×{ŸíÛ·‡©©)NŸ>9sæH¬{ôè~øáa¬ï¾û:::˜3gŽp‹úöíÛ1bÄdddÀØØ~~~PPP€OmO: À½{÷ð믿böìÙèÞ½»°^EEŸ|ò Ö¯_íÛ·Kl«¡¡gggaàB,[¶ »wïÆ°aÃ'''$%%áâÅ‹0`6oÞŒqãÆÁÈÈýû÷GQQŽ= ooo‰Ï@qq1ðí·ßÖùx*((`÷îÝ7n"""`kk‹¸¸8üùçŸðððÀªU«àëë‹ß~û mÛ¶Å”)S@Dðó󃿿?† ‚#FÀÆÆS¦LA\\NŸ>uëÖ g—ª²jÕ*L›6 ÷î݃ ––++«*·9~ü8öìÙ#äý\ºt ºººÂ?e6n܈þýû£oß¾6l²²²péÒ%´nݺÂe1ÆÊ“÷õõõmìN°æ¡M›6PPPÀG}„N: 円†ÐÓÓÃĉ%N¹‹D"XXXT:¶ˆ¹¹9ºví @|YÄÞÞ÷ï߇H$€ ®®Ž-ZÀÑÑQÈ5iÓ¦ lmm%öÓ²eK899_&˜1cìíí1bĬ_¿·nÝ‚¡¡!\ís344„«««0Æ þCoccƒ¾}ûJÔ522΀ijjbÔ¨Qˆ‰‰Azz:кukÈËËÃÑÑQâ’ˆH$Â|På%äææâéÓ§HOOǨQ£°téR‰ÎÁÁAâËÉÉÁÉÉIâÌ›’’œœœ ®®+++XYY!,, jjjøê«¯`ccƒ=zH¼†vvvhݺ5D"‘Ð÷êÎæ%&&âøñãðòò’È-IOOGDD455áèèMMM(((à½÷Þrq:vìˆ#F ""Ož<««+„²²2>úè#L›6 ÊÊÊÐÐÐÀŒ3 ¦¦†øøx(**bîܹB°<~üxèëë#22:::X³fD¢¶H$‚¹¹¹D°VVVèÓ§’““¡ªªŠÕ«WÃÎÎÝ»wGçÎ+}={ö„¶¶6^¼x˜˜XZZbÇŽïKKKXXX ##mÛ¶EÛ¶m¡¡¡!<Ú´iƒž={¢U«VðôôáñãÇÈÍÍ…‹‹ –,YòÆyw¬é‘´{Çedd@$ _œÏŸ?‡••öîÝ ww÷Fî]Ó’œœ 333|÷Ýw˜1cFcwGP\\Œ:ÀÛÛÞÞÞ ÒfAAzöì‰aÆá¿ÿýoƒ´Ék‚9@ÅÅÅxþü9ÒÓÓk½MRRÒ;3ñ$“ÐÐPèêê¢K—.èÓ§,,,àîîÎÁ èêêbË–-X´h‘#ó6ð÷÷GJJJ…ád©ô’mm.á1Ƥ§IòööÆ÷ßV­Z —üüü ©©YiýØØXŒ9‰‰‰ÈÎÎÆÇŒ-[¶ÔûŽön+((@hh(¢¢¢ ¨¨ˆ=zT:“žsçÎA[[ûrФ)44ùùù.•ÊJzz:‚‚‚ЫW/a|&ÆXÃhRÐáÇ1pà@ã¯Ì™3Gbæð²ÆŽ‹víÚá»ï¾CFFÞ{ï=¬_¿žÿãgŒ1Æš¸&uªcòäÉ„‘jjj(..®ò¶Ò¢¢"œ>}Z8MKK ãÆÃÏ?ÿÜ`ýeŒ1ÆXãh’·Á¯\¹—/_Æøñã«]ôÕ«W(((8íܾ}{\¾|YX~øð!>|(óþ2Æco •ï|m šd$''‡¶mÛ"$$ hÛ¶m…:¥IÒe‡WUUEFF†°|ôèQ=zTb04YIHH€¶¶¶0t}]EGG×zø÷ÚÔ­®NUë*+/_VTTTá5yòäI•#ØJSAA’““kœs¨*uy233QTT*ëHë¿zõ Âȼ•Õk¨c\Uk«®¯QMm¥¦¦BAA¡Ò[¢«zJ¿ÊAPÙû¶|Û±±±Ð××—øN‘•79Æuݾ1¿/Ê¿F999HMM­ô;]Ú*{ÔE]Žqm¾[duŒË—åççãöíÛHHH¨Ußßi >ýjš>}:Íž=»Òu¥3Rgee e;vì gggaÙÇLJ|||dÝM"ÏÖüìÙ³zo_—~Ö¦nuuªZWYyù²´´´ 3w:´ÆþHÃË—/içÎõÞ¾.¯ÑÕ«WéìÙ³ÕÖ‘Ö1öóó«0kzùz uŒ+k».êúÕÔÖÙ³géêÕ«•®«ê5 ¤ÀÀ@‰²ÊÞ·åÛž9s&EDDÔØgixÓï¥wåû¢üktíÚ5š7o^ý‘†ÊÞuQ—c\›ïYãòe/_¾$ƒjûÒT4éè‹/¾ ‰'Vº®  €455éÆBÙüùóiæÌ™ÂrC@)))”ŸŸ_ïí_¾|)ÕºÕÕ©j]eååËŠŠŠèÕ«We uŒ )99¹ÞÛ×å5ÊÎΦÌÌÌjëHë§¥¥Q^^^µõêWÖv]Ôõ5ª©­ÌÌLÊÎήt]U¯Ñëׯéõë×e•½oË·½sçÎ7zîuñ¦í¼+ßå_£gÏžÑþýûkì4Tö>¨‹ºãÚ|·Èê—/kNP“I‚.((À¤I“pîÜ9<}úþþþØ»w/ÆŽ+ÔéÝ»7Ö­[PTT„‡‡|}}ƒ«W¯âÇlÐñ?ÊÒÑÑ’’R½·oݺµTëVW§ªu••—/“——¯v‚EYRPPx£i!êò©©©U9üB)icmmí —]êò~¶7i»®¯QMmijjJ\,«ª×¨t´á²*{ß¾«Ç¸®Û7æ÷Em>G²RÙû .êrŒkóÝ"«c\Ó¾›²&“$//CCC¬X±qqq022¦M›àææ&ÔéСtuu…å 6`Á‚pvv†ŽŽ¶lÙRí¼ML6xØÙãc,{...õÎaµ£«« {{ûÆîk"šTôÿ÷ÕÖ9qâ„IJ²²2vìØ!Ën±Z¸rå zôèÑØÝhÒøËÞÝ»w¡££SïXÍ233Ž:4vWXФB”¶Òyb«›/öƈˆˆh˜1ö122‚««kcwƒ1V °²²jw5™3@åÈ‘#¸rå ,--»+Œ½5âââ //Ïcì­ÅLœ8±ÁfŽfì]€M›65v7TJJ ´´´  À_«²RPP€¬¬¬jÇ×b¬¶šÌ]`Œ1Ö˜%ReÒ—œœ,1Z?co‚ÿUaŒ1)3fLcw¡ÉkÓ¦ FŒÑØÝ`MŸbŒ1ÆX³ÃcŒIAJJ ŠŠŠ»MZAARSS»¬‰àˆ1Ƥ€s€ds€˜4qcŒIçÉç1iâ3@Œ1Ækv8bŒ1)à Ùã &M1Ƙpìq“&ÎbŒ1)à Ùã &M|ˆ1ÆcÍ@Œ1&œ${œĤ‰ ƓΒ=ÎbÒÄ«ÀÃÃnnn ‘(?sæ æÏŸßH½jzâããáèèˆàààÆî “‚1cÆ U«VÝ&s€˜4qô[¢¨Hü599@I©ú:gΜ¢¢".\ˆ›7o åOŸ>Å_ý%ã6ŠŠŠ°°°€¦¦fcw…1Æš>ô–X´PU•ý£ÿÚõç‹/¾@XXüýýk¬ûàÁüñÇxöìY­ŸoVV®]»†ààà ×ôóóóqýúuüùçŸÖeggãùóçÄY@@…õ¸téJJJ$¶KLLDjj*òóóqõêU";;[¢Ntt4rss™™™HJJÖåååáúõë8{ö,âãã+<Ÿ””#88X¢?¥ÏçæÍ›8þ<"##…r]]],]º]»v•¨ÿêÕ+œ;w¡¡¡(,,¬°®4×äÖ­[ ­ÐÖ88Hö8ˆI±*ùøøOµuæÏŸO7n|ã¶æÏ'dÿ°¶®¹/­Zµ"òöö&sss***""¢­[·R·nÝ„zééé4dÈ@íÛ·'äéé)Ô¯Š¿¿?µlÙ’ÔÔÔH__Ÿèûï¿'"¢»wïRÇŽIUU• IUU•~øáa[???RRR"RRR" RVV¦Ã‡“‹‹ )++“šš™ššÒ;w„í\]]ÉÚÚšŒISS“PÇŽ)&&†ˆˆJJJ­[·ŽH$‘ ]ºt‰ÚµkGzzzÔ±cGRRRúKD´}ûvRUU¥:¡¡!ÉÉÉÑîÝ»‰ˆ(44”Ú¶mKzzzÔ¹sg’““£‘#GQVV a_›6m"%%%jÛ¶-)))Q·nÝèÑ£G;àŽ IDATÂúÁƒ“½½=YZZR‹-HAAzõêE)))5¿° èìÙ³4hРÆîFƒ:qâ%''7v7𴏏8:uêTcw£I{ùò%4v7ŸbUZ±bâããqàÀJ×ûúúâÑ£G¸wïž>}Š   =z{öì©rŸQQQøøãáåå…¸¸8$&&"88­ZµBII >þøcôèÑ111ˆ‰‰Á×_ OOO‰3'………èÞ½;222‘‘WWWÌœ9£FBzz:ÒÓÓahhˆãÇK´žžŽ#GŽ ##W®\Aaa!Ö¬Y#QgãÆ˜8q"ÂÃñÿ~¤§§c̘1˜;w.âããñøñcìÛ·óçÏGbb"²²²0oÞ…¡¡!>ùä‰z±±±Ø²e ÒÓÓ‘€¤¤$üþûïUsÖ08Hö8ˆI@o EE@Y¹aµ¥««‹E‹Á××yyyÖ:t£FB÷îÝpttÄ¡C‡ªÜçáÇ«V­‚¶¶6ÀÖÖîîî¸}û6ÂÂÂðé§ŸBOOŠŠŠøì³Ï ¢¢???aÊÊÊX´hTTT ''‡ÀÐÐsæÌŠŠ áì쌠  ‰¶]\\`gg‘H 6 çÏŸ—¨³sçNxzz¢K—.077ÇéÓ§‘ŸŸ?üQQQˆŒŒD=PRR‚[·nAAA***¸sçÒÓÓݺuƒ““@CCñññxôè@GG“&MªòØbÚ´i——‡‘‘f̘‰Ë‹ƒ ‚‹‹ D"Zµj'''œ;w®ÊcÎc¬"€Þÿý/—'ûG]o8Z¸p! °}ûv‰òôôt¤¦¦ÂÎÎN¢ÜÎÎQQQUî/** 666PSS«tH$‚­­­P¦  kkëj÷©¬¬ "’(ÓÐÐ@NNNµÏ­wïÞˆ‹‹“(“““üHDEE¡°°“'OÆøñã1~üxxxxÀÂÂ………PQQÁŽ;àïïV­ZÁÒÒ6lr‰|}}!''sssÂËËKÈaªìùWvkxœ${œĤ‰ V- ¬\¹k×®•ãDSSJJJxúô©DýGAWW·ÊýéëëãñãÇ•®ÓÕÕUH¦~üøqµû¬¯{÷î¡]»vÕÖÑ××G«V­pçÎ 777âa¢££„ &à믿Ʒß~ °²²Âõë×qïÞ=¬^½×®]¶+OWW·ÒãYºŽ½Ýx Ùãq€˜4qÄjäåå…-Z`ÇŽB™¼¼<ììì$.!ãÏ?ÿDÿjn5³³³Ã‹/pëÖ-¡¬¤¤QQQèÕ«444$öŽçÏŸW»ÏúÈÉÉÁÙ³gáêêZm={{{ÄÅÅáÌ™3å©©©HNNFZZâââ ¢¢{{{¬Zµ C‡‚¼°°0@÷îÝ1sæL¬X±ÑÑÑîî€þýûãöíÛwŸ={zzz°°°xÓ§ÌdŒs€ds€˜4ñ8@¬FJJJX½z5&Ož ¡|ýúõpvv†••†ŠŸ~ú EEEøÏþSå¾ÜÝÝñá‡ÂÖÖ£G†¹¹9~ù帹¹áË/¿Ä7ß|ƒ  ((&&&Ø¿? „Q£F½ñóøñÇ‘••uuu=zøòË/«Ý¦GXºt)ÜÝÝññÇ£}ûöxðàΜ9ƒß~û -Z´€µµ5Ƈ®]»"&&'OžÄµk×LJ¹¹9>øàäççÃÏÏ_|ñQPP ÑÖ”)S°wï^˜››cÚ´i¸wï±ÿ~¨¨¨¼ñógŒ1ö/y___߯îÄÛª4‰ÖÑѱÊ:ÐÖÖF¿~ý¦S äƒ>€žžž°Ü­[7á,Çûï¿@üߨ¸qã‘‘'Ož`àÀØ¿? ªÝ÷ĉaff†´´4ddd`øðá˜9s&TTT`mm [[[¼xñééé˜1c6lØ ‘›c``P጑‘Q…×ÀÄÄ}úô N0¶´´D÷îÝñêÕ+Œ9{ö잣H$ ~­Ë_nrqq³³3âââðøñc´nÝ‹-‚ƒƒôõõÑ«W/¼|ù=‚ºº:þïÿþOh×ÅÅE8>¹¹¹˜2e >ýôSÈÉÉA$ANNŽŽŽÐÒÒ‚œœ<<< ¦¦†‡ÂØØ6lÀ°aľˆD"ôèÑ;w–(ëܹ3ºuëVíqoHOž<Áµk×ðñÇ7vWLJJ ”””*ä‘1é)((@FFTUU»+MVVVvíÚ…E‹5vWdNDå³G™ 46¬.Fôöö†©©)¼½½¦S¬^ˆN:UHæf²€M›6!  ±»Ò`üýýáääÄ—Ád(>>·nÝâË`2”+++$$$4vWdŽ/1ƘŒ3¦±»Ðäq“&€X³àëë ÆîcŒ±·@¬Y(;¶c²’’---((ðת¬ ++Kâf Æê‹³õcL x Ùãq€˜4qÄêdçÎØ·oŸ°|õêUcÒÀãÉç1iâˆÕIPPþþûoayôèÑØºu«°|èÐ!DGG×iŸ±±±ÕÎV^zz:}úôJ§£Ø¶múöí‹¡C‡ eX¾|9‚ƒƒÑ²eK,[¶¬Â$ŸU‰‰‰|}}ajj*”‡††bçÎÓq”µ|ùrÌŸ?_}õUµ‡2&+<Œ%''ó8@Lj8zKüýüoìÝ-óv¬¬« €ŒŒŒ`ee%,‡„„à·ß~Ãܹs+­ÿÛo¿AEEE"úì³Ï0vìXŒ9?üð\\\ “ûgllŒìÙ³_ýµP¾yóf¼~ýJJJ•n§§§===´hÑ¢Æ6“Hö8ˆIç1 NNNðöö†··7ÜÜÜŒ7 S;ÔÆ¶mÛpðàA,^¼ÐÐÐÀîݵ îD"¼¼¼pàÀŸQò÷÷ǬY³êõœcŒ±ò8b•ÊÍÍ…»»;† †9sæÔiÛ²gaäääðþûïãÁƒµÞ~Ú´iHNNf`÷ó󃡡!X§~0ÖRRRPTTÔØÝhÒ *½û“±úàK`o‰q]ÇÁB×BæíhT?Qi)OOOÈÉÉa×®]oܦºº:òòòj]_OOîîîØ³g†Ž}ûöÁÓÓS˜°”±·çÉç1iâè-aÛζíÞŽÑŠ·lÙ‚€€„††¾ñ^%%% ÄŒ3ê´Ý¬Y³àâ₳gÏâÞ½{øã?Þ¨ŒÉçÉç1iâK`LÂ¥K—°hÑ"lÙ²JJJHHH@BB²²²j½ððpàåË—X±b²²²ê4Ð!888 sçΘ©¹¹¹øûï¿‘[[[˜™™U[?66qqqeÚÚÚ°°ýx<Œ±¦…s€d¯¾9@%%@x¸äÙ'Oj·mzºøRX™© YѤ ~ýúAAAæææX¾|9:uê„‹«ž`tûöíØ»w/Ú¶m+”ÙÙÙaûöí Ñ]ÆXÒP9@%%â³òò€’ ¨(~4‡OµÍÊÌ®_ÿ7عvM\V_wîpÔ5©ŒŸŸ,--ùùùhÛ¶­ð_YUÆŒ#•ÑŽcLÖ I“€'*__ • Œª+«kyUu••---ÉŸ-Zˆ×ÉÚ“'’—³îߊuehØÚññâý•ºsps“^ÙÛ¡I@¥Á(++CGG?®6bŒ1iuPn.0z4pölÕu Åœ™t¡^ª ŽªúYU™¼¼8(%% QQ:B°sõ*ðêUÝû%/ôè!xJ¥gy¾ÿ^2º}[*‡‚½m¨‰ !zñâE•u–,YBêêꤧ§G]ºt¡¯¾úŠŠ‹‹…õ>>>daaA7n”x”åääT¡¬) ¢Ë—/W¹þàÁƒ]§}>þœ:Tëúééé´ÿ~ÊÊʪS;UÉÊÊ¢PXXX…uEEE”@………Ö]¿~¤Ò‡üü|:yò$mß¾ˆˆ(''‡üüüh×®]”––F‡¢çÏŸ×j%%%äççGÁÁÁRé_]={– Tá³Ñ”—Oœ8AkÖ¬‘Éþ33‰ú÷'6@eÍgY]HC×NÕkû–-‰ºtÙH_}Etñ"QVVÕÇûúuÉí¥ÿ~y›–/^¼H7n$777Z½z5µjÕŠšƒ&EFF’®®.íÛ·¯ÚzÑÑÑôüùsJHH óçÏ“‰‰ }óÍ7Âzòöö¦´´4‰GY³fÍjVиqãhÚ´i²¡¡!Í›7OXVPP 'NÔiŸ§N"%%¥Z×'•k×®%WW×:µ?uêTêÓ§½~ýZ({øð! 6Œ ÉÉÉQÇŽiñâÅBùóç“££cÚªLéûµk×®äêêJ—/_¦ëׯ“¶¶6õîÝ›œ)<<œ”••é×_­õ~Ož}Jÿ § Е•‰TUÓHQ‘H$ª¸¾9/‹DDææD“&¥Ñž=DááD%%µ?þ99Dòò’ûOM};Þ²XÎÉÉ¡´´4ŠŽŽ¦ÈÈHÒ××§æ I]€GaРAðõõÅôéÓ«­kbb"üîêêŠO?ýýõ–-[&”kiiA[[»Ê}(Kë÷ÚµÀÁƒÒÙWu¬¬€£G¥¶»;wÂØØXjû«¯}ûöáÔ©S8sæ >øàƒZo÷×_áÈ‘#¸wï444ˆ/e899ÁÒÒ§NB÷îÝñìÙ3\ºt »víªÓ´ µáïï¾}ûJLøºpáBŒ1ßà=áîîŽiÓ¦aÆŒ¸ÝHçðËvx¹n˹¹ÚpußÁô¿€ž=óç}ýë……ÚÂe0ñCr¹  úõõ­Ÿ—'N2ÎÈÐFFFéï@f¦6$g)ÿ]*e55 o_ÀÖV¶¶@¿~€øf¼úUUÀ܈ˆøwý;€“ÓÛõþÖ²ªª*TUU¡­­„„ˆD"4M*ºrå Æõë×c„ ÖGEEAKK ºººÄ3•ËÉý;ÚóçÏa``Ð`ý•˜DFʾj‚9¸~ý:}„²€½½=._¾,Ì•vàÀ´jÕ jjjøå—_`oo M›68p`¥ý¿|ù2þúë/äççÃÑÑ®®®ë¿ùæ´iÓgΜÁ!Cj}\XÝI;(:0ˆŠ’,ï×OœTþ£¬  ~¨ªJ¥y©ÉÉDPTÕïÕ­ÏÎ.Ý[€,ëHäîôì)ý»à¬¬€ˆˆ—ÅtÛ`«É@ÙÙÙpvv†ƒƒ"##áëë ÐÕÕÅܹsˆgŸ:u*Ö¬YèÛ·/ŒŽ;",, ÄåË—ë)¼îÞ½‹3gÎËñññ¸yó&NŸ>]iäãヱcÇ¢{÷îBÙâÅ‹¸pávî܉;wî@SS³Æöµ´´°`Ádee ¯lÚ´ ÁÁÁ˜2eJ¥Û•&ºß½{·Ög;^¾|‰lÚ´I¢¼4™>((H€J•ž%*‰Þ½{£k×®(((€¯¯/„@dýúõèÕ«—D´{÷nˆD"ØÛÛãŸþA||<òòòPRR333èèè %%ÿüó233aeeUi´xñbìܹnnn(..ÆæÍ›±|ùr,_¾\¨£¥¥…‘#Gâøñãɘ4ÇŠŒ?/^H–;;§NåÞ†o555ñÃаþû(.C'ãáÃ[˜2EöÓaXYGŽü»|çŽÌ›d ¬É@òòòXºtiµu.\«2“º|ùå—¸páÎ;‡víÚáöíÛ•þ‘oN<==áéé @<°¤­­-¦M›†áÇ×zãÆÃ7ß|999DGGÃÒÒ{÷îÅ‚ jÜVUUS¦LÁÞ½{…¨¨¨‡ÂÚµkë÷¤ª ôêÕK¢ÜÒÒnnn˜>}:6mÚkkk¼÷Þ{9r$Z·n-QWEE/^¥>úþþþÎÄTeÞ¼yøùçŸacc#ñüŽ=ŠiÓ¦áóÏ?¯²ïß}÷>|ˆŽ;Nœ8|öÙgÁæ AƒàããS«þ°ú“Ö8@wîŠÇú)køpñíï q[ùÛF^ÐѬ­ÛÀÚºaæ+?ÿß Öô4™HEEE8ëS•… J,:C‡•a¯ê`ùr`Ö,Ù·S‡óã3g΄‚‚vîÜY§&úôé#\Z455EïÞ½qãÆZoïåå…Í›7ãÖ­[èÓ§~ÿýwäççcüøñuêGM$.W•õË/¿àòåËØ³gnÞ¼‰àóÏ?ÇÊ•+%m‰³D½zõÂáÇ¥ÚÏÊœ={¦¦¦¸pá.üo¢¢¬¬,äååáÑ£Gxï½÷$ú"j6×ößUW®C‡ŠG.kÂàСæ1ØáÛ¢ÜÿExðÈÏožhSŧ·…¾¾øñ–Ø´iΟ?ÐÐÐ7Nô655ERùg«Ñ¥Kôïß{öìAŸ>}°oß>xxx@MMíúQ^vv6Z¶lYåú>ø@H¨ÎÊʪU«°bÅ L:µÂ™ RÊÊÊ(..–j?+‘H„‡J”ÏŸ?_"w tttPTT„ÜÜ\©Cö¯7ͺpA<ØÞ¿ù.bžžÀÎ@™tÅfKšsÕDW02Oˆ ˆ“ÌÃÃÞ½eÞ4k ü‘baÙ²e8~ü8Úµk÷Æû»råŠp™¦¶fÍš???<~üðòòzã~”§££Sa.8@œ_ž††fÏž’’„ÖavDeeeäU6Ãâ233ƒºº:6mÚTáQþ2n\\ÔÕÕ9ø‘±ÀÀ@dddÔkÛS§€aÃ*?‹»wsðS*99¹Aó4Ë_ã< ¦…?VLBll,Ƈµk×ÂÑѱ^û(,,~?{ö,ž={VçüˆÑ£GCEEcÇŽ…Ä(ßÒbff†gÏžIô6lØ€¹sç">>^(ËÊʺuë ¡¡¾}ûÖº ggg\¹rEÿ»øŸþÁÍ›7߸ï£FÂýû÷qàÀ‰ò“'O"½Üõ“ÈÈÈfŸÛÖÆŒS¯èŸ~ÆŒ_^)kõjà¿ÿ•R皈ÚÎ&-5m1 +V¬@rr2víÚ á±eË–ZïcÚ´i°··‡¥¥%† ‚E‹Õy:%%%L›6 wïÞŬZäF-Y²ýúõÃÑ£GñÏ?ÿ _¿~ððð¨v›þýû£°°Pbü@œ3säÈÁÈÈVVVÐÖÖÆü£GÖú–~pssCLL ÌÌÌ`aa‘#GBII©ÖÛWÅÊÊ [·nÅܹsѽ{w 0úúú˜2eJ…€Îß߃ zã6™ôíÚLž ‰±rD"`Ó&`ÕªÆë+ŸÄPÓ"""jìN¼­J“ª«K®ööö†©©)¼½½¦S2^i¾Ž©©)LMMyyyaÜœÐÐPèéé ƒ!^ºt fff¸zõ*^½z…~ýúI$äV&%%áááèß¿¿Dyjj*îÝ»[[[‰ !''7nÜ€ŸTY¿544$n?¯ÌŒ3ðôéSüõ×_å………¸yó&bbb••…öíÛÃÖÖVâ2Ò“'Ož={ e/^¼ÀË—/ñþûï eùùù8uêáêêŠØØXˆD"XXXîܹ---‰dê›7oÂÐÐmÛ¶Ê.]º„®]»JœeHJJÂ7cccØÛÛC]]]XçÎôéÓ÷ïßÚkØ´i¬ÍÆV× õë%K$ËäåÅóPÕ0†k³Õ9@€x ¦²Wï[´'¨7å{ `ee…„„„ÆîŠÌqTæ57111èÒ¥ öíÛWå ‹ïª’’ØÙÙ¡C‡øñÇ´íæùûû×z •+¯¿–,ST_;VFlâããqëÖ­» F$žõõëËž<:thæEs €økÖLLL°eËxyy5Út²2{öl$&&bÛ¶mÝ•f¡69@DÀ¼yƒUUà×_9ø©ICç‰DâQ¦ËâË`M@¬Ù›1c233+ ˆø®Ûµkž>}Zí\v¬áÓ¦[·J–kjЧ¶àºßNœÝtqÄcR’’"ÜíW^A0~|ÅùŽut€‹‡è`PPP€ÔÔÔm“¡›.€cL ª(798yR²¼uk 8(“/ÏjÐÐã|¨)ãˆI(**‚©©)®\¹"³6vïÞ?ü°Öõª(((çΓV׫·Êr€23Aƒ€ò¹à&&@HЭ[v° hè èÚUr ’/€ääí“€X111ÈÍÍ•Ùþ322$¬INNbbb$ÊÒÓÓqéÒ%LŸ>¿üò‹´»ÈØKIÏÞ"Ynn\¾,y{5{{)+]ºH–ñY ¦ &ÅÅÅÈ/?”­ 3ÍâVMön(›ô5ÅÊ ¸t (3¼«ƒÆÈø2XSÅ“¡¾%6½x£¯^ɼnêêØû¿A kÃÃ×.]ªP~üøqôíÛIII˜5kΞ=‹ÂÂBØÚÚbÏž=èܹs­öáÂxzzâêÕ«000Êׯ_ààà £4—Ý@.¥±¦+?HKRWú³ôwyyñ¤–mÚˆúú²™[+00NNNÈÌl…€§O%×ÛØgÎ|S^ý%''7è8@¥zõþw™ ¦ ·Dt^®gf6v7*ðõõEf™~}ñÅHHH@×®]ˆçìÊÏÏÇéÓ§¡££ 0‘‘‘PUU­qÿÈÍÍÅÁƒ±xñbâü¶oߎE‹ÉæI±·NI ‘QySÙÏòeu™oVA00 ŠJeËê:Øð˜1cðààêúï â¥\\Äž–¤›ÕCcä|¨©âˆUËÌÌLø}ïÞ½¸uënÞ¼ uuu\¿~!!!¸pá\\\ˆœŒŒpìØ1L:µÆý+**â“O>Á¾}û„èÂ… HNNÆäÉ“eòœXÃHO~ü±vLf¦xÀ†PT$PÊ)婨††£òÁ’††¸þ?ÿˆžË'ÈŽ ;&Î%aï¦òÐÇâ [E¥qúäƒ V+ׯ_ǼyóàïïŽÿËÞ ‡²²2ììì„zmÚ´¹¹9ÂÃÃk½ï™3gbíÚµ¸téú÷ï}ûöá£>âüÞq‘‘â‰>ßUyyÀ³gâGu45ÅP\\ ²²´PökuÒ$àÀÉ»ˆXý5ô\`¥Z¶ŒçÏÅËÅÅ@Xað®ãå[bAÛ¶øH__æíhÊË×y›ÄÄDŒ=Ë—/Ç2ÃÕ***¢¸¸………P)ó¯PNNk½ <{öìA×®]ñ믿6øXìݦ©)þ#Õ²¥8Ǧìï99â¤ä¸8ñÏW¯Ä—ܤåõkq°‡(JÚ IDATp ¾ÞË Ø±C6ùFÍUcåâ³@¥ ¾ Æл ·„‰Š LÞÂó©EEE7núôéƒ+VH¬ëÙ³'ŠŠŠpéÒ% :ððáCÄÆÆJÌ^³fÍÂøñãÑ¡CtíÚUb6uÖô)(ˆ'¬*ˆ©îwmmq¢sm â`¨ôQ•ý=-­®ÏbŒðÛ_ˆg{gÒÕX9@€8úôé—9èÝÇ«ÖâÅ‹qãÆ l޼ǎÊûõë‡=zÀÍÍ óæÍÃóçÏ¡­­õë×£k×®=ztÚ2dtuu±fÍìØ±£Æú—/_FVV’““!''‡€€Ö9ðb²£­ V»@FS³áú¥  ¾ ½¦[Ñssk’âãÅg˜Êúê+ñlï¬iáD覇 &A$ÁÁÁ-[¶ ´ÐÚÚGŽ‘¨§££SSSøùùaÅŠظq#òòòàää„ 6@¡š¤cccôéÓG¢L^^+V¬À‰'0qâD‰uzzz°··—(Û³gbbb ®®Žììl|ûí·pvvæè-bn.yëð»FUèÐAü¨Nzº8zø0êêZ4ˆ¿Ve¥±r€€Šн{âÄ}‘¨Á»Â¤DDÔP÷^¼{|}}%~VÆÛÛ¦¦¦ðöön˜N1öÀ¦M›P~ˆ&ÌßßNNN¦Ã`Òßh9@€xh„²—F=:uj”®ÈLBB¬¬¬šÅ ³œžÇcRPÙ\`Lº3¨xèöíÆé“€cŒ±Zà< ¦… Æ“‚²s1Ùh¬¹ÀJõê%¹Ìл Æ“‚ÀÀ@ddd4v7š´äääF#ŒÏ5-1Ƙpì5vP—.’Sš¼|)X“½›8bŒ1ÆjAAøß<Ð> ôîâˆ1Ƥ€s€d¯±s€¾¬)áˆI())ÁÔ©S!³6Μ9ƒÿüç?µ® //¯J×½xñÏjš­²œ«W¯búôéˆŽŽ–(OKKÃÅ‹qìØ1„„„ ¹Ü´Þ6lÀ¡C‡êÔVeˆ˜>}:&Mš„´´4# ˜û ‡ßå‘ßqœ${p"tSÂÐ[‚Š%y%²T? dII <ˆ—/_Êì¹Þ¿§Nªuý˜˜üøãÊóòò0a¬[·®ÖûJMMŸqãСC˜šš å›6m‚±±1† †åË—ÃÉÉ ­[·Æ”)S„:þù'®^½Z붪²wï^L˜0úúúèÔ©ˆëׯ‡§§'LLL`jjŠììl²ºã Ùkì €¡›³ý-µ( /6¿y;-¬[ ÷µÞ2oGÖFމ . '']Ë_”¯ÆÚµk¡««‹¥K— eçÏŸÇ‚ °uëVÌœ9ÊÊÊÈÏÏÇùóçñË/¿H½ï¿üò æÍ›‡åË—K”­\¹žžžPáìTm¸ººâ“O>Á‚ ¤¨1Æ*êÙS<ýEé ‰çƒSSkÜ~±ºãˆUkÆ  «P¾lÙ2˜››ƒˆ°mÛ6üöÛoÂ\`Ë–-ƒJ-g¶¿ví¾ÿþ{lß¾ªªªBùï¿ÿŽ*Ïîìܹ˜:uj­ŸKff&vìØŸ~ú òe¦¿rå ´µµ1gΈþ7±²²2†ŽáÇWØÏO?ý???ÈÉÉaÆŒ>|¸°ÝÂ… áææ†þýû õ¿üòKôîÝÇÇW_}…   äååáÑ£Gèׯž={†[·nAWWW®\Á€ðÁTh7==kÖ¬Áµk× ¤¤„áÇcþüù“û÷D®Ú´iƒË—/Wº&;)))ÐÒÒªv<öfs.°Ršš€™%^.)ÂÂkëFë«'¾ƪeff+++áñôéSœ>}€O?ý>>>èÛ·/ÜÜܰÿÿÙ;ïð(ª¶ß[Ò{ $’@Hè% ]”–E׆ú*ŠRTøh* ,4C/J ( H‹ÔÐIƒR6eË÷Ç&›ÝMÛ„Ýìl˜ûºreçÌÌ™'›dç7çüÎóüø#ýúõ3¹ÿæÍ›³~ýz6oÞlÐ>kÖ,ƒ»1õë×§Q£F¢©2öíÛ‡““ 0hê©§ÈÈÈ`þüùä—ö6"<<œï¾ûŽÞ½{Â!C8yò¤nÿƹvíšÁ9;vìàܹs4mÚ¹\Ž¿¿?aaaÑ¢E 6lHXX Ë(S~ÿþ}Ú·oÏåË—yóÍ7éß¿?‹-â³Ï>38®^½z<ùä“lß¾Ýä÷EÄ<ˆ Ë#ˆÓ`µñQE Hì$H,¯G«z!C†è^?~œiÓ¦Aƒ ¸ví+W®dãÆ 6 €~ýúѪU+vîÜÉÀ+íßÓӓ矞U«V1zôhΟ?ÏéÓ§Ù°aC•b­Œ“'OÒ¹sgììì Ú}ôQÞ~ûmf͚ŧŸ~JóæÍ騱#¯¿þ:]»v58vàÀlܸQ·]ìgêܹ³I1Œ5ŠwÞy‡§Ÿ~ÚÀ_4~üx ¤gÆS` ,ÀÞÞž;wêF›\\\˜>}z)Ô½{wöïßoR<"æcøðáÖ¡Ö#hпýV²-®³MD$š,hB“M¬F¹Ü»waÆ1}útÝÏ©S§Ðh4<þøãºãZ¶l‰¯¯/'Ož4IL˜0®]»rõêUš6mÊêÕ«yúé§iܸ±Y†äädËÜ·xñb¦NÊ–-[8wîýõ?þø#ï¾û._ýµî8c“k`` EWÌsâÄ $ /¼ð‚®-==ôôtÒÒÒ ¦ŠJÎ""ÖBªˆH¤R >|8]ºt10îfeeáè舻»»Áñ>>>dee™Ü—.] cÕªUÌž=›Ÿ~ú‰Õ«W›-þb”J%...åî÷óócÒ¤Iºí3f°páBfÍš¥›ò3¦,¿‡¦ØiF²²²hÑ¢Ï?ÿ¼Aû¤I“p6r_ÚÙÙQXXhöD*FôY!x€ ´:^ëª`Ö^D€ˆ¿.‘Jyï½÷HMM%<<\7ýЬY3 …æÞ½{\¼x‘¦M›Vé'N$<<œM›6áèèhòèQUðññ!66¶T{Ym õ') 222L¾†··7÷ï߯vŒåÑ¢E 233û,M›6eÒ¤I4kÖŒß~û­Ìc5 áááÌž=›ÐÐPž|òI^xáÖ®][ÃQ ‹œœ5jıcǪu¾R©dæÌ™U’*•Šýû÷3kÖ,&MšÄÌ™3¹páB¥ç-X° J%76nÜH×®]ËÜ·wïÞR¢EDª‚è²ù$AAAlذٳg³qãF† VîyéééUÊVšM\\œA[ZZgΜá•W^a̘1ôêÕËäþDDô=@–Gh Ðú€ô+æˆÈv¨U#@úü÷¿ÿ¥}ûöôíÛ·ÌýÉÉÉ8;;ëÚÜÜÜtíŬY³†™3g|ésêÔ)³Ä›–¶—Û·?³ø×Ý»«ª—B¡(U<´"ˆŽŽ.·.Wnnn©¶¶mÛrýúuvíÚÅòå˹|ù2íÛ·gþüùUе:ôë×ñãÇ jX½6aüÿ"n‹ÛµmÛÐ=Ó@ !>S¶wîÜÉÌ™3yüñÇY¸pa¥…¥k µ²ØÔ©SÙ¶m‡¢^½zeC£FP(8::°råJÖ®]Ë_ýÔl-°ë×'¿øú0w÷.tèPþHSvv6nnn̘1ƒ;w…›› ,àµ×^àñÇçöíÛ¥Î=~ü8~~~´mÛ–¹sç2pà@îÞ½K·nÝxóÍ7 çâÅ‹x{{³xñb^Ò/¤cÄo¼ÁÞ½{¹qãF¹ÇL›6£Grøða"""˜:u*§NÂÍÍMwÌÇÌ7øõ×_ùá‡øä“OHHH(ÕW·nÝèÕ«Wˆ®‡±˜ˆ%R-°b “::jжZF¬f£¨ÕjÞyçöíÛÇŸþY®ø¨W¯2™Ì`J$66– ÔD¨‚gãÆ¼÷Þ{=z”0aÂ._¾ ÀŠ+غu+[·n%""WWWš6mª{¿cccÉÉÉ´ž ˜˜~þùgf̘Á_ýEÏž=;v,111e^[£ÑðÇð裚oß¾}ILLä×_Õµðí·ßÒ¿ÿê¾ ""&#z€,=@~~àë[²—ÑÑÖ‹GÄtjÍ£ŠJ¥bذa¨Õj:„««k©cÞxã zõêÅÈ‘#qrrbÀ€¬^½šùóç“››ËæÍ›™={¶¢oï~Èåž¿ŽƒCC“Ž[ºt)}úô K—.ìÞ½›Ÿþ™Ï?ÿœ-Z莛6m¹¹¹¬_¿©´|=½zõj:tè@ûöíñðð`óæÍ¼ÿþû¥Ž2e ÷îÝcÆŒ&ÿ\...Œ=šU«Vñú믰uëV4 #FŒ0¹‘ê"z€,=@ 5BëvFEž½TD Ô”••ŶmÛ ¦@š5kFt‘ß¶m^^^Œ9€E‹1`À6lØ@VVÆ cÈ!5Û´iC÷îÝY¹r%|ð`É’%UîGD¤:ˆ Ë#Dˆ%1l•Zåª.¢ø©€ÔÔTΞ=Ë#<@ff&C† áÕW_­ÐÈ\ÑÑÑÄÆÆÒ¹sg@;}ùÖ[o±páBöíÛW-ñSÌĉùùçŸY¾|9={ö¤iÓ¦ÕîKD¤*ˆ Ë#DˆÈVUDÊä§Ÿ~B*•’ÀÒ¥K b̘1¼üòˤ¥¥ñÈ#°~ýzÝ9O>ù$uëÖ-³¿Å‹3lØ0®\¹ÂòåËéÒ¥ ƒ `îܹ,_¾œÿýïDEE¥÷éѧOBCCMŽ{ĈLž<™… òË/¿Tz|dd$ùùùdddpóæMöîÝK`` -[¶4ùš""P³ œ‹ÚE; R;){‰öµ½‰D׎¤ÆBª„ê Õf….ZûAJ ÄÇCCÓ,—"VB@"Èd2zõê…½½=“&M"==ž={¡óÒЪU+V­2Ì)ÔªU+êÖ­K=J­ÀÓh4|òÉ'2`ÀæÎ‹D¢ýt®[·.=ö'Nœ(•A:$$¤\¬[mVŒ££#Ó¦MãÀ¥ü\þþþtëÖÍ méÒ¥¤¦¦âëëKRRóçÏç¹çžˆ ɈÌàæÔ›Ü?qߤã%r#QTüÚ¾H8é¿¶—T,¨Š^—Õ&s•áÜ—Ö.ØÕ±³ð» <¤Rm]°¿ÿ.i‹ŠªšRe©(L+D™®Ô}W¦é½ÎPâñ¨u‡ÖEæb£kìF­Ìd.j2Pm%..ŽÀÀ@NŸ>­[&Rûó™—¬3YÜšv‹´ß…Ÿ°ÓÞß×6®¸´vÁ¥ .­]pnéŒÌùÁoÚBõü÷¿°r…7”¸£äÝ× ùÏsFb&½eš²Dä·¥+Ñ(M»Ë\eø óÁwŒ/^Ox™}”ïaÊ$މˆˆˆ˜ÈÈHžxâ êÔ©c¶>×ÜúøI“ÀFU îv7´?JÄšD*Á1Ø—6.âÈ)Ô ‰Ìô;xJJ §N²ø4˜*WEaR!…©…e =SüzD²’‘¨J:Y çWZ ¶l‰á‰$†'âà€ßh?|ÇøâÜ̹ò“E ˆEqssãwÞ©0)¥ˆHmÀœ ‚»Üžu›»«î–=2 —–.hT4…Ôj4…4Ô…j4ÚvZªI£Ö ¸®@q]AÊ–]»ÔAª:+)*Geöó  e¦’¤B îè¾ ïm'é½¾W€*[Uy‡FXÃn•—OÌÜbæÆàÞÅß1¾Ô{¾vÞß4duˆEñôôdÑ¢EÖCDÄ&Pf(‰ý2–„Å ¨r˾ {=åEð¼`Ü©|åje©¸ýA÷¦’s!‡Ü‹¹åÆ]ê|5ÙQÙdGe´Ë=ä%ShEâÈ¥µ‹á]…©ZÑR¦°I29ê|µÉqY©ƒ¹·;/;íwo;ä^r]›ò¾’¤õIäÇç—yþý÷¹â>7Þ½AuðãKþuØÕ2'¼ˆˆˆˆxZ¡&~I<±ócQ¦+Ë<Æ­“Áó‚ñêãer¿™‰L‚Ô±f2žhÔònæ‘s>‡ìóÙä\È!ç|Šk 4*ÓG£”™J2f’yÔ0­€Ô_вŽÇTG “ MöÍÔ$d## ;î#§E';|šÈ‘{•a£×&uªüwüE03H\›HJD ªœÒbS] &9"™äˆdìêÚQï…zøñí£˜êÅQ‰ˆˆˆ˜êx€4J ‰?&rû³Ûä'”ýdïÜÌ™Æsã3ÌÇ\¡Z ‰T‚SˆN!NÔR’C§&÷r®VÏÑ £ò~æòH¾›Ì•»WèNws‡n€D.ÁÎÇ»ºve —rFi^~CÆ/¿–Œ¸üßhxë-3Æ%•àõ¤^Oz¡Z®"ù·dî­½GÆ¡Œ2§; S IX’@Â’\Zºà;ÆßÿøâРì)Ƈ Q‰ˆˆˆ˜*y€4ü[2·¦ß"÷jn™‡84p ÑÌFøó«’QXˆH¥¸¶wŵ½aFeº²”(ʹƒ2³ìQ°:Ô©¶ø‘ÚK±«g‡½¯=ö¾öØù–¼6Þ¶«cW-SOX{ø¥¤³E"Ê\eø½ì‡ßË~äÇåsï§{$®M$7ºì¿§œK9Üüè&·¦Ý³'~cü´KêͰ:ÏVˆˆˆH ’¾?›So’u*«Ìýr/9ASƒhðf“¦El¹—Ïžžxö4,«FÓF¹Ñ¹¨ }îYëgV†(€D P*•üôÓOôíÛÿ½¶J¥"%%…:uê˜ä£8sæ IIIôëgZÙëׯsæÌ]1Ü%&&†ÈÈHžyæ|}} öåååqÿþ}|||t ‹ÙµkþþþfÉ‹”‘‘Á–-[P«Õ¼üòËÈårRRRزe ööö<ûì³lß¾çŸÞ¤¢°™™™lݺ•Î;Ó¢E‹Žïa¢2PÖ©,nN½Iúþô2÷Ëœe4x§"÷|¸?šq t¤Î€’éDRCÆ… 23ñ ñÅÞ×™›°F/ŒÐÅ‹ TBM–‡sïìŽ{gwš|Ý„´]i$®M$uW*šÂÒSdªl‰kI\“ˆc #¾£}‘õÖ{jIj÷ã…H•ÉËËcܸq\ºt©ÚçK$/^lò9—.]¢G8::âç燓“£F"=½ìE1›7ofÞ¼y&_çÏ?ÿäÝwß-sßСCùøãMî+??ŸÁƒsøðañsàÀ:uê„‹‹ ¾¾¾8::Ò±cGV¯^­;föìÙlÚ´Éäk•Ço¿ýFÆ Y½z5áááäåå±jÕ*‚‚‚øùçŸY½z5±±±Œ7ÎäU\¾|™~ýú‘–&ü¤{B¢¼Z`¹Ws¹8ò"§;Ÿ.SüHäêO¬O—ë]žüЋŸòÈ%(ê)8Wp§'Á‰hРd;?._¶N,R{)u‡Ô¥õ–Öt¿ÛÐ%¡¸u*ß›GÌœn>z“Îùk0Rë!þ§ „©S§²bÅ ‹_§S§NìÛ·ÏbýÛÛÛó믿Vit#!!6mÚðÍ7ßDdd$¯¾ú*“'O&<<Üb±LŸ>ÇsôèÑ*NýꫯÈÈÈ`ùò庶ãÇÓ¿^~ùe-ZD@@ÑÑÑl߾͛7óꫯš5öððp¦L™Â'Ÿ|¢kûá‡X¸p!“&M0¨«f*sçÎåàÁƒL›6o¿ýÖlñšuže¦Õ}Êû%ßËjSÝW¡ÌT"‘Kphà€CCÝwûö84p0kIcP~B>·?»Mâ‰åæò©7²?oŒSˆ“Ùâ¨Íµ˜>aaP²}ö,´ic½xìêØÑàÍ4x³¹—sI\›È½Ÿî•»¤ÞCíQÃZQ …BQ#•¤³²Êö£ÑhرcÇŽ£^½zŒ7OOí<}DD÷ï—®E4räHœQ«ÕØÛÛ““æM›0`þù''Ož¤Aƒ¼òÊ+¸¸¸ðÔSOT€5jûöíãàÁƒ&ÿ\çÏŸçÂ… ¼ð íÿþû/7oÞ,U¬˜víÚZ¥´ï999,\¸%K–àäTrãÚ¼y3uëÖeùòåºiÀÀ@ž~úi²³³Kõsùòe¶mÛ†‹‹ £GÖ½¿?ÿü3=zô ((H×AË–-iÞ¼9[¶láÀ4kÖŒ5kÖмys®_¿ÎñãÇéÛ·/kÖ¬¡]»v¥¦ß@û»Ýºu+ÿüó... 4ˆ6zŸÐR©”¯¿þš>}úðé§Ÿše*T­Pˆ’²„JyF¿­¬aüAî)/[o7pÀ®nÕ’Ê)Ó•ÄÎ%~Iúè#@;RѺuk~ùåÆŽk0ísáºuëÆêÕ«ñò*ÿCýÃ?ÔÝHÿþûoºwïÎŽ;}z)3yß¾}9tèPµ:_MaFéU(¶†:Oâ¦ÅME¹ÇH¤ËV†;î¥ö;7w¦ñç¶‘ËGÈÔT-°ÁV>2W~cüðãGü™x®?}ÝÚ!Õ¢óæÍ«°ê¼¹05Km§Nt¯[´hAóæÍùçŸ;v¬®=##ƒ!C†0aÂFUa;—˜êºu놯¯/'Ož,%€îܹC¿~ýxúé§yíµ×Lе˜‰'òÎ;ïðõ×_ãììÌêÕ«yì±ÇJ‚<(‰‰‰4jÔ¨T»»»;—.]bÓ¦MlÚ´‰]»v±xñbüýýY¶l™ÁÏÚ¡CÜݵ7J™LF›6m8zô¨É¨ºìÞ½›æÍ›óÃ?èÚòòò¸pájµ©´d]DPPçÏŸ·h|X·½uëVîÝ»÷`£õ)­[·Ž3gÎèÚ X·n]©c¯\¹Bhhhµ¯%uÔféªø1™³ §P'<ŸðÄ÷?¾~Hè’PZG´æ‘¡[B7zæ÷$çÛšG5§ñœÆ¢ø±)))ÑWÅ}@#â©H™ 6ŒŽ;rçÎnܸÁ¬Y³t¹}^ýu$ /¾ø¢Á9¿üòK¹ùúöíK‹-ˆ%;;›ï¾ûŽÀ¢1á-[¶‘‘ÁºuëJ݈—/_®^¦Ð¢E zöìÉ_ýEDDD¥Ç8””.^¼Hll,‡bÈ!L™2¥ÜsúöíˬY³HHH ^Ö³€€f̘ÁÔ©SiÒ¤ ‰„«W¯Ò¦M6nÜhòÏZCóóÏ?ÏÑ£GÉÌÌÄß߇/`8~üxΞ=K·nÝèСr¹œ¨¨(‚ƒƒ=z´î8…BÁîÝ»Y»ví_óa@"—ð„*?Pä°ˆÈVˆNNNDFF‘#GHOO§Gyb~ûí7”ÊÒ¾ˆ&Mš°sçNš5kf°oÏž=¤§§SXXH÷îÝiذ¡nߤI“8p`™ñTd`~íµ×Ê,kNbb¢.žbú÷ï_ª¼ÃôéÓÉÏ7LV¿~ýr¯ Ð¥KZµjŲe˘;w®®ýóÏ?gÊ”)üóÏ?ÄÇÇ£ÑhhÚ´)]»v5˜[¾|y)ÑŒ3 ¦ DBB[·n%00Þ½{sòäItÇüñÇ´jÕJ·]ü»Óÿ]…„„©›2H$,_¾œ>ø€Ó§O“™™IÓ¦MéÞݰÀäÚµkquu5¹ÌˆˆˆH ­[kË_LÞ¾ àéYái"5ŒD£Ñ3A‡(^•UÑê¬É“'Ó¨Q#&Ož\3AÙqqqrúôi³Ô¾ ;vì`øðáüûï¿4oÞÜÚᘕ””š7oÎÌ™3yóÍ7«ÕÇÞ½{Y´h{÷î5stÂ¥²Z`"Ž­x€Ú¶ýE”‡A¯^V Çd «RrX[Ŷ'çED¬Ä³Ï>ËèÑ£ J%P*ÆèÒ%((°N,"e#XtáÂÝPçÊ•+™3g'NœàèÑ£5’1WDDD¤*ˆ ËcK ooЯ˜SX¨A"ÂA°ÈÑÑ‘üü|òòòøçŸtËt5A%r!0|øð*%»©:¶ähßÞp[œ‚@ƒæÝwßÕåniݺ5 ÄÇÇãççgíðDDDDDD*Dô Á  ²lÙ2žzê)Ö¯_@TTóçÏÇÎÎÎÊщˆˆˆ"z€,-y€ ´W‚ A-ƒÏÈÈàØ±côíÛ™LÆ!C ö0ÀJ‘‰ˆˆˆTLdd$O<ñ„8 fARRR8uê”ÍLƒ  ÿµN¦RX_|áŠZmZG[GP૯¾b„ Œ3†qãÆbí*%++ë¡Èš)"b*éééÖ¡ÆóY[ójÍÐŃV™™pë4nlݸÊãðaX´Èw÷‡c°APÈÓÓ“pëÖ-~üñGúôéC£Fxå•W1bÎÎÎÖ±îîî,[¶ŒeË–Y;£Ñ@~~饰R)¸ºV½?užM^’I È\d‚™ØîÝ»·µC±:íÚAddÉvT”pÐÖ­ÖŽ ft-0µZÍþýûùá‡8xð ƒæ•W^¡k×®5r}Sj‰<8‰‰‰µÚØ®VÊðñÇÚ‚ˆÆ|ø!|ñEÕúÌ<–ÉÙgAï¿7x~0SË<¾¶¿ÇB@¬fyl©X1ï¿_]²=c|ö™õ⩈€ˆw÷)dfVñCÉȳbÙH¥Rž~úiÖ¯_Ott4mÚ´aÒ¤I´jÕŠ~øÁÚቘ‰­µø±ãøqèØÞ|³´ø‘Jáõ×ᣪ֧F©áÚ¤kâÇ¥¥ ï”{Nm~…‚˜ÈòØR ble%Ø©SZñó0!h¤··7o½õgÏžeݺu( k‡$b&&NœhíÌNJ ŒÝ»—½ò£cGøûoøî;ðòªZßñÿOö¹lƒ¶Ð¡Hì$åžSßc¡!æ²<¶æÛY ö0># V]¹r…Å‹Ú ÕS§Nåý÷ßçÞ½{tèÐA¬T-"HÔjøö[hÚV¯Öú~ôñòÒN‡8;W½ÿü„|nzÛ ÍoŒž==«´ˆˆˆÅhÑJ¶ãâJLÑBB@âÇ$%%€+Vðûï¿#—Ë=z´•#17µeÝ?ÿhEͤI`¼J"W_…«WaâDíôWu¸>ù:ªì’R0r/9M6©ô¼Úò 1å±µ<@r9´jeØ&´i°ë×áâEýµµB©Q+€bbbèÑ£›7ofúôé|ñÅ$$$““cåèD̉­ûSRSµ^ž®]áôéÒûÛ·‡cÇ`Õ*¨ûé5Òö¦‘¼9Ù -x^0v>•'µõ÷Ø=@–Ç=@ |ñǃTúp<0 VÕ«WèèhîÝ»GTT‹æ îß¿Ïýû÷­ˆ9±UŠZ ߯îZ¹²ôt—§',] 'OjÅÑ]+O͵7¯´¹wvÇÿ5“ηÕ÷Ø–=@–Ç=@ üš`ÆH&‹µN 5Œ`Ðøñãùä“OhÞ¼9Ç' €S§NQPP .ç±:§NiEÍ„ ¥çó%xùe¸rÞxd²¿^ì¼X7JŒÿ™„¦ß6E"-ßø,""" „<tïžvAF1 ÈdqÖ ¨¬jÓ¦ —.]b÷îݬY³™LÆ–-[HÄýÚ„-ùSÒÒ´ž.]´#;Æ´kýkÖ@½z湦⚂Ø/ ŸÈ¼Ñ×ö¦gO´¥÷ØV=@–Ç=@ ý\пm]¾¬MŠ*¶o׎fÓ®]!I®õªA+€8À¿ÿþKëÖ­‘=B·oßžîÝ»[92sc þFëáiÖL»t]mäôð€Å‹µ G5ﵯ¾qu~Éíýíi4»Q•ú°…÷ØÖ=@–ÇV=@nn\²­TÂ… Ö‹Gã†~ýò¬ˆ¬ÊÊÊbÞ¼yÔ¯_Ÿnݺ1}út8@^ÞÃóËyXº?åÌèÖ ^{M›ßǘÿü¢£áí·Í3Ý¥OÒÆ$Ò÷.) ù:¹{Õ² ý=® ˆ Ëc« æ4Xv68`ØöÌ3šª+€Ì‘#GHMMå믿ÆÙÙ™çŸOO1߉HÍžÿý/tê¤ÍÛcLëÖÚâëÖ%liª,7Þ½aÐæõ¤õž7ÓÜšˆˆH!D´gáT\h(4múðLã ºhB¡`Ïž=üöÛoDFFÒ§OFŽiÒ¹‰‰‰dggWZM>..Ž„„ƒ6OOOš7o^í¸Eª†ÐêTi4ZÏ”)œ\z¿››¶–Ï[ois|XŠ[3n‘§äÓIê %tYhµúÚ{\kY[¬VŒW‚O l8¬…`G€V®\‰kÖ¬aÈ!\¿~õë×3tèÐ ÏS*•ܸqƒ±cDztéÒJ¯³lÙ2Èĉu_K–,1×!bBò§DEi=<¯¼R¶øyáíê®wßµ¬øÉŽÊ&a‰¡0ø0ç¦ÎÕêOHïqm¥¦=@©……5v-¡`« (=ôï¿¥SgÔ$……°k—aÛÃ&€û¨ÒµkWÆŒÃþýû‰ˆˆ@*•òÌ3ÏàääTáyû÷ïgþüù\¿~ÝäQœáÇóí·ßš#l‘j JF|ò‰¶L…JUzË–Úœ>OÿüsÔzkœ?ΡC‡ ¾ô·|[?GMM^ÿß¡mÛCŒ«ÿÔS²äHøñÇC|ðØÙÕL|û·íçæG7uÛQDá3Ìïg¼¨ÿâ÷X¿ïÚºššÊ£%5U9?O­æ›;øøÖ-º9ƒ÷‘#<»nßÄÇs.; ”ºcþòÇÜÒ[+¤÷ÃÜÛj5÷ìáJb")……¤+•ì:p€l• …ZM¾ZÍÈH4&ögí:u ··m³N< lÛ%ŸwWiÒä‹-âØ±c÷ÀÚŒ`§À>ùä¾ùæ{ì1ž|òIÞ~ûmÚµkgv4iÒ$Þxã ìíí9wö2™Œ©S§šõ:"å³uëÖ¢ÉÌ„3`Ù²²§»š7‡%KàÉ'µÃÄ5Éïۖ:I YTý1òB†Ñ—/óÛêÕ¸ŠäÒ%f62dr¶çYôa›,lBÀûÕê/¥°Á.pÔŠÉù$ ½Ù}É‹n<úm2Ð &ý6ƒsÊ8¯¼¾ ΑHp‘Jñwp ¾½=õõ¾»™;iS%h€sÙÙ,òñü™‘AVY ¼d \] pt$BÏ¡ï •r«kWü­ñG[Ch€Ð'¸¡PTz¬ÐI$:d¯”që’ Ú/'d¼4T¦LÆ_nr¹Á¶»L†‡\Žü.¦MƒyóJ¶GŒ€µ¯ {(²Ç vÈÏÏ´´4¾ÿþ{®]»†§§'Ï<ó O>ùäõ{ãÆ <<<¨[T–[­V#Õ›øŒÅ××÷®!",nÝ‚;á—_àøñ²6 ¾ùª§5RÃÕÿ^5?.m\høNÃjõw%7—çÏ[ý†¡A;Ò¡às–«Lf ˆüííK‰¤úöö8?€Pº¦Pp0=ééDfdRÅ•[­\\èíéI//zyzâ)—£Ôh9q‚˜¢©¯|µš¯ââXؤIµã:edXýo¹º¨42•J2‹Ë¤4+Ù§VÝ­zŸNR)r9r¹N¹Ëåx½v7:¦øuÄ9Pòwý°­þ*F°(>>žN:ѰaCzõêÅ­[·1bÿûßÿ˜>}z¹ç]¿~#GŽpéÒ%Ö¬YC¯^½hܸ1½zõbìØ±|þùçtîÜ™~ýúÂùóç ·Ùe–¶Š¹sÔ¨TÚâ~;wj¿*Ö ÕNwõík¶ËW‹øÅñäœÏ)i@ÓM‘È«þ¤w(#ƒ¡.®_—*- l0wŠ%ÉV©¸š›ËÕÜŠëyÈ储¬Ñ${{rÒÓÉrtäϬ,fdp0=¸*|jìèH//z{yÑÛÓß2Fuä ðƵkº¶oïÜaj` uª9b.tÖF(• Pàê鉓TŠ ­ÀÐ}ê¢×Â“ÜæC¡V£(( ±  z|h$#C’+gN ŒgµÂÉ¡°‚-̯P¬Z¸p!Ï>û,ßÿ½®íâÅ‹tèÐ÷ßGGÇ2ÏËÎÎæöíÛºša·oߦcÇŽºýï½÷azVüÏ>ûŒýû÷óûï¿ÀÙ³g ­^²9‘êaPFüþ»VðìÙ©©ïì¬þàëLw铟Ïí™· ÚüÇùãñ¨G•ûZ“˜ÈëW®Ph4âÒìÌv=8(ÔjòÔêÒߋ̤ÆûE¾S÷å«Õ(kÑM¨øéýr%BÉéÈmÛ‚»»É}ûÙÛëÄN//•ó¹fÌ+þþÌŽ‰ÑÝsT*þ/!Ï52ùÚ¶BŽJŦâ)¿ÌL¸r…ß&NäéJ½Cq¤ÖßÖ{­¦´ˆRi4¨5òÔj²U*²U*²Š¾•Õ®ßV TC±D®J4®J.åzU¦ÜVoäÙÖ¬hĈ >œQ£FéÚ4 6äÈ‘#ºK"z€„ÍÕ«°c‡Vô9¢}84…ÁƒaÑ"ª~Z³rqøE’+ñtØÕ±£ó•ÎØÕ1ýi^|rësÊX=ÎÏïš5ÃÎ ¦ä⛲ŒŒA;†7!¥‰m*0ì[ï˜âö”ÂBîp'?_÷=± À*Óržr9{zÒÛË‹>žž´tq©v_ ââøðFI©/¹œ˜nÝjÜÛdiÖ&&òrt´n»¡ƒ1]»"µ“}¡F£EJ%Ù*k6¨X°TN*pVѹ—Š‘/›&ªî+•ÜW©P[øo×}õj2×­³è5„€`G€yä–/_NŸ>}¨[·.J¥’uëÖ¡R©ÊK¤F),„¿þ*™ÚÒ›¨¹\›Ýyà@xöYmΡ¶'Í@üÏ®’øÉS«Í£ f`Np0SË>± ²yPÓ¦¹ÑÉ¥„‘þ÷»Ü+(@õ7g™Œô)=\]ÍvãžX¿>óbbtSéJ%+øÐŠ¿oKð£‘w´¯¯Íˆ;‰/¹¯¢Ôñƒ›Ã‚³%û³ïÀû›ÞŸí¨Xf‘*¡,~}_©$Óèõý¢cRóUÜLT‚‹:2UƒV½÷Þ{9rD—Ÿ'..;;;~ýõWÓ²ˆíS‘(%E;¥µs§vŠËÔMÞÞЯŸVôôë^^f ØL¨óÔ\{ÓPÅyt÷ÀÿU“ûH.,dÐùóü}ß0q¢£TJxóæŒ¬§-œ*Ö3DÔ³·§ž½=a®®å§ÒhH*,,W$ÝÉÏçnAIhîßÇÎÕ•.^^Z§']Ýݱ·Ðç•›LÆÛ òÙíÛº¶¯ããy»aCkÉgäí¼<gd”4(• qp°^@f ];mÂÁâ™±+W@¡€JŠè€nUXƒ*^û»ï@ç6khÿ¨’Í{ôÄ“JElj*Ÿê¸Õf+€ìííÙ¹s'QQQºU`;w¦ º¦/Ábìºp¡d”çï¿K>(*£eK­à8P›&Dè31scPÜ,YÙ"‘K]ªý„3˹¹ 8wÎ hoìÛZ·¦«ž¥¦s-Õd þööøÛÛóHÇ)5V¯_Oÿ®] (5ÁÛ ðU\ÙEËéï°úî]ÞhPÕ[£0 OL4ð‘=¢Rq÷ôim] ÅÅE›èêUí¶J¥ýÌëÔÉò×6Èþ¬”0üi;‚ G› ™gù`€`P1aaa:Órtt4 à†Þ¼·ˆí3nÜD~ÿ]ëçÙµ ôh+ÄÞzõ*=ÁÁ Ó¬ä^Í%îKÙo5Àµmù£úLOgØÅ‹dŸZ8;³«m[™iEñcYä ^x¡Æ¯ëmgÇÄúõY¨wÃú2.Ž×ë×·ŠçËœhÐ }&´mËsþ¦ •°°ÚÜŽ–@÷ïÃÁƒ†mëò÷b7NªV«™6míÛ·gܸqD Å8p€nݺUZ ^Ävˆ‰!C Ní4Õ²e•‹__7~ûM»Òë?àí·mGü¨rT$mHâÒó—Pç— m94p ñ,ÓŒý«ïޥ߹s¥ÄO//ŽuèPJüˆÔnÞ ÀA?—Y^?—•éÓÆ8œ‘a0ºé$•2ÒÇÇŠ™kÔÛ³ô'PBCµ£æ3‚Z½z56làã?&..Ž#Fðúë¯3uêT–,Y¸q㬢ˆ™˜=»xH6(ߟ¦5/¨}J²µ[UŽŠÔ©$oJ&uw*jEé9½E!È\+ž³ÓÓnÞd~ll©}¯úû³¢iÓrŸúEåIMMÅÃù¼f?VýííyÅÏwîè򾂮2ÆÆÌÂÆ¬1ýâビZMZZÞ6žÓÊȸøéÃ>ú@ä“O>aìØ±Ü¹s‡Ï>ûŒÝ»wÓ³gOë'b6nÝÒVaײ(™¢qú> IDATr‚>}J¦¶lqº_•£"uW‘èÙU¶è)Æ»Ÿ7>Ã+~²U¨ÕŒ¹|™ÍɆ+Æ$À¼à`¦T²òGôYžÈÈHžxâ êÔ©Sã×þ00•wïê–ö_ÉÍå·”FØèˆI¶JUêo}¬Ÿ)))œ:uŠçž{ÎJ‘™ctîœÖëh)ïzAìÞmØ&   ÌÌLƒ'ÕÐÐPÆ/ŠŸZÆçŸëçí™H:Úz4BïÞ¦¯ˆª\i»ÒHÚ˜DÚî4T¹•×yrnæLè’Šo&ðÜ… œ0Zéå$•²¶E †›p“Åå>|¸Õ®ÝÈÑ‘}}Y«7j27&ÆfÐæädrôê¤88ÐÇÓ©——Í‹ít~ñLeN޶0ª¥jÚFFj=@ÅøúB×®–¹–-!8¤V«9|ø0EKÏ;GFFëׯ×óüóÏ[+<3pó&¬]kØöÝwÚz\¶†NôlJ"m—‰¢§©3>#}¨7².m*N„w1'‡çÏs»Œ•^Û[·¦K²‹Ôn> dÞª©¨ìlv¥¦2À #RŠñô×??›žÎ+‹°0mjb¢¢,'€Œ§¿ž{Îr£M¶„àP“&Mسg{öì1hŸ?¾îµ(€l›9s ³6·l™ÈС¶ãOQåªHÛ¦ÞÚ™j’èq u¢ÞÈzøŒðÁµi+½ö¥§3ââÅ’"ŠE´tqaW›6&—MÑTXËTL gg†úøð›ÞÔÑÜØX›@7 þÔÏý¼\ô·[PP@vv¶Í{€ ´:{FŽ4ÿu4ضͰMœþÒ"8´lÙ2k‡ bAÊýéÑc+‰°§hÔ 5©»SIÞ¨õô¨rL=!NøŒÐŽô¸†™&zŠYy÷.ÿ½zµT¹†§¼¼ØÔªU¼ÉŠ ËcMP1Ó бÌLed𸧧Õbª*á÷îäþyÔÃТ9ñÚₚ3BŸ8wõ*λ¹i=–"@"µCï6+ê·ß óƬV¨IÝS$zvš(zšè‰žöU= ]é5åÆ ”‘ˆì5–7mZ­²¢ø±<ÖôÓÁÍ~ÞÞìMKӵ͉±TVz#—õëׯâjNOõë6žLÛlNÍš5‹3gÎTxÌVãߨˆMpã××ûôSa-k׉žâé­ìÊEccGÝô–Û#nÕ¾v®JÅèèh"ÊXéõE“&|Pí¾E¦ }ééœÌÊ¢“[õÿ6kŠCÄèùÝœe²Z“ûǘ¦MÁÙrsµÛ‰‰ZS´¯¯y¯c|»2ļýÛ2‚@'Nœàï¿ÿæ•W^¡nݺÖGÄŒ”5ú3x°õý)ê<5i{´«·L=u#=nüÆ’XPÀsçÏs2+Ë ÝI*å§-ú€7k¿ÇÖöó˜‡yxð—^á¼¹11liÝÚŠQ™Æús5Àкuq×{?k“H*…¶máøñ’¶¨(èÛ×|׈ŽÖÖ+ÆÎú÷7_ÿ¶ŽàÐ/¿üŠ+X¹r%Ï>û,ÿûßÿhذ¡µÃy@®_‡Ÿ~2l›9S;úc Š:OMÚÞ"ѳÃDѤ'z:™ïiúBNΟ'Öh¥—¯½=;Ú´1Ë“»è²âÝwß%<<œ¾}ûÒµkW¦M›F“&M¬žH51ý ƒAƒ´¯kêÆ\,z’7%“²#U– ¢'P+z|FøàÞÅüKÎOKcä¥KÜ7ZéÕÚÅ…mÚd¦²¢ø±]ÍÎ4ýo6‘¤G¦“ùg&ÊLeåçéáèHAu¨7²zXl¤'G¥bwZ›““Ù•šjP×Èg™ŒŸZ´`ˆ¸êQÄÌ(5BOœ0(¥ònÆ|bŨ ÑŽ7X°ºY3^ñ÷·^P5È‹/¯¿–l¯XjÙËÌ(,,i»t L±€%&&F¢‘(­c¬V½{÷Z;3qõ*üò‹a›ñèO…h çbN‰à9œIaZaåçéáPßÏ'R)ïðÁº¶¥ |`õ•†÷•J"*Éý£OmË¥ÐùóÚÇr4`¥äçƒQ-qQ•ƒ(€D,¬Y†£?<Ï>kxLò¦d.޼HºD’IýÚyÛáÑ˯'¼ð|Ü—Ö.óñè“ZXÈÖ”6''s =B|hÁNN /=Ö.C ŠË#¤<@ÆL¬_Ÿy±±¤Í‹d(•,¿s§”Iº¦Ù˜œlû'ÈÑ‘'ŒrÿèSÛòÔ«þþ%K ídu§¬ý„ò~~еëƒÇYˆÙ¹r¥òѵBÍÿÝ 2äržw6ÄIj½ÅÀÆÓ_/ûùÕúÜ?efX±=*ªúÈxúë¹ç„UoQHˆHÄìÌšU’× cG8Ð𘸯âÈ‹ÕÓHÃ휾ÌM†çc%×ö®H¤5÷ß›XP@D‘èù33³T’²hîìÌpFøøÐÖµêàkÑdy„ê*æí† YGvшKRA«îÞå-+%‡¹¦PpT/m€x¹’J µÑÚÜhúÓVQQð UïG­†íÛ ÛÄé¯òæªˆÍ ë×¶þäßÉ'v~¬nûGxeÈ+N Äí7$òš}\IÈÏ'¢hzëHf&jDOkÝHë+#z€,P=@ÅxÉåLª_Ÿqqº¶±±L¬_;+ „çþñô¬4Kumôù2B?®­*_Œ›ôî]ý¸j;¢1+Æ£?:Á€†ÇÜšzË IáPÏ¡4û¾vuknYn\~>›‹FzþÎÌÄ”ÌRí\]u¢§¹9RµÖ ¢ø±: fü/þÄàáñ`1Õv„ý¨"b3{ºtÑÎ?£V¨¹1ÅpÙ»÷ÓÞÔX‡‰˜æOÉS«9Ÿ“£Õ9“Í…œòMHDhŒT"¡»»;#||êãCÃZ>Q.z€,-x€Šù(0u÷îé ÿç²³Ù™šÊÀ0poHJ2(ÓØÑ‘^žž&[[=@ ] ¦Ïƒ qú«rD$òÀ\º7¶þÄ.ˆ%?®dÎ_"—Ðä›&åö™£Rñov6§‹¦±Îdgs)'Ǥ\<åQ×ÎŽŽnn ¬S‡¡>>øÛÛW»/[¦¹³3CëÖe³ÞHÌœ˜˜@ÆÓ_cÒÜ?Æ<ÈPBœ:U²-‘À A扫6# ‘¦¬ÑŸ~ýJ¶óò‰û"ÎàœúêãÒR»|üj|Þô—‡aÜsË޿ݵ¿ Ãål^žù-âË ÆÓÓD섺Õ\ü GEˆÛãH ¦FG+Ð/7o²7+‹;ýü¬~/$ñir²"ÏRós ÎìÙT"€@ž«HedÀï¿+ó„² !€UæðaSã±÷çÜXå²wïÖÞ„ÿ7œ÷aðQÍêÕÓO_uðõ¥½¯/2½ 8Ýýý¹Ûߟ]ïÅÙ—/óMëÖV¿×O7orÕ8öO .½wÚ¶…Ï?/M[âúî;(Þã€ØØªï#V×ß&‚*c<úc÷çÆ7ÈÜ­9ÍÞoÆé‚<¦^¼Xš™žN£ˆºûûëGwÚùøà'ÄŽÕ Ûãh ¦FGóÀ‘#úô·©©ÍÉ¡•·x16??Н± qfTÍ-VUHP%’w†Žþèòt\xý‚¢<ä‘üz0ìÔ)}(~ï¿þbOûö¬kÙ’± Ò# @ˆ+#âÙG‰dL¿  ÚûúêÓðÎ¥KV½GFQ›SSy•þ玦èØ1(**»~Aüðƒ2O ËHP%ŒGŒãþîö vWÓt~Sæ]¹Â^ƒU M˜ LË6Fx€lÏÀÆmÌ”Fé/SR8——gµç_ã†âGOŒ§'=«`¶vvPHDF–¦óóå ¦ËâçŸK·Ïhкt±]ûœ !€•æï¿Ëý1Þí òåHÎGèx+!A‘? 4”'¢Ò ‚šå±Å¿ZIb®£Cl4ýõŒˆýS&• ˆh<°ûÈ#r e$¨4Ó§+Ó]º(ãþïöîêFä´F¶=iii•7_Q‹Q«TL6ú49™DÓrU9•›«õ­lìC I7XµæŒXêÒé`Ëež˜þªB *ÅÁƒ¦o:CAdvÙûŒÆ¼›q•¿oÝRä/‹%ÔÍMøSjÑǶÇQ=@%11y u:æ_¹RΖal~¾'  Êûì9»,@þ)ï_‚¯/ôîm»v9#B *…ñèO×®pÿý¥iãeï>·ûpcˆ/3L•C JM úØö8²ÀU¥b¢Ñ(ÐÊë×I1\c]I´’ÄgÕŒýcˆ³{€Àrdü›æ@X)+‡@‹9p¾ýV™g(ˆÌ-{^ЄgΞFcà˜nàîÎ’æÍmÙT@P†7h X«Õ²01±ÊÏ·ýæM®L£ù¸¸0¨ø‡Àv Ο/M»»+c° ,C Eìß/G5ÄpôÇܲ÷Üé ˜sù¸âš§4à‘EžØ§Êöˆ>¶=6ß lëV˜9öîµÍóób½z¼³~=éű2ÝÜXš—ÇÃÅpÓ×—o¾úJGÅ ûäyÇO#¡DƒÍß8ó^`†´m«@‡ÁצGzõÿši›3á´hß¾}¤§§ÓÏpyRlÙ²…ü‘ÀÀ@ž~úibcck …Ž…ñèÏÝwCŸ>ò¹¹eï ^‰à±‚‹L}Ezx°¨Y3“ç_̶Gô±í±É^`:|ý5ÌšeYX`+à“—Ç+_}Eü°aú¼…2fãF¼*±*ì‹{ï¥À@ü4¹~{æÎU+A¥‚à`¥(2úˆ9sæ0yòd.]ºD·nÝ8pà1115Ó``ß>ضM™g(ˆŒ—½»‡¹óÉàDzŽâšU-Z "< £ÕÂ_ÈÂçäÉ¿ý+_Í{O<Á­âØ@)¬|è!^ýê+‹Ÿc­Ñgï3?ü€ÊœøY¥¦Ê‡Á¶&¨Tr´ÀæÍåM¯bcå-Ôcc¡Y3yKu' <t劼·•ªRûI pºo£ÔÔT®\¹bññüùóY¼x1=ôgÏžåã?fºñr§:ŒñèO÷î¥Ë-Í-{—¦…3ç¦rDhDx8ýÊøŸŠí}l{¬âÒhàÓOáw”&cúö…'žµmÖ±/j4Ì3È›ÿ⋌|è!ÌNTåäÀµkpý:\»Æ Ž}qqúb•$ñÌ?V»]…’DvJ A))ò:pCÔjhÔ¨T>FE9T„ÀÛn“gKlW/BV–lŽ6BÛ¹³›»Ÿá³½ÿ^gM—ôqmi3¦È|P¹ë§N… äµÏ£Go$~âAÙ<&¾m[…ø±åßÓÀÝ67*Ê_óM´ÓXæ®×JëJbÿ¬] À³-ZÀ¿þE|r2̘«VÁwßÿ裔$ïꙘHüóÏËC+VÀ›oß¾=<ô´oáá|¤R)Äòîå¤óòàÈâ7n„Ù³á™g kW⃂ 4T65Nü}÷É ãÄ ((°ëëIž+M:“&ųs§ò/4@úëu:HM%þå—a÷n¹OW¯–ÿ¾‰aøpxä⣢ 6–­~~Ä»ºÒ3,Œ%÷ÜCÛÜ\ê*I*kRÖ±3f .,³Î¹sçhÞ¼9¸¯@øøãY±b{÷îÕ¿˜Œ_¤u…={ä@‡†ìØ={ÊËÞ÷ÅíS¬üR=äϽ²Ð¼¤FEF²TÄü”deÁ’%ðþû²ïÅ®®ðÔS0y²¶=y€ÒÓaÑ"X¼Êš¦÷ð€gŸ…×_;/ƈöôä©úõÛY¼séCÂÂÊÜÌÔxãÓAaaÖ?@aQÙ>>õéSº µ„‚Ù7U"ˆ ’áž–¢ÓÉÿ/;ñDñ¡g“|(¬åéÀšl”“Qç¦ÀÖ­[Çþýûð÷÷§cÇŽ|gàfûöíô1~cÕAþúËt½ÄnnÙû©§½ÙTºDV|Ü¢…EAÏÄ>U¶Gô±í)w/°7dA o¿m^üxyÁرpá,_nwñS¤FPˆæäðm£Vé ߦ¥)òžµ¢ð.w/0hÕJ6ÅLœ({ªþø’“åþÞ»>û ÞxCžRl×¼½­Ö6‡ÇÕÂÂ(Š%ÙFæúÚ†ÓMíÚµ‹U«V±oß>:wîÌÈ‘#¹óÎ;hذ!Æ cæÌ™üòË/<ùä“ôïߟëׯ“œœÌo¿ý†¿¿ž»ÿ~9àl ={ÊÓ_§ž9EÒ§¿òB]yhm9^¥Yc6äýrŒÏAàêU˜7>úHö¡˜ÃÏ^zI?µt›ˆ'ŽgCJŠ>ÝÙϽíÛ›Ô[zõ*£ÏžÕ§›Õ«ÇÙâÏÞZËÕ«æG.^´I´íA­–ÍÝ!!–ÁÁòtŸJERRmÛ¶Uxa§›kÖ¬Æ c˜A¯ÆëÏ7lØ ˜ èÝ»7{÷îå?þ ((ˆ^½záååE]æÏ?•âJGÌ-{ÿx„J!~b½¼˜mÁÔ—@à´$$Àœ9ðñÇPXh¾NPŒ/¿,ùÔb¦DG+о¬,~¹y“ÞF+¨Œw~Ʀ]##å£W/e¾F#{µ4ùXXXzn.¯2åÖ=}´ì›Ü)Ä•"2ñGv…yG÷ 6/h‚‚l"Á™p:Ô Aƒr½]]½@LLŒ|hÀ[o)Ó÷Þ ÷Ü#Ÿ/{¿ÙÒÏî+Ý#H­R±6.Žz•xó Šíqˆ>–$ÙwQòhx˜Ë³´®Z­4š>–çש$iiiøß¸ë»ïºueÔ¯ãÇÃÈ‘àãcµûÛ’¶>>ôf›ÁôÖ¬K—èXNnÝÒ§Õ*O[ÙOY£{¹¹É##väÿâ•[òýŒ A‚ÊâtHP=~þY> )™4·Ûûô4HnÈ QQt5ÜÊØDŒÛcÕ>Öéä¥ËW®ÈÇåËÊÇ«Wå/ÿÊŠ{ RÉ_tæÄQYe”í8r„^\Öß%{SFŒpȈÅS5R ìÉÊ¢KñûÝxôçÞ€Yùï¬+{•`º??ôáHUG žÄD:T™×»·|ËÜnïû{ª9|Gé‡}+ooÞ®ÂHš?¶§R}œ‘a*j ¯^- QëèHRé”CNNÅõË¡ÌÀš6…I“àé§-Úð³e)€s IDAT¶r—¿?=øÝÀÀ=ëÒ%¾mÓ†"ÃØ?Å ³ÁˆcDDD?P¶zà‡~)Õ„ò Ò”«EUªÒáWãeï:wïÿ·Tü¸ªT|‡‡˜w®Ýäç—ŽÜ”%p²³íÝJç U+˜2† ‘w@w¦FG+ÐÖ´4Ždgs¹ €d¯“Ÿ«+j©¡Û‘ˆ‰‘íaÆ‹Åæ§ÖA /¾ÅÑôL™wÝe~Ùû%®DÄŸÔ¨}}«to‡ð§82ãÆ‘ôË/4(Ùr ¶¢RɇZ­<Ìå••o.O§+5˜O­¸Ê' ð\Ûµ“·«0À¡öŸ²„ûéèë«ðú̾|Y(`phh¥|€–R£ ZBÛ¶ðÛo¥iwwèßßnÍq*„°t©>b½žþýåp%`ºÛ{F°ŠÏ‡–~àÝîãÃÑÑU¾¿ðÙO?…÷ßg3`Õ “=-QQò”†ç‘‘²Ç¥²Æ^Š¢²Î+*×hرg½z÷&xÐ ûý-5ÀÔèh;vLŸÞ’‚ñø–-¦¿ îy€ÀTõê%{€ÕÇéâY“ºè?dŸ¡¥£Y3y4( @^ö~ðÎƒŠ•_ïN„ïÏÝT*öuè@[YÍR§HN–§a*ÍÖϯlq :¤‰W`$ Íþý/Ã3ëåÅéÎk¶QNLZœ;'OƒÝ¼)ïŠb&“Õq€u‚«WaÐ ¥øññÍ›KÃ’/{?Û~0Xz9-:ZˆŸÚʨQ¦âÇÃC0e‰›¨(ð÷·O{ ˜Ü¨O÷P«ù$.W+|™Š}ª¬Lz:Œ­ÈÚ-Ç âÇf”»˜“á¢RñzT”I~ŸÀ@zxØì¾åî&T!€ê Ë–Á£„xfÌ(M/{/t‡_(-‰á6+m$( ÐVfÌåО›/~û­Ó,Å®­ 8à:4W1¬A"ÄŽ­ÌÏ%Ôµ8@Û"Pc×.ùûÑfÍàÿ+]ˆcnÙû†ATüÙv§Ÿ¯™ùõ'¨lÛ&ïxmÈ”)Ц}Ú#pZÜÕjÆ|¸ºòXHˆ[$T!€êW¯ÂÀ回ÁtÙ{Z0|^!Ú³xêËÅŠS)uaµA•/¼ ÌkݦN}\¤¥¥Q䨻‡W‘Âà qsd_§Ã’^ÙUATG((ý=FÑê¦g€Œß3Lv{_5òêÉç³7¦…—ÖDx€¬Äĉò~&%¸¸Èsnn¢k€ºä*ÁËÅ…± ðl ,d 5qþå ^z öîUæM™Rjz¸uàÇ9¦Xö~&~ì+ŸßíïϘâ;k"<@V`Çøè#eÞ¸qЩ ú¸&8°ÌÝÀœš—"#ù.=Î5Ox€ÖDŒÕV¬€Õ«•yƦ眣9é{„¢,åþÒ—äeï^..¬‹C-VÕ>rsáùçå=Kˆ- å-ØWW¾2F!€œœÝ»á•W”yƦçÜÓ¹¾ï0štåß+^,]ö>·IšÖ«g“6 J5™6 Ο/M«T°j•"Z³ècÛS=@%4¨¡­É…H`M„rb®]«Øôœ1ŸÃ½S˜\¨¸öãgaý`ù¼W@/EFڬŸR öìE‹”y/½Ý»+²DÛžºèªi„H`MÄ^`åàÈ{Bò÷£!6È¢  ±€îù‡ü‹ùŠ:_ )ùãëâ‘Nˆ{?Õ> äMNœ(Í‹Ž†cÇd¥+•¤.í&F€œ”Ñ£MÅÏäÉ¥â§0¹Ã}›ˆŸM)ÎoÚTˆŸÚÊŒJñ°r¥?@`B9!~(ү̜)ŸkÒ5¾ï0¹§su¾>x¹4=<<œÿFDظµÂŸR%‚¹s•yÇÃ}÷™­.úØöÔePM!<@k"“ñ矦¦ç¦MKMÏEYEé{„œ£9Š:¿ô†y¯•nt:"<œU-ZÔH›…?¥’ÉbÇðË6"Þ{¯ÌKDÛá²=Â$°&"qýº<ÅUhàgöö–MÏ ÍÑr´ÿQn¸¥¸n×ÝðÎäRñó߈VÄÆRS ÞEŒšJ2oüó2oùre8o#DÛžº¨&q€ÖDŒ9 ……rPÃëוùk×Ê»!èòu{ä™»•¿P÷u†éo¶xŸÌkXü*É©S0}º2oÈ_ @P)„r^~þúK™7i’<"$i$Ž?~œ›¿ÞT”j o¾ EÅ〣"#Yfñ#ü)¢ÓÁsÏÉ«¿J >¨ðRÑǶGx€l𠬉@NÀÊ•¦» ôë³f¤•8ñï¤mKS”ŸhSfC‡œÉÒæÍí2ò#ü)òÁ²ÉË8Ï‚¸EÛá²=Â$°&"P98B ={äx?†¾Ÿ¦Maÿ~ð—8õÌ)’×)w@=ÛÆ-€ìâÕÒ¯4lÈ¢fÍj°Õ‚Jsñ"´i9æõG• ^@`%D C”$û~Ê2=ŸyÖDü$ÄÀkóJÅÏ!~ƒ#”â' @6>  Jä ”˜ž¯]Sæü±lz>7ö×>R&6„ñïA¦¿œÅûµ@üÔ…_ÕbåJøõWeÞ{ïAx¸ÅO!úØöí 5ÈAyõUS;ȤI0h\œz‘Ä…‰Š²äú²øI’Ó¯EE1¿iÓjmùJ9\½ ¯½¦Ì»ï>9P%}l{„ÈöÀšP9ÔVЪUðüóʼ¾}aÛ6¸2ç§^T”¥Ã+‹áZqPç×5bN“&5ÔZAµxøaغµ4íã#ïõm¿6 §Ex€µ–½{å; iÚþïÿàÚâDñ“ žKÄÏd!~‡ÿýO)~æÌâG ¬€@DR `jzÞ´ ò6\ãÜØsŠú·|aÂ|¸ÜHNOŽfv-?uá—F¥¹qÃtO“îÝaÔ¨*=ècÛ#<@¶Gx€ÖD A£‘ƒš3=‡NæìȳŠü\/˜ø.œ/¶ù¼ÍÌÆk¨µ•CøSÌðòËf»ÉÓV¯UÕ"5‰>¶=Âd{„H`MÄ^`«¯ÂîÝʼ×_‡žêN ;…¤+µrxÀäwàTœœŽ‰á­˜˜škl%ûT±y3|ù¥2ïí·¡yó*?¥ècÛ#ö³=b/05ÈX³Æ4äKß¾0áî4Ž8¤-?7˜: ŽÜ.§ßnܘ7„gÄq¸yÓtš«S'7Î>í'ELÕröí3ý>lÒ>zñ&'GÒ”ŠŸ"Wˆ‡ƒäôL?ŸbÀ¸qÊmÝÝeìâR­§}l{„ÈöÀšT‹IN–Mφ{_z{Æ·2¹ôÔ1tù:}¾N ³¦ÂŸÝäôì&M˜êâ„?EÏöí°v­2oÊ9²e5}l{„ÈöÀšˆ8@å`Ï8@ Ü{/¿×¿~ç¡ï¦(«ô—¦¤‚9“`ûýrzn“&LlÔ¨[+¨6Ùٲйt©4¯M8xÜÜì×.@P§¨Kq€„¨–2v¬©ø™9<‡°yGÐd)‡ÙŽ)?óš6eBTT µR`5&MRŠyêKˆ@ ° BÕB>þ–.Uæ ¹+—^ߦ0]£È_6 ¶/Šx¯iSÆ9 øIJJ¢Aƒön†ýøãX¶L™7atìhµ[Ôù>®ÒÒÒð÷÷ÇÕÕº«YYœL=ÉÉ”“úÇ 7/ R©pwqÇÝÅ7µ›þÜøps)§ÌJ×yºz⪶ý×Iaa!ÙÙÙÙü^çG ZÆþý0r¤2ïÎFyŒ¾p˜ÂäBEþêç`à ùüýfÍÓ°a µÒºlÞ¼¹î.ÓÎË“÷õ2œ‰Ž•ÝìV¤N÷q ±cÇzõêEppp•®OÊN2:'SOríÖµŠ/®Œì8’ù÷ÏÇËÍËf÷HMMåÀuj)¼NÒ¡•´è$’$!!V”¸W$$ÅyIYUë¥ä¥ ¹Ö gŒð•CM{€nÜ€ Ñ`Óh¯> øݵ|EÝχªòù¢fÍxÅAÅOgÂyg÷T*عî¾Û~mØ ¤ãRæ%‘s2å$7óoÚ»yÕ&.$ŽÏ|NûðöönJµHÈH`æÎ™Ü*¼…V§Õ‹ãóaRÑyyÏQÑóÙ¿~dþâü†~1TK(*’wr7?²Úÿ°‰øùêñRñóAóæŒŽŒ¬Á– ¬Æ¾}°p¡2綾ì"~J~JHŠ_š†:IgQž„„Z¥ÆEå‚‹ÚEqÓj•s/@-Ôr6ý¬‰Ð9zš¼¢<{7ÏfœJ=E—U]˜qï ^ëöšÃýŸ%$–í_ƤŸ'‘]˜mïælŒ@µ„±cåþ%ø¡áÓÐø\ÏUÔÛú,} TÀ’æÍåâ§NúS å©/­¶4/&ÞyǪ·¹’u…W¿•ïþù|0*öú… èÅ¡0*97N ªòž£äP¡’U*›¤ó2óH.JæÔÍS\¸y"ub¹ª]iØ”–¡-iÒ’–¡-‰ ‰£žk= µ…&‡F§1›o«#O“‡FWêMÔè4LúyߟýžÏ|F”Ÿõ|‰¶ôK?Çs[žcç¥W8BÕV¯†%KJÓÞñ¡ß|Rrõ~ºŒT°,6–#"j¶¡6Â*þ”‚ÙO“—¹¹¥çÆéÂBÐéJ­V™.ï°fÝÄD8~\ù7¬\ >>Õë‡bŠtE,Ú»ˆ·v¼EŽ&ŽÖóT[­$ó~y:4'€ Šo7oâB∠‰SˆfAÍpS×ÞÕ€9š^ýþUVÿ³Z‘ÿû¥ß¹}ùí,p9CZ±Ê½láÒI:Þßó>oüúF­+ü%b[…¼ ªx_@*ÅyIYUëI:‰‚"ƒàsNŒð•CMx€V®„_”¿<ѲÄóMó•ó¯t—}±’VÄÆò_'?|÷¼üÛœp©HÌ”¤óóK;ÐQyî9XµÊ*OõWâ_¼¸õEŽ$±Êó ¬K¨W¨^àŠ(ÿ(ý—‘#²éÔ&žßòjÑ‚áá5ÜR I0y2Ìkï–ØŸˆ¥ºŠÜ̿ɤŸ'±òàJýÊŽÊP2­Sò«ÐðgYyÆ×”{ì3îndƒ‘®ˆ¹»ç2ã÷hMG;ú5ëÇGdÕé;AíéЕ+WxôÑGINN&''‡§žzŠÅ‹£V›7â½ñÆÌ›7O‘wÿý÷³eË›·5>¦O/M»"1CuœÛµÊ• ÿ´ƒ7ß›ŠU±± wñ“ŸÏ<£ßñ< ¨c %Ë—ƒ¿µžbÝ‘uŒß>ž97LÊü=ü™ÔvcúŒ1ñ°×4Ö\)SÞÊ›’ÃÐàm‹tvf6QaQ´nКÁ-lº¼¶îÎÿù‘E{1éçI q‘‘@ϵ=™Ü}2oõx«Jqƒ¬á:”tˆáß çŸ¤LÊ=y¿ßûg%¿(ŸÙÌfîî¹j MÊ›5gÙƒËèÓ¤Z'Èh ˜ôó$íYd2-ëíæÍÂ~ Ñ~„ÍÛ±ïê>†3œã)ÇMÊB¼BXÒ ƒolóv8uÉä4ß7nÜ °°hƒÐ7nÌå˗˽î×_å‰'ž`Ô¨Qì4\‡^Lff&ŠÃKÓEE0t(¬\YZD!ïºþ¡?Ùds±1¼6ò}T,WˆŸªÞßîé;¡kW2ŒÄOFË–°w/Ì›o¿MÆÈ‘0f ü÷¿ðŸÿѧ<ø ¼3l×®dDGË‘’£¢ $„ F!~jÍßkÃôöóÛi½¬53vΠ0G)~<4Ä÷Œç訣ôiÒ§V´W¤ën:ïVï÷}ŸžúAY1i–£Éáù Ï3`ý½qÚÚ÷ONMfâO鶺›,~”!Õ3€/Ћ{÷—½ÒyyydddpéÒ%²²²p’q‘ qTòôððÐçÕ«WÌ̲£YöíÛ—7ÞxƒÇœÀÀ@xà6nܨ¨óÃ?°víZÅaˆ%é‚0¾ø@.oF6¹älÑEý/~`ü{ã¯âÓ¸8r·m«ôýj]úóÏá¾û =EiŸ>¬}úi’ÜÝkW{kiúzöuî{/}×õåüÍb!y¨´nŸ&}ï5ž·z¼…‡‹‡âú’_sµéïq¶tZZkÖ¬©5í©Méû›ÞÏÑQGis­¢œCòê±6ËÚ°ýüö ŸoõêÕ¤§§[tÿ]—wÑæ…6ÌûsZI«¿È^¥Mƒ7ÑýfwB½B«ý÷9zú¯¿þbíÚµŒ3†õë×£5ŒOæÄ8Íصk׈ŒŒ$;;ooo–/_ÎÆùå—_,zŽ©S§rüøq6oÞ Xg ,'} ›Ð¦ªNá!)_dç›Âäw =Lź–-VåûÖfÌ€7ß4Í>>ü\]Y±b…اªt’Žå–3õ—©d˜ ú> XÐwÿnýï2ŸCô±íÙ¸qcµö«+¬þg5¯~ÿªŸÊ*^íò*súÌÑ xc®]»V¡(G“ÃäŸ'³dß³«!Ÿ¹ãÞï÷>žÕûCœ1怄††âëëˉ'ôy§OŸ¦iÓ¦?‡››[™+ƪBF†<ða(~žâÓ9n"~vÝ £—ÀÍ0ÿsñ£ÑÀ°a¦âG¥‚Y³äèÅ»f‹/æ²ùûúßܹêNFom"~Ô*5£:âÔèSåŠ}\ 8Pˆ x®Ýszñ#;+ò%$îYH§:)ŒÓ†DDD”+~~¹ø m–µáƒ}˜ˆŸ(¿(¾ú=kÿµVˆàDÈÍͧŸ~šøøx.]ºÄ_ýźuëxöÙgõuÚ·oÏ\ƒ¸3£GæçŸ&!!o¿ý–>ø€¡C‡Z¥=7n@¯^¥‹ÜÑ1“<ÇE“EÇŸ•—ºK^j¾hÕŠ']üdd@¿~ðÉ'Ê|y:lÊû´Ë¸Ux‹Wx•Î+;sàÚ“òöáíÙ3bKû/ÅߣzK肚¦YP3vßÍ´{¦™û;zã(>êÄÂ= -Žg•UÅ¿ý/}>íÃÅŒ‹Š2*^èðÇ_:N¿fý¬ö7§ZÿÞ{ï1vìXî½÷^‚‚‚X¼x1]»vÕ—7mÚ”}Úßߟ×^{¤¤$¢¢¢X¸p!?þxµÛ‘˜}úÀéÓr:ˆBfrŒ–d)êiÜ`ÞkòQ|Õº5|}«}»’ ¯ô:yR™ß|cv£Ï:¹X9l8±1?ŒáÚ­k&e~~Ìè5ƒ—:¿T©(±¢mOZZþþþ¸º:ÕǪÍpU»2£× ú5ëÇS_?EBF‚¾¬@[ÀØDzíì6>yìýÒtsq€¶ÝÆ [_ 1+Ñø4 lªGVÑ+¦—Íÿãá4 [PÐùó²øIHÓÍÉfG Emôf ¼1CŽõÓ;0/Zµ"ÄÍÁã¡ìÛ'Ç´INVæ7kÛ¶Asó1?„?EæÂÍ ŒÞ6šïÏ}o¶|P«A,ì·ßÊoƒ"úØöPÕÉ*Èbô¶Ñ|vä3“²àzÁ¬|d%Å=¦ð¥ç¥3æ‡1f¯Q«Ô¼Üùef÷ž]'RV‡ºä¨*+€Ž—=?ׯËéî¤0•Sx`jvž2n„ÁÄF˜Ý¸1.*ÇÝ€M›äuþyF› vë&üŒ¼ ”j ™ÿç|fîœiv3Æ&MXÚ©¾8=ë¯çÅ­/’‘ŸaR6¢ýö[ˆ·›7›NmbÔw£HÊ6ý’nÜ‚5®¡[T·šh²ÓQ—«µBß¾VÌù?\âY3~ŸÝwÁÌiàæíÂÆ¸8 5y.‡cÁxí5Ó IŸxBöÕò=ÙìÉï—~gäÖ‘œL=iRæîâÎÄ»&2µûÔZÅY °ƒoL·¨n<½éi~KøMQ¶êïUü–ðmÂÚ°éÔ&“k]T.Lè6øžñâý"°!€¬À®]r¬¾¬,Ùì<‘SôÆtO¦ÿ= «F@ o/¾nÝš–^>4«ÕÂ+¯À²e¦e¯¿ï¼#¯úªgò§è$ùEùäå‘§ÉS<æå+ò¶ŸßÎ'‡?1û<½bz±ìÁeÄ…ÄY¥]ÎÔǵá²Q~Qüòô/Ìÿs>oìxCéü\Ê9Î]=õ”×´ kÚG×Ð1¢c ·VàȈwj5ùñG9Èan.›ã̘çO€í÷ÀÐPÖÆÅáëb¹µV’ ƒËÞC\]eAôüó?ÕæÍ›mæOÑJZr5¹äjrÉ)Ì1+L Ë/–Ö5·³teócþýóùÏíÿ±RÈØ²2;vì +¡V©™x×DîkzO~õ$§ROɹÀ5 …œtS»1¥û¦Þ3Õiö”ÔÂTy€6m‚!C °šs‹Y31;gÀ´™pªµŠYóz£F6nu põ*<ô:¤Ì÷õ… ä¹@ ÑI:næßÔ ½XÑä(òJҖ敤«+Hj *žïðü^zIò2äÁå]^}ÊW0ùEù¬ú{svÍáê­«ÊÂS€¼‹%‚ÄÛÝ/7/¼Ü¼*%P*ûèîâ^q£ì€ðÙá²=ª\É'“Á|H1 RT ¦N…Ù³+6;ÿÞO͇͛ó|x¸ZjE$I^Ñ5ožiÙ¨Q°x1”cèÎ+ÊãÃòîîw¹ž}Ýl÷;Ý ÷ × /7/…`ñvó6›_^YI¾X+#Äí8p ½›àôT´˜@P„²I‚W_…>Íγ9FHfç¬öì¼í6:ûùÙ©µV$/þóøê+e¾Z ï¾ ãÇ—yiŽ&‡åû—3ÿÏù$ç$›­ãåæÅ‹_äµn¯ÑÀGLÏ æ¨$IÍðá°v-ô …Ée˜§Ì†ÛâÙѪ¡Ž¾¥@Jм­Åž=Êüzõ`Ý:yí¿² ³Yº)ïýù)¹)fëø¸û0ªÓ(ÆwO˜w˜¼ÚÀI<@µá²=Âd{Ìí&T'0§Ø–²v-…„1 Ìí3—0oy×ûÍ›7ÛäÏ”"úØöìØ±ƒÌÌL{7éIMMe×®]ön†ÀIq€Ê!>>ž9ÓßäuNÑ« ³óÿ½ fM«– tÔ--។ǥK¦õââä ‡+²3ò3X¼w1 ÷,äfþM³·ð÷ðçÕ.¯2¦ËãF j1"jXhIDAT@ÏBþ!å²ï³ó¥y±×Q¶´ÐéàìYS±“šZñµ={Â×_ƒA£ô¼tîYÈ⽋É,0ÿ«7Ð3±]Çòʯàïáo¥?D ‚ê#P‹Ÿ³slö·lY;·´((€cÇ”BçÈÈÉ©üsýç?°j¸ËË»SsSYð×–ì[­Â[f/ ñ a\×qŒî<_wß o!ü)¶Gô±í Û#<@k"Þ©•àB˜öŽŠWºÈ[ZT¼Íg •%oIa(vNž¦êÏéî­[Ë{}MœÀœ¼÷×{,Û¿ŒìÂl³—…y‡1¡ÛFu…·›·Å·1jlècÛ#âÙžÔÔT8 – ¬‚ð•C||<=§÷d³ó²én¬í`Ç--’’L§°.\×éW__¸ã9šs»vо=´jÅfî¤ì$æý9V«É5û |0ñ®‰¼Ðá¼Ü`:P f ‚/†ÀÁq>ìnÓšèšØÒB’dac,vªû‚ +:%G³f 2˺vëswÏeåÁ•äå™}ºHßH&Þ5‘ÿvø¯8(‡B  ˜3 "‡…óGóæxÚjK‹+Wäååÿ- C‡ä©­ê豩Ø1³WWjn*  ŠãÂÍ üzñ×2wRò‹bÒÝ“x®ýsx¸xT¯JM úØöí 5ïÔ p‹9Íê=­û¤·nÁo¿ÁO?ÁöípútÕŸËÅE^¢n,vCó' &b'Gc¹1:& †ÉwOfXÛaVÝóJøSlècÛ#<@¶Gx€ÖDx€Ê!>>^ñXe´Z8p@;?ý$¬ŠI¹^=hÓF!tÒšFp± ÉDØTEà”EÓÀ¦Lé>…§ïxÚæ»– À~ ú\¸P:Âó믑Q¹ë m[òÚÄ‘Ô<‚³Ñ> Òp1ër±¸ÙEÂÏ ä|_}S±Á±Lí>•¡·ÅEU —û @PE„²²Ð)=.X~­«+i·7ãl›HŽ5tç¯Ð|öºÝàRæ² ƒtäÃx»ycöhÞ®F„ð§ØÑǶGx€l𠬉x§VFžÊúé'ùØ¿_žê²ô¨ö´ôeCd_7¸I–Ç)à”\XÉÁ¢òðq÷1+n¢ý£‰ ˆ!Ä+Äz7«"Ÿb{DÛá²=Â$°&ÂT& Ó§KGx~ûM63[H¶;4wgSTÛ›H\ °N}Ý}MÅM@´þ<¸žø0e@OëãÇaÄYô\¹bñuW5„›JüÔF¢SVúþ~~fGnJŽ zb(X ‚Ê"P ܸÑâº'Ca{Sø© ü#‘cáJqo7o:Dt mƒ¶&£9ua÷táO±=¢mðÙáXñN­)Þðs“RÑsÕ¯âkÜÔn´©ß†Î‘éщΑiÚ µÊFAáO±=¢mðÙáXá*‡øøxâ§O×§ \aW£RÁs¨Hå숪BEóàæ ±Ó¶A[±m„@ j%Â$Ðs¤>üT,xvFCž[Ùu#|#b§cDG<­äv@`5„ª€;FšÏ÷÷ð§Sd'½ØéÙ‰HßÈšmœ“ ü)¶Gô±í Û#<@k"Þ©àéêIÛmõb§sdgš7GE9ó_‹þÛ#úØöí 5 rˆçºê:KÞX‚›ºœ¹/@ œáè —Â…ø'¦¨¨ˆœœ“C£Ñ I’þÐétŠ´ñaëòš¯ÈÊÊ¢¨¨Èæ÷© $°;Ÿb{DÛžêz€t:YYYdffVxdee¡Óé׫T¦SòÆy¥«ZG­VãææfÑáîînq]ãúÙÙÙ¸¹¹éEJvv¶YñbÉQrmaaåƒÔ:3~~Ätq„ØáO± ééé\½z•ÄÄDÖ¯_O=Ðétú£ägYiKóÌ¥K(ù¢,ëÑ’:•©k.O­VãêêZîáââRaŠê}ÿý÷tèеZm‘ˆ13™™™dgg×È/|@ #ÀîñS94 ׯ_׋›«W¯š=òóó×}òÉ'vj±@ Ô>„Ô8ÆCýùùù¸¸¸è]þÊ6Î3WfnˆÞQÉÌÌTˆsçÆb¤@ °"...x{{›îîî¨T*Å¡V«Mò,)³FyM››ËÖ­[kä^öF A¥Ðjµ&C÷•=¬=Ô¯R©,N...¨Õjý£ñyeÓÕ©+IIII q“““cµ~8*• ???üýý‡¹<\\\×›{OçU¥Ž%×èt:4ÅGaaa¥ê^'I¾¾¾f‹¥‡"íááQþ?§‘””ÄöíÛíÝŒA  8yò$6l ¨¨¨ÌC«ÕV©Ì¸¼äCÅÖ> Kê–eȬ_Ð’$é? ¥øúúIddYYé´lÙ¼X|•þÒT«U¨TêâG•¢\®ƒÉ5ÆõJžÇ°T¨T ¿¤UH’„J¥*~«‹åך$aT}~)*ƒ<•Áõ’Qº´NI™V«5xŸiŒÞƒýûP£)ë=ZDQ‘éûX«-Ò_£ÓiÉÎÎÁÇLJÀÀ ³‚¥¢Ã××שF3mÁµk×D Õ¨¾üòK¾üòK{7C УV«iР>a4hHýú>„…yª&8XKpp>Ù¸»§RX˜ŒFsY ²wÓë¨T×qqñÆÅÅÇ¢C­ö!7ׇüüò¯QÕá “Kˆˆˆ°ªø‘$:]¾Á‘§8—¤"$I‡üþ‘ªq^²8ÀšçæîWýzÙÙÙÄÆÖ“B jµZ¯¯oñð¾/^^^èt:ƒ_Ö:“ѱ’ssyÎä‡ñññ&<<„ ü©_ß›ÐP7BCU”G``¾¾ièt×ëe>F#‚šG’´eQT”eÕçU«ë¢zÅ£j.ÅÂH]ü¨L«T.eæÓÖº^õS>ÊuTV-—$m™‚¥ä\«Í+·¼ô\,7Gt´X/˜àî¯>>îøúºãããjp¨ñöVàí-áå%áí­ÅËK‡—W^^<< tº«H’–ôt(k•ч <Å"X»!I*´Zù$ÐjÕhµ*t:ÐéTÅç*}ž$©õr}ù¹ Ët:’$ò¹œ'×)Í“ïMq]ôm‘ÏK¯)­Oq}ù("((—ÀÀLoáå•”?ÍhþÅ"*îcAuÉÊoo0²æX ùK;&Å67pŠŠ /|}íÝ3 Ptï. ™;Ôê²ËªR^2ýon@ÃØa\Ç0m:®®àãC±)=ÜÜŠ€"*ú’.­V™Þµ ÊÕ.²Õ™m3È}WÎMîV V{°gøäšúÀÌ••}n|ñs³[2UeMm”5]R~½ò?|-Í+-+ûƒ»ìó’T—-[¶Ð¿êׯoï¦8-ׯ_g÷îÝ<ùä“önŠÀ p*´sçN DŸ>}¸rå yyyüòË/eîl[ÙúÛ››kï&8=¢mOAAS…d¨H’$vnX §Š¬5uêTâããùüóÏùý÷ßñððàóÏ?·Z}[’žž^­7vRR’Uë–W§¬2sùÆyZ­–”û,ã-**"--­Ê×Wæ”››Ë­[·Ê­c­>ÎÈÈ    Âz5Euî]ÙÿQE÷ºuëV™â¯¬ÿQvv6ÙÙÙŠ®ìõöü¼0þi4šÛŽÇÜë 2T¦-ùl±UWôÜÎŒJr’1Û„„7nLAAîîî|üñÇ|øá‡ìÙ³§Jõãããùâ‹/ˆ‹‹³yû“’’ÀÓÓ³J×'$$cµºåÕ)«Ì\¾q^QQIII4lØPŸwèÐ!Ú¶mkQÛ«Caa!©©©DDDTéúÊü²²²(***׬i­>¾qã>>>xyy•Y¯¦ú¸¬6ZJeÿGÝ+==WWW³ÓÚeý222Ðç™{ÝßûÔ©SDEEáíímQÛ«Cuú¸²×ÛóóÂø”••ERR±±±µ½:˜{T†Êô±%Ÿ-¶êc㼂‚þùçŸ:1*ä4 ü|9F†áÉ...e+ZRÈ!5ö¥!Am ª?Ä §@ÁÁÁ€³g϶wSœ’ï¾ûŽo¿ýVŸž6mšbªG`vîÜÉ矎««+K—.µwsœŽ_ý•/¿üRŸnÑ¢cÇŽµc‹œ“Ÿþ™o¾ù___FŽITT”½›Tu$'bñâÅRóæÍ¥µk×J³gÏ–‚‚‚¤ .èË###¥©S§Z\¿<®\¹"ýý÷ß’F£‘&Nœ(}ôÑGVÿ{’”ŸŸ/=ñÄRË–-íݧ%>>^Z»v­tñâEéâÅ‹RAA½›ätüúë¯Ò}÷Ý'=zTÊÉɱwsœ’ÌÌLýkxÛ¶mÒ³Ï>kï&9éééRïÞ½%­V+9rD2dˆ½›T-œfÀË/¿ÌÌ™3Ù¹s'éééìÚµ‹ÆëËÇǽ÷Þ[fýÎ;ãff'ÍeË–Ñ©S'ÚµkÇÌ™3‘$‰† Ò®];öïßOBB=zô¨‘¿Ñ‘‘$‰'žxÂìj‡%K–èûxÖ¬Yúx*/¿ü2'N±™*ÁŸþÉ„ LòÓÒÒxê©§hÑ¢÷ß?{÷îÕ—%&&’@DD„É´€³gÏòÌ3Ϙäçææòâ‹/Ò²eKzõêÅ?üÀ¢E‹xâ‰'øûzõjM7×a6lgΜ1É_³f ]ºtáöÛogÚ´ièt:üüüˆ‰‰!&&†U«V1mÚ4;´Øñ˜={6[·n5Éÿí·ßèÝ»7qqq >œ¬¬,<<øàiòäÉRçÎÅP9deeI#GŽ”¢¢¢¤¸¸8“ò§žzJ4htþüyiÓ¦M’tæÌ©mÛ¶ÒòåË¥¯¿þZêÒ¥‹tüøq;´ÞqXºt©Ô£G <¨(ûþûï¥úõëKýõ—tâÄ ©S§NÒŒ3ôå;vìF]ÓMv8¾ýö[iÀ€’Z­–>üðCEYbb¢äãã#mذA:þ¼4tèPéÑG•´Z­4fÌé•W^‘bcc¥;wÚ©õÖA I’<(íØ±Cruu•NŸ>­({ðÁ¥ èÓŸ}ö™Ô¶m[Eo¿ýV7n\´ÕQ9pà€´cÇI­V› ~ýúI .Ô§?ùä©C‡Òˆ#¤ÁƒKƒ–‚ƒƒ¥×_½¦›íPdeeI;vì&L˜`"€.^¼(¹¹¹I·nÝÒçuíÚUZ¹r¥¢ÞÀ¥cÇŽÕH{‘‚‚iÇŽÒܹsMPvv¶äéé©x}4Hzë­·¤{î¹GÊÍÍ•$I’>øàiÍš55ÚnGãСCÒŽ;$???4xð`iúôéúôæÍ›¥fÍšI’$I:NêÙ³§”œœ\£íuDΟ?/íØ±CêØ±£‰š7ožôðÃëÓ‰‰‰’«««ôÍ7ßH/¿ü²$I’”––&Ý~ûí5Úfkã4&èêPb|V™ÙdêÌ™3¼òÊ+út›6m8}ú4»w令¨ˆèèh6lØ@Ÿ>}j¬½ŽH‡ó}|öìYƯO·nÝšS§N)‚uéÒ…9sæØ¾¡Œ¯¯/={öäܹs:tHQvöìY¢££ñññÑçµnÝšÓ§OóÕW_ѪU+rss¹pá‚ãkÛwwwzöì©£aÈåË—hÚ´©>¯uëÖœ¶2æ>/ÄÈ¥ '11QŸ.y];[|0!€*`РA¬]»NÀ§Ÿ~ÊÀíÜ*çBô±í騱#AAAúF/]ºÄÎ;0`€[æ)EGGKjµZ ׯò’¤Òà¨ÁÁÁR£F¤Î;Kׯ_·ck“wÞyGŠŽŽ–<==¥àà`)::Z:yò¤¾ü­·Þ’|}}¥ØØX©Q£F&«ñœ§Ù ^ ÀRĘ@ ‚:‡@@ êB @ ¨s$ Î!@ ° úPöâòåˤ¦¦Úµ À1H pB._¾L¿~ý=z´"Ñ¢ElܸÑê÷+**¢qãÆŠ €5É¥K—èÔ©½zõ—E$8!ÙÙÙ8p€¯¿þš_ýUŸüøq.^¼hÇ–Ù†/¿ü’Î;sþüyþïÿþÏÞÍ€@“âááÁ›o¾Éäɓ͖>|˜•+W*ò¦M›FFFŸ}ö¿ÿþ;sæÌaðàÁ,Y²I’X³f C‡eÅŠäää(®?uêcÆŒáÉ'Ÿäûï¿W”íܹ“矞Áƒ³zõjJBíß¿ŸO>ù„K—.±`ÁÖ¯_o¶½™™™L›62eÊ};wïÞÍêÕ«¹xñ"“&MâÈ‘#&מ8q‚åË—“””ÄâÅ‹õÑš;v,ƒ bΜ9°gÏV¯^­¿~Ë–-üüóÏúôÂ… ¹pá;vìà…^`èС̘1C=W Ôn„œ˜#FžžÎ×_mRvòäIþ÷¿ÿ)òæÏŸOff&_}õ$//þýû3þ|âââøóÏ?éׯŸþ9ï½÷žâú±cÇK@@ `ÿþý|÷Ýw >œŽ;2hÐ –-[ƼyóYˆM™2…ýë_\¸pACŠŠŠèÞ½;ÇgÈ!œ9s†»ï¾FC`` ^^^„……‡ŸŸŸÉõgΜáÍ7ߤ_¿~œgÏžE’$‚ƒƒ¹páÉÉÉœ;wŽV­ZÈ™3ÿß¾ý½2ßÇqÎÙdÍhj¥°ÙR(I±eÍ$±f%9v¢ü”¤‰òȉ9BI9!;ÎEìí@räÔû@vßî«û¾ÙU—º¾¯ÇÑw¿>ß÷v°^½?ïÏÔÖÖ244„ÕjeppðË¿±ˆ|u€D~s###”––²¶¶öåÏšÍæüµÕj¥¨è¯¿ŒòòòN\™L¦üu{{;wwwÀÛÔìì,^¯¯×ËÔÔ‹%ÿÞÊÊÊ ?™L¿ßŸ¿¿Éd"Éd>ý]***òáç}Íîîî5455‘Éd0›ÍƒAR©[[[ŒŒŒ‹ÅØÙÙáøø˜P(@4¥ªªŠššB¡‰D‚\.÷éšDäû¨$ò›3™L,..211A0Ì?_\\ÌÓÓÓ—Öù¯Çÿ”N§©®®Àf³‘H$èêêú|ác³Ù~ÞÎf³„Ãá‚Ö{_ó}Ž —Ëq{{‹ÍfÞ:B©Tг³3ööö¸¹¹a~~«ÕÊää$‡ƒƒƒÒé4§§§ÌÍÍQTTÄØØXÁu‰È¯¡ˆôõõQ__ÿaÈï÷suuÅÃÃ///lnnòüüüS÷yо¸¸`ŸH$@$aaa!ÿúãã#ÛÛÛŸ^7“N§I&“ÀÛ@õåå%½½½×ÚßßO2™äüüœ××WÖ××1›Ítttäï¹»»KYYN§“ÎÎN®¯¯999!pttD6›¥±±‘x%%% o¯X,†ÇãÉoßÝßßÓÚÚŠÛíÆçóa±X/¸&ùuL¯ïgQEDDD B 1 1 1 1 1 1 1 1 1 1 1 1 1 1œ?˧™2Ö>ÙIEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/compressed-writing-shuffle.svg000066400000000000000000001003721416254111300267360ustar00rootroot00000000000000 Writing with small (16 bytes) record size 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 0.0 0.5 1.0 1.5 2.0 2.5 3.0 MRows/s No compression zlib lvl1 zlib lvl1 (Shuffle) lzo lvl1 lzo lvl1 (Shuffle) bzip2 lvl1 bzip2 lvl1 (Shuffle) PyTables-3.7.0/doc/source/usersguide/images/compressed-writing-zlib.png000066400000000000000000001211741416254111300262320ustar00rootroot00000000000000‰PNG  IHDR@°AàÚ²sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwX××ðïÂÒ;R¤HQ‘¢QlˆÁ+ˆ½~VÔ$j¢Á¨‰%‰…X¢b‹Qì-&¶4;±¤ISzg)÷ýc_F–º »¬.çó<ûèܹ3sv¶pvæÌcŒB!¤Q’w„B!  B!„4:”B!¤Ñ¡ˆB!%@„Bit("„BH£C !„BJ€!„ÒèPD!„F‡ B!„4:”B!¤Ñ¡ˆB!%@„Bit("„BH£C !„BJ€!„ÒèPD!„F‡ B!„4:”B!¤Ñ¡ˆB!%@„Bit("„BH£C !„BJ€!„ÒèPDȆ1†èèhÈ;±ÄÅÅ!77ÀûØ ÅZ6!!III² PTT„èèhË|[’(((@tt4òòòäŠÔ¼}ûiiiòƒZQDdnçÎX°`H[rr2FŒ .ˆ´cþüù5®ï«¯¾Âܹs¹é|ùùùu^GE<>>>6lJKK¥¶^yš9s&¾þúë:/¿`Á|ùå—RŒˆªQDdNSS[¶lANN×võêUœ9sGé»ÿþZ [ZZÂÊÊŠ›^¹r%:$ÒGGGPUU•Â3###ØÛÛsÓÀ÷ß/Ljä#??“&MÂòåË1hÐ ®ýÝ»wØ¿?<==ñLj¼gÊäååaĈ@›6m°lÙ²Z iˆ‹‹Ã¸qã¤ztCMM 'NœÀ“'O°yóf©­÷cfee…fÍšÉ; ÒðåQ|}úôAqq1þý÷_îèÇÕ«Wáêê*rô£¬ÏÎ;©©©())‰‰ JJJ Œ3†ûµœ––†‚‚dff"::`nnGGG,Y²ÆÆÆ„G&ÒÓÓѬY3¼~ýQQQèÕ«TTT*Åûüùs¼zõ vvvhÚ´)Š‹‹¹õT”žžŽ‚‚˜™™qmqqq033ŸÏ¯ô<ÜÜÜо}{.¦ÌÌLî4 LÊËÈÈÀ½{÷ðÉ'ŸÀÔÔ´Ö}]RR‚GáíÛ·°´´„““x< &&ÆÆÆ(..Æ;w`jjŠ6mÚ€ÇãáÝ»w¸ÿ>\\\ ¯¯/²Î¸¸8DEE!77hÛ¶m­qÔæ—_~ŠŠ >ÿüs‘öääd„„„@MM­Úe7n܈àñãÇ000xÛñññxøð!š7o„,99Íš5ƒ²²2×·°°‰‰‰hÖ¬âããoÞ¼AQQÔÕÕÑ´iS®oxx8^½z++«Jû¨¨¨>Djj*¬­­¹í€Ö­[‡Å‹ÃÏÏÕÆžœœ B p¯3”––âùó爊ŠB‹-àèèXåòÏž=ƒ¶¶6Ú¶m+²ŸÓÓÓmmm8;;C]]›WÕgÑÜÜÊÊÊ`ŒáñãLj‰‰A›6mÄz 22‘‘‘ÐÓÓCÛ¶m¹ç=iÒ$®Onn.Þ½{WiY4iÒ„›ŽŽŽÆÓ§OajjŠöíÛ‹¼†„T‹ÒœœœØ’%K¸iGGGöûï¿3>ŸÏbcccŒÝºu‹`IIIŒ1ƦNÊ À–,YÂ455öüùs6cÆ æååÅclÁ‚ŒÏç3555¦§§ÇôôôØ£GØ7–™™ÉclÇŽLOO3†©¨¨0fbbÂþüóO.¦üü|6räH€5kÖŒéëë3---n[U b¬´´”1ÆX||<ãñxìðáÃ\Ÿþýû³ùóç3ÆÛ¾};³±±aŒ1¶oß>¦¦¦Æø|>ûXFFÀ&L˜À´´´˜¡¡!SQQa ,¨qGFF²V­Z1}}}æèèÈø|>sqqáæ«¨¨°Q£F1===fhhÈ0___¶bÅ ¦ªªÊ ™††[·n·Ìõë×fffÆ™ššëÖ­ËÎÎæú˜šš²}ûö1ÆËÊÊbØ¿ÿþ[c¬mÚ´a«V­ªvþ‹/¸×»¼üü|f``À‚‚‚j\E<`˜··7SVVfZZZ `Œ1–˜˜ÈTTTرcÇD–[³f kÙ²%LGG‡`ºººLOO 81ÆXJJ 4hSQQaíÛ·gJJJløðᬸ¸˜1ÆØãÇ™ kÒ¤ ³··gÊÊʬÿþ•ž—®®.;yòdÏ£wïÞlÊ”)lúôéLUU•`ùùùìÍ›7¬gÏžL]]µk׎ñx<6uêTn¹ÒÒR¶xñb¦¤¤ÄŒ™––Ó××g?fŒ1öÛo¿1---Ö´iS¦©©ÉlllXXX·¼ëß¿?[ºt)÷Y|úô)KOOgýû÷g˜µµ5ÓÓÓcÌÏϯÚçPTTÄFŽÉTUUYëÖ­™ŽŽÓÑÑa‘‘‘Œ1ÆÆÇ&OžÌcìÈ‘#Üg£ì€[~~>›Ÿmذåçç³ÒÒR6aÂ6bÄn™%K–0cccvùòeƘð‹ÚËË«Æ(**Š`=bŒ1öóÏ?3---6räHƘðžž;{ö,cL4bŒ±… ²~ýú‰¬³,òôôä’Ã3gÎ0UUU–••Um,'Nd]ºtá’±ììl¶gÏn¾ŠŠ ëß¿?‹ŽŽfŒ1̰‘#GrÛÙ¼y3³´´ä–y÷ˆà¦“““™™™ äÚ$M€âããöâÅ‹jûT—•µÏœ9“µjÕŠ©««³æÍ›³7V».ÆÞ'@ ,`iii,''‡-[¶Œ)))±—/_2Æ„xÝÝݹeŠ‹‹™••·îÇ3ìÍ›7"ëž8q"óðð`)))Œ1ÆD’´!C†ˆ$<ééélÿþý•b3f óõõ­ñyôîÝ›©©©±eË–±°°0öðáCVRRÂȼ¼¼XFFcL˜ «ªª²Ó§O3ÆÛ»w/SUUeÇŽcEEE¬¸¸˜íß¿Ÿ=}ú”½~ýš©©©±µkײÂÂB–’’ÂÆŒÃìííYQQcL˜)))±Y³f±7n°§OŸ²ÜÜ\6kÖ,fmmÍ®\¹Âc¬°°¹¹¹Õ˜>|˜©««³˜˜Ƙð3ròäIϽe PE›7ofFFFܲ˗/gNNN,..ŽÛ·NNNì믿®q?ÂcTDDß¾}†œœ\»v ]ºtºº:z÷îk×®žëÓ§ÈrC† Á–-[àêê ''§OÔÆÐÐ ,€ºº:x<¼¼¼péÒ%n~pp0FŒ¾}ûø|¾ÈaöªØØØ yóæ¸~ý:àäÉ“X·nþüóOäææâÙ³gÈÍÍ…›››ÄñΛ7«…:t(c• ¦ËÓÖÖFjj*W®­­iÓ¦‰ô™>}:¬­­€;éïïÏmgàÀxóæ "##OÉÙØØàÞ½{ ž={ ¡¡Áͯ‹ØØX())¡E‹uZ¶,®ƒâÁƒ˜3g-ZÄ:­É¼yó```---,]º<—/_Ì;!!!xþü9àܹsHKKƒOµëËÏÏÇÁƒ1f̤¤¤ <<YYYpuuÅ?ÿü@ø:$%%qëÕ××ÇäÉ“+­ËÎί_¿®õ9øúúbÕªUèСÚ¶m‹„„\¸pcÇŽERRÂÃÃQTT„:p1ìÚµ ={ö„··7ø|>”••1yòd899áÈ‘#àñxð÷÷‡ªª*š4i‚9sæ <<·oßæ¶ûé§ŸbëÖ­èÚµ+œœœ ©©‰`üøñðð𨪪V:…Z‘¶¶6 qç΃ÇãÁÓÓæææ5.÷Ï?ÿ`ñâÅ8räW¸k×.Œ9¹¹¹Grr2ºwïÎ=oBjB5@¤AôîÝŒ1„††âÚµk\BàîîŽY³f¡¤¤¡¡¡ðóóY®¬~E´µµ‘——ÆJKKW)GŸ>}pýúuŒ=?ÆÅ‹±wï^œ;wÙÙÙèܹ3tuuë+ǃ––w¹yU-Z„—/_ÂÙÙFFF8p –-[&Rt]^YýcŒkÓÖÖ®ýÉ“'>|8JJJàêê ())¡¤¤¤ÎÏ%==ÆÆÆ\T]|þùç\ý½½=nÞ¼‰Ã‡cæÌ™b¯CSSvvvÜ•a]»vE‡°}ûvbË–-˜2eJ¯ÝëׯÁÃæÍ›±mÛ6‘y666€eË–aæÌ™prr‚©©)ŒeË–ÁÖÖV¤¿™™RRRj»âg¢,]½zu¥yeu<‘‘‘Õ^]‰N:‰Ô¹¸¸€Ïç#22Ý»w¯r»ïÞ½Cvv¶ÄŸ™~ýúaöìÙ?~<”••Ñ¥K|ñÅ}ú`Ñ¢E8sæ † Œ5 ÇŽƒ¾¾~­ ê{™–-[âÊ•+Ü/÷_~ùˆ‹‹ƒ’RÝö._¾­ZµÂÙ³g¹‚ñðððzÅ©££ƒ´´4”––JWË–- ºË@kkkãåË—­+??QQQ°°°àÚæÌ™L™2ÿý7~þùçJË•½LLL»w«k•Ûiݺ5þý÷_<}ú·o֭߯[ñé§ŸrG„ʤ¤¤Ôzô¤*e1œ8q‚Û?UõyõêU•󌌌*Ñ‹ŠŠª±ø&˜˜(Q¼ªªªøùçŸñÍ7ßàÖ­[8}ú4† ‚{÷î¡cÇŽ•úÂËË îîîX´h×®¡¡mmm¬\¹ãÆ“(Bº ž4 ¾}ûâäÉ“ˆˆˆàþXhii¡sçÎX¹r%:uê===‰×koo_å•"’êÑ£Nž<É%Aøï¿ÿj]ÎÃà ظq#¼½½£GÆùóçñ÷ßטI+vxüø1·ÎÉ“'cíÚµHIIAzzz×™œœ .ù)((ÀÛ·oë§……ŠŠŠ¸«ª$aee…-ZàÀ\[ii)þøãîH…¸BBB ЫW/®mܸqPQQ——úöí+rµ– ÔÔÔD^/ccc8::bÇŽ"ë.--EDD€÷¯KëÖ­áãダ€ÄÇÇWO(::ZdxqÙÙÙÁÜܼR ÅÅÅÜ{¹{÷î¸xñ¢È6333ñîÝ;ôìÙ‰‰‰xòä 7ïüùóÐÐШ2!)£¥¥…æÍ›s§áû#99¹Æx_½z…ÂÂBcèСعs' ªMÐæÌ™ƒ‚‚ìÞ½[¤Çã¡W¯^صkW¥1”$M†IãDG€HƒéÓ§V¯^ž={ŠÔòôîÝßÿ=¾úê«:­wøðáðôôä~=Ï™3§NëY»v-;;;XXX 77¦¦¦µ^RkllŒ¶mÛ"::ýúõØÚÚÂÉÉ OŸ>E×®]«]vèСðóóÔ)S`aa¾}ûÖøG§&3gΟχ‡‡”””pìØ1øúúÖZÇTøûû#%%ZZZ¸téÒÒÒÐ¥K—:¯ÓÖÖ6668{ö,fÏž-2ïå˗سg7ÖΆ `hhˆÙ³gs—¨oݺÆ Cff&¬¬¬ >Ÿ€€€Z·=uêTôíÛ=ÂéÓ§1kÖ,|òÉ'Ü|uuu|öÙgX¿~=¶nÝ*²¬¶¶6<<<¸ ÕÕÕ±téRìØ±C† ALL ÜÝÝñîÝ;\¹r}ûöÅæÍ›1zôhXXX W¯^(..ÆáÇáïï/ò())Á… °víZ‰÷'ŸÏÇŽ;0zôh<{ö ݺuC||<._¾ŒÉ“'cùòåX±b~ÿýwXZZbÊ”)`Œ!88ÇǧŸ~ŠaÆ¡k×®˜2e âããqöìY¬[·Ž;ºTåË—cÚ´ixôèºvíŠ . ==ÎÎÎÕ.sôèQìÚµ‹«ûùçŸ`ddÄýx(ïĉ ÂÀ±páB®ÝÑÑóçÏÇÆÑ«W/¸¸¸`È!ÈÉÉÁ?ÿüƒ¦M›V:-FHEÊ+V¬X!ï Hã`nn>Ÿ±cÇÂÎÎŽk733ƒ±±1Æ/rÈÇãÁÁÁ¡Ê±EìííѺukÂÓ"={öÄ“'OÀãñзo_hiiAWW½{÷æjMÌÍÍÑ­[7‘õÀÝÝ€ð4ÁôéÓѳgO 6 ëׯǽ{÷`ff†ÖøÜÌÌÌЯ_?nŒ@ø‡¾k×®pqqékaaÁÓÑѧ§'bbb‘‘7774mÚÊÊÊèÝ»·È)‡=zTûGÉÍÍ ùùùxýú5222àéé‰%K–ˆ$pnnn"ûXII îîî"GÞTUUáîî---8;;ÃÙÙ?†¦¦&V­Z…®]»¢mÛ¶"¯a÷îÝÑ´iSðx<.öšŽæ%''ãèÑ£ðõõ©-ÉÈÈÀ³gÏ ££ƒÞ½{CGG|>;väjqZ¶l‰aÆáÙ³gˆˆˆ@¿~ý°ÿþ½²ª ,, Œ1|ñÅX²dI¥¾?Fdd$+Õ½ 4ZZZxúô)СCX[[ÃÇLJ+Äåñx1bþ÷¿ÿASSÈÎÎFDDrss1~üxÌ›7OäôßùóçqðàAlß¾½ÆBggçJ§ºZµj…I“&!##/^¼€ššÆŽ‹iÓ¦AMM ÚÚÚ˜>}:455‘Ì™3‡K–ÇŒ„‡‡ÃÐЫW¯)Ôæñx°··IÀÙÙ:uBJJ 444°råJtïÞŸ|ò ZµjUåsh×®ôõõñæÍÄÄÄÀÉÉ Û¶my¿899ÁÁÁ™™™°´´„¥¥%´µµ¹‡¹¹9Úµk‡&Mš`æÌ™`ŒáÕ«WÈÏÏGŸ>}°xñâz×ÝÅÇcÒ*@ ä#—™™ Ç}qÆÆÆÂÙÙAAAðòò’stŠ%%%Í›7dž 0}úty‡Ã)))A‹-àïïÿÙ¦@ @»ví0dÈüðà ²MBˆÖ•”” 66b/óîÝ»æÆ“DvÂÂÂ`ddGGGtêÔ ðòò¢äGŒŒŒˆÏ?ÿœ«‘ù?~©©©•†¥²S¶âœÂ#„HBò÷÷ÇÎ;ѤIîtBpp0tttªì‡áÇ#99¹¹¹˜8q"ë|Å ù¸ „……!22***hÛ¶m•· ÒsñâEèëë׫¦HšÂÂÂPXXXéT©¬dddàêÕ«hß¾=7>!¤a(TtàÀôïߦ¦¦Üø+³gϹsxy£FB³fͰaÃdff¢cÇŽX¿~=ýâ'„BœBê˜4iwÃHMMM”””T{Yiqq1Ξ=Ë œ¦§§‡Ñ£GãäÉ“ /!„BäC!/ƒ_¶lBCC1f̘jG}ûö-Èag[[[„††rÓ/^¼À‹/d/!„ò¡PWW¯õÊWE  ’’,--qýúu$%%ÁÒÒ²RŸ²"éòÿkhh 33“›>|ø0>,2š¬$%%A__Ÿº^RÑÑÑbÿ.NßšúT7¯ªöŠmÅÅÅ•^“ˆˆˆjG°•&@€”””Zï9TI^£¬¬,ÃÐаÚ>ÒÚÇoß¾…¶¶672oUýjW£¸$}jÛVZZø|~•—DW÷•}7”‚ ª÷mÅmÇÅÅÁÄÄDä;EVê³%]^žß_£¼¼<¤¥¥Uù.mU½$!É>ç»EVû¸b[aa!îß¿¤¤$±bÿ¨5øíW›5kV•óÊîH““õmÛ¶yxxpÓ, @Öa2Æ„wkŽŠŠªóò’Ä)NßšúT7¯ªöŠmééé•îÜ=xðàZ㑆ÄÄD¶}ûö://ÉktóæMvþüùûHkWºkzÅ~ µ«Ú¶$$}jÛÖùóçÙÍ›7«œWÝkÂBBBDÚªzßVÜöŒ3سgÏjYêû½ô±|_T|nÝºÅæÍ›Wk<ÒPÕû@’ìcq¾[dµ+¶%&&2SSÓcQ }ñÅlüøñUÎLGG‡Ý¹s‡k›?>›1c7Ý Pjj*+,,¬óò‰‰‰Rí[SŸêæUÕ^±­¸¸˜½}ûV¤­¡öqQQKII©óò’¼F¹¹¹,++«Æ>ÒÚÇéé鬠  Æ~ µ«Ú¶$$}jÛVVVËÍÍ­r^u¯Qvv6ËÎÎi«ê}[qÛÛ·o¯×s—D}·ó±|_T|¢¢¢ØÞ½{kGªzHB’},Îw‹¬öqŶƔ)L´@ À„ pñâE¼~ýÇGPPFÅõéСÖ­[PQQÁäÉ“±bÅ ÄÄÄàæÍ›øí·ßtüò ¡ªªZçå›6m*Õ¾5õ©n^UíÛ”••k¼Á¢,ñùüzÝB’×HSS³ÚáÊHkëëëW:í"ÉûAÚê³mI_£Ú¶¥££#rj°¼ê^£²Ñ†Ë«ê}û±îcI——ç÷…8Ÿ#Y©ê} Iö±8ß-²Úǵ­[‘)L ²²2ÌÌÌðõ×_#>>Ø´iFŒÁõiÑ¢ŒŒŒ¸éŸ~ú ,€‡‡ Xã}›ˆlа²GûXöúôéSçz"###ôìÙSÞa¡P Ð?þXcŸcÇŽ‰L«©©aÛ¶m² ‹ˆáÆhÛ¶­¼ÃPh´eïáÇ044¬ó… ¤vYYYxúô)Z´h!ïPˆP¨¥­ì>±5Ý/öÎ;xöìYÃDˆ”©ªªbüøñòƒòHJJ‚³³s£¸ LaŽÉË¡C‡pãÆ 899É;B$"ðçŸRDi”(’‚ñãÇ7Ø£ ‘–ŒŒŒz%CD¥¦¦BOO|>}­ÊŠ@ @NNNãk".…¹ ŒBä)$$Dd U"})))"£õRôS…B¤ÀÛÛ[Þ!(ÿüstèСÆu—””`Ë–-¸|ù2²²²ðÉ'ŸÀ××mÛ¶œ8qû÷ïÇÛ·oáââ‚o¾ùFFF„§çÖ¬YƒÅ‹cãÆxøð!Úµk‡ü¯_¿Æ?ü€¸¸8Œ3“'O†ŽŽ ((ùùù044Dpp0ÒÓÓ1|øp|ñÅàñx >ÚÚÚÜQ™µk×8€£G")) m۶ŪU«`nnÈÊʆ  @{{{øúú¢S§NܲÇÇÛ·oaaa///îÆ£ß~û-|||йsg@^^¾ûî;\»v ÚÚÚðôô„¯¯/·ïöìÙƒììl´lÙAAAÈÍÍ…¼½½éÞSº˜ìѽÀˆ4Ñ)0RÉš5kðûï¿ãæÍ›UÎðà\\\’’‚yóæ!;;ݺu«¶™Ñ£G# -[¶Ä¤I“˜˜ˆ-[¶~þùgLœ8æææ˜>}:®^½ŠN:!''œœŒýû÷£_¿~044ÄØ±cqñâE¸¸¸`È!°²²ÂðáÃñí·ß"00Ûfhh(.\ˆ-[¶ W¯^033Ã’%K°~ýz®Ï‘#Gàïï#F ""‚+²üâ‹/ðÍ7ß {÷îøòË/‘˜˜ˆž={¢°°{>§NÂĉ1mÚ4âàÁƒ€­[·ÂÏÏçÐ IDAT½{÷Æ‚ `gg‡ 6pÛÛ¸qc·±q#c€ìÖÖµÇòÉ'Ÿ°ÀÀ@ÆccÆŒa½zõbŒ1vâÄ fhhÈõ6lëÖ­›È²ýû÷gÕ®û¯¿þbXHHˆH{jj*ËÏÏgúúúlÅŠ\{AASWWgk×®eŒ1víÚ5€EDDp}vìØÁTUUYjj*×¶råJÖ£GnzÊ”)¬oß¾"ÛôõõeVVVÜtÓ¦MÙøñãY~~>×öêÕ+¦¬¬Ìþùç®-++‹©ªª²S§N±’’¦¦¦ÆvîÜ)²îÂÂBÆc^^^lèСUÎcŒ1]]]väÈÆcGŽaXTT7ÿþýŒÏç³ØØXÆc>>>¬gÏž¬¤¤Dä¹M™2…ÕUzz:ÓÓÓ«óò„Å“˜˜ÈLMMåFƒ cµræælÜ(ûíHZn²jÕ*899áÂ… •æÝ¹sÓ§OióððÀwß}Wíúnß¾ ]]]ôèÑC¤ÝÐÐ?FFF<<<¸v555tëÖ wîܩԿŒ‰‰I¥6 ¼zõJdmmm‘éîÝ»cÇŽÈÍÍ…–– gÏžPWWçú„……AII [·nÅÖ­[¹vMMM¼zõ JJJ0`¾üòK}ú ÿþhÕªU•ûæÎ;°µµ… ׿ááââb͚ܿ5èëëCIéýA[kkë*_B!µ£HÎÚ·>>4vvvøì³ÏðÕW_᫯¾™— SSS‘6cccäå塸¸¸Êˆœœ˜˜˜T9/;;ª\gMc~”OÊðù|0ƪb!š¤êdeeA]]cÇŽi;v,'OžÄž={pâÄ cîܹسg<==1}útØÛÛcÛ¶mذaæÏŸ9sæ`s_UíOðù|nßTEœçJÕÉÕi¢ R­€€„‡‡ãÈ‘#"íööö¸zõªHÛ•+Wмyój¿üèèèJóÊŽŒ”_'c !!!Õ5©Ë—/ÃÚںƫ°œœœ gggŒ1BäaooPVVÆŒ3páÂ$%%aРAøé§Ÿ¸uôìÙÁÁÁxýú5öìÙƒ­[·"%%¥Ò¶ìííqïÞ=®Þ ®]»†ââb™<"T${TD¤‰ R-333Ì›7¯Òx5³fÍÂåË—qðàAäççãðáÃøóÏ?1{öìj×5bÄ4mÚóæÍÃÿý‡ÒÒR\¼x6l€‘‘FíÛ·#44éééX±bRSSáççWïç‹/^ ==?ÿü3Ž;†E‹Õ¸L×®]áââ‚I“&q§ÔbccñÓO?áòåËHKKÃÔ©SÆòòòššÊâó÷÷Ç•+W  ””GGÇ*¹Nœ8X²d ñàÁ¬_¿...ÜeäÃGãÉD¤‰ R£Å‹su-e¦M›†yóæaêÔ©ÐÓÓÃĉñÙgŸÕ˜éêêâìÙ³ˆ‰‰AÇŽ¡®®ŽñãÇsGa¶nÝ KKKôìÙ¦¦¦ Äo¿ý&•±ˆÑ­[7âË/¿„ŸŸfÍšUã2JJJ8qâLMMáääUUUX[[cïÞ½hÒ¤ ÔÕÕñúõk888@[[†††ÈÉÉÁ—_~ PWWÇ!C ¥¥bË–-Už¶311ÁéÓ§qúôiXYY¡}ûö(,,ÄáǹKõ !„HQAµV¬X!òoUüýýaccÿ† êRPP€ÈÈHØÚÚBSSSìå233‘ ssóJ AZZÞ¾} ;;;(++×;Æ©S§"33ÇŽCBBÌÌÌ ¢¢"Ñ:bbb`dd‘y999ˆ‡¾¾~¥:ž¢¢"¼yó<–––µÖ†0Æ mmm4mÚT¢ë"##666ÈÈÈù¶ª’=ª’½¤¤$8;;#))IÞ¡È}RI©««£uëÖ/§§§==½*çÊäËÏçÃÊʪN˪ªªÂÎήÊyÚÚÚ\MPE***°µµ{;<-[¶¬SŒDþBBBàîîN§Ád(%%÷îÝ£Ó`D*(" ÍÊÊJ¤¸˜Yñöö–w j€ˆ4QDÚÊ•+å!„ABˆ¤¦¦¢¸¸XÞa(4@PãØ`„H‚ "–ôôt$''W;ÿÍ›75ÚW@€èèh”––ŠÕ¿´´ÑÑÑm‡†@ãÉD¤‰ "–•+WbÊ”)Ü´¯¯¯ÈÕqÝ»wÇÞ½{%Zç£G`kk‹ôôt±úgeeÁÖÖ÷ï߯4ïÆ•nÏAHC¢q€dj€ˆ4Q ©É; ©ræÓÞ>í}äáX?žžž˜3gŽT†õöì,,,$ZÆÏϾ¾¾øñDZwï^¬^½ºÞqÒ¨Hö¨ˆH#„EÏãÆC‹-ðÝwßÕ{}ùùù¸zõ*z÷î-Ñr#GŽ„ºº:f̘œœŒ5ªÞ±Ò¨Hö¨ˆH"€M›6áâÅ‹5jfϞ͵wïÞ“&M{ 8pàš5k†ùóçK‡ªª*¦M›†õë×cáÂ…PWW¯±ÿ™3gpóæMܾ})))X²d ´µµ±lÙ2‰¶K!¤q¡ˆÖßTj/+‚8p :vìȵ?Üô‚ `llŒ{÷î!..óçχ¿¿—À›››#  RŸ9sæ@CCÓ¦MiWWWG@@,-ß×3©¨¨@]]nnnpss¨©©IðÌ ‘ª’=ª"ÒÄcŒ1yñ¡*»ÕCù[>Täïïøûû7LP„HIFFlll‘‘!ïPÂñãÇ©HƨHÆ’’’àì쌤¤$y‡"sôS…B¤@šQ ‘&*‚&„BH£C !„HAjj*Š‹‹å†BHK“ý½Iã@ !„HAHH233å†BKIIAhh¨¼Ã ‚ "–U«VaÊ”)ÜôÎ;qìØ1nº{÷îØ·oŸDë|ôè‘DE¸YYY°±±Áƒ¸¶`ýúõ˜={6,X€£G¢¤¤D¢8‘Höº(:(÷uC %@D,iiiHNNæ¦/]º„ÿý—›~óæ ²²²$Z§@ @LLŒØ Kii)bbbPXXÈÎÎFûöíqòäI0Æðöí[L›6M$Q#„qwî›6£G––€­-ðùçòŽŒÈ ]FêäøñãÕÎ+,,ŸÏ‡²²²LcPSSÃÝ»wÑ©S'®m÷îݘ1c¶mÛ]]]™nŸòh Ù“æ8@ïÞ7o7nwï•û……Õ{Sä¥PŸÔüü|üûï¿HJJB·nÝмyóûÇÅÅ!>>^¤M__² SÔž=ÀÊ•²ßŽ¥%Pùó­[·â‡~¨Ô¾hÑ"Ì;·R»¬¬¬DÆHzñâúöí‹þù|>óæÍÚ5kÀãñj /##...øñÇEq‡††bÚ´i¸}û6””DXªªªŠ$?‹-Z@GG§Öm"Mt/0Ù«ë½ÀJK§Oß';7nâ-›‘!<fc#q¸ä§P ««+ø|>ìííñÕW_ÁÎÎW®\©¶ÿÖ­[$2²p÷îݱuëÖ†W(+ ˆ‰i¸íUÃÛÛÝ»w禃ƒƒ±mÛ6ôéÓ§Êþoß¾…–––HÛ©S§°|ùr¬]»‡ÂO?ýxyyÕº}}}}´k×Û·oùrÛ¹s'Ú´iCCÃk…öíÛ‡ëׯãîݻؾ}»XI!ÒÔPã•– ^(+ªª€ŠŠðÑ<‰[”•ܾý>Ù¹uKØVWP¤ˆê# '''ÂÓ0–––ܯ²êx{{ã—_~i¨?X¦¦¦055<|ø[¶lÁ¯¿þÊíOq,]º³fÍtêÔ ·nÝÂo¿ý&Vïß¿ÄÆÆÂÊÊ ™™™8~ü8N:U벿üò Þ¼yGGG:õEVQ0aPîúeÉPùĨ¦6I۫뫦èêzz¢ÿêê çÉZD„èé¬'O„‰¢¤ÌÌ€nÝ€„áúÊüéÂBá)»ˆà¿ÿ6!$8søõW`Ë–Møî;`ñbÀÏ?~† zöÚµll6ÁÐPxzO[hÒd ¬¬Bѳ§p™3g6UH~ªÇÀptÜ„U«€+W€ÌL`òäMز?^˜ü”íoggÑåË.…ÿÞoÒœþû￱iÓ&xzzb×®]g@O¦€ÂÃÙ‘‘Û½{wý¢££Yll,KJJb—.]bÖÖÖìûï¿çæ0–žž.ò(ÏÏÏmܸQ&Ï£¡-^¼˜ÙÚÚ²ÔÔÔJóüýýÙ€¸éÁƒ³9sæpÓVVVlóæÍ"˘™™±yóæU»½»wï2,%%…kKIIajjjlíÚµLUU•%''sóÒÓÓvëÖ-Æc•ÖyõêU€Ý»wOŒgܸ¥§§3]]ÝJm4ýaM§¤0Ö©cc@úÿÿ˘šcéLE…1¯òüÆ<Íã1foÏØ„ él×.Æž>e¬´TüýŸ—ǘ²²èúÓÒ>Œ÷ƒ,¦óòòXzz:‹ŽŽfáááÌÄÄ„5 u ^¾|‰`ÅŠðññ©±¯µµ5÷ÿ~ýúáÿûþþûo,]º”k×ÓÓƒ¾¾~µëPkˆÜ àôéÓX·n–,Y‚K—.qí-Z´@çÎÅZÇüV­ZA `çÎÈÌÌÄç¢Ñ¤IŒ5 _ý5F“jûž={›6mÂĉѪU+DDD`Íš5pwwG‡$ÚncUñèZÅ÷:MËw:?_ýú ¯`úÿ„GG.]LLÞ÷/.ŠŠô¹Ó`‡è´@Póüºö/(gfê#3³ìÿ@V–>D&Tü.•δ¦&àâtë¦nÝWW@x1^Ýö¿†`o<{ö~þƒ€»û‡õþÖ´††444 ¯¯¤¤¤Fs‰B%@7nÜÀ˜1c°~ýzŒ7®ÒüÈÈHèééqµ!¥¥¥"—VÇÆÆr…ÀMll,ÜÜÜpóæMÜ,wò{ðàÁèܹ3Z¶l ®ý“O>™™7íêê ]]],]ºqqqpvvÆÍ›7ѬY³j·©££777‘õÀܹs‡y󿉴óù|¸¹¹qEÎ...pvvÆŽ; cccxyyaÅŠæL>Ò®ŠŽúö"#EÛ]]…u@—ñù‡††T6/5yyIŠªûMóssËÖ&++C‘Úví¤œ³3ðìÙûia$Ýmùâ1Ƙ¼ƒ†ÜÜ\4iÒnnnèÚµ+×ndd„9sæ,--1uêT®¾¥S§N8p Z¶l‰ÇcÇŽ …óÿŸ.ã¦üX7ùûûÃÆÆþþþ²yb„ÈHFF†D·"!5;~ü¸ÔÆ &?oÞˆ¶{xëg´µë½‰JI‰0zõ*/^ÜÔ)²¿Æ?_~ù~zòd`ÿ~™oVî’’’àì쌤¤$y‡"s sHYYK–,©±ÏÂ… ¹ä¾ýö[üõ×_¸xñ"š5k†û÷ïÃÎÎNÖ¡B´Æzðèß_X8\ÞСÂËß䬻D”•CC KstéÒ0÷+÷§] ¦ˆ&RWW¯ñH L€Ê¬'ràíí]§èƒooáé•òV®â’÷Ľ˜´P¤Ø("b¹sç._¾ÌMûúú" €›>~ü¸HB$ŽÔÔTìß¿bõØ¿?ÞU¨ýõ×_a``€>}ú  Ê»Úò!úå`Ò$ˆŒ•Ãã›6Ë—Ë/."T±ˆ ÅBg•I\¼xÊÊÊòÛ¶mÃ×_ƒbøðá„Gƒih’Ž´~½ðvå)+ ïCUË®–@ @NN d{=|(š†S ”ÉÙ¥´4ªËü$d¤¢‚[´¨vþ¹sçpüøñJí^^^Urþõ×_abb‚ &pmX½z5._¾ 555ÌŸ?_ì«ì222°`ÁÌŸ?_d¨‚ÈÈH¬^½?ýô“È •€pì§o¿ýëÖ­ã’•V$¤!„„„ˆ=вeÀw߉¶©¨O‡%£@JJ îÝ»×`§Áš7tt„÷b„ƒ2¾~ ÔðUJ>"”ÉÙ³¼<ìo€§¬ÕÕkL€LMME‡bÿþý3fL•ý¯\¹[[[‘hùòå>|8†Šàà` 6 wîÜAÇŽkO__/_¾D`` öìÙõïܹÏž=ƒ¡¡a¥û._¾Œ´´4ôêÕ »wïFJJ 1xðàâèi\Ĉ1`þ|àçŸEÛ54€ãÇO?•Qp ¢¡k€x<á(Óåë®< HQP tîÜþþþð÷÷ǤI“põêU,]ºƒ {ß|ó Ž=ŠÏ?ÿaaapppÀ–-[Ä^ÞÏÏGEVVáÊýõWøùùUÙ?&&<ƒ Ÿþ‰;wî`Ò¤IpwwGaÅŠRB䬤˜6­rò££#¼µ%?&*„V\”%%%;v,°jÕ*‰–ÕÑÑ™vuu•¨0zÔ¨QPSSCpp0áÍU 0vìØ*û¿}ûÖÖÖxõêNœ8'NàöíÛ¸{÷®ÈQ$BBMã À˜1•o¥`h\¹¸¹5@€  !Ç*C…Њ‹NÉYìupùv´Å<%ôÕW_áõë׸wï^¥šIijjJ40œºº:¦NŠ   øúúb÷îݘ}бcG̘1999HKKêU«P\\ŒñãÇK´]B¤-5Ux÷öŠÉ½½°¨–’Ÿƒšàè(ÚFG%@€ðj«ÜÜ\,X°fffÜcÁ‚b¯ãÒ¥KÐÓÓCÓ¦M±bÅ ìܹ®®®Çâçç‡ÔÔÔj‹ŸË())áàÁƒHLL„š6mŠ}ûöáèÑ£hÕª•ÄÛ%¤>Ê×%$½zaa¢}œþ,-å G @§Á#€ÀÀ@V;ãÆ"ÓçÎ™Ž‰‰¤¥¥!!!vvvP«å¶É:uc¬Rû¨Q£ªl××ׯÔnoo»wï"!!¹¹¹hÑ¢E½k—Èǧ°HOÞH´ìß²ÿ++ ojin.|˜˜ÈæÞZe5@YYMз¯p¼˜òºvþüÐ×—þ¶ yÔÂBèÞOS¤("RehhØ`£´–gnnÞàÛ$ÒSZ dfVÀTõoÅ61ï¦@XÏaj*š•=Ê·Iú6öööÆóç@¿~@|¼è¼>}„7<ý@Êý>Zò¨è¢¢ˆ"5Â#Õ%.Õ%2YYÂABq±0A©˜¤T¤®˜™UNŒ*&KÚÚÂþÿý  ,-oøpàÈa- ù8UL€^¼¾×ÕÕå‘J€!u’-ü%|ÿ¾ðÿÿÏŸ‹ÞØócVPDE 5ÑÑ&Bññ©ÈÉÑCù¯Õ €}û„GHý5ô½ÀÊVV@l¬pº¤xüèܹAà RFKBH­RSEû÷W¯î¨Mmtt„¤ „56åÿŸ—',JŽþûö­ð”›´dgááÀ€p __`Û6ÙÔ5Vòª„GÊ @˜üSôq£ˆˆåîÝ»ÈÈÈ@¿~ýªœüøq´nÝŽ¯­Ajj*~ÿýwŒ7®Ö‚i@øëïСC2dŒ* ‘‘‘ ±ÖCj–ð>É)KxÊñËŸèéUŸÄÔô}}a¡³¸Š‹¤$áó,{”%GåÿŸž.é³x?Ð_ïöN¤K^5@€°úìÙ÷ÓTôñ£ˆˆåСCxþü9—uêÔ ¶¶¶8vì`Ñ¢EX´h‘D PTT¦M›†¡C‡Š•¸äååaÚ´i¸uë—]¹rsçÎÅ‹/ ¤¤„ÁƒcûöíT-&Æ€'Džä亯Çlm…µ3’$2î¢"S|¾ð2ôÚ.EÏϯ=IJHa*oÕ*áÝÞ‰b¡BhÅC ©“ï¿ÿÚeÕŸròìÙ3 2sçÎÅ•+W››‹ñãÇã³Ï>ÃùóçåÛÇ¢°ã&æURV€Þ?œ]]éÆ(/»~×vçïŒ a"ôâE*´´ô0`}­Êмj€€Ê УGÂ<^ƒ‡B¤„>©r–û,Ùw²e¾eme{W;ÿÑ£GUÞ?«C‡hÛ¶måõ)+C¹Šó¡¡¡¸|ù2ÔÔÔ0aÂX[[‹_VVNž<‰¢iÓ¦\{FFNŸ> OOÏJËAGGkÖ¬ábùå—_бcGÙiÛV˜$4vúúÂdzg!èÔé} ‘>yÖÙØX–ÍÉ"";»…H %@r–~) "d¾ukõ —/_âôéÓÜtZZ®_¿Ž;vT™Hlܸ¶¶¶èÒ¥‹H[QQœºcMM IDATñ÷ßcóæÍøï¿ÿ`aaQk|:::Xµj^½z…ï¾ûŽkß·o6mÚ„É“'Wº-G||<:tè ’ˆµk×|>ááá”Õ‘¦&Юh²Óº5 ¢"ïÈ>lUÝ ŒH—>Â/}J~¡: ECG€H%K–,ALL îÞ½+Ñm%œ¡òÿ)MMMÑ­[7ܹsGìå}||ðÍ7ßà?þÀ°aÃpç΄‡‡ã³Ï>«²ÿ¼yóŒ:`èС(..Ƶk×’’ÂÝžÔL]]ô-©»ÔÔTèééOƒþÈŒ|¸HMPy†††¸wï~ýõWDDDÀÌÌ 3fÌ€««+lmmë;!’*»X“&T$+ò¬è¢¡HÎ û°¿|~ÍTôøñc|öÙgسgÚ´iSïõݸqvž ÷óóCŸ>}ðêÕ+>|'Ož¬±¿ŽŽfÏžÍMïÚµ Íš5C§Nê3!uE5@²'ï GGá-M …Ó‰‰Â5ML䩪"€ôôtxzzbÖ¬Y=ztÖQTTÄýÿîÝ»¸}û6FŽ)Ñ:ÜÜÜ`gg‡Q£FÁÔÔb/{õêU,Y²Ë–-ãNÅBˆ´ðù Ê££@/J€`íÚµˆŒŒÄ©S§àààÀ=–I0¢ÛÒ¥KáêêŠ:ÀÅÅcÇŽÅøñã%ŽÅÏÏ>„¯¯/xµ ²áíí 777ØÛÛcРAøþûï1cÆ ‰·IH}¥¦¦¢XQn„öHKK“k OƒÝ¿/Ÿ8HýÑ)0˜1c T©ÝÌÌ 0gÎäææríkÖ¬F¹A`Ž9sssÜ¿qqqpvvF=jܦ½½=BBB §§'Ò>}út´k×®Òi,mmm„„„ÀÉɉkóóóCDDЮ];ˆÿ¤ ‘"ª’=y×T­HxŒ}(·3üð¬X±BäߪøûûÃÆÆþþþ !R’‘‘dddÈ;B>¡¡@Ïžï§€çÏå´%%%ÁÙÙIIIòEæè!„"¦víDoñòeåûÁ‘%@„"T${B ŽмùûéÒRàñcùÅCêŽ B‘‚dffÊ; …–’’‚ÐÐPy‡Aã)J€!D ¼½½©ZÆä=P™Š…Ðt%ØÇ‰ B!DtH1ÐeðRpêÔ)DGGË; ÒÈ @ùk:ù|@Y¹úþ…eÃÙ© {Éž¼ïV¦bôø±°H‚['’}RëÉÓÓ666òã£vãÆ tëÖMÞa|Ô"#íÛßO+++VÚÚÂéêöñš5k&ÀF€Æ’½a °°ŒŒ€”át^žðj0¹†E$DãÕ@œq€ùLž 8ð~zäHàøqùÅCˆ¢ë×øë¯÷ÓÁÁÀرò‹GZh BÈG##£r²Cw!D¶hDè%@DîÃ/ Y:tÈÏ?me%üuZícÙ£q€dïC¨ ÝìãG ‘»Ó§OË;„ZPè´OåbLÚDzGãÉÞ‡2@W‚)ªªÕ‘]XPþž±JJ@t4Ь™ÜB"¤Q()Ž ]þèkBðÿ÷þhQ !ä£PñèOÿþ”üÒ”•6mDÛè(ÐÇ… "wá—†,äå ëÊ«®ø™ö±ìQ ì}H5@Bì("rGõ)usì•õ~ÚÄ:´ê¾´ej€dïCª¨ècG!¹óóó“w¥Š§¿¦LTTªîKûXö¼½½å‚ÂûPîV†®û¸)Ô Í›7£GhÙ²%¦L™‚K—.ÕØ¿¨¨ .DëÖ­Ñ£G=z´"%¤~^¼*þž>]>±ÒXµm+zÅeD“#¿xˆd* ÇâÅ‹ñçŸÂÕÕ#Fü{çEµ¿ñw[R!„@z/†*`¡*U@Då"zñ^ÀbDƒ ~rEŠÒ‚ .õRUZèÒ;¡$Þ³m~L¶Ìîf³IæìÌÎ~?Ï3ÏÌ9sf曓d÷sÞsÎ`¤¦¦[~òäÉøë¯¿°iÓ&ÄÆÆâŸÿü'Ž;æÅˆ €ü)eÁ±õç¹ç€Æ‹/OuÌò±Gn  aC[šã€s礋‡(Š@‹-‹/¾ˆÆcܸqhÒ¤ 6lØà²,ÇqXµjfΜ‰F¡W¯^xõÕWñÓO?y9j‚ü)¥Ã`ÿLKjý¡:fy€Ø#7@Fh_FQÈžüü|\½zO<ñ„Ëó©©©HKKC‹-¬y­ZµÂ•+W¼"QùSJÇæÍÀãǶtÅŠ@Iöªcö :”BeŒÜ<@¡}Å  ñãÇ£M›6èÝ»·Ëó‹¾A‚ƒƒ­yaaaÖ| qqqˆlöPšÒÞN »¿bñÚk@… ò‰Ò”ö§´PÅ âó$½mÛ6ÄÆÆ¢[·n˜;w. üEÎ=uêTlÞ¼ûöíCÕªU]–¹sçêÖ­‹üü|–.]ŠŸ~ú @3A{‹¤¤$T¯^]ê0|‚;w€úõ³Ù–wê”s3¼#TÇìIMMEDD´Z\Ë ½^œœDFFJŠ•¤$áìÏAA¼Z£‘.¦ò@3Aû(f³&LÀü+~ jÕªÐh4HLL´æÝ½{5kÖôF¨„äOñœ+„â§]»’Å@uì ÈÄ9z€ªWªU³¥ øQš„üQŒ2™L2dnݺ…}ûö!**Ê©Ì;ï¼cê^¡BôïßË—/äååaýúõ4—‡?Å3Ìf`åJaž§Cß©ŽÙC öÈÑÚWQŒÊÎÎÆæÍ›±uëV„……A¥RA¥R¡iÓ¦Ö2›7oÆ9»1ŠóçÏÇ–-[P¯^=<ñÄxî¹çðÒK/I>A”È®]€]ƒ%‚ƒ×^“.‚ xÈí›(¦³ºbÅŠ(ÉÎtïÞ=Aº^½z¸xñ"Q±bE„……± ‘(ò§x†ãÜ?Æááž]KuÌò±GŽ €¯¢˜ òP»vm?Bþ”’yôغU˜Wܧ® :fy€Ø#G@ÈW!DHùSJfÕ*~D M›O?íùõTÇì!{äêjÔˆŸÚBJ àÐá@È@á8vѺ_!Ôj~]0{¨Hþ"$Çæ›(W¯ÚÒÀ¨Q¥»Õ1{h-0öÈm-0{¨Ì÷ DHùSÜãØú3hP¥JéîAuÌò±G® €/BÃÉ!Jñddë× óÊÒýEuÌšCŒ=rõ$€|j"óË/@~¾-]§Ы—tñášV­„Ë_ܼ deIQ2$€É!Jñ8v½õo¸,-TÇì!{äìªPhÒÄ–æ8àìYéâ!J†!9äOqÍÉ“ÀéÓ¶´ZÍ  ²@uÌò±GÎ €ºÁ| @„ä?Å5Ž­?}úµj•í^TÇì¡y€Ø#g@È× D2$/÷ÿØCsÿ„¼!ä["$‡ü)ά['4PV«¼øbÙïGuÌò±GÎ ÀY]¸ÐŸ„|!DHùSœqìþzã   ß!9äO±a0?ý$ÌÃüLuÌò±Gî €/Aˆò§ØØ¼xüØ–®Xc…ªcöˆ=r÷$€| @„ä?ņc÷×ë¯AAå¿/Õ1{ÈÄ_ð5h„…ÙÒééÀÝ»ÒÅC ‚ wîü!Ì{ûmib!¢l¨TÀ“O ó¨Hž"$‡ü)<+Vf³-ݾ½óiY¡:fy€Øã  €F‚ù $€É! /|V®æ‰9ó3Õ1{ÈÄ_ðäòÊ1µAˆùS€]»€ÄD[:$xõUñîOuÌž¡b¸Õ ·ø‚ ä+P AÈGóóðá@x¸4±Q>Z¶ÎÜ~û6‘!Y8D1"$Çßý)[· óÄ^øÔßëØˆ=¾â š5æ=+M,Dñ"$Çßý)«Vñ ZhÖ èÒEÜgø{{ò±ÇW<@uƒù$€ÉñwŠc÷—Ø­?Õ±7 y€Øã+ €F‚ù²@8|ø0€ã8ìÚµ [û ÂÇ9p¸zÕ–F’.‚ ÄZ€älÐ’%K°|ùrÀ† 0räHÌš5 cX¼’âÏþÇÖŸÁƒ¨(ñŸãÏuì-ÈÄ_ñÎèâE@¯—&Â5²@ýõ—µ©séÒ¥øòË/qìØ1>|&“Iâè1ñWJv6ðßÿ óXé{­coB öø’(2¨[×–6xDÈÙ     ¢  ÇG—"W(ÇqxðàÄÑbâ¯þ”µkÜ\[ºn] W/6Ïò×:ö&äb/y€ MašºÁä…lÐàÁƒñÁ _¿~hÖ¬Z¶l‰û÷ïãÞ½{¨^½ºÔáD¹qìþzë-~!‚ ”ù€älЀ°páB<ÿüóX»v-àÌ™3˜={6t:ÄÑbâþ”sç€ãÇmixóMvÏóÇ:ö6äb/y€ &wd%€222°}ûv«Ç祗^ÂÔ©SñÄOú÷ïwß}WÊ ø£?űõ§O V-vÏóÇ:ö6äb/y€g$÷É `ΜP˜Í FbÈÇqœÔAXÈÈÈÀË/¿Œ«W¯bÔ¨QxóÍ7ѰaCÉâ‰ì B þüèß_85þÆü0‚ ”EåÊ€}£ÕÍ›@½zÒÅãŽÝ»çŸÂÃ'#3sŽÔá0GV-@+VÄž={pàÀh4ôìÙ]»vŪU«——'uxQ.?滹žyF(~ªW ..‚ Øñä“´œ}@þÖP,+d¡^½zøâ‹/pëÖ-L›6 ;vì@ݺuñüG•:Ü£k“’’““Sâjò‰‰‰¸ÿ¾ ¯bÅŠhÚ´i™ã&JGRR’¬ŒíÇ{x&Oæ0u$, ˜1xï=ökx‰…ÜêX‰¤¦¦"""Z_ù£ðAôz=rrr)u(¥FŽ#Á»¿–&©m ÐÒ¥KQ¥JÄÅÅᥗ^Âõë×±víZ 2ÄíuF£7nÜÀèÑ£ñý÷ß—øœ… bÀ€;v¬u[°`X?árò§œ9Ã{xÞz˵øyõU~t×øŽøäUÇJ…<@ìñUàÜtö¬óÔÞÄ`þ÷?až¿ Ù~„wêÔ £FÂîÝ»ñßÿþjµ}ûöE… Ü^·{÷nÌž=ׯ_÷¸gèСX²d‰ae@þ”Œ `út~™ “Éù|óæüœ>Ý»{?61C+¡C‡J‚âñePÍšüD¨av6pãPB'3öíìõzµjüÔIȶ¨U«VX´h._¾ŒqãÆaÿþýhÖ¬Y‰]`}úôÁ¾}ûèÈðŽV­âGw}ÿ½³ø þïÿø–!_?AÈ9ù€…ôþ=R#ë÷ÚµkXµjV®\‰;v //É@kÖ¬AÕªUѼysÌš5 f»1ÎGž}û›=”.Ú~Žo>ÿìY uë}=Úþ­Çv~øp`åÊ}øøc@§ó~|b¦-u,—x”˜NMMŇ!5rŠO éÝ»w æ’:žÒ¦+W¦7o–&Ž6olŸwWÑ Á>ÌŸ?þù§à;PÉÈVMŸ>mÚ´Áo¿ý†-Zà·ß~Crr2~ýõWQŸ3nÜ8\ºt çÏŸÇ·ß~‹eË–aΜ9¢>ƒp·ý)™™À„ @»v®çâhÚøãà×_¥[»KlÈÄžøøxZ§1™™™>뜻»®_—&Ž'û±?ÁÁ@Û¶ÒÄ"%*Ž“Ò†UoŠövK³¿· Û.°{÷î¡E‹ذa*Uª„Û·ocذaøòË/Ý^wýúuÄÅÅáâÅ‹¸xñ"âââpëÖ-ëù®]» Z…:vìˆÏ>û qqqøðñjÕ*Lš4‰ÙÏE8#ö›†É:L™ÂOVX¿>?K³+ñÓ¨°s'°~½²Å?¼ÍI ­Æ_] Ì‚F´j%Ì;}Ú»1\½Ê¯EfA§ú÷÷n rA¶hîܹxñÅqâÄ Ì;6lÀ¡C‡ðÅ_¸]#''·oßF—.]Ð¥Kܾ}[Ð/?iÒ$ôèÑÚž1crss±k×.h4œ>}1ŽV}‚)bøS22xÏÎßþÆç|öY`ÎàÂ×僃Y³xPïÞå~¼ì!{h öøò<@¤ æøQЭ¿–¡?"Û.°û÷ï; eoÞ¼9¢¢¢ððáCÔ«WÏåu111nŒcëNÿþýÑß_å¯L(ë5W¯[·òÝ[‡ñ zÂàÁÀüù@:ez¬OB󱇦Þ`/ÏdAnÈ_»¿  víÚaÑ¢EèÙ³'¢¢¢`4±zõj˜L&Ôñ§o.ŠÁ<È žmÛ€k×<»N«ågw0xñE¾ž B ¤@IIB+€J ä½çË Ù  I“&áСCÖùy¡ÓéðŸÿüG`Z&|wëT¥¤ð ömÛìÚ%œ¹Ô‘‘@Ÿ>¼èéÓ¨TIÄ€}Z Œ=´{|y-0 O>ÉO8h™jçÊ ?(a‘QؼY¸üFûöüH4E¶ÿ©ضmΜ9cÖ±cGèõz©C#DfÓ¦M‚.š¿þ²µò9bû (‰æÍyÁ3`Ð¥ o8$x똟øøxtïÞ•+W–:Å’’’‚„„Ÿî áçºz•O›Lüg^‡ìŸMÝ_Bd;+._¾ŒþýûãÆ^yÍä ùui¶nåç»}۳뀮]m¢§~}–QAˆÃ+¯ØæÝ€~þñ¶ÏÌʪTìÛ.\à_í¡y€$Äl6ã³Ï>ÃŽ;ƒÉ“'£iӦسg†Š1cÆH"!wî'ò³.{:nµj@¿~¼àyá~­.‚ _"&F(€¼áÚ±C(~5r?þ†ìÐòåËñ믿â³Ï>Cbb"† †üã˜:u*,X€7ß|Sê ‘˜9ÓÒ$› xJL o^0€o&f°œâ!{ÈÄ%x€iŒÐÔýåŒìþS÷îÝ‹éÓ§côèÑ€`ÆŒؾ};ž{î9iƒ#DãÖ-~vžMlþ” €ž=m][þlÒ ò±‡<@ìQ‚p@çÎñ^GVã{ôz`ûva   ÌÌLÁ›j£F0fÌ? cÖ,ûy{Æ¢re~=š€=¼3"Ÿ ñÚˆ=J˜jÔà»ó-kææò £6nÌæyññ¼ÈBµj@§NlžåKÈN™Ífìß¿€sçÎ!##k×®µ–1b„Tá"pó&ðÓO¼~à×ã"‚ðbbø©=,œ9ÃN9v È®µÉ—jРvìØ;vògÏžm=&äÛ|ù¥pÖææÍ“0dùSXB öˆ=JñÎèôi`øpñŸÃqüü?öP÷ìþS.\(uC\µþ<óÌ&¨TÔEÃò±‡<@ìQŠðžúØ1àáC[:,Œ÷X2@„²zøYQ—,¡/fÖøay€Ø£à=äØýÕ§ÈæY¾†ìÐ_|S§N¹-C+[û&7n«W ó>ÿœ†µá4n yy|:)‰7EW«&îs¿._zIÜûû2²@ÇŽÑ#GðÖ[o!**JêpqÕú3x0ùS¼Õ1{ÈÄ%y€Ôj ukáâ¤gν{‹÷ŒË—ùµÆ,ètüD²ì|à¿üò >ùälܸIII9r$¦L™"Øßãúu`Ía^l,ßúC-zì¡:fO||<2=]­—()))8tèÔaˆën0Çûî݈qŸáËÈNEDD`Ê”)¸xñ"š7oŽÞ½{ãïÿ»×Öÿ"ØàØú Ä“?…=TÇì:t( £$à,€NŸ÷þ4û³{d'€,bРAèÓ§Ö¬YƒmÛ¶IQF\µþ÷‡ ‡e ЃÀñã¶´JÅÏÿCØ¥JLLÄûï¿æÍ›C¯×ãêÕ«˜0a‚ÔaedæLÀd²¥Û´¾‰øÃªÃRCuÌžÔÔTí›9 ÑÑëõHKK“: ÑhÕJ8!áµk6StyÙ¼™ŸÈB‡´¬#²@_~ù%:tè€ððp\¾| ,@:u¤‹(#×®?ÿ,Ìûüsašü)ì¡:fy€Ø£4Pp°pög³™_L ¨û«dd'€<ˆääd|ùå—¨Zµ*T*•ÓFøŽ­?mÛÚ¼?ȟªcöˆ=Jó|‹¸=btƒefò0•n IDATëÙCÈÙ  ;w‚ã8·á\½ üò‹0ϱõ‡ ŸaáÚ¾0léÆfÍÊ_¥!;D(W­?®^Þȟªcöˆ=JólF‚Q÷—g"˜põ*ðŸÿóbc]—% {¨ŽÙC ö(Í8  óç…/Ž¥¥°pXKœP1"˜ðÅÂâví€_t]–ü)ì¡:fy€Ø£DPÕª@¶t~>ÿYVöî²³méêÕNÊ~?%Cˆ+W ÿý0léÆfÍÄK©"ÊŒcë§óþ8BþöP³‡<@ìQ²(›¢Ñ_e‡Q&Μáwö”µõ‡ü)ì¡:fy€Ø£dà,€þú p§© ;…y$€<‡Q&[J3ï#äOaÕ1{ÈÄ¥{€¢¢€š5mé‚~éâØ½[8R¬zu S'vñ) @D©9uJ¼Ö‚ ÂFiŒÐŽ »òsžAˆ(53fÓ:•nÞGȟªcöˆ=J÷žû€Ìf`Ëau•@D©8yÒùŸÎQ•ò§°‡ê˜=äbÒ=@€çèÏ?ùUã-„…={²‹K‰ÐZ`D©p;;/¼P¾{’?…=TÇ졵ÀØ£tà¹r|§éÛ`“R¡ Âc€­[…yåmý!‚ lÔ¯„‡ÛÒ©©À½{Îåhø{ù!DxŒ£Ñ¹KàùçË_ò§°‡ê˜=äb?x€T*àÉ'…y§O ÓýܸaK”m6‡á'Nð3ŽÚ#VëùSØCuÌò±Ç<@@ÉÝ`ŽÿÎÝ»lcR"Šõ?~iiièãÁð¤-[¶`×®]¨T©F…Æ{!Bß±õç™g€^½Ä¹7ùSØCuÌò±Ç<@@éu• ŵݻwË—/ÇÈ‘#±ÓqŠLüøã˜8q"bŠþâºté‚Û·o3ŽÒ·8~ؾ]˜GóþA°ÁJLäGãZP©øùˆÒ£8”’’‚ÄÄDDFFzT~îܹøî»ïðöÛocÖ¬YèÙ³'V®\É8JßÂQì<û¬¸Ã-ɟªcöˆ=þà€-Ζ¾u ÈÊâ'¡í؈Žö^lJBq(&&±±±èäÁ|àYYY¸víºtébÍëܹ3Nœ8Á2DŸâØ1`ÇažØ­?äOaÕ1{ÈÄñ Wtç8àìYþxãFaYêþ*;Š@¥ÁòVn7æ022Rð¶‡ØØXÁfÒÓ#G ÓuêÄ¢GqŸgïO‘úçUjÚRÇr‰G‰é¡C‡bÁ‚²‰G‰éüQà’:–i¾Ì–>s˜2%® ²>oÛ¶mˆE·nÝ0wî\ ø*޳_ÒR9Lœ80þübË\¿~5Baa!ŠfZ¹r%–,Y‚cÇŽYÿ8ÿhü…£Gù‰í‰ºu“$‚ ¿á›o€I“lé7ßäG{eËkÒÄýb©e!)) 111~Ñmî×-@UªTÀ÷Ý[xôèªU«&UH²ÂQ÷uíÊFüøÃ?šÔP³‡<@ìñàÚM£¿ÄÅïК5k¬Ÿˆˆ´oßÿ³›àæ÷ßG/±Æwû0GŽ»v óXÍúLþöP³‡<@ìñà,€.\pþL&T>×vèÐ!,[¶ ÇtìØãÆÃSO=¨U«FY³föìÙƒ×^{ ýúõÃÇ‘œœŒ}ûö!""¯»À^xøã[º[7¾û‹ ‚ðuëwî¸>W£pÿ>? ^Lü© Lq!6lØ£GÆèÑ£­yõêÕ³¯[·Õ«W·¦{öì‰cÇŽáàÁƒˆŒŒD÷îÝìÍeÇŸ Å@k~Ax›˜˜âÐÀâ‹Cq¨zõêãHgGW/€ºuë¢nݺ £ò->ÿ\˜îÑxî9vÏKJJrû;#ÊÕ1{RSS­Vq«²A¯×#''ÇãyÞ|˜çy,P÷Wùñ;ážÝ»ùÍÖ=€äOaÕ1{ÈÄòÎ> ááLGB” zU!¬Ü»¼þº0¯gO~æg–Ð:Uì¡:f­ÆY ÌBq¨o_~x¢|P (,† =²å©Täý!‚ŠºuŠó©ûKH€±cÇ@>ýxúiöÏö‡ÑRCuÌšˆ=þ4ÇV € _?ibQ$€,\ÄÅ óúõ¾øÂ;Ï' {¨ŽÙC öø›p@Ý»ó ¢ü(n 1ñ‡y€ä}>öK¿4lÈ·¹jz%‚ ¼Gj*pý:‘¤§mÛ²{ÍDø÷ïÆ ÅOh(?Ý:‰‚ é©\™ßñ¡.0?ÅbzNN¶å©T|WX‹ÞÅÞ4¤†ê˜=äb?z€vòSÆŠV ±2u*ðòËÞ…ü)ì¡:fy€Øã ‚ärƒR=@‹ï¼#ÌëÛض P“$&‚ð[üÉD_w~Æ¡CÀĉ¼† _~!ñCAøô•çGÜ¿ *?Ó³?¼iH Õ1{ÈÄòbBÈO(,äý=ö¦g@Ó³#äOaÕ1{ÈÄòbBÃàý„wÞŽæ}ú©4¦gGh*öP³‡Öc¿­F°…Z€ü€%K€åË…y}û3gJAAH …sø0ðþûÂ<¹™žÉŸÂªcöˆ=ä"ÄD&_ <§éÙò§°‡ê˜=äby€1¡y€ÜàËóéõ@×®ÀÑ£ÂüuëxQDAŽÐ<@„Ïóî»ÎâgêT?AR$?ü,]*ÌëÓ˜5KšxJÂÞ4¤†ê˜=äby€1!¤0þüÓÙôÜ ¼LÏŽ?…=TÇì!{ÈDˆ y€Üàk ‡víø½…¾+¬eKéâ"‚ |ò>‡^ÏOjh/~~¦g?A!„Bxï=àÈaÞ”)¾azö‡7 ©¡:fy€ØC BLH)€¥Kæõé|ù¥4ñ”ò§°‡ê˜=äby€1!|Átô(?ß^oËkÐ8q¨TIº¸‚ ߃<@„O”Äû~ìÅOH?Ó3‰‚ ‚(@>ŠÅôüà0åJß3=ûÛ†ÔP³‡<@ì!!&$€|” ø9ì™26LšxÊùSØCuÌò±‡<@„˜È rõ-[¼ý¶0¯wo`ûvùNvHAÈò²åØ1àw„y ÿù‰‚ ‚ðúÊô!’’€!CœMÏ7ú¶éÙÞ4¤†ê˜=äby€1!ä# ü¤†®LÏ­ZI“X?…=TÇì!{ÈDˆ‰VêϘ08|X˜7y²ošž;v¬Ô!(ªcö õ…i×}œèèh 8Pê0…@-@>ÀŠÀâż޽¯¾’&‚ ‚ðuHÉœãÇñã…yõë+ËôLþöP³‡<@ì!!& ù U&Éɼ鹰Ж§Ä™žÉŸÂªcöˆ=ä"Ä„ærƒ”ó @€ãÿú¯¿Ç{=‚  y€Éùàgñ3y2‰‚ ‚&CV®.æ½ð‚rMÏIII¨^½ºÔa(ªcö¤¦¦"""Z­¸«Y…Y¸”r —_²îo¦ß„J¥B€&šèÔ:ë±ã¦Ó¸9'ÒuAÚ hÕì¿Nôz=rrrÉüY„ò!$3NœÆæ5h¬]«Ó³#›6m¢aÚŒ¡:fO||<ºwïŽÊ•+—éú¤œ$'¡s)åd?(ùb0®ý8Ì}a.‚uÁÌž‘’’‚„„¿ oæÌ0q&˜938ŽÞµbq¯pàÇ–se-÷8ÿ18­8cÈäo{€=Úµîݳ兄GŽøþd‡Að_fw2ï8‰œK/!½ ]êðÊMÓ¨¦øyÈÏh[£­Ô¡”‹Û·1ëÀ,dë³a2›¬"ÄñØ"LJ:vw’î'ᑹGù†~j’ F#?©¡½øø9€Hü¬±¼ràošö{3gö(µJ JZ#8Ö¨ø´Z¥ÐæÌ"ô&=®¥]s:WR® ߘ/ux̸œr–uÂÌ3ñq—}î÷Ìâ‹0e÷äès¤‡` ™ðÁÀÂ<1=“?…‰Y‰˜°cþwú@(œ„ŠTo˜¬bÈ^YŽ…SI‚ÊÝ=,› *~¯R1Içgæ#Ù˜ŒËé—q3ý&ŒfqæÒªµhP©šUi†fQÍЬJ34jŠ Ú Ð›ôN›Álp™ÏjË7äÃ`6Xã5˜ ˜²{ v\ÛÕCV£vxmQê`뺞vßòw¸s ä„" $–/¾ÿ^˜§dÓ³#äO£Ùˆo}‹Ïã?G®!¸ ½ÔQ 1q|3¿ý—§Os@]e´À„èBÐ4ª)šF5ˆ†‘ ¡SëD T\r ¹˜°c–Ÿ^.Èßg?Z/nÅýcDË¢<‹…ÈÌ™ñÍÑo0}ïtY¶ÎY¿El« ¨TE{¨Ç–se-Ç™9í&ŸS0ärƒ7<@K—cÇf»ñúõy34 t Ê‘{G0vÛXœK>'u(„ ªW± {±S;¢¶õËÈÙxy#ÞÞò6RóSÎl= û-Dx`¸‘ÏÅÇñÖæ·pìþ1§saa˜Þu:êV¬[b %ËóÞÆŸæRd Ðãdž   ©Cq˼yÀ‡ óBB€Iü¥'½ SvOÁÒ“K­#;Jƒ¥[ÇòVhÿÆY\žã5–}I&O)»Þ¼ *Ô©XGÐee9®\¡l£ÄäÎKM_B§ZðÆÆ7ðÇÍ?çÖœ[ƒCwaõK«ñÌÏH¡ £Ùˆ9‡ç`æþ™(49·vôiØ?¾ø£¨Ýw„üP”JLLÄ AƒœœŒÜÜ\Œ9ß}÷ÔÅŒŸ>}:¾þúkAÞ /¼€-[¶056˜1C˜§ÓkÖ­[3¼¬ PùYsn >üýC<Ê}ät."0Sb¦`b¯‰Nû&po#æHw#o,›½Á›E:'3µ«ÖFËê-Ѥr¦ÃÁåJÐØõ·]øöè·˜²{Š@\ÜθnqÝ0õÙ©ø¼ëçeš7H Й¤3xkó[8tÚé\¥ Jø¦Ï7xãÉ7Ê|ÂwP”š4iºuë†yóæ!33íڵæM›0dÈ—å ^{í5Ì›7Ïš§Ó±ïkÿðC¾õÇž  `ýz æ—ä*;WR¯`ܶqˆ¿ïòü«-_żÞó°iÍ&iåÕ"ªQñÍýrö·”†õë×£{»²Ï¤TPab§‰èY¿'^ßð:Î?:o=gâL˜u`~¿ñ;Ö YƒF‘Juïòx€ô&=f˜…Ù‡f»ô n:‹ú/BÐ¥¾7á›(Æd4‚³gÏ¢iÓ¦€O?ýwïÞÅš5k\^3eÊddd`É’%.Ï‹í2›y¿ÏÒ¥ÂüÐP`ëV [7QCøÆ|uð+Ì9<z“Þé|£ÈFXÔzÕï%AtÁSh*Ä”ÝSðíÑoºeCt!˜ßg>Æ´Ã<Žã÷ã­ÍoáÂã N碂£ð}¿ïñJ‹W˜Çá ø“È·&ipãG ×ëQ§Nk^½zõp÷î]·×íÝ»ÇÇøñãqÀq:€ÌÌLddd6{èÞkzãFú >óŒ­l¯ú½ðað‡ø¼ëçÔ ®·Ô±œ~¥¥SSS±bÅ qï¿t)pãpð °v-âFæûÐGŒDÜÓOýú½{Ï?¸ø·©gŸž~q11@ÇŽüómÚ ®ys eK Y3 IÄ5jÄ;­S¨]quêÕ«U«•+#.:盩+T@\T Õòëò¨Õ|:070††"®^= &èÞxùeÄõí |ò ^Xw ׂ>ÆÀ#O çM ÍC n”Àkµ¨~¿ñ{‰õ±|ùr¤¥¥yT‡îB«¶Â×~ gâ3‹þ_j„ÖÀÆW6âÙôgQ%¸Šx¿/M9rqqq˜8q"~ýõW˜L&øŠé{ðàjÖ¬‰œœ„„„/^Œõë×cÏž=ÝcÚ´i¸pá6mÚ@œ.°Ü\`Ð À1„ڵݻÆË|kŰdÉò¹ÁÌ™±8a1¦í™†ÌBgA_=´:æõž‡W[¾Zì=¨ŽÙ³~ýzÏ×3™€ädàÁàþýâ÷龿0™xßÇÙ6Ç´ãÆò¼Z DEÙ¶Ê•##yZü© L1&è*Uª ,, /^D‡W®\Aƒ <¾‡N§+vÄXYÈÈà_ÎŽæ7lÈ ¢'žíQ> }1Ï©‡§ðÏmÿDƒ§sj•cÛÅW=¿BD`„ÛûŒ<HLä»+ ~_ÚcOË€FÃkµ|뀫coœ³lE-n7P•}DÜСCùƒŒ ×bÆþ89™ÿ‚% 5Qyü†Ô‡Àå‡Å–P’ý9À›E›kŽm D¥""Ü‹$ǼʕË-š|ÅüÔ:£FBll,-Z„`Íš5غu«µLÛ¶mñÊ+¯`òäÉ€wß}ƒFÆ qþüy,X°?þø£(ñït|§øYbF~B©Ù³­„I¨†=ÄlNÍî!©" ò‡j@Ío5k Ä¢ÌÛ{ÇÖ²ìl -ï¶KOw}ì*/3“ÿâö=€ô*"ö¢éÆ h.¯»Y¡ Êâºqƒ?·o óŸ{ضÿÌ"„?…çfúM¼»ý]츾Ãåùa͇a~Ÿùˆ‹v}ƒ‚`Å àÿþ¸sGpj ªa¶¬Ð€G³©T¼ÑØ^ظÚW®\®n9ŸÀlFvr"flšˆýg6!2¨”~_D*`@•.¨cǃ¤$$¤§c`TŒ*W3nà^^LjÀ¤âýD&5`V«Ð ª1Z×l ­.ÐÖ%«ÑØ6wi˱EèY6Ç´ãæî|y¯5›yÁ˜š ¤¤Ø6ûtj*PXþu¼&‡‡cŽ›DJJ+€.\žxèÐ…Ý·/°aP¡‚¸ñÊ@oÒcîŸs1ëÀ,—‹1Ö¯T û-DŸ†}\ß 'X²ø÷¿âŒ‹*P£ߢÀûd,Çž¤KSF«µIF›/ÈñØ[ç,fÖ’6“Éã–·„‡»5ÑÑüïÂO}îøõ¯»m,2 2œÎi;óûÌGˆ./oÄøÿGRŽóß{“ÊM°bÐ t©ÝÅ!Ëœ¡@rI®D“C——¿ ú‰“'yÏOªÃ:€C‡?ÿÌ7„#ûïìǸmãp)å’Ó¹M>yúL{všëYœÓÓï¾ã7»¡Á4`Ø0`êTÿ[c¥¬x"–,‚É1/*Š_Ð(¯´x]jwÁ¨£°ïö>Á¹e§–aßí}hUµ6^Þèt­F¥ÁG]>Bl·XÙÍzîUBCù­n]ϯÉζ ¢ôk×phÂfáÉ @"pè¿„EV–0ôh`Ù2þ;ˆ(%y€ÌœÆäó‘oÈì Œ‚¼ßoüŽUgW¹¼O÷ºÝ±¨ÿ"4jê|29™_KeñbþƒË:0j0y2Ш_Ç"þœŠÆbj.%©©©ˆ ¤ÕrR;¼6öŒÚƒ¹ÎÅôøé‚™Î¯?¾Žë÷¯­é­ª¶ÂŠA+Ð>º½—£UaaüV¯ kׯ ?ùÒ¢ÿÕr²k?Éa^ž0ÿÝwùs¥wß‹˵ÀLœ y†<äò«Ïu)Lì÷ʼnO˺ZYº4T ©Š¹/ÌÅßZÿÍùäÝ»À×_󪺠Àù<À÷³¾ý6ðÑGüdSEÐzkì‰÷| Â-j•Ÿ<ý žoð<^Ûð.§\æOäx  ŸÔ©uøôÙO1í¹iŠYSŽðärCI ù YG NÊ<&JÆÌ™‘^n(V±bÈäYÒžæYÒå$ÞBÞn÷6f÷šJA•„'¯^åGt­YÃ{Z\¼ó0q"o®%…oÌLJ»>Äâ„Å‚üv5ÚaÅ h]ºvÅ„&B$JdõjàÍ7§¾ø×¿€)S¤‰É—0˜ ˆ;‡¯~…Û·¥GRž¬ö$– X‚Nµ: Oœ;Ç+éu늖L˜À79V¬È>X‚ð2´°¨ÿ"ôoÜßüwdd ¶[,>êò´jú #Êýõ”E‹øïû¶3• X°€ 'ЧÐTˆ§W`ö¡Ù¸›Y´Pm€PIà µJ Ú ¨ «àѾUµV×~œðƒüØ1àË/ùyŠk ­Y“_êŸÿ‚ƒKŒKI>+¹’ššŠˆˆhitú7ê“cNâÞã{xªÑS%_@%@ÿ©¥dÎ熟~eÔ(ibò ŒXvjfšûÙ÷…'/`ä]´’€ë‚¬ .•@)í>@SŽá~{÷ò->îÖ®«_Ÿ76]ª¡…äby€Ø£ÊS!ùR2ÐHêH%@¨L›æìí þóÞM8“oÌÇ ?àÿÿæ¸^ã'à©Ô­a(Áº`` Ñ…¸ÌwwÎ’ïÃa·mã[|Ž-¾L‹¼¹lĈ2 +$ñÃëZ`3¢££1p`I«„gòŽãm ó+Tàн{K—œÉ5äbñ‰Å˜ûç\$ç&»,¬ ÆØöcñq—Q=TAÝ3F£maPËæ*}õ*?k³»uº:t>ý4ˆ†Aˆ  à85Þz ˆ‹懇ó/îÏ>+IX²%GŸƒ…'âßþó»,ŠñÆãÃ΢jHU~´Aq ‹X(,´­8^žtqb¤¾;ö2 ]/šW)¨>èü&Ôá붺ñsÛ(†_ŠÂq³,j¿µl |ò Ф‰ÔQAø$€<¤I`÷n V-©#‘–”¼Ì;2ßÿÙz×ëPEGáãöðÞ£º¨0÷7`Ǽ1¸’€âשÒhl+ŠsìN—_š²eXGŠ5äby€ØC BLè?Õž|øýwÿ^aàQî#üûÈ¿±èÄ"äès\–©Rs#G`DBt³¿)~…r •*#F`Sp0ÆŽïZ´ÈPLø"äby€ØC BLÈ䆨ØX,_þwœ?_ÛoWHÊIÂ×~% KgÈsYæIsU,H{ Oï¹õ¥Ëîo¨Ñ}úo¼ È‹‚ Bˆ°2jÔjT¬ø©ÔaxÙ0çð,=¹ùÆ|§óAFàÍ;‘˜|½:꿘¶º¿aË–üìů¿PW A!1$€J@§Ó—\ȇIÉKÁíŒÛ‚ífúMì½µ×åJêîï^ÅпLÌNছ+* xõU^ø´m[l1ò§°‡ê˜=äby€1¡ÿT…ãJàØo¹†ÜïQ+ øÛY`Ìyê?2€_½´t:~.›7Þ àÓ%@þöP³‡<@ì!!&ärCll¬`/GÄ8®¨`†\Þ8 ô¼ ¨Kú+‰‰á[z^{ ¨R¥LÏ$‚ ¤…<@„l`%pt& z Ô°ì펟¾ „;÷€ ©V÷ô¼ñкu™â ‚ äC!Çó å^ ’÷²î!áA®§]ˆ›;™wŠr^&›©‘í,p,é¨<@U–¶¿€àÅùÖž>}üäOaÕ1{ÈÄóq Ífmùf3òM&äÙççÙ;¥]\“çâú³€ðnÝ$þɽý§z™ô‚t$tù™ÖVû®«ê¥ U<:Ш¿9vEÙ ý+¾‹¹H`ä;ŒjqÊ+R\Ù²”÷zKQ.´*Â4„j4Ójù}Q:¸H¨ÕP©P$:i»ã•JvwΚ.:Öøág.ÍDX¹67UÊ6ÕŽ3@“&¼¡¸ys E ~ߨ‘(ÃÉ ÿa4â‘^GƒuÿØ!mÙ§õé«U*¨‹ö*Wéʨ×yvi€ºë(R $B‰¶H<Ø‹§½ƒˆqw>L£A µb¾ }ë–@™ÄOPMèXDNóæ@Æd*vùSxrM&«hyìBÄXEN‘Ð)• HK"#aâ8˜€Äˆøde!!%þk[+JÑzaßB¡S© -Úì]¦Õjʺ»ÎUYo·øÛ<@[H•‡ €fÍlÇ"xê×÷9Ž'8y&r‹&ÕìM&äM¦å*Ïñû¼Œÿþa/½MÑ«ÆîCV8ç9î‹Ê¸<çp/µJ|w {\Òùb¯Àyxln׊“g2±ûå:øñúI/‡£ÿÂѯát¾¤´ÝõG¶nE§'ŸD•Ê•Ý(ŽFíeÑ $h-0BLHyBHˆMèØ·èÔ­[*¡£7›‘f4"Í`@šÑˆtûcƒ…EóN˜&{SiÊNÂÅȪՠ_?d–´îQ.dð…¡v<9ÖhD)¯õ‚èè1jógø;ÑÑÑ$~Ñ Tó'LÀÄo¾sL&^¸äå!Í`à…Œ˜±ßÛ‹œ\–oø„߮բªN‡ª‚}y•u:¨U*A —½èu™çB[Z°œòìÒ€óÐÝ ¢‚ ¹@¨?û,Ö9ƒt;QCfN‘)ò§ø;AjµKñb¿·?_£¨ÅgeõlP7ŒèÐZ`ì!!&ôŸZW+WÆÕÌL©ÃZ• Á BÔüäY!ECR{»ó®ò\]³vÙ2¼Þ¿?Lv“«‹ºáŒŽy“°•TNP| …e“ ðè¸4e=ºøQS*v­8a òþ>7 µÀØC טóÍ0å˜ø-×ä|l—gÎ5»-gÈ2 {~w©$¯@ó¹!663D\W§R!R§C%­Ö¶·;® V; /öÆ^ã*°Îj+Ô…A„#œ‘n&‡½‘L(öœåØ©Œ‹ò0Pê`54!h‚5P‡¨¡ Ö@¢±æ«ƒÕPÈãóŠ3s0ç›aÎ3ÔNj ˱`ï"ß)Ï…p1çšÁ™Åý_¾«3W‹zO9B-@e ¼H¸X„L¤V‹JE{{aã˜*Æ>˜ fpzf=¿ç ¶c§½µ¬Éù¼s}Þ ˜]5ÔµÁÕ‚±V 4µ¡ ¥aü„2áŒLÙü‹1ÛÈÁ¥÷Ũt*¨´*§½Z§v™ïQÇóZ83ÿ¿ÏŠþÏ åL¹2ÝC®¨´*@òD49žà‘P±ä»,[@ëšÉ@%ðúùóxï7­6Žs_pFNЄ(hbÌ͇)'™¹&¤çš…M’¹vM’¹&˜ K1Òàh+jX+µX+Aµƒ¬Ç–½&¤t"‰æbRë˜3q0e™`Ì4Z7S¦C:Û3c.,ßU²‚h@/ ¬0ˆ|ä# %¯Æ9³Œ€7Ö`$|@%ÐEB&¢0׌{¹&Üq!tÌzÿQøÆ #ŒFäþUü ‘ÚJZ^YD‘ƒ@ ªu[ó4  g*æÍµ¨9f¾i梲f»<iWåź^®E@õèªéP=U ÒŠoP–£ˆ3 Å‹@¸d#fÓE­.rà4N£ Ú áR‡¢X2‘‰+¸‚.ðÎZ`¾‚:P M¨†ßBø½:Ä9ϺwS.-? ñ½ã¥þ‘¼y€Ü‹n3ºI†"ÑUÖYEQ@õÀŒ’›Üõæ’›ç‹ZÌäò¥XjT|ÝT@@µ~owlJÕ ‹ÒA¥–f4—9ß c–‘0–}¶]º8á’!OñB”k7žV•Æù8ŸwWÎÅ9û<ÎÌ »¡rü2E{Ù¼˜ªM‡®¶àbö–.¸âÎ…Ø ;1£Òˆ÷9@k²F¥SA †*Àa_\~€Êí9W×@áÃBÞ+DabÑþ^¡hýÙ†T ©äœkEY…À† )·­l Ò¨ «¢+Q(T €.RÇqd ”l“P¼Øï³‹ÉÏ2YÏqFå)•FÅ¡„i{m˜Ö–vw.T¨aki4Úµ:í^›òœ7rP©‹¼Aº"o®”i­ªô׸¸‡\—+猜[$ðì8æ]À¥)Nĸ|ˆ;wJ¡õªràÀ 6 ½zõBbb"òóó±gÏ„‡»PÚòòòò¤AñP³§°°d©d ÇqÐëõR‡A(EµM›6 ±±±øù矱ÿ~â短:d©R©Ð¿üþûgÍ–-[ðàÁƒ2_¿dÉQ˺+SÜ9WùŽyÙÙÙN"³<2¥!%%ëÖ­+óõ¥ù;w‡v[F¬:Þ¹s'nݺ嶜·êØÕ³KCiG%=ëðáÃ8wîœËsÅýŽ ÈsõwëøìÜÜ\FOÂ.7å©ãÒ^/åç…ãïÈ`0 7×ý¡báêï 4”¦Ž=ùlaUÇ%Ý[É(füíÛ·Q¯^=" €7…®\¹?üðŽ=Z¦ò±±±X»v-š6mÊ<þ¤¤$T¬XAAAeºþöíÛ¨[·®heÝ•)|Ç<£Ñˆ¤¤$ÔªUËšwæÌÄÄÄx{yÐëõHIIAttt™®/Íï(++ F£Ñ­YS¬:~ôèBCC\l9oÕqq1zJiG%=+-- Z­Öe·vq¿£ŒŒ @ÅŠ­y®þnŸ}ùòeÔ®]!!!Å^ÊSÇ¥½^ÊÏ ÇßQVV’’’иqcb/®þJCiêØ“ÏVuì˜WXXˆÓ§OûE«b<@ƶ³F£)¶YÑ“ò#FŒðÚ—AAȲ¾ˆûŠ@–ÙW³³³­Š===U«V-sù¦M›z¥õ‡ ‚ 'žxGEŸ>}'OžDÛ¶m­enܸˆˆDEEyTÞÉÉɘ1c0"e²{÷nlÞ¼aaa7nj×®-uHe‡Sß}÷רQ#...Žûꫯ¸ÈÈHîæÍ›Öó5kÖä¦M›æqyw$&&r§Nâ ÷É'Ÿp?þø£è?ÁqÜðáùfÍšIŠb‰åâââ¸[·nq·nÝâ ¥IqìÝ»—{þùç¹óçÏs¹¹¹R‡£H233­ÃÛ·oçÞ|óM©CRiii\Ïž=9“ÉÄ;wŽ1b„Ô!• ÅŒ€÷Þ{³fÍ––†C‡¡^½zÖó“&MB=Š-ß±cGèt:§û.Z´:t@›6m0kÖ,p‡Zµj¡M›68qânß¾®]»zågôe8ŽÃðáÃ]Žvøþûï­uüå—_ZçSyï½÷ðÉ'ŸÐÜL¥àÏ?ÿÄG}ä”ŸššŠ‘#G¢I“&xá…pìØ1ë¹{÷îáöíÛˆŽŽ¦–L¸víÞxã §ü¼¼<Œ;Íš5C÷îÝ­“ö}ûí·>|8N:…û÷ï{;\ŸeôèѸzõªSþŠ+ЩS'´nÝŸ}öÌf3ÂÃÃQ·n]Ô­[Ë–-ÃgŸ}&AľÇW_}…mÛ¶9åïÛ·={öDÓ¦MñÖ[o!++ HMMÅž={žž.ø~õI$`²`Þ¼y\—.]8ÜÕ«WçÖ­[Ç=ñÄ\BBwþüy®eË–Ü7ß|ÃqÇ™L&î£>â^ýuîúõëR„î3Ì;—ëܹ3À©®Ö®]ËÕ©S‡;yò$wîÜ9®yóæÜwß}Ç}ñÅÜ–-[8Žã¸§žzJа}ŠÄÄDn̘1\•*U¸^½z9ïÖ­7~üxîÖ­[Üòå˹°°0.--Û¿?·`ÁnêÔ©\ÇŽ©È YYYܸqã¸ÚµksM›6u:?räHnذaÜ7¸7r¡¡¡ÜÕ«W¹˜˜nñâÅÜÿû_®S§NÜ… $ˆÞwX¸p!×µkWwòäIÁ¹;vpÕªUãŽ9Â]¼x‘ëС7sæLëùøøxîÝwßõvÈ>ÇÖ­[¹!C†pjµšûá‡çîݻDž††rëÖ­ãnܸÁ½þúëÜ Aƒ8“ÉÄMœ8‘{ÿý÷¹Æs(zq ÄqÜÉ“'¹øøxN«ÕrW®\œëß¿?7oÞ>žû補Э[·8NÇegg[ó:wîÌ-]ºTPnèСÜ_ýå•x}‘ÂÂB.>>ž›3gŽ“ÊÉÉá‚‚‚߯ ã>ÿüsî¹çžãòòò8Žã¸ p+V¬ðjܾƙ3g¸øøx.<<ÜI½òÊ+ÜŒ3¬éM›6q 6ä8ŽãÌf3×­[7.99Ù«ñú"7nÜàâãã¹öíÛ;  ¯¿þš{ñÅ­é{÷îqZ­–Û¼y3÷Þ{ïqÇq©©©\ëÖ­½³Ø(Æ],Æg•Êy…ë«W¯âý÷ß·¦[µj…+W®àðáÃ0¨S§Ö­[‡^½zy-^_¤]»v\×ñµk×ðá‡ZÓ-[¶ÄåË—“uêÔ ³gÏf¨†nݺáúõë8sæŒàܵk×P§N„††ZóZ¶l‰+W®`Æ hÞ¼9òòòpóæMßoÖfH@@ºuëfFÞ»wï4h`ÍkÙ²%.]º„~ýúáÛo¿Åˆ#°eË–ÿoïþc¢®ÿŽ?ù5.~Ç‘'Äæ…i è„ùcRÂPºÃÖ˜ÓÕZm¹Ì0X3' uÕ²–P9Ò,³µœÌŸ¹h-„püˆ_#8´XàçûóS'”Ƚ¸{=þºÏç}ŸÏçu﻽yñþ¼ßï•••·,æÙ(&&`ÒËÚl6¬V«º½hÑ"ZZZ¥ªªŠäääœý+þETTÔ¤k•555±hÑ"u;<<œ€€|}}©««£»»›ææf‡÷ÌF’]‡ÝnwX`.00¡¡!‚‚‚¨®®¦¯¯+VíÄ(g·¾¾>‡:Öjµ 244Äm·ÝÀúõëžK°Ûíj]^¥Õj±ÛíøúúRRR‚ŸŸUUUß…˜ºkÛ o/ìv;¯¾ú*{öì¡´´”]»vqûí·;)ÊÙo²öBQúûûvøgJLÝnŸ0»K«Õ222BII ¥¥¥ÜqÇìÞ½ÛIÎ I€®ÃÏÏÏaqÄ¡¡!¼½½Y¸pá¬Ï~ÿ+üýý'Ô±Ãb\’ý®­c°«ÕjIMM%55ÕI‘¹ŽkÛ ÿ-kµZ¼½½yùå—™k™¬½€ñ?ÐëÖ­sVX.åßÚ‹ØØXbccÙÌr©Y`7ƒÑh¤³³SÝîèèÀh4Nz+GLÏduøà®\¹@EE‹ÅÉQ¹©ã›/..ŽõA£ííí?~œŒŒ 'Gæ:ÂÃÃyøá‡)//ÆðûŒ#GލÛo¿ý6­­­ÔÖÖòüóϳvíZ¶nݪ®ž+„øo“H¶~ýz.]ºDMMÍ„²††>þøc‡};vì```€`±Xâ©§žbÇŽDGGsúôiRRRøè£Ø¹s§Ãñ7nÄl6£ÓéÈÈÈ ®®€/¾ø‚uëÖ‡Õj¥¤¤„ââb`<ËÍÍeÕªU´¶¶ª×ÿ»ÑÑQ’’’¨¯¯gÍš5455‘˜˜ÈÈÈÁÁÁøùùFtt4Z­vÂñMMM’’BCC\ºt‰„„~ûí7¬V+_}õ•úhòòòÔãóòòغu+ÃÃÃäææ̉'ÈÈÈ ))‰gžy†ÞÞ^¾ýöÛ)?Bç‘§Á á¼½½yóÍ7ÉÏÏ'==ý†ã7xñÅñ$¢««‹½{÷Dqq1êû>Lhh(v»òòrâãã),,dÛ¶mdee ÑhÈËËcóæÍ˜L&Ž;†Ï¤qÿüsZZZ(,,$ €^x€ÐÐP¾þúkùæ›oÈÏÏÇÓÓ“gŸ}vÚq !n é ¬X±‚yóæ9ŒJLLäÂ… \¼x‘+W®P+Gån½IDATUUÅÈÈÈÿu«ƒ¢ëëë9|ø0iii¤¥¥±mÛ6µü÷ßçÀS>ï²eËhll¤¶¶PÝÐÐÀòå˧ë“O>Imm-çÏŸGQ*++ñòò⡇R¯yðàAüüü˜3g>ú(ÍÍÍœ>}š¤¤$Ž=J[[÷Üs999Üÿý7”T !œG !ÜDQQ‘ì­9sæ°fÍ¢¢¢ áÈ‘#·¼¦cùòåÌŸ?Ÿ˜˜rrrÔqGÅÅÅ„„„Ùl&44ÔaVÕõÜ}÷ÝìÝ»—ôôt"""X¹r%ï½÷f³yÚ±.]º”×^{|ˆˆ¶lÙÂþýûÕ  àëë‹ÅbÆ{¼V¯^ÙlVoßõôôðÀ0þ|,X€¿¿?Ï=÷Ü´cBÜ:ÊÕ¹¨B!„nBz€„Báv$B!„Û‘H!„nG !„B¸I€„Báv$B!„Û‘H!„nG !„B¸I€„Báv$B!„Û‘H!„nG !„B¸I€„Báv$B!„Ûù™àêÆF¨P„IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/compressed-writing-zlib.svg000066400000000000000000000746131416254111300262520ustar00rootroot00000000000000 Writing with small (16 bytes) record size 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 0.0 0.5 1.0 1.5 2.0 2.5 3.0 MRows/s No compression zlib lvl1 zlib lvl3 zlib lvl6 zlib lvl9 PyTables-3.7.0/doc/source/usersguide/images/compressed-writing.png000066400000000000000000001241221416254111300252700ustar00rootroot00000000000000‰PNG  IHDR@°AàÚ²sBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwX×úðï²KgTÄŠØÁF±K‚±DlQ½h¼Ñ{£QcÁ‚%j,ÁÞƒ-s%EAADAzïœßûc`X–ºËÂò~žg3gfÎÎ.»ïžyçcŒB!¤QSv!„Bê@„Bit("„BH£C!„B €!„ÒèPD!„F‡ B!„4:B!¤Ñ¡ˆB!@„Bit("„BH£C!„B €!„ÒèPD!„F‡ B!„4:B!¤Ñ¡ˆB!@„Bit("„BH£C!„B €!„ÒèPD!„F‡ B!„4:B!¤Ñ¡ˆz†1†ÈÈHäää(»)UÌÌL%mÏÍÍ­Ò¶ïÞ½Cll¬"›ÈÏÏGdd$ ~¬êÈÉÉAdd$²²²”ݹùðá’’’”Ý B*EQ¸;wbÑ¢E¼²¸¸8Œ3/^ä•>| .¬p_}õ,XÀ-áñãǼ:·nÝ‚³³3Þ¾}[ËÖË×¾}ûðÑGqË>Äõë×yuÒÒÒ`mm¿þú«Ž[W3½zõÂñãǰ¶¶Fppp¥Û]¿~mÚ´Áƒ¤ÖåååaĈ2_?ÆvïÞ©S§¢sçÎ6lX…Ç …µµu­Þ©©©8rä²³³k¼²¼¼¼0jÔ(Ém¿Ê4{öl|ýõ×5Þ~Ñ¢EøòË/åØ"BÊGQ8lÙ²\Ùõë×qêÔ)üöÛo¼º•þ¶°°@«V­¸åo¿ý¿þú+¯ŽX,Fûöí¡¡¡!‡g ?ÆÆÆ°µµå–8€uëÖ)±EÊ‘O?ý«W¯ÆÇÌ•ÇÇÇ# cǎŹsçxï™bYYY3f |}}ѹsg¬ZµªÒH¢££1yòd¹önhjjâĉxòä 6mÚ$·ý6d­ZµBË–-•Ý Òˆ”Ý¢ú „‚‚üùçŸ\ïÇõë×áààÀëý(®³sçN@bb" ѬY3âÝ»w077‡‡‡÷k9)) 999HMMEdd$ E‹èСV¬X’ž‰ääd´lÙ¯^½Âëׯ1pà@¨««Kµ÷Ù³gxùò%lll`ff†‚‚n?e%''#''Í›7çÊ¢££Ñ¼ysˆD"©çáää„nݺqmJMMå.ƒ’©´””Ü¿]ºt©©i¥çº°°?Ƈ`aaŽ;B ¢¢¢`bb‚‚‚üý÷ß055EçÎ! wïÞ044äí3::¯_¿Fff&ÌÍÍaggWi;*³}ûv¨««céÒ¥¼ò¸¸8ASSSæ¶7nÄÇ‚&MšTûØ111xôèZ·nöíÛdqqqhÙ²%„B!W777ïß¿GË–-xûö-òóó¡¥¥333®nXX^¾|‰V­ZI£üü|ûŒ}ôÑGlÅŠLGG‡`Ïž=c³fÍbãÆcŒ1¶hÑ"&‰˜¦¦&300`ìñãÇìÎ; KMMeŒ1¶cÇf``À<<<˜ºº:‹Å¬Y³fìüùó\›²³³ÙøñãÖ²eKfhhÈtuu¹c•Çßߟ5iÒ„1Æ‹‰‰a€9r„«3dȶpáBÆc¿üò ³²²bŒ1¶oß>¦©©ÉD"×ö°””€M:•éêê2###¦®®Î-ZTá9Žˆˆ`íÚµc†††¬C‡L$±Þ½{sëÕÕÕÙ„ ˜322bØœ9s˜ŸŸÓÐÐ`FFFL[[›­_¿žÛæÖ­[ kÞ¼9ëСÓÔÔd}ûöeééé\SSS¶oß>Æciii ûóÏ?+lkçÎÙš5kd®þü9÷z—–Íš4iÂüýý+ÜY>d˜»»; …LWW—`¾¾¾Œ1ÆÞ¿ÏÔÕÕÙ±cÇxÛýûßÿfmÛ¶eyyyL,3L__Ÿ°¡C‡2ÆKHH`ü1SWWgݺucjjjlôèѬ  €1ÆXHH³²²bM›6e¶¶¶L(²!C†H=/}}}vòäÉ Ÿ‡³³3›6m›9s&ÓÐÐ`Xvv6{ûö-0`ÓÒÒb]»ve€}öÙgÜvEEElùòåLMM™˜˜0]]]fhhÈBBBcŒ}cŒÅÅÅ1---–ÍØþýûcŒ­_¿žuîÜ™Ûæ³Ï>cjjjlÞ¼yìîÝ»ìéÓ§,++‹1&ùRX¾|9ïxå@"‘ˆýøã,;;›±©S§²1cÆpÛ¬X±‚™˜˜°+W®0Æ$ÔãÆ«0zýú5À?~ÌcìçŸfºººlüøñŒ1É;}ú4cŒ1ÆØâÅ‹ÙàÁƒyû,€ÆŽË‡§Nb,--Mf[>ùäÖ§O.KOOg{öìáÖ«««³!C†°ÈÈHÆc‡fØøñã¹ãlÚ´‰YXXpÛÄÇdzððpn9..Ž5oÞœmÞ¼™+«nðçϟˬ#+*.Ÿ={6k×®ÓÒÒb­[·f7n”¹/ÆJ E‹±¤¤$–‘‘ÁV­ZÅÔÔÔØ‹/c’/^n›‚‚ÖªU+nß!!! {ûö-oߟ|ò suue Œ1ÆÞ½{Ç ÒFŒÁ x’““Y@@€T=<<Øœ9s*|ÎÎÎLSS“­ZµŠ³G±ÂÂB6tèP6nÜ8–’’“Ã,001ÆØÞ½{™††;vìËÏÏg, €=}ú”½zõŠijj²ï¿ÿžåææ²„„æááÁlmmY~~>cL©©©±¹sç²;wî°§OŸ²ÌÌL6wî\fiiÉ®]»Æc,77—999U9r„iii±¨¨(ƘäoääÉ“,&&†{Š ²6mÚÄŒ¹mW¯^Í:vìÈ¢££¹sÛ±cGöõ×_Wx aŒ1Ê"uÂÍÍ ÁÁÁÈÈÈÀ7ЧOhiiÁÙÙ7nÜ ¹,6hÐ Þv#FŒÀ–-[ààà€Ž;Vxy 2FFFX´h´´´ 0nÜ8\¾|™[øðaŒ3nnn‘HÄëf/••Z·n[·nNž<‰õë×ãüùóÈÌÌDhh(233áääTíö~ñÅ\.ÄÈ‘#Á“J˜.MOO‰‰‰\B¸žž¦OŸÎ«3sæLXZZw9ÒÇLJ;ÎСCñöí[DDD\’³²²Âýû÷áïï={ö@[[›[_oÞ¼ššÚ´iS£m‹ÛuèÐ!<|øóçÏÇ’%K¸K§ùâ‹/ФIèêêbåÊ•¸rå `Á‚ ³gÏgÏžERR¼¼¼dî/;;‡‚‡‡†´´4888àæÍ›$¯Cll,·_CCCxzzJíËÆÆ¯^½ªô9Ì™3kÖ¬A÷îÝagg‡wïÞáâÅ‹˜4ibcc†üü|tïÞkî]»0`À¸»»C$A(ÂÓÓ;vÄÑ£G!àãã 4mÚóçÏGXXîÝ»Çwذaغu+ѱcGèèèàÀ˜2e \]]R—PËÒÓÓCnn.þþûo@ `ìØ±hÑ¢E…ÛݼyË—/ÇÑ£G¹À]»vaüøñÈÌÌDXXâââЯ_?îyRÊ"uÂÙÙŒ1ܾ}7nÜàÌ;………¸}û6¼½½yÛç¯(‚žž²²²ÀCQQ¢££¥°ª4hnݺ…‰'"$$—.]ÂÞ½{qöìY¤§§£W¯^Ð×ׯU[tuu¹ÛÍ˳dɼxñööö066ÆÐ¡C±jÕ*^ÒuiÅùŒ1®LOO¸Dô'Ož`ôèÑ(,,„ƒƒÌÍÍ¡¦¦†ÂÂÂ?—ääd˜˜˜p9R5±téR.ÿÇÖÖwïÞÅ‘#G0{öì*ïCGG666ÜaŽŽŽèÞ½;~ùålÞ¼[¶lÁ´iÓ*|í^½zÆ6mÚ„mÛ¶ñÖYYYV­Z…Ù³g£cÇŽ055ÅðáñjÕ*X[[óê7oÞ •¶»ìßDq0ºvíZ©uÅy<2ﮌˆˆ@Ïž=yù@½{÷†H$BDDúõëWîqãã㑞ž^í¿™ÁƒcÞ¼y˜2e „B!úôéƒeË–aøðá2·‰‰‰Áĉ±víZ.ØÊÊÊBll,>ŒÓ§OóêŸ{B*B©úúúèÕ«®_¿Ž7n`óæÍ€~ýúáÍ›78}ú4²²²jÔSð¿ÄkB(B,WéxYƒ Â’%KpêÔ)Œ1êêê˜0aŽ;CCÃJ¿ jÛöbm۶ŵk׸_îÛ·o‡««+¢££¡¦V³ÎÞÕ«W£]»v8}ú4—0V«vŠÅb$%%¡¨¨¨ÚíjÛ¶-IBwéh===¼xñ¢ZûÊÎÎÆëׯannΕ͟?>>>˜6mþøãüüóÏRÛ•~½š5kؽ{7Ê=N§NðçŸâéÓ§¸wï¶nÝŠaÆq=BÅ*í=)OqNœ8ÁŸòê¼|ù²ÜuÆÆÆR=z¯_¿®0ùðþýûjµWCC?ÿü3þõ¯᯿þB`` FŒû÷ï£GRõsss1nÜ8¸¸¸`É’%\¹¶¶6ôôôðí·ßbòäÉÕj!ÝOê››Nž<‰ððpîËBWW½zõ·ß~‹ž={ÂÀÀ Úûµµµ-÷N‘êêß¿?Nž<ÉAáááøçŸ*ÝÎÕÕïÞ½ÃÆáîî˜8q".\¸€?þø£ÂH^m€nŸžžžøþûï‘€äääï3..í۷炟œœ|øð¡Ví477G~~>wWUu´jÕ mÚ´Á¸²¢¢"œ;wŽë©¨ª   äååaàÀ\ÙäÉ“¡®®ŽqãÆÁÍÍw·–••455y¯—‰‰ :tè€;vðö]TT„ððp%¯K§Nàåå___ÄÄÄH'ÉÞ¡ªlllТE ©6pïå~ýúáÒ¥K¼c¦¦¦">> Àû÷ïñäÉnÝ…  ­­]n@RLWW­[·æ.!’÷G\\\…í}ùò%rssabb‚‘#GbçÎhÒ¤‰ÌmþüùÈÉÉÁîÝ»yåÄ®]»¤ÆPªn0L'ê"ufРAX»v- ÀËåqvvƺuëðÕW_Õh¿£GÆØ±c¹_ÏóçϯÑ~¾ÿþ{ :666077Gff&LMM+½¥ÖÄÄvvvˆŒŒÄàÁƒÖÖÖèØ±#ž>} GGG™ÛŽ9ÞÞÞ˜6mÌÍÍáææVá—NEfÏž ‘HWWW¨©©áرc˜3gN¥yLñòò‚ ««‹Ë—/#)) }úô©ñ>­­­aee…Ó§OcÞ¼y¼u/^¼Àž={¸±v~üñGaÞ¼yÜ-ê[·nŨQ£ššŠV­ZáðáÉDðõõ­ôØŸ}öÜÜÜðøñcbîܹèÒ¥ ·^KK 3fÌÀ† °uëVÞ¶zzzpuuå.ÔÒÒÂÊ•+±cÇŒ1QQQpqqA||<®]»777lÚ´ 'N„¹¹9ˆ‚‚9r>>>¼¿ÂÂB\¼xßÿ}µÏ§H$ÂŽ;0qâD„††¢oß¾ˆ‰‰Á•+Wàéé‰Õ«WÃÏÏgΜ……¦M›Æ>ŒãÇcذa5j1mÚ4ÄÄÄàôéÓX¿~=×»$ËêÕ«1}út<~üŽŽŽ¸xñ"’““aoo/s›ß~û »víâò~nÞ¼ cccîÇCi'Nœ€¿¿?†ŠÅ‹så:tÀÂ… ±qãF 8½{÷ƈ#‘‘›7oÂÌÌLê²!e ýüüü”ÝÒ8´hÑ"‘“&M‚ WÞ¼ys˜˜˜`Ê”)¼.w@€öíÛ—;¶ˆ­­-:uê@rYdÀ€xòä ÜÜÜ «« }}}8;;s¹&-Z´@ß¾}yûiÒ¤ \\\H.Ìœ9 À¨Q£°aÃܿ͛7ÇСC+|nÍ›7ÇàÁƒ¹1~ɽ££#z÷îÍ«knnÎõ€‰ÅbŒ;QQQHII““ÌÌÌ  áììÌ»$"п™_JNNNÈÎÎÆ«W¯’’‚±cÇbÅŠ¼ÎÉɉwŽÕÔÔàââÂëyÓÐЀ‹‹ tuuaoo{{{„„„@GGkÖ¬££#ìììx¯a¿~ý`ff@Àµ½¢Þ¼¸¸8üöÛo˜3g/·$%%¡¡¡‹Åpvv†X,†H$B=¸\œ¶mÛbÔ¨Q Exx8Œ€€€ ½âª>úÁÁÁ`ŒaÙ²eX±b…TÝDDD`óæÍRy/ü1tuuñôéS´oßÝ»w‡¥¥%¼¼¼¸D\@€1cÆàóÏ?‡ŽŽ\]]‘žžŽððpdffbÊ”)øâ‹/x—ÿ.\¸€C‡á—_~©0Ñ_ ÀÞÞ^êRW»víðé§Ÿ"%%ÏŸ?‡¦¦&&Mš„éÓ§CSSzzz˜9s&tttðîÝ;¨««cþüù\°ìááfÍš!,, FFFX»v-/Q[ ÀÖÖ–,€½½=zö쉄„hkkãÛo¿E¿~ýÐ¥K´k×®ÜçеkWâíÛ·ˆŠŠBÇŽ±mÛ6Þû¥cÇŽhß¾=RSSaaa èééq-Z k×®hÚ´)fÏž Æ^¾|‰ììl 4Ë—/¯uÞQ}&¯B¸ÔÔTîƒóÍ›7°··‡¿¿?ƧäÖ©–„„´nÝ?þø#fΜ©ìæp ѦMøøøÀÇǧNŽ™——‡®]»bĈøÏþS'Ç$„¨`Paa!Þ¼yƒ”””*oß`&ž$Š ccctèÐ={öDûöí1nÜ8 ~ÀØØ›7oÆÒ¥K¹™úàøñãHLL”>@‘Š/ÙVå!D~TªÈÇÇ;wîDÓ¦M¹Ë ‡†X,.·~tt4F¸¸8dffâ“O>ÁæÍ›k|Ç iØòòòŒˆˆ¨««ÃÎήÜéˆü\ºt †††µÊ)’§àà`äææJ]*U”””\¿~ݺuãÆg"„Ô • €8€!C†ÀÔÔ”eÞ¼y¼™ÃK›0aZ¶l‰ü©©©èÑ£6lØ@¿ø !„§R]Ÿ~ú)7a¤ŽŽ eÞVZPP€Ó§Os§`âĉ8yòdµ—B!Ê¡’·Á¯Zµ ·o߆‡‡‡ÌÑE?|ø€¼¼<^·³µµ5nß¾Í-?þÏŸ?Wx{ !„úBKK«Ò;_UJ@jjj°°°À­[· ©:ÅIÒ¥‡×ÖÖFjj*·|äÈ9r„7š¢ÄÆÆÂÐк¾º"##«<ü{UêVTGÖºòÊË–H½&ááá2G°•§¼¼<$$$T:ç,ÕyÒÒÒPPP###™uäuŽ?|ø===ndÞòêÕÕ9–ÕÆªªîkTÙ±’’’ ‰Ê½%ZÖkTüÙPz‚òÞ·efÍšñ>S¥6縺Û+óó¢ìk”••…¤¤¤r?Óå­¼÷AuTçWå³EQç¸lYnn.E›6m”Ý¢Tj Dy+ž'¶¢ùbÿþûo„††ÖMƒ‘3 L™2EÙÍ „Ô±±±°··ow©L²üú미sç:vì¨ì¦R-yyy8þ<@„F‰ 9˜2eJÍMˆ¼¤¤¤Ôj,—˜˜ˆDô±ª(yyyÈÈȨp|-BªJeî#„e â ¤Jä/!!7Z?!µA?U!DÜÝݕݕעE Œ5JÙÍ *‚z€!„ÒèPD!r˜˜ˆ‚‚e7C¥ååå!))IÙÍ *‚ B‘ÊR<Ê"òD9@„"”¤x”Dä‰z€!„ÒèPD8ÄO?ý„§OŸòÊÃÃñcÇ%µJõÀÏÏ÷îÝSvSˆQâQ‘' €gÆ X¹r%¼½½yå?ÆW_}¥¤V©ž‚‚9r/_¾TvSˆQâQ‘'ÊR²]»€Õ«œ–-ÿý¯òz³gÏÆ¶mÛpîÜ9 >¼Òú™™™ÐÕÕ­v{òóó¡®®.UÎCvv6ttt*Ü>;;ÚÚÚ¼²œœœ '¢dŒ!''Gj»Ê0Æ••%óy¡°°°Üç¹¹¹ÐÔÔä–µ´´ðüùórëæää@]]B¡°Â6•Ý'Q>ÊR<Ê"òD=@J–™ ÄÅ)þ_µö´mÛ3gÎÄW_}…¢¢"™õvïÞ èéé¡E‹زeK¥û~÷îÆŽ ±X mmmtêÔ ÇŽ h,XCCCèééÁÎηnÝâ¶½wשּׁ¬€víÚAGGíڵÓ'O°e˘››C__&Làõ¬,]º“&M¬Y³```±XŒaÆ!99™«Ó»woìß¿?þø#àìì HMMÅŒ3`hh±XŒ6mÚàòåËÜvOž<³³3´µµ¡¥¥>|øÿ_×L̘1ÐÒÒ‚±±1,XÀmÛ¥Kœ;wŽ[~úô)úõë±X }}}Lž<™×Õÿå—_bêÔ©X¶lŒŒŒ`bboooÞó „Ru)ÿú׿Î}™—õûï¿cÁ‚˜?>Þ¼yƒ%K–`åÊ•8pà€Ì}æççcðàÁˆÅðìÙ3Ì™3>,^¼çÏŸÇŽ;ðâÅ ôíÛC† ATTIGTTüýý±yófüóÏ?hÑ¢ºuë†Ó§OcÏž=¸{÷.ÂÃñ}ûvî¸  DÓ¦MqõêUüôÓO¸ÿ>ï’^tt4æÍ›‡=ÉŽ~ IDAT7nà“O>§§'ÀÃÃo߾ťK— OOO¸»»ãÇ$¿ø›5k†°°0„‡‡cåÊ•ˆ‹‹øùùáÊ•+¸~ý:âââpèÐ!äæærÇ|óæ 233ééépuu…™™‚ƒƒqüøq„……ñ&)MLLÄÑ£G‘ŸŸ .à÷ßÇ©S§pâĉª¿°D¡(Hñ(ˆÈ#2ùúú2__ß ë,\¸mܸ±ÆÇظ‘1@ñKËÊÛÒ¥K¶yófÆc+W®d­[·fyyyìĉÌÈȈ«çèèȆÎÛÖÃÃuéÒEæ¾>̰ÇK­KIIaB¡mÚ´‰++**bM›6eK–,aŒ1vãÆ €%%%qu8À455yûúá‡XçιåiÓ¦±1cÆðê,_¾œpËfffÌÏÏWçÞ½{LMM…‡‡seùùù¬I“&ìàÁƒ¬°°éëë³µk×–û|'OžÌ ÀòòòÊ]¯¯¯ÏŽ=ÊclË–-LMM÷ÜNŸ>Í;_^^^läÈ‘¼},X°€5ªÜýWErr2ï<Ú9vìKHHPv3TZLL ;uꔲ›¡ÒÞ¿ÏLMM•ÝŒ:A=@J6{6ðþ½âUÉÿ)íË/¿Drr2víÚ%µ.44...¼2„……¡°°°Üý=}úèÒ¥‹ÔºâíJïS ÀÉÉ ¡¡¡2Û(‹Áã•q=4²8::"55•÷KÒÔÔTª½Ð¯_?˜™™ÁÌÌ ÈÈÈ@TTÔÔÔ°xñbøúú¢S§N˜9s&®_¿Îm?kÖ,„††ÂÂÂîîîð÷÷G~~~¹í E×®]ѤI®làÀ …>ÿ¦M›âýû÷>WRwÜÝÝÑ´iSe7C¥Q‘'J‚V2É£¾144ÄŠ+°fÍlذ·N$!''‡W–••¡P5µòcj äåå1@ µ?åîSVb1©ýyüÒ>|ø¡P===™uÔÕÕ¡««[n²rqµ¯¯/<<<ˆ[·nÁÍÍ ß|ó ¾þúk¸¸¸ ""GŽÁ½{÷°téRøûû㯿þ’Ú_yç3''EEEܹ)OUž+!„òÑ'(‘iÁ‚ …Ø´i¯ÜÞÞAAA¼²+W®ÀÎήܠìììðáÃ3gÎT¸M§Nðé§ŸÂÃÃß|ó :uê„ððp;v Ó¦Mƒ‹‹ ±dÉtëÖ ïß¿G`` /^ 5júôéWWW¨©©aÛ¶m>|8š5k&u¬©S§â»ï¾Ã˜1c°téRÄÄÄà¿ÿý/&Ož ›Z?R7‚‚‚àââB—Á(!!÷ïß§Ë`D.("œž={‚W6}út\¸p—Ûãä䄳gÏâ›o¾á¾¤Oœ8aÆÉÜ·šš®^½ ___8pÛ¶mC—.]¸€aÍš5hÒ¤ °eËôîÝÿý7Œprrâ]kÚ´)œœœxÇ155E¿~ýxe8þ<žùß|ó WßÖÖfff¼öYZZ¢G2Ï9©[4âQ‘'+›EJ8~~~¼Ëããã+++øøøÔM£Hµ|öÙgHMMÅï¿ÿ®ì¦Ô;)))°²²BJJв›B©'bccaooÏ»$¯ª(ˆBä€r€r€ˆRëÊfƒºB9@ŠG9@Dž(ˆÔȺuë*œJ¢®?~‡ÆùóçѲeKe7‡4b”¤x”Dä‰ %{û7"o(ü8úšúðêæ%sýãÇñÏ?ÿH•wïÞ½Üé„B!„B¡TùíÛ·qåÊhjjbêÔ©°´´¬RûÒÒÒpòäI :”7à_JJ 1vìØr·³°°ÀÈ‘#!‰¤¦Ù „Bd¡HÉnDÞÀ¢K‹~KË  /^ð¦²HJJ­[·°cÇŽr 7ÂÚÚ}úôá•åççÃÞÞüñ6mÚ„þùæææ•¶O,cÍš5xùò%¾ûî;®|ß¾}øé§Ÿàé鉴´4©íààà€øøx €ˆR%&&ÂÀÀ Â lIíäåå!##ƒ75!5E9@€¤û>008qâ4440lØ0Ìœ9³ÊûpqqÁëׯqöìY¼~ý"‘Hj"UYfÏž½{÷ò¦Ýؽ{7fÍšE3Ÿ“zr€r€ˆ<Ñ· ‘²bÅ DEEáСCÕ <ìíí¹¹ºLMMÑ·o_üý÷ßUÞÞËË ‰‰‰8wîàï¿ÿFXXf̘Q½'@ˆ¸»»ÓD¨ F9@Dž¨¯Vɺ5}*ü8FÚUë2>zô(¶oߎ»wïÂÐаVÇ´¶¶ÆóçÏ«\ßÄÄãÆƒ¿¿?F…Ý»wcôèÑR“€B!µE’9Y:ÁÉÒ©òŠu $$3fÌÀž={йsçZïïÎ;°±±©Ö6ÞÞÞ4h^¾|‰#GŽàäÉ“µn!ur€r€ˆ<Ñ%0HNNÆØ±c1wî\Lœ8±FûÈÏÏçþÿ¿ÿý÷îÝÃøñ㫵'''ØØØ`„ 055…««kÚBH]£ Å£ "OÀ÷߈ˆüþûïhß¾=÷XµjU•÷±råJ888 {÷îèÝ»7&MšT£¹¸¼½½ñèÑ#Ì™3 ÂºÿùÏààà€-[¶ ::6lXµIHmQâQ‘'ê«%€Y³fáã?–*oÞ¼9`þüùÈÌÌäÊÿýïC[[›[>zô(Z´h ::öööèß¿…Ç´µµEPP xå3gÎD×®]ѳgO^¹žž‚‚‚бcG®l̘1èÕ«¯ž††F%Ï–BHcG´mÛmÛ¶•¹¾M›6¼å²ÓN888ZµjUåcŠÅb8;;K•ëêê–[.‰¤ÊmllªgDˆ"PâQ‘'ºF!r@9@ŠG9@Džè§ !„Èͦx”Dä‰z€!„ÒèPD8ÑÑѼDgyËÌÌDttt•ëçææ"22Œ1…µ‰yILLDAA²›¡Òòòò””¤ìfAáôèÑC¡þþûïèÞ½{•ë?xðÖÖÖåN‚zôèQ|óÍ7òl!µB9@ŠG9@Dž(ˆ4(·o߯;w°aÃôíÛWÙÍ!„C9@ŠW×9@‘‘@J `o_g‡$uˆ e‹bb  Ôø9•ILLDzzºT¹‰‰ tuuHº£=z„äädtïÞÆÆÆUÞff&âããaiiÉì0;;qqq°´´,w»;wî 44Mš4©ò±!¤2ùùÀƒÀ;%˜`Ð àêUe·Ž(@Êvè°h‘âci)ù9SEÿú׿pèÐ!n¹  ™™™8~ü8ÆÐÐPŒ7‘‘‘hÚ´)’’’°iÓ&Ìž=»Jû‹‹CÛ¶mqùòe¸¹¹qå6lÀ±cÇðäÉ“r·ûòË/3fÌ@|||•Ÿ!ŠFã)ž<ÇŠîÞ- vþ÷? 'Gº^pp­Eê)•úKÍÎÎÆŸþ‰ØØXôíÛ­[·®°~tt4bÊô¾¢}ûöŠlfƒ°uëVlݺÀÃØ±c‘œœŒÑ£Gƒ1OOOØØØàúõë066ÆÎ;1wî\ôíÛ·J©¶n݃†¿¿?aïÞ½X²d‰BŸ!ŠšCpÿþýj_+*ž>å÷WmÛ”ÉoG+«j7—Ôs*988@$ÁÖÖ_}õlllpíÚ5™õ·nÝ XXXpeýúõã¾ø‰ÄÚµkŒàà`ˆD"|˜›'*77ܯ2YÜÝݱ}ûöºj¢4 kWŧE‹mvîÜ9üûßÿÆõë×ѬY3À«W¯€7×—šš¹uU1räHãÀX´hvïÞI“&IÍ F‘ÈϦNŽ+}q0T:0ª¨¬ºå²êjjúú€ÿ_}}É:E ç_ÎzòD(VWóæ@ß¾À»w’ý{ø3F~í%õƒJ@¥'ÉÔÔÔ„‘‘^¾|Ya¤tS§JõPxx8>ùäüüóÏèÝ»7W^œì=zpå/^¼àåóTF$aæÌ™ð÷÷‡§§'qëÖ-ù=Bê¢s€²³ñã d×ÉÏ—<²²Ò„‘ÉúWV™P(ÉJLÌ@D„ìܽ |øPýv …€$à)~÷òìÜÉ€<Ë© õ SQ·nÝbZZZìíÛ·2ë,_¾œéêê2Ö¡C¶fÍVXXÈ­÷õõeíÛ·g7nä=Jsqq‘*k¨LLLØþýûcŒedd°Î;³9sæHÕKKKc†††lݺu\Yxx8À~ýõW™û?pà366敽yó† …BæááÁºwïÎ[w÷î]€¥¤¤HíËËË‹9²ZÏð%''3]]]^YÙ÷2-W}ùرclíÚµ ÙZc2ld+õh<˺ºŒééù1‘èT¶oÒ„±6²5k»v±Œ ÙçûÞ=þö­ZÉÿýRŸ–¯]»Æ6nÜÈÆŒþýö[Ö´iSÖ¨dÆŒÙîÝ»+¬ÉÞ¼yÃbccÙåË—™¥¥%ïKÝ××—ùøø°äädÞ£4ooo• €¼½½™šš›>}:›3g÷¸xñ"cŒ±;v0¡PȆÎ.\Èôõõ™³³3+**’¹ÿò Æ5jÀvîÜÉ+//Úµk[¾|9ëÒ¥ kÛ¶-[¾|9Û¾}»<ž~£“œœÌôõõ¥Êh¹~-'$0Ö³gñr2÷…®©É˜¶v2SWgL ^ߘ—Ælm›:5™íÚÅØÓ§ŒUýüge1&ò÷Ÿ”T?ÞŠXÎÊÊbÉÉÉ,22’………±fÍš±Æ@¥.’Ë0}ôüüüàååUaÝÒcÍ <Ÿþ9þøã¬\¹’+700€¡¡¡Ì}hÖÅî:²lÙ2týÿ|¤þýûÃÔÔTªNñ@³gÏF»vípìØ1|øðë֭Ü9sxcú”eggÇÝÆ^šŸŸºwïŽÉ“'óÊ-,,àëë ---®LCCZZZ7n¯ŒÔLÙ׫ì{–•»œmˆÁƒ%w0ý ’´ÁË—fÍJêùù†Üe0Ƀ¿œ—WñúšÖÏÉ‘$§¦"5µøÿ@Zš!ø³ƒ”ý,•ϲŽл7з¯!úöÉÍx5;ÿÚÚ€­-Z²þáCÀÅ¥~½?䵬­­ mmm"66¶ÂÏqU¢RÐ;wàáá 6H}™’œ.‡¥¨¨jj%³¼yó¦Ü/ýÆbÙ²eÜÿ§V!/ÉÙÙÎÎÎUÞ¿ììì¤Ê»uë†nݺI•[XXÀÏÏWFwˆ‘úJÞ9@‘‘€›Á/wpä•ý]&IÚÚr9¼ÜdeÉúEëK¦(ÌV­Œx¹;]»Êÿ.8{{ 4´dYÉ÷D¹T&ÊÌÌ„««+œœœÆ}qcþüù'''|öÙgX»v- wïÞ:t(Ú¶m‹Ð<3„‘ç8@aa’àçí[~¹«+pê §WëCÔÉ£yóšï£°P ½|™€çÏïcÚ4ÅO‡aoüúkÉòÇ ?$©c* …B¬X±¢Â:‹/†}©I]¾ùæ\½z—.]BË–-ñàÁØØØ(º©„$¯q€>† ‘ŒõSÚÈ‘’ÛßUèª{• …€‘ЧO ôéS7s•ÿ‹îS=*iiiI].)kñâżåáÇcøðá l!„TÝ;Àðá’чK›<Ø¿¿q vX_”½*ÿì›Û8PU¥VyB!•ILLD?ã·Z®^•ôü” ~fÏ¤àŒ”””T'Ç26ÌÍK– J'£U@!„ÈAPPRSSk´í©SÀˆ¥“}%–.vìÔè“€d.°ºÌÓ,{Œò€T ýYBˆ¸»»×(úÐ!ÀÝ]ry¥´o¿þó95NETu.0y¡HµQ§*!„(ÉöíÀܹ’¡öŠ ÀÆÀÂ…Êk‘(›Dj¡ Âéܹ3~ÿý÷o¿~ýz\½zµÊõcøóÏ?ñÝwßáóÏ?ÇêÕ«qïÞ½J· ¨Öün7nÜ€µµu¹ënß¾sçÎUy_„ÈRÝ  €Ï?ç?B!àïOÁ,u™H÷=zĽHÃF=@Jö #7Êf=*€¾P¯Jâxóæ 222j|Œ“'OB$UyBÔ`àÀpuuE»vípöìY¬]»[¶lÁ¼yódn—žžŽ·eH©@vv6"##ye©©©xòä f̘>}úÐÝ€¤Öª3ЪUÀwßñËÔÕ%—Ã&LPPU@BBîß¿_g—ÁZ·Äb =]²œ–¼z´iS'‡' F’ÝHIÁ¢ðp…ÇRK«Ò¨´œœhhhðFÊ®LE½7ÙÙÙÐÒÒâ ±Þ²eK<{ö íÚµãÊFމuëÖUÉÃÔ©SñäÉÄÇÇ£OŸ> =iª2c’ÞŸæ—kkÇÆ)¨q*¢®s€É(Ó¥ó®>¤HUÐ%0ÂóðáC 0b±ø®ÔÏTXYYI==zpssÞ={………°²²Âwß}‡Þ½{C,ÃÐÐ?ýô·?^ð}úôArrr•ÛûçŸÂÚÚoÞ¼á•ÿüóÏ4hÌíΞ=‹ÈÈH :´ÊÇ"¤6 éÓ¥ƒ±X2µ?õ%B«. €ÏñãÇ1mÚ4ܹsŸ~ú)V¯^   Àwß}‡ÀÀ@îѪU+4mÚ¶¶¶€wïÞñnŽŠŠB@@æÏŸ;wî`üøñX´hîß¿/óø—.]Bß¾}«Ü^GGG0ưwï^^ù–-[0dÈêo¶ö²~üñGŒ1€dîµ3gÎàðáÃèÙ³§TÝŸ~ú ÷îÝßþYå祦¦†Y³façÎX½z5ÔÔÔpëÖ-DFFÂËË«Êû!¤¶dåegãÆ/òë›™W®;×a#¸ºÎ¨H•Q¤dN††p*;­s=Ò»wo¼zõŠWvëÖ-,[¶ gÏž…¥¥eµöçèè(µ?8zô(–-[†}ûö¡W¯^ÕÚçŒ3ðÍ7ßàòåË:t(vïÞqãÆÁÄĤZû!¤6ÊËJK“ pxë¿ÜÒR2òs©ß¤ ê::u’ŒÂ]ܹ÷ö- )š4lt ŒT()) b±˜[މ‰Á„ °fÍš sldùðá xe˜>}:8€©S§V{Ÿfff=z4üýý‘žžŽcÇŽÁÛÛ»Úû!Dž%³·— ~lm%Iµü4 šš@‡ü2êR ™òóóqýúutïÞ€äúûøñãáì쌥K—V{ÉÉɸÿ>ﮫo¿ý ,À‰'0iÒ¤·ÕÛÛ§OŸÆ¦M›ÐªU+8QR©c¥s€Þ½‚ƒùuìí›7:¸ê­’”‘Ðe0UE—ÀÏÉ“'aff†´´4üòË/ÐÐÐÀ‚ >>> Á¬Y³päÈn›~ýú¡eË–åîÏßßyyyxÿþ=¶nÝ +++.7Çßß¾¾¾øüóÏ…íÛ·sÛ9::¢k×®Un·««+¬¬¬àçç‡~ø¡ÒúwïÞEjj*bcc¡­­‹/¢Y³f\°G–Ü\ 9Y2‘hñ¿Åÿ %“Z¶h!y4k¦˜¹µŠs€ÒÒšÂÍM2^LiŽŽÀùó@=¾â]ï)#$B8P²Lj ˆpú÷ï,^¼±±±èÕ«îÞ½Ë%u¦§§£W¯^8Pú“’KP-[¶D¯^½¤! ¬[·éééèÛ·/Ö¯_MMM€¾¾>œœœŠÐÐPÞvMš4‘™››KÝ#ðÕW_aÿþýðôôä­322‚³³3¯lïÞ½xñâÔÕÕQPP€ï¿ÿýúõ£HIŠŠ€ÔÔò˜òþ-[–“Sõc‰D€©)?(*~”.32ªÞspwwdzgÀàÁ@L Ý A’ Ouu«·O§Œ €z€T•€1Ø[???Þ¿åñññ••|||ê¦Q Daa!D"Μ9ÃÝFê—””XYY!EŽ#‘çäHz>d.²™´´ú7Å€–м¹t`T6XÒÓ“Ôÿçà£$ ²¥ =*É%! Sr2?  Œ É{DÕÄÆÆÂÞÞ±±±ÊnŠÂQ!¤FÒÓ%¿„<|ùÿóðìYÉÝ2 ]NðúµäQ±XÅÄ$"#Ã¥?V§Nöí“ô:‘ÚËËËCFFŒªÛ=WKMš­ZÅã­!!@5oX%õ ýY…X¸p!Z·n­ì¦9HLä:/_ÖŸ^±Xò%Õ¤‰$Ǧôÿ³²$IÉ11’?|\r“—ôt , ‚¸\2ž3ضM1ùF•²r€Ée°ÒÎ?|HPCGQ555Þ´¤áx÷®$È)xÊÌ4"w"`` ;ˆ©èÿ††’KUUPÄÆJžgñ£88*ýÿjÌÈòÿJÆZ¶L2Û;‘/eå’DèÓ§K–)¨á£ˆFŒ1àÄ ~ÀWóý €µµ$w¦:L©¡¦N$’܆^Ù­èÙÙ•IïÞIz˜J[³F2Û;Q-”­z(’ƒk×®!§:·¡Räää 7¨Â$æå öíîÝKöö€¾¾|Û©,ÚÚ’Y¿+›ù;%E=ž]]|ô}¬*вr€éèñcɠΛBä„þRkÉÍÍ ZZZr½“¦±yôèQµÆü!U—Ÿ/éá‰}„ÂBés\T´¨JûÑÐÌYU:ر³“ ¡¡ä„ž=Kr€ˆü)3ÈÊJÒcY|i4#llê¼)DN(ª¥#FÐmÞ¤Þ ~ü±zÛèè]»òƒNuuÅ´QU”7‘/eæ’^   ’å(jÈ("D…ùûW¼ÞÀ@’ÜYètë&¹¬Ew."­lôð!0q¢òÚCj‡ ¢t±±±033Sv3TÎóç’I7%b˜á£JîÝÖ­)‡A^a`` ú£0ÊÌ$7¥Q"tÃF¿óˆÒ*» *‰ßûˆþý‹uë€ $ɽüÈOPPRSS•Ý •–€Û%Q}£;ÁT @Dé¼½½•Ý•“Ÿìß_ºÄ³f)«5ƒ»»;7oQ eçuèÀŸÒäý{ÉÀš¤a¢ˆtê_²l` éõ!„ÔœH$¹! 4êj¸("J×&Ý«ke“ŸÇŒ‰¥[Ö,11ª2Z=•——‡¤¤$¥¶¡ìe°”ÓR{¥£ ùŠŠ®\á—µhAçXÑ(Hñ”P"´*¡ÛˆÒQ|íÙßì³{w`Ý::ÇŠFã)ž²s€J„V+³/· IDAT%ÔDˆ )*öîå—Íœ©œ¶¢Šºvåß=ùâ…ô|p¤a ˆ(åÉÏ¥K@ttɲ¶60e ãº@9@ŠWr€ÄbÉøYÅŠŠ€嵇Ô@Dé(H~Ê&?O˜ ¹ŒÎ±âQâÕ‡ €.ƒ© €ˆÒQ||øœ9Ã/+¾üEçXñh Å«9@€t"4Ý Ö0QDˆŠ €XÌÖ0@yí!DUQj ˆ(å§ÈGÙË_¥“Ÿé+å)^}Ȥ þ—¤a ˆ(å§ÔÞÍ›’»QŠ©«žž%ËtŽr€¯¾ä™›ÆÆ%ËYYü¿?Ò0PD”ŽòSj¯lïϨQ@³f%ËtŽr€¯¾ätLPDH—’?Î/£‰O Q,ºá£ˆ(å§Ôί¿ÙÙ%Ë­ZƒóëÐ9V<ÊR¼ú’М`ª€ ¢t”ŸR;e/yyjeþ²é+å)^}Éè˜*0Ƙ²Q_ùùùñþ%¤¾ zö,YVS"#–-•Ö$B…ÂBɨХ{_ß½š7W^›ä!66ööö¢×˜z€iÀÊöþ BÁ!uA(:wæ—Q/PÃBQºÆðKC²²$ù?¥ÉJ~¦s¬x”¤xõ) D膎 ¢t”ŸR3ÇŽii%ËÍš#G–_—αâQâÕ§ €ò€:‘²@QS3e/M›&±PömZYïcÅ£ Å«o9@%B7d*•–/^ U«Vå®OLLDRR:uêÄ•uéÒaaauÕDòÿ(?¥zNâãK– ÊÒOè+ͦxõ- Dè†Le ¹sç¢[·nøè£Ê]ÿÿß :::\™X,æÊ‹íÛ·~~~¼Gi´LËu½Ì¿üå‡)SmíúÓ>Z¦åÆ´Ì€üxP}h_U–Ïž= ???8;;ã¿ÿý/òóóѨäHÐ+W®Ä©S§pýúu4+=%v)QQQ°²²Bvv6´´´»víÂþýûqëÖ-4t]‰…™™™²›Ñ DE­[EE%eÿü#Ý _cÅKLL„D"º¹VQòòò‘‘###e7…ËýYKK’-*¯MµA#A7PEEEX¸p!®\¹‚›7oÊ ~ Y³f …ˆŽŽæÊÞ¼yssóºh*)…òSªnÏ~ðÓ£GåÁ@ç¸.PâÕÇ 33ÀÔ´d9'Gr—&©ÿT&*,,ĸqãðúõk\¿~ÆÆÆRuæÍ›ÇÝê®­­áÇc÷îÝ€¬¬,?~œÆòPÊO©š¢"`ï^~YUo}§s¬x”¤xõ1 Dè†Je ôôtœ:u gΜX,†@ €@ @ûöí¹:§NÂãR÷(þôÓO8}ú4¬­­ÑªU+ 8cÇŽUFó ©Ô¥K@©KèèS¦(¯=„ J„n˜Tæbµ¡¡!*Kgzûö-oÙÚÚ¡¡¡ˆŽŽ†¡¡!Äb±"›Hd ü”ª);öÏ„ €¾~Õ¶¥s¬x”¤xõ1 ¨¡R™ ÚhÙ²%?JDù)•ûð8s†_&kâÓòÐ9V<ÊR¼ú˜PÔPQD”ŽòS* ±Xûö@¿~UߞαâQâÕ× ɨÐÅ€2H=D! @ÙË_4ï!õ‡ššd^°Ò¨¨þ£ˆ(]co¢6nÞ^¼(YÖÐ<=«·:ÇŠGs)^}› ¬4º ÖðPD”ŽòS*V¶÷gôhÀĤzû s¬x”¤xõ5 ¨!¢ÛˆÒQ~Šl))Àñãü²š\þ¢s¬x4†˜âÕ× € †ˆz€©Ç~ýÈÎ.Y¶´ÜÜ”×BHùºtáOñê–¦¼öÊQD”ŽòSd+{ùËËK’pY]tŽr€¯>çik¶¶%ËŒ)¯=¤r¥£ü”ò”,«©I š s¬x”¤xõ9 Ë` @Dé(?¥|e{†,,j¶/:ÇŠGã)^}Î(jh("¤ÊÊ’äÿ”FcÿR¿QÔ°PD”ŽòS¤;ÆO 45Fެùþè+å)^}Τ §OzKÔ_¥£üie/M›ÔfŽM:ÇŠG9@ŠWßs€LLsó’åÜ\àÙ3嵇TŒ ¢t”ŸÂ÷ü9Pö3¾¶—¿è+å)^}Ϥ{JßÈ@ê €©gÊöþ89I&[$„Ô”ÔpPD”ŽòSJäçû÷óËä‘üLçXñ(Hñê{@PCBQ:ÊO)qê_²lhÈc†:ÇŠG9@ŠWßs€þ½ó¢jÛø½›Fzè¡IèAz_ªA‘©‚ÔÏ(ExAz“Î * "Ò$4CEjè¡…é$$Ù$;߇Mvv7›ÝÍÌÎììó»®¹vçL{r6;{Ï9÷y@È‘ DHùSò0ìþêÛ(R¤ðç¥:ò‰#x€ªT|}óÖ‡¥‹‡È@!<þþ›_6dˆ4±a*P·.¿ŒZä BrÈŸÂX¿ÐjóÖ52¾‘Ú Õ±øH|ÁÐH0G!9äOaÂgÃ~™™Ÿ©ŽÅ‡<@âã €|@ŽB!R«„0?8pxô(oÝÛøøcáÎOu,>¡B¸Õ ³8‚ ä(P AÈCós¯^€ŸŸ4±Q8j׿gnŽ’’$ ‡È@„ä8»?åùsàÏ?ùeBO|êìulÈ$>ŽâòðjÖä—ýû¯4±ùCˆg÷§lÚÄ ê¨YhÑBØk8{Ûò‰£x€êsH’ãìþÃî/¡[ªc{@y€ÄÇQ<@sd+€’’’pâÄ Çq8pàþ4ì' çøqàÖ­¼uww éâ!B¨HþÈV­Zµ ëÖ­üöÛoèׯfΜ‰Áb<’âÌþÃÖŸ®]%„¿Ž3×±½ ø8Š0@ׯ4±¦‘­ºzõjnSçš5k0kÖ,œ9s'Nœ@NNŽÄÑBâ¬þ”—/ßç—‰¥ïµŽí y€ÄÇ‘<@ÅŠAAyëYYLòA¶¨H‘"ÈÌÌDFFΞ=‹¯]¡Ç!&&Fâè!qVÊÖ­@ZZÞzPо½8×rÖ:¶'äGò@ýúüuꓲ@]»vÅØ±cÑ©S'Ô¬Yµkׯ“'OðøñcJAÃî¯AƒØ gk(–•ÒQ©R%|÷Ýw¸ÿ>¦NŠ}ûö!((C‡ÅéÓ§¥¥ûS´Z`ùr zu`ãFÀ°Íµþĉb ô:–äGóŽ5ì?¤ŽÀ¾ÈRéP«ÕèС¶nÝŠ¨¨(Ô©SÇÇ›o¾‰õë×K!Jö§œ> 4jŒi<´Z  Lš$~J®c¹@ ñq4à8#Á"#Ç¥ŽÂ¾ÈZéS¬X1|ùå—¸xñ"6oÞŒôôt©C"B‰9jââXRÃ-LühÔ8u øñG hQñãQbË Ê$>Ž–pœ‘`ÎøŒ$[tóæM,^¼ðêÕ+Lž<ãÇdzgÏРA|ñÅGHÆhµÀªU¬»kÝ:ãE•+3g€&M¤‰‘ ûQ³&àá‘·þèß-HɈ 6 ..°råJ8p®®®øä“O$ŽŒ¥øSΞe¢føp 1‘¿M¥>ûŒM|:lëþ²'J©c9C ñqD«+ðæ›ü2¹uƒÝ¹\»¦_¢•*»"[ôàÁ´lÙ°cÇL:sçÎÅ“'O¦?áð8º?%>žyyš5Ο7Þ^¿>ú¾v­8Z‚£×±#@ ñqD áíA­vŽ&Ù  R¥J!** Ïž=Ã¥K—ÐäuAJJ RRR$ŽŽGõ§hµÀêÕ¬»kÍã`Ù2àÜ9&ޤÄQëØ‘ ø8¢ÿH0CäâòPš@ìŒlÐàÁƒ1}út#44*T@dd$4 ÍFHNd$5ŸnÜŸ¯RŸ~ ܼ |ñ›æ‚ çEÎ-@Ïž±:T*ÀÅå‘tÙÙ  :uêàúõëØ»w/6nÜpqqÁÎ;¡¢#…#ùS˜‡§iSÖ²cHݺÀ?ÿ°|?¥JÙ=¼|q¤:vTÈ$>ŽèØ}AÿgëÆ 3SºxôÙ½›µfë¨[7 *•s$–­ Ç¿ÿþ‹ÚµkÃåõ#týúõÑ¢E ‰##„Æü)Ç<<5j°¡ëZ ¿?°x1ó½ý¶41šÃêØÑ!ø8ªÈר\9o=;¸zUºxô1¼5„„dHˆÈV½|ùßÿ=Ê–-‹æÍ›cêÔ©GF†ó|8΂Üý).Í›C†°ü>†ôëDE£FÉ·»Kîu¬È$>ŽêäÙ –𠄇óËÞ{O&MSv@¶¨k×®ˆˆˆ@||<,X///ôîÝR‡F8 ‰‰Àˆ@ãÆ,o!µkÇŽ›7³ù¼‚ òCŽhß>~W\µj@õêÎÓ+ò D…#==ûöíÃo¿ý†#GŽ ]»vèÕ«—EÇÆÆÆ"55µÀÙä=z„'OžðÊlsÜ„uÄÆÆÊÊØÎqÌÃóõ×lSC|}o¿¾üRü9¼„Bnu¬DâããáïïWGù§p@4 RSSQ¬X1©C±9Ž3ìþêÚUš8¤B¶-@kÖ¬AÉ’%±qãFtëÖ wîÜÁÖ­[ѽ{w³ÇeggãîÝ»0`–-[Vàu–/_Ž÷߯ Ë]–.]*ÔŸAX€œü)—.1Ï A¦ÅÏdzÑ]cÇ:ŽøäUÇJÅî øxû]K&8ª0nú÷_ãÔö$+ øë/~™³ ÙÞ›5k†þýûãСCøý÷ß¡V«ñÞ{ïÁÓÓÓìq‡œ9spç΋[qBCC±jÕ*!Â&l@þ”¤$`út6MENŽñöZµXNŸ6mì›È¡Ž•Nhh¨¸ˆ‹Ža¦Ã‡»wYú^'j­vdP¹r,ªÎGøò%û 褣G}½^º4Kíñü¹4ñHl[€êÔ©ƒ+V ** ÇDZcÇP³fÍ»ÀBBBpôèQñoF„"à8`Ó&6ºkÙ2cñããü÷¿¬eÈQÅá ¼| ìÙŒÇÆQ—*ôêņ!޾͆"Ξ-u”„ÈÉdØ(Ü¥‹ý§è‘Yÿ¹·o߯¦M›°aÃìÛ·¯^½%Ж-[PªT)ÔªU 3g΄VoŒóéÓ§qôèQÞ¢­~]?G=¯ÿï¿À[oÅ€úO=yÛ{õ6l8Š 77ûÇ'亮Žå×ãããn0¤ÆªóedàèÂ…À´ilØa±b8úÁÀÂ…ÀåËÇ4pôçŸû÷Eù{d·®ÑàЯ¿"áæMÖŒ’˜ˆ£ýņ2¥§™™8ÎëW’UüŠç¯ÿñ‡4ñpðÇ@ÞýîªT9ŠE‹áäÉ“¼ß@%#Û.°éÓ§cáÂ…xçwо}{Œ5 uëÖ\ >_|ñÜÝÝqùòe 2...˜*Ž“Ò†•?±±±(^¼8ÜtÞV2fÌÀ¢E‹¬:nîܹ8|ø08€°°0È}%ŸÍ› XúwC¼½ÙÃ÷¸qìéðÄÅ1Wã‰ÒÅÀòêç-:ad¸äW.Ä1ÞÞ@™2ì‡Lñõµo]pkÉ9|˜ùxŽg?¨–ââÂ~¥*T`"U‡‡k*SFø˜åDZ1ÚwïJ‰õ¸¸ä ¤Lw_\¹ïƒT°%ÇÓöõÉL†‹a¹Ÿ˺ZÈS¦ßŸ·Þ³'ðë¯ì}ll,êÕ«çÙãeÛˆ„„¬^½·oßF@@Þ{ï=´oß¾Pç½{÷.üýýQâõ´ÜZ­j½ŽÏ‡¢téÒ…º!/îßgVŠŸNŸ6½O¬§¡BûÆ&7o;KÿƒÁq¬¥CŽSDøøð‘)‘T¶,{<¶•Û·óÏ‘#¦3išãÍ7¶mví€V­Xë]v6kJxð€í“™ üð0¾íqÊþ‘þÙVrrX³sr2<4Òß–`­ çôôdBÈß?Oé^M•l;ø»?ŸÜÓ9Ûè/²@?FãÆQ¾|y´jÕ ÷ïßGÏž=ñÕW_aêÔ©ùwçÎDDDàúõë€7¢U«V¨T© U«V0`fΜ hÒ¤ BBBPµjU\¹r›6mrØa–ŽŠÐ9jrrØä~{ö°Å\³nµj¬»«cGÁ./=GÝ»³Lޝ‰@Y€ HMe]·n™ßÏß¿`¡T¦ âÓÒàÿò%\g¢çðaÖ=b •*1±Ó¶-[L=Œ¹º'²™vu¬ZLžÌº_”Èëù 5Róña" 'ÇxÑjÙ«<;7„!=-6¶ÒDÐBø!Eår³ü€•Lxx ™A×R‘­š?>>øà¬^½:·ìÚµkhРÆ"EŠ˜<.55ÑÑѹs†EGG£Q£<Í=nÜ8ÔÓ³âûí·8tè8€ *àâÅ‹¨V­šHa !<@IIÀLðìÛWpŠ//Ö »Âq,‘ѬYÆÛdC§môÓ:®œœðÁ,ç¢ÈÈ ¶mã—«TLI9¢Q¥b€ÜÒfsKõm(Œô—§O™[ÞÔPAKñòZ¶ÌëÖjÐ@¸îaØ“U×Õ™˜ÈFƒé‹"%°aý“OGüìÁ£hQ¶(Û8ûŒÝáLê OsRl)F–•)ÆÅë3gŽè#…DçuîŸ\ºuƒÆÓ ’„#$R ÃÉO5ù¡>²@‡ÆôéÓ1pà@üßÿýÞ~ûm|ûí·Ø»w/(ux„@ܿϺ÷üo¦§'ëÒZµ xü¸xøî; IæÿsJÒÓÙÓïœ9ür•Š•­]k¶Ëc—áÝœ#GŽ Yª¡Ã'òGÚݼɚI•ÔT`Ç~Ù€ˆ‹‹SD¢ZCtù²¸zU£tiÀt’¡ :99™7Z¥Zµj-y€Ä'44Å¥ž½A–"AŸÙ³¥‰Å8NtcÀ€Ü·eË–U„°Ÿ2|ö ágMpfd×öÝwßáÂ… f÷¡§YÇäî]fQÐç›oœxd—!¯^±yŽ~ÿ_®Rs粄wQS¦û÷ç­ÿý7pîœ}R >$sC IDAT–£GY‚0^^ŠÉýcHõêìÏ{õŠ­ÇÆ2StéÒÂ^Çðç²[7aÏïÈÈN9s§N AƒP¢D ©Ã!ÄTëO×®äOÀî~]º°*}<=-[˜·£P§§:É=@:Þy‡-ÿü“W6{6°s§t1YŠáħݻ~~¹«Jò©ÕÀ[o§Oç•]ºtì(Ü5¢¢XFnn¬g`È® ìçŸÆÄ‰±sçNÄÆÆ¢_¿~˜4io!;wØï¸>aa¬qÃé[ô®^e£¹ ÅOéÒ,®ÙϾŽí€,<@: ½@üÁFÊ™—/séuÊòâwƒ~íÛ´±©]±ÈNùûûcÒ¤I¸~ý:jÕª…Ž;â³Ï>³Ûü_„8¶þÔ«Ç&1œÜŸrà3y>|È/¯]›¹ê¶pê:¶²ðé a~ |ÿ½tñXÂöíyýA›Ó¬m[Þ.JòÆèâEaÏOÙŸÍ#;¤ÃÃÃ~ø!BBB°eËìÙ³Gê1ÕúCÞ°T×;ó3”@‡À‰@ÅŠÒÄE(ÃV ­[Ù0L¹b*÷Âob¶ÅÄgÏæ­«T¬—ÈC–èÑ£G5jjÕªFƒ[·naôèÑR‡EØÈŒ@NNÞzýúü'§š§*3“%0ìÕ >œ_1ðùç,ÇžïAœªŽ%B²¹Àò£[7–HGN3ÓË‘»wùž%À¨û ` %̦£N~BÂÛ·ù`…á?øÙ7Ê•æÜJAvhÖ¬Yhܸ1üüü…¥K—¢"= ;,·oOLýÍ7üuÅûSÒÓÙÈ®>}€’%Yßßöíü}Ôj`Þ<Ö*$‚‰Vñu,dåØÿ”¡grãFàÉIÂ1‹aëÏ;ïUªí¦4—?û³VËæêþ*Ù  þùÏž=ìY³PªT)¨T*£…p [4ÈóþèP¤?%-‰œ>b¢§Gà—_˜ÑÓ//6÷ÑW_‰Ž"ëXfÈʤ£O~ÒLøáÉÂ1 ÇgG5Ñú(ϰq}„èKNfóéCÈÙ  ýû÷ƒã8³ áܺüü3¿Ì°õGQ¼|É|=z0ÑÓ«ðë¯üI ©X‘ô¢ä„¸º²Äšú¬^ ÄÅI)æðòrÜI\m@ ÐÞ½@VVÞzõêüÞP‚!;D(S­?¦ÞÚŸ’œÌÞ]»² J?þ˜uw¥§çLùòÀèÑÌópÿ>Ш‘èa:t;²óé4ˆM?®#- X¼Xºx 1ìþêуÍ×`¥y€qF‚Q÷—e"DáÖ-Öã£OX˜é}Ο’˜ÈÒõ¿ÿ>=Ÿ|‡ùS±"0n››éáC`Ñ" eK»rq¸:v@dçÒááÁþ÷ôY¶Ìxô¡¤¤g>ϧû Pž0@W®°†ÌLÀ`.q@ù â¨O)_Â^ÿb‡å÷ËMäK¿~|ósÆ@d¤tñšøxöXµcÎo_ÎÊ•ÐP¶8Â4„rIMe"\¿õäûïMÒöfíZ6ÇŽŠY«¨“y=Ë–åOXzýºí]Vûöñ³=²!ñ–Vill,êÕ«ç­ÆÔDÎÍ›–·þÈš/˜_¢Cv<˜Í±dNüT«Lž œ?φöÎKâ‡`Ô(~ÙÂ…æ»jía÷×§Ÿ:ø„õ6övéâ”Uj$€Áùî;6œSG£F¬·(?dõ¤ ¬XÁ2Ж)Ãòòüý·ñÌìúÓ¦ÿþËúþfÏægᕲªc…"[ŽQ£˜Òñü9k‘ŠÛ·YÂO*@fP¢n$˜VËÒŒéCÝ_ùCˆ”¨(6JŸ‚Z$÷§h4¤¦¦¢X±bâÄ%*ëA?y2¯ìÒ%ëÐõëì£ÔáæfÙý×™!!…iý)”?%+‹Ý)Ö¯gÞæÍÙD¢µk³Ù¤-b ͉•Š÷ÃÀƒÀ™3À„ Š?y€ìì=@:*Vúöå—Ížm¿ë'';wòË,èþ”ë ß føoÓð÷/\LJGæ*„£`èýiÚ”õ?[‚Åþ”Œ –%Lתsá3!gfZ°Z ´hÁšÝ»wgÙ™ y€ÄÇ!<@:&MbÞÝ—öòeÖ=ln¸¦PlÛÆ~_©€`Jõ… FÝ_ÖCˆ(4ׯ³)¯ô)´÷'- +× Ø… 3̸D‰¼1ùÝ»ó§ g"8˜}vìÈ+›5Ë>Ȱû«JTƒÂµ=yÂO4«RO:MCˆ(4¦ZBB,?>öÖ->}Ê;7oòOj-ÌÀܰ!{mÐxã ÛÏçàH|Ƥcʾ:}š¥hÓF¼kÞ¼ÉF]ê° ÷>Jõ¬×ÞÅ%/ûÆ;Ìm5 üÁÓѸ±õjgÄA¾©„\¹|Ù¸õgút ¾sèÔ »îÜÁ°ÂÌÈR¾<_è4lH­;ìÚµ‹ºÁDæÈ‘#hÓ¦ Š/.u(–Q¿>б#pà@^Ù¬Yâ ÃÜ?-[Z嵋‹‹Cdd¤"»ÁŠjÔ` Ý4ÿþ ¼ývÁÇZª¨ûË2H…",ÌøÉ£ ¼?Xë΀ÀíÛ°êg9(ÈXì”,iÍœ?âãP S§òPx8д©ðײ1÷>JöL“êÀºÁ @IIÀ±cü2@–Aˆ°™ÿ56ÞYìýY´ˆŸß•Š%ÇÐ: °l¶AÃ;ï°Vý‘U³g³>¡ùûofVÑáä¹LQ¯iK|@ýÅOæ]½ºí©:$€›1lý±4ؓçkb¾ñ»ë„Nýúl8;!ä‡óé˜:•?dóÏ?ÙhË:u„½Ž¡ù¹G6Wƒ(ÙØf„¦Ñ_¶Cy€›¸tÉø!Ñ¢ÖŸœÖì­KÅ`—·73`nÙŒˆĒøÊ$>“Èþä½|ÿ½°×HJ2þ¥¶²û Pv ÀX]½j~àkf&°?¿Œå"l°õÇâ¼?óæ1ÃV¯&Ó²ÈH|BCCÇmÈ”)üõ_eƒ„bÛ6ÞC‚‚€Ö­­>Ò=@%J°ù˜udd°óü8t(oú € ~mÖL¼ø” Âj.\°±õçêUà›oøeÝ»}úA¶Ð­‚ßœ`î\áοaýÓO)÷O>X“ѰQ­KªVk DXÍ·ßò×›5³ ïOv6»éi4ye%K«V!66Vð >TÇâìÂ$ꔵ˜<™_ö¿ÿþÜQQüV_+sÿè£ÑhPø˜dŒ¥> ­ؽ›_FÝ_ÖAˆ°Šóç¿t†‚È$³g³¦#}V¬J–$Š :‡õéèÓ‡uMéÐh€ùó ^Cóóþcó<{J÷–  “'Ù¬ñ:|}víÄ‹K‰"¬ÂPì4otèPÀA—.3gòËz÷f3@ƒü)ö€êX|Ú®®l¦x}Ö¬^¼°ýœ99lÎ1}l0?ëPº°\>Ó¼÷àî.NLJ…a1‘‘l„¬>¶þh4¬¹[?QE` °l™àñQH âHxõŠå첕ƒ˜˜¼uÊýS•+óÁÆÇ›î‰¤áï…‡a1†Fç-€wß-à 3Ø|úüø# ÷¤Lþñ¡:‡öéððÆã—-_ØÚµgØýÊ Úˆ3x€T* n]~ÙÅ‹üõ«W»wóÖÝÝ-ÌÁFð DXĹs,ã¨>¶þDFsæðËú÷gCô ŠøP‹Ã{€t è'LNf"ÈZ‡‹¢û pPp7˜á×¹MÀß_ܘ”ˆbÐÙ³g±ß0CT>ìÞ½_|ñ¦M›†[·n‰™cbØúÓ²%о½™23Y×—þq¹rÀâÅF»’?E|¨ŽÅÇá=@:||€Q£øe‹±î0kغ•ÝtT®Ì Ð…À<@€õˆº¿lCqèñãÇX·núõëg‘Z½z5ÆŒƒz¯ÿãZ´hèèh‘£t,Ξöîå—˜÷çÿþ?«¬] Ab0jšŠ/˜!Ú »¿(÷Ř@±Ñ¸:T*£FuÂB'€âââðèÑ#‹çŠ™?>–,Y‚!C†`æÌ™h×®6&írr ÅÎ;ï0ÜòÔ)ã᳃ç›,ˆü)âCu,>Šðé(Z”u…é3>?—9®_gON: ‘ûGgðÀ›onnyë÷ï))ì½a¯b“&@Ù²ö‹MI(NÕ«WaaahfA>ð””ܾ}-Z´È-kÞ¼9Î;'fˆÅ™3À¾}ü2³­?é鬟_«Í+{ã `Á‚|!ŠøP‹b<@:ÆŠÉ[ü˜%G´ÃÖŸÖ­Š ’³x€<<ø3ºsðï¿ìýÎü}©ûËv'€¬A÷Tì§7æ°X±b¼§å7",,Œ·è£ôõ~ýøë+†¡m[3Ç·m èù¨Â`ýúÜætS×Ó÷§Hý÷*u]WÇr‰G‰ë¡¡¡Xºt©lâ)ôz`  f„ûúk–ÛÇÜñ99lbc¼þþÀÀ‚Ä·zõjžHVõ%ð:ëË[¿t ˜4) ÇóŽà [¯·gÏ„……¡uëÖ˜?>²ôÓ–(ÇéOi©ÆŒXd&‡Å;wP­Z5dffÂýu© 6`ÕªU8sæLî?‡á?³pú4Kt¨Ï‘#fæ0üç¶Q¿õgÄÛF!=U«ò3üü3ðñÇù³w/йsÞº¯/ xy‰§Y¸Ÿ‘`à@6Ú«ÿ¼²5ÌO–j ±±±¨W¯žSt›;u PÉ’%°¾{ÏŸ?GéÒ¥¥ IVê¾V­ÌˆŸ´4ö Õ?•+ÿýo×q†/šÔP‹¢<@:*Vúõã—}ÿ=ë“ÉCeÏž‚‰gñ¦Ð4úKXœNmÙ²%×ããïïFá/½7D{³ã»ƒS§€øefóþ|ý5?3—JÅn„$=#ŠøP‹â<@:&Mb“¥ê¸rÅ8%¼Ž„ãm¯»¿„ÀY<@€±ºvÍøžL¨p(® ,""k×®ÅÙ×#š4i‚áÇ£iÓ¦€òåËcÀ€˜ùznªððpôéÓ:uÂÓ§OñìÙ3=zþþþNÝÖ¡ð÷ßyë­[³î/“>Ì’éÿ+ÃÚp ‚p|zõ¶oÏ[oÒ„?ûŽåË‘#óÖ«Vnß?>…Äz!MQ¦ ðä‰ð™œ© ÌUꄦjÕª0`èe­¤7óðöíÛ˜»Þ®];œ9sÿüóŠ+†6mÚÀËÉûªOžä‹ÀLëÏË—lþ }ñS½:›ý e0e _= „‡çÃ0•û‡°™zõò@]ºPZ¥Â¢8È8†47tõ BPPˆQ9ß|Ã_oÛÖL×ñãùßPµšÝ==-¾^ll¬ÙÏŒ(@Kq¦<@: »ÁÜÝ™‹(<$€,_n|ÿêÔ øî;;'$C‡òËÞz ˜>Ýæë“?E|¨ŽÅGñ †­@Û·»wóËôFá ‰³y€cÔ¦ k`# y€Ìà  þa>ý©_ªVe­A¦š^Ñ·/K…¯ÃÍ ‰ÍÏ­G„²à8 N–™ÏÕ«7oÚ7&ܹ$%‰‰¬z4ïzÎäRàxMÂRžU«²û–Iñóì™±ZjÚ˜0A´ ‚pÜÝÙT8:€nݤ‹‡ ¬„ñä j…é>ÿœ CÐQ¤ëúrq,.gxÒªcñq*ŽÏ?J”`ï{÷f÷!!$$€œ„ÌLæïÑ7=fLϰy³ñL|³f5jùSćêX|œÊ¤ÃË ;–½8PôË‘ˆò™AI ÁƒuëøeS¦0=c’˜¦Œ’’òÊZ¶dó™ì+#Â)INf™SOœ:BÈD(ŠU«ŒÅÏ{ï3f˜9hð`¾øñòbÍE$~‚ÐÇßøí7©£ «¡_3…sâ0j¿Ì¬é`jiß>~Ùܹ@•*¢Äè ORCu,>NéÒh—ˈ@ &&ÆÓóíÛÀ¸qü²6mŒG‚ ùSćêX|œÒdgÈD y€ÌàÈ hÕ 8}š_¾};E&¹}›‰ý¼¾¾ÀåË@PX¡A2<@„Ã3r¤±ø™<ÙŒø¹u hÝš/~`þ|?A„â ¤@~üX³†_Ìœ™ÏQQLüÄÄðË †#DÎð¤!5TÇâãÔ ;A BHH)Œ“'MÏUª˜1=߸Áº½ž>å— ¬]+Zœú?E|¨ŽÅ‡<@âC BHÈdGó=}Ê&h××2ÞÞ¬+¬vm\¿´mkœqèP6v^¥5^‚ B^ˆp84–éÙ°!gãÆ|Äϵk¬åÇPü F⇠‚P<$€—_§NñË&MÊÇô|å ?ÏŸóËGŒ`SÅÛYü8Ó†ÔP‹y€Ä‡<@„RkÖ«WóËBBò™æâòeÖíõâ¿|äH`ùrIZ~ÈŸ">TÇâC ñ!!$ä2ƒ#x€NŸfù~4š¼²*U€s瀢E v¾t hßž?»;À\Ó‹‹+A!oÈD8±±Ì÷£/~¼½Y¦g#ñsñ"Ю±ø3†ÄAátrPt¦gÃÔ=6˜0=_¸ÀÄaßùøñÀÂ…¢Æi Îð¤!5TÇâC ñ!!$$€”Ñ£YÎ}&Mzö4Ø12’u{%&òË'L`YžeùSćêX|È$>ä"„„<@f«híZ`È~YÇŽÀÞ½ÉÏ:t’’ø;ý50gŽèqAŽy€ÙræŒñÄìUª¿üb ~ΜÞ}×XüLžL⇠‚pzH9±±@÷îÆ¦ç; LϧO³–Ãæø©SÙ³í«58Ó†ÔP‹y€Ä‡<@„r²²XRCS¦ç:uô Nždâ'%…¿ãôéffC•ò§ˆÕ±øH|ÈD‰«Ô–1z4pâ¿ìë¯ LÏ'Nï½¼|Éß1, øæ±C´™aÆI‚â¡:ŸP“i× !)[¶,ºté"u„B ä¬_¬\É/ëØÑ 7럀N€ÔTþŽß}ÇZ‚ ‚È…ºÀdÎÙ³lŠ.}*W60=;ÆZ~ ÅÏÌ™!~ÈŸ">TÇâC ñ!!$$€d̳gÌôœ™™Wf”éùèQ sg -ðìÙÌôì?E|¨ŽÅ‡<@âC BH(¤Ì”•Åæ,5ü®oÛôêõzåðaàƒ€W¯ø;Í Lœh—8 ‚ åàLy€È$SÆŽ5?_­'~ÂÙøIOçï4oðÕWv‰‘ ‚ @2dÃ`ùr~Y‡z¦ç¿ÿ>üÐXüüð0nœ]b’ØØXJ†¢¡:ŸøøxøûûÃÕUØÛjJf nÄÝÀ7r_ï%ÞƒJ¥‚»‹;Ü]Üá¦vË}o¸¸¹˜Ù&ÐqE\‹ÀU-þωF£Ajj*Š+&úµåCHfœ; Î/«RغõµéùÀ kW #ƒ¿ÓÂ…lfwd×®]4L[d¨ŽÅçÈ‘#hÓ¦ Š/nÓñ±©±FBçFÜ Ä¼Œ)ø`0¼ÑpÌï0^n^¢]#..‘‘‘N5^Ëi‘Ãå@ËiÁq80׊νÂã½×m³u¿é/À¹:‡3†<@f°·èùs aCàñã¼2ooàÔ©×É÷íºu㻢`ñb`Ô(»ÄH„íh9-$?097^Ü@bFbÁ'9Á%‚ñS÷ŸÐ L©C)ÑIјy|&^j^"G›“+B ßë„IAïÍ£ óIßq?$‡+ßÐO-@2!;›%5Ô?ËT§ØL§†CÂ`éR`äH»ÅI(ÝÓ!Ž÷¤©ÿªå´•qà V©á¢r‹Ú…÷ÞEÅÖÕ*e@Õähp;ᶑйwéÙéŸÀA‰Š‹B³µÍ0£í Lh1Áá>gVœ[I‡&!U“Zð„CCH&Œ ?Î/Ë5=ïÙôèÁŸL¥–-3Nä€?E<¥<Âè}£ñ×Å¿ ©ž0äŠ!}a¤{o(œ TæÎ¡[TP±W•J”õôät<Ë~†¨Ä(ÜK¼‡l­09\Õ®¨R´ j–¬‰š%j¢fÉš. OWOhr4FK–6Ëd¹XKzV:²´Y¹ñfi³0éÐ$컽›»oF¿ ‚Ô ®èNÂ|¶û3p¼à E@H¬[Ç´Œ>¹¦çÝ»YÓ¡øY±Pˆ§ƒü)“­ÍÆâ3‹ñÍ‘o–•\ÐHê¨øäp¬™_ÿÇÓ¡¹ €o7o—Fp‰`žØ©Z¬*ÜÔn*,iYi½o4Ö]\Ç+?öàÞZùVv^‰Þµ{ r-1<@ZN‹…§búáé²lÓ ~ØVAP©^¿BÅ{¯Ûfë~œ–Cf¶AOƒB!ìáZ³†é­ÞƒxåÊÌ ]ìø.Ö”¥÷¡R«VC‡ŠáØœz| Ãö Ãåg—¥…0AI¯’¹G_ìTð¯ûcäˆìŒÚ‰!»‡ >=Þh[¿·úay§åðóð“ ²ü¹þâ:ý1gžœ1Úæëî‹é­¦#( ¨ÀJ1·ÛÊäà¼xñ¾¾¾(R¤ˆÔ¡˜eÁ`üx~™·7°s'PìØN࣌ÅÏêÕÀàÁö ”p31éÐ$¬9¿&wd‡5èºutO…úOœù•£{-Èä)e×›=PA…Šy]Vº÷Å=m%&wºwC³òÍðéÎOñ÷½¿yÛ¶\Þ‚ˆ‡ØÜm3Z¾ÑR¢óÈÖfc˜ql2sŒ[;Bª†`õ«í¾#䇢УGðá‡âÙ³gHKKC¿~ý°dɨզxÓ§OǼyóxe:tÀîÝ»E5, øö[~™›°e ðÖí߀޽™3Z‡ZÍš‹ =6{C Â³åòŒ?8ÏÓžmó÷ðǤz“0¦ý#‹~¸½r¤Œ¹‘7ºEßà-Æzjr**”ª€ÚµQ£x Q‡ƒË•2>epà“X|z1&šÄÑIÑh½±5&¿3ß´úƦ¼ABx€.Å^ ?ábìE£mE‹ÅÂ…ø´^º^ IDATî§6ŸŸp%€Æ‡Ö­[cÁ‚HNNFÆ ±k×.tïÞÝäþYYYèÓ§,X[ææ&~_ûøñ¬õGŸ"E€;€Î¯¶}ú‹Ÿuë€DM Èd;7ãobøžá8}ÄäökŒ`×–](â*¯Qkî—³¿ÅvìØ6 mϤTPaL³1hW¹úþÖWž_ÉÝ–Ãå`æñ™8x÷ ¶tß‚jŪYuîÂx€49Ì<>s"æ˜ôu îŠW ŒO«ÏM8&ŠñeggÃÛÛÿþû/‚ƒƒS¦LÁDZeË“ÇLš4 IIIXµj•ÉíB{€´Zæ÷Y³†_îãüù'ÐúÙ6 _?cñ³aп¿ 1Ê #;³ÿ™¹'æB“£1Ú^­X5¬è¼í+·— :‚`dædbÒ¡IX|z±Q·¬·›7…,ÂàâwéŸ}rƒþ„k/®m+áUË:-ÃGo~$zŽ€3y€+Iƒž?FƒŠ+æ–UªT >4{ÜáÇѫW/Œ1Ç Ç¡HNNFRRoÑÇÒõìl o_`Íþö€€$:´~ú з/’ôÅ‹ ’V®ä‰[¯OëÊY?x÷ j¯¨Çg@“Æ?Yk†+#® }åö²ˆ—Öw=ýe:v\ˆýýö³–½öiYi²}ºoëžkœúúÏâžaâßÑb] &~ èwêŽë_\Ï?R×—TëéééHJJƒ’’…´‹ˆbîƒôððÈ-óôôDrrþÙ,;vìˆéÓ§£G(Z´(Þ{ï=ìØ±ƒ·Ïþýû±qãFÞ¢%ë™™,‡áÖ­·½ti`Èhzm=ðÉ'@NNÞVàÿÃFƒ™Þm¹¾Ü×õŸ4ä\ן¦>EÛ±mÑqKGÜM¼Ë /åíÛ¾r{Œ÷oZ}Þñº:–Óߣ´õøøx¬_¿^6ñÈi½C•¸2â êÄÔámÇ%6z¬ÎŠ:8x÷`ç[·n,º~ÄÃÔù¼朇.'÷zó*íüh'ÞI|%½JúïsôõS§NaãÆ3f ¶mÛ†œœ8Šé‹‰‰A¹råšš oooÀÊ•+±cÇ„‡‡[tŽ©S§âÚµkصkaºÀÒÒØ¼¥†!T¨„ïÏBµåcXN}\\˜º·0¹3äΪU«Èd-§ÅÊÈ•˜>əƂ>Ð' :.Àǵ?Î÷TÇâ³cÇŽBÍæ,¬»¸£÷fù©ôPA…ÑÍFcNû9¹Þ˜˜˜=@iYi˜|h2–]fr4ä§u?ÅÂ…(Z¤háþ…B]`HÉ’%áëë‹ëׯç–ݼyUªT±ønnnù޳…¤$àÝwÅOÕªÀ©ßbPmHkcñãê üü³ÓˆôÃl† O/ éÚ¦¹w¤‘øQ«ÔÑx¢FF™?Õ±= %ñcŸÕÿ —†]B“rMxå8,:½W7æ§õ)[¶¬Yñ~?uVÔÁÒ³KÄO¿ Ø×w6vÝH⇠ äææ†þýû#,, <À©S§°eË 80wŸ `îܹ¹ë#GŽÄ¡C‡?ÿüK—.Eß¾}‰çùs M6‘©>µkgæÿƒr]'Oò7zx°~²^½‰p\^j^bôþÑh²¦ "c"¶7(Ó§ŸÆòNËáïá/A„a;U‹UʼnA'0í?ÓŒ’ý]y~W7Æ¢Ó‹,Îg•’™‚¡EûÿµÇý¤û¼m*¨ðyÃÏqí‹k©"Øß@8>ŠÿÃ?`ìØ±hÛ¶-Š+†%K– yóæ¹Û«T©‚%Jä®ûûûc„ ˆE… °hÑ"ôèÑ£Ðq<~ ´oܼÉ/oÜ8Úm1¼B¿âôXŸØo¿±œ ÊÄgûõí³ b^ÆmóóðÃŒ63ðE“/¬ÊKu,>ñññð÷÷‡««¢n«¢áªvÅŒ63R5ý~ï‡è¤èÜm™9™{`,öÞÞ‹MÝ6åM7•hïí½ø|ÏçxœòØð¨\´2ÖvY‹6AmDÿ{ÇC1 1°Åt÷.?ÑÑüò-_aO™!pÛþ³ñAíÚ±–=qæL?…q/ñFî‰}wö™ÜÞ³VO, Y„²¾e­>7Õ±øÈvR2S0rïHl¾¼Ùh[qÏâXÓe ºwãy€Ò0fÿ“ǨUj|ÙäKÌn7Û)Rgò‘2ƒµèÚ5æùyú”_>¨Õ]¬‰ïõUs3MœÈf=u±ÿœ/„<Ðäh0ÿä|Ì<>ÓädŒ•‹VÆòNË©ùžP<Û®mð=Ô‘d´mpƒÁX²ÞnÞØµ#þØTãéÅk`ý‡ëÑ¢B {„¬8œIQ[­@œ?tìÄÌ8»å^Lþ·/sDëãëË ÐåF8.ÇÃð=Ãq#î†Ñ6wwL|{"¦¾3UvYœ B >zó#´¨ÐýwöÇÑ裼mk/¬ÅÑ裨SªvFí4:ÖE傯Z|…°Öaô}!,‚DD;))ye*pø½Þwèzâ[À°‘-8øýw fMû*S”äOÑrZddg =;éYé¼×Œì ^ÙÁ»±éßM&ÏÓ&¨ Vt^àÁ‚Ä¥¤:–+ä† ~Þ?óOÎÇô#Óy™ÎXƒ;Oîžücꔪƒõ®G£²ì-áÈÐ7µ8À’êç+ @"‚úáÍKн;°q#k"ˆ;X—ƒWY¯ð*ëÒ4i&…‰þk~âÅÒ}MÍ,m ¥¼Ka~‡ùøä­OªÍ·&>GŽ!@¨UjL|{"Þ­ò.úüÖQqQlÃ+1j°U7µ¦¼3Sÿ3U1sÊöƒ<@f(È´s'Kףћ ®àhÑn(¦ËÔ«ÃŘ5 øúkq‚uP´œ‰‰¹%W¬d¥ñÊtë––éÖ +Hì… * i8sÚÏ¡%¡Gzv:Æ•‘+yå Ë4Äú×ã­ÒoI™2!Q ›7úÃ?Æ/Øä6n‰üé+P¢ðË/lxÈÒfa㥘ýÏlÞðWg¤néºXõþ*4+ßLêPBvxºzbEçè\½3>ûã3$e$!¬u¾jñ\ÕôFØý÷ØÀŠÀÈ‘yÖWdc&` Y;7jÄòû¼ñ†Ýã”#™9™Xq=æDÌÁÃä×Õ¦ð‘4,ÁP«Ôðtõ„§›§E¯uJ×ÁðFÃE¿‘“H|È$.«uÆùÁçñøÅc4­ÖTêp@ßT+™;˜4)o½4ža»ªÞáŒg’ÇgŸË—³ ÏNNFvÖ^X‹9sðäåþÆ("yu‚ÄÛÝ^n^ðró²J Xûêîâ.ÎRHÈ$>äÕ+žÝxT“:B ²‚©SYÊÍq ¿©BQ†3ÈØëá,] bßeHzv:~Œüÿ=ñ_#›Œ„¯{ÁõCþñ¡:ò‰y€!!P‰µøþ{–ÜЉÄÏó´çøúÐר´¸¾øÞ¤ø)å] ÿ}÷¿ˆÉ-'[$~ò§Øªcñ!øˆò™!,, aß~›WP¼8ååDSZĦÆbÞÉyX¹ ¯²^™Ü'Ð'ßžˆÏ~/7/;GHAy€c6d­>+J‰]ˆyƒ¹'æbÍù5HÏN7¹O9ßr˜øöD m8”A K4ˆMiQDy?òq¯âÍ[î%ÞÃáû‡óI½‚_Lj9 Ÿ5ø .…Ÿæƒü)âCu,>äòBBßÔøóý÷ñÁºuR‡a3¦Žþ’–•fñ¹‚‚0¹åd ¨7@Ð9¯hž*ñ¡:š L|âââIÓa‚@ 3„……ñ^åˆ'?ª­‚)ïLAÿºýEŸµœ ‚ò²Á'?ª¯Ž©ïLEß·úÂEå"Úu‚ ÂÞ’S#2&wîðÄ̓äHÕ¤Šzmo7o™\ê—©oáCþñ¡:ò‰y€!¡oªIÌHDdL$Î>9‹sOÎá쓳xšúT´ëù¸û˜7ý+"( %¼JˆvmK!ŠøP‹y€Ä‡<@„È …õ¥g§ãRì%œ}r6WðÜI¸ÂU¹¯»¯±¸ ¨˜û¾¸'ÝŒ ‚ Ë a59\®¿¸Î;Wž_A¶6»Pçõóð3Ùr£[ŠyRS0AAX ¹—xçbÎåŠ O/ØlHövóFò Q/°žQkŽ3ÌžNþñ¡:ò‰y€!¡oª>ÙÙ…K|J˜G£Ñ !!Aê0…@ˆœ]»vI‚â¡:Ÿ#GŽ 9Ù¶”„eÄÅÅ!""Bê0…@ 3v.0‚ ‚p$ÈDA¡`H’ã ORCu,>äòBBy€É¡5â’Åqظczœ[¦ßïmØ ÎYðÞð8Ãm**Ë”¥2XW›Ù¦ 6³Íh»‰}ÕÛôcÔÅÌé—ü=œÁvÝ6£2ƒcþ:tíÚ¶E¹’%Aˆå"„„!9$~¬G£ÕâyVb5<Ó-YYyïõÖ²²ÀծɧOK¶²)]¸v .*¼]\àcåâ­V›Ý®VQâÕ²eË *~´‡ ­6wI7xŸÍqо¿Üëýmy¯Ežˆê½©ë ±_jj*²ªW¬Žå BRr8Ù—o 3‘¡Õ(ftK"uµÈ–ŽCJv6RþŒ<õ’§‹ 8Žƒ‹JõëV/µJ—ׯ¹ëzïÕ€éuŽ×oS|—…Üž£'Z KîûœóÛ_¿j´ZA?#¥àW±¢Ô!Ø@°­vm\¾z•×Ô˜oâ·uý‹˜¯†71S¯keÈÔj‘ÉqÈ|}³Ð½Ï´b[¦V M>Ûr8HHÌÌí“_×F~Ý%ígêæjî†kio›e˜••+n’Å5Ô1!))€·7àâ"ÊéÓ_ÿp¿ÈÊåüAv6žøúJ ¡H@T‰ˆŠ‹“: e˜iÖÖ5Ùæ>«Qæ«ðP«áuú4ü»wÏ-Óo]St­¨,xoxœá6ýfvKšÝõ×µf¶å×”¯¿®5ئ£.fÞˆÁßcø€¢ÛfTfpLÖåËÈ©W™>> D"9¸yhÑBêH@ˆ24Z»ZÒnn(íîŽ@ww”~½º»£´›¯,ÀÕøÏ¤Yù´l €u¥åä õõ’¦Õæ½×/Ïïýëý ·gPw P¼¸ âG ˆZ OQ«ÙûׯEÔj¸vÉЪlÉ{ý.þ¼77(ÀÒýLµ¢§¦¦bÉÍ›‚Õ±œ!DHŠZ¥‚›J•o+€3á¦R劖üÄŒî}QWúêÊ• ~®®ðø3Êá¸\1”öÚ ›óÚ¤«ûÎä¼~Í]×{¯}}£uŽç™~ Zû„Ü®V©xBE_°ðÞç#jôß{¨)Œ!±±±Xñð¡Ôaغ‹ÀGW¯â£Þ½y?΀ù&~[÷Ñ¿±ˆýjxã3|X+ƒ»J×7 µzëæ¶mÏg›«J…ØØXæûä×µ‘_wIAû™»¹ZZÆÛÓ7ns7s]™Ÿ«k®È)êæ&šñ» :& O||<üýýá*’8uQ©àïê '¿©©©(F~6B÷MЉ‰Á‰'P´hQ¼ýöÛðôô,ÔþÁqqèV¢„˜!;=ÿûßÿ0qâÄ|·ë››Y³+<Õ1QxvïÞN:¡téÒR‡¢Xž>}Š'N OŸ>R‡B(E  ãÇ£gÏžhß¾==z„ôôt„‡‡ÃÏÏOý qxõê•Ô!(ªcñÉÌÌ4J*I ÇqÐh4R‡A(Eu€N:aaaøé§ŸpìØ1xxxà§Ÿ~l1IHH(ÔÛš©,Ù×Ü>ùm3UnX–““ƒ/^x}1ÈÎÎF||¼ÍÇ[ó½zõ /_¾4»Puœ””„ÌÌL‹Ï-6…¹¶µŸQA×zùòe¾â/¿Ï(55©©©¼2Sÿ·ŽZÇÖ/åýÂ’ï‘X˜ú?°kêØ’{‹Xu\й•ŒbPzz:"""r³„ªT*tîÜd±Ù½{7bbbl>~ÕªU‚îknŸü¶™*7,{ùò¥‘È,ÌMÆâââ°}ûv›·æ3º|ù2Nœ8av¡êxÿþý¸ÿ¾ÙýìUǦ®m Ö~F]ëĉ¸|ù²Émù}F‘‘‘ˆŒŒä•™ú¿5¼vZZšÝæ+L[{¼”÷ ÃÏ(++ iiiÆ#¦þ¬Áš:¶äÞ"Vtn%£âÒfJ•*!33îîî€ 6àÇÄiSX²XX¶nÝŠàà`ÑãE@@Š)bÓñÑÑÑ l_sûä·ÍT¹aYvv6bccQ¾|ùܲK—.¡^½zÅ^4 âââP¶lY›Ž·æ3JIIAvv¶Y³¦PuüüùsøøøÀËË+ßýìUÇùÅh)Ö~F]+!!®®®&»µóûŒ’’’¹e¦þo ¯… *ÀÛÛÛ¢Ø CaêØÚ㥼_~F)))ˆEu;LÕ`êÿÀ¬©cKî-bÕ±aYff&.^¼è­BŠñedd\ô²°º¸¸äÛ¬hÉþ½{÷¶ÛAAÈ[Ä Å âÅ‹`MÕ:Åž˜˜ˆR¥JÙ¼pp°]Z‚ ‚°/Š@%J”Ào¼Ó§O#$$pþüy4hÐ wŸ»wïÂßß%J”°hs<{ö ß~û-bbbP«V-„……åv¥Â’€ùóçcöìÙR‡¢Hþúë/üù矹ëÓ¦MãuõÂpüøqüôÓOpuuÅòåË¥Gq>|¿þúkîz50vìX #R&‡Âü___ >*T:$ÛáÄ’%K¸jÕªq7näfÏžÍ+VŒ»wï^îöråÊqS§Nµxs—™™)uHŠãðáÃÜ»ï¾Ë]¹r…KKK“:E’œœœû?¼wï^nàÀR‡¤8¸víÚq999ÜåË—¹Þ½{KR¡PÌ(0øòË/1sæL?~ ˆˆˆ@¥J•r·7mÛ¶Íwÿ&MšÀÍÍÍè¼+V¬@ãÆQ¿~}Ìœ9Ç¡|ùò¨_¿>Î;‡èèh´jÕÊ.£#Ãqzõêer´Ã²eËrëxÖ¬Y¹ùT¾üòKLœ8‘r3YÁÉ“'ñÕW_•ÇÇÇ£_¿~¨Q£:tè€3gÎän{üø1¢££Q¶lYjÉ´€Û·oãÓO?5*õê† †š5k¢M›6Ø¿?`ñâÅèÕ«.\¸€'OžØ;\‡eÀ€¸uë–QùúõëѬY3¼õÖ[˜6m´Z-üüü„   ¬]»Ó¦M“ bÇcöìÙØ³gQùÑ£GÑ®];cРAHII‡‡âããŽÄÄDÞï«C"±“ ,àZ´hÁànݺÅÛ¶}ûvî7Þà"##¹+W®pµk׿.\ÈqÇåääp_}õ×·o_îÎ;R„î0ÌŸ?ŸkÞ¼9À¨®¶nÝÊU¬X‘;þ|8W¡B.88Øh{¿~ý¸ž={rwïÞåvîÜÉùøøp·nÝâêÕ«Ç­\¹’ûý÷ß¹fÍšq×®]“ zÇaùòå\«V­8ÜùóçyÛöíÛÇ•.ýÿíÝ}PTUð/°Ä,ËË+Ä-Ð E†—´@Hq$Ai—%iÆÁ©iÔÑ$3``rÄA-)… ¨(ÄlšwDQ'¬A„ey‰·!X°H^"^Îó·Vè Ùv÷÷ù‹sÎ}ùíaçòãÞsÏù»qã»{÷. `|»J¥b;wî\è NEE‹‰‰aæææì£>Òiëììd"‘ˆ;wŽ577³øøxÍ&&&Øž={ØîÝ»™L&c•••zŠ~~PÄûᇘJ¥b€©Õj¶ˆˆ–Í—‹ŠŠØêÕ«u¶©¨¨`o¿ýö‚Äj¨nݺÅT*377Ÿ–…‡‡³ãÇóå‚‚æççÇ™R©dJ¥’I$öî»ï.tØå·ß~c*•Š%%%MK€Z[[™¥¥%»ÿ>_·víZvúôiíär9«­­]x Ñèè(S©TìÈ‘#Ó ÁÁA& u¾ß …‚¥§§³uëÖ±ááaÆc~ø!ËÏÏ_и MMM S©TL,OK€”J%;xð _þꫯؒ%KcŒMNN²ÐÐPÖÓÓ³ ñ¢ææf¦R©˜¿¿ÿ´(++‹EEEñåÎÎN&Xyy9ÛµkcŒ±þþ~¶jÕªy¾Í èÇñ`à³Ù kL566b÷îÝ|yåÊ•P«Õ¨ªªÂøø88ŽÃ¹sç¶`ñ"???3÷±F£Á¾}ûø²··7t&![³f 233Ÿ| ÌÖÖ¡¡¡hjjBMMN›F£Çq‰D|··7Ôj5JKK±|ùr £¥¥Åðok?AO=õBCCùi4þªý+h{zzòuÞÞÞ¨¯¯ÇæÍ›qâÄ ÄÅÅáüùó(**Z°˜ ‘̸°¬F£B¡àËÞÞÞhnnÆøø8Š‹‹ò·oÿ’?yxxÀÃÃcƹÊáííÍ—]]]!‰`ee…êêjtuu¡©©IgCD Ð?Ðjµ:ÌÙÚÚbddvvv())Aoo/6mÚD‹ó=†ÞÞ^>‹ÅÂÈÈ¿8mbb¢¾Â3 Z­vÚB¿b±Z­VVVÈÉɵµ5Š‹‹u~dö¾VS× ­V‹}ûöáÔ©SÈÍÍEvv6-˜úfº^0ÆÐ×ׇÑÑQ¦ÈÜhµÚiow‰ÅbŒ!''¹¹¹xúé§qòäI=E8?(úÖÖÖ:“#ŽŒŒ@ `ÅŠŸýþ[ØØØLëcKKKɸ(z<÷105`W,#22‘‘‘zŠÌx<|­¦¾Ëb±»víÒSdÆe¦ë0õzûöíú ˨ü¿ë…¯¯/|}}õÙü2ª·Àž©TŠŽŽ¾ÜÞÞ©T:ã£273õñâÅ‹©ç‘T*Eww7&&&øºŽŽ¸ººê1*ã"•J144„_~ù…¯koo§>žg3]/ìííéÎå<’J¥èììäË¾×Æ6?%@ÿ@¡Pà“O>Áää$ °°r¹\ÏQêã'ÏßßŽŽŽüB£mmm¨¬¬DLLŒž#3®®®X³f òóóLMàyáÂú.Ï3…BÂÂBŒ ëÅ“ —ËqáÂôôô>ýôS¬\¹zŽlžé{ö¿Att4ã8Ž`®®®ÌÇLJobQy\Ôþ“IDATQQÌÙÙ™¹¹¹±uëÖ±¾¾>=Fk˜¢¢¢túØ××—od|‡„„°þþ~=Fk˜nß¾Í8Žc‰„ …BÆq;vìß~õêUæââÂd2³³³ã§s ³×ÛÛË8Žc...ÌÒÒ’qÇ’’’øöÚÚZöÌ3Ï0OOOæààÀöï߯Çh ×¶mÛÇqÌÜÜœI¥Rþ-/ÆþœU"‘0wwwȺ»»õ­a:|ø0ã8Ž …B&‘HÇq¬¾¾žoOOOg¶¶¶L&“1ww÷ioã£Y žB!d¶è!„BL%@„B19”B!ÄäPD!„“C !d^üôÓOüTúÒÞÞŽ¾¾>½Æ@1 ”b„ÚÛÛŽ;wêÔŸ8q%%%ó~¾ññq<ûì³:“.¤¶¶6ॗ^¢— !³B !Fhpp·nÝBYY._¾Ì××ÕÕ¡µµU‘=_|ñÑÜÜŒ³gÏê;Bˆ ˆ#eee…´´4¼÷Þ{3¶ß¾}§OŸÖ©KMMů¿þ (**•+W™™ ¥R‰“'O‚1†üü|ÄÇÇ#77CCC:û744`Ïž=ضm¾ùæ¶ÊÊJìØ±J¥yyyx0Yuu5 ÐÖÖ†ììlÏïÀÀRSS!—Ë‘œœÌÇYUU…¼¼<´¶¶âÀ¸sçδ}ïÞ½‹S§NáçŸÆ|ÀÏÖÜÙÙ‰½{÷B¡P 33£££€›7o"//ßÿüùó¸té_>~ü8ZZZ*• o¼ñâãã‘‘‘ÁÏžKùw£ˆ#–˜˜ˆ{÷¬lZ[}}=>ÿüsº£Gb``PZZ ¹\Ž‘‘lÞ¼G…——®_¿Žððp|öÙg8vì˜Îþ{÷î…L&ƒ½½=bbbP]] øú믱}ûvøûûC¡P ''YYY¦±äädlÙ²---üùÿj||ÁÁÁ¨««C\\„±±1888ÀÚÚ...ðòò‚X,ž¶cc#ÒÒÒŽúúz àÞ½{ Äýû÷¡P(ðí·ßòKƒ˜™™!%%…ß?%%€ÑÑQ$''ÃÁÁW¯^ELL ‚ƒƒñꫯ¢§§ßÿý¬?„ý¡Õà 1bï¿ÿ>RSSýÈû&„èÝ"ÄÈÅÆÆB(¢  à‘÷µ°°à‰D07ÿó’akk;í+333þ瀀tuu˜z•œœ ///xyy!)) 666ü¶‰äo“Ðh4 âÏoff†àà`h4šY;;;>ùypÌV¬XF „††âÚµk())All,¶nÝŠ²²2TUUaýúõ€èèh8;;C*•býúõ(,,ÄÄÄĬc"„èÝ"ÄÈ™™™áðáÃØ±cBCCùz@€?þøã‘ŽóÿÊS«ÕpqqØÛÛ£°°/¾øâìÿ {{ûiƒ·[[[6§ã=8æƒq<011ŽŽØÛÛ˜º#tíÚ5ܹshnnFzz:D"Þ|óM€““¾ûî;¨Õjܸq©©©077Çk¯½6ç¸! ƒîb6mÚOOO±@AAAhhh@?&''Q\\Œ±±±Ç:σAÑuuu¸xñ"¢¢¢QQQ8tèß><<ŒÒÒÒY7,, jµ*• ÀÔ€êúúzlܸqα¾üòËP©T¨­­c EEE°°°ÀóÏ?ÏŸ³¼¼ÖÖÖX´h^xá455áúõë\¾|­­­xî¹ç€U«V=RRIÑJ€1™™™:om-Z´qqqððð€££#.]º¤óÈk.6n܈¥K—ÂÇÇ ü¸£¬¬,8::ÂÍÍ 2™ NNN:oUý“%K–àÌ™3ˆŽŽ†››"""ðñÇC&“Í9Ö 6àwÞŸŸÜÜÜpàÀœ={–¿´lÙ2XYYA.—˜ºãµuëVÈd2þñ]ww7V¯^¥K—bÙ²e°±±Á믿>ç˜! ÇŒ=x•B!ÄDÐ B!„˜J€!„br("„BˆÉ¡ˆB!&‡ B!„˜J€!„br("„BˆÉ¡ˆB!&‡ B!„˜J€!„br("„BˆÉ¡ˆB!&‡ B!„˜J€!„brþ wT¤.·—©IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/compressed-writing.svg000066400000000000000000000727501416254111300253140ustar00rootroot00000000000000 Writing with small (16 bytes) record size 10 3 10 4 10 5 10 6 10 7 10 8 Number of rows 0.0 0.5 1.0 1.5 2.0 2.5 3.0 MRows/s No compression zlib lvl1 lzo lvl1 bzip2 lvl1 PyTables-3.7.0/doc/source/usersguide/images/create-chunksize-15GB.png000066400000000000000000002074041416254111300253500ustar00rootroot00000000000000‰PNG  IHDRÐß}™SsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝy\Óõðׯ1nD‘C¹|ˆ?üÿüó¡›Ò BCCñý÷ߺMÎÞ½{±`ÁC7ƒÒP€&„4˜ýû÷ÃÇÇVVV@§N`aaÑ£G<œÞ»w!!!¸}û¶Ê±œœ|ýõ×j5Û¶mñcÇ”Ê-Z„ˆˆµ¨iøý÷ß±yófC7ƒÒº„æ§´´'NÄ‘#Gàëë‹Õ«W£K—. …¸yó&~üñG 2"‘È`mLNNÆ—_~ tíÚU阓“–,Y‚ÀÀ@µ®a…„„ÀÝÝ]©líÚµ;v,&Mšd˜FBHBšRï6oÞŒ#GŽàÍ7ßÄÎ;aff¦86~üx„††bÙ²elaÍœœœ°téRC7£Á„„„º „Ò¤ÑBH½ÊÍÍÅÒ¥KѪU+lÙ²E)<ËYZZbÍš5Šû¿ÿþ;‚‚‚‡mÛ¶aÈ!hÕªŽ=ª¨³k×. >NNNèܹ3Þ~ûmäåå)·  +W®Ä AƒàââGGGôë×ß}÷cŠzÄçŸ CPP‚‚‚ðÅ_àz§ƒ‚‚ðûï¿+Ÿ1† 6 _¿~hݺ5ž{î9Ì›7EEEJõnß¾   œ}P\\¬tÞÿýï˜0aîܹƒÉ“'# „ŸŸ’’’”ê~öÙg=z4._¾ŒqãÆ¡_¿~øí·ß 2Ö}òäÉ EÛ¶m1þ|XYYaøðáHNNÖø\ !ÏF!õhÆ Û±c‡ÖÙ¶mÀœœœX^^žÒ±ýû÷3,44T©<::š`ï¼óŽ¢¬¤¤„‰D"•ó6Œ³ôôtEÙüÁ°ˆˆ•ú7nÜ`ضmÛeÇŽcØ´iÓÔ¶}þüùв³gÏ2ÌØØ˜Ý½{WQž••Åø|>{þùç5¼"ŒmÙ²…`ÑÑÑŒ1Æd2kݺ5{þùçÏcL,3 6yòd•6µjÕŠåä䨜ÛÖÖ–3F©L °‰'ªÔ‹ÅÌÛÛ›ÙÛÛ³ÜÜ\Eyii)1b377g>¬ñ¹L›6™››³¬¬,•ceeeŠïÈ(=ÆÛ¼y3ÀV­Z¥(ûóÏ?6gÎ¥sÄÇÇ3ÇÆŽ«(»rå ãñxlüøñL*•*Ê333™™™4h¢ìüùó {óÍ7•Ú°iÓ&€ ‚Ÿ+!äÙ@=Єz%ïµuuuÕù±AAAŠ^R¹o¾ùfff˜7ožRyÏž=áãã£4ÌÂÌÌ 7Ü"//™™™˜4iJKKñßÿéÜ&9ùЋ7ÞxC©|Ò¤I066ÆþýûU3dÈtêÔIqßÁÁݺuý{÷4^oذa€Ó§Oîܹƒœœ,^¼fffŠòK—.A(bèС*ç9r$ìííµ|†êEEE!11ï¼óZµj¥(722Bpp0JJJpöìÙÏáææ±X¬öyóùªÿ ?^éþk¯½‡}ûö)Ê6oÞ >Ÿ?þXé:t@ÿþýñçŸ*†ílݺŒ1„„„Àظb꣣#FމsçÎA(€b%’×^{M© &L¨ñ9Bž-4‰R¯œ™™™:?¶ê¸[¸{÷.,--1sæL•cÅÅÅHOO‡X,Vç}ûöaÆ ˆ‰‰X,VªŸžž®s›ä!п¥r[[[ôîÝ/^DII ÌÍÍÇ^|ñE•óøùùáÆ¯×¡C¸¸¸àÔ©S Å™3g`aaAƒ¡_¿~8uêæÌ™ƒ3g΀Ú=zôh]Ÿ¦ yè=wîÆŽ«t¬¤¤Wã9¦NŠo¾ùýû÷G@@F…3fÀÓÓS¥.ŸÏÇÀ•Êàç燄„0ÆÀãñ •_¬î½'‰ððáC¸¸¸ 66¦¦¦X¾|¹JÝÄÄD0Æœœ ___$''ÃØØƒ Rªçää„N:áþýû5>WBȳ4!¤^y{{t~l»ví”îK¥RÀÝÝ]Ì+“—Éd2ÀîÝ»1cÆ 2«V­‚¯¯/ÜÜÜpãÆ ¼ñÆJ¥:·I®  -[¶T Èr...`Œ¡¨¨H鸵µµJ]§õ5‡ †ˆˆˆD"œ>}ýû÷‡©©)†Š5kÖ@&“áôéÓðòò‚›››Êã«.UWò±ÝÎÎΰ³³S9>{öl•e«òööFZZ¾ÿþ{„‡‡cåÊ•X½z5^~ùeìß¿Šº–––°µµU9GÛ¶mqçÎH¥R˜ššâÉ“'°²²‚“““J]''' <&&&€¼¼Š²Áƒ#;;‘‘‘¯7xð`…B­–ìÝ»7ÕŸó¥K—CV!„Vá „Ô»ÂÂBÀ°#F°ƒ²˜˜våʶsçNÖ»wo¥Õ ä«Füûï¿*çÊÉÉa­ZµbNNNlÏž=ìÑ£G¬¨¨ˆÅÄİ 6°EÝ)S¦0léÒ¥,33“¥¥¥±¹sç²V­Z©¬ªÁc]»ve:ubßÿ=;{ö,ûï¿ÿcêWá(((`ÌÞÞž;vŒ=}ú”]¹r…ùúúª´]¾ ÇîÝ»UžÏ´iÓ˜.½;wf˜½½=“ÉdŠòqãÆ1ŒÇã±ììl¥ÇÔôz2¦~ŽÅ‹3cccÆN:Åþúë/¥cؤI“ØíÛ·™P(déééìäÉ“lÊ”),66¶Æç0gζ}ûv–À$ »{÷.{çw¶nÝ:E½2sssæììÌ"##™P(dW®\a;wf|>ŸÝ¹sGQ·¸¸˜yxx°-Z°-[¶°´´4V\\ÌâââØöíÛÙœ9su% óõõe–––lݺu,%%… …BvïÞ=¶k×.¬¨+ ™³³3kÙ²%;~ü8 …ìòåˬsçÎÌÎÎŽVá „0Æ£Mi‰„-[¶ŒYXX0J7vøðaE]M/>>ž 4Hå<lñâÅŠz¬k×®JuzôèÁNž<©6@?~œ½øâ‹ÌÒÒ’P„Juš1Æîܹ£´ò[«V­Ø/¿ü¢T¯>ôûï¿Ï° &(•Ë—¹ëÚµ«Êcj “’’ØŒ3˜‹‹‹â¹É•••±õë׫ýYúøø°”””ŸÃ„ õy<ÀÚµkÇæÍ›ÇJKKõÈÚµkÇŽ?΢®••‹ŒŒT9oZZ{ùå—UÚdjjÊÞ}÷]¥ºYYYìõ×_W©kll¬²dÝ;w˜‡‡‡¢½<-_¾œMœ8‘4!„1Æ15_%„z"“É””„˜˜XXXÀÛÛJcn ‘——gggÅjêо}{•:ƒ BJJ RRR  ²²2ôèÑCíÄÂÊíÅ“'Oàì쌮]»V»‰Off&bcc‘““gggøùù©)‹qíÚ5<~ü}úôAË–-‘““¡P¨2Ù•òì¡M!¤Q¨  !¤1£I„„B!„耖±#„Ò(899¡´´ÔÐÍ „h‡wïÞÅÑ£GUÊ ¤XâH.)) ?þø#’““ˆàà`Å‚ü„B!¤ù¡h5nݺ…ÐÐPôìÙSi¢SÛ¶m•ô70|øptèÐÝ»wÇÚµk±oß>DEEU;…B!„4mÔ­Æ0iÒ$”””Ô¸‹ÚСC!‘Hð×_ÁÈÈéééèܹ3-Z„ è±Å„B!D_ha-Ý¿gΜÁÈ‘#[ú¸¸Àßßß}÷[G!„B èLš4 C‡ÅìÙ³qåÊ¥c‰‰‰€.]º(•wîÜÉÉÉj·/&„B!MVÃØØÇG§N••…ƒbçÎøî»ï0cÆ Ü¦˜+ëÒ¥ JKK‘——{{{À´iÓpùòe8;;+ÕussÓó!„B©^jjªÒýŒŒ ôìÙ{öì1P‹?êVcüøñ8qâV¯^]»v!++ Ý»wLJ~‘H¤óù¢££‘šš ©TªTžšš ±XÜhÊ’’’”þ5Ä5þþûï:Ÿ/55UqÓå±.\Ðúòº5Õ‹GNNNµõ®^½ª²!„¼^~~>bccÕ>öìÙ³î½ÑP?ËšÊnß¾¸¸¸:ŸO›ŸeÕ2]þ-dff*þ"U—÷_TT”ÎMJJR´µ1½7ª–]¿~IIIµz쓤ÇðLsEÇ,OtÌò„ÐVç×OÛ2ù犮Õåß‚6ŸC111ŠÏuõîß¿›7oª}¬|§Eu={öl­^}—ÕÇϲ¦²ÌÌL\¾|¹ÎçÓåÿ9]þ-¤¥¥)>[jª§éýwá­››› ©TŠŒŒ DGGƒÔÀ`›ˆ71ááá »zõ*cŒ±'N0,22R©ÞìÙ³™±±1“ÉdвÁƒ3???½¶·6~ùåöË/¿4è5¦M›VçsÔ¶º\[›ºÛ¶mcÿþûoµÇ#""Øü¡öØ7؆ ÔóòòÒª†V?Ëšœ={–íÞ½»Îç©M;uyiÛNMí¨éxuÇBBBXHHˆÆkZ>[nH›UPq;)V[>[8ôÙ¢}¶h>æççǬñÚÏ2¡¥¢¢"€‰‰ ÀÛÛƒ1cÆ(êÅÆÆÂÓÓSiù;ŠáY§NücÇŽ­ó9jÛN]®­MÝÀÀ@899U{Ü××·ÚU\œœœTÖ—ëׯŸv4°úøYÖÄÝÝ-Z´¨óyjÓN]ÞcÚ¶SS;j:^ݱ^½zi¼nc@Ÿ-ºÕ¥Ïúl ûÙÒ2‹¡Ñ2vjddd(WÎËËCÿþý‘GÁÔÔ€öËØ 2pæÌý>Ò$#<<ÜÐÍ M@dd$€†u³ØZRqÿ50ÌÔpíiÂè³…h‹r‹fԭƸqã •JѹsgäææâäÉ“011Áá¾úê+ >ýû÷G÷îÝqìØ1téÒ³gÏ6`ë !„BHC¢­Æ—_~‰S§N!%%Xºt)¦M›¥zˆŽŽVlå½`Áµ[y»¹¹© Þ'¤:!!!†ni"šÊŸäIã@Ÿ-D[ŽŽŽ†nF£FZ`À€ZÕõòò¢E‹4Ö“/{Gˆ&aaaôgV¢ù*Íz©7ôÙB´•••EKíj@ZOèH´EÿÁmQp&º Ï¢-Ê,šÑ:ЄB!„è€4!„B!: ­'4‰h«êî{„T'77¹¹¹†ni"è³…h‹2‹f õ„&m………º ¤‰¸pá‚Òv„Ԅ>[ˆ¶(³hF“õ„ämÑD¢-šDHtAŸ-D[”Y4£hB!„Bt@šB!„P€ÖO´E}ˆ¶h!Ñ}¶mQfÑŒ´žÐ€|¢-šèC´E“‰.è³…h‹2‹f4‰POh@>ÑMô!Új®“÷ì9„¬¬<€Ã£¸å©8‡t<¸ž  ôÃ!ϤM}¶mQfÑŒ4!„<#öïÿjîY èŒáÃè¡Eê-Z´ii¡ð.lÇ^àÚúôÙ‚þ1\€–J¥(++ÓXÏÄÄFFFzh‘zwïÞÅÓ§O5ÖsuuEÛ¶mõÐ"Bš> ЄòŒøüó½HIùDc½=64@›šÚè]~¯@I¥£®L|þvý6¬Š¡¬ÌOc=1¢¢vë¡Eêñ. &j¬çç·.ì×C‹iú(@ë È'ÚŠ‹‹C§N Ý ¢ƒœœk¬ggg[[Ûz»®|¡½½½VõMMmQL«gddØ`ÚTµGJŠæ×ÊÍ-¸áS  Þ ¦Ï–Kzjчëׯ£  @c=OOO•!b± ¡šÖ,P€ÖO´Fc›˜^½^ƒP8Pc=oïx\¸QoוO lJc¡ËʉDù&+߉´;—P$$ææ€™YÅWŽ–h䄺MžP(„D"ÑXϦ¦¦zh‘z£GÏÃÓ§Áëõì¹ ýµW©,++‹ÆAk@ZOèH´Eá¹é11i‡¬¬¥ëyx×ëuu Îb1^µñè°|¹jЭ.ðêRO&«å“UãÆ  CÕrccÕP]Ý÷u)+-ս͌©¿Éd wL*•_=¼¯6‘ëÜy8JK{h¬×©Ócœ:µG-RÏÌÌ™™Áë1ö—JeÍ(@BH#ÅP\ T+,¬ù¸ü¦E‡™Bj*°xqÃ=¯†VZʽ.……†n çâE€Ïç~ž™Hĵ‘Ç3tK7cãöxð`£ÆzîîÁ ßb0  !DOòò€;µÄ……õÛc«+>SÓêošŽ×¦ÎÛos½àštê|þ9üD" ¤DùkmÊ*zkëWcÏpí`gtïÎݹ›§§æÇ’ÆE*5ìgdz€´žÐ$B¢-šDØ4”–IIÀ“'Ú=&! ‡õ‡›DhaaÀư¶†âûª÷7n²³5ŸµgOàŸ 7–ØÜ\»z­Z“'×ïµËÊTCuu¡ûý÷µ{=]]3¸ž]u7>¿a-X o§¦I„ÀÓ§À™3ÜM®j¨îÞðð¨Ë«ül()þû{¯ˆÅÊ·ªeu½_µ¬®¿°Ñ$BÍ(@ë M"$Ú¢I„Nœ8¬¬õ||: 0ð¹z¿¾P$'s!9) HL¬øþÁƒÚ•31©9ìjsÿüù 07ÆÓn,tx¸vÏv'â––ÜM“O?Õîõts–.­sÓjí‹/äí¬y¡·70d×}çNŰŸÇS§¸›\Ë–Ê:0h×®Ÿ„‰DÜЦû÷¹¿iãúuÀOó*‡M"ÔŒ´žÐ‘h‹Âs…Y³6 5õ=õzôø —/ÿX«käçW„âª!9#£~ÿôîïDEqá×̬îç›<¹é¬¾¡ ±øll¸åá¬ÊZÀTZ1†ÀÂ86ÆòÔÚ@ã-š­ð::ß~Ë}/‘p!úêUîví×›*â’Ÿœ<ÉÝäìíUCµ««ö­ z ùùšgºÐ_}µ@ûkA"árJ ’SR*n÷ï™™ú†cdÄ g’ßÌÌj¾_Su븟SmPfÑŒ4!¤Ñ23³¤±ž‘Ñj1ƧU“’¸Þ5mµn xyq=u^^·©S¹si~>€ƒƒö׫obqº"˜Ö„1-בk 'NlUìœgyOÇ#6(((„5zàin'BW×— ÙL2†}­´!‘<†6k<ËdáÕÔ´bØÆìÙ\™X ܾ­ªcb*þ“›Ëý‚UqÎÖ­•u÷‹úë'&–!))\c;MMƒ5Ö©ª´HKSÇ•¿ô¨þÇ wè¬Z¥[ 6®ÇT¶sgí4ÑŒ4!¤ÉcL9Wþ>9™‹¨ >ŸûϽj@–ß·¶Vÿ¸¦2Ü!*j kñƒ‹Ë‹zhMõ|||*âAÄ=\¸×ǃƒ+ÐÛpkëV6l˜;:vüPc½  azhMõ–/YY75Ö |§ÆãУw“‰€›7¹0-Õ±±K&æäüÁÝä•')vï´iS›gV¡¬ HOW Æòï>Ô~G€û,hÛ–ëíî®üuÚ4n—&­[¯¾Z›gCš ÐzB“‰¶h¡î._Ú·×®®@Àý'X5{yqåµÙ÷ ¬¬@ŠÆz•{øêƒ®;vìØQé¾T*ÅúmÛðÉܹõÚ®úv-6Ò€çpùÖ- €á¶¯jûö/ Ý­L›6@ÅgKAAB–/ÇÖµkë|n33 woî&'*‡ê«W¸¸ŠÞ¬,à÷ß¹›œ³37QÙÙÜ:啃rZšnsx<ÀÉI}@vwçÆ­›˜¨l}ö7$±8À_ë1¦ÚõN“5k"oƒ¦&mÑ$ Ú.)Vul¢ú€ìåÅõ0óùõÛΙ3‡ %%\c½~ýÆ×ëuëºá7»vaÅîÝÜ·/zÖgÓê͹‹Qhk ´°ÅÃô4<-(€-´û…(“¶,]¿û®_ÇÄóç1°ÿz¿Ž…з/w“+.æ6¾©ªïÝ«ÕÚŸ?!A»uÊÔ‡cnÂcmó!cRÚ,½cص 7lxyy)ëªþ…&jÆc¬)¬NÙ´ Éa„hrïpîð÷ßÜ-55GXž IDATÚìžæâŒ°°pEXÖ²Cö™&•JÑåÅ‘ðÞ{è»g.þü³ÁÚRÆ„2ŠËÊ ,+C±LÆ}-+ÃúC0¦ÅGÜo=……ÈmýB½o°¶6u˜<Éヒ^;vàÒ/¿¬-EEÜJò¡‡C* ×â‘ÁÂѲeõÙÝ ò á£V!-Myù‘H33å ŽÑo¿ýZÃ4¢222ð^h(k‘E(·hF=Єƒ`Œ›Õÿ÷ß¡¹¶¨qq©ÿõ€›»ovíBêÀ€µ5âíìpùêÕj{¡Åòp[õk•°«Íqueâêfo]¹ww  üOÖÖ8óàVÿö¼áenÎÝÌÌàYþ½›@#ÚJ¯ZKׯGÊÈ‘€¹9۶Źê…Ö†•0`w€èhí&ãúûsŸ66 Û¾êlØð©ÒýsçÏãÕ©S‘‹†Jíõ`aXN>y‚SgÏbØàÁ†nN“Gš¢eeÜŸpåùÂ…êgˆÛÙýûÿþËMB"õK*•bëádžòÆŒÁˆE‹ÐqåJµaWf¨?Tþþ;ðú àx¥²AƒP›3gâfQ‘ÊCLx<´33ƒ—¹9<Ë¿Êožff°lÀŸb±›vîÄ‚÷4/½ØÐdŒ!W*E†D‚ ‰Äb¤äçãûK— ä)aaØÐ©Ú››ÃÛÜM`F¬¥¥á³:!6àñ‡bù¦MX]þÚ668—žŽÂ9sºaèz@ZOh!ÑVs™D(‘W®T Ǹx‘ÛšZGG.0ÈõFùùq“|:v¤]]'Ê}°e ’ä^d°²ÂS\¾z•[{«ŽŒyXµ g#"tz¾"™Lc(~$#W*­ÝÔ4¡ÛŸ½jïøØ±ÀW_~~`ÒÅb¤‹Å8[ez>Wy¸®°=ÍÍk|mjOXl„Ÿ5À¹ëNÊæo܈¼·ß$kÖ`Êû1”1Æ}÷‹RYùW•ïËëV{¼çåæ".9xå@þ‹/jì…¦I„šQ€Öz#mécÒF¯^¯A(¨±ž·w<.\P ¸^ey`¾r¥úU3¼½+ó€Ü$m>¼BÑËZeÚ°™Ñ68'–”àóû÷q0;ì·ß€#*zŸå¬­aÔ¶->‰ ¾"ǯüäNª_/ÌݧŽCnn.ìííá"ÀE P»ÀÝ“ÒR¥@\éût±XixJqYî㎚_.x<¸ \¨®Ôk-Ù›¶mCêС€­-bÍÍqûÎtõóCaY™"?ª! ?©Ë¾ðZš˜ÀÙÔN¦¦p–ßÅ÷á_}…½Ÿ•Ñæææ°íÔ ï<} ¾ŸKJPÞƒ_Tiádcx áH„SUÖ7*6ÓÞܪ„kw33­Ç¤ÛÚ>†O°â~NIòmJàVx æf­åžžŽº¾µ_â£!ìÞ ¼Vi2£½Ð”Y4£MÈ3ÈĤ²²–j¬çá¬ø>/·,Ÿðwó¦ú x<Àǧb8Æ€µß$!°‘.­ÖTdJ$ø"%ßed@ÊPZ Þ¥K`U{ŸËå‹V¯ÆÅÇõÜReŸ……ÁÈÏv‘‘°Éq„ ¥»â˜Õ?wa—vùVlÚ„Õ<¹ÆÆènmîjvÁËdH‘÷ZWé½®:4¤Œ1܉p_$RݾR*…ѯ¿¢¬|mµì±cÑ;$üë²{GF<LL”‚°“špìdjZãðŠ‚‚̸u ²aê7tyúâ‹øKÍŠ ¨…BE¨–­ü¼ÊCrI ’KJUåÜ&<<* ©Ü{í&€_)\_»vTñ½P(D×ñã‘ûn(\öìÁùCáÚ¿p•Ÿ{i)€Õ„âl‰D©z‚jö£/‹qúÒ%­—´“?§-Z¨{$«kùÄNa¥p-–ÉpW(ÄݪáZ,/* lÉ@ÎË/cà¢E0ùì3Ö¡G_Έǃ½‰ †ËC±C¥¯­ËÉËlÔü¼ú.Zá¬YjÏÿ((¿îÚeð9ä+o ê{³\M½Ð4‰P3 ÐzB“‰¶ÛN„ЫWÅŒ>}¸e¤ˆáÉ'¾>[Úh#``•pÍÀ…ëÊÁ:A(T„ëyoqd$ؘ1´²‚ÈÙ¢ØXµ3ŽyìäX]®r¿•‰ êÒÏ{îüy$¸¹U;ÎYæãƒC_mð9+oT§†^hšD¨h=¡7"ÑVc Ï]ºp•ÔÑ8;¿ææ¢ëÕ«ˆ©4ñ­ƒ…Vzx`|ëÖl]ó'‹±çÄ H.T{<{ìX|¸z5ΔXC3ôg‹|™¼¶Õ„ë‡b1bòó1%99'*?øÕWáòí·˜?z´RïpëòPl¬ÇÍs>\±¹={r³¨«‘le…U[¶`Å‚zkWU%%%è}ìXudeeˆ½_%@SfÑŒ4!ϘóçôtíêZ[Sxn¬.<}Šäd\¬´[Kڵà gg½ŠgÕÚmÛ𠚉ykkÄXX(Vä Õ“™Ù¾gWO.ge±³3úfd gzo_e[—.ÕüWå.]àë뫟Uãç; zýæŽ4!ψ‹%K€Ó§ ÝRÿ#49Çóòe-Œñ‰›>pq9mv¡7¿Ÿ< SSî·ÒjÈÇ”EZ#¡PˆÿÒjzôs^~ó׬ÁùC‡ôÜ2e}úô1èõIã@ZOh!ÑV}O"ü÷_.8Ÿqõ„&mÕ×$Âèh.8GUZ˜Õ‚[–îС¤¤¤h<‡LV÷ï¤nr¥R¬|ðÛ=R,ÉeÄã!ØÉ }rrÐ*=v´^6ÑBc› \Õßÿþ‹öFFÜbóÕ …8EºÑ$BÍ(@ë ½‰¶êúÜ•+ÀÒ¥Àï¿W”™›ï¾ ,\88-[AJŠæëôë7¾Nm!µW\V†õééø*- •v©{ÅÞ«<=ÑÉ¢ú=Ð Q£1‡gøûèQÍ•ˆ^PfÑŒ´¥¥¥X¼x1„B!–/_ëJkh~ñÅÈÏÏWªoccƒ/4ìŒEHC¸v ÎÇW”™›ï¼|ò àXi÷ÛÙzoÑŽ”1ìxôË<@–D¢(Ø¢Â<=ÑÛÆÆ€­#„P€ÖhÍš5X·n$ BBB”ô®]»`ii‰öíÛ+ÊìÊ×[%D_nÜà‚så%~ÍÌ€Ù³¹àììl°¦0²³±èþ}$•TlÒÍÊ «==1²eKÃ5ŽBˆ Ð5¸{÷.Ö¬Yƒyóæ!,,Lm)S¦ D‹Ý†h!Ñ–¶}nÝâ‚óÑ£[l À¬Y@h(ЦMö“ÔŸ¨ü|„&'ãFQ‘¢ÌÃÌ Ë=<ð†£cµ›>T݉š4öI„¤ñ I„šÑzGÕÉd˜1c,X€Žõ0Î&mU÷ËšÜ;Àøñ@@É…gSSnŒsR°y3…ç¦ârA†Ü¼‰ Û·áÙÁÔ›Ú·G\Ïž˜\Cx¸å»¢‰¦ÏBä(³hF=ÐÕØ¸q#„B!.\ˆýû÷W[o×®]سgìííñüóÏ#44¶¶¶*õh@>ÑVu}bb€eË€Ÿ®èq65fÌ>ýpuÕ_‰f%%%077W{,^(Äg÷ïãpNŽ¢ÌÚÈó]]1ßÕVFFZ]clMÛôREcŸDHÊ,šQ´‰‰‰X²d ¾ûî;˜˜˜T[¯W¯^˜2e Þ}÷]ØÚÚbÆ  DQ¥?ÃÊ]¿~{÷îEdd¤ÒMþ'X9*£²ªewﯿøùEâС\0˜˜o¿ |óM$–/ÏU Ï¡ÍÏzYLl,Úxy¡¨¨H©Þ#±oÇÇ£ó† 8œ˜0åó1×ÅßdgãVVJáÙÐÏ£1”={"‘¨Q´…ʨ¬¹–É3Ihh(öîÝ‹ÔÔTšQ€®‚1†™3gbÖ¬Yè¡aÉŸ~ú ‹-Âûï¿ãÇãСCHLLÄæÍ›õÔZÒœ%&“'¾¾ÀO?AœgÎîݾýhÝÚЭ$ê|°r% Þy_lÜxZZŠädxGGcgFdŒÏãáMGGÄ÷쉯½½a£e¯3!„Ãã1&ÿc0€'N`Ô¨QX¶l,--—/_FDD¾øâ bäÈ‘j[VVwww<ÿüó8pà€¢<88b±zy¤i‹ŠŠÃ¾}ðã»S§Ÿxx¶}¤f1±±†ì3ÐnÍÌ\·ëóòð¸ÒZΣZµÂjtµ²ªÓµž‰I„7K­«’à50ÌÔpíiÂh!ÑÖ¤I“ hØO h tfff ıcÇe9åã###!‘Hª Ð………ÈÏÏW;šäMåË~cá##`Ê.8{y¶}D;¬\‰ìñÜ4FŽÄ¢I“}llæé‰-ZÔ˵äi,4ÑFc߉4´¡f «0`.]º¤TŽéÓ§ã·ß~ƒ““àáÇ077GËòµY‹ŠŠ°jÕ*…B >\å¼ôF$ÕINæ‚ó¾}×I##nøÆ¢E€··¡[ظ”––ÂØØp]eŒá‘D‚T‘©b1ˆDH‰ð@,FÂÝ»H”Éùzð~~À‘#èÈã!¬KŒ­çžb ÎDž‰¶(³hFº–Ο?àà`tíÚÖÖÖøçŸ ‰0kÖ,ŒOÛ?«ºu…âbõ:w6æMÛ±b°g<8|>×Y¹x1СC7¶ ŠGß¡C‘vï,,,äÅeeH‹¹P\9$—}(£´º‘oááÜBÜ•ð^y/?±6H{ !„èh-ôë×»wïVšñâ‹/â§Ÿ~­[·PTT„_|}ûöE¯^½ ØRbh"‘’’Â5Ö+, F‡€TÊÝçó‰%K€zXv¼Ùš»bžÎ˜›6a•UÅdK$J=ÇUržü‡¢ƒÖ&&h‘$[[ˆ«ìFʺvÅÏ_~‰%EE°ªã˜gB!h-x{{ûÊßÑ­¬¬0fÌŒ3F«sÐN„¤²ìlî+ŸL˜Àg®Œ&ú¨[Œ¡lÈøòK|>w®J/´D&CšX¬2´BÞ›œ&C$Ÿ™©%c.Ú™™ÁMþÕÌ í¸•—YaØäɈ0Aí9RGŽÄ7bÍçŸ×úù«óLL"$õ†>[ˆ¶h'BÍ(@ë M"$UɃs—.Êå4ÑG½¹+V ë•W©/¼€WV®D×3”†[dJ$ÐuY!k##.W È•¿ocj >¯¦ý¹•7n@ùœˆªd]»â`X×s/4M"$º Ï¢-šD¨h=¡7"©¬kWààAõÇè?¸ 2Æp«¸®^Å9‰hÕ PæïË–áÄ€@ ½$<ަ¦Š㪽ÇíÌÌ`Wç,Z„œ.]€›7«­“æêZï½Ðœ‰.è³…h‹2‹f  1€šÿÖ,Ä …8óø1N?y‚¿žujµ½Ç®ü†ß/ê“Ù³ñzZZÍ•z÷F÷nݼ-„BhBˆA=‰pæÉœ~ügŸ<Á£ªóÒÒ¸ß8Ê{Ÿàvânûù5ØŠÚ=b„A¯O!D¿h+o=¡I„φºŸ#..®î'iIJ$ÈÎÆÛññðŠŽ†û¥K˜‡ýYYJáÙÁÔÐù×_×_W{®´^ÀŠM›ôÕôF'77W1‘Mšûg ©?”Y4£­'4‰°y+->üÈȨû¹ÂÂÂê~’FäIi)ŽææâƒÄDø^¹§þÁ¤ØXìÌÈ@rIÅÍ¶ÆÆxÙÞ½½q§Gdöí‹eFFÈ31Qí}.Wêg! õõt• .(&¢Isûl! ‡2‹f4„COh@~ó•™ ¼öpþ|ýœ¯©Oô–•áÂÓ§8óä Î<~ŒkEE©ÙxÄœÏÇó¶¶jg‡!-Z »µ5Œª¬v1gñbduéÄÆV{½4¬Ü¼+?ù¤ÞŸKcG“‰.šúg ÑÊ,šQ€&¤þù‡[ŽîÑ#î~«V-áé SÓš7`€Ã7NGpvvÖùqRÆp© g?Æ™'Op© 5k-›ðxèicƒ!-Z`ˆúØØhœà÷î›o"(5¨nç?ðóCïçžÓ¹Ý„BHmQ€&¤–¶nå†mÈ7®{çàë¯×k ÏÑå«W1üå—qÿ¿ÿвšµŒådŒázQ‘"0_xúÅee*õxü­¬0ÄÎCíìÐßÖVFF:µküK/éTŸBÑ ÐzBò›’.,ïÝËÝ73ãÂôôéõs~Cì6Í~ú)>]»ÛW¯V9[\ŒÓåC2þzòOJKÕž§£……bHÆà-ÐÒĤ¡›þL£‰.h'B¢-Ú‰P3 ÐzBò›‡”`Ü8àÆ î~»vÀáÃ@÷îõw }ïvùêUĵjæë‹¨³g‘ŸŸ§8ýø1Δ/-—)‘¨}¬›™†´h¡ÍmèW¯h'B¢ Ú‰h‹v"ÔŒ´žÐ±é‹ŠÞxÈÏçîDDT»@D­éû?¸ùkÖ 782z4Ü?þ…S§ª­ÛÚÄCÊÃò;;x››ë±¥¤* ÎDž‰¶(³hFš V­/äsãBB€•+=lr× .]¹‚›vv[#zz¢ððanAkkkØc ­­b³¯¥%x5Ÿ’Biö(@Rƒ‚`êTàèQî¾µ5°gðÊ+†mW}8ýø1Æ,Y‚â÷ÞS>ðê«èyâ6­\‰@5KËB!Ϻ&ÞÖtÐ$¦'6èÑ£"<ûø—/7|xnèÝÂâ„B¼tç†:„â6m*zŸå<=‘ýèÚ—–Rxnäh'B¢ Ú‰h‹2‹f õ„&6-‡½z÷îq÷_}ˆŽô1½¡v Ë•Jñ^Bü®\Áñ¼=Ÿ®]Û í õ‡v"$º ‰¶(³hFC8ô„ä7 eeÜøæ¯¾âîqcõ¹É]}Oô‘ÈdØôð!VXëå…Ñ­ZáÆªUšÿD×»7Ú·oßÀ-%„Bš ÐzBò¯;÷ßä?¢3€-[¸ ¡.»…¥ŠD½YY`åeö&&XæîŽ·Û´qù¤À€€€zj-1$Ú‰è‚v"$Ú¢5£I„zBò±˜5 xûmî{SS`ûvàûï žÚMô),+Ãg÷ï£ãåËø±<< ø|,puEb¯^x·m[Ex&ÍM"$º I„D[”Y4£h=¡ùKZ·²Æ•+Ü}à矹•7 M—‰>eŒáûŒ ,NIAV¥í¶_sp@˜§'< ù›ip4‰è‚&mQfÑŒ4yæœ9ÃM”/Ÿ;hðÓO€ƒƒA›¥³ùù˜Ÿ”„ÿŠ‹e½ml°ÞÛ}ll Ø2B!¤y£Mž)kס¡Üru0oðå—€qú—[\Œ“’ðG~¾¢¬™Â<=ñzSû-€Bi‚šPlhÚh¡aÓ§sÃ4ÀÒ’ëó\]âæ+##C7‘4B4‰è‚&mÑ$BÍ(@ë ȯ›Þ½Çãñc«ëÙÙfɶÞ|ر07×G k§Œ1ìÈÈÀ’û÷‘#•r…?þˆ77lÀ*OO¸Ò©|!…&Ú k–ã ³²²!C† k×®†nN³••Eã 5 ­'ôF¬›‚+Ü»®EÍ`˜˜ëÖï¿ßЭÒL"‘`ÕÆXºp¡Ê±?òóñqRb+m„ÒßÖëB µµ>›Iš( ÎÍ[dd$>üðCX[[ãÚµk055­ÓùÂÃñoß>¸ººbàÀõÔJýHKKÃéÓ§1dÈ•ÿOSSSñÑGaË–- ë eÍhí+Ò¬˜˜p; 6†ð vìÀW‡áâ¿ÿ*Êîã…Û·1êömExö27Çá.]ðw@…gBàÛo¿Ejj*þûï?=z´^Î9{öllÙ²¥^Î¥O7nÜÀôéÓqíÚ5•cööö˜6m O!zEš4+]ºýúº‰D‚ïŽGñ²e˜¿f ²$¼€«Wq¢|A;cc¬óòBl׺µ[Li,ÒÓÓqâÄ ¼õÖ[°³³Ã®]» ݤFËÃÃááá2dˆ¡›Bž!4„COh¡~˜˜º6ìØÔÁƒ ÜnÝž»vAXÞCbÂãaNÛ¶XÒ®ZVi4Mô!Ú¢I„Íמ={ “É0kÖ,cÇŽxøð!Ú¶m«TïØ±cøí·ß°zõjØUYâòÓO?…ƒƒ>üðCdggcîܹH$¸víÞyçE½mÛ¶W¾ÂORR6n܈[·nÁØØݺu"%Á= IDATÃǬrÝ ÀÝÝ“'OƺuëpáÂ899á7ÞÀK/½8pàŽ;†û÷ï£_¿~X¶lÌ+MJ‰D8|ø0Nž<‰ÄÄDH¥R´oßÓ§OÇСCõ~ûí7lÛ¶ °}ûvDEE1sæL;v “É Æ¤R)žýôÓZÓÉÉ ÷ï߇¥¥%z÷î(—H$˜3gÌÌÌpéÒ%x–¯ýyëÖ-ôë×ÿûßÿ­è©€‹/",, Ÿ|ò ''íڵÄ бcGÄÄÄÀÚÚ2™ “&MÂÁƒñßÿÁ·ü3ÐÓÓiiiprrRœ³¤¤½{÷Æ¢E‹0uêT´hѯ¾ú*LLLpáÂÌ›7¯¼òŠÆç{ëÖ-|üñÇ0`Ž9ÛJÛÏfffÖê5|–PfÑŒz  ©gKJ°âçŸQÖ§Ryé¸qØ·u«ZEi*víÚ>Ÿ7ß|SQ6uêT$$$àüùó rͨ¨($''ãã?V„gèÖ­fΜ‰+W®àêÕ«JqvvVêÍmݺ5zö쉧OŸbéÒ¥°.ŸÍçó1²|'«Û·o+êÛØØ(…g077ÇܹsQPP TWWëׯGii)¶nݪž¨\“Ú MH=º/¡Ë²e(>\õ ¥%Z·VZ‘ƒB*+((ÀáÇ1xð`¸ºº*ʧL™‡Ý»w7Èu“’’}ûöU9Ö§¼3 >>^©ÜÇÇFU6y E/sÕòª½¿gΜÁرcѾ}{XYYÁÌÌ ï¾û. 99¹¶OñññpvvFÇŽk}BjBC8ô„&Ö c†nfgŸ<Á„›7‘wþ<°r¥Ú:ùcÆ`þš5¸ôË/Õž‡&mÑ$ÂæçÀ …(,,TšèÖÖÖ8tè6mÚ+«š7–R'..®Úcyyy 2Z¶l €¢Q™¹š]ªŒÕ“—Ëd2EYdd$Ƈ.]ºà…^€Ú¶m‹»wïâÓO?­Óÿ›999ôï¢h¡f õ„ä× c-\]*Âzöì ·6UöÍÇø(1¥¿þ ŒU}ÅJ½ÐÏWâ!×\w #õv"l~víÚcccdeeáÏ?ÿT:fii‰ŒŒ :tÓ§OÅæ*OŸ>Uê±€üòå2åjš ,óšžžŽ.]º(KOOÀ-WŸ6lØSSSüñÇpqqQ”—””ÔùÜ*CNˆöh'BÍ(@k––†ýû÷£mÛ¶˜2eŠÊñ#GŽàôéÓ=z´ÒÒ;•ѱö.\ÖÿŒÉ;W"“áÝ„|Ÿ‘àß¾ g بYì_®T$ÂÁ߯6@Sx&Ú¢àܼįÆ"::o¼ñöï߯r¼¸¸ŽŽŽØµk—"@ËCóßÿ­4l"::Z±J…\xx8Nœ8¡Ä•uëÖ ÷Ú /¼ tìÈ‘#àóùð÷÷¯Û¬âáÇðññQ Ï×3]•|³º¶«Ó«W/œ>}ÇŽS,«G´G™E³FC¯™3gâÔ©SèÑ£‡J€ž?>¶nÝŠ×_ùùù ºuë0wî\µ¶ù).‚ƒ™ 03öîm<á9S"Á«11øçéS€«@€È_~Ás´“ !¤䛥Tž>>¸xñ"Ž=ŠîÝ»ÃÄÄŽŽŽèÙ³'F…Ý»w£wïÞ˜:u*ÊÊʰiÓ&?~3fÌ€»»{½>____=z?ÿü³â—Á7*–Þ«¬C‡022¡C‡˜››«L”ûä“O°{÷nÌž=|>AAA022Bff&Ž?Ž™3gÖës!ÏšDXƒÝ»w#55#FŒP9‹õë×cûöíØ½{7"""°bÅ ,\¸Peœ©½… ò¹-X±ðñ1l{ä®"ðÚ5Exîgk‹«Ý»Sx&„ÔŠT*Å?üðöÎ;¾¦óãï›)‘D!b%!¨­µŠ”Ú¥ÖR¤(QÕªh•µÕU»Z£Ôl©šU›¢¨ˆÈ0""!‘È›œß'‰ìÜpWnž÷ëu^Î=çÜçùÞëæ¹Ÿû=ßAÅŠóüÎÉ ÿþ™É„U«VeÚ´i™åæ,--iÖ¬_ýužÕ&¦M›F¹råðññÁÅŇ̸äµk×òöÛo3dÈlll°±±aüøñøøø0þ|µ¿æY³fQ¡BÞÿ}ìíí±¶¶fÙ²e¬Y³&×µŽŽŽLš4‰³gÏÒ¼ysùè£ò󮮠ƒbggG×®]±²²¢\¹r8::2wî\µ¿AÉC!IÅ!=Kû<|ø0ó×ñüù󉈈àìÙ³™ç¿øâ æÍ›Gtttf‚ÅÿýGíÚµùî»ïøôÓO3¯­á›é ¡²rúôi¬­­©[·n¶ãÁÁÁܽ{—-Zdó€'$$pþüyªW¯ž-V;66–ýû÷AݺuyóÍ7Q(üóÏ?xxxàèè˜müØØXBBBˆÅÞÞOOOâââ¸téîîî899e»>99™3gÎpýúuRRR¨^½:íÚµÃÒÒ²€ÿ A¿~ý077!… t>ôèÑGGGV¬XO.íããùsçx˜û ²¡téÒøúú²|ùòÌã–Ž=ªÕ×Pœyö êÔ{÷ ti¸zªW×­M©’„p0óïÝävÜ‹jÔàãü2_‘Áƒ‹EK ±¢ ]B´6k‹@U¼¼¼¨\¹²ø¼€øóôíÛ·sáÂ…3–ÃÃÃñôôÌvÌÔÔ77·<+nDEE±iÓ&vïÞmËð e ŽÉÇFŒˆ"]§2w.\»¦[ûb”Jº\»&‹ç¿ÿ¦\BÖ«—)žÕ9ï† ôîÿCÓÏc>>>øøøè…-š:vìØ1’’’ô–â~,C éƒ-â˜~ËÐ$'NdÓ¦M GèDGG3zôhV®\‰®Í)‘\¼Ñ.íÚÁÈ‘ºµ'01‘¦—.ñGzI¨*¥Jq¸^=ZÛÚêÖ0@ :A„päà³Ï>cÿþýL:5óØ‚ ˆ‰‰aúôé4oޜʕ+9„Dy2Uxòj׆ˆ(S®]“ë>ëŠýÑÑüïæMž)•¼ooφš5±ÌÑ}K hÂ!h¡[ GO ‚é …‚äädüýý3EFF’ššŠ¿¿?K—.¥råʸ»»³gÏž $E’072bcÍš,¨^]gâ9 @'ó ŠQQQBh”Pââ∋‹+Òs4±¶$%%›ÙüuIHH &&F-c ^¡Y Gh-!òófèPˆ•÷W¯–㟵Eä‹x]¹ÂêðpœÌÍù«~}>ÐEüHD¢@UD¡ (‡µeá…ܹsG×f”x„f)¡%D@~nV­‚C‡äýAƒÀÛ[{sÿ‡ÏõëÜKÿ•ý– ¿Ö©ƒ£™îëËŠDªˆ$BÃE©Trûömnß¾M™2e¨W¯¶ù4oJIIáÒ¥K$%%ѰaÃ\MÀž?N\\k×®Íõܨ¨(ŒŒŒ2˱‚\ºÕÌÌ [[[9wîæææÔ¯_ •ìOKK#22##£l¡÷îÝ# €ÄÄD©[·.¥J•Ê\\\ðôô¤{÷î´iÓ†Š+²5£ÎgŽ9BùòåiÖ¬mÛ¶¥råÊìß¿?Û5K–,ÁÑÑ1OÏn‹-èÚµk¶cÕªUÃ××—õë×S¦LÚ¶mKóæÍñððàÒ¥K…ÚŸ€®®®>|€ÔÔT† B•*Ux÷ÝwéÞ½;M›6ÅÖÖ–sçÎe>wôèÑ8;;g{/óÝ~ýõ×ÌkŸúH¥×.UA¸Ý´„È—¹u ¾úJÞ¯V æÍÓœ ¼ùÏ?™â¹véÒœoØP/Å3D~ ’ ‹àà`ÂÂÂŒ••ï¾û. ™qßqqqøøøÎÇ_yÞ’†Ð,…#<ÐZBäCj*|ð$&‚‘lØšnð÷{t4ýoÞ$V©ÀÇÎŽkÕÂÊØX³¿"‰P *"‰Ð° ÀÍÍM¥ëó=–ë\Q~œuì¾}ûbnnÎò|n·nÝ>|8›6mbãÆ¸¸¸0tèP>úè£<¯Ï‹;w2qâDz÷îÍÌ™3³»}û6ñññþðVy®’ŽÐ,…#<Э1gœ?/ï ­Zix¾»wévý:±J% `r•*üZ§Ž^‹g@PrɈqÎK æ…¢ˆµê¥ôFQYIIï¼úºcÏŸ?Ÿäädz÷îͳgÏr733cÕªUܽ{—eË–Q£F ¦M›†««+—/_.tüóçÏ3pà@Þzë-6nܘ˾R¥JQ¡BΜ9“ïöÆoé5 !<Эðï¿0uª¼_«|û­ææJLKch@["#(mlÌÆš5éio¯¹Ià5Éð<¿NˆC^dÄÈ_¿~=[Ìñ‹/¸ÿ>j¨}ß§OªV­JïÞ½iß¾=¤larvvvŒ5ŠQ£Fqáš6mÊš5kX¾|y¾c‡……e&VîÙ³'WÙ:OOOΟ?OõêÕ±k½@ ´–(ÉI„))rèÆ‹`b7BëŸZ¸—œLËË—3Ås•R¥8Ý A±Ï¢¡@UD'BÃÂÉɉ.]ºðóÏ?sñâÅ\ç•é¡hE%C˜ïܹ3Ûñyóæåë~¼½½Ù³g×®]ÃËË+Ûg3¯øc'''ÌÍ͉ˆˆÈwÌgϞѵkW’““ùí·ßòÇC† ÀÏÏÔÔÔ\çEüsÑ(ÉšEU„ZK”ä€üiÓàêUyßßš4QϸÛwí¢w™ÿŽ¥çD¾x@[[[~©]›ò¦¦ê™PKÌž=[ÄA T"#PÄBË–-£eË–´hÑ‚AƒQ¯^=ž>}ÊÑ£Gññña̘1E³uëÖÔ«W-[¶ððáC:tèÀÙ³g¹{÷.NNNjµ¿C‡üþûïtëÖ6mÚpøðaøßÿþGbb":tÀÅÅ…öìÙCJJ Æ Ëw¼¹sçrýúuÚ´i“çºØ·o_êׯOË–-™8q"3gÎ$ €=zP¡B}@ˆgªálxT©R…k×®ñå—_ròäI~üñGÊ•+GÓ¦Mi•%q¤qãÆyzZ­¬¬hݺ5•*UÊ<¦P(8pà£FâäÉ“„……áååņ ðóó£LŽBü­ZµÊUEÀÞÞžÖ­[S¾|ùÌcÕªU£uëÖ˜eiDÕ¦M:ÄW_}Å”)SXºt)¾¾¾lÙ²…uëÖI™2e¨S§óæÍËVn¯V­ZÙ*‹899Ѻuk$IâìÙ³¹ljŸ¥ê·ß~‹—— ,ÈœÇÁÁ ˆêFE¤¤j–¢ òÊ*¨•Áƒ%O%%AÆpó&˜™ÉbZ]9otî̵¡Ci½cžS§²2<3##–»¹1LdZ †Á%¬H|ù¸·9´]CMRRuKQhƘ4IÏS¦¨O<ïÜ»—Ð:u |yN›šrâÔ)¨Z•ŠffüZ»6͵ÑÖP A‰E$j‰’ÿ÷ß°p¡¼ÿæ›0a‚úÆžºjqï¾ @J¯^°};¬­¹Ø¨‘Aˆg‘D(P‘D(( ê^[öïßO›6mD›l¤¤i–WAh-Q’’Ÿ?‡Áƒ!- ,,äªê*½¼sï^BêÖ•ËyØØPªBVš˜PÉ@ÚŽŠX=ªˆN„‚¢ îµåæÍ›œ8q‚û÷ï«u\î)IšåUZK”¤€ü/¾€;wäý™3!KÙÑ×fÚêÕÄgIHêÙ“ sæ¨o#bΪâãã# *£îµ%C8;;;«u\î)IšåUZ V†•+åý·ß†O>Qߨ;÷î%¨ví—Þç ll¸aaÁ¿×®©o2@ H†€ÎZñC ()-P±±0dHXYÁ†  Î*rSW¯&!‡÷9ƒHÆÎš¥¾É@P B@ J2B@k‰’?v,Ü»'ïÏŸU«ªoì{÷rËÓ3·÷9kknXZ„Z$ TE$ Š‚º×Âa¸”Íòºˆ2vZÂÐò÷í“=Î:ÀðáêÚòå¤í[˜*8˜å®›òì³–/g˪Uê\ˈN„UEAk‹R©$"" Ê•+§–1úƒèDX8B@k Cþ FGÃGÉû¶¶°v­zÇO“$ÌgÌ@zúc…‚ãõëÓÐÚ:ÏkM‹YÛî¼âY *B8 Š‚:×–‡’––&Â7 CÖ,êBhÁk3jDDÈûK–€ºïæ-~ð€ ÏŸƒ™ã\\hno¯Þ @P$¨ªÎX= !b ¯Åöí°m›¼ïãªwü¤$¾  †…SÅb-:çܹs4mÚTÇ–ºAh-aˆùÁÇËûvvðý÷êŸcø­[`¨£#mmmÕ;ž": TEt"u®-h!  CÔ,êFÄ@k C È߸öî•÷ûõƒ^½Ô;þ£/€£™óªWWïzŒH"¨ŠH"u­-¡¡¡DFFR­Z5ìENŠAbhšE´:66–‹/r÷î]$IÒµ9Ãýû/; ::²eêŸÃïömž*•,wwÇ6¿úÏ@ Ð*áo½õ–Ž-t‡Á èßÿN:áää„­­-Mš4ÁÕÕš4i¼yóˆ×µ™Åš¡C它?üê.º'*Š_? §½==ììÔ;@ ^™Œð ! %ƒÐ{÷î¥I“&ôîÝ›²eË2jÔ(¶oßÎ¥K—8xð 3gÎä­·Þâ‡~ J•*̘1ƒgÏžiÍ>C È_µ ’÷‡ .]Ô;~¬RÉÇ·oPÖÄ„ennê  ’ª"’EA]kËéÓ§ÿlÈŠfÑ$s_|ìØ±Œ=š!C†`›G²Y‡$‰ƒ²dÉ*W®Ì| û ! ?8Æ—÷+W†… Õ?ÇÁÁ„§ÿá.¨Q#ÏŽƒ†ŽèD(PщPPÔ±¶Ü¿Ÿ .P¶lY4h Ãz‡èDX8# ƒ‚‚0R¡Ä™B¡ S§NtêÔ‰´´4-X&SÜ?ˆiiðᇠ¬_66êãxL ?„‡ðNÙ²|èà Þ Š B< TEgAQPÇÚ²mÛ6$I¢gÏžÑùU7Å]³hƒ áPE<«ã9%•ŋ᯿äýQ£ÀËK½ã'¦¥á{ë`ilÌjwwõN ‚×fË–-ôíÛWÇ–ºÅ`<ÐYÙ¾};fff™Þ™Ó§O3eÊnݺŸqã;v¬Ž-,^À—_Êû5jÀœ9êŸã›ÐP‚˜^¥ Õ,,Ô?‰@ !Î;ÇÇ ½®fÍšÔ¬YS  êçöíÛ\ºt Ú¶m«ksbzÊ”)|ûí·™G¥¥%]»vå‹/¾ ÿþTPw×B(®ù©©0h$%‘‘\ÿÙÒR½süÇ‚{÷hbmÍ'•*©w‚bF@@€YÅŒ9~~x_¸@A­~þU(¸2r$ß,_®¶y3íD¥ ¼îÚ²uëVz÷î-îà8ÉÉɘ››ëÚ ½Æ ÿîÞ½‹‡‡gΜáòåËüøã¬X±‚¶mÛräÈ­ÛT\“çÌóçåýÏ>ƒæÍÕ;¾R’vë©’„©BÁÚš51V(Ô;I1Ct",~|¹laööø@¾ÛWWÆLŸ®Öy‹Ú‰P©T’””Tè–ššªV;úÁë®-"|£äP\5‹61Ht¹r刉‰`Ïž=4lتU«àááÁÅ‹éׯŸVmÒ×€|¥R‰2½aIN®]ƒ©Så}OOS¦O7VûüóïÝãrz]î •+S·tiµÏQÜI„ÅÆM›²ÈØÇóôB_62¢rÇŽ”SsÑô¢&vnÒר¨¤F½xk§N,X¿þuÍ诳¶\½0Füã IDATz•›7oR¥Jš5k¦>£z‰¾j} ô;ï¼Ã?ü€¥¥%[¶láÓO?Í<÷àÁêÕ«§Cëô‹7ÞèD||î6Ù’AJŠüØÌ,só­j;0!©¡¡Ô´´d’««ZÇ×ý ÉŠ©©)ÆÆêÿA"Ðc,`q×®LIo”•EÎÎ,œÇÖ–¦íÛkÔŽÂ R©Ó^åÊ•qrrR¿QQ˜M(gnyüS²[döJIèhy{ °¶&ÊÊ ¬¬°+[V%QndmMï>}Ø2gr »ÀÁÏ-z=ÛÔÀñ#GˆH¯ _6̆·]2ÏÝ=Á£‡òû÷FƒxÖ©£‹#¯º¶Ì›7¥RÉðáÃq(¡õùK"‰°p F@/\¸ððpºwïNóæÍ122Ê&žÊ—/¯RÌ`bb";wîäÒ¥KDDDP»vmºvíJýúõ³]wöìYŽ?žëùÝ»w§V­ZÙŽ‰€ü—ˆ/ùøömž¥{>W¹»cm@±À …‚>&°%/ôwŒ+Äç$B Þ8C3gòn^èå¶¶ŒÒƒÐÏçÌábŸ>Œxò$ßk|]\˜6mZÁÅÇgÆY…² nLL jUpswwùߌ­re¹Î#0öüùÌXèE®®,ܾŠ’<˜š qq*‰mŸ‚ÎÇÅÉ-Ds`„,¢·²_|%·Í##(_>óßW¬çþåòåüÑ¥ _åñ“á‹þ±jU~È(P/P‰WY[¾ûî;’’’:t¨ø+AˆÿëÂ1íåå…—— :tˆ½{÷âëëK||<íÛ·§{÷îtëÖ {{ûBǪQ£ÑÑÑÙ’¼V­ZÅÈ‘#9tè]ºtÉ<îèèÈÙ³g5òš ‘m‘‘ìMÿRì_±"Ë—×±Eê§ç€ôž9“~¹<|O?Æ£Iù¼‘‘ü¯Žö‡KLLr èçÀE##¾Ø±~ûM.ü±YX佟×c3³×~/Û´kÇÊjÕHxò„¼~ßÊ´n££#$&BPPÞB9"Bµ ŒÀÕ5»8ÎËUªÈ"º2*r‹ŠzµÊÆÆ`k+o¯ƒ$ÉÞìgÏ 66›¸îK¯¯¾¢_DƤ{ŸË—ÇÓÅEÎ~üò ïIK“Ïå‘(™'ÖÖª 튡lÙ̧5nÚ”%<‹ŠÂ&aOš˜Ð°OŸ";GÔͨž=y^H.AJj*­zõbÄĉZ²J}<}ú”•+WbllŒ¿¿¿®Íô ƒÐXZZâããƒiiiœ9s†½{÷2wî\|}}i֬ݻw§GÔ¨Q#Ï1Lòø’lݺ5/^¼Ð¨ý†Ì“”Æ`gjÊâ|ÞÿbËóç°w/Š­[é”ËÃ÷0.û¨X­€?¬‘Î+€Ÿ<� ŒŒTÛ…œéíÍŠ[·ø<÷m¾•ÓBCÁÅÜè­P°%½"Ç>;r2’% ž<ÈHYPöo~a'qqòvçNáöššÊb:]P)S†%LJÌ}éÊJ•XóÙgE{?4@GG¼wï¦Ažþ •-KÍ&M´h•ú˜5kqqqôïß?ßïK ¤bp:+FFF´hÑ‚-Z0gÎÙ³g{÷îe÷îÝœ:uªÐ1ÒÒÒ¸|ù23gÎÄÙÙ™:d;ÿäɼ½½INNÆÃÃ??¿\±× ’>½s‡Èô ‹jÔÀ.=¦X“œ –-°o¤'fõD¾Mžg <­Zž=e/ž$É[ûOŸRÓÆ¦ÀkòÝ/ʵ’Äp¥’gÏÒ>½úsà‚¥%ã]]å×’±%&æP iirèD •ªÐX $@6/ô- L|<Žù5qpÈíEvs“ûÑk¸U|ã¦M¹ öºÏ9yN„9Þùï¿Ü•7 9T£|yÈŽ–' ª íÈH92¯:))ò h ,â«,—]…†bY¾¼{^¦ÌK}Ö-¯ã9½fÝùOfÌ`ìo¿±!½gNsÕ«3Vǹ%‰ðêÕ«,\¸333¾þúk [&Ð7D¡ H%”çÏŸzÍðáÃ%äŠkÒo¼!ÅÄÄd;ÿÝwßI>>>Ò¤I“¤ž={J¥J•’ÌÍÍ¥#GŽd»nРA’«««´qãFi×®]Ù¶Çg»VÛÇjÕ”®ªvIðXz©²²kÒdÐkÍûGt´Äôé»wKÿýWg¯W-Çvî”oß.I~(IeÊHH»@zœñÆÙÚJÒÐ¡Òø÷Þ“V”.-I MppTšcРAZ}m 'O–™˜HHƒJ—–vïØ‘÷s%):Z’îÝ“v-_.=>~\’NŸ–¤Ã‡%iï^i×gŸI—,‘¤eË$iî\IúæiWÒãaÃ$iÈIê×O’ºw—vÕ«'=nÚT’4$Irq‘vY[KK•’$…"ó˜õ==Ò¼dž^®œ´ËÃCzܧ$MŸ.I[·JÒ?ÿH»~þY>/<öºkÉ–uë¤*Òßÿ­½×¡TJÒÇҮ… ¥Ç¿ü"I›7KÒ‚’4a‚´ËËKzÜ®$5j$I..Ò33ÉŸ:R" $)}›ÁÛÒÖ¬oy|^T>fb"IvvÒ.éq½z’ôÎ;’Ô³§$ *íòö–ûûKÒ’%’´i“üÿö[éñ‰’&I±±’”–&õíÔI:bd”çß•-+ûóOÍ¿§*4hJ×¥¦¦JMš4‘é³Ï>Ó©Íâ˜vŽe¬#þþþÒÆ¥ºuëf~^ycPhI’¸zõ*J¥’F¡P(¸sç›6mâܹs4iÒ„/¿ü •bç&L˜À€8uê?ÿü3­ZµâÏ?ÿ¤bÅŠ|úé§Ùš´ÄÆÆR£F >þøc²ec“W$ŸîINN’€ 9}?ƒ¬ÇT¸5žÏSS€•±1+óðÐë=’§NÁÖ­°y³Sš•R¥ sg4:v33šíÚÅúÿþÃ' ³òÆÍ›7 jÆ ìÞ½[C/$7Ãýýù`ófZäà@‹ôp¥\”*%oåÊÉ!µkËU2HM…–-³Û½»hÇÊ——½Ý °kÔ­ ææ´IH`å!¸¤Žnez÷ÆqÛ¶¼Ç {ý7¦QUèU?/½ âŸÿ-°*ŒÚ16–ïT©Règ£1°ªJWR ðPa„G³æ˜Ô© ööðâ…\)%6VŽw72’ïxÄÄÈ[a!wJåË$Ò¼âä÷î-øùFFtµ°`µ‘^9îÐ$ç+TàSÕB‹´€ªI„Ë–-ãÂ… 8;;3nÜ8Í%ÐK^å®VIC!Izò—­ú÷ïÏ–-[¨^½:çÏŸ§Q£F<|ø[[[=zDÿþýùé§ŸŠ>ž¡C‡ ñ,€þ«<ùóÏ?©V­sçΠ\¹r¼ýöÛL›6-3‘gèСŒ?ž .ðÖ[oå;Vxx8+VÌ&²·nÝJDDo¼ñF¶ë²v? cÓ¦MÔ­[7Wˆˆ¾%ž< ³fÉû ÀôéêŸãEZÃnÝ"M’072âŠÞ|XK„†Ê^æ-[àêÕìç hÞ\Íï¿/W P…B÷ûïÉmt"̉……Ó—.ÕꜯÂÈY³ðyï=Φÿ—t^'‰°8Q§Ù[>p€÷Ô/übubfö²2HØÕGæÒÊ•4JKcUÙ²ŒX¸PEÉÙÁòîÝì ¹qqpù²¼å¤lÙ¼aÝÜä„HIJJbâ_!ù­,k¬­¹T¯ûöí£bÅŠÌ›7Oåñ†‡H",ƒЗ/_¦a×7Ð2öëÖ­›yÌÒÒ’*UªpëÖ­ôêÕ«Y·no¾ù&VVVœr£ -¡N„Å•6íÚñÛ©SÂûœÎßéHTé°Zœ±··§gß~z}gï“3ðÛ¿ŸïÃÂ8W­ã ÊûÂädλE|ÎRŒOŸÂùóò–“ ò¯2“£ºÈgsç2ùÄ RîÝcC&¥[8³v- Ç@—ÍR—[[„††£B÷ÏJ•*éôGã¾mÛx BžEӷߦaZCŸ GW£"’–––Íc\PˆFaaß#FŒÀÙÙ™+W®ðäÉÞ{ï=ZµjE×®]Qdi)¼~ýzþþûoM_–[YñïÓ§$$$0nÜ8zõêU„W®>>êÖvP?'P©ÄaÈ&ÒÑU“l^²„¡§OSÐ=€ã&&\˜1£Ø h}Ò,úŠÁh€ëׯóÍ7ß LlÙ²l¿T£ThÛëàà€¯oá sÞÞÞx{{¿š±:â§Ÿd‡+Èyo#GªŽ4IbØ­[¼HKÃX¡`mÍš˜*^/xã§+Ê+~0[·¦Kß¾ÙÆÇËÙô[¶ÀÈõf³âê*{™ûõƒúZ¾E, †OfÌ`j\Ü«çXZÊ•gòú‘'wÚÌK\çü^˨§}üxæ¡Ï€ÉÀ9†Mf¦¤ð8>žvõë3·{w9V»Ly³¶ÎlS¯iÆ}û-¡2âÙ³|¯ñuuåãI“´bO~ø/]Êï;óUw–—T«Æú,Uº†‡A è7npãÆlÇ–/_®#kôÐPøøcy¿bEH¿[§v–>xÀÙôð“J•hb­bÆ|œ=vŒî;vPP/¬&&Ü,_ž. ß&=p@Íû÷g68ɤbEèÝ[ö67k&Ç9 Ák`kkËÂ53¸µµVÖ Aîs11Ù[×gÝÒC"2È% ³¦4w''SØvå Æ9 yî A±e4¦Qe³±QiíèíMŸjÕråJž^èÿ€ íÚé<æ¿AÆ,õôäÙ£Gy¶š?jjJ«?ÄL±‘½Á`ô¢E‹X¤Ã[:…¡ë$ÂÔT02~د_/—QU7aII|@5 ¦W©¢–qý-bú¹s¬Êç$À„Ê•Yß´)|ø¡\C8g­æ²eá½÷dÑܶ­\QCÑE¡ xRR’…`k MšÈ[N¢¢2Åu÷£G™¿c?¤;VGÒÀ Ͼ™’$q<{–»”§ªdá…ïßy‡uŒÈéô:;3ë£ä E캪î}¿ÎYòÏ?LÊù=¬«Z•uż~¶H",ƒÐúŽ®“gÎ|*çç'—/ÖÃyžš À÷îîXªI¤:;;Sªys‚¶mËÓ }ÜȈ&XvïžýDéÒàí-‡gtè ™lI5#’ªRR’¯¼5kÆÚcǰ{ï=nmÞL(0*ý’õ?ü@ÝV­d§Ã«l…5¬)‚ïô†@6/ô@…°{óÍW|#ÔK`) Ùb¡ѲC‡bï}I„…c0TUöòZZZjõíë‚äçÎÉͽ”J¹yÜÅ‹rC9u³)"‚Aé?tp`š½¨<`z³fyz¡ûëK“t:v,°Á‰@ (&\QŠ, Mz›C»â-NtÉÇZ¯Ç?& ˜6m_ýõë š”ô²6¶DøA ‘e _` O÷Z.û¬ïÞ@`-`Ö¨øúÊßCzÚ‰¸ t­[Šãž8q"‹/VéÚõë×g~8 øxøßÿdñln.'jBL÷îÝQ¦¦âáêÊÖ_~ѵI/É!Â?üî;Ö¥WäXèêʬ;äP=c̪U,îܙɱÞÍ5‡Éžª5käzßÏŸËIGë×Ë·€}}åF åòŒ8#´S›F |òÉ'L˜0›7oâïïÏ©S§psscðàÁ¹¶ðކ.’wìÿfÚµMUÔÙͶÈH|ìì襉ìD€pîߟRJ%A野›˜Ðdøp, H<¤‡Á…¥RiNàèÑ£tëÖÄÄD¾úê+BC³õ5Ð7:z{s¬Z5® •7ò£AƄԪÅnSSšŒYåÊ0q¢\rðða¹DjFÈè0v,89Aÿþr/=¢Õuáƒâ€ÁèªU«2sæLîÞ½ËöíÛ‰§]»v¸»»3sæLî½jö°šÐváƒðÑGò~ùò°q£f*µ=S*@–»¹©›xyÁ;Œæ§—Æ[éêÊÇ_~©™9uÄ삚0Yøûï¿3 ‚üøí·ßèÖ­III|ùå—̘1C×&©Ä‡S§ò~™2|ªçk☠ð/WŽ!Y+o(ðÎ;rÓ®²w-KN–K¬zyÉ »fÏÎÝ(GÇèºðAqÀ`tFFFtèÐ-[¶ððáCÆÇ®]»¨R¥ ýõ—ÎìÒf6«$ÁÈM÷V¯–ðj‚ ÁÁ€ï¿/-·E¡@UD¡ 'çΣqãÆ\¸pÚµksþüyÚ¶m ˆµEoiÒD޵|ø~øš6•§¥É!u*¿¿ò¡DaáL»-[¶°{÷n¼¼¼hÑ¢/^¼`íÚµy^ëããCýúõµjŸ6ò?û 2ÖÇo¾É»««:¸Ïüô¤Ì†ÖÖŒ«TI½ÄÇË­ÏŸ—÷ëëÖÉ58K¢¡@UD'BAVV®\ɧŸ~Jrr2ݺuã§Ÿ~Â:=áÄÚ¢÷XYÉwZ‡ “ï¼þðlÞ,ß}ôæÌ¹s¡Mùšž=å@t",ƒÐX[[sáÂ.\¸PàµîîîZКþ îÛ+WÊû-[Ê9 š U’zëJIÂD¡`­‡Æêô '$@—.pú´ü¸gOØ´ ÔÔ¼8 ¾àª"„³ä.­C† áСC( &NœÈŒ30ÊátkK1â7`éRY0ïØ!‹é“'å*ÇŽÉ›ŸŸ\SzØ0¨SG­Ó ñ\8# g͚ŬY³tm†NxôHN(SFþÁª)gí‚{÷ø'.€ñ..ÔψÙRIIàí ÕR¼½åR?jÊ"ÅÍ›7ãççGLL •+WfÆ ™!ÀÂBÉ­[²Þ´I®Nõ䉜è´x1¼õ–GݧOfìf¯V­°Š‰)pø¸/h;p £3’§*#”‰0x°ü·rަzŠ%&òMh(î––L®RE}ƒ''Ë-¸‘wê¿ü¦¦ê›C  „Ç3bÄ~ýõWÌâÅ‹±±±Ñ±eáá!—Ùš9vï–Åô‘#²WúìYy;VN†6ŒÓfÉZPï {{Z{{kñETzåÊ•"]CXX˜†¬É¦ò—.…ƒåýþýáÿÓÈ4H€ï­[$¦¥¡~pw§”ºÜÜ))ðþû/_H»vðë¯/»7•0D¢@UDaÉdçÎÔ©S‡_ý• *°gÏÖ¯__¨xk‹`f½{ßÂ;ðå—QF6.NN¸oÒ¿#GXbk›ï0‘À#OOê¾ñF®s"‰°p F@·jÕŠ¶mÛ²wï^Ò øµȨQ£¨T©/^Ôš}šH"¼q¾øBÞwu…+Ô>E&k>äxú­ áNN¼]Àe‘P*¡o_9ˆä䈽{¡T)õŒ_ ª":–,®_¿NûöíéÕ«‘‘‘¼÷Þ{ܸqo=ˆbm1@ªV…o¿•kFïÞ-ç¥ç Ù\»ÆOžp:Ÿ§.°·çó%Kò<':ŽÁ„p„††²páBˆ©©)îîîÔ¨Qƒ*UªMPPwîÜ!44Nœ8A£F´fŸºò““esR’ïüãrü³&ONfüNK;C IDAT;8››3§Z5õ œš ÈÞf€-`ÿþ—šJ("ÑG *"‰°dÍäÉ“ùþûïIMMÅÉɉùóçÓ¯_¿"#ÖÆÄºw—·û÷åÊUëÖáÆG@ó—?Õ®§÷D¡*Œº|ùò̘1ƒ°°0¾þúkjÖ¬ÉíÛ·Y±b'Nœ lÙ² <˜k×®±cÇ­ŠgMàïÿ²¿ˆ¿?´j¥¹¹FݾMlz÷•îîØ¨#©/-MÞÞ¶M~üæ›r½KM®‚b†R©dñ⟹¹±bÅ ÌÌ̘4iEÏ‚D¥J0y2csàukÔÈå…^`oÏgšê´VB0t¶¶¶|òÉ'º6C£:ô²Ã`“&rÃ"MñËãÇìN±ìS¡ÝÊ—ýA%IÎÞ¼Y~ܨ‘ÿœ¥^©@ ”d8À¸qã2ã–ûôéÃܹs…gP :FFб#~—.1¼vmšß¿ÈÞ爼ÏÕ0´¾£®€ü¨(Ùq+I²³ö§Ÿ4Wåí©R‰ßíÛ”35eIêxäHùö@½zò/uÅT"ÑG *"‰Ððøí·ßhÞ¼9;w& €FqòäI¶nÝúÚâY¬-%êôêÅ™ôÄÿïTð>‹$ÂÂZK¨+ ß×Wîö °h¸¹©eØl\LoŸ=.(ˆG/^°°zu*¨£*Ƙ1r†0@íÚrq¹r¯?®!}ª"’ ƒ´´4~ùå4h@×®]9sæ ®®®¬_¿ž .вeKµÌ#Ö–’‹ßÔ©,­\™(à¡ Þg‘DX8¡¯¨ã¶ÛêÕr’-È%“‡ {í!sqüäIºöéêßgCzÕåÊñƒÃëþùçrÝ=ëY9öö¯?®!}ª"’‹7J¥’Ÿþ™Y³fez‡ÝÜܘ8q" ÀTÍuðÅÚRr±±±¡¶¬[ÇbŸE¨Pá]L „O?•÷œäúéšà‹ x>kçO??Jó½»ûëüå—°`¼_£= +¾þ¸@PÌHLLdÓ¦MÌ™3‡êԩ×_~IïÞ½1N/C&¨¿©S1·²±ÏjBèb@JŠ\².! ذÔ‘Ë—“ã'Or§R%pu%A’àáC¾mÕ ××­ÉŸ>]×f =yòdî¤×-ÎÉìÙ³u3ø:ù“'Ã¥Kòþرо½šŒÊÁ„ï¾ãI×®òƒ¾})ûë¯ø9;¿Þ ³fÁ7ßÈû..pì˜ü¯ _D¢@UD¡þóâÅ ¶nÝJ›6mðôôdÉ’%ÄÆÆÒ±cG8ÀÅ‹éÑ£‡VijX[ª"’ Ç ôÞ½{yüøqžç~ÿý÷Ì[fÚäUòOœ€¹såý7ÞxéÈU7ÇOž$ÈÙùeÀ 051!,4ôÕ]°@ÝÙã|ô¨ìˆHô¨ŠH"Ô_BBB˜8q"...ôë×'N`ooÏ_|APP cÇŽZµI¬-UI„…S¢B8¸~ý:õë××úܯÊ=GJ•‚Ÿss Gº÷yøðlÇ"ß{±ß~Ëž5kŠ>àÒ¥rÒ È±ÎGʱςB‰>UI„úEpp0»víb×®]œ9s†´´4Z¶lÉÈ‘#éÙ³'æšZÄU@¬-UI„…c0:$$„N:r[ï¾}ûR*KìnZZ!!!ØÛÛS§N]™Y$FŒ€{÷äý¹såªošàøÉ“VªôÒûœ½= ¡jÕªªøý÷r¹ºô18rD®º!ÆÕ«W3Eó¿ía‘ãMȈ#ŠÍwŽ@ PƒÐÖÖÖôíÛ€U«VÑ®];*Uª”yÞÄÄ„7ß|“V­Z‹DM›^v¹îØüü47—ÿÂ…Ääð>gîãS4/ôúõr£ë;ÿù§æ”¿@ h™´´4NŸ>)š³†V¨PooozôèÁ;ï¼£So³@ Ð,# íììø&=YÍÁÁ®]»fк¦(ù!!0z´¼oo/kRMqüäIn:9åR/ôæÍrqjI’; :$w‰€€jÖ¬©k3Å€ŒB;;;[bؼxñ‚£G²k×.öìÙ“->´ZµjøøøÐ£Gš7oŽ‘‘þ¦‰µE *ÉÉÉâ`!Œ€Îʈ#tmB.T ÈOM…ÿýââäÇkׂ:z˜ä‡ÿìÙ<·³ƒ-[PåLL0Êá¡OT*™4>?-_žÿ@Û·Ë=ÆÓÒÀÆ„F4g¸3{öl«(P‰ŒB ­~âãã9pà»víâ·ß~ãÙ³g™çêÕ«G=èÑ£o£šºbm¨Ê£GDt!¤€6lAAAùž÷÷÷×zö³ªÄ3àÌyÄèÖMƒFï¯ZŹô’ó«WgÜ«”˜ÛµKVý©©Pº4ìßo¾©fKKâ N *B8ÎÉ“'ùüóÏ‰ŽŽæÚµkXXXä{í£Gøí·ßصk‡&)) ###ZµjE=ðññ)ZNˆ!Öªñ\8) œœP*•™%Iâüùóжm[¬¬¬th]þœ95ÎkÖ|Ù¸OS^Vù›6É1Ð@ %Ž\>Æ—_Mæüùóå+žRRRÇÇLJ3fàççW,i ýÇ ôï¿ÿ΋/²³³³Ó»„yóàøqyÐ èÝ[ósŽ "1- °ÂÍ-WÍç|¹|:t€gÏ@¡€uë Ú*9y£ZÖ¬YCDD!!!„„„ðìÙ3ÂÂÂxðà‘‘‘<}ú¥R‰¹¹9ÆÆÆ¼xñ‚O>ù„­[·²}ûv½j²%Š') ŠâU̓ÐÐPöîÝË7HHH uëÖtîÜ9ÏqïܹÃÏ?ÿLpp07fðàÁ”.]:×u9“/]‚¯¿–÷«Uƒ¥K_Ëd•ØÍÞô¤£aŽŽ¼ic£ÚÿýÚ·—]å ¬^-+~F‰>U)‰I„öe챯ëLݺu ¼.))‰ˆˆ"""xøð!„‡‡³dÉüüüpÑôí>=D¬-UI„…c &&†•+WòÏ?ÿðäÉ“Ìv«]ºt)ô¹[·neõêÕ4nÜKKKf̘¯¯/³fÍÂßß?óºË—/Ó¾}{ÜÝÝiÔ¨óæÍcóæÍüñÇØä§Y“ä¾#))rñæÍ`m­¾×ž‰iiŒIO,ojʬôÄÁ}[¶pçæÍüŸøø1lÚDƒ„Z,[&·ëh ‘è#P‘D˜?¥J•¢J•*T©REצè bm¨ŠH",…$I’®P7´lÙ’èèhjÕªEµjÕ8uê111ôêÕ‹íÛ·—×/¯Ž;ò×_ñìÙ3LLäßï¼ó/^¼àøñãsÿþ}<==ùú믟¥”ÆàÁƒÙ³ço*Uj @x8 ‚"3xð`@$ž„†ë=è†#FP±bEîß¿ÏÿýÇþýû‰ŠŠbÛ¶mìØ±ƒmÛ¶øü¼n[´nÝI’2c«CBB8zô(:uÂØØ€J•*Q¿~}Ö¬Y“ëù11-¹~}ׯoàÉ“ €¼YX÷™?>~~~šÝ/îÝ»ÇÌ™3155¥W¯^:]g÷îÝìÙ³‡O>ù„õë×óÁ¼FTñ¯ñÜWó,-÷ïPÊÒ’™Ù´©õðð@Y©aÿ9~ÝÄ„ªd*ˆùköìÙ†AšBB!´‘{‹ÐUll¬¡C(ðŒ²ˆpðàÁüïÿ#::kkk\]]5ÿšòòòÂÁÁA3vذa ÓaW‰Ÿ~ú‰qãÆBùòåÙ¿?;vÄ××—î/uâ9r$+V¬ 99YS¨èããó¼U÷êL×mØÐ‡“'3Ï oÝbîóµÏk«UcP©RÙŽ æÇ5XøR+Üw+Vä׳g%BŽ ¡wRD¨QncW«V-œœœt«ë†ú 6D¥RqúôiÊ—/§§'—/_Î@_¹r…Š+¼ÛÕ•gÏX¦žSnéè˜cò à±s'i ÜÊÇMM©Þ½»$ÏB!„ÿa” ôË[È½Š»wïfØd_©T²páBLLL¨Y³& ^öжm[öìÙÃçŸ®ÙÆîܹs|‘Þ!Å€F߸AŠJ…¹‰ ‹µ­ù>r>ûŒÏ€yff,LKãgww~>]¡ !„B*F™@¿®nݺannNõêÕ‰‹‹# €Ç3cÆŒ ˆóæÍ£C‡´hÑ‚úõë³sçNjÔ¨Áˆ#²¸jRÇòdžˆ>~ Àزe©‘EgDˆuñ”< Òš4áÇeöÙ€¤[˜ÐUQìD(^Ü[„®¤¡vFYD¨T* dÚ´i´jÕŠ&MšdøÚ½{wŽÏ_µj½{÷ÆÌÌ ÆŽËÅ‹™:uj†qõêÕãĉtêÔ‰¸¸8&L˜À²I<#²8–÷ž¤¦òÙóõÞn Ósê•–¨;»,\Èg«V1ÞÁeöÙ`¤ÐGèJŠEnȽEèêåîÉ"kF9=yòdæÍ›GíÚµiÒ¤I¦Ei›­©[·.uëÖÕéµ*Uª¤Ó’ gç;T¨à“éx­Zº­ÁÖÕ!!>ðÁx!jº- ý“¢ ¡+iá-rCî-BWÒÆ[;£Ì’Ö­[Ç„  Ô¿¶ß~»y¾ß¼ÎÅÅñ˽{t(^œ~%Kf?xÛ6˜;Wý¸n]X¼XsJ’g!„Bˆìå€::½R^¿NšJ…¥©)‹r*¼~Þ{OýØÑ¶nkk½Ä)„BQØeÝ©S'6mÚdè02Hoè’_V޿ϱ'Oø¬\9ªÔ4KÁÒþü¤ñ‚B!Ä+1šúe×®]#!!!Ës …‚²eËbgg§×˜òcAþoááœ~ú€)*àae•yPZôïÏ··ã§Ÿ Q£€K—Ô'M‚nÝòäµ…~H·0¡+éD(rCî-BWÒ‰P;£L ?þøcÖ®]KçÎY°`›6mâý÷ßgÀ€4iÒ„† Ï¡C‡¨W¯ž^bÊ‹ùáIIL  Z±bŒËªppÑ"õÏíÚÁŒ¯ýºB¿¤ÐGèJŠEnȽEèJе3Ê5Ð}ôÌ™3‡={ö`ccÃ;ï¼ÃÌ™3177'55•£GR9»½“óX^,ÈëOÓÒø¥J,LL28v ÆW?.[VÝuÐÌìµ_Wè—ú]I¡È ¹·]I¡vF™@Œ1‚#FO|||¦}R­¬¬ Õ~˜~11l~ø€wJ•¢ÍÇ<|}ûfl–R¢„"B!„0nF›@§+V¬X¡oݬT2úÆ ìÍÍ™ÿß‚À´40àE³”„Æõ¥B!DÑ`” ô¸qãrÜ]c̘1´mÛV½Þ‚üùaa\‹àwwJ[Zf0u*¨ÿßÿÁ‡¾òk ÓnaBWÒ‰Pä†Ü[„®¤¡vFYD¨P(°²²Ò|) Ξ=ËöíÛ‰‰‰ÁÌë‚_uA~hb"3CC¨ckËGnnøúœ9êǵjÁ’%¯¦(¤ÐGèJŠEnȽEèJе3Êè¬n*• ___&MšDõêÕõÓ«.È{ó&ñii˜‹+WÆìåÂÁ7`Èõcغ ùr!…>ÙÙ´eÛ÷o'ì~^ž^¼ÓãÚ´jcè° JŠEnȽEèJе3Ê謘˜˜Ð³gOŠ/ÎÖ­[ ŽNþÅöçÑú”.Í/NÆÇC¯^/š¥¬^ zÚQD}ºwïM;5åýÍï³Éq YÎrúÌíC¯!½xö|_t!„B_ŠL΂۷o: ­•JÆÜ¼ @qssæü·pðåf)'‚ÌD #”––F7Ÿn¯yœøZñ`óü„#D7Œf{±í úpAcBQôåŽ3 çï¿ÿ&001cÆè=¦Ü~wç·ø¶bEJXX¼8ùË/°aƒúq›60kV^…) )ôyaÖ³¸Rö Øf}^YZÉ?§þ!àŸ€"¹œCŠEnȽEèJе3Êè=zP­Zµ _íÛ·gß¾}̘1ƒ¾}ûê=¦Ü,È¿™Àœ;whhgÇeʼ8yü8Œ§~ìæ›6I³##…>/ì=¸—Ä ‰9މ®Í﾿ë)¢‚EŠEnȽEèJе3ÊèÍ›7“ð|ö6]™2e(Uª”"ÊÝ‚üoÜ I©ÄÔÄ„ÅUª`š^8ù¢YŠ…lÙ%KæSÄÂPŠb¡Oª2•Û1· zDУ ®E]#èQ'ÃO‚‰–';@пAz‰³ ‘"B‘EñÞ"^jg” tÕªU Â+ÛÉÞèh>(S†vvêéÍRÂÂÔßÿð4mj (…±yòä ¡¡¡T®\++«|{˜Äu‚üèZ†dùVô-R”)™Ÿ Ôá¢Ï l™²y«B‘£L X¹r%çÎ#::ºuëFË–- Z¶ž¥¥ñÉóÂÁ|[±â‹“Ó¦¿¿úñ;ïÀG BalvïÛͤ9“x¬xL\±8â(iR’óWP³FÍWºfš*Ç!/ä—’åÈøH®áh列‹1•c¸þð:ª’ªì_ƒïR”)X˜Zd?N!„È#F™@‡‡‡Ó´iSîܹƒ³³3îîîlß¾ùóç3zôh-Z¤÷˜t)"ü&4”»ÏÇÍ©T‰âæÏÿólßþ¢YJ°ti~…) }ú|5ç+~9ø QÍ£àù2úbI ¡ÃÈü2ázuë•íóŸ$=ɰÜ"=Y¾}ƒä´d­¯ofb†»£;^.^Tu©ªþÓYýgIõÒ¤¸þqÔï\Ÿë­¯kbÌ ¸ [½·Ò`iV÷XM½Òõ^áo£p’"B‘RD(t%E„ÚeýÉ'Ÿ`jjÊ¿ÿþK½zõ055%66–åË—óÙgŸÑ«W/½·òÖ¶ ÿj|_Ó¡«P+*†TÄrˆ%çžãBÄ-kÄ”S˜ÖrZ‘˜N/ ”µÐBú¸·ã!ë µ0Q©T9|6Z8¹ºº2}út>øàƒ ÇU*ÞÞÞôìÙ“/¿üRoñøøø9p´=wŽ€Ç131áLýúÔ¶µU7KiÒ.^TÚºUÝ} 9YÝ,å?À€ `„þåg·0*~>ù3'OÂáœÇV¸^Q>£ò-–üTÍ¥GÞ?œösP˜)HU¦2óÐL,mÀ™ûg ^ž‘N„"7¤¡Ð•t"ÔÎ(—pôîÝ›‰'2cÆ C‡¢ñßjVðᤩTX˜˜ðK•*êf)¾h–2w.4k¦ÿ`…AåWÑFhl(ïù¾G@H¸WÀâ²)5þS çSÎŒí3–²e oƒ’ôÙè®U»òžï{œ¸wB3ýy³Ïù²Õ—…~m´ì¾!rC „®díŒrúøñã<~^”WP­ºŸ£±±Œ/WŽjÅŠÁ_€ŸŸzÀ€0v¬#Ædù™åÔZ\K<îŽîø¯ógVóY¸ïw§øÅâ˜Ü2¡ÄÙTñ¯ÂÊV2îÃqŽ:o¼<menEª2•Y‡gQi}Nß?mèð„BBF¹Ý AƒHNNfóæÍ†ȼLtJ UOžäQJ å ®6j„Íÿþ=z¨÷{®^Nž”ýžÅk ΰÃØssæØpïáÌs>v–ê6ñIII\¸p[Á·¨Y½&ÕªUÃÌH×Ü= ÂÇׇ÷N`njn4³ÑFK¶±Bïd;íŒr ǰaÃ2d={öäÍ7ßÄËË Íy///Jéy]ñË ò§óèùVz <=± †ÁƒÕɳ4K)âòª[؆‹øx÷ÇÄ$ÆàfçÆònËyËó­ ã  6¤aƯýš—‹GÞ?ÂÇ~àË€/ILMdÖáYl¿¶UÝWÑÀµ¡CÌéD(rC: ]I'BíŒr ÇØ±c Å××—Q£FѦMZ·n­ùÚ³gö‹ä±ÐÐP\Ê–åŸXv_ÝÁí-''zÙØ@ïÞð|9+WBÕªzO¯[èIï?zóÛþO“<ª=ˆK^Ê”<Ef&fLxcgGœ¥IÙ&\zx‰¦+š2Õj¡Ú©CŠEnH¡Ð•jg”K8ÂÃÃINÎþ— ‹‹ ¶éÍJôÀÇLJƒÇŽÖ§%bcyЧ SS.5lˆçˆ°nzয়¼yz‹KŸmW·1r×H"ã#(iS’%o/¡‡—›e%M•–a6 F‰¬î±ºÐÍF-YÂ!„ÞÉíŒr Gzó”‚$29™´xðÍ7ÀçÕªá¹zõ‹ä¹eKÙñŠbcøx÷Çl¸¸As¬Oõ>üÚåW\ŠÉÇûÙIŸîZ¥+ïmãaǹy™&Ë›0±ÙD¾jý 3ùS!DFF™@§KJJâÌ™3™6÷òò¢téÒÙ>/%%???üüü ÃÕÕ•îݻӲeË ãþùçŽ;–éùýû÷ÇÃÃ#ñøô-azöÄq×.&;9Á'Ÿ¨•)›7ƒ¹QÿçùdÏÍ= Û1Œð§á8Y;±¨ó"ÖhàÈ //Ž =Âücó5³Ñß~ÇŽk;XÕc ]}¸BÝåèË—/óöÛoãääÄo¼A›6m2|íÛ·/ÇçõÕWtëÖS§NaeeÅÖ­[iÕª_}õU†q{öìaÆŒøúúføÊríPzr\«ÊóçYòæ›ü–œÌ33u³”zQ´èÚ-ìiòS†ïNç 5ÉóÛUÞæÒ‡—$y~¦&¦™ÖF_޼LÓåM™ì7™¤´‚×™K:ŠÜN„BWÒ‰P;£L GŽÉíÛ·Yºt)!!!Ü¿?ÃW¿~ýr|~·nÝ áàÁƒ¬ZµŠàà`ú÷ïÏŒ3xðàA†±•+Wæøñ㾚4i’ù¢11š‡Ïúôá_¥’4à`³fмy^¼ma$t)ô   ÖâZ,?³{…=+º­`çÀ”±-“ß!µôÙè¹æbenEš*Ù³©ókŽ…eþÄɤˆPä† ]I¡vF¹fàܹs¬X±Bk¢œÿ&À&&&øøø°yófnݺ•ãòl•,©y˜V·.GÝÜH»s‡ßvî|¥…ñÊ©h#>%žÉ~“ùùÄϨP×ÿ¶óhÇÊî+)ï £òŠ©‰)Ÿ½ñ™¦‹á±°c\‹ºFó•Íßt<3ÚÌÀÊÜÊÐaJ'B‘+R&t%µ3ÊèªU«òäÉ“<½æÉ“'177§Zµjއ……ñöÛoóÖ[o1~üxBCCuº^舄5lˆ½½}žÆ)Œ×±°cÔý­. O,D…ŠbÅXÔyûï—ä9ŸTu®JàÐ@Íl´R¥dÞÑyÔý­.Gï5txB! Ä(èéÓ§³dÉ’<[ïuåÊfÏžÍøñãqrrÒ/V¬mÛ¶ÅÛÛ…BÁ/¿üBõêÕ9uêTæ‹Ü¸ÿ HZ\מ<É”pûúúfZÓ(ÇŠö±¤´$&˜Dó‰Í¹v€fåšq~äyÜî¹õ(ªÀÅlLÇLMLñ|èI@ßš–m  žþ¼9nù„Ô„\]OŽåîX@@‰‰‰"9&ÇŒõXz ×äÉ“Y»v-wîÜAäÌh–p´nÝ:Ã÷¡¡¡T«V-Ë®ƒ“&Mâ­·tk(‘>ÃܨQ#¦OŸžáÜ—_~™áû‡R©R%>þøcŽ?žñB‰‰Ïži=ªPùK–°ðÛouŠE /ÿÃïÌý3 ñÂ¥‡—@–f–Ìê0‹ñMÇcjbÊ%.0Ò¢ÅÓÉ“À¡üxüG¦ùO#Q•ȯÿþŠ_„«º¯âro T*Yµaÿœü‡È˜H*¸V b©Š4ϧ:) ¹!…®RžwKÙ3šF*é›~ëbذa:ýB{ðà-[¶¤D‰ìÛ·O§æ+£FbåÊ•ÄÇÇcff¦‰ÍÃú>þ{{Ú Œ¥¥%-6¤‡ŽÉ¼(|||X¾r9³Ïbæ¡™¤*ShàÚ€5=ÖP½DuG(®E]Ó¬õ,õòC8³î ×Ê\#Ñ=,€D°¿jOSES¶,ß‚]žÆáëë ùZhi¤’g|||d´ÐIÛ¶m)_¾¼ü¼äÀhè„„”J%666yr½ÈÈHZ·n Ðy­ò AƒøóÏ?yö즦ê2>>><[³†-À SSŒÃÔÌ“8…ñ¹y™Á æÌý3X˜ZðE«/˜Ü|2æ¦Fó¡Q¡§T)_ÌF'%Ÿ@w «ÚÂGðvôÛìÜ Eù& ´z'µ3š5Г'OæÝwßÍ“kEEEѾ}{,,,Ø·o_¶Ésxxx†ïƒ‚‚زe 5Ò$Ïé.+ÀÂòåùøë¯ó$Na\”*%ßùžúKêk’çZ%kqrøI¾hù…$ÏŒ©‰)Ÿ6ý”ó#ÏS6¨,4&ëäÀ?=Œ_€Ÿ>CB‘Oä7r¾þúk.\¸@ÕªUéÔ©S†s3gΤ}ûö€z›—æÍ›ãééÉÅ‹9yò$¥K—fÙ²e™®©tvæÄDª÷è!;oˆLnDßÀÇ×G³³ƒ™‰›MdzëéXšÉl[AVŹ nqn„ÕËq\låX6îØH»6íô™Bˆü" tºté‚««k–çÜÜÜ47nÜȉ'¸wïuëÖåwÞaèСY®s¬\§ãŸ>åŠÌ>‹—¨P±èä"&˜D|J<©l¹‘Æn ÐÕÓä§ÚÙõÓ×òôuÓ‹]\\òôºÂ8I¡ÐURR …ÂÐahF•@ÇÆÆrîÜ9­ãÊ—/Ÿa;ºÿzóÍ7yóÍ7µ^§oß¾ôíÛW§Øž={FHd$ææFõW.^Chl(ïù¾G@H&˜0¶ÉXýþH’çBÆÖR{1q”ÄŠ³+è_£¿nÏÑ"½ ¡QŠ<3{ölYÓ*t!ÍT´0ªlîàÁƒÔ«WOë¸U«Våj׎¼P¾|yIž‹•JÅê «Ùþ÷v"¢"ð(ëÁÀéÚ©+ËÏ,gü¾ñš™KGV÷XMË -Aû¿ÝDÓ´nSþ½ÿ/Ê2Êì]…G¥1lÇ0ÆíÇÀZî=œ® ^ùu%q¹!ɳЕ$ÏÚUFרQ#&Ož¬uœ···¢EUhh(}?èË%çK$”O€Jp<î8{–ï¡ÎÒ:Xt¶à@øÍø‘ F2·ÃÜ<™‘†ñÝß±·Ã^®¹\So_÷_±àç²½’ÐØPž&?eéé¥,=½”º¥ë2Ü{8ïÖ~…ƒÞcB‘{F•@—)SFfd„A¥¦¦ÒupW.6¾/ï¨h k?柇ÿÀ¯@w(k_–ÝVбRGC…+òˆµµ5¿/úAcq£ê R\SÀH›[6ÔŠ©ÅŽ-;pvqfÿíý,;½Œ×v¢Lá܃sŒÞ=š û'зz_†×N³rÍ ý–„BäÀh¶±+è’’’ ‚Ѓ٠fsÝãzÆäùe%RОö\úðR–És^µ úå]×›3Ÿa’û$ZœmAÝ£uiw¥sÛÍåè(QSSÞ¬ô&öû“»ãï2»ýl<<ˆO‰gÍù54_Ùœ‹k°àø¢¢r|ÍGI7B¡3¹·]I΢$Ðzaè„ø÷#ÉC˧:”.ŸíÇõ³gÏ·Ȅ>( ¾™ò ‡¶âì¾³Øz€QïÂÄÄ$ÓØR6¥ø¼Ùç\ÿø:þCüXs 3uÕû•È+ŒÛ7·ùn¼³õBP‘¹çU`` ¦PmäÞ"t%9‹vF³„c„ $'':ŒlÉ‚ü¢!:>Zû {¸}áv¶§¥Ð§h1Á„6îmhãÞ†¨„(Ö_Dz3˸y…¤´$6^ÚÈÆKñtòd˜÷0|êúPʦ E„"wäÞ"t%9‹vF3íææ†‡‡‡¡ÃEœƒ•E`qàVÒMû8Qä8[;óI“O¸üáe‡2¤ÎŠY¨»˜ÞŒ¾É¤“(÷C9zÿÑ›½7÷¢Tå°ë‡Bˆ|c4 ´A“ÚM0 3ËqŒmˆ-Ý;t×SD¢°jV®«{¬&üÓp~éü uK× E™Â¶«Ûè´¡ªÈ7ÿ|CØ“œ» !„È[’@ë‰,È/jv­IÚá4HÉfÀ¨õ¬}{e߀G }ÄË|ØðCÎŽ8Ë©á§î=;Ku·ÓÐû¡|µû+ܸÓucWv\ÛAš*ÍÀ‹‚Jî-BW’³h' ´žÈ‚|ã÷ûÅßú¿¡ÐLÿ2ÅôîKÿ{©Àê†õ/ÖgÛÊm9^G }Dv¸6`i×¥„β®Ë¨Wî@š*]×wÑ}SwÊÿXžiþÓ~¬õz ܸqƒÔÔT=D/ Mî-BW’³hg¢R©2—v‹<•ÞõP 8Œ×ú ëññõ!M•†­¥-õþ ¿Í~>u˜'‰Op¶u¦kû®Œ5.Ë„xU".°ìÌ2Ö_XÏãÄÇšã&˜Ð¾b{†×N¯X˜¾èðx4ÑÓFóØä1‰Å±yfCY»²lX´råÊâmdï\*,Nxñ}?´·4\F- Eî-BW’³h' ´žÈ‚|ã²êÜ*†nŠR¥Ä^aϾAûx£Üyrm)ôºÒÖ‰ÐÒÌ’þ5ús`ðnŒ¹Ai«Ò`ó5ÓÜÒð;æ—Ç‘Š‚@î-BW’³h'k õDºú•gW2|çðÉóÿí£IÙ&yv})ÚºÊM'ÂJÅ+Qʦxó@3¸úà*÷žÞÃÍNþ¹·]I΢Ì@ ‘ +ή`ØŽa(UJü=èïBWÙf§oϾ4³i†"X‘ù¤ N90Óg&g>?ÃçÍ>ÇÝÑP7jñ öãƒPz^i:mèÄšókˆMŠÍ£w"ôAî-BW’³h' ´žÈ‚üÂë·c䮑/’çAûièÚ0ß^O }„®´fÅw/ã=ÇSî`9¬/[C(Ø_°§R@%~úŸýœz¥ë1»ýl‚Çs|Øq>iò‰f=tª2•½7÷âãëC©¹¥è¾©;/m$.Y¶¾+èäÞ"t%9‹vÒ‰P¤£Oáõë¿¿2ú£Q¡¢¸UqöÞOý2õ –¯-::šSÿžâêÍ«4¨ÝoooŠ+–íx*‡fóåÍüyåO>{˜á¼µ¹5]ªta@Ít®Üks-Û}èJ: ¡w’·h' ´Èbá´øÔb>Úý‘&y>0øÞe¼ –—¦J# 8€M—6±íê6bc2œ·µ´¥[Õn ¨9€7+½‰¥Ùk$¼’@ ¡w’·h'K8„È¢“‹½[=óìdí„ß?Iž…xÎÌÄŒöÛ³¼Ûr"&D°ë] ª={…=qÉqü~ñwºmìF©y¥xoû{ì½¹—Teª#Bˆ¼! ´žÈ‚üÂãç“?óñžÔÉó`?ê•®§·×—B¡«Üæ S ºTîÂÚžk‰ø,‚mý·Ñ¯F?ŠY¨—ƒ@Ru •…ß“¤'øù²éÒ&Ü>@Š2%Ãù²öeéW£ýkô×lyîÂ9Þó.ÖÅ-qg0˜JØQüožØ&°uÅV\\\ ðn„0~’·h'3ÐB?ÿ1Còì7ØO’g!òˆ½ÂžÁu³ûÝÝ<øìK».¥G;ÌLÌ{ÆÇ~ ñòÆTü©"Ÿýï3ºïΕæWH+¦NžL!±l‡Ü1pÄ@ý!!D‘' ´(òæ›Ïø}ã(Q¬þCü©]ª¶£Â89Y;1Ü{8àÞ§÷XÔyÍË7Çäy–ü8˜ùóçs§î°Èî"pJyŠ¿÷ÿ­¿À…â%’@ë‰,È/˜æÇg@I›’øñ§VÉZI }„® Báë(eSŠÑ Gsø½ÃÜw‡ùç«—pÄ®9?7Ö#–­{·ê%Nc!÷¡+ÉY´“ZOdA~Áóý‘ï™°ð"y®Y²¦£’B¡»‚VDø:ÊÚ—e|Óñœv‚ÊΕµ?ÁüÏûsúþiÙÑCGroº’œE;sCPT”/_ÞÐ!ˆ—Ì92‡I&êY0ÿ!þT/QÝÀQ©IцÐU= B¾(eSŠÜÈyP ÜL»Iƒ¥ p²v¢{ÚWlOûŠíñtòÔO …ŒÜ[„®$gÑNhQä|øSü¦ê_Ô>Ts©f਄éšÖnʉ»'H)—’íÓ«¦(+ªgž£¢Ùzu+[¯ª—tTp¨ I¦Ûz´¥¤MI½Ä-„(:d ‡(R¾=ü­&y.m[šƒ>%y¢€ùfò7T ª‰YŸ7yhBgç΄üÂònËXs`†$946”gW0pë@JÏ+MÝßêòÙߟ±ç枥<ÓÓ»B3™ÖYox3Íä‹€/(c[†Ÿª:W5pT™áååeè0D!^@hlû![YY±}Õvúê…• (QO÷¤‚M° o¹¾Å†¥°··çýzïó~½÷Q¡âbÄEü‚ý8pû‡B—‡ ç#Îs>â<óÍÇÒÌ’&e›hf¨º6ÄÜ´hü*”{‹ÐURR …ÂÐahÒHE|||¸sçþþþ†¥Èšqh_| €«+þCü dò êŸY«(táëë ïZè´´46¹â›¬HJIÆÆÚëAŽ4ŸÖFësS”)œ;ÁÛð öãD؉L \@½Guk÷Ö´¯Øžví L-D~{‹ÐUÛ¶m)_¾¼ü¼ä@h=Ž>†õõ?_3ýàt@<  ŠsÃ%„Ð͹TXœðâû~ ho™ëËÄ%ÇñOè?øÝVÏP_zx)Ëâ®v®´óh§™¡vµÓ²Ÿ T*Y·qÿ øcR¹|eÞéñmZiOô…(ˆ$oÑ®h|n%Ьé§óõ?_àfçF€O•tØ"KaTl-méR¹ ]*w âYþÁþêêÛ~„Ɔþ4œuÖ±îÂ:ª¹TÓ$Ó­Ý[c¯°ÏpÝàà`z íŵÒ×HðH€rðOÜ?ü5÷/š-kÆïK~ÇÆÆF¿oV‘ï$FëË€/™qh Þc6`H€lo%„Ô;ð ¬95Õ-ÁoFßÔ,÷ðö':!€«®rõÑU~>ù3f&f4tk¨I¨”j@·¡Ý¸Ôð¼œ#ÛBTÃ(v…ïÂçc¶¬Üb€w(„ÈO’@ë‰ê×_0óÐL@<ô9H¥â• •n¤ÐGèÊX‹ ÁÓÉO'OF6‰R¥ä܃s¸}€·x'„ÔÒTi;Îñ°ãÌ<4‹¤º§fLž_¢tUp*€cÇÑ´ISý¾¡,ȽEèJе“ZO¤«þLõŸÊ·‡¿ œ}9ú¤bñŠŽJw&L wïÞ†CgΜÀÛÛ€ZµjQ¿~}C†dLMLñ.ãwo&6›HRZGïÕ,÷ø7ü_ÒTi¤ÜOÆ9_+Ê#Še[–Ѱ‘áwû˜={6«W¯&""‚;wîP­Z5lmm “(˜"""¤™Š’@ë‰ü êÇ¿)|øåÊ0$ P%χæäÉ“X[[:QHìØ±ƒ„„’’’xüø±¡Ã1: 3mÜÛÐÆ½ ³ÚÎ"6)–ƒ!yoÏ{Ä“ó“a•ï*ÖÌ\C)›R¸Ù»áfç¦ù³¬}Ù Çì,íòí}téÞ…j-«ñ´ØSâmâ±}lKË2¬œ¿’ÕkäÛëŠÂGrí$ÎBtt4¾¾¾üûï¿öÐ¥R‰©é‹^@“LbΑ9€ºY€Oކ ï•%&&²iÓ&Þzë-C‡" ‘½{÷ívvƒÂîU»ãéìÉ)Nå<8p¥JÉý¸ûܻϿü›íp;K»LIõí’6%15É]´ñÓÆ³öòZ¢ÚF‰úX 1ÜMºKÇQY6u;vÎÕ5…(Ê$ÎBóæÍ‰ŒŒ¤iÓ¦X[[3oÞ<æÌ™ÃþýûiÖ¬™f\`` ]ºt¡víÚÔ©S‡o¾ù†-[¶°k×.¬¬¬ øŒWxx8Lø€àÁqwt7l°B£V§Jþ}ø/ª’Ùï[,´#úÀ¹–3÷žÜ#ìI÷žªÿŒ|™i+½§ÉO5…‹Ù175§Œm™,m7{u²íj犵¹úS¬S§O±éô&¢šDe¾˜Â[‡3fúZ4m]þÍ€¿ Y‹+ *I ³0wî\Úµk§I‚Ÿº~ý:£&"üI8‰&‰Ø(m¨ãY‡_¿ÿ{{{í¯Mþá¢$ÐYèÒ¥K†ïíííéÝ»7[·nÕ;{ö,gΜáÇÔ,#¨\¹2U«Veùòå™h)"|=>äÓÙŸr§ÃÍÇ.@O°ðµ`ÿôý…:yõÒ!DÁgggÇÊ9+ñïCH½”¥Ÿÿ¿«„b·Šáuß‹M6eû|K3K*8TÈñž¥BEä³¾µ¡ IDATÈLÉuú÷éŸ&?ÍôÜè„hõv|q€d³ÊGå¦âËß¿d§íN\йàlí¬þ³˜s–ßÛXäÏÞÖKV-a溙„5 ƒ—ò·«QW9Ùù$;–ï šWµ|yíÜ gô¤Ñ„< !A™€­©-­¶â»/¾ÃÒ2÷Í~ )"ÔNh¨T*N:E­Zµ4ÇnÞ¼ á@õêÕÙ»wo¦kÈâë™2k !Þ!™“çt–@}ðÛé‡çн׳™YVSYBˆ‚¨Å-8û÷Y¦ÎšÊ™ÓgHHKÀAá@×v]ùdÔ'ê4^… &”´)II›’x—ñÎvÜÓä§f®_N´÷›ï'‘Äl“gL!61–·è—ÂL¡I¨µ%Ûéßÿ· Íݹs‡o×~KXë°Lç”ÎJn¶¼É€pfÿƒß'·lß§s?ånã»ðÒ©Â.°»ínöý¾¯PÿÞ/̱ëËëýŸ]D,X°€³gÏ2cÆ Í±ôåÿ&Ð5jÔ 66–øøø ÇÏœ9ÃÚµkñõõÍð•¾k:9–õ±ÀcPŒŒ‚€—þšSʧ°zÃêóëâu¤Ÿç×9@bbbˆ%»cÉÉÉü<çgŽì8™ÿaìà± î?8Còœß±ØYÚq-ðuíë2¤Î¦¶˜Êâ.‹j=”º%êBÚKOþϽ€ãàééIc·ÆT*^ G+GL‚L2{þܤ´$î=½Ç…ˆ ì `Ë¿[øíßߘyh&ãöcÐwƒè¼¬3–5¢ÒÂJ8ÌvÀüsJ}SŠš‹kÒzukzÿÑ›7§½É¸mã˜l>Ý?êΆw²Ž/Hƒ«%¯²yëf½üfwlÕªUŒÿvЍ„(®?ºNjñTbLcˆIŒA©ÊzZš*‡ÏòPõðÅÁ[ðwÊßê ’P@Ë6æ)åSñëfDÎ ñR"Ń‹cãhƒ¥™% 3Ñg£)S;G;æ f îž½ËAåAÕãÌ\»qÛv·qvqFa¦ÀÒÌ’³÷Ï’šF‰g%^<÷É]B‡h™¨yîÒ K óËþQ;¸l™S§OÑ¢E‹œß(´LTR±”­={öУG&MšÄ×_áÜ–-[èׯ ]»všã `ïÞ½öbõññ!))‰7ê-vcÓ¾O{üªûe]¨“.F[ŽfÑìEz‹+?( ¶oß.ÛØ‰\IßÆî¿³µ…Þ¹TXœðâû~ h_¸×—Âÿú?¶…m#¡ABÆiàrÂ…ùïÍgð€Á¯ý:J•’˜ÄMržx?Š”áûÿ>NU¦Â6 —¶vÝ_;ÔWçKÎËažBó‡Í™8m"®v®”±-C)ÛR˜™èé‰R©dåú•ìòÛÅè‡T­X•!}‡ÐºEëlŸ3pà@ «W¯Ö[œ…Ì@gãÀôêÕ‹O>ù$Sò êº.^¼˜!¾rå U«VÍ4^Š_Ï€®8±ëq5ã²SòrIÏ|ý_†–SaBBõêuÅÔ4óžä/S©RéÕ«>³f{¥Z´HT”ö l//;¶mûù•^C¡?ë¯ãjã«Dí"¶T,±V±¸Ä¹P<ª8¿Ìø…ömÚçÉ똚˜âl­^±I±´>ÒšsªsÙÏì<„ÚµkãUËä´d’R“HJKÒùqžÈ)¾t6x5ÀšCf&f”´)©N¨íÊàjçªù*cûâûWÙç;;·nߢÏð>\-u•$÷$¨ÇžcÇœ´]Õ–µ¿¬Í²i—j' tþùçºwïÎ|Àœ9s²S¯^=¼½½Ù¹s'cÆŒÁÔÔ”7npíÚ5-Ê<*?ˆ¯çwÞaÜÂqP(•ù¼åmKÞªô6Ò{ly-§â˜¤¤$ââjrïÞ-Wy̵kÓ_9†¨(W¯®Ö:ÎÖÖç•_ --ÈÈHlmm±µµåñãÇœ8q‚’%KR«V-Ìͳ¾EÅÅÅqñâEž>}JíÚµ)]ºt¶¯‘ššÊÍ›7¹~ý:Ô©SGGÇLãbcc¹páÉÉÉÔ®]›%JdR©ÄÅÅ…äädNŸ>µµ5uëÖÍ0.,,ŒK—.Q«V-ÜÜÜ2œKNN&::¬­­ çüùóÔ¨Q#ËûÄ£G055ÅÉɉ””Î;Ç£GèÔ©S¶ïYˆÿ211áôÉÓ¤¤¤páÂnÞ¾I½:õ¨\¹2&&ºd„ùËAáÀ°~Øä?‰¸ªÙO”” *öåÛ¨T©R¶cr’œ–œ«„;«Ç üp-k„³Ø20M•¦i¦ÃýìŸjfbF)ÛRY&×/'ß%Š•È1ÑNLL¤Ë .\ky ^nMaÑ £ùëÞ_ 7œõ¿­Ïô\ÉY´“: ƒ "11‘'NФI“ ç:¤Ùžæ§Ÿ~¢K—.´jÕŠ:uêð×_Ñ¢E  dˆ°š¯qoÅXÙZ‘è‘N@$”-Í€¦˜;}®¡Ã¹t÷î]<<<øê«¯HLLdîܹšøzõê±}ûvÊ•+§¯R©øé§Ÿ˜4iR†½ÕûõëÇ’%K2%ƾ¾¾Œ=šððpÍ1KKKÖ¬YÀuÿÍ7ß0kÖ,ÒÒ^TZ½ÿþû,\¸bÅ^T¯vîÜ™ÇóóÏ?Ó³gOž={¨?‘:tè …‚N:qòäI@ý¡~ø1c^ì­{ôèQÚ´iÃ’%Kعs'»víÒœëÕ«k׮ůæÅaÍ›7ÇÑÑ‘)S¦0pà@âããqpp–Ýâ•XXXP¿~}ê××²ØØFÍÆ¿6r,âÊR™?‰+v©ïµï•“gPohiözK€žv}Êw—¿SÿÊF©k¥øsΟؕµ#üix¶_q¤©Ò2<7M•¦9ŸsSsJÛ–Î`¿<³ýûâß¹]ývÆäùå×qKãïsæÌ¼½³ßáEdMè,Lœ8‘¸¸¬ÿüòì`óæÍ9~ü¸¦•÷—_~)­¼óÁŒC3Øre XB‡Ñ˜]s6{ý÷r!èMê7áí)ok–ÔˆÂiéÒ¥”,Y’ŋӴiS–-[Æ¢E‹øüóÏùý÷ß5ã~ýõWÆÇÛo¿Íwß}‡££#ëÖ­cÚ´iÄÄÄð÷ßkÆîرƒž={R¯^=Ö®]K:uxüø1Ô¯¿þš3fðî»ïòÅ_ P(X¼x1sçÎ%99™µk×fˆõþýû 6ŒO?ý”~ýú±mÛ6¾ýö[Lbb"åË—çûï¿'**ŠiÓ¦ñé§ŸÒµkW<<2¶•Ÿ:u*-[¶äòåËØÙÙ±bÅ ¾þúk¬­­Y¿>ãŒÐ75j‹-¢E‹²$L­=›ö0tÌPOQ&•ƒ óGæ” .ÃÈž#™2~Š¡Cdêø©lm·• Ž xæó×-èQ§Íë7 N©:Ù^+½°2§$;üix–]+S•©„= #ìIæmÿ8ˆÖµâ‘î‘lÙµEèW E„z E„¯î¯ ¿è½¹7*TTq®Â‰a'p´Êüñ»1É©ˆðñãÇÔ¬9]§%Óqw×6.kAA>$%­Ö:®aCNžÔ>.;!!!xxx`nnNDDNNNšs^^^ܹsG³%dzrš@TTT†FýúõcË–-š¢^•JEåÊ•‰åöíÛÙ¶'ŽŒŒÄÝÝ{{{ÂÃÃ3|”ݲeK9þ¼f»Ê&MšpâÄ V­Z…flÇŽÙ¿?­ZµâàÁƒšãkÖ¬ÁÇLJŸ~úI3 }ðàAÚ´iƒ 111XXXhÆ·nݚÇsõêUªT©¢ù{¸ví+V¬`èСÙþ]J¡Ð&((///C‡¡“   üý¹tã­¶¢UóV9.ÕÒ·ˆˆúëËy³ó<©ð€‡PúFiz7èÍ‚Y ²]‚ö*R”)<ˆ{ 5ÑŽNˆ~ñ¤@7-VÂ[×ÞbϦ=K¡v2­'2c”{".0hÛ T¨pP8°cà£Ož!ï:ÆÆÂùóyr©|×¶mÛ É3@›6møí·ßˆŒŒ¤D‰ܺu‹ÈÈHºté’©ËWÇŽÙ²e GŽ¡]»v„„„pëÖ-ÆŽ›mò páÂâããéÛ·o¦u ;väðáÃ;v,Ã~ïffftíÚ5ÃØ-Z°ÿ~zöì™áxëÖ­ ÍôÚ-Z´È<tèÐþù‡#GŽhhP¯_íܹs¶ïC]Ìž=»Ð$D^^^:Ù/Uª‡v"à`»üwq=ø:ox¿A¯q½²ÜHàuY˜ZPξåìËå8.15‘ûq÷ Î{GÞã7r¾ðc¨Z1ëdtÎ$ÖùAÌÈøHºoêγ”g˜™˜±©Ï&ª:çýM© Ê«[®®Ð°á«=×ßžêqKíÿÖ€úÀ³gÏ(Q¢!!!v½I—~,½Cèµk×´.íI¿fÇŽ³¼æ_|¡¹fºJ•*áìœq‡ô‚ÃF2±¾üþ«U«V™ŽµmÛ€Û·og8^£F5û& §Â’<&mZ·¡Më6†CÃÊÜ G<=h^µ97ÝDå’ýBÇ;Žôø$óž|’³h' ´(pR”)ôù£!Cø¾Ã÷¼å){"çVÓ¦ð矯öÜêÕáêÕ¼''ºì¾ÕRV]Ó¥¯m¶µµ²N\³ºfTT”Ökêknv3Èé5_.\Ì*!„Ðfþ7ó9Øù Á‚³î¡ -ÓºUk}‡f¤•·(p>Úý‡B0¤ÎÆ7oàˆDAP±bEöïߟé\ú±Ê•+hº©9s&Ï®™×üýý³}Í×Ùe@!Š/Ϊ¹«ðô÷Ä4â¥tO 6×mhz½)ëgÞÂNèFh=yyË-‘½E'±ôôRš–mÊ’®K ‘þI]oÖÜÝÝiÒ¤ §OŸÎ°Ä!%%___ ½z©Û˜9;;Ó³gOþüóOŽ=šéZ©©©Ô¯_OOOWWŸ¯¡R)iÖ,óZa]yyÙéÔ$ÅÛÛC똼´`ÁÚ·oO“&MøàƒpttdË–-œ:uŠ™3gâîî®ûã?ròäIZ·nÍ!C2lc×­[7ÆŽ‹…… .¤G4hЀ¡C‡¢P(ذa·oßfÉ’%™ŠóJË–-iÙ²%£FÂÎÎŽõë×sëÖ-~ýõWòå5EÑV˜ŠEÞ±µµeáw sõ)"ÔNh=‘Ĝ݊¹E¿-ýHU¦bmnÍöÛ)m[4‹¦r*"T(=úG¾Ç ¯öÜVVV´jÕ*Câ›ÎÝÝV­Zaeõ¢ @ãÆ9{ö,“&MbݺušN„[·nÕÌ>§«P¡/^dÊ”)>|˜õë×ãääD£FhÑ¢…f\§N8qâÓ¦McéÒ¥šN„óçÏÏT\èííåñ®®®´jÕ {{û ÇMMMiÕªU†5Ò 2333–-[Æùóç©R¥ ëׯçÝwßÍ0®Q£Fš5ÝB¼Iž…®$gÑNöÖƒôýbåæ•µ'IOhº¢)W"¯°©Ï&ú×èoਠÇÊÊ __ß|[: +}èÿî%ýºdh!D^‘¼E;Y- J©Ròî¶w5ÉóÔS‹tò,„Bˆ‚Oh=‘ùY›â7…]×wÐë3ÚÎ0pD†' !òƒ ]I΢$Ðz"E„™m¸¸9GæP«d-Öõ\‡ ºï£k¬òª¡(˜ÜÝÝùꫯ¨[·®¡CEÌìÙ³ ‚($$gÑNŠõDägtòÞI†í€K1¶ÜŽ­¥JAÞu"“»»;Ó§O7t¢’õ¬BW’³h'3ÐBÓssOS±0µàÏ~âá¨ßíЄB!^•$ÐB¯S鱩áOÃø¹óÏ´ªÐÊÀQ !„BèNh=‘ùjÃv ãTø)>lø!#ê0pD !òƒ ]I΢$Ðz" òaΑ9l¸¸€6îmøé­Ÿ QÁ$E„Bˆü E„BW’³h' ´žõù»®ïbŠß*¯È–~[07•Ö¬H¡"?H¡ÐUQÏYt! ´ÈwW"¯ðÎÖwPª”ØYÚ±càœ­ –B!Ä+‘Zä«è„hºmìÆÓä§˜š˜²¡÷j”¨aè°„B!^™|†®'EqA~ª2•¾[úr+æ3ÛΤk•®Žªà“"ÂÌ|||X³fM†¿›¬Ž 0___uºî¯¿þšíÿ›ü±,§F%((///C‡! ¤¤$ …¡Ã(Ð$Ö“¢¸ ÿ“½ŸàìÀÀš™Ü|²#*¤ˆP&OžLlll–çFŽ) ´0*³gÏ–uÐB'²Z Y¡'Eíqéé¥ürê¸6`e÷•ލðÐ5i»{÷.=÷¤Þ›õ¨Ö®ͺ6cá’…yËÓ§OþÉpvjˆW;/wnÌ„¯&œœœ§¯ó*/^LLLÌk_§sçÎÄÄÄü{wWsöÿüuÛ÷V%I$BÖ’,#3e+BegŒÁ`Ã`¾ŒìÌŒc²3¡±2Y²Œ¢Œ"$­*Jû¾œß3ÝŸëÞê¶Ü¥¼ŸÇ}pÏç|ÎçýÉÇç¾;÷œóá{)))5C”„HJž‰°>¶œ¥1¨š4»›I7ñÅ¥/†j†š%9JFšÓþ#ûñýÞï‘Ü+°øÿòÂþAà…@\222jÝ÷›o¾á]ãÆ9r$ÊËËQYY üôÓO˜5kV£c%„BjÐ1ù&–Âý„;²Š³{?Û‹>Æ}$UËSß$Â9Ëç µò\£Z¯73n6é©cßmüñãù’g.e Î0ûíoô1àĉxõêÏëÀàp8ppphT›˜8q"PTT„øøx8::böìÙ¸ví_ý[·n¡M›6PWWGÏž=±zõj”””4鼑Fô$B"¬!gi*ê“Ö>‰Áç¬þÉü°´ßRLé6EÂQµLõM"|ûP®»·FoÑM¨;6nGFpªGÕG©i).þu³¦6¾W×ÀÀ€çý³gÏðÕW_ÁÆÆÇŽkT›ÕÕÕðööƸqãøý÷ßaii‰õë×cÈ!ܺªªªèÝ»7,,,<|ø§NBDDÔÔÔ}n„HšDH„E“ëG ´˜´ö quØjœ; Ùq$ü]鑱U׈²²2”@ˆÞQMà]ü;¼Ë{׸ *„¨£¼ÍyÛ¸öÈÊÊÂÈ‘#¡  €‹/BCC£Ñm¹¸¸ð¼·°°@ûöíqÿþ}žò—/_òLÌÏÏÇŠ+°k×.¬\¹;vìht „HJž‰°Z{ÎÒ(&Mv*öþwó€Îzq|ìqÈpht((**BS¯(ç ýúÃÂ΢޺‚\ùë Ò«ÓëäUX˜6®ý•––ÂÝݸ~ý:ÌÌÌšÔÞÀùÊŒßÿoß¾åN(üp¥ lß¾¸zõj“b „ÒzQMšäaúCLýs*@[Iç&žƒ†bã{Iýºw쎸ì8TëÖ>ÄÂ8Ålùƒoˆ„°~~û3–ýµ ÅÖ‚WÈxLþbr£Úc ~~~¸wïÑ«W¯&·™ššŠÎ;ó”%''ƒÃáÔ»:‰¼¼<ÌÍÍñêÕ«&ÇA!¤u¢nB1iò3‹2áqÂÅÅåÈâÔøS°Ô©}Ù1"œú&þ²ñtxШå’R‰SÁÌQ3<À¼óÐ-·+x»L¦ ú©ôÃp×á>F•+Wâäɓظq#FÝäö ,,Œç}ii)ÂÃÃall\ïúÎ¥¥¥HHH@ÇŽ›%B¤M"$Âj9Ks£ZLZÛ$ÂòªrŒ99)ù)€í#¶Ãµƒ«„£jê›D¨©©‰3¿A·;Ý øL(P 0 7¬ö³°jɪ&ÅÀáppîÐ9 ~54€ @ ÷Pî…î8ö[ã&ù½ïÀøá‡0{öl,Y²¤ÉíÕ8}ú4*++¹ïÏž=‹ÒÒRLš4‰[öìÙ3äæòþ†žžŽ9sæ ¸¸nnnÍ!ÒÀߟæ¦á´¶œEh‡˜´¶ù³/ÌÆ”;€=f`~ïùލõf]å®¶]ñ ôŽ<†àÁx›ð½»÷†ï"_têÔ©YâhÓ¦ þ ú —B.!èJžG=G7›nðZæ…þýú7Ë1æÏŸYYYÂÏÏg›¦¦&vîÜÙà6 ¢¢‚à³Ï>ëW¯pðàAtèÐ'I?~ü86n܈~ýúÁÜÜ/_¾Ä­[·P]] ¬ZÕ´_B‘64‰«µå,¢@ 4i°í÷¶ã`ôAÀÓØ5r—dúHÉÉÉÁg’|&ùˆô8#GŒÄÈ#EÓö;),,äÛÆáp¸ïÑ£_o± ²^½z¡ºº‡ÆêÕ«qöìYäççÃÇÇ›6m‚®®.·î'Ÿ|‚ׯ_#** —/_†‚‚ Ì™3rrt{$„"‡Õ7à’4YMÏZkøíÿòËËp;ê†*V3M3DÌŠ@ÁH&£¤¤„   Œ1BÒ¡$$$(--•t(Í+ºøå½¥=WÉÅCÈG 5å-¢Bc Å¤5 Èžý' ŠUAU^Nü“’g ßi !¢@“‰°ZCÎ"j”@‹IKŸ[š‹OŠÜÒ\pÀAÀèØéÛI:¬V©¾I„„Ò4‰«¥ç,â@ ´˜´äùU¬ 'àyösÀjçÕÛy¬„£j½„™DH! E_ÇaµäœE\(&õZze).¿¼ g3ß9}'áˆ!„B$‡hR§ƒÑ±ýÞv@wƒîðœzö"„Bi½h&1‘öù999˜ÿÍ|Ä&Ä¢°¢ 0ï`Ž úÐVµ-þœð'TäU$j«G“ !¢kkkI‡AZ€²²2(**J: ©F=Ðb"ÍòÃn†¡·GoS>†èþшwŽÇÃ~q¦ú Êÿ(‡|®<Îx©&‰šDHšDH„%Í9‹´ èZäççãáÇˆŠŠBrr2úõë‡ñãÇóÕ[¸p!_™±±1–.]ÊS&­òóóó1kÅ,ĉçÿuÊ€;`fG#GI„÷Q¢I„„Q I„DXÒš³HJ k±mÛ6¬]»úúúÈÉÉAII‰Àzçΰ··ç¹ØÞ‚š´[±~ìjÿ.BxÓá ~?ò;fúÎkl„B!ÒˆèZøúúbÆŒ011¹¹yu¿üòKîS{ZšÈÇ‘¨îS÷2ó2\üë"%ЄB! 1еjß¾=LLLš­=iDX\Q\%àMöÑC|\“cÈÍÍEIIIý• !MBO"$Â’ÖœEšPt3ؼy3üýýaddWWW,Z´ÊÊʉïc,£I„Í/&&k×®ø ’tx"!-×sSËÂÂÂPZZ*±´ô²šäYb¡2é*«ÉI¾ùæ:t¤~ÔÝD×®]ãþ}Ñ¢EسgfÏž“'OÂÛÛ[‚‘Õ¯¬ª ¡ê¡¨|Z ˜ ®#ûZƒÛ†ó g±ÆFjgkl ‡z’ìW%%˜²jf|ùe£Žñiß¾ÐLM…\bó*+aì䄟oÔ1$!88#FŒt„BZ8û˜\6’¹¹9FŒÝ»w×[·¨¨˜9s&¶mÛÜ †ÇJPk IDATÒôÕYqE1±ø{·ï¥ÕÅHII AAAµ&zk¿øŸýú+ìëêágnŽQQÐÒÒjT A'N wæLøÖZgm›6ø,8ö={6ê››Ë7„ãÉ“'ÈÏÏX_AA=zôà){ùò%ÿüsܸqCà6}}}ddd***ðí·ßbË–-þýÅ£¤¤ööö8~ü8:uê$°ÒÒR())5{Ü„H;z!=‰°~4º QPPÀ}Ÿ““ƒï¿ÿÕÕÕpuuå©+-“³K²áàÂMžGXŽ@Èä¨+¨ÃÙÉ;~ØË§.cÃw(y–ú&jii¡½›¢dÿ÷ÝijŠëÖ5)‡ƒIß~‹#jj‚Ѧ ü÷ Ks»párrrx^GއÃÁ Aƒ¸õ~þùglÞ¼S§NÅ»wïPPP€³gÏâùóçpwwGee%_ÛÞÞÞPVV†ºº:qúôi‘œ!ÒˆžDH„%-9‹4£º7nÜ€¹¹9ÌÍÍ‘ššŠ£Grß× ‚¡¡!áää}}}`åÊ•8p O{Ò0›5½0Nœð ý`Lç1øsŸP–S®gO"NÂL"\°nv ¸¦¢´ýZææ€–V“^îsæàbI >LCEÙû jjjÐÒÒâ¾1gÎôìÙ“ûubee%Ö¯_eeeìÚµ ZZZ••…‡‡&OžŒgÏžáèÑ£Ü69zöì‰iÓ¦áûï¿G÷îÝqãÆaÙ²e"9B¤ }O„% 9‹´£!µ°´´Äš5kê¬ãççcccÞ[Ú<Ï~×C®HÉOÌq˜ƒ_Ü~-ç‘ã„߇c¡£8´ïÖ ZÎÎÍv wžGŽ`rv6ä úÞç÷bÔ¨Q(((ÀíÛ·a``ÀÝ–WUUa„ ˆ‰‰ÁÅ‹ù¾ÕÑÓÓ¼xñ‚oßš2zÓ¾}{@jjjSC&DêÑ$B",šDX?-&’?í>œ:s“çµÎk)ynò$š±ÐͱòFmܽ¼pÑÜY_ïó¢E‹pñâEìÚµ Æ ãÛnii ‡ƒÐÐP¾mW¯^tèСÞãDEE%ä£@“‰°haý(qÈ¿‘t®‡\‘SšØ6|¾súN¬1ÆiÈ“kz¡¿jÓ¦É+oÔ¦fEŽájjbé}þñÇñÓO?aéÒ¥˜5k–À:êêêðòòBZZBBB¸å8uê”””àããÃ- åY•£¼¼çÎÃwß}UUU :Tt'Dˆ” I„DX4‰°~4„£ ŽÆØ“cQRYŽ vÚ™=fJ:,"" ׯ‡£««HzŸk¸{y¡¼¼\,½ÏË—/‡¬¬,ž={žmÚÚÚ8pà`ýúõ¸sç<<<0fÌèéé!88 ضmLLL¸ûMž<UUU°µµ…¶¶6›› yyyìÙ³ffµ<Š“B€èVæôÓÓð>íòªrÈÉÈ!À#Þ]¥û‘â¤i4551üƒD³¹q8x¾×£Û\àëë‹Þ½{sË&Mš„ŠŠ õ544¸ïС=z„ 6 ""Ož<££#öíÛÇHXÃßßaaaxñâž†Š &`ïÞ½8~ü8¦L™Âýè³Ï>ƒ¶¶6· ˜˜˜p߯[·¹¹¹ „³³3·\WWÞÞÞÜ÷ööö1bLLLðàÁœ;w[¶lAHH"##é+PBi¥¨ZŒòóóyÞˆ>ïÓÞ¨¨®€‚¬þÿ%ÏDhÜÞçÊÊ€¥%ðøqóääIÀË‹§H½ÐöööÐÓÓã)sqqDGG׺ߋ/PXX¾žûO>ùðø¿Ÿ‡½½=:uꄵk×bÉ’%¸{÷®Àá2<€† RgÌúúúxøð!~øá|þùçØ¿?2220wî\ÄÄÄ`ÇŽõŸ8!„‰z ÅäLð\s ªÕªèhØ}§ôÅÊ¿W‚AE^g½Îb˜Å0I‡I¤€0“?ì}æòð€ñO?aƧŸ69Ž¢ìlì-*Bž™O¹(z¡û÷ïÏWÖ·o_@JJJ­û%&&Öºÿ Aƒøöÿã?àïï;wbëÖ­hß¾=¾øâ LŸ>;‰011±Ñ__ÊÊÊbÍš5øõ×_ŽeË–5ªBD&aÑ$ÂúQ-&ª(Xx–÷ –\ K \𾀦¢Y=´<ÂL"\¶m²gÍâß ¬ŒÒ08%¥É+røí܉¼±cn«é…n®9ž>}ÊWVóØáºVâÐ××>¾QçòèÑ#TTTÀÆÆ¦Qû"*”<aQò\?J %E`½nkþUHë¶dãFd;9……‚_UUx®­Ý¤9–þð’‡ ©ý……Hrtl¶±Ð¥¥¥<îyyy8yò$455y&î}H[[“&MBrr2.\¸À-óæ wÿI“þWðâÅ =ݯ_¿FUUwã×_ ‡ƒ9sæ 77—§nrr2÷ï¡¡¡HOO羯¬¬ÄÕ«W1uêT€§§g~„BZÂ!AÕíªqýîu,_¸\Ò¡¤§­-Ìÿë­•’*šp + Œ‹þQ½:z‡ÂËË kÖ¬ÁéÓ§add„°°0¼~ýûöíãY1C 6àþýû3f † „††"''ûö탮®.€'Nœ8={öD§N ##ƒ¿ÿþ/^¼À7ß|Ã}·¶nÝŠ¥K—ÂÚÚƒ ‚ªª*bbbPXXÈMÂ÷íÛ‡ÀÀ@tìØ&&&ˆŽŽFVV8–,Y·÷'xBiU(AKÙÊÅb…H·ú&îö÷y ßΟ/òc€¦¦&–-[WWW¬]»û÷ïGDD\]]áëë '''žúnnnh×®OYÍr;vì@dd$RSS1~üx̘1ݺuãÖsuuÅîÝ»qëÖ-¤¦¦BMM óæÍðaÃйsgž6,Xggg>|ÑÑÑxóæ ìììxzÿúê+tìØ?Fff&zõê+++øøø G"øiÒ44‰‹&ÖÃúÜ`Ò`~~~¸ø~°!˜%; ¿mýM"qé$//óçÏcĈ’…´ !!!ðððàKÞâEW¿”üÿ{OEÀUArñ´`~~~4šÅÅŦ¦¦t½ÔÆ@‹‹&QÛÇm1{ÊlñÇB¤ZCžDH!¢dˆ‹&Öh Q~® ¯Þ^èѾê%„BiIh ´¸xÈfÉÂ0ÍÓ†OÃêe«%!„Bi J Å¤O›>è¥Ü Ç „ó@g´mÛVÒ!)EÓ!¢@“‰°haýh‡˜¨(«à'ÿŸà9Ö“’gR'ažDH! å/†|Hë™™)é¤õ@‹ È'Â’••ÅÖ­[qâÄ I‡BZ´´4I‡@¤M"$¢œ¥~ÔMˆ”Ù¾};Œ%iaŒ±}ûvI‡A!ê&DÊÌ;sçΕt„B©õ@‹IYY™¤C -D\\œ¤C -DVV²²²$i!èÞB„E9Ký(®Eyy9"##±gÏøûûãÕ«Wë1ÆpäÈÌš5 Ë–-ÃÝ»wÖ£ùDX4ч+<<ááá’ƒ´to!¢œ¥~”@×¢oß¾èÕ«.\ˆo¾ùÏž=ã«ÃÔ)S0oÞ<ÀÓ§O1hÐ ?~œ§^rr²Xb&„Bi”»ÔÆ@×bÓ¦M033Caa!ìííÖ ÃÑ£G‚áÇøwüê—_~‰±cÇBAAAœ!B!„1 èZ 2–––uÖ9xð ”””àêêÊ-swwGVV.\¸ ê›]\\œÈÇÈ5¹ÆÆÙc S722©©©µn‰‰A||¼Àm¸wïžÐñH£æø·¬Kbb"¢££›ÜNcâlÈ5&lœõÅQ×vQÿ¬Eî- «K÷º·to‘v”@7ÁóçÏamm YYYnY×®]¹ÛÞWQQ!ÖØƒ>äVWTryyyÂ)aô!÷/I~Èåçç#??¿ÞcKÝ[V—î-to${oi 9‹¤q=7¸NÑÑѰ··Gpp0FŒÁ³ÍÔÔÄÑ£GyÊutt0yòdüøãkkk$%%ÁÁÁòòò|ÐÓÓ“ªkãò‡BSS Þ·òY†•;CNî߇qú/^x¯A??aËj&N™ššŠìÿ‚0÷¡‚‚´mÛmÚ´XïÕ«WÈËËCçÎùöUPPÀ»wï`cc÷ï™3g0fÌ©º6ú¡9Ê222œœ ;;»&µ×Ï” ù¿ÀápPVVÆý6¼±×_xx8LMMëÜ·æs5++ šššˆŽŽ†‰‰ ž>} "%ÐõhŽÚ××÷ï߇¡¡!ßþ„B!’ôá„Áׯ_£OŸ>PDÒ&6‘‘ž>ãÇÇôéÓ1lØ0”——K:,"åNœ8AƒÁÀÀ@Ò¡HŒìš5kÖH:ˆ–(11÷ïßGvv6ŒÖ)**Â_ý…k×®AVV–ïA*`gg‡>}úàï¿ÿFjj*œE9'Æ^¼x+W®àöíÛ022‚ºººÀºçÏŸÇ¡C‡ðèÑ#hiiñŒ‡Ÿ:u*¾ùæXYYáÞ½{ÈÊÊ¢•^Z¡üü|ܽ{W®\AZZ:uê$°^RRŽ9‚3gÎ ;;:u‚¬¬,wûðáÃáââOOOâÆøä“OÄuDLŠ‹‹‰ØØXèèè@YYY`½¸¸8üùçŸHII¾¾>O½7n ==ðóóéS§ ¥¥UëµGZ¦æº·Ô˜7oæÏŸÿqc¤A®\¹Âôôô€999 ¬—œœÌ¬­­™¶¶6ëÙ³'“••e_|ñ«®®Xÿï¿ÿf;waäDìíí&##ð°°0¾:ÕÕÕlÚ´iLUU•yzz²~ýú1eeeÌ­3`ÀÖ©S'Ö©S'Ö¶m[¦¥¥ÅæÍ›'Æ3!¢vöìYÆáp¸×Km÷–0===fggÇ&MšÄtttØ'Ÿ|ÂJJJÖÿã?ØÈ‘#E9‘777&++Ëý,toaŒ±M›61Ö­[7¦¯¯Ï Ù£G¸ÛÏœ9æM›Æ}¿bÅ ¶víZQ‡OĨ¹ï-±±±ÌÀÀ€UTTˆ!zéE t=|øíܹ“ݸqƒ999Õz!Ž5ŠuîÜ™ååå1Æ a‡2ÆËÏÏg<`UUU,##ƒMŸ>}ûí·â: "&§OŸf<`7oÞ¬õC.44”`—.]â–yyy1SSSVUUÅWûöílöìÙ¢ ›HÀ³gÏØùóçYzz:>|x­÷–°þýûs?¼=zÄää䨾}ûcÿÞ[þøã–’’ÂBCCY÷îÝÙ©S§ÄuDLvîÜÉNž<ÉŽ;Vë½%22’`{öìaŒ1V^^Μ™­­-·ÎÛ·oY»víXbb"ËÌÌd¶¶¶,&&F\§AÄ ¹î-5¾úê+¶téRQ‡-õ(n‚Ú.Ä””&##ÃÖ­[ÇSÞ±cG6lØ0ÆcÙÙÙlèСÌÜÜœõíÛ—­Y³†egg‹#l"wïÞ­õCÎÛÛ›©ªªò|;Ȱ+W®ðÕ¿téß ´.µÝ[ž={ưM›6ñ”ÛÚÚ²~ýú1Æ+,,dÓ§Ogýúõc¾¾¾,((è£ï)jÍ꺷̚5‹)**²ÊÊJnÙÑ£GvçΞ2{{{æââÂ:$ް‰„4åÞRcþüùìÅ‹¢ ³E÷‘Á?ÿüƒêêjØØØð”wíÚ·nÝèèèàÊ•+’H™§OŸÂÆÆ‡[ÖµkWî¶¡C‡òÔ§±¬¯¸¸8@—.]xÊ»v튀ªª*öíÛ'ö؈ô‰ŠŠâÃZso‰ŠŠâ>³ÀÛÛÞÞÞ‰‘Haî-5~üñG±Å%ÍhHKK øBÌÊÊ¢΄GZZß/[–––PVVæ^K„uß[rrrPRR"‰°ˆ”JKKã»V¬­­!//O÷£æzÔñG÷Á(ââbà{’“ŽŽct!UUUPPPà)ãp8““CUU•„¢"Ò¨æzøpޚ뇮ò¾ââb¾Ï!yyy¨©©q?§þÿÞñágÝ[jG ´Ô,WWó•HØØX(++CSSSa)edd„ØØXž²¤¤$ÀÈÈHBQiTs=|x½<~üêêêPSS“DXDJò}¥§§#''GಪäãE÷–†£ZÌÌ̾k¶RÃÒÒOž<á){üø1w!5j®A× ]+äCfff?‡j¶R£æþ!èz¡{‹`”@‹@¯^½Ð¾}{ܽ{—[–ŸŸÇÃËËK‚‘iäããƒÜÜ\Ü¿Ÿ[Œ6mÚ`ĈŒŒH›nݺÁÎÎŽgrrr2bccáãã#ÁȈ4òòò›7oÀ- ‡ššFŽ)ÁȈ´©¹·\½z•[F÷–ºqcLÒA´$ïÞ½Ã÷ß Bee%ÆX±bÚ¶m ÀÔ©S±hÑ"ØÛÛc÷îÝHLLDDD}uöÙºu+®^½Š¼¼<Ü»w½±Îê!IDATzõ‚ŽŽœ±|ùr@ee%F…èèhøúú"55'OžÄþýûáëë+á3 â’™™Éý÷~ðપªÐ»woÀ¾}û`bb¸zõ*ÜÝÝáââ‚îÝ»ãøñãÐÒÒBXX444$?¯cÇŽáþýûÈÈÈÀÉ“'1vìX˜˜˜ GÜ„§¤¤½zõ,^¼©©©øßÿþ‡U«VaÕªU’ ŸˆÝ[Dƒ–±k ªª*ddd€û¾²²’[Ï×תªª8xð ®]»{{{=z”’猩©)ºwïprrâ–¿ÿõ©œœΟ?_~ùáááÐÓÓÃåË—1dȱÇK$GAA{­ÔüYCQQ‘û÷¡C‡âæÍ›8räbcc1cÆ Ì›7>à>2yyyÜÏžšo6322››Ë­£¬¬Œ[·naåÊ•øù矡««‹Ÿ~ú ³gÏ–HÌD2èÞ"ÔM!„BHÐhB!„B€hB!„B€hB!„B€hB!„B€hB!„B€hB!„B€hBH«Éó46QËÊÊÂõë×yÖ†o¨èèh<þ¼£ªÛ³gÏ––Æ}çΤ¦¦ŠíøñaÌ„".”@BZ´””Ìš5‹û”GøøøàŸþáÖ™1cöìÙ#¶˜ÂÃÃ1xð`6º… bÛ¶mÍUíòòòЧOždt̘1 lrÛ?n–v¹|ù2ÆŽ+’¶ !¤.”@BZ¬?ÿü]ºtÁÝ»w1iÒ$>|óæÍC^^ß·ZšY³faôèÑb9ÖÏ?ÿ [[[îã}›ÓÅ‹±dÉ’fo¦M›†¸¸8\»vM$íBHmèQÞ„)//³fÍBÿþýÄóHÚyóæáüùó÷{ûö-ÒÒÒйsgž}ÊËËQRRMMMžúùùùPTTäÖ-((€¼¼<”””jm«®˜åää ªªÊ-KKKÃëׯajj }}}nùgŸ}YYY@uu5òóó¶©®®Î­¥¥¥xúô)ôõõaddToLEEEرcvïÞ]k”””––¢cǎܲÊÊJBCC22¼}1………‘‘¬¬,JKKQ]]Í}Ä´‚‚TTT¸u+**ðôéShhhÀÜÜ\àñóòò‡¶mÛ¢}ûöÜr555Ìœ9ëÖ­Ã!Cê=WBi6ŒBZ €=yò¤ÞºvvvláÂ…lôèÑLVV–`FFFìñãÇÜ:¿þú+ÓÔÔäÛ×ÌÌŒmذ§­E‹1OOOn[†††,**Š[çìÙ³ ËÉÉaŒ1V]]Í.\ÈtuuÙ;wcŒ]¾|™uîÜ™ÉÉÉ1===€Í;—Û†““›={6cŒ±§OŸ2_wïÞeŒ1VZZʾúê+&++Ëäåå6pà@–œœ\çÏæäÉ“LVV–•——ó”ëëë³U«V1GGG¦¨¨È0;;;–Àcìõë×LNNŽ;vŒg¿ââb¦¥¥Å6mÚÄ6lØÀ¯——cŒ±ªª*æïïÏ”””¸ñÚÚÚòü›$$$°¾}û2999Ö¶m[&##ÃìííyŽwç΀%%%Õyž„Òœh!¤EЉ‰:wî,Tý_~ù666HNNÆÃ‡¡­­}ûö5êØ»ví‚™™ ZÇX—••a„ 8wîîܹƒ¾}ûV­Z…áÇ#??oß¾Evv6&Ož,°N:¡¤¤„û*..ƧŸ~Š: S§N€•+W" ÇŽCnn.îÝ»‡ÒÒRLŸ>½Îs‰‰‰™™äååù¶mذÆ CFFþúë/TTTÀÛÛ`hhwww¾ó>uꊋ‹áçç‡åË—cÆ 033c Œ1œ8qð믿bÕªUصkÞ¼yƒØØX˜˜˜ÀÓÓ“;ùróæÍPTTÄ›7o™™‰¢¢"lÚ´‰çx–––Üó „q¡šÒ"%$$ÀÔÔTèúÆÆÆX·nŒŒŒ`ooqãÆáÒ¥K:¶6nÜØÙÙaâĉ¸|ù2_½ÜÜ\ > ¸{÷.¬¬¬ü;l!..–––PVVèèè _¿~Çáp ¤¤Ä}­^½·nÝÂÅ‹¡­­‚‚üøã˜={6<==¡¢¢‚>}ú`ùòå¸zõ*’’’j=—˜˜nú¡öíÛcíÚµÐÒÒÂàÁƒ±xñbÜ»w>Ì;ׯ_çY-dÏž=;v,Ú´iSçÏpýúõ?~<¦M›---ØØØ`ýúõxúô)nß¾ xôèLMM¹Ãj”””àêêÊÓN›6m ¡¡A 4!D¬(&„´Hzzzx÷îÐõÝÜÜxÞwëÖ /^¼¨ulq]>ýôSp8î{{{{$$$ðµåêꊪª*\¿~mÛ¶å–ËËËcþüùøòË/akk‹µk× ½ÛÞ½{±cÇœ>}ÖÖÖ€øøx”——ãï¿ÿ†§§'ƱcÇr{ØëZ/99·}˜¬Ö¼úô)ÀÅÅVVVØ»w/ 66wîÜÁìÙ³ë<‡œœ¤§§#!!'Þÿýï••寻páB¢]»v˜?>ÏÊ*ï300@rrrÇ$„æD 4!¤EêØ±#’’’PTT$Tý÷'îàN|«®®ž„ø}eee|ejjju¶U£ÿþxô袢¢øÚX·nž={†Q£FáðáÃ077çžð¡«W¯âóÏ?Ço¿ýnyyy9 W¯^9r$ÜÜÜðé§ŸÂÓÓàóÄ¢Öõž•””¾¯bÁáp0gΠ¼¼{÷î…µµ5œœœê<šxmmmyâuwwǾ}ûпÀøñ㑜œŒ… âþýû°³³ƒ§§'_{iii°°°¨ó˜„ÒœhBH‹äéé‰mÛ¶aÇŽøöÛoù¶—””p‡GÃÆÆyyyHKKƒ±±1€‡ddd4:Æ;wB__#FŒÀ¹sçx’^àßñ»þþþð÷÷Çòå˱eË|õÕW“ã¿5ÇÆÆbüøñøúë¯1uêTžmÖÖÖàp8(++ƒŸŸ_ƒbìÒ¥ öïß/p[hh¨À÷5CQÀ××+V¬ÀñãÇqèÐ!|÷Ýw<û¨¨¨ðý¢¯¯mmmdggׯ®®.–.]Š¥K—"((£GF\\·÷===EEEèÒ¥‹PçK!Íz  !-RïÞ½1sæL¬]»›6mBqq1€{ŒƒƒƒÑ­[·µ×·o_¨««cïÞ½¨¬¬DRR¾ÿþûZ{¦…µiÓ&,Y²nnndgg#44Œ1n½’’”–– <^ff&ÜÜÜ0bĬ[·Žo»¦¦&¦OŸŽÃ‡ãܹsÜÞ²²2œ:uªÎøºuë†ÔÔT”––òm‹‰‰ÁPYY‰/^`×®]°±±AŸ>}¸utttàåå…/¿ü%%%ðõõåicàÀÈÈÈÀýû÷yÊ/^Œ .àØ±cÜoª««Ì]ò.00'ùÎËËðÿ=ØðâÅ @×®]ë6lÀ–-[PUU…;wîðõ¢‹Zff&”••¡¡¡!ÖãB%ЄB%..oß¾…£££À'BHkE 4!„B! @ËØB!„Ò”@B!„Ò”@B!„Ò”@B!„Ò”@B!„Ò”@B!„Ò”@B!„Ò”@B!„Ò”@B!„Òÿ«T8ªœø™8IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/create-chunksize-15GB.svg000066400000000000000000003727101416254111300253660ustar00rootroot00000000000000 image/svg+xml Automaticchunksize PyTables-3.7.0/doc/source/usersguide/images/create-index-time-int32-float64.png000066400000000000000000001677161416254111300272010ustar00rootroot00000000000000‰PNG  IHDRÐß}™SsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìwXÇ÷ÿß÷ÒQé(MAŠˆ¢ X#v°Çk”X¢1ĨјØ»Æ’øµ7Ô AT#Š€(*Š"½Êåžßüî~Xî.ØÍ¼žç>ÊìÙ™3³³³ggÏœÁ`0 ƒÁ`È…ð]+À`0 ƒÁ`|H0šÁ`0 ƒÁhÌ€f0 ƒÁ`03  ƒÁ`0ŒFÀ hƒÁ`0 £0šÁ`0 ƒÁhÌ€f0 ƒÁ`03  ƒÁ`0ŒFÀ hƒÁ`0 £0ú rãÆ Êõ»ÿ>¶lÙsssž?ŽÐÐPn h*Ïž=Cpp0¶lÙ‚¥K—b÷îÝ …H$z-z¾-***ðûï¿cÒ¤I°´´ÄÀß©>ׯ_‡¹¹9NŸ>Í¥EDDÀÜÜçÏŸçÉ–””àèÑ£5jÚ´iÃë#—/_Æ‚ `kk sssTTT¼µ:üðññAŸ>}^{¾²®ÿÛfòäÉèÒ¥Ë;+ŸQ7ŠïZ™µk×âìÙ³rÉnÞ¼ùùùÈÈÈx/zÉÉÉðööÆ·ß~‹I“&½öü‹‹‹‘‘‘òòòמwAAÁ{ۮOŸ²— ™™™°°°à¥9::¢o߾ظq£\òåĉðóóCff&—¶yóf,X° QùˆÅblÛ¶ K—.Eii) E‹(**ã›o¾¯¯ï+éû¶øñÇñã?ÂÇÇS§N…‘‘Ñ;Õ§¼¼(..æÒÊÊÊ‘‘’’ž¬¯¯/Ž=Š‘#GbÖ¬Yh×®àâÅ‹0`ºvíŠQ£FAMM ŠŠïÿ£7''=zôÀÌ™3±páÂw­úôéƒÖ­[ãСCRÇžãÆƒ¿¿?ºwïŽ7ÂÎÎÍ›7Gvv6bcc±víZlß¾ýƒ1 ÿøãXXXàÌ™3ïZ•:qwwGRR’Ô8}þüytïÞÇ—J€ÀÀÀwþBÐD"RRRðìÙ³w­ àþýû¨ªª’yÌßßb±øµ—ÙµkW$%%½ñë–žž^ç ÀÞ½{ßÈÄãÕaô¤.CXUU666RéåååPTT„‚‚—VVV†ŠŠ hjjB  ++ Ïž=ƒ££#'÷òåKܾ}&&&õÞì"‘wïÞEQQ÷°mˆ—/_r3ZeeeÜ®¨¨(u~~~>îܹXXXðt¬555BYY™K«¨¨@YYZ´h¼xñÉÉɰ´´„Ay=}úéééptt„ššZƒeggg#55&&&033ã“´óæÍ¥fÊËËQ^^uuužÞuñèÑ#dffBQQíÚµƒ––VƒçÈâÉ“'HMM…¦¦&lll ¢¢"%SYY‰ääd¼|ùvvvPUU•+ïÂÂB((( Y³f¼t‘H„ââb4kÖ JJJªûEii)w}rssqÿþ}tìØ‘“ªgIÿý÷_hii¡M›6ReA  yóæ "$&&B$¡}ûö2ëVÉ5ªÝžMMM^z~~>TTT¸¾Ñ²eKîÚŠÅb¨nÚ5Y×K,#..ŠŠŠ°¶¶æÕ»>æÌ™ƒ9sæ¨6¬šb@Ÿ?þþþ:t( þÏ#ÏÀÀ^^^ðòòBXXï¼’’ˆD"®]>|ˆ´´4ôèу{ªªªššŠ‚‚™ã„¤ïKÆ% ………‹Å2¯…’’’Ô‹ŠɽþøñcØÛÛsí^ûÞª¨¨À;w  akk+³½köa"Brr2Š‹‹åú ^TT„„„´k׺ºº2eTTT`hhÈÝSeee(**Bnn.,,,8ÝÕÔÔPVV†ÌÌLhhh@MMMªïIxüø1EJJ ºuëÆ»GêËéëöôéS¤¥¥ÁÚÚšW_‘HÄÝ\„B!444lÇšºXXX U«VRÇ+++QRR­ùùùHJJ‚­­-ïž%"@,C$ñîII=tuuAD¼ük+ ÐÔÔ”š¤ÉÍÍÅÝ»waoo/ÕÏ•••ahhÈ»^ReIPRRâ•••¸{÷.ž?###XXXHõÙ‚‚TUUA,óꦡ¡¡Pmmí:_D"’““Q^^;;;™Ï»ââbˆÅbîºIîk[[Û:ïG"B\\Š‹‹9½2 Æ[¹¸¸È<¶bÅ @<àÒæÏŸO(11‘œœœ Šˆˆ "¢… ’ºº:wlΜ9$‹yyWUUÑO?ýDªªªœœ@  ¹sçRYYY½:ïß¿Ÿ;§æ¯f=ž?NC† á744¤   ¹ÚERFxx8—¶yóf@‘‘‘äååE€Ë{áÂ…RyäççÓÀ9%%%Z¸p!­]»Vª]‰ˆnݺEvvv<Û·oO·oßæd®]»FŠŠŠ4tèPÞ¹Ô¶m[222¢ìììzëG¶¶¶Rí7~üxÊÉÉ‘«}ˆˆÂ¨mÛ¶¼<444èÌ™3œŒX,¦M›6ñ®³¢¢"ùùùÑË—/yùyzz’™™/ÍÀÀ€ú÷ï/UöŸþIèøñã\Ú®]»…††Ò€¸ë£©©I$"¢íÛ·“¶¶6§ËÀ©  €—·µµ5¹¸¸PHHéêêr²FFFtåÊ•ÛeãÆ€RRR¸´ÌÌL®?}ú”KOII!´qãF.MSS“† FDDIII2ûzÍ¡R"ïïïOÜñ¶mÛR\\\ƒúÖ& €ÐæÍ›už)**ÒÝ»wuÞ°aÃHSS“âââÈÜÜœÓ?//ˆªïÅšõ4eÊ***âòøå—_EGGsiyyy$ O§ŒŒ @Ë—/¯S'É=Zû·ÿ~""ª¬¬¤åË—“’’wLEE…Ö­[GUUU¼¼$}8 €ë{µûymÄb1}ûí·œþ€úõëGçÏŸçéA$}/HÆèÚ?ÉøUû7kÖ,.¯+W®¥¥%ïx—.]x}™ˆhÖ¬Y€’’’xcVRRÉ7–ùùùŠ‹‹#777NVAAÖ®]+UÇÚ¿†Ú‘¨úsvvægooOÿþû/Oîøñã€Îœ9CÆ ãÆÉsI$Qu¿ªëž|òä ¹¸¸µµ5/ɸòçŸ’ŽŽwN§N(//òòò¨W¯^Ü5WSSãoDDáááR×_SS³N}jŽ?þø£”¬¶¶6íÝ»—W†ŠŠŠÌ¼$×¶ÿþd`` ÕÎÛ·oç=÷…B!Í;—ÊËËyr’qþæÍ›Ôºukž..\àÉ>yò„ÆŽKúúú<]Œë¼Þÿe˜ý–©mxÖ¤>ÚÜÜœFŒA!!!´qãFÒ××'SSSúꫯÈÖÖ–vïÞMAAAäîîNèÔ©S¼¼,X@ÈÇLJNž|8]¿~bcciÚ´i€fΜÉËóuÐ&&&Ô»wo  ýû÷“¥¥%5kÖŒV¯^M¦¦¦´fÍ §áÇZ¹r%/okkkÒÓÓ#š;w.]¹r…–/_NdaaA•••õ¶Mll, ;wriû÷ï'¡PHB¡Ž;Æ¥ïܹ“Pll,—VÓ€.++£¨¨(RVV¦¾}ûr}=**Š'oddD†††äççGW¯^¥E‹‘ªª*uêÔ©^]eѺ¢¢‚ÈÞÞ¾Ñå 6Œ”••©uëÖ´zõjºqã…„„Pyy98p€PÏž=éòåË”@ß~û- O>ù„ËCò¢QÓè:sæ ÷¶k×..}ß¾}€{á—ÅãÇ)**Šttt¨[·n\›?{öŒˆˆæÍ›GhÚ´iCÑÑÑ4bÄ@ß}÷//200 ###Ú·oÅÄÄPhhh½m"¹ÇÇGñññ”@“'O&CCà èôôtŠˆˆàÆW‰î’:yxxPóæÍ¹ô´´4""ºzõ* …B²³³£ 6Pll,­ZµŠ ÉÈȈ÷Â"1 MMMé›o¾¡k×®QDDåææ6j,“Ðæææ4xð`ºpá8p€ìíí ݸqƒˆª'#‚‚‚Mœ8‘Ó=&&¦Þv|ñâikk“ŽŽ>|˜îÞ½K'Ož¤–-[’ºº:=zôˆ“•ÐFFF4räHº}û6ݹs‡f̘Aè›o¾!""‘HDQQQdhhHNNN¼{R2)P—­§§GFFF´xñbºrå ùúú’P(¤1cÆ»»; 4ˆ‚‚‚h÷îÝdjjJÍš5ãMjÈ2 ###)<<œ÷“<_çÌ™ÃÉ-Y²„¶oßN7oÞ¤ÔÔTÚ·o÷|¾zõ*'wãÆ ²³³#333^Ý$[² èM›6P=!Eÿþû/ùúú©gЧ§'µhÑ‚ZµjEÓ¦M£ˆˆZ·néëë“¶¶6åççs²#GŽ$@@;vì øøxÊÈÈ ÞKã0ú-ÓTzâĉ|˜Ð¢E‹xéÉÉÉ€:wî\oÞDõÐîîî<Ù„„îa'áÚµkTÏêÖD2Y»]û÷ïO€{Ë—ð÷ßúú믹4±XL^^^¤¢¢B111´{÷nêŸQ“ɬmnnn½r"‘ˆÚ¶mK:::õʾxñ‚Z´hAÆÆÆR_ œœœHAA’““¹´×e@wéÒ…'+yÙª¿œH¨¨¨ UUUrppàÉ[[[:zô(/]b4Ԝ唅X,&]]]>|8—6qâDrvv&gggš:u*—>|øpÒÕÕåµOMZ‚ŠŠ 3Ffy’Y¥Ú/OÆ #žnŠ˜˜H¤¾Œ•——s3l’Ÿd6¯¦ž5û8Qu?355%eee*..æóöö&Â¥µjÕŠúõëÇý=gβ··§=zðÆŸqãÆ‘ºº:UTT4X/Yý/--”””¤Æ‘ªª*jݺ5©««ó¾2:qâDƒå“ŽŽŽÌûÆÂ¢Aš¨úÅ«öì²ÉŒm:uêDŠŠŠ<£’ˆèĉ€Ö¬YÃ¥IîY/Üue—.]’ºÎºæõ!" %€ÿeïÉ“'€üüü¤Ê¬‹E‹:tè/=00{’ 1 õõõ¥¾"ØÙÙQ³fÍxÆ¿™™yzzÊ,·.ZÖdR§N 4ˆ—¾mÛ6@‡æÒdе¹yó&©««“««kƒ_s333IAAÆŽÛ þjÐ………¤­­MZZZR“ ={ö$@À›í÷ôô$€ÿÕèöÆÉ“'¹4sssòð𨷌ÿÁÂØ} Œ5Š÷·$üØ€x~[VVV000àEW†H$Âüùó¥ò7nÄb1nß¾ÝdÝþþûoÀ'Ÿ|ÂK·¶¶†¹¹9nݺÅEh µëngg‡–-["##ƒK‹ˆˆ©ƒ‰‰‰Ôb°ŠŠ „††¢ÿþR¾è066Æ­[·¸4@€C‡AWWÇÇ‚ àáá+V4ªqqqÀo¿ý†Ý»wCOOD„ÔÔÔzÏ{ðàîß¿Ï>û ÚÚÚuÊÅÄÄ ¨¨ýúõ“ò=4hªªªÙ(å¡®¾ikk [[[.]YY]»v•ùCUUC† á¥õíÛx×Y½zõBxx8·èÒ¥KèÓ§úôéÃù‹Åb„‡‡£W¯^rûæ×EË–-áééÙ$}_yyy¤ý»êhÚÚÚ¼_||¼”Ü€x?xð™™™èÞ½»”¼——€êplz÷î+W®p!Ù¸6çüDÃÂÂàîî.×:Y\½z•••R÷¶P(„——JKKqóæMÞ1EEEôïß_®üããã‘››+ó¾y¡Ñ€êu 1119r¤”ÏóСCѼysÞ$¡ö5«o,óôô„Ì|jß³nnnPVV~å¾+鵯UïÞ½¡¤¤Ä=+jÒ·o_žÿ¾äü’’üóÏ?¯¤ªªªT›IƧáÇËLOOO—;ÿÌÌL 2†††8{ö¬ÔZ“òòr„‡‡ãèѣؽ{7‚‚‚`bb‚”””&Ô¦šøøxäåå¡_¿~Rër¼¼¼@D¼û¨#GŽÉK“5^õîÝQQQعs'ž?Þdÿ+0ú¡{÷% k§€¡¡!Ï`•hÞÞÞ¼ßèÑ£¼ÚC?55ÊÊÊpww—:&¹I3(ÕFVŒŒx¡…ÒÒÒxåÉÒ¡¦¾UUU¸xñ¢T{…B$÷`í—àíVû~066–Z<)k¼úê«¯àææ___ÁÝÝ'Ožd!`ë€Eáø@¨ý†ÞPzM$«~Ož<)u“Kx•U¶jjjÜŠêÚ«€%«Šå‰¦PòÔQò柟Ÿ/µâ»vx ‰á3aÂL›6­Þü$ˆÅbìܹ@õ¬BPPÜ›iÌœ9øí·ßàååÅ \»víœ9sê\Ñ-AÒ¦ …2’ÈIf'kR32@}Ô53[VVVç9¯Ò7›"+ ‰a ¨¨¨p3J*** åÚ .£²1¼ª¾¯JË–-¡¥¥…””ˆD"ÞLTÇŽѱcGÕñÛ%/j5QUU•Š Oÿ©yKÚ1,, <€¢¢"<==¡¦¦†fÍš!,,Œk'YF’¼4¥_Ë)൑DryÝHÚþóÏ?ǘ1cdÊÈŠT»^MË€7×UTTê Ç–ŸŸ%%%©YSYò’4y£ÕE}_š^å+”H$¨Q£’’‚àà`©Ùÿ¬¬,L˜0ÆÆÆ „7Aàèèˆììì&—-¹eõͺîy¯wûöíñ÷ß#66GÅéÓ§1fÌØÚÚ">>þ{ïÌ€þ ùŒ®¤¤„ž={6)ÉM[{ö¨ž Fhh(ÆŽË¥‹Åb\ºt o<¾³ä 44”ç6 I«‰¥¥%”””¸™ayøá‡Ž;wâĉX°`\]]áààPïyD„K—.¡{÷î˜>}:ïX`` \eK6dhÈÍF2+"÷÷âÅ‹<™ºÐÑÑÁ;w¤Ò%翯´k×ÆÆÆ ƒ††ÜÜܸ‡ˆ››ŸŸccc®=ëCEEEf_Ÿ3f öìÙƒ½{÷âóÏ?åüjöŸÚHÒj¾h›šš¢mÛ¶œÝ¥KÎÀóððà h]]]ΠU½FŒÁ;&o¿®šc‡$´ Y/¯É•——×ä1hÚX&/õùuaaaË—/#99™gT>}ú °··—2\CCC¥6j‘ŒÙ5ûÛûtOúúú⯿þÂd~¥¸~ý:ÊÊʰ|ùrtëÖKðàâââ¤fèS7I›„„„`Ñ¢E¼c’ûôUîprr‚““6lØ€•+Wâûï¿GXXúõë÷Jù~l°×‰ÿÆ ƒ––V¬X!s&Q¶>lll ¯¯/Óˆ“øÓðÒÿþûoäææbèСrÇÇm*ÞÞÞPRRÂÙ³gy3ºÑÑÑRŸË”””0nÜ8×iÖ|»ǪU«0jÔ(Ìž=ÇŽC³fÍ0f̘}»”••‘••ÅÓëéÓ§—«n­ZµB¿~ýpäÈÄÅÅI—äÛ¥K˜››#<<œ7[WVV†   hhh4èjii‰‡âÞ½{\Zqq±Ôµ}‘øä†‡‡ó\$~ÐW®\‘{öÙÃÃã•Ö¼ Ö®] }}},[¶ AAA2eúºQ“Ö­[ÃÅÅqqq¼mÅ«ªªpîÜ9(((ÀÛÛ›wNŸ>}àà`©6ŒŒDDDÄ+ûœ÷éÓ:::¸xñ"ï~ËÏÏGhh(Z·n 77·&çß¡CØØØ <<œ‹} T÷{Y>»¯MMM 6 §OŸFtt´Ôqúÿ±B2–ýõ×_Rñ¾%4u‡>mmmØÛÛ7ê>¼àÔÞGòwmßk ÚoºæxUZZŠ¿þú ÆÆÆ<7 ÄÇÇ×ùmñóÏ?ã×_ŲeË0yòd™2’/µÝgŽ;&S¾{÷îHOO—ÚÝRh×®"##‘““Ã¥¿|ù.\@³fÍ0pà@y«ÃCVŸ³³³ðfÜÿ>tØ ôCCClݺS§N…f̘sssûŒ›%HMMŹsç0jÔ(¬\¹Ïž=Ãøñãaff†ß~û @µ¡qðàA <¾¾¾Ø¿½º=;wîÄ€0|øpbÛ¶mpssCDD„\ÆÅŽ;àææWWWÌ™3ŽŽŽÈÏÏGxx8z÷î ___(++cãÆ=z4\]]1{öl¨¨¨`ß¾}HOOÇæÍ›¡§§Wo9³gÏÆ¹sçàêêŠ3f ??GŽA×®]ë|@¿/ôî݇Byy¹”1·lÙ2NF¼¼¼pḹ¹¡sçÎPPPÀ–-[^›®wïÞÅ×_ à>ðÿ÷ÿÇl_}õç‚RÚÚÚ8}ú4&OžŒÁƒ£W¯^èØ±#ÌÌÌ››‹èèh„„„ÀÔÔ´ÎMAjóóÏ?£_¿~ðôôÄüùó¡££DEEaÁ‚R r{÷î_ýÏŸ?—jó¯¾úŠûÿ« ¥¥…Õ«WcöìÙpwwÇôéÓ!‰°k×.a×®]rm˜TB¡ëׯÇðáÃáææ†Ï?ÿ¿þú+Úµk÷ÆSíØ±ÑÑÑðððÀŒ3àììŒŠŠ Ü½{øâ‹/äÚÚ]2–yyyaêÔ©pqqá'Ÿ={cÆŒÁÊ•+›¤£——6oÞŒ^½z¡C‡ÐÓÓÃòåË딟={6þïÿþË–-CVV\\\pûömlÛ¶ mÛ¶•š1ªg;%㕲²2öîÝ‹¬¬,›5kGGGèèè4Ø>@µ¡æèèUUU^›téÒ...R†µ©§M›SSS\¼xQQQ¼/4²äêYEGGG™‘1jSQQÁ['iIš<3QÀÿfåÖ®]‹ÈÈH_W{ãÎ;X¾|9‚ƒƒ±k×.(((ÀÜÜ àE011£££LÔÚcÙ¾}ûdŽe@õ×,Éõª¬v[¾|9ºté‚àà`DFFÖ;ÉTψGEEÁÏÏ.\ÀÎ;allŒ)S¦`Æ Rc,]ºÿþû/vïÞÔÔTØÙÙáäÉ“RQ#† †àà`sÑ_^¾|  z|”ì–+¡®qÅÀÀ€'jëîèèÈ[CÓ¼ysÞDDèСàìÙ³RywïÞ&L€ŽŽ.\¸€ `Ïž=8~ü8\\\ð÷ßcëÖ­RQ8<==†àà`\¾|eee\Ô¬¶mÛJ}!4h"##áçç‡ï¾û/_¾DûöíqôèQŒ7Ž'+kœª£"9::ò®éŒ3†S§NA$¡k×®˜:u*ÆŽ+sgËÿ:jÌ7>ÆGCEEÅ+-ì«ÊÊÊ7î²ÑµVÉ#/ ßø"‰×Õ6ò\?"‚X,–;ZHmÛ†Œ÷ƒ—/_69d\mÞÇ>PUUÅEÌy¼«:WVVBQQñ•C,¾­±LêïNœ8O?ýþù'Må}ìo¯Ê›®¡ªªêµ—ñ:Ç‘•«§2äæMÏÞ¹ñ  ÑƒÉÛ´_WÛÈsý$¡¬šÊÇö û¯ð:zïcx•>-ïªÎ¯klxŸ®Ycëô>éþºxÓuo¤ f<7Ì»Ee0 ƒÁ`0> ˜Í`0 ã­bkk ??¿WÚƒ€Áx—0hƒÁ`0 £°hƒÁ`0 £0šÁh€­[·~‰Ô䨱crÅ€gÏžaÁ‚ï}œç·ÅÓ§O{÷î½7;Ÿ1ÞD„¬¬,ܸqIIIR¡ÑêâÙ³g¸uë–Üá_•¢¢"ܾ}wîÜAEEE£Ï‰D¸ÿ>®_¿Žû÷ï7¸‘Ö«påʬX±b±ø•Á`¼ˆÁ`ÔIll, .-//ð~:::äììL¿üò ½|ù²Qeìß¿_*¿º~ýû÷—+ÏÉ“'“¼·wRR µk×6Jï×…¯Ž-Z´ :ÐÊ•+©°°ð­é±xñb²°°ÒeõêÕ$‰äÊ#++‹–,YBŸ|ò éêê3fÌÖ¼iøùùñꪨ¨H4jÔ(Š‹‹{cåVVV’ŸŸ¼±2šÂÑ£G©M›6¼6äèèHAAARò•••´|ùr222âä…B!ÙØØÐ™3gd–áââB***MÖñÉ“'4fÌ\™***4oÞ<*--•+RSSãÕSSS“¾ÿþ{¹ûù7hîܹ俿Fêêê€6oÞ,S6==èĉrדÁøøøbÆ0¯‘üöööèÛ·¯Ô±víÚá³Ï>üûï¿ ‚¯¯/¢££qàÀ¹ËèØ±#V¬XÁK;räîß¿???^ÀKK˦Uä=GKK ~~~€ 00+W®Ä_ý…Ë—/¿ñÐe@õF<={öÄœ9s`ee…þùÇŽÃÒ¥KQZZŠü±Á>ž·=rnn. €èèh <#FŒ€……n߾ݻwcøðáðõõÅŽ;^›ŽD„áÇ#** &LÀرcQPP€}ûöaÛ¶m(++ï¿þÚ`>b±óæÍC‡ ¥¥…›7o" +V¬@aa!6lØÐ`üñvìØKKKØÚÚâŸþ©SÖÌÌ ÞÞÞX½z5FýÊq®Œ÷†wmÁ3ï+‰‰‰$è·ß~ã¥Kf kÏçççS³fÍ%%%½RÙýû÷'”——פó?´h333^ZUU988€·6S+•öøñcRRR"UUUª¬¬l0‚‚ÊÊÊ"¢êÙB|3Ðááá¼ôü‘аaÃÞH¹eee€fÍšõFòo Ý»w'tåÊ™ÇËÊÊxûúúZµj•”lQQ}òÉ'€þøãÞ±W™NII!Ô±cG^zaa!µhÑ‚š7oNUUUMÊ;++‹”••IGGGnù‚‚"":~üx½3ÐDD‘‘‘oõ^f0ÞÌšÁ¨ƒÍ›7CUU&LK^SS“Û~6..7oÞ„——Î;'Sþ»ï¾ƒÜþ‡ñññ˜7oœœœ «« 2·nݪ󜊊 ,Z´:t€¹¹9&Mš„§OŸÊU„‡‡ÃÛÛfff077ÇðáÑ””$SnÑ¢EÜÇVVV˜ÜÝݱ}ûvœ†:vì(•ÖªU+xyy¡¼¼™™™ æ¡¡¡###y«)ÅÅ‹áåå…«W¯Ê<>þ|Œ3†«Sff&6n܈@GG&&&èÛ·/þøã&ëðÙgŸA(rí¥¥¥X¼x1:wî }}}ôèÑ›6m’òk-//Ç¡C‡0iÒ$ÃÀÀ:uÂ?üHNNÆÐ¡CTÏbzyyÁËËKjûá;vÀÅņ†† h£¢££aiiÉ{x6D~~>€ê‹Ÿ~úIJ.''ëׯG‹-äÞEê÷ßÇ¥K—àéé‰o¿ý...¸rå ÷¯,FމëׯcòäÉ6lN:ggg<~ü¸ÁòÖ¯_>}úàêÕ«ÄŽ; ¡¡@€ÒÒRôêÕ «W¯†±±1V­Z…Ï?ÿêêêo²………‹Å\»ÃÙÙ›6mB‡°xñbhiiaÑ¢E6lïÜ/¿ü“'OFQQæÎ‹o¾ùÎÎÎ8{ö, Y³fœ[ˆ®®.:v숎;ÂÎÎŽË㫯¾Âܹs¡®®???ØÛÛcÒ¤IØ»w/‚ƒƒQVVÆÉ&&&"88'ND`` <==1iÒ$((( ¨¨...X¾|9JKKáëë uuu¬[·½zõâŠæææÈÉÉÁ¡C‡|áºtéD"fÍšUçvÙVVVèÛ·/ñèÑ£F´~Ý´oß6668yò$o±bRR®_¿ŽAƒ5y÷¸‹/"..ÇcîR¸uë[LÈøxxÇ3à Æ{‰H$"òññ‘:V— Gbb")))‘‚‚¥§§Ñ7ß|C(11‘'ûóÏ?ŠŒŒ”Y¾,Žüü|)¹gÏž‘ŠŠ ¹ººòÒ%.<׃óçÏš9s&—&Ë…#>>žiÈ!¼E‘/^¼ -ZP×®]ye©©©Qvv¶”~ò|R–å‘Mfff€BCC‰ˆhÅŠ€,--©¨¨ˆ'¿hÑ"@ûöí“ÙçÏŸoPY8p€Ð_|Ñès›êÂ1tèPRSSã>‘KX½z5ÏÍ <<œÐöíÛ¥ò§Ýe¹pTTTÐçŸNh„ DD´téR@;wîä?sæL@§NâÒ´´´¨OŸ>õêSŸ GZZ)))‘§§'W¯r Þ|JJJ5j/]âpøða¹ô¨I||<¾øâ ˜™™aÍš5>¿©|öÙg(++ƒ¿¿?/ýàÁƒ°²²B÷îÝ&&&ªgækº—·Ý`ݺu˜2e F…–-[b÷îÝÐÕÕåê|ôèQ…BŒ;–wÞøñãðÛÖÄÄOžÉÉÉèׯš5k†àà`´hÑ¢Qç¿ ƒ ‚¾¾>/’Ëõë×q÷î]Lž<™KkÛ¶-† ‚­[·ÂÈÈÓ¦Mßþ)·Ï·„;wîàúõëHNNF÷îݱ|ùrܽ{&&&‰Dxøð!œ¡­­Í;OÒÞ5ÛvîܹHIIA«V­0dÈìÚµ ÅÅÅrërÿþ}À'Ÿ|"u¬_¿~užW»Oddd ¤¤"‘>>>¼þ;a¨ªªreI055Åúõëñøñc\¾|_|ñ^¼xyóæñÚ]2&4äß/9®««[¯œ¼ˆD"tèÐ;wîÄ¡C‡ðüùs<~ü?üðfΜ‰áÇ7*¿ääd¤§§ãÁƒøî»ï°lÙ2 <øµè* I»IÆVãC‡Ð † Ú´iõú/öèÑÉÉÉHJJÂÕ«WqøðaôîÝ[JnöìÙÈÍÍÅéÓ§{öìšš&NœØ(&L˜€uëÖaðàÁسg¢¢¢””Ä-¶“µé‡¬€Ö­[@½›DäççCEE†††R?Ìš5‹3¨,--ñðáClݺ°zõjXYYÁÇLJ7sW­[·Frr2’““ Œ3FfÈ+sssÞß’zHêU555èèè °°P.=€êÝ>}ú@$!$$ÖÖÖrŸû:PRRÂøñãqíÚ5n1ÜÁƒ! 1iÒ$žì¹sçWWWœ8qDûöí!wy‡Brr2âãã„U«VAGGPVV†ªª*™m«¤¤}}}^ÛΘ1©©©øüóÏqûömÌ™3ÆÆÆX¿~½\º¼|ù¤^„êJ“P»OH^&õôô`dd$õ=zt!ôÔÔÔ¸¨©©©PUUÅéÓ§¹uVVV e€×Fr\2–¼*—.]½{÷0eÊŒ?Í›7‡æÍ›‡Þ½{ãܹsxòäI£ó577Ç×_ 777\ºtI®õMA2–¾®ö`0Þ9ïØ…„Áx/©ªª"UUU™¡¼êò®//333rww§ììlRRR¢I“&Õ{Nm蜜@ýúõ“’µ±±!<H‰ïoMÿT ÖÖÖ$ 9ŸQY>ÐãÇ'¡P(åk,ééé4oÞ<@hP^–´,$>Ð5}`‰ˆÄb1©©©‘¡¡¡Ô9wîÜiÔµJKK#ÒÒÒ¢˜˜¹Î©‹W c÷ï¿ÿúöÛo©¼¼œ´´´¨oß¾õžSRRB'Ož¤-ZPÏž=,£®0vµÑÑÑ!)¿ê´´4@žžž2Ï‹ÅtíÚ5ruu%%%%zþü9•——×é½~ýz@{÷î•:fiiY§tí0s……… ÁûL Äó=ÿçŸ8¿ëºxüø1©¨¨P›6mH,sé¯â-¹^µCã­Y³†ÐñãÇ›”wÍ<už¼>Ðýïß¿ßdŒ÷ 6Í`È@(ÂÆÆ¦ÑŸþëÊkæÌ™ˆŒŒÄ×_ÊÊÊF»oHÜ-jF+ªWàK\)dQ{#‡"%%;w®×ß±W¯^‹ÅØ»wo£ôª7NX¹r%„B!‚ƒƒ}~cèÖ­ž>}ŠøøxÞ±¿þú ê OV“ÌÌLôî݆““S²D„ôôô76[סCtêÔ ‡F@@òóó1eÊ”zÏQWWǨQ£Ð§O\¾|¹I[<ËÂÅŹ¹¹Ráj[@WWWÌš5 •••\_TQQ••ÒÓӥΑ¸nÔךšÚ¨{±E‹\ôy®Q]îLùùùˆŒŒîKDçÎñé§ŸÖ¹aRUU¾þúkTTT`Á‚¯mãI˜€€©c’´š‘b ‘žž.×väD„€½½=—^^^Žôôt™îaåÞ½{PWWg3ÐŒ‡wmÁ3ï+³fÍ"UUU*))á¥7všˆèéÓ§¤¤¤DÈÞÞ¾AùÚ3Ф££CÚÚÚ@%%%tëÖ-rvv&:g [µjEûöí£¢¢"ºsç¹»»KÍbÉš®ªª¢nݺ‘ªª*­]»–|ø0}õÕWM………”““C§N"eeervvn0yg cbbH(’ƒƒ]¿~ èüù󤧧G:::”››KDD>¤Q£FÑ… èÉ“'T^^NW¯^%'''RTT¤””.Ïy󿑆†mÚ´‰ÂÂÂèÚµkܱQ£Fq×!00öîÝKmÛ¶%GGGÀE¸!ª{šˆèæÍ›¤  @666D/^¼ ¼¼<Љ‰¡ï¾ûŽ…£uëÖ4bÄ:yò$ÅÅÅѽ{÷hß¾}܆(µ£ñdff’¹¹9¡%<<œ222èìÙ³Ô³gO@ÞÞÞ¼Ùg¢êhEEE™ýkÿþýõF¨xôèikkS³fÍhÙ²eO‘‘‘4{öl@T^^ÎÉoÞ¼™Ð®]»¸´ÈÈH6l8q‚nÞ¼I7nÜ uëÖ‘‡‡ Ñ£GóÊ”Dz©ýµàÙ³gœÎ’{ýÓO?åÒ222¤ôo×®]_+Œf@3u’’BB¡÷"jšMD4zôh@Û¶mkPVV»ÀÀ@ÒÔÔäÂy úꫯ8CH–}óæMÒÒÒ"@@HQQQª>uíD˜››KS§NåÊ“ühÈ!œœÄà‘è€ÌÌÌháÂ…2Ã€ÕæuÐDÕ¡Ètuuyº:88PBBBƒyKÜ-êûÕ44ë3 ëËcþüù ê"!''‡”•• MŸ>]êøÑ£GIMM—óæÍiðàÁtïÞ½ó—×€&"ºpáéëëóÊjß¾=ݾ}›“yòä ðú‚P($)W¢ÄÄDš8q"'_Óý¦¢¢‚|}}¹¾Þ¶m[ÚµkN¯f¨µú h"¢ˆˆ²¶¶–ººººtôèQNnæÌ™Ü‹hÍŸ²²2M›6Mfx·‚‚š8q"þNòSRR¢Ÿ~úIæÎ•...õöýû÷×{"""¨}ûöRç¹»»ÓÝ»wy²² èØØXÒÓÓ“:_MMæÍ›'å²U—-ya­ëW{ÇAI>M 'É`¼ˆ¹d›Áø1vìXÄÅÅ!11‘û+‹‘™™ 55µF…ŽòññApp0²²² ¥¥U¯lvv6ÊÊÊ`jjÊ ç•››‹èèhtíÚÚÚÚÈËËCAALLL¸MrrrP\\ sss¼|ùÿüóòóóÑ¥Kèëëóʪ¬¬ÄãÇ¡­­Í •'áùóçHLLDvv6 agg===)}ããã‘›› '''n¡•<<|øÆÆÆõÊåçç#??ÆÆÆun>SZZŠ„„<|ø666°±±‘kcˆªª*<|ø°^CCCnS"BFF”””¤ØÉrM ¡¡Á-Г‡¬¬,¼|ùzzz2ÑUTT 66?ævÒ«v°>$ý¦f½ê£¬¬ ÈÌÌ„µµ5llld^‡¤¤$¤¥¥A$ÁÃÃC*zGMˆˆ[\& ËWóXAAw¯x{{#<<œÌ‹/PTT33³:]%ªªªpïÞ=ÎÕÉØØ;v”êUUU¸sç233QRRcccX[[7E£¬¬ ñññHMMž}ûpéÒ%üòË/˜3gŽ”ì“'Oêu­©ë:×Ö3>>iiiPVV†……lmm¥ä ‘›› ]]]^±XŒ¸¸8dff¢¢¢fff°±±‘¹³`yy9ž>}Š-ZðÚ¡¢¢¢Þ‹-[¶„ºº:÷···7222ÝŒ f@3õGGGnÛá¦ggg,^¼Xîˆ Æ‘‚‚©¹«W¯ÂÃÃcÆŒÁ±cÇÞ‘f S\\Œ>}úàŸþÁñãÇ_yÌ´´4XYYÁßß#GŽ|×ê0¯ f@3 °gÏaèС>÷âÅ‹ ÁéÓ§!‰’’Òà ƒñ_fݺu8räzöì ¤¤¤àÌ™3ÐÓÓCTT”TȺ÷/^àÈ‘#PVVÆŒ3êüZò_áÚµkˆˆˆÀ7ß|óÚT2ïÌ€f0Þ «V­ÂÁƒÑ£Gøúú¢k×®ïZ%ã½æöíÛØ³gâãã‘““ccc899aÉ’%ra0Œ7ÉGg@ÇÆÆâ?þ@bb" 1xð`©Í-¢¢¢ˆ¼¼ >Šh‰÷Ç‘€M›6!((ß}÷Ž?þÎw?ºrå ·h‘ñvÈÌÌ”9 0Þ¬Íß>¬Íß>¬ÍµùûDRR’TàŸbZ]]ššš°··G«V­¸ô‘#Gâûï¿G\\œœœÐªU+””” ##ƒŒ?>>ž;OII ÚÚÚ¸s篌ÄÄDà¶nnÕªÄb1îܹƒÎ;ó䌌Œ¤ÂâõïߟùF¿ETo!Ëx;°6û°6û°6gÔæcìS¦Ly×*¼÷|3Ð@uDŒ²²2^šäo@»ví€g‹Åb$&&rÇÀÒÒ’3˜%Hα´´äå/%W3/ƒÁ`0 ÆÇÅGc@OŸ>111HNNæÒŽ;EEEn÷·ÁƒCOOçÏŸçdBBBPVVÆ{Ûš>}:RRRšš  ÚEäÂ… èÔ©œœœnnnh×®Î;Çwûöm<|ø½¹1 ƒÁ`|Ä|.0qâDœ:u ŽŽŽ1bRRRƒ~øeeelÛ¶ “&MÂãÇahhŒ?½zõâååïïž={ÂÇÇ ˆEPP¯ÌmÛ¶ÁÇÇ}ûöE»vípúôixyyaäÈ‘oµî ƒÁ`0Œ·ÇG³‘ PAãСCˆŠŠ‚††† OOO)¹·½•·dFšù@3 ƒÁxßavKÃ|Tôû ëˆ ƒÁ`0>˜ÝÒ04ƒÁ`0 ƒñ6øh| ?töîÝËb.2 ©©‰­[·¾k5 ƒÁ`Ô›~O¸rå 3 ÿãQ£FÁÁÁ#GŽD||<'—‘‘±cǨö;v,ÆŽ‹iÓ¦Õ››6màèèˆÒÒRÞÌw¯^½xÆ3(**búô逄„„×UEƒÁ`0Ì€f¼7ܹsþþþøþûï1zôhäååA__¿ÿþ;ºuë†ÇTÿ{÷ÅÕ=püKG,ˆb¯ØE,Øcш±`{mQcŒ=–ägÉ›7&ÑÄ[b7±£h»‘ˆÆ¨{ ØPT,¡ˆXæ÷Ç «+ ¢ÀÂr>Ï3ÏîÎÜ™9» r¸{î½¶¶XXX`kk‹­­mŠ$øygÏžåÔ©StèÐ ‹—Æ“\?]¡B…7|gB!„0%RÂ!²;w@•*Uذa,\¸o¾ù†²e˲jÕ*V¯^M³fÍXµjUš×š8q"‰‰‰øûûãïïOïÞ½™={öKc¸víëÖ­ÃÅÅ…zõêeÔ[B!„ Zd;}úôÑ'Ï:tÀÜÜœK—.¥ûZ«V­âÉ“'<|øüùóÓ¢E Š/þÂs?~LïÞ½yüø1?þø#VVV龯B!L—”pˆl§[·n¯ííí©P¡÷ïßO÷µîÝ»GDDgΜaìØ± >œ®]»¦Ù>>>ž®]»ròäIV®\IÆ Ó}O!„B˜6éÙŽƒƒCŠ}hšöÚ׬]»6µkׯ××—]»vqÿþ}Š+fÐ&11‘^½z±wï^-ZD¿~ý^û~B!„0]Ò-r•îÝ»£Óé8zô¨Á~N‡‡‡ÞÞÞÌ;—aÆ)B!„Bdw’@‹«R¥Jú™9^Õ‘#G V?LJJÂÓÓ“M›61}útÆŒ“¡q !„´H ‡È±Þ}÷]Ö­[ÇúõëquuÅÊÊŠÊ•+süøqV¬XAŸ>}¨T©š¦áççÇîÝ»ñòò¢uëÖT¯^]#F°~ýzÞyçj׮͞={ îSµjUœœœ²úí !„"›’:‡HLü˜gì0þuÐØ0pà@®_¿ÎG}DLL  $""€5kÖ°|ùrƒöffftëÖÅ‹cnþôË—àë닯¯oŠû|ûí·Lœ81߉B!rI s€"EŠP±b’’V;½š5«¾Ñù_}õcÆŒ1èÙ4hnnn©ööz{{§Xü¤~ýúìÞ½›øøxõû6lHDDþþþ\¿~˜˜Ê•+GÍš5Ó¼v\\\š±–,YòuÞ¢B!L”$Ð9€‡‡Æ#C•-[–²eËì+Z´(E‹Mµ}µjÕÒ¼–µµ55kÖ4Ø—'OZµjE«V­^Ë‹®-„Bñ<D(„B!D:H-„B!D:H-„B!D:H-„B!D:H-„B!D:H-„B!D:H-„B!D:H-„B!D:ÈB*B!„oèðáÃ\½z5KïY³fMêÕ«—¥÷Š$ÐB!„ohÅŠx­[‡£•U–Üï~Bò‰$ÐF" tðË/¿0wútHJ2v(zU\\X³~½±ÃB!²:«tº,¹—›µu–ÜG¤NèàîÝ»_¼Èè„c‡ÀŸÀ¹¸¸7ºÆùóç¹sçNªÇlllxûí·صkãÆcÅŠ4kÖìî™Q=zÄñãÇ9qâæææ¸¸¸ÐªU+ìììÒ<'""Ú´iƒ¥¥üè !„9•üÏ!ŠZZ21›$Ðó€Uoxï¾ûŽÕ«W§z¬X±bÜ»wPÉj`` ÑÑÑoxÇ—Û»w/&L`Ñ¢E4iÒ$Õ6¾¾¾ôîÝ›ÐÐPÌÌÌÐ4 € &0}úô4¯=dȼ¼¼•L,X0ã߀B!²„$Ш6nÜH¹rå öYék©ˆˆΞ=Ë£GR=¾{÷n:uꄳ³3ÞÞÞÔ­[NÇ©S§xüøqš×õòòbÛ¶mT«V€€€Ì _!„YDhaTµkצZµj¯}~XXAAATªTé…½ºQQQܸqNGåʕɛ7oºî£i“'OÆÁÁ?ÿü“üùóëµhÑâ…ñ1‚‰'rîÜ9I …B ó@‹éÒ¥KÔ­[GGG4h€ƒƒÍ›7'((È ]PPM›6ÅÁÁZµjáêêJþüùéß¿?áááúv'N¤OŸ>´oß333ÌÌÌ0` J7Μ9È# ’ç—ùøã)\¸0Ÿ}öÙ›¿i!„™"::š;wîpñâEþüóOvìØÁ¼yóX´h3fÌ ,,ÌØ!ŠlFz EŽs÷î]7nŒ kÖ¬¡N:;vŒqãÆÑ°aC®\¹¢ïŽˆˆÀÉɉ &àääÄíÛ·ÙµkK–,!..ŽM›6ªFÙÚÚš¯¿þš3fаaCŠ/À¹sçhܸ1 .d÷îÝDDDP«V-ÆGåÊ•SĹ}ûv~þùg:„MV|4B!^`ݺuüôÓODFFòðáC"##‰ŒŒ$111Õö–––¬Y³†Â… gq¤"»“ZUÆ 177ü"ä§Ÿ~¢[·niž3eÊ¢¢¢X»v-îîL>11‘áÇ3}útý€¾ºuë²nÝ:ý¹5kÖ¤}ûöÜ¿ŸÍ›7LÙ²e©P¡ÎÎÎÔªU‹V­ZÜóÚµkÌš5‹ßÿ·Þz €%K–°zõj¼½½y÷Ýwõí>|ÈСC6lM›6}ÍOG!DFòðð €iÓ¦½´­™™sæÌÑ;)ij$FÕµkW ö999½ðœßÿKKKZ·nm°¿C‡8p Å9·oß& €{÷˜HÑ¢E ¤lÙ²/3¹ÜãàÁƒøûûë{¨?N“&M2dúžæqãÆaaaÁ·ß~ûÒk !„ÈfffL:•èèhæÏŸŸf;+++6oÞ¬ï¤ây’@ £š4iRº&&&rãÆ 5j”¢¹lÙ²T¯^Ý :::OOO¼½½IJJÂÆÆ†âÅ‹÷ï<Ö!!!¯tß%JЬY3}ò ª½yóæøùùqþüyêׯÏÞ½{Y¹r%;vìHW½´Bˆ¬ñõ×_³~ýzBCCSË›7/Û·oׯG Djd¡ÈQÌÍͱ´´Ls^èèèhƒiðÆŽËÖ­[™:u*ÄÄÄpãÆ }ÏCò<Î/SªT)@-‚ò¼wÞy@Ÿ¸oܸkkkfÍšE«V­ôÛáÇèØ±#;v|Åw,„"#9s†6mÚ¤š<ÛÛÛ³oß>IžÅKI-rsss*V¬È¹sç¸ÿ¾Á±K—.qëÖ-*T¨ ßçççGõêÕ™8q"U«VÅ€mÛ¶¥¸vrâÚ`’úõëêÞç%ï+]º4U«VÕ×H !„Èbbb?~<õë×çøñã)¦>-T¨¾¾¾i.¤%ij$9N·nÝÐ4 ƒý[¶l K—.ú}<|øN§ßÅÎ;S\·iÓ¦˜™™¥š$·lÙöîÝKLLŒ~LL {÷î¥`Á‚Ô¬YPSâùùù¥Ø’—"ß±c;vìxƒO@!DzøøøàììÌœ9sÐétôïߟ˗/S±bE@­€ëççG½zõŒ©È)¤Zä8Ÿ~ú)«W¯fäÈ‘S«V-Ž;Ƽyó¨^½:#FŒÐ·íܹ3³fÍ¢cÇŽôìÙ“'Ožðý÷ßS£F Ž=jpÝbÅŠáêêÊœ9s¸téeË–¥~ýúôèÑP3ptíÚ•òåËóᇰ|ùr¢££Y¼x1ùòå˺A!ÄKݾ}›Ñ£Gó믿êÂ%K–ègZ²³³£T©RøúúRµjU#F*rI sˆ+ññ4¶µ5vÜÓé(hþf_^”(Q‚ªU«¾t~ä PµjUƒä4þüœ:uŠQ£F±dÉBCC)^¼8ƒ bΜ9×üòË/‰eõêÕìÛ·*UªÐ¯_?Þ}÷]ú÷ï½½½Áý6oÞÌöíÛÙ»w/§NÂÌÌLŸ@»¹¹±oß>&MšÄ¼yóHHHÀÙÙ™e˖ѵk×—¾çÒ¥K”‘!„È<«W¯fÔ¨QDEEakkËäÉ“™0a‚Á8¼½½ Jÿ„xfÚ«Ž¢¯-y5»U«V½V›'N°ÿþŒì +VŒ; @ |YﯦiÄÆÆ¦{ ï´$$$ išÁ?ÄojãÆŒ3†{÷îeØ5…"·‰ŽŽføðá¬]»€¶mÛ²hÑ"*Uª”¢mhh(ŽŽŽrßÀêլʫ½œ›µ5uÆŽÕ¯{‘^%oÉí¤:¨_¿¾~›HéUJ'ÌÌÌ2,y5G¨BˆìåìÙ³ôêÕ‹ÀÀ@¬­­™1ccÆŒI³}F%Ï"÷‘Z!„9ÞâÅ‹7nqqqT¬X‘7Jç“È4’@ !„"Çzøð!ƒÖÏÄÔ«W/–-[FŒ™0e’@ !„"Gò÷÷§wïÞܸqƒ|GGGºvíJÆ S\ÿòåËlܸ‘   êׯπ2t¾a!„"7 ÆÃÃÇ0|øð«Î ‘ÕLfááÇùßÿþ‡···ÁlÐnïÞ½¼õÖ[=z&OžLçΉ׷IJJ¢W¯^Œ3sssÎ;G³fÍôSä$;~ü85bÏž=äË—éÓ§Ó¦M=z”%ïY!„0e[¶l¡N:>|˜B… ±uëV.\(ɳ0:“é(T¨ÇŽ{a›Ñ£GãææÆÖ­[2d®®®xyyÑ·o_vî܉——~~~´lÙPƒF»»;––êcûä“OpqqÁÏÏ ÆG5X±bãÆËÄw*„B˜®ØØXÆŽ˲eËhÞ¼9ëׯ§L™2FŽLÅdz _Å‘#G¸|ù2;vÔï«S§åÊ•3Xï}ÅŠØÛÛë“g€N:¾}û¸~ý:üñíÛ·ÇÂÂ'''jÕª%kÇ !„¯éÌ™3Ô¯_ŸeË–aaaÁ”)S8pà€$Ï"[1©:**Š.]ºÐ¶m[FŽI@@€ÁñË—/àììl°¿fÍšúcW¯^MQ7|Nr»äÇ5j¤¸Ö•+W2àÝ!„¹ÇãÇ™8q" 4àï¿ÿ¦L™28p€/¾øBßQ%Dva2%ÖÖÖ´nÝ®^½ÊªU«X¶l>>>¸¹¹¤ž@ïÛ·MÓ033ãþýû½Ï•*UÂÚÚZ]+..Žððp *¤ßŒ···AÛ.]ºdÀ;B!r¶ýû÷3tèP®]»@Ÿ>}X¸p!FŽ,wx>? ¦lÙ²FŠ&g0™zøðá >\ÿ:66–êÕ«óÑGqóæM#F&„È’’Ôfiÿì]¿Ç«ÇøxhÐ:t0vTB¤[hh(ãÇgÍš5”/_žÅ‹ë;¾„È®Là7Iêìì쎅GÀÚžû÷NˆW±víZÆGhh(Œ;–/¿üRV4‚çó“ç{¤EJ&›@DGGèçxNžp=µúÙÉØ+UªÄÁƒ ®uéÒ%ƒk$?^¼xwwwƒk=›P !Œ¤Q#øû璘ÍÌTïóõëjۺƎ…9sR?ÿøqxflDª²:yöóƒo¿0Þ~ììÀÉ)kãøñGõDóæpèPÖß_äXþþþLš4‰è ººº92!^É "L®INv÷î]V­ZEåÊ•õË#7iÒ„*Uª°cÇ}»3gÎpóæM  ß7xð`"## ’èíÛ·S²dIÞ}÷]*T¨@óæÍÙ½{7:€   Î;gp-!„‘ètP¾¼JOžT=ÒÃéÓðïÏ1sçÂúõ/¾Î´ipà@ê[÷î™þ6 ìÙ£[·†åË¡o_èÒj×ÎÚ8„x çÏŸÇÝÝÆsàÀòåËÇܹs9vì˜$Ï"Ç1™èúõëS¾|yªU«¦Ÿb.þü¬\¹Ò Ý‚ èÚµ+mÚ´¡J•*xyyáææF=ôm:tè@=èÒ¥ ={öäöíÛüöÛoüüóÏú9 fÏžM»víhÞ¼9õêÕÃÛÛ›Úµk3xðà,{ßBˆ4,^ -[Âó£÷ëÔ; iSøë/X´<<Ò¾Ž‹ ´j•©¡¾²7Ôã[o5 !ÒãÊ•+|ñÅlܸMÓ°´´ÄÓÓ“/¾øB¦¦9–É$ÐË—/ç?þàÖ­[TªT‰:0pà@ Úµk׎£Gê—òþæ›oR,åmnnÎ/¿ü¢_Ê»V­Z|ñÅ)–ònذ!þþþú¥¼'Nœ(Ky ‘]´nö1++•4ÿõœ9£j£Í3è ¹Í›!&*U‚fÍRoãç§’á|ùà™?ÞÓ´gÜ»÷´¤äüyxv¾ùþýŸÆÿÏ?°k\º!!ªÜ£jUhÜX•[¼ÈåËàåÁÁ Bñâê‘fÍÔµV¯õüþ}ÃX:wNYýð!ì߯b¿v *TPÌtê¤þ{'&FÓÊ–Uí¾ÿ>åqNÓÞ{O3ÆðXr4hZš–ö}„ÑkŸþ¹V²dI ÐÍÒÒRëÖ­›öçŸ;¼,%=й‹ô@ !r—Û·Õpÿùª×MM‰àæ¦zGK”€'O 0¾ÿ^M÷í·ªWö«¯Rž›/ŸêµmÔHõD·j¥æmÞ´ òäQ™1VÂÞ>ícUª@ÇŽª·ùèQÕóž,<\=–*•öù/ê¥OËÒ¥ªžÚÕFŽLyÜÜ\õ^ïÚ+WªYQRóå—2×t6’””Äž={X²d »víÒÏDU¦L>üðCL‰%Œ¥™Kh!Dî¡ S åÂ…i·õóK¹¯ukøðCè×~ùE%Ñ={ª™:žW«–*Q:FR}F ¤Þ>#ݻǎ©?îÝ{ºèJò,AA†í4€íÛU¼ ªÌ¢N7ãôiõøöÛi·©UKÍe©b-^³~ýÔ¬%C†H|ð@- ~á‚zo,$$DŸ,9r„Ó§OÿÜ R{{{Þzë-Ú·oOçÎ)ŸÖ·BˆTI-„0MÉ+ &%©$îE K;¢¢Ô*{+V¨×vv Ó©’dÃòå)¯5i?®JFÖ­3LÔ<Ì-[ª$üwTòšœœ`ôh5ÝÌ™ªç·n]•ôß»îîªldÆŒ”ç^¸ ê§?þXÕ‰;9©™:®_WÇóäµk Ï©PA%å+V¨^ö/¾xzìäɧóLOªþ˜øñG6 ÆŽUƒ­¬ÔÑaaªÝ[oeÌçËèt:Î;Ç‘#GøóÏ?9räˆ~йgU¬X‘&MšÐ´iSš4iB50™U„€$ÐBSÕ¢…ê ~vv†¯ûöUu·«rŒØX5‹EÙ²*Ñ:4õÄ÷òe8uJ%È“'«dôyMš¨äÖËK%ØíÛ§¼Z\\T2šÖŒ³g«’󿩏ÏS‹˜|ðL™¢’à–-Uïñ³¾ùFõÖÿù§J¸CBTIFùòðÞ{ª·<µ—Ë—«øýûÕ9QQjÿ³å#yò¨$ÛÃCÅuñ¢𘔤þ hØP 2|þótpP±+öjŸM.pïÞ=.]ºÄÅ‹¹xñ"—.]âÔ©SÄ<78ÔÚÚšzõê$ÌÅäs"C™iÚóó‰Œ6àßUÏV­ZeÔ8„Bd÷îÝÓ'ÈÏ&ËáÉ+F>§H‘"4iÒDŸ0ׯ_›,ŽZ 0V¯fUÝÏÍÚš:cÇ2}úô ¿¶ä-/'=ÐB!„ܽ{WŸ$?û˜V¢lccC•*U¨Q£ÎÎÎÔ¨Qƒš5kR¹rå,Ž\! ´B‘ ?~LpppŠíêÕ«\¼x‘ˆˆˆTϳ±±¡jÕª‰²³³3•*U’¥²…È&$B!ÒIÓ4îß¿Ÿj‚œ¼ýóÏ?/¼†­­mª‰rÅŠ%Q"›“Z!„xNZ½ÇÉÛ­[·xòì¬,i(Z´(eË–5Ø*T¨€³³3*TDYˆJh!„&M§ÓNXX¡¡¡/Ý –³NKž>þµ5M#666Ý ðãÇ3üýØØØ¤+vtt$ϳKµ !Ä’Z‘#ét:}ÏdTTTªÏ“¼ä¯ïŸý?µç/;žm“’’HHHxã„÷ÙGc¯—eiiI¾|ùô[Þ¼y ^¿ê±B… Q¸paòåËgÔ÷#„’@ !²¥ÄÄD®\¹Âùóç¹páçÏŸçï¿ÿ&""‚¨¨¨LéÙÌ ,--±¶¶ÆÊÊJÿøìó´mllÒ•ì>{LVÅB˜I …Fl(_¸p¿ÿþ›øøøžgeeEþüù ¶|ùò¼¶¶¶&)) PÃ’¥öüedz¢­¹¹ù+'µé}´²²2¸§Bˆ×# ´"Ëݺu |||8vì=Jµ]áÂ…qqq¡fÍš¸¸¸àââB©R¥ô‰²ôl !„0I …YâÌ™3lÛ¶ N:ep,oÞ¼Ô¨QCŸ$''ÍÅ‹7R´B!DÚ$BdŠ„„<¨OšƒƒƒõǬ¬¬hÕªîîî´k׎Š+JiBˆCh!D†òõõeÅŠìÞ½›ÈÈHýþ о}{ÜÝÝyï½÷°··7b”B!Äë“ZñÆt:^^^Ìœ9Ó <£T©RtîÜ™.]ºÐªU+Y˜B‘e,XÀ¢E?‘U³8Þ»w‹®Ys+‘ H-„xm±±±üôÓOÌ™3‡   òçÏÏàÁƒéÛ·/õêÕ“Ò !„Q„„„píÚ?Ê¢;NË¢ûˆì@h!Dº…††òÃ?°páBBCC(Y²$£GfèСRž!„ÈÌÍËc²èn?¡Yt/al’@ !^Ùõë×™3g?ýô“~!“5j0~üx<<<¤DC!D® ´â¥=zÄäÉ“Y²d :€V­Zñé§ŸÒ¾}{)ÓB¼±€€"##)]º4Å‹ÇÂÂÂØ! ‘&I …/´uëVFÍ;w077§gÏž|òÉ'4hÐÀØ¡ !LH¡B…hß¾=7nÜÀ‚bÅŠQºtiJ•*•â±hÑ¢899‘'Oc‡-r)I …©ºuë#GŽÄÇÇ€ºuë²lÙ2êÕ«gäÈ„¦¨hÑ¢ìÚµ‹úõëKHH!!!/<ÇÕÕ•Í›7S±bÅ,ŠRÅÜØ!²NǼyópvvÆÇÇ;;;fÏžÍñãÇ%yBdªêÕ«ãééùJm{÷Ÿ$ÏÂ($Bè:uŠF1vìX¢££qssãâÅ‹Œ?^ê…Y¢C‡/œÁÆÆ&E{{{{|}}e…S‘­I-D.qúôiÜÝݹu뎎ŽxyyѲeKc‡%„0a‡bàÀ\¿~€.]º0oÞ¼ö,çÍ›7«ÂâµÉ B!rÍ›7Ó¬Y3nݺE­Zµøë¯¿$yBdNÇ”)Shݺ5ׯ_§R¥JìÚµ‹_ýUÊ2„Ih!LÜúõëéß¿?IIItëÖ5kÖH"Óܾ}:Àˆ#øî»ïR-×"§’h!LØ–-[ðôô$))‰Ñ£Gãåå%ɳ"ÓøøøP»vm:DáÂ…ñööæ‡~äY˜I …0Q;vì OŸ>èt:†ÊüùóePŽ"SÄÍÍÍ›7ËÒÜBˆ @×®]  Ož<,]º”÷ßߨa ‘å$"‡‹‰‰¡[·n„……Ѻuk~ýõW¬­­–ÂÄlݺ•…““[·n¥N:ÆK£!r¸>ø€‹/R±bE¶lÙ‚­­­±CB˜NÇĉéÞ½;QQQ´oßž“'OJò,r5I …ÈÁæÌ™Ã/¿üBž#FŒàï¿ÿ¦E‹üüóÏí.\¸@ãÆñññ¡@Ì;—·ß~›ˆˆƒvãÇÇÃÃƒØØXBBBh×® ,ÈÜ7(rµ•+W²dÉ,--ùå—_(Uª”±C2I›7Ã[oAٲЫL˜ß|~Í›C½zðûïiŸ¿kxzBP 4n o¿ :dÝ{HM‡0}:œ;… AíÚP¥JÖÇqý:  œõ÷)]ºt‰-ZðÙgŸ‘@ß¾}9þ¼$ÏB¼ˆfb¾ùæ­I“&ZÆ 5wwwƒc¾¾¾ íÙ³G¿oèÐ¡š£££öäÉý¾÷Þ{OkР¯iš¦Ý½{Wsppоúê+}›‹/j€¶jÕ*ý¾)S¦h¶¶¶ZXX˜Á}===5OOÏŒ|›"ºzõªfkk«ÚìÙ³ŽIëÓGÓ@ÓòæÕ´¶m5mÔ(M›6MÓú÷×4[[uÌÚZÓþú+õóTm’7++õX @Ö¾gùù©,,4íìYãÅ¡išöçŸ*;;ãÆ‘ÛEDDhü±fii©Z¡B…´_~ùÅØae˜ &hÖÖí ~3w«¨yfÝÍ´vÖÖÚ„ 2峓¼ååLª: €éÓ§³bÅ ÌRù~uÕªUØÚÚÒ¶m[ý>wwwBCCÙ±c gíÞ½777ýWãÅ‹§~ýú¬X±ÂàZÉç'ëÔ©qqqlܸ13ÞžÈå† B\\îîîŒ?ÞØá˜´âÅaÚ4†}û`Á˜<V¯†K— dIˆW½Ì©qu…I“ÀË ‚‚àûï³6þÔªÇúõ¡V-ãÆ"Œ+))‰eË–Q¹reæÏŸObb"½{÷æüùóôìÙÓØá ‘#˜L tRR|ðãǧzõê©¶¹|ù2U«VÅÜüéß 5kÖÔ¸~ý:š¦Q£F ƒsùí·ßˆÇÚښ˗/Sºtiƒyw«U«†¹¹¹þZBd”Ÿ~ú‰ßÿ‚ ²xñbc‡còæÌIû˜“̘￯’éðpUñ¬Q£ _¿j½tfº_=–+÷ò¶ p÷.$&ªö¯~Ÿ°0u¯üù¡pa°³{½xS“”¤êÑ‹…|ù^ïü«WÕù¹uÊô?þøƒÑ£GsæÌ\]]™?>Í›77rdBä,&ÓýÃ?ɤI“Òl’"1.Uª„„„pÿßß2ÎÎÎí’Ï{ðàþZÉÉw2;;;Ê—/¯¿Ö³‚ƒƒñöö6Ø„x÷îÝã“O>`æÌ™”(QÂȉg§Ü¾~=ã®;g”/¯z°CCSo3r¤jӪĎüšS§ªöÉìØ¡^'o§N©ýÀ矫÷fc£çŠUìêªÎKKX|ú©JJUíwÙ²7¯ºÞ3_ÞáîÝ»«çÆR¾<¤öÞ¯¿Bƒ*i®XQÕ•Wª3gª¤øy>½^x¸ªWoÓFÅWµªúv ·¹uë½{÷¦E‹œ9sGGG–.]ʉ'$y)ò“àà`c‡”í™DtPPÿýïÙ·oŸÌH LΨQ£ˆˆˆ eË– <ØØáàßÎ;ÌÍUB–QFR ä_Aÿþ°s§aïõúõ°p!XY©v¯²j{ž<*q|òD½¶¶6ì}µü÷·À™3*Ù¶µU%NN ÓÁùóêX§N*ñüæÃë?y;±c*Ö TÒüä‰*9~û ’ÿ×ÍŸ_m÷î©×Ï÷[[?}®Ó©Á†É xÕªàì¬z¸OžTƒ;wï†={TÒŸ,) nÞTÏwî„!CÔsuMM{ùçf*®^½ÊìÙ³Y½z5qqqXZZ2|øp¾üòKY¹Tˆ7` ôÿû_ªU«Æ7¸qãaaaèt:6nÜHëÖ­)Z´(%K–äâÅ‹çÞ¹s‡ˆˆJ–, @±bÅ5*ùÙæäóŠ- @É’%9yò¤Áµbcc¹qãFª“Í—-[–.]ºd̹ƶmÛðòòÂÖÖ–eË–¥ZÛ/²–¦ÁÊ•êyÆ*Ì(VVðË/ªÇw÷nÕÃúïb“\¹Æ©çß|£föxãÇ«í«¯à‹/àÝwÕ=žW¼8,Z}û‚½ýÓýII*:fÏVußÏþÑà㣒g•,W®lxÝ  øû璘׭SS6mª’ûä?FR³x±ºw‰°j•Š=Y@të~~0w.Lœ˜ú5TÛ‚ê~ JTLÝ_ýÅÌ™3Ùºu+IÿvÓ·k׎9sæ¤ø†Uˆçóù–üåL¢„ÃÒÒ’0qâDýÌÅ‹™8q"×ÿý޵J•*êÿ18þ¼þ@… 033K‘h_ºt‰²eËbýo÷H•*U¸}û6>Ô·  ))I-!ÞDdd$ÇàóÏ?—ÿ¯²‰™3áÀUüÃ}''øñGõü¿ÿUÉæ“'j:½¨(5]fŒ!­UK%èÏ&Ï zÙ‡ QSø%$–c€šã G”Ésòûyï½ôǪJJ@ Þ|6y5÷O?©çÓ¦©Ï&5ÁòåO“gP¨˜ª½{÷Òºuk6lˆ——ÿùÏ8qâ{öì‘äYˆ b ôêÕ«õ½ÏÉ›««+ï¾û.7nÜ ñ¿]5 ..Žß~ûMî¶mÛptt¤cÇŽ€ª‰nß¾={öì!áßnŠ{÷îqâÄ ƒ¯Ï  ??ÙöíÛ±µµ¥wïÞ™ý–E.0aÂBBB¨U«ÿ÷ÿgìpª·ó¿ÿUÏ?ùDͺw‡#Ô ¾Þ½UïïéÓPº´J&³ê‹NÕYÇÅ=íñ¾vͰM‘"êqïÞ§eaÏUË\¡<3q’ÆUMtt´Йšçtš¢ÄÄD6lØ@:upssãÀØÚÚòÑGȦM›¨—Yÿ³ ‘K™D Ç«zûí·ñðð gÏžôêÕ‹{÷î±{÷nÖ¬Y£ïY˜1cmÚ´¡Y³f4jÔˆ;vP¡BFŽ©oãìì̸qã:t(~~~<~ü///æÌ™C¡ç‡ä ‘N‡bÙ²e˜››³bÅ ,-sÕj¶tü8tîü4©}¾8£}÷ê}>}Z•/XZÂÏ?«™-2Kx8,YÞÞpë–ª5~¾^8(Èðu÷îêŠÐPÕÛܬôì íÛ«äöu%O»gk«Ižü6))儸 J:5JyjÕ^?†ìîüùó¬_¿ž 6pëÖ- ,ÈðáÃ=z´¾$Q‘ñLö·òˆ#È›7¯Á>333Ö®]«_Ê»zõêLš4)ÅRÞ...;vL¿”÷رcS]Êû»ï¾£iÓ¦øúúâààÀÞ½{e)oñÆâââøðÃÑ41cÆÐ Ac‡”ë;nnªL sgX»V•6d&Õ{:hzݽ{æ.~ù²J@>Tå)5kª­`Aõ^ïÜÇÕòßÏ*QBÍr1dˆØ÷Çj¨^]ÍòÑGé› TÍ7¨žåW™5ãîÝÔ÷›Ú¤5ÁÁÁüüóϬ_¿^_‚êÛÓ±cÇ2dÈ¿«„Ïdè÷ß?Õýfffôë×~ýú½ðüòåËóÙgŸ½ô>ݺu£[·n¯£©™7o—/_¦|ùò|ýõׯ'× Tõ·ªWuóæ§3Wd¦»wŸ زEõH7i’9÷óðPÉs÷îªÚÑÑðøÚµ*NMݺpâ„JÂ÷ìQ ÷ïWƒGŒ€C‡RŸžîE’k±;w†/¿|yûǧÙèd…ððp6oÞÌúõë9|ø0Ú¿Ýîvvv¸»»ãááA»víä›*!²ü´ ‘<|ø™3g0}úôߢˆ¬uù2¼ý¶*ehÛ¶n5œf-³$%©„öŸToj`aŸ>jÖ ‡Œ½ßÇ*Xº4õ2‘äá©REm£G«’Žo¿UóOÿò |ö™êÑ~UÉSöߺuê¼úy¦âñãÇlß¾õë׳{÷ný˜ Ú´iC¿~ýèÒ¥ ù^gE!Ä“ZˆldæÌ™DDDàêê*KêÙ•+*y¾{W=nÛöjó.g„¯¿V3},¨zn‹5ãŨD>#%/cmú:]úïéè¨æ•^¾\•¾>M “ÿˆU‰öó½Ýðt€æ¹spö¬áâ5¦êÚµkìß¿Ÿýû÷³oß>=z¤?Ö°aC<<<èÝ»·~:U!„ñ˜À—[B˜†{÷î1þ|¾ùæ™óÙˆ®]SIsH4oÛ·Nƒ–™üüT ª×¹\9uïM›Ôª€¿þªSÉH•+«™=âãÕb-Ï›5 ž›ÙSïØ±´§;}úé±gga|v9ñãÇS?·iS5ϳN<]ŠüyIIª\$'zðà7ndðàÁ899Q©R%†Š——=¢råÊL™2…+W®àïïÏèÑ£%y"›h!²‰©S§KË–-qss3v8¹Ú‡ªAs zI;tH»íçŸÃóc‡OœPÓÜ%KࣖàNV±âÓ9ŸA•lxx¨¤qÄ•@&«^]Í;=hšºY³Œë•ÍŸ_-±íí­ì<©VŒŒT=ï[·ªúïÝ»Sž»`ZL¥gOU ]±¢úÌŽWõâ ÊP\\žžcg§>Ó;ÕyMš¨AÍw<®{áBU÷}æŒZ¼eØ0uB…Tiǵkê‹§+-fg111:tHßË|þüy}=3@¾|ùhÕªmÚ´¡M›6ÔH®cBd;’@ ‘ ±lÙ2¾ýö[#G#ââž>nÁÑ>ú(å¾ðp8x0å~ÎpDÄÓ皦Vù Q5¿ß}—òüÁ×Wõ÷ê¥b˨2ùE‹àöm•ü¯XñtÁ335¨°D‰ÔèbÅT¼råÓŸåæ–ú¬%?ü z¼ûíéªö99.^\õ|¯¦ò›>=åõ--¡uë×~Û™*22’Ó§Oë“æcÇŽék™A-Ö¨Q#}Âܸqc(D!?©BdÿûßÿHHH sçÎ)¦UYoáBÕûú*R[Ø­AUÃü2ÏŽÿŠ…ÿû?µU«¦¦±KÍÒ¥¼¦SLÌ«'ОžÐ¢…ª§NM‰ªÎzûvUwüä Ô¯¯–+/YÂÂÔ{²³3§OŸæÔ©SúÇäUpŸU£F }ÂܲeK™rNˆJh!ŒìÂ… lذsss¦M›fìpàêúfç;8–j¼Š¼y_íœWm÷¼rå kScn®J9ÜÝS+\8íû*¤¦›{… ¿Úâ0•*©­{÷—·µ´|½ÏèUݼyÓ Q>}ú4!!!)Ú™™™Q¹re7nLÛ¶myçw(ajS ‘KI-„‘}öÙg$%%ñþûïãòl¡¨¨ži–eÒEFZ½z5GÍÒ{º¹¹Ñ¥K—,½§âÅ$" $/ÛÝ·o_,-åÇîuišÆþýûY¼x1>>>ú3òçÏOûöíqww§C‡2@SdšppÍeQéÏA33 ,( ´ÙŒü&"“ÅÄİeËÞÿ}#G“3………±råJ–.]ÊÕ«W033ÃÍÍ¡C‡Ò¾}{¬­­¥È-Zj«²h¥H7ùÿZˆlIh!2Ù¯¿þJtt4ÎÎÎÔ«WÏØáä(GeñâÅlÚ´‰'OžàèèÈ Aƒøè£¨P¡‚‘#"‹Œ„Ç!o^x¹Ìà!!õcææ 3Ú& ´™,¹|£ÿþFŽ$gˆ‹‹cÕªU,^¼˜sçÎé÷7mÚ”aÆѣGlllŒ¡9ØÂ…pèüõ©}£FÁ‚é¿V«VpáBêÇ †ÐÐ×SˆìNh!2Ñ;wðõõÅÜÜc‡“­ÅÇdzbÅ ¦M›FHH j›ûõëǰaÃd !2Â7ß@H88@… pýú›]ÏÌ † I¹?_¾7»®Ùœ$ÐBd¢õëד””Ä;ï¼#³A¤!11‘U«V1uêTnÞ¼ @µjÕ3f ä“_ÄBdœ+ Z5pr‚åËSO~ÓÃÜ–,ɘ؄ÈA$"IùFÚt:6làË/¿äÚµkTªT‰ÿýïxxx`nnnä…0AíÛç¾ÂêÕ`m­’v ÃãÂéÓP·.´haœ…HI …È$§NââÅ‹äÍ›—îÝ»;œlCÓ46mÚÄ”)S \¹r|þùçxzzÊ4Bä4‰‰ðÏ?ªî9­YC „sçà§Ÿàî]øê«§Ç®\ŽÁÖΞ͚˜…xCÒÅ#D&Y»v-ݺu#oÞ¼FŽ&{øõ×_©]»6½{÷& €R¥J±páB._¾Ì| ɳ9N§’ã’%ÁÎ*V„ï¾K}vŽï¿gg˜6 |}Õ¾øxèÕ bb`Íu!rùm%D&ùõ×_ðôô4r$ÆȰaÃ8pàÅŠcâĉ :[[[#G'„xm ‚‹‹4xý:ìÛŸ|Û¶ŸŸª‘Nfg›6AƒЯœ9£5ž> Ÿ~j¼ò!^C¶H u:¾¾¾œ={– .píÚ5Ê”)ƒ‹‹ ...¼ûî»äÉ“ÇØa ñÊÎ;ÇÍ›7)X° -[¶4v8FÇ´iÓ˜9s&ñññØÛÛ3yòdFމ±ÃB¼ _ß”s=A»vðÇjj¼1c ר¡z¢VÓà@ãÆªWZˆĨ%ÉÓVU­Z•víÚ1kÖ,®\¹B©R¥ añâÅtíÚ•òåË3cÆ ¢¢¢Œ®¯ÌÇÇ€öíÛçÚ²„½{÷âââÂÔ©S‰§OŸ>ðÿ÷’< a R[(ÅÉééœÒÿ®ÀšÂ@×®*yΟ6n+«Ì‹SˆL`´ßì‘‘‘¸¸¸'OýtU)Ú=~üæÌ™ÃŒ3øã?¨Q£†"âÕmÛ¶ €Î;9’¬w÷î]ÆŒæM›¨\¹2 .¤mÛ¶FŽL‘%š5SÿúñÐP8~\=‰Q¥åÊeMlBd£õ@›››³hÑ"9rdªÉ3@žÿ\-”2iRÊk}ö™šênéRµï£à?ÿ={Tr-D‘mF75jÔˆqãÆ1hÐ ºvíÊŽ;°²²bÙ²eüõ×_FŽPˆ—‹‰‰á÷ßL?¾sçýúõÃÏÏ€Áƒ3kÖ, ,hÜÀr‘˜¥÷tuu¥Q£FÿÏÞ}†GUíoÿNBï¤!Ô B‚4)RƒQ=r@,t=)GxñJEB/RT@D0Ô:„z I&ó¼ØÌ!…@23™Éý¹®¹&Ù³÷^+!Æ;k~k-§¶)™èý÷ÍL’úî;ãÆsÖúd0J.îÜ1I?Æ__c-èóç×òä)SàÙgïù²1êl2Á¼yP À½×¾þvì0úÖ¨èçKÜ@–б±±ìÝ»—-ZÅòåËùõ×_ñ÷÷§V­Z9r„òåË»¸§"i[³f ±±±Ô¨Qƒråʹº;³lÙ2úôéÃÅ‹)\¸0ß|ó :uru·²o¿ý–Eß}GI'­`p*>žC†(@»³ téÔ_/UÊþóÒ¥aÔ((VÌþø+¯@áÂÆ¨rt´1úܲ%T¬h¬²qÿ†(»wCïÞÆHvíÚö¯,‹ýû Å-d‰m]žÎ:‘pîܹT¬X‘çž{0jŸýõWhÉò<½|ãÎ; :”©S§РA¾ÿþ{Ê”)ãâže_!f3³Ìf§´Õ*µmšÅ}´oo<Òë‰'`ôèäÇúö5éѤ‰ñHMÍšÆCÄMd‰èbÅŠQ®\9þúë/æÍ›G—.]l¯›L&nܸáªî‰¤Kbb"Ë—/<3@8p€zõê1uêT¼¼¼øàƒX¿~½Â³ˆˆd;Y"@ƒ±áDûöíiܸ1‘‘‘¼ñÆ€1™ð¯¿þâÉ'ŸtqEÒ¶yóf.\¸@É’%©S§Ž«»“©¦OŸNíÚµÙ½{7O<ñ¿ýöcÆŒÁÛÛÛÕ]qº, §NÊ´iÓ`Íš5<ñÄDDD@ëÂì"YÔ/¿üK¸™>žþýû3}út†Ê„ ðòÊ2óŒEDD²Œ,1‰°nݺԭ[0ÖÒŽŽ¦D‰äpÒîZ"qãÆ ÛVóÖÍÜÉ¥K—xñÅY¿~=9rä`Ú´iôë×ÏÕÝ‘Œ°X`ÎØ²ÅØbÛd‚ \Ý+‘%tR^^^¶%ìDÜÁÆIHH råÊ”,YÒÕÝy(‡¢M›6:tˆ"Eа`Áš¤µ[˜ˆ¸‡¾}aÖ,ðõ…Ê•ÁR&@\|ðãÛq±, EÜ»–oìÙ³‡æÍ›sîÜ9X¾|9+Vtu·D$£¢£ð\­lÛΚÇ0m\¿®-Ù‚ E2È Ý©|cçÎ4iÒ„sçÎѰaC¶nݪð,â)Ž3ž6t^xÉf4-’W¯^寿þÂd2¹MéÃÖ­[iݺ5W®\¡Y³f,]ºTk«‹xŠÏ>ƒŒ„É“ëׇºuÚè;`íZ#h߸åÊa;µÉúgÏ#ÚQQpí*•*A»v11ðý÷Ækqq÷Ú£|¤U+ûû­Xüaܯti£oíÚ%o7, N‚þÓèëš5ÆuÏ<fìû$’A Ð"°iÓ&Ìf35jÔà±ÇsuwhÆ ´mÛ–ëׯӺuk.\H®\¹\Ý-É,ï½g„X€ ŒI„£GzÚ40À˜TX²$äÌ ?ýãÇCHÌŸÞÞ÷îM›Bl¬QO]®œ˜/_†añb8}FŒ€;wŒkFŒ¸wýK/Ý Ð/µÙK—÷*S.4j§;u2&=&ýc~êTãk¨XºuƒÛ·õøxhq9•pˆdÀŸþ @pp°‹{ò`¿üò ­[·æúõëtìØ‘Å‹+<‹xš7àçŸß~Û¾±±÷Bm@ÌžmŒŸ> GŽ5Ó;âEðÕWö÷{çãúyóŒúæƒáÒ%ˆŒ„^½ŒsjÕ2Î)S ¾×fl¬1rmõÚkFxþ¿ÿ3Ú?t®^…¡Cïé”tï}d\síš1Ê.âbY*@ÿøãQ°`AF ÀîÝ»éÖ­f³ÙµIÁ_ýÀSO=å➤mÅŠ´mÛ–[·nѵkWæÏŸ¯¯¯«»%"ÎÖ¼9ôì ùòÝ;V¼¸1ú[ Ìœi~d$”- ]»£ÆV+#ÖéõÛoF@ïÞÆŽ5F¾ÁXäã¡N˜8Ñøà~¡¡ðÖ[÷ê¹ÝàÝ>ñ|Y&@öÙgôîÝ›R¥JÙm\¥J~ùå6oÞìÂÞ‰¤ÌôÂ… áÎ;ôìÙ“¹sçâã£ê-‘líÔ)øåc„øË/çÂ…áèQûóÚ¶…ãÇ•5îía¬]k<¿ürò×L&#Øß¾}¯~;©=½]É2ÿý÷¿ÿ͘1c>|8ï¾û®í¸¯¯/ìÝ»7Ím¼÷íÛÇÊ•+9xð 4kÖŒV­Z¥X—º{÷nÂÂÂˆŽŽ&88˜^½z%Û´%11‘9sæ°iÓ&{ì1BBBl›½$É?þÈÑ£G©S§½{÷ÖÖÇÙÄ¥K—8~ü8ÞÞÞºº;)úá‡xõÕWIHH 44”ÿýïÛ]ðÖ­[ÄYk/$W®\*Cy.Ü+¥HLLù³ù^ô[oÁÖ­ðïš5á…Œ‘ä‡ù½g Æ/¿ )ý²ÖOGE£ÑIioÉ‚²ÄôÅ‹¹pá]»vHö?x‹Å”ÞÖIbêÔ©Ìœ9“›7oϰaÃ(Q¢3f̰;oõêÕNΜ9yï½÷hß¾½ÝÿøéÖ­o¿ý6^^^üý÷ß4hЀ÷íâ´}ûvêիǪU«È—/&L yóæ\»v-#ßq»ví råÊäÎÛŽInÅŠ¶ðž1z÷6v*üúkxòI£<äØ1cÔºpᇫ«–Ls>9qâeÊ”qQoÜC– ÐVÏ?ÿ<Ï?ÿü#]›3gNÂÃɉ‰aÅŠ|ôÑG\¿~?ü0“{)Ù]DDf³™råÊQ¸paWwǶ³`dd$+VäçŸ&þü®î–ˆ¸BùòƤ¼úõ“¿6t(T©?üçεÉ~hLœ9ÓXNÎú®U°i¬_{÷™3ƈsǎЯŸqmRO=‡ÃêÕFо}Û~„ÚÏÏý^¸Ð¸ïþýÆñÚµ!(È¥NªOxî¹{kF‹d!Y.@ÇÄİ}ûvâããíŽ×¯_ŸâÅ‹§y­Éd"((0j–óçÏϧŸ~ÊðáÃÉŸ??~~~€±äõ<0ÂPÉ’%mµ×%J” ""ÂîÞQQQÄÅÅÙî‘ô^+V´»W®\¹ìFŸÊ”)£g’•Ê7âââ áÏ?ÿÄÏÏ5kÖP¬X1WwKD\Åßߨº;5mÛûõécÿ¹ÉdŒT?ûlúÛ.VìÁë6wêd<¤wïô·ërqÀ%v›€ ²`ÀJÃýùäþiI.ËG®Y³†gžy†%Jжm[BBBìÖÀò0‚ƒƒ‰eß¾}Ø>·Šˆˆ°½F¹Æýúþ{XŸ÷îÝ›ì^Iµx¦;w®Љ‰‰ôèу_ý•B… ±jÕ*Ê•+çÒ>‰ˆã:tˆI“&%[VU\Í(L ÐÈ4¾RYÛDÜ\–Ћ…W_}•¢E‹²víZbbb¸råŠÝ£Y³fiÞãäÉ“vŸ'$$ðù矓3gNÛD¿úõëÀòåËmçíÚµ‹ãÇÓ;É_º¡¡¡\½z•ßÿÝvlÙ²eøùùÑ¢E Ê—/OÆ ùùçŸm+}=z”¿ÿþÛî^♲ÊôСC™?>¹sçfÙ²eYvCy4ŒzÔ7ß|???8pàÒ3‚+Nf¢Ð ãßíW`8ƪA%€>À"àªËú'™)K¼ÃpíÚ5Î;Ǹqãxúé§éõêÕÃßߟJ•*qþüy6lØÀíÛ·ùòË/É—/Ÿí¼Ï>ûŒš7oN@@aaa´jÕŠÎ;ÛÎiÓ¦ ;w¦cÇŽtíÚ•S§N±víZ~øá»-'NœHË–-iذ!µk×fñâÅÔ¬Y“ÐÐÐGÿfH–—Àž={×èÙ³góßÿþ///~üñGX—‰·vX Ì~‹Ã{ÁÛ@Í+¯¼Â—_~i+9”¬Å X„ëëp7ÙÀbàtZõïdw’¬.Kè‚ R½zu"""9@/X°€µk×røðaŠ/ÎÈ‘#éÞ½{²·³[¶lIxx¸m+ïqãÆ%ÛÊÛËË‹yóæÙ¶ò®Q££FJ¶•wݺuÙ¶m›m+ï#Fh+ïl`ÿþýÄÆÆR¬X1J•*å’>üñÇüãÿàÃ?¤ýý“oDÄ­DKH 'pýîkæ¸8òäÉC›6m˜={¶Ã³ÅbÁl6“˜˜ˆÙl¶{¤tÌQçfV[7n$!!ø`ï>'}¤tìaÎMzìóUwÆ&ŒÑhî³®½çîcÆ¿u#ŒÐÝ (›Ùÿ°âY"@L›6ÁƒãííM›6mzeƒàà`‚ƒƒÓunÍš5©Y³fšçxyyÑ«W/zõê•æy|ðÁé?W—oœ={–bccéÖ­#GŽtI?DäÑÅbŒPþ„š0‚–u미$çšL&òæÍË… hÚ´©ÃB­%éÞå+§µtëî#½,@. 8à<æˆN‰Cd™]½zu .LÏž=S|ý矦U«VNî•Hr® ÐqqqtêÔ‰Ó§OóÔSO1cÆ §÷AD2f-0ØÎó- 111¬[·Î½J™ÉdÂÛÛ;ÙÃËË+ÅãsŽ£__½z5›6ÅlþF‘Äý¯Tާ÷õûÏiE'Nð)ð%0㥤ò`ü±TèŠ1ê\ÛÿnâxY&@‡„„pèÐ!HÍš5íJ*jԨᢞ‰Øse€îß¿?ááá/^œÅ‹“'O§÷AD2æy`pãíþÏî~ì~¡:ã IDAT…®Ì)\“'O>úè#Ê—/ïÔpêεÖW¯^%<|fó;Nj1ù1ÞUø #<{y;À³@w -PÒI=ÇÉúúõëlܸ‘ СCWwG$U‹…]»vÎÐS§NeÆŒäÈ‘ƒ°°0í%âæÊÿ¼ûH¶?c¬ÔpÈͽ:èÛ·o3jÔ(Ö¯_ïòÕ$u{o1‚sa Ðh†Qª!ž#K,c—/_> .œ%vtIËÑ£G¹zõ*ùòå£B… Nkwݺu¼û¤6lè´¶EÄñ|0Vcø7Æä²`&àçåEžùä®\¹òà D\ÄZ¾Q³fM¼¼œóŸÏÑ£GéÒ¥ ¼ñƶÕ7DÄs^}|xóÍ7‰ˆˆ`Ê”)Bxx¸«»(v¼´FzÀóe‰€={ö°aÊ+Fýúõ“F5Jo[‰Ë9»þ9..ŽÎ;sñâE5jÄgŸ}æ”vE$k©X±"+VdÀ€$$$°ÿ~WwIìX'Jv‘e4ÀsÏ=çê.ˆ¤ÉÙzĈìܹ“’%K–lr­ˆd?>>>ÚuTÄŲL€ž9s¦«» ò@Î Ð+W®dòäÉxyyñÝwßQ¬X1‡·)"""–%j EÜÁ¹sç8sæ 9rä Zµjm+::š^½za±X1bM›6uh{"""’~.¾técÇŽåÙgŸ¥K—.|ðÁ\»v-Õóßxã *W®ìÄŠØÛ·o•*UÂ×××aí$&&Ò³gO.\¸@pp0cÆŒqX["""òð\ ïܹÃÖ­[ñóóàÏ?ÿäâÅ‹©žÿÒK/9«k"):~ü8åË—wh;ãÇç·ß~£`Á‚Ì;Ÿ,Si%"""¸0@—,Y’Ï?ÿÜ6)jÅŠ®êŠHº;v €råÊ9¬-[¶0zôh¾þúk‡¶%"""Æ¥5Ð'NdÚ´i®ì‚Hº9:@_¹r…îÝ»“@hh(]ºtqH;"""’1šD(’NŽС¡¡œ8q‚*Uª0eʇ´!"""§-’NŽ ÐÓ§OgÁ‚äÊ•‹yóæ‘'OžLoCDDD2‡Ëg'EDD0yòäžBÙ²eÐ#‘ä8uêùúôéÓ <€ÿüç?Ú ADD$‹sy€Þ²e [¶lyày•+WV€—9uêf³™ $Ûf>£þñpõêU5jÄÀ3õÞ"""’ù\ ûôéÃÿûßž—/_>'ôF$eŽ*ßøî»ïX±byòäaúôé˜L¦L½¿ˆˆˆd>—h___ *äênˆ¤Éúüùó¼ýöÛ|øá‡T¨P!Óî-"""Ž£I„"éàˆ=`À.^¼Hpp0o½õV¦ÝWDDDË¥:oÞ¼Zm@ÜBfè FΜ9™1c^^ú[VDDÄ]¸´„cúôé®l^$Ý23@_ºt‰0zôh*W®œá{ŠˆˆˆóhØK$Ž?dN€~ë­·8wîµk×fèС¾Ÿˆˆˆ8—´È˜ÍfÛÐþþþº×Ê•+ùî»ïÈ‘#3gÎÄÛÛ;3º("""N¤-ò§N"!!‚ fhŘøøxÛªï½÷ž6LqS Ð"YõÏS§NåСC”-[–#Fd¼c"""â Ð"úÂ… üûßÿŒíºsåÊ• =WP€y€ÌУFâÊ•+<ûì³tíÚ5s:&""".¡-ò Ðûöíãÿû&“‰É“'g^ÇDDDÄ% E £úÝwßÅl6Ó³gOêÔ©“y—P€y€Œè•+W²zõjòæÍ˸qã2·c"""â Ð"iHºôÃè„„ À°aÃ(UªTfwODDD\@Z$ YzÚ´i8p€Ò¥KkÇA¢-’†G-߸|ù2£G`„ äÎ;s;&""".£-’†G ÐcÆŒáÒ¥Kѽ{÷Ìˆˆ¸Œ´HŽ= @Ù²eÓ}ÍÁƒùâ‹/0™LLš4 “Éä¨î‰ˆˆˆ (@‹¤áüùó<öØcé¾fÈ!ÄÇÇÓ½{w‚‚‚Õ5qh‘4\¼x€¢E‹¦ëüµkײ|ùrrçÎÍ„ Ù5qh‘4ÌgŸ} eëDDD²h‘TXGŸsåÊEž`ذax¥w˜FD$ xþyûðlåç]ºÿùgúï×¼9T¬&ӃϽx&O6Fº“¿¾nñú¦Méo_Ä•4-’‚ÄÄD®\¹Ü Ð?ýôGŽá‰'ž gÏž®ìžˆH¦Ê—ÏxÎèdÂÔ-j„óテ˜ø¿ÿ»÷Úж-äÏ»v9¦}‘̦!4‘\¾|™ÄÄD¼½½)T¨&L`ðàÁäȑÕÝÉ47nõÑ&4jä¸v¦M3FÀÇŒ Œc±±Ð­›ñ>žo¿ý–ððpüüüèܹ35kÖLv¯={ö0þ|Nž¿þúëy˜]DD²¨o¿…þýeè~ýÕ¹µÇçÏ›¯€±ƒá±cÎk[$³xÌô¬Y³xá…ðñ1¾¤Ó§OS¾|yÞÿ}-Z[¾4ˆW^y…™3gЧO‚‚‚X±bíÚµ ,,ŒU«Vñ×_Q«V-:uêÄ Aƒ8xð ­Í·Þz‹F±bÅ L&  FÌ;—¾}û:óË—Lvùòe, +W®Ä××—Áƒ»¸W""7w.ôí ¥J©”)ã¼¶-èÑΞ…¡Cá³ÏŒzèÝ»nDWó˜èöíÛÛÂ3@©R¥hÖ¬QQQ¶ckÖ¬!::Ú”êÕ«G±bÅøæ›olÇfÍšEÙ²emá mÛ¶DFF²eËvîÜIDDmÛ¶Åtw̪U«R¡BfÍšå¨/SœäÖ­[ìß¿€W_}???WvID$ÃæÏ76O)QÂÏåË;·ýñãr‘ž=mÅ?ùĘÀØ»·sû!’Q ïÏž={ ´‹ŒŒŒ ›TÕªUí‚vdd¤ÝuI¯±ÞÃú\í¾)˶×Ä}ݾ}€Ã‡ãååŰaÃ\Ü#‘ŒYº^~|}áÿý?0›5˜“>Ξµ¿fãFÈ• êÖM~¿˜˜{×>m»víÞ±#GìÏß´ >øÀXÊî‹/Œco¾ ;²eÆF*"îÂcJ8î7|øpbbb=z´íXtt4¾¾¾T¨PÁîÜjÕª1oÞ<ÀxËþÌ™3¼üòËvçXttt´Ýóýa<00… ’`7"~âÄ /^lwnÇŽ3ðŠ#Y´Åb¡K—.T¬XÑÅ=ɘùó!!Áx¤¶ív¿~ä YáΈ‹K~îĉÆ(rR+V0¶·†è‹R c)»¤gÌ€;aøphØj×~ô¯QÍýùäĉ”qfmòÈ=mÚ4¦L™Â¬Y³puwÄ Ý¸qÃöñ›o¾éžˆˆdŽNàÉ'Ó>çé§í?/WFJy’áóÏCîÜ©ß+鮆»wá¼Nc)»ûÏ[´È!ß·OZ܃ÇèÙ³g3pà@>ÿüódÛ-ûùùGTT•+W¶ß»w/%J”Àd2Q²dI"""쮵®æa­ƒµ>ïÛ·ÆÛ΋ˆˆ X±bv£Ïeʔш³±Ö>—(Q‚ ¸¸7""b<FÙ²ä\;Í›ôhÚÔx¤æé§“‡wqžûóÉý#Ò’œGÕ@Ï›7¾}û2iÒ$ú÷ïŸìuëhôýKÛíÛ·Ï®¬# Õm½‡õyïÞ½vçEDDhÔÛX´Â³ˆˆˆÜÏcô¢E‹èÑ£&L`РA)žÓ¢E üüüX¶l™íضmÛˆ‰‰!44Ôv¬wïÞ?~œ]»vÙŽ-_¾œ€€êׯÀÓO?M`` Ë—/Çb±FÈŽŠŠ¢·¦»µÈÈHbbbhÒ¤‰k;#"""YŽÇ”ptëÖ Â ³ÏŸ??k×®ÀÇLJÏ>ûŒîÝ»sþüyJ—.MXX;w¦M›6¶k:wîÌœ9shÑ¢;w¶-_g]OÚjÊ”)´k׎¦M›RµjU,X@“&M’M@÷’tIÃâÅ‹»°'"""’yL€;vlŠÇsåÊe÷ù‹/¾È¦M›l[yúé§É¶òöõõeéÒ¥¶­¼ƒƒƒùôÓO“måýÜsϱuëVÛVÞcÆŒÑVÞnκ…»Už0&¶úgM wb±ÀˆFHX·6n4>7ÎÐS§ÂÛoƒÉ~~#‡~ǃ.]Œ“lÌËÆðüópçŽQO]¶¬1)ñʇŸ~‚“'vïÜ1®Iº¯YÏž÷tL ôî +W“Ë”… aüx£íÙ³!éæÁ“'“ Œ×cc!o^£ï Ðâj*álmíÚµ?~œbÅŠÑ¡CM ·d2!sÉãóaÃŒÏccáÝwc•+ÃwßÁõëpê= ÑÑб#ÌŸw#²yûm#/\h\sð \¾ À+¯ç<óŒÑÆãC‰÷ÚŒ…»{SЧžÇŒ1îuèÄßyÇhû“ORþººw‡?†k׌Çĉ™û}y Ð’­YwìÕ«9rä EÄsµliß¼yï+Q˜t˜7/Ìšeþ¡CP±"„„£ÕV•*ÁÝ1‡tY½V¬0VùàcŒçO?5&<~ü±ºï׿? x¯Ï=–þvEE%’m?ž¥K—j;nD¨-"žêäIc4ùäÉ{¥… #ÒI½ð‚Q‹>žFQ©R%Ûq@‹ˆ§:w^{ –/7j¦ïçußûÒï¾kLÜ5ÊxÔªeLøëÞªUK»Ï;'oî…øC‡’èÒ¥Óߎˆ³(@K¶åIi¡ˆxª.]Œ‰#F@ðÄP €1 \«üý·ýùuë£ÂK—;®YcŒO˜`<† I_»ÖÒŒþý‘îÔT©’üXÒ‰…"Y…´dK'OždÛ¶møøøÐ±cG»×¬:—~k‹ˆ9sÆÏ-[+_$e±@TTÊ×yy“ ;v4–±[¼úö…áÃþøãn»zuøõWhÜØ( qwšD(ÙÒ‚ °X,4mÚ”"EŠØ½f6›ðñÑß—"â9NŸ6ž­ø’Z¶ìÞ:ÎiÉ—ÏÍÏ?o,—wâĽ׊5–ª»q#ùu ÏIWåqg Ð’-YË7:wîœìµÄ»‹¨šL&§öIDÄ‘*U2VÒX¹¶o¿wü?ŒU.îß7êêUø×¿’O,Ü¿ßXcÚ××Y¶ªRÅÕ_}/Ú_óâ‹Ð´©1z=t(Ü}£ÏæÏ?á½÷2þ5Š8‹´d;§OŸfË–-x{{’ìuËÝ™5^)ÍtqSùóÃèÑ`6C½zÆ%•+¿òбÑJRqqðÑGð䓯D¾† ] «V5jš¿øÂ~«ðaÃŒêÁƒ¥æL&cã«o¿5BôĉP¬˜Qs]«–±³a:pwO+· ÷¨%Û±–o4iÒ„ÇRXPÔ:­-"î¨bEcÅ kÙDRï½g¬žñÓOÆŽ‚Õ«“;v„o¾±¯O.Z6l€õëmÁÏž…š5¡S' 5‚tRÏþƘ”ø€k,\z9¸g6 ì^²„%K–8«EgnÝýø0 ø(tFiÿ?B %›HÏêV Ðò¨<Ȇ ‘˜Í]Ôâ6'µã©¼Ï+€Y@ÔÝãfŒ°•\ Ø)ýƒ?½¼È_¶,NjÑ3ܼy“-[¶Ø~Ÿ'gýýžè†QÖÑÈë„Þ‰'P€wáÂÖ¯_Édz`ù¨„C2ÆÛ;³y‚“Z ®8©-OVóîã=à*°XüŒQº‘HÒ2€›´ƒÐ³V>>ÔêÚ• œõ3åþ"## ¾oI;ãßóiŒÐü ?LäÑ(@‹Ç[¼x1 Ô¯_ŸR¥J=ð|@‹dgÎw»•À|``ÆL"Cï¾êŒ-éEPPW®\ÁÛÛ›„ ðÐÁeÖÔOÉ8hñxÖò­¾a¥-"÷XG§GbŒNW¦¹‚·wÌæÑNjí;Œí%DDD<›j Å#=jý3Üö„-"""™OZ<ÎùóçÙµk>>><÷Üs}½uÚÝK8DDDÄ1 Åã¬]»‹ÅBPP xèë5-"""iQ€“‘úgð¬hÉ| ÐâQ, k×®=@kZDDDÒ¢-åï¿ÿæìÙ³.\˜gžyæ‘î¡hI‹´xkùF³fÍy+n@‹ˆˆHZ Å£ddù:+Õ@‹ˆˆHZ Åcܺu‹M›6 О²¡ˆˆˆ8†´xŒßÿ;wîP©R%Ê–-ûÈ÷Ñ´ˆˆˆ¤EZC‡eäÈ‘DGG';'..ŽöíÛóÞ{ï‘3gNÂÃà fõêÕvç­[·Ž   6lØ@îܹ5jmÚ´!66ÖvŽÅb¡gÏž 0€ýû÷Ó¨Q#~øáÇ~¡bÇZ¾Q·n]¼½½3|?@‹ˆˆHZ>>œ;wŽ-ZØS¶lYòåË—¬þúĉ,^¼ØîXÇŽ3þEesÛ¶m#11‘Ê•+S¤H‘L¹§F ED$;¹?Ÿœ8q‚2eʸ¨7î!Û@‹gÉìúg¸7m±X0›Í™v_ñ ÙjÚd2Q²dI"""ìŽïÛ·???»ç}ûöѸqcÛy+V̰J”(‘ì^ÇçÆ¶{X•)SF#ÎÙõÏpoŒQèÌXÙCDD$«º?ŸÜ?"-Ée»耀€Tt@@€ÝóÞ½{í΋ˆˆ°½Ʋy{÷î%111Õ{‰ã$&&²mÛ6 s´———mò¨Ê8DDDä~Ù.@÷îÝ›ãdzk×.Û±åË—` aO?ý4,_¾‹ÅÁ8**ŠÞ½{Û® åöíÛ¬]»ÖvlÙ²e<öØc´mÛÖ9_P6¶gÏ®]»F¡B…¨R¥J¦Þ[KÙ‰ˆˆHj<ª„ãwÞ!<<œ«W¯ðꫯ’'O:uêİaÃèܹ3sæÌ¡E‹tîÜÙ¶|Ý¢E‹ìî5eÊÚµkGÓ¦M©Zµ* , I“&v›7oÎ+¯¼B×®]éÖ­gÏžåçŸföìÙZÚ ¬åAAA¶ãÌ’#Gâãã5-"""ÉxT€¦D‰ÉŽ'ÝÞÙ××—¥K—Ú¶òæÓO?M¶ôsÏ=ÇÖ­[m[y3&ÙVÞ&“‰9sæØ¶ò®R¥ #GŽÔVÞNòçŸF€ÎlZÊNDDDRãQºk×®é:/GŽ„††šæyÕ«W§zõêižc2™èÑ£=zôHw?%süý÷ßÉþøÉ Ö‰„*á‘ûe»hñ ‰‰‰ìÙ³€5jdúý5-"""©Q€·tøðanݺEþüùñ÷÷Ïôûk3I´¸%kùF```¦O ­Â!"""©S€·d ÐŽ(ß@‹ˆˆHê Å-9:@«ZDDDR£-nI#Ð"""â* Ðâv®_¿ÎÑ£G1™LV ´ˆˆˆÜOZÜΞ={°X,”+WŽüùó;¤ @‹ˆˆHj ÅíìÞ½p\ùhZDDDR§-nÇ‘;Zi'BI´¸GO @‹ˆˆHê ÅíDDDŽ Ð‘Ô(@‹[9vì×®]#oÞ¼<ùä“kGZDDDR£-nÅZ¾Q½zu¼¼÷ã«I´¸•½{÷èÐv4-"""©Q€·räÈ*V¬èÐv4-"""©Q€·røðaÊ—/ïÐv4-"""©Q€·bväBдˆˆˆ¤NZÜF\\'Ož ­#ÐqqqmGDDDÜ´¸cÇŽ‘˜˜Èc=FÚ–F EDD$5 Ðâ6œU¾ª‘Ô)@‹Û°N T€WR€·avô  I´¸ gŽ@[tBB‚ÃÛ÷¢-nÃÑúÇ$11³Ù €···Ý97oÞtHÛ"""â> Åm=zp\€^¶luêÔá§Ÿ~ââÅ‹€ ¯]»Fxx8¯¾ú*ãÇwHÛ"""â>|\Ý‘ô8{ö,7oÞ$wîÜ”,YÒ!mtìØ‘¹sçÒ­[7۱ɓ'óŸÿü///¢¢¢Ò¶ˆˆˆ¸@‹[°N ô÷÷Çd29¤Ö­[“3gN»cI7Ryá…ð÷÷wHÛ"""â> %K:sæŒÝçÇ L™2k3_¾|4oÞ<Õ×è°¶EDDÄ}(@K–´~ýzZ´hÁž={ˆ‰‰ hÑ¢€1"=dÈ®^½š©ívìØ1Åã+V¤E‹™Ú–ˆˆˆ¸'hÉ’^xáÖ¯_O`` ¥K—æ‹/¾`ûöíÀéÓ§)X°`¦¶Û¾}{¼¼’ÿg1`À‡•Žˆˆˆˆ{Q€–,©`Á‚<÷Üsœ:uŠƒpèÐ!¶mÛ†ÙlvHIEñâÅ©_¿¾Ý±¼yóÒ»wïLoKDDDÜ“´dY©•S<õÔS<ûì³i7$$Äîó=zdúH·ˆˆˆ¸/hɲ:tèjÙ„#'ôÝÜ à°¶DDDÄý(@K–åççÇ3Ï<“ìx‘"EèÞ½»ÃÚ-_¾<…  B… :¬-q? Ð’¥¥TÆÑ¯_?rçÎíÐv­}ðàÁmGDDDÜ´di÷h///þùÏ:¼Ý×_R¥Jêð¶DDDĽ(@K–V¥Jž|òIÛçmÛ¶¥\¹ro·V­ZŒ?ív/"""ö %Ëkܸ±ícgîسgO§µ%"""îCZ²¼víÚÆäÁ´¶Úq½?-Y^‡xüñÇyÿý÷µ ˆˆˆ¸œF %Ë3™Lôèу^½z¹º+""""÷0vìX‡/]'"""’· ð,"""Y…´ˆˆˆˆÈCP€y Ð"""""AZDDDDä!hñHß~û-áááNm³U«VtìØÑ©mŠˆˆˆó)@‹S|ôÑG|úéT§µwóæ5JÆÝ&ÈË9o²ün2Q¨P!h‘l@Zœâúõë\¿þ8 #œÔâÛ4á6³ÒZ+__§´#"""®§-Nãåõ8ð’“ZûpÎIm‰ˆˆHv¢I„"""""AZDDDDä!(@‹ˆˆˆˆ<h‘‡ -""""ò EDDDD‚´ˆˆˆˆÈCÐ:дsçN,XÀ¹sç¨_¿>½zõÂÛÛÛÕÝÑt,[¶Œúõë³cÇräÈÁ°aÃèÔ© ®îšp“®îB¶£ï¹óé{î|úžËýß}Hv¢èG”˜˜È Aƒ á‡~ wïÞ±téR:uêä⊈ˆˆˆ#hú­_¿žcÇŽÑ¡CÛ±zõêQ¬X1f͚庎‰ˆˆˆˆC)@?¢ÈÈHíŽW­ZÕöšˆˆˆˆx•p<¢èèh|}}©T©’ÝñjÕª±sçÎdç¯^½š¦M›Ú+S¦ŒCû˜•üùçŸÄÇ*8©Å¶:±µ3ññœ^±‚³gÏ:©ÅÓ÷Üùô=w>}Ï%5ÎûÙ¸y÷ù" MnÍ*36Nœ°¯ìß¿?-[¶Ìð}=™´4hÐ ÙgvS»vmj×®íÄ8±­¬IßsçÓ÷Üùô=—Ô8ÿgÃsT©R… ô³žèGäççG\\¤Zµj¶ã{÷îÅÏÏÏîÜÐÐPBCCÝEqÕ@?¢€€"""ìŽïÛ·ÏöšˆˆˆˆxèGÔ¤IÊ•+Ç’%KlǶmÛFLL ½{÷v]ÇDDDDÄ¡L‹ÅâêN¸«eË–Ñ¥K7nLùòå™?>Ï>û, ,ÀÇGÕ1""""žH#ÐЮ];¶lÙB:uˆç?ÿù .̶áùرc˜L&¶nÝjwìÆ™ÚN«V­ìFùß~ûm‚‚‚ú>… bòäÉižsãÆ Ž?žêëçÎcäȑܺuËv,&&†)S¦Ð¯_?zöìÉ'Ÿ|±cÇ’]ÁèÑ£y饗èÖ­_~ùeŠçeÄúõë1™L¶YÚW®\Ád2±jÕª‡ºÏäÉ“yüñÇxÞÉ“'¹zõjª¯ÿý÷,]ºÔîØÊ•+9r$;wfÈ!„……¥zý;wøßÿþGhh(/¾ø"ï¿ÿ>'OžLÿ’Åmݺ“Édû98{ö,&“‰õë×;½/kÖ¬aúôévǶoßΘ1cèÚµ+dúôéܹs'Õ{üôÓO¼ùæ›tìØ‘·Þz‹]»v9ºÛv¦M›FýúõñòòbôèÑé¾®\¹rvçwìØ‘—^z)Ùy§NbäÈ‘ÄÅÅÙŽEGG3iÒ$úôé믾ʧŸ~šêÏèÁƒ2d;wæÿþïÿ´j*FŒA­Zµ¨U«O?ý4mÛ¶e„ ܼyóÁ§ÓùóçùòË/¹~ýz¦Ý3½nÝºÅØ±c©R¥ ¹sç¦H‘"<óÌ3|ôÑGÉÎÝ·o]ºt¡B… (P€àà`ƇÙl¶;oéÒ¥ÔªU‹èèhg}ÙŽt=ýôÓ|ôÑG|óÍ7ôíÛoooWw)Kñ÷÷O3e†*Uªì{‡……%[ª0©… ²dÉòäÉÀ/¿üB5˜>>–ýû÷ÛŽ'&&&»_ýúõ-… NñµG±nÝ: `9sæL†î3iÒ$K‰%x^‰%,ãÇOñµððp‹¯¯¯åÚµk‹ÅbùÿíÝiTWÚð?4û¦ ì (« !"â.‹¨‘m0—8†èãžDM4:'›1jpŒñDQFÍdQÁ h\pEd‘Mvž÷/5Ý*8bŸß9ý¡oߪºu«»ú©{oÝZ¹r%)))Ñ¡C‡DùRRRHKK‹fÏž-JŸ1c™™™QYY™(ýyÕÕÁ¹sçݺuëw-ǃH"‘ÐÕ«W‰ˆèûï¿'!ÊWXXHVVV4lØ0ÑqøòË/IQQ‘233Eù_ä±Úµk©©©Qkkk·—533£Õ«W ï}}})((H*Ÿ‹‹ ­Y³†ˆˆJKKICCƒ|||è·ß~ò444P`` ©ªªRqq±>xð`²··Ê×ÚÚJNNN4~üøn—÷¯.((ˆlllDi«V­"”œœ,¤577ÓÕ«W)33“ZZZd®«±±‘RRRèòåËTWWGDD---”””D(==ªªª¨¦¦F´ÜƒèܹsÂ2ÕÕÕÔØØ(|^SSCõõõDDtûöm:{ö¬hù’’:sæ •——‹Ò«««INNŽ-ZôÄ:¨®®&===òòò¢ææfÑg/^$‰DBk×®Ò"##ÿç’¿2 Y—„……Ñ_|!J»té988PEEIÐÎÎ΀LMMÉÁÁèÖ­[t÷î]rpp ³gÏÒôéÓÉØØXˆ?ýôSrtt$MMMÒÓÓ#QÐI$@ñÅ&ÊsðàA²··§^½zÑ„ (''‡(11QÈÓ@üñÇdeeE}úô¡Y³f 'ÈØØX255%999¡üu555¤¤¤$ìï¢E‹HQQ‘ ¥êïáǤ££CþþþO¬çÅ‹“žžÞ3ýéËÒ9€®­­êþÑý ¥¾}û’µµ5­]»–¾ùæòóóòtÐYYYäááAZZZdgg'ªÏ×^{ÈÐÐP¨¯ŒŒ áó÷ߟ&MšDDDeee¤¢¢BóæÍ“Yîµk×’¼¼¼pì‹‹‹I"‘ÐâÅ‹ŸK½tÅæÍ›ÉÏÏNŸ>McÆŒ!mmmòóó£ÒÒRºÿ>“®®.YZZR||¼Ôòß}÷999‘††ÙÙÙÑÖ­[¥ò|õÕWdmmMúúúF?ÿü³èO¯¢¢‚èÒ¥KD$ûøIÿvîÜI¯½ö?žÆGÚÚÚäããCEEETUUE¡¡¡¤¯¯Oýû÷§½{÷J•kÏž=dnnND퉉 ;Vf=ÅÅÅÚ¿?µ_0êêê’»»{j¹gÌ›7ŒŒŒH^^^tîéü½îààà@±±±Âû®Ð$‘H(--ˆˆæÎKjjjRxy555iÖ¬YDÔ~>““mƒ¨ýü€ŠŠŠžu×ÿ’dÐ'Ož$´{÷n""ŠŽŽ&RTT$‰DBúúútøðaÑ2aaa¤¡¡AÚÚÚ¤©©IŠŠŠ”M.\ %%%@JJJ¤¬¬LfffDÔ~Ñ·bÅ ’““#RPP o¿ý–zõêEß|ó°n333Z¶l¹¹¹‘¼¼<éèèQûE¦««+ 555@o¿ý6555Qee%)++ÓªU«žXŸ}ö ¬¬,™Ÿwì[Ç…Ð=‡p°.ÉË˺s;ÔÕÕ!==ÍÍÍ2—Y¿~= $$›6m¦M› §§‡¦¦&¤§§#44õõõضmÞzë-ÀƒðÁàäÉ“øê«¯PSSww÷'Žu»{÷®hìà™3g8p£FB@@ÒÓÓQ]]-ZvÆ ¸xñ">ÿüs,Z´†Z8;;#$$ BùçÏŸ/,›˜˜ >pöìYŒ1fffReÔÐÐÀäÉ“qöìY©ÏP^^Žü‘‘‘˜;w.äå{æ§ÙÚÚŠôôtÔÖÖ i8vìÞ{ï=lܸIIIøøã‘““#ZöáÇƘ1c°gÏôéÓG4¿ùG}MMMx{{ õõh]ÄÆÆÂ××påÊ444à7ÞYÎÐÐP´µµáÌ™ögz¥¥¥¡µµŽŽŽX°`ƇiÓ¦‰ÆÛ?o%%%8qâæÏŸÀÀ@lÚ´ ééé˜9s&^ýuèëëcçΰ´´ÄìÙ³AÜýÑGaÙ²e?~<âããáííåË—ã믿òlٲ˗/ÇØ±c±oß>´µµáÍ7ß•¡¹¹éééÂ}²Ž ý¸wïNŸ>ððpøúú"""999˜6mBBB ­­;v`ðàÁ˜3gŽÔo866>>>€¢¢"ܹsGæ` ý^ á»]\\Œû÷ïÃÍÍ +W®„««+ñÓO?u÷<³ððpøûûCUUUtî)++“ú^@zz:*++»µÃ‡ÃÄį¾ú*€öß¿››ôõõ¥òöéÓžžžBµµµ‰¾/åççw«,/£„„íÃóóó///äåå!''G8ïwÜÃrüøq8p/^Dee%jkk‘šš ===8;;ã—_~Ð>.½¡¡A¸!&&Ÿ|ò Ö­[‡²²2¤¤¤àûï¿—9VzÓ¦M>|8ŠŠŠPTT„¶¶6€ˆpñâEÔÖÖ">>û÷ïî¿ÑÖÖ†¿¿?6lØ€°°0ÄÇÇ˼$++ úúú¢çN<ÊÅÅuuuÏý>ö¿sÏþ$†Nï¾û®(­sëfW‡ptä{Zk,QQQI$úñÇ…´Î-Ðï¾û. >\xïíí-´tذaµ¶õêÕ‹,--Eù‚‚‚(00Pxÿ¤!!!!4gÎá}ïÞ½iæÌ™Ý—5kÖª®®¥^üñc—QUU „„"jïE@QQQÂ2MMM¤¡¡!jñÙ¸q£TýeffJµˆJJJPZZ*UÏÏ­ìššš¢õË*»±±1 D=#'Nœ€¢¢"Æà¿ÇêIßíÖÖV!Ÿœœœ°ý«W¯béÒ¥HNNÆ”)S°zõê'ÎÚñgÑÐЀ£G ¿999H$’'ÞÐÖÒÒyyy¡wiãÆ¸~ý: € ÀÂÂBè!SQQéùø“©««Cbb"’““ahhˆ-[¶ !!rrrÈÍÍÅðáᥥ%ä722ÂàÁƒ‘›› †ºº:Œáïï}ûö=¶÷ôQ7oÞ”ú?qqq‘ÙKØ9_ff&ðùçŸÃÏÏOx]ºt Â÷E[[Ë—/GAA.]º„ððpDFFbäÈ‘Bž¾}û>ñǎ¥åS÷‰=/ç|kì¹ ÿq q;;;Ñûšš8::¢oß¾˜]twñÓ<|øPÔ:ÊÌÛ9]EEE˜%âIN:…ææfQ`nee…›7o>v™›7oÂÈÈêêê¢t[[[ÀèÑ£¡ªªŠ5kÖ`õêÕ=6SÆ£jkk¡¬¬,5˃±±±Ô”Z½zõ’*»²²r—êëÑá@{]íw×›››Kå¿uëÚÚÚ„ºé8žð@ûôbß|ó ZZZzdúH]]]ÑÌ:ÊÊÊ 5¥ß£õPZZ ¸~ýºTv˜9s&Z[[…?ˆ>Ö??Y¿EÑö»Rv ýXyyy õÙq¬÷ÝnkkCqq1&Ož à¿ÇÊÏÏO”Ïßßñññ(((ÀÀ»µ=éYÎcLJ¢¢"\\\„4++«'vŸçççÃÂÂB¼¦L™‚ÔÔTDEE¡¤¤óçχ²²2Þyç©ïúõë÷Øé[[[eþþDè7èèhüýïÇ’%Kpþüy˜šš>v» Rÿ?2¿7‡ï444@"‘`êÔ©2gèê¸Øì ‘Hàää'''Œ38zô(¼¼¼`mm#GŽ ¶¶Vt¡Ð!;;ÆÆÆ2ϧ¬gp 4ë###©ù$»2þT^^mmm]ÚFRRŠ‹‹±oß>¬^½Z˜ëòþýûÝ*«¹¹9Ž;&J{Öyt%‰ÌeLL <==E-Eîîî¸pá®_¿.•¿²²ñññ¢–^YìííÑÚÚ*sœfO°²²Bcc£Ô±ì\]%«¾îß¿””Q@5tèPhii!22RæzvîÜ %%%!@4hH ¬©©’’ÒjúÈŽ 800‘‘‘R/áO®ó˜øŽVìÇÑÐЀ¦¦¦0¯w‡‹/>—²âããEÇÊÄÄVVVؽ{·Ìeöïßúúzá»mjj ™Ç €ÔEØ‹ddd„òòrÑ9éYê.&&“'OmîîîHNN–9o|II ¥~ÿöööøì³Ï°gÏ,Y²'Nœ€ŽŽŽpáȺÆÊÊ çÎÝ+S^^ŽŒŒ á€ÌîÝ»%%%hllæ:×ÐЩkkk=zT”–œœÜ¥ ¯¢±±ZZZ–z=é^—Ž‹¨Žÿ‚iÓ¦ˆ°qãF©¼uuuضm›¨·Œõ;Æì÷IDAT< Y—Lœ8ÉÉÉœ’µµµˆŠŠzêrÎÎÎ]uuuÑÖÖ&´´µµaÛ¶mÝ.khh(Ο?¸¸8nß¾o¿ý¶ÛëÚËßÔÔ$Õú'jQ€åË—CWW³fÍByy¹^WW‡Y³f¡­­ Ÿ|ò‰Þѵøh¾õë×CUU¯¼òÊ3•·»<<< ¯¯Í›7£¼¼­­­Øµk×3ßÄäìì,ÕrsssQƒŽŽV®\‰¨¨(ìÚµK”ÿСCˆˆˆÀ‚ пí½#GŽÄ?þ(üÁUTT ..cÇŽ•jÉù= >ýû÷GDD„ÔC„:Z§555áêêŠøøxá†ÀÊÊJ:tè‰ëVPP€»»»è'33.\x.eOMMEEE&Mš$¤ÉÉÉáË/¿Ä•+W°jÕ*Qà‘‘eË–ÁÃÃChVUUEXXbbb„óêëë±wï^ôïßÿ‰­}=mâĉ¨ªªÂ•+W´·\îܹ³[ëè¸Èèüû_±b…Ðcöèæ555˜1cTTT°zõj!½s vùòe=zK—.æ•g]3gδ´´`Ù²e©eË–A"‘`öìÙþÛ@Ó¡ªªJ4œhðàÁÐÕÕELLŒèØ,X°¥¥¥˜9s&®_¿Ž}ûöá“O>‚‚ÂSÏ;¾¾¾°µµÅÚµkqòäIá­´´ñññÚož3g®\¹"ü÷åççcÕªU——.º0wî\üóŸÿDDD„PÆ‚‚Lœ8zzzذaƒTîܹƒÂÂBÑ«óMÈìÙpͺÄÓÓuuu033ÃÔ©SabbGGǧ.7uêT|ýõ×pvv†§§'îܹóؼ...°µµ…««+ÂÃÃamm+W® _¿~Ý*ë¼yó0iÒ$øúúÂÚÚ3f <è*¼úê«6l<<<ðÞ{ï!-- wïÞ†ššš8|ø0*++1pà@x{{# VVVHKKÑ#GDÁƒ««+ Œ7:::HMMÅŽ;лwïn•óY©©©!22?ÿü3ÌÍÍaii‰µk×",,LèòýíoØ»w/áéé‰ëׯK ßè°hÑ",\¸o¾ù&1mÚ4Œ5 ¾¾¾˜5kÖ­['Ê¿eËܸq¦¦¦ D¿~ý ¢¢‚Í›7?óþ÷555üðøvíôôôˆ9sæ`äÈ‘¢Èõë×#??ýû÷ÇÌ™3Ñ¿©aM²øùù!** ŽŽŽ˜8q"&Nœˆ &<—²ÇÅÅÁÝÝššš¢t|ýõר°almmwwwŒ1öööRÓ~ø!ôôô`bb‚ÀÀ@˜šš";;;vìø]/vÌÍÍáàà€±cÇ"00æææÝ´÷¼UUUÁÓÓS”®­­Ã‡£¸¸666ðññ¿¿?¬­­qóæM$&&¢oß¾Bþo¿ývvvxýõ×áââ‚Ñ£G#((K–,y.ûú2±··Çwß}‡]»vÁÜÜ ÀÁƒ±{÷náAXqqq077G¿~ý`kk Œ5JxÀŽœœÞÿ}|÷ÝwPUU†:¹¹¹aûöíˆÇÀ±téR¬^½‰ä©½)ÊÊÊ8xð àææCCCÂÄÄD¸XVRR¹sç0tèP¨««ÃÀÀVVV8sæ þõ¯aÈ!Âú6oÞŒuëÖaÉ’%ÐÒÒ‚0,èÌ™3011‘*ÃØ±cѿѫ»L69ú_²²—Fff&öíÛðöö†™™âââ0cÆ ¨ªªâáÇˆŠŠB@@ôôô„å*++‘œœŒŠŠ C"‘ÈÌ´·ÊÄÇÇãܹspttD`` bccaaa!ÜuäȨªª 7H¥¤¤àÞ½{ð÷÷ÖÓÖÖ†óçÏ£¨¨C† Aqq1&Mš„‹/bذa€ÈÈH899ÁÞÞ^X.;;.\À¬Y³DåÊÎÎÆ¹sç ««‹+W®à—_~Á‰'dÖÓ¯¿þŠC‡!##---pttĤI“¤‚âììl$''#??DsssÁÈȨ[ÇåIJJJDǨ©© ;wîÄäÉ“EÁ|YYΞ=‹^½zaĈB‹FLL €öcŸ––†éÓ§‹Ö¿gÏ 2DT‡µµµ8yò$JJJàåå[[[$$$ˆÆ‹>*55gΜƈŽ=Z8Fݾ}±±±ÈÍÍ………¦OŸÞcOé»pá $¤•——#::ÁÁÁ¢ã¹gϼòÊ+”f@û÷~ÿþý¸~ý:$ ¬­­áíí-ú“+((ÀþýûQSSƒ &ÀÞÞÑÑÑ …¦¦&êëë±{÷nøøøˆ¾qqqHNN†‰‰ ¦NŠ»wïŠ~W¯^ŵk×*,óàÁüç?ÿÁÔ©SѧO!=** 666prr‚ƒƒæÍ›‡ððp™uróæM;v ×®]ƒ¡¡!œœœàáá!3(®©©Att4ÒÒÒ`dd„ààà:>333—/_–ú-WUUaûöí(--ŨQ£ˆíÛ·ÃÍÍM¶­8zô($ <<<ðþûï#==]˜F­³šš9r " :žžžR%wïÞEtt4rss¡¯¯1cÆÈ¼y—µkjj’º·¥³ÊÊJ\½z‰¯¾ú*zõê%ú¼¨¨¹¹¹——ǨQ£ÛÒïÞ=455Iõ–TVVBGG‡”)SpêÔ)Œ;@{K¯†††ÌÆ"B^^òòò ¦¦†JçKKK‘——‡ªª*èëëÃÎÎNê;Ó¡¾¾×®]í[·°bÅ hiiáäÉ“¢üuuu¨¨¨¹¼ŽŽŽÌqÔ¬{8€fI555Âɳ¾¾³gÏFRR ÿç1˜Ó§O‡›››ÔóŸÕ£u´ÏÑìää„M›6aÁ‚ÿÓºSRR°lÙ2œ:uê5N™I{ðà<<}+W®Dyy9²²²¤n~Ñ 1zôhØÚÚâÈ‘#ÏÔsÈž Ðì/I]]...PWWGbb"ZZZ°sçÎÇ>¸ãe¶fÍ8pÈÊÊBFFÆŽ‹¤¤$>3Æ^zæææ(++ƒššªªª`kk‹½{÷Šzœ~O hhh€††FÌFÄdãšý%?—/_FEELLLàîîÎSC=FEEŽ?Ž›7oB"‘`РA˜û |ðèá3³gÏFcc#~øá‡¾ÏŒ±ç‡hÆc/ððp!11QH»zõ*œœœ0þ||õÕW¢iëêê7Þx?ýô“0G½h½ÍÍÍÂÔf²èôôt :999¢ÇL3Æþ\xcŒ±—JSSvíÚ%5­åŠ+```€O?ýTj !Ç# ­­­¥Öý´y`mm;vü/»ÀûqÍcì¥rãÆ 455Iµ§¥¥ÁÍÍ ªªªO\ÞÏÏZZZxã7°}ûv”””tkû¶¶¶ÈÈÈèv¹c@3Æ{©äää,--…´_ýeee]zܸššbcca``€·ß~ÆÆÆ3f âââЕQ‘ÖÖÖ¸víÚ3—Ÿ1öûãš1ÆØKåÖ­[PVV†žžž¦¢¢EEEÔÕÕui®®®HHH@^^¶nÝŠúúzøúúbñâÅO]ÖÌÌ ÅÅÅhnn~æ}`Œý¾8€fŒ1öR133Ccc#ªªª„4‰D‚àÆÝZ—¥¥%æÎ‹ÔÔTá‡~xj`\RRCCÃ§Ž—fŒýqqÍcì¥bggÈÏÏ¥ûùù!11ñ±ó4·¶¶>q½#FŒ@EE ž˜/77W(cìωhÆc/+++())IÐ~ø!Œáç燴´4!½µµû÷ïÇ›o¾ HHHÀ¿ÿýo455 yÊÊʰuëVhkkÃÂÂâ‰ÛÏËËãš±?9 cŒ½T‚ƒŠÒÕÕÕqöìY˜™™ÁÑÑ&&&:t(´´´ðÖ[oÁÙÙpÿþ}̘1½{÷†££# CCCTVVbïÞ½PPPxì¶oÞ¼‰¬¬,̘1£G÷‘1Ö³øA*Œ1Æ^:¹¹¹°··G~~>úõë'õyVV®]»†û÷ïÃÜÜ£G†¶¶¶ðymm-RRRPXXˆÖÖV˜˜˜ÀÃÃêêêBžââbÀÕÕUH[´h Û£ûÇëY@3Æ{)M:æææX¿~ý Ù^]]LLL””„aƽm2ÆzÐŒ1Æ^J÷îÝÃíÛ·áääôB¶W]]œœŒ9ò…l1Ös8€fŒ1Æc¬ø&BÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺhÆcŒ1ƺáÿóCi¯Ïª|IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/create-index-time-int32-float64.svg000066400000000000000000002504541416254111300272040ustar00rootroot00000000000000 image/svg+xml 2.1xfaster 1.5xfaster 25x faster 21x faster PyTables-3.7.0/doc/source/usersguide/images/filesizes-chunksize-15GB.png000066400000000000000000001602771416254111300261100ustar00rootroot00000000000000‰PNG  IHDRÐß}™SsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝyXUUÛ?ðïaFqBqB™DKÅ10sb EL…Ì,‡4¥i¥ùV¯–¥¥½•å@f¦>išòˆSh*ˆ2)àˆŠ2Ãþýqbëñ{o…s8ðý\×¾€µ×^gÀÅíÚë^[%‚"""""’ÅHß """"2$  ‰ˆˆˆˆ`MDDDD¤h"""""@)ÀšˆˆˆˆHÐDDDDD 0€&""""R€4‘  ‰ˆˆˆˆ`MDDDD¤h"""""@)ÀšˆˆˆˆHÐDDDDD 0€&""""R€4‘  ‰ˆˆˆˆ`MDDDD¤h"""""@)ÀšˆˆˆˆHÐDDDDD 0€&""""R€4‘  ‰ˆˆˆˆ`MDDDD¤h"""""@)ÀšˆˆˆˆHÐDDDDD 0€&""""R€4Q-)..Æ™3g““Sm™.ÅÇÇ#==]/¯MDT_¨AôÝ ""Cs÷î]Ì™3§Êó&L€““:wîŒÅ‹cÉ’%€´´4­2]jÞ¼9†ŽÍ›7ëüµ‰ˆê }w€ˆÈ`ãÆhÔ¨Zµj¥u~РApqq½½=š7o®‡V®cÇŽ•ö—ˆˆäcMDôüýý«ÍMKKÓ]gd8{ö¬¾»@Ddð@Õ’ ** nnnpss“¬_XXˆ={öàÌ™3P©TèÕ«FŒ•J%ëõÊÊÊpüøqìÛ·EEEèØ±# WWW±Î¯¿þ ;;;ôíÛ[íšèqãÆÁÈèaºLjj*<ˆóçÏÃÞÞ/¼ðºuë&«DDõh"¢ZrãÆ „„„`ñâÅ’ô™3g0aœ?Í›7Gyy9îÝ»‡ÁƒcëÖ­°¶¶®öú ((wîܽ½=7nŒÌÌLáÎ;b½)S¦`øðáb½nÝ:lÛ¶M«½û÷¬ °°°¬Zµ ,@aa!:vìˆÌÌLã“O>Á¼yó”~{ˆˆ wá "z ׯ_Ç¡C‡4ŽcÇŽ)j£  ¸ÿ>~ùåäääàêÕ«X³f :„… J¶ñî»ïÂÈÈYYYHKKCbb"nß¾Ÿþ¹Úë¾ÿþ{ܹsGãØ²e T*üýýannøÏþƒ·Þz ¾¾¾8}ú4ÒÓÓqþüyŒ5 ï¾û.Ž=ªè=2ÐDDOáàÁƒðññÑ8Ư¨¯¿þéééxûí·ñÒK/ÁÜÜVVV˜1c|||°nÝ:Ü¿¿Ú6nݺOOOØÙÙ‰e&&&:t¨¢¾ÄÇÇcìØ±ððð@dd¤¸|dÁ‚077ÇÚµkáéé èÚµ+V®\ @=;MDÔPp ÑS2dþçþG£¬bÖV®'NÀÈÈvvvøÏþA ÊËËÑ©S'”——ãÒ¥KèÞ½{•mŒ7Ë—/G@@^~ùe :-Z´PÔììløúú¢Y³fˆŠŠBãÆùùùHHH€——þúë/±oýìÔ©’““½‘!cMDôZ¶l //¯§j#%%‚ `òäÉ•ž777Çõë׫mcáÂ…°²²Âš5k Œ?sæÌgŒ«“ŸŸܹs111hÛ¶­xîòåË'OžÄèÑ£+½¾¼¼\ò5ˆˆê .á "Ò3[[[XYY!??………•/¾øbµm4oÞï¿ÿ>222°ÿ~Lž<Û·o‡··7 ª½¶¼¼&LÀ™3g°eËxxxhõ^ýõ*ûW×¶ë#"ªM  ‰ˆôÌÝÝyyyˆ‰‰yê¶ÌÌÌð /à›o¾ÁêÕ«q÷î]ìܹ³ÚkÞ~ûmìܹ«W¯Æˆ#´Î·jÕ ¶¶¶øý÷ßQZZúÔ}$"2t  ‰ˆôlΜ9hÞ¼9^{í5\¼xQã\AA¶oß^íõ………رc‡Vp{áÂ×2WfÍš5øâ‹/0wî\̘1£ÊzK–,Arr2fÏž¢¢"siiiˆ­¶DDõ ×@é™~úé'„……ÁÝݽ{÷FÛ¶m‘••…³gÏ¢ʵǀ:€=z4lllУG´nÝýõ’’’àããƒáÇWyí§Ÿ~ ˆ‰‰©t-÷áÇaff†×_ X»v-vî܉ž={ÂÄÄiii8{ö,æÍ›'î-MDTß/Y²d‰¾;ADdˆÌÌÌàíí —*ëXXXÀÛÛ:uª¶¬K—.˜2e ¬­­QZZŠ[·n¡C‡ƧŸ~ZíŽfff0`¬­­‘ŸŸ`Ô¨QX¶læÏŸ͹’hôÙËË ]ºt£££ÖñüóÏÃÈÈ*• #GŽÄСCѨQ#ܾ}‚ ÀÍÍ óçÏÇ«¯¾ 333åßD""¤AÐw'ä¸|ù2N:…ÔÔT¸¹¹Á××·Òz øí·ß••___ ¾þúk\¿~&L륤¤ÀÝÝ]ãÚŠdšŠGÑV|tuuÕ¨çîî®ñ¸Ú¤¤$­ä¡Š¯SRRjèQ]R¯¶±;v삃ƒÑ©S'˜™™ÁÂÂ'Nœg›AÀõë׫  ³²²4>>»»»ã×_Eii)LLL••:ööö°²²Âµk×IJÐÐPœ8qvvvu;vìXˆˆèÉeddh|Þ½{cãÆzêQÝWof ËÊÊðꫯâï¿ÿÆ’%Kðí·ßÂËË cÇŽÅ™3gôÚ·ãÇ###C\fR!##Cëú,»té’Æ?¢ÚxÇ?u{â¡äÚGŸò&umEÝêê%%%áÆUÖ;uê”Öã+êݾ}çΫôÚƒÖ¹ßÚúYVWvöìYñA OÓžœŸåãeJþ-äääˆwœžæ÷oïÞ½Š¯½té’Ø×ºô»ñxY\\œF2·Ükݯ:cìïÃ19f4^‰ B»;mª¼¶ºïŸÜ²ŠqEéµJþ-ȇű¥²z©©©8sæL¥×æääˆcËã×>>‚»»»â÷ªkÛ·o¶oß^«¯QÙÏN©'í§’×–S÷믿bcc«<)ìÞ½»Òs§OŸ>ÿüóJÏ988ÈꣾÕÄϲ:ÿM>'é§’ß1¹ý”êGuç«:·`ÁaÁ‚’¯­oO<¶ì.„×î=<Jª¬Ê±Ec‹4Ž-ÒçÜÝÝÉ×nÈêÍŽ#GŽÀÈÈýúõËT*ú÷ï?ÿüS,sttD||¼Æµÿ[wrrÒø˜˜˜¨‘ /ž€®]»bÏž=•¶åèè¨QnccóÄïMWœký5Ÿº'í§’×–S÷¹çžC›6mª<ïææ ‹JϵiÓ¦Ò§¾ê‡\‚šøYV§S§NhÞ¼ùS·ó$ýTò;&·ŸRý¨î|Uçúôé#ùºuÇeu9¶plô;¶BÌ¢wúŽà•ªj:""B lܸQ,»ÿ¾Ð¬Y3aèСb™>¶±óññáÿäH¶Úž}¡úCwôJÁ 4IãØBr1n‘f3Ðqqq˜1c ==·nÝÿ‡}øða˜™™aìØ±øòË/1eÊ„‡‡£C‡عs'Š‹‹ññÇ‹m#""C‡EPP¸}ÝöíÛ5^sÕªUð÷÷ÇàÁƒáââ‚mÛ¶ÁÛÛ[cG!C† $$FVVöîÝ‹ˆˆ˜ššêà;CDDDDºfòÎÈÈ@DDD¥çæÍ›ccc@ii)ÂÃÃqâÄ Ü¿ŽŽŽxõÕWѾ}{ktý(ï°°0!22òi¾ Ô@\¸pA'·¼Éðݼy@=¾Ýº§øõ‘ħ9–€«AÌûÔI[H®˜››k<û‚4Dmè‘‘?þøCß]!ÆA‹dÙ±c€Ú_3ª7  kÇ’kðàÁèØ±#_ªÁ‘HG¸ç3Éŋ䪷3Õ Ž-$ciõfh"""""]`MDDDD¤hyü @DUyüé{DU¹yó¦˜HH$…c ÉŘEh¹víš¾»@bÙ²eú˜Ç U‡c ÉŘE“u„ òI.&ú\L"$%8¶\ŒY¤qšˆˆˆˆHÐDDDDD 0€Ö.È'¹˜èCr1‰”àØBr1f‘ÆZG¸ Ÿäb¢ÉÅ$BR‚c ÉŘE“u„ òI.&ú\L"$%8¶\ŒY¤qšˆˆˆˆHÐDDDDD 0€Ö.È'¹˜èCr1‰”àØBr1f‘ÆZG¸ Ÿäb¢ÉÅ$BR‚c ÉŘE“u„ òI.&ú\L"$%8¶\ŒY¤qšˆˆˆˆHÐDDDDD 0€Ö.È'¹˜èCr1‰”àØBr1f‘ÆZG¸ Ÿäb¢ÉÅ$BR‚c ÉŘE“u„ òI.&ú\L"$%8¶\ŒY¤qšˆˆˆˆHÐDDDDD 0€Ö.È'¹˜èCr1‰”àØBr1f‘ÆZG¸ Ÿäb¢ÉÅ$BR‚c ÉŘE“u„ òI.&ú\L"$%8¶\ŒY¤qšˆˆˆˆHÐDDDDD 0€Ö.È'¹˜èCr1‰”àØBr1f‘ÆZG¸ Ÿäb¢ÉÅ$BR‚c ÉŘE“u„ òI.&ú\L"$%8¶\ŒY¤qšˆˆˆˆHÐDDDDD 0€Ö.È'¹˜èCr1‰”àØBr1f‘ÆZG¸ Ÿäb¢ÉÅ$BR‚c ÉŘEšÁ$^¾|§NBjj*ÜÜÜàëë[i½²²2üüóÏ8zô(îÝ»WWW„††ÂÖÖV¬SRR‚7"66mÛ¶EPPºwï®ÕVBB¶nÝŠÌÌLôéÓaaa077רS^^ŽððpÄÄÄÀÆÆ£GFïÞ½µÚâ‚|’‹‰>$“õëðáø}û¶d='''¸¸¸è G•;~ü8rss1~üxìÙ³§ÊzèÒ¥‹{¦©¢ŸRôÝOCù¹? Æ,Ò "€^±bæÏŸSSS‚€—_~¹ÒúöíÛ;v,NŸ>Áƒ£I“&ظq#œœœÄ?4ÅÅÅ5jþúë/!66Ÿ}ö¶oߎaƉm[`CåÈËŽ«òt­{ØÏêåå±±Úå‚ðäeJêTß¿úàÚµk\-E00]»vBCCµÊÿýï *•J°··zõê%¼÷Þ{‚½½½`ff&9rDA(//LLL„… j\{÷î]€ðñÇ ‚ Ÿ}ö™@¸~ýºF½?üP022JJJA„V­Z S¦LѨS^^.XYY o¿ý¶X*î°Q¶k=zŒ×¸~ûöíÂ7XÆ2–±Œe»‹„Ôã…‚I×áµ{‚PR§úçàúÏX~CP‡ZÇãerëÕVÙø®W[e¡5\¯¶Ê|dÕsvö©¿§rʶoß.lß¾]X°`°qãFÁÇǧÒX‹2ˆ%r”––BxzzâĉøøãqéÒ%´lÙï½÷ž¾»W¥ü|€wJˆˆ ý{À¶mÀÔ©@zº¾{Cuÿ¶×o*A¨êfFÝäìì ///­[Ü6lÀ+¯¼‚ððpLœ8Q,Ÿ6m"""——•J…víÚ¡gÏžˆŠŠë;v }ûöÅúõë†Í›7#$$D#9ÆŽ‹#GŽ ''àéé KKKÄ>r/)-- ;wÆòåË1oÞ<@XX6n*¿uSÓ pszöž}V}xxÿlöADÔ0í)~}$ ™c ¸ê~åáß»w«£GÒÒŠ3a¨nID7·0¬^­]OWK­ýúsq ÃgŸI׫-S¦Èë§«kV®Ô®÷4Kb”,“™81 YYÚ¯¯- nÀ¬YÀ˜1€‰-š ÀmU«c@?ÎêuíÚPðØâ¤‚‚˜˜˜ˆÉ€ŽŽŽˆ×¨sîÜ9ê=ý˜˜˜¨@ÇÇÇ‹ç*^óñ=5+ÚÒÞš®êÿŠ–”§O«ï¿W—™˜..ê`º"°îÞhÔ¨Êf¨ž¸páœõÝ 2 „666zîIýrç°¿:`Þ³ÈÊÒ®S1ççK·×¬àãS³}Tâáß ª[Z´†×E*'÷ï[óæÀСµÛ—êXZʯ{äˆúhÛxã `Ú4à‘ÇRÔYEEEÕnÜ@² ‡}ûö…««+¶lÙ‚²²2Àýû÷±{÷nôïß_¬7uêT¤§§ãÌ™3bYtt4œœœÐ¯_?@Ïž=áîîŽèèhTLП;w)))âÿÊ*ÚºwïþüóO±,** mÚ´ŸŸßc=¬<‰ð™g€·Þ¬¬–—–gÏë×o¾ ôë4m ¸¹¡¡ÀêÕ@L ðàÁ“|·¨.ãÓÂH.>‰°f‚zã“O€Aƒ€V­€±c~Ð ž¹s}û€Û·;;ýõùÉplÑ¥víkkõçYYÀ;/¿¬ßdM9ø$Bi1‡3fPoWwëÖ-xyyP?ÈÌÌ °fÍÀÑуƶmÛ`jjŠÏ>ûLl+88:t(‚‚‚Äíë¶oß®ñš«V­‚¿¿? lÛ¶ ÞÞÞ˜0a‚XgÈ! A`` ‚ƒƒ‘••…½{÷"""Bcë<µÊ³Y[¶>ÿ\ýyy9pñ"ð×_ê#.N}Ü»§>_V$&ªM›ÔeFF@×®šË?zôš4‘÷½}öÙQ¸{·…d=GGcìÙó½¼Fé©ð–ÉÅÝ7žÜ;ê@¸b–ùŸ•y7FŒP:iž/(È ½×oYY‰dÚTXxÀãÿùX¹òòÒ*ÏéÂÃ~VOßý”ûsïСüDF_}¥þOZq1¡>ž}V=A\÷–lri±:##•ž›7oŒÅ¯“’’ðóÏ?ãâÅ‹èÖ­^{í5­Û›º~”·z t€Z¯Ñ£Çÿ!.ns•ï]€”u ýh`}çN•—@¥ºtÑ\þѳ§ú6âã\\Âpþü†ªûG¯^a8qBºQ©Á5гÌk™SOJ<®[·‡óÀ@uw± å‘ÎÿùÏ>ܺ%ÝOWWgôìé)Y¯¶J?ŸôçþßÿªémÛÔK7+ØØ¨“R§OWÏP×\-Í hC†¤¤Lš4U뜋Kx{÷¯äªê]¾ü0˜®øxëVÕõU*õr‘ŠYêž=ÕÇ€  ‰ŠŸ~ÚŽ«W¥oÍöèá‚_¤ƒU®"j{¶%:žj%–_š‰;í®[“ ¤rs5g™+»+me¥9Ëlo_£o…HKv6ðÍ7êãÑ;ÆÆ@@0k–úwRŸ@Kc­aaa(**Bddd­¾Nzºæ,õ_7nT©iJJ6H¶ÍZw˜DHr)M"ìÜy4ÒÒÞ•¬×«×—8qâ§§êÛÓhß~$®^áhŠ1x¸Äl®#ÅûöýGþ(~-걯b–ùøñÊg™]\4g™ÿYXïql©[JJ€_~QÏJÿó d‘«+0s&0i’f~”®„„„ÀÜÜœt5 b t} ‹ùööêc̘‡e™™ÚË?ýo‰Ì¥y7oªgrììÔGË–UoûS† {×®Uò—ð1îî¶ÿ·zT{ø$B’Ké“ÍÌšð’¬glüOÓ­§faÑÀpÅÐÜÁ¨ ý³ed´·okÎ2_¿®Ýž•ð ƒæºr›\×8¶Ô-¦¦@Hˆúˆ‹SÒ‘‘ê'2&&3f aaê`ºKÝõO"”ÆZGôõ‹Ø¡ƒú5êaYVÖÃ`úÓOû÷¥ÛIM† {øµ™™z+ž¶mÕ•­[«oK=­ÌÌ2YKMÌÌžþÅôŒà ÏâÅ«‘šZÉ^g4èYL:¶Æ^·¡'ž=«Þ1£¼\ûœ«ëÀyÀ€†3Ë\Ž-uWÏžê]_V¬Ö­¾þZ}Wùî]`Õ*õÎ[Æ©—wŒ¡Þ@ 61x–ƺjÛV}øû[¶çÏ+o£¸X=»™Y}=##u]U€]€·iS?þÀ½ôÒ›ÈÊÊ“¬×¿¿+>ýtžzT¹üü|KÖ³´´Ôë^ §N—)T§sçÎâ^ðúq ))+%ë%'ϯÑZ)¹ önݶnUþ覊Ï++{Úó–ÉÙ[òù§Ö¤‰æ,s‡òÚ ªKZ¶Þ}xç :øòKàÀõ¿Ý={Ô‡ƒƒzvzÊõžØ¤  I`íZuòCv¶z»âóŠãÖ-í?Ðååê%#99ê øê´lYu -÷ª¾%$ä!9yƒŒšaµÜ“êyxŒDa¡t»ƒÃ üù§þÖ¾ôÒ"dg‡IÖóô\Œ'ªÞͦ¶™h#YO¥ª™i£¼<õ>Ĺ¹ê~^ÝÇŠ-1¥\¼ŒW#]­U©omWÌ2kíJd ŒÕwŽGROr}õ•zûÚû÷K—€·ßÞ˜8Q=+íî®ï7<  u¤¨¨ê'ss ¿Äf!%%ê@¹ª»â¸~½òÄž[·ÔGB“÷óêUà³ÏÔ³QVVêeVVº]í„.}Œ;áêÕ/$ëuèV«ýbaÑ%%ã%ëKï[×Ë |/ËÍ}ôQÒ³ó óI„=z˗뻆ƒI„†©[7`ÍàÿظQýyR’zbéÛoÕÇ Aê=¥>2|þü#''W²ý# $Dóáo|¡4Ð:ÒžêcjúpÍuuÊÊÔAteÁõ£wNŽü$Ç YYê[_RT*õC* ´« ¼«*«É€œ‰>uGq±:¡§°(*Òþ¼â£œ<@½f·Cu0\3wU*žBøp-´¹¹ú‘ÌÖÖÚÃëßi IDATB÷îê=šw•*û\n™’ócÆW®H÷“”áØbØš6Uɳf¿ÿ®ž•ÞµK}§÷ðaõÑ®z?é×^¶l9Œ 鉒ôôZ4“¥1€Ö‘ºú‹øÜs¶°¶“¬çååZc¯ilüpiFuAýÇ>;ðó22j¬ uÐ#7ð‘RÊ«Ÿ–¦~$°‰‰öÑ­Û|öYåçjê¨ì@eAýžA}”—ëös¹7nnÜPÏÂHºr>ôcMoò™Ÿ//p62R?øèñX;(ÔªÓ¨QÕíþç?òhKKõVoú‰¯ÚÁà¹~P©€¡CÕGjªzyå÷ß«ïN]½ ,Z,] ˜™Y@ÎÒ²ÊÔÕ˜¥.aÝÀmÚTw·|S©ÔOh²±Q§rôêì߯Œóò4ÇËäÔÉÏ—DUäråä_HO èÝñãê`ª®»t xýu}÷BšµµzMcõA±:x®í,{"2|;«wîXºøé'õ¬ôß«ïžÉȧ§Àšê¦MÕGM(/<tÿ½zÛ!)&&êÿ”–><äÎ “<&&€……z6ÓÂBóóÊÊžæüرê-§¤89©·ªÒ—¢¢+hÚTzgAy+¥–^…©éf˜-aTþðI„¦F™0U=|ay¹Â5^Dõ”¥¥úqàS§Gލé­[kþ.=ÄZG =‰°¡22z¸ÎYŽèhyôsϱ±Ú奥@bâ888k×5},^\ù'סƒz—•J}éöó3ÔëÚ¥ôè¡^ øh€[ûË¥¯%JŸD¸wïäæJ'µoï'Y§6mÛörssasª1lÿûð›Û5°)îÛ?ÜjÃÁa‰zg¸˜DØ0 ¨>bc¥·š­ “¥1€Ö‘†DX›:t0–õ”®]mk¿3µÈÄøüóÚOôùüsyt»vê=Iõeþ|yõÌÍ¥×Ô×GJŸD¨Ï½²•èÓ§Ï?Ÿ': è¸òÏÖ“baÃò4“L"”Æ‘HGø‹øtöîý^ß]Ðþ3<¥¥ihÛvd=SÓš]”ØÐŸDHÊpl!¹³HcMTƒ¬¬ráä&Y¯m[™kBj‰ ”¸#§fmw¥Z……7Hïñ\V¦ßl™¸¸(((¬×´¦ç‘^1€&ªAýµSß]%0З//‘¬çí=´ö;Sï¾›‹k×nHÖëÖMÆæßµ¨Y³fhÖ¬™^û@DDºÃZG˜DHré"Ñgùr™‹‹õlèÐôÝ…:Mi!5lL"lXJJ.ÈZZfi©½g&“¥1€Ö&’\Lô!¹”&RÃÆ±¥aIH؃BO÷ªìî“¥1€Öþ"’\üGr1p&%8¶4,Í›7âk³Hã³®ˆˆˆˆˆ`MDDDD¤ha!ÉuáÂ}w ÄÍ›7ÅDB")[H.Æ,Ò@ë“I®eË–é» d bbbÄDB")[H.Æ,Ò˜D¨#\Or1чäb!)Á±…äbÌ"3ÐDDDDD 0€&""""R€´ŽpA>ÉÅD’‹I„¤Ç’‹1‹4Ð:Âù$}H.&’[H.Æ,Ò˜D¨#\Or1чäb!)Á±…äbÌ"3ÐDDDDD 0€&""""R€´ŽpA>ÉÅD’‹I„¤Ç’‹1‹4Ð:Âù$}H.&’[H.Æ,Ò˜D¨#\Or1чäb!)Á±…äbÌ"3ÐDDDDD 0€&""""RÀ`–p\¾|§NBjj*ÜÜÜàëë[mýS§NaÿþýèÕ«^xás%%%ظq#bccѶm[¡{÷îZm$$$`ëÖ­ÈÌÌDŸ>}sss:åååGLL lll0zôhôîÝ[«-.È'¹.\¸ggg}wƒ @E¡ž{B†€c ÉUTT¤ï&ƒ˜^±b0qâD,Z´[·n­¶þƒ0vìXüë_ÿBTT”ƹââbà½÷Þƒ¹¹9bccÑ·o_ìÝ»W£ÞÁƒáåå…ÇÃÒÒ‹/†¯¯/ Å:‚ `âĉxë­·`llŒÄÄD 8Û¶mÓêä“\Lô!¹˜DHJpl!¹³H3ˆè‘#GÂÛÛ•Î?nÁ‚èÓ§ ´ÎmÙ²{öìÁéÓ§áéé 3f fÏž¤¤$±Þœ9s0hÐ ìÚµ *• 3g΄‡‡"""0eÊÀþýû‰C‡áùçŸLŸ>³fÍB@@LMMÅö¸ Ÿäb¢ÉÅ$BR‚c ÉŘEšAÌ@»ºº¢W¯^²n'9r‘‘‘X½zu¥ç×­[{{{1x???$''ãèÑ£€¸¸8ÄÇÇÃÏÏ*• àââGGGhݺuhÚ´©<€¿¿?rrrý$o•ˆˆˆˆê8ƒ å*((À«¯¾ŠåË—£uëÖ•ÖIII»»»F™‹‹ 99Y㣫««F=wwwñ$%%‰×>ÞVJJÊS¼""""ª«êUýþûï£}ûöâ‹Ç ‚€ëׯW@geei||<8vwwÇ7PZZ*Ö{¼Ž½½=¬¬¬´Ö8q›6mÂŽ;4ŽÇŸ"Æ2–]¸p¡Îô…eu»¬âI„u¡/µU–––®‘{R×úgHeO"¬ }aYÝ*«ˆI.\ˆM›6áÒ¥K êÕ›úøñãø¿ÿû?|óÍ7úîJ¥òóóõÝ2Lô!¹˜DHJpl!¹îÞ½«ï.Ôy*A}wB gggxyyi%Côïß7Ö˜}~ã7Я_?Lž<þþþhܸ1Úµk‡ž={jìÎqìØ1ôíÛëׯGXX6oÞŒä@;v,Ž9‚œœ€§§',--+ÖIKKCçα|ùrÌ›7€ DDŠì)~}d Ð9–€«Aä¾4Æ-ÒêÍHdaaääd,X°@,ËËËCLL Î;‡ qãÆpttD||¼ÆµçÎ899i|LLLÔ ãããÅsеkWìÙ³§Ò¶kðÝ‘”Û·o###]ºtAãÆõݪÇêÍŽ --MãhÕª¦L™‚´´4´oß0uêT¤§§ãÌ™3âµÑÑÑprrB¿~ý={ö„»»;¢££Q1AîÜ9¤¤¤ˆÿ+«hëÞ½{øóÏ?Ų¨¨(´iÓ~~~:x×DDÔDEE!,,¬Æ–a”––"--Í oÕß½{iiib>Ò£vìØ=z ..N=£†Ä 踸8xyyÁËË éé騵k—øuqq±¢¶‚ƒƒ1|øp :3fÌÀ!C°gÏ­mïV­Z…ÇcðàÁ˜9s& oooL˜0A¬3dÈ„„„ 00o¼ñðÃ?૯¾ÒØà“I¾ŠD")I„Tÿ-]º7nÄ|€7nRÃÆZG¸ Ÿäb!ÉÅ$Âúï‡~€……ÆŽ èÔ© „ÈÈH­'4JÉÍÍ•UoÆ ¸zõ*>úè#ôèÑ`llŒåË—£[·nø÷¿ÿ’’kºuë†O>ùÆÆÆ033ìY³`ii‰cÇŽá믿wÂzî¹ç0|øp\¹r©©©âõžžžç΃ OõhéÓ§O£wïÞ°··â6Æ,Ò^6†žtìØQß] Á¤ ’+00Pß] Z´qãF”••¡U«Vؼy³X^VV@=;"»½¡C‡ÊªwåÊ¡uëÖZçìììééékÑ¢…VÝÆ£Y³fuE9Yì]»v!00fffpvvF·nÝ`ccƒÒÒRœ;w÷îÝ“Õ÷ÇåççãæÍ›ü¬¿_Ò@ÕA뜿üòK|ùå—ZçÿøãdddˆÁޱ±1JJJ ‚FКŸŸ¯èu[·nòòräææÂÚÚZã\Å’¡ŠÝ/jÊÌ™3annŽÔÔTÝ3>úè#8pà‰ÛmÔ¨š6mÊUªq\ÂADDTÇ>|)))˜6mRSSµŽ-[¶ ¼¼\㎕òóó‘ ÑÖéÓ§µÚ¯ØîÎ;ZçÅ><îÈ‘#011ëÔ„‚‚¤§§#((Hkë¹Ê–(U×÷ʸ¸¸àäÉ“²ëÉÁZG¸ Ÿäb!ÉÅ$Âúë‡~L›6 :uÒ:‚‚‚`gg‡ 6@øçq áááâÙÙÙX¸p!Í$B[[[XXXàÈ‘#Z3Ô¯¼ò š6mŠ÷ß_cæ6<<GŽÁo¼¡±7óÓ²´´D‹-°ÿ~§ ÿøãØ»w¯VýŠDÀ}ûö‰ï½:|ðîÞ½‹éÓ§ãÁƒbyyy9Ο?ÿôo bÌ"´ŽðöÉÅ$B’‹I„õS^^¶nÝ ggg­¼ FFFFjj*:˜8q"ºuë†+V S§NxñÅÑ­[7Œ7€f¡‘‘¦OŸŽ“'O¢E‹èÚµ+üüü­ZµÂ·ß~‹Ë—/ÃÅÅðòòÂäɓѻwo,Y²¤ÆßóüùóqõêUØÚÚ"((ýúõÃÛo¿7ß|S«îˆ#йsgüë_ÿ‚ œñõ×_WÙöˆ#°xñbüüóÏxæ™gàïïØÛÛcþüù5þ^êÆ,ÒŒ—ÔÆ¿Ò°cÇ4kÖŒ ?$ OH.ggg8;;뻵'¥ 8_öðk/S uýŸ÷IOOGÓ¦M1uêÔj—J888ÀÚÚíÛ·‡ƒƒŒ1iÒ$˜˜˜ÀØØ]ºtÁûï¿ãÕW_Õhoذa2d<==áààOOOôêÕ àææ†   ˜™™áÆèر#¦M›†¯¾ú VVVý033ƒžyæ­>vïÞýúõÓ*oݺ5|||Ä5Ö @÷îÝQ^^Ž¢¢" 8+W®„‹‹ ìììàíí-îDbjjŠiÓ¦¡{÷îpssC×®]ѧOq—6mÚÀÇÇGcçooo 6 –––¸yó&ÌÍÍ1dÈÌ;·ÒdɆîàÁƒø÷¨:|”·ð‘˜DDO€ò&Ò Æ-Òêÿ剈ˆˆˆjhá‚|’‹I„$“I Ž-$ci  u„ òI.&’\L"$%8¶\ŒY¤q1™Žð©>$ל‘\Lð!%8¶\ŒY¤qšˆˆˆˆHÐDDDDD 0€Ö.È'¹˜èCr1‰”àØBr1f‘ÆZG¸ Ÿäb¢ÉÅ$B€   |þùç’õj{lùøã1yòäkoîܹøÿïÿÕX{$ciL"Ô.È'¹˜èCr1‰`çΰ°°¬WÛcKll,Ž?^cíc98­#\Or-[¶Œë I–ŠB®…®Ÿ¢¢¢ðÒK/ÁÖÖï½÷ºt邬¬,DEE!==Ï<óŒX7>>xå•WðÊ+¯`Ó¦Mزe `óæÍÔcKqq1’’’´^K$%%!''G,+))ARRÂÃÃqåÊ̘1¯¾ú*¾ùæ¬^½Íš5ÃÒ¥K«ìÿýû÷1vìX:t›6m‚¿¿¿ØE‹! Æ C›6m™™‰;v °°7¤¦¦jÌ÷êÕ o¼ñ†Æk”••á£>B^^LMMÅ~8gΜÁÌ™3áåå…?ÿü›6m‚§§'¹ÆWµk×ø=’"P­ BCCõÝ ""ò»H^»÷ðH(Ñwt&//Ohß¾½Ð¾}{áöíÛÕÖ511T*•päÈò~ýú FFFׇ„„fffZm „iÓ¦‰e—/_5ÒÒÒÄòòòr¡uëÖBëÖ­5ÚðõõlllA„¬¬,¡G‚µµµV¿úôé#tèÐA()©þçÙ½{wÁÓÓ³Ú:o¾ù¦@X½zµXöÙgŸ „;wjÔMLLaaaÕ¶IŒ[äà""¢:æðáørå Þyç´hÑB²~¯^½0`À²Áƒ£¼¼¼ÒåJŒ3öööâ×*• ÞÞÞ¸~ý:òòò´êŸ;w^^^ÈÍÍÅÿû_­~yzz"''‡‚ Oܯ/¿ü_~ù%fÏž7ß|S,G×®]µÖP»¸¸àÙgŸÅžø5‰*p QsñâEê`SŽÎ;k•9;;€Æ:è'!Õv“&MÄò¼¼<ôïßÖÖÖ8zô(lmmµ®>}:~ûí7¼øâ‹èСF…iÓ¦ÁÝÝ]vŸvíÚ…¹sçÂßß_kì””˜šš¢OŸ>‚  ¼¼‚ 33·nÝB~~~¥‰‹Dr1€Ö.È'¹.\¸ þq"ªNE¡ž{B5­¸¸`ff&«¾‰‰ôŸóꔫ› –Óv xyyaß¾}ˆŠŠÂÔ©SµêtïÞiiiˆŒŒDdd$¾ùæ|õÕWðññÁ¾}û$_ïï¿ÿÆøñãѽ{wDFFjì6RVV†¢¢"tìØQ#ñqµ±CI}RTTsss}w£Nc­#L"$¹˜DHr1‰°þêÒ¥ uÐÛ·oßisÙ²e°²²Bqq1®]»¦1;|õêÕy SSSìܹãÆÃ´iÓPXXˆY³fiÕ333Chh(BCCqëÖ-|ðÁX»v-¢££«ý}ÎÊÊ‚ŸŸš7oލ¨(1á°‚±±1 ,¨‘÷Ô1‰Pÿ ¦#üE$¹<“\ žë©ŠíèV­Z…²²²isÆ èÔ©àСCç"##kä5upüË/¿`ܸqxóÍ7±bÅŠjë·lÙ¯¿þ:õÓ «òàÁøûûãîÝ»ˆŽŽFÛ¶m+­7räHœ;wÑÑÑOþ&8Æ,Ò8MDDTÇ´jÕ Ë–-ÃôéÓ1hÐ ¼ûî»ÛØM˜0½{÷VÜî¸qã°xñb,\¸7nÜ@Ïž=mÛ¶ÕhÿMLLðÓO?ÁÂÂóçÏGAA>øà@¿~ýˆþýûÃÎÎ X¹r%T*† Ve›o½õâââ0gÎdgg#;;[ãü€`ee…?üÛ·oGpp0.\ˆ!C† I“&HOOÇï¿ÿŽ;wî`ýúõ5ú~©áaMDDT½ñÆhÙ²%Þ|óMŒ5J,···¯tm±ŽŽŽøî»ï0cÆ q犊5˳Ó5ÅØØëׯ‡¥¥%/^ŒÂÂB|òÉ'hÓ¦ –,Y‚‚‚±®­­-¾ùæ <¸ÊöÒÓÓ«V­ÂªU«´ÎŸ={îîîhܸ1Nž<‰wÞyK—.Åûï¿/Ö±··ÇÛo¿]ƒï’*•ð4{È,aaa(**ªÑ[dT1‰äª÷I„{Š_IÀžc ¸6ÌyŸ«W¯âÊ•+°µµ}â@÷ѱ%??IIIèСƒ^~ŠŠŠžžŽ[·n¡U«V°··„R“JKKqñâE}¦µ/byy9ÂÃÃŒ=ú‰;ˆˆˆ Õ÷ßï¾ûááá˜8q¢¾»CTë b»+VÀÁÁ'NÄ¢E‹°uëV­:ëÖ­ÃóÏ??þøØ¿?ªQ¯¸¸xï½÷`nnŽØØXôíÛ{÷îÕ¨wðàAxyyáðáÃb„¯¯/ Å:‚ `âĉxë­·`llŒÄÄD 8°Æ³™‰ˆˆê²+W®Ú·o¯çžé†AÌ@9ÞÞÞððð¨t¦Pg'%%‰›ÏÀüùó±bŠ̘1}úôlÙ²{öìÁéÓ§ÅG¤Ž3³gÏFRR’xíœ9s0hÐ ìÚµ *• 3g΄‡‡"""0eÊÀþýû‰C‡áù矠~Dé¬Y³ ‘Á'’\L"$¹ê}!Õ¨Ú[@×/|¡4ƒ˜vuuE¯^½ªýazxxhÏÄ@7%%E,[·nìííÅàüüüœœŒ£Gâââ???¨T*€‹‹ 5Ö­[·M›6ƒgð÷÷GNNŽÖîL"$¹–-[¦ï.ˆ‰‰ ‰¤ÔæØR@?m¢#Õ ŒY¤Dý¤Nœ8pwwËRRR4¾ÔÁ1$''k|tuuÕ¨çîî.ž€¤¤$ñÚÇÛz4h¸ Ÿäc!ÉÅ'’µ5¶äçç#77ÖÖÖ°´´¬•× ÝbÌ"­ÞÐ×®]üyó0nÜ8xxxP¯Y¾~ýz•tVV–ÆÇǃcwwwܸq¥¥¥b½ÇëØÛÛÃÊÊJëoqqqØ´ivìØ¡qTÜ‚­À2–±Œe,{(--]#÷¤®õeóG—oÔ¥þ±Lº¬"&Y¸p!6mÚ„ŒŒ Põêe}éô®R IDAT÷î]øúúÂÆÆkÖ¬Ñwwˆˆˆê­[·nàò j`ÓµkW!44´ÊóyyyBß¾}'''!;;[ë|Û¶m???²ØØX€°~ýzA!22R :tH£^PP`kk+~ݽ{wÁËËK£Njjª@X¾|¹X*Œ?^î[¤îüùóúîˆ7n7nÜÐw7jÏî"AxíÞÃ#¡Dß=2hµ5¶üðÃaÚ´iµÒ>éÞøñ㫵HêÕ t~~>|}}qíÚ5üñÇhÓ¦VGGGÄÇÇk”;wħ"U|LLLÔ¨¯ñ䤮]»Š×>Þ–£££F9ä“\L"$¹˜DHJÔÖØrêÔ)¨r—,2<ŒY¤Õ›º°°HMMÅüQå­¤©S§"==gΜË¢££áää„~ýúzöì wwwDGGCêÀ8%%aaamÝ»wþù§X…6mÚÀÏÏOãu¹ Ÿäb!ÉÅ$BR¢¶Æ–Ьú÷ï_+í“î1f‘fû@ÇÅÅaÆŒ€ôôtܺu ^^^€Ã‡ÃÌÌ k×®ÅЩS'k\?sæLLš4 Œˆˆ :AAAâöuÛ·o׸fÕªUð÷÷ÇàÁƒáââ‚mÛ¶ÁÛÛ&Lë 2!!! Dpp0²²²°wï^DDDhìMDDTÝ¿ñññhÚ´©V‚>Q}f´8Ëòøl‹±±1 _¿~øßÿýßJ¯th333üöÛo⣼ûöí‹Ï>ûLëÖ“Ž;&>ÊûÃ?Ôz”·J¥Â?þ(>ÊÛÕÕ‹-⣼‰ˆ¨A8qâÊÊÊàåå#£zsS›H’AÐ;vÄ‚ ª­ãåå%ÎJK155ÅÔ©S1uêÔjë¹¹¹ÁÍÍ­Ú:FFF Õzdøãø$B’‹O"$¹ø$BR¢6Æ.ߨŸø$BiüpA>ÉÅ$B’‹I„¤DmŒ-±±±@×7ŒY¤Ä t}Àù$“I.&’5=¶‚€ØØX£OŸ>5Ú6éciœ&"""ÅŽ?ŽÜÜ\xzzÂÊÊJßÝ!Ò)ÐDDD¤Ø?þ ÒsOˆt´Ž0‰äºpႾ»@âæÍ›b"!‘”š[JJJ°e˨T*„„„ÔX»T70f‘ÆZG¸ Ÿäb!ÉÅ$BR¢&Ç–={öàæÍ›èß¿?ìíík¬]ª³Hc¡ŽpA>ÉÅ$B’‹I„¤DMŽ-ááဗ_~¹ÆÚ¤ºƒ1‹4Î@‘lwïÞETTLMM1nÜ8}w‡H/@‘l¿üò 1|øpX[[ë»;DzÁZG¸ Ÿäb!ÉÅ$BR¢¦Æ–5kÖ&NœX#íQÝØEhá‚|’‹I„$“I‰š[¢¢¢púôi888`̘15Ð+ª‹³Hc¡ŽpA>ÉÅ$B’‹I„¤DMŒ-K—.,Z´&& !ê+Æ,Ò8MDDD’vïÞS§NÁÁÁË7¨ÁcMDDD’8ûLôhá‚|’‹I„$“I‰§[öíÛ‡cÇŽqö¹`Ì"´ŽpA>ÉÅ$B’‹I„¤ÄÓŒ-~ø!Î>7ŒY¤ñ_ŽpA>ÉÅ$B’‹I„¤Ä“Ž-ß~û-Ž= GGGÎ>7ŒY¤qšˆˆˆ*•™™‰yóæÖ®]ËÙg¢0€&""¢J½þúë¸wï¦NŠ_|QßÝ!ª3@ëä“\L"$¹˜DHJ([6n܈ݻw£]»vøôÓOk©WT1f‘ÆZG¸ Ÿäb!ÉÅ$BRBÉØ’¹sç¾ùæ4kÖ¬¶ºEuci\̤#\Or1‰äb!)¡dl™>}:rss1iÒ$øúúÖ^§¨NbÌ"3ÐDDD$Z¾|9vî܉6mÚà‹/¾Ðwwˆê$ÐDDDøí·ß°páB˜šš"22ÖÖÖúîQÄZG¸ Ÿäb!ÉÅ$BRBjlùûï¿ñòË/£¼¼kÖ¬···n:Fuci  u„ òI.&’\L"$%ª[®]»†€€Ü¿sçÎÅk¯½¦ÃžQ]ØE“u„ òI.&’\L"$%ª[ ˆŒŒ Œ9’[Öc8MDDÔ@•••aòäÉ8vìÜÜܰyóf14 ’¢“è²²2\ºt ‰‰‰HLL„©©)ÜÜÜàææ†Ž;B¥Ré¢DDDôÒÒRL˜0[·nEëÖ­…&Mšè»[D¡Vèû÷ïã믿ÆÊ•+‘““hÞ¼9JKKqÿþ}@—.]ðî»ïbÒ¤I033«Íîèä“\.\€³³³¾»A "ÐÆÆFÏ=!CðèØR\\ŒqãÆaçΰµµÅЩS'ývꌢ¢"˜››ë»uZ­Ý§Y»v-ìííŽÅ‹ãرc¸yó&rss‘——‡ììlÄÄÄàõ×_ÇÇ lÛ¶­¶º£w\Or1‰äb!)Q1¶T¬yÞ¹s'Úµk‡?ÿü®®®zîÕ%ŒY¤ÕÚ ôíÛ·ñÿٻ󸨪þãŸvdqETÄ%÷}É\rE­ÔL±|²òyÚ,Ÿ¬~=Ùª•VfÙb†Z™[â–Kî’Š n .(n€Ê² 0÷÷ÇÀÈÀ ƒƒú}¿^÷5̹gî=³pùÎá|ÏY¾|9>ú¨Éý^^^xyyѵkW^{í5Ö¬YC\\\E5Çâd@¾0—$ sI¡(‹2226l[·n¥~ýúlß¾___K7MT1³”®Âèwß}×ìºjµš   ŠjŠBñÐKIIaèСìÞ½›F±}ûv4h`éf q_’T[!„âM‡ؽ{7~~~ìÞ½[‚g!îA…Л7oæÇäìÙ³ÄÄÄð /ШQ#zöìɾ}û*º U‚$ sÉJ„Â\²¡0Çš5kèØ±#gΜ¡{÷îìÞ½›:uêXºY¢ “˜¥t: Ç;ï¼Ã'Ÿ|b¸¿yóf>þøcŽ?NÇŽÙµk àÆX[ß¹)çÏŸçСCÄÆÆÒ¢E  d²ÞX½z5†nݺñÌ3Ï›Ó2''‡E‹±oß>¼½½5j­Zµ*v¬ÈÈHV¬XÁåË—éØ±#ÁÁÁŲRu:K–,!,, OOO‚‚‚èСC±cÉ€|a®™3gÊ8ha–‚B -LQ…3fðÁ ( M›6eÛ¶mØØØXºi¢Š»v회ƒ.E…ö@/^¼˜©S§råʾøâ ÆŽË7¸zõ*›6m">>ž¼¼¼R{¡?ÿüs|}}7nï¾û.+V¬0YoÕªUtëÖãÇ£V«yõÕWyê©§Ðét†:Z­–¡C‡òöÛocggǾ}ûèܹ3›7o6:ÖŽ;èÔ©»wïÆÁÁÿýï 4ˆ¬¬,CEQ7n¯¾ú*VVVDEEѽ{w“³‰ÈQ˜K‚ga®áÇKð,LJMMeøðá̘1;;;~ù墣£%xf‘˜Å J‰ŠŠRT*•¢ÑhEQ”¼¼<ÅÍÍM™6mšQ½(ï¾ûî©8p@ÉÊÊRüýý• &«“““£x{{+ÁÁÁ†²;w*€²aÃCÙâÅ‹@9r䈡,((Hñóó3:^`` 2pà@E§Óž•••òóÏ?êlÙ²E”;wÊ^xáÅËËKÑjµ†² &˜l³Bˆ;ؘ­(ÿJ½½EæXºE÷…èèh¥iÓ¦  Ô«WO9xð ¥›$î3·”®Âz Ïž=KÍš5©^½: ŸiÃßߣz 4àòåËw>ž!C†Êzö쉫««QÞ‚ hР­[·6” <˜3gΰwï^"""8qâƒ6¬’ؼys7n\ìX...ôìÙÓP6dÈ®^½Êúõëïøœ„Bˆò”——Ç_|A›6m8uê={öäСC´k×ÎÒMâSa´¢(uh“Μ9èÝ û@ŸÄhT§à1õ n‹N,_ôX§OŸ.v¾‚û111Få2 _˜K’…¹$‰PˆŽŽ¦k×®¼ùæ›äääðÖ[o±uëVjÖ¬i¨#×a.‰YJW¡c SRR=z´a‹‰‰áçŸ6*Ûºuk¹œ+>>[[[7nlTH||< ê¯_¿^b]P¯àÖT0ž˜˜Hnn®¡^Ñ: 4ÀÉÉ©XÒà¾}ûX¼x1¡¡¡F[Ñ?~R&e3gά2m‘²ª]V°aUhKE•]¸pÑ(÷¤ªµÏÒe¹¹¹|úé§´iÓ†ððpüýýÙ·oŸ~ú)ÖÖÖF-X‰ÐÒm–²ªWV“LŸ>Å‹…¸³ ›…ÃÕÕ£o¼uëÖ%''ǨÌÉÉ顬îââbé&ˆûDHH¡¡¡–n†¸$ÊçåáÍk¯½ÆáDZ²²bäÈ‘Ì;ooo“õ%AY˜ËÓÓÓÒM¨ú,=»¬JJ"üòË/@‰ŽŽ6*ïÖ­›ÒªU+Ã}oooeðàÁFuöíÛ§Ê/¿ü¢(Š¢,]º´Xr ¢(ʨQ£”Zµjî·jÕJéÔ©“QØØXP>ûì3C™ ÆBˆ» I„&%''+S§NUlmm@ DAQ®$n)ݳ¡ŸŸ'Ož4*?qâ„a@ãÆ9qâ„Q‚ÇÔ+¸-ú/Œ¢Çò÷÷/v¾‚ûE‡’!„÷"77—yóæÑ¸qcfÏžM^^o¿ý6’((D%«°!†ÈÈH³êzyyÑ´iÓ{:_¿~ýðööfݺuŒ1€]»v‘’’Bpp°¡Þ¤I“?~}hÞ¼9«V­¢W¯^Œ;ÖP§oß¾Œ3†áÇóÔSOÏæÍ›ùý÷ß‹MV/+ sÉJ„Â\²áƒïرcL:•mÛ¶вeK¾øâ {ì±2K®-Â\²aéTŠR1óÍ­Y³†áÇÈĉéÕ«—aNå¢<<<îøF]ºt‰ßÿÝä¾7ß|+++Ãýª¸”wA¸\¸„¢ 6iáÏBÿ½{Å*¬ß§J9yò$3gÎä·ß~C§ÓQ»vm>üðC&NœXìošåMâ–ÒUX­ÓéØºu+¿üò ¡¡¡Ô®]› &Lƒ *â”U–|…â.<„txx8Ÿ~ú)k×®EQyã7˜6mÕªU³tóÄCBâ–ÒUØ×XµZM¿~ýXºt) ¼ùæ›üõ×_øøøðè£ò믿’™™YQ§B!î[¶l¡wïÞtêÔ‰5kÖàììÌ´iÓ8þ<3fÌàYˆ*¦RþäææÆ‹/¾Hxx8‘‘‘4mÚ”ñãÇóÁTÆé«I"æ’Õ„¹d%Âû›N§cÅŠ´mÛ–þýû³sçNjÔ¨ÁÇÌÅ‹™5kµjÕ*·óɵE˜Kb–ÒUê@ª°°0æÌ™Ã’%K¨W¯^±±Â2I"æ*X-LˆÒ¬D(î/†9sæÐ¬Y3ž|òI"""¨_¿>sçÎåâÅ‹¼ýöÛ¸¹¹•ûyåÚ"Ì%1Ké*|0ÙÅ‹Y¼x1‹-"!!#F°zõjúôéSbRáƒH²Y…¹dÌ™0—̾qÿP…;vðÓO?ñ矢Õj dêÔ©<ýôÓX[WìŸd¹¶sIÌRº ûm=}ú4/½ô;wî¤K—.LŸ>'Ÿ|ggçŠ:¥BQ¥\»v,X@LL ŽŽŽŒ;–É“'¦dBÜ_*,€ŽŽŽfûöí´hÑ‚êÕ«³nÝ:Ö­[g²nŸ>}xùå—+ª)B!D¥IOOgÓ¦M,]º”µk×’““@‹-˜œÞ½{ckk[­BTE6ôÆ©[·./¿ü2§NÂÔŠá999ìÝ»—±cÇÒ¨Q#>\QÍBñÊÎÎfîܹtíÚ''§Rë§§§³jÕ*žyæjÖ¬Iß¾}™7o—/_¦Y³fLŸ>ððp®\¹Âüùóéß¿¿ÏB}ú¡Xo]Võæ’¤ a.Y‰P”…\[„¹$f)]¥ô@²råJ“û–-[F```e4C!„Bˆ{V)ôË/¿Ì¥K—èÕ«6l ;;›ï¾ûŽÖ­[såÊÞxãÊh†B!„÷¬Rèzõê±eËÒÒÒx÷Ýw¹páS¦L!##ƒ¿þú‹FUF3,Jä sI¢0—$вk‹0—Ä,¥«´iìÚ·oÏáÇIHH <<œË—/sæÌz÷î]YM°(/Ì%‰>Â\’D(ÊB®-Â\³”®R’O:Å7èÚµ+^^^xyyö]¼x‘ÔÔÔ~´ Èæ’Da.I"e!×a.‰YJW)=Ðßÿ=ݺuãõ×_G§Óí[½z5³gÏ®Œf!„BqÏ*mGß¾}Y¸p!#FŒ ==½²N+„B!D¹ª´ºk×®ìÝ»—cǎѳgO*ëÔU‚ Èæ’Da.I"e!×a.‰YJWi4@óæÍÙ¿?ÖÖÖtìØ‘ãÇWæé-Jä sI¢0—$вk‹0—Ä,¥«”$ÂÂjժŎ;?~<ݺu£W¯^xxxTv3* Èæ’Da.I"e!×a.‰YJW©=ÐX¾|9/¾ø"ëÖ­³D„B!„¸+•ÒýÞ{ï¡VÇê*•ŠY³f1hÐ œœœÊå<ÉÉÉlذ;wÒ¡CŒ··w±zaaa¬Y³†´´4zöìɘ1cŠÕIOO'$$„C‡áããÃèÑ£ñóó+V/""‚U«VqíÚ5ºté„ °²²2Ù¾ÅüÁøÑ£ïý‰ !„B‹©”hÜÜÜLîëÑ£<òÈ=ŸcýúõÔ¬Y“?þ{{{,X@½zõøê«¯Œê…„„ЫW/Î;Gnn.Ï?ÿ<Ï=÷œQÔÔTúöíËÌ™3qrrbÓ¦MtìØ‘Õ[·n]ºtáСCØØØ0mÚ4FŒAnnn±ö‰‰á¥÷ÞãpDÄ=?Wñ`“Da.I"e!×a.I",JQ¥"EQhÛ¶­ÑP‘öíÛ£V«ÉËËôlRRR6l˜¡ÎÀ±¶¶6ú„„„ТE |||°²²bРAìÙ³‡óçϰeË.]ºdt¬Ž;R£F “¸ì:uÀÍhggé…w$,a®áÇ?¸Á³(wrmæ’$ÂÒUØWùÏ>ûÌðóäÉ“™WWWÖ¬Yc¨³qãFrss 6”Ill,õX  IDAT Ÿ²nÆ tïÞÝp¬~ýúQ¿~}£c…‡‡“˜˜ht¬99aíí gÏrôÖ-:>ÌÛçÏ“­ÓÝÍK „B!*I…ÍÂáîîÎÉ“' ÷µZ-»víâé§Ÿ6ªW§N{ž°ÛÚÚšÏ>ûŒÿüç?4nܘþýûóÏ?ÿpìØ1^ýu6l€££#_}õ“&MââÅ‹xxx°lÙ2ž}öYÃГ&MbÅŠôèуáÇsøða¢££Ù¼y³¡Ž••óæÍã‰'ž ÿþ4jÔˆ+V0tèP†Z¬ÕNžÄ#îé9·nQ]«åŒŸ9ŠÂ§—.±Z£aaÓ¦tvq¹§×D!„BTŒ ën×® ,Z´NÇ’%KHNN.6]TTõêÕ»çóM™2…ýû÷3bÄ’’’èÝ»77n,–DÌÎ;ñõõÅÚÚšü±X"£‹‹ [·nå­·ÞâÖ­[ 0€ððp:tè`ToÈ!ìÝ»—víÚ‘““ÃgŸ}ÆŸþ‰µuñï%CºvåÒš5F[¶mDþú+Û¶åggNedÐíÈ^‰!£Pâ¡xxH¢0—$вk‹0—¬DXº [‰à‰'ž`åÊ•¸¸¸ššÊc=f´"a^^>>>L™2…ÿþ÷¿Õ ‹ æÒ¥Klß¾½Ä:¹ŠÂ—/óþ… †a>öö,ð÷§»{e5UT²Z˜0—¬D(ÊB®-Â\²aé*ôJôÇðã?rüøqúõëÇàÁƒöŸ;wŽ>}úT‘ͨJo­RñVýú ÷ôä¹Ó§Ù›’BlV;Æ¿j׿ __\Lôl‹\°„¹ØÀYT¹¶sIaé*4"³²²âÅ_,q¿ŸŸŸüBÑÔÑ‘=­[óM\ïÄÆ’ž—ÇO ü•”Ä~~ *²ŒB!„¨\[‰P”L­RñJݺœhßÞ0|#.;›Á'N0.:š2Á¹B!„ÅH]Iîf@¾½=ÛZµâGÃðß®]£ùÁƒ¬HL,ï&Š*B}„¹$‰P”…\[„¹$‰°t@W’k×®ÝõcÿU»6'Û·7 ߸®ÕòdT#"#¹ªÕ–WE1sæLK7AÜ' ³t3Ä}B®-Â\÷³<,$€®$÷: ¿ŽëùµY3ªÛذZ£¡ù„\½ZMU„äsÉJ„¢,äÚ"Ì%I„¥“ú>ót­Zœlßž'jÔ 97—‰§N1àøq.eeY¸uB!„>  ïC5mmYÀª€¼lmØœ”D‹ƒù..Ž ›Ø[!„BH]Y*b@þˆ58Ù¡¼¼HËËcÊÙ³ô:z”˜ÌÌr?Ÿ¨’è#Ì%I„¢,äÚ"Ì%I„¥“º’lÙ¾…C;ðøØÇùá—(¯ Ý­­ iÚ”-[RÏ΀Ý7oÒòàA¾¸|™¼Š[hRTIôæ’$BQrmæ’$ÂÒUèRÞB/88˜EGAN1N4½Þ” ¿o fÍšåvž´¼<¦;Çññ†a\\XèïO@µjåv!„¨²”· HâéHteQåßZÁ-ÿ[jyˆ‘Ï,×S8[Y1ßÏí­[ãëàÀÔT9|˜.\ G¾+ !„BÜ3  -ÅŽØá•”û¡{¹¹q¢}{^¯WµJ…V§ã.Ðîða§¥•ûù„B!&@W–ÜâEéMÒYµqU…œÎA­f¶¯/{Û´¡yþðã·nÑ1"‚·ÎŸ'K§«óŠ{'‰>Â\’D(ÊB®-Â\’DX:  +Kº‰2ˆKŒ«ÐÓvtqáHÛ¶¼Û Ö*yŠÂ¬K—h}èÿ¤¤Tè¹ÅÝ‘Da.I"e!×a.I",ЕÅÕDY½y”w·¿Ëùäóvj[µš}|8ض-mœœ8‘A÷#GxùìYÒóò uãââ¨Ý¨Öqg’´!Ì%+вk‹0—¬DX:  -)2ëfòñži<·1}õá·¿‘™[1s8·vrâ@Û¶|ìãƒZ|G‹ƒÙšœ ÀÔ?FóÄ|2wn…´A!„â~'teI®ÚNBýëõiÓ¦ ;.ì`ÜŸãðžíÍK^âpÂáro†µJÅÛ p¤];:»¸p!+‹ÇŽcìîÝ„i4äÈ»wK/´B!„ @W’Vç`ÖýöÙ"_üiÅè“zmFäK‘¼Þùuj8ÖàfÖMæšO»ÛÑúûÖ|sà’2“ʵ=Í kÓ†/7ÆÑÊ €¥sç7lû÷—^h ‘Da.I"e!×a.I",ЕÄCÓ²õÛ›9 Ssó¸åáÁkŸ~J@f÷›MÜÔ8V>¹’¢Véßšc׎ñòÆ—ñžíÍè•£ùûüßè”ò™AC­Rñjݺœh׎.Z-ää@:ä¶jÅüíÛ‰HL,—s óI¢0—$вk‹0—$–NV"¬ÁÁÁ°h!…Êb/GŽdîÊ•&s%õ !GCXxd!±7cöÕw­ÏÄÖ™Øf" \”KG¿ô˺v5Ð= §Oã7q"AžžyzÒÁÅŰ&ŒBT(Y‰P‹•K'=ÐòEݺ¼1gN‰ûëºÔåÝïrî•sl¿±c±·¶àRÊ%fìšÏW>ô[Ò?"ÿ ;ïîÿÝÇ^Æ8xhÝNâLr2³.]¢SDõöícÊÙ³lMN&W¾{ !„â!$´ÄVuêPßÙ¹Ôº*TôñéÃo#~#ajóŸG¯Û‰‡Ÿÿ›1«Æà=Û›—7¾Ì±kÇÊÜž©Ìåü±ÏE©‡§ÎƆ^ç¸ìl¾‹‹ã±cǨùÏ?L8uŠP†ŒBSá !„B<È$€®$…û‡¿Þ‡5 K˜1Âá”ÕÝìݘÒ~ “#ˆ˜Á”öSp·w )3‰o|Cëï[ÓöǶ|wð;nfÝ,µ]%ö>çÓµj…ý™3œmÕŠo›4¡¯»;Ö*}8œ›Ëâ«W ŠŒ¤ÆÞ½ŒˆŒdɵk$çšXvQ˜M}„¹$‰P”…\[„¹$‰°t2ºs~Ñ"v“?öÙÊŠ¹¦zl=<à±Ç ýæí]ê±³r³X}j5?GüÌöØí(Ü~;í­íÙl$Ï=ò½öBebôò“'³ÒϯÄÀúøqþëâÂGo½èçu «56'%‘Y$ð·V©èåæF§'Ã==ñ¶³+õyˆÛ‚ƒƒeÜ™0Khh(Àƒ»˜ŠŒ.WrmæêÓ§õë×—ÏËH] ‚ƒƒ9þÇDdg3¥n]þ»cõ/^„M›ô[d¤é€ú`º{w°µ½ãyboÆòË‘_9ÂåÔËFû¹7bb뉷¦®K]Cùw‹sâôi@ß›{9–É7¨åY Ÿú>¸¹¹гCF›æ‘‘—ÇæädþLLdýÜ,Òû¬:¸¸’ýK{¹„BOh!,B’K't%fÏÆüyý:?›šy#.6oÖÓ[·Bþª€FªUƒ^½nÔMš”x>¢c˹-ü|ägÖž^‹6OkاV©éïÛŸgÛ<ËPÿ¡ØZÙ’ššÊø)ãÙ“¼‡¤zIà$BÍØš μYó°±±)õyæ( ;oÞdub"¡ Zm±:Í«U3ÓmÍ.„xˆI-„EH]:  +App0‰×¯sl÷nöž]·ŽÅ¯¼ÂÈ¡CË¥]U…$úsI¡( ¹¶sIaé$€®ö¯ôtؾýöpsçŠ×Q© mÛÛÃ=:u‚B©_Mg¾N¼…ÏN³[ Ÿ×ó ,ê$µªÕ*×§p]«eͬNLdÛÍ›h‹ W±U«é“?£Ç0OOjÙÚ’MÀð᜛:•À9s8þ×_åÚ&!D!´!C8J't%¨´â¹s·göرC`åê >j¨Û£Áᣄ¦–|ØNîî †fžÍèÕ°½}zÓ³AOjV«YnÍOÍÍ寤$þLLdcR·ŠLõ§V©èìâ‚óÚµlur"·m[œ7nä—®]¸^h!@ a!@—N®D__˜2E¿iµú!Ã=Ž××II?ÿÔoÀF7&©õóS›ê…Þ©†³Þ@=ýýhM4Ñšhæš@óÍõuÃÞôlØ“Ž5LÅ<.ÖÖŒ®Y“Ñ5k’­Óñwr2«5Öj4hrrÐ) ÿh4°s'üï¤=öÿ™5 û®]iæèHC{{Ô’Œ(„Bˆ $=Е  ‰péÒ¥–kD|‚÷ß/ñ±* ½½!¨nV­š!Èö4cŽëÊ$‰>Â\’D(ÊB®-Â\’DXº.€>räýõQQQxyy1xð`úôécTgß¾}„††’œœL=xúé§Qù·vv6!!!„‡‡Ó Až|òIš5kVì|ÇŽcåÊ•ÄÇÇÓ¹sg&L˜PlÑ‘*?–(9™S 0ûí·ù©ÐJ‚O?w\;°fMt> ¹îåÌ)×öÚ^ãoÕyN¹æpÕɸª -j¶¸P7ìIu‡êf5ñÕ1cØÁ±çžCסC±ýÖ«Wãsþ<µ‚‚pn×ŽèŒ .fea·»ºÍíÀºP¯uy ‰‰‰¡sŸ>\:}‡{:–  …°ˆ*·TÔ•èÛo¿åÕW_¥S§N4kÖŒèèhNžl8ÞË/¿Ì€ø3?!ïù矧M›6¬\¹’±cÇVÆÓ.WSçÌáÓ½{™wå ó6ä§+À±HÿóÆuáŸãâ Ð?4l³rht%‡F&Ε§†K® çÜsÎã8ûÜçò›‡ uã&ø´}”ÎÍúѳaOÜíÝHsq!áÙgKn¼µ5W‡ !×ÞÞPä VÓÚɉÖNÆ]áyŠÂ…¬,¢M×É…zà³t:ŽßºÅñ" ¿¨U*ØÙ )èµ.<$&&†ˆìlrûõã×Y³xëßÿ–^h!„âðÀÐ3fÌÀÉɉýë_Få…‡S¬_¿FÃ!C e=ö„„„è àïïO“&MP©T <˜)S¦päÈÚ´iÃÞ½{9sæ Ó§O7«uëÖÔ«Wbtvvñ¤»ª¦N:ØvéÂ’U«xä©§p,<ƒ>Y¯zu0Õœ ±±ÅësçôåYY†ªV:ðIÖo}Ï”*Àà ‰ÕæsÚ’¼Ý±jâÇ?rqÔ¤PûÇq1Ñöë*¸nmE˜‡£† »ãó´R©ðupÀ×ÁÁÕ‡\Ójõu‘àúJv¶a8ˆNQˆÍÊ"6+‹¿nÜ0z¼mz:nññ¸%$pmÝ:RòÇkÇöîMgZÛÚrS«%àñÇùøûïM¶Ï‰>·nÝâ…7ßä×ü/–âþðÀ'Šr%I„Â\ÙÙÙØÙÙYºUÚ@‡‡‡Ó·o_~ýõWÖ®]‹••}ûöeòäɨóÿÝ~æÌš7onxœZ­& À°ô=‡ýúõ3:~ÁcbbbhÓ¦¡~`‘¥¹›7oΩS§ŠµïÚµkåð,+ÞÔ9sè½{7Gßy§ì¶³ƒ¦Mõ[QŠ¢Ÿ ÄDïµr.•Æ8­‘®ß¸’ Âéd€åEô©£›«é!Ö¬wwýæá¡¿5õeÀ„Z¶¶Ô²µ¥§››Qyz^§ êBÁõÙŒ r õºk«Uãz“&\wt„úõ «@*:pÜÇÍóÏ“«Õr­qcÆž}š)S¦°gÏ~ÿýwâãã©V­ 40z|`` [·nôC>’““‚l€€€àv Z­6YoÇŽ(Šb”˜¨ÑhX¼xq±?üݺu3ê= µhY:u8“PþçP©=xP_Ö£Gñz¶¶†ÀzåšÕø¥_ÇñÂY/_¥æ,Öë ÐØ ôBÑ—…SÓaøA¡G³øw ÷Ç…Ýlmñ,¦ÝÝ ÕjéÖ¨ž^^† ;ôüyºuìˆgƒ†à;tϺõê…§§'miëìLhh(¯æ?·\Eá|f&!+Wbݲ%íìXNÚŠäèÇŒ»º  BÜo¿ÁäÉ\ËËcÿõëú†…A` ¡“&±æ³ÏPâV½º>À¶µ%wÏštè@Ýš5 AwÌßÓ½[7{yQÃÖg++³ÞøË—ñß°'; ]³fä¾ó§ÆŒaÁTV981p E?“?|ò 5’’(qšÊÑôtFÕª…Mþã蔺 Ä—…ƒÊnó'¯½Æ™cǸ¤ÑPËÕ;’32pwt4*ëĸÿü§ÜÎ[8›ûØ7^z  ZþP¢Ü¼<þض îÝ e9ŽŽ¼øÊ+»E;ÆÕøxj÷Ä~ŸÎ..$&%‘á“MJÄ->Œ¿Ÿ]{ô y‹•Þ¾‚²Û¶±~Ý:üýüpvvF«Õ²açNêU¯n(hÙ¦ gbb,ö{¤ÒéÈLOgÀ£òÇ’%ì?p€+7n0jà@C½C‡4r$]»w¯ôö”íܶ«ññ†÷×ÞÁÍ»w3¸OC™³³3-Û´¡y‹»6}ôÁÔòô4¼¿9¹¹,\º”±Ã‡Ê4ÉÉŒ;Öb¿G±±±ÜÈÿ/é¾}ûð÷÷çô¹stnßÞP@§Nˆˆˆô‘¦&LÅ=tÁD"—/_æòåËx{{ðþûï3cÆ Þ|óMÚ´icÉ&ŠÒ¸¸@ëÖк5ÖVVxº ¤g¦ðÞ#M¨Þ2‘Æið×è›?$X¯†U ÜqÊ ­®^Õo2¯mŽŽ·{²=<ôCQ5oo¬ÝÝñsw§ÃùótspÀ³A>;|˜O33¹Y´—´];¬øGòòP¹¸ ÉÉ!1'‡’T€äÜ\’ss9›™ ©©¸vÍh( /ê_»‹ýÒçN§OSÛÉ ¯5 =Ûšë×9“@C޶¶ úè#f„…ñT\Ÿ9:ryòd°±áz›6ìØµ‹§srXU¿>Ë'Ofýúõæ½N`ЈÔ˜1ƒ§òŸsK77r_{Zß|ÜTý+7ÊÅ…¡Ï}:±±±4lØÐP¯}ûöXYY±ÿ~ªW¯N¿~ýŒ>Ù¹s'½{÷fùòå<ñÄ„„„0qâD:DÛB¼pêÔ).\¸`(“é`î]¿'ûñ·ÿß` ®;aé˜_ÚÁô!  ×ëà~ ÜÁ#Ü3Á=ËôÏÕ³ÕÔ̲Æ-SÁ>+U9þ ¬Y“Mß|5‹/s® çÿ~ý•õê½=ØÛ“ãèˆÆÕ‹ ''Ñ8:¢±·Gcg‡ÆÖ–D4VVhÔj4@VñÓÞÌLÔ³f¡+˜C[«Åéwh:jê¦M ð÷ÇÑÊ GµúŽ·%”Û«ÕÜË ”ÜÜ\F6kÆê˜"U*ú ȵ7ߤє)9y’àÕnÝX²gO9¼wO£ÑðÚ#°äòeÚ׮͡3÷Ö[,IJ"áïÏê“' CÊ,!&&†y={òU|<Ù@@ãÆœ›;—ö“&q >€'7fÑñãMxÝ¿w/Û† ãM+t<Î R;v"<âÜø€1ñÓ±c8I®L‡`ó A¼£Ñ ´lÒ„˜Ù³éù¯±3ÿ¿•OûúòÓñã¦sJ,ÐÎl  IÎ}þ9½^xù U¡·ßw};[øú3{6}&Of[þëYÕÞ÷l Ðϳ_|Aÿ^`SþïQp“&üpâ„EÇoZ·Ž˜ñãù÷Í›ä-||8óÁ<õÊ+ü‘¿˜ZI¯§Ä-¥{ z üýýÉÌÌ4*+¸_ðöóóàäÉ“†Z§ÓŨB=.7&**ÊèX'Ož4ì+|¬'NÐ'Ož4™¤q?$VeFMàಃÜly“”®ðÎqè›óÝ ;Pý/f~ø5j/5ç’Ïq.é1ÉçˆIŠáJêtŠ®Ðu€~ñ+>¸.²kf[ã§®/4ÈsÆ;×O­î ÕniQݼ©_É1922ŒÚmÖÌdð  tìÈ’ xkãF  vþp (-Í'ÝÞ«+‰nnúà»Ð–èáÆÝMÁ>ggn89‘gj—|ŽØäX“K“›¢V©©ëR—Æñu÷ÅßÙ‡¦V5i¬ªNýx··oçCýpŒÂ[f&Á¹¹„ÜÅës'ŠJE²““Q }ÅÙ™© Y_|a\Y«Åö½÷ð™2… ;;2ìíɰ³#Ó’ÓØXض &Mº]öÑGðúë%&ˆZ) 6yyØæåaS°ét†Í¶È}E¹½OQ ÷mt:lEÁ¶ ^á ý4™¡r5*Š _}¥Oª½|™.³f‘åïÏ”=PT*tj5y*ºü-/¿Ìè~Ñ}pûq`Øwöða a‡Åöé '¿\§R‘ªÕsö,×BCɘ5Kÿbefâúþû8ŒM+??¬T*T`øb¸ÍÿV¦}*UÙ—nMJ V&Ó~›=ÚŽúY¾~ìI¿¿‡ýãÓ¥S'¬­¬LŽÜº—2Ðÿ¾+3ñ§R’ÒÒˆ‹ˆàâ–-Üš9S¿#-}„CPÚ¶ÅÊ‚ÿu(”–ÆÁ÷ß'×Þþv;SS©ùÉ'8 F‡ví°*á‹gÑ…ÆT&^’¾´šÌ¹-[HûüsýŽ”jò öƒU©÷=áÈ.lÙBZÁp’´4jþ9Ò±}{Ãë©*ø}Rãû›û)|¿hEA¥VëoUªÛ·*ç/]"içN8À/¿• ââxêå—Q¹¹•Ø›/+–îé~æ™gX±b­ZµbäÈ‘œ>}šˆˆ>üðC5ÒÏDlkkËܹs?~Ï–àæ={B½z·Ë\\¸Þ¤ xyq±ª´ÓÃÒÓ_OWWš4zõªÖû~ó&þ{ïìL‚Ô­ËyS×ÒfU*i¿©~΢eù9 Åx{ëÔGº}ü:uØáçǤG)±7_b–Ò=0=РŸAcñâÅìÛ·† FÏž=‹Õ“¥¼ïoçÎcgØNŽœÿ64ñë7ˆr¿†ý·ßâõÜs†×§ØkVÂëhNYY©hµœ !÷Ãw¦¥á0{6õóÛiiJn.ç.,ÞÎÔT¾úŠúõ(“½°¦Žgª^ `YŽ©ää÷Ãä~ô‘ñΔìçÎÅ+­‹¿ï¹¹ú÷}Æ ãii8~û-ÞÏù¼¡×úóퟓH¢~§.v„ž[áRãÇej3‰Hˆ(S;¬Ì ´‹nN¶Å{žó nÝ);ÜÖ–„Ö­Y´v-*9‹\ 8åo#œ¤~Gæxl,?}üqe5ÏH®¢£(äètü½c“Z´àfÑ¡.õêQ[«emÆxÕ®ýb>j•êöÏ`|¿ÐÏ(Šé JÞw‡í£~àƒ  âOÆÑÛ–-ù¬V-zuèpçs™:wiuÊxÖÊ•üÓ±\/ÒN77\kÕb½¢àãs7o[¹šñÇ|ljnggœë×çwEá‘üœKúhÅ >0ÕNœëÖe¹ZMËJ¼®–ä“•+™aj1,WW\½½Y“—W%ÚùÑŠ|0thñÎÎ8yy±ÎÚš¦õë¶ Ü©ì^÷þ}ÒéøjÓ&¦÷ëGVÑ/1uêpàæMâââ¨S§ÎŸ¬0éꮪ‚ƒƒÉÎÎ6šÕC<˜†ÆÚzk¡à¿Œ:¨vÒ ÇÞqÐ&§ 4$)3ɰ%g%“‘“À¼œ0³Ø¨mpw0îåÞ6#‚LC÷ŠM2ÔÈ„«. +ˆ\³:xû±aÙRl°·¶ÇJe¢×µíÛ¿Ÿî³>!ï•×K¬c÷Æë\Û¶ׂy´-¤ýСš2Ÿ÷¹ÀåËŒ;tˆ%_]®ç,kavv6͇ áüÛo›®‘A»ï¿çàÚµåÕÄ»¢Õji1|8¾½>`ÄžÛÓA~=âQ>Ù’Bß+øû×_-ØJ}¢zË‘#‰É_q´˜´4zþú+;—-«Ü†‘M‹  bFÖ/ðTTZ½—.e{þš –’M‹aÈyë-ÓRRè³lÛª@;GŽäìo˜®–FÿÕ«Ù´paå6¬ˆœœZ ™ÿþ×ôð¸8žúçþøî»b»ÆŒƒô@ßÁÕ]•Ý/+Š{3ýßÓÙÿÎ~®wËï6S žúÑõY¿l½a¾ò²ó²?a<ï½÷žQpmjKÎL6üœšŠRBjLŽ.‡ëé×¹ž^¨+Ï(Ô+ž“¿‚n¼¾ÍäÁ —¨ñy C=µ öÖö8Ø8à`í`øÙÞÚÞ辩}¦êÝiŸƒµA“'‘7â 8z´Ä×<»]{Ƽ8™¿~ÿãïLÅÚºcç40<Ô«Ç®ÐPÊu¸QYW"ü|þ|.?úhÉ9çíÍÎ={èeÉù€ü‘‹}úà«+¡‚«+'lm‰:y’€"‹YU¦Ï¾ý–‹}û–\ÁÙ™h''"Žá ®Eðùüùúvþþ»~ˆQQÎÎD9:rüÄ ZY]·2Íþþ{.Ýéõtu%ÊÞÞâíü|þ|.äO:`’³3G¬¬8uú4Mý‹çÝT–o.äR%­®S‡½É^hY‰°t@Wù >:uèĨGF±$b imÒŒÐå‚Ç?LvºÉàÀÎÊŽe¿–½·*OÉ3 ¨K º³ôuŽX!›B38ÃÍ‹Táv0/G—CŽ6‡4mZ™ÛxW²làx¡±º Ø^máÿÜk!×¶wÑGVªÿ~ú)9M›âò×_€>s?3#ÇB yIvv¼7{6?õä”u ï°ðpþŸ½;‹ªz8þDveQ„QPÀ­pßÓLÉ5÷…4¿-–fVj»–i«ZYýÌrËÜ55MË-÷E•DV‘íþþ0&Gf†eèy¿^¼lÎ=÷Þs‡Û‡3ç9§Qv69Üïé‰ÁÛÇG=Å^nV›ví2iºq#îvv¸]K¡Fbsòòò¸›™IýŒÃd9Ü_)5;5•E«V1wæL“µsÛÎÝdíüvÑ"j7lÿLË›••EÊíÛÔuqQ·3ëÖ-æýðŸ¬ð‰Yt“!F ƒñÿ{¯X̬og‘V'ÛÖ·qÊpÂá¶ >Z@×NÅô\ÉÓ£ŸfS½MP\^b< rD=¸›s—»¹w¹›s—¬Ü,õßÍýçu1Û ÊròsŠ9Y€:ka¾ÁœŽ“;ÒÌ¥™úÇ×És3ãôDGG«‡S§AƒEþñ$´Ø– a÷ÿÐËÊÊÂjjMhR±û}RSS©ñð*¤Peigrr²ÞC”L)--McïŠ 33“ììl²øøxÜœ‰°µµ•ÉJ b?‰„¨¤žñ,!ÃCˆŽŽ&æR MüšP¿~}S7KíÃ7>äèä£$u(b¡¼.x±pãBjÖ¬Y&çÌSòôº ¶¾ós┸¢'˜¸¹¹ì¼´ScjAËj–4­ÛT#¨t ÄÞ¢ì?ä¼½½Õ‹,‰òaeUv³Ð”§Ê”Båigež <ØØØZÇÑѱˆÚÂP@‰¬Døß£R©hذ! 64h¿¨¨(­«Y–¥@ÿ@ÆtÃG~àVË[šC5îó~gfMšUfÁ3@5U5ì,ì´Î R”ôþéÌ<9“»^w‹¬c{Ö–î}ºs­æ5N_;}?“ûãÉ'çxâ¿‹©PáUÓKP7wmN3—fÔ³/û,ôüüür_¶ÛÐ$BñßfŒg‹¨d%BÝ$€6I"úš3gŽQ¾6›óÞÛøï~ù.©v©¤Ú¤R3½&µsj³hÞ"Z4oQîmÐå͉o²îÉus:Z:ÌÌ®˜Ñ»AoV¿~Üx¾’Ï…›O WÿœL:©N TPîÏç}ûoÖ[§>Ž““FOu3—f4vjlð¬#±±±¼0õî$™Ÿ‰Êަ4å»Ï¾+—?C“Å›±ž-¢ò“$BÝd ´ÈX"QÑݸqƒØØX7n\ᾊ¼víCþ7„ÕOæžÀMpºäDz=X4oÖÖÖÅ#1=ñ߀:ñ$áIáDߊ.ræ+s+õæ.÷{ªœŠìAÿ%ô¦}5øÖñðÀb~fÉf¦cnfNÓºMiZ·)#FªËãSã ¹tç’z{VJÖý¡%ÅÈiÃ×뾦æc5©_£>î5Ü©k[3UÉ K“D˜––ÆÉ“'¹sfM›€………îM ;;›ôôtì²ëbQÁ?¶*âtfäÙ"ô%I„ºUì'Q"I„B_’èSñ¸×pǽ†;Á>ÿö€§ÜKáTÒ)N&䃭p‡;ÅÄÂ/‡3`õ¿[W7«Ž›½îîÔ¯Q_X«ÿÛÁg[ç"ƒì’$æçç3óÓ™,Ù¶„[N·H³I£æ†š8Ýrbö”Ù ì;Pïc•·ðÓáDo>K¶Yyæ¹,qiµ2YñÍ ¼¼¼LÝ<µÈs‘¼ôöK$¦&rWu—f5ðs÷cáç +Ô´a~ø!¶um '37+‚»óæÄ7Ë}ÆCäååñã²Ùº{+7SoâáêÁ¨£èÑ­‡©›VHxx8þõ'‘Ñ‘thÕî]ºW¨)K $''sôØQÎÿ}ž A4kÖ¬ØÜI"ÔMÆ@Œ%¢jëØ·#5û«ÐÊnç€ö†ÛÜÌü~ýP`ý`Àíbç¢wOö€Ñئlã®÷CS*PóHMÞíó.“_šlX#ËÁ;¿ÃÅõgéžßA=øü 8kE½ƒõøéýŸ*D@µpÙBf.›IÂc  ©’Ux…{±nÁ:M×ÀìÙ·‡qSǃâüïǾu´5~I~lùy ÎÎÎ&lá}ç/œgØ‹Ã8çvެY`¤A­èZ´·kÏ/ÿ÷ ¶¬ði*wîÜaäK#9œz˜äzÉàf7Ìp»äƳ=ŸeÆ´êÕþL)//7g¼Éê=«¹ér“,Û,R¨T›Ï¦~Æ€§µ¯T%q‹n@܈BTmÿ·øÿxcǤ5*z‰óºûê:;»úv\I½B|JŸù¹ñ÷žƒz²§Æîy<4ŒTZGj1³ÿL&ŒŸPh?‰[t“!BQJÏ?ûÆå&—™úÍTžyöT¨0S™¡R©P¡Òø×LeV¨¬¬¶½:ëU['ïWÄÅ@´W4Ÿýð£CF«÷4Ž :ÏÃeºö)nû¤÷'ߪè÷ ˆrâçU?2*D½¿±Mz{7û»èoHjÀIó“lÚ²©Lu õÖ¬·¸à{Akð ï’ÏÎ#;9tä­/ù <¥õù7ŸY7Rkð åŸÅ/»áù‹Ï¼VYZ¾j9GòŽžTp+èŸ-ÿŒÁqqq1~+9é6‚îÝ»ÇÊ•+MÝQ H¢Oå”––FÈË!쿹Ÿkn×ÀT7T¸Åº1¬ã0>yÿ“2gzóîMâS≌‹$!-;fw õj?d°è«ãàùÀV w™4µd~ž†'£»iïÈþz™¢ÿØè~žìzÞY^t·íê’«Üëw’¢òMS æÙšÔïW_ë± ý·¸k-êßóp§·Ž\‚›ÐðZCš?Ó¼\ß³â¶-û` ÝÔC‹´ºÝTÝè2´K±ïÉÃePº?ð,ûâ­/ˆjU|;ã`n«¹¼úÒ«ÅÆ ÃÒÒRz ‹!=ÐF"I„B_’DX9ÙÛÛ³né:"""رw§ÏŸ¦]Çvôø ez®ÚÖµ©m]›ØC±4¤¡Ö$›wo ª¿ûý;nq«øƒç†-ÀXöô騭Æý`¿¢³•£  î·2V÷UÁû´¢þpûÚmn_¿m¤Fi‘­GZpñ¯‹\<{±Ü›S¤LtߣuàÏmògÝ?Ñ"í’ÑÝN7Ø}hw¡Z’u“ÚHäFú’à¹ró÷÷Çßßß(ç*nö‚ ;Ðùß¶pÏp¶ÜÝÅ,ܨº¢bêÀ© 5Œ|%_ð=üoyn›qt§òOñ‰0¸ó`‚ûk¥(tž‡ËtíSÜö{p‰Kë:tz¼];w5ÙûùÛ¶ßH&¹øÞò[àííM€o€ÖcúïÃï›>ÿž­v–L2‹?S ¦KMœœ‹(--ØØX5jT1—ñÞ–MÎêt’““IIK%ÿ% ¼û5®m½pá»þÚÅÙè³thÕN:Q·nóœ™HHHOôz‚Ml"!)??FI‡vLÝ4 ¼ÿéû=s”´¬4jÛÕ¦ßý˜ð\áùŠM)::š—Þz‰«iWÉÈÍ ¦EMš=ÚŒ¯g]!{)ðÇ®?˜òÑîp‡{6÷°NµÆËÉ‹¥ó—¹jb×®]iРääCh#ÐgBò^xŸþ'§¢æB»øøxÖ­[Wâ Jˆ k[6„=Ð6ÉšHꎨxòóóMÞã¬KFFW¯^ÅËË‹jÕŠŸjGRÑMžDDVVƒ ’›UÌÅÅ…¬,ÃW¯BQ6*zð ÷¿­4õxüª¤âÿÆ…B!„¨@$€6/„(k%I"ÿ]’ ,ô%1‹n@‰¬D(„(kûöícß¾}¦n†¨$æÌ1ýŒ ¢r˜E7m$²ª¢¬Iâ¨0„äØ}IÌ¢›ô@ !„Ba  …B!„0€ÐF"ò…eM’…!$‰PèKbÝd ´‘”v@þС¯qúô-õjÔHçСÐãóÏä‡vP­šU±õrs8p`¥,ú"„‰$ÊXh¡9sæÈ8h¡—k×®É8h$€6’ÒÞˆqq·8wn‰Îz>>!%>Gtt<.Ì<‹­çìüééé¥  oݺE^^uêÔ!77—ãÇ“™™I‹-pppкO^^/^$::///5jTìjJ‰‰‰DFF’>úh¡:999œ?žØØX|||ðöö.4!~ff&©©©899annÎßÿMll,íÚµÃÊÊJ£Þ¨S§þþþ…ŽsãÆ ÌÍÍ©Y³&™™™9r{{{š5kVè:222HKK£N:T«VK—.A»ví¨]»¶Î÷Wü7Hà, !Á³Ð—ϺÉaO=õ=öûöí£N:´nÝš®]»âîîÎúõë Õ?rä5Â××—àà`š4i‚··7(T7..Ž=zàææF÷îÝyê©§ðöö¦oß¾õþøã<<<ð÷÷'88˜F@DD„F½… âêêÊÞ½{yüñÇñöö¦{÷îÔ®]›åË—ðÎ;ïP³fMžxâ š5kÆSO=Eff¦Æqüýý6l .ÄÁÁ.]ºÐªU+}ôQN:¥Q÷믿ÆÕÕ•ýû÷€——}ûöåèÑ£%z¿…BQv$€&“œœÌðáÃ7n§Nâ›o¾ÁÖÖ–qãÆ‘’’¢®wõêUºtéBFF¿ýöׯ_ç÷ß'''‡®]»réÒ%uÝôôtÚµkÇÁƒùöÛo¹t鉉‰lß¾-Z¨ë={–'Ÿ|vïÞÍõë× #11‘Ž;róæÍBí=z4ÞÞÞìß¿ŸÕ«WÓ A^zé%^{í5~ýõW,XÀ‰'1b¿ÿþ; ,(tŒ£GòᇲiÓ&®]»Æ¦M›ÈÊÊ¢k×®¤¦¦ª?tèPºuëÆñãljˆˆ 00°´o»B!J©JሊŠbÆ øøø0`ÀmŠ¢°bÅ öîÝKÍš5éׯmÚ´)tŒØØXV¬XÁÅ‹iÙ²%!!!ØÛ,⺠IDAT۪Ɵþ‰¥¥%½{÷¦[·nZÛd¬ùqqЬYÉö½r¥lÛ¢KFFü1'N €[·nñÞ{ï±{÷nuñÇLff&?üð½zõ G|õÕW 8÷Þ{OÝ<{ölX³f Ï<óŒú\...<ñÄê×ï¿ÿ>yyy,\¸N:п™0asæÌá³Ï>ÓhoÓ¦Mùå—_Ô¯ÓÓÓ7nóæÍ#!!WWW-ZÄÚµkY¿~=o¼ñ†Æ1nݺł xòÉ'èÓ§³gÏfìØ±|õÕW¼óÎ;õýüü˜;wn ßaQÕ$J>‚ÐGTT76u3D%pïÞ=,--MÝŒ ­Êõ@çææ2bÄÞ~ûm–-[¦±MQFÅ„ 8wî;vdåÊ•õΜ9CëÖ­ùõ×_©Q£sçÎ¥K—.ܾ}[£Þ”)S1b™™™\½z•ž={òÕW_im—±Võ¹wN*Ù–N×r¥R©8p FYA{ùòeuÙP©TtïÞ]£n×®]©V­‡R—ýñÇ8;;kÏÚ8pÚµk§QÞ£Gcèß¿¿Æë:¤ž¬¬¬hÕªqqq…Ž¡R© ý‘ÕµkWþúë¯Bõ m!´‘•…!d%B¡/Y‰P·*×ýÙgŸ©˜‡íÚµ‹+V°mÛ6zöì À‹/¾Èĉ8p L:• °oß>ªW¯Î[o½…ŸŸß|ó ï¾û.‘‘‘|ùå—,Y²„1cÆ0cÆ ¦NÊÈ‘#©U«–ƹ5 ßΊè×éÔ)ˆ-ÓæËÝÝzõêi”¹¸¸÷{§ DGGãïïOݺu5ê:::Ò²eKNž<‰¢(¨T*Ο?OÓ¦M‹=oFF‰‰‰ôèÑ£Ð_ØÞÞÞ4hЀX-oÄã?®ñºN:ZË ¶={¶P¹¿¿¿z¿xxxS¨~—.]нñß&I„Â’D(ô%I„ºU©訨(æÌ™Ã¢E‹P©T…¶/Y²+++¯òûöíKrr2›7o !!­[·òä“OR½zuà~PתU+-Z¤q¬‚ý “••ŪU«Êãòôâæ6”ì矿)ŒFÛïH[[[­c’áþWØÖÖÖêcÙÙÙißÚXZZbnn®uþÜÜÜ\îܹƒµµµÞíÕ÷:­×‘——Ç;w°±±)´ÍÖÖVïc !„Â8ªLŸŸÏ¸qã˜2e ¾¾¾Zë\¸pFiL/æïï¯Þƒ¢(4iÒDc_???._¾Lvv¶º~ýúõqttT×iܸ1fffêc‰²Ñ°aCˆŒŒÔ(‰‰!&&†GyD]æëëËÅ‹µ&ä077ç‘GáäÉ“…‚è#GŽššŠ——WÙ^Ä?´]lj'HIIÑ:ÍžB!*ž*@óÍ7¤¤¤0}úô"ë\½zµP`\¯^=jÖ¬ÉÕ«WÇýøùùiÔ+ØïúõëêcßlllðôôTëAGŽaÙ²elذAãGVÓmÈ!¬]»V£¼àõСCÕeãÇ'##ƒ·Þz EQ4êçææjSQÖ­[§ó˜e-4Ts¡›°°0† V¢ã=|Úî+)«še+V„¶”WYllYYY¢-•½¬`%ŠÐ)«Xe1ÉôéÓY¶lÿý7¢xUb ô¥K—xçwؾ}»zØEEóðœÀ†züqòòBtÖsv.~ÁâùsìØ;˜™[¨T=ïåíÿû‹/æƒ> &&†V­ZÎâÅ‹ñ÷÷çÕW_U×*òx7.4täaÇgøðá:Û&„Bã«c —.]Jll¬ÆOóæÍéÑ£±±±´nÝ€²²²Ø±c‡zß7âääDŸ>}€ûc¢{õêŶmÛÈÉÉ ))‰cÇŽñÜsÏ©÷ Qï_`Ó¦MXYY•ëøY!„BaZU¢Z_]ºtaĈ <˜!C†””ÄÖ­[Y¶l™ºgà“O>¡{÷î´oßž   6oÞŒ——/¿ü²ºŽŸŸ¯½ö/¼ð»wïæîÝ»„††òå—_šŒ·¡â¿CV"†•…¾d%BݪD´6&L(”¦R©X¾|9 ,î'tíÝ»·ÐìM›6åСC<ýôÓ¤¦¦2yòdvíÚEÍš55ê}ñŬX±ÜÜÜøý÷ßÕËR?LVõ¯¿þ:#GŽ4u3D"+ CÈJ„B_³è¦R”‡æÀe®`¸GqÙÏúÔBæÍ›'C‡DÕ³-Âøön’54ùO}q*„IHL¢[•íB!„¢æÌ™#ã …^®]»&ã uÚHÊêFÌÍÍåÝße硤d¥`gi‡¿·?ó>š‡ƒƒC™œà‡e?°tíRnݽ…E5 ê×®Ï7ƒ§§g™£$Ú·oÏíÛ·±±±)õ±’’’ ý…]ÇÂX$p†àYèK‚gÝ$€®Dâãã  æ\ƒsde«ËOÜ8Á¾>ûX8k!]:v)Õ9233 ÌQ³£¤µLÕýòS§δQÓxù¹—KuŽ;wî””TävwwwlmmµnËÎÎ&)) WWW,,, mÏÏÏçÌ™3œ={–€€üüüP©TZåàà€••UÉ.B!„ÿY’DXI(ŠÂÀçrªÕ)²=³5·ÕQˆîÍøéãIII)Õyþ7ùìqÚCšÿ¿Á3¶Ð5Y+f~*¼TçXµj¾¾¾Eþüõ×_Eî{äÈ|}}Y¿~}¡mwîÜáñǧY³fŒ9’¦M›2`ÀÒÓÓKÕ^!„BˆIm$¥M"\½n5gÎ@Q# Ì ¦y o|ðF‰ÏqéÒ%v]ÚE^½¼"ë$µIâ•÷^)ñ9FŒÁ¥K—4~"""ðôô¤Føøø”踓&M¢]»v\¿~Û·oóñdzqãF&Nœ¨µ~@@–––4lØaÆqñâÅÒ\–F'+ CÈJ„B_2ñn2„ÃHJ›Døë¿r÷‘»ÅÖQê(,ݸ”íó¶—è'3HvÖñal ׳®—èøìíí±··W¿ÎËË£oß¾\¹r…-[¶àååUâãΟ?_ýzúôélÙ²…eË–ñî»ïòÈ# R©xüñÇiÙ²%Š¢°qãFV­ZEXX[·n¥k×®¥º>!ŒE’…!$‰PèK’u“ÚHJ{#^I¼®ºëeçe—W²“$zÌp”©Ê$--M#.I“&±eË.\H=J|mo×®]Ù¿?ÇŽSÐ_~ù%ÖÖÖê: ,`Ë–- 8¢££µŽ¯¢¢‘ÀYB‚g¡/ žu“º’hÚ¨)Ýù ‹©¤@]Ûºô ìU¢s\É»Âî¨ÝäÕ.z€ƒÊ¡Ì‚ç¹sç²`Á¦M›ÆøñãKu¬Ž;Y«.{0x033#88˜‘#G²xñbΟ?¿¿©Ú"„BˆªKèJb䀑¬™¹†›A7‹¬c}ɚɃ'3­ß´ãf‡›´ì×’¸&Åô`ߟº%£ü° 6ðúë¯3xð`>þøãR/>>¾È2}¦øóõõîÛ@ !„¢(’Dh$¥ß¶u[º¹uÃìJ¿²Thߘ7'¾YâsÔ®]›W†¼‚í)íSÈ‘ GÄÊ +9~//ú=ÛàÞÁeÐÊûf½3‹qÃDZxåb<„c Gžìù$£‡¦zõê¥>þ‡~ȵk×hÔ¨¯¿þz¡íS¦L)ÑŠÞ½{ÓºukF…••+V¬ 99™ÐÐPu»“““iÒ¤ 4nÜ•JÅo¿ýFjj*vvvüüóÏerBƒ$ CH¡Ð—$ê&t%Ô:¨5­ƒZ—ë9¼¼¼øðíËåØôíÛ¸¿øÉÃrssprr¢oß¾ÿ#k+suu¥oß¾L:•K—.±lÙ2Ξ=‹¯¯/?üðݺuÓØÿ­·ÞâàÁƒ;vŒÌÌLüüühÙ²%o¿ý6®®zLu"„Bˆÿ4  …ÑMœ8±ÈÅMÔ´iS6lØ ³,((H]ÄСC‹<¦³fÍ*A«…B!î“1ÐF"ò…eM’…!$‰PèKbÝ$€6/„(k’D( !I„B_³è&C8ŒDä !Êš$ CH¡Ð—Ä,ºI´B!„Z!„BHm$2 _QÖ$‰PB’…¾$fÑMh#‘ùBˆ²&I„Â’D(ô%1‹n’Dh$2 _QÖ$‰PB’…¾$fÑMz …B!„0€ÐB!„B@h#‘ùš222HMM5u3„¨Ô$‰PB’…¾$fÑMÆ@‰ È×4pà@ÂÃÃIJJ*×ó|ðÁZËÍÍÍyçwÊõÜB”·‚B -ô1gÎ-ôríÚ5­ƒÐF"7¢i̘1Ck¹¥¥¥ТғÀYB‚g¡/‰Yt«2tRRaaa„‡‡“••…¯¯/ãÇÇÉÉ©PÝ#Gް~ýz’““iß¾=£FÂÌLs4KNNK—.åàÁƒ¸¹¹1hÐ  ëÌ™3¬]»–øøx‚‚‚ ÁÒҲ̯ïõ±c¹°u+µªW/²N>p­N~?~¼Dçý徘8‘F66ÅÖÛ÷.QIIT«V­Dç1¶1cÆÈ‡B!ÊL•  ýüü°²²¢M›6(ŠÂÌ™3ùôÓOÙ¿?~~~êzëÖ­cذa<ñĸ»»3eÊ6oÞÌêÕ«ÕAtvv6}ûöåøñã 4ˆƒòÅ_°~ýzzöì©>Ö®]»æ±ÇÃÏÏ÷ߟµkײyóf¬¬¬ÊôúÆN™ÂŠß~cV||‘uVÙØ@Côѻֽÿ>K¢£‹¬ |?|x™Ïׯ_'&&¦Èí¾¾¾888¨_ß¹s‡#GŽpáÂ<== ¢N:eÚ&!„BmªL½dÉžzê)ÌÍï_RBB^^^¼ýöÛ¬_¿€ÜÜ\&NœÈˆ#X¼x1Ï>û,­[·fË–-ʶmÛ8yò$Íš5`À€Lœ8‘óçÏ«Ï9iÒ$:vìÈ–-[P©TL˜0€€~ùåÆŽ«Ñ¾ÒÈ÷kÒ„ë$ïØAá>õû½Ïë<=YRâsX[[4|8»?þ˜Î¹¹Zë|áîÎ{Ÿ^âs%,,Œ_|±Èí[·nåÉ'Ÿà÷ßgôèÑ\¿~233±··ç»ï¾cĈZ÷ÏËË#??ŸêÅôà QÙ$jû¦Mˆ‡EEEѸqcS7CT÷îÝ+—oÓ«’*3 ÇÓO?­žêÕ«G·n݈~ 7uûöí\½zU(êžËE‹©Ë–,Y‚‡‡‡:xèÓ§.\àÀœ8q‚ˆˆúôéƒJ¥î÷‚{{{k.PI„“çÎe®³³Ömkll8eJ¡¡(†z~Ú4þÏÓSë¶hÀ¶C\]]KumÆŽËíÛ·5~bbbðôô¤víÚ4iÒ€øøxúõ뇣£#ááádddð÷ßãííͨQ£8zôh¡c‡……ammµµ5>>>L:•ôôô2¿!ŒMV"†•…¾dâݪLý°œœΜ9ƒ¿¿¿ºìÂ… C: ^?h_¸pAc¿÷)8FÁ¿]õ¶•Å€|u/ôCåùÀº¬,¿ö8:–êÇÚÕ• øxvk9ÿîîL)‡Þg Õ?666Œ;–ÄÄD6n܈»»;ü1YYYÌœ9S=&ÝËË‹¹sç¢( ï½÷žÆqÆ ãúõë|úé§<öØcdff–˵a,ýúõ“DB¡7Éú’$Bݪl=uêTnܸ¡1ÙÕ«W±°°ÀÛÛ[£n“&MÔm)ŠBbbb‘ôÕ«W5þ}8÷÷÷çÆä>4âĉ,[¶Œ 6hü:‡«¶^è5ÀÀü|ÌRR  ~ž¿wÿ{è¼åÙû¬ÍøñãÙ³gK–,¡]»vêòcÇŽ¡R©èÞ½»FývíÚQ£F Ž;¦QžÀ¢E‹xûí·Y¾|9ׯ_çÕW_%**Š™3gåZÊ›¢(¯µÝWR&e•µ,66ެ¬¬ Ñ)“²ªZV“LŸ>eË–qùòeDñªÌè}÷ÝwÌŸ?Ÿ%K–àããcêæ”©‡ÇBçëjÕbõÈ‘ðÏP’Ò²‚b÷Ñ£tÎÏà‹úõËeì³63fÌ`Ù²eÌš5‹¡C‡jl‹‰‰¡I“&Ô®][£ÜÜÜœ:°eËÒÒÒ°··(”ìhaaÁìÙ³ùæ›oؽ{w¹^‡B!ª(¥ŠYºt©bff¦|ûí·…¶Í;W”sçÎi”wêÔIñóóS¿öððPúôé£QçàÁƒ  ,^¼XQEY¹r¥(»wïÖ¨7hÐ ÅÙÙY£l̘1ÊСC‹m÷˜1c”1cÆèº>>н½½’]h[Æ +++%//Oçùëׯ¯Ô«WÏð†W@ÎÎÎÊÊ•+MÝ a7nÜPnܸaêf”Ÿ­÷e|ê¿?grLÝ¢JíáÏ>!Š2tèP½c’ÿª*5„cõêÕŒ;–¹sçjÑ¡ 7:22R£<22RcX‡…êúh™µ]S$BaI"ú’$BݪL½~ýzFŽÉœ9s˜8q¢Ö:=zôÀÍÍM›6©Ë>Ì7xî¹çÔe!!!ÄÅÅ®.Û¼y3>>>´mÛ€-ZàïïÏæÍ›ÕcP###‰ŽŽ&DK@[Öò'ÏKo{û2™y£(ÏO›ÆlWW£Œ}>þ<ýû÷§aÆ„††j̨¢Ñ¦çŸà§Ÿ~Òû»páBíp¼ôõë×5ö?sæ £GFQúöí[Ö—!„QI¡0„$ }I¡nUf ô!C077'44”ÐÐPu¹½½=;vìî“ýꫯԳ1¸»»Ê AƒèÝ»·zŸAƒ±|ùrzôèÁ AƒÔÓ×Ì']`þüùÓµkWüüüX·n;wføðáå~½~Mšðæ‚ ,bÞã²`mmÍôÿû?üZ¶,·søä“O¸}û6 6d̘1…¶¿ÿþû4oÞœ>}ú0|øp–,YÂÅ‹ """‚;vйsgúçŸfÁ‚4kÖ "##9wîpÚÃW_}µÜ¯K!„UO•  ‹šQááȾ}ûÔKyñÅ…–ò¶°°à×_U/åݦM¾øâ‹BKywéÒ…C‡©—òž1cF¹-å­Í3£F•û9:÷êU.Ç}â‰'hÔ¨‘úuÛ¶mÉÿ'aQ›{¤W¬XA·nÝØ¼y3¿ÿþ;žžžÌ›7W^yEã÷8|øpÌÌÌ8uê‘‘‘ØÛÛ3bÄÌÓO?].×%„BˆªO¥(Í%Ê\HH÷îÝcåÊ•ÅÖùŠMÎÅÅ…yóæš±DT}U~%ÂmÙöÀ*®“¬¡I•é÷1:Y‰PèkذaXZZJLRŒ*3º¢“ùBˆ²&I„Â’D(ô%1‹nò§¼‘È€|!DY“BaéMú’˜E7éB!„Â@ !„Ba  äÞ½{º+ !„’““Õ‰„Bèeê&ˆJBbÝ$€6/„(k’D( !I„B_³è&I„F"ò…eM’…!$‰PèKbݤZ!„BH-„B!„$€6/„(k’D( !I„B_³è&´‘È€|M‰‰‰\¹rE¯ºwïÞ%66–ŒŒŒrn••‹$ CH¡Ð—Ä,ºI¡‘È€|Mýû÷'))‰ØØXu÷ìÙC¯^½X¹r%C‡-ÿÆéðÍ7ß­uÛäÉ“ñðð0r‹Ä•$ CH¡Ð—Ä,ºI-„BCCù믿¨S§N¡m£G–Z!„¨âdG%•““C£-HHH(×óô1‚ [¶”ë9*#www’’’ ý´hÑÂÔMB!D9“h#)ëù ~ú‰¸6m˜2k«¾ý¶L]àtD'ÌÌxÁúõî].爌ŒdÏž=äååѱcGôÞ711‘½{÷rñâE¼¼¼èСîîîEžçÏ?ÿ$%%:w·w¡z'Nœ <<œøøx\\\èÖ­›ÖzB˜ZA¡“““‰["*ƒ¨¨(7nlêfˆJàÞ½{XZZšºšÐFR–òsrrønýzîMÊï¾#!!zõê•Ùñ Lž=›ëC†µoa›61 8¸ÌÏ1oÞ<¦L™‚‡‡ äææ2gÎÞxã û®\¹’^x´´4\]]ILLÄÚÚšùóçóÜsÏ©ë8q‚§Ÿ~š„„jÕª…££#ñññäää (ŠºÞÍ›7y饗X³f æææ¸¹¹‘˜˜HNN§OŸÆßß_ãüYYY¤¤¤P·n]T*UÙ½)Bè© PÆB }Ì™3GÆA ½\»vMÆAë C8Œ¤,oÄ?ýÄåŽA¥"¾o_¦ÌšUfÇ.p:"‚³VVàà@jÏžÌøî»2?Ç•+WX°`/^$&&†äädºwïΛo¾ÉÁƒ‹Ý÷ܹsŒ5І GBB‰‰‰4kÖŒÿýï9rD]wÚ´iܼy“ððpnÞ¼ÉßÿMZZ«V­Ò8æóÏ?Ohh(sæÌ!==¸¸8233Ù°aµk×.Ôv{{{\\\¨Q£={öäÔ©Se÷桇~ýúIð,ô&Á³Ð—ϺIt%SÐûœ5uêý‚zõXÀêÐP(˯q?ù^xáþ››s©I“2ï…ÎËËcÒ¤Ixyy`ooÏìٳپ};sæÌaãÆEîûÑG‘——ÇÌ™3ÕC6œùä“OèС3fÌ`Ë?c·¯]»†ŸŸêý---2dˆúõÑ£GY·n!!!L-xosssúöí«qnÆŽ‹¯×L¸ôIDAT¯/)))„……±}ûvvîÜÉþýûyüñÇKÿæ!„¢Â’èJæÁÞgµÁƒá¡ÞÔR¹t j×uQZ9õBwíÚUãuóæÍqrr"<<¼Øý"""°°° S§Nå­[·¦Fœø€Ó§O³cÇòóóyþùçuî/„BˆÊMz ¤,’ õ>¨Wùùú¨Äç8A„¢@µjEž#µ];Þûê«2»®%K–­~ýý÷ßðâ‹/»ß„ €ûÃ2òóóÕåß|ó /¿ü²ºlóæÍu._¾  žÆ®{÷î´hÑ‚yóæqìØ1º·oß&-- €˜˜öï߯±ýÂ… ¼øâ‹DEEѯ_?ÌÍå‹!„¢*“OúJ±V-úœ:¿ýVl=߆ K|ŽÌìl:::‚Ž•kh:a¨zõêagg‡¿¿?íÚµ#**Šƒ¬3™¯wïÞŒ7Žü‘Ó§OÓªU+N:ÅñãÇ4hÇW× ¦~ýú´lÙ’ºuëræÌŽ;†¯¯/Ï<ó fffüøãôë×6mÚÐ¥KyäâããÙ½{7$00 .ЫW/\]]yôÑGIMMåôéÓò]9$Z !„¢b‘ÚHJ›DòÌ3„üì•—Ö-[Ú²e¹ž£ÀèÑ£IKKãµ×^ã믿fÏž=899ñõ×_óÒK/aföï—#^^^L:µPÂá¢E‹èÕ«›7oæüùóøùù1yòdFŒ¡Q/,,Œ]»vqáÂÎ;GÇŽùðÃiß¾½F’D³f͈ˆˆ`Á‚;vŒÓ§OãææÆ¼yóÔCJZµjÅüùó9räW®\ÁÁÁ±cÇÒ¹sgFŒ¡Ñn!Ê›$ CH¡Ð—$ê¦R\ŠM”‹._¾ÌÎ;‹­2FMÎÅÅ…yóæé5Ÿ¨Z6lØTá$ÂmÙö@çÃ$kh"ý>%"Ÿ1B/]»v¥Aƒr¿CžDF"ò…e­Ê΢\H0$ô%1‹nò}³B!„Z!„BHm$e±¡B<(99YH(„.QQQ¦n‚¨$$fÑMh#)‹•…âAûöíS¯F(„.sæÌ1uD%!1‹n’Dh$ú Èß·oŸz6!ô•’’bê&‘$BaI"ú’$BÝ$€® &Nœhê&ˆJjìØ±ôêÕËÔÍB!þ3$€® Z´h!½B!„•€Œ6/ô%‰>B_’D( !Ï¡/‰Yt“º”Nœ8ÁÛo¿ÍsÏ=ÇO?ýD^^žÖz2 _èK}„¾$‰PBž-B_³è&t)lÚ´‰¶mÛrìØ1ªW¯Î›o¾É€ÈÍÍÕ¨wùòeµP!„Âp»OÆ@—P~~>'N¤ÿþ¬\¹€Z·nͯ¿þÊ€LÜB!„BQ¤º„vïÞMll,}ûöU—Q§NJ› Uîcä6lØPêc”´†œ[ŸºÇŽãÊ•+En?sæ ÑÑÑZ·%%%qèÐ!½ÛS•Åï²8±±±„‡‡—ú8%i§!÷˜¾íÔÕŽâ¶—÷{]ÞäÙbX]y¶È³äÙRÑI]B.\Àßß_£ÜÏÏO½íA999FiWiȇœauËëC®²Ìë,r÷™òC.55•ÔÔTç65y¶VWž-òlÓ>[*CÌbj*EQS7¢2zï½÷øä“OÈÈÈÀÜüß‘0&L`ùòåj7&..ŽV­ZQ½zuã8;;cii©~}ùòe“•åääP½zuõêåqŽØØX:vìXªã=8.Ë}=Jûöíõ:Ǿ}ûhß¾}±õbcc©U«uêÔÑZïØ±c899áééYhߌŒ ’’’ðóó+´oXX­ZµªP÷Fyý.‹+;}ú44nܸTÇÓçwYšÿnß¾Mzz:ÞÞÞ¥ºÿ~ÿýw:wîlо'NœÀÉÉ©BÝ—8q}ôQƒöµÈ«ÎõØëÔ­S KKîVÏ"Ï,Ïà÷Oß²‚Ä© ”Ûÿ ú<‡ÒÒÒ¨[·.uêÔÑZïÒ¥K¤¤¤àëë[h_ nݺ…ŸŸ_¡}ÃÂÂ0`@…º7ÊëwY\YRR—/_&00°TÇ3ä3¥€!ÿ/¨T*îÝ»‡··w±õtÝûöí£AƒÅî[𹚜œŒƒƒáááÔ¯_ŸsçÎ!´“º„  ÇŒÑ#GpuuÕ8†¬ô#„BS{8aðêÕ«±téRµ¨â“$Ârss#;;›óçÏÓ¤IuùÙ³gqssÓ¨+7 B!DÕ!c KÈÇÇ€ˆˆòÈÈHõ6!„BQõH]B;wÆÓÓ“7ªË>Ì7 1]ÄB!D¹’1Ð¥°iÓ&žyæ:uê„——k×®¥]»v¬[·Nc\´B!„¨:¤º‚ƒƒ9pà­Zµ"''‡O?ý”°°0½ƒçâåå…··7!!!ܸq£œ[,*»„„jÖ¬É /¼`ꦈ ¬uëÖÔ«WOOO<==Ù³g©›$*°'Nжm[ÜÜÜhܸ1wîÜ1u“D”ššª~¦xzzâääD³fÍLÝ,“‘hº|ù2 4 ''‡W_}>þøcS7KT`ýúõ£FØØØðý÷ß›º9¢‚jݺ5K–,јPm’““ ૯¾¢ÿþ¤¥¥akk[hÊU!6nÜ8|||˜:uª©›bÒmBÓØU¯^úõë“‘‘a≊lùòåøøøÐ¢E S7ET¿ýö»wï&??ßÔMØ?þÈã?N«V­ˆ‹‹ÃÑÑQ‚g¡Szz:aaaŒ3ÆÔM1  MlòäÉ4kÖŒíÛ·3cÆ S7GTP×®]ã믿–{Dè¥m۶ܺu‹ ШQ#Y A)**ŠèèhžyæÆG=ÈÎÎ6u³D·jÕ*:v숋‹‹©›b2Õ>øàƒL݈Ê(66–#GŽpóæMêÕ«§µNFF;wîäÏ?ÿ¤Zµj…R $((ˆÃ‡såÊ:wî\Î-Ƥ( /^dûöíìß¿777ìííµÖÝ´iË–-ãÔ©S8::ªW!xöÙg™>}:>>>:tˆäädúôéc¬ËF’ššÊÁƒÙ¾}; 4jÔHk½¸¸8~þùg¸yó&5¢Zµjêí={ö¤k×® <˜ôôtöìÙC¯^½ŒuÂH2339vìgÏž¥V­ZX[[k­ÅƉÇÙÙY£Þž={HLLäèÑ£„„„°fÍ‹¼÷DåTVÏ–&Là•W^ùoS„A¶oß®899)€(:uÒZïòåËJãÆ•š5k*-[¶TªU«¦¼üòËJ~~¾Öú‡V|}}˱åš7o®Š™™™(»ví*T'??_;v¬bkk« }___%%%EQEÙ¶m›¢R©”ÐÐPEQ%55U9~ü¸’——§$%%)ãÆSÞ~ûmc]†0’uëÖ)ÇWöîÝ[ä‡Üü¡Êo¿ý¦.2dˆÒ A%//¯Pý¹sç*Ï?ÿ|y6[˜Àùóç•M›6)‰‰‰JÏž=‹|¶´oß^i×®úÃëÔ©Sй¹¹²hÑ"EQî?[Ö®]«ÄÇÇ+üñ‡Ò¬Y3eÍš5ƺ a$óçÏWV¯^­üòË/E>[Ž;¦ÊÂ… EQ”ììl¥sçÎJÓ¦MÕunܸ¡¸»»+±±±Êµk×”¦M›*gΜ1Öe#(«gK×^{Myã7ʻٞХPÔ¯˜™™)}ô‘FyÆ •=z(Š¢(7oÞTžxâ ÅÓÓSiÓ¦òÁ(7oÞ4F³… Ü$mƒ>Ï–_}õ•ÑÚU‘É,å !!Ð~#&''K†³ÐPè-ooo¬­­Õ÷’Pü³åöíÛܽ{×ÍTBBB¡{¥qãÆT¯^]ž-BCÁý ­ãOž-ÚI]233pttÔ(¯U«Š¢È(4äååaaa¡Q¦R©077'//ÏD­QÁýðð<½÷Ü/âA™™™…>‡ªW¯ŽúsJø÷Ùñðg‘<[Š&t9(˜®®à+‘gÏžÅÚÚS4KTPnnnœ={V£,..Ž´´4ÜÜÜLÔ*QÜß/ØÛÛcgggŠf‰ ÊÕÕµÐçPbb"·oßÖ:­ªøï’g‹á$€.€ö±`›¼½½‰ŒŒÔ(‹ˆˆPo¢@Áý í~‘{E<ÌÃÃCëçPÁ6! ìììxê©§LØ2QÑ<[vìØ¡.“gKñTŠ¢(¦nDerëÖ-fΜ À† ÈÍÍeРA¼õÖ[Ô­[€¥K—òì³Ï2yòdš7oÎ÷ÿßÞ½EYõqÿÂr‘›.æ JróR:¡1F`Y‰y#s4-¦¤2q@4‡ÊK‰Ž‘†áØhšƒ–“†„÷„ÀÔM$/‹\dù½4>¯+—\_Äáíû™Ùç<çùsfùqöìy>û /^ÄÑ£GùÖÙ¿ÈG}„œœhµZäåå!88NNNxê©§hjjÂĉqêÔ)DGG£¬¬ ™™™Ø¼y3¢££ñ¨³TVV*?ïãÇC¯×cذa€M›6ÁÃÓ“ƒððpŒ3ؾ};ðÓO?ÁÞÞþ‘õŸ:WFFòóóQQQÌÌLDDDÀÃÃ?þ¸’ðÔ××#88‡²²2,_¾ HHHx”ݧNÄ×–‡ƒÛØI¯×£¢¢ÊqSS“R/::¶¶¶øâ‹/pàÀ᫯¾bòü/ãéé‰ÀÀ@À¨Q£”ò»ß>533ÃÞ½{±~ýz>|...ÈÎÎÆØ±c;½¿ôèXXX(¿+w¾Þaii©|ÿôÓOãçŸƶmÛPXXˆY³faÞ¼yü÷/£Õj•¿=wÞÙ¬¨¨ÀÍ›7•:ÖÖÖ8tè–.]еk×ÂÙÙŸ~ú)bbbIŸéÑàkËÃÁh"""""#p 4‘˜@ 4‘˜@ 4‘˜@ 4ýß;vì˜ÁÝØ¶¿þú 4ØÞX§NÂüѽjßÙ³gQ^^®çææ¢¬¬¬ÓÚ÷ö™ˆ¨³0&¢.íÒ¥K˜3gŽr—G___L›6 ¿ýö›RgÖ¬Yظqc§õéðáÃ=z4jjj8Fll,Ö¬YÓ½j›V«Åðáà ’ÑçŸ;wîüŸcŸ9s¦Câ´&;;%6Q{˜@Q—õí·ßÂÏÏGŽÁË/¿Œ­[·bÞ¼yÐjµ-î¸ÕÕÌ™3“'Oî”¶Ö®] åö¾iß¾}X¼xq‡Ç€3f ¸¸x(ñ‰ˆÚÂ[yQ—¤Õj1gÎŒ1»wï6¸%í¼yó°wïÞV¯«ªªByy9 dpMcc#êëëѽ{wƒúÕÕÕ°´´TêÞºu æææ°²²j3V{}633ƒ­­­RV^^ŽË—/ÃÓÓjµZ)î¹ç R©ÍÍͨ®®n5f·nÝ”z ÓéPTTµZ^½zýcŸjkk‘ššŠÏ>û¬Í:—.]‚N§Cÿþý•²¦¦&ÔÔÔÀÞÞ¦¦†s1555055…J¥‚N§Css³r‹i ØØØ(uoß¾¢¢"ØÛÛÃÛÛ»ÕöµZ-Š‹‹Ñ³gOôéÓG)·³³ÃìÙ³‘˜˜ˆ±cÇþãX‰ˆ:ŒuAÉÉÉ@~ÿý÷¬ ±±±2yòdQ©T@zõê%gΜQêlذAºwïÞâZ///IJJ2ˆµpáB‰ŠŠRb¹¹¹ÉÉ“'•:»vírãÆ inn–ØØXqvv–ÜÜ\ÉÎΖAƒ‰™™™¸¸¸™;w®cÔ¨Q#""EEE ÕÇ‘#GDDD§ÓÉ¢E‹D¥R‰¹¹¹‘#GJiii»ÏMff¦¨T*ill4(W«Õ’ !!!bii)$ @4ˆˆ\¾|YÌÌÌ$##Ã຺º:qpp””IJJjÑß)S¦ˆˆˆ^¯—ääd±²²Rúëïïoð3Ñh4*fffÒ³gO155•   ƒörss€”””´;N"¢ŽÄ%DÔ%ÀÕÕƒ º¯úëׯ‡¯¯/JKKqâÄ 8::bÓ¦MÔöºuëàåå…‹/âÔ©SprrjsuCC^|ñEìÙ³¹¹¹ $$$`üøñ¨®®FUU®]»†W^y¥ÕD}}½ò¨««Ã¤I“ðØcaàÀ€¥K—"==¸yó&òòò Óé0sæÌvÇRPP///˜››·8—””„gžyøñÇqûömL:àææ†ðððãÞ±cêêê0}útÄÇÇ#)) ^^^ˆ¾þúkÀ† €uëÖáêÕ«(,,„‡‡¢¢¢”_®Zµ –––¸zõ**++Q[[‹””ƒöúõë§Œƒˆ¨³0&¢.I£ÑÀÓÓó¾ë»»»#11½zõBPP^xá|ÿý÷Ô¶««+V®\ ॗ^Bvvv‹z7oÞÄøñã¡ÑhpäÈ 0ÀßËŠ‹‹Ñ¯_?X[[œœœðä“O¶Úž‰‰ ¬¬¬”Çûï¿C‡aß¾}pttÄ­[·ðÉ'Ÿ &&QQQ°±±ÁðáÜœ”””´9–‚‚% ½WŸ>}°lÙ2888`ôèш‹‹C^^Nœ8˜;w.ø Õmìî•••e«µØ+W® Ùµk—\¸pA,,,ÄÅÅE†*...âææ&Û¶mSêß½ÝöíÛ•~©ÕjƒÇñãÇEDD«ÕÊÔ©S€888ˆX[[‹››[»ÏaCCƒ¸»»KzzºA¹Z­–7ß|S¼¼¼ÄÝÝ]T*•¸»»Ë±cÇZÄÈËË2mÚ´çš››eÊ”)âàà òê«¯ŠˆHcc£,\¸PT*•ØØØˆŸŸŸtëÖMœœœäÊ•+""âìì,vvv(^^^booß⹟;w®Lœ8±Ý1u4‘»¦@ˆˆº ºº:A£ÑÀÃÃ6X§|ñâEØÚÚì QSSƒ²²2 0ÀàF 'OžÄŸþ‰¡C‡ÂÓÓ.\€££#œœœÚŒU[[‹K—.)±ÚŠ­Ñh "èÛ·/jkkqôèQTVVbðàÁðõõ5Sii),--¡V«qëÖ­6?dèíím°VY£Ñ °°·oßFŸ>}ø³è©©©HOO7X«}þüy8;;ÃÆÆùùùÐét m±þø{©K`` Á6}÷ÒëõÐh4°µµ5¸ÁËåË—qæÌTWWÃÓÓO<ñ„²„¥©© ÇGii)<<<l°¼åúõëèÝ»78€vÇHDÔ‘˜@ýËÕÕÕÁÛÛ™™™=z´Q×êõz„‡‡£©© ?üðÃCêaë’’’°ÿ~ÞÊ›ˆ:h""BUUÌÍÍáààpß×$%%aõêÕÐëõÈÍÍm1‹þ°UVVÂÚÚöööÚ.h""z ÅÅŨªªBHHH«w2$"úÅšˆˆˆˆÈÜÆŽˆˆˆˆÈL ‰ˆˆˆˆŒÀšˆˆˆˆÈL ‰ˆˆˆˆŒÀšˆˆˆˆÈL ‰ˆˆˆˆŒÀšˆˆˆˆÈL ‰ˆˆˆˆŒÀšˆˆˆˆÈÿXö¸OI°ÂIEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/filesizes-chunksize-15GB.svg000066400000000000000000003701641416254111300261210ustar00rootroot00000000000000 image/svg+xml Automaticchunksize PyTables-3.7.0/doc/source/usersguide/images/indexes-sizes2.png000066400000000000000000001671711416254111300243320ustar00rootroot00000000000000‰PNG  IHDRÐß}™SsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìwTTW÷÷¿CQ@R”.M@D°"(VtĆ[Œ‰Æ$F£>1ÑD5ÖØ±DcAEeÔ`;"ХТ€ ˆt„Áöûï½ÃÁQŸür?kÍZ°OÛ·œs÷=wŸ}DDàááááááááááQ¥­Ï? Þ€æááááááááái¼ÝHòòò°oß>üôÓOøöÛo±råJ=z/_¾ää €ŽŽrrr>‚¦M'//Ÿ}öìììЦMèèè ººúƒêddd¼·6¢££¡££ƒE‹½·6Þ7b±‹/†³³3ôõõ¡££ƒ¸¸¸®Ç£GpòäI„„„ 55õëËÈÈÀÙ³g±zõjÌ;6lÀ©S§PXXØ ÚþßåŸ:æ|H~úé'èèèàÊ•+ —‰‹‹ƒŽŽ¾þúë÷¨Y-®®®°²²’’CGG;wîä䎎ÆÀann]]]øûûKé=dÈXXX@WWC† yïúóp™:u*tttðøñã÷ÖÆ¬Y³ ££ƒ„„„÷ÖFcزe tttpôèÑ­Ê{Cåc+ðObݺuøùçŸQVVÆIØ»w/&MšÄÊÊÊÊP\\Œš›ù¨Q£pãÆ xxxÀ××ÊÊÊT‡×¯_£¸¸555ï­ªª*£¢¢â½µñ¾Y°`6oÞ Œ=ÐÓÓû m×ÔÔ`À€¸wïž”a»víZØÚÚ6©Î’’Ì›7üñ‡ÌtMMMbñâÅ011iRÿ—ù§Ž9’ŠŠ £ªªŠ•UUU¡gÏžèØ±#vïÞÍ)ó!ÇŠ’’”””HÉÞ¼yƒââbTVVJÉsrràãリª*øøøÀÒÒvvvj'B¼½½!‹áíí kkkŽaþObáÂ…¸víN:CC펛7oÆÁƒ±eËtíÚ•“þ!žgååå(..þ`“]áááøá‡0sæLLž<™“.‹Q\\ ‰DòAôùð´‚9rß}÷LLL°cÇx{{C[[OŸ>Ž{÷ ±X,UfáÂ…˜8q"Ú´ió‘´n<Ïž=Ãõë×áîîŽ[·n}4=æÍ›‡1cÆÀÀÀà£éðOàСChÙ²%âãã¡¢òa»sMM ÂÃÃaee…þýû#;;M®/==}úôÁ³gÏÐ¥KÌ›7h×®ÒÒÒpÿþ}ìß¿;vì@=ØŒGÃóoa„ puu…³³3+«©©ATTÔÿì‹Gß¾}qøðatéÒEJ~öìY”””`éÒ¥X²d‰TÚ¹sçPXXˆï¿ÿ+V¬øê¾RSSÅy‰ø_ ##QQQ(..–™>gÎøûûÃÈÈè½é0sæL 8fffï­ºäçç#** Ç—™îçç‡víÚÁÓÓóƒèó1à hÙ¾};`Ïž=ðõõeåööö°··Çĉ9ƒo¯^½>¨ŽÍAvv6 sçÎU=z|Ôöÿ TVV"??ÜxB[[ðã?¾“ýå—_âÙ³gøæ›o°víZ©cÒÓÓƒ‡‡>ûì3ìÝ»÷ƒÍ²óüߣS§NèÔ©ÓÇV£QXXXÀ‚#oh¼þ_Ëy€îÝ»£{÷îïµ www¸»»¿×6ƒ­­m“¿DþSà} $11¼!ê»9,_¾B¡ùùù¬l„  …rŒ¡^—={ö`øðá°¶¶†½½=ÆŽ‹{÷îÉÔáÈ‘#èׯ,--Ѷm[ØÚÚÂßßçÎ{ë1Ž7óçÏ\¸pÕéСClžššlÞ¼ƒ ‚©©)ºvíŠO?ýT¦o×õë×! qêÔ)®YYYøüóÏÑ­[7´oß ÀÚµkþT{óæM…Bœ8qBfú˜1c0{öl)Ypp0„B!bbbÀÀ@tèо¾¾Øºu+›/::³fÍ‚ƒƒœñã?rô’H$ …X¸p!ªªªðË/¿ÀËË ¦¦¦èׯ.]º¤Ðqµ/Y'NäÈ/^ ¡Pˆ={öHÉsrr8ýsß¾} …ˆDDD`ôèѤû’P(ÄáÇ9mbÞ¼yèÖ­,,,0|øð&ù†††bèС033C¿~ý°bÅ ¹×4<<B¡ÿý7 )) B¡!!!€+V°:_¼xB¡û÷ïPëRŤÕ#«««±~ýz <æææèÔ©&Ož,s,?pà„B!bcc)S¦ÀÁÁnnnRùvïÞÍ>«0vìXÄÄÄpê;wî„B!.]º„ääd|öÙgppp€½½=¦Nм¼<©üB¡QQQ€3f°ÇSÿ¾mˆÝ»wÃÏÏæææpqqÁäÉ“e^·¸¸8öüedd`Ö¬Ypqq½½=¦L™ÂfΜɎK–,au9r$›gÍš5 …xþü9+ËÈÈ€P(ÄÆQXXˆ¹sç¢[·nprrÂÂ… Ùu ÅÅÅXºt)¼¼¼`aaQ£F!33“£÷¦M›  ñôéS)ݲ7V¯^Íæ}öìV­Z…þýû£C‡011A=°hÑ"ÎózýúõX»v-€ÿÞÌ/== ‰  qíÚ5Ž®ŠŽÅuÏiYYŽ;˜™™ÁÃÃkÖ¬‘铘˜ˆ)S¦ÀÑѰ°°@Ïž=ñóÏ?sò¾ ¼­ íÚµ)còmDFFB$I¹v£¨¨ˆó‹‹‹ƒH$bu v†qذaøôÓO‘˜˜OOOtêÔ aaaðôôDpp°T{kÖ¬A@@îÝ»+++…BØÛÛãöíÛ 9ò‹Åb¼yó@íà*‹!‹Ù›Z,ÃÇÇ_ý5RSSÑ·o_èêêbÿþýpqqÁÙ³g¥ê{öìD"þþûoxyy!44FFF011y«/XTTD"ÊËËYYQQD"ÂÂÂàããƒ'NÀØØååå8sæ ºuëÆ1pß¼y___,]ºyyyèÝ»7òòò0|øpüõ×_2Û.**Bß¾}ñÍ7ßàéÓ§èÓ§:tè‘HWWW©—‘!C†àéÓ§˜?>ç%eêÔ©8vìzöì©¡YTT„îÝ»cáÂ…ÈÌÌD¿~ý ©©‰;vÀÉÉIj ª¬¬d﫚šöZ1ׯ!fÏžC‡±‹Šœœœ ¡Pˆï¿ÿþ­åߌa0gΨ««7ºüµk× ‰°|ùrLŸ>999èÔ©”••Ô¾Ì9;;cÇŽÐÔÔD¿~ý™™‰ÿüç?èÞ½»”·ššD"ǰ۽{7D"6mÚ$å ›––†püVåñàÁ899aÙ²eÈÉÉAß¾}áî¬,Îù¿|ù2œñÇ@KK ýúõÓ'O0þ|ôìÙS¡6™~˜––&3ýôéÓ—’%%%A$!$$ƒ ÂÓ§OѾ}{„……aöìÙØ²e ±X¬;Guu5†ŽéÓ§#..½zõ‚±±1Ž9777|8‰ÅbV~âÄ €æÌ™#UföìÙ€~ýõWª©©aå?&CCC233“:þÔÔTÒÒÒ"}}}öZoܸ‘Ј#>æùóçš8q¢Ôõ>rä ²±±‘’—––êÙ³§Âm…‡‡K "¢‚‚ruu%JMMmT} ?üð µk×6ºl¯^½?¾Im»»»244¤˜˜©4‰DB¶¶¶$($$DJHhÞ¼y¬<--ÐøñãYYyy9©««“¥¥% ‹/²iÛ·o'´cÇŽ·êY]]M]»v%´~ýzNztt4ûwee%YYY‘’’ýõ×_¬üÍ›7@èûï¿—*/kÌ9|ø0 ß~ûM¦Nêêêdgg'%ûþûï ©¨¨Hkrr2µjÕŠ444ÈÄÄ„N:Ŧ=~ü˜Ú¶mKÊÊÊôäÉV^QQÁöÛAƒQII ÕÔÔЮ]»uéÒ¥¡ÓƲoß>@[¶lae7oÞ$diiIÊÊÊTTTĦMš4‰ÐƒXÓÏêWee% nݺÉl÷îÝ»ì1Ìž=[jìùî»ïMž}: 5kÖHq>$===²¶¶¦ÊÊJV¾páB@€‚ƒƒ9õ-[¶ŒKëŽQ999Ô¡CjÓ¦ åçç³rf\@»wïfå4pà@@[·n•jcøðသ>}Êi¿!˜{¬OŸ>ì½GT{ßhii‘¶¶¶ÔsôÂ… ¬n_|ñ…ÔskÓ¦M€†*ÕÆ¼yó………ÉÔaôèÑ€>|ÈÊâã㥞gŒ-Q\\Ìöc š2e ½y󆈈^¿~McÇŽe¯]]&OžL(..®ÁóñâÅ 233# ŠŒŒdå ”™™ÉÉ¿bÅ ™cÍ‘#G­X±Bf;kÖ¬!R÷KcÇb"¢¾}û211¡¤¤$Vž˜˜H­[·&UUUÊÊÊbåL¬ÿ¥¤¤È;-M‚7 ¤¦¦†–/_NìM€ôôôhòäÉ”––Æ)£ˆ]XXHvvvœUII ijj’•••Ô€ÄÀ<7nÜHDµFrëÖ­ÉÖÖVj@l,ò èššÒÑÑ!555™Ç3eÊ@Û¶mceŒíèèØ(㙨aÚÐÐPÊ€%ªøƒƒƒ”Ü‚ÓÁ„B!Ç€ÎÈÈ eeeòðð©×ï¿ÿNèÈ‘#RòêÝ»7ݹs‡ÔÕÕÉÌÌŒ :Þ²²2RWW§V­ZÉ,3lØ0 5è4Õ€–ÇÉ“' 5©ü»ÐmÛ¶å<`RRRèÏ?ÿ”úÕ7´zûöíœòGe_¢êSXXH­Zµ"uuu*--e妦¦Ô¶m[öÿ‹/Ú»w/)++Ó‚ Ø4æá¨ÈKê±cÇd>€eLhôèÑœ´W¯^‘¦¦&µhÑBª/4·ýÅ_pòûúúÊÕk̘1€Nž<ÉÊZII‰Ó+++ISS“”••œŒ`ÈÊÊ"$ YÙÏ?ÿL€öïßO¤Œz©ëHôn´‘‘çå3%%…çañâÅrûÉ_ýõÞ è‡’’’õéÓG¦~ÌD‰H$beŒÀÉ_TTD-Z´ :ȼ†Ìs`óæÍ¬Œ1 ÈÉÏ\ÇiÓ¦IÉ›j@[ZZ’@ zYa`Œ­eË–±2Æ€nÓ¦ •••qʸººJNNfeïb@qžg{öì!¤¥¥%õBHT;ù€ÆŽ+%WÄ€.//'777œgXCèëës^r›b@7e,f è?þøƒS†y.Ö½ÿg̘AèÎ; _Sá]8D `Ñ¢EÈÉÉÁ¡C‡ðé§ŸÂÞÞùùùØ·o:uꤟq]$ üýý‘’’‚mÛ¶¡ÿþlZRRÊËËáì쌄„ÄÅÅIýZ´hHNN(++c̘1HMMÅ!CpðàA…ýnáÙ³g(**býfë3bÄÿõ¯Kß¾}¡¤Ô|·Z×®]¡¡¡!%ëСÚµk'ÿ¶¬¬ Ož<‹‹ ë‚S—aÆqd±±±¨®®†££#çœÇÅÅ¡U«Vþ{Þ&Nœˆ©S§âúõëèÓ§jjj…ŽéÑ£G¨¬¬„———Ì2 ߦžžŽõë×cΜ9?~<Xÿû'Ož4K¹?d¹ö„‡‡cêÔ©R¿º¾{uñññáÈ?G¡PÈIÓÑÑ——*++ñèÑ#VîííÜÜ\¶lxx8”••! Ñ¥Kö7áÊ•+033S(DãðÖ¼ é­§§‡^½z¡¢¢‚ý¬ÿ>µð‰Yà+k¡/#«ï³æææœ~¨¦¦†ž={¢ººZf,ýú´k×666¸zõ*ëê'''Œ9jjjìµyøð!²³³áííýÖzÅÍÍãbdkk ccc…co3~È~~~œ´¡C‡¾÷¡111¨©©‘;Æ1nõÇ8@vÿJJJBEEœñàÁƒ·>«êÒ³gOŽŒ¹^Í˼¼¼Ož< 8éo{nµlÙ’#gžòÖÜ4–Î;sžgL?rttä¸ÿ5ÔÇ‚ˆ0qâDܽ{Ë—/ǘ1c8yÄb1<ˆ~ø“'OF@@P]]Ý,Ï…¦ŒÅ ŠÞ+ãÇP»¦kÅŠxøðá;ë-> G#ÑÕÕŸqã0nÜ8µþ9K—.EXX¦OŸŽôôt…}8?ÿüs\¹róçÏç,@b|üNž<‰“'OÊ­£î‚üb±GŽÁ¹sç ¬¬Œ=z`òäɘ:uê; ÌÌÂSSS™éæææRùêÒÜ¡{ä…¶ÓÕÕEAAû?snŒeæ—î‡9ïüñ‡ÜXÄuë®ËºuëpèÐ!TTTà›o¾‡‡‡Üòõy—óÛXþóŸÿ`Íš5KKKèééACCƒ](RTTôÎm4<þéé霅ºB¡}ø¥¥¥aÆŒrë‘u¯5æÜº¸¸¨5öîÝ‹°°08::",, nnnÐÖÖ†V­Z…‚‚deeáÕ«WrC9Õ‡Ì---ßš·1zË2š}}}ŽŒyÁ“Õ™4Y¾ø õ[yedáãム  Ü»w;vÄíÛ·ñÕW_ASSžžž ÖîׯŸBõ*BCÇ è¦OŒ!"k\RWW‡¡¡á{ËŒq[·n•ZZYcœ¬þÅÔwüøq?~¼QõÉ:Ÿ½"==DÔ¤qõmÏæšhhÎ>Ö ,À‰'0mÚ4™k]¢¢¢àç燼¼<ÀØØ­[·†@ @UUÕ[þ+BSÆbEï•Þ½{cùò娲e ~øáüðÃìš°ÿüç?2ÏwSá èw¤gÏžøóÏ?annŽœœ$%%)6hÅŠøóÏ?1räH¬ZµŠ“®¥¥ vuÿÌ™3åÖSw¶ÒÌÌ ÀªU«‚ãÇãÆ¸~ý:®\¹‚4ákaÞ‚em"€]`кukNÚDZ€-–×ñe sÞ¿ýö[™oè Ÿ|ò GöÓO?± G>Œ… *üòð.ç·1„††bÕªUèÓ§>,¥ßÍ›7?ZèE\»v 4hTš±±1û0{Ûñ˺ךrn™™ððpLš4 ±±±ìŽ•>>>X¹r%®\¹ÂM²fædÁè¢Èâ¿æ¾'HFŒc"ú໌¾+ÞÞÞ Bxx8òóó!‘HX#ÙÇÇK–,ANNk@+zm>uÇ%f¼©KYYY“Ò* Ó&¹FmÛ¶åÈdõ/¦¾)S¦àóÏ?—[Ÿ¢_ãš“wéCo{n0×ñŸÀÎ;±víZx{{ËŒôED9r$ÊËËqþüyøúúJM¸ÙÙÙ5j‘¬<>ÄsŽñøî»ïpêÔ)ˆD"ˆD"¬[·Dlll³mÄÃÐÍ@»víàèèˆøøx…fïŽ9‚üݺuÃdÎ ;::rss5“ ÔsçÎÅܹsÙ7¹àà`¬ZµJî[õÛ°¶¶ û3\]y‡šTÿû ]»vPUU•;k[w%5sÞ uÞOž<‰Í›7ÃÓÓÓ¦MÃgŸ}†‰'âÒ¥K ¹¯|¨ó{õêUµ¡ê÷ÍåÒ„B!víÚ… 6àË/¿„¦¦f³Õ]÷ÜÊrÛ‘unagg‡k×®áâÅ‹¨©©a ±=z@]]aaaÈÊÊ…Ýìíí÷ïßÇ€Ö{àÀ é- &f¶¬û=33Sfè¨ÿeúöí @€°°0¼zõ ªªªèÝ»7€Úë°dÉ\ºt W¯^…•••BK0}ôCœ KKKܼy?æ©999(--}¯43Æ5úÙÒP}MyV5†¦\£víÚACC>Duu5•‡¡¡>ô¶çFݯHòþi,/^Ä—_~ {{{?~ªªªœ}:qùòe…Ãæ˜ššÂÞÞwîÜAll¬TZUUvìØ%%¥wþÍ„ëªï7[SSƒ-[¶¼SÝïÂàÁƒáï|õÕWœ]=ß…þýûCII üñg¶õîÝ»ˆŠŠ‚ç³¢··7JKK±jÕ*hjj²þÀèÞ½;.^¼ˆëׯÃÉÉIæl,† UUUlܸQ*D£,˜Y ]»vq|Ã#"" ''§·¾3pÔï·dn[ý¿Žžž:uê„[·náï¿ÿ†‡‡ë«êîî---üþûïÈÏÏW¸¿¨¨¨à“O>AJJÊ{Ÿ‘g^†¶mÛÆI“%kn<==ahhˆ4‹ÿ|‡бcG\¸p·oßn eÃÜçyÑèß¿?òóóeº—0cž¬çÖõë×9m•”” 88:::R/ MÑíC€Ñ£GCWWÿý·Ü¯òž $ÓHnÊ17u,~W455Ù~×®( ¼­ #FŒ@§N°yóf\»v ÙÙÙHIIÁÞ½{áããƒÊÊJL:•õÉ‘EAA†‰D‚åË—ãùóçœLÌD%%%ÖÀóóóî]»••±XŒGáĉ>|8«6##^^^8xð ž>>‹ÅHMM•2’UTTлwo¶6æÚ¸¹¹¡¼¼_ý5NŸ>«W¯ÊôÛ}WÆŽ GGGv±Vnn. °qãFlذ¡Y¿¾È¢U«VذaÊËËáåå…cÇŽáùóç(//GJJ ‚ƒƒáíí­ð‚²²2vîÜ "ÂСCñǰϪ´´4?~Æ ãÄ–n,̦-kÖ¬Á¾}ûpõêU©8Æòøí·ß ¬¬ŒéÓ§#88ùùùxøð!¦L™‚ððptïÞ]LX :ׯ_Gee%F·Ý»wcûöí¸zõªBãþûD,cèС())Á²eËP\\̱7˜ûÛÎÎúúúˆŠŠÂš5k““ƒ/^`íÚµX²d‰LÃÛÁÁ-[¶Ä¥K—°zõj„‡‡ãêÕ«rÝ3€¦ÅaüøñXºt)âããQVV†¢¢"œ?žu•mÎ5|; à„°c~jjj4}útN(šú!¥’““e–¯û«6*<<œ¬­­eæÕÓÓ£¨¨("ª!ª®®Î¦11޹¹9ÅÇÇ+tœ Å&ª Ã¥££ÃÑeôèÑœðkLø¢Õ«W+Ôv] cW?¼ƒ““ijjÊÔYSSSê¼899ÑéÓ§9aìNž}Œ6˜˜äääÀÞÞ º@DFF¢²²ÎÎÎhÓ¦ +gÎ=©{´1<}úÑÑÑHII––lmmáíí-ó>ŠEll,ž?ö3|}ê9uÉËËÃ… ‘‘v‡Ìëׯ£E‹RÛ2?yòœãjûRZZ8+ãsss‘œœ kkk6d]MM ®_¿mmm™ ¬“’’ðòåK¹}H̘Ô.ä®;¾àþýûPSS“†/==™™™èÔ©ç‹aMM 233‘““ƒ7oÞÀÜÜæææ(--Ž{÷äö»w²Rf¨­†ŽáÚµkˆ…µµ5zôè###DEE¡ººZJ÷/^ %%…3Ž4t222ðäÉ8::Ê|[[[γ“¹_tuueŽ{yyyÈÊÊBII Z¶lÉÙN\………¸uës»»»³n›¯_¿ÆÝ»wahh;;;©z$ """ ¯¯Ïú˜3®]»†Ö­[Ké’’’‚/^ÀÍÍ -[¶dëhˆúãó³gÏм¼<899ÁÝÝmÛ¶ELL JKKѧONåååÈÈÈ@^^jjjеkWhii!++‹$+‚†¢c1óŒ©ßÏÿŽ7666Re“““ììlTWWÃÔÔ¾¾¾ »Ú) o@óððððððð€k@óðȃ÷æááááááááái¼ÍÃÃÃÃÃÃÃÃÃÓø8Ð<<<<<<<<¨.¤Ð†h<ÿnxhžFÀ»pðððððððððð4Þ€æááááááááái¼ÍÃÃÃÃÃÃÃÃÃÓxš‡‡‡‡‡‡‡‡‡§ðQ8xþµœ?8vìú÷ïÏÊÏœ9ƒ×¯_³ÿ«¨¨°;3ÊÛÑKBBB•¿G ïàhll cccDGG+”?..^^^˜4i6mÚÔ(½š›””ÄÅűÿ èêê–––R;˜}hJJJpéÒ%¤¦¦âÕ«W055…››zôèÑä:=z„ ²²;w–¹«æÿ HHH`ÿhÓ¦ ,--aaa%%~.¦>W®\A||<222вeKÂÝÝ®®® ÞÓ<@||<ЦM8;;£K—.øä“Odæòä ¢¢¢`mm-sGØÆréÒ%v×KCCCtêÔ lT¯_¿Æ;w‹ÜÜ\TWWÃÌÌ ½{÷–¹» "”——#66Ïž=aìØ±2w}ñâ\\\ðÕW_á‡~hR[<< ѬƒóðüC¨ªª"ruu夵oßžp~JJJHÏŸ?ot{ÕÕÕ2ëlèwôèQ…ëoÑ¢ÙØØ(œÿîÝ»€¦OŸÞècinV­Z%÷ØÚÚÒ™3g>Š^sçÎ%uuu™zùøøÐË—/®«ººšúõëGºººRõ¬]»ö=AóñÓO?ɽF;v¤ .|=V®\Iîî˜øAÚk ?&OOO¹çËÚÚZf¹¼¼<:t¨Ì2ššš´sçN™åþüóO@_|ñÅ;éýêÕ+¹z»¹¹Ñ‹/®«{÷î2ë4nÜ8ª¬¬T¸.‘HDNNN¤¬¬,UWUU•Ü2³gϦ–-[RNNŽÂíðð4~šç_Ipp0’’’pôèQ¹yBBB ­­ ±XŒsçÎáøñã8pà222påÊ•Fͺ)))áðáÃùÂ… ‘™™‰ßÿ†††RiŠÐÿÆÀÀ@µ³pGEtt4FŒÛ·o£K—.TŸØØX888`æÌ™pqqA›6m ‰°k×.„‡‡#00çÏŸW¨®šš„‡‡ÃÊÊ ýû÷Gvv6"""Þó4?S§NŘ1cÔ~Å8räâââ0lØ0DGGÃÑÑñ½¶ŸžžŽ¨¨(©/DÿKˆÅbøûû#>>S§NÅœ9s`mmÒÒR<|øgÏžEXX§\ZZúôéƒçÏŸÃÏÏ_}õºv튼¼yòdDFFÂÛÛß}÷zõê…›7oâ÷ßÇÅ‹1qâD\ºtI¡ºúô郑#GÂÃÃíÛ·G^^.^¼ˆíÛ·ãðáÃÐÓÓÃæÍ›ªëáÇxüø1;{¿sçN¼yó¦Á2ÿùϰmÛ6üúë¯Øºu«Bíðð4šmÁóð| ºuëF:::$‹9iÌ týÆÇ“@ Þ,z899zøðá;ÕóazÉ’%œ´!C†1bÄ×+%%E¦<11‘{üø±Âõ±ÿðÃÿÈèß~û“Ö·o_@&Lxïz|öÙg€îܹóÞÛj —/_&Ô³gO¹yjjj8²Áƒš={¶Ìôôôt233#tîÜ9©´æ˜.//'À™Ýôè RRR¢×¯_7¹ "¢£G222R¸Lii)UWW³ÿ·lÙò­3ÐDDÞÞÞÔªU+*--m²¾<< Á;®ñüëxðàîܹ¨««+\ÎÒÒ^^^lååå9r$fÍš%·Ì®]»  ©p;D„ÂÅņ††prr¨Q£Ó`Ùªª*üúë¯ðöö†™™üüüž%­ËîÝ»1|øpX[[ÃÁÁcÇŽ•ÛvHH|||`ii‰¶mÛÂÎÎþþþMj·>Ó¦MP{¾ëʦL™ظq#† ccc¬[·ŽÍ“ššŠ©S§ÂÕÕfff2d¶oßjľQò|“гgO@RR’Âõikk+œW—/_†P(Dpp°Ü<Ó§OÇèÑ£QYY  vöûÀèÛ·/,,,Øk4zôh„‡‡¿³N²®„‡‡c̘1°³³ƒ F…ÐÐP™u$$$`Ò¤IèØ±# `ii‰^½zá—_~aóLž</^ÌŸ?B¡B¡ýjÁ••…)S¦ÀÞÞ:uÂ_|Ç#$$B¡±±±lÞââb…B,]ºøñÇáíí \¸pÍ÷èÑ#LŸ>îîîh×®¼½½±víZTWWKµ˜˜°±±‘{¾êûí^¼x¡¡¡hÛ¶-V¯^-Ó¯×ÂÂË–-Ì;—Óî»òæÍ|òÉ'°··—J³²²‚‰‰ ”””Þ:óû6|}}¡¤¤‰D¢p_ÔÒÒj’ýرcQZZŠ#GŽ4º,"ð4Ï¿æ3d¯^½]VMM @­Q¢©© ‰D‚   ©Ep UUUøùçŸqåÊ8;;+ÜFEEƇk×®¡}ûö …hß¾=NŸ>nݺÉu;©ªªÂøñã±yóf´mÛ...¸|ù2†Š={ö(Ôvee%üüü0}út$%%ÁÓÓNNN ƒ‡‡:$•ÕªU7nbbb`ee¡P;;;DFFâØ±c ³<êžo†óçÏ#44_~ù%¾ÿþ{ÃÅÅ…Íó×_ÁÕÕ„úôéƒÄÄDÌœ9ƒ ‚D"yg½^¼x Ö¸ø899!44+V¬™~ïÞ=ìÞ½b±˜}9\ºt)&Mš„û÷ïÃÆÆB¡¶¶¶¸~ý:N:õÎ:ɺFK—.EÿþýqñâEtìØ...¸rå † ‚ H•ONN†§§'BBB ¡¡???xxxàõë×RÇYYYÉŽ•••‹Å‹Åì‹PkÀº¹¹aÿþý022‚››îÝ»///œ={"‘/_¾”ªS$!,, ~~~ضmTTTàèèÈÞ'ÇŽƒ‹‹ ‚ƒƒ¡¯¯Aƒ¡¸¸óçχ···ÔýÔ®];€H$RØÍ„¹_~ù%Z´h!7ß„ `dd„ääd¤¦¦*T·¢hkk£_¿~ÈËËã¸i\¿~Ïž=ƒ——tttÞ©}ûö¡¦¦þþþ2_š“>}ú@³¼ÈóðÈä#Ï€óð|p„B! „„™éò\8233III‰°‹¦BCC Íœ9“Sω'ä¦1ÈráH$Æù”›ššJúúúdbbBåååRi-Z´ äääD¹¹¹¬<66–Ú´iC­[·¦üü|V.Ï…cÙ²e€æÌ™C‰„•çääP‡¨M›6RõPË–-9 +% =zôHîq×¥!Ž#FòóóceFFFìÃú.EEE¤¯¯O-Z´ Ë—/³ò²²2òõõ%´qãF…ô’ǹsçÙÛÛK}Zn ïâÂ1|øp¹n ³gÏ&tüøq"ªuhݺ5ikkK]7"¢7oÞ(ì‚Ò Ç€;–ˆjï9%%%255•º233ÉÚÚšPDD+Ÿ3g ;vpê®ïFó6Žþýû –’ÿøã¬ÛÍùóçYynn.+ïß¿?J•{þü9µjÕŠÌÌÌ8nVK–,!´~ýzVVZZʺZ´jÕŠ&MšD§N’ê“õñöö&$‰ä機÷äÉ“¬¬¹FEE‘ ÑܹséôéÓ4oÞ<211!KKK©k¦(×®]£Ã‡ÓêÕ«iÀ€¤¡¡A>>>r}ª¢.DDmÚ´!}}ý&·ÅÃÓ¼Íó¯ÃÙÙ™Hù¤Ö…1 CCC)22’®\¹B ,`Œnnn¬qYSSC–––Ôºuk*++“ªgàÀ€âããåêÒXhÆðº{÷®”œ1 CCC9e£xåÊ•¬L–]TTD-Z´ :È|8ù„”””èÖ­[M®ç] è“'OúòË/¥ä•••¤§§Gúúúl”ƒòòrÒÐÐm¦10ô¬Y³ØkD^^^$HEE…½'íÛ·Sã;tèPV6mÚ4@111oÕ£!úþýû€úôéÃI{óæ µk×N®­ªªJOŸ>å”c^Hê¬ ‰„LLLÈÂÂBJž@nnnœèîîîÄy1fôR$²s®ê¾È4—MDôðáC²²²’ÒÝÌÌŒ’’’šTß Aƒ¤êrvv¦ØØØwÒ±143¾ÊëyxÞÞ…ƒç_ÇË—/¡¬¬ŒÖ­[7˜oðàÁðôôDß¾}±zõjdddÀÏÏÇŽƒŠJm@€/¾ø%%%R¾vOŸ>ÅÅ‹áááÑ(÷ºÜ¹s¿ýöfÍš…qãÆ! €õ}òä '¿ªª* À‘6 ×Gµ>III¨¨¨€³³3Ç ÞÞÞœ:˜XÅB¡“fmm '''¼xñÖ¯°°ƒF^^~ûí7xzz6ºŽæ`È!Ð××GHHˆ”Oê™3gŸŸñãdzn-Z´€P(DLL „B!Ž9‚ÒÒÒ&·½mÛ6öÍœ9W¯^…©©)BBBØÄ ]ƒáÇCII‰õj£¯µ>«+W®DZZZ“tcüÑûöíËISUUEï޽喵¶¶†™™GÍú×ï 077ÇÓ§OQQQÁ–騱#¢¢¢¥K—bðàÁÐÐÐ@TTfΜ‰Hù03þ½u]`äÁ䩪ªzkÞÆ GGGˆÅb¬X±‡ÂÊ•+Q]] ggg…ÝÀêòË/¿àܹs8zô(/^Œ¬¬,tíÚûöíkvýeѦMrÛáái.ø0v<ÿ:B X6mÚ„V­ZAYYÆÆÆì¦õ™6m/^Œ;w² ªvíÚ…šš|þùçÖO"‘ÀßßgÏž…ºº:,--¡«« UUU”””ŠŠŠ8å e.¶a YFw]¿ÊãÇãøñãró=}ú”ý{ñâŨ¬¬ÄÑ£G eeeôìÙ“'OÆ”)Såç8lØ0Œ1ÚÚÚ055…ƒƒ4448yuuue.}üø1ÀÔÔTfæææˆÇãÇÙ‡«"”––bàÀˆ‹‹Ã¢E‹0þ|…Ë67ªªª?~<6mÚ„³gÏÂßß°wï^µ¡æê²lÙ2ÔÔÔàĉ‰DPUUE¯^½0uêTLœ8±Qm=ƒ†@ €ŽŽLMMѱcGÖ`j¯¶¶¶ÌT555!##UUUPQQ··7~ùålݺ‹-¢E‹`mm#F`áÂ…ÐÓÓSH·ŒŒ à„ƒd`ü“eadd$Sžšš "â¼ÀÕ'33SjÑ©@ @—.]ØÐ‹………سg–,Y‚K—.aÇŽìâc[[[dffâÑ£Go Èô=“ó5–gÏžá³Ï>ƒ¶¶6âââ¤6Œš>}:œœœ0sæLv!ª¢Ô =9zôhL˜0]»vÅ—_~‰¢mÛ¶ÍzõaÆù÷íoÍóï„7 yþuàùóç(**‚®®®Ü|rwÿª‹žžÆŒƒàþýûpppÀž={ ££ƒ±cÇ6Z¿•+WâìÙ³øâ‹/°zõj´jÕŠMÛ°a¾ýö[™åä-Z*++©zd¡¥¥˜2eJƒ†Ý…DæææƪU«‚ãÇãÚµk¸ví®^½Ú¨™&6ºÆÛ`¾Ô‡‰tÁs}˜Ù×·}}¨Ëëׯ1xð`ܹssçÎÅòåË.û¾˜2e 6mÚ„}ûöÁßß¹¹¹8þ<:uꩼ:tÀ‘#G™™É^£Ë—/ãòåˈˆˆ@PPÂívéÒå­×H[[ùùù¨©©‘ùBWZZ MMM©¯8?þø#,X€“'OâÔ©S8sæ Ö¬Yƒƒ"66oÕ1úd½\h𫃼ûIKK ¸|ùrƒF˜¼6]]]Ì›7)))øã?péÒ%Ö€¶±±Á¥K—دòH$ì,{ÇŽl¯±\¸pb±œÝVõõõ1zôhlÞ¼/^lÒ¤ƒ­­-úõë‡S§NáöíÛ>|ø»ªÞ Ì5Wäþáái,¼ Ï¿kkkµ³.Íó0ܵkNŸ>/^ 00°ÁUõò¸zõ*~ûí7ŽÑ[÷Ów} PXXÈ‘§¤¤¨ Ã×ÌìWnn.<<<äþììì8eMLL0oÞ<ܺu iiihÙ²%öïßÏF«øP0×–q3©KMM RSS¡¬¬¬ð,ZEE†Š›7oböìÙR¡ò>&;w†³³3Î;‡¼¼<ŒÜÜ\ 2999R!ûƒ\– Ež«RÝðuŠÂ¸4èêê6Ø'íçƒ  mä3Ó5¹cÿþýxùò%:vì77·FKC0í¶lÙRf:#¯ëªò®Ôýjñ> "<{ö mÛ¶mÔ 3¢ð4Ï¿&|Ý;wš­NtîÜÁÁÁظq#4y¦F,ƒˆ8./_¾DHHHƒeeÍ&nÛ¶ 0pàÀËvèÐ;vÄ… pûöíFjý_¬­­Y¿ï½cã¾cÇNÚ©S§¾}û*ôð‹Å6l®^½Š3f`Ó¦Mo-óâŠܾ}éééW¾‘L™2‰‡ÆÞ½{¡ªªŠ &(TÖÁÁ}*//oV½º[¶l‘Ê#–-[²÷kÝ{ÈØØ€ìÉîÝ»ÃÂÂÇŽãœÿ°°0Ü»w¯GQˈ#?ÿü³Bù=z„üü|¹é̆Î;³²Þ½{cÔ¨Qxõê¾ýö[™1žSSSY–,Y¢ öŠãää 6Îxýö«««ÙÐvRiQQQ¸}û¶Ô MC¾Ü¸zõ*ÀÕÕU*-66·oßn¶û199EEElìþæB,#''§Yëäù‡òQ—0òð|˜ä¦L™"3]^»·±sçNvµyC;‘ÕEVŽ êׯݼy“ÊËËéÊ•+dopR> IDAToO€vîÜ)UO‹-¨eË–¤¥¥EAAATXXH9994wî\6rH]ä…±‹ˆˆ @@zzz´k×.ÊÌÌ¤ŠŠ zøð!ýõ×_äççGaaaDT»;š——¯_¿&cccRRR¢_~ù…²²²(;;›V¬XAJJJ¤§§'a̘1´lÙ2ЧÒÒR*(( ÐÐP255%@ í„ Ù¹sgÚ¹s']¹r…n޼ɦ‡„„úä“OhÍš5$‰hñâÅÔªU+rww—…Ã××Wæ±ÔÔÔP÷îÝÙ0}qqqTVVF¹¹¹Ißÿ½Ô8D-Z´ 3fÐñãÇ)!!²³³éÆ4cÆ @ZZZœ¨OŸ>e£_ôíÛ—Ž=JéééEëׯ'mmm@sçÎåèÈDáèÕ«—Ì{4((Hê˜eñæÍruu%äïïO111TPP@±±±4jÔ(@ŽŽŽœ[••• ½zõŠ•EEEQçÎiÓ¦MFÉÉÉtëÖ-úý÷ßÉÄĄзß~ËÑwïß¿/%/((:uuu@[·neeõÃh2×ýùçŸ {c9þ_ý5 6¬Yëäùg" jÄÞ¶<<ÿGF`` :„qãÆI¥Ý¾}b±=zô€ªªªÂuJ$´o߉ÙÙÙ2£GÔ'::eeepww—ò£¬®®ÆéÓ§‘œœ ¸¹¹ÁÉÉ /_¾DJJ lmm¥"ܸqêêêèÖ­rrrpóæM¤§§ÃÕÕ½{÷æèRZZŠ{÷îÁØØXæ¶Ãb±·oßFrr2^¾| ###ØÙÙ¡GPVVæäKIIÁ‹/ ££ 0@*_CdeeáñãÇ077‡¹¹ù[óGFF‚ˆÐ½{÷óEEE!>>ùùùpttD÷îÝŽèwïÞ}« Š ëVÔúÕ?zôFFFœ­Àãããeú¨3p>‘¿ æÜ@Ïž=e.†«¨¨@dd$RSS‘›› ]]]ØÚÚ²Û*+ÂÓ§OñôéSXYY¡}ûö •©¬¬DDDîß¿šš8;;£G2ý…“’’ììlLMM1`À¹‹x ™™‰ââb¨¨¨ GRéD„‡BMMõw9r$Nœ8üü|6‹D"ADDÚ´ióÖp“©©©ˆ‹‹CZZ455Ѿ}{ôéÓGæµäädÜ»wÏŸ?GEEÚ·okkkôìÙó­!²²² ##;v쀑‘"þ{÷åÑ5pø·,Ml€bAÅ‚‚ˆQ¢&ö^‰½ƒÄcï%¾Ö(öcKìh"&öÔ7*Š5j±¼¢ØT ÒÙýþàcà HYtÏ}]^ç™™ç¸8ÌΜ9}:͵ûaaaš}é±²²ÊôÆÃS§NqõêUBCC5_÷é•}šëׯóäÉ,,,°±±á‹/¾HwC_ò÷]­µØoÞ¼áüùóïŒÙÙÙYkó½{÷(W®Ç×,©Ë)¶¶¶„††‘f% ¡?$zI¥RáììŒZ­& GÊ­\¹’aÆ1}úôL¯™B|x!!!8::R®\¹ “ͼfÑ¢EŒ?[[[NŸ>ýÁK¿}  ÄÖ­[¹qãFŽVà¸yó¦fìŸþIóæÍsllññ‘M„B/°téR^¾|ɱcDz=NBBaaaøøø0sæLÊ”)Ä r0R!Df…„„зo_|||¸sçÁÁÁlÞ¼™ÆËܹsub–7OOO (ÀĉÓÜd(þƱcǘ9sfŽ—¯;tèæãƒæèØâã#3ÐB¼‡€€ÍŽz¼½½5[!r×­[·¨R¥ ±±±Z×ÍÍÍY²dÉ;Ëü ‘'''.]º„Z­¦H‘"„‡‡ë:$¡C’@ ñž={ÆöíÛ)^¼8-Z´ÐF"„Ðèèh.^¼ÈljÇÎÎŽ*UªÈצx/111,XP«dßíÛ·3µoC|š$NG\\—.]âŸþáùóçôèÑ#Í jµš-[¶àç燅…;v¤^½z:ˆX!„ÂáÇSÕÒ_µjƒÖQDB×d t:êÕ«‡‹‹ £FbÒ¤I\¿~=UµZ››C‡’v^7lØ­[·æv¸B!„ø@<¨µÙ\¡PÈ:h='3Ðé8zô(eË–%22’š5krðàÁT¿};vŒfÍšqèÐ!ÍÉZƒfÇŽ<|øðƒU*„Bˆ¯R¥JܼySëZ¾|ù¤œ“èt4kÖŒŠ+¾³——¦¦¦Z¥l¾üòKÂÃÃ9pàÀ‡Q!„ØíÛ·S%Ï´ÞÞÏÏO‰¼ uÕ}‘iÁÁÁT®\YëÀˆjÕªiî%‹‰‰ÉõØ„BñþöíÛ—æu…BÁþýûiРA.G”{2s ˜¾’ú=ûLסè yÍsŸ¼æ¹O^óÜ7cÆ æÏŸmÛ‚‘ìÝûîCÕªð÷ßð÷ßDDD|Ô³¶±±±˜››ë:ŒCÃÿO ‚»wßÝX¡€G’’h33 iݰüÿúôÉŽ÷àááALL GŽÑ\Û»w/E‹¥}ûö:ŒL!„ï-EA€4©Õpû6¤8¡Pè™NÇ‚ صkQQQŒ=š3fШQ£¤·v€&MšÐ§OºwïN= ãàÁƒlÞ¼Yj@ !„³7oàÙ³ŒÛÅÇg9rJ:>Œ¿¿ªëvvvtîÜYóyFGy§¬Â!»Y…Bˆ¼- €š5kBíÚpá¿7Фç´Ô«&&püøGÿ³^ò–Ì‘ú“ˆB!ÄÇC“@—*•´¾Y­NJž “f›!éãÄÄïYZBµj’@ëY-„Bñ¶°°dc㤂ɬ­¡`Ág¥Ÿ=94M¯H-„Bñ¶ÄĤÿC¿~²´›‘ôD'ËÌziñÉZ!„"-ÆÆàîž4ãœrÅ«B‘”P÷ïŸTîàùsÝÄ(tBh!„Bˆ·&Í<—*•ôùÛ 4h'ѹ£Ð)c'>z»ví"..N×a=åââ‚­­­®ÃBä°°°¤š4ù7y†´hø7‰þùgY­G$½þýû£P(rü˜t!2Ί+$â˜ôA‘"Ú7Tª?N™@CRíägÏ~ØàDž! ´ø(…†† $Õâöòò¢cÇŽ:ŽJè›Ê•+ë:!DkÒ¤IÚ7Ò›N&åÞôŠ$Ð⣴téR®]» Ë7„Bä˜tOÎ(zE6Š’*å[iB!ć–26ôIßÉ¿!„BˆŒ¼k ´Ð;’@ !„BdäÕ«?–õÎzOh!„BˆŒ¤¬óla¡»8Dž ´B!DFRž4( ´Þ“Z!„"#2-R2vB|=zÄÙ³gñ÷÷'_¾|8;;S¯^= .üÁžyéÒ%®^½JëÖ­177ÿ`ÏÉŽ›7oR¿~},X€»»{¦úrüøqÂÃÃùüóÏiÖ¬Yú媄"¥˜øÿ³I …$ÐB?íß¿Ÿ\}fýúõ©]»v–û-[¶Œ &‹µµ5ÑÑÑDDDP¢D ¼¼¼hÕªU¶cZºt)K—.åüùóXYYiÝóööfþüùøûûãää”íg| <~ü˜¨¨¨Lµ_¾|9#GŽÄÌÌ KKK<==iÞ¼9»wï¦@8Z!ÄG/åò 33ÙD($úiÝêuøö£˜Q±\yÞ½¸{L÷œžåzܸq,^¼˜–-[²nÝ:Ê”)ÀÅ‹éÕ«mÚ´á÷ß§[·nÙŠëÅ‹ܽ{—ÄÄÄlõÿøùù1bÄ:wîÌÖ­[166ÆÛÛ›¾}û2räHÖ¯_¯ë…y,ßo‘Zè'54Khư„a¹ò¸a¦YΕ+WøñÇùâ‹/ðññA©TjîÕªU‹³gÏR±bEÆG‡0ýÿ‘ÐÐP"##©T©¯^½âôéÓ$&&R¯^=Š)¢ãÑ£G„‡‡IK"^¼x@éÒ¥³4+{ãÆ ‚‚‚P*•8;;k’|€—/_J™2eÈŸ?ª¾wïÞ%!![[[­ë÷îÝ#00ØØXœœœ¨X±b¦ãyÛªU«øá‡4K6z÷îͺuëØºu+‹-ÂB~ !ÞEhñÙD(Dµ`Á˜5k–VòœÌÒÒ’‘#GrïÞ=¶nݪ¹>iÒ$\\\Ø·oVVVtéÒ…:P®\9öï߯i7fÌV®\ @ƒ pppÀÁÁ#GŽd*¾gϞѩS'ìììèÚµ+;v¤\¹rüç?ÿÑÌh?zô/^œªÿ›7o¨Zµ*“'OÖ\‹ŠŠâÛo¿¥lÙ²¸ººÒ£G*UªÄÀ‰‰‰ÉÜ ÷–}ûöQ¸palll´®W«VèèhþüóÏl+„Ð#’@‹·H-DuùòeŒŒŒhÔ¨Qºmš7o®i›Rdd$C‡åÈ‘#¼yó†[·náàà@×®]¹{÷.6l`âĉ\¿~ˆˆ"""h×®]¦âëÔ©ÇgþüùÜ»w«W¯2`ÀæÎËÆppp N:lÚ´ uÊcp;w‰‡‡‡æÚ·ß~ËÆ™:u*ׯ_çöíÛLš4‰uëÖ1oÞ¼LÅ•RDDÑÑÑT­Z5Õ½jÕªððáÃ,+„Ð3÷ïÿûqÑ¢º‹Cä’@ ‘GSªT) Òÿ2­P¡‚¦mJ‰‰‰ 0€  P((_¾<óçÏ'..ŽE‹`ff¦YöQ¨P!ÌÍÍ177ÇÈÈ(ÃØöìÙÃÉ“'2d&L téÒ888ðóÏ?S¶lY,X iëááÁ­[·8yò¤Ö^^^X[[Ó¢E üýýÙ²e ݺucæÌ™ØÙÙaccÜ9shР .$!!!¯Ü¿BCC“å”’¯%·Bˆ4={Ož$}l`•*é6‘'Èh!ò µZZ­~gò hî§µ °Y³fZŸ×«WΟ?ÿÞñýõ×_Ô¬Y“¿þú µZJ¥B­VS«V-öìÙCll,&&&ôìÙ“Ñ£G³iÓ&6l$­}>~ü8&LÐ,O9sæ jµšºuëjÆL·jÕªøùùqçÎ,­‡V©Ti.I¾–ÜF!ÒtíÚ¿—-›T…Cè=I …ȃ •*UâêÕ«¨Õj Ešínݺ€½½½Öu¥RI:u´®™ššR£F îÝ»÷Þñ%?÷]Õ?ÂÂÂ([¶,æææ|ùå—lß¾åË—cffÆæÍ›Q«ÕZË7’Ç1bDºc>|ø0K ´µµ5AAA©î%_Kn#„iJ¹D®JÝÅ!òYÂ!Dåàà@\\gΜI·Í‰'4mSJLLLµ¬C¥RqãÆ Š{ÿÒ}EŠÁÈȈ¨¨(ÍLñÛÊ–-«iÿÕW_ñúõkvíÚÀ¦M›¨[·.•+WÖ’*‚¤7æ»Öƒ§ÅÒÒI …Ù aaI+ðÖ÷Z¡¿$";v, …‚iÓ¦¥Ú€I—,YBÉ’%éÝ»wªûGÕú<00ˆˆ­¤5ùð”—/_f)¶ªU«ŸêéiÑ¢¥J•ÂËË‹S§N¢5ûœ<&ÀÁƒ³KFÚ´iCDDDªÍ‚AAA§Zê"„¾¾ÿ~\½:ÈÁKâÿI-DU«V-¾úê+Ž=J÷îݵ’Ü4hÀÓ§O™;wnšu›7lØ@Äÿ—^JLLdéÒ¥ öoMêä |Hwƒ^\\111ZzõêEÙ²e9rdªÙÝ'Ožhfš“àææ†¯¯/žžž˜ššÒ£G­6íÚµÃÅÅ…iÓ¦¥ÚpøêÕ+­R}Yñí·ß0uêT͵ÇãëëK§NRÀ(„MPPf3`“&M(]ºtº}.\Hƒ 8yò$ŽŽŽ4oޜ֭[§jwìØ1ÂÃÃ9vìÏŸ?×,ñèÔ©åÊ•Kwü† 2cÆ vìØAPP?¦V­Z¸¸¸¤ù{{{~ùå"##iܸqšcV¬X‘³gϲgÏüýýyðà5jÔ`„ ´oß^Ó®D‰üôÓOšª™9s&Í›7Ç××—ððp&NœÈ—_~‰™ì¦B¼ES™çÍ›¤ÿB&¿×ý! ´ÐKjÔü­ø›™Š™¹ò¼ñÞ«ÕªUÓ< $#®®®¸ººfØ®hÑ¢tïÞ]ëZ:uRUòHKŸ>}2Oß¾}3lc``@çÎéܹsºmÌÍÍ4hP¦Ÿ I fn!„þZ»v­ö…6mà­wÌ„Z襶Úb]&÷ª/ØaGÍš5síyB!²nïÞ½¬Y³æß 5j@­Zº HäY’@ ½”ÕÙK!„Ÿ¶Í›7óõ×_ÿ»„ÃÜR,"%ÙD(Ä'fíÚµ<~üX×a!ÄGcáÂ…xxxhW#úâ 02Ò]P"O“ZˆOŒ‘‘&&&ºC!ò¼ØØXFŒÁ„ 4õö5‡+,¨ÃÈD^' ´B!ôÎ… R•³¬Zµ*^^^º J|4$B!„Þˆ‹‹cÊ”)Ô«W+W®h®×¯_???9\IdŠ$ÐB!„Ð >>>ÔªU‹Ù³gkÖ;ãé鉯¯/:ŽP|,¤ ‡B!>i‡fÚ´iüý÷ßZ×Ù´iS¶êì ý&3ÐBˆNbb"aaaDGGë:!DväÈ>ÿüsZ·n­•<1kÖ,Î;'ɳÈ™â#pìØ1Ž?Ž¿¿?ùòåÃÙÙ™öíÛ¿÷7þ˜˜bbb(\¸0 …BëÞ¡C‡8~ü8#GޤdÉ’ïõœœvãÆ øé§Ÿ2¬éÀÁƒ¹téaaa,X*Uª0lذw….„ø8EFFâííÍêÕ«ù矴î)•JúöíË´iÓ¨P¡‚Ž"ŸI …^Z¶lYªo¬Z·nÝh×®]–úDFF2bÄ6nÜH±bŨ]»6OŸ>eΜ9L›6 OOOÆŸ*ùͬyóæñý÷ßJ‰%´î?~œùóçÓ³gÏ<—@gżyóسgUªTÁÊÊŠsçαÿ~~üñGþûßÿÒ A]‡(„È/^dÍš5x{{©uO¡PУGf̘½½½Ž"ŸI …^:vø0Á>>|–KÏûC©¤råÊYN ûõëÇ®]»;v,óçÏG©TðâÅ zõêÅĉQ*•Œ;öC„ýI˜5k7n$_¾|¨ÕjöîÝK§N4hÖ.|!ÄÇåéÓ§ìܹ“uëÖqñâÅT÷ ;väûï¿§Zµj:ˆP|ª$z«%°4—žU7§Y=z”]»vÑ­[7-Z¤uÏÜÜœ}ûöaggǬY³èׯE‹àܹs<|øÎ;söìY|}}ILL¤Y³fÔ«WO3Æ™3g¸|ù2{÷î¥páÂ4hЀR¥Je*Æèèh8@PPJ¥Ú¶m«¹ïÞ=þúë¯tÇ<|ø0‰‰‰Z}ñññ! €ØØXœœœèÔ©“æ—‡¬ªT©’ÖçÉ?PkժťK—HLLÌöØBˆÜ÷èÑ#víÚÅÎ;9yò$‰‰‰©Ú”(Q‚¯¾úŠo¾ù†òåËë Jñ©“Zˆ·oßÖ$ÍgÏžÕœ˜’-Z´`àÀ¸ººbh()Žøpä_—yÔõë×155}çÛŽuëÖÕ´MéåË—lÚ´‰›7oR¢D âââèÝ»7“&M¢eË–8;;³|ùrŠ)Â÷ßÏùóçS­~—¸¸8ºvíJLL ‡¦Y³fÄÅűqãF†Ê¢E‹˜>}:ÖÖÖ´lÙooo.\ˆQŠ™ø-[¶‡‡‡æš»»;AAAìܹ“víÚa``ÀîÝ»qsscÒ¤I¬[·.Ó1¾íèÑ£¼|ùöïßµµ53gÎÌöxBˆ'""‚“'OrüøqŽ;F```ºmíííéÞ½;ýû÷§\¹r¹¤Ðk’@ ‘©ÕjnÞ¼‰Í;Û%ÿ°¸qãFª{ Ð$ÅÆÆÆLœ8‘;wòÃ?ðË/¿¼W|¿þú+ÁÁÁ,Y²„–-[/_>† š5kX³f Ó§OÀÃÃâãã×_~©cÓ¦M888ðÙgI+Ñ“+Œ1‚Î;kÚuïÞooo6nÜÈŠ+055ÍVÌC‡Õü¢‘?~|||4Ïú'<<œS§Nqûöm"##)W®ÎÎÎ8::¦ÛçÕ«Wœ?žàà`ÂÃÃ144ÄÆÆ†zõêåXE‡[·nqïÞ=\\\ÈŸ?¦ûÞ¾}›»wïP¬X1ªT©’nÛ;wîpçÎ4ÛFGGsîܹT}LMM±¶¶ÆÚÚ:ÇgwŸ?®I˜?Î¥K—P©T鶯^½:]ºt¡K—.ïü&ć" ´yB¡ÀÌÌ,Ã:ÇÉ÷Óú!Û¼ys­Ï)T¨PŽlšKž Š‹‹cÕªU¨ÕjT*jµš"EŠHdd$ àË/¿ÄÂÂ///MH@@óçÏO5¦‰‰‰fÌäqQ©T„„„dû‡å©S§ˆ‰‰áÔ©Sìܹ“Æ3vìX.\øž¯†ø˜~ùåöíÛGhh('OžÔJéÝ»75ÂÑÑ‘’%KrçÎNœ8ÁúõëÙ¶m‰‰‰ìر#Û±õïߟ¸¸8òçÏÏ›7o²=N¡B…xôèûöíÓz7'ÙåË—9uê… âÕ«WéŽcjjªõ5ÊéÓ§9tèsçÎåÎ;x{{g*¦gÏži%ÌAAA&ÌåË—§qãÆ4jÔˆfÍšIÝv‘§H-DU³fMMÍâN:¥ÙfïÞ½899i]OLLäâŋԩSGs-..ŽK—.åÈ©[eÊ”A¥RqâÄ MõŽwñðð`ÕªUx{{3dȼ½½iÕª•Ö,_™2e€¤å!Õ«Wï3âææÆøñã9}ú4­ZµúàÏyƒ wîÜI•ŒÕ¨QƒN:Q½zuΟ?ω'4Ë“ ©šË¯¿þªÕ§|ùò4iÒzöìÉž={4ï¼dÕªU«Ø¿?;vì`ðàÁï•@÷èÑ///~úé§4èŸ~ú HúX¹reºãÒ±cG­kƒfÛ¶môèуß~û•+Wbaa‘ªoxx¸&Y>qâ—/_Î0a®X±"5¢Q£F4nÜXó=Aˆ¼HŽò"úî»ï055eÚ´iiþ0½}û6«W¯ÆÑÑ‘nݺ¥ºŸ\a#Ù¹sçˆŽŽ¦fÍšškÉk¨?~œ¥ØjÕª…Z­ÖzÛ÷]\\\¨R¥ ^^^øøøðäɭ̓ÉclÛ¶-K±dWòR–²eËæÊóDÞP¸pátg2«V­ªYº”é1Û¶m«y—(;ï’\½z•qãÆáææF—.]²ÜÿmÅ‹§sçÎ=z4ÕþˆÈÈH~ýõWjÖ¬©Ù„œU®®®£V«‰ˆˆHuÿþý+VŒ®]»²bÅŠtg›íììøæ›oزe <àÆ¬[·777IžEž' ´yTÙ²e™6m—/_æóÏ?çäÉ“DGGóâÅ vîÜI:uHLLdÙ²e©J±°víZMÎÔ©SQ*•Œ3FÓ.y†mõêÕ\»v°°0bccµÆºpá‚fcOòŸÒ¥KS£F Æ··7ñññ@Ò,wò †oóððÀßߟ)S¦`aa«««Öý† Ò©S'.\ÈŠ+4‰HBBçÏŸgòäÉY~ ãââèß¿?§NâéÓ§¼xñ‚0|øp6mÚÄ_|‘jù‹Ðo<ÈÒiu«W¯&!!-Zh– eVll,½zõÂÊÊŠåË—g©ï» <µZÍÏ?ÿ¬u}Ë–-¼zõŠÁƒg{ìíÛ·GµjÕÒÜE­VÓ£GTí“KÑ1dÈ47mذ±cÇ2bÄFމ••ááá$&&R§NÍ©¬øý÷ßÙ¸qcªëmÚ´aݺui®·úiëÖ­S¶lY7nœn»uëÖ‘@HH'OžäŸþ¡}ûö©<ÊŒ‰'ÄÑ£G3µ*³5j„ƒƒ^^^Ìž=[S½æçŸ¦P¡Bôîݛݻw¿sŒøøx¼¼¼4Ÿ'¯>qâ•*UbÁ‚iö+Y²$•+WF©Tj–c4lØâÅ‹çØßO]“Zˆ<®{÷î´oßž   ‚‚‚055¥F888¼³””»»; 6äôéÓ¨Õj4hær…~ø~øàà`ž?®I°‡šjýcJ*Tà»ï¾ãÊ•+ñøñcJ–,‰‹‹Kš'•(Q‚‹/êtÀdæææ¬_¿žÿüç?ðàÁŠ+FõêÕµ6E–+WŽ3gÎdX:ÌØØ˜ˆˆΜ9CHH¯^½¢T©R8::¾³Ä—Ð?þþþ|ýõ×(•J~ýõ×w®c>|¸ÖRêÕ«3xðà,ÍZCÒ†ÙeË–1jÔ¨wV½È®Aƒ1räH¶mÛ†»»;gÏž% €¡C‡fªZHll,_}õUªëæææŒ?ž¦M›¦Û×ßß?Û%'…øH-ô“BÁfCCåÒIUwããI?͘™™uêÔÑÚ˜åÊ•ËôÁo/e(S¦L¦Ö!:::fº´\52ÕÎÖÖ[[Ûtšfzý¦±±±fc’i  eË–ÄÆÆ²víZêׯÿÎö¨T*ÂÃùxñ"K—.¥]»v 8Õ«Wgê™Éû˜3gNNü5Réׯ“&Mâ§Ÿ~ÂÝÝ]ó.Ó Aƒ2Õ?_¾|üõ×_šÏãââxøð!^^^ 8uëÖqäÈ ,˜ª¯$ÏâS' ´ÐKƒ‡§uûö¹úÌìnØB|8ÿý7­[·æÕ«Wlܸww÷ û¤üe³~ýúôêÕ '''Ö¬YCÏž=35›<`Àž?ÎÁƒ?X²Y¸pazõêÅúõë9zô(Û¶m£Aƒ™®Ä£T*Ó\"Ö©S':vìÈÞ½{Y°`³fÍÊéÐ…Èó$zéS.[V²dIÙ'D&øùùѾ}{bccùí·ßèÚµk¶Æ)V¬mÛ¶eýúõœ8q"S ôÙ³gIHH ^½z©î%oä­]»6 …‚U«VÑ¿ÿlÅ6hÐ Ö¯_OÏž=‰‰‰ÉôìsFZµjÅÞ½{9zô¨$ÐB/I-Ä'fîܹºAˆ<ï?þДÜ»w/­[·~¯ñ^¿~ é*={öL÷“ßÿ˜˜Z·n……Å;—3e¤víÚÔ®]› .`ee•í_ÞöâÅ ŒŒŒrd6’@ !„Ð+ÞÞÞôë×333öïßOÆ 3ìóðáÃtK­;wŽРA­{þþþœ8q‚2eÊhÕx^¶lYºÏòññ!&&OOÏ,åžßÿ{÷îQ´hQŒß{¼hÊãe´^\ˆO•$ÐB!ôÆ… pssC¥RQ©R%6lØÀ† RµkÑ¢}úôÑ|>nÜ8Ο?OÛ¶m)[¶,E‹åÉ“'œ={–}ûö‘ÀŒ3Rm”=~ü8cÆŒ¡I“&9rHJvT¨P!Ê5iy{ÉGò&ÂãLJ³³s¶ê³ ñ)Z!„Þˆˆˆ@¥RpñâE.^¼˜f»B… i%Ðõë×çŸþIó°“Zµj1~üø4ëŸÌ´ªŠR²dI>ûì3ÜÜÜpww—jBoI-„Bo´hÑ"ÍSò22tèP†Jxx8< ""‚bÅŠQºtéw€2zôhF¥g=yò$Ëñxzzâéé™éö}ûö¥oß¾©®W¬X1[¯‘úDh!„"“Š-šå㺅Ÿ9ÃVñÑyóæ ‡âþýûºE!„’Zˆ<.!!%K–àêêJ™2e°³³£gÏžüþûïï=ö;w4‚Þ¶qãFZ·nÍ­[·Þû99íþýû´iÓ†?þø#Ó}._¾Ì×_££#–––8;;³xñ⥠V«9yò¤®ÃBä0YÂ!ôҨѣ8øçÁ\{žL;¯¾ú*Kýnß¾MÏž=ùûï¿iÚ´)îîîDEEáëëKÏž=Ù»w/kÖ¬¡@ÙŠËËË‹ï¿ÿžÐÐPJ”(¡uïúõë>|8ÝZµ“;wÒ¯_?ÌÍÍi֬ŋçÆìܹ“±cÇê:<ñ‰ æ›o¾¡I“&©ÊÛ !>n’@ ½t#äÁ‘Áà;Ï3úÛˆÇg©Z­¦W¯^°yófÜÜÜ4÷T*&L`ñâÅ/^œ~ø!§Cþd„††âææF­ZµØ·oš{ñññ:ŒL|ªðôôdÖ¬Y¨T*–,Y¢ë„9Lh¡¿JusçQÊÿ)³Üç—_~áܹs >\+y000`Ñ¢E?~œ•+W2dÈ*UªÀöíÛ¹zõ*S¦Laݺu;vŒÄÄDZ´hÁÀQ(lݺ•#GŽ0{ölòçÏ€»»;UªTÉTŒwîÜaíÚµ¡T*qqqaäÈ‘š±®]»Æ¦M›èÕ«Wªú¸‹/ÆØØ˜áÇk®…‡‡³jÕ*‰ÅÉɉQ£Fe{ã–§§'111¬ZµJ+y9EMä¼þù‡¯¿þš€€ŒqvvÖqTBˆœ&k …È£’×8O˜0!Ý6£G&>>ž={öh®ýñÇ,^¼˜~ýú±lÙ2,--yúô)ƒ ÒªkûèÑ#ÂÃøyó&ÿûßÿøßÿþ§9’8#{öì¡zõêÌ›7èèhBCC™|ˆ““®®®šã–llløå—_2uŒ³ïâëëËÀ¹yófª{¹⃓h!ò µZÍ;w°¶¶~g»2eʤY)£OŸ>šäÐl`\»ví{Ç·yófž={ÆØ±c5É3ÀçŸNÆ Ù±c‡æš‡‡QQQZ×Ôj5›7oæ‹/¾Ð,=ñññáúõë åÇ$::š.]ºðüùóì¾ BϽxñ‚o¾ù†fÍš¥™<çÏŸŸ¦M›ê 2!ć&3ÐBäA …‚¢E‹ñÎvÏž= X±b©î5oÞ\ësJ”(‘j¶:;®]»À±cÇðóóC­V£R©P«ÕDDDðüùsž?Ž¥¥%M›6¥L™2xyyi’øãÇs÷î]¦L™’jÌÀÀ@ˆZ­ÖŒ{ãÆ nܸ‘¥õ¤É3ÞjµOOOºtéÀˆ# cîܹlÙ²Ek ¶™±{÷n†JhhhºmZ´h!G] ñ‰’h!ò(;;;ž>}JtttºmBBB4mSR*•iÎ^—*UŠ—/_¾wlOŸ>ÅÈÈ•JE\\ñññ$&&¢R©prr¢_¿~¨T* iÃc¿~ý8yò¤f¦ÜËË 333ºwï®5&$mìK3!!•JEÅŠéׯfffYŠÓÒÒLMMiݺµÖ½:ÿ&îBdVbb"æææŒ7îkœ]]]s92!Dn‘h!ò¨úõëãç燗—ƒN³Mò:â/¾øBëzbb"§NÒš…~ýú5Ô­ûoé‘ìnn²µµ%>>žyóæejít¿~ýðôôdóæÍŒ7Ž;wÒ¹sg *¤5&$ͧŒñ}ÙÚÚL\\œ¦:@TT •8DÖ)•Jš4iB“&M3f  >œS§NiÚЮ];F)„ødZˆ¯^½zŽ>Oè'''ìííµ®Õ©S'Í¥UBˆOƒÌ@ç€/^pçÎjÔ¨!åŠDŽ)\¸0«W¯¦wïÞÔ®]›éÓ§S·n]¢¢¢8räóæÍÃÚÚšåË—§êkddÄæÍ›qtt¤eË–\½z•aÆ‘?~ÆŒ£i׬Y3”J%3gÎÄÝÝ œµ~ðoÙ²…ãǧzFÓ¦MñôôäÍ›7ôêÕ‹%JðàÁNŸ>MPPëׯ×jïááÁ€˜4i666©6WU«V#F°téR”J%ÄÆÆ†ÐÐP.\¸Àü¡U®/³¾þúk–,YÂĉ¹ÿ>-[¶ÄÛÛ›µk×booOß¾}³<¦)ÅÄİ}ûvš4i‚¯¯¯f‰âÓ$ ô{ðõõeöìÙøùùa``€……;vdÉ’%äË—O×á‰O@ÇŽ9þ<ýû÷gÔ¨QšuÅffftíÚ••+Wbii™ªŸ™™;vìÀÕÕU³ÑÐÚÚš#GŽh%Çæææ¬Y³†Ý»w3pà@"##Ù½{7;vÔ´Y´hQš±-_¾œúõë³`Á.\¨¹niiÉ!CRµïÞ½;#FŒàñãÇL™2%Í_6/^L… ˜:uªV^ @­õÒYaiiÉÙ³géÚµ+“'OfòäÉ@Ò¯Õ«WkU*";öïßÏ«W¯¨R¥ ›7oÆÖÖVÖ? ñ‰“:›¢££éر#ŽŽŽ\¾|™²e˲cÇúöíKÑ¢E™5k–®CQ ¹ô,uö»:::rîÜ9¢¢¢¸zõ*fffØÛÛ£T¾ûtÃÏ?ÿœ§OŸrõêUÔj5UªTÁÀ õª­þýûÓ¿͆@ccc é¿3f¤;¾‘‘J¥’©S§róæM?~LÉ’%©P¡††©¿µ,XˆˆT*•æo300`øðá :”Û·oóàÁŠ+F… ´]{{{¢££3½~¹lÙ²œ?ž§OŸŒVVV™ê+DF~ýõW ©tdéÒ¥™9s&ŽŽŽ:ŽJñ!IMwïÞåÕ«W ,Z´ˆ   G'2b 0€¿Iú“ bˆyï1ÌĮ̀]»v–ú(ŠLÿ 700Ð*¹ehh˜f"ü6CCC*W®LåÊ•3l›^âœV,¶¶¶š…oS(Ù*fee%‰³ÈQÏž=ãàÁƒ( ÍIŸ'NÔqTBˆMèl²³³ÃÚÚšóçÏãææ$ά9YMä]‹.fò¤É¹úL›\}žâÃÛ¶mñññÔ¯_Ÿ²eËê:!D.‘:› 404mÚ;;;>Œ‡‡_ýu®ÅñôéSM-à¬ÊŸ?¿æ(h}óvÝäOI:u2\Þ!„È[¶lÐÌ> !ôƒ$ÐïÁÄÄ®^½Jbb"Ož<¡P¡BéVâøæ›o´//¯÷Žá?þМî–UUªÔàÊ•€÷ŽAä-éÕŒBä¬Û·osúôiŒŒŒ²½ÉUˆ¼ eYÑÄÄDÝò‘:›^¼xA£F5jóæÍÃÀÀ€'OžàààÀëׯY±bEª>k×®ý Ǻš˜Ø{=‹½VðkŽÇ"„ú"yö¹M›6iVÃâc‘rB/&&F³1V¤ORɦýû÷Ï!C4• Š+F·nÝØ¶mjõ{”]È¢¤oÓ,þ‘ß„â}ìß¿€Þ½{ë8!Dn“:›’“æ7nh]¿qãJ¥RTBˆOXLL þþþÀ¿'s !ô‡LCfSóæÍ)Z´(&L`üøñ888°k×.|}}=z´®ÃBñ]¸pøøxÊ—/OñâÅuŽ"—IMÅ‹çðáÃŒ5Н¿þš˜˜¬¬¬˜8q"Ó§O×uxB!> 3gÎP¯^=G"„ÐI ßƒ³³3~~~¨ÕjâââäH`ñÁ={ö  ( ëPtêùóç¬Y³†Ö­[ãä䔥~ÆÆÆzÿú‰÷' ´úMÖ@ç…B!ɳø`ž}ʈ#4É3@¾|ùèÙ³'„‡‡g)Î"EŠàêêJHH³fÍâÙ³g<~ü˜I“&$•"³îß¿Odd$fff”+WN×á!tDf …ȃ ¥K—æñãÇïl÷èÑ#lllRõoÚ´©Öµ2eÊ`kkËÍ›7ß;¾äïiÓ¦1uêTÔj5*• µZMXX‘‘‘<~ü˜âÅ‹ãììLµjÕðòòbìØ±ìß¿ŸçÏŸãáá‘jÌŸ~ú‰õë×£V«5ã>þ€Š-š¥XW®\IXXÓ¦McΜ9¨T* 1bË–-Ó,c"3’gŸ+W®ŒB¡Ðq4B]‘Zˆ<ÊÎÎŽ?ÿü“gÏžQ¤H‘4ÛüïÿÓ´M)½Ú䦦¦DFF¾wlQQQÑ«W¯tÛäË—OóñW_}Ř1c¸xñ"µjÕÂËË‹¢E‹Ò¾}{­1ºtéBÁ‚Ó3­µÌ)]º4gΜáÈ‘#R´hQš7oÎÎ;4…ÈŒä5óUªTÑq$B]’Zˆ<ª]»vüùçŸüøãÌœ93Í6+W®D¡Pкuk­ë*• ___­eOž<áÊ•+Z3ÓÉ%¸T*U–b³³³#>>ž:d*íÓ§&L`Ó¦M”.]šC‡1dÈMåä1>ÿüsš5k–¥x2b``@Ë–-µ*ìÙ³sssœsôYâÓ–<- ´úMÖ@ ‘G 2„Ê•+óÃ?àëë›êþŠ+8vìnnni&Ôú<¹ªGÛ¶m5×>ûì3®]»–¥Ø\]]100`öìÙiÞûð•äµÑÞÞÞlܸ‘„„­å-Z´ þüÌ™3µZá˜ïc×®]œ8q‚1cÆP¨P¡W|ú$B€Ì@ ‘g±e˺té¢94¥nݺDEEqäÈ8@ýúõY¸paª¾¦¦¦øúú2vìXZ¶lÉÕ«W™6meÊ”aðàÁšv 6$_¾| >œ:`aaA—.]´ê"O›6 KKËTÏèÞ½;7näÁƒôêÕ‹%JðàÁNŸ>Íýû÷SPâááÁ¾}û˜9s&Õ«W§fÍšZ÷mllX°`Æ £N: 0BCC¹pá¾¾¾šä%+–/_ÎéÓ§©]»6J¥’óçÏóÛo¿Ñ´iSFåñ„~KþeSh!ô›$ÐBäaÎÎÎ2eÊŽ?ÎæÍ›122¢zõêÌ›7qãÆ¡T*Sõ311áèÑ£xxxðÓO?¡V«iÔ¨6lÀÌÌLÓÎÔÔ”cÇŽñÇpôaFcŠ IDATèQž?Ž‹‹ •*U¢X±bØÛÛ§*‘—ìûï¿§{÷îÌ™3‡aÆE¾|ùpqqaÀ€©Ú·oßžZµjɰaÃÒsÈ!T­Z•)S¦0nÜ8^¿~‰‰ ÕªUã›o¾ÑúûÙÛÛgªfu… Xµj¿ÿþ;8::j6?¦õÚ ‘ž‡òòåKLMM©P¡‚®ÃBè$ÐBo=Ù˜ÐÌ–èèÔK2«P¡BšƱ±±f*ñ³±±áرcš¥)KÃ¥”|€Ê¬Y³´®3†1cÆdøœN:¡R©xýúõ;kVqáÂ… Çkذ¡æÐ”—/_¦9fùòå5(3Ò®];ÚµkG\\ Z¿@‘Éï€ØÛÛË/_Bè9I …žRâë i,-þ@bsd”´*kd$½Ä9'¼3yήœÓØØXkÓ¢YÈò !„$ÐBOíØ±“ÄÄÄ\}fn$²BˆçòåË@ÒõBý& ´ÐKFFFŸlBÛ­[7œœœt†ŸI …É$âÓ®];]‡ Ä')9®ZµªŽ#BèšÔB!2ƳgÏÈ—/ŸTàBH-„Bd$((€Ê•+c` ?:…Ðwò]@!„È€¬B¤$ ´B‘I …)I-„Bd@6 !R’Z!„x•JÅ•+W™B$‘2vâ“pîÜ9]‡ ôPdd¤®C¹ €7oÞP²dIÊ—/¯ëp„y€$Ð⣧T*Yºt©®ÃzD©Tbh˜ôí3;Ç«‹‹ŸŸ 4Ðq$Bˆ¼BhñÑkÚ´©®CzD¡PЪU+† ¢ëPD.IN 6l¨ãH„y…¬%¥R©ë„žR«ÕºAä"µZÍÉ“'™BüKf ÅGiòäÉ$$$è: ¡§dÙ†þ¸víáááXXXP­Z5]‡#„È#$¥‚ ê:!„H^¾Q¿~} …Ž£B䲄C!„H‡¬B¤Eh!„"RC‘I …Bˆ4„„„ððáCòçÏO­ZµtŽ"‘Z!„HÃøüóÏ5u¿…$B!Ò´aÃz÷î­ãH„y$ÐB!Ä[.^¼È¥K—(P ÝºuÓu8BˆI …BˆÿwïÞ=Ž=ŠR©ÄÝÝ]×á!ò(I …Bˆÿçåå…J¥¢M›6”,YR×á!ò(I …B 66–µkײ|Cñn’@ !„ÀŠ+xðà¶¶¶¸ººê:!D& ´B½÷òåKæÎ ÀìÙ³åèn!Ä;I-„Bï-\¸gÏžQ«V-ºwï®ëp„yœ$ÐB!ôZhh(?üðóæÍC¡Pè8"!D^'ïQ !„ÐkC† !**ŠæÍ›Ó¼ys]‡£åÌ™3<~ü8Ëý®\¹‚¥¥e¶*‰T«V [[Û,÷BŸH-„Boy{{³gÏLLLX¶l™®ÃIeêô©øúú¢4Vf©Ÿ:.…B©iÖ~Ì¿y“ÀâÅK5jT¦û$ÄÄðøÒ%Þ<~ŒB©Ä¼\9¬à=fò#ÃÂHˆ‰ÁÔÜSsól#ć" ´B½ÆðáØ9s&öÙ³gÙz^ݺuiݺu–ú$ªQÕU¡j®ÊR?“E Ü{©éÝ;>Ký¾ý6_–Úÿ³v->Æ‘§u½x´ÿùgJ×­›¥ñÂÿ÷?~vr"16–FÓ§ÓxÆŒ,!ć& ´B½4xð`ž?Nݺu;vl¦úìÙ³‡ý¿î§œQ¹,=ëZÜ5ì«ÛsèС,õ»yó&”ÉR—\eR¸0_L˜€Mƒ*]š—wïòßÿrnùr¶vèÀЫW1³²Êôxj•нýûcV¤¯=ú€‘ ñ~$B¡wV®\Éž={055ÅËË ¥2óK$\]ø.ñ»,=¯§¢ /¹såJ–ú=Q'äéÚ±{wST-±ªR…ŠmÚÆåß~㮟]ºdz¼s?þÈ“  Ú­ZÅn9J]äa’@ !„Ð+GÕ¬ñ]¸p!ööö¹ò\Wµš¥ñY[RQÄÈ€ç¨?PDN‰š5¹üÛoåÏŸé>ÏoÞäèäÉ´X°€Â66i¶ Ü´‰“sçòù¸q8 ¹ÿæ [ÚµC­RÑÇÇãÞûï Ä»H;!„zãÆtëÖ„„ À°aÃtÒ'çÅ;œ_µŠ|––ThÖ,sÔjö}ý5%œœp2$ÝfÕÝÜ(hmÍÁáà K±ý¡C¹ëçGÃÉ“%y¹Bh!„záåË—tèЈˆ5jĪU«tÒ'ãÔ¼yx5nÌÒråø±|yJ:;ó­¿?FF™ê~Õ*œ=‹ëºu( ÒOMtñöƤP!¶wïNÜë×nÞLà¦MÔŸ8ÛV­rê¯$Ä;I-„â“C×®]¹~ý:¶¶¶ìܹ£L&w"cJ## MMQ'&ðòî]^Þ½›©¾/îÜáÈwßÑ`òd¬ªTɰ}%è¼e !!lëÚ•?† ¡Ì_ÐdÖ¬÷ú;‘’@ !>Zÿþ›ã3fpé—_RÝóß°ã3fæïŸ£Ï<5oÇgÌàõÇ9:®øp¢££quuåÈ‘#.\˜ýû÷S¤H]‡õI©7v,}bôýû ¾t …RÉÆ† yrùr†}÷ó …Ë–¥þw™ß˜Y¡ys>?žÿþCºþö†²­Käù×&„ž‰ åÑÅ‹<ºpÐþ!öÕ+ºïØYÑ¢éö{vý:û¿ý6Ãñ{íÛ‡I¡B9ï»<üûoN|ÿ=š7§º››Ö=ÿ ¸ú4…Ë”¡DÍš9öÌSóæûò%•Ú´¡`©RYêóâ 11( ë4sITT:tàØ±cXXXpøðáLÕ{ÙW¬Z5NžÌo;rå÷ß)VµjºmŸ\¾Ì­#G¨Ð¼9N˜ ¹žü êÍC‡ˆyñ‚j½{Sê³Ï4÷Õ*¡/I¹$"·H-„9÷ãJç„±ÄØØwö}ýš»'Ndø U« è“}pmçNN™"o7ç‚7oÞЮ];Nœ8A‘"EøóÏ?©™ƒ¿L‰ôÙÔ¯À“ Êö) ò/Îã  ý{wWSþÿüu»­ÒBÚhUd_JƒŠ˜l‰,Cöu cŒ}_F3ŒacŒ1ÌoY³Ž,#;Ù³&%*¡’ŠŠöûûƒî·”tÛN^ÏÇ£G·s>ŸsÞ™¡WŸû9Ÿüxο#±wï"1<¦mÛæ ÐþK—âÑÉ“hÿÝw¸ºv-öxx`ìÕ«PÖPl#¢âb€&ªB²22‰5­¬`lg5mmÜøûo…®¡¬¡!GŽ|ô¼šŽNIË,Ý׬Aê«W¨Õ Ð¥ЫW/œ?úúú8yò$š5k&tY¢#ËÎ.ð¡¿;ÞÞæMó¹u ™©©¨Óº5@¿qc̈ŽÎ×?ÂÏ^:ÀaÆŒ|;Fúû㌧'õ뇎‹¡Ž½=¼ÝÝqdâD¸ÿóO)}gD…c€&ªBšÛ1c ®« àÝ"E´’T ‹Ê ºÒUšÓ6¨r¹{÷.z÷î°°0âÔ©Shܸ±Ðe‰Òßöö0ut„±­-´Œñ6>aÇãö¶mÐ61Ág,¸»$„…aáû‡ õ6>û ‚Ž©)z¾ÿ·«~Ïžh3u*.­Z…º..h2hP‰¿/¢Oa€&ªB4 ËýžÉÑÑxø~ûb«.] U»v¾6Ù¸ãí Yv6 ›5ƒ±­m‰ïûÐ×É110stDÍzõò7Éê단K—ðúÉÛÙÁÌÉ F-ZàŽ{xzõ*tLMaù‰ulŸ]½Šˆsç}ëjXZÂÒÅæíÛçi“‹Ð#Gä«Dߺ…›^^òóR42$ßµ?FĹsˆ¹so^¼€~ãÆ0iÝæÎÎÖˆè[·PÓÊ fíÚ!9:‘çÏãÉÅ‹Hމëï¿:Ï] vïÞÑ£G#%%7†¬­­….K´ š6ÅÍM›peÍù1‰’{x ãâÅ mã]>#G"9:£ÎŸ—@§eËéïCãÆ¡¶½=jò¿9•1h"*Sš¸³};< SŒôóË÷´ü©ùóqqÅ TÓ×Ç×¹6G(‰sK–àÉ… pÿçŸ|:=) ÿŠÊåÚ®«V!;3'fÎDý=> e2Ž|û-®®]›ç¸ß¢Ep˜1W¬{‚£FÉ¿9t!‡É¿V­^=_€¾òÇ81s&2SSóݻɠApûóÏ<‚öíÃù%KÐlèP@"ÁöîÝ‘ž”$?ßÁÓS´:++ óæÍÃòåËýúõæM›Pk–©Þ^^pÿûo$†‡ãM\4ôô cjúѹÈCBŠt]sggxÊòïÀ8(×ßÙܤªªøêÊ•¢NTB ÐD¤¬ôt;/‚‚ ¬®ŽZ660lÞ͇‡²ºz¾ö%%ôݶ ë[´À“‹qzþ|túùgùù‡¾¾¸øË/€D‚¾[·8B]Úv÷뇰ãÇ¡ª¥…Ï/†™“Ò’’ðàÀ›6 FÍ›òǦMCÌíÛpöô„©ƒàÞ®]ܸùV]º nçÎýF0`ÿ~\X¾QhÔ¿?š,¿Ö‡¿P›6 —V­‚²º:gφUçΨ¦¯èÀ@œýþ{ÜõöFJl,†Ÿ}aÇCIE_^¸ƒ\9Ytè33›6í“×y~ã¾ D­\K¢Yué‚ô”ÜÛµ W®”hš5Ñ woÜÞ¶ PËÆ z÷.ðº1·oãòï¿CªªŠÑþþ0¶³“Ÿ3lÖ 6½zam£Fx|ê‚}| ¼NìÝ»h:dúlÞ ‰T °üüó"üéT>6lÀŒ3˜˜ˆš5kbÇŽèÊÝ舨Œq#"*²:Ÿ}†Î+V`ðáÃséúø Ý¼yPÑÔDâãÇØÕ·/bs-E•›å矣ý‚€LŸ#ð*"ÿ‚7/^¼ÛElÑ¢rù.ÿþ; ÉÀyÂsû Š4 Þêë¯ó„ç¶_~ xñ‰å»>æÌÂ…ee¡ÕøñyÂsu]]8Íž ¸¹iSת©¡óÏ?Ë󅇇£sçÎ3f áââ‚ëׯ3<Q¹à4IíV­0æòå|Çmzõ‚ݸqøÛÞ)±±ð5 c¯]+ðÎ "âÜ9„Ÿ=‹¿lmñ6>zz庋ØË¼-.ˆTU;âÎöí…^'gÚÆ‡t--IÏž!+=RUU…ê{~ã€ÂGŒMÚ´Äx^¿aC…7y©,²³³ñÇ`Þ¼yHIIŽŽV¬X¯¾úJèÒˆ¨ ©:##***B—A$Z:ffè²r%ö†çׯ#9:ÕŒòµ“H¥øbÇüÙ¤ ÞÆÇ úlÞ m“ò)T&CÂãÇíB¦®¹ù'/¥cjZàñœeÙÙÈ|ûV¡ñæ ^GEöôïÿ¾d “åû A–••o¤9'Ä‹M@@¦OŸŽ€€@=°~ýzÔé/ DTq‰2@‡……aÆ ¸uëîÞ½‹ÈÈH¡I“&hÒ¤ † »Þ%¢â³ìØQþ:æöí4$FD -×ÊHʺ4¹Üá³°û´1DqÚ(êm|¼¼>‹ R­Ú'ûȲ³óhUMÍR¯MHAAA˜7o8ÐÓÓÃêÕ«1¤€¥ÿˆˆÊƒ¨tPP~úé'ìÚµ 5‚ † SSSDGG#,, X»v-\\\0þ|8½ßn”ˆJF5×raoÞØ&51{DvFtÌÍñ*">#Fàë[·Êeõ ‰’t-,ð2$IOŸ~´]âûõšË›v:PÑÔDFJ gÏíƒEõäÉxzzbË–-ÈÊÊ‚ªª*Ƈï¾ûú¥¼¾0‘"D _¼xV­ZaðàÁ D“&M>Ú6** kÖ¬AÏž=qøða8::–c¥DâôìúuùkÃl™|`ôh¼Šˆ€~£Fø2 Û»wÇ“ ðï!~êT™Œê~¨f½zx‚ð³g ܼ$;3çΕɽ¥ï§’´¶3@"~Æxvíž\¸Pet||<~úé'¬]»©©©H$2d/^ K‘NO©èÒ^¿F£G¨ai 5STì;PÕÒ‚®…E)VKTöD³ ‡¶¶6"""ðÏ?ÿžÀÄÄ?ÿü3¢¢¢¸½+Q)ÈÎÌÄ©¹s¼Û8¥F!çÊ xÿ~(kh ß®]PÓÖF?oohÔ¬‰ð³gqîÇË¥Vûñã·¶nEüÇùÎßÜ´I¾c`iË™ëó `ArÖ‡¾¸re¡£äbôðáCLœ8fffX¹r%RSSáêêŠÀÀ@lÛ¶áY@‘çÏ㯖-vâ„üX„ŸþjÙÞ/eYq|úô"µM{õ ‰ááÈÎÌ,öýˆJ‹h´šššÂoéijjB÷ƒ¼ˆÄ,+- ágÏÊ?¢åçžÈ4û—­-ü—-CÜýûò)™©©ˆ<›Ú·ÇÓ÷»€u_»6ßüâèÀ@Ÿ1àºz5 Þÿ’«mjŠ^7x·ƒ_YüæVÏÍ fíÚ!+- Ú¶Å­Í›‘†è›7á÷Ã8üõ×г±)“{ç,›÷ôêUÜÞº‰ááH{õ i¯^ÉÛ´ž4 u>û i¯^a³‹‹|íÜâîßDZiÓÞm@#gΜA¯^½`ccƒ?þø)))pttÄÙ³gqäÈ4/ÂÆ6Tþ”54 cn^nsî/ÿþ;V[ZâUdd¹Ü¨0¢™Âñ)×®]ý{÷жm[Ô¯__èrˆ‘òâ6çzØ/·œU€w;ã}—‘‘çüËœš;W>Ò¬¦£ƒ´×¯å½))+ÃqÖ,4ê×/O¿ô¤$ì0Yiih2p l?Xn̦W/´ž4 —ÿûÆø[· ¡§Wâïµ0÷ïÇîþý~æ |FŽÌsÎÙÓÊjj85oTµ´Jõ¾M Â…ŸÆ‹  ì>\~\µzuÌ}ÿ`¥D*E¿;±øpDúûcK§NPÓÑAMkkd¾}‹W‘‘HON8UâÝöÒÓÓáííß~û 7ßoß®¬¬ L:Ÿ}ö™ÀÒ§ÔíÔ SÂÃ….ƒH¢ Ðþù'~ýõW¨ªªbôèј6mpùýúµ***غu+ P*÷{ùò%~úé'\¾|¯_¿F£F°páB4jÔ¨T®OTš”ÕÔ`îìüÉv­Éì²d Ÿ9ƒ§W® %6i¯^AY]5¬¬PÛÎíæÏ‡^¿œÞz¿=w-ôøë¯ï×yÅ $GG#9&7ÂaæÌ"}?ÚuêÀÜÙ†ŒP·l %eeT76ÎwNCOÃŽǽݻué^?yc;;˜·ksgg7 œŠbæä„ôään‹-UU•ÿøç¨¤¢‚±×®áÚ_!æÖ-$=ŽÌÔÔ|«mèZZbÔ¹s¸þ÷߸·{7^áùõë()¡š¾>L`Ó«}ñEž~5,-aîì\à/Epp0¶mÛ† 6 :: ««‹±cÇâÛo¿…éG–¤Š'éÙ3Dœ;3'§|KQ>¹págÏÌÛ·‡Y»v;v ©u;uÊ1™ OŸFÄùóPVSƒu·n0jÙ2ÏõbÞoÒrø04 FÍ›çùÿ=%&NžDÌíÛÐ44„E‡0¶µÍs«ÄÇuù2꺸 ;+ OÂó7Ðdà@Ô¶·/?ªD £££1a´lÙ7†§§'?~ŒÔÔTìÚµ Õ«WÇòåËñ矖J€~øð!:wî 45kÖÄÍ›7ÎMR5}}Œ|ÿCMQ­'OFëÉ“¼[:-íÕ+¨ëê~r:ûo¾ý7ßÚFªªŠ~»v)\Sƒ>}РOŸϹ®YSh_%ee4ç8·œ" ZOyðáÃ…^W£fÍBÿŒ•54ÐfÊ”B¯H`7v,ìÆŽðn$_YC£Ð gZ~ù%Z¾ß ±"yþü9¼½½±}ûvÜÈ5ÿ»^½z˜®®®P*‡•W¨ü„Ÿ9ƒóK– ±‡úlÝ ©ª*ÞÄÅa»«+bïÞEíV­òõ 9|6½zaöË—PÕÒBĹsØ;p NÌš…ƪ©Á}ÃèZXàÌÂ…sù2jÔ­›çÑøwèPÔïѽ6n„†ž^GEÁoÑ"\þýw˜99¡Q®ijptêTôX·͆…D*ýøê8DÕ¿\~~~h×®üëœ×íÛ·—³³³ƒŠŠ ®^½Z¢{=xð>>>øâ‹/ž‰*™+kÖ`ßàÁýï?$„…áíË—xzå NΙƒ}ïG¤ÛL™"Úí°ËJrr2|||àááCCCŒ5 §Þ?Ù©S'xyy!&&»ví‚››óüú+TªUƒÛºuò]8«Õª…ë×C–•U`5mmôÞ´IþK¢yûö¨ïæ†äèèWÊ)È©ù󡦭¾Û¶ÉŸ¡Ð61A×÷õ\Ê5£éàÁh>b„|4[Y]]áï—ª.Q@§¤¤À(×îg9#ÌjjjòcJJJPQQAzzz‰îu÷î]€……ºté‚›7oÂÌÌ “'Oư÷o9QÅ”™–†»ÞÞ¸ëíïœDI m¦LóÂ…TVùãÈ‘#8räΟ?ŸçßV[[[ :„qsÑI|bï݃Q‹ШY3Ïqc;»|Çrµh‘ï3''Üøç¼zòúEXnöùõë¨Q·."ýýÿ·í}v6d2ô5‹{÷òõ)p.6Q‰*@@BB‚ƒƒO߯¡šgºFÎ[‰%ñøñcÀ‚ àááI“&aÛ¶m>|8²²²0òƒ'ûàÇ„r®ù‹ßÿ}‰ë "ÅÙ ½úõéï×QQx/ÐÑòóÏ9²oß¾ÅéÓ§áëë‹#GŽÈÿ-‰D[[[ôèу Bƒ ¬”„üü9Œ>²ì`΃R/`9Ùœ@ýÁj@Éxó)±±xóò%v~䙈œv¹ØÕâ/ur¹óH&×Ù.Ñè-[¶`Ë–-yŽ•ÅrH9£Ú]»vÅŸþ èÑ£‚ƒƒ±téÒô°aÃòŒ†‘0Ô´µaãîww¡K©ðRSSqåÊøûûãüùó8{ö,RsÍÕÑÑA—.]àêê WW×<ïRÕ£mb‚ä÷««|(9:ºØ;F¥Z5¨jiÁ¼]; þï¿¢wüÄÐUIîÌ’––†%K–WL%!ª=cÆ 8°Hmm?XÖFQµk×ð.4çæêêŠåË—###*ï·íÍannuα"¢ ,...\€¿¿?üýýqãÆ|SÞš6mŠîÝ»£{÷îpppÈóÎUmM›â¡¯/Rbb ih(?þäâE¤&&–èÚ9s”so<”ðiSD]¾Œô¤¤J÷ÀmE`‘k+õT>LY$¢úW¯U«VhUÀ¾e¡E‹H$ ËsüÑ£GÐÒÒÊž‰ˆ*¢‡æ Ì9Sàr³°°€££#Ú·oîÝ»Ãäƒ5‰r8Μ‰àýûá3j¾Ø¾ê5j !, ‡ÆŽ•?TX\ºïC^Øñã0jÑ"ϲ³§'¶u튣G£×ÆyBtJL bîÜáœg*U¢ ÐåÉÊÊ ;w†··7F…:uêàþýû8zô(ºwï.tyDDy¤§§#((7oÞ”ܺu ‰Œ J¥R4oÞŽŽŽprr‚££#êp5*"Ç¿„ IDAT“¶mÑeåJœ˜5 ËkÕ‚¦¡!’Ÿ?Gç+pkóf¨hhûÚõÜÜ ka“sæàЍV«ÚN »qã`Õ¥ \–.ÅYOO¬®[µ[µ‚Ц&^ED úÖ-4èÕ‹šJ•¨ô¢E‹púôé"µ]¾|y‰çFÿßÿýzôèKKK4kÖ ×¯_GóæÍñ믿–èºDD%[·nå Ë÷ïßGFdU¯^mÛ¶…££#ѦM.ÍIùèÕ¯gOOèçÚ$LÏÆæÝ±v¾l;m껹!ÒßÀ»eéjÖ«¿~€±]ž¶íæÎE +«|÷ÓoØΞžÐ³±‘S©V ßÜ»‡ÐÿþCbx8ÒSRòÔã4gôê…»»váEP2ß¾…±­->›8õsM·4jÙΞžÐ57/Ù Ui¢ ÐAAA¸páœ?ù 4¦X˜››ãÊ•+¸xñ"‚ƒƒñã?¢S§NœHDe.++ ‘‘‘ Ehh(>|ˆÐÐPܹs‘‘‘ö166FóæÍó|ØØØ@ú~\¢©Y¯:|°r”^ýúùŽÉÏÙØä ¿!‡!=9ùÝÔ‹\œæÎ-°­† ¼¶Jµjù6D)J¿ÜŒZ´ÈW‘¢D•ô:wî \ºt ýúõèQ£Ð¾}{HÊðI[ ¸¸¸ÀÅÅ¥ÌîADUSvv¶<$çäœÇt={4lØ0_XÖ××/ç¢ãÓ§£ÎgŸÁ°Y3Ȳ³qîNÍŸMCCØ}õ•Ðå• Qè/¿ü#GŽÄ‰'°iÓ&tíÚ&&&9r$†333¡K$"’KHH@TTž>}šïsxx8 ÝôI[[ÖÖÖ¨W¯žüs‹-ШQ#>ÈL‚IŒˆ@ÀªU€L&?fÖ®Üÿþ*ššVFTzD wÀtëÖ ÝºuCBBvìØ///xzzÂÅÅ[¶lá:¥DTfd2’’’˜˜ˆèèèÃqÎë·oß~òzÚÚÚyrîÏÙ˜‚HH{÷âíË—xõä ²33¡W¿>Ô´µ….‹¨T‰.@çV£F L˜0Íš5Ü9spâÄ DGG3@ÑGedd 11¯^½Bbbbž×~.èØëׯ‹¼Û©ŠŠ j×®:uê N:011‘¿633C½zõ8í‚*•œ©E666ÐÐÓ+÷ûËd2\¼x-Z´€&G»© ‰6@GDD`Ë–-ؼy3¢££ñÅ_àÇDól1JD✜\ä°[й¢Œ в²2ttt```'ç~]§N”é3De-55~~~8xð þûï?4mÚ{÷D555XYYÁÂÂýû÷G÷îÝÑðƒ•BˆJJT:%%ûö탗—üüüàèèˆyóæ¡ÿþÐâÎDTdff"##ééé }.j›¬¬¬O~dff©]qÛªOiÐÔÔ„ŽŽtuu‹ü9÷kŽ|‘˜=zô¾¾¾Ø³g ¢¢‚””tìØûöíƒj 7L)©V­ZáÈ‘#h×®®^½ŠùóçCKK ={öDïÞ½áââ¿£Tb¢ Ð_}õöìÙWWWìÞ½Vï×–üp·@àÝF( ÕT¼~ýÑÑш‰‰AtttžÜÇbcc \ç·ª‘J¥% ¿:::\Š’(—Ü£Ì>>>xùò%¤R)Þ¼yàÝß¹:àèÑ£‚‡ç¶¶¶8wî:t耔”ÄÅÅaóæÍØ»w/RSSѲeKxxxptšŠMT?%²³³‘™™‰C‡áСC…¶õõõE·nÝÊ©2¢ü¢¢¢õÉp¬è”‰D¨¨¨@UUªªªò×%ù¬¢¢©TZ¤eeå"·-í>êêêeô_Œ¨ê ömÛàíí°°0(++#===ßü~˜™™aÈ!رc‡@Õ~Ü´iÓðóÏ?ËkOJJ\¹r˜;w.ÔÕÕakk‹iÓ¦áóÏ?¸bª,D .\ˆ¯¿þºHm›5kVÆÕ½“{ å[·nÉ?âãã‹Ô_*•ÂÀÀFFF…~BSS“£§DT,YY@BB&–,Y‚©S§B"‘@ö~)ºÌÌÌûdddàÁƒøª®ïœóŽ]FFüüüàççeeåïRLUƒ¨~Ò6jÔrmëITÞâââä[(ç|.pjEµjÕ`nnþÉ`\«V-()) ðÝQU"•5j(£OŸ/Q«V-lÚ´ ÷ï߇T*EVV–#¡¤¤‰D©Tо}û¢oß¾022Bûöí¨–*Ñh™LV¬§Ù‹Ûª6™L†<£Ê7oÞijgÏ lojjšoW8kkkc"ªpŒŒŒ0eÊ̘1ÉÉÉ8}ú4|||pøða$%%A"‘ȧ–eee!,, cÆŒÁäÉ“®<¯¤¤$´k×éééòð,‘HP½zu¼}û7Fÿþýáææ†æÍ›Ë³ÀÍ›7…,›* Ñ蘘¸¹¹aêÔ©0`@¡»peggã¿ÿþÃêÕ«ñÝwßÁÙÙ¹+¥Ê*55§NÂpøða<þ<_5554nÜ8OPnÖ¬jÖ¬)@ÅDD%S½zu¸»»ÃÝÝpÿþ}øúúb÷îݸqãÔÕÕ‘œœ,Ÿò1iÒ$+~'99íÛ·Ç;w ¢¢ ¨©©ÁÕÕ}úôAçΡ««+t™T‰‰&@`„ øþûï1kÖ,ôïß666°¶¶†©©)ž={†„„„àСCHJJÂôéÓ9׉ õâÅ >|ÄñãÇåO€¾¾>lmmó„eÎA&"Ñjذ!6lˆiÓ¦!%%E>:}èÐ!L™2ÊÊÊøæ›o­1)) ݺuÃíÛ·Ñ´iSùj-Z´´.Ñü¤WRRÂèÑ£1bÄìØ±ëÖ­ÃÆ‘œœ,o£¦¦†† b„ 7nªU«&`ÅTQãàÁƒ8xð òEŸ>}ЧOÜ»w†††øé§Ÿ‰eË–1<Q©m€þ÷ßѪU+\½ziiiˆ‹‹˜ššbåÊ•¸qã†ÀRiÉÎÎÆÚµkѨQ#øøø@SS¿üò ÂÃÃ1wî\.¤ODDD¥J´zÚ´i˜:u*îÝ»‡Ï?ÿ\~¼zõêhÞ¼9®_¿.`uTZBCCáää„o¿ý¯_¿F×®]q÷î]LŸ>êêêB—GDDD"$Ê€ˆˆŒ7À»m¾sÓÔÔD||¼¥Q)Êy—! µjÕÂÖ­[qôèQîHDDDeJ”jkkC]]!!!¨_¿>$‰üÜ«W¯pîÜ9Œ3FÀ ©$²²²0wî\¬X±ЩS'ìØ±úúúWF$n}¿è‹cÇ)Ü/#- **R(++ú#G‚eËV`üøñ ß“ˆ¨,‰2@K¥R¸¸¸à×_E³fÍä#ÐqqqX¼x1TUUѾ}{«¤âˆ…‡‡üüü ‘H0oÞ<,Z´(ß» DTú^%àé@ÁåÓÕJàä”…víÒê·n*ÒÒÒ»Q9e€€õë×£]»v¨[·.  ªªŠ¥K—B&“áÿþïÿ¸"C%ôâÅ tìØAAAÐÕÕÅ–-[гgO¡Ë"ªZô4P°ÏaÀÜprR¬ÛÖ­RoDDT>D MLLpÿþ}üý÷߸víâããѯ_?Œ9Mš4ºN:…úõë ]‘¸´¥¥%.^¼ˆÚµk£M›6º$*‚ÌÌL 0¾¾¾¨Y³&Nž<‰F ]‘hÐÕÕÅÑ£GѳgO|þùçØµk—Ð%Ñ',Z´>>>ÐÕÕÅñãÇѬY3¡K""""’õ*9TTT°aÃX[[cðàÁPWWº$úˆ»wïbÙ²eH$Ø»w/ììì„.‰Dæ·ß~Cxxx±úöéÓÎÎÎ¥[U:¢ Ð €ŽŽNžcsçÎ…••–.] mmm*£ÉÎÎÆ˜1c‘‘1cÆÀÅÅEè’H„vìð‹w`jªØ&7ndšˆˆÄ W¯^]àqxxx”s5TkÖ¬ÁåË—add„+V]‰”L&CçÎÙ<8[¡~ãÆi”QEDDTÙˆ&@gff"** ZZZÐÓÓÓ'O••õÑö†††ÐÐàÄŠ""" ,ð.Hëêê \QÁD £¢¢`ii‰qãÆaýúõ°··GLLÌGÛûúú¢[·nåX!æë¯¿Frr2ÜÝÝѯ_?¡Ë!"""ú(ÑhcccÀÐÐpäȤ§§´}Æ Ë«4ú„íÛ·ãèÑ£ÐÖÖÆÚµk….‡ˆˆˆ¨P¢ ÐjjjhÓ¦ük[[[«¡¢Š‹‹Ã”)SK—.…‰‰‰ÀN4º02™ çÎC@@lmmÑ¥K¡K¢÷V­Z…¸¸8888`üøñB—CDDDôI¢ÚHÅÛÛ5BjjªüXrr2¬­­Ñ¡CÌ;]»vÅ´iÓ¬’r¤§§ãŸþðnó‰D"pEDDDDŸ&ª}øða4oÞ<ÏF)6l@xx8¶lÙ‚ððp|ûí·X½z5=z$`¥{öìAll,lll¸æ3U¢ Ð=ÂgŸ}–çØêիѽ{w 6 æææX¹r%TUUqñâEª¤9 ~óÍ7WBDDDTt¢š––%¥ÿýN‚ÇcÑ¢EòcªªªhÑ¢ž>}*D‰ô^``  ©©‰#F]Ž`’““‘™™Y¬¾PSS+劈ˆˆèSD5mii‰³gÏʿ޳gÔÕÕó­÷œ˜˜È:–3ú}ú`Ö¬YX¹r%öïß=z`Þ¼yyÚlܸ***hÛ¶­@UÒ¦M›ðöí[899¡Y³fB—#8==ÀÚZ±>êê¢z󈈈¨RÕOa‰D‚¥K—")) ±±±Ø¿?êÖ­›§ÍðáÃʹ£Ú°a`„ WBDDD¤8Q@çPSSûh@600(çj(·çÏŸ#((jjjèÕ«—Ðå)LT#ÐTñå<äÙºukhhh[ Q10@S¹:sæ  cÇŽWBDDDT< ÐT®rF  ‰ˆˆ¨²b€¦róôéS„††B]]mÚ´º"""¢bm€>pàÀGÏ:u ~~~åX ÿ›¾Ñ¶m[®‚BDDD•–hôĉáëë›ïøùóçáîî‰D"@UU§o‘ˆ6@/]º À7äÇ._¾ 777Lš4 íÛ·°ºª‰‘ˆrhàݶÞOž<››.]º„øøxtëÖ £GÆÒ¥K….¯Êyúô)=z |öÙgB—CDDDTl¢ Ð0gÎDFF¢K—.ˆ‡‡‡~ûí7¡Ëª’}:aff†Ù³g#<<`hhÈ<ÊQhh( ^½zWBDDDT2¢ ÐQQQ°´´,´•••üµ¯¯/ºuëVÖeÑ{>X[[ \ QɈ&@ÉR+ŠfÍš•a5ô!h""" ÑhuuutèÐAè2è#r4§pQe'Úe삃ƒ)ÿ:66¿üò ¦OŸ.ŸMåC&“!,, G ‰ˆˆ¨òÍô‡\]]±bÅ ˜™™z÷î{÷î¡zõêØ·oCt9zöìÞ¾} MMM ]Q‰ˆr:%%áááh×®àÚµkÀÕ«WñðáC¤¥¥áÎ;WYupþ3‰‰(tZZ@MM °{÷nØÙÙ¡~ýúÐÐÐ@›6mäÛJSÙc€&"""1e€®Y³&7nŒ³gÏ"##;wîÄÀåç“““¬®ê‰‰‰Ô©SGàJˆˆˆˆJN”ˆþýûÃÒÒ/_¾Ä—_~ HMMÅõë×Ñ A+¬:¼ûņˆˆˆ¨²m€^°`nÞ¼‰9sæ 885jÔ„……ÁÃÃWXuÄÇÇ€ü¿Qe&ÚU8 qãÆhܸq¾cëׯ¨¢ª‰#ÐDDD$&¢ Ðééé B­Zµ`bb‚{÷î!##ã£í­¬¬ ¥¥UŽV] ÐDDD$&¢ ÐÏž=CË–-1nÜ8¬_¿...ò‡× âëë‹nݺ•Úý/]º„ÄÄD8::2˜€S8ˆˆˆHLD ëÔ©ƒû÷ïËCš¿¿?233?ÚÞÔÔ´Ôî}åÊ899!++ hÑ¢E©][ 8MDDDb"š­¢¢’geòZs8==£F‚««+>\.÷¬l8MDDDb"šý¡ÐÐPܺu !!!ÐÖÖ† ìíí¡««[ª÷Y´h 0zôhèddd %%G ‰ˆˆHD “““1{öl¬[·2™,Ï9}}}üñÇððð(•{bõêÕ¸~ý:‚‚‚Jåšb“˜˜¨^½:”•E÷¿UA¢K4“'OÆÖ­[1kÖ,ôë×–––HNNFHH/^ŒÀÐÐÎÎÎ%ºOFFF¹sç¢~ýúE ÐòíÅÀ¢D5T9Ûª««« \ $<<\þ:çç6NTúÑ£Gزe 6mÚ„¡C‡ÊëééÁÜÜ:uB—.]0þ|øûû—è^Ë–-CVVfΜYä>[·nÍ3 ûý÷ß—¨†Ê ==À»9êDDDTñxyyÉ_¶ý¨ôéÓ§aee•'<ç&‘H°hÑ"888àÍ›7¨V­Z±îóèÑ#üøãزe ž>} ˆðn9=cccæë·`Á‚*7›³74QÅ”{@/55K–,®˜JBT:<<<ß΃jÒ¤ àÉ“'°±±)Ö}"##‘žžŽæ;çææ†®]»âèѣź¶ØähUUU+!"""*¢ ÐOžDHDDDbÃMeŠ#ÐDDD$6 ÐT¦ ‰ˆˆHl ©T…‡‡cÓ¦Mò¯ ‰ˆˆHl ©T™››cÆŒpssÃÑ£G‘””ÐÐм|ùkÖ¬Á•+W„,“ˆˆˆ¨Ø”….€ÄE"‘ÀÉÉ Ä‘#GäÇGŒÌÌLDFFÂÔÔÁÁÁVIDDDT|¦RçììœïXpp0>|ˆôôt,X°ÊÊü݈ˆˆ*'¦*uèVVV:th9VCDZ¿~=nÞ¼©p¿À¼ ""ªd ©ÔµhÑÚÚÚxýúu¾s}&ÞÑC‡pß×-%…úÅ)É ‰ˆÀMe@*•ÂÑѾ¾¾yŽsô™¨âp•Éð›L¦P=©â¡X""1b€¦2áììœ/@sô™¨t#::Zá~/_¾„Eé—CDTe0ÍP™øp4GŸ‰Jß²e˰yóf…û©°+ýrˆˆª h*vvvPUUEzz:Ž>••®èŠ9˜£PŸ’/Y|UDD$~\ÆŽÊ„ŠŠ Œ5jÔàè3‰4•™¾}ûf̘ÁÑg""" h*3½{÷†••fÍš%t)DDDD¥†šÊLëÖ­±dÉŽ>‘¨0@S™QSSÀ„.ƒˆˆˆ¨T1@)€šˆˆˆˆH ÐDDDDD `€&""""R4‘ ‰ˆˆˆˆÀMDDDD¤îpAE’ššŠ›7o"""2™ }ûö…ªªj‰¯ˆúõë÷ÑMWüýýqõêUDGGÃÖÖíÛ·‡±±±B÷ºpáž}3fÌÀ;w™™)?==½]ûÅ‹èÒ¥ ââânnnÐÒÒÊÓ&11cÆŒÁ¾}û‰2™ ðööF¯^½Š|¿U«Vɯ3aÂüñǶ[ºt)Ö­[à]¨Ï Oœ8ñãÇØO"‘ wïÞX³f êÔ©S予ˆˆ¨rá*Ô£Gpÿþ}ØÙÙaüøñÐÖÖ.µkOœ8 ©©ùÑ6S§Nž}ûУG„††"-- .\€©©)¾øâ œ>}ZáûêèèÀÛÛéééùÎ¥¥¥açÎÐÑÑ)ô½{÷†¯¯/|}}ñï¿ÿbÚ´i¨_¿>öïßÞ½{#++KẈˆˆ¨r`€¦B 4III¸téþüóÏ|#ÄÅuàÀìÚµ ¿þú+ªU«V`›ÀËË ÆÆÆØ½{7¬­­¡¢¢ìܹÙÙÙX¹r¥Â÷öðð@||<:”ïœàááQè5,,,Э[7tëÖ }úôÁÊ•+qòäI(++ãÚµkòi)DDD$> ÐT(MMÍÎK.®ÄÄDŒ?]»vŰaÃ>Ú.00еkWhhhä9ײeKÔ­[¾¾¾WèþýúõCõêÕáåå•—jÔ¨¡ÐÔ&&&hݺ5€wß#‰4•»©S§âõë×X¿~}¡íž>} Ð××/ð¼¾¾>d2nß¾­Ðý555Ñ¿=z111yîwüøq 4jjj ]¢¢¢pùòeèêêâ³Ï>S¸?U ÐT®Ž;///,^¼…¶µ´´€Ž0GDDø_ÐVÄÈ‘#‘™™‰mÛ¶ÉmÙ²ÙÙÙ9rä'û‡……ÁÇÇ>>>عs'&Ož ¨««cÑ¢E¥>jODDDÊS¹IJJÂØ±caooI“&}²½½½=TUUqèÐ!DFFÂÌÌL~n×®]xþü9€âèvíÚ¡nݺؼy3¦OŸàÝôFÁÞÞ'Ož,´ÿ¡C‡òÍ¡–H$Xºt)¾úê+…ë!""¢ÊƒšÊÍìÙ³ñìÙ3:tR©ô“íMMM1sæL,Y²mÛ¶Åܹsaaa+W®à矆ß<±† IDAT¡¡!bbb ¢¢¢p-‰#FŒ€§§'nܸÔÔT„„„`ùòåEêß»woŒ7 “ÉðâÅ =zsçÎÅÞ½{qìØ1Ô¬YSẈˆˆ¨âc€¦ráçç‡õë×cîܹhÖ¬Y‘ûýðÃÐÑѧ§§|Ù;eeeLœ8R©¿üò êÖ­[¬š†Žï¿ÿ^^^HMM…T*ÅСC‹Ô7gޝgdd„U«VaéÒ¥X±bE±ê"""¢Šs ©\œ:u 2™ û÷ïG‹-ò|$$$Ñ¢E‹<öI¥RÌœ9‰‰‰¸uëüüü˜˜ˆ_ý>ð¿¹Òв°°@‡°cÇìÚµ ]»vUxwÃõë×àëë[¢ëQÅÅh*FFFhÞ¼y±û«ªªæ¹Ž‹‹Ã‰'`dd;;»b_wäÈ‘1b„üuIeddx·õ9‰4•ºàà`$&&¢^½zòí¾¿ùæ|óÍ7¶700À‹/páÂ…"mÔ"“É0þ|¤¤¤à§Ÿ~Ê·F´"¾øâ ÄÇÇC"‘ÀÝݽØ×ɱeËïÖ©&"""qb€¦B¥¤¤`ëÖ­y¾Þ­X‘³w÷îÝó¬1eÊ;v ;wîÄ€Jtÿ?ÿü¯_¿†ƒƒôôôpïÞ=lذLJ½½=ÆŽ[¢ëkjjbÊ”) ÷ ’oÄ’û!Â3gÎ@SSžžž%ª‹ˆˆˆ*.h*TBBÆŸïøŒ3ä¯>œ'@—¦G¸]÷ðáÃñ×_A]]½Lîû)ÇÇñãÇóïÚµ+/^Œ&MšP•h*”¾¾>Μ9Sh›WÕX¾|9æÌ™ƒÆé>>>HOOGµjÕòûùçŸÑ·o_ܸqñññhРZµjU¬•7~øá|ûí·hÚ´é'ÛÚÙÙáÌ™3000ÈsÜÝÝ 4ÈsL"‘ÀÀÀ%šNBŠIHH@ZZšÂý233Ë ""ªJ ©PjjjèСƒB}Y¦>zN*•ÂÁÁ¡Ð6EUÔ@5jÔ(ðû®]»6j×®]âZèŽ;†Ç+ÜoõšÕ V¸Ÿª*`o¯p7"""9h"Ôº5kpîØ1(¸ýùÃÌ4À@[Åî'Ù$ S¬Q. ÐD$,™ Ã33ñ›‚S+ôT”¯!j)x?‰‚퉈ˆ>ÀTˆˆˆˆˆÀMDDDD¤h"""""0@)€šˆˆˆˆH ÐDDDDD à2vTæ.]º„7nÀÞÞöÜÁ‚ˆˆˆ*9Ž@S™Û¾};&L˜€Ã‡ ] Q‰1@S™ËÙúúùóçWBDDDTr ÐTæŒ0@‘80@S™Ë~öì™À••4•9Ž@‘˜0@S™ËŽEvv¶ÀÕ• t ü÷ß8p ¬­­¡§§{{{üõ×_ ‰ÐÓÓƒªª*²²²+t9DDDD%Â]?ýôÞ¼yƒ)S¦`ÕªU¨]»6¾þúküøãB—VáäLãà«ð}C†B³º&¬¬¬ê X*|;"""ª„ ©Â¸ÿ>öîÝ‹­[·"44jjjHKK+°mjj*Ѻukøûû£fÍšyÎ_¾|÷^DK´T¨†PILei°ò÷W¨_‚²„šˆˆ¨Š`€¦ CEE 4€»»;üüüpïÞ=T«V R©ÉÉÉùVàÈÌÌDpp0lll“<ç£1¦A±åï®àº ¿)Xû!‰ià !DDDU4UÖÖÖ°¶¶Fÿþý2™ ¡¡¡¸zõ*.^¼ˆsçÎáÁƒPQQAvv6RSS!“É{{{\¾|fffDDD$v ÐTaI$Ô¯_õë×Ç!CYYY ÂÕ«Wáïï___ÄÄÄ ::­[·Æ¹sçP¯^=+'"""1c€¦JE*•¢iÓ¦hÚ´)F HOOÇíÛ·qõêUlÞ¼3gθJ"""3hªôTUUѪU+´jÕJèRˆˆˆ¨ à:ÐDDDDD `€&""""R4‘ ‰ˆˆˆˆÀMDDDD¤h"""""0@)€šˆˆˆˆH ÐDDDDD `€&""""R4‘ ‰ˆˆˆˆÀMDDDD¤h"""""0@)€šˆˆˆˆH ÐDDDDD `€&""""R4‘ ‰ˆˆˆˆÀMDDDD¤h"""""0@)€šˆˆˆˆH ÐDDDDD `€&""""R4‘ ‰ˆˆˆˆÀMDDDD¤h"""""0@)€šˆˆˆˆH ÐDDDDD `€&""""R4ý{wVUµ>pü{dP BqÂÁÃP M@ME Å!,ÅáÖM‹´B½iƒæ€&™†Þ1ÍÌR2 qÉrÈqDLaÿþಯÇêQÄ÷ÓÃó¸×^{uVûœóîµ×Z[!„BèAh!„B!ô ´B!„zZ!„B=H-„B!„$€B!„B@ !„B¡  …B!„ЃÐB!„BèAh!„B!ô ´B!„zZ!„B=H-„B!„$€B!„B@ !„B¡  …B!„ЃÐB!„BèÁ¨¬+ð¬Û»w/+V¬àÒ¥K´nÝš`hhXÖÕB!„Oˆô@?†ØØXZ·nÍîÝ»166æ£>¢Gܹsç©Õ!;;›¼¼«OíõDñn(YÖµx¾H›?}ÒæOŸ´¹å“ô@?¢‚‚FŒA`` ?þø#!!!xzz²fÍzôèQÆ5B!„O‚ô@?¢øøxNŸ>M·nÝÔ4ªW¯NLLLÙUL!„B–––Œ‹G'ôcð÷÷'11‘–-[’——ÇÔ©SY¹r¥Öªú(((àØ±c¬\¹’„„½Oþ1cÆðÃ?èuÌ•+WèÛ·o‰Kï=ŽcÇŽÑ·oßRý¢»×”)Søå—_ŠÝÎæÍ›ÕmOOOÞÿ}uûòåË\ºt©TësøðaÂÃù|ùr±ûoݺ…F£)±Î%‰‰‰y¨¡@.\xfz&/ñÉGE£Ñ””¤W¹Ÿ~ú)ÎÎÎÌwæÌ²ôY¢ªœ:uêáááŒ3æËoÝ%66–ððpþóŸÿ”Êëùøøðÿ÷êvHH¾¾¾¥RöóhÙ²eDFFêüésÞõÕW´iÓFÝ^»v-¶¶¶<‰*?UYYY8::²téR¢££iÓ¦ sçÎ%  ÔÞßÉ“'™2eJ™Ð={ö$11‘¹sçrâÄ :Ä—_~©GDDàççG‹-ضmÉÉÉL˜0%K–вeK­x!77—ŒŒ d‘µ'LåÂîÝ»GGGPêÕ«§˜™™)FFFÊ„ ”üüü‡*ÃÍÍM5j”^¯{æÌP¥Ú÷µ}ûvPΟ?_êe©W¯žòÎ;ï»PƯn{xx(ï½÷žºÝ§O¥sçÎ¥ZŸU«V)€räÈ‘b÷çææ*;wÖ»½,X ˜˜˜<0Ÿ“““Ö{.Ïf̘¡JNNN±ûÏ;§tîܹĶ,Éøñã''§æ311Q,X WÙåÑ–-[@Ñh4ÊŠ+tö7jÔHÑh4Š··w©¼ž···ÖgnÚ´iÊØ±cK¥ìçQëÖ­ 套^ÒúûöÛoºŒÏ?ÿ\qppP·þùgPîܹó$ªüTyyy)]ºtÑJ1b„Î÷lrr²²lÙ2%66V¹téR±e}Z155U†®³ïðáÊ‘‘‘®¦}ûí·  dddÜ·\ñxda9pàÀ^yå¼¼¼ˆ§fÍšäåå1oÞ<Þ{ï=RSS™={öˉ׻÷»N:\»vªU«>jõ…Œùõ×_˺τڵkK[éÁÇLJ˜˜zôè¡¦íØ±ƒ³gÏjõN–¶ân5 ýx{{³fÍš²®Æ3£U«V\¼xgggÆŽËäÉ“©V­YYYMPPÙÙÙ´hÑ‚ÔÔTš7oNff&‡&99™S§N1uêT €F£ÁÉɉիW“““CPPëׯÇÁÁ””¾øâ FÍO?ýDïÞ½ÂÏÞ€øùçŸÉÎΦaÆ’˜˜H¿~ý8wî 6äøñãøùù±lÙ2*W®Œ……¦¦¦¼k…¢(Œ7Ng_ãÆéÓ§3gÎdâĉÈÀ‚§EZº ÃÖÖ–uëÖQ³fM 0Ð åßÿþ7sçÎåàÁƒ@áÓ###9qâÛ·og̘1Lš4 (¼UûçŸj•}ðàAÆŒÃûï¿ÏÚµkIMM%22R½u••ELL ÿüózÌÌ™39vìIIIŒ=š#F°uëV­r³²²X¾|9~ø!ƒ b„ ¤¤¤<±6*mãÆcãÆ$&&âêꊫ«+ , ÿþDDD0kÖ,\]]©\¹2[¶lÁ××[[[ªT©BË–-ùñÇõzÝÜÜ\\]]Ù²e‹švóæM† ‚ Œ7ŽèèhüüütŽ?qâ¾¾¾XZZÒ¤IÖ®]«î äôéÓÌ™3G}O‰‰‰Ò<åÂéÓ§quuUÏ}(rÄ‹/¾ˆ‹‹ 3gÎ$""‚þýûë¿{÷n|||°°°àå—_Öj rss7nœÚV§OŸ~oë‰ aÆ ZŸå àïïµµµNþ3gÎлwoj×®M5èÕ«gÏžÕÊsêÔ)ºté‚¥¥%îîî¬ZµJ§œqãÆ1lØ0u{âĉ 2D+Off¦Îùèêêʺuë9r$ööö8;;3wî\–,Y‚‡‡–––ôêÕ‹Œ ½WO®ÒÒÒèÞ½;'OžÔJŒŒdüøñeT«²•ŸŸÏ?þˆ7fÍš5|ùå—L˜0Ó§O“’’B·nÝ0`©©©@á¸[·n‘žžNBBàüùóÔ¨QƒvíÚÀ¾}û8zô¨º8ÀìÙ³‰‹‹cýúõ?~œ“'O²qãÆbëõÍ7ß0iÒ$RSSIJJ"++‹   <<|8üüóÏ@á8é°°0>þøc:uêÄñãÇÕ±OÓ§O×êÅØºu+žžžÄÄÄ››ËÀ %,,L½â½víaaaZÁCXXcÆŒÁ××—³gÏò믿Ҿ}{6mÚ¤æY·nŸ}öYYYT©R…ŋӬY3vîÜù$š©ÔÓ¢E œÕ1‡:t Óþõ×_3oÞ<†Ê’%KHMM¥k×®¬X±‚5kÖðꫯ̆ úu Ø¿¿V@ðÆo°jÕ*†ÎìÙ³IJJbìØ±9rDëØ;wîЫW/ZµjÅ¢E‹¨S§ƒ Rǹ=š5jàë뫾§‡ \^ݺu‹ýû÷sóæM ðâÑ××—?þøƒÏ>ûŒ/¾ø‚Å‹3cÆ 1üiii <???¢££166æÝwßU÷GDD`ddDpp°ÚV5jÔxªï¯´5oÞuÄÍ›7Y¶lÔÉ›ššŠ««+W®\aêÔ©Ìž=›ÔÔTÚ´i£Ž½ÌÊÊ¢uëÖ¤¤¤Å Aƒ5j”ÎüÙ³gINþ߃œÎ;§ðåçç³ÿ~õ ­û÷ïgèС\¼x‘Y³fáååÅСC3f _ý5Æ ãÓO?eóæÍêÅíóææÍ›¬^½Z§‡rÏž=$$$”Q­ž¾Âo¼AÍš5Y¿~=}ô¶¶¶ÌŸ?kkkÂÃÃ133£FŒ?žììl–.] Àõë×)((àܹsj™¶¶¶|JðâÅ‹iݺ5]ºt ^½z¼ýöÛÅæmݺ5ýúõ£R¥Jh4~úé'ÒÒÒøâ‹/ÔïWWWúôé£þžCap¿páB²²²6l...8::òÛo¿©yÎ;‡­­m‰õ¬]»6PxQ,žÂQÆŠ~øK t¬¬¬°³³Ó öìÙCJJÊ}?T|ð­ZµbãÆ˜˜˜0mÚ4Ú¶mûPõúóÏ?9{ö,(Š‚‹‹ ß|ó ;v {÷îôéÓGë///ÆŒ£Õ»Z^9;;S£F *Uª„ÎþË—/sôèQªÜõÜ~ýúiåiß¾={÷îeÞ¼yꬾ:Dll,QQQêĬŽ;bkk«3$??ŸáÇ3xð`\\\¨_¿>{÷îÅÍÍ wwwÌÍͱ··/ö==ë6lØÀîÝ»Y·nÚ;ß¡C¬­­©[·®VÞŒŒ ¾úê+õ¢ÈÖÖ///.^¼HÍš5ñòòÂÀÀggç ÕV!!!ÌŸ?Ÿ°°0–/_NÕªUñõõå»ï¾ÓÊ÷ÙgŸ¡ÑhˆÅÜÜ(üüÖªU‹Å‹óöÛo3þ|.]ºDBB 6 ϹW_}µÔê[·n]õÕÏÏåË—3eÊ.^¼¨ýõ›6mÒš\Ѭ[·Ng’ðµk×ʨ6妦¦T®\™áÇããã£þ–%''Ó¡C4š¿Q£FØÛÛ«r!!!¬^½^~ùe ÀÀ8tñäÉ“ôìÙS+­]»vÅæ}íµ×´¶>Œ‘‘AAA(ŠBAA\¿~+W®¨ù*UªÄ›o¾É›o¾É¥K—X²d 3fÌ [·nüý÷ß4hЀZµjÝ÷ïÅ‹xñÅïû~Dé’ºŒ}^xá…óXYYi}àºtérßàùÊ•+ìÝ»—iÓ¦©=ÛææætíÚ•½{÷>°^={öÄÂÂFC»ví´†q˜˜˜M||<çÎ#''kkk¶mÛöÀ²ŸmÛ¶Õ ž¡0€MJJâ÷ßgÛ¶mdeeqþüùÇZ*¨¨7¯S§NjšF£¡}ûö:½ùtïÞ]ݶ··ÇÆÆ†íÛ·ãææöÈuxV8p###¼¼¼Ô´ªU«âé驳úÄ‹/¾¨{xx`hhȶmÛt.ü*’àà`>üðCöíÛGLL o½õ†††:ùprrbá…껢(ØØØ¨w>öìÙƒ½½½nݺ¥Ž½õ÷÷'++KkåÅ‹?ð¢£S§N;vŒóçÏ…+Ïܯ.¢dEÝîwðàA­‹Èç]«V­Xºt)¿þú+NNN´mÛ–ôôt6lØ Ã;xð mÚ´ÁÔÔ z÷îÍ{ï½Gpp0Px‡*22’m۶ѬY3õn€——+V¬ %%…ÀÀ@vìØÁ¬Y³tî”ÜËÐеk×Ò AÞzë-¬¬¬077Çßß_ Э­­Q…àà`êÖ­‹µµ5Ý»wWŠ&Lž<™E‹±dÉ5j„••~~~êäýâÆ?wíÚ•víÚiýÅÆÆ>~£ @†p” ü1[·nÅËË‹Q£FѲeK.^¼Èüùó9~ü8K—.}¤¡S§N¥G¸¸¸Ð»wo-Z¤^q?¨‡ùAúöíËœ9sprrÂÒÒ’3f”øD¹'ièСlܸ‘àà`ÂÃÃy饗ÔÕBæÌ™CõêÕK<ÖÏÏO>ù„ àèèÈ AƒÔu=ïehhÈÛo¿ÍĉÙ·o999ìÚµ ÿbƒp???õvb‘‘#GòÖ[oi¥™˜˜CÏž=iРuêÔ!77—vìØ¡osHŸ>}hÞ¼9vvvLœ8±Ü~饗tÎÇI“&Ѹqc­´jÕª1þ|ú÷ïÏÊ•+±²²ÂÒÒ’   G ¢{õêÅ|@||<ÖÖÖÌ›7OëëYedd¤5V¾8#FŒ ))‰:дiS|||HKK#..ŽèèhºwïNçÎéÝ»7}úôaþüùܸqƒëׯӴiÓû–ݪU+ìììhÒ¤ ¾¾¾lݺµØ%Ń0xð`"""8zô(ÆÆÆ$$$¨ë ?î]Bµ8½{÷&((ˆäädÌÌÌt>Ç‘‘‘Lš4‰3gΟŸOýúõu~SXlGU`` êvÑXìúõë«i÷Þ!(bggÇ/¿üBvv6ÉÉÉT©R…Úµkcll aÚ°ayyyê|šÚµk—8°_¿~ôë×ôôt’““ %%%EgžChh(¡¡¡%5—(%@—•+WfË–-Ìž=›ØØXæÎKÍš5qwwgùòåZ+ h4¼½½‹ ÝÜÜ´&üøûû³yóf.\È¡C‡ ÃÐÐ;wªÇ›ššâíí­51ÁÇÇGgb…½½½Öøé#F Ñh˜+‰ŠŠÒ:_îÊíÛ·Õm–.]Ê!CHHH --fÍš1tèP¼½½Õ|K—.¥GìܹGGGzõêÅÎ;µ&L 0@kl¹¡¡!þù'ÑÑÑ\»vyóæÑ¹sg<<<´ÎÕ¨¨(@044T'(éܯ(|ßåÔ"##iÔ¨‰‰‰4hЀ/¾ø‚¿ÿþ[kò²···Vh“&M?~üsõP JÜoff¦÷²žŠ¢0{öl033cÕªUL›6×^{í¾¯u/sssa˜w366Ö È¤ZµjT«V_ý•Ž;òÆo°fÍ•\Ä“¥Qv¨¨üüü8qâ'Nœ(몈ÿÊÈÈÐZ…åСC´hÑ‚O>ù¤Ø'O=Ïîm«7âëëËÂ… ‹}˜ŠB<*EQÔ‡9)ŠB¥J•ðóócΜ9Åv²ˆç‹Ðؾ}ûøî»ïh×®™™™,]º”ßÿï¿ÿ^gMcQv&OžLLL /¿ü2ÇŽcïÞ½´lÙ’¸¸8yÄú=ˆ§iÓ¦ìÞ½›cÇŽáççGllìsÕÛ&„xzn߾͵k×tV÷Ï7  +° .0vìX:Dvv6 6äwÞáõ×_/몉»\½z•ßÿ“'O¢Ñhhܸ1¯¿þº|QãÒ¥KlÞ¼™””LMMiÞ¼¹ÖÚB!ÄÓ ´B!„z{žB!„BèAh!„B!ô ´B!„zº‚X¹r¥úøn!„BñäH]AäääpéÒ¥²®†B!D…'´B!„zºœˆ‹‹# š5kFhh(YYYêþ]»vÑ­[7ðööfΜ9÷-/))‰îÝ»sçέô>úˆ ¨Û&L 22’õë×ãëë‹««+ãǧ  €S§N1pà@œéÚµ+§NRËÈÈ {÷îüõ×_Œ?777ÜÜÜøæ›oJ©E„B!Ê'£²®€€èèhþõ¯Ê!CÈÎÎfãÆdffR¥Jâãã騱#M›6eôèÑlܸ‘aÆqöìY¾üòËbËLMMeõêÕÜ»Ìw||<ùùùêvRRGÅÄÄ„Áƒ“ššJDD·oßfóæÍ´nÝš¡C‡I¯^½Ø³gPød¦Õ«WsòäIj׮ͰaÃØ¼y3#FŒ nݺtëÖíÉ5˜B!D’ºŒÝ¸qƒ‘#GòÁ¡¦÷íÛWýwxx8ìÛ·FàAƒø÷¿ÿMDDǧfÍšU‡Ó§O“––FµjÕ8{ö,S¦L!::š`eeEÿþý9}ú4öööê±õêÕcݺu 4ˆ]»v±lÙ2  …BQaÉŽ2vàÀ®_¿Î›o¾Yìþüü|öîÝK§NÐh4jzçÎÉÍÍ寿þzì:´iÓF ž‹¶ ð÷÷WÓÜÝÝÂàún={öÔ)+--í±ë$„BQ^I]Æ.\¸€­­m±û¯^½J^^...ZéEÛ¥±òFݺuµ¶+W®Œ¹¹¹VP]¹reòòò´òÖ¯__çØ{Ç] !„BT$@—± ’’RìþjÕªaffFbb¢VzÑö½Áo333ÒÓÓÕ´[·nqôèÑÇ®³B!ÄóLè2Ö¼ysêÕ«ÇŒ3t&ü)Š‚F£¡C‡üöÛo\¿~€‚‚–/_Ž••nnnÅ–[˜oݺUM[¹r%™™™Oè!„B<da311aΜ9áííÍk¯½¦®Â±zõjêÖ­ËÔ©Sñññ¡^½z 0€M›6qâÄ ¾ûî;^xá…bËurrÂËË‹!C†°iÓ&Μ9ÃÅ‹iذáS~‡B!„‹á§Ÿ~úiYWâyרQ#‚ƒƒ¹qã‡"''‡=zжm[ ©^½:ýû÷ÇÐД”\]]™6mj¼½½Õ´   E!55OOOfÍš…µµ5nnnZtóæÍiܸ±VìììhÛ¶­Vš‰‰ íÚµÃÊÊ cccÚµk‡¥¥¥V>GGG\]]K­}„B!Êrï¸!„B!D‰d ´B!„zZ!„B=H-„B!„$€B!„B@ !„B¡  …B!„ЃÐB!„BèAh!„B!ô ´B!„zZ!„B=H-„B!„$€B!„B@ !„B¡  …B!„ЃÐB!„BèAh!„B!ô ´B!„zøG`ë»a,Ù½IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/indexes-sizes2.svg000066400000000000000000001731311416254111300243360ustar00rootroot00000000000000 image/svg+xml Full PostgreSQL 23.4 MB Sizes for index of a 1 Grow column with different optimizations (PyTables Pro 2.1 vs PostgreSQL 8.3.1) Originalcolumn 1.49 MB 15x lighter 3.4xlighter PyTables-3.7.0/doc/source/usersguide/images/objecttree-h5.png000066400000000000000000002452201416254111300241060ustar00rootroot00000000000000‰PNG  IHDR•Ê­zû pHYsaa‰f΀ IDATxœì}w`\ŵ÷™»UeÕ%K²%Ëror‘lcÓ   ¦cZh/„GBz¾ð!¡„ ó( ð€BÀ‚`llÉ6Æ€mT\%K²Uvµ+íîÝ;ß3çÌÜU±$Ë„„ûCX»·Ìœ9sæœßœ™{Å÷Ô&–eY Ëzù÷\fààÀ8pà o0n©ç~õÃ0 Ãp¹\lWý¶D"ñâ››SÒ³ Ë*Ü.׿ZJ8pàÀƒÄâfËî-‘pðüSºÝnV»ýÃ?¾µ%­`R^~Q[—‰[fb ë_-çç€Ñ/Àðˆ-Ë—ôðkŸŸ™Vü…ôþ0À5Ø·²NÒ&ƒ úR¥”‹I}‘í c­¢ƒ”MKcàÒ´¹ÞJÒ‡¸Œi:·„ÞÝŠ"ÛÆŽ¬šH!ôacd vQlŒ†m»,I\j’ɹvÊ/ªhÖC.Eé ‹%_ F¡mËÖs)‰}Œ3Ò“t)ŒkÕ“¶µFSðÀHv€ZŸm€]5IHCvཨÀCF™ é?¨9è^—ávAŠ×ÈMõ´¶î ·Ö-=ež;‹ùR³Y£¶5uwÇ.5hpÝ•‘s‘AG™,£À„®\„VÍS(6£y"^™v#0àœ1¦‡"Žá/;ÕUYj„ãó˜ ÝV(Ê;Ã2ìqà¯b4ì…Ðù¸Æ«4v"%m‘lHÒ-üÚ<5ÓcæqÀŠVHݨš’úO%-Š—cÀ-éç¤Ããøêæ30¨HñÈKrÅô¦G$p\ö‰ªA jLùsnÉŽ”°@NËF­d0VQ ÜæÕ˜êÔczàQv)ZI¶h+ãȼ´îÂP –% íŽJµ”™’<ÒTˆµi!ÒæÆÑìÔŽ6N‘H ýÅ»~.£Šô’õ8˜t²â>(ç6é^,nåH'CÒ‰”¸ÇÒ­ÇÆ ˆ$q)h¡Üâjt1M4w<*­H‡RPŸtŠ#ËN‘Î8p°5ˆI§ ­±ŒƒE¡˜«± ÈÓD 2¢Ü\k“¢0(–°Hnã(z‰Lë5œ¤aÚ—jçÊY¡xšUعº¡?ìHâ‚4Ã1#)³æMˆQÉK•o•“ ИyRoƒ”ƒké5he}ºUTÞ¯M&{)ÆA?CB|h ÅËs‹áýÑhÔU<~VÆØE=‰ÎîÄ¿V¾30eýrhËÑ+ŒRÙ£í¼ÂøE í6G¶qø©] |Hó1¯‹Õì6CQp›j”ç¹Æå¹Fe)Ì`¬+ÊYBi…³K"’? C85 cj˜ÚÄW1V+‹©MÑ“QùšÃ£D’Ú"6Ó4cš \§ *"0-à1[³5ÙeYªÚ¿\«SôR+F õÆ•œªêzr¶ô‘Šcª j#i¨£´Øf·%TÔ,cL&Ȉª¶h¹6Í T2EPZ¦šÃ4K½,© úeݪõ N 43"þ;©(´1¡Ñ":ÇA‰Êt¾"N3B1©8ºUAÒF’2ø*óT…2ÔRR•ÚdZ7‹n’AZ#æÈ¹êf£;ÁQc„8£ÎPç`Œ†‹âiú¿âVä JE$¿6x˜Ð œÃhiŒJJ‚Då¬Ir€>œ†VWŸ³àŒ+‹e˜ V뎈2pZ²\M8 ?FE¹n6âê#Ȩô®SÙ2r0*F£âV²¬D"n™q‹[¢DÆŒÁÞ?r…Œ†!Œ™à xjƨMëÞvÇ»Z±žàý@zWašœr!LåÒAÍ»M–qqš»p€âeÌrIJÌ×´1Ôq´üŒ–›f|,ÝÏ F¬`7ÛÉ9¤ú ?à*Í6г Ôc|²?ìÑ…’“J®$Á†óWЦ±x… ¨*%NÍW«HÄ/œY¸nʹ2K4’¢€¶ȱ:PsNÍ}©ëõÙ-€ (û”D@ú6ltͲŖˆƒ½bžæê€Zε¼ׂ™¼“J€œXe2ålœ‚:6iVèˆ1h€8Çl­™‡pŽ8€’`¡êlj¾²(=‰%[Î(ÿ*I‚ÊÊp2m4ˆ“^Èé3B9WlKÝÚïϱ÷ç­ˆ4iñ]Š/SúÄGy¢s*S(›ÌhÁJ'gd: «QÜ&ã0(í0¨¤-ã89àrJ3´ñ<=¿‚w MR—© Ô¶ iH³%.ÏmMæ‡$ÿçêÂdý‹s–Œ8Ä×e٘Ǵi=‚²1­‡E«(-Åäà”I>$Lºé³’\2 {‘âÌÂ!(gèõ¤v4w-G4%}¶¤1*J¦kŒŠ8]/F…9J™îÒú²ÞÃdTœ<ê¹£âý2*- n%ftr¡|QffºÏír™‰DG¨§¾)´½9ârû˜qè-Ú#RÈHáp„é1yšÏïjq Ý%¬!“ªÿðd L(Líœ?slÉ„\yõÉcƦÀoïÛѦã àç”g¦¹àΗ¾<'¯¢,w½Òpý©¥©¾~¥oîˆýæ/;¨ñ¢c‹¨¨ƒ¡ø0.d°§¬&W8Ÿ<&}ny`tŽ/3Íã2 I|Ú^ùÁÁΈ ¸¼C¼Jz:©>׉3s¦—¦g¤¸#ÑÄŽÆÈÊ#¦W)œhóŽH‚|¨t;Œ@$Ê÷%ø°i%`L¶QYêÚyÐj8hi¼J/PúzL^3Œ’L9WDàÐÐ@äÉZâ])gjÞÉÉåb0 @–‰ Þeèê¤ â’­µI5¹ƒÅZ1!@_“¨ÆXl5^‡Á0‹æ¨¸üF]ÆP_äë‘ÔáöÑcÄ 5v%9ª}³”‚Ë +o£ ”f1Ÿ„ 3ÆQë½ÈÍ÷Ro ßVL ´Ø€É@ÏLõ2píhô+ CõJU»¹}™ÄV®Ò›­ÈŸ0¿Ó›B‰ÉAµ­N©ø @D 4"دD³åÅ2r ˆ  ÄD€i«¾¸å •U£aÐ’6^ŒH ‡šœÉÈÔY†Íx“è”R©N§S§«U*Õoÿ †2“,X3×dVL‹«d°6H¹29š@dçA$Ód—³®ä$¸Ú¼íùž’¡_F6îGÔ•¶s £â¶&©¥‰¾U?«~:£ê?G¥3*èVÔ ê‰„™é‰8·ˆ¹<ážDs‡iñ¸Á˜×íQ^0½,ööææ éq¹ÜGº‘Âa câæ)CôOB·³Aà¹_.ÛÛPû#™¯¾ß±nÕÊ.ÞÔf èÇË R£ÚÕÚÝÖ5\fó¯Æ<Í‘sÆ`á¤ÌYe¼ ¯ÇÅ Æ2ÓÜU2¿yZi ÅmZ¸W€1¯Ç¸vIÉ1S³³Ó<.ƒRÜ•ã3®?µ43ÍCi)L{à₊ Œ²áâ1d ³\ÇŒwÍ(r-(w7ù–½‰±9FVŠÁ…S €Qšœ7N›¸Z+Áð‡þ‹Ü<ÍÆ@zVÀð­ñ"e\º":k‰qÏ@s ý 嵄c×"¡üIF«yqrÖÀ¤¿¶eN”Hz›¨ÚGû´MªÄ’¡Œfã”ïTù1I&ÄaFR†+=SM #‰Gÿb†€È`qYšèzéÞ §'÷@ \r°8É(÷1ÍÓ â¦´(s]²xƸpãœI˳ .ë#˜3BPg€¸]ÿ~~ú¸Ë b±r’ëâ‚s,ŠEgØ78 [&ù9<5Šóy¸¼ÄÁBKÌ»žapäËD™”çá¢O91šÚŒn k˜ ŠÑ'P6Ï’A {WÔEçNd‹š©?æ¤ЧA²qÊÖ’È«ìml¯ (›`DZÈfqÖC\ŒÓ„JÝÈRÁ"é8f‡Å¶B»H$;§ïäĤVQ1œ $¢*ɯ¦¤c‡QA/FÅy:åuô<˜Æ¨xoFÅpÃ1*Άʨp„:˜A0*ËJdy{N­*&\­±–ÎX[(ÞÑe¶…â-±ÖÎXÔtŸZ5&Ó³¬~·H!#…Æ2S®Å‹§žŠ&ÿÀßžmëß}ùws&uýüʺÂÌŽ÷–ì úŠRƒ™9}^0?vj¶a°œtÏ»Ÿ´SRlñŒœ1¹~Xµµ}ïÁžƒ]ñmûÂì 5uDw·ö|°3´©!´uw׬²€øÉ¿7njmjmÝÓÕ6pæØÀ¨,¼·­£;ÖGÃyÁ ý™"àe={þZÝúúÆÖmû Óü^Ãç1Ì„U·¿[N0ú 3_2;ozi:¬ÙÖþûÕMsRqšÏcd¥y¶ì ÉÈ ÔTNJmÇrà ÕÃfqUïJìïH4w´b£©“Ǭ4ר׮¼&¶Èk3\ÁÕpŒƒ¶O\:GéÒ¤02Òú8rÒ€<]Í—¥›¥pË€áR×9¨–i< =nnœ@ê©ÕNQñ@Î ôE:`´$£…5q'±uÚ¨rŠ™Ê™. &6IfÒ<]!äì#Š£8P n5å+º¦§.Ä’ÁHj°ˆ¸¨%$ÁŸ6À°È°ódê…¢¾6Q·Ë®}!ÂÄ4ª¤}$û}ƒ‰‹v½þ%¹ðdýÙd#}1Ê!QcìÖº5¢˜ÒzÍܹØ8Åh9TU.MD}æ2 ¢©744Q?²û1„¿dc‘ÐSžLËÒES‘â°Rc R©°'0+ÜhÃp¹ûQM1èK’…£4B·¸`­•ŠõcCìÞDº ÀÁÈiŒ“+…½vÕ¥ÔkÚg† …Q†^Y"“ %Óé+A¥\£–ä&*™É×Ô‚\G'©Ø&¢±LÎÒ…rts²3*E÷Q¡Ï âz(pn%z§V÷$ŒÎp¼;f1†ÁÄcOpËâ.—1~TêÇ;nëUìˆ2RÐ…ù´¶þO¿ÿ]AQi #KÓÒ´ó/Ï?”;ª$™=€0nƒü®®ÆÍCΪíÙ¶þÙÛ—@óÎ]ËîZ¶pÓµ§ú¾zçÔ}{»û[ŒÆ­÷vUŒ ø<ÆÔ1éî €ÁØÌ±’-mÙ€ÅÓshI®nDÜëuØl ¥ÃбÿúrInÀ›ê38‡`·Y×ÁÅ5Ò|®“*r§–¤3€mû¯nh ÷ôM3‹s|‹gäŒ+HIõ¹ºcVmSä­ÅÄÙü ï)sòÊ RR}®D‚wDâûÛ£¯nh uc*sËØ‹ïíÇXWO÷êÛΜW¹/åP¤ ‹TcsÇgm¼±é iZk¶µ35;+Í=­$-Åëꎙ4n(¥ÄqéHxiÛåP”ÉZCVW”Ÿ4Õï×™“ ]›÷%&¸3R Ø‘žàÚ¢èÄ x1¸9®,pPÏíÏ“8r¥67¦æˆ@›·J±AzPK°6%DR)”nò."-A„€#_ÑÂê °É# ƒœÊ0©|†¶oL.¬pÙnb[Ò¡‹sÊ5!»"wˆþK$œzJ®2XvŠ&4Wî/‰`·¨& QRùèì- žèI]LÝ(ÃÞopÎE ±Ô$›ã> bR6ÆU“‘LÚII&ÁA‘Hê9^$S¨CdÏ{S.Þ«pEC„mâØ «Ã(¬Ò´ét6 2¨*°¸²BÆ êpÙ¯x6ß–íµ±(•Àžzµìáab TÓËxhe˜%34*µÌ'5…+ÚÉ\Jó7r( €Ì"GCÀé& _"•IòÂH~Ždë)3šç¨~eJÃB)àŒó]¢‘vc‘Š–Ä\%¨TÊ ‡9ö2Xl¼JPqÌôSŸhœÑáj×#h *ÝQö³ä‡³-ÀP—*år• ²›7u.à½R>T³ôkvg=L3>µÐíñúƒ¡X<ÁÝ®>îŒ'x$šÈ ø¦Œòlk‹{<¾Á²}Ëûyö¾¯^òí‰3æ ¦‘‚.Ìê7^xÿïüdãêëúHAñØ–} ÿ⺮΃)~ßè«oŒ0CÞ]_2e¾ø°§ ö@dêvïÅ+kö¥ pצú ø ÒN0¡(5Íç€íû‘èÐ’{%yþ±ù)ë ›f‚g§yª&d^÷å;¹w/9¾xNy†ßcø<Ƭ²À׿4Æeôa“G§]jiÅØ@šßŽ&R}®Ùã7œ^Z˜åƒ±«N3£4=Ýïêê6{â‰ü ï̱@Š}×CÞ¼;f¡¿äà÷È+ÛºâÝààgÀ²RÝé~´wÅÍ„ôëÍQQ{q®Oz ±î'Sàj?‡‚©ð35äÐG¾_ŸXWgrÆÜ.H˜ü@—•›†SJ¥ãÂ@¦¦r´á‡’ñ*¥Á”ëÖVmüÆ•|ÂE¡Êh:ʼnZ`Ä[-Hj9Š…Ê«— yAJTp\Ã8Ê00s$’ºç‘M#º£36òþ4Õ×BP“9¤@ºjM}¤&¥O žÀ<—4PêsàÜÂ(ˆezèâz S‰0Špι…­á©‚‹õ`˜ÒãRfÙCtˆ®Jj½ýDŸ?}A”«‚hLëoZ^•©M!3^mÉžâ`q´1$Ð[ªyP«ð;&–8Z¢Ð–\”“¦ÓwÍwÆz8J-ÄgŠU÷bÔZ2-Q‘fx\‘298u?h°Jqb)ÛeT“ÚŒ#c:Ú2OvÐÏ0”×Hû—ö¤RBX³¶Œ@wH‘,°œ+Ú†‡éNKàš‹@ŽD’J7 ”Ìpš˜9’~FJ&𬨍JP>o+ݻЈJpqŨ”zT‚J1*ŽK~\]ÇìK~BÛ’¹X.¿ØVž¢î¤¼ûàÀy<Ú]V”[qÓê<Ø´âÅ:4º]Lüt´î]ñâÁ¶Æ¸iEãÖØ¢Œx´;y†d/DÜØÞ²ûOËÿ'ÜÑüÒc·t´î*d¤`æ„Ó/,)-7xü÷÷Ý\ÿñúgî»Ù fiÙøN¿` ™TUn_–oB/™ãÉðG¸kGc$MÀ”Ñi>Úþ*Úq5x|¼·ëžWwþìùÚ;^n¸åÿjÿñQ;d§yf”’®lÇïx¹áWªßÕÒ Å9¾¤}]`0öµ£F¹ Œ˜w½ÒpûKõ÷ýugÜä>ñ•ª|ÈI÷d¦ºàÏë[nÿcý/^ªÿù uÏþ³©«÷K(¤‹`4ãÀ³ÓlG&¦&ÕU”ãËHucðµ…£®:yÌó „啦0ÆB=¦Øƒ¶xFÎ׎:nzöè\ßÖÝ]ÝfeŒ3°äP㙾kO)Ió¹ÌfUc{$!\7°Ä>aöéXL,ÛØW·¥Ë“CËâbÁŠsÎ,¼4nñºƒÖ˜,×ÄWA€™VmÏ.u-ç>f¼«;ÎrèˆBF*º*&C Nà|]F_‹Ñ{ Á°pHZèŸ-¾‚£×)C=ˆPÇ1¶Q(S§¤ ¥('Ã"³€Ó´Ÿƒ\š’¢jÜILâ¥þ…Ø€*y„Ë7 q¡)²+K©‚s ðê5š\ꆒdÏÔ4‘I³(ÖÈðg·méÈ9n!Áf R­Dú*S P6–3 =3`«EVK›Ð3îâ¥6Z2€E*’&§‘-ÎpC;àÌ¢Ϲ0]}šN= ª½*ƒÀ dQÄ¿8ÆrÛÅúéó®—(+50ñ@?d4š“adùBx¡yñFKÙ0iùœƒFž€Îs‘9Á¡açBׂ!Éâ…qª\·$³ÜL›¸3Ç$2eNvËÐðÈÞ¸j2?Ùÿ–hšà`’d8‘jÀ¹…\Šsà oT†$†¨”*NE Ò°•OîYäpû"ÇR®EJƒIפ¹’CêGw5¹:`qÀPÔ0ò,\Ê93 ¸¥qe™£ÅÞW:Ç|Åä#-ò^.¹E£Fäâ>½Ì˜ìJ¡¢dò0" ãÒC‹L—ÉÈÂ1jPgqÔ@©&õ?ø‹[¦ÇYÂân[°øÌÑcJÝÌ|í™_í«ßòÚ3¿òÖè1e Åíb ‹ÇãÜ4ã·(¤«}?ÝxÉ ?§»:š(d¤~’„q»XAј³.ûvvvVfF ;+ëìKoÊ/-N ,ŒÀöT…v­¯­^sá¶3á¥M5Á²`Éåð·Ç ¼´3ËŸ1ðíëƒ 'gÀìqѸå÷ðáî™àߘpù £§ŒNë}ÊÓkq·÷³q»Uª?ùM éx$â¤Øb0æ÷‘hâÕ -§Wæg¦ºçMȧBÝæò·÷5µGû•`\~êe'§x]=që©wö54w—s!J…ˆ\o8*Åó{ †ã\¨B= t§"!¬‚cjÊÅ€uDà“fsb¾;;ÅU”ilÙg¾³#îb`YL0ëŒT6:ËH$dâŸq ÝK åÅR<‰©>ì©zÊ[à~ 1-¹iÐrE@ó_à@yl†Çµ¼6Çæâ´Z*"îò‚c°4B“ãÅÉ·È¿a„=‚»kTm¨g¦'-ÔN30¡Ck*Ì& ê [«Ž ©ŽÉ¯rΊ{1€B†²-ŽÙŽ@ù9]¥õp\M@±æ¬0ù¼ÃK)ë%´$XÎÚeúBÍí¦û¤­hÖ¯©ºai ±ÍF Z™¤OE£(›H#†Ðÿ”§BîŒ:RÃUŽ5iÎLìþA•26ëÕ4 Ò`¥­XhròÁW4ɶãíˆå$ô2ñ¥¯èíEµdP÷3n3<êFÐ͘ãî9aÚCbÒðq°ˆá(Ç ¦xe 2“ÇäõD/h`‚ÊZéòsU&ЊVAH%snŒÚÍÐ?ªk4ÿ!¬™¥Uk¨ 4“#“K8ÉÑ××tóÃë[£P q¤.ÓªàJ6ñe¯pÔÒ©ÌKãÍ”á¤2ìQ™êÒvrÂjw·vRý^·‘›_tî5?[ñÜ=ÝáÎ5¯>æuñ¬1¥§\ðŒìè‰Y:¢€k7ýR¿e~c.¸së{U',í¯‘B’0Ðy°©fås™t1`7¼õlNþw2sF Ð"ƒ%U¡]ëÌ]vu*Ôì†ÊR¸åtøáŸöî‡Yg@Ù´y ¿6zàvè9Šç<ã Sݸ±‰öZ £sý‚Qí=ØóÌêÆŽ°9mLúe'÷yqVš»5‘‹€H¯ê]xdsCè¹w›ú,gݎΚº`Iž??Ã;&×?obf Å}âÌœ?ü£ïë`fiúǹ]¬3b>ñöÞý1møÌm–°,‹C{—ÙÕ“H÷»²Ó*Ã(É6NšìÞ%an !ÄÒÍÙ¨`ð´Ua§V¢ eÓ“^ D#eô¡xœc¾]úHŽîx;Ðetú¨,ï1S²™’-N…ºÍת[Ñ'àb½F­ÄÍ>DÅËSíÔÊL°–—/çÜíb£2ŒÒ\c뾄â@L1”‹á/Š;*³$®ÒÖ1¼’“#ÿ©»IÈÏzâ úHZ”ù";úeW‚ õ»’¾06hÞ^£+ b^ŠãmÄÝdE‚…{ZI8 TË“Àʳ@l,“͵q,“’8ê@i2‰féÙ¬~˜‰Á1N3Ê3`(V´H†rÎò®t©ýÏ”¾PT]fèeºù÷íåãzß§iPªGO‹s¤*FÙ,—–h´,X*ù”À‡é™JÅAM˜^ü ¤å ’BA‹âÊÂûcQ@¤GŒxƒ–Ê|Ðx!"ƒŒ›quzU"²ÚaM¢j©6 Òæ,Þêk|4:ûáRHbð7ÓZ‰î h4@_©)í,µha {\Ñ)•¾’çlܸ•“¼ åá§ŠØöž¾è”&Ñ)àè•I›Ì&cœ¹´G®i{è”TœáòøRëöíñ²½ž,‡‰§¬ Æ”Œ)§ËB=æÁ°Ù ÕîmÏ((Iúë.ý’„ )$ 3jb•ßï=avJz6dfç-:íŠÆºÍY%³)Ì¡I•{ëïZBЬT€_žgüiþÚcž ¤C¿ ¢~p0ßÕÚ3÷•cízâÖò·÷9¯`tޝ4ÏÿÉÞðší]ØçÅ¿_ݸhrÖÔ’tƒÁ'{ÃÞÐÒçäÙ¶/ü»×w/žž3nTJºß•H@sglWkwM],‹¯ÝÞQ’çÏ x2RÜñ„µ¿=º¡.¸åä€OžcõĬ‡Vì9iVîô’4ú35o~p #l¨—SQ¤ — ¢qÈN5|nhïæÑ¸d3¹ÇÅÒ<,'•åºÂQ^ÝÆÉXÂ…×rÈtAÞ£%®dMŠY c¥t%®0µ‚]ã8ò mYÙØ#j`cW"‚I÷ƒÎIm¿Ò`¡”9ÚQ!›Åè™eZ&Þû>(‚%}5Uµ•1Õ?Ç’³f=$q,Ðh–n=5€Ö-änET¥­é¸¦‰7ØrZ |4öl2ß-œ å(c˜ÅÉH”ypôڌ…*Uܯ“lû@o fÿïëFuÓkT4EµPKÚr’F˜¤Ù!gÞ›6%‰¯)s òŠIƒÆ§Þ ´oOR:m £ÂÀ©³(uTÒ/-UEä õ ¸ŠH­Ðsc´€¥D$"EZËÛ Õq%•Ú}.Ì”KºªmjDêÃȈqÄ•…3´éL×IË|²ºsÈÊ0¹ê¦¦‚ŠK‰²í©©$cC'¤ö€“3I§ð„š|&ïÂÒ²SÊK§8š´¢SŒt2"tŠàñ¥¸S2w5Ô›|J<á• 9énÚÐOð¶.³¹3ÚÜÖݸëSoZ¦Ç×Çë–F¤#Ò¢¬”QŽõ§»éý[)ìì Ç6wF›Ûƒ†ÝrË-…ó.ß׋š}¸¨Ð®õæ-Û°ž¯†_/…?5ÎË{C`ì²Sú× ]° @²…ìÓ5´¾qN± ð¹Yv øYªÒ¼ÌçÃ`. ƒhœ7´Úº,À™%曈_`-*°’ŸãX/©ä7ð¸_‰¨“ˆ ˜ G.BìCËüË LŠy$ÍÝ´ßÈÇ(ueÓ'òÔ².½$Í µ9E%©©´ÿXV¤Eí³)õƮ݊Uɺ¾”Sñ*R-UÖ¯Þµú*$ùˆN€‰ðiæÐ T•ö±:$øH¿4î‚V©16 Ú‹A$·¿k¤‰x•íQê@a ·ZS…ŒZ§^{oä¶"´#È;Å«Âé;P¯6"á—£"‰q´¢:”žÔòUL†dœ™2*ŠÆ –®d²ƒÓý~!ƨ\ ­²žÜV-&ªqK….EãR4o’YfÊPiŠ¢ŽS•é§÷)Xå'•áÐEÚúmœô Zg`eª$l—üïO*å”lqÅÖ#ŒhO$ØÖ”ºGìò¦ægxÒü†Û0LË ÷X­Á¸ ïnø4Êü9E>ê‘+äóÓ"ŸÛåÙ¿áÉ2UÍŸ®|Ô2˜WO¿×¶>“5qþP³Sú†r`t„<ŸZÜæêõ«Ï`äî‰Ãþ¸µ?¤®Ókãx¥-~¢{uHï¢{3 ¸8!wQ„Q‰+lz\°“-÷€þW:FLCË]ãÊgHç…a‘N)ÉËGåÄw #Š£-"èý¹Æ•¨=Hv„„Ú3M˜@#§ö‰qtqÒ‘kÒ2Ùƒj®nÆÕÔXNsd¦Ò’L¾‘Géζü@I+CËqpRm÷jÊViõbqT¶(j¼–)º¡ ©3euœþ×c&öŸ–ÅE´^è; eG?é,{ û•Aò‘´z‚SG ] 6Âà˜Ó¢n Š¸NqTmL WLÞRZE=ÉtÆ^Œè< k ${°u32*J#âž!\á%m©åC,NˆGà &&Hÿ» ª"µ‰ˆ¿-Íh_Lb9-žt`.à ˆu…;Ôïnògä² ¼>ÿ-äsÕ"H_¹Ì}p?ÙuCê•ß<²Dñ‹ ´éeo(I¬ü*‘íZ°ÅNá pf€|»³E3ôZÉ2V*2§äJbW4N \ë]QìÁeÀC±+Z£¦ B)ýŒðQ8‡§üC"ÂH‡Ï,Æi]³$" Õ–îÈÅÊ{ˆÚ"¥äº9&ÕèálÎ5z Èðd»‰ ‰[ñ1=¢Y”9@éé0mRϸ…iZ #¬?-Óëó°?"…|®Z$0©ŠŸøÌù/KÿÊ3yÓõ¾# Ía~¤M^íëè€OÖÚ–*0&0ËâÊQèñBÎh¾ŒsS¬XR,:I!öèSþÁ³+!òáÄMQ¦æe-øD`ºˆ¦}L±-Š4´¿I1óåD°0v0U•F°ΪITÕ8XZb‰ö‘Ø8îÑ9 ¤),§ôURŸôÊfáFl9tà`©©å´lY3y©Æ€’ùÈ}9šÅÉþ²5 Tu,³ß¨±1¯äcIèÏ?õZíóÞAªþ¾Ð¨±¹HÞ×/PJAÂDÈô¥:½‘´n æ¤7BÆU=ÿÄã`¡¨dšŽü(í“z›)÷ odÒ Ô< = `"ˆ$e€I†̲ٓ¬Hed¹HõÊHÙˆ9#9[¢Ù—LJá‹ Tg23ô¤¡¬B±©;Ë’i¢ªÒÇ¥/ôŠ”fÂ…H}ŒdG¦¦¨ŸqX‰Ú…KÒ2¿ F³,%Ùáë7r®=‰ƒjù,è”c^ŠÛëõ§gģфå–Å Ãåöy|>·Çg f_ùˆ2R!a$©"^¬cÌ”ù0e;ôuÊÁȃ!ëA7‰³š9ØÜ²äCô6vÌ<+Z`ñ$Pî_eCtv%é”4vÑ1¡·4vÅmÁ¤7»’ JZ¤wãÌ‹²YœèǦžÖ•¹ã }‘x­°ØQN{b”sg`Éé-£%YLÄ'nÉPRá*ˆ©ô”zƒ®p€c‘†´m HG 9i`À,N mò&ÿ`‹q‰Qv 6G6›x ¦%8Ö/­Åz’òwßBºLÝÁIPš£Æe€æÖB)±½ƒ)ØÏèÎ^}½rº7íuú»‚ÙOð¾h“NRé:¼h¬}Qq0”Ê£hv꥘ŒIbN€¹KÆ@'Y5“F,’Ø(âòdçB ª‰Û(ãš} æ¡–ÐD5sÅÒuv¥H¨Ê‡Q œfJ’,‰÷–XÄ%£z¥æÌ“lÆP'1ÚáΈ"¡”G PßÏŸæx@q)™ìÓ$.…‡\ªŸe>]­ÒàDjvenáÐSShD¶Mô\Ž8t:E ?ò¹œ>!óxSd:_hCß[ñY2R¶0tvoTD骤AÙ[ÄuOÂõzm V„!ýHn W®Fk¤MBÎÁHAÝÎmÕ(·S™!ÖzÁž¨´Íülƒ[‡¤4E®LÙ0{av»Ã¬ uÝiÈ+íÁŒ¾1ÿ´>KW(·ÉpNÃì¥$·E39×R³hÜÇ…•©ãÀTÏs ž•ß±+´ êZsæŒÚ$}æÖ’¢ê!-oÀmÚHŽ>jÈhƒJ»Åfð¤‚pLÿ+µÃAªßÓ?ÜBün7Ø{òPVæmŸök·HôùŠ¥!1P/F€Þ¤êGgæf8pàÀ_üâÕ µdR‰$ÿ8pàÀ‡Dò:8pàÀƒaÀ!U8pàÀ#‡T9pàÀŒRåÀ8p0 êz=—èÀ8pà pí_'SåÀ8p0èÏÔ8pàÀ8è¶<•ÊTñÁ«çž{¾¢bVÒÏšw×<úèc %®Ñ? DÕ‹ŸÐÝÝM÷5VTÌzáņWæÕW_óÍëo!“±~Æo^ÃqÇ_YYuÚi§ßqçmmmG¨®Ä믿^Q1«¦¦†Ž<Ø&4¯_öÜóÿWQ1ëÓOkáðzüˆvÁ0‰Dz[8ýœsÎ9Ã+vÄ… úÁæææŠŠYË—/ÿ—ˆäÀ_8ØéÓ0ÿöß¾ÿýü‚|ú:eêÔ¦ýMãÇ—¾x‡D[[ÛþðìUW}ý3¨ëpðôÓÏÜ}÷Ý3g~ó†ës²rv|ºãÙgŸ{sś˗?^VVö¯–n TVV@MÍFñjjªý~[[[CCøqãÄÁ55YYY&Œ€œœìϦ÷?ø|¾_ÿúnúzË-ÿ3vléW\!¾¦§þEr9pàÀƒÏ;†Iª.ZHÁU`éÒ¥K—. ‘Uõ£žzê©ó/8?žþT7<|°yó=÷ܳdÉ’;ï¼Ã0 8éä“Î8óŒ‹.¼ø»ßýÞ /ݱã†nWÌœùû?ü¾¿+ï¹ç7yyy>úˆ×ë€ùóç_tÑE>úȽ÷Þ;¼ªàšk¯yù•Wžxâ¿ýío%zàw¿++û›{ïq»ÝPQ1ó´ÓNÿß'þ÷Ç?ù1<üàÓ'O¹óŽ;Ä¢ÕØ±¥çž{þÔ‘µ+îê ]ÒûTaQ!c¬iÿþÁ”sHyâñØOò“ÙsæÐ-K—.}â‰'¾ûÝ›ÒÒÒà¥_JKK=õ´S‡$?TÉmU53gÌØ¸±ÆãñL›6-++«©©©q_cñèâšššôô´É“'õy{<ûŸ[n™8iœwîy/ÿéå7ß\)VÐÏ0ŒÊʹÖ¯¿îºk-˪®®9ÿüóž}ö¹H$’ššº~ý†iS§¦¥¥õôôÔÖÖÝzë­'œ°X”¹xññCm~غ>KY õ‘ØC2éx<þÃþ`Ò¤ÉpÚi§}ôÑÇ>úØ^èqÓK8pàÀÁ ÃÌTÝö‹Û–?±\ü$Å ñx|Ãú §œ²Døt`Œüâ›6¯^üüüóÏ?ÿ¹çž;xÐö0],ÛúÑG'Ÿ|²Ã@~~þ¼ùóªkª ~ôñÇ'Ÿ|’ç0yò”±cÇQQû„e%yÍ`äñûý³fÏÖïúÚ×¾ÅþöÚßD ¯üùÕ¯œþ•”””¡J8zÌèQ£FÕTo€šê3gz<ž±cÇæääTo¬€ê5³çÌq¹\}Þ£’¥ÓÜ҇ꂻÌ_°ùƒ-±Xlûöí¡PèŠ+®ôx<k6À†õæÍŸ/t2~|ùC?ôÜsÏú駜ثBïÂFVB}$.bùÝwßE§†jÒwîܹôuÑÂ…uµŸŽx8pð…Â0ç 3gÌHÚ¨Þ'‚ÁPÜ4Ÿ|ò©§žRëœ[‰Ä¡YÅÀ¸êª+_zé¥ÇìÒK–éÕY–•“«_™›³í“O ê²,+/×v6??ÄEMOKKKKÛÛ¸·÷©ýMû9ç…EE‡,d0ò¤¥¥;ÈÍÍ9ñ¤/½øÂÏ;÷¼·Þz«½½íÜóΪü•••ï¾û.缦¦fÑÑ‹ÄÁ¹sçÔÔÔ,˜?¿q_ã¹ýïL÷z=úWf0Ë4a]0@÷Àüùóâñئ͛·mÛ6iÒäÜÜœ9sçlذ¡°¨¨­­mþüùâ²xà¡zøáGÚÛÛòòr/¹ä’+®¸"IQÃÀÀâRx#(aÒHlnnÖ’I§¥§é[™ÐÙ†T8pðÅÄ‘Mìé.—ë’‹/>7:`r IDATûœ³G¶äì윋/¾è©§ž9åä%t0##`F{ÛAýʃím™™™BÃ0B]]úÙP¨+5%udEeŒÍŸ?ï½÷ÖvvvŠª ï¼óuÔñÕír%å)bÑ(ÒGžóÏ;ïÊ+¿¾åÃ_|ñ¥Y³fMœ8qx­¨ªª|íµ×¶lÙòɶmß¼A¾GjÏÿßÿUWW. wÁÀÝ&NÌÊÎ^ÿþûÛ>Ù¾`Á<X0þŠ+ =Ïœ92iW\\|ëm·@}}ÃËúÓ½÷Þ—Ÿ_pÆ_ª´IX¼C ¯ãI˜„¡šPW(‹Å(­ÕÚÒ ™™#+•üãÈþ™¯×[UUµ~Ãú’’’qv~á—_~¹ßç}äÑGôêfΘ±råJš‹·8°aý†yUóÀçóMŸ6mÝûëèúÖêêj„¨—_vy,»ýö_ê‹;ûöî{ä±Ç&NœxÌÑLj#ù£ šöï·,K| uuíøôÓÔ§ªªj„ñ¿¹ç7ÕÕÕÃNS¾­jùòåœóY³*ÄÁ9sçîÞ½{ÅŠ~¿ÚŒC-ó]0@÷cl^UÕºµë6nÚ8Á|˜?Á¶mÛß~ûí3gøýþ¤êÊËÇÝôÝ›|>_mmíPEíÅ;¤ð}bd%ì-ðLÈ4Í·ß~›¾¾ñÆ™™™ã' “”;pàÀÁ’T ú…êCÆÍ7ß´s箯_uÕk¯½V]]ýÎ;«îÿíoï»ïþÃ/9##cÙ¥—¾÷ÞZýà7®¿¾¡açßpã?ßýçÊ•+¯»öZ¯×{ù—‹³×}ãº5ï®yæ™gâñXssó~ø#G-T ¨sæÎùÖ·n|ýõ×/»üŠW^~eÕªÕ>úØ…]äq¹î¾û.ZdYròI=ôP(Ú¹sçÍ7ßìv«]JÖçÜóί©©ÉÈÈøò)§ CxqãÆåää¬^ý)S&§¦¦ŠƒâóêÕÿ˜UQ1¼ýËwÁÀÝóæÏûpëÖžžž¹s*…ôÐwÜ vfddΘ>ý ‡üæ¤>±lÙ%Ï>ûlGGY¸ð¨|ð¡‡úö·¾ãñxª*«î¼ó®ââbqöØc޽óÎ;xàÁ{ッpTáeW\zäD½âŠ+¦M›öÔÓOÿúž{ÄÛ&Ožòè£egçÐ5S¦L½õ¶[zè¡'–?QRZríµ×Æ¢±Ã—ç”%'ÿòöÛÏüê™´”3Îq ÇZ _@kù6Ùƒ/Mª ©q/8‰kƒƒc-/ µ|›ìÀÁ’TñÁ½Q]$®…SHBÓ˜a°¤ƒ±¸Å «3€ªŠÑ‡)î<˜÷ì¼…õ”›™•Uê_åÎÈj!œs˲\.×PoL$†a0–ÜÃÃÖò/G"aµ´FzzÌqeYÃ.¤­=ÚçñM›>üÒ‰UÃ.¶?ìÙ»·;ÜM_SÒRJÆŒ@Îãs2rÓZÞ[»véÒóú<Õ¸oÏaÈuñy 8$’Þ¨>¨L•ÀÀ‰kÃ`³gØf]ç=QÓ4-Ó´vïÝŸ•¨ÞRçðªC \—š½>%‹eϼªcgÓÁV»*Og^ÿP‹q¹\»7U×½üB°í Ûãå–F›9×3f<ž•?ªüìsKçTr>2¯ès¾Ì±kwÛ¢E³×¬ùð0Ë™>}¼ø ì<†0óí¿W,¯ª««‹Fc‹OGV­Z][[;a„Ã,ùó0r‡a-<ð`mm-}mim€»îº+/ONB"áÐ{ï­ýóϔœóh4šHXtÄå2|>ßð&!à {6oÞö׿eFÀQ]˜_V6ŒB8pp„€¤ªÿT•ž¦ê¤Êe0Ë`.ß×3oîÔ ?9BÞùλïÈÌÌK˜f² ÷¦êš‡~pxÅ®ZµêU«[[Û,‹[VÂ4ÍŒ@ÚâŸyÆW cÈ/¤hm ·¶E¦åvÁP$7fÂãv™¦‹%L31Õÿè‚E;`O- ­L=PQÛ6Å·üÞ!TÁ9gŒ­{èþ¼ÕqÂq)3ÇXñËâÀ8XÀ-žHpËâÜàœC¤ù@÷­ß^·äÜ£®»a¨Òq8ÖòYÂåò0áp7@6<Ønk‹Lœ?¬çŒL¢ªªâðy•eY-­­ÑîžÎ`(‘0O9eIOO­ªª\³æ½-[¶ff|)þÂQ£§®$|6#÷p¬åwVÕÖÕN/9eg0gžyfqQa,€®PG8úó#&m8©¯«cŒ…ÃÃ`>Ÿ˜1yÒD¿sža7ù@]݇¯þ¥ ›;nܼ‚Q¬»Ûr»÷¾öÆ»õõ¡¢Â…]˜UT4Ô9pà``OU *S5¼4µÁ˜Ëep¤¦úÓR=Ó¦Œ«ÞÒ0<ïüæ¦à]ní}üªEÆi óÇ›ôß7Ü`ÚÉ!p{ìüaT·k×Î{ï{ È>þø“gΜâñDkkÛ‡nYµêígŸ}îûßûneeåÊ<ÐÞ3qR©á2ÆfÿŽc1sT~Æ®;]œåñ®ØÖë ‹A@§Õ¶{ÿ;-U‘?¼{í5K_cl÷Æê¼ÕM=÷«áæ]¶lãÀ™Ûe¸ÝV"nÅM`ÜðxÜ)©.—3`†Á ÃSZ`dgd¿ñâîù‹Jç­QIÁE ‘Š8üdLoöÓÕ¥VÓö5¶†¿´´ >¼2c.—+‘HlÚ$`‡É«jkkóó òrs[6ož0~Â’.(++«­«=fJ0ܱcǤI“†]—޹àp¬å„Å‹ó›{Äg±ü l·ío’¥µìo|iáp¸agÃÄ£ñ”Ô”£Ž:jÏž='//oÍš5‰Db¨b©É¡ææÍ~5µ­­rÊÔ KJY$¡$`tw—Æb¥@Âpízá¥wöì6Ç_tÁùiÙÙ‡,–P<º¤¿åÑâÑËõ¯û¾>øb8øÂâˆ?ýG©q¿ÏåvÅEÙi©þáÍz/¿'_qZÒÁå+[¯ú隃mm©–L@g‡7eCXåX¿aÃã?yÉ%—-]zŽØ›‰Äzz¢™™YãÆ[²äÔ×_ý§ÿïÖË/»ø¼óÎ|±Ñh<Òs» XSóiIaNF€¦¹ÜlxÒ›×S!îµBO"ãcE3Ö½U=$Rõ¯¼8}ñ±áæ]í«Ýó¸¡'‹u§–fŒ+w§ÌH¨kÿ®žöVoz&¸ÝÜ0bí)ÙYñ™åõ¯¼t8¤jDVýzs©gW]]‘X, …Å×ú†fÓtüÄÍ›kG y±•`F"‘X²ä¸`°+ ôÑöÃÒ4EE…0pð`[ïkf"===55uÿþæÃ©«7Fpäö‡aXËc=þøòåÐÒÒúÁ–-ï­] ?ýéOrrràŒ3Ïa ˆètw÷ PTš[ZÞ|ó­éÓ§õwa9<çÀ%%%°ÿþh´ïMu`HM~oùÆ–Ï9õTwQ1Äb Òð¢c´]Á`9@y #ÔþÃYçL¸÷žésæ ^¤>y1ªÆ}_Ÿ‹G/wx•‡Äú1fìí¿¯yûï¶ã;v|zÑÒÅC* êƒ-íòk,—œ_~Ô™ß^÷ƒSXÝ~ˆwƒËœsÃ`œCÜ„+yIðعs×£þïw¿û½N8æƒ>)))òx¼¡P¤§'fšf{{çÞ½M dÝ}÷ÏÆŽ-]°`Á KŽÅÍX@mmmFFF^^Þˆl!èêêŽDº»º"ðñ'{óxâì`°»µ¥eTAé0 dŒ¹\ÂÚp¸»»;:ŒÕáÞ0MÊËÇmßÞ7?+/gš¦eY‰Äíd`ŒìÈíCõ-ÓgL?å”S|9D£Ñp8|Ów¾=®¬,##ó¦ï|û0…)**úê™gz½^Ã0Ün·eY¦iÆb1¡áƒÛjëjãq™ÈŒÇã–5œ ˆƒoòþ×߸ª¸xÓóÏGrrŠÊË'ååõfTÀX‚ó}mm=)Çttu U¤$^¥3*px•CÁÐR8CÍÕ㜌]yÙRH${÷µÄâ&çü¥—^²°Ý‘xÝÀ0€õ»`Ê„‚ާüüîGšÛ Ò70 Ø^0‡âú‰Äý¿ýÝ¥—^qüñG¯][ãñ¸7nÜZ^^æñxâq³½=ØØØ4qâèmÛjKKË/¾øšþèÇo¼þš×;¨}O±˜‹›Â~åÄ٥ʹέöU†¯ºrÀr%º2ÌÑsZ÷vv¶›æ× —ËŠG9pæq[ñžÜI3g|ó—¾¼b0c¡]ÛšÅãÒŠËÓŠËSæm{úÑ®NÃð0—8©GÿàPÖòÆ+fΜA_Ÿzú™ Î??##0a„ÚÚÚ)S¦”••¹Ýn¨¨¨ˆÅbÛ¶mÁLU(Ô÷ƒáM›ë|¾´ªª){÷¶®ZUsܱS§XƸeY‘Ha†aÌ™3SBp¨OÆã1˲ °°°°°P?¥ÿή®.˜8qbOOÏîÝ»GdpÄGî!1Hß²háÂqee+V¬hiiu»Ý99Ù7ÝôqŠ> Ù™YYY™k×®«¬œë÷û'6M3‰twwÇbñ`°3ž^TTÔÔÔär¹ŠŠŠêëë§ÆC69`¾1%ó:;¡³3ôɶ÷B¡D^^騱c}^`Ìâ¼¥³³±üO}öÙ®Œ ð¼ýÖø‰‡!Lï|•ΟˆW9pà``ŒØÓý1À].‹™]án‹s—˰†ûˆÙ0ÀÛߨY¬6RæŽ:mÍ.èî_Ù©öÁCáÝ5k22rÎ=÷«[·nOIñ——··?þ¸®°°(7çÌ™œ››‘‘xãu F~ýÉ'Ÿ¼æškSx,ÅÍ„eµ#ç|¹’sÎ9om A¢;3ôWov:´¹ ê …½ui3;‰îHþ´Ñ Á-nYœ¹]¼ÛÌ,™0ý¿ðå‡vlÜù·§»öÕ»RÒÀ23'Ì{Æ•iÅå“/ùþ'OÞ–àsR›«FväŒ!ù–¢¢¢÷×­]ºô¼’’1´§êð‘™•9göìM›6mÚ´iÞ¼y¦iºÝn–ÅÃáȺuëB]]3RRR8À0^\"pÈ&×ïÛ_‰Â”©ÐÕ`Ç¢ÎNèìlÝöÉ?Âa#˜pÒIy?üaa¾í¹ 3¡oó`•‰C“ªÃyB‡[œsnq‰D{¢1ÓLøý^nÁ°ŸÛß° ‚ïx¾…¿\œ|jŒ=ÝÄzx´;‘–é~ø¯»çL)QXµjuIÉ´xœWRS³5ŒdggVTL¨©Ù'æÍ›š•ˆÅâ›6mcÌsà@[YÙ¤¿þíoƒ&Uf<žvõlØÒ`š ÓLÄMËL$ò­ÇÍØÝÙâií‹¿5Û¡žht;a8Ëp¹x"^þµ«üc‚¯ÿðÑŸuw˜¼ô¿J¾råÇß²ïý•ÝmMÓ®º%}̤ыNݹúeWJªxpèÕ) ÉZÌ„i&LˆÇl{çL™ýgqn&¬„ÅMÓ²þ?{çÅñð·»W8®Ñ«¨4i*bC±'{‰Ø±ÆÄK,‰1±÷н$1‰cïí§± vDê\Û½Ýýý±xž”ãî8t¾>|v§¼y;³w÷öÍ›Y¦p^‰G@mÆOE‰ÜIM.À…§@`@2@3@3@1— *5`<ÈˤsÒuÞA<žKM™È²æ²³ó|}%œŽŽŽò÷¯}ïÞƒzõe2i“&a ÃÊåbŠ¢ÿý÷FFF>Ÿ/¸}û2†yyæ1pž*–¤ô4EÑ”ž¦ô4K©Â=.ñd^EÍSçÒ§3<Ó0š$IæµQ¥×ÓaÁΜ4E:ú;Öi ýpßo:uÐÑÅÞÙ DN.vÎnÏžÝâ×wœ<¸©ðú)šeÁ?™æß-,ð «×ëëÕ«›ÿ*ÄN(ôóóKKKS*•\LUrr²L&óóóKNN¶Î_U|FŸó€À/ó´ è Ï?T(<:vlöäɋӧ¯|õe¡o…dšf9ˆS$×êUEšðTÃyS†IIIáR¬±«2žÀíó€LËzŒÏ}rí®žpØ´P.qK³˜±—ZýÉ-2ï– /6hÔ¤Vm_!ŸÇ#pÇ1 HŠv÷®©×Ózš¡h†ÒÓzšÑÓŒR¥}ùìqê3+Ý-8Ž»¹¹7mÚôü¹ó¿~ýú`ˆ¬J}–š™•Ýþ#ww÷òÌ•yÉ9s9;üö‘Ë>zžà|p_;:»yX¨_`88B ŸGWþ êÔ‰+ÌhµŠÃ‡ïnÛvúäIªysÿ1£Ã—k#ƒ]e”òFˆ0AÅ®þÓéh’Ò³Àâ8Æçñ§i†eY+¦´8H=P4€B <t4èÐÓ@Ò@Ñój¢OG²*†bgAB€Es$I …vIITªüV­šÖ¨QãúõÄÆCÄbËIÒ'Oþ—“£!âþý«/_¦ŠÅ. m®|’¢IŠfV,èùŒž¦u:½ nz×b” —$§¦+dÁšŸ¡Rët$ð”žÖjHŠ¢ø|¾T*2«% =%ööå9¸äÜýOùø¾@îÈRºÂŸ|€e ‘Dùì™›!võ»x)_>Ê驲ènÑëiÎhÈÊÊ6$ê´:.TE¡Pr‰D©T*•J/¯¢îÉ2Ñå½H=41$(É%Dr^ÁcH?þ·0 oDØ(jÏžµZíÈoºÙY´Ø›°,KQöŠn­[¨×Ó¥y†J³­ëÖ­ËÅ¥§§› …$\e.¨”øîuäGCu8_xå¸Ã¦…§>y&¿CÕì«?¹%bæÝÒ³÷'1}û»ÈÅ"¡@(à8–—¯ÎQj 4:µ–ÌWëòÕº ©Ö’R3/Þ–‘xÑj•0 suumÓ¶õ¡ƒ‡…BaPPŽãz=’’rïnB¯^»ºº”'îМK¾ùB/ókLkÉ'jM"€´•÷}™àG·}wî Ð6¯W7ÈÇçÁ?ÿÔpv~¸mÛù'” $H;Ä-neoooµnŒç‘E…@X„Y¯©±bõ·)3Ͱ†Y Ç07¸¬¬#_ :-€š z(H„<;`@$ @,¹ AbMY`(Ð4Ã04AàII322üù|†áAp?pz=›‘ñ<==Y©ÌÁqŒahógèt:Ф(©Ä.ºeM3Z-©)PØ)΋\!ŸV¬Ê¥)Ÿº5iERôÚ?©ÕN—“£äªSzÚ,£ŠeY–Å0I€¦H†¡p‚ õ8†á8Žóy Cë)R€8‡< ™òÙT–Ü-ÜáééiXQÅalc€J­¾?±Y³HK§ZôÚ‚GÛ>ýðƒÜÌLÁÓ3”£'# xB*n·Ì©F££$Úg×FN›"Y&¶ØU˜š+Ϫ@†e ž*½ž¦i½Á &“ÉŒKBÔj5EQVúQ"ÚÁ‘M Èåû?gþìÿZyJÈSæ}3Kõ‘ V3£Ì»åô©ÿÍ›?ÿàž¿oÙéꈰ8†Ñ KÑ´žf@O3”žVª´R37ÏûºwÎ˧—kÇO Üœœ{ôì±cÇ?†Õ¨Q#99ùêÕ« Ëåå‘ÌQæ%»Ù³”«#CR´–Êå´ŽLUèdDʇun ÞIçëß=%ß²¹À×·V×®ýðƒÏìß·ÚÄ¢âàì*dQ!ebýkjÀ ǵÁ'¤Õé†ÅqŒ[ˆÇã÷ŒË#†e¬ü~.ÐI(5Ð è ÐëAGø\?õ0ëÙCàAVŠîåc_¤Ã™{ðû¨IaamAët:gy<ìùót;;»ØØž2™˜$õ ÃJ$v=z´ùãô7ÒD"Ë⥧ôænIQz’¢…zZ¯§Õj­VKÑÚ<9öÃ< €eçfåÛûhµ¤J¥Q©ÔµÆJßË`|>èµ@…<;{ÀpŒ ¸ŽÇ0#x€ã<¡ˆ'Ã`'ø,SÞ˜*cʼ[ ¶T~A~‰rrr¸ý‡ÀÑÑÑÒ€ª»û–¶|qý¢½°ñ4§•™ñü¤Ñ’0ˆˆNn7h/’,ódN(ø´Æø‹„]E¡IÍYN%ZØÜª@Š¢y<œa,¸ù¹µŸ:ÖÄê}Ãs 7QÅÍ6Zv Ü'Wæ ôÃ6-Ä”yDÊûËç8‹J9r¶6ºaËóÉ-“ÒîOO>½{ß¼}gò÷ßN1ÛÛÓ]Èçq³à,ËRz†¢™¥æÑƒ¤ÿíûkÈÀO[4tuu+¿>R©´OŸÞ¿ü²~øða‡5j¤M,*cJ»äz> ïŠ\kv\Bku­ØÎё֑a²øß/’$iš¶³³Ã0,§€¾Ÿí6já¢I#¾ˆª_¿<*¡@u¢œØxõŸŽ|‚cÀ0,À¹TXÐ3Àà¸µÑÆùZ t -úWUÜ îàäÀòA…«‰\…<ÔS£€oÄ·iÓÚü&ä2ifæ ww­VS¯^XÿþÝÄb¡^Ïnß¾_¥Ò~þù'ööv#F  …GŽì··çååŠÅæ> êtEÑjµîñ㬗/ó)JOès½<€rL@ëñÛ©š'\£}bgÇÓh´$I2FFn¶“ehB $s_2¥SPC¹]ÅÓû|±'ø€!è)ÄÛ_àà¦~ñD—ŸÇ³‘Œ þ¸£XlDz°sç¡Ó§OQ)ò‡ éÇãგp/++7)éFçNæîxNRz­ŽÔ“$޳öö<ŠbyJ†ñAM¥?¾ñ¿Âz€1|>β ERIqÁ㎠…B¡Ð,#˜e¥iž½$/%)ïÑ=§ºÍü» ºûç|]’Ò©@¯Ó*¥¼VPû€öÅ#=©%DÒòï§`éZÑ2wúQƒZ&“ɤR+”qêr²XÜ£ð¥xÏO- ¯¡§žÁ­›¾Þ5E ݲž ü@wûÈ$—î;-m‚›øÃ0ðⓀÅW²–ìÆ0ŒV«£(ÒDî?I’ðêÝ;ÆæcøäâO±ôt΢…Jø×Mû^¬Tåûä–ˆ™w‹££cTóæ>K—öëÿIßO};~¢L,Ä Çp–…§/rNíß ê—Ë–.õõóåó¬\ÂY9˜sÉRÉ’qŸ^½„ßìØÅ]_æ„Ú{¶”ÔlÌÙ]z˜Û.‚=t]±õä­cç÷8:œ¯Ê4ïåàõq¿ÐÎGœw1ûÏ€ ïšþC ,¥¢Vÿa° `°,7X8IÁý¢à˜•“j}áöèJ0Æ`TÑ çŽiÀ 0€]Gkùƒu§Ž·mÛöâEŠ££Ë¡C'cb:=zæðáƒ|>Žãü#GöóxDÿþ=víÚ›£Tª^¾Lùê«Mf ×é¨üµ€À ´ …š¢ô¼Ì'L=ºôørzí<žT_ Ñëi>'IŠÔS|–JDB¡Ç1óY–Áp Ø==´A^+HØ04ö‡''·¥žÝÿòÖyJ­t«Û¼VÇÁ¹›&ãé‹k§øö2–ÅXšaÙòÎÈ䲨¨æ\ØÇ#B(òx<gggÇlÞ¼%4,Ôº`‘5¸KÉ3çW_ÍÍ ”‰žÄ/Ý ñ‹Y¥ÿ/ ˜´+ºöý½³Þg¼}Ò³‘ú[àÔŒ¿mXÐgLDD=î øª@½ž±(¦Ðô‹Pp×ëõ\ÚdwââQþºYœEÅ2<,OM¤\sèÞ)oïaF"…r|rKÃÌ»ÅÎÎÎÏßoÝÚ5ûöèûq—¿wÀ ´ºÄ”—Ë&2ð“èè1~þ~„-:¡¦ìB–`æ%« ML—ÈæíUù:åµc»/îþU¥n°ÍÉcåö£Žò ¾uu­û8»;EÞ!Ú§çÀQ8èïo‘2‹ª4g°ˆ ÷ŸmÑЬŠmfJ6ÃÃÀ€a gq!(U„ZGdæ£x‰9[ÖAßM˜0yÊŒ¶m{&$<Š‹û%55™ €aš¦âàÁÝ w322µZúüùcsæÌ,5lJN­Ò0|Eéišfº €¼øï5{âÆC¥ƒÆÞ›¡ô\ä;†±4­gh=†öö–½À #„¢‚Œ”G;WôýVZ§ahÍ UúZiïè&v¯ _—›‘zò/š¦ ;;.â¬_E`áÝR7,ŒÛbÑôÆ2©ÔÒuŽg¢6ŠÌC}<Ñðà?|.yÌöò‡ %ZM§²ùNÃdµ;ójw~tàãà gÓ“,2ªJÛÛE½øª@n7)ó—5ÐzÚôËg¸ *0ÚªÊnþU0¶¨tãçëkÙ÷ˆÆÔ$qû–¼G§œ]Y³ïs31ÿnÁ0ŒGAAuše6Å0lÙ’]zö~žþòÀŽzvíÐ,²©Ÿ¯/ÏF{˜áÜùó6”fþ%;céyiR{™‡Ô£ÃÇ®-»¨rUyOn_–ôÔnŸwÒ­vkïo¨g/_Ö®]ÛVÚ"a)þî?²t lìÎÛv®{N—þøÈÇ!CÏtÏ?ë&ß%¶ ø–mA>xЀõ¿mhÖì#™ÌQ °cš¢t,«ÇqÂÎN˜žþ2?_uþüñþý{wëÚÕ|ÉN£!fôz=Ë2 Ã0¤^ûüi¦G)uaõ4÷sÈãfJO ‹ †aYÃqgxBIfâuíoÓj´î!õ­'ó cYÀÌËR=½óâÆ¿zR‡ ìpŸ&)½JòVÆk0ónyüäÉã'Oʔ֧wŒ!ê­¿\rx«·ôÎa!ä«Â=#?õmÔ…ËR)hMŽj¾Ðã…Ò+YþB S_ˆa޳ cÝjÚÜ´Îx* âÐ6΢"', :÷aVýÏQûÎјBÁ»}Kpᬶc6ÇaÑw‹ƒƒC«–-{÷î[·Aã´çÏŽíÜxþÜWW7[ÅQ¡]»Ôu Mwó7ó’£Bêÿ±a{£ÎµÝÜÅ|‰X põ÷héžß8Ÿ8çÖ7àƒÔ‚Ûïßýä´3ýA˦M­S 9«ˆòS‘ïþÃJXIVž™;7q©ãôðÑÃG*µà°©¢¯\à±ØÏßO*µø »wïooïé3fH¥n¾¾Á‰T(1Œž¢ô¹¹¹Æ¿|™2}ÚÔ?îi‘XZ[§$y¸J¥¤(=EéI±›. =Kˆù”€fšeA¯§5&_©S«Döo¡$srU¿Èâ×r#s€ã<;±*ûEâ®5w±gmŒÇÇÕd¿Ð(x")!ã<>!’h³ž¨Ò^:Ùà '¦ï–Áƒ¦¤¦”)ÄÞÞÞê]Ôö’6ŸM˜Z<+Oë)¢<¹t°~͆”¦€WpGª·dç²1gU i4 EQQê‡Ôب2¬´ê‡üÑýôŸÖwé týpåþ£²nTsT„EeŒ™ß->5|€¤hni¤¯o>æÕ¯_¯â„›¾ä­[û?ðܺéÈnõU—öõZÕtqqó%„Ö®Q­0®ÌKMÎ?I'·ž<òäÊÓ¶ug6û´~h¹^^‰@ ÊÉ«ïkÓU€yŽë×Ûl²À°lá:p€sŸp›K²ÜÚl‹‹íüÜÝÜHÊÜ- |¾T*3už1Í›7Û»g÷æÍ›<”“£`h–¦JOI¥¢N;|ñÅKe~Ø:tÛö3”^Ÿœð„aX–ehšaÎgUøŸ-ta±ªN ¾›ÐßÒVüzöÖÌ‹;ÉEŽ´Ž‚ x<Œ ÔÊ\u^6Æãá„€goOˆ¤8Ë04Ii³ž¤>-ÈQùõŒ±´¹’u(ën …Þ^e¿Lú‰¿2Q ›²÷¼ó¶ž\“å%t}þà¡KW æþʤøª@KŸ(bbzíܹ«Äà<6ôT1, özýA`a^rézõ³“Ÿsëv­þä–‰1Ï3ó²rJÞ‰£Z`Î%× œWïÜùû—§õÿy4”Ôo]ëÀýSO±ô­gŽÞ<›å8¸i«ˆñ#\\\Ì|­» ƒ °˜77ª²ñê?Á«Ÿq_Ê4ð,0 ËÃp–ÛùhXLoMäŽãR©D*µl~ª~ýpãÓ[·nš_×ÎÎnøðáÇ·¨EtíÚ¸k×r½D¢LX–­Ù°Ñ¥è>ŽGvPõýE.R`–fX–ÁY¶ÐÚ¥š¤Y–f€eõj­*í¥ »  fXXD£ò´nþÝB„Ôª5}6¡Nûá»×ïS÷q]ÞV¨ ùùX"Ö«•—ͼt%® ,q… ibbz•˜®Õj‹XTå4°*ô“[åy¯èµËç òól¨Lå`Ý%7®[·qݺ,Ë^ºyëïåÇîäÝM8šØ©aãY£º¹¹VЃ°›­þ#ü»Ism¤•-¹uë¦Á®²È¢ª¦p¿Ü;••¼{G^B:AðY†}mN³†ÿÀ²8`@ÓB§ (¿½Ã"Y´æ¿D¬{Sd%#vö ¼~ÓcùI¹¾Ê¯³Øq¶^Úª@’’’bxÍ_9y»Ÿ\«ï–ã{¶T„>•€Õ—ŒaXóáÍ„ƒ…{s ˆJ›>}ºG“!³tZ=;–àâÜÔUügÒ4õ뇿UUà¸[•Æ{x·¼‡—Œ@¼Ã,8Rv|Ü×Yøâʆê´ú¯< ‹ªÒxîD¥ñÞ-ïá%#ï–í•Ç9®+HÄ;º[æóÞ-ïá%#ï<6~÷Áî„ù¼‡wË{xÉÄû€Á¨2cO€°t· Ìç=¼[ÞÃKF ÞEÞØSÁö¯ÊB x):ýwíÚõ·¢@ Du#Ðø¤¨QÕªUËJT@ ¢ºrbW†ñiQ£J­VW¢2@ ï(¦ @ ÂzªÌZû‡@ xEÛ yª@ l2ª@ l2ª@ l€Ñê?T…@ a>ol¨Ž’9¸U59…çȨB˜òT!ª82ªˆjHù‡¸Ï~U“ƒ@˜2ªUŽ"ßc˜£³OX0mÆÇ`¯5:†Ô3J í$d=Lâ)s*MÕ÷™@_·æüàÒõä¤ä ó+NÓ¹mó üíÏ_äYQ r™(¶Wdp€g@-W>Ÿ€Eký÷®ù*!lBùMN@U“ƒ@˜õF•Z­nذaiöï?`­V6`ëÖ­ë×ÿšžþB,_¹r¥ÌòC‡ì~þy]™%/]ºtëÖ­/¾øÂLM,X¸oßÞóçÏ›YQäùs tå×ÔP$[X8«Ð’Œ“¯ínw%)ÿ»žµfì"òo¢eÏ•A ¯û ÞÍàé£7ó_ØË<Í®Z8¾ çWÛywŠ-/ð®NÒ;F§¤ÜÛ—›®rò 7[%„ (¿g³‘‡É¶ró±>¦J(._¾Üp:eÊ”Zµj >œ;•H¤åU­¼xñbÖ¬YŸ~úI·n=„B¾m…_ºtiË–-æUK)òE–˜’›–ÉÃx8­×ã8ŽaÍÐN°À2 @8ˆ‰‘•Ï,Ü—”ð4Ç}V †aRf>Ôägˆå^æV|ã+þ»Uf"¨4äß{ÏŸ8°¡qãÆ_Œa~E„mÁqÛ1UM *„ÙXï©Âq<::Ú2sæL7WWãã)BŠ"ù|ÕZZJjj Ã0Ü+44Ìœ¬dÁÌ’,°æÊ4&LͧˆÓ>ÌÏ~˘:Œ^˲,ÆÒ$Fð0–&q‚ϰ Æ€`èê F‰~J+œe3úxpÇ‹/æ&/Ü{õæÓÖÍ{vhàé.—IìX–ÍÉSÇßMýóŸKY9…•_³³ó¸±½¢šã8\¾þdíŸÿ*”ã€cŽaþµ\ûuo\/Ø[*µË/ÐÞ¸›ºyçeÃÌ`fVþº'3RÒÂ#_/×Â0SÙT\,TJâÙŒ´ÞæîåW3¨•Er H•åmžþ„-cªX£ôE íÛ¿oÑ¢EË–.½Ÿ˜Ø»wÌäÉS )1qùòW¯]Õét¡aaãÇkÔ¨‘A‚éÜ"œ?~õêÕ÷îÝ57a|€¿?üøã¤={ö@LL 8pÒ¤IÅ«=rt媩©ÏkúÔ5jô+›Š€II+W­ºyófnn®L&kݦõ÷¾spt€yóæýùçŸîîî§N2Q¾°_nܸ1oîÜ„û‰.ÎN 2d—iºnjjÊ‚ ¯^½ªR©äryXݰ¸%qb±Øt_™¨U-(òM(Ë_lx™¸)'=Ï9Øé)IÙµZE‹‡Oî<¬Ýöúy²†uâ Ó$²¾öòa*ôKZñ”øëùàÚ–ü< ÿ–¡u< Tiiir7i‡6¡õê8Ÿ°ÅD`d2ýþÇOÏÂIöQuÜðq³vf÷†§ ƒ& jMÛ•Ç#h†ÉÊÌtqqiÔ¤~o§lzžIqÅ„ö5ƒ;ØK=Œ+"›ª’)·cÈ U4½D‹ŠK¯\‚1Tšƒ(C­'÷Ïf¤%»{ù•XÝBK±q º!V©ÌŸ7oÞ¤I“üüüHŽeÙÄÄûƆ„Î;×ÞÞ~ÇŽÆ Ûò×_a¡¡`:·.\øòË/[¶lµjÕjF³jÕÊØŸîܹËËËë» ßµlÙr„ïÖ®[àç/–HŠk{æì™qãÇuèØqêÔi999qKã4MhH(W2ýÅ‹:AuzÅô’IdÏž?ûõ×_¿5róŸ›૯¾bhzÏÞ½{vïœÇcYÖDy®+Ôjõ÷ßO1â‹Úµjÿïÿ[¸p!Ø ÁƒL·ß|3’ÇãÍ™3ÇÅÅ%;;ûâÅ‹$IÚÛÛ›î«ÒjY7Ö•O‘çK>,_JkÕ: 訬笰¥~©žÌð]0F«Ë|Á¸{³š|ÖA@àhÒ§2;}G}Ÿìùs§Àw&lØð—^§qìåø'›þXáÔv‚'äÛ‰'}?ö믿öòtpy™”é|ƒYõ,5õã^}tšüŸþ¥iddHp@˜—âNšÀØUÅãáßÿÇ#ÒÓÓ»téü,õYX½ˆC÷J$âA×wT$u76y4ýWùTh,T³‡O¼trC‰…K“ãÒ^ÙU¾!­'œá,*.½49„ù¼2ª,ÚRÝ<(Šœ6uZDÃפK–,quu]ÿÛz@‘‘‘ýûõûyÝÚ+V–™[„•+WÖ®]{ÅŠå<êׯױcÇß~ûmÊ”)ŽŽ..®àæêæåí]¢nkV¯ ^¼h÷™©U«VLL „æ¶jÕªU«Â瘈†!!!=zôx”X§Ž\.K%8ŽK6QžKÔh4ãÇoßþ#®@vNÖºŸ×}òé'|>ßD]­VûðáÃ9sæ|ðÁ\víÚ•Ù“&jUŠU<¥Ì|pë>Áçß?ýœfh{i~^vCZâÌäž»sò?ÇðìKº|ªa7œŒªÊÂ+àõïPíº]Ýj6áŽï%¥×«×xÀÀX7Ww”Zóµkz]¾“àân0™,˜Ÿ‘UÞg׉䦑‘Ù4üìÚc€u1Hôótv”Žã+V¬˽0œaDE5Oû껀F±FJ[UèN¨l*4ª4á%¦›ãÖ^ÙUœEÅ¥˜Ð0Å›[ªóФ›+¤dOû:;;»ðᆊ¢þûïÊ!ƒù|¾!±MÛ¶[þú‹eYÓ¹Eš!IòÎ;#FŒ ‚ËuuumÒ´é•+W¸ÓÂÿÆú¡ÓéîÞ½;räHCÉ   Úµj¦/)ŠZ¿~ý±cÇÒÒÒH²p~áñãdžžz3b¬Œò|>¿M›Ö†*íÛGïÝ»ïÁÃ!Á!&ê …BÿÕkÖ¨Tê&rŸpÓ}UZ­jDÑ-€È\öív7£Fk6óQÂ%…_'ìIN|–CÓá]È·j™Ì+ƒÉ¤ yì+‰7—hv;†ÁÔ±]š„×.^ÞNd§UgaØëŠÏŸ?¯Qç¡Ø!_]˜âìä¬ÌN6–ì /œ¶vwwwww7Êú\ÃðB-Þ°©ÐPÙØÂSUªœÒ„—RØT•€ºmá•EÅ›Ö0AE¾¦†}cú â1¤( Š¢þøcÆ ›Œä04M³,k:·H; …‚aGG'ã,g'Çû ¯ŒªWZ–¤§R©dÆÙÙÙ8×ÅÕÍ`Ξ=ûÈ‘##GŽ o Û+•ÊjuZƒ­oške–K$<ÏPÅÁÁòò,Ëš®»fÍš5kV¯]»vnnŽ‹‹KìÀØaC‡•ÙW%ÖªF¦U‘çK>³4­ºu´à¹@óð ™ TpÁÍãTü}–rÖ$ž(¸–Lz„«³nŠ<;ðp¬ü˳0º£p¬°Û}Ý8‹*>>~ØÐ!Ïž=ëØ±ãŸ›·–£imUy{{+p9Žc®N.%;'[O©í5¥²ÐàÚ¹óŸ/_­ì3†¡u|}1Ãlà8AXDùWpCV¢œÒ„—\¸t9tûgQqþª:õJõå£[a)£Ê²ù¿2Õ‹»s$ A±b?îõqqi¦s‹¤H¥RÇsr²³²srär¹‘ÝÃÙT%è)‘HpÏÏÏ7ÎÍÏÏ·‰¸” 2dÀ€\Öƒ€5˜Œìæ£9åUùù$Iòù…›;äæf€\&eYÖt]OOÏY³f@rròî]»—-]æêâÖ¡C´é¾*±V·n]‹wEÕ¤ˆýG¸^§}©ðÇìµi‰ÎzÒ…¥^¤Ý ÕëEû(-±&…KøJ^^®¿¿Ëí¹ð¶4¯ I=wtþf.×íR‰ˆK¼}ûV^Û¨ýøÏGöy]§0Щp€&þ0)î×ã2Gyÿž…S‡gþý—/9³àÑӌ쥳“¬k×nÝc.gQ¾ —‰Z5 pw•ý¶õ<`& À°ŠD,v”óåR‘²@[]€x“ ©²ÐSUªœÄ[ÿã,ª úÀ«y@îØüF#Þ03*ïÝ qãÆÿ]ùoô˜Ñ\ ”ù¹Å ׫[÷øñãÜ dfe]ùïÊÇ=‹%" ÃBC/]¾4hÐ .%3+ëÑ£‡înnÀ0Œ^¯K^¯•;qâD‘Ö)½ÞpZfy ôúÓ§O·oßž;=zô˜\.÷4§.‡ŸŸßø ã·mßöðáÃnݺšÙWƵL«j5ª0 ì%>-$ÏoÒµ£ôÏ’“®ºùƸ0î'œûõtTߺ¯fzy>g_²<EÒTž¼dYðáŸ>üóÏ ÿ×ë“’3ò 4R‰hÀ€ØÚ~!žÞµ}¼^oÝY$xÜÛÛ{ë/?Nïܹ½k×®ZaÝŒ[avͦ3“Fv …¿ý¼(OQ€ã„L*€Sg®>¹{Ø·ng7é/ Œ#«`ÞüÜA¯¡Ë\XW(ý: 7N_»çîåÒàÃâ…+:¦ŠSÆÐ:÷ŸSÏ„>„ùØpúïI±âsd0aÂøÁƒ‡|6ü³¾}ûº¹¹å+ îܹÍ2ìè1£ËÌ-ÂW_óõ×_5ú“Oúi5ÚuëÖ ‚ÁC¿r™òTÀ—_}9rä¨?7mê׿_NNî”)Sø|>§1†a‘‘‘;ÿÙÙ®m;//¯ãÇïøûo0ŠÐòó÷×jµÛ·o òùuê˜.Ï+‰–Æ-U(µjÕ:uêô¾›0G`¢nZZÚ´iÓ¢£ÛûúúéõúãÇOètºÈÈH–eMô•‰ZÖuåSä{Œà¤"=ïê9uäÅ?dS5TfžòZ û —Ìŵº{ù*û‹LZ®ÔC…£˜ªÊ"#S¹ê·Ã=£ëzרã8\:8³~»ñÓ—ìù´kP½zuë…ž>sqíêsq‹çÖáâœ^ а¡C¾ñõG}@ıcG'ý0‰/rvõ©÷f¸9\¿“úåøe]Ûú7kÞÜÅÅ…¢¨¤¤ÿ]¾´uÛV¦ÀtìÔÇ6ê0 ÇÑ›ã+‹>z%6DæÁÝËïô¡_‹—w÷ò³HW%4âµýd86!0ÛUE&ÅŠÍ‘@``-[þZ·ní‹”J…L& û¤?®˜éÜ"4k¹jõêŸ×­;vŸÏoÔ¨Ñü… ===ßT/ݨjÑ¢å‚ù Ö¬]³lùrwwÁƒ…3gΜ9sfLLŒ€ÏˆX¼xÉ¡C ZmÛ´‰‰‰Y½zµB¡puu=vì˜éòÀ‚½H4oþ¼ $&&999Ž?n@ll™mI¥Rww·7¥§§ã8°pÁÂfÍ"Y–5ÑW&jY7Ö•O‘§C™mW³³v¥GS u ¸ó\ ½^ÇB$¿i£×2X;ÆR´Ž/ i"=H¡‡ËJãÔ唿¶ÿ“št†Tçq7†aŸ)~˜·+ùÖ°‚¼t‚'pòjÙñËÄ«; F@K Ãâ~9ñÅçŸg>»k·\™2ke΋û,Ë8y× ïJ|ã.†a9jÉš­ñ?ÎX¡ÈzL‘* #ììeÎ5k´À0,3» Ë§óþ;¶¸D%1´ °²(1†©nÃê–ú>³¢z˜ŠÉ±Hˆi9%jhZa>Øô×Wõz IDATéÓ=š y”©ÑP üØYVZÑÍ›·Å߸9öÛo E%*‰¨®äååíÚ½»wL¯ƒÅÆ(’ëéU£HJTTTÓöcž¥¿ñÝçr—:¾¾dY; ²`’?öVdÙ@ua Ÿ<ŽjÚ <.üw÷¶oU“Ã+rž«ÒN\¸€Þ+úž’žö¬´¬¹‡” âþ®v/®l@^qD•£ÈÃa e–ú¦Yv’ Yë@ * [ìSUå æcãÕˆòƒ\îDu¤BWÿ½E9„ù £ Qå@_dDu¤üûT• õvå 惌*D•£ü_…¢’©ðï…kíZ6¶®ú©sWëTA9„EUðê?ÂJM…@TG‚N»juÝ*+0AE¾¦°ÈåŽ@TS‚뾓r3AF¢ÊŒ*@TG¬1ª233ÕjuÙåï=öööVÔB/1E Duä•QevPUȈÍ[¶”]€†æ¾xQÕ\´`b îtÉ’ëÇO¤TŒ^aŠöÕ?¾p÷ÉS.ü¾óìÛÕQEyÓv²ØSÕµ[÷®ÝºÛLÄ{À½»·­¨ÅYT·nYS@ l‚Á®B ÌÁÆ;ªØ¿™\›ÀYTöw»výZ…6Ô¨a#–e®ÇÇWh+ˆ*çFECoï@_}>ü³k×®›s #"5jøõ7§ŽŸHAFÂ"llTYmQ‘$y7áÃ0¡Á!"‘ȶZ!ª/×®_ûvÌ˜Š ]gYvÙòåP¡­ ª hèÍçÝè«ÿý÷z|ü”É“Ë,9köìJÐñNòÖéØŽ}'–ü¶vüg_•ß®2‹«KPPP9…œ={æ·õëß4ªŠ¦TfÍšíííÍ „‚·«ŒuTÎh£÷4ôæSÝûЦ鷭â]ÆÆ;ª›ï©RiT©9Ï3³^®Üõó'íú|Ú½ÃμUußÞ_^=ÓqÇ€ò’ˇtú´Mûú嬼SOy{Uêì æn DÌÞ°,ýe†i!§NêÜ©c o/?ßÚ>ý411±H«W¯vˆnïíåÕ ¼þÚ5k éqK–Ôªéc8½w÷nì€þ~¾5¼½:wîtéÒEc!÷îÞtØgcÇ;°ÿœÙ³üýüºuÝù;u,((‰D|ðÁô3j×ö-m\ª,,šþCT$hèͧº÷•±ÍäççµjÕJNN~{!Þ)lSeEÜâñËÇ%›=dº]-róËå/µ™Åè(JKQÅä‘™ó§òÿþ¿îÙÞñyËž;2ÞÜ€€€›6q&W£Fš4n´rÅŠ…‹qÔjõ´éÓ»ví M##33_ÆÅ-ùløp>Ÿo,gÆôéîîîÿìÜ)  U«VÑí?Š[²xã¦?`ú´©nnî{÷í·³³€fÍšsµ¥2Žã>5kDO’Ô-^¼¤id¤™-šÎ-ŽJ¥š;o~ëÖ­ ]»v.œß»o/gTÉd²¯¿þ¦id¤X,¾uëæÊ+:uìxúß3îîîŒV ’ªµk×þòË/ñ¥,½:tˆ@`÷óÏëÊÙÜ¥K—nݺõÅ_RŒÛ-ž‹¨8*yè-eÁ‚…ûöí=þ|%·["Uʨ²bD FU`àë×úùù=xPÍVð ª&åþ¦$LWùý¿ ã?kÔœ¤I©T“š|]A¾®@©+Pèò Huž6O«Õ†ûÝð¶3'ŠKÐétñññݺw78±<<Ÿß¡CÃi·îÝsssïßO0–C’ä¹sçzôèÁY0€aX‡/]¾ $©»xñb¯˜^œEe"‘¨IÓ¦f·h*·DÄbq«V­ §Á!!éééÜqDDÄŒ™3»téÒ¶mÛÑ£Çlÿ{Gvvö/?ÿlõµ¼'8;;Tø;ê/]ºô믿–Önñ\D%P9Cx‹p|ÆG`` šþC”ÛxªæÎžeœòã« A.nÝø?—î(w\qp¥ˆoÿÅ'±N˜ÇgÞ?Mèi†fh–aI†\š¸ÄSì3õ—EÙ/í%²â* †a\\\]]\nߺe8•ÊdÆN)ggÈÍÍ+"‡¢¨U«V­Y³ÖèŠ ?W …’¦i7·rùu$‰ñÎ.eµh*·Dìííåóx}Km”µJ,°Eä·[<QqTòÐ[ŽÍnŠ"ùür-®Z÷¤å#Â}—Þ½{·´,¢<ØÆ¨úáÇŸŠ¤pœeüŸcÙ§q¹y¹ÿœ4.`ík`›OnW¨ò1–°#ìvì'ņŸ«Ê›óõ®N.Å•Ëå8Žgee'ffe9::Nó•J’$‚Âoìì,ptt0®"“É‚1â˱±%µ"ãñx/ËŠ—·Ó-šÎ-?z=]÷î«d£jݺu¿þúëµk…;¹=rt媩©ÏkúÔ5jô«¯ñÂÂI‰‰Ë—¯¸zíªN§ ?n\£F¸¬E íÛ¿oÙ²e‹-JH¸/—Ëbc~ñÅç0oÞ¼?ÿü¸…™îîî§N2´[þ™F­Y´h¡P(9j”¡€½½ýŒ3òyÜê¿;þž5kv‘(u˜9kV—Î{öì1tèPOOO…Byýú5–a'O™ÓgÌìÚ¥s÷n]¿úúkww'ß¹{gÞ¼ù¬Ñhþøã÷ "„AhXXñ”57Ý¢éÜS§N}Ò¿ßê5kbbz—ÙÃôôô ong'º}ûÖ†?þðöö1bDÙcƒxÅšÕk‚ƒƒ/ZÄyøjÕª¯,ó%K–¸ºº®ÿm=猌ì߯ßÏëÖ®X±’+@Q䬙3ë@¿~ývíÜyôèÑèèh¹\.–Jp÷ò.a3¶sûöé»þ·õßÿ½X,€ï‹Å»t©Øë1=ôPúàš#\­VOž<¹I“&P?<¼eËÇŸ8tð77§V©§N›–žžîééÉ•×h4ãÇoßþ#ˆh‘“µîçuŸ|ú ŸÏ7ç&œ6uZDÃÛõ hµÚ‡Ι3çƒ>àRÚµkgÈ¥(ê§Ÿ~ €.]ºÞ¹{×LmMç–9"怌*D…R^£ªaDIJeKKL/³®Xh¯ÔC°Ì«ç –epÀÔ:-WªEm۶ݶýïE  <˜ÏçGEµøuýo†ý@,ÿüË/?Núáλ..Î3fÎ4ÞOÁ0 V÷ø‰“ .øéÇŸòòr#""†ÎåÖ¯_ÿÐáÃóçÍÿnµZíããÓ¿ÿ'\V§N <Þ¼ÜÜ\[·ïO)QsÓ-šÎe†¦i3 ÙæQÍwíܵsç?:ÎÝݽoß~ø‹-«^T²§ÊϤÓéîÞ½;räHCnPPPíZµX–e)Šúï¿+C† æóù†ºmÚ¶Ýò×_…§,ȤÒÀ:u ¹^5jdddr‹\ÚqTÅrczǬ]·vÿýýúö£(j÷ž=]»v³³³CžªòcéÐsEM.[tpáõD ööö7æN¥R‰‹‹KóæÍx¼ÂÉ×Ï222<<<¸ò|>¿M›ÖíÛGïÝ»ïÁÃþeÞ„vvvá ÂmuŸpr„B¡¿¿ÿê5kT*u“Æ_ǰÀç F44´ÕßÞ#šÑ°rIoªi×®ñã‘1Ün¤pôØñâ¹jµÚø58¿þº¾´VêÖ­·yË–âéA,‰‹[g"Å FL·h"÷ƒ?ÌÌÊ6!߸â¨Q£G]Z+Õˆ·5ý§T*†qvv6VÀÅÕûuäVüñdž 6 ahš6Ìòðãº8†Ñ´Þ(ýÍp7gˆŠä:99}øá‡oßÑ·OßcÇåææôíÓYT6ÁÒ¡‡²÷M±¯„êŠD"ã\‚ Dv¯S0 ZÿúVK$.YT ¾¨×¿¬ÜË¡LyªØ¢Þ·oßaÆݼukÇß;ÂÃÃQe,z®(”>¸</ò¥Jê´¬TRZÝWU‹ªa(¯ÊÏ'IÒ½›› r™ÔŠ›°œDyzzΚ5’““wïÚ½lé2W·nݺ ùù:ΰHèåË—æh[fn#R #"J|<.­p|¼Â̈÷™W´Â›ÐûTSæÛiŒéÞ±»O7Ú¥®}ØÐ&CDv">Ÿ?¸Ù€—ún|—múµµ}Hõÿ]Žàäì>ŸøB§#MÌü¾² ^©ñÊ–2”·‰æÍŸ·`Á‚ÄÄ$''ÇqãÇ ˆµò&´E_I¥Rww·7¥§§ã8°pÁÂfÍ"Y–å´?o¾ÚšÎ-cDÌÜU>OŸ>µ²wï=ØôéÓ=š IÊPk(¦t•—Vtóæ-ÁÁAݺ÷¬Dõï÷îÞ>xðPlì€"éž^5Ф`X»ñãÆŽß:vÚsëÖí3¼Æ~û­BQQÁ yyy»vï€ mņìÛ¿ÚÔ©»vïö­]ûmëR½©vCÿ±¨¯âââÞ/9YqäpO˜<åÂ￯eÙS¯&¢*’žö¬´¬Y`/ ÝD/®lx›1U6¬‹@¼ó<~üøÌÙ3k׬iÓº ²¨ë`Íàm눨Ƽ͘*ÖE¼ÛTvLU•dþüùׯ_¯W¯þ“~¨âªV;PšOÙ}eëõ†¶¥Ê*†x7¨B1Uf×ý×BÁEcáÕdTÀÚµ¯ß±]ÅU­v þ4Ÿ2ûêÛ±ß~;ö[Ô¥ˆ÷“jé©j·ä¶áøÔøz¦O­Ö§ByñâÅòåËnÄÇß¾}[§Ó]¿ïS³fÙÕÞWQ…¨PÐЛOuï«ê®?¢ŠS]cªN¯gl0™>­‚<}òdÏîÝŽŽNÜË¿Q  ˜*D…R-=U• Iê¡9‰æÓ¤iÓ„û‰ðóºuçÎ+—~ïÈS…¨PÐЛOuï«ê®?¢ŠS]=Uí–Ü6žæ3}Zœ„{÷† T7,ÔÓÃ=$8hÔ¨‘9Ù¯_¨7mêÔÐà3gÎtˆn_ÃÛkê”)¥%šsøðaWç›7o·Óëã>üpÜÆ=ÿncÎÃ¥uTN+ˆ*zóy7úª2¾ªï=ÕÒSUdjÏôi‰¤¥¥…†… ˆ•ËäOSž._¶là 2P(”“úqÎÜyuêÔÑiµ¥%šííí½qƸ¥K¹ê?>{öl\ÜR3/Á‘™™©V«+H¸á½ÚÚ ¢ ‚†Þ|Þ¥¾BÖ¢B©Ž«ÿl¨þáG}øÑGÜqÓÈÈzõê·jÙ"áÞ½ÐP.‘$u‹/ii\«x¢ 9A 4xÅŠå3gÍ’H$°iãF‰DÒ+&ÆœkDp4jØÈüw ZÝË2Ý ¢ Ò0"ÐЛÃ;ÓWȨBT(ÕÒS¯¬%ƒýdú´8$I®X¾|ß¾½©©©$Iq‰>4U"‘¨IÓ¦EjO4-gà A‹/úçŸC† %IrëÖ­}úô5<ó!ÌeËÞþ¸üMtíÖý‹/¿I{žêåíƒþ¿?ÿ·oÝܪu›*ÍY¥8{æßêÞW3gÎxÛ* Þ}ªœ§ª´ÿ¶ÕsâÄï÷ìÞ=iÒ›4‘H$м¼Î;iuZC‰D‚aX‘ZÅMËquuí񵑮 ‡ zàÀþìì¬!C†ØöBÞy®ÇÇ;fLñ±°,Ë.[¾œ³¨ý¯þŸ=cé¦wˆjòT!*”*ç©*íŠx¡LŸçŸ;¾9ò‹#¸Ó„{÷¬Ó¹L9C‡íѽûµk×6nØØ¤Iƒ' a>†™óº.ëHM}ý+‹x¯àýÂ…óƒ‡ µ¢Tüê?/oC[«W¯jÑ@¥R¹º8—öתe xµ ´4ã–,©UÓ§´\[U±9gÏž]¾|™qŠ™ZUœòYYY_õU`€­š>}ûöIJJ2dqƒeØ-im!Š`Q_U©Ž=}úôéÓ§M#ÖQåtèéZæ`<ç[Ñm½{XÔWUªcÍÿZ@ ¬ ºzªÊ¨^9$%%=yòxþ¼¹:t |ÛêTc*ÓSÕ¡CÇ€€3+ªÕêÏ?>tØgcÇ;°ÿœÙ³üýüŠ;´ÆÆòxü•«V»¹¹effþûïi’$MK6¿ŠB¡˜9sæÚµkØ?zÔ( þüê+°|“[©LÆÐô¶mÛΜ9 ¯è÷ÃÍ›7»uí²hÑbw÷äGÉ÷JXäq÷îÎ:Õ¯¾jõ±X¼iÓÆ˜^½>©)) FŒ=fÊÔ©eô/Àýû÷ƒƒß\ ùßÉ“:N(Z4XE(î©2Ý–u­¼«XÔWU­cÏž= ­Zµ2qŒ@XGµŒ©*ÿŽê•ä&^¼x±Q£Æ -zÛº¼§¼|ùò—_~¹sçö½{ $Iž;wí~Û*¼#Xí©JMI9tèP½zõ#"\¾ü”âÛ/Se&b±Øø¹68$$==½H‘H´pÑ•ªETTpHH™ÛnYTE 7íÚ¶[·víýû õêÕ·n“ÛÒ IÝÅ‹GÃYT¥#Ï;÷Í7ßœ†uèÐñ×õ¿r§>>>/2^šÙhÅö©z?aY¶E‹yyyÜiiÇ„u¼§1Uˆê…Õ!Sá œ;wníÚ5íÚ}Řv IDATP¢N¾Õ1UöööÆæÇ£õúâŶnÛÞ"*jñ¢E­[·ªºbÅò2ÍDó«H¤ã÷sË 77&Nü~õêU±±wîÚ}êôé={ö@™›Ü–†B¡¤iÚÍ­ ׂB¡ (jÕªUÞ^^†¿%Kçæä˜Ù1ò"žE^†ar¹Ü iÆ©ª¸¶Þ=,ê+Ô±ˆ÷ƒ§Š°AØJ¥ÅT!æ`lm˜ÀjO•™øøø¬\µ’’’¶lÞ}ú„…‡+++—–”Hk ‹Åºqã:·™œ|MGGÇÎŽ Y‘[%E%&“)ô’¢¢ÒСC“’’¨Tª( JJnnn99Ù6?ÓâꂌïUYY™›û€Ó¬¬¬ÌÎÊòòö’@‚žªö[«ëѪ{Õ¡n,ú#§Šû@ðµ\ƒt :eEuHw£íFǪjΨü~•"åååÓ¦N9}úÔƒ233ׯ[G£Ñ<‡‹J lÕUUÕ;wž9s:7÷ÁÖ­[RSRÖ®]§  À-Nûþý{&“yíZ’8Enmíì¨Tê©S' Ћи®††…Wûæ3iâµkI<8wöì–-›…„GD¼}ûvÊ”ÉIIWssܼysçÎ;""¸ïÎÈÐ`çÎ-*ð>зïâß¿pþ|jJʯ¿ÎÖÐÐ\¾|…8sE#hI·ßZ]Ñ÷êÞ½{F†IIWÅ,{†Z÷#ÝÜkD2:åî?HwC ¿ÑÿŒ*Á‹íê©ÒÒÒ2119˜PQQÅb Âñã'FŒ!­)jjjGÛºesaa‘žžnXx8§ž¨È­———¿@äîÝD"ÑÈÈèÅËBÞ«7oFîŽÜ°~=…B177Ÿ=ûWA!ýúõ¿}çnTÔžm[·Õ×µµuœœœ.\øý2вÙl„-ÖyŽ IIׂƒ‚‚ƒƒ Æ!CŽ=&•-c‚»ÿÚo­®‡è{…"›ÍæÙÙ¡n,tDAÚLhh¨‘ë¼×Ud*OÔnnèÙ³çììl'ùLinkN‹Å*,*{ó¶bêd¼@}ŽLffæ…óç>Ì«ªªÒÑÑ5zô¶mAFFFòÖKj½¼q#ÃÏo_¿±‰_3rÝÚ5ëÖ9Æ{¥¼xñ2,ÌdÍêÕd2¹:œýóÏ=QQݺeòsI…ššškÉɇï¤g*o ¹rå²`YZˆ8dgezxvšÝÄò¥ Ü«ðð°à  šššG úº±±ñì¹s–ïß“nÝœ Î=yòŠÞ“ç€È/•Í]Џ^PUÄÙ¨~ý÷ôð*Tu¹xªPmll¼sïí×j5Ssû»÷^r!u b¢£_½z5w®츹þþéii&xÃó§x‘Bø¯cäTA:íó…tp&L˜ÐâkD,~6Ÿ:_ª+é™JŠX( Xl0ÆÃáõ›ª/ÕÊv6æúºÊ4;»RçÕëÏ;©ýÅlîL4iÃ[Àо¿ýüùóÒRSÿ7‡ßµÓmi»ÇþGN•pQí½ûÒuªº'(Š4¨ººšÓlî5"òÜýw==-<<ŒûxZPÀy!:cýb©ÅlÑxÐ=×Çä~§.ý‹¡®ÆFªÀô±Òyüœùõ[ UqSSR† bjbâáîvãÆ ßiSÿ÷ë÷ìÁ3Ñ8ý÷îÝóöofjbiÑ{Îÿþ÷êÕ+®´¥K–Œõ ¯üÉ>>þsýx6w@_IèÁC†kHB$EÑ;·oß¹}ûÕëW€œ9wnß~öóînЙ=UB¤„ˆôTuOÄukC !OOÕÓ‚‚  `Áþ;"DÑÖÀYôTj¤ õŸÉFºôa£<{h+ †FC,6Šbñt66ïiõ0g&ÀÐ@OPÈíÛ/Z´pÆŒû¢£ëꈻwí¬¯¯wppäà; pÿþý_gÏ5jôŸgÏQ(”=‘»'x{Ý»Ÿin.ÖŸf´ñ‘—› àÖ¼†€6xªX,Öºõë¹Í;w<<<âââx‡AOU7zªº-ÿþû/ÀÕÕUÄkD2ä¼ûO‚ä'A åMÆ(} §¡’"A MÎ%¾ªb++ªd¿kÊyM«üÜxa£jßÞ½... qšaèÁ¼ÏD‹Ü½ËÚÚúLb"' ÞÅÅÅu KÜbžë'â€6ÞaD"14t»½½Ã¸qãZsWº8U8®@À/%(ZTÝÁÝîú£UUÕ÷2¶Í½†@$@Î9UUœØŒÂí¤· M‚°Y‹˜ äÝgj¯n¢_AÆ":yEµøz† öìÙ³mAAÜ+++[[[Þ1|g¢Ñéô‚‚‚µëÖq÷¹¹»sËÙµˆˆÚx›@¦P’®%ãp81%wÚÛ!=UÝè©êž (êââÂ9xÐÜkD2äï©Úµ#‚·gë€ Ç>ã}æô³Ø(àï=,5,L”ØJ8"­kÄD³§`á^~D¨Môú2Rù³ò¿ë .ÚÐЀ ˆn]ÞÎ?7ùÎD#‘H‚èéý$M_Oïå‹b¾S´q`0èþþs _&§¤ZXXˆ)" EÕ ž*"uäï©Ú¼u_çGï36jëÙÔF†·»~‘‰ªáªè˜ª&ämø\ÅþVE©©h¨/'ö,c-Œ«  ©©‰Åbkëjy;‰Ä:55µæôÔÒÒÂb±55?m ©®©ÑÑÑá¼Æãq|Þ:€&·)â€6ƒÁ˜?oþãG®\MrppŸž*ˆÔžªî ÌC‡´+r>û†è)l6ú¼”<„ ÑˆÁ”ÑðyÅÈ«èÇ äS9óCí] éKy™Œâ1 ·9Þº—º eeåÜÌÈàö¼{÷®´TÔ*%%%ggçô´46›Í驪ªz“ãææÎiW|þÌU¾¡¡¡øçÃk›; siÑÂYY™ç/\€9’B‘B™*‘Û| EÕ »ÿº'âÿY€@$@ÎgÿI`T5‘Ùe©®ý”+H€ÆÂ’)(Bg+0ƒ¦©†1ÐÁêi¡<»ŸÍÌÔ@¨õ6äçç¯X¾,//÷ÆóüõõõyÃs‚lÚ¼åÍ›7~sæÜ¹s;=-mÆt_%%¥œ«>>>ĺº¨¨=$éíÛ· .à«êÞÜm€õë×eddÌœ9«¶¦6=-ó(,|ÙŠûØÕio£ ~¿vC %Ý=F¤]‘gN•³“Óþýí1«ò£oUsM¼º ©€U ™Ì"“Yµæ¬ÆF hdªø°Ñßš¥¬,¼hç˜1c=¶gOdrrrïÞ½7oÞrøÈaMMM¡ƒ9Œ1ââ¥Ë{£öÌ PPP6ÌíØñÜz ööqñ QQ{ÄÆZXX¬[¿žN£óNq@[þ“'€ÄÄ3‰‰g¸ã,X¹'J„>)¿_»!0§ HyæTMœäÃ;X̹ߪi¶Ø^=°X @®–¢‚¡©u,• ( …‚¡Ññ÷Ÿ1܇XŠ3eêÔ)S§r^‰Ä+–Oš8‰ÓäT#œ2räÈ‘#G6'pÖ¬Y³fÍâ6§Móåàââò×ß·'f爻…°ÛÒÞ¿aNU7æTuO # Ò®È9§J‚¹l óãWúù;uþ]{îvÍ…»ÕïV_¸ûíâݯïV]þçëÕ{UWïW©`êmmL›B&“ƒƒƒnßþ;???5%eºï4UUÕ™<&¤ãP]]Mn7TUU•ŸË9@øÜÝž!Ý þƒ´+˜ÐÐP#×y¯¿’©LÞ¼§OŸ‰ušššnnnAÁ!|ÇÅH‘í!!W®\î¶Ç‰½¼q#ÃÏÿ(Cc3¾ f亵kÖ­sŒ÷JyñâezÚ„§ÂªwJg''--­v]È{÷ï•—·ì“f±XgÏ+(°|ÿžtëæ@PpîÉ“‡Pô^û« éˆ|©¬hîRDz=@Ugc¨úõßÓr®S%ÁÜÑ#µ<¨%”””Î_¸Ðv9bÒ\<ÒAðð.o r ;+~ôbÒîÕ½û÷ ÿAÚ9ש’Ö\HWåiAÁêU«x ±JE÷ÇÆÂxH7Uv¥óyª Ý #ÁqFb"N,@ qsªŽ0Òñ‘¸ •© ñÎNÈd²¾žnsw7Àö¾»ætމŽîÕ³u©ÐL‘:ÙÙÙ±±ûy{ÄÔª”ÿúõë–-›½Æ335Ñ×Ó-ÿô‰÷*çÃ’ÍZ^233—,^<ÀÑÁØÈ°/Á.0p…èSójjj–-]jcmÕ«§ùÌ™3^¿~-3Uù€‰êv¥‹xª — "]”••Ož<Åm®Y³ÚÒÒ20p%§©!²¤=}=¾SºÛcŠÔÉÎÎ:qüøªU«¹=òÕêã‡)ÉÉNNή®®999]f­ÎNLttCCÃܹþæ={¾{÷öÈáù¹¹™™YêêB±`2™Ó}}kjªÃÂÂÕÔÔbþˆ™ì3)+;G__ÈÙ¬íJYY™ŒW„t7ºHNÌÇêÚÈþ·#‡›äóß¿¨M›6ñö´ˆ¿€¿@«•`Ša0芊BŠåÊW+×AƒJJ_Ž>ÜÞ†Ž,×êìDÇÄðn—¶ïo?þ¼´ÔÔÿÍáßä HJJ***LNIuww¸ä:Ð%>.NÆÛwœœÎž;'þà‚R»êé’|7ªP€~/HÐ6:¸§êæÓÊ+¹XOÖ3 æ¿×T:Â`! T¶†2ÎdOW!•®²³³Ÿ>Íçý)/Ñõb¢£cc÷üÔrfOffæ…óç>Ì«ªªÒÑÑ5zô¶mAFFFbªÑ©éÈù’ââM›6=}𝥥µè÷Å«Wÿ‡ÁûÉ~øPœ››ÛÔÔ¤££ã8`Àñã'ÓK0…ó¯ë@\ÜŽˆoÞ¼Ñ××[¼xÉÒe˸ºíÙùäÉ“ÚÚZmmíÑcÆ„…†õÐÕå{øÈÑ;"ŠŠŠüüüpxüÑ#Gúzºccã/ ùþ‰íÙ™——רØhff>sæÌ 7 ޖ⢢]»våååÒétGÇ!C†Jp{E%]d¹Vg‡¯Íà!C_¾|:ø¯[79ÀÄÄÄÃÓ3##CÆF•±‰iÈ$Ÿ/•Æ&fâ<Ÿ8ù§,ÕƒtR8¶ׂê^žª?R>:™ZP™ ,ÀpnP0Ј2¶wOå_7n˜Ò+ìZ~3F|D6´ÊßÞÅ¥Q5nÜxñ+–Q(”E‹ÎÿmÁšµk¯§§ïÜaei)èКëç‡Ç+ÄÅ'TWWgfÞg0¢%‹?…D"…‡‡:tÈÚÚæúõô• †sReeeß~ýæøùiij}üô1vÿþ¹þsoÜÈà™Û´mëÎ]»ûôéC§Ñ4456ûâÅ‹YYÙžÿïÃóçÏ'Mœ@ öîÝghdøþÝûâ’b @QQ¡·——ƒƒc|ÂA55µÄÄ3¾Ó¦eܼåèè(ÿôÉÙÙiåÊUÁ!!-Üß–hÕ‡i?òrs„¾}…^---µ³û)û`Gøçî]:®¤$ü$±öÀÅÅE|‹*??_fŠAºÝ+§ê+‘v£ºƒÇ²Y,,‹Á`؇šEœ¶î WÓˆD¥½®¬£J¬O{Ð*;„CnnÞµk×òóŸTWWkiiyzz®Y³ÆÀ@ø1Û¬­­Åÿž&“É»vGzzzFŽ™›û 5-•Ϩ¢R©¥¥¥qqñ^^^œžñãNj۪) #22²_¿þ_ßéÏž=‹‰‰^°p¡‚‚¨ѣGÍ6hð`{{w·’âbî—ƒAß·/zÐàÁ\iššX,Ö¼gO¡k…n100LMKWVV4ç| 544¼š”ÄùÊôðð;ftLô¾3‰ƒÃá°8)¸…ZõaAÚ "‘ºÝÞÞaܸqÍ ¨ïooÏÛ£¥­¢h}}½¡¡¡Lt€üü|ñí*„ü`€@DÓ½þ÷ßÔ”555©(¦¦¦æááÁmÚ‚ŸˆŠŠŠ­­mÔÞ¨&2ÙmØ0;¡Å²[­š¢¨¨ÄkÜŒ1òð¡C¥¥%öö ã@llZZjyy9ƒÁä xûö-רRQQq$n5]ƒž——·rÕ*ŽEÕü0FNNÎòå˹N 3nÜøcÇqšæææ_«¾‰¹(¤ƒC£Ñæ)”¤kÉ8NÞêˆzª 2 {yª”Ô´t¾žþö*±îK½®9 }ùôº¶—Ç`ðõí‡Â·½GüÂþüžŠöPPªTל©ª%ÄîY¿~ƒ`|¤Å ™L^ºdÉÚµk­¬¬nÞ¼Ì Ðð":h‡h{Cb£*44Ô¢7·I°µ[µzõ­›·¦ùN“†^@UU•×ÜÁãñlKpØ…‹—¢öDîÛ»wKmÁâ%KWŠ6­ÄŸ¢®¡Î› ¤¥­ ë›6mLINÞ²eë@WWuuuR}½··Nûo®ººø…UI¤6›m`ЂkD"1™Ìøøøƒq;„Íf³Å\ÒY`0èþþs _&§¤ZXX47L[[‹Dúé7*©¾ƒÁÈøx(è©‚È€îå©RÀTAƒM£Ð©:³æ3ªdƤ|c}~(èa½ú+bhŠRQmEVÈ—ŽŽŽ`|¤Å …BÙ:qâD΀êêoÜ ¯ð‚&<´èo‡p°°èÍÛtvqT}«’±æææqñ €×¯_Ÿ;{6"<ÜÈÈxæÌ™R™Ò@"ñæ¦pÊéèh®^¹²|ÅŠß/æ\*)nÓ—„––&ÿÖÒÝÓÔÔÄáp‹/™ãç×–å ƒ1ÞüÇ]¹šäàà b¤]ÁÏ'x–”–ôêÕ[´ËSê@ODHy·K÷TáñxfCõ›¥M¤òÒû¯?Òzc}­ I}Â$Þ}ü©–ýµøá›§,/̨ ƒÁØ·w¯§‡»Eï^¦&&£~ùðöí[î^ëg’‘H,--á’““3yòd¾ ÉÃGø–ãúÛOœ<ÙÁýíÒBZ¥>ÿý÷_€MyUùëÓ§OXx¸²²riIIˣśÂb±nܸÎm&'_ÓÑѱ³# Âd2544¸—Ò¯§·¸œ’¢“ÉzIQQièСIIITª¨tC%%%77·œœl ›ŸiquHgÅb-Z¸ ++óü… ®®®¢ïUYY™›û€Ó¬¬¬ÌÎÊòòöj5"??_L‹ê‡§ i5]ÊSÕÜ3w°5õgN¯,ª2ó´B«ß•<$Yzéa>ÔÔhZ8ñæ%‰¢©iR…T³Å7WZ ²hhjò:¥tuõÀ 1ƒ&búÛ»R±~H$Ò¾}ûv„áÃ=eiN•——¯Zè3y²M‹•ž–F£Ñ<‡‹:›¶USTUUwîÜÙØØhccsýúõÔ””ˆˆœožÃ‡ÿ™˜èååmnnžžžvæôéµµµ³£R©§N0ÀIIQ±o¿~¼WCÃÂ'Nðö™4qé²e††FÊÊ ‹ wïŽä1ÁÛ{Ê”ÉóçÏ766&‘ž>ÍG4(8˜óî\º®\¹m[P‹ú (z==PXT¸s÷Žž®ž¡¡!or½´åZõë×eddøûÔÖÔ¦§}?ÐÂÂÒ¢{À½{÷~=+áàA_ßéßéÓ:¸ø÷ß·nݦªªóGŒ††æòå+d¬3ôTAd@—Ê©j‹²Ùä5}V¤¾ýÀ()L»¦ç·™¥(S—úêNSþ{†‘#¥æ¹Šñ8ñ=U-Y †¢¢"§Y[[~h¸ˆ4ßßÞÅh» D§ÓW­ZM¡R?ŽÅbeiTiii™˜˜LH¨¨¨Àb±áøñ#FŒÖ55µ£Gmݲ¹°°HOO7,<œ›®¿vÍOwEEÅAƒŸ:uzÂoÑÚzyyùûDîÞM$ŒŒ^¼,ä½êààqófäîÈ ë×S(ssóÙ³Ò¯_ÿÛwîFEíÙ¶u[}=Q[[ÇÉÉiáÂ…ß/£(›ÍFØbçÈf³ûm>·¹qÃÀ˜1cÏ_¸ ÎôV!˵:;ùOžÏ$&žáv.X° rOE6›Í=²SAA!)éZpPPppƒÁ2dÈÑ£Çd¹ïï»Î0§ ÒþtO•˜àpXöd…Q¥U¾Òe1ôPæ×Êâ¾,– }Wùª'«®Ð€¯'ZY™ ÍU䋈da2™ݺÅÝiŸš’Ê Ðü$öGÐ$(8/PðøÛ/]¾Ü¢¿ “ÉX¹jÕ«W%'Nœìi.ÉùtBk·†…‡óU/rt玈¢¢"???ΡÅ÷îÝÛµçÅ‹ŠŠŠC‡ Ù¾ÝÖÖ–#mé’%oÞ¼¾s÷®üÉ>>ZZš‰žå <·#bÇ›7oôõõ/^²tÙ2ÎÈNäTëªxx—· 9• ?z1é÷ zª 2€×¨’B~I«ÐÓPI‹ €…‹&ç_U±•U²ß5弦U~n¼°GˆQµoï^—„ƒ‡8M0tÈ`Þ }ß¾èAƒÿëŒÜ½ËÚÚúLb"¸¸¸¸t‰;p jï^qÔf0‘‘‘ýúõøúNöìYLLô‚… Z{º F‚'bR^^ÞN’!H‡zª 2@λÿaˆžÂF€‚Q¸ý’v1»éÔíú“Õü«îÌíÚÝ¿<)GP=ý œÍÀ<ï-îó† öìÙ3/oon••7ÇAEEÅuÐ n“N§LòñáXT###7w÷ÜÜb¾SEE¥!C†r›#GŒ$‰¥¥%bNïæÈr÷_BB¼³Ó2™¬¯§ÛÜÃÃÝ °=$¤/Á®9c¢£{õl]bŸS¤Nvvvlì~Þ1µj'å333—,^<ÀÑÁØÈ°/Á.0pÅׯ_¹W9–lÖ‚4Çüyúzº+–‹SSS³léRk«^=ÍgΜñúõk™©Çzª 2@þžª];"x{¶þr"‰¼Ïœ~üýo£‡¥†…‰[ G¤ u €Ø€höÔ,Ü˵‰^_F*V¾ñw}ÁEÑí¡ËÛÙã禺º:oȉD"!¢§÷“4}=½—/^ˆùNÕ5Ôys§´´µDb½˜Ó!2FYYùäÉSÜæš5«---Wrššš-JÐÓ×ã³ÔÛcŠÔÉÎÎ:qüøªU«¹=òÕ*&:º¡¡aî\óž=ß½{{äðáÜÜÜÌÌ,uuõN½V—áÖ­[|(óŸëgmeidh@°³={VSS“ ( ¦pBwîÜ1|¸©‰ÉG‡Còê6/À¿¿¾ÆF†;ÛÀÀuµµ|s³²²Æcfj¼mÛÖ?bb8QNûþ@ ®W\Tà?·µ±‘¡ëÀ{£¢„Þ–â¢"¿9s¬,-ÌLM¼½½>Ìÿ–òÂkåøòå‹dÒ:ÎZ]ƒ]»vš˜šÎñó=ì¯[79ÀÄÄÄÃÓ3ƒç/°l€Õ!2@þžªÖ.ÑDf—}¤úÒ®  K¦°:[ x„ 4M5Œ:Ë£ »ŸËÌÔ@¨õ6ÌùßÿV,_6Çϯ®Ž¹{—¾¾¾èÒ›6o™5s†ßœ9 . R¨{÷F)))­ ä\õññ‰Ý¿?*jÏҥ˪««·nÝÂ;⠪ªºsçÎÆÆF›ëׯ§¦¤DDìàd©£(z==PXT¸s÷Žž®ž¡¡!oš|7G–žªqãÆó}¹Š€B¡,Z´pþo Ö¬]{==}çŽ+KKA‡Ö\??<^!.>ÁÀÀ ºº:3ó>ƒ!$ÛO²)"6–VVVöí×oŽŸŸ–¦ÖÇOc÷ïŸë?÷Æ ž¹?írÕÐÔDØì‹/feepxþ¿ÏŸ?Ÿ4q@Ø»wŸ¡‘áûwï‹K„„HŠŠ ½½¼ãª©©%&žñ6-ãæ-GGG@ù§OÎÎN+W®  iáþ —› ôíËi¶êÃjãZ>ž={vúÔéÛ·o·¸9·´´ÔÎî§ìC‚៻wy÷2zª 2@ž9UÎNNû÷ÿ!´_ĬÊoŒþ½UÍ5ñêj€¦T1d2‹LfÕš³14* ‘q4ªâÃBFk–²²ðÿ±cÆŒ=zôØž=‘ÉÉɽ{÷Þ¼yËá#‡5E&ÊŒ1ââ¥Ë{£öÌ PPP6ÌíØñÜz ööqñ QQ{ÄÆZXX¬[¿žN£óNWSS;zôØÖ-› ‹ôôtÃÂùõØlöo¿ÍçŽÜ¸aGCNÑ,H[xúôéÁƒ ¥¥¯ˆD¢ªªª££Ã²eËEL±¶¶ÿ{šL&ïÚééé 9rdnîƒÔ´T>£ŠJ¥–––ÆÅÅ{yyqzÆ/Zl«¦ˆØX:jôèQ£Gs† <ØÞÞÁÃÝ­¤¸˜k(îrÕÐÔÄb±æ={ ]+t{ˆajZº²²2€wï/a¡¡†††W“’8_™cÇŒŽ‰Þw&ñOÀ`p8×j79‘H Ýnoï0nÜ8NO«>¬6®á…Íf¯]³æ·¿õíׯÅÁDb}{{Þ-mmEëëë ÛMG~`NDÈÓS5q’õ¾UÓl °½z`±€(],ECS&êX*P( C£ãï?c¸±!gÊÔ©S¦Nå¼&‰+V,Ÿ4q§.4Ø?räÈ‘#G6'pÖ¬Y³fÍâ6§Móåàââò×ß·'âñøêšZÁ~‰=UZZÚ ,ì¡Û£¶¦ö⥋óæ\¼x‘@ŽûAMMÍÃÃ۴#£E***¶¶¶Q{£šÈd·aÃì„Ù·jŠàÆÒÇ•––ØÛ;0Œ±±ii©ååå “3àíÛ·\£Šo—«h z^^ÞÊU«8UóÃ999Ë—/ç:!0̸qã?Æiš››­ú&æ¢\h4Ú¼€2…’t-‡Ãµvz‡]«“røð¡ššêM›6Ë[‘V=UÐÎþ6…ùñ+ýü:E „JaRi ™F¥2iTÊ 1Ø*’­isBÈdrdänOOÏ=t+ÊˈUUUÉcA:U'Nœ8q"·éåíõË/¿¤¦¦I+›MUU•×ÜÁãñlaçy_¸x)jOä¾½{·ÔÖ,^²$0p¥hÓJü)"6–nÚ´1%9yË–­]]ÕÕÕIõõÞÞ^4ú»7øv¹Š†Dj`³Ù-¸H$“ÉŒ?ø£AØÜ|D `0èþþs _&§¤ZXXH,§£­ÕI©ªªÚ¹{w$›Í&‘HœNƒI"‘ÔÔÔðQcmm-î0¤úz £¥%êÐ ©=UÐÎþ“ ëY8ìg€áÔ/EQ•ï{ìP€r*š¢(¬©±¨oM<ÿîí»+—¯‰ušššnnnGŽëÑ£G»j‘;:::8¾ýJ‰6‡¹¹y\|àõë×çΞ722ž9s¦T¦ˆØXzõÊ•å+Vü¾x1çRIq›¾$´´4ñxü·oU¢‡ijjâp¸Å‹—´˜¿,& cþ¼ù=ºr5ÉÁÁA*2;ÂZ—òòr*•ºzõªÕ«Wq;¯]Kºv-éҥ˿ŒÅ7ÞÎήàçÉJJKzõê-Úå)u § ":Ÿ§jôq£"PRR’eÆRsñDˆ˜´1QN§³ÙìÚÚšcÇO())úúúʾF‡>}ú„…‡Ÿ!á¼½§L™<þ|ccc©áéÓ|Aƒ‚ƒ9ïÎu Kàʕ۶µ¨Ïúõë222üýjkjÓÓ¾‘[XZôïo/z¢Èr­N†††››o‡340àvÞ»wï×Ù³ôõð>ýࡃ‹ÿ}ëÖmªªª1Ähhh._¾BÆjCODt>O¤ÒF(8(¸±©áÛ·ê‹/-Y²äð‘#[ž&%´´´LLL&$TTT`±Xpüø‰#FHkŠˆ¥qqñk׬ñôpWTT4xð©S§'Lð*„‹———¿@äîÝD"ÑÈÈèÅËBÞ«7oFîŽÜ°~=…B177Ÿ=ûWA!ýúõ¿}çnTÔžm[·Õ×µµuœœœr=(Êf³¶XAØü'O‰‰gÏp;,X¹Gx}¬¶ ˵º6(‚°Ùlnœ]AA!)éZpPPppƒÁ2dÈÑ£Çd¹ïôTAd&44ÔÈuÞ«¯MTâÓl^ÑÙ³çììl}&?!.=Íi’Oˆ턼օˆCQá´72üüøËR›˜ñõ`0#×­]³n3`¼WÊ‹/ÃÂLÖ¬^M&“¥¢ “Éšä3ÉÈÐèôéïGÐÔÔÔ\KN Ù.ù²g{HÈ•+—‹KD•®…4GvV¦‡§(—!„K׸W­òTíØQüþ=éÖÍ)€ àÜ“'¡è=y¿ˆ|øRYÑÜ¥ð´:€ª"®‘ú×OÿØ4„þx´ yY6ТêÚH«~:³¶²þü¹BŠÕ!H§VT‡´ ?›OR>¦&=MTÝÎöC^ëBdƒÄV‹Åâm’H¤/^ôêÕ UHwæTAd€”sª:¸§JÏZgÀ(K6»Ù¯R:a±Q ÑTÅg•Õ¿¯óË/ƒ™FF戩۾}cýüŒŒö ½ä¹y³»ºú®嘘hlÞìîêj2`€‘²2ÞÂ"öÃ!êAxY¹r•±±±ÁVEEåËç/×R®555þ¾øwyë%5àÆRDL`NDt/O•ÙÀÞZx}u\ œ®:NW§«ÿñ§«„êè«ê;š›ê)ëÍÒ‡àa%TÈ/¿XlÞì.UõÅÂÒRgÖ¬~µµÔ¼¼fƒ»]‰=Unîn¯^•ÆDÇŸ?ÎÎÖîôéÓ.Î.ÐSt7 § "º—§êSec ‡Áa6ƒÁb0A,‹€¢X5%\5Û@E’þð¹±½Õn¹¹å††û«W9²·œµ‘-[?³gÍš-P(ÚRH7zª 2@ÊFUßý×ÓHyÓ4a¢( `а0X<EXX,E€Á RÏŠû÷_µj0E·>n43‹±·7 9dˆ™¾¾j]5#ã͆ ·kj(¼‡ 1‹ïèhôíyÿþ‡11yB5tp0ܱãOÏ^JJ¸§O¿lÞ|';ûç‚t_SšA¤@ODt/O•‚’ªzý]RÅ?D²†™ «+ úÚ‚ú/ß>VØ;"µ_¨N¡VEÕCIu´ „ððL0ÀÁá€ÅBffš/^T8ñ´¾žfa¡³e‹{JÊlw÷“ÜYêêŠgÏNÛ¹3ëõëÚÉ“í¢£Ç¢(úÇù„;:>x°àéÓ/ÉMMŒßw¹sÇذùù‚Ö@ZôTAd@÷òTá°Å©°™t&Àd7Ô¢x]„Ô~Epš„Á$m]ƒŠªá±X!ÍÖÕQI$:‚ ¼Iâ7o¾½yó-çõƒå_ —ÙÛ¼|ùÓ©¦¦°qãík×J8 Õ‚‚<ãã3™?Õ?ŒŠóåKãØ±Òh,À?ÿ”=~¼((ÈsêÔKÞ—®ôTA 6=U åDõî©Âápl ©²¬‚F©©xùþó7&Ê¢’ÉÊŽ(›þñùëêF¤¾üUå[*ƒÁ ±©„£¨ˆ þòåÒ††-4ZP~þb€­­w“‰¤§¿â6¯^-îÑC¥>!#GZ\¹Ṟ¨( ÒÓ_»»÷W®‹Ä‰ê-"ïw@d¬S‘]ÊSÕÜ3w xU-k·ÚOõzýŒPÒ×òWd#-Ì·¦w j}ƺ²*?骪=ê‚ÛàLHðž5«pð?yyt•~SVþïÞ’H4^§Tu5УÇOGÒêè(+(`7l¶víPn'‡Áá¤løvF õ@ÚôTAd÷‹EÛ^O]Þžªæž¹à°AheOiµxF*fy `™ÑÊ Xï+KƒQñŒúî+KÇ‚ÞP¦¨ãŒþÊœ9ûö寯>â4íí øhi)+*â 6§©¯¯ ¨«£òŽ!‘èl6ºÿÃ'`ux‘20§ Òpl'®ýÔ½êTa±X6“QO1Æ(iÕVè׳,‹R[®Kd[À®ý¬ßˆéÍ àêªÅ`„Ut:KAË#£¨ˆkh s{|}ûòMQPÀúøØr›3gö««£~ãC£±îßÿ0r¤ÅÛ·u¥¥5¼qÞZ׆ÿ Hž*ˆ è^»ÿ°à”Tô ʵeˆ“:»¶ªò–Ñ0M¤²¢¼XÙhˆ:ýCµRÑ©$´9OUQQµªªÂÒ¥Ÿ<©¤ÓÙ/^Tݹó~Ñ"çÔÔÒê§Oï»x10žLfîÙ3ZGG™³ûÏÏÏaíÚ¿ø²ÔëÖý•“óÛ½{‡=ùü¹A[[yÐ S,³eË]óÝ\st4xyYWWS¾|i|ð ¼µ·«sQ]]M¡PZ'ªªªí$t( § "ºTNU‹Ã°8,“RG~SL¯ä÷•h ƒÕÐDy[V6±åí3f9•¦TŠÖ6©èÐ0ÍxñRSK͈ø¥G•ÊÊF3³˜ùóSŽôâÅRƒýàAùôé—sr~ãÒÔÄøßÿ’ðrt4ª®&¯_ÿ·`=ÀóçU®®ÇBCGìß?¾G•º:ê¿ÿ~Ž̹ŠÃa¯\™Á|ðàÀo&Né Àœ*ˆ è^žªòÛå·»ÝiÄ KK c³VMÁ`0Ÿ&-ÊË¡é t  § "º×î?HGàKe…˜n*.²Üý—ïì4€L&ëëé6÷ðpwl éK°kNç˜èè^=Í[õ6%˜"u²³³cc÷óöˆ©U;)ÿøÑ£™3¦÷ëK024°²´˜9cúãG¸W9–lÖ‚rçÎíI'öêiÞÓÜlÌèQ•ÒÑé¸RO_¯ú[õéÓ§¦Nr믿ìí:õZ]€3gN¯_·ÎËËkÇÎx¾°¨°¶Fxý&“9Ý×·¦¦:,,\MM-æ˜É>“²²sôõõe©0ôTAd@÷Ê©‚ÈÉÕe_P ‡ÃMòù/ÑjÓ¦FFF¼=-âïàïЪE%˜"E º¢¢’`¿|µš>}Æôéÿmz:mš£ƒýåË—ÛÃБåZòOŸ¶mݺlÙò°ðð'%%&§¤º»»\ rè'Î\)=UÐj£ E··4@>9¿òZÒ"ÅE/y›­JTçБ«t–oÚ´ééÓ|--­E¿/^½ú»'&::6vÿÇOå€ÊB‚ƒsss›ššttt 8~ü„ºº:Ÿ( ¦l ¹råò¸¸;Þ¼y£¯¯·xñ’¥Ë–quÛ³'òÉ“'µµµÚÚڣnj ë¡«Ë;÷ð‘£;wDùùùáðø£GŽôõtÆÆÆ/^òj(.*Ú³'2//¯±±ÑÌÌ|æÌ™6n¼-ÅEE»víÊËË¥Ó鎎!!!C† ÖZtuuñx…ö˱“×ZŽsçÏ¡(ºvÝ:‚ X‘§zýu릱±1Ç¢˜˜˜xxzfddÈØ¨‚ž*ˆ ²§êzzš\6RÉk]Hk‘ QÈÖ¨7n¼µµµ˜ƒ)Ê¢E çÿ¶`ÍÚµ×ÓÓw²´thÍõóÃãââ ª««33ï3 Ñ’ÅŸB"‘ÂÃÃ:dmmsýzúÊÀ@ ³déR@eeeß~ýæøùiij}üô1vÿþ¹þsoÜÈà™Û´mëÎ]»ûôéC§Ñ4456ûâÅ‹YYÙžÿïÃóçÏ'Mœ@ öîÝghdøþÝûâ!_b]˜ BèÛwÈ¡S¦NMºv­‡®nD{æâÈr­ÎK=žžÜžÃGH¤Š !…ß´µµH$o©¾ƒÁhiiµ·ž¼äçç‹iQýðTA ­¦ÛyªdŒÐUÍm³“è˜Þ‡}ûùó祥¦þoΉeÊ ÕÛHll¬¡±±¯¯ïîÝ»e¶(/æææqñ €×¯_Ÿ;{6"<ÜÈÈxæÌ™R™Ò@"ñæ¦|ýú ££ ¸zåÊò+~_¼˜s©¤¸Má --M<ÿí[•èaššš8nñâ%süüÚ²œPv„—-íTku:líìž‚"¬°3Síìì ~Ú]RZÒ«WoÑ.O©=UÐ=U#£_ò†ùD7)).žàß¿__c#C‚m`àŠºÚZîUNAȬ¬¬qcǘ™š„7×)BÎÍ›7õõtŸ?λ®ï´©£GýàK"}ÂÂÃÖïPx IDAT•••KKJ¤5…ÅbݸñßI”ÉÉ×tttìì‚0™L î¥ôëé-.§¤¨Äd2…^RTT:thRR•J%AIÉÍÍ-''ÛÂÂÂægZ\]6›ÍÛ¬¯¯ò䉕••¢:ÔZ &îÞý‡Ûs÷Î===S3!ÛPÆ÷ª¬¬ÌÍý^´²²2;+ËËÛK6ªrž*ˆ èvž*¾Ðžè¦PZ»£ª¹NrÆŽkjjzæôé˜?þàL/++ËÎÎŽ‰ùCPŸ¼Ü\7K¦ã#Jbë‡Íf‡†lÿõ×Ù6}úp„ 2/ÐP^^¾je ÏäÉ66}X,VzZFó.jD«¦¨ªªîܹ³±±ÑÆÆæúõë©));žÃ‡ÿ™˜èååmnnžžžvæôéµµµ³£R©§N0ÀIIQ±o¿~¼WCÃÂ'Nðö™4qé²e††FÊÊ ‹ ÿÃ#"&x{O™2yþüùÆÆÆ$RÃÓ§ù(‚sÞë@—À•+·m jQ¿9sÌÌÌììUUUË?•Ÿ=w¶±±aÝúõ-N”Y®ÕÙ=zŒ»»ûêU+¿|Ùlnnž’œœ½wß>N$úÞ½{¿Îž•pð ¯ït€ïôé\üûï[·nSUUù#FCCsùò2Özª 2 »íþ“B¢º;ª„vŠƒÃáüýˆ ˆà”&J>ùùùgNŸquuåÍšb0óçÍüèÑ•«Ié`2YVT·´°mLT—lG•dr† s³³³ ÍËËå ±X¬E dee^º|¹3–%”Y1uuµòöàp8===¾ÎNÐæTAd@×ñT‰IÕ±X¬;ª$–3oþo›7mÔÖÖž2e ·sýúuþþµ5µéi߃›–ýûw蚥mAª^%õƒ@º#ÐS‘]¤NU«hcEõ¸¸x++kOwÛ>6—/_>uê´djˆ#gòäÉ€Y³gó;šÿä 1ñÌo¿Íç>Îþù§djȘ÷ò¨¨Î%777,,œ·§=Þ#é€Àœ*ˆ àzª¤‘¦ÞÓ»TTÇ`0‚´“>åååí$t@ QiWä¿ûïÕ«Wµµµ A"‘øîÝ»gÏžåææfdd\¹r%**ª¹‰ññq {÷îã5žz÷¶X»v]QQá?ÿÜåô,]²dô¨_x'NöññŸëÇmùÍ™ceiafjâííõða÷Òö¾»¬¬¬qcǘ™š„ß¼yS_O÷ùóç¼}§Må[¢¹é-®øáC™ÿ\?k+K#C‚íìÙ³šššÄy¶mÛúGLLCCƒ¾ž®¾ž®ƒ}2åÅ—ÊŠÖæªËr÷_BB¼³Ó2™Ì¹‡BînàÇçÛœÎ1Ñѽzš·êmJ0EêdggÇÆîçíS+(?^€¾žnàŠåÜ·%›µ |Üû矉'ôêinjbtÍÔÌÌÔÔôÌéÓ1üÁPVV–ó‡Pù|Ó[\q®Ÿ¯Ÿ```P]]™yŸÁ`ˆ{7X¿~Âf_¼x1++€ÃãÛ.³{¢¬¬|òä)nsÍšÕ–––+9M žhusèéëÙÚÚ¶jQ ¦HÁðqGÐ pëÖ­(**v±µ:)>üõ×ÙÎÎÎÄ))+_ºxaõêU(ŠúÍ+8˜ÉdN÷õ­©© WSS‹ù#f²Ï¤¬ì}}}Ùkm&H»"Ïœª÷ïßãp¸˜˜++«OŸ>ikkãp8“Éd2™?~ܰa _¿~‚s{õê-xÉÔÌ ƒÁT|Ëjhhx5)III àáá1vÌè˜è}gÿä `0èûöEâIíò÷8p 6<"B]]x挺ºú4__¡ò§‹X‘J¥–––ÆÅÅ{yyq?^œwÁEGGGCS‹Åš÷ìÉéi»Lé"Y¢ºì Jáp¸I>ÿýKÞ´i£‘‘oO‹øûøû´jQ ¦H‘æÂÓòÕŠ™LÞ¼icHÈömÛ¶v¥µ:/×®]œ;AGG0vìXW×—/_jT%%%&§¤º»»\ rè.cµËÊZ}œÒ*äYQÝÛÛ{ìØ± .|ôèQÏž=8A²²²ÀÀÀK—.I¬ "ÆÏƒ‘““3yòdŽ}À`0ãÆøèwŒŠŠŠë A¼³æúû3Œ«W¯p$\¸paÆŒ™Í•;â›.zE[[Û¨½QÇ/).–Š%Ñ2ÛˆÕeþk-%ÅÅ>“&™™šôëKØ¿ÿ¿Ào8L̬S8!È;wn>ÜÔÄd€£Ã¡ƒyu›àß¿__c#C‚m`àŠºÚZ¾¹¼ái¡ác¾¸^qQQ€ÿÜ>6ÖÆF†®îm&@/"Æ-»ví415ãÇïnd¹Vç‹Ãâp8ÎK‹ÕÔÐÄá„¡üu릱±1Ç¢˜˜˜xxzfddÈH×p곈ùpvr’±z®A»xªš{:e–ÏúKiûÜ]§›™™UTT(((¼ÿ~Á‚=RVVnn! uuõŸ> ^ú\Q¢¨©YË_Û$‰ÉdÆÇÇ<¹Ë{ï½w˜ð}ïÞ¼Ùºu«ƒƒãÙ³g~˜=›ÅbM›>òþý{W7·1úiéië×­;nì¹sçù¶ýæö´ž¾~õÛÇü>|è߯¯‹‹ËêÕkÌ-ÌS_¥>M~Z½IÂïqg¤§{z²ø!0tÉ’ZÎ/!„Dï‰þý÷ß«ÿ§ë—UÇ}¿qcÇ:xpÑÂ…Asæhjj:t0%%yOôÞ §¤¤T™ÊÅÙ%îêÕÒÒR^÷Rî'&ŠYØNzúJ*cªý¬Ñ¡Ók!ç¯îNNþÑÆÆæÙ³g£FJHHpqqyóæ ­X,Vûöí¯]»–››K]æ¹pá!¤cǦTQSS­ò'¹´´„}Bˆ¾¾¾ªªê÷ßO·W:qâÄýûß»wooô^oooWW7¬u5Ú¸i3!äùóç±11ËÃÃ-,,‡.ä(j%¨NÛL;qG©×E|ü­ï¾›È¿ÆÈÈðÖ­¿…lâàà úß颢¢ŸWFvèÐÒ¹sçøø¿N>U%TIpV¬M8Ndd¤›[sBÈ!C}jffFÑÔÔôóóûóÏ›‹CCÕªuÊ…ðõõsvv^vëVü¦Í[jßà_¢ïÑÉÉiYxøîÝ»R’“…EÕ]hh–••‰R§‘øJ—p ! æ/°´´ Ö¨khÐxÝ«AƒíÛ·ç-:»¸|øð¡JÞØÂ¢"?__g—Z¯‚ˆµ‰††&¸éÜ©ó¶­[SR’[´pçp8Ö¯?}úTFF‡óÏÿŠ—/_òBUõ»ÛBp8¥·nÝú!0PÈ%dòï=î™3gV¹ÇýëÎ_©ÅFe~ü$âN·mÛúùsÖüù D,_²Ü—¢{øðáÈý½[Oš˜8q¢¥¥e~~Áýû÷¸•ÜÅÿN È„‰ß-˜?ÏÐÐpàÀb¯=fddþ0»ÿ€ŽŽNååågNŸ.))éбc­GÁ¯™³sqqñž=»=<Øšzúú‚ê” YTçr !m}ÛòÏ¡JGG‡?©U”—W/&ÁXÑ7ÑÕÓåŸUÄÀÐ’››G™?ÞÉ'.\ÔÊÛ[WW7?/¯OŸÞ%¥%ÿm[íî¶ùùffµ\Z宺ˆ>~ü¸*2råÊÈŠŠŠüü|j%‡S–ŸŸß A±:BŒÚW=¾,ÌÀÐð·˜êÌtèÐáÇ .è?`@õ  x§”’Ÿ—Çb± d×âáÓ UŒ{§:t¨è…gÏþ¡eK-[6‡…-ÍÍÍ%„4oÞâè±£&&ÿ}MM‹î7mþå—UÖ¯oÚ´ipHHiI)ïY7·æ¿_¹úË/«~ZôS^^®¡¡›ÍžáÇùúù¹Š<‘ûª¬­­Ÿu-.nÎܹÔ\ƒ×®]5rÄæ-[† J2tè–­[¾Ÿ:uÑ¢ŸtttÖþo­žžþÌ™³äÒr*T¥¦¦ÚÙ}óá¾ÔÔT\Ä‚º“[¨’ƇäÕÔÔ9J{µU,\0ÿÖ­[^^­~Y½ZÚûŠÄÿ[ÜÜÜx“ǶmÛÆÛ»õ˜1£c~‹ š#»/œrW—–M4h°cǯ‹.xò$ÉÔÔdYx85Ÿ!dãÆMsçÌéFkŸ={¢ûöíSc% ` ùv zõû€,Vçà¹s‚ƒ= !½zŸ|ôèñ²eVs‚‚ŠŠŠèjL¿~ý,,ÌwîÜE-~þüùø‰K–,¥«~[ºdÉ‘#‡Ÿ&§È»! éæëí;ÈíCŠ¥œ«ððe¡‹çääð¯tvvNI©úòÉÏωML´KMÍ¿xa !dqhüîÝ[¹Ük²k.0‰,ËN~&„èhª5³Ô˼͸êPïI0PÆëš„°˜0³<È^•+RIII¸F4B¨™’ñ@õòòrþ±Ò oß¾íÕ»B€rB„©B¨Y“åd AAsÌÍÌ\Ü\45µRRRŽ>laa0fŒÌ mB>  Õ!TT!TøÂ’§ûâ…Kç/œçp8¦¦¦ýúõ›1c†¡¡®T('„**„*)Ϩ>aü„ ÕæC¢PZU UbÌ@ j ºX›p¥FJÇLãÉf‹ø5É1±±žl¶´Ûõ®TLI6P Ž¼¼< !1±±µ–ôd³½¼zôÈÝÝ¡ … ¹D¡R§QÜ;ÿS=B¢ùB¨Pý211Õ%sÿþ}Bˆ§§§A&ä…þIF‰_ì‹ÈBÈTª‹Wª„ëÛ·ï»wï${ ‚0!—(ôOê4bòOPhU k˜Q]¾ÜÝÝ322${ B0!—(ôOê4"TBC¨™w º'›½<""`ÌÑ7÷{ÁhÁ„\¢Ð?¥÷b‰õd³%ýň¡ dM¬kTÔw ÆÄÆŠR˜zß±0<ÙlK+›{÷îyyyá§d?½!Òy±{²Ùø6e „*`¨îÝBf̼F!ÄN”MóE/ @£ÄÄü]»#„ò?ëð“HãÅž˜˜¿k÷5QJòKMͧޅD‡P2%ú@õà`OBÈïWÒ¥Þ&€jºwkL½ ˆ¡ dMôêÁÁžxS&+çM=ê1BÈ”d3ªȯ¯¯¼›uOo…åœâOožè4ó$D Tì ºFåëëKˆfn^ÉâPšÿÓÔÝÌ™3åݨ“øøx___zs•š†¶Y“jšÿ,ÒX5@­]ÛîÜ­òn@Uaa^¾|)ïV@˜™™±ÙlÚs!\Þ#„*©ZªyȰ9PäääH¶¡±±1“ë)..~ôèQßý}}}‡ –––V—J€’››+ï& hŸ-·ÿ@Î$˜Q@œœ{{{ɶ}õê/Ç0­z±X,iT õÓútÕC;Üþyªu :ÞýAtÙÙÙ'Bˆ½½ý«W¯LLL˜VÄ5Ђi} föUøàöÈðkTUBUTT±*>-I›@aÕ=…S50­z¡¯¢cZƒá}ÜþÓ9ê1ïñµàÂeÚ2†ÑÑÑiãÓÚÒÊÒ¬aCUUUBÈÅ‹—ž$=¾•¥…E›6>VV–šššååå_¾f}Îzš”œúï·ª™››ÛÛÛBRSS33?Jû(ÄEW\`Z=ôB_Ä´>3û*Üþyªu zÿõ©´ÄËOB™ù'M6tuu==¿ùÖØ¿þú3;;×ÞÁAÐ&VVV#†UQQ¡544LLŒMLŒß¼yóþý;kkBˆ¹¹™oÛ6„çÏžåää0íæÓÞµ™üîÏ}©²kÚÔÕÕÅÜÜLOOOEE¥¨¨èÍ›´¿âoÊ»i"aZÜLkK IDATƒáoìjZÿ=–c;@9 ¨^ÇÓ_{jjåå墯 ‡SëäÉ“-ÝÝÇŒá­rNZ{·¢Õ† n'Üf©¨4±µõiã“““‘žacÓˆó·o³³?›ššÖ±ôbZbf¨B_EÆ<Ø-íšþ×uÔÓÓkÑ¢¹Ížèh.WÈvLÁ´—3_V5B¨™’lFuþ~s­‹2ÓÌɉÍni``¨­­E),,ÌÈxËßí×·““#!äð‘£lÆîÚ= ¿ë]]\„TØ¥Kg–î„Øý>~üDíbü¸±&&ÆÅÅÅÛwì,(øråÊÕôŒŒvíÛ‰x„¼¼¼]»v³=½,--Ë**Ï_¸ôáý{---BȨQ#,-,¨Â‹C¢?qòͦ|D_z]áK7^~|ŸZ½¤¹•]Ï/þÕX!¤Ê&5®”žúÝW‘ŒT{8ÜJnÜÕ¸ýû÷¿|õªiÓ¦+Wþlaaaddh` Ÿ™ùI[[»ŽõKÓÂB€@b T'Õî>_”廿…¥…••UQÑ×?êééëë뻹¹š™5ܽg¯††É®]º˜˜B23¿œ>ur@ÿ×wéÒYH…›íAùRXxèðas æŸOôU$†P²V?ª³X,ÿ¾M›4©þ”¦–fAAÿ%¢ää§Þ­[[YYB´µuj\¯£Ó Ö 32Þ~üøÉÜÜlð'Nž240 „;~ÜÜÜÜÈȈ¿mUš*üXnßÙ¹s·šºª——׌3ôtuÍÍÍ>|x׸qjj±j“1©¾û‹ª„þèÓ¹Ùùkÿ$ª>›ÕÚ`f_E2ŒíᘘÐ_[[›ÃáÌ3777·Sç.²8#uƒ¾ŠÄª@¦$›Q]ôÁ2ûƒdnnF 'O’‚ƒƒ?|øÐ©SÇ 6PÏV”W𷤬¬ÜÔ´aõ¶ñ¯±Â'IIææfÍš9÷îÅ!„]ºxÉÛ»5åU 9'†yùù&&¦:w&„äååÝ¿¿c‡„UÕÌÌL›oNñצª¢BX,òïwi©¨¨POq¹ÜÊÊJBÈ?ŒË­¨¬éœJDQBÕ¹¸ê}ÿãûÔó×Hß.ÎÂÛC/Eé«H†±=kk+ÿ~}555 ‹Š~˜ýCbbbž½tuui?´S”—û*U kâ~ëŸXƒ?d†úp!$%%¹¼¼|ä¨ÑCRž7” õ"VøìÙó6>­4hв¥;!äâÅ‹åååvöö„‹EU»£SV^©­­]\\\ãÞ»ví¢¦¦ò*õͧOŸJKK›y·jE=õæÍ›Æm !¼OBÙÛÛe~úoòÏü©Èµmû¯ÇÍÕµs玄«W¯=IJÒÑÑ™2ù;BHFFƱã'~Ý)D—úìÕdê}¿_W—³WÉÇ÷©çâH¿®²»‘ª(}É0³‡ãà`ß³GwUUÕOŸ>MŸ>=--½WïÞ––VÒ=4Q”P%ß¾Jª€YeðÇÇÌÅÅÅÚÚÚƒ ²³³³¶±1þö\õ{p‚Þ2¨õ"VXQQñôi²·÷?Ñçø±ãööêêê„}}½ñãÆòW¾páBêÁÿþ·^M]½Æ17·07·¨²ò?®¿xñ¢eK‹õéã'.—Ëb±F5jô(BÈ¶í¿–••ñ ïßÛß¿?ïènÅÇ¿ÏüàééE-¾ÿþÒÅ }úö«qïuWãY=såiõñ°æVvþÝ\Õ ½wª1¼½ûws=s…||Ÿzö*Òicf_E2 ìá4oîÖ¹SG‹õ¥  rÕ*c#“>}úéëë—rJsrr%=PÙA_EbU SõfFuNYÙ‰§Z¸»97s¶··¿{÷î;w,˜Ï+P%Eñ/Ö¸^” ©O’žzz²UUU_¼xñøÉ“ƒ ïÌBbccÆŽ_}€Tü­[„Ëurr´µµÕÓÓ+++KOO»xéò¾½ûlmmML!_¾\¼x¹¥{ K ê¯NôžÝ#FŽët1ùR‡ ¶™[ÙíÜw¶ÆõbÕCªå9*W‰ÛžºP”¾ŠdØÃqrt Êèé믊âmûúõ›ßbbùG@2ú*C¨Y>P]ääæ9rüVü_Ÿ?VWWoܸq`Мkqq„w÷–„‹—._wåÕË—U6´¾Ö )ÚÚÚT¸9zô¨±‰‰™™µ¾ àKdä/û÷ÇŠ~?~ÊÊÊ:}úLfæ‡üüüÒÒRUUU}}v ww^±W©©gÏMLL,*,äþ;!ôÉSgÎ=óîÝ;jñIRÒá#‡ÿ¼y“Ò®}û’’’5kÖîÛ·—bmm-z“ÄUã{eÿîn„¸Õ± *\OU5®”Eé«H†=A²³?ÿõçŸþýÅûîEE¤ }•!TLÕ›êssóƒó¯qrúf°d·nÝ»uë^}CAë…Whog×¶­®ž‹ÅÊÌ̆ý]¨?ÿ @QHuFuP*Mš4¹wï^«çÖ×Ý»w›4iÂÀzh‡—•4ÔãÓú ÑW¡ TLÕ:P@,Mš4¹{÷®ÄÛ2¶z!Tè˜ÖÇ`l_¥FU kâT®iSw“ºyaZƒ™}•!T³ TÐ/+ÓúŠÒWA¨™Â@uÙÃË @6ª@Ö T¿uKsåÏ-œœ¨Åø[·R_UýJ€úÍÎÞηm[êñ;w·oFKµU²P2%â@u*Q9;7«µ$@}’’òŒÂËUu‡¾ €”ú*5B¨Yå[ÿ¨D¥¢¢vïþ½ºìËËÓ‹Ë­¼Ÿ˜X—JdÓÍî۷Ϲsçi UüÐWåD{_E„*`®{÷ïJ|ç‚Ëå®[¿žR—Jdƒúï*d¸aÝ¡¯JHÚ}•*ª@¦j¨^‹Åª¬¬”l_u¯@6xÿ]¥ }P2è«TP²&|Fõê¸\nÝwJK%õú* $dÖWáA¨™ÂŒêL€¾ €4 T¬‰~Š‚wPUÀtU´ÃË @ª@¦Ä¨Nè~÷ÿôéÓŽ;ž¦@+„*)¹T‚—€t T¬1dFue†—€4 TLI0Ph‡P U k ™Q€^UÀtU´ÃË @ª@¦Ä¨ž••õõëWÉö¥££S÷Jdƒ÷ßU6ª¤¡ dMôê^ž^1±±uÙ——§—[YÇJdÃSVó£¯JBÆ}‚P2&Ö@u.·²Ž»ãr+ûù÷ïçß¿ŽõÈÀÍ×e°ôU@©È¬¯BA¨Y} úýÄÄ À@‹%ÙŽ¸\îºõ둨ø¡¯JE6}„*`4‹UY)á߀ŒŒ zP ¯ =*òn(—ÔÔ×ÔXuÑq%U½ªÍ›7y²=„ïîæÍ›ëׯ«µUk£¢l7ë@j$J“èEõU$“žž.ïæ0BÈ5P]Þ­èæÍÖ¯¯µ˜iCÓf͚ɠ=Ò@c_%33sá½{õ´±¶jhj’ÔJ ¡ dÊή©½ƒX›ÐøîO §”2nÜøË¿_‘Ò.HÚ›7'Oœ022ööö–w[ä ¡ díÃû·bMª.¥PõæÍëqcìí,ÌÍ\œ›9¢°°ð§ŸýoíÚ‚‚‚†¦& MMÜ[4'„,]²ÄÕÅùÆ={t·±¶ZJ¾½ýGøûï[½zö°¶²rsuY·î›ˆ§NžômÛÆÚʪ};¿sçÎ }ú1räwwwggçwïÞÉ»EŠ¡ dŠÆê-=<þüóÏ­[·tîÜ¥Æb«=pðŸ¯ïšÕ«;thßÜÍuÆõB6ÑÕÕ>¯††ÿ¢ŠŠJEy9!¤   ²²ÒÄØ„ÿYãodO}Àí?5±F© QÇ‘5Ú¸i3!äùóç±11ËÃÃ-,,‡NKÛxôõõUTT²s²ùWæææ4hЀÞˆEP<¢ú*„}ûö%$ÜF ®TÓ êRÿ×uæÖ\LÄúœœ–…‡kii¥$'B454ËÊÊèj¼–––‡‡Ç…óçyk^½z•’’BWýô¨s€ºÀ•*©Tñª×¨T%VÊÈÈüavÿÊËËÏœ>]RRÒ¡cGBH3gçâââ={v{x°554øGJI&äÇÇŒ=kæŒ199¹‘+nذ!þt|Õþbáë«È =õBÈ5P]ô›€"¼û‹ª ¬¬¬¶lÞüöí[—;wuêÔ‰Ò»wïqãÆG®\™››kaañèñë¤{÷;vüºjUä‰'š4i²`ÁÂmÛ·éëë×±Z€º ±¯ÂårÏž9Cy’ô„råêSSsss!Ÿ–¨¯ª@¦ììš2á;jôõõ7mÞRãSªªªQk×F­]Ë[³,<|Yxx•bsƒƒç *ðë¯;ù4pÐ êqnnî¬Y3ýûù×ñê‚Æ¾JEEÅwßMä-ÎûñGBH÷î=ö8P×V(„*5qª‹ÐQþ§3ïSEF®ìСƒ±±ÉÛŒŒ Öëèè 1BÞí ‡ššZÖçìÚË(„*`:‘ïS0t>C55µW/_9|$77G__ßÏÏoûŽ_åÝ.PjŠÞW`&„*PT\.÷ê•+„gÏŸBþüëOc##SSS6[ÞMû†¦¦&îƒÓ(z_€™ª€é½§———‡„ðW¬XAiß¾ýÆeÔ2€zGQú*Ì„PL'(T©ªª&&&Š^xÐW„*`:„$Ú¡¯ UJ! @ª€éðî ¡ ˜¡ €vxYHB0Þýh‡—€4 T(„*i@¨¦Ã»?(„*`´¬¬¬¯_¿J¶­ŽŽ½¨7ÐW„*`.O6;&6¶Ž5ܼq®öÔè«H BÔsí;t”wD"›ú*ÒƒPÌu?11(0ÅbI¶9—Ë]·~}?ÿþô¶ ÐWE!ãB0‹Åª¬¬”lÛŒŒ zP ¯ =*òn@-¸’ª^ÕæÍ›<Ù²?„1ª1 T¨¾ŠdÒÓÓåÝ|æB¨P:4öU®_¿>íûï=Zº[Z˜»º8Ïž=+33SöGÀ¸ýLÇ„Ï~s8¥š¢¬P6k£¢ ÆŽרqãW¯^nß¶->>þúõºººòn€¬áJ0]j~ÉOŸN?®¹›«¥…¹‹s³Ù³gådgóž]ºd‰«‹ó7zöèncmµ$4TÐJ!õ\¸p¡¡©ÉÇù÷;dð n]»Ð|ŽÄDãË*jíÚkü2|øð… mÚ´9=-íô©S²?(¹Ã•*`:¯TõìÙËÁÁzüþý{W7·1úiéië×­;nì¹sçy…óó ÿ´hÅÏ+œœJKJ­RO=¬­­÷FG¯ýßÿ¨Í_¿~}óæÍµkÿW¥12Fã˪ÊcŸ6m!>| «~‚PŠ*>þÖñãÇïÝ»›••e``СC‡9s昙™ ÙÄÁÁ÷ k·n]»u£·öñiѽ};¿ä§O]\]©•Néš5Q­}|øk¨¾RH=ªªªãÆß°a}øòåÔ­}{÷êêê2¤JcdLzwÕoÅÇBx¯#¥‚PL'èÝëÖ­_¾|6t˜•õ›7¯÷íÝwûÎS'O6hÐ@”j9ΆõëOŸ>•‘‘Áá”Q+_¾|Éûc ­­íݺu•­ª¯^ÏØqãÖ¬Y}ôè‘ &r8œ 6sRcIÐWá—››¶´E ÷ž={JµÌ„PL'(T………5mÚ„·èÒÌ90(èâ…‹ƒ‡ ¥ÚùóçyòøÄÉSM›6¥­¡ ¡ ˜NÄÁwîÜ!„8::‰R¾²²²¬¬LOO·æÌÙ3´M”z|}ýœ—……ݺ¿ió ö 3M›6á_±¯Âáp&N˜x;!áÈÑcîîîÒjãaJ`:Q>æ——·fÍg—Ž;ˆ2¥‚ŠŠJ‡ŽÛ·/55µ¬¬ìøñc{££%h›ˆõL˜øÝ­[ñ†††”`/´qþ¾Š —Uyyù”É“nܸ¾ÿÀooo™ ƒ TÓÕú¾_RRôµ¸xÍÚ(QB!dãÆMööÚ·kæäxøðá={¢%kž(õ 0€2bäHMML Œ@c_%$$øüùóÇÈþœ}æôiêß“'ePr‡Û ØÊÊ8?>{–¼k×îÆ‰¾¡¹¹yìþýük²>ÿ7ùç²ððeááU6©q¥ðz(W®\!„Œ?AôæHU­½ŽÒÒRª¯òëÎT_EPÉ{wïBöíÛ»oß^ÞÊI“&E®ú…®Ö( „*`:!ïæeeesçÌ}˜¸}ûvggçZÿNÈÞóçÏß¼y¹òçž={9::Ê»9"«¯róÏ¿dÓ*æC¨¦•ÊËËü1äï„¿·lÝâîîÎÀDEY¸`þ­[·¼¼Zý²zµ¼Ûð…î«0B(ªˆqq׆š—“{å÷ß©•7jÖÌY¾ ãwìø y7  ÝW`,„*`:Aoë>"„=vôè±£¼•#GŒX°p¡ŒZPï(D_€±ª€é…ª£GÖ¸}k€Z¡¯ UÀtI´C_@ª”B€4 TÓáÝB0Bíð²„*`:¼ûÐ/+i@¨P:UÒ€PL‡wPUÀhYYY_¿~•l[zPo ¯ UÀ\žlvLllk¸yã:]í¨ÐW„*`.//O//Ï:VbieCKcdàiÒciï}éA¨ñ´ïÐQÞM‰Œ;UJä~bbP` ‹Å’ls.—»nýú~þýém@ý€P \X,Vee¥dÛfddÐÛ€úDEÞ YãJªzU·†êæêbanfo×tø°¡·dDL€+U ¹ôŒt##ãÙ³0mhšõ)+:zÏ A/^ºÔ¢…»¼› kUJ‡Æyª†6tè0Þâ Áƒ[º·8|ø0B(!„*¥#½É?MLLÔÔÔ%³ Ðª”í¡ª¤¤¤¢¢"+ëÓúõë555ÆÒ[?€B@¨€Ü¿Ë–Í))ÏrssuttZ¶tŸ1c¦§gísð2˜Ÿnbbzðà!é7€qª”Ž +Uoß¾500œA¨ñ´ïÐQÞM‰Œ;UJä~bbP` ‹Å’ls.—»nýú~þýém@ý€P \X,Vee¥dÛfddÐÛ€úS*(®¤„W;qÂø†¦&³gÍ”ÍQ0 BÐàâÅ‹ýõ—†††¼ 7UJ‡ö+UEEE æÏ[²d©šF•€òB¨P:´‡ªŸ^… ¤ò IDATaem=& @–GÀ4èR(ZGG‰åÁƒÑ{¢ÿýw‰?TP?àJÔà‡f;;;/\¸@x±ŠŠŠ¹sæ|7é;W77Ù4 €±p¥ @éÔz¥*.îÚíÛ·54Ô ·–ÂÛ¶mýü9kþüZ²€2À•*¥#|àTQQQÄòð¹sƒUUոߖ­RÏÇWEFΟ¿ ¢¢"?????ŸÂá”åçç———ËãÈä ¡ ¾±~ýzsKË!C†ÔZ2##£¸¸8((ÐÁÞŽú÷õë×ãÇ9ØÛݸŽï¥ƒÛJGȽ'II:|èà¿E…nÖ¬ÙÉS§ù׌>Ì×Ï/00ÈÕÕ•¦Æ( „*¥#('UTT„-Y:jÔHG''ª WhÓÓÓóóóã_£ªªjnfVe%€’@¨P:‚rÒ¾}û²s²g̘ÁW@ØôTÀ¡ !$+ëóæÍ›.\XYYùåËje§üË—/ÚÚÚ"N•ž–Žo\å…êJ§Æý½{ÿ®¤¤déÒ¥¾ÿ*..>Ἧ¯ïß·þ>£:\©PB5Æ#»¦v»víâ_3}úôV­ZMš4ÉÑщ  VU@!ºº ZµjÅ¿FUUÕÔÔ´ÊJ¡ @éˆsÙ wýD…P tDÌIñññ¢„*¥ƒœ øô p¥ @éàJ€4 T(„*i@¨P:UÒ€1U4À•*¥ƒ+UÒ€P \²²²¾~ý*Ù¶:::ô6 >A¨P"žlvLllk¸yã:]í¨Oª”ˆ——§——g+±´²¡¥12ð4é±Ìö…ê4@¨ B ªh€P@„* TС €U4@¨ B ªh€P@„* TС €U4@¨ B ªh€P@„* TС €U4@¨ B ªh€P@„* TС €U4@¨ B ªh€P@5y7 vöv))Ïìííë^UnnnÝ+sçÎÛÙÛÉ» !„*`"ß¶m !çΗwCdÊÎÞŽúÏ/¥ÊÑW%$˾ B0”oÛ¶Òûë „ÐWå$Õ¾JUÊ}©B¨ÉÏ/¸s箼[ „*` .÷‹ÕYÞ­`.÷ZÝ+A_@fª€Ahù<è«T!Õ?4Uõú*2ƒÉ?h€P@1nÿý;qÜcéµê%L Ê@ŒP…‰ã@2²œx @^čމã€FÞ¿•whƒ1U4@¨¨]jêëÔÔ×XÄ¢.€èªDbiiiieƒE,*á"ˆ“ÔÎή)ÿß,bQy@t¬°°0 ï ÏÞ|åTB–4•w“À²“Ÿ !:šjÍ,õ2ïD‹q¥Š¿ïòþýûÆ/^¼8,,lΜ9[·nýðბ‘õlDDDdddaaa•ÇU„„„lÚ´ÉÑÑqýúõ...%%%M›6å/îÜ9ÿ€€€I“&egg‡††æææzzzž={–Ú|ûöí7ž1c†££ã±cÇvìØqäÈ‘¡C‡æää,]ºtïÞ½="„¨©©ÙØØ4oÞ\]]=,,ÌÂÂâãÇW®\Yºt©‰‰‰(­zøð¡ŸŸŸ§§gpp°®®îŽ;Nž<ïååET³ð­ªïÈÒÒÒÂÂbêÔ©¿üò Õ˜?Z[[¯Zµ*88XxmçÏŸïׯßðáçM›–••µhÑ¢¢¢"Þ¹ʇ÷oïÝ»?1±Ö’žl¶——§è5Kxû/&&¦¢¢bäÈ‘„‘#G®[·îСCÓ¦M·žÒÒÒmÛ¶ùùùÕølxx¸Ï¾}û¨ÅæÍ›;;;ó(,,ܰaC×®] !=zô¸~ý:ªŒ TTTš4iB•üúõkRRÒž={ @­éß¿¿è­š7ož¥¥ååË—µ´´!]ºtiݺuDDĉ'„Ô,d+A;6lØþýû###UTT! „Œ=ºÖÚ–-[æááqàÀ‹Eqttd³Ùµœ}åC%ªÐÅ‹k-¹<"‚"z®’p úÞ½{ÝÝÝ©ˆãããÓ´iÓ½{÷JPŽŽŽ¯¯oOß½{wàÀ¼5NNN®®®üetuu»téÂ[lÞ¼ù»wïíÈÕÕuÙ²e›6mzüø1—˽UçÚµkÆ £Ò !„ÅbùûûÿùçŸBj¾• Ã7nÜ»wïâââ¨Åß~û­k×®–––Âk£ÎÕ!C¨DEñððprrrŒÊIÄDE 3F” Z<’„ª»wï>}úÔßß?ï_ýû÷ÿû￟?.nUzzz¼PE~~~ee¥©é7c¼ª,6hЀs55µòòrAû:þ|ÇŽÃÃÃÝÝÝ­¬¬V­Z%(ZUiUnnnYYÙêÕ«µø,_¾<;;[H͵nUãá·k×®I“&¿ýö!$99ùþýûãÆ«µ Ô¹²°°à¯ÊÒÒRЩPr"·NInÿQ¥V¬X±bÅ þõûö틈ˆ ÂQ÷ï>þÌ¿2;;[WWW² mmm£££ !ÉÉÉ»víZ°`••ÕØ±cEi‰ªªjPPФI“D¯yذa·ª‹Å X·nÝÖ­[ûí7]]ÝAƒÕÚê\åççó¯ÌÏÏ—ø\Ôod¦Z‰}¥ŠÃá8pÀÇÇçÚ·<<<~ûí7á·ÕÄ¢­­ÝªU«“'OòÖ<þ<))IÄÍ555ËÊÊj|ÊÅÅeÍš5ÚÚÚOž<¥*--­N:]»vÍÁÁÁù[Bj}«*ÆŽ[XXxüøñØØØÁƒëèèÔÚê\]¹r…WɇD<:%T墔®T;w.;;;**ªS§Nüë¿ÿþûéÓ§ÿñÇ;w·NA–,Yâïï?~üxÞ§ÿÌÍÍ©ܵrssûúõëÖ­[[µj¥©©i``ðÝwß 6ÌÙÙ¹¼¼üèÑ£ÅÅÅݺu±%QQQíÚµëܹóôéÓ­­­óòònß¾]YY¹råÊ´´4A5 ÙJȾœœœ|||,XðîÝ;êÞ_­m „,]º´_¿~ÿûßÿf̘‘••5aÂMMMÞ¶—/_îӧϾ}û¨1ïJŽ?3ÙÙÙBlmmSSSëR§ØWªöîÝ«§§7lذ*ëG¥­­-ÙpuAúöí{àÀ„„„îÝ»ÿôÓOaaaööö¢l;`À€©S§†††úøøôéÓÇÐÐÐÆÆ&**ªGþþþ÷îÝ;tèP÷îÝElIË–-ïܹcmmÔµk×)S¦ܾ}»¾¾~§N†Ú¦MÞ¶••••••"/@ýÆ»E%*Š]]®T‰1ù§ÜçØÍÉÉiܸñòåËçÌ™#ß–€‚ _ºxqNN!¤Æ19)))Ôƒüüü˜ØØ)“Ž–|òOÙ+,,\²dI×®]MMMÓÒÒV­ZÕ Aþ;b .DÕ8V[â1ìŒUjjjÏŸ?‰‰ÉÎÎ600èÔ©Óþýû«Ï i|úÑ¡JKK ß²´SºP U4`ÄäŸ íõë×Ò¨Wª@‰x²Ù1±±¢½f„*Pl_¾|ÉÌÌd±XEEEêêêòn(#ÉCUBBÂÚµkoÞ¼ùùóg}}}ooïÉ“'2„ÆÆñÄÅÅݾ}{Á‚¼5‘‘‘………ÒÞQHHHLLLff&]»¸víZTTÔßÿýåËÿE‹™™™ÑU¿BàÿõU?ç¢[¸páš5kÊËËÍÍÍ333mmm§M›F}«·”þ‡€¢»Ÿ˜ºx±ˆ…—GDxyyŠXXÂ1U›6mòõõMJJ Љ‰Y¹r¥Áˆ#¤4B\\\dd$ÿ333WWWìˆ^k×®íÒ¥ËçÏŸ#""öïß°oß>6›ýìÙ3éí”á$>çiii‘‘‘_¾|IKK#„´iÓÆÖÖ–î@=ôUâ½’äJÕ­[· tðàA5µj˜2eÊ£GÊÊʪ—/--åÿr_ZL:uêÔ©ôÖ)mñññ?þøã°aÃ>>ÚÚÚÁÁÁTGõïßßÐÐP[[ÛÏÏïæÍ›¼m?~iÒ$CCÃׯ_¯\¹ràÀþù'!dÉ’%{÷î}ôè!¤J’ãí.88xÆ NNN‡ž>}º££ãСC !çÎ9rd@@À¶mÛ²³³/^œ››ëéYÃýQA;***9räŒ3~úé§cÇŽ-Z´ˆW¹ðƒâÇår©óVýkv 4wîܸ¸¸>}úÔzòkÝc•3߸qãFmß¾}ÇŽT—/_ÆÅÅñëø‹0`€ººztt´……Åǯ\¹Âápj= žϹ(u®Y³¦wïÞãÇß¿?ïë0Û¶m[ZZ*ÁIe#+Ub‡ªüüü‚‚‚¦M› /VZZºmÛ6???ÞšyóæYZZ^¾|YKK‹Ò¥K—Ö­[GDDœ8q‚Ò»wïÞ½{S%ýüüØlvóæÍ?~Ü¢E ccc•&MšÙÝÎ;ÝÝÝ !Ó¦MÛµkב#G¨Üîãã³oß>ªdóæÍküJjBˆ nذ¡k×®„=z\¿~W¹ðƒâWPPPPP`ggW}¿7f±Xééé‚OçjÝcõ3?uêÔU«VEEEéééBvìØ¡§§7jÔ¨ëë÷õëפ¤¤={ö 0€*Ü¿QŽ‚§ú9±NkkëÆBš4iÂû…ªªªÖ¸ÑM  lž*___Þ"‡Ã¹víZHHõWÂb±üýý7nÜÈ+yäÈ‘´´4Þe‰gÏžµhÑB”ÝR‰ŠÒ´iÓwïÞBŠ‹‹ïÞ½ûóÏ?óžrrrw„»®®n—.]x‹Í›7§*¯õ Ä"ÊÔ®¢ì±Ê™'„L™2%<<<66vÚ´i':::  Aƒ5îB¬_œŽŽŽ««ë²eË ;vìØ¼ys‹%æq×Ðzë¤÷׊NzóT‰=¦ÊÀÀ@__¿Öˆ§§§Çÿ·077·¬¬lõêÕZ|–/_žM˜9sæš5k&Ož|åÊ•ÄÄĸ¸8BuûIUÆS«¨¨P·Bóóó+++MMMùŸ­²X« ð‹ššUy­ÅO___OO¯Æó–žžÎår©ë.‰²Ç*gžbnn>xðàíÛ·BŽ;–••5mÚ4A»÷wþüùŽ;†‡‡»»»[YY­ZµŠËåÖz ÂÑ[§X¿&¨÷¼¼½S§N Û·ooÛ¶­ˆ×ÿDÙ£­­mtt4!$99y×®] ,°²²;v¬£¨• :EÜ\ÜC¥rïÞ}§ªZA‘îkÖ,##£áÇ /)ú1¦¥¥}÷ÝwÆ svv.//?zôhqq1uÉMøQð«rÎ Õ)1á'íòåË}úôÙ·oßèÑ£k]€úA”¡Ìâ’p úìÙ³½½½×®]»víÚÏŸ?´nÝúرc}ûö´IË–-ïܹDÝóööž5kõìž={¦Nêî¡áççwôèÑvíÚñ¶0`ÀÔ©SCCCsrr¬¬¬Þ¾}+zSûöí{àÀ¥K—<<|ݺu5wGªºyóæyyyEEEýøã999„Ë—/7lØW†ÍfGGG‡……EFF:88„††òß8w<Æ ›5kÖøñãyãµE$d†††666QQQiiiªªªÍ›7?tèP÷îÝk= ~UÎyRR’ :«ÐÓÓóòòÒÑÑá­ñðð°¶¶ë!•••¼‹j ~F¨b………YxOxö¾à+§‚²t ÀAÜ–V6‚žR 9997^¾|ùœ9säÝ2uêÔ]»v;v¬ú¬˜´Û»wï„ ’““qÛ ”Yxø²ÐÅ‹‹‹‹ùWÚÚÚR_ÔÁ/333&6vÊdCr—üLÑÑTkf©—y'ZZS*0Gaaá’%Kºvíjjjš––¶jÕª Œ7NÞí"„-[¶¤¥¥3æúõë5NqN‹äääW¯^…††úûû#Qo¯TQ³HÚÚÚ¦¦¦Ö¥ÎúªÔÔÔž?“m``ЩS§ýû÷WŸÖ\.ÔÔÔ.]º$í½Ìž=ûÆmÚ´Ù²e‹´÷ x¡ÊÑÑ‘·ÒÎÎîÅ‹×YÿC•–––’Ïî•+WäÝf¡BUõ8ŽŽŽ)))’ÕYÿC@T¨ªq–%‰Ç°#T€ÒaД Š ¡ €U4F¨’ä»ÿ(×®]ëׯŸ©©©¦¦¦½½}PPЧOŸø lÚ´‰Åbñ¡¿ÿ¾‰‰IË–-³²²¨5³fÍb±X·@\¯_¿–Fµ^©Z»vmpp°ODDDÆ =z´qãÆ#GŽÄÅÅ5kÖ¬ÆMzõêåààpéÒ%ccã:´@BžlvLl¬è…E¯Y’+Uñññ?þøã°aÃâãã§M›6dÈe˖ݹs§´´tøðá5~KÚÍ›7»wïîêêzõêU$*—û‰‰R*,É•ªÕ«WkhhlݺUEå¿Lfoo¿xñâ9sæ\¸p¡Ê×*ÇÅÅùûûûøøœ>}ZWWW‚=Ð%tñbK.ˆ½Z±¯Tq¹Ü¸¸¸Ž;Vÿ¦ê[ãââøW^¼x±oß¾íÛ·?wî( q‡^‰}¥ª      iÓ¦ÕŸjܸ1‹ÅÊÈÈà_9gÎ{{ûS§NijjVßDCCC]]]Ü6Ô³>ý'¢¾}û¾zõjåÊ•5>«§§§¯¯/í6ð«¸uŠ}¥J___OO¯Æ bééé\.·Q£Fü+W¯^meeµlÙ2mmíùóçWÙÄÌÌÌÆÆFÜ6Ô•™lmm«?•––&Yb_©b±X]ºt¹qãFNNN•§N:EéÒ¥K•òÛ·o7nÜ‚ Ö­[We“™3g>xð@Ü6Ôu-*55µÊúÔÔT‰¯TIrû/$$¤¤¤dæÌ™ü³'¤¦¦FDD´hÑ¢wïÞUʳX¬Ý»w>|Μ9Û¶m“`4â%§/^ðV¾xñB¦·ÿ!íÚµ‹ŒŒœ?~ZZÚ„ LMM©É?544:Ä?Ϫªjll,‡Ã™1c†¦¦æÄ‰©õ›7o.//— ’áÏL)))ÎÎÎ)))u½.áŒêóæÍóòòŠŠŠZ¸paaa¡µµu@@ÀÂ… -,,îIMíСCƒ š\ÓÃ)öZkllœ={ŽH³m›ûYjµÚh4v{<b*ª8nºXÂç³ÙìK—.–•íH›%®ØWÁss³9‹ÅJœ–˜8-ñÖ­[++J+ÿõÙg^^^ S&O~nàsdÏ£55£"Ln–l *,,4íòõõ™:5‘út›;jZ­vÙ²¥~~/@||ü… mfÖ&2r&“i¯ÑcF3ÚôuŸ?t¨RRRcc£¯¯¯©ñ¡œTÞþCõæü… ±±±ÄÝf</4,ôô™ÓdADFÝ{Ø-6n¼\.ÿù§Fóq´ZmÝ©º âL?ü€F£EFFÕÿhµš3õõ“&M4UTƒƒÃðÏHµ×&6›Fnúøúü~ó¦éëÀÀa¹y¹ãÆÅ„‡œ3gŽd“¤µµõÓÝŸ€^¯Wš±“Çãe¤§WT|¶ÿþ¸¸Øýû÷ÇOž\¸²ü/|{üxLt4tvv^¸x1:æÞ3qÞÞÞXžäŽƒÁ$«[ˆ·Î¬Mæ‘wÕaÓ¦Í/¾øbxx„P:cF \½zõÞÔ|R!d‚WªB}…B¡4 ®ýž1o|ƵßåK—ÈMG.×üú‹k?Ëãhuº;wíÚuoqŒÑhÐëõ P¨ôz½›ïAButdÓh´ÏHµ×&ËÁ||:nèb9TP` ——×…óçàü… i³îÝ´:{öG›Q)*¥ª]­¦Óé,ËÔxê䩎ŽÎÈÈHP*UƒÁÕÅÕüS.÷oZnsGÍ‘ãh¾,‰ëìV™µÉlYk‘ƒU¾ ƒN§säܫު««-yð“ =mîVT–ðJB¨y#+ëõ×__¸`QÊÌêŽM›61™ÌŒÙd‹µ~ý…Báíí-“ÕJ¥Ò7ß̳^Ëü曹bqÆ+¯¾:㥗ÜÝÝ•JÕ¹óçŒã¢E  '7'==cöìÙb±ØÇkúåúå†+Ë–.€!C†ttt|¶wï°€aöL†¯ŸŸu‹ÍÈ©g¤ÞûÝw'²²² WÆÇÇw{ˆ/Êvïïàooïpùòå½{÷>ûì³³î^†±V_¦TR:>667/O$šß³#=wîVssŒÙ"ªy¯Ï[0aþ;ùÓ“¦·µÉKJŠŸy¦ò­Ô¹‹‹¿}Ûv‰D"‹[ZZVw¥4a±X?þXu[5xÐàê£G«ªªÈÌFS%t¥á ûöx?WWž›[Ÿo¹u¾FŒñù¾Ïc¢£xxT9RQQaùC9©(‚¤8hèqDVTB¡@zoÿ!„úªðð‘¥¥¥‰${qƒÁ E«WhþL>›ÍZU´ªèƒU—¯4ôëçš›—kþè;Y1øù½°gÏ?K%’¢¢ ¹““sà°a/¿|ç=Iþþþå廊‹%+W¬Twt 0 1ñΣmÑÑQÉÉÉ%Å%r¹œÇãUW±n±9õŒÔ{ƒ^¯'¯âPŠ••‡+++5››[BBÂo¼A^s²&à jkkÉÛj6Éjdþýû÷'[ÆŽ»º¨¨¤´ôÐáÞž³²²ÊwÊ5»[g:wC‡ú¯X¹B"‘”m/óôòœ7ož¦Scþq6›UTTd3³z½>77ìiZ\?vÌØâ’­#·Î׊+Þ[þ^RÒ‹L&#„Ï_·v­8=ÃbjŠ“ŠDDŠ )zì˜WTÖkªhË—/64ãʯŠvþ1ÍöC¹!ô÷Ùºm{櫯<à }´ñ‹/öËd²î»¢ûMš8eÊ”¹s3»ê —Ëãâ&ÌŸŸÕÃGýYk׬•”þ…Üu9B=ÑÃoA•ô«Òë^À¶'^Àý­n'^©B=ö Ź³çª««ƒ{;–ÇÒ_Z´´··—””Žæââú¿_-+ÛÁb±z%< Ö‘#ô7±®¨¬û`Q…zì}ÿÃ÷9‹s—,Éë¾7ê‚ ®]½&•”ËÛ8Nh¨¨¨h•‹‹KoÇ…Pï°YQáš*„Ð(rlä™ú3½Å…Éd>ÊÅ@¹y¹¹y¹l:„þ”®**ë5UøJ„B!Û(**Ó×:šìŒEB!„ =©¨n^=¯ÓÜyA?¡ëî¥v!ôwðù[·mïí(BO)­×‰u[Q™ÖT¹ "˜ö Óéü»E¡^' „BA÷ýBè‘èIE5Àc Ns`Þùšz½žöֻﻼCZ‘bt„B!:š`²L÷þܳX¬ç8†Öˇì¦MŠnn”iµ=z“/B!„ÐÓìÎ:*m'Ád¹ "öZ¾¥¡&~\„`)/‚¯T!„BQ!k) ˜F–üœ‘ÎÓÓ#†ÚiÚ¸Íÿf¨oÐôênC!„zŠL éÕ õ îM]w;ZàN'"Šï¥R©N|_GSÝ´ñÌB!„Ðýì8¼à 6›MNÿ?…+Ë˯ññvIEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/objecttree.dia000066400000000000000000000067311416254111300235470ustar00rootroot00000000000000‹í]]oã6}Ÿ_a¸3 ¸ŒH‰úhš)f»èî»`Ð>…!ÛŒ£YòJò¤~Ùß¾”ä|ز¬O*Žs Ì$v(]™—<¼çR÷ÇŸþ\ù£o"н0¸S¢G"˜‡ /XÞŒûõ—ïíñOßý¸ðÜä¿eä®Fòˆ NßÝŒï’dýÃÕÕýý=ñ·±›„ñ½ ‰ÅÕÿ\ßw¯d£«ñÇw£Ñó,ÜÄM?Û}ê&IäÍ6‰îJÜŒgîüë2 7Ábœ·Úµ›‡~¾¹þÍø»Ûì5¾Úæjï<'νv—b ÷kù©5ùrœ6§^‹èð´«u{²I²]š”œ'ýÿY›]«X6 –¿ûd|—_Òs»ÐRdåFK/(âÈ{ãç7‚›1&owlG³ ‹>Ü“æp³aáüaá¢aá¼xº£$r½¤9 C_¸AŽšDÑ'ž»¾ìb§Ìê`Å­—$aÅõߺ~\Ç€üãGokê¹ËÈ[œvܽ%g¹÷ÉÝôOE·+?ûVÑÙ¿y±7óű«÷‚¤·Óoû9ýá·“ÞO‡wì ùÙJ§‡…-¸àͧ‡åÆ[ˆ¸¢›í·)9ÓÝ®ÙUÕ]?lW÷ÆäLÖ„ïnE´;ý/aG»¯v7Ö<³?œýGÌ“a¿ýû_£ïG?ûnœò@6$Õð7ãÏÚþ]9´CžIx…Û²ŸúŽF4ÊøD#ÔdüÐàâpQ†2›‚DÒ7Xú"rˆcæ8îZç9¬IlÚVøb5‡QP$=˜!eÃHÑÈÇiÊ Žá<uÀºÞò.)3‰©í½Zv@˜öèÒ­ç‹ÏYOüð‹üõ/GÉS°8‘Ó^]ÙB:èJ‰²ó»3y¬;/œšzÝ¢Íz‰8ž>þ!VJbí&^:$)‚z˜ÃjµO½ÚÕ4©;Ю¿)»s÷‘»à Ê`Ü[éŸÓù[MÇRCkwþÝmšJN,¦¾–Å´7ŒÄ].÷©w¯·*3aŸç”,„µ–†ÈqׯFØ_Å7CHÄŸ‰Z‚P.ìüémX‹ÓÏF·îÊó·7ãU„ñÚ‹ñ(N¶)’Ä&?ÅÏá&òDÔÿaœns¶}p ßÿ5ôŸg¾÷ß¶ÃÅ:ô·«0ZßyóVWtxA.fžRÉô×êK‰Ý”§>\ÅãWóá‰7w³;Óñj}9öñËévovƒHÃK)^I§«xæ=•LP#ö>ÔŒ~úèpÈ{Î:ì~ǫĥ½ösUpÏ;°2“JiÜá¢|³òã•ÑÛöžþ½$‚{"ìQ6î/C `ùÏÞàäýÞœÄç‹þ$‚ÜñqoÀÅQ½UQ+#SVìù^²='‚ÍjçÂÏã/ÍaŽ,ÂjÆ@[ܾÔk§ñ<<Þ[ZÃ' äGQ&ƒùÑߣp³ÎRÛ2Èâ…»{á\ÎÉè×=ŽFØÔuÔ}·ÒÓ‡tA/¸‘—¸Á\L˾¸§›ÈºÉ¥O´}ÍKi¢B$J/‹¡08h_Ð(ô¾{ù=–óà «šørCn‹|lN¯ïrmQ»¹zܯ^°(-Ã*ÁJéäÀ}óäMe׬X®¡ožeßìoyÔ;1ûÕùâR‰Y¿Æ˜˜˜˜aò11CßlÚ7"žGÞ:eo/ÒEÿÿMá §¾Õ%ħ(r·—º„è×8,!°„ÀKL~XB` ¾Ù´o†§³=TöN÷$BçĪ¡öªÁã‹•°TÀRá— W½&£%bµöÝD¨Ú½ôpþxv>}¶ÛL;ï@¶ˆÆ'T#*wS+E0ø5³ˆ%Ñ8ÑÕî:îhTÇòì&W¸ÉØ8Üiaõ¿Éø _÷Cö;¶c«ñ…m5îÁ$ì5Æ^cì5Æ^cì5Æ^cì5Æ^cì5V¸GrúmzÙÛ¯°Å[Œ•¹Oú Ú5½ÔMÆË§€…Á‹”z{^ÄàEð"U^” ôôµ&FVyQfæ"<í¢ñÓ.®.M‰d]•HN cÂt¢q¥OBæDw2ñkj*A-B5µÏAînÜYh’ýKÏÙ<ôGèС?B„þýú#ôGèС?Bì[Ì"ˆü*ó¡$}Ê}­Õªò Ì:~áECÈ'ì È'!!Ÿ¼eùDï*Ÿ0“,“¨¥R?‘8º5aŒ8¦Å¯u‹P‰jfZJ”Ì;E'lIÉ”J* ’ $H*T ©@R¤I’ $H*T ©(‘T$ƒÕJ*ì H*#ü–ƒÁF×`°ÎˆiLtNlÇrƒ%w2Ãr® pcbPbmQëƒ{0¯N0Ø’f9Ï_¦ÂÈ0äy_÷qá/á=¢Âˆ _XT¸;‚ #(Œ 0‚ #(Œ 0‚ Z¿¿÷Ò6/Ù¾ÿãb#Èe!"¬Òäw1Ñ€$‰=5á?ðŸKðŸx-ÄbXçÑœÎóú'ˆÂû!ýÆ4à7ð›s -Tæ®×"X RÙy8*•¡RYy•ÊsxçœJ4gÂl µ9 ÇHq Çr®u‡8ÎDgÄrç4t7¯NNƒ=dNƒ1Àn·çic²”5ä5 ¯y Èk@^ò×€¼ä5 ¯y Èk02~Ñ;ÝòmHØéiI™ ©,}é«Òyžv`À} 0©Â$ç݋՗’~7µBd‚Èt>"Ó´##Åãíëõ{_…AMÜX=è±Ù[=êqçVûÕ e ¥#W%X)kT;U¾BAÔìA5tÆ'Ô Ž®sµ’¨nçH¶­ót£·ž!Ûòs+–E»yv¨NtgÿÕ¿0ú¼6å‡ìÁÎF!ŒB…0 aÂ(„Q£F!ŒB…0 a´a4[q¡ª”(;/ ì¸ýÖ®‚²eÛ‡–ŠãpîåÃÝSÌœîbæVç*YŒ¶Å'&±L¦´L–Òs Êøµ|k:J‰c·E’»ifIûÊ[ÚÄÒ¹F«š:õRè¬ å ¹¸x{.Š×P2—׈,o’0 7‰ÂÔ©8~çˆ÷ÂKûæ>(¿k´Ž`QN`*‡·HÞ»A&¾ÕÆO¼µïÍKfóþgÚ( !Üå2K7JIJß\¨ff!::Po+°yù(úÛÃ_Fwn°ØE°“PÒ ùóé¯7ã‚"\8i_<ÇîÌs,bM¨N˜JŠc“O(#:¿ÖM¢qÉ&¨Ixû\„z§hZy#í$JŸ Ÿ]uí†R-ÀmÀm05Û ¡½8·¡û܆'ž#ÓÝÁæÁÁƒ#§‡P–¡œè ©ÕˆE3”<ö“rfHºÄTGŠÆ•6c”húIÚ“ša6i˜ò(zz„Ù ³è:Ð¥Ð#³ ;b/ÅŽ¨ö:èQŠÂè‘N‰cäôˆZgFN…ÒB‡ ÚIrÔÍ@##Ìm Gè@è@¯–é/FŽhn³‰Ü$¬ýD^s¿­éd@Ô´$CâY4I×mÿèãZ ©ÌÂò–©Î8­ß¬ÓÃA‘@‘0Ã"¡¡½8EÒ;P$þb‰u¦HùDžÖKP\¥Ã̬̀Lƒ™ Pº•UmPJ‘J,,oiœ7ÉKçfýfœØ((f8Ìp Hè@¯—"ñÉx1Фw¥H¹2V—\™ÚIHŽÆøµ|ç˜YÉ4M¦’!•XÞÒ®Pвvµ›9Dëf!&80$t t eH¬C¢/–£M^(ÒD× k?‘×gH)5Yδ,ˆd3j(§HEK¦…4)­l¨ñFMMº٠¢¢„yD èe’Ì.LÉz1¦Ô¹(:5³ªáœXŠcIfV_3p3¢Ô m%¦lx2­['ºY·™¤Q"˜&6Ll`Fè@—Bj¶ÍŸÚê˜QYY$Ú¹.’Itw”êp"“0;ÇÉ¢Gœ0 «Kú@ÛÂÖ+‰Ôƒ}gW)Zí½Xÿ,$+ýøPé×ô "¡  "¡  "¡  "¡  "¡  "¡ ÒÑX "5.ˆ”­¸PiÔlõ¥,R6pA¤A( ïóŸ/=bU:4l«€û¼I÷A=1µ=´ (›¼POì<|õÄú­'víÈHñxÀAûú£EýÞWaP7VzlöVzܹÕã~õ‚EhéÈU VÊÕN•g\û®TµºK¢¶Î9ë˜ò]Oµìµ¨qM´lŸŒhV[人hFÖQF­!•Q~²úWFŸÖäª(TQ¨¢PE¡ŠB…* Uª(TQ¨¢PE‡u~ï-¤m^²}ÿÇŪ£u Šªt ù]ÌD4 IbOMøüçü'^ ±ÐynýÐMt÷û¼~÷ †ÌʑӎiÀoà7o>Ç]¯Ep±ù8HÅA*Î%¦â\½ùô»óSplâè¼ÓC[j=Ç&¶Ã×ÌJÓ Ò‡šJ³ºÛvv›½u¢;û¯þSÜ(r·yRûð)}ƒ´¤5 ­i Hk@ZÒÖ€´¤5 ­i ØìÝÏfïlÅŰÙñqìV>:žyBä‘#D~ö!òü½ïnEôñ]þFþ[Js?¾û?m-¡×ŒPyTables-3.7.0/doc/source/usersguide/images/objecttree.pdf000066400000000000000000000531661416254111300235670ustar00rootroot00000000000000%PDF-1.5 %µí®û 3 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream xœ½YÛn7}×Wy‰ŒF ‡wMm(‚¤z‹‚@¶džn•åþûž¡–«XZÛÒÊ©[Ôh9Cž¹ÒÿtHðkq!^•¸¸î(Á¯'BËh¬·^ÜBö'~¯:Ÿ> %•8ïXñ—øÓøÑ¿%%#²AœM:dµTÑ +½r"¨ 5™$}Œb1ïćµáÚâN­ÔÉÔ*I¥MQ¹Ò•ïúr2åŸ,["ë¥1Ñ+:HèO‰DÒyýµ§zxTô¿v>uš'£ã#Ó×ËÅåƒ ñF¼˜apz5:[.1XŒFò›ÃàÅÑç>CÔ#i)¬¨Î:³Ù2ë8YÌnæèÞ~-Xt|R‰^1t×È ŽŽF¼çN3VkÕËáé˜;©~u>º>c_\Η—³é Ãßþqýë胳,e»S^B#è=à­õ †‹Åƒ»ÝV0ƒ ÙØò˜çåܱ¹·+•É àQ­2Ô~åÓË ­ÕQØQŠƒszOŸ–ÊAùv™s4£Û2–ãÐ]VÉû¤o‹sôãÎÑíœãQd)Â9@Ô»gqNQ™4œk•û÷6O€ÂÞAIˆ)˜¦*¾‹wô¦w܆wôãÞi oˆúÄmˆêPZk‰;ßµN‡éuÑ™KZÚàƒ 6*8äëóZ¥€?½¼3Ћú°å9ÜOn&§£E5çrʸ’ߘë)×s63:‰‡³ö¥Ù¬YEÿt1»Í\y{ wµÉ£THê(m‘ŒÒF‹`mr8Ÿ”óöM2 tœK"ª$‘NÚ)}8¨GÖ*m” ™[tÁ-RÈ.£•LZ[w&£%é¶rU×üó6ÇÅÇÙ-žÏÝí„“C¦}†–ͧ$‡@µù¶åæu@í‹Î6’NÐ2øŸ™HÕ²ª9-oAXmÀ˜K0˜ÃªK¥ÒkÉœ¹¨l‘È)G)(fòÈ «mhê­Í±Pú[ÕU[#“(»ñY¶î'( o¢}÷¼:¬uYûNK¶8÷àÌ#köh˜Q$@.Ÿ<Ç+I¤&I²¹ü;âolíÑ@ÍÍ>€ÆÂ'QK|⤅ڈï<³Þ-I ¶ Ù”Ë)zxžÉʇVçŒA¿`gKPÈâø ŒâŒ’”Û’Âzž¶Rfpޝ «%õ#¯=z‹ Ñ ’ªV?m¦LHˆSÌÔT–@) ã ’šdtž¶T€Çc$Ȥ†>  ˆŠ3t ’ˆÈQñt ”) ‡û+;Y€ÈB*VJÑ.·(¥ ç»¸Ç‚étÊhMÄ=IŽ_‡†Ä'¶m Æi®ìfÏfÈøDî•à¶gÖJ%`ðàcEåþí˜ïS’u(] Ñ2åPLöíÆ›çr»tã–gÅjë ÞóÆP¶×[×`É6çÌíŨ%ÊP´Ï@ôkÈQkl{žïP]£¶`5¤äÊ+Ôÿæë‡hþ×ñl¸4|û\¿€I 6‘¹ÒwT/š·3¡â½)®žèT]¥b]žƒé„?2:}Û5àËÚ¥ˆFǺ˜TÜêz§[Ò»õFP1(¹ûÙ«R|¿ƒf«“Ú“à¡„µæõ'«ØsšÐìvÜkœN¥SVªVJÑ/ý«•°¶ÇwWG½\LÝß/Çy\S1­2ÀÆJà®\u•Ç7ô' ~tuï×[Ý^ º|-Øã{ÁJ!Ь&|z^*«•¦•Þ„ƒT÷.ßæóÇ% ºoùY'¾ ^-‡fU 5‘O®:ÉçKÌš¯· ónæü±ÞD”.Q` kNxPm+¢.‚õ"‡€C…Ì“ó};OZý/ûG¤c·ˆëÙÞQDIè%Ôk°@x{Ï*Ú[Á eX[â./ª:(ÑcémÈ Á{  ^Â×YDºqÉú%ó¿G4Zxãí]hX²þ~ÉH¬ÿÙ ¢ endstream endobj 4 0 obj 1708 endobj 2 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> /Font << /f-0-0 5 0 R /f-1-1 6 0 R /f-1-0 7 0 R >> >> endobj 8 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 1020.472473 1190.551147 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> /Resources 2 0 R >> endobj 9 0 obj << /Length 10 0 R /Filter /FlateDecode /Length1 10216 >> stream xœåzy|U¶ÿ½uêVïÝUîìétÒt–…‚,MXÃ&ÛH@`‚D4€"" “ Œ ˜d42ˆ^1‚I؇Aˆ"è O\À–ÇÏaQè\Þ©ê—™÷~üþxŸÏûuqnÝ[˽gýžs+J1’Ä=­hêÜÏxõ,!1¿'D˜8í™§Ý—«tfBâ>Çq挹=,<~—Œomô‰…3ί÷«}B¢ÏΜ>µðö·Qíñþ>¼Öm&^Ð7Ù8¾Žãv3‹ž~öÚ‡í.’ã£OÌ™6•‹¶àç''Ц>;—S qã|Ä=÷©és{9 Ãî BtqD 3y¥8“mAnu$q©”¨£žêÙ2A$-g]ˆ|&p&¡$)Þ$%i¦H‚ó .ø%¯ÔYøþ)©¾C(É'OŠ ÅuÄDRýòšÖ°×t`bE¯5JfùL¯@°—6ŸÖɤJ’3Iñ(IÙIа¾Ëûoãè>qP«vjé>œ·™1N\L"Hg´Á ¨T®4™© ‘¡ƒdä¯|Aœ:£×¥ë%*''÷(]S<É’;YÎ>4Ë)Æí™õäÒ²††.Ûæ½õ¦Pß6T¨¯za÷[m+Å‚mÓ.¢Y~÷ уë™H2ÛßѨ#±ns´MGê£uåö¤2÷þ„òvMöêh c¬(Ø·(9ûtDZ‚ŸOÉÉAFZ.]^ÈWå«JŽ’cÏQì9™þ„ÌÄLwfRfr ©¡5B±Æ´9²&ª&º&¦&Ö:™ªì:"³|ݺgwW<ÙV¼’ݵÛ4;]½§ËÖĈrQ¡©÷kZôĆtÏžÞ.yëÄÜ¢+ÖM9ððŒÆü²C½SÜBÖ“s§Ï=½·Ãð¶¥[ ûÞæÆƒ +vëÚš:fŒo]ÈfE¨Û9(k2ù“?5Ún3ˆ:’'éœær74ÅŒ‘uD±éGH#•¶‘ñÑ#bzäëÃêÌã†Õ)ãÎßCbïè1!Ø+PBÕh^Ÿ*t"Ó?8SÌd™R¦.SŸiÈ4fšúFöêÝ7¦ol߸¾ñ}úºJ D,a%R‰®D_b(1–˜*"+¢*¢+b*b+â*â+*\:™jÒÇПö×¶$?6tåœmÙƒF=°-gh^Îo$Më;|:\2ð¿Ð¶@XúݼÅ_¶ K¯ÍUÏbAA¯¾ƒT»/F»ç£.œ$ž ô§H Æ2Ã)²ýÍBß‹)h²T'€/"%’o—‡$ Íƒ”E—Ñõ.©—¯_E{£ :“ÂÖt¢%ÝD‘I–Ï® ™RÌ~±Gþ¢Çšæwø9ê¾öÑ­qméòí²0e¢´ûhœÝ:ÑAÍÔÏÿ~hó›u›ˆ@1Þ…/1ªÕ˜ó[„Íd»¸Yb4A$z5²|h„ÀuŒ®,U-Ї^âOLå§x?²{+Ê*¡¬©d¾ßo1 VST¢KotÆ(W¢+7Ám4¹E')£DG™ó@t¹"–{›”êö FSbœŽ<'Yót’#y`{ùz *á’ª„{jù«ò«ö(Íí‡Í×ÉÖïÐtZ;!™:ÓþžH]ÔéÐÏS³]hÁnÙ]3„tŠNÖ„¬»dñ¤ÝCW­ |8¶~Ö£ûÆ-ZqC?ðÕ?œ{âV1gWzúè±Ã†z¬±›–lmôxš²³§M(é"X׿öoIš¬ËÐÇõìÄ.þh+ÓÛ ž(ô ¾Þ¨7ã$Ùnu âZ‚½Z|Zè®÷jQ=8'ŒPŽÈ¨SÄRÍ ›ù”)óO^?ÆÿƒË»™@~öò‡@Æ®#ª‡?j,×Óz[¬/ÃA}³Ý¤ ‚M”¬Ä®a`KK’5'ñÅ´ðuHh6r†ñãòy3½ñöëüÊĉ ø²âNyóéMK¤×/‡p²õàEýÇ ¬¹Ìr@&e1"ËA.74Aul„ÝL¤A±èÀ¾{Fà×UäËôÞCü$tÍûh§æÑ;ôcxŸ§^*ŽÞ82oÝä?ïmÜ^°¡_Â}êÀ£GÇÎïõëùùßN^| òÂ|Ú}I®'›š@DùŒ¦u æUˆ§]4p×K¨«_×K/ÿëz)ð/ë%åÿR/I޶-ZÁDIê( ý<ôö'‹º„Ø*R&¿à¨²U¤ÔR­«u‹`ÍbL&r¢K¶[Ð&šqT—¹yUæh­P,©Ë§ƒü<¨LéN ×ÛZ:ïü •ù·9iïÔ7Þi|côÆA¬µ–¯•müê•¿ævàëR·¹f§×‹¼Í@ÞF±kÈ[/RL,æôتøÈ*°¼mÞ)W*¡Úã2“n>)Y ¨Ì… ©šòŒŠAÎÿlÌètS‹´$qÔèãÏðK¼…úi”í£F×þöСý‡ûOïð ݸxØZEçÐÙ´:»ÛÉyü$ÿ+ºwkb]»õ¾Å‰S~e¯j#=hVm5¦Ù+ʾ°½"ÐH¡PW“|¨Tlnx|îó«÷ìéòæ“Û·ÑmªÁTs ïlÞ6µðbÛšøCâ8ôic;Ëψ#¶Üê(×7Y«i3LPì¦ÁQ˜…ŸE7šê~x{ïçžp\ÿ"Þ`ª.<þõ7ïϨ¦×V†b}åš¶c’±bìx~˜_ÁÐ>6žÞ‡{ˆ§ù¨ƒIh§0ΪlP嬴UGŸ¹›ä³gG«¶ùÎÝ6d"âg}XµrcõÊ•ÕWž¾ÕÖvóV°í–p™æÑXþßÍøW4†æñE¼„þŽ–Ò•´„—h6 LjŸÃ~˜Ø± É;mFJ&ûËMÑ”Hi‚ ñb ‹7ÄcM‰æ’B;)¬ƒÔAçÑw6çÝXŽ”£ËÒ÷2Ô癆šÒçÆ›&˜gÁ ñ16Ë0ÝôŒ°ˆ=«Îð”)ÍfŒ!'M4&; X¡ëebœ`œ%<Æf ŰP,±çŒÅÆèÉt²’GÕÔc¢žúæš?nn®ç·ëÞÞY‡È!·­ VÝ©f´m Ùh¿(|O3#Ñ~ü‰l–@DДԬ}"lœ,'x"èÍà¡ÓhÆI¾”‡Þ„öM½g_j_5éI´h7¡}ƒßOöÍŠ´;‚ÎÓÍžÝU “4ÛÖ •%{€÷ºp‘÷ |Cq‰V}¬ík”qÞôpÆzó!¨g a¬™†Tß žÕŸÁ€–GQw ÅOøøòëïðOèZùÜï69?¾Þ·ØïåP®¾ÏÕÖ&ú2h‘<c:¼Wñù~™«ïƒG¸Œ¢÷Ê iÀ¤¨óå+ïOßÈm¥Ë——•-_^ §„þ?ÖŒO‘=ªÐîã¹é£sŸži=N•óÊ)a>0¯?¨æ½X…û“îàÒ‘î³#!, –ùî×Pj‰y zó#4'ØLsøÄõ;ŵµâÒF¬ÂyÇà¼z¬ÚûR•¡”TÙ$b3èÀgî¢sÉ™v %”Ð>DÛyÝÝ9%É£DhøÝÙÝEî¾ì.)(¤Ñ|¯å§¶Þ¡Cè°;w° ¾ž—ðçùK¸Ê’knÆ®ŽÄû­’*‹H{b6ëÉÔ NK ÷Eð(Iâ<žvœwBÎo·²N¡b&âíRŒk;©ñ´DY-jh›ƒ9 £[‰²cÅe“‰zRd‹Õb‹²Z-¹r„…X åºfÅz0B‘m£Žèìz{žu`„ú¡A‰ÒÊ5ääÜÛcýStk{.b`÷°S:Ù+ÊÏÈ‚^ÑÛcÌ1–kŠ­‡’§äÙ'XŒ“Éd*镆 ¤î4Ë@±¦N<-9}õ¤õE£' æ6Óátp39?ÿ¡çÈ‘§´ˆ#ƒëቼ¨¯í¨/É?â¥ÁKð2Q‡$yÅ ^0ꑨ×Fâ€1—€®ŠÖA©ô:I…/b`F“üi aXÆÝ2,\èßÏzš”:Ф4©ÀCjD0‹¤_AW zUJHR‘’À#ÌlE<ÿË zìÔì¶Oœbž6vÜîD—óŪí Qž"´D¦û;`,¡,QT`ê 0/Kˆ¿DÊ€dC®™˜GêÐ>×—|ÿÒ.Èfª ¢¬MJõ E&xq.BU9õî§é ýü©ïÞÆÁö;5Z-“+v×öˆi‚ê‰."¸@Ôv"êV¢%SkaSðê~Öúc¾sùÇú­‘âw@•(T±R©2è=’ ˆ‡šðå–PùŽž„éΛ¤°lo–ÔJ{òtÔû´[ðh­8xÃÐÛ­µ!ŸÞ€óz0_Ç“Ñþ$Þ‹ÒGÇÄBTœW’X®¬¼a©rTФJ ²Q FWT² íÔÒ/pà@è;‘ºàõ÷ô£ù)û.Ê7!Ù«bA7Ò½ —~ÚG/ÑéÐ5é|aGp~#Î.´®äá£s=2õ<5M(ìÑZ[[{ˆ¦÷YTõà’rûŸèâ»ünÁ§û}¥búIÄôˆ‘:âòÛÄzi·POÞÁýôV7tAmCÔ>š$©›þ$ÝvíÇ=PÅÁ UfA†Z¡IÓ¿}7EõãN.T„kšÇðšÂ›·P™ˆk ÷Ö„zº›©k’Þ’ nÿB†Ðšï* ƒÀó¯]“?ü½B+zŽ&í°ºöècªŒ‰U²ñ-‘–‘ĪÈJ¹Ú›ì")–dO#½8e x)p¿†ý2´ûɤá²U«Û0iyÜB¨žýù–.ô}aÄÙµ³Ï~wý3üžz¨cèzþÙÒõë—®(-e»özSùþuáãüÖ?¾ç7é|º–.¢k۞ػeËÞ¿üyGæê¾µ;òÝž Fÿˆ¨rË [-URb™{k|•§Rªv¾Ù!2‚€#Æ•"» 9ÑaHì€þr&¶@HupùW?‰¿Ø„`ê×Núu›ø·7ýèчٺsç†Ë6­]1¡iæÂwóÎQ¶ S¼ô×oSÚËîZ¹æùê­‹Šæ-nß~¯Û}þÅ[B86Cý.ªá˜…äú©,ˆSÄ)“® ݦÔ@ÍFâÒ‹’ÍÜÎ*ƒg´OžjÌ«½{P%†«*M‚,ué}ì.ðôã¤+GŽk[É‚ßÂÁ¬-|-ܯ®]¡Ö¸vF×,D0J^yEr±\p‚è¬2à«Ô$2%Ò ocŒOTº8Œ.s&¦ÙU—JŽÖh• jמcÿE¸÷IÎE£"<Žè¯r©é«ƒ‚…FÎsü“#O{ï8­¹I¥×ÚþöÙÚª^ÞsÌÙÊgÒ…¯Mn[ÅZÿþáúFaBÛÕçW,]Þ‹¦j{Ñ®X™ÈÔ¬¯—h9–TÒA£¡C—,6Ó/¿¾]ò©[vÔ¡ê´hÚzÿ¯êWA1õÔ´t)/nP?¾½ù¶ä¨ýè´Š`œªÙ°ƒBVQ›1HGâÙ…Õ+)&wéX:•>‹õÿ„çîw¦»§{{RòÝ»êßHH C ðþ’ðý¼Ÿsÿþý£¸Æ§t#ÝD_Á£&|Æã(=Š÷uHÖûOw"qø¼úV¶$Ûö¤IÇŠÃIÒÂOIZk ¼Z›¬µ‘HI?[=Fk3‘<¿àª#Rg¤X$@ØQBFº ^Ôo; ^M$¸ÙÁl©þɈµ•w`)èy„´ûoåýÿõ×@ŽkTK×ây†ve¥PŒ:¼w4 ×dzú\=NWѽØßJ‚Ø.#ßS#¡Ý±×„ïæ‹Ixµ‚lÒÞ¬€¯É|ØGNc }{_ÓÀwéi’D/àl«~ZšpÔŒíbh‚|šH‹Èª:ÿb\s)ð,ŒÁ™?OáÕÈJ<Ö‘-döUΖ!ÿŸ‘]¤Œ\'„Ëd"ö÷’CÈG?ÕÖ ­X? µBoa>wgÛH6Òe¤•Ì 5â“™šî¶à»›p•GÈ&ÖÊ6¨úÀs+â¿ § RƒäÐyP Uo[é>Úcò4¾¿˜ŒƒIð$œ§ËE¸.“ , È,r’µ"jTè<¤BšAŠÚ±X•OX ÐZrç|~Àqr¶I“˜]Â6’D™gàµMZ[j%™|·QïkN‡ˆƒ /ÞY,ÇZf3¾™Šš!ddãêsÈb¶&tZ<ÒØ¨Äù5mÐ,¡7Ù$ÌÀ\!ÜDmΤ;®‘À®’åt—úÁR·„ÌS7²[Ú“În¹NðæÖùGç»NHJëü«¡[Ö¹ëȨ:ËBwÃÝ»£òÅ86¡ŽÅ×W_'z=ÿ«›Ó:•ïn Q„§X0/ŽÍÇ®:ÂËx}àힺjó⿼‚:÷´™îÕòjOÏÕòôžib ãuû¤¾ü·¶^7H¢^sùzôJ»w¾³º²&ýop¨ÞÔ¾Z¨­®ˆ'¨yëÎê»Ö¾þÓχ™‰”ÔŒ´©i±xŠ^V=©iŽgáygèžú<ƒçUHï!í ½¯½£ÎS¡Z:ÜoBšš‹Ö†Ïë&a¿&<ß±ð\a~¼H…á9n"mÀçOЧÏÃ|® ßW×jË3 }›j§ZÍH»~‘x‰–ô ‚ø(Ô¢«nR ª ûúm„ÜHêù ï¤Ï 1ýé:¦­R„}Ô¯¥+R9&޳„Øp-n³l-„Èã¶¢à îQì¸npI^‹Ày=pm'b¿×u⺑ÈO®µžèõÿhVò fÌFc®ÁZT«Veã©‚YBÜ#”PçÎõ“Y¿xê$•°-ÑþÇ~„Öâî[EëËZkCjÕú–ßfý¼ÔB–àÈŒ) ÷cć­Q›Ï =¥GÀªÓú’ö Óú¢v´+‚v…ú'pàÚ–@÷}ðc#ü°nÝ,g·8Ü: Þ¼1Ý,‡›%âë)ìÆ¸á¯§À?¾Ï`ÿ¸ ßgÀÿápÃøàª¾«„²àh¸{ÊWüv0\¹\È®TÂåBø†Ã×_ű¯9|_r¸ô8|Ááßáâç1ìâmø<.TÂß9|ÆáÓóNö)‡óN8W Ÿœu²O8œ]cbgðñø¨'´â µ'œápúC#;ÍáC#œâð7'W+ìd<ü5>àp¢Ž—yÙqïs8¶Žr8Âá0‡C-¬…C3‡ƒÞãpç;à€ýfhz·‘5qxwßdön#¼["îkô²}“aŸ_lôÂ^{*¡¡¢ÛÍ¡Oõ·áœk‡· ag!üÅ uvø7;¸¿ þÌa;‡·ìPËáÍmVö¦¶Yá­ {£=lUàõ-iìõ%°% þÄa3‡×8Ô¼Ãj áÕWdöj ¼"ð‰Ã˸ÈË6Z zC:«æ°!ªpýªJ¨|©‘Urx }ë¥Fx©D\ÿ¢—­Ÿ ëýâ:à°ÇkáE/T 2*úÁ (í Xc‚r¼P^e¨´2/¬V`‡R+9¬X®°–+ð{Ë8<¯ä²çÇÂR%ÏBñï–°b¿[K\ð‡ÅVXÄa‡g8ÌÚÌæÛ`~%þsâÓfxú€8ÏóüâSžä0—ÜÙcÙœJ˜]ÔžÍ Eíá û`‡Ç|0ó6<Ú38LçPÈaÚ#.6Ã#Df¸`*‡¿å0e¢‰M±ÂäB˜tÆÁØhôè|Œçð‡ßÄŰ߸`‡±Æp½FqxÐ#9Œ il‡á0¬= Í‹fC»C^;Ë‹†!£Ùƒq4¸áhP# Œ†xa@w蟫°þvèß øý1·Ÿå*Û õó[Y?ôk päïkf~+øh Žú𠬝ú6P¿¿Pìá7²Ðû6ôâð@{èÉ!œS=ºÄ²à;‡niÖCö0èš˺ƒ,]ðv—XÈŒ… ìeDCº!’¥7BZç–怴A]¶³¬°ÎÐYe·RìÔÑË:qèˆOvôB¡'ëÀ¡=‡T)6ðFæ2ï@hg‡d›%sHr§±¤%àNƒÄaà•]8Ä£nã9Ä¡Uâb –C ‡hQ8CÔ ˆt¦±È\p:dæL‡ ø\„ìø¾ƒ‚’+¹ ã ²rHw6«™Ùl` éÎj12«¬!ÝYPw#XPw»D³̪ouMŒ(‰‘ƒ!ô2è8H8µÄ9P8¸ÅBzEhí@háò5´ÓÿžùŸfàÿñ—@þP£ endstream endobj 10 0 obj 7150 endobj 11 0 obj << /Length 12 0 R /Filter /FlateDecode >> stream xœ]S¹ŽÛ0ìù,7ÅB2Ï5 6‹ˆ“IÊ+ ¦Z.ü÷áp …ÍÑp8ïàc÷zürÌó&»e §´Éiα¤Ûr/!ÉsºÌY씌sØÞ¿Ú¸Ž«èêáÓã¶¥ë1O‹Ùý¬›·­<äÓ縜Ó'!¥ì¾—˜Êœ/òé÷ë‰Ôé¾®Ò5åMöâp1MÕîë¸~¯Ivíðó1Öýy{<×cÿ¿k’ª}ï˜RXbº­cHeÌ—$†¾?Èaš"åøßžÙóÈy ocƒ:Wiß×E .5\—ŠwÄ;àH-±­X ×¥úôôé+öºáºTlˆ °"VðÙÓgì‰=<K#–¢^5ýDÍLÞ5ž98äà×!®b-ªÕòBþþÌ_#øq=yÞ³Z ãĵÄXÑSÁÓÓÇÃGò˜5ªV#yÞ±'=1Ôh yÞÛÖ7j|Óð^ îÅ;ò±˜›Bnš f¯4zey×w­˜³BΖ=·è¹¦§†§f?5ú©™n¹ÑÓ¶þS_ ÛûTaìð>>æ9ÜK©£ÜQ›aLïœÓÇ;[—§Úï/Õ‘ã¾ endstream endobj 12 0 obj 449 endobj 13 0 obj << /Type /FontDescriptor /FontName /SGIDRT+DejaVuSansMono /FontFamily (DejaVu Sans Mono) /Flags 32 /FontBBox [ -557 -374 717 1041 ] /ItalicAngle 0 /Ascent 928 /Descent -235 /CapHeight 1041 /StemV 80 /StemH 80 /FontFile2 9 0 R >> endobj 5 0 obj << /Type /Font /Subtype /TrueType /BaseFont /SGIDRT+DejaVuSansMono /FirstChar 32 /LastChar 121 /FontDescriptor 13 0 R /Encoding /WinAnsiEncoding /Widths [ 602 0 602 0 0 0 0 602 602 602 0 602 602 0 602 602 0 602 602 602 602 602 602 0 0 0 602 0 0 602 0 0 0 602 0 0 602 0 0 602 0 602 0 0 0 0 0 602 0 0 602 0 602 0 0 0 0 0 0 602 0 602 0 602 0 602 602 602 602 602 602 602 602 602 602 0 602 602 602 602 602 0 602 602 602 602 602 602 0 602 ] /ToUnicode 11 0 R >> endobj 14 0 obj << /Length 15 0 R /Filter /FlateDecode /Length1 8260 >> stream xœåY{xE¶¯ªÓÕ3Óóê™Ì#“Éc&a‚á•!uêw~çtB(!D!åH`Òô %'¦¬ºÇEBؽ“þ83Ðo{W#!®‘ØÎ/.™2},ûŽyªð©S˜SüN 3hu|fÐÔÉŠÈ7Þ#„xq>é1;ložÃöØn7uúÌGŸxЬµ×c{êMš@ÈĪØ~`ú„GKäñrglÏÃv ä‘É%ó&¬ùÛ+ 1¼N)R1¯Ai $©žH4'Ê4õ]jäÏ0‰¤745w%jSsSsFŒ#èÁb‰´ÌËYQa°]½ôˆœ†ÏªÉÌœ² ×J|¬eÔJTImÁÇÓ›3¦<á%¼œK´0ÙH‹E¥ìú¹Y{–‘‚Öo¸"Í%.â'ÃAâþ’š›øj ý"vµs¿ey¼ßÍŒn+ĬöÞñ¸î忇3…;s¹ùŒ*._P/ädlMOèŸÀh!ui·ÌÙM ‡J‚»§¦$Ë®´ì°®|þáó¥e¢LoÓ\šL´¯X<;2õq•u+ž?à ќѕfQ/uÒ^bÏÒâÒY¶ÉZÜú\Ì/3ñ’ÌpŒ¼ÉI6YV:—Çš:Û³¡³»c,J§í[“L½6Ÿ7Fõ¥ûúû8Š–ÌÜ.g·LgVw7EÊk÷ì©Ý¼gÏf:•.SÅr±BL¡+¤ã¢¥ùœh¡Ò¹f*Q¯(ËD…(¢+éýôt¥.ÓFÔß}¨{3‰'=Ã~¹ÚrØNªÝËíûýkcû`€3k1YãÔ”-3ó²v*gš5ù¾½±uTb$•Êì‘¥«Èíò@?TŸ&VJrj{7sæÌ˜1wîÜ[g¢Š¸rhÖÖ¢’·¾ªjý†Õ«7°£ Å{â:¦÷ '®Aq4ÙZëÅfb'}Ã>3#†ÕÜvŠlá_YÖªÜ([©ÕBTUméӜ٣©.ÙGNŽGÄQâ(w´Ç%w¤Yx°YŽ6}Ù¸¹_ñH LRäÚÖÓçÏ[(qRÃ#bJÊBLùHv8ޤM’í µI©vHÕÞýŽåq†Vâê§¶4dÞ<, E?¢BÆûËüŠ]h›:þJ™)køÒQ¯¿^‘ÿr8ÿ­ß‹#â :š¦¼)õ'33Þ~õÕ·3»ŠII4›º1e'µÙˆŸ¨á«H ™޳q£ªt‹±š(F³‰™$bU¶|—Ú’[kÏÏ­UóÇæÖ:´Â™?¶`;±‘pÏ1}Zú4485E¥74_F¥iŠ¢áØþ¤ C0€]µ;òXä¹#,¦B‚;Au9\žÞXINÍr¤8º9X7ú°x¡ß¸m¢±is]_%ö´º3»•ln¢'(¡ýt™kÅ%d'±’¤°ÔÖÚ T2³CRmj jOq•Aƒ©,«»3;(kI|±âÅ¥ËiZyy™¸ôÝyâýðÇó¢÷©S¢W›>ê ‘ð£ÄD:†ÒnyÛM95€‡€¢aµ©·y­=ã]Õœg.1C´¥zÉ~ý6åú QÆŠSâ{qêúBÍ~á"I ÛdD›´–qj•ˆjÔ¦ÑÒe˜LS‰©Ü$ýº®NN70U%»48¡¬5⻪ë!>l%5òZ›Õ 8d6µ !tS 1=4Ënô8Ý.f¨)Ç‹¦-_úâ qéýøÔ)º÷ü¢ÿ‰bàO¿ð&4#NˆSÃf’ v›_²í7m1(²•U§†‡˜|€±uwÏ1 ºôg.Ÿiipä fÐ6˜j¢»5»õx¡9½0ýÙeÚ.o)uv¸ Ò=îͯ]o‘"Ûœ ü&ç¶Ç÷j<Öy¬ÚIª-û5` ܽÿ‰ÇÞÍŽéï…ݰ ¤‹[)Œ}8»´tö¬yófÑ ,vˆ¯Å_Åv:æ¾Y]ý¦–)ûD3¦}´'uaêÙ¦äTENM ¹á/ÎcQMÄV5n{¼g¨›L+ay|¢ß×ÕÒ¤Çt–;&j€Ò5èâaÚ‹ºhûeÔO¢Hh²tÓb%¯nËmÔ‹¶œâòèb±ÿ5ñœIëéã˨cNéOó¾ýùâ©>SÒႇ† ¡Õt2FWõ̆­?œ®¢³`šÌU(³,E_z‡ýv ÔrÂuØ»\¥[œ2#1v«c(C§ûªLÝW¥ŸÉ¼|¦Í=UÄ_®q zUÄ ªÓ«;,ײi’S«èTfsx†v))×°x×;Ó÷ [ÙÆ’±â|—…³ý)©W°´kUk&^Ð}€ÍïÃóTÑô'x«Éa›£š6î·-§…- qÌaR »Å ¨gÎ šÕ3·¸_ptƒÿcn9næÔ0u‹ ¨A—0]Úë[e¥æÙšOÐ|CÖ¤½(›BBäÕp“L|Ifò™÷¼ÚvØ8˜t ~u ’²…¤x!Öj²šû&ÕÕ;m@íi§ëïr‹î³®\Èi;èðÈôö}}ƒýÛ߸#X( >x08?0?XÒþùÀóÁW¯ß ¼ÜØtg&f$ L 'Ý“˜—4)1’ôdbyÒÒÄÅIk«’êk“ÔÂ[°Ý—†nžDV÷vÁnm®Ñà@Å$¶¦äá±wM~–N/Ûº`Óqj§ÉŸ?õçÿnÆßfÒtj¥WGŽtÇ’éi ¯/XW\x`ÍÞmñ¿Õ¥ uÄ'ü cÞ'†p'âÇN{„ÚˆKf‹l œK q‰%PƵ0;ÎJ IJ)LåDYmü ÈÕn3›ŒEÏ¡ªM¹µJ¾î*rk]º¿ š¿hðêQ·CSZNFî=•Ÿ7¨Æ™ëÙ›c0ž“LÃ5x`LÅ`a–Ç&³™ì)VÎ^b‹YÛÊjõ´S;Ìþ S¬DMÀ9—¹=ÔÍݲÛÐŽ·“Û²Hí =ä†[Ž}0LGÀi0,Oa“å§ØSü)ùiëÓ¶—Y¯WXWØÖ³7à iƒmƒýZÛ¤ZS­òy»u»m;`=`ûÔ~Œe9èÔb‚&äm‹úS-¦Ñm‹´¡;_9T2ö¯"E®ÙÕk=v,;×+7I×;Æ¿\BŸ£ÛÂnØ$±M|‘l2cå8 ±œš‘Ï›Ú"WÔšú\}Á‹4_¬¥céƒ4ÿZ3U`ï0*»–%®èq˜‚Ñs{\Û€5“TI‚,s%Á¨ zLZ%H@1@%Á¤H„‰l2J‹d¦˜ŒcZ!UðeÆJÓ»Æí¦d/ÝËöMÇÉ1zŒ75~eJ×BŽ ©ž@r}ÝxQÊÒè–&J¯¯§+RU\äG£YˆÝÝWC{°Is½nC/”ê>èiò­v&`8kÚoÝ’RíüІT ±ÚÂîÀ¹wÚM~иA h‘ÊÄaGuˆtÐøL³a’ÕhŽ1+¨¹Œimt ·º²u#êf5ŠËÔÒ8«n*ÞõÓ&Ý·ulmMóCóQ2oÞ®‰ãèÀè5zû¸IëZâ’ø&¤ÞY•5 ×TT®®YVQƒ[ƒqÍ×èÏbÉsá4¢³—zb5ËñXb[l7ot®”ÈÛ,Ö͉ÍèqS¿ |êÉÜZ«ææAwó-Mš«päü‚C-ÎÓßvÞÖª¶v*óvðtð›—Çz½ÞØd’ìMŽEÓófÅ #øïX»~$ˆ`Úþ•¹]²ÝÉ–\ûˆ‡Y4ëÞc=.£š6?õ‹³ÔOO˜?hZù#é°Ž›?ìóͺ!Öùý:Ö»a„¥nšº‡ô䯛TdÓYˆk,¤GÈefк°é×XKááÀï0U‹ß®Ý)ÕékÃïÓäF }<e~Îò‚í%ã+–Ŷul¬“6ð׌LëÌoXÞ¥Ûe´dËÇ´öKŸÊGÙqøBúŠŸ5þÝô­ù{ËXgýTûÑ ™‡Ñqõ;iGÌãÄq1gg½˜#EZZ@º.±–kUÀZÚ2Æõ2AlÑš†3ýq,Þ—àñ¢ x½žÏ£Ä áÊ›,‹¼Š'&ÔxŸL$+Fý^Õdð˜!¡-ôGÄ{sm‡£@Ž3ç–óᚌI®‹ÇX/œäõûââüþ¸øîžÁîÁžÑîÑž¼ÄÉîÉžH¢½êFÞ‘ú® ÷‰Ô Ù×s¦M›³F”±‘´=yañ¨ÒðQünöÃ÷Aÿ{§ˆâÊuüRùøØK»:;ˈ:£D'.D+Æ~x·‰Ý‰÷DŒÀÐIi¥÷Ð ôQ:Ÿ¾È>f'©Œ@¯ÀÆ`rk«ö÷REï¦/½1ƒã9¿ŒÿöEñ'i%]IWaªº‘>Æ´îûoæãÇÚ?õÄÏ¿|ƒë—š wò襳÷7Ÿw  ±:!n¢ýEŒërkÆÄDÓš8±c‹Ùù/¥ùza8Ó£-ýë}ѧD!I ÍH1öj÷4Ÿ4ãøDœ¹@z’æã}º„᎗I¸£ÝÈDò0ÖBR ­'øôºˆã÷j³õik]áÑ‹<‡åiºÔWª“Hu8c–T,- µXæ°#ÒJi®tãøM2:RËú×v%ASH%«¤ƒ0 Äɇºüý¥½ù~€%GiÎÜHf3…~B/a\@ëð©+ä MÂr)½@¿C‰—“#PÀRI^ NlÕ“F”û,¹DfH¸*yEÚ"§ÉØOÈýÚ'I€Îü(¦‹d=¹5sã磲Ë”ŠÙUÒLÿÄÖ±«4…2LNš„Ú¼¥ˆô‰ô Ž¢v(ƒn°§ÍàGi%JqZ.¦Èrzš‹ïif±m¸Ç]äî ßÎÆ±¹¬’œ¢›èûTÃò“t“1L”ü¤R®” ÈM7äkD}äéúxŽ<'w%W$™\„‘4"­×4FBüC `ƒ†²“TІ?áNd“¹ºE~J ÿ°-á,£œ@*¤ö° eg¬ô¦ÞèÒÈr`"Y©§¥tYJ¶‘šQCê{†6oH:ÔZ^T¾« °oL°s§jTC –äÕZç¶µ¶æH~>¦–Ç×BÈX+…RNÿÖàéÎró ÛèmƒÝXvpdvÞS€U­…ÝØ?x>¦½µ–‡ð7'~XOjùlR¯ÍÁz \¹e8w έ#UÚ|^L x6ññ%ä;«Þg%»‰„Q©™ŒÁ>m°,Ç>3UH*öi#€eû´ &ýI£^P#Z©=!׽܅ßCe}_\/%}è;bzÕKn-…Ö~ ´\ëÄ[\ëQ?_Ê.…«Cá§(\ð£€Ëþs\ð. ø!.8߬ðóšhKç¾Wø¹Lø^¿GáoK<üo¾‹Â·Q8‹³ÎøFÀ¿ 8-àß|-à¯Q8u2–Ÿ*‚“±p¢*‘Ÿ(‚¯Ž‡øWQ8‚/„ø—Qøâ˜‹ácGU~ÌGUhúÜÌ›ð¹>ßEá®$‡_²ðÃ)pè/.~(þÒèäqA£âðÁ8à‚ýŸîàû|º¯º>-—ö…[? ñ}…°/,}‚ì-‚†Å*oðQ<ìð¡€Ýô⻣ðÁ[~þA/ص3ŽïÊ„õ¾3êwØy½v¼oá;ìð¾¶ã˶ Ø&à=7¼ë„wl°E@6û Öoã:oGaÞ6Eá-œÿ–6âmc)¼)àTØ `½€×¬ðš5Ö®±ñµÖØ`MXªFEUG¡ ©J„Õx[…U¸ùUñ𪀕¯ìà+¼RYÈ_Ù¯”K•/„xe!T†¥–#:– x¹ Tàƒ‰áVX†. ÀKXŠ]KsáE¼½(` êa‰«ðBþ,`‘€ç<'àYÏxzaˆ?-`ažð¤€'2áOð¸€Ê}P¦À|¥æ ˜…Ç¢0GÀì?®ã³üqÌšéç³¢0Ó3¢ðH)<, ä¡Nü¡Nð`¦Gá(üAÀý¦ ˜:ɧfÂÅ™0¹Há“)P–&MTø$ LT`BÄÍ'T@„:xÄ ã¸O@¡@Oçà㌽×ÏÇ ¸[÷úaŒ€‚(ü^Àhl‡[G ø€üD¸ÇwßåãwGá.¸Ëy£|¸Ów$ÂÈ\é†Üžë‚Ãm|„†Û`X†qñ¡nâ‚ÁQ4ÐÆÙa  Üâ¢p;®y{Âýí<, ?ïo‡~6èÛÇÊûz zA/9.è) ;zdÅñ!ÈêîâYqµ[ê®Xywt/—ºeZx7t K™èš±Žwëg¬ƒt t‰ÎzñÎQèäñN½ ct(‚4·¹¡½×ÁÛ'BjB‰Ð.б]"¤8 ™Xyr‚v†¥€ ’HL„„xOA¼=†Çû ~rÆÉo…8_.+¾Ô— ±¼ðàÛ> stream xœ]’Ën„0 E÷ùŠ,§‹oÒ‘R5ݰèC¥ý€˜)R Q`ü}íx4•ºŸ8×7VœäÜ=wnÚdòÓÃ&ÇÉÙër ä—ɉ,—v2ÛmÿfÖ^$XÜïësçÆE4L>psÝÂ.OvàAH)“·`!Lî"_çžSýÕû˜Ám2m+-Œh÷¢ý«žA&±øØYÜŸ¶ýˆeŠÏ݃Ìã:ã–ÌbaõÚ@Ðî¢IÓV6ãØ pöß^^qÉ0šoDS”¦)䊹B.ÇÈ0Ÿs>'ÖÌš¸`.UƒhòÇÈЧfŸšô'ÖŸHÜ+öWÑŸÏ­éÜRq­" ÷¦¨7•2§¤ÏXŸQž=yœ/(_2—Ä5{ÖäYð¹[qÿõ¯X£HS±]æíÖèZiþ÷y™k8ªøHâŒh:“ƒû;ò‹§ªøý%û¨< endstream endobj 17 0 obj 342 endobj 18 0 obj << /Type /FontDescriptor /FontName /XAMBSN+DejaVuSans-Bold /FontFamily (DejaVu Sans) /Flags 32 /FontBBox [ -1069 -415 1975 1174 ] /ItalicAngle 0 /Ascent 928 /Descent -235 /CapHeight 1174 /StemV 80 /StemH 80 /FontFile2 14 0 R >> endobj 7 0 obj << /Type /Font /Subtype /TrueType /BaseFont /XAMBSN+DejaVuSans-Bold /FirstChar 32 /LastChar 121 /FontDescriptor 18 0 R /Encoding /WinAnsiEncoding /Widths [ 0 0 0 0 0 0 0 0 457 457 0 0 0 0 0 0 0 695 695 0 0 0 0 0 0 0 0 0 0 0 0 0 0 773 0 0 0 0 683 820 0 0 0 0 0 0 0 850 0 0 770 0 682 0 0 0 0 0 0 0 0 0 0 0 0 674 715 592 0 678 0 715 0 342 342 0 342 0 0 687 715 0 493 0 478 711 0 923 0 651 ] /ToUnicode 16 0 R >> endobj 19 0 obj << /Length 20 0 R /Filter /FlateDecode /Length1 3288 >> stream xœÝU}tTÅ¿ó~3»Éf?“ÝÀf“͆d£–D !hb€ƒHB1(µEY"~¤"àbj¨Rµˆh° "Ek¶Øj$Q¡X%j”à'm©R¿¥TŸ9“Þ÷ÔÓszNÿìé9}³ï7óûÝ;wîÜ}o "J§6ù›nX÷ì¨4"±È˜Ñ¼àŠ–KŒ«ásöÚrÅ5Kš‹šÖ¿Äãuì#çÏ›“¤†ôIö§1óYðns.bþ6ó¢ù-‹oruÑ*æÌÓ®¹¶iÏ«'R’¹»eÎM “¥ÌýÌc Î[pËœ1Iä|Œ jÖí²Ymâ윔ßMR³£C?-ÒÔ]†¤/èEþýúGf ñ‚@A³¤Eˆ ÑíNï© gðŽ5œãNt9V˜.Kd9ÂéÒCž q«/Ý“5)˜‘ã¨îï/û¼z z ²r%²²ÓLï#Æ!—Z/éÑ ë)¿{Dõ‡eìç?z¬,³²rd"p<²3’Š´q[iŒ$"ib¶à4*æÈ³Fv+ ”WŒSŽwÄ•zMíKuU†ÖkôÕzM*µÿ #¨ßEŸU3íë hà[дg¶qÚ¢Y”‹fê¦Ox<–6’‰|Þ W†U«ß"fP?Ûç²ç2¹\Ìà¾En"ƒí·Êk碜æÒu<ŠËM¢›vО½LÜ«ÎW³,oú¦>T»ÅqUiTRƒl‘cåv¹Lngëe³\F)ÆJ£W®“Kåkr)5X™‰©ÖmåA¢VR‡Ñ!jDXÔ=´Ëμèç¨WÔ+ÔG}üïoáv£á{Ä 1B4ˆí<ë$ùÌ*Œ qL|Ä?D½hP.ê •"“Y7õpÞGè-’•Vª>c¸ê£Ýt˜Þbè*a0æ¡Tõq;NÓU\™ÃÂP}Ž ³@6§¨_Üfl6N‰BapËù\ÍËÐ#åy[¹:Â@9ò1‘ñRËCõ‰Îâ°£Y,a?«-åuúÝF'ïñy:ÄûâÕK¥F[ÅΘh¹Ø*se„:²ŽYµ¡^£‡ëQo×ãnºÛ1ŠNJÇTÑ(·*FqµK(pÖ:2©]Ô:oã΢¥dë>Aj×7½ÒyÔ.OÃÜ»a´~W7±„zŒJÌå÷Ôj«E'­¦Nâ7“ÿâgœ%a*‰ùSF|J2•˜ÞÛ;³ ´äßhÌ¨>åYë¬o53¥rSˆ§¥d¼ðð2.-©«oˆuŠÓ'Õ|vRc ‹5ðÐb,³>©Æ¶Y«¦TœSS±¦ù±þ…U+üóªJÉ:ŸŒ7Ÿ÷Øú'.÷UAùiöãÚsvuáwý— ¶úzÒ籯e¶£³Eçù ¾\øU™¯ç[ý_—ÁoG³:f ö5Õ:Ó¾YÓX½‰Üü\û)aEUGîå„Û6>„4AÄ)ÀX$ ¨Š”("“Y!e3ûVfûYcˆ˜mϧ.Æ(%ólk.ŸFŠ2æØJØÆ¡6±1ÛÆ’—£†lf!²ìq¦>á¥V¶ûlf!<ÂM÷°æ±5íä#Ô-2h&k–Œm¬e³fYÀ˜`ÍR Òí™i6:¹"Z3Ûל©&d ‡½/e£´½`ïȰa#%[18Zcàë5 ñu L¯NMV_µâÔd|iâ¤ÆŸkü£ '4þ®q\ãoQÓ8ÚïRG5ú]èOÈÏ>u©ÏÊð© Ÿ˜øxU¶úXã#5q„É5>Ðø‹Æa?küIã&½?TJâý¡xoCT½—Ä»ïÄÕ»&Þ‰ãíÞ¸zÛÄ[ƒê­lìó«ƒAôùqàÍ u †73ð{¼a¢—ã÷Æñúnõz!^{5¨^+Æ«=™êÕ z2±ŸÍûóðJ/ïëR/kìÛ;[íë¾6¹71¸'®öÎÆÞ„ÜÇKHâÅûüêEݹø½Æ./T©&^x2¢^¨ÂóÏå¨çËð\w@=—ƒî.Ÿê k‡[uù°Ãgy±g5:5ž áéLüNã·Oil‚ß„‘ÊÆ6޳ÍÄVî¶šx’ýŸŒ` w[Zñk'Šñ+Ç5ÓØ¬ñK6i<ºÑ«ÕØèÅÆ„|„ õˆ‰ •Ð?ΫÆû0΋±Õ56Õœ“D•Fegkœ•…19jL£ƒª";åh—Gbt›,/s«ò ʲÌQ#7«Q#9þÈÍáÆ™Y(-©R¥&JBqUR…áI|/‰34Ná´!uZÅ1Ä£(*ä /Š¢0€aäQÃLøP± ò]ˆF‘—Vyqäú²Tn¹|f¬’rÂu*§a^4\‡¡CÈæÕ²M„X ÅL"+€Ló€†? Ÿ×¯|Yðí”^?¼mÒà w2xkÙÈh“.\ ™®‘¦áÔp(—rh(TBJHò2  ͧ—G‰ÈÑ)’ËïÃÿ?.ú_'ð_¼ò蟱2g endstream endobj 20 0 obj 2254 endobj 21 0 obj << /Length 22 0 R /Filter /FlateDecode >> stream xœ]ÏjÃ0 Æï~ ÛCqÓsŒî’ÃþÐlàØrfhd£8‡¼ýd7t0-$}?óYúÚ¿ö2èOŽvÀ >c\âÊaÄ)j.à‚Í{Uo;›¤´Àödœ{òQµ-è› —Ì^\ñ¨@°C4Ááû:> endobj 24 0 obj << /Type /Font /Subtype /CIDFontType2 /BaseFont /LDLMNU+DejaVuSans-Bold /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor 23 0 R /W [0 [ 600 741 ]] >> endobj 6 0 obj << /Type /Font /Subtype /Type0 /BaseFont /LDLMNU+DejaVuSans-Bold /Encoding /Identity-H /DescendantFonts [ 24 0 R] /ToUnicode 21 0 R >> endobj 1 0 obj << /Type /Pages /Kids [ 8 0 R ] /Count 1 >> endobj 25 0 obj << /Creator (cairo 1.12.14 (http://cairographics.org)) /Producer (cairo 1.12.14 (http://cairographics.org)) >> endobj 26 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 27 0000000000 65535 f 0000021259 00000 n 0000001823 00000 n 0000000015 00000 n 0000001800 00000 n 0000010299 00000 n 0000021094 00000 n 0000017430 00000 n 0000001970 00000 n 0000002200 00000 n 0000009446 00000 n 0000009470 00000 n 0000009998 00000 n 0000010021 00000 n 0000010795 00000 n 0000016685 00000 n 0000016709 00000 n 0000017130 00000 n 0000017153 00000 n 0000017879 00000 n 0000020229 00000 n 0000020253 00000 n 0000020555 00000 n 0000020578 00000 n 0000020854 00000 n 0000021324 00000 n 0000021454 00000 n trailer << /Size 27 /Root 26 0 R /Info 25 0 R >> startxref 21507 %%EOF PyTables-3.7.0/doc/source/usersguide/images/objecttree.png000066400000000000000000007155501416254111300236040ustar00rootroot00000000000000‰PNG  IHDRüÐÁ$PsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝyXUÕþ?ð÷‘Yå0"2C†8K8 &jh*fšæT9T×R«kל½Š×Êœ-Í4Ms*,•ÄQpVgp™Öïì/Ûs€ÃäQz¿žg?ÏÙköZŸuØrãs÷ÞK!„Õu´Uüˆˆˆˆˆˆˆˆˆjüˆˆˆˆˆˆˆˆˆj]m'@TY …ÂÀ]mçADDDDDDUv\ í$ˆj ÞáGDDDDDDDDT‹°àGDDDDDDDDT‹ð‘^ª5‚‚‚ЪU+m§ADDDDDDX¿~=îܹ£í4ˆj%ü¨Öxã70iÒ$m§ADDDDDD8pà ~D5„ôÕ",øÕ",øÕ",øÕ",øÕ",øÕ",øÕ",øÕ",øÕ"ºÚN€ˆˆˆjŸääd(•J˜™™i;µ233‘’’‚¢¢"XZZÂÜÜ\Û)½ðäɘ››£Q£FÚN§R‘••4nÜXÛéÕÞáGDDDÕ"== €µµ5œœœ`nnL:·o߆§§§´­_¿^vn»ví¤c“'O®‘ü®_¿Ž?üNNNP*•pssƒ‡‡,,,ШQ#Œ7—.]*õü¤¤$Ù6oÞ\¡ñÏŸ?/;çÎURµŠ‰‰)óøéÓ§áíí-Z`Ïž=RûÂ… eóÒd;qâ„tþ¤I“¤öN:ÉÆ\³fì¼”””*ÏsݺuhÑ¢š6mŠ;wîT¹?"""¢—ïð#""¢*+((@Ïž=qôèQY{jj*ôõõ‘ŸŸË—/Kí=’Å]¹r÷ïß´lÙ²Úó[·nÆŒŒ µÇoÞ¼‰eË–aåÊ•˜4iæÍ›YL^^žléééÊ!77Wv~i¹¼hçÏŸÇĉñðáCÄÅÅ©Bàƒ>@aa!”J%† "KMM•ÍK999Òç;wîHçgffÊâ=z$ë»   Bã¨3zôhÌž=YYYøä“O°qãÆ*÷IDDDô²á~DDDTeW¯^•ûÌÍÍñöÛo£_¿~ ÔbfÀÔ©S1lØ0 l………X¸p!Þzë-!^@vÚõŸÿüÍ›7ÇþýûËŒûå—_päÈÀ;#zõ꽈ôj„½½=z÷î Ø´i<¨åŒˆˆˆˆªïð#""¢*KHHí/Y²DvXFFÂÃÃ¥ýçݬ)À¼yódmíÛ·Ç„ ТE àÔ©Søá‡°}ûv)fÛ¶mX°`>ýôÓjËÅÁÁAö´jÕªÚú®¬ˆˆîš[°`ôù½÷Þ+3vþüùðññ)3¦äñáÇ£mÛ¶€úõë—›Kuxï½÷¤Ÿ÷üùóѱcÇ2.ы‚UÙíÛ·eû-Z´í+•JLš4©ÚÆB@¡P”“••…áÇËîÔûøã1þ|ÙãºèÓ§–/_ޱcÇJí_ý5F¥ñÂ#åådmm]áï@“yÖĹ%8p§N899¡Y³feÆûûûW¨€Ö³gOôìÙ³J9ÓtÎ]»v…±±1233¤¤$¸¸¸TKDDDD/>ÒKDDD•¶wï^cÉ’%²ö1cÆ 88£G¤¤¤ 88XÚvìØQá±¢££1pà@¸»»ÃÀÀMš4Á!Cð÷ß«ÿã?œœ,í{zzª}7_Éœ»uë&ígeeaÕªUeætçÎŒ9022B«V­ðÉ'Ÿ ++K%öÊ•+²ï 22RmŸ¿üò BBB`ooºuë¢uëÖ5jT¹ïÉ+,,Ä¢E‹Ð£G888@WWvvvèܹ3¶nÝ*+|þþûï–-R’””$åvöìY©ýÇ”>? [fÏž-ûöÛoWøü'OžàóÏ?G‡ T*amm®]»bÆŒÈÍÍU{޾¾>zôè(**Â÷ß_¥9½t„ܸ½’[¢x DDôb­]»V”ü]üüæéé)„âÚµk²öÅ‹Ëú±²²’Ž………ÉŽ‰ÿüç?BGGGíuêÔÓ¦Mùùù²ó† &‹Û´iS¹ó9xð ìœ®]»JÇ.\¸ ;6tèPacc£6'777qöìYYß±±±²˜õë×ËŽgee‰¡C‡–ú]Ö«WO¬Y³FmÞ ÂÏϯ̟ŀDaa¡Bˆï¿ÿ¾ÌØH}7hÐ@jß½{·ÊØŸ~ú©ìܨ¨¨r¿ç’  kgg';¶`ÁYß·nÝ’žžž¥ÎÃÇÇGœ?^í¸%¯] åLDDÕÃßß¿äïícâ%ø;“·Ú²ñ‘^"""z©ýøã˜>}º¬­E‹¸páž>}Š¢¢"Ìž=øøã¥˜’w©š½3ïù‚Oœ8Qjìúõ뎎ŽpvvF||<ÒÒÒ<»›oذaˆÕø±Ú©S§J}€®®.š5k†S§NAììlŒ17FçÎ¥¸ÂÂB„††ÊækbbKKK$%%Im[¶l‡‡¾þúkò€3gÎàîÝ»Ò~óæÍË=çСC*«0S(èÓ§Æã—%''!!!¸qã†Ôfee$&&xv ôèÑW®\¾¾¾ìü¦M›JŸ/_¾Œ[·nÁÞÞ¾Zr#"""Ò6>ÒKDDD•†´´4,\¸PÖ~âÄ ¤¥¥!&&¦Jýgdd`Ê”)Ò¾¿¿?âããqêÔ)ܸq#GŽ”ŽÍ›7ÙÙÙÒþýû÷¥ÏuêÔƒƒC¹ãÕ­[¶¶¶Ò~zz:òóóKŸ1c®_¿ŽÈÈH\¿~¯¿þºtìÔ©SØ´iSù“pþüyÙc¥ÄÕ«W‹K—.ÉV:þòË/eç.]ºTVì›:u*=z„ÄÄD\ºtI6ï%K– 33C† Áµk×døà\¼xQjS*•x÷ÝwKÍí·ß~C^^ž¬-!!gΜ‘ö5]è¢ä<ÏŸ?œœÙ<ïÝ»‡+VàÈ‘#HOO—æY§N¸ººJçîÚµKåî·µk×¢ÿþðôôD½zõd ‰”\ÈäéÓ§*y•|<¹äâ)Úfoo/[(æèÑ£²ïËÐÐû÷ïǦM›pöìYÊîæ+V²\r®DDDD¯:üˆˆˆè¥†úõëKûãÇ—½kmΜ9˜\*dEGGcÞ¼yÒñ:uê ((HÚ/ù^¾û÷ï###÷ïß—î|óöö–Ž¿L¿:uê`Ô¨QÒþ¶mÛ°yófiÿÎ; Ã»ï¾ ???4oÞ\åñçÜÜ\ÙœZ´hQó‰½ ,øÑKËÂÂãÇ—öOœ8„„„àµ×^Ã×_-srr’¿ 443f̵ݸqS§NE·nÝбcG|ôÑG8}ú´,æÝwß•«ŽvïÞ 4oÞ8zô¨t<88¸ÌG‚K @÷îÝ¥ý7ÂÍÍMºKq×®]Ò±^½zÉÞ-8aÂYanË–-033ƒµµ5Úµk‡ääd騤I“dEÌ’ì>|øÖÖÖ°¶¶Æ@¶øFvv6>|¨Ñ|^„É“'Ëîò8p üýýѳgO¸ººÊîÞûꫯTîð;wî <+ ¶jÕêÅ$NDDDô°àGDDD/µ3f`РAÒþ£G°k×.\¸pAjsppÀÞ½{e¨ûòË/±jÕ*˜˜˜”;Vݺu±`Á¬Y³¦ÜØÍ›7£qãÆÈËËÃéÓ§eÒúúúbÅŠåöQÒÏ?ÿ,+:ݼyÛ·oÇ­[·¤¶€€lذAvž®®.~ýõW4oÞ\j+**Âýû÷eq ÀÌ™3emo¾ù¦l¿ø®Ââ1ÝÜÜdºFEEUhN5©aÆعs§ì.Ř˜DDDÈ/™1c† ¢r~||¼ô¹iÓ¦¯BLDDDô*ÐÕvDDDôê³³³C§N¤ý’w^ÏÜ(yÜÞÞ^v¼]»vÒ»õ¼¼¼dÇtuu±qãF 8«V­ÂáÇñøñcÀÑÑaaa?~<ÌÍÍKÍoäÈ‘ ŪU«S§N!==À³ÇZ_{í5tëÖ ãÆ+õ]nuëÖ•ÍÁ××ÇŽÃÌ™3±oß>$$$ I“&èÞ½;þóŸÿ¨¬Ð›ŸŸ/Û/ù¾A077ÇÑ£G±råJlذqqqxòä êׯ777Œ3ÇW»š¬——bbb°`Áüõ×_8{ö,ÒÒÒ`kk ooo|üñÇèÙ³§Êyo¼ñ–/_ŽE‹!11æææhÑ¢¥˜©x©R$tqq‘}/šVKòöö–η²²’sppõýü܃‚‚pùòe,X°»víÂõë×QPPøùùaòäÉèСƒÚq:$} «PÎDDDD/;…ºÙˆ^ …€´axx8&Mš¤ÅŒˆˆèEyúô)ôõõ¡P(*ÝGAA ÕÐ*ÛŸ®néÿ_ê¾}ûеkWÙ~É÷é©“““##£Jå“——§öŽÇÒäçç«!àÔ©ShÙ²%€gŹóçÏW*ŸA¼¼¼r¦B4hÐ)))ÐÑÑÁ7`gg÷‚²$"¢b%ßK{\ Í|ˆj>ÒKDDD¯ƒ*û€gwVW±¯¸?uîÝ»‡K—.aåÊ•²ö’ïÏ+Me‹}*TìTï8,æëë+=j|áÂܼy³Ò9Õ4…B¡ÑÏ4..NZ°£k×®,öQ­Ã‚Q 3f ¼¼¼d«È€µµµ–2ª¸É“'KŸŸ/\¾ŠV­Z%}þâ‹/´˜ QÍ`Áˆˆˆ¨ÅÆÆª´ 6ì•*ø½õÖ[h×®`ÅŠÈËËÓrF•—žžŽuëÖú÷ï/Í‹ˆˆˆ¨6aÁˆˆˆ¨†Õ(¶U«V044Ô¸ïììl¾ÓÂÂBܹsGÖ–““ƒ¬¬,ÇÒ[[[XYYêׯ###ØØØ”{Þƒ™™Y¡±RSS5ºþgÅÖ­[ÃÀÀ@£øÇ#--­BùÜ»wÙÙÙ:§¦¥¥¥iô=~üééé÷›žž^¡øÊÈÈȨö1lll`mm °´´„B¡€­­mµŽADDDDôOÂÑsœ1iÒ$\¼x055…••p÷î]•ø‡bîܹpwwG½zõàèè###Œ;yyy²Ø®]»bÈ!X¹r%4hggg$''ãÛo¿… Ú´iƒ›7oªŒqüøq4mÚJ¥–––puuEddd™óxôèkänÆåË—ÃÍÍ J¥NNN022‚‡‡222TbSSSDEEáÀ055•¶øøxµý·hÑcÇŽErr2ºté+++(•JôèÑC%öüùó²>MMM±uëÖRs·µµÅÔ©SqúôitèЦ¦¦°´´DÇŽqÿþ}•ø‚‚L›6 J¥ 6„µµ5:tè€F¡^½zeŽUž={ö`Ù²eenG­tÿÅ\\\àää$í;99ÁÅÅ¥Ôøï¿ÿ 6„••LLLàîˆˆ2ÇX¾|9acc¥R‰‘#Gª-jÅÆÆªü¼~ÿý÷2ûþûï¿áéé 333˜››ÃÛÛÇŽ+5>55aaa055Eƒ  T*áîîŽ9sæ¨ÄöìÙ¦¦¦8uêvíÚ%ËëêÕ«*ñ=zô€©©)âââ°sçNYüµk×ÔæãììŒÏ>û —/_F§N`ii ¥R‰·ÞzKmü¾}ûàááSSS˜™™¡I“&e¾ÓnïÞ½pww‡™™™|e;v þþþ077‡™™‚ƒƒÕþ{¯Œ’×§¾¾¾ô»‘ˆˆˆˆˆ*ô=çñãÇøë¯¿°qãF¼ÿþûøê«¯°cÇ„‡‡cúôéX±b…,>)) Û¶mÃøñãáëë‹ääd¬Y³Ë—/‡‹‹ &Ož,ÅfeeáÊ•+HJJÂÿû_Œ;ƒ B:u0þ||ðÁؽ{7Æ'sèÐ!ÁËË ß~û-,--1{öltëÖ ‡B›6mÔÎ#33÷î݃¾¾>„Õö¨ÜªU«0vìX 2 ,€››’’’póæM(•J•øáÇ#00‹/†B¡Àĉ¥cöööjÇÈÈÈÀýû÷Ñ·o_cΜ9HNNFÆ Ub4h -HqûömÌœ9S¥ÐZÒãDZ{÷n¬]»ãÇÇÌ™3±yóf,]º³fÍÂâÅ‹eñ³gÏÆœ9s0wî\„„„`Ë–-˜>}:ú÷ï%K– eË–}oêüúë¯Ø·o_™1£F*õg¬©¶mÛÊ~6~ø!<==ÕÆ~ñŘ5k†ŽaÆ!''áááèÕ«6lØ€Áƒ«œ³~ýzXXX`úôéðððÀÚµk±zõjèééaÙ²e²ØFI?¯k×®aþüùeÞíöûï¿£OŸ>ðõõÅÊ•+a``€Ù³g£cÇŽˆ‹‹Ãk¯½&‹ONNFË–-¡P(0kÖ,´lÙˆG³fÍTú3f BBB0wî\˜››côèÑÒ1 •ø±cÇ¢OŸ>˜3g,,,Êž]ÏwîÜAHH5j„ùóç#11ÞÞÞ*±;vìÀ›o¾‰Ö­[cÍš5ÐÑÑÁìٳѾ}{œ9s²øíÛ·#44~~~˜:u*ttt0kÖ,´oß§OŸV‰¯ŒmÛ¶aÙ²eøè£0þ|¬]»?þø£ô;Qœœܺu 6,÷wO`` ÜÜܤýO>ù„?""""¢ªBpãöJnlˆâ-<<\.»wï^äääÈÚÖ¯__åBTÓ¦MaaaððpŠEîîî°··Gxx8ž)õ¼¾}û¢~ýúð÷÷‡µµ5d/÷Yýù矨[·.Nž<‰›7o"##EEEøã?ðÖ[oáàÁƒÚN±Z­_¿-[¶DÏž=akk sssL˜0#FŒÀ¬Y³Ôžóþûï\\\`aa‘#G¢uëÖX¼xq•óy÷Ýw±dÉDDDÀÚÚ...066†«««Ú…š6m +++899•Y, …žž|||4Š×ÔèÑ£±hÑ"ìܹ–––Ò|ÝÜÜðõ×_«Ä3 .ÄŽ;TâgΜYê8/^ÄåË—±k×®*ç\ÒøñãѦM(•JÔ«WAAAðòòÂæÍ›¡££S­cQé/óc@DeQ(¶î‡cÒ¤IUî÷È‘#°²²Ry þåË—ñðáC´mÛVåœüü|ìܹÙÙÙhß¾=œ‘ššŠ .ÀßßFFF€¸¸8( iÅÓ¸¸8èèè iÓ¦€“'OBOOOÚ/)55ÇŽÃåË—¡¯¯'''tíÚuëÖ-u.‡‚M©/ô¯ŒÜÜ\ìß¿IIIÈÎΆ­­-Ú¶m«Ñ*¤999ؽ{7îܹOOOB___%îøñã011‘VzÕTvv6Nœ8ooïRW&=tè4h ò.º‹/âñãDzGP…èÛ·/ °eËÙw––†† â½÷ÞÃ’%K*”çË®°°GŽÁ™3g`hh___øúúªŠŠ‚ƒƒœœœpêÔ)DGGÃÍÍ ;w–®ûÒDGG£]»vؾ};úôéSfì;wƒ„„ÂÅÅAAA044TŸ––†˜˜\ºt &&&hÕª¼¼¼Ê,:eggcçÎxð༽½Ñ©S'èê–þª[Mã9ß<[}¸x¾FFFpuuEPP 4ŠwssÃ믿^j|QQ6nÜRW5.**ÂÁƒѸqc8::ÊŽ9sùùù*é>|ø{öìÁÝ»wahhˆfÍš¡}ûöÕº‚7Õ%Ÿ’8.„xù_LLôŠ`Á^Y5Uð#*ÄÆÆª6’““Ѹqc,]ºcÇŽÕR†¯¶õë×ãwÞATT:vì¨ítˆˆˆˆècÁ¨æð‘^"¢RØÚÚBWW_}õ•ô¾Ä¼¼ €Ã‡ã÷߇——ú÷ïßÿ½ZÇòõõEÏž=ѦM›jí·2† ‚éÓ§k; """"¢,]m'@ô"= …Ë—/×v*¯¤¢¢"|òÉ'hÔ¨> 333éX›6m0aÂ-fG/»üü|üë_ÿ‚——¢¢¢`ll,k×®3«©©©Xºt)ºwïŽü …À³+Mš4Á¬Y³Ð«W¯jÏÍÍ­Ú‹ˆDDDDDôjbÁþQž‡ &»N6n܈۷ocúôé²b_yV®\ Jó555E»ví¤Ÿ‘‘M›6áÌ™3022’ò×ÑёŠ!ð¿ÿýmÛ¶…ŸŸŸìØÎ;ñøñc :T%Þßß^^^ضmNœ8{{{ 8®®®²>~øáäççcܸq²ßfff}z•ÆB{{{1räH1gÎ@(•J@têÔI%öìÙ³ÂÍÍM‰N: ???¡««+Úµk'îÞ½«6ÞÕÕU¯££Sj|hh¨°±±zzzÂÀÀ@6ׄ„YìèÑ£‘ššZ¡ùÚØØˆ±cÇŠ3fÈæÛ¥K•Øøøxáìì,ŒŒŒDçÎEëÖ­…ŽŽŽèر£HII‘Å bÆŒ*ýôîÝ[x{{ËÚ 1pà@áææ&EPP¨[·®077'NœÅ‡…… …B!rrrTúÿ믿±nÝ:©-//Oƒ ®®®²þK»>£££¥ïÛÜÜ\üñG©ßåýû÷E§Náää$\]]* Tâ Å”)S„ŽŽŽ°³³ÁÁÁ" @Ô­[WLž88XØØØ]]]ahh(»Ôýû%""¢6Qâoºcâ%ø;“·Ú²i=nÜ*»ÕtÁÏÕÕUX[[‹ˆˆQPP >^sçΕڊ ~ …B¬ZµJj?wîœ033Íš5+åzæÀåüÞ}÷]¡¯¯/"""DQQ‘(((ááá€øþûïUâ¿ûî;@|ñŲ‚`aa¡ÚBf1Vf¾DDDD,øqãVs›ÖàÆ­²›&¿Q£F iÓÓÓzzz²¶ &¨œçíí- ˼ƒN!"##±`Á•cÿþ÷¿Ù]?û÷ïÄÿû_•ø/¿üR±±±jǪhÁ¯&tìØ±ÔFY\\\D½zõdE*Sð2dˆJ{ñÝrGŽ)o:Zgoo/ŒeE9u~ûí7@¬X±B娤I“„B¡—.]’Ú¶mÛ&ÈŠYÅ&Nœ( …¬V’&?___µ?“òØØØ•;Ÿ·yóf@¬]»VåØ| êÔ©#+ÚW¶à7fÌ•øÏ?ÿ\§OŸ–Úš4i"ÜÜÜÔæzóæM@Lœ8Qj+.ø7®ÔþË*—Wð»v효S§ŽJá¶°°P888Y{vv¶011ݺu+uÌÒ°àGDDDš`Á·šÛ¸J/Õj#FŒÀ²eˤÍÏÏþþþ²¶·ß~[í¹½zõ‚¯¯o™ýŸ:u зo_•co¾ù¦,¦2ñ/CCC<}úTÖ6mÚ4HÛÎ;ÕžÛ§Oøøø”ÙñÜûôé£r¬:¿KKK•¶âw ^¸p¡Êý¿ýû÷‡§§g™1qqqJÿ>…RLeâ+JÝõóÑGÉ®Ÿ}ûö©=wàÀpss+³ÿ²®Ÿ~ýú¡¨¨ñññ•Ìþÿhzýèéé!??_m ö½ˆêúïÖ­ !!¡â ÿgΜAQQ²³³1eÊi›6mLMM‘˜˜ˆ¼¼<)þÂ… *ï1$""""¢Wí Z­¸ˆPl÷îÝP(4hP¹çÖ©S~=üÚµk€† ª+~i~qLeâ_6öööÈÎÎFff¦ôòý† ÂÓÓ>ÄîÝ»‘ššªö\M¿O…B;;;•c5ýýyûöíJ÷ѧOŸR žÅ>úè#,Z´¨ÒcÓôûÔÓÓƒµµµÊ±âïóúõë²x}}}XYYi_Qööö8vì ¤B—ƒƒÒÒÒ’’‚={öàÁƒjÏÕd¾×¯_‡‘‘‘ÚeœœœªœYÔ]?¶¶¶HLLT_<ϲÝ)©Aƒ€”””J瘜œ àÙ‚ Ï>íìì`gg‡ôôtéz¹téÀÅÅ¥Òc‘v°àGTÅ…‘´´4ÉŽÿA_²xR2¾øø²â_6žZ·n øàƒ'OžÄîÝ»«Ô¿••„HOO‡………ìXM?>ØØØTºéÓ§cܸqeÆ4nܸÒýW”••òóó‘••…úõëËŽŸ%ï&³´´D^^²³³Q¯^½rã+ÊÁÁEEE¸r労"òÇ ˆŠŠÂž={*Ýwqn¹¹¹ÈÍÍ…¡¡¡ìXuä_u× 233ñàÁ•q‹‹oš^oÅù«+†kª¸¸øõ×_£S§NåÆÿ[«J‘‘ˆˆˆˆˆ´ƒôU»»;àøñã*Ç:$‹©Lüó +Ÿl5 üôÓO5Òñ#›ê¾ŸƒPý~tttÔÞU(„(uœŒŒ •¶£G\]]5Oø9¾¾¾.sóðð¨tÿUÑﳬ보¦¢¢¢2ó)~Ô¶¦®www!£rL]þ … …¢Æ®Ÿ®]»vìØ¡¿}ûvèêêâõ×_רÿ#Gލä_QÅEÖ¿þúK£xooo@DDD¥ÆÓöï+""""¢4m¿D·Ên¨Ä*½GG-7ÎÛÛ[ 0 Ü¸¬¬,akk+Z¶l)ž¸¸¸h´:òãÇ…¥¥¥­úðáCáìì,<<V¬XQå9‘´]q䯭²*q‡Ÿ¦† &¾úê+ã<(ºté"¬¬¬„­­­èÑ£‡ˆ‹‹+5>**ªBñB<»[hÖ¬Y¢I“&ÂÔÔTøûû‹… –Ÿ-^ýu1~üxç¡©´´41~üxáçç'êׯ/E¯^½Äßÿ­6>,,LÌš5Kãþ###Å믿.,--Eƒ D¯^½Ä™3gJÍeðàÁÂÌÌL¸»»‹ñãNjNj3fˆªÄЉ'ŠcÇŽ‰Þ½{ 333ѪU+±aÃóÓ¶ˆùóçkÿ矊ÀÀ@aaa!6l(z÷î-.\¸Pjüž={Tâ/^¼Xæ™™™â«¯¾ÂÌÌL´mÛV,]ºTmìýû÷ÅØ±cE«V­D½zõDãÆEŸ>}Ä‘#GÔÆ÷ë×O,Z´HãùþñÇ¢sçÎÂÜÜ\ØÛÛ‹RïtMMM¡¡¡ÂÔÔTxyy‰O>ùDdee‰©S§ŠáÇËb‹ïðûüóÏETT”èÑ£‡ôoqëÖ­¥æó矊ž={ KKKáìì,,®]»¦—ŸŸ/:uê$Ö¬Y#8 ëûöíåÎ[“;ü„xvò‡~(š6m* …™™™ðóó?ýô“Úøœœñå—_ abb"œœœÄ€ÄúõëK#--M|ñÅÂÕÕUXXXˆ:ˆU«V•;"""úgá~ܸÕܦ¢ô÷½Ì …-€»Åûááá˜4i’3¢W‘‘ÆŒƒÅ‹k;zÅBWWŸþ9fΜ©ítàŠ(È IDATTìØ±}ûöÅþýûÕ¾ˆˆˆèePòýÑÇ…ÕûHÑ?í ""ªΟ?àÿVã%""""¢®Úó²*""¢˜¡C‡"##عs'<==¥Õx‰ˆˆˆˆèŸ‹wøÑ?ŠŸŸœœœ´½¢üýýáàà í4$Í›7‡R©Ä£G0jÔ(ìÝ»·Úè!""""¢Wïð#¢”¨¨(m§@¯(;vLÛiÈ|òÉ'ÚNˆˆˆˆˆ^B¼Ãˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨Q!´Q¥( w—‹÷ÍÌÌ`ll¬ÅŒˆˆˆˆˆˆHS÷îÝC^^^ñîm!„½6ó!ªM¸J/½Ê K¥!--M[¹Qå™h;¢Ú„ôÕ"¼Ã^e…%wŒQ·n]måBDDDDDDðèÑ#äççïh3¢Ú†?z•=,¹3cÆ Lš4I[¹QàøñãÅ»—ËŠ%¢Šá#½DDDDDDDDDµ ~DDDDDDDDDµ ~DDDDDDDDDµ ~DDDDDDDDDµ ~DDDDDDDDDµ ~DDDDDDDDDµˆ®¶ ¢WOQQ6oÞŒãÇãܹs011››† ‚&Mšh;½WTT„Y³fá_ÿúŒŒŒªÔ×çŸ޶mÛ¢W¯^Õ”Ý˯°°7nĉ'pþüy˜™™ÁÃÃC‡…‡‡‡¶Ó#$$$`Ú´iøê«¯´ö{íâÅ‹8räÞ{ï=­Œ¯ÎÓ§O±aÜæË$??ƒÆÖ­[‘ŸŸ_åþ,X€ÈÈÈjÈìÕðøñcôìÙC‡EDDÌÌÌžžŽåË—ÃÏϹ¹¹ÚN‘^cÇŽÅ‚ jtŒèèhlݺÿýwŽS–¼¼<|ú駘?¾Ör()%%xï½÷pàÀXXXàþýûX¼x1Ú·oÿÿØ;ï°¨Žïÿ¿—Þ«H* È"*ˆ  ‚ b%klQò1ÖØKT¬Q£QcĒ؈bCKŠ‚`A)‚€H‡ßþv¿¬÷î²Ø2¯ç¹Ï3gΜ¹wîì½çΜÇûØ&R( …B¡Pþ?t†å?Å™3gÀápпÿmJ›sûöíVqH½Ë¤I“pêÔ)lÛ¶ ãǤ×ÕÕ!99êêê­^ç§DTTnݺ…ÄÄD¨©©}ls>;"""pñâEìÝ»#Gޤ×ÖÖ"%% Ñ:ÊçBBB***Ú´ŽQ£FÁÖÖ\.·MëG×®]qêÔ)xzzÂÔÔ¡¡¡Í AJJ bccáïï/H¯¬¬ÄÇ!%E¿#S( …B¡|*Ð'3ÊŠ}ûöaß¾}Í*SXXˆ×¯_7«LYY™D3•Þ¼yƒââb‰õVUU!;;»MyP^^.ÖžçÏŸãÀrö€¬¬,œ%ª§´´555MÊ7ûÜ7—ÂÂB‰Ïç¥K—°qãFlݺºººmbOEE^½z%‘ìëׯ%–mLii)jkk%Ò_RR"±Þ¦f÷ddd 66‘‘‘BÎ>““C÷îÝ%ª§¤¤D"û‹ŠŠP^^.‘Î÷¥  õõõm¦¿¼¼¥¥¥ÉVTT ;; é%„ýߥ¥¥Û4ÝÞ¥¬¬Lbû[«W¯$ºç¥¤¤àìì YYY‰u—••I4˹®®yyyBi¨ªªbÈöèÑ?üð&Mš„üü|‰miíÙv‰‰‰HHHÀÌ™3…œ} ¤¤$±c´¸¸X¢{¦  •••ïe«¤äçç·y£P( …BùXP‡…ÂBii)"##¡­­ ]]]hhhÀÌÌ ÿûßÿXå]\\ðõ×_#77>>>ÐÑѺº:úõë'ôRÍ')) öööPWWG»ví`nn.rYgQQV¬XKKK(++ÃÄÄJJJ˜8q"«Ó#<<ˆÇÕ«W¡¡¡!8þùçÖ:Ñ¥K=¸téCî÷ßG}}="""Äœ=&ŽŽŽ?~<²³³Ñ¯_?èèè@MM d•ÿõ×_Ñ¡C´k×êêê°°°@ll,«¬¾¾>~üñGFzdd$ºtéÂH×ÓÓÃ?ü€Ý»wÃÔÔºººPSSÃ7ß|ÓäKhTT¼½½Eε··Ç´iÓD–_¼x1´µµY¯Û¿ÿþ 777hhh@[[}úôé|½rå lmm²:uµk×DÖkkk‹iÓ¦áñãÇðôôD»ví ¦¦†ÀÀ@VùË—/ÃÆÆÐÒÒ‚ ®_¿.R?äææÂÐÐ!!!"eöìÙ×ìþcee…™3g"33}úôÜ_ÁÁÁ¬ò›7o†@ÎÚÚgÏžeÈñx --U«V!""îîî ÙÙ³gcÊ”)ðõõÅ¥K—pòäItèÐÄüÁj7ÛlÜŠŠ ‘ýgË–-ؾ};-Z„«W¯â«¯¾Â¦M›0oÞ¼fµ‹²²2:t2d.\¸€ˆˆœ?k×®eÈß¹s—/_ÆÜ¹s‘˜˜ˆ;wÂÑÑ«V­bÌf~ýú5þúë/ÄÅÅaÉ’%ÈÈÈÀ AƒpãÆ ,X°qqq ‡ÓÑ£G1pà@hkkc÷îÝØ³gjjjЫW/±×º¤¤EEEbe`ùòåøßÿþœX³fHÝÀÛÙhµµµŒþ#++‹iÓ¦áÙ³gHLL«C‚‚‚ ­­-”6nÜ8”——ãÆ-Ö333Áÿ222ptt”8Þ§ðìÙ3Fž««+!##===ôìÙòòòPTT„¶¶¶Ð˜rëÖ-deeaìØ±¨©©ôMMMôíÛ.\k‡±±1äååÅʘ™™!==ûöík³Ø¤0hÐ Ö¥ÿ­Á°aÃ`ll,ø_QQ]ºta\/AšŒŒ ´´´$º¶†††þo,j Ælâ–PVVƸ…î_Qí ir×ø?þø„DEE ¥+))aòäɸ{÷.^½z%[kÙ²e8p à8sæ k]–––MîàÈ·§sçÎŒ<!™ÖÆÆÆÀ[§,¹¹¹PVVëÀ°°°ÀëׯQ^^Ž“'O"%%/_¾Äï¿ÿBrssY~l¨««ƒálü¶/]º”q½Ž=ŠššV'ð6ž8g_cý‹/fè?v쪫«Í÷A__………BióæÍê?¢ºÖÖÖMêÏÌÌ„””lmmyªÿHêŒi.êêêb­IIIؼy3¦NŠàà`‰6÷‘‘‘û?ÿ\ýïÿcô‡sçÎ –í¶„µk×ÂÒÒ#GŽ„žžFŽÙ*NýwiìPÿ¨©©1î_iiixzzâàÁƒ‚q&66™™™í¯££àíXô1`»§L™"tÿþû↓e%9ÿ™™™ÐÔÔ„¾¾>#¯­ï_¾}¢Æ …B¡P(”Ï™¦E(”ÿü(ýoMø±Â¢££áêêÊ*Óxfߦ!C†àâÅ‹àr¹èÚµ+ìíí‘››Ûbçßž 6ˆÜa×ÄÄDðw‡ÀãñŸŸ/˜mòóÏ?ã§Ÿ~½{÷Ð"{***Àáp   ÀÈSVVÈ´|Gž¨x]ššš¨ªªÇ鸴°°ðv&ßæÍ›1tèPtèÐ[¶lÁ!CPSS#±Ã þõÚ¶m›È8Šâf Jªÿ—_~aÝðÚ·oÿÞú;tè€ŠŠ ¡™B¿ýö*++‘””„°°°÷Ö ¼írrr ÇðáúO[ÎRcãÅ‹ ÄíÛ·áêê ;;;tëÖMl,IIá÷‡ßÿ]зßåÝ¿ÍÅÀÀÉÉɈÇ_ý%ØIÝÛÛÇŽ\·/…íÛ·ÃÓÓ–––èØ±#?~Œ¯¾ú “&Mj²,¿ïjjj¶µ™¬áîÝ» „>œ8q5558}ú´DmGEE”””Xó¾Ôû—B¡P( ¥-¡? ¥üYD÷ïßoSýG¤Ãï]¦L™‚Ë—/#>>žžž‚ôÇ·ØáÇ·GJJJ"{øËœÏŸ?1cÆx;ëDGGGâå†â°°°!Ïž=c8Æø sssF¹¦vוþÌQéÁãñðêÕ+´k׎UÆÜÜçÏŸG\\¡¯¯Õ«W 6Ph‰Ã½ddd$î?Ÿ’~þÌ»óçÏ vóå;EÍLl¨®®F^^c–ÐÇ2ïÒý‡?¶¹´”1cÆ 55wîÜ£££ ýêÕ«"w­•~““k“þÀ‡Ãá oß¾èÛ·/–-[†Ÿ~ú ‹/ÆæÍ›EîŒþ¹RUU555„„„ _¿~ppp@÷îÝ%*ûòåKZ>ü!±³³ÃñãÇqùòeôéÓ÷öaK077ÇùóçQSSØI-îþm 'ü÷ƒ…B¡P(Êç ]ÒKùOa`` ?é]ôôô`gg‡­[·¶Šë],,,СClÚ´I(6ž8®\¹WWW!g_~~>뎾‘••e,¿z+++bãÆíü8lØ0hkkcûöíBËz[‹^½zöîÝËÈÛµkddd;%«¨¨0â.UTTˆátáÂÆL‘˜˜±ŽOþ޶ÉÉÉ"õ*))A__Ë–-ƒ££#\\\`llŒAƒaÅŠ’’jÑ ¥Ú·oõë×·Š“ê]:wî DGG·Éõ3f TUU±uëVÆrÇÖ€¿ë'[ÿÙ½{7äååÑ­[7Aš””ý§¬¬LìÒ¾³gÏ2îߘ˜ÈÉɽW8€–€ 9ûž>^pþ·³wNœ8Ñ"ÛÀÇÇXºt)Î;à­“%::ÇŽôiÓ3ÿ,--‘€;wÄÄD¸¸¸ˆ={÷î]Œ9Ïž=ÇÃÎ;qðàA„……‰\R]UU///|ûí·-ngc:tè ˜MZQQ£GÂÝÝõõõbÇIÐÔÔÄòåËqóæM„‡‡£¤¤ÀÛsš™™)Ö)ZXX777¡©l¤¥¥ {X¸p!*++áææ&²œ‡‡®\¹"£ž>}Š›7o6§yœºº:¼zõ GŽAÿþýáë닾}ûÂÍÍ øûï¿Å–?qâ|}}%ZVŸ››‹ž={6ù{׌ŒŒðõ×_ãÏ?ÿĸqã¼Ü»wOp¿µ„°°0téÒßÿ½àw«¡¡óçÏÇÕ«W1gΓx; ..÷ïßÇÃ… àìì,6¶ä•+W0~üxäåå¡¡¡kÖ¬Á¹sçÙfá<( …B¡P> {›`zÐã}zø¿-ÜItt4i-þúë/¢¯¯O}}}Ò¾}{€¸ºº²ÊÛØØÐÐP‰õïÚµ‹¨ªª‡C:vìHTUU 2`À†ìµk×ˆŽŽ@¤¤¤2oÞ<²~ýz€±Ö‘‘‘!ÐË/onnÎ*»cÇ!{TTTâççÇ*¿iÓ&"##CEEEÁ5ððð >dÈ›››“#FHtn Hß¾} Ò¾}{¢©©I‘#G’ŠŠ †üÉ“'‰¬¬,@455‰œœÙºu+6l166fÈ+((I“&‘ÐÐPÂápú¹\.)((kÛÊ•+‰²²2yþü¹H™±cÇ---RYY)HãñxÄÚÚšøøø0äeeeÉwß}ÇH:t(133c­cË–-DII‰p8bffF”•• Â*ohhH"##Ŷ­1›7ofÕ/®ß¼y“ ¦¦¦Mê_µj‘––fôòôéS†¼®®.™0a‚D¶çææ777€èééuuu€Œ7ŽTWW3äÿøã-šššDQQ‘ìÙ³‡øûû[[[!Ùúúz€Ìš5‹øûû õWWWòêÕ+‘veddÚ)ªÿÔÖÖdþüùŒ<___Ò¥KFúÉ“'‰†††`|àp8dÆ dΜ9DVV–ðx<¬‰‰ ú̘1‚ÿuuu…þ'ämß]³f ‘——'RRRÄÌÌLp;þúk‘í½xñ"@ìììDÊBȰaâ­­Mìí퉴´4‘––&S¦L[.!!(**‡C´µµ Ò»woVY%%%%VgÏž%H||M'''‰ä%¥¡¡üðÂ¾Üøþ "ùùùŒ2RRR¬}šG€ÂápHTT©¯¯gÈoÛ¶p8€hhh555rìØ1âêêJ<<<„dóòò²xñbÒ»wo"---|||È›7oÞëœP( ¥e¸¸¸~KÜ ŸÀ{&=èñ¥B>Ì2 ¥µáp8zòøÿGGGcúôé­¦¿¼¼IIIHOO‡‚‚œœœÐ¹sgÖ nݺeee ê« IDATÖÝAEQXX(Ð/++‹Ž;¢_¿~¬Aêëêê‹ÊÊJ¸»»£cÇŽ(((@ZZ\]]Y7¹ÞÝ?uêrrr`mm ///‘3Ì ””„‡BNNN`¨ êøçŸðàÁhkk£sçÎ"—3&%%A]]]â2y<®]»†»wïBVVŽŽŽb—JÞ¿.\€ŽŽ<==¡§§‡´´4”••1–è***büøñˆŽŽFrr2`nnOOO‘måÓÐЀ^½zÁÐÐPäL¿§OŸ¢¢¢‚±Ópff&çàÊ•+044dÄ&|ðàÊËËK˜ùäååáæÍ›xøð!äååann޾}ûBQQ‘!›˜˜mmmXYY‰mß»ú“’’‘‘˜™™¡_¿~"ûÄÇÇ£cÇŽű{ñâ’““‘––†öíÛÃÞÞ^°ç»\¿~:::Ç?¬¯¯ô%%%8::‚Ë劔OIIÁ¥K— ¯¯>}ú }ûöHMMEUU•Вˆ†ÈÈÈ`îܹX¼x1nß¾ÄÄDX[[£OŸ>bÏ ðvŒZ¹r%òòòX7~!„àòåË055eÌ4½wïjjjXª*=zÒÒÒpssƒ‘‘rrrðèÑ#¸»» v¿qãÔÕÕ; ߸qššš‚X}ׯ_‡¶¶6ëŽÈ999¸yó&233¡¨¨KKKxyy‰Ýµ:..ÖÖÖèСƒH™7oÞ ..YYY¨®®†¾¾><<¶9 …BùÿP‡…ÒvÐ~ …Ò ÌÍÍ‘€3gÎbR(M¡  €íÛ·SgßCCCp8Ì™3G¿²ªª {ö쯯/ ???¡2çÏŸ‡‚‚Î;G} …B¡P(”fA~ …ÒLLMM‘œœ,rƒ å]TUUáååõ±Í |D±k×.œ?PSSl„4eÊ=z”r! 'Ožl2Ü…B¡P( …ò.Ì`d …òó×_‰)&)l±Â(_6RRR8}ú4#Þ"…")cÆŒÁðáÑ‘‘¢¢"ÁÄÄ„56,:Ö´ZZZ8}ú´ †%…B¡P(Ê—uøQ(”ÿÞÞÞÛÊg ‡ÃÁ€>¶”ÏYYY±›PPÚ999zÿR( …BùOA—ôR( …B¡P( …B¡P(_ÔáG¡P( …B¡P( …B¡|AP‡…B¡P( …B¡P( …òA~ …B¡P( …B¡P(ÊuøQ( …B¡P( …B¡P(_ÔáG¡P( …B¡P( …B¡|AP‡…B¡P( …B¡P( …òA~ …B¡P( …B¡P(Ê„ÌÇ6€B¡|ÔÕÕáÂ… Éöïßç½ê9þ<ŒŒŒ`cc#‘|rr2 ÿ[XXÀÂÂâ½ên—/_†‡‡G‹õœ;w&&&°¶¶n«>²³³qçÎÜ¿ššš°¶¶†§§'¤¥¥?¶i  ´´ûöíCXXÚµk÷±Íi7nÜ€´´4ºwïþ±Mi3Š‹‹qàÀ >šššÛáàÁƒ1b444>¶9‘““ƒÔÔTFº²²2ÜÝÝ[¤»¼¼×®]ÈÉÉÁÖÖzzz-Òù9‘ŸŸÃ‡côèÑPUUýØæ //GÅèÑ£¡¢¢òQlÈÉÉAuuõyfGaa!þùçŸ&å444àêêú^uðû¿ƒƒƒÄýþâÅ‹¨©©üß½{whkk¿Wý å? !„ôø,zÿˆŽŽ&”ÿãàÁƒdÿþý­¦¯¨¨ˆ4>ß⎺ºº÷®GII‰L™2EbùÁƒ Õ½`Á‚÷®[R6mÚD´µµI^^^‹uÉÊÊ’ï¾û®¬ú|X¼x1áp8i×®‘““#ˆ©©)©ªªúØæQ>vîÜINœ8ѦuüòË/Y·n]›Öó!èÞ½;ñòòúØf´)6l ÈÖ­[[]÷Ï?ÿLΞ=Û¬2ëÖ­#È/¿üÒêö´Û·ogýM·²²j±îääd†^}}}²qãÆV°üÓgÉ’%‰‰‰iuÝÑÑÑäÒ¥KÍ*óã?äàÁƒ­n¤ìÚµ‹èêê’”””f!„ÄÆÆJôlëììüÞuðûsÎwûöí…êoîô9áââÒ¸­7È'ðžIz|)áGùO‘ 066þÈ–´=»víB]]† Ö*ú´µµQUU%”Ö­[7¨««#>>^(]FæÃ -‡BCCjkk¡®®ÞæõíÞ½³gÏÆùóçÿS³Z‹~ø+W®Ddd$-ZBœœŒÄÄD(((|l)ŸÑÑÑàr¹4hP›Õ1dÈ””” ,,¬Íê ´aaa¨®®Æ!CZ]÷Ê•+áçç‰Ë„‡‡£®®AAA­nO[ÔÕÕÁÊÊ cÆŒÁÂ… !++Ûju,X°#FŒÀÝ»wƒ©S§"..Gý¢gxGDD@NNluÝ‹-Âøñ㛵ê`ìØ±PRRBÿþý[ÝI‰ˆˆÀ£GЧOüûï¿íÙ|РABÏ·¯_¿†®®.¾ûî;,[¶L.%õa#aeggƒ‚Ë—/cÀ€´n …òå@~”ÿsæÌ‡ÃÁï¿ÿþ±Mù,y×#%%))©ê¤‘••…¬¬ìyËÊÊ´iÓ°bÅ ôèÑ£ÍëûÒxõê6mÚ???ìØ±CÎápàèèGGÇh…"Œ¶¶6¾ÿþûmEBÚ·oÿI]/]]ÝOÊIPQQŠŠ jkkjjj055mÕ:´´´`ii KKKcÞ¼yX¶lvíÚ…o¾ù¦Uëú”044ü¤úƒ‘‘¢¢¢>¶X¶l®]»†ˆˆÄÅŽw8˜–ðîs,¿ÿËÈÈ|Ôç[yyyo—ÀS(ÊûB7í PD°oß>ÃÖÖNNNøú믱oß>VÙ)S¦`Íš5¨©©ÁòåËáììŒÞ½{cîܹ „0äKJJ0uêT8;;£K—.;v,òóóEÚ’˜˜ˆo¾ù®®®033CŸ>}pèÐ!VÙåË—#((ÉÉɸwï‚‚‚Gff&k™W¯^aÊ”)èÞ½;ºté‚ÈÈH¡¸x-åÚµkˆŒŒ²ÿðáÃbËÜ»w°³³ƒ§§'~ýõ×V³§¨¨ß~û-œœœ`ooqãÆáåË—M–›1cŒ1iÒ$Öü±cÇbÛ¶m"Ëïß¿ƒ>£GF÷îÝaff†~ýúáôéÓ ÙqãÆaÛ¶mˆ‹‹ƒ››Æ‡úúzœ>}½zõ²eËPWWÇ(÷âÅ Œ7\.\.S¦LAYY™Ø¶”––"$$[·nm²Ý‰‰‰9r$¸\®Àþo¿ý ÙY³f!((ÙÙÙ¸té’Ðø&ª†……á·ß~Cii)æÍ›x{{cÍš5 ÙÿýWHgPPîݻǪ·²²AAA8r䊊Š'''8::bÍš5¬c?>|^^^°¶¶ÆðáÃ1þ| >ÎÎθ|ùr“ç«)Ö­[‡^½z Æ6q¿-ÀÛY$cÇŽE×®]Ñ­[7̘1oÞ¼)õêU„‡‡ÃÁÁæææðööÆ´iÓD¶·¡¡ëׯ‡¿¿?¬¬¬Ð«W/Lœ8gΜa•2d8€¢¢"üðÃèÚµ+ú÷ï72do޼ɸ^>dÕ[ZZŠ   œqqq …½½=,,,пÌž=[¤|mm-V­Z___XZZÂÝÝß~û-.^¼È­®®FPPbcc‘——‡¨¨(téÒ¾¾¾B±ø¼;Fáùóç¬vð¯Ñ… pîÜ9„††ÂÚÚþþþ"Ç…ˆˆ¡¢¢Gª‡øøx†=¹¹¹"Ï œ>}ÁÁÁ°¶¶†››¢¢¢P^^Î{þü9‚‚‚péÒ%dggcÊ”)èÚµ+œ›|~ûù矈•ãóäÉøûûãØ±cÉ·õõõ8räBCCáààKKKøûû7ðäÉ“ µµ5ZeÜçsïÞ= >¶¶¶èÑ£~üñGÖßu …òß:ü(”w¨««ƒ¿¿?FމÚÚZ >¾¾¾(**ÂãÇYË\¸p·nÝ„ °bÅ èèè ¬¬ ©©©Œ¯•Ïž=ƒ££#¶mÛSSSôîÝû÷ï—Ë,9nÌÙ³gáåå…’’øûû£oß¾HIIAXXbccòrrrPPP€´´´à«%ÿ`›÷ôéSp¹\üòË/033ƒ»»;öíÛ.—‹œœœ÷<‹ÿÇ©S§Ð¯_?”••! ^^^ø÷ß1tèPœ:uеLbb"|||P[[‹ððp”——#22?ýôS‹íyôè¸\.vìØKKKôìÙ{÷î—ËE^^žÈrùùù8yò$F%rÙQZZšÈ—XàíÃöµk×_kÓÒÒàää„›7o –m,Z´[¶laèxøð!¸\.víÚkkk¸¸¸`÷îÝàr¹"´gÏžÅ?ÿüƒÑ£G#::(,,DFFC6==\.»wïF§NàììŒ]»vÁÑÑQ¬SôîÝ»øóÏ?±nÝ:‘2<ôéÓG¤ gΜArr2Fމõë×ÃÐÐùùù¬ìC‡¡gÏžHMMÅСCáé鉓'OÂÁÁõ!<66éééŒô¤¤$Æ&5„ÄÆÆbÓ¦M4h8†Ž‚‚„††"&&¦Yíz¾þƒ¢[·n¸{÷.üüüP[[‹¹sçb×®]Œ2ûöíC`` êêêŒ^½z!)) ¾¾¾¸~ýºì¹sç°ÿ~Lš4 :::رcfΜ‰éÓ§CKK óæÍc¼x¤¤¤€Ëåbß¾}èÚµ+°mÛ6tëÖ ¥¥¥"Ûrûöm9r6lÛæ£GÂÍÍ <@¿~ýðÝwßÁÖÖÕÕÕ¬÷™¼¼¼`,“––ßDÍ 9qâRRRŠ­[·ÂØØÙÙÙÈÊÊbÈ6ÖYZZŠØØX±ê­««Cll,öïß'''¤§§cРAxóæ ¢¢¢X_^·lÙ‚¡C‡B[[Ó¦MCVV–.]Š‚‚ôìÙºººbÏWSLŸ>3g΄¼¼<† †—/_ÂÍÍMäØpëÖ-p¹\:tÝ»w‡ 6mÚgggTTT0ä÷ïß>>˜>}:¬­­QWWÇzþ‹‹‹áââ‚Y³fA]]‘‘‘pssÓ'ODÚtüøqܽ{ÁÁÁعs'LMMñäÉVGIãëU\\ŒØØXVgðÖY‹U«V!,, ***6l=z„Aƒáï¿ÿf”á÷7‡#˜í#®¿5¶§¨¨H¬=°yófôë×ÏŸ?ÇÈ‘#áâ₽{÷ÂÁÁOž<’­ªªBll,öîÝ‹îÝ»#++ þþþ‚v'NœYÏÕ«W+öƒÔû°sçNôíÛÏŸ?‡ŸŸ¦OŸ ‹f9deeŽé´´4¡¼Ë—/ÃÉÉ Ç‡››LLL°zõj¸¹¹±~8Û¾};¼½½‘›› ???L›6 æææ"íyñâºuë†ùóç£}ûö7n\\\žžŽW¯^1äëëë‹{÷îÁßß¿ÿþ;ÌÌÌ––ÆêXoÜòóóËê,€7oÞ 66‹/Ƙ1c ­­°°0¤¦¦¢ÿþ¸rå £ _7Fÿd£±=yyyˆëÜ_±büüüðòåKŒ3\.Û·oG·nÝÑÊËË‹_ý®®®ÈÏÏG@@òòò)Ö©ekk GGGüòË/"e‡“'ObçÎÉ· ,Àĉ¡ªªŠððpØÚÚâìÙ³pwwéHݺu+&L˜===„††"99ýû÷gu07—“'OÂÅÅgΜ··7Úµk‡E‹ÁÛÛ›õƒ!…Bùð±ƒÒƒï{@‚M;ž={F’““‡¯¯/ñóóJËÎÎ*³jÕ*€ìÝ»—¡O666DMM8::’—/_ Ò²¡¡¡DNNŽdee Ò?~L¤¥¥Ixx8C¾¶¶– ¥½xñ‚p8âíí-Ò&âééÙ¤íÁÁÁD^^^èM]¯ãÇ$11‘5?// ºººäáÇ‚ôÂÂBb``@Dê666;þ±qäȀܼy“5?''‡(**’Aƒ mtõàÁ¢¬¬LBBB„äŸ?Nmmm¡>™••EÑ£G‹´¥¦¦†ìÞ½[¨Ýâàÿž6Õ/íìì—Ë•hÜãoZ°~ýzFÞáÇ rèÐ!AZ]]éÔ©±°° oÞ¼¤;vŒ kÖ¬aèéÔ©qrr’x&ÊÊÊBã¹8ÊËË÷|XX˜ÐukªîÝ»— ÷ïßgÍøð!@:tè ô|˜““C´´´XŸøhhhÿýïµÏîÝ» ’žžÎšŸ‘‘AdddHXX˜PÛîܹCäääÈ×_-$ÿþ}€èéé‘ëׯ Òù¿GMõ¥9sæ‡Crrrš´½²²’ìܹ“<}ú´IÙ÷¡¬¬Œ ßÿ½X¹’’ÆXxðàA€,]ºT(ßÿÍÌÌ„ž?Ÿ>}JTUU‰›››Èzâãã›Ü´£²²’GGG¡qs×®]ùõ×_ŶåcC7í =ÚîøèЃï{Hâð>|x“»nEFF 䈪ª*4hC—8lllˆ‘‘Ã1÷.üŽáÇ3òüüüˆœœœÄªÎÎÎÄÌÌLd¾$¿œœ€Œ5Š‘7`À¢   ö!V‡Ÿ(YwþSRR"“&Mb¤ïÛ· ‡fÕ'‰ÃÿbÄæØëÛ·/QVV&<µì¤I“R^^.Rÿ¢E‹Hûöíÿÿý÷d„ ‚shkkËx)“••%Ó§OgèêÙ³'ãü@ÒÒÒyݺuc8üDŽ™:u"–––Œt777bjjÊøPÆFvv6áp8bÃlp8âââBjkk›UNR‡ßÚµkyüKÙÎ!mãð[°`ÈHS§N%„œ|‡ß† òúúúdÀ€ͲO’:ü¸\.éÔ©yñâE“:Å9üøV­Z%Hûûï¿Y{ ÄÈȈ8991ôØÛÛ[[[¡b¢à;¨ØÆQð~^^^M:øÞER‡ßŽ;y3fÌ „Ei ‡ßôéÓ‰”””ó‘ÏèÑ£‰”””ÐDþùܳg,ÿ¹ºñoü]ÑE·I~¢ÊÊÊÊ’ˆˆ¡t~ÿgÛ¥yâĉ€È~+‰Ã/&&† »víJ¯ªª"b'| P‡=èÑvÝ´ƒòE³eË¡8M“'O‡ÃŠw£¤¤$øûÉ“'(//oÖN||zöì ---±2üØB‰‰‰puuÊ{ñâjkkñäÉXZZ2ÊÖ××ãÑ£Gxøð!rrrPWWÇC¨9ðíIHH`Ø“››‹êêjdeeÁÌ̬Eõo—¿ñíÏÍÍECCƒHûÙ–ò999ë2óæÍìY³„dù¿§ÊAÑ>Æý›ŸŸvíÚ±æµõxÕZ 4éé騾};bccqøðaL:K–,ÁäÉ“%ÖÃÿgaa!HãÇx†††¸xñ"!"70i ÿú8×cllŒåË—cùòå¸zõ*.\ˆ¨¨(˜˜˜`èС¹Ž;x;s¾sçÎÍl͇åcÜ¿ÆÆÆ¸sçkßÕ’ß»…™™V­Z…U«VáÂ… X¸p!¾ýö[tìØ¾¾¾é8pàäää`cc#Hãtÿ¼yó&k¿¶¦=üMÁ>‡>ÏgÈ!øçŸ°sçN|õÕW§ì“þyü>ù>˜™™‚ðððÏêR(”¶‡îÒK¡4ÂÔÔZZZØ»wo›,éÔ©”””°oß>‰Ë:t\.‹-8ûêëë;ø±ÑÔ’_(**6ËžærðàA899aÁ‚g_}}½X§*)Ocø;víÚõ½m±µµ…¼¼ü{µ—_ï£GDÊhiiASS‹/F»vígggtëÖ óæÍ <ƒ¡¹ØÙÙANN®Í®WçÎ!++ÛfúGŽ )))ìÙ³§MôwéÒXw>~ü¸ ðv©‘ŒŒ ëîÛâf ²-á×ÙXÿ‡à?þ€f̘!pUWW·ÊÛööö––nÓñá]zôèãÇCII‰uWJ>‡uÙO”––âöíÛˆ‰‰ATTT«8û€·ã[mm-®]»&”^SSÃ:ÇÁÁ§E××ÝÝLJ¬¬,ãzÙÛÛCFF†uw鉨ûWZZ¶¶¶¬e8N‹Ch¼K—.]PQQK—.1òŽ?™¶¼¸µèÛ·/Ž;)))±÷ocbccqíÚ5LŸ>ÚÚÚ‚t.— -êŸýúõÑ#GÀápö8::‚Ãá|6ýSAAAäóC[õÏüü|ÖÝí?EEÅ=ϼKFFdeeEÞƒŸùùù¸|ù2¾þúkŒ1Bàì+,,löóÉ' ®®Þ"G££#€–Ý/ åË„:ü(ÿ)¶lÙ"¿ï]¤¤¤°fͤ¦¦b„ ¨©©Êoéò5eeeÌŸ?/^Ä¢E‹@iR¿œœž>}*XÊQZZŠÐÐP\»vMì—ËÅ?ÿü#´ô÷ݘRªªª˜;w.âââ°dɉìi.òòòxúô©à¨¤¤ÁÁÁ¸qã†HûÏ;‡E‹ êOKKÆ àêê*x x4551{ölœ>}+W®lV{CBB ¤¤$p‰ÂÂÂ÷îÝÃøñãK&OžŒ´´4èêê¶(6Y»ví…'NŦ”Ä~IÐÑÑÁ¬Y³‹uëÖ5K}}=,X öü˜››#88{öìÁ–-[„òÄŧ“”ÐÐP˜™™aþüù‚pýúuìØ±C† šA¼•˜˜ˆêêjoï‘aƉ}Y=räÖ­['è?)))ضm<==ammÍZ†‚Å‹ãСC-m¦rrrHOOØÿòåKøúúâÁƒ-~466ÆäÉ“±ÿ~lݺ•‘/®?TWWcÞ¼yøûï¿ÅÖÁ·»1§NBee¥ØÙ°¸råŠÐ‡™wÇ·Oªª*ÄÇÇ£¸¸?ÿü3V¯^%K–`Þ¼yøí·ßÄ.'•äþ3f 1oÞ<¡þþù§Îòâ:Ÿ‰è_ ¸G áÄéE'`”ÞõñÇ‹‘‘‘8::JÏž=¥[·nR£F QÕjÕª•ïdjÒ§O µjÕ’~ýúIÛ¶mÅÑÑQºt颿dÉ ¥K—–€€Q©TÒ´iS™0aBž£ÆªG`´¶¶–°°0qqqGGG¸§OŸjF3®]»¶& Ës_ 2J¯zäÔ2eÊH@@€K‹-dܸq@žQ©Tâåå%iii×[·nÐ\7$ @o¬¹¹¹Œ=:Ï<òÚ‡üFé1c†Î²ñóóÓ™¿dÉ133Ó;b¼™™™Ü½{Wï¶"##€˜ššæy\¾ùæ Õ«W—nݺ‰tîÜYêÕ«§÷û+99YZ´h!ÄÛÛ[  Í›7—*Uªè=f½{÷[[[éÚµ« >\|}}ÅØØXäÑ£G:ñ?Ö\ok×®-}úô‘ÐÐPqtt4ø™V(y޶nHAGé}÷Ýw¥J•*R»vmiÛ¶­˜™™‰½½½\¸pÁ`ÛK—.Õ×N:‰]¾#+ç7J¯:ç2eʈôêÕKZ¶l)*•J‚‚‚$99Y+V=Jï¼yótÚñòò’f͚܎zÏçGî~ÞêÕ«ÅÚÚZ¬­­€˜››‹µµµÎˆÓj;w–ªU«J÷îÝeèСâåå%J¥Rš4i¢3*¶z”Ò²eËŠ¥¥¥æ¼ïÔ©“$%%émÿöíÛ,$ @  M›6•Ê•+ËÔ©SuâCCCÅÞÞ^zôè¡•O³fÍäéÓ§:ñ©©©Ò¡CÍ÷]xx¸´k×NªV­ªw„Võ(½Ï\¥wÈ!bii)õë×—V­Z‰J¥½ß_jšß”¡¡¡bcc#]»vÍ3ŸüFéY»v­˜™™‰ƒƒƒôéÓGš5k&J¥RZ´h¡óÛS=JïªU«tÚquu•Î;Üξ}ûò…6· iÙ²eâ « £ôöêÕKó»ÇÃÃC³N›6m¤AƒZ±êóÿƒ>råʉ———4oÞ\Œ¥nݺˆüß(½ÎÎÎR¿~}ƒŸõÄÄDñöö…B!AAA2`À KKK™?~áÄkÄQz9q*ºI9iÒ¤—,É“'— é%¼U«V:#‹¾¨-Z uëÖ055ÅíÛ·ajjŠÐÐP„‡‡ÌÀ××·@"(•JtêÔ þþþ022Bbb"T*0pà@Á4h€Ž;"''åÊ•ÃСCñå—_ÂÊÊ 666ðõõÕ €›ƒƒZ´hœœ¤¤¤ ((“&MÒé Z©T¢sçÎðõõÕäcbb‚ÀÀ@ 0ÀààÀ³ÇH|||ò¼ëÎÛÛ:t@NN*T¨€aÆáÿû,--Q¥Jøúúj ”¡P(ЦML˜0fff¸zõ*š7oŽo¿ý6ßÇy ¼½½áææ777ëccctíÚÞÞÞP(HLL„©©)5j„þýû|g¯ÍŸ?<À[o½¥7ÆÂÂ7F£F´¶éââ½çHãÆõâR§Nøùùéä¦y)!!¦¦¦ BÿþýuFtT 4x÷Yn*• ݺuÓ¼§‰‰‰033CPPúõëg°}õy5pàÀ<C333C=P»vmT¬XIII¨_¿>úöí‹•+W<¦aÆùÞ1<ëÿçí·ß†J¥Â7P¶lYôêÕ ‹-Òz\L-88VVVÈÊÊ‚­­->ùäŒ3*• îîîšÑ;ÿ»K¯C‡X¼x1Œqýúu´iÓóçÏÏóóoee…Û·oãôéÓ˜0a‚Þ´  BBBtúR(:ùÏÎÉ–-["##¶¶¶1b&OžŒòåËÃÆÆ 6ÔêÔ> @+Ï€€­÷«aÆZ¯MMMñöÛo£nݺ$&&ÂÜÜMš4Aß¾} €¤>OÞ}÷Ý<ïÔ³´´„B¡Àýû÷qûömT«V #GŽDDDDžƒŽÔªU ÁÁÁxüø1ÒÓÓÑ¢E |úé§zïÐR( 28PR^*T¨€&MšèÔB¡P@©T"$$D§/+…BÍcVÀ³óç×_Ejj*¶nÝŠéÓ§cÒ¤I˜8q"<<<°víZØÚÚêý³±±Á•+WpéÒ%Œ7Îà¡~~~ðôôÄ“'O`llŒbêÔ©P©TðòòÒ¹†–*U ½{÷†›››æý-S¦ š5k†^½zéOõgHý~U¯^£GƬY³ôx¤R©ÐµkWøûûC¥Ráúõë°²²B·nÝЭ[7XYYéÝB÷g¥P(`ii‰>}ýõ×èÓ§¾þúkˆnß¾Ž;báÂ…ynÏÝÝHKKCvv6Z·n1cÆè½¦äV©R%4iÒÄàYnnnèÒ¥ ŒŒŒpíÚ5XYYáÝwßÅܹsõŽJnjjŠ&Mšè½{{{üŽtpp€ˆ`ðàÁyîgff&,,,àíí4lØÞÞÞðóóÓŒûüþåää 99÷î݃««+ƇéÓ§ë½Æ•)S ÄàÁƒ1mÚ4 2Äàg½téÒèÛ·/ªW¯Ž¬¬,$&&­[·F=tî€Tç“””„{÷î¡F7n¦M›¦w0SSSôìÙžžž066Ƶk×`kk‹ž={¢S§Nzß_¥R‰&MšäùÛH…Bkkk„„„è}o“’’0oÞ< <_|ñ233qïÞ=„……aáÂ…y.âååÜ¿ …¡¡¡Z}>bccƒ&Mš¼SÏÝÝ¡¡¡P(¸zõ*lmm1dÈ|õÕWz?ïêïõ ¹ùùùé‘:++ ¡¡¡hÞ¼9Fg¾jÕªUƒˆ`èСyþf{ê÷ÙÐoàÙ`þþþxðàêÔ©ƒ‰'bÈ!(S¦ t®åeÊ”AŸ>}0hÐ ¤§§ãþýûèÑ£,Xçݼ …åË—Gýúõáææ†:uê 00P'®\¹ršß±™™™¸rå *T¨ =;¿ó¡8}ÿý÷¹ŸÌ¸>iÒ¤ï‹3¢’D!"ùGý ) 7Õ¯#""0bĈb̈þ vìØŽ;â‡~@ç΋;z²³³allŒñãÇã‹/¾(Ôº™™™h×®œœœ°xñâ"ÊþÍöïß   œ;wN§zãÆ ØÙÙaùòå×Y7==Í›7‡ŸŸŸÞÇù)·nÝB•*U0gÎ|øá‡Å‘–sçΡfÍšX¶lúõëWÜé¼999èÛ·/:„£G-œJ>ÿÜhG‰È«¹ƒƒˆØ‡Qa´hÑëׯǀôv„O¤ÏÍ›7ѰaC,Z´¨¸S¡b¢ddÇŽZó/^¼ˆ®]»¢Zµjh×®ÞuѾ}{ûˆ¨ÄX¸p!bbb°oß>ûˆˆŠ ~DD…Ô¦MÄÄÄ|œ‘èyøì³Ï P(Š;*&Íš5Cxx8† tèÐþþþ¨Q£ÌḬ̀sçNƒuÕ¬YcÆŒyÍÞ½{cïÞ½¨R¥Jq§BDTbévhADDùrtt,îè5S(øä“O\Ü©ÐH©Tbùòå5jöîÝ‹{÷î¡jÕªX´h<<<Š;½¯L™2øä“Oàíí]Ü©é¨X±">ùäÔ«W¯¸SymôõµIDD¯ûð£7ûð#"""""zs±?¢¢ÃGz‰ˆˆˆˆˆˆˆˆJüˆˆˆˆˆˆˆˆˆJüˆˆˆˆˆˆˆˆˆJüˆˆˆˆˆˆˆˆˆJüˆˆˆˆˆˆˆˆˆJüˆˆˆˆˆˆˆˆˆJüˆˆˆˆˆˆˆˆˆJüˆˆˆˆˆˆˆˆˆJüˆˆ¨ØˆHq§@DDDDDTâ°àGD/äÎ;@@@îß¿_Üé¼VwïÞEÓ¦Mqûöí—n+((ß|óÍ+ÈêÍqóæM 8õëׇ©©)¬¬¬Ð¨Q#|÷ÝwÈÌÌ,îôˆ»wïF¥J•°eË–âN¥H¤¥¥ÁßßkÖ¬y¡õ·oߎJ•*aÇŽ¯83*.6lÀûᅵœœâNEË¡C‡àïïvíÚw*DDDoüˆJ¨öíÛ£U«VEÖþo¿ý†ÈÈHDFFbÓ¦ME¶››7o"88vvv¨\¹òK·…ÄÄÄWÙ›!&&¾¾¾X¿~=üüü#F bÅŠ˜1cŒŒøµDùóòòBÿþý‹t™™™xúô)ÒÓÓ‹t;Å%33QQQ¸yóæ ¯ÿo9>nnn>|xq§ñÆ Âž={ðÎ;ïü«Š~kÖ¬ATT¶lÙ‚ãÇw:DDDo ãâN€èuŠˆˆ€B¡Àˆ#Š;•"—‘‘Q¤wK­_¿5kÖDzz:6lØ€¾}ûÙ¶þ-D}ûö… –-[…BQÜ)½qˆŒŒ ìß¿uëÖÕZ–žž¥RYL™Ñ›äéÓ§E~7hË–-‘ššÊ"´íÚµûן×q>üT®\ýõ<<<0{öl|üñÇÅD7nÄ[o½…½{÷býúõðôô,î´ˆˆˆÞ,øÑʱcÇ \¤ÉÌÌD\\Ξ= sssÔ«WzcãââPªT)8::âéӧصkÌÌÌàëë‹2eÊè]çöíÛ8vìžþøc<~ü‹/ƃP¶lY½ñgÏžEéÒ¥áàà€ôôtìÚµ æææðõõEéÒ¥ _ºtiøøøè€«W¯âôéÓ¸sçѰaCk_â.]º„GÁÝÝ]gý””$$$ÀÙÙz·±xñbüóÏ?8yò¤NÛ‹ *ÀÎÎNïú·oßÆÍ›7áîî®·°õðáC8p=Bpp0*Uª¤·àنǎCff&|||PµjUƒ±gΜAÙ²eaoo'Ož`çΰ°°€ÁóáÆ8vì²²²òm?÷vªT©‚ *è]¾{÷n;v S¦LÑ)ö€™™™ÞõNŸ>råÊ¡jÕªxüø1vî܉òåËÃÇÇ¥J•һιsçpòäI”*U ß“˜˜T©RÖÖÖZó/_¾ŒŒŒ Ô¬YS'ÞÆÆ666¸téŽ9‚ªU«ÂÇÇ&&&z·QP"‚'NhÚOKKÃþýû‘‘‘àà`T¬XÑທ/_Æ™3g””gggêbΜ9 ØÚÚb÷îÝpuu…££#²³³±{÷nøøø |ùòzÛ¿víŽ;ðõõE•*UòÝŸS§NÁÁÁåÊ•Ë7öÂ… 8}ú4RSSáììŒ5j轃öܹsxüø1ÒÓÓ‘œœ¬u}«S§ŽÞkâ©S§`ii [[[<|ø;wî„••¼¼¼tιàâÅ‹Zó\\\ô^ÇsrrpòäIØÚÚ¢råÊHMMž}û““ƒàà`ƒÇ2;;Àõë×áíí '''$$$àüùó¨_¿¾Ásµ Dqqq8uê,,,àéé©s~ësîÜ9DGGÃÉÉ ^^^zeZZ.]º¤5ÏÕÕÕàuY-11ÑÑÑP©TðññÉ7Ÿ'OžàÌ™3ˆ‡••êÕ«§w¸¸8¤§§###÷îÝÓ: ]gÕNœ8êÕ«üŽÎíüù󈋋CJJ \]]áïïo06÷5%-- ;wî„­­-4h ÷‘;>55»víÊ3xö^ÅÇÇ#55...zó9{ö,”J%jÔ¨¡³Lý]T«V-˜ššj-sppÀÌ™31lØ0´mÛµjÕÊïðŽ?Žš5küNyQÄÍ›7ñõ×_CD°aÃL›6Mo¬úªþL¦¤¤`×®]°··‡§§§Î9­þ ÛÙÙÁÊÊ*ßxµ¸¸8œ;wiii¨Q£|}}ubΜ9¸¸¸è,»yó&nß¾Úµk¿ô÷QžD„§7r`@ÔSDD„äçwÞ‘^½zåwèÐ!qssbjj*ÆÆÆ@:vì¨7¾V­ZÒ­[7Ù·oŸ”-[V““£££äääèÄÏž=[T*•ccc122’I“&é•-ZˆB¡SSSQ(@ÜÝÝåÎ;:ñ:tÜÇ%÷¥7ÿ¯¾úJ³ê|¦L™¢7‘eË–iÚÛ½{·5kÖ<žÕ«W—^½zÉîÝ»¥téÒš|\\\ôÆ;;;KïÞ½e×®]Zñ®®®:±'Nœ¦M›ŠB¡333Íññðð¤¤$­Ø)S¦‰‹‹ÓigäÈ‘¢T*åÆzsÊÎÎ 7¸Ÿ5kÖ”>}ú\>jÔ(111‘¬¬,Í<•J%£F’… Š©©©˜˜˜±´´”ØØX6rrrdÚ´i¢T*5ï—±±±|ùå—·kgg'”mÛ¶‰¹¹¹æxÖ­[WoûS§N(OKy IDATÕiÖ¬YÛ‰‰‰R³fMƒ1ÇráÂ…<Ûzžµµµ¼ÿþûòÇH©R¥4ù{xxèĦ¤¤H×®]5Ÿ]###Q(2nÜ8­ã.òì= S¦LÑi§}ûöR»vm­yYYY@F%aaa@ÌÌÌ€x{{ËÝ»w µ_ÏËÈÈ2qâD‰ˆˆ•J¥9¬­­åüùó:ëDFFJPPÐ:6òðáC­XGGGy÷Ýw¥uëÖbdd$ÆÆÆræÌyë­·ÄÈÈH¬¬¬äâÅ‹:û<~üxQ(¢P(D©TЉ‰‰|ûí·yîËÁƒ€4hРϸÔÔTiݺµ…B!–––@Ê—/¯ó~‰ˆxyy¼¾]»vMï6ÌÍÍeôèѲnÝ:155ÕÄ7jÔH'vûöí:íîÞ½[o»)))@¦M›&ÿûßÿÄØØXó~ÙÙÙIbb¢Î: R¯^= •+WÖì·z[Û¶mËóxåçîÝ»šã©¾ËôéÓu®ç÷îÝÓœomÚ´Ñ|fˆ¯¯¯ÞóyóæÍ:ÇgÿþýóÉÈÈ‘#G 122###133“¥K—\ç×_ æææšãóé§ŸêĪ¿§õM÷ïß7¸?ÿüSHHHˆÁ‘Ý»w‹Îç«yóæ’‘‘¡Ÿûš²lÙ2Íù@Z·n­¯¾¦L:U–.]ªߦMø;wŠ···N>­Zµ’ÌÌL­Ø^½z‰¹¹¹zôHÈÌ™3eáÂ…šßY¤S§N:ñ>2kÖ,Y°`V|çÎuâÿüóOñôôÔ9þ:tììl­Ø®]»Š………ìØ±CÈï¿ÿn0¦mÛ¶Ò¤IƒËÃÂÂÄÍÍMkžJ¥’òåË‹§§§8p@222äÇòöÛoë´1wî\ ~ø¡ÄÇÇKbb¢ôíÛWÈÆõn×ÎÎN7n,–––òùçŸËÑ£Geݺuz‹ @FŒ!ñññ’ }úôòÛo¿Ü·Û·o‹«««ôïßß`LçÎÅÈȨÐÿɱ¶¶–&MšHÅŠeòäÉrôèQùùçŸeÇŽ:±íÚµSSSY½zµ<}úTÒÒÒdâĉ@&Mš¤û¢?¥R)½{÷–K—.Iff¦,_¾\”J¥„……j¿ž§.ø•/_^üüü$**J2224׌wß}Wgèèhùì³Ï$>>^rrräüùó2dÈ _|ñ…V¬£££”)SFÆŽ+Ж'Mš¤)Ü? úâ‹/€|òÉ'rñâE¹pá‚tïÞ]È_ýep_®\¹"Õ«W×{ÊmÀ€bdd$?ÿü³¤¥¥‰ˆHRR’ÁÿÐ'$$ÈÙ³gÅÕÕUÚ·o¯u}{¾à¡fnn.Í›7— *ÈŒ3䨱c²víZùûï¿ub=z¤iïûï¿/PÁ¯|ùò$G•§OŸÊüùó€|ðÁ:ë´lÙR*V¬¨ù.Y¿~½æ¼ÿ¾Á}(¨FIéÒ¥eÆ ’‘‘!÷ïß—?üPÈœ9s´bÕ?¥R)ƒ–+W®ÈÓ§OeÞ¼y¢P(¤gÏž:í?|øPs|.\˜oÁoìØ±bdd$Ÿ}ö™$&&J\\œ„†† 9tèNü¯¿þª)v?^rrräÁƒràÀIHHЉ¿té’œ={Vììì¤{÷îZçÃó˜ÜâââÄÉÉIo1·½{÷ÊôéÓåâÅ‹’““#§OŸÖ\,X ¯¾¦´nÝZÊ—//³gÏ–èèhY½zµ}¤T©RÿcP½zu ?üðCžù¨9;;*þyR®\9 ÐYöÖ[o‰³³³Ö<õŒ6lØ`°ÍO?ýTÈ­[· Æ|øá‡ZÇìæÍ›ZwùûûëU*•„„„È£G´æ;88èÜÁöàÁ±´´Ô¹k*##C*Uª$îîîzó²³³…B!¿üò‹ÁÜEDÒÒÒ¤bÅŠâíí­5ÿéÓ§bii)õë×Ïsýüøûû‹M¡×³¶¶###ƒMµýû÷ ™/..·nÝBË–-±mÛ6|þøã¤¥¥i£Í›7###ÕªUCdd$ zõêxòä þüóOtíÚUï¶Ú·owÞy§À¹uèСPñ¹©T*4nÜ'NœÐYŽ>}úàСC¬]»•*UBûöí ¶yåÊ(Š<ûÕsqqÁµk×<ë¯fÍšxôèPµjU\½zU³ÍÜ4h Ó‡bccµæÅÆÆ")) ;wÖy¿4h€;w"==]o_x]»v5øÞ¨:u ÉÉÉ Ó{>ìÙ³/ÜçPff¦NŸHË—/G||¼æõÛo¿zõêé¬Û½{wtêÔ)ÏöwïÞ zGo0`6oÞŒ#GŽ uëÖ/’¾†§§§NÿxݺuÃòåË {{û—jßÇÇG§_-ܹs§@ë—-[þþþzG_uvv( T®\YóÚØØ–––HNNÖÄ=z<€‡‡‡ÎùP¿~};v "òRƒ×„„„à믿ÆÀ1dÈ4hÐà…ÛÊK¿~ýвeË"iÛßß_ë¼V*•¨W¯ÒÒÒ´âŒQ±bE­cœ™™‰ÔÔT÷ûEìÙ³ô~ 0ááá8yò¤Î5ÈÛÛ['¾[·nذaN:U þõ‰ŒŒDzz:ÜÝÝuΟºuëâèÑ£ZóNœ8«W¯þëGë¶µµEíÚµóÝxèС *p›Ã† +T|nU«V…›››N> …}ûöÅôéÓqûömMˆkÖ¬···Þ¾lsSÿNºråÊ åõ*¬_¿`mm­ù½áîîŽÕ«W#>>^§Uµ‘#GæÙÏ¢¾x??¿ÊÑÅÅNNN:Ç_©T¢W¯^øæ›oœœ¬éƒuÍš5hÔ¨\]]_h{DDD…Á‚•hýúõÓúÏO¯^½ P(°hÑ"½ñ§OŸ€wR[A:¦?þ<àçŸƺuët–›ššâòåË:¿””¬\¹‡B||<®]»†¤¤¤uÄ^|~üñGüôÓOó©_¿¾fÞ† @ïè}ëׯ7XT*ÈñyÑøû÷ïcÅŠˆŒŒD||<®_¿Ž¤¤$½…—.]º`èСøá‡€§OŸbÆ èׯŸÁºàîÝ»077ϳx¤§§ãîÝ»X¿~=LLLP¥J,Z´“&MÂÍ›7 ü#__@ý~­\¹«V­ÒY®R©påʽ´æü\¾|9V¬X¡³ÜØØW®\ÑÛ yAT­Z'OžÔ*ýóÏ?øûï¿ñôéSܺu z ~ÍßÄÄDï êcrþüù—.øéS­Z5ÏÏ( y„îÞ½‹eË–áÈ‘#8wî®_¿Žääd½Ç1·ç uÏ¿¾pá`Ñ¢E¯™·nÝzá‚L›6 øî»ï°téRØÛÛ£ÿþÿõýÑáyNNNž †ô¢ÔçODD"""ôÆäìée¾‹Ò7°téR?~ñññ¸qãRRRô*£V”ßwׯ_DzeË­ù#bJJŠÞ•úöí‹©S§â§Ÿ~‡~ˆÄÄD8póæÍËw;êÏ̽{÷ ¾#¯˜ú÷FÇŽõ.7nœÞõŠòø_¹rË–-CLLŒæø§¦¦êˆ*<<_}õ~ùå 4çÎÃÑ£G±téÒBåGDDô¢þ½B%*ê‘W322Ф}õH†¿üò ÒÓÓõNÏßáròäI8;;cÊ”)°µµÅˆ#°qãF½?€_4Ÿ7Ì'w±ïáÇضmzôèãÇkMmÛ¶Å–-[žžþÒyFtt4ªU«†éÓ§ÃÞÞ#GŽÄ¯¿þжmÛê777GXXÖ­[‡ÌÌLlÙ²©©©ùÞY¥Jöïßoð|x™bðìý˜;w.îÝ»‡7¢V­Z˜((?üð€gÜ355EÏž=óÝ–úNÔÜOD¼NÇÇ¥K—0qâDß5jÔÐÜý÷:­_¿ÎÎÎXµj<<<0qâDìØ±Ãà÷z:uàãã£9þk×®Eéҥѭ[·×™6ý‡ñ?¢\ÜÜÜQQQù€^„úñ“#GŽ C‡Zç£>Bff&._¾¬õ8髸 ±:Ÿ£Gè?œê‚^Ÿ>}àáᡵ¬W¯^زe ¶oߎÐÐЗέ FŽ àÙãÒ¹ïŒ\°`ÁuÂÃñlÙ2lß¾kÖ¬Aƒ ò½J}×;wôÞA<»+ÆØØ«V­B\\¶nÝŠŠ+âÓO?ÕÜáò2?õùyäÈ4iÒä…Û)Hû7~åíbÞ¼yX¿~=†þÊÛwuuEVVÎ;§s—PLLŒ&æy=*ÔvDDgžúî$õ#²¯Ë°aÃ`aa‹/j=nnnŽ”””—j;÷ù ï±ÏW©téÒèÔ©:uê„]»v¡Y³føþûï1}úô"Ýîë¶uëV>|óçÏGóæÍáììœç]Ã…áêêŠ 6àÖ­[:Eš¼Î}ruwêëÞ‹È}þ¨ÿõg622²Pc¥÷ßNNN8sæŒæ‚@ÞwÜ¥AƒÁÙÙ±±±Î'<< @||<Ö¬YƒŽ;æÛmܾ}ÀË/cÆ P(xï½÷tî^ìÒ¥ f̘K—.½¶k®ˆ`РA¨S§Ž?®õØy^…ôððp 6 X»v-ÂÂÂP¦L™×‘2ïð£ÿ–¦M›â­·Þ2¸ÜÂÂí۷DzeËôöÿö²ˆo¾ùׯ_/Ð:ñññ¨U«–V±oß¾}øõ×_õþGM­\¹r8wî\žmW«V ~~~ˆˆˆÈ³?"µ 6ÀÂÂM›6ÕYÖ¦M˜˜˜¼ö¿ºÇÅÅ¡nݺZÿÙ³g6oÞlðø¡zõê˜?>¶lÙ¢·Ï·çµk×À³GP 166†££#.\ˆ¶mÛÂÑÑeË–EïÞ½±dɘšš¾TÿnnnnðððÀ¬Y³Šä1«Úµk£^½z˜9s&’’’ ½þÇó<'ÃÂÂààà€ùóç¿Pûùi×®ŒŒŒ0wî\­ùÙÙÙ˜;w.lll´ FFF(_¾<Ž;¦üøq>|Øàv–.]ªé«xöÁ… ¢bÅŠy>.ùèÑ£<Ï‹ˆ‡———V±oóæÍسgÞÂda4hÐ5jÔÀ´iÓššZèõžïcñy=µk× U\[³f ¢££_ùg9?iii¸yó&µòYµjbbb æsssŒ;±±±þcæÞ½{amm]àb~dz°Ö¯_ooo½*«ûsU?òû:ܽ{ÉÉÉhذ¡V±oÉ’%8{ö¬Áãß³gO˜˜˜`äÈ‘8þ|~o½*,øÑÊó}úé3þ|”.]ÁÁÁX¼x1Î;‡³gÏâÇÄ?þøRÛW÷˜‘‘___üüóϸrå Ž=Š+V`ãÆ:ëøùùáÈ‘#?~|Ë–-ÃüñÂmçn?%%ÞÞÞØ¸q£¦ý¥K—bË–-×½t鬭­ó¼óÐØØ³fÍÂ¥K—аaC,]ºQQQX¼x1¦NúR¹ÏŸ0`/^ŒqãÆ!>>ÑÑÑèÖ­¢££1}útGn½½½qðàA|ýõ׈‰‰ÁŒ3вeË<ïØ011A‹-°zõj?~}ûöÅþýûñá‡ê ¾¢vçÎT©Ržžž¯´Pàëë‹mÛ¶aÆŒضm>øàôîݵk×.tÑäy&&&X¸p!nݺlÚ´ W¯^Edd$–,Y‚;v\÷ôéÓ¨\¹r¾woÞ¼‹-©S§pÿþ}lß¾}úôAFFFžƒÌ´jÕ ÇŽÃ’%K‹åË—cÉ’%/¼¯¯ƒºàÿí·ßÂÜÜ\Ó'¨J¥BµjÕ0f̃ë&$$ÀÚÚc†™3gbÚ´i¸pá>Œ:àÂ… ˜5k–Þó:''Í›7ÇÏ?ÿŒcÇŽ¡[·nˆÅèÑ£_êîCsssÌ›7—/_†¯¯/¶nÝŠk×®áàÁƒX¼x1þþûo­x•J…%K– 55ÞÞÞX·nƒ+Vè t•[ëÖ­qàÀ¬\¹§NÂÒ¥KõöCª+++ôèÑÃ`LéÒ¥Q§N¬[·غu+Þ{ï=|ðÁ¨Y³æK¾ ËÂÂnnnøé§Ÿ0gÎlÙ²ï¾û.F•g>eË–E—.]ðÛo¿ÁÞÞÍš5Ëw[غu+zõê¥U\4dïÞ½¨T©úöí[èýÒçôéÓˆGçÎõ.÷ññ½½ýkýcåÊ•áää„Õ«WãÛo¿Åü~ýúaüøñpuu5xü+T¨€:à·ß~ƒ‹‹Ë ÎBDDôBŠ{˜`Nœ^t`ƒÿÂ]"""äU¹zõª´mÛV”J¥¦ýòåËKÿþýõÆ×ªUKºuëVàöOŸ>ýüôbdd$ƒ Ò‰½víš„††Š±±±ùóÏ?eÑ¢E@nܸ¡wwïÞ•æÍ›köÁØØXºté¢7666V|||´òQ*•2dÈMÌÆ€¬[·Îà~-Y²DÈ–-[´æW¯^]zõêUC#""ÎÎÎÒ»wïÅ&&&Jûöí5ûéää$ýõ—|óÍ7@îÝ»gp=…B!Ý»w/p^¿ýö›( 9tèÁ˜aƉ«««ääähÍ ‘ÐÐPx•J%£FÒ™&ÎÎÎz·qüøqñôôÔy¿ôµ#"bgg'Ìk×´DGG‹‡‡‡VûÆÆÆòñÇ\çâÅ‹bnn.ÁÁÁù¶¿ÿ~qss…B¡i¿B… 2}útyôè‘N¼µµµ¼ÿþûÊ=++K>ùä111Ñúì®]»Öà¾ÚÚÚjb½¼¼$66VÚ·o/µk×Öi€Œ?^fΜ)šc?bÄ÷<·›7oJéÒ¥€\½zUoLFF†‰'ê,kÓ¦¸»»ëÌ‹‹“-Zˆ‘‘‘WWW9xð L™2E …dddhb¥GZ¯ÃÃÃ5¯­­­µ^«EFFJ:u´Î•J%Ÿ}ö™Áý333iݺµÁ‘‘#GjrWO®®®y^gDD.\¸ 5ÒœCfffZ׫ÜÌÍÍeôèÑy¶§ÏöíÛ€ìÞ½[ïò”” 3fÌÐY"~~~:óããã¥lÙ²*Ç—1cÆÈرc%000Ïm]¾|YLMME¡PHrr²ÁœŸ>}*C‡Õ|_+++ùý÷ßubïÝ»'dæÌ™òÙgŸiÎO•J%Ÿþ¹Ám¨mÞ¼YÈþýûóŒû矤FZﱉ‰‰|ùå—zãOœ8!~~~Z×kkk™:uªÁmœ>}Züýý5ñæææy^¯"##ÅÄÄ$ßëtt´4nÜXÓn:uäĉòᇊ………N|vv¶)S¦äÙ®šúš’×¾åvìØ1 ÖäS·n]9uê” 6L*T¨`p½]»v ™0aB¶óÅ_HÅŠ þ¾xÞ?ÿü#*•JúôéS øüLš4IH\\œÁ˜áÇ‹B¡+W®hæ=zôHsNÄǀ̚5«@ñ‘‘‘Ò°aC …B<<<äìÙ³2pà@©R¥ŠÁõ¶lÙ"ä‹/¾(Ðvˆþkžû?Q¤ü þŸÉ‰SI™Š=Nœ^t*Ê‚ŸZzzºœ:uJΟ?Ÿçæ_Tjjª>|XŽ?.IIIyÆÞ¼yS ½[·nILLŒ¤¥¥8Ÿ˜˜˜<ÿSùotãÆ ƒ…}>œç° éÛ·¯Ô«WOûì3\¸pÕ«W/îtÈ€ììlÔ©Sµk×ÖÛmHngÏžEãÆ1xð`Lž<ù5eX²eff¢FÀÚµk‹;¢%DEE©_F‰(½"¥—ˆþ3~þùg3Ø¿—‹‹ :¤5xSq˜={6”J% ¾ÿþ{ØÙÙÁÁÁ¡Xs"ý¾ûî;(•JÍ CéãõöíÛøôÓO1räÈ×aɶpáBcÉ’%HNNÆŒ3Š;%""úbÁˆþ3~ÿýwlÙ²õë×ÇöíÛòB혚šbþüù¯69*ÑŒŒŒXìûsppÀÞ½{1yòdŒ;ÉÉɰµµ…——öìÙƒàà`ƒëªTªb/öåää`Ïž=8vìîÝ»‡:uê`Μ9P©TÅšé¬\¹111hÞ¼9öîÝ —|× yáïEú?YYYX±bbccѺuk|ÿý÷ptt,î´ˆˆè?ˆôÒ‹ô½¹øH/QÑ1*ˆˆˆˆˆˆˆèÕaÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨1.îˆÞt?ýôbbb4¯CBBЪU«bÌè剦M›†#F L™2ZËnݺ…Õ«WcôèÑÅ”å…?¢—tæÌüý÷߀¨¨(ˆÈ]ðËÊÊBß¾}qòäI :TgyFF¾ýö[\»v sæÌB¡(†,‰ˆˆˆˆˆˆÈüè?eÛ¶mP(hÙ²å+ksÊ”)˜2e ÀÈèÍJ~üøñØ·o"##Q¡Båøë¯¿|ôÑGÅ%òæW'ˆ á‡~À?üP¨uîܹƒ´´´"É'99©©©Žðà‘™™Y ø‡"))©Àí8p_}õæÏŸ[[[ƒq5kÖĬY³0aœ9s¦ÀíQÑcÁH”” 8–––°¶¶FùòåáììŒO>ùä•´¿jÕ*888ÀÒÒRÓö†  Æ/Y²5jÔ€……œœœ`nnŽ5j %%Eo|dd$êÕ« TªT ...سgO¾y3ÁÁÁhß¾}¾±ýúõƒ‹‹ &L˜o,½>,ø=')) îîîøå—_0nÜ8ìß¿;wîİaÃàçç÷ÒíÏœ9}ûöE`` vî܉íÛ·ÃÝÝ]»vÅâÅ‹uâ—/_Ž÷Þ{>>>ظq#bcc±aÃ|ðÁ(_¾¼NüÞ½{ ###ÌŸ??ÿü3Ê”)ƒæÍ›#22Ò`^ñññ8xð ºwï^ ýP(èÔ©þøãܽ{·à€ˆˆˆˆˆˆˆŠûð£mÛ¶mˆ‹‹Ó¼Ž‹‹ƒB¡Àœ9s4óÜÝÝÑ´iSÍëÑ£GãÎ;ˆŽŽF:u4óßzë­—ÎçÎ;˜:u*Úµk‡üQ3àE³fÍФIL˜0={ö„………fM›6¡jÕªX¹r%ŒŸ}dsç•›ˆ`äÈ‘(W®¢¢¢`jj hÛ¶-lll0fÌìÝ»Wﺿÿþ;h‹ü4iÒS§NÅ–-[^àõˆˆˆˆˆˆˆ¨è°àG%ÚîÝ»±iÓ&Íë›7o-Z¤™¦)r=}ú«V­ÂÀ Õ^Æü‡â£>ÒÝÖÈÈ£FBÇŽ±sçNtîÜY³ÌÙÙ›6mÂwß}‡~ýú¡T©RÛ¿pᢣ£1jÔ(ˆÒÓÓJ¥;vÄÚµk‘žž333u/_¾ °±±)ðþ¨ûùKHH(ð:DDDDDDDT´Xð£mæÌ™˜9s¦æu¯^½ P(°zõj½ñqqqÈÎΆ··w‘äsþüy@ݺuu–yxxhŨ}öÙg8zô(†бcÇ¢E‹G›6mtFV¯;{ölÌž=[o/^Ô[̼~ý:T*Ê–-[àý±²²Ò¬KDDDDDDDÿ,øå¢£L™2EÒþ£Gæææ:ËJ—.­£V®\9üóÏ?8tè~ûí7üðÃذa¼½½±mÛ6XZZjbÕwôÍ;¾¾¾zsprrÒ;¿B… ÈÌÌDFFLLL µ?úú$""""""¢âÁ‚Q.5kÖœ>}ºHÚwqqðìñÙçï²;wî zõêz× @@@¦L™‚%K–`øðá˜2e æÎ«‰Qç¯P(àïï_¨Ü÷îÝÓ<ª›õ`ŽŽŽ…ÚŽÒKÿ)¶¶¶y³lllP§N,\¸III¯|û€U«Vé,[¶lŒŒŒg¦¦¦6l4EBµ5j jÕªøæ›o‘‘Q¨Ü‚ƒƒÑÑÑ^GT¨mQÑaÁþSfΜ‰/¿ü2Ϙŋ#99-Z´@||¼fþÍ›7 tçßÓ§O‘’’‚””äääh-óööF§N0wî\lذÀ³‘u—/_Ž+V ÿþ¨Q£†Ö:—.]ÂÉ“'5m¥§§#""W®\Ñ)´©T*Ì;.\@hh(nݺ¥ÙFBB‚ÖˆÅÏkÚ´)ªV­Š7æ»j›6m‚‡‡êׯ_àuˆˆˆˆˆˆˆ¨h±àGôœ† â÷ßÇÍ›7áææ[[[X[[ÃÖÖÌwý¹sç¢B… ¨P¡‚VÁPmÉ’% A×®]Q¹reTªT ýû÷GXX"""tâ,X€úõë£bÅŠðôôD¹råðÑG¡gÏž3fŒN|çÎñý÷ßãÀ°µµ…³³3,,,P­Z5|ôÑGóVüÓO?hÔÝ“'ObëÖ­=zt¾±DDDDDDDôú(D¤¸s z! …ÂÀMõ눈Œ1╵ÿàÁDEE!..fffðööFݺuall¸ëËþù¹?S¾¾¾zèj999hܸ1Ê—/M›6A¡PèËÊÊBHH¬¬¬ð믿晑>þþþˆŠŠR¿Œ‘ÂuDND±àGo¬¢.øýW%$$ qãÆèÔ©æÌ™£³<;;ï¼óNž<‰þùVVVÅ%½éXð#*:|¤—ˆ´899aÿþýøûï¿qíÚ5å111¸|ù2öíÛÇbÑ¿ág‰è?ËÞÞÇŽƒR©ÔYæåå…ƒê]FDDDDDDDÅwø‘^yôXì#""""""ú÷bÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨1.îˆ^•ëׯ#&&¦¸Ó """""¢xüøqq§@Tb)D¤¸s z! …ÂÀÍâ΃ˆˆˆˆˆˆ^Z”ˆøwD%é%""""""""*AXð#""""""""*A؇½ÉRô,î$ˆˆˆŠÐRæÿÿß»|_Œ¹¥{ÅQIÂ>üˆˆˆˆþ¥ E €rÿÿåby¿8ó!"""¢7é%""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""""*AXð#""""""¢ÿÇÞÇ×p/þŸ$ö¥jW*b}ßŠŠ¥Š"-ªÊUj«ZªÜ.®V×Ûö*EK­âÚ—ŠÅ­}¯"¶ØBb ‘X²JÌïßÌ/“sNÂéëùx|÷ÌÌgf>3gŽ^oŸÏ|¸?À…ø.„Àp!~€ !ð\àBüBà¸?À…ø.„Àp!~€ !ð\àBüBà¸?À…ø.„Àp!~€ !ð\àBüBà¸?À…ø.„Àp!~€ !ð\àBüBà¸?À…ø.„Àp!~€ !ð\àBüBà¸?À…ø.„Àp!~€ !ðx<äÈêàñ@àðxx2«€ÇàBüBàðèº–Õ Àã‡ÀàÑŸÕ Àã‡Àp!~€ !ð\àBüBà¸?À…ø.„Àp!~€ !ð\àBüBà¸?À…ø.„Àp!~€ !ð\àBüBà¸?À…ø.„Àp!~€ !ð\àBüBà¸?À…ø.„Àp!~€ !ð\àBüBà¸?À…ø.„Àp!~€ !ð\àBüBà¸?À…ø.„Àp!~€ !ð\àBüBฬnœº!éúÿ}ŽÊʆàña3 #«Û “0¤p! é<²l6Ûg’Údu;Åg†a¬ÎêFà ü² ’fu#E‘¬n®‚!½€ ¡‡à±={vÕ®];«›È$ÑÑÑ:|øpV7—Dàx,”(QB»wïÎêf2‰¿¿?ÿÀÂ^À…ø.„Àp!~€ !ð\àBüBà¸?À…ø.Ä#«ž›7o*_¾|iÖ;zô¨-Z¤]»v)$$D×®]S‰%äéé)ooo 4HeÊ”yð ~Œ†¡Õ«Wë·ß~Óþýûª¸¸8.\XU«VU»víôòË/+wîÜ÷ŽŽÖÈ‘#ÍåŽ;ªC‡é>ÿõë×õÞ{ï™Ë]ºtQ›6mîÿ‚2YZÏ^tt´êÕ«§õë×O&LpX/!!A6lÐÒ¥KuìØ1…„„èöíÛòôô”§§§š4i¢×_Ýé}þ;ذaƒºté"›Í¦Õ«W«I“&YÝ$ð°†A¡P(Ê#Y$-’dH2<== ܿ۷oß~û­Q¹råTë]¿~ÝèÙ³§‘tߣgÏžÆ7Ò<ÚÎ;gøøø¤yߊ/nÌž=Ûá1""",u?þøã{jÃÅ‹-û7.3.-Ãnß¾mLž<9ÍgoôèÑfÛ8à°ÎÁƒ*Uª¤yŸ *dLœ8ñA\Îc!11Ñ(S¦Œ!ɨU«–‘ÕMrèÀ)¿»>Æ#ðß …B¡P\¡0¤·uëVÕªUKÇ×µkלÖ;}ú´jÖ¬©ùóç§yÌ„„ÍŸ?_;wVlllf6÷±³iÓ&Õ¨QC›6mJ³îåË—Õ§OKOñÄFdd¤Y'£CzE¾¾¾æõ+VÌa°°0#W®\æPñˆˆËö;wîÞÞÞ–{Ó­[7ãêÕ«vÇŠŽŽ6Ú¶mk©Û£GrmƒÖ­[›÷aÑ¢EYÝ; é¥P( åÁzøpŸ¦L™¢[·nI’Ú¶m+OOO}ûí·Ú»w¯6l¨Ž;ªvíÚ–}N:¥yóæ) @áááªP¡‚jÔ¨¡çŸ^åÊ•Kóœ‡Ò‚ têÔ)EFFÊÛÛ[5jÔP‡T²dIKÝÀÀ@-[¶L'Ož4×EGG뫯¾’$uîÜYÞÞÞŠ×Ì™3Í:îîîZ¶l™ªW¯î° #FŒÐ–-[äçç§’%KªvíÚ:{ö¬*W®lžãÛo¿5ë÷îÝ[QQQš6mšÕ²eKuîÜY^^^–ãîÙ³GK–,ÑéÓ§¥ªU«ªFêÔ©“ .l׎;wîhܸqærÆ åãããð$éÑ£‡<==%I×®]ÓŒ3Ìm}ûöUÁ‚µnÝ:mܸQªQ£†Z·n­çž{ÎîüGÕÒ¥KÍå9rhÅŠæñ“Ë™3§&Ož,mݺUÒÝI6¦OŸ®wß}×Á]þÿ–-[¦Í›7ëäÉ“òööV“&MôòË/Ëf³YêݺuKS¦L1—}||Ô°aC»ã…„„hÙ²e:pà€.^¼¨*Uª¨V­ZzñÅõÄO¤Ú–£Gjݺu:r䈂‚‚T´hQ•+WNýû÷WÙ²eÍz§NÒòåË-Ï^TT”ݳ'IsæÌQLLŒ$©iÓ¦*P €åœ7nÔ±cÇÌå*UªháÂ…rww·k_®\¹´xñb•.]ZQQQªT©’rçÎ-Ã0ÌûuàÀ­[·NÒÝÞ˜C‡Õš5k4þ|yxx¨mÛ¶êÔ©“òæÍk7>>^~~~Z¿~½ÎŸ?¯\¹r©FªS§Ž:uê$77û·ä?~\~~~ærïÞ½õÔSOYê¬Y³F‡’t÷ùyûí·Ím»wïÖæÍ›%I Ðo¼¡ÐÐP­^½Z7nÔ­[·Ô AuéÒE+V´;¿$ùúúê?þ$}÷ÝwêÞ½»ÃzÀeuâH¡P(г¢G¼‡_ñâÅÍž)“&M2š7oné­2hÐ ³nTT”ñúë¯;d gΜƄ ŒÄÄD‡çºv횥§^Ê’?~cÖ¬Y–}V®\™êÄ ,0 Ã0–/_nY߯_¿4¯ýÊ•+Æ•+Wn»|ù²åx+V¬0<==-ë¦M›fÖ 1Z¶lé´EŠ1–/_nwžøøxK½wÞyǮί¿þj©³aÃsÛ‰',ÛvìØa´k×Îa `ÄÄÄXŽ=nÜ8KQ£F¥yß6oÞlÙ§iӦ涔=üFŽéô;oÛ¶­ÝýOϤ+V¬0 *äð˜^^^Æ®]»¶;!!Áøì³ÏŒlÙ²9Ü×ÝÝÝøðÃÍú~~~©>{ .4ë¶jÕÊ\?~üx»s÷êÕ˲ïªU«Ò¼ÏFTT”ÃmÓ¦M3U¼xqã×_5ÜÜÜÌunnnFHHˆYÆ ÆSO=åôZš4iâ°WçâÅ‹-õvîÜiW§oß¾–ßpr_~ù¥¹­L™2Æ‘#GŒ%JØ?Ož<ÆÜ¹s^ëÙ³g-u“_×£€~ …B¡<¸’å  P( ÅYyœ¿"EŠØýE|óæÍ†aÜ¥ô™gžI5I*¯¼òŠÝynݺeT®\9]û9ÒÜ/½ß»ï¾ëpýýJø¥¼7FXX˜aw‡s–,Y2]×öÕW_YΓٟ——WªçïÓ§åØ)ø?þø#Í{sçÎ#GŽæ>Ù³g7âãã ðül6›åž¥lÏ3Ï>^ááá놡&Mš( @qqqÚ¿¿9 V’vìØ¡•+W¦ëÚbbb4räHsÙËËK3gÎTDD„,X`7!!A|ðeßhúôéær½zõ´cÇ…„„hΜ9–{ôÉ'Ÿ(**JuêÔIõÙKj¼uëVËDކ‘_¸pÁü\¬X1åÊ•+]ל111ŠŠŠRýúõõÉ'Ÿ¨E‹êÙ³§¤»²ôíÛWwîÜ‘t÷»9r¤víÚ¥;wjèСæq¢££Õ·o_%$$dZÛ’»qã†ÂÃÃÕ¯_?;vLçÎÓgŸ}fJüÎ;ï8<µjÕÌÏIÃ{Àß@V'Ž …B¡8+zŒzøIÖaªIâââŒÂ… [z»]»vÍR',,Ì(X° Y§hÑ¢æÐÞ«W¯Zz@U¨PÁnhi`` ¥NõêÕ-ÛÓš´£E‹–ë8sæŒ]ñãÇ9räpZú÷ïoÖMÙÃÏf³9ìÝhéÅÖ°aC#!!ÁRgïÞ½–V­[·6·ev¿üùó›=“lܸÑR§o߾涊+Z¶¥ü^œyî¹ç,û†aßÃ/oÞ¼vS;vÌr?Ú¶mknK­‡_ò^mrÐÛìÈ‘#–íÛ·o7·uíÚÕ\Ÿ3gNãÒ¥K–}¿…ÜU¥ IDATýö[ÃÝÝݨ\¹²Ñ­[7ãðáÃæ¶´&í˜2eJªÏfÊ{R³fM‡÷´yóæ©>ŸÉ{¿¥¼^^^Fll¬Ý1Ço©÷ïÿÛ®Î|`©3uêTs[föð“d¼ð vû:4Í^~ 0·7hÐÀáýË*ôð£P( åÁzø *¤Ø­ß¼y³®^½j.¿ýöÛzòÉ'-u .¬·ÞzË\ ÕéÓ§%I«V­²ô€3fŒræÌiÙ¿\¹rêÓ§¹|äÈݼy3ÝmOÙž»:‰‰‰Š‹‹sZâãã¿yóæjܸ±ÝúåË—Ë0 sù£>²›ˆ¡^½zêܹ³¹¼{÷nË>™©gÏžv“ƒøøøX&D8qâ„ù9{öì–ºÎzê¥fYvÖc­cÇŽ*T¨e]åʕմiSsùÔ©Sé:gÒÄÒÝžv)¿ªU«Z&}Ù³gùùèÑ£æg/^ܲïàÁƒuëÖ-;vLK–,±ô(KË¥K—ÌÏ)'ë¤|ùòYž‰äõ“KíÙŒ‹‹3{é92bÄåÈ‘Ãn}òÉ^ (`é!™dôèÑÊ;·¹¼sçN§çɨáÇۭ{ölº†‘&£¢¢T¼xq»råʳNÒ0ïÄÄDË5§œ Z’<<<ìBèôJàåÏŸßn»»»»%` UTTÔ}Ëg3Ü&¿nooo‡×˜;wnËìÄɃՔ2ú|&“”+WÎ2[sÊß”t74M’ü€k#ð 8 mRþ…>eoº$ÉÿR.I×®]s¸¿£^PŽöOoo3IªQ£†eù¯¿þ²«Ó¿;vÌ,~~~é>~Ê^sI’÷\”dy__rÎîMJ·nݲ[w/÷!ùûМ?yÏÉ”÷mÿþýiž#88X‘‘‘æ²———Ýõ¥%oÞ¼æçÄÄÄt…FÉüÄÄD]¹rÅ®$Ï;'én8˜üøÎîÑýJÞ¥ì1™$=ÏçâÅ‹-Ïç!CÒ݆ô<ŸÎ~w’õùpölJ>“{IräÈa¹oŽÎ‘<¨LLLL÷ùÀãÍ#«€+pV<õÔS–å3gΘ$—r〉U«:Ü?((H¥K—Nsÿ{Vùâ‹/jðàÁfà3cÆ ½÷Þ{*Q¢„Y§`Á‚*X° ¹|/=“²eËæp½£kK>)E’¤ðIºzT©RÅáñ’iIî¥GSpp°ÃõÉò佹|||ôÝwß™ËãÆÓ€,C>>Në&Ÿ¬ÂY{Š+–êù’<ýôÓ:|ø°$©D‰š5kVªõ“ìüùó«páÂæ}L~îäÂÃÆީI>ᇳá¦/¿ü²~ÿýwsù“O>ÑÆ-uRN˜â,Äs$µßî7$¥Þ{3ùó™Úï.3žÏ”×yýúuK0é¨Whò°H‘"é>x¼ÑÀLÞÞI)gàM²|ùró³Íf3g+MÏþ†ahÅŠær¾|ù,Á@ò¡ÆŽ‚ºB… ©C‡ærLLŒ†®ØØX‡m•¤}ûö9Ý–’³{S³fM˲£k‹×êÕ«Íå²eË*Ož<’î‰É‡: TRΤ›š5kÖØ½ë-**Ê23o¹råÌÏ:t°„–!!!zçwœö¢Z¿~½æÌ™cY7hÐ §íùßÿþgw¬¸¸8íÝ»×\N@¦&y»ÃÂÂÔ´iS=ÿüófiݺµŠ)¢æÍ›ëùçŸWýúõÍúɇ¼îÚµËî¹Ø¿¿ .¬"EЍE‹–g1­g/åp]G^|ñEË÷¼iÓ&ýôÓON¯566VN·§ä,NþÛ;uê”° '8p eø_ò!‡111º}û¶];'Mšd©·lÙ25hÐ@k×®5{!]¼xQ«V­R«V­ôúë¯;¹öœÝ›Ž;ZÎùõ×_kÆ æòíÛ·5|øpË;ïRdÉ{û8pÀ2¤òرciödKîÔ©S?~¼eÝØ±ca.·iÓÆüœ={v}ôÑG–úßÿ½Z¶l©­[·êÖ­[JLLÔÑ£Gõþûï«]»v–@±S§NjРÓö\ºtÉn¢ˆÏ>ûÌ0=÷Üs麶®]»šŸôñÇ[¶OžüðCó}t·oß6{-^½zU[¶l±¼ç/ù÷m÷ì%,SöVKòÄOh„ –uýû÷×k¯½¦¿þúK±±±JLLT@@€f̘¡J•*9 Öqö|öêÕËü|çÎõïßß2¡Í¹sç4pà@s9{öì–És’zé&IþlKÒ”)S,½Ó2}útËï<""B~ø¡åüÏ>û¬Ý~—/_vÚ&à²zš` …B¡PœI‹$’ OOOãQS¼xq#©}Ý»wwZoãÆ†»»»YW’Q½zu£GFÕªU-ë‹-j\½zÕ²ÿ¢E‹,u$õë×7zôèa”/_Þ²¾B… Ftt´eÿ#FXêÔ©SǨS§Žñßÿþ×RoîܹvíL*yòäq¸^’a³ÙŒ)S¦˜Ç¹|ù²eû—_~éôÞLž<ÙR×ÍÍÍxæ™gŒîÝ»¥K—¶lkР‘`Ù¿_¿~–:U«V5>úè#£OŸ>Æ“O>i×Ö 6˜ûž8qÂáõ4mÚÔxóÍ7 XÖ—-[Öˆ·»†>}ú8<Ž›››‘+W.‡ÛÊ”)c÷=GDD8¬[«V-cøðáFË–--ë‹/nܼyÓÜÿâÅ‹–íãÆ³¿Y³f–ííÛ·7&Nœhôë×ÏÈ;·å¸ÉŸ¡˜˜£L™2–}«U«f¼üòËF¡B…,ë6lh9ç[o½e÷ìÕ®]Û˜7oža†qþüyËö3gÎ8}Vºvíêðþ¸»»9sætú|*TÈ8|ø°yœiÓ¦Y¶?~ÜáùŒV­ZYêæÏŸßxá…ŒöíÛùòå³lûì³ÏìŽQ°`AK^xÁøüó϶mÛÚýÖòçÏoÙ÷Ë/¿´»–\¹r]ºt1ú÷ïo”,YÒ²møðá¯ÃÛÛÛ¬3uêT§÷7+8p å5ö1ÿöP( …â %Ë@¡P(г" ü Ã0fΜiäÈ‘Ãi(!É(Y²¤±eˇûñÅNø¤R¡BÃßßßnßÕ«W;¬ï( øßÿþg.\8Õó¤ ØÖ­[g9ƽ~†a#GŽLó<µjÕ2íö ° ’J¶lÙŒ)S¦XÖ¥ø5kÖÌÈž=»ÃcåÍ›×X¿~½Ãö'&&o¿ývºïÙ3ÏeÊ”1Fm.§øµnÝÚ鱪U«f„……Ù?**ÊòçÆ¾}ûœ^kV ð£P( åÁ†ôðôïß_{÷îU·nÝ,³fJw'FèÛ·¯Ž9¢æÍ›;ÜôèÑÚºu«:tè ëœ[ Ôˆ#tðàA»÷âIRÛ¶m5tèP˺ìÙ³Û½¯N’Z·n­€€?Þéð¿‚ êå—_ÖŠ+tøðát+uf„ Z·nZµje7¼²hÑ¢;v¬þüóO‡ï'óööÖï¿ÿ®ªU«šÃ˜ÝÜÜT³fM­ZµJ½{÷Nw;^|ñEmÙ²EeÊ”±¬¯_¿¾öîÝkΛœ›››¾ùæíÝ»W]»vu8ë®›››4h ùóçkÛ¶m*V¬XšíiÓ¦¶oß®úõë[îKÕªUµsçNùúúZê§ü>SÞËråÊiß¾}zýõ×Í÷ &±Ùlzá…´}ûv5kÖÌ®--Z´Ð‘#GôÒK/Ù=¿Ù²eÓo¼¡½{÷Z†óJÒóÏ?Ÿæ³—üùÙ´i“ݹ“·ñ“O>‘¿¿¿† æt’Š+jذa:|ø°–/_n¯Š)¢íÛ·küøñ–wJwﯷ··V­Z¥Ÿ~úÉîw)Ý‚>aÂË» sçέ:h×®]ª\¹rºÛ2sæLM˜0Á2IKöìÙõúë¯kÏž='*ù믿Ì÷@–-[Vµk×N÷ùÀãÍfFV·‡l6Û"IÝ¥»³p:›EõqsëÖ-:uJaaaªX±¢]À”–ÈÈH*22R•+WNw ®#Gލ@ªR¥ŠÓÉ Ržëüùóºxñ¢òå˧%J¨L™2Nß{–Qááá Ô­[·TµjUKP’–«W¯êÈ‘#ªY³¦ž|òÉ4ëŸÕ«WOÒÝYfS›7¹;wîèÊ•+:wîœ"""T¤H•*U*]aêý Rpp°rçέjÕªÙ§©9yò¤ÂÃÃU¯^½týö¾úê+=Ú\Nš¥7!!A‡V||¼j×®ítRI3fŒ¾øâ IÒG}d÷îÆ¬æïïŸ2„ìkÆì,j.ÅþŸ"À•7oÞ õ´)P €Ü‹B… 9|©Zç*P €eöÑ©P¡BN{n¥¥páÂjÑ¢E¦´ÃÓÓÓ2Óñ½ðððPµjÕ2¥I ,˜æµíܹӲì,”¤œ9sªN:÷ÕwwwU®\ùžz§¥öÝÔ­[WuëÖÕ¾}û¤   yyy¥yL777•(QB%J”Hw;2ÊËË+]ms$eÁûåáá‘î??Ö®]+énïÈ{éí  éxŒ;V%K–T¿~ý,ëS ü5Ç7?Ï™3' [â:ÌY};wîl™¸>?€ÇضmÛbYW¾|yËðÞG]¯^½Ì^k3fÌÐíÛ·³¸E¿ï¾ûNÒÝw,þç?ÿÉâÖ€‡Àà1e†Ù‹+IÁ‚µbÅ åÍ›7‹ZuïÜÜÜ4iÒ$IÒ¥K—ôË/¿dq‹oš;w®$iðàÁ™6œ<>x‡ø[*[¶¬‚‚‚Ìå{œãQ±~ýzs‡† ªV­ZrwwÏêfݳæÍ›ë§Ÿ~Rhh¨råÊ•ÕÍÉrƒÖ+¯¼b.ßËl×/_Ö‡~(I8p`¦· <ú˜¥ðÈrÕYzÌÒ ÀƒÄ^À…ø.„Àp!~€ !ð\àBüBฬn©È•ô!&&FkÖ¬Éʶ2QPPPÊU¹Õ÷ÎfFV·‡l6ÛYI¥³º€‡â'Ã0úgu#p é\àBx‡àQ¨ÿÒ[¨P!?>‹›È,çÎÓG}”|Õɬj ®†wøY6›m‘¤î’äéé©ààà¬m Óøûû«víÚÉWõ5 cv5—Â^À…ø.„Àp!~€ !ð\àBüîÓ† ôÛo¿eu3)7oÞÔÚµk*I:r䈶lÙrOǘ3gަOŸž®ã>|X[·nÍX£\ p9§NÒW_}¥ðððzž~ýú©GºsçÎ=ÏãäÌ™3j×®vîÜ)Iš0a‚^{íµtï©7ß|S111·ª]»vÚ½{·$iüøñêÓ§O†Û àJü.#&&FþþþJLLÌê¦ ‹hôèÑ { çùþûïõã?ÊÍÿK•Y~þùgI"ÄȬn™% @õêÕSXX˜ .œÕÍÁß@ûöí³º œ H’òçÏoþïO<‘®} ÃÐ÷߯^½z™ÇÉÌãü]ðÏÑ8qéÒ%ùúújáÂ…vÛîܹ£W_}U_~ùe†Î±xñbùúúêöíÛúý÷ßÕ½{wU¬XQíÛ·×áÇî®ñãÇ«mÛ¶ªX±¢jÖ¬©!C†(""ÂRoÛ¶mòõõÕÅ‹õÖ[o©AƒZ¿~½bcc5dȵlÙRûöísxŽyóæ©C‡ªP¡‚Úµk§+V¤y-‹-RçÎuþüù{¿©ˆŠŠÒ¤I“Ô¢E •/_^µjÕÒË/¿¬Õ«WÛÕõ÷÷—¯¯¯ù½ :T¾¾¾òõõÕ·ß~ëðø¿ýö›|}}­õë׫[·nªZµªúöí«C‡ÙÕÿàƒÌcúúúêý÷ßwÚö¹sç꥗^’$ýúë¯êÖ­›*V¬¨Ž;êøñã÷ Ó!CT­Z55mÚTï¼óކ®víÚ©K—.iÞ/g¢££uüøñTˉ'îûøIž~úieË–MeÊ”‘$•)SFåÊ•K×¾ëׯW`` † â´NéÒ¥åááq_Çø» ‡N”(QB§OŸÖ¸qãÔ£G˶-[¶háÂ…zþùç3tŽãÇkåÊ•úç?ÿ©åË—«K—.jÖ¬™æÏŸ¯:èÔ©SÊ‘#‡eŸ¦M›*GŽjÕª•š6mª+Vèûï¿W@@€6mÚdÖ»pá‚V®\©øøxåÍ›WAAAêß¿¿š5k¦;wîÈßß_ƒÖŸþi9þ°aÃ4eÊU©REÝ»wײeËôÒK/iüøñ5j”Ók™1c†6mÚ¤.]º¨wïÞº/Inß¾­¶mÛêÀêÔ©“:v쨘˜=zTöÿ7ÆÍÍM9sæT¶lÙ$IÙ³gWΜ9%É\—ÒéÓ§µråJýñÇêÖ­›ªU«¦R¥JiÅŠúôÓOíê'?æÆÍÉ#9räˆüüü4|øpùùù©k×®Ê;·-Z¤ãÇëØ±c–눊ŠR“&M«Áƒ+""B3gÎT||¼¨§Ÿ~:ý7/…;vè¹çžKµN¶lÙßçî~mÛ¶5ÛZ½zõt³Ÿ:uªš5k¦5j8­ãîî®¶mÛªT©Ræñm6[†Ú àr àP( å‘,’I2$žžžFJÑÑÑÆÌ2þ|C’±iÓ&ËúÄÄD»}Ók„ †$ãÈ‘#–õýúõ3òåËgDEEÝ÷± Ã0>þøcC’ѦMãÚµkæú>øÀdlÛ¶ÍnŸË—/Û­ëÞ½»!É0×-X°Àd 0À0 Ãøðà Iưaà Ã0ŒwÞyÇÈ™3§qûömsŸ­[·’Œ1cƘë ___#wîÜÆ… œ^Ë©S§ŒY³f±±±÷pR·}ûvC’1eÊ”{ÚÏÏÏÏd;v,ͺ'N4$EŠ1üüüÌõéynš7on4nÜØéöwÞyÇd¼ð Æ7Ìõ#GŽ4$ûöí³ÔÿùçŸ IÆêÕ«ÍuIß›£ïý^ÄÄÄAAA©–ààà ##‚‚‚ 777cÑ¢EYÖ<\0’þŒÿ¿ÒÇxþÛC¡P(Š+zø[IïìKÉÇÇDz|óæMåÍ›÷¾ÎÑ«W/½÷Þ{š7ož9L4..NË–-S÷îÝ•;wîû:nJ~~~–c5nÜX’tùòe»ºÅг[×¹sg-^¼Xòöö¶lKYºti»åØØX;wNeË–•$}÷ÝwÊž=»† fîïææ¦AƒÉÏÏO+W®Ô›o¾éðÊ—/¯òå˧ûšÓ#{öì’¤‹/*!!Áa¯¾ÌòÍ7ߨsçÎærfMÄa³Ù´råJËñœ}¿!!!²ÙljÚ´©¹®Q£F’¤'N8üîÓ+gΜæ0ØGÑ?ü bÅŠ™C pÿü­š5kêÒ¥KæòÁƒõüóÏëèÑ£*X° ¹>Ož<÷}Ž¢E‹ª}ûöZ°`¾øâ Ùl6­^½Zׯ_Wß¾}3ÔþäR†K%K–”$%$$8Ý'44TÇŽSPPüýý%Ý}O[JIÓîCÒrRÀ˜üÇ—‡‡‡%ø’î¾³0iûÃT¯^=uíÚU_~ù¥æÏŸ¯nݺ©[·njذa¦Ÿ«cÇŽ™~LéîýNùý>õÔS’d7ÔµI“&2 C¿üò‹9K­ŸŸŸ²gÏ®ªU«f¨:xð`ªuÜÜÜÔ¼yó ç~ÄÅÅé§Ÿ~Ò!Cœ½@úø[*^¼¸¹|ñâEIwCºÌœ¥·oß¾zñŵmÛ65oÞ\óçÏW¥J•Ô¤I“L;ǽصk—Þ{ï=mÛ¶MO>ù¤Ê–-›i³”†††ªL™2òõõµÛöÒK/e8tºW6›MK–,ѯ¿þª… jæÌ™š0a‚êÖ­« ¨bÅŠµ=Z³fÍôÊ+¯¨ÿþZµj•®^½ª­[·jüøñ*T¨P†Žý×_=”wøÝÅ‹ëúõë4hÐC?7€+"ð :tP‘"E4oÞ<Õ¨QC«W¯Ö'Ÿ|’%m ÖóÏ?¯Š+j×®]æpOÕ®];ÃÇ/[¶¬bbbRyöa³Ùlêܹ³:wî¬[·niúôéúâ‹/Ôºuk={Ö¥&lpww×È‘#µbÅ ÅÆÆªzõêúè£ì†©ßV­Z)&&&Õ:Yu/§Nª—^zI%J”È’ó¸?Ò-[6õìÙS³gÏV­Zµ”i³ÐÞ«ßÿ]7nÜЄ ̰O’Nž<™)ǯS§Ž~øá:t(Õ™R³JÞ¼y5jÔ(ÅÅÅi̘1 –———]½¤à***êa71CvíÚ¥6mÚhîܹzùå—3õØI3?jþúë/ýùçŸ?~|V7ÀedÎÛ¨x$½Ó/£CéÓ§"##õ¯ýKÏ?ÿ|–õDÊ‘#‡$éÀ’$Ã0´téRó}‚ŽÞáw/ÆŒ£ÓîÝ»eFV71ÓM:U5jÔP³fͲº).ƒ~—‘ò~™©fÍšª]»¶8©“uÜ«_|Q}ô‘FŒ¡Ù³g+44T7nÜÐâŋթS'ó=†÷«D‰š7ožúôé£êÕ«ËÇÇGO<ñ„Ž9¢€€>}Úœp"¥>ø@[¶l‘···9áDF………©dÉ’ªY³¦5j¤ÐÐPíØ±C!!!š5k–9‹oJ¥K—VÍš5õþûïkóæÍº~ýºöï߯¥K—ªC‡™Ò¶á7ÞÐæÍ›5uêT»mcÇŽÕðáÃ5yòä,hÙƒ®E‹¹Ô5< Ü?þøã¬n}òÉ']%U“¤ hĈYÚžòåË«lÙ²êׯŸÜÝÝ3å˜6›MžžžjÑ¢…e&W›Í¦W®\êׯŸ<==¡Ž;jâĉzæ™g”#GÕ©SGåË—7Q´hQµhÑByóæµ,'ÍÐ[¼xq˲$UªTI½{÷Vîܹ©ððpyzzjðàÁªW¯žÓ­|ùò*R¤ˆú÷ï¯\¹reÊý1 CÅ‹Wtt´.\¸ =ûì³úé§ŸÔºuëT÷õõõÕÓO?­àà`yzzê­·ÞR»víÎ[ªT)ùøøÈÃãÞþ-Ôf³©V­ZªS§ŽÓ:åʕӳÏ>k·>þüòññ±ôH=þ¼æÌ™£·ß~[ ,Ї~¨O?ýTƒÖ¡C‡´fÍ5*Óž¿¬öÇèòåËúòË/™÷oèòåËš>}zòU+?þøcÿ¬j®ÄæŠCC®Áf³-’Ô]’<==œµ °víÚ)_¾|Z²d‰Ý¶¡C‡jÅŠîÅ <*L6Ô×0ŒÙYÔ\ ïðxDDEEiïÞ½–÷ &&&júôéš1c†† –…­Àã‚wøþ6mÚ¤ƒ¦»~Ë–-ïi–Ú7êСCé®ßªU+U¯^=Ýõñ÷0qâDµoß^ÅŠSÆ •'O9rDQQQúüóÏõî»ïfuð ðü-lß¾]óçÏOwý¢E‹ÞSà·mÛ6-\¸0Ý—mb€ IDATõ‹/Nà;uëÖÕÙ³gõÛo¿éÔ©SÊ–-›  çž{NùòåËêæà1Á;ü,Þᮋwøðàð?À…ø.„Àp!~€ !ð\àBüBà¸?¥ ÃÈê&ào‚g ü]xduÀƒ­©S§jΜ9êܹ³þýïgi{~úé'-Y²Dû÷ï×Í›7UºtiùøøhĈòööÎÒ¶ÁµDFFjÔ¨QÚºu«.\¸ Zµj©W¯^2dˆ]Ý &hÕªUÚ´iÓ}«U«V²Ùlúã?2Úì‡êúõëúæ›o´téRõïß_#GŽ|(爈H¹ê9›ÍV࡜ìí6 cwV7È,~¸¸yóæéwÞÑ•+Wd†4hem¹s玆ªiÓ¦©yóæ6l˜ž|òI9sFK—.UãÆ üiÂÂÂÔªU+‘Dà—Á^€Ë8}ú´|}}uóæÍ¬nÊ#eÞ¼yjÚ´©:¤ìÙ³gi[üüü4mÚ41B›7oÖØ±c5lØ0Mœ8QgÏžU¯^½²´}p-“'OÖáÇ5kÖ,͘1C£FÒºuëäëë«/¾øBAAA™z¾íÛ·k×®]™zÌû‘˜˜¨¸¸¸t až={¶:wî¬={ö<„–€‡…~—©•+W*..NùòåKµîåË—µsçNåÉ“G5’a:uê”BCCÕ¡CKÝððp?^ÕªU“‡‡‡Ž?®C‡©^½z*[¶¬Ããß¼ySþþþ:wîœ*Uª¤êÕ«+GŽvõ®\¹¢K—.©V­Z–õ±±±:~ü¸Ê”)£þÿ·Ë—/ëòå˪Y³¦bbbô×_éÊ•+jذ¡J—.í°-óæÍSáÂ…S½ŽDGGëĉª]»ö=ïëÌ×_­ è‹/¾Íf³lswww¸ÏÕ«WuáÂU¯^]îîî ÐÑ£GU¿~}•)SÆá>7nÜ¿¿¿ÎŸ?¯Ê•+«zõêÃÎK—.),,L5jÔ°¬ŽŽÖÉ“'U¶lYåÏŸßR?44T5kÖTTT”öîÝ««W¯ªQ£F*UªÔ=Þ¬sáÂíÞ½[O>ù¤6l¨øøx:uJׯ_×sÏ=g©ªÕ¬YS6›M‡Ö‰'Ô A§ÏÜõë×uàÀ]¼xQUªTQµjÕ”-[6»z/^TDD„ªU«fYëÖ-ª\¹r–ßòÅ‹®5jèÖ­[úóÏ?¡Æë©§ž²#!!A³fÍ’···^}õUs½ÍfÓÇ,???Íœ9S_|ñ…Ãk¸uë–öìÙ£ÈÈH5jÔH%K–tXïäÉ“ŠŽŽ6—óå˧råÊ9¬›$66VЙ3gT©R%Õ®]ÛéóŸäܹs PDD„ªV­*ooo»{©àà`;wN’täÈ]¾|Y’T¤H‡×°víZ.\X‘‘‘©ž<^ü;3fÌЈ#äáá¡„„ÅÆÆš=aÊ—/oøýòË/8p ‚ƒƒ5pà@­_¿Þܶ|ùr½ôÒK–ú«W¯Ök¯½¦ððpåÊ•K111ªP¡‚–,YbìýðÃúøãízâ$m .Ô+¯¼b©ÿÉ'ŸhÍš5êÙ³§¢££'777M›6M´»Þû û$é•W^Ño¿ý¦¥K—ªk×®÷uŒä®\¹¢Ý»w«o߾ʕ+Wº÷[°`Þzë-…„„èÕW_ÕæÍ›Ím«W¯Vûöí-õW¬X¡×_]‘‘‘æý÷ööÖ’%K삥ɓ'kòäÉŠ‰‰±¬?tè7n,???uîÜÙ\?iÒ$}ýõ×Z³fºw﮸¸8ÅÅÅÉÃÃC?þø£^{íµ{¸#VGޱbš’»»»æÏŸßç¤ñãÇk̘1æ½¹}û¶ùüÕ¯_ß.ð›={¶Þ{ï=]½zU:uÒÎ;Ím6lPË–--õ—,Y¢èÆæ9ªW¯®%K–¨råÊ–º_ýµæÌ™c÷.·¿þúK>>>Z»v­Ú¶mk®7nœfΜ©Å‹ëÕW_5¿Ù²eÓܹs-¿•cÇŽéÒ¥KêÓ§Ý=¨Y³¦Ê”)£ 68¼G+W®TÏž=•˜˜¨ØØXyxxhÆŒêÛ·¯]ÝW_}Uûöí3—[·n­ÿýï+IûöíS·nݤlÙ²éöíÛzöÙgµhÑ"/^Ü®~xx¸†® Èf³)wîÜŠŠŠRΜ9uùòe=ñÄfÝuëÖYîA›6mÌÏo½õ–&MšdwüûýóáY,éý¬n€¿’Žgu#€…Àð·rìØ1 2D={öÔ”)S”˜˜¨Þ½{kõêÕ:{ö¬¥7WJ#FŒPDD„V¬X¡%JhÇŽzá…,uöï߯Ž;ªyóæš;w®J—.-uëÖM>>>:}ú´ ,˜áëøÇ?þ¡Ù³g«mÛ¶ºv횺uë¦Aƒ©Q£Fv½ÕîWÆ uðàA•/_>SŽwñâEIJ³÷“3o¾ù¦âââ´råJ-ZT;wî´„A’´k×.½ôÒKjÓ¦fÍš¥’%KšKÒýOí;NÃ0ôÚk¯iáÂ…jݺµBCCÕ¥KõíÛW5R¥J•îë¸îîîÊ™3gªuÜÜ2ö6–={öèÝwßÕСCõŸÿüGÑÑÑêÖ­›þüóO={Öa/Ô$ýû÷—‡‡‡V­Z¥'Ÿ|R{öì‘¥ÎæÍ›Õ½{wµoß^?þø£Š/®={ö¨[·njÙ²¥N:¥¾$3FcƌɔcIRHHˆ¤»í½‘‘‘úã?”;wnIR£FìêŒ=Z ÔŠ+Ì`©^½zZ´h‘5j¤ñãÇëÓO?½Ï+øÿÖ¬Y£ºuëJ’J•*¥yóæ©R¥JúöÛo5uêÔû:¦···fÏžá¶¥fãÆ’¤÷Þ{O¹råR®\¹4`ÀmÚ´IgÏžU:uœî­uëÖ™¿¥&MšØÕyï½÷T¬X1-_¾Ü¬×¨Q#Í›7O-Z´ÐäÉ“õ¯ý+C×àææ¦µkךÁ¶§§§æÎ«êÕ«ë»ï¾Ó„ $ýÿÀÏY¶B… içκsçŽ%Huww׺uëT½zuIR™2eôßÿþW5jÔÐwß}g÷ýzzzšŸÓ l?ûì3ݸqCÓ¦MS½zõ$Ý ROž<©¯¿þZ»wï¶<×?üðƒöïßoד¸lÙ²_'/_>U®\Ùü•/_Þa¯Aàúü­lÙ²Yþ2›ô¬¢E‹Zþâü]qE‹•$]»vÍ\.)í¿¬ÿøãfØçÈ;w´yófõèÑîçM­ZµT·n]3pɨ¤° IéÒ¥Õ¨Q£ Í0ú Ý¾}[’,alpp°~øás¹xñâ1b„ÃýgÍše†}ŽÄÇÇkûöí0`€]/²† ªjÕªÚ¸qc†?›Íf†}I*T¨ Úµk?Ò÷_ºûü†¡ÈÈHó}nIÏj½û¤»C{SûDEEéÏ?ÿÔˆ#ìê=ûì³*W®œ6nܘáÀ/{öìv½X«U«¦*UªXî||¼$ÉÃÃñÿÝÍ–-›”˜˜h üräÈa†}IªW¯.ooo8p Cmß¼y³J•*¥«W¯jíÚµæú¤÷îÛ·Ïø­ZµJåË—·{m€«pÐÛöáM €‹#ð<¶J—.m ‹öíÛ§_~ùEãÇwÚ«ÇÇÇG… Ò˜1c4iÒ$ÅÄÄè믿VåÊ•U¿~ýTÏ—2ÄKéüùóŠ‹‹s:„°bÅŠZ½zuWuÿ¼¼¼´råÊvüŒJšÔ"©§Ÿtwˆã¢E‹$ݤbÅŠN¿ä“—8¤ÄÄD§©T¬XQ;v츟¦§‹———¶lÙrßûïß¿_#GŽLµŽ»»»Ó÷Î¥GÛ¶m•/_>5JãÇWXX˜¾ýö[Õ«WOÞÞÞ©î›Öý ”¤TïÿÑ£Gï¯áéàå奃šËIÿh¦®‚ :œLÄ‘2eÊèðáÃjc`` âããåëëk·-GŽædIŽ=j÷ÞOW⠷h®ˆÀð·R°`AýñÇjÚ´©9|±aÆZºt©ÃY\ïEÒþ ·ß¾};ÃçHMlll†ßö =ýôÓ’d 5êׯ¯àà`IÒ3Ï<£›7oÞ÷ñ“îmRO”ÆýO­hZžx⠇Ô“Ëè;üJ•*¥µk×ÊÇÇÇìÅÖ¢E Íž=;ÃÇ~Ôî±bÅ$ÝYÙ‘Ë—/ßÓpט˜˜4CÏ´äÎ[:u2Cî´¸»»›=îào%66V'N”———fΜ©råÊ©H‘"™rì%J(Ož< øìÝwX××ðï "¨€bTPEcAÀ±Ä£ØÀ’¨±Å–Ø¢K4F%b= ÖXˆØ{°¢ˆRD–zÞ?üí¼Œ», è"žÏóÜçÉΜ¹sæ2¬ñxçŽÒý·nÝB½zõ”î{ýúu‰Šuo¿Õx3¨°ÙUåA5`mmýû÷cùòåÂ#ØeÅÂÂÚÚÚ¸{÷®Â¾üü|„††*Œ¿D"ANN²³³‹]Œ""‘èQq¸sçN¡³;‹ÃÆÆ .|çã‹#-- K—.E“&M°zõjXYY¡zõêeÒ·µµ5444”Ž^^ÂÂÂÖ”H$ÉdÈËË+öý ìÞÏÏÏÇÝ»wѤIa[ýúõ¡§§‡cÇŽaúô颸§OŸâÎ;4hP±úÏËËÃÝ»w•®ÛYvvvVX7°0 4@HH²²²Š|äš1Æc¬ ÒýS.cŒ1VŽ˜˜˜`äÈ‘ÐÕÕ-4fë֭ضmþùç|þùç011!//¯LrèÑ£<(ÌZ“;tè>|¨ð(Ÿ¼Ø,lËËËÃúõëUžgîܹ¢Ï'OžÄ½{÷Þ\Z¯^½*³¾$ &Mš„ØØØ2{±HAšššèÖ­üýý…7ËàÉ“'JÇ?//Oô(hnn.6nܨò\o¯xäÈ?!ËcŒ}²äÿJÍ7nܸ•·`@–––T‚‚‚HSS“¾¾>ikkÒÒÒ¢:ÐñãÇŽñóó#ÿQQQT¹re²±±¡ÀÀ@züø1íØ±ƒ ÈÆÆ†233Eñ—.]"äææFgΜ¡ƒRûöí©Aƒ€vîÜ)ŠŸ3g 3f ]»vöìÙCÆÆÆdddDIII 9mÞ¼™Ö¬YCkÖ¬!MMMjݺµð922²Ðk>|8I¥R:qâD‘×]\äääD‰„&NœHÇŽ£ÀÀ@šûŒ¦NJ!!!´eËÒ××§Úµk“L&SyýÍ›7§Ž;º?//Z¶lIššš4sæL ¥‡Ò¾}ûhÁ‚JéÚµ+ ‘#GÒåË—éÉ“'tôèQZ°`Âý,÷êÕ+ÒÖÖ& ¦£G’¯¯/={öL!Vþ]°téR@Â6eñeéÆ$ÿŽÿ_ó¡rðg7nÜ> Ò[ßA¾êΉ·²ljO€7nܸq+¬½‚Ñ´iÓ¨F4räHš8q"͘1ƒFMÆÆÆdoo¯_’‚Ñ›¢‡½½½ð?‰„<==)!!Aiüرc…X}}}š9s&«,øEDDPË–-…ã,,,èÖ­[Jû755}û/ÕBûçŸ ½Ž÷Qð#"ÊÌ̤#F¾¾¾(—N:ѵk×âKRð#" &[[[ÑøwïÞ]i1”ˆhĈB¬Í;—.^¼XhÁO"‘Ptt45oÞ\8ÎÊÊŠnß¾]òÁPƒ‘#G’¹¹9=Z¸ÿ¿ùæ244¤6mÚ(Ä—¤àGDtåʲ±±ïÞ½)%%Eiü Aƒ„XCCCZ°`PˆUVðÓÕÕ¥‡R“&M„ãêÕ«GíûùçŸáëë‹”””"ßÈ[/^¼À“'OP¯^=¨Œ}þü9`oo-­Â—ØõõõÅܹs!“É ££ƒ¸¸8¼zõ õë×/u¾Ê¼zõªÈÜß!22ééé¨[·n©^x¡L||<ž>} [[Û"ûŽERRìííU®%÷ã?bñâÅÈÏÏðæÃ¯_¿.tmÆòæ—_~ÁÆqõêU…7YOš4 «V­ÂëׯUÞƒÅ‡ØØXØÙÙ¹>åÓ§O‘’’{{{•kÛM˜0ëÖ­CFF†p\VVlllŠÌ'11OŸ>EÆ ‹½^ãÓ§O‘]ìµ15j„Ï>û EÆæææâáÇHKKƒ©©)êÔ©£2žˆðäÉ$''£aÆÅZÓ/;;aaa033C­ZµŠu ÒÍ›7áèèXpÓP"Ú¬¦tcŸ‰DR @fMs‰ÈWMé0Væø¥Œ1Æ>)»wƒƒB±O&“áøñãppp(“bðfMÁâ¾ÄÌÌ fff%>GÍš5Kô¦Ñ’z_Å>àÍš~uëÖ}oýרQCxSkQ>ûì3…{¢8ÊcE•Ý»wÃÙÙY¡Ø—––†Ó§O£U«VeRìJvoš››ÃÜܼÄç(É1ÆÆÆ ×]–ýË xo° ¥¥¥… »‰DKKKXZZûmmm4oÞ¼ØñŒ1Æ«8¸àÇcì“Ò¹sgüôÓO7nºu놪U«",, K—.EBBΞ=«î{o:wK—ÂÂÂÐÓÓÃ7°téRÈd2ìÝ»WÝ)~Tnß¾;w¢^½z8qâ²³³Ñºuku§ÅcŒ1Æ?ÆcŸ–Y³f¡ZµjX»v-Ö¯_‰D+++ôîÝãÇ/öŒ<Æ>F¿ýöÌḬ̀qãF,_¾R©ÖÖÖðññÁwß}‡ªU«ª;Åʽ{÷pøðaܽ{=z4† ¦î´cŒ1Æx ?Æcå×ûXÃ1ÆXùÀkø1ÆÔ‰×ðc]á«"3ÆcŒ1ÆcŒ1Æ>:\ðcŒ1ÆcŒ1Æc¬á‚cŒ1ÆcŒ1ÆcücŒ1ÆcŒ1Æ«@¸àÇcŒ1ÆcŒ1ÆXÂ?ÆcŒ1ÆcŒ1Æ*.ø1ÆcŒ1ÆcŒ1VpÁ1ÆcŒ1ÆcŒ± „ ~Œ1ÆcŒ1ÆcŒU ZêN€1ÆcïOBBvî܉‹/">>5kÖ„››¾ùæhjjª-¯ððp:t!!!HMME:uàîîŽ>}ú@[[[my±Š‡ˆ°mÛ6œ={111pttDŸ>}àää¤{ðàA\¿~?ÿüó;Ë×׉sæÌ)mÚDLL vî܉«W¯"11æææèܹ3 ‰D¢îôcŒ1V <Ã1Æ«À®]»†‰'",, UªTÁáÇ1zôh´k×999jÉéŸþ³³3,X€¤¤$T©R×®]À°|ùrµäÄ*¦ÜÜ\øøø`È!8sæ ¤R)Ö­[:tH!þøñãXºté;ŸÏÏÏýõWiR.AAAðôôDJJJ‘qÓ§OÇÇQ¹reÀÛÛ=zô} lcŒ1ö>ð ?ÆcFBBvìØQ£F¡R¥JêN§\hß¾=Q­Z5@ff&Ƈ 6`åÊ•˜8qâÍçúõëèß¿?Úµk‡ÀÈÈHØwïÞ=˜˜˜|Ð|XÅæçç‡-[¶`„ X¶l 55­[·Fÿþý+ºKëòåËå¢P‹ÀÀ@dee©ŒûꫯЧOÒÒÒàíí}ûöaçÎ0`À‡H—1ÆcïÏðcŒ1VaÄÄÄ`âĉHOO/V|rr2^¿~-Ú–˜˜XäqD„„„„"ãrssû^ ÙÙÙxñâE¡ûuuu…bŸüóÔ©SçÏŸ/²ÿüüüÒ'YÀo¿ý©TŠ={ö(ZìììP½zõbå¤êšårrrÞûøgeeëž)‘™™)|&"$%%y\~~~±®Y>þï“L&S™óÚµkQ£F üþûïÂ6CCC,[¶ غuk©ú›……,--‹›——Wâû3##ÏŸ?/v|QªT©"ûäŸ'Mš8wî\™‡1ÆcücŒ}r®^½ŠfÍš¡zõê022BóæÍÑ´iSèëë£U«V ñÿý7ŒŒŒ‡ à³Ï>C5`aaÿþûO!>&&Ð×ׇ¹¹9 àíí´´4…ØE‹)a###ìÝ»W´}áÂ…022Btt4<==a``SSSÔ«WÁÁÁźþ:uê@*•¹†ß´iÓP½zu\¹r¥XýåÕ«WØ»w/ºtéSSÓb·víZáåË—˜;w.jÕªSSSÔ©S§OŸVˆŽŽF‡„ñ744Ĉ#Š»0wî\Ô¬YSaûõë×add„#GŽˆ¶ûúú¢Zµjˆ‰‰AÇŽa``ØÙÙ!44´Ø×¤Lll,þúë/•ÍÏϯTç€3gÎÀÞÞ&&&044D‹-ààà===|ñÅ ñ+V¬€‘‘2331sæLÔ¬Y&&&°¶¶ÆÅ‹â#""àêê ===˜››ÃÈÈ£Gå¦OŸŽ:uê(l?þ<ŒŒŒpâÄ ÑöiÓ¦ÁÌÌ ÑÑÑpssƒŒÑ¨Q#ܽ{W!ÐÐPôìÙSá^wwwGõêÕ tŒ=zWWW¡ÿ àÖ­[JcÝÝÝadd$´ž={*“KMM…—— „ûsÖ¬YÈËË+ô˜uëÖÁÚÚúúú¨U«ªU«777¤¦¦ŠâöíÛ###|÷Ýw[[[!¯éÓ§«ÌK®^½z Ö5>cŒ1VzüH/cŒ±OJBBºté‚zõêáôéÓÈÊÊÂÔ©SqûömüóÏ?044T8&;;©©©X·n–-[†ÁƒÃÌÌ .\PXø?..ŽŽŽJ¥ðóóƒ.^¼ˆ3fàÆ†T*âe2™Â_Ú7³RSS‘-Ú.oÓ¦ úõë‡iÓ¦áùóç˜4iÜÝÝñðáCÔ¨QCå„„„ ''­[·V‰—/_">>^e\qÅÄÄ //M5Ùäo IDATš4)ÑqYYYHMMÅŸþ‰U«VaРA055ÅÅ‹ѬY3…s8::B__6l@½zõpîÜ9Ìš5 7oÞÄ•+WD…ŒÌÌL¥ãŸ››‹ÔÔT…u333‘’’‚V­ZaÀ€˜={6ž[·nèèèRç¦ÊÁƒ‘žžŽßÿ](ÖEEEa„ hÔ¨êÖ­[è±ÿý7.]º¤ò~˜1crrrpöìYÔ®]À›ñ711Áˆ#Êd]¸üü|,^¼¸¸¸ÀØØݺuúuë0yòd ÕÆÆÆJû©^½:nܸ¡°=//K–,ò”÷ß½{w¬^½?ýô“(¾wïÞ¯_¿^eî+W®ÄÝ»wqàÀ|ùå—€Ž;BCC«W¯Æøñã…û±yófÌ›7Ot_µhÑBiÿ666°±±®®.þþûo 2Dé VUŽ?¸¹¹•è8ÆcŒ•/üH/cŒ±Ö‹/D;ÊÏÛ¼y³h{ÁYZòÿ®U«–°M>#®¨µº¶nݪ²Ø»víB·nÝâz÷î kkkìÚµ«ø¨Âˆ#DŸÛ´i{{{¥ôôéS,^¼cÆŒ½½½ÊX--­2+öÀË—/@4+)%%ÇŽÚ…  =~çÎ*‹}D„Ý»w£wïÞ¢¢ xyyÁÜܼLÆ_"‘( ;tè?~¼Ôý¿O999H$¢¢kqïÿ={ö¨¼rrr€~ýú Å>9ooo˜ššbçÎ¥ÈþJ•* Å>¹.]ºÀÜÜ\tÿËgêëë+íGOO™™™ /¶¨\¹²BQ²k×®077Ç©S§J•ûîÝ»annŽ:@&“ ÍËË ¹¹¹8{ö¬(~ݺu055Å?üPªó×íÛ·áç營~úIôÉcŒ±ÏðcŒ1öѲ··Ç½{÷„ÏÁÁÁprrÂ¥K— ÕÓ¡Chhhà÷ßÇŠ+››‹Õ«WÃÈÈÎÎÎ*Ïgkk«rBB^½z¥´&‘HФI¥kž•• (]Ó® ñãÇ£zõê˜?þ{Ë£0ò"SÁž„‡‡‹Ökܸq¡káÙÙÙ©ì?&&2™Léøkhh qãÆ¸sçλ¤^, 4ÀÍ›7ßùøGáï¿ÿV£¡¡Ù³g¿ó9:vì"Â’%K0þ|dffbíÚµ¨Y³f‘Z5þ=B^^žÒñ×ÒÒ‚½½=>|øÎ¹«"‘H`kk‹¨¨(a›üÏ)))Jyùò%ôõõ¡££S¬þëׯ_ê˜<@JJJ¡EÈû÷ï‹>‡‡‡ÃÁÁÚÚÚ¥:oqäççcÔ¨QhРÁ+02Æcìýá‚cŒ±OJ:u°qãF 6 ûöíCNNòóó±uëV¥ë—•„|í®Ê•++ݯ§§§r}¯ÒÒÑÑQXs®   ((çÎ+´àð>Ég}ÉíÞ<îüèÑ#ÀW_}¥°faI¨{üµµµUŽQ’’’pìØ1•1ššš¥*ø5iÒ+V¬Àøñã±}ûv¤¥¥AGG»wïF¥J•Þ¹_@ýãÿöý/Ÿ¹XØ…Kô¸«T*­?ø.²²²àéé‰9sæ(Ýÿv>/_¾,rVqY™4i9ùùù066ÆÄ‰accWW×½5¶0J¥Bëm÷ïß/t´ÜÜ\hi•îåÈÈÈB¹Ü¹s'|}}ñï¿ÿ–ø¥e¥V­Z044Äÿý‡E‹xS¤‘¿¥UGG§T?+++hhh”xüóòò@D¥.æDEEÁÚÚúwrr^˜ð>I$Ô¨Q“&M‚µµ5\]]^Dñ.äQ6 ®°ñûÅïêíñ·¶¶†††Î;‡ &ˆb_½z…°°0¸»»»ÿÈÈÈRý|7³„“““ñùçŸ+ÞÎηoß.Õ9‹cÅŠX¿~=NŸ>]êkdŒ1ÆXùÀkø1Æ«0*Uª[[[•…³Ã‡cĈ8rä¦M›†¾}û–I±x3ûªeË–Ø»w¯ÂL¦ððp\¿~mÚ´m—Ï´ m Qy®ƒŠ>GDDàÊ•+ ýÀÙ³g1tèP¬[·®Èu¼OR©£FÂÍ›7qéÒ¥2ï¿R¥JhÖ¬öìÙ™L&ÚŒ°°0…ñ©R¥ rrr¥Tö29"¡C‡DÛîܹƒ›7o–û7›nÛ¶ “'OÆ™3g0eÊôîÝ»LŠ}```€Fa×®] 3/\¸€‡*ÿôôt…"¡ªñ—Éd kU†„„àÞ½{¢ñ755Å_|£G"99Y€¬¬,¥/pÉÈÈPX‹ñÊ•+ˆˆˆ€‹‹K¡y‡»»;®^½ª°V_a:t耇b÷îÝ%:üà‚Ïfÿþý˜:u*þùç•/-bŒ1ÆØÇ… ~Œ1Æ* ùš~FFF…ÆÄÅÅ!??ƒF÷îÝááá777¸»»ã—_~ÁãÇK•Ãüùó‘’’‚E¿ØØXxyyAOOOá ®ò—PlذéééHII¯¯/Ƨò<#GŽ„¿¿?rss/¼Ä`üøñ¢¸û÷ï£gÏžðòò‚³³3îÝ»'´·‹\o[³f Z¶lYd\IL˜0FFFèÚµ+<ˆŒŒ ¤§§ãßÿElll©ûŸ?>0xð`dddž[·n%---@•*U"T¿~}züø±BìØ±cIOOöíÛGÚÚÚ¢þ~'“’’⣣£©C‡€ôôôÈÚÚš444H"‘УG =Oûöí U«V455ISSSáw gÏž…~?Ô¬Y³Èë-­7n¼}^*öpãÆíÓh*½õä«îœ¸q+Ë&!¢2/"2ÆceA"‘ìÐ,--Ký†L  Aƒ°mÛ6tìØQ´oÊ”)Xºt)RRR`hh(lþü9î߿֭[ûm™111¸|ù2ž~ü—/_ÆÓ§Oagg‡¶mÛŠ~¦effbïÞ½HJJB«V­Ð¼ys¤§§#$$7=òúã?bñâÅÈÏÏGBBNœ8ôôt´oß^Xî<Û¿?F…}ûö¡U«V¢}#FŒÀÖ­[ñúõkÑ8ÇÄÄ 22íÚµƒ†Fñyôè®\¹‚ØØX4lØmÚ´Qú³ÞÌVÛ·oRRRкuk4kÖ ©©©¸yó&š4i"ú½™0a‚03>>AAAÉdhß¾}¡ëWo^séÒ%ÄÄÄÀÑÑ-Z´Púr‘ˆˆ<þ...ˆ‹‹CPP²³³Ñ¾}{a­IUall¬ðHðÛrrrpéÒ%ܾ}iii055…»»;,-- =æÚµk¸sç’““ѤI4oÞ¼Ð{Zîúõë8sæ ÌÌÌо}{…—‚„‡‡úR…{¤¬Ý¼yŽŽŽ7 %¢Íïõ¤Œ1ö?‰¤€Ì›æ‘¯šÒa¬ÌqÁ1ÆX¹õ> ~;w†žžöíÛ§°oÈ!8yò$ž>}Zêó¼/ ~:::êNç“S°à÷1rvv†½½=6mÚ¤°ïË/¿DTT”Âz’åIÁ‚_ydhh¯¹÷©â‚cL¸àÇ*:^Ã1ÆØ'ÅÜÜGŽA@@€°žUdd$† †;v`Ù²ejα÷ÇÜÜ{öìÁ‘#G™ùæï8wïÞEÿþýˆßÿ]Í~\^¼x   <~ü~~~xõê6l¨î´cŒ1ÆP¼çbcŒ± bÙ²eHOOG¿~ý ‘H •J‘™™‰¶mÛâØ±c ù2V‘¬[·>>>èÖ­¤R)444 “ÉбcGœ!‰‰‰xõꕺÓäææB&“!//OÝ©°O@ll,._¾ŒŒŒŒBcrss‘••õÎ爋‹C||ü;¯n HOOWwŒ1Æ+\ðcŒ1Æ>!!!èÚµ+LLLðý÷ß«;@×®]¡««‹)S¦¨;Vݸq666077G«V­```€¾}ûB&“)ÄNœ8Õ«Wçs999ÁÉÉ©4é–‰ÄÄD\¾|999ÅŠ?wî\]]Q£F üúë¯ï9;ÆcŒ}\ðcŒ1Æ*¸©S§¢yóæ …†Fùø£?%%'Ož„††öîÝ«îtX777dggcûöí¸~ý:|}}€/¿ü²ÌÏ7hÐ ôïß¿Ìû-©Ã‡£U«VHJJ*2ÖÇÇíÚµCttôûOŒ1ÆcLùø¿~Æc¬ ܼyFFFHNNVw*åJrr2V®\‰ˆˆhii©;À››‹±cÇâÉ“'¸zõªºSbÐÒ¥K‘žžŽÿý @óæÍ1kÖ,̘1ÇÇ¥K—Êô| .Äâŋ˴Ï÷-==›7oFpp°ºSaŒ1ÆX*ÿ×ÏcŒ•¼¼<¤¦¦"??¿ÈØü÷ßˆŠŠ‚®®.Z·n~ýú) GPP¾ùæ¼~ý{÷îEhh(œœœàéé‰Zµj‰âÃÂÂpâÄ |ûí·HOOÇÞ½{'''xxx(Ä@pp0þûï?}£GFJJ öî݋۷o£E‹ðôôDÍš5UÆàÎ;…ÆÀ•+WpâÄ  3gÎ(į]»–Ð7ÈÜÜ\öìY…øŽ;’T*UÈg̘1¢¸œœ255¥®]»*ôJhÉ’%*¯][[›†ª2FÎÝÝЦM›Š_\©©©¤­­M&LÎcmm]hüòåË …††RÍš5ISS“ôôô]¹rE!~Ù²e€ÂÂÂâ¯^½ªïââBÚÚÚÔ¼ysrww'---@“'OÅÉd2222¢>}ú(ôqåÊ@+W®,épÎ;G5jÔPÙjÕªõÎýåççÓçŸNÚÚÚäääDnnn¤©©IhÚ´iJY´h ;w iiiQåÊ• ݺuK!~áÂ…B¼±±±(>44T!gggÒÑÑQÈgæÌ™¢ØôôtÒ××§*œóÌ™3€Ö¯_/l &4þ|¥×Õ¨Q#jÒ¤‰hÛØ±cIWW—&MšD•*U¢öíÛ“µµ5 ¥ýtîÜYøùhiiQÇŽ•ÆÉ­Y³†´µµI[[›Ú´iCdjjJ—.]RéÒ%ª[·.éèè³³3uéÒ…j×®Mzzz”‘‘!ŠÝ¿?Õ¨Qƒ ™˜˜¹Íš5Ke^)))*ïƒ÷áÆ$ÿŽÿ_ó¡rðg7nÜ> Ò[ßA¾êΉ·²ljO€7nܸq+¬½¯‚ßÿýGqqqÂçÄÄDrqq!‰DBÑÑÑ ñò‚Ÿ=š)77—‚ƒƒ•ö//øÙÙÙÑwß}Wd|`` ÅÇÇ‹òiݺ5ihhPLLŒ(vâĉ¤¥¥E ¢í?þø#iii‰®K™’ü:DC† ¡gÏž+¾¸¶mÛFèôéÓDôÿ½¥ñòývvv4aÂJNN¦œœœBÇS^ð³³³£ï¿ÿ¾Èø£GŠî™„„rrr"---ÑÏ…ˆhôèѤ££C/_¾mŸ0aéèèPrrr±ÇámÏž=£M›6©lÿý÷;÷/wäÈJLL>ÇÅÅQÓ¦MI*•RRR’B¼¼àgggG?ü𥤤Pvvv¡?/yÁÏÎÎŽ~üñGzùò¥ÊøÃ‡‹ÎG¤­­M)))¢XÒÓÓ£ôôtÑö‘#G’žž½zõJÔ¯ª‚u‡ÈÄÄD´mìØ±€Z·nM©©©DD”——G ¤´/¹æÍ›«,øÝºu‹444hðàÁÂïð½{÷¨^½zdggG999¢ø—/_R5¨Q£Ftÿþ}Ѿ·Ç  Í›7zþü¹Ê| â‚7nÜ>µÆ?n½©=nܸqãÆ­°VTÁïÆdhh(4ù,:ÑvU1–[½z5 #GŽ(ì“üFŽYd?Dÿ_ð+lÆ`qüù矀EÛoݺEèÏ?ÿ¶åçç“……õèÑ£È~KRð{_zõêEÆÆÆ”››KDDÑÑÑJgtÉÉ ~òE‘ü¾ÿþûwÎqñâÅJgpÊgòœI–››K5kÖ¤~ýú½óùÔmþüù€.^¼¨°O^ðûñNj՗¼àWšÂѼyó”Îà”ÏäÛ¶m›°-;;›ªU«FÞÞÞ¢Ø7:xð Òs|ýõ×$‘H(;;[Ø6vìXÒÖÖV(èÆÅÅ‘¶¶v‘?㢠~}ûö%MMMzòä‰hûï¿ÿNèßÿmŸ6mijjÒÍ›7Už÷m\ðãÆ·¢ü¸UôÆkø1ÆûhYZZ⯿þ>GEEaæÌ™X²d‰°nèèèÚGnn.ž>}*Ä«záǬY³J”_Iã‹“ƒƒ±}ûvŒ;pþüy´×¯_ãØ±c°²²½ÜÀÐÐþþþøå—_ =öCŒNNž>} CCCoÞ&\³³34h€mÛ¶aøðဓ'O"..C‡-ñùÔ-''111…^oAjücbb`dd¤4X[[cÛ¶m8p àØ±cHNNVùÚv¹¹¹…žKCCššš¢íR©T¹5j qãÆxðàA‰¯© 7nÀÄÄ«V­mOLLܹs]»v¶_¸p k2ÆcŒ… ~Œ1Æ>ZU«VEÿþý…ÏÁÁÁ˜9s&zõêccãBKJJŠ+àïï‡"''EžOC£d/·/n|bb¢ODDD‘ùøøø`„ ˆŒŒ„ ¶oßSSSQ¡ ¼:|ø0233ƒ…  Ûsrrpÿþ}„‡‡£Q£FJ}»0S”âÆÇÇÇcÅŠØ»w/"""››[äøO›6 OŸ>…¹¹9¶oßssstêÔ©Dù½íĉèØ±£ÊMMÍB XŇeË–aÿþýˆŒŒ,òz ž»$Šÿüùs!Ÿ¨¨(•ùH$x{{ãçŸFBBLMM±}ûvØØØ ]»v¢Ø5jøÿbÚÛ’’’`bbRìßS333ܸq£X±…yüø1jÖ¬‰›7o*ìóððýCÜ¿...¥:'cŒ1Æ>M\ðcŒ1öIIKKƒ››âââ0iÒ$téÒÖÖÖxüø17nüÁóIMM…««+^¼x!äcee…¨¨(4mÚTé1 À”)S°}ûvLŸ>{öìÁСC¡¥UþÿX€‘‘ •J…í÷ï߇ -ø½ÉÉÉpqqAZZ&MšOOOXYY!<<¼Ð··<3fÌÀŽ;0~üxìÝ»ãÇ/qAømŽŽŽ8zô¨Ê‰DRªs$&&¢M›6Éd˜„„„´iÓÙÙÙB>VVV¸víÜÝÝ•3dÈøúúb×®]6l<ˆ3f(ŒümÌ=RÚ¼øV\‰‰‰033+v¼25kÖDÓ¦MqðàÁbÅ#!!¡TçdŒ1ÆØ§©üÿÍ€1Æ+CGExx¸è‘@àÍ£¦êpäÈܹs1Ì- IDAT;wîÍVT•±±1ºuë†íÛ·ÃÑÑQéãŒåQff&>Œ^½z‰Š}`kk‹† ÂßßsæÌù`98p>ľ}ûгgOa{FFF¡Ç˜™™¡sçÎØ¶m¬¬¬––ŸRçR­Z5xzz–ºU…ÿýW4#TÕõ¾OþþþxôèŽ=*ºvUùÔ©SnnnضmªV­Š¬¬,x{{+Ä5hП}ööïßùóç‹öݼy?F¿~ýŽËÍÍEFF*W®,l{õêÂÃÃñå—_¾ËeŠrº|ù2ÒÒÒfó)coo#GŽ ..®DÅI¹¼¼¼wI“1µ‘H$~LÕûd¼ý/uý%‰òmeìýXBDçßWç\ðcŒ1VaX[[cçÎ*OLJJèêê Û¢££1`Ào-ý”å%# ËÇÇÇ=zôÀôéÓÑ¢E ØÛÛ—yn FçÎKü8§2xýú5zõê¥t¯^½ð믿âÁƒ¨_¿~©ÏWòÇ= w?ÿü3vìØ!üŽçççÃ××5j”ÂqYYY6lvíÚ%l[²d ÒÓÓ•KbÊ”)èܹ3¦L™‚5kÖ9+tÊ”)ÀÈ‘#±{÷n•ë‘daa¸zõj¡¿oŒ•SXª; öɲý_cìCÙUtÈ»+ݳ'Œ1ÆX9"_ÓO¾X¿2¨\¹2Fމï¿ÿ @“&MЧOoÖû<==¡««‹o¾ù'N„——š6mо}ûªÌ§K—.055ÅíÛ·…bMaFމþýû£ÿþÈÍÍÅéÓ§…Ï—/_.ô¸Áƒ£K—.¢ÂGiøûûCWW·ÐYl½{÷â>”®]»B[[C† Áäɓѯ_?4oÞ^^^ ÿ=z jÕªÅÿò¤[·nJ¥8p ¦L™‚¾}ûÂÙÙY(†}èû¿{÷îÐÒÒ‚——¦L™‚¯¾ú -[¶ Þ…åÓ§OèëëãÎ;*g·N˜0Íš5ðaÃ0|øpÌŸ?îîî8pà|}}aee¥pŒŽŽž={gggüñÇ>|8æÍ›‡: sçÎ¥ºÞN:aܸqðó󃻻;þüóO¬_¿ßÿ=<==ÖgtvvÆÜ¹sqèÐ!´lÙ‹-ÂÖ­[1mÚ4xzz"==]éy\\\`dd„)S¦`éÒ¥˜6mÜÝÝqçÎ…Xùwü%4¶Ý½{·T×ËcŒ1õá~Œ1Æ>)ÖÖÖ Š+°uëV4mÚË—/‡îÞ½«ô1;333¸ºº{vMIâëÖ­+ä³eË8::båÊ•2dÂÃᯯ¯ô8---ôéÓ›7o= ¬LBB‚ð¶Sù âââ2™¬Ðãzö쉗/_ÂÉÉ©Èë( !-- cÆŒÍæ*¨Y³fèÑ£‡›œ¹¹9\]]‹½FaIâ6lˆãÇãÏ?ÿÄæÍ›Ñ¼ysøùùÁËË 7nÜ(4WôèÑþþþBqöcààà€ÀÀ@¬\¹›6m‚““6lØ€¯¾ú ÁÁÁ¢™¦rpuu-ö…%‰oÚ´©ÏæÍ›áää„M›6¡W¯^F¥J•”§§§‡nݺ!00Pô(öÛªU«†Ó§OcÚ´i8{ö,àèèˆõë× ®‚êÕ«www`úôéXµj²³³ñí·ßbùòåźþ¢¬X±®®®Xµj~ù夥¥ÁÔÔíÛ·GFF†Â åY³f¡]»vX´hþúë/$''£I“&hÑ¢d2™Òï---lÙ²k׮Ŝ9s„ÇЕÍ~.øûæêê*Úö¡g|2VP•*UP«V-u§ÁceF&“áñãÇì|"ú`'cŒ1ÆJB"‘ìÐ,--­Þ„Ê‘¼¼<4hÐ...ذaƒºÓùädgg£~ýúèÚµ+V­Z¥ît>92™ 666ðòòÂ’%KÔŽ nݺ°µµÅáÇÕÊGáæÍ›ptt,¸i(mVS:¬H$Ñøß#½ýúõ+³æŒ1V\¾|ùí—ÂyÑ{û¢ãGzcŒ±Ðúõë‰ &¨;•OÒš5k‹qãÆ©;•OÒ²e˘˜ˆ1cƨ;AFF=zôN/×`Œ1Æ+küH/cŒ1ö‘¸ví/^ŒÄÄDœ:u Ó§O‡ƒƒƒºÓúd\¸p+V¬@\\Ξ=‹¹sçÂÎÎNÝi}2N:???ÄÄÄàÂ… X´h‘Ò5ø>¤ãÇcîܹ¨W¯Î;‡üü|~IcŒ1ÆÊ.ø1Æc --- 77§N‚›››ºSú¤hkkCOO&&&8wîÚ¶m«î”>)•*UB¥J•P«V-\ºt Ÿþ¹ºS‚¾¾>Z´h;wî Y³f˜?>ºuë¦î´cŒ1ƸàÇcŒ},ä/`êÑ¢E ´hÑBÝi|²Zµjõöº7jWsbŒ1Æx ?ÆcŒ1ÆcŒ1Æ*.ø1ÆcŒ1ÆcŒ1VpÁ1ÆcŒ1ÆcŒ± „ ~Œ1ÆcŒ1ÆcŒU üÒÆc…øøxxzzª; Æce$--MÝ)0ÆcücŒ}d2ÕcŒ1ÆcŒ•{\ðcŒ1ÆcŒ1ÆX±¤¦¦" ‡FDD •Jabb‚Ï?ÿ=zô@çÎ =þúõëX¿~½ðyÚ´i¨S§N±ÏñâElÙ²Eø<{ölÔªUë®å}HKKC•*UŠŒ‹ÅرcADÐ××ǶmÛŠ<æË/¿ÄÙ³gáää„   Bãrrr0qâDäææ ÛfÍšssóâ]ÄG*22NNN "¬Zµ TwJjÅ?ÆcåY,€ûêN‚±ȶÀgx¤®DSƒTu'ÀSíÔ©SðööFLLŒÂ¾˜˜„„„`õêÕhÞ¼9üüüЬY3…¸‡bíÚµÂç¡C‡–¨àwïÞ=ÑñcÆŒ)¿ÄÄDÌš5 999ذaC‘ñ³fÍÂþýûFFFEÆûûûãСC€¨Œ=xð V­Z%ÚfjjŠŸþ¹Èó|ÌlllàèèˆS§NaÊ”)èÞ½; Ô–ÚpÁ1ÆX¹ED“LVwŒ}(‰$€äcˆÈNù0Æcr¿þú+~úé'Q‘±ÁÁÁpqqÁîݻѵk×z­\¹³gÏFJJ ¼½½‹ŒÿóÏ?±yóæb÷/“É0qâDoŠƒ^^^*ã Π”Û°aæÌ™MMÍbŸ÷c4zôhœ:u qqq˜3gþøãu§¤6\ðcŒ1ÆcŒ1ÆX¡Nœ8¡Pì«[·.zôè¦M›"##!!!صkRSßLÖÍÈÈ€——®_¿Žúõë—Y.–––èÑ£‡ð¹<Ìàš:u*d2Y‘q999˜7oæÍ›W¢þ·nÝŠ§OŸ† ]]ÝBccbbðßÿ)löì:„ž={–èÜ›ž={¢FˆÇš5k0cÆ ˜˜˜¨;-µà‚cŒ1ÆcŒ1Æ”JMMÅСCEÅ>ooo¬Zµ zzz¢Ø3f`РA8wî€7ëÙM˜0G-³|:tè€:”YáàÁƒ˜3gnݺUâc—.]*|îÓ§ÊøM›6!?? ‘H •J‘ ðóó«ð?©TŠž={bíÚµÈÊÊ‚ŸŸfΜ©î´Ô‚ ~Œ1ÆcŒ1ÆXd2vìØóçÏãùóçxýú5 „gÊ^T±k×.DGGš7o777¬X±§NBÆ ñÅ_ÀÝ݇FXX ~ýúèÝ»7Ö®]‹#GŽ N:ððð€§§'444¼™­µaÃÜ»wÏŸ?¢I“&ðòò‚­íÿ/ {ñâEœ={Vøüõ×_ÃÚÚZ!× .…:6lLMM±~ýzÑš}ŽŽŽØ´i$‰B€••^¿~ 8vìBCCáààPèØ¾zõ [·nÅ¥K—ðòåK8::¢k×®øüóÏbCCCqäÈáóðáÕÎàº~ý:‚œœ4mÚNNNèÞ½»ÒÜ ÄåË—Žääd˜››£iÓ¦1b„è…¸qã†èåaaaX¸p!`âĉÐÑÑÁÙ³gE…6©T SSSÄÆÆªÌNŸ>{÷îªU«†6mÚ›ŸŸ7 Ÿ]\\`jj !ßÇÃÒÒRéñÿþû/ÂÃö¶¶BáìèÑ£¨S§<==ááásçÎáÒ¥K€ZµjaÈ!رcöîÝ‹êÕ«ÃÓÓ]»v…¶¶6 )) 7nDhh(ž?ŽÜÜ\¡Aƒøúë¯áèè(¿Ã‡ Ÿ»té¢ôÞ¹uë–¨\ð¾–ç kÖ¬Á?þ-­O°üEDܸqãÆ7nÜÊA€þ×"Ô7nܸ}È Zþد_?*Onß¾MfffTà;Z¡7ŽòòòDÇuèÐAØ?vìX:t¨è˜¶mÛ‘···°­gÏžôÓO?‰â¬¬¬„>ׯ_OR©´Ð;çÆ7nܸqãVüÆ?nܸ}Ê­¼ü^¿~MµjÕR(FI$…âÅ‚ DÇ,¢™˜˜(į^½šˆÄ?ccc…¸3fÑ›ÂÑÛ…,---¥Å²°°0!F)-ʽxñ‚455…˜É“' û k´µµŠšÊœ8qB”Ï_|!ì{»àWp•]˼yóD}Uðûæ›o”ŽGÁÏ”‘‘!:.++‹lmmUðPåÊ•)88˜ˆJVð«U«ÍŸ?Ÿ^¾|IDT삟ƒƒƒ·téR•ãÞ·o_!VGG‡RRR(''‡LMM…ífff ET¹‚E4e÷á¬Y³ˆH\ð«ZµªèÞ@>>>DDNÚÚÚEÞ¯€¸Ü¹sgQñP&“)ü¬ …e…Ж-[ û}}}UŽÛ‡ò¡ ~oæ3ÆcŒ1ÆcLÁ–-[ðìÙ3áóÒ¥K‘œœŒ¤¤$ìß¿_ôÖÓ   Bûyñâ455áåå…éÓ§ÃÎÎ}ûöUˆKLL|ùå—˜={65j„~ûí7a}6###œ9sˆŠŠÂ?ü ô‘ŸŸ“'O Ÿ‡*ü÷£GpýúuÑ9÷íÛ‡¼¼<áó!CéééÈÌ̶׮][x¬XÑç‚ã÷6"¸qã€ôôtüóÏ?Ð××öÿöÛoxñâE‘çÞ¼xÆ ÂçöíÛã¿ÿþC||<,X ¼àãÉ“'X½zµèØeË–áþýûÂçáÇãîÝ»xøð!æÎ+lÏÈÈ€¯¯/€7knÚ´ R©TØß¦MlÚ´ ›6mBåÊ•NNNˆŽŽÆôéÓahhX¬k€øøx„†† ŸU=˜˜ˆŸ»wï###hiiaàÀÂöçÏŸãàÁƒEž[~öèÑ?ýô5j¤ôíÀ)))ÈËËCûöíáëë‹-Z÷ëü!¬¨­­C‡!==111øí·ßDýüÝ)x¿¾zõ ÇŽÅ?~\x9 ðÿ÷kAöööJûþ¤¼Ïj"7nܸqãÆ·â7ð ?nܸ} åt†_bb"ѲeËhþüù û >òÚ¸qcѾ‚3ü€7ý*Sp†êÕ«—Ò¸ˆˆÚ·oÍ›78 Ú—œœ,zԷࣛñññ¢}S¦LÛ©S'aŸƒƒƒ°=**J”—«««Ê±’ËÍÍÍܳ°°ö½=ÃÏÍÍMáøåÿÇÞ}‡WQæÿÿBI#„ÐA IhBè_j" TÁ‹"JsqñƒR–]QXwÙum«‚¢K“U”Þ1È"DÐBoÒKh ½Ü¿?ØÌ/‡“ IN2>×5מ™¹gæ=“C„×Þ÷Ü|àÔfêÔ©Ö¾¬zø=ñÄÖöÊ•+›˜˜§ó¾ûî»Öþ *XÃNSSSŸŸŸµ¯U«V.5uïÞÝx{{›æÍ››çž{Î$$$Xû<==­c‡ ’£g”“~Û·owº×sçÎez¾÷ßß©múïÇž={œö=öØcž#}?I¦oß¾¶KßÃ/íy¥¦¦º´;sæŒY¹r¥yóÍ7Í¿ÿýo§}III& À:ǰaì}qqq¦lÙ²Ö¾Aƒ9›þÏKåÊ•­áçé½õÖ[V›råÊeúÜ RA÷ðû ¾µ€œ)W®œḚ̈7nÜÐÖ­[®C‡YÛoݺ•å¹&Nœ˜£kfÖ.$$D!!!N@œ—+WNݺu³Ö5j¤FY3oذA'OžTPPP–×Ïé÷õå—_Îp"”êÕ««zõêêÙ³§µíüùóÚ²e‹ÂÃÃomOÿ}õôôÔÀ­^˜+W®T||¼<==•””äÔCqРAN½lÓ¤Vׯ_WJJJ†íìŒÀ€lüøãZ»v­Ö®]«Ÿ~úÉZ›^V³¿úúúªjÕª9ºVúYvï§7jíÚµZ·n5 pvµ 6Ì üNŸ>­üQ-[¶Ô²eˬYf=<<œ†–+WÎé¿üòKŽê?yò¤ÓúÝçI/£ÐÉÛÛ[VÀxâĉl¯™˜˜¨3gÎXë«V­RåÊ•ÚÜý3;{ö¬êÕ«§£G:m¯V­šËù³ -óKúÀÏÃÃC>>>¶Û±c‡§OàÝÒ÷î“î„Ó?þøc–çž7ož¦L™’éyK”(a½ó0;™}·oÞ¼©>}ú8½OR’Ê–-«¶mÛjÇŽÖ»ïþ³óÿþßÿShh¨öïß/éÎ÷µOŸ>Nß׆ ªQ£F^ÛÓÓÓi=ýwã·¢p|‹(„Þxã §°oàÀZ³fnܸ¡ï¾ûNMš4±öeÝ=<ö^ÚõìÙÓ Ó|||ôç?ÿY»wïVTT”¾þúk/þÿ÷é¹»–âÅ‹kðàÁÖú×_­ëׯkóæÍÖ¶ŒzKuìØÑi=mŠÌ$&&êí·ßvÚÖ¡C‡LÛŸ;w.ÃíiÁ¤”q/À»UªTÉ)¼êÕ«—Õ«,³¥sçÎ’\'Ií4 º}ûv¶u䥊+ZŸããã3¼þ­[·´xñâ\Ÿ;**Êi’»åôûšUÛgŸ}Ö û<<<4aÂíØ±C×®]ÓŠ+`µÍèÏNúáÞ+W®ÔÍ›7­^ª’ôÌ3ÏdZÓÝÏ*§»ø‰U«VYŸ›6mª… ª[·nVÈ‘>ʪ‡_V=´rÒ.**ÊivÝ?üáúÛßþfõž‹Í6J œ={V'NTRR’¤;C Ó¿0ÍÈ‘#Ö/^¬o¾ù&Ãócôç?ÿÙi˜qåÊ•õøãgZÓÚµk]¶ýüóÏNïw Îôø4ªQ£†µ~óæMuíÚÕiiÒ¤‰‚‚‚Ô¥KuíÚÕ ïºzw4éÎìÌ~~~ RÏž=†§7\Ú¬´yáî!É—/_vi³xñb§ž€5kÖTË–-3\îîi9kÖ¬L¯ÓïkfmµqãFk}ذaúç?ÿ©V­ZYN²û³3xð`ëÜ111š0a‚ÕóîáçwKÿNFÿ\Ý]ø‰Ã‡[Ÿ1NÁDdd¤¶mÛf­g5iGN‡‰fÖ.}’k@òñÇ;­gTKýúõÕ¼ysk}öìÙÖç~ýúeøžºfÍš©OŸ>Öº1F}ûöÕ˜1c©äädÅÆÆjûöíêÖ­›Þyç§ã_{í5•*U*Ã{’¤E‹9õÚŠÕ¸qãœÚtéÒ%ÓãÓëׯŸõùûï¿w œ¤;Rݺuåãã£&MšXC†K—.­°°0«ÝÒ¥Kµ~ýzkýêÕ«š:uªŒ1:uê”víÚ¥ÀÀ@kú÷ÅEGGç¨Öœ¸;ę̀çaúἇC7nTDDD†ËO?ýäô~ÂÍ›7gú~ÄÜ kΨí™3gëT[z .tzV}_Ó&›I“þûÚ¹sgU©R%ÓšÒ?«úõëgsöDà@&êÖ­k}Þ½{·†ªåË—ëü£}ôQ%$$Xûó2ìɪIúàƒô·¿ýMK–,Ñ Aƒ4iÒ$§ý™Õ2|øð ·g6ù$ÍŸ?ßå½}~ø¡5j$___ùùù),,Ì)$“¤§žzJ£FÊô¼ÒIžzê)uíÚUcÆŒQÓ¦MõŸÿüÇÚß·o_µnÝ:Ës¤7nœÓ̼=zôÐ /¼ ©S§ª{÷îÚ´i“¤;Ãsk×®í4ÌóÃ?´‚«ÔÔTõèÑC;vT¯^½T­Z5]¹rÅj;jÔ(§áÓéß¹¸nÝ:uéÒEAAAöÈËÚµk; ë½pá‚Óþýû÷;½«¯mÛ¶N½ïæáá¡¡C‡Zëi“wä‡5j8½Ÿ}ö™&Nœ¨eË–iĈ.=G3û¾f4‹³”õ÷U’öìÙc}nÙ²eN˶c K!X$¥J2ÿ[Ž»»–‚\$JûØ¿SX¬\¹Ò¤ûÝì²4iÒÄiýÔ©SÖ±:u²¶‡††fz!C†Xíüýý3m7räÈLëðòò2uëÖµÖ6l˜á9~ýõWãééétìƒ>hRSS³|W®\1aaaY>‹ô˘1cLRR’Ëy¾øâ «§§§ùë_ÿšé9jÔ¨aNž<étüܹsÚDFF:íÿú믇‡G–µ5hÐÀDEE¹Ôöæ›of{l÷îÝMrr²Óq£GΰíO?ý”éóìß¿Ž~æ}ûöµÚ?Þiߘ1cœ®÷¯ý+Óó¤9~ü¸q8Ö1+V4‰‰‰Æcžyæk{@@@¦çxå•Wœ®›a»¬~¶Å‹7 4°Ö+Uªäò\1&))ÉTªTÉéØÒ¥K›˜˜˜,ïó°ÚõÕWÙ>—‚°cÇŽ»ŸÃ“¿Séá@&zöì©Ï>ûÌ©§•$=ðÀZºt©¶lÙâ4iÁÂ… ó­–iÓ¦iܸqN½Ë‡~øaíÞ½[S¦L±¶GFFZ3œ¦çïïï4DWº3Ô5«÷JRùòåµuëV}ùå— sª!žxâ íܹSÓ¦M˰ÍÝþò—¿hÁ‚N=Ó<<<Ô»woýüóÏ.=Öîžmõîá¤ýúõÓŽ;Ô±cG—}ÞÞÞz饗´eË—Ÿ§$Mœ8QÛ¶mSãÆ]Ž­P¡‚Þ{ï=-]ºÔé}’ôÊ+¯¨M›6NÛÊ”)“'“|<úè£ÖçððpësBB‚>ÿüsk½T©Rzê©§²=_HHˆÚ·oo­_¾|Yß~ûí}×™‘É“'kÊ”).CÅ7n¬ˆˆ§Ép¢¢¢œ&Is÷d3Ò^Ÿé{rÞíüùóÖd0ÞÞÞêÚµëýÜF‘å0wþŸ¸™ÃáH•”ö/®Ƙšî¬ ’Ãá8%)P’ú÷ï¯E‹¹· »$&&êøñãºpá‚êÖ­«xÀmµDGGëÈ‘#JHHP£Fäçç—«ãûõë§¥K—ZëÔC=”«sܼySÇŽÓÕ«WU¼xqUÑÁ÷ IDAT¨PAuëÖÍÕì®wûå—_táÂ5mÚ4Ó@'-ôLsáÂ…LßåvõêU;vLÑÑѪZµªBBBäãã“£ZbccuàÀݾ}[ÁÁÁzà\‚¾ôŒ1:}ú´Ž?®Í,œ·nÝRÕªUuûöm+VLW®\qšÝ¶(¸}û¶Ž=ªèèh5jÔ(×õ¿ôÒKš>}ºµ¾iÓ&uêÔ)ÓöóæÍÓˆ#$Ý ³?ûì³{+K/Ü‚ž¥·ð÷Ýc~€ø6BàØ`#~€ø6BàØHqw€$´ÑÑÑŠŒŒtg-§Nœ8q÷¦ùy=‡1&?Ï€r8©’ÿ[=aŒ©éÎz  9Ž$Ñ)ÀoÇtcÌKùur†ô6BàØÝ¥…A´¤r’Ô¤I=ûì³n.òÎéÓ§5mÚ´ô›Næçõü…Ámý/ð«]»¶ÆŽëær ïDDDÜø]ÌÏë1¤°?ÀFü!ðl„À°?ÀFü@¡óË/¿èöíÛî.£PIIIQ||¼µž˜˜¨¤¤$7VT8¥¤¤èÒ¥KNÏ*³vv}ž~ ÇÎ;§]»våë5®_¿®:uê¨oß¾ùz¢fþüùòòòÒùóç%IM›6Õ AƒÜ\UáaŒÑâÅ‹U¿~}U©RE+W®Ì²ýÌ™3åå奫W¯J’êׯ¯aÆD©ùŽÀäØÌ™3õÈ#äë5Ê”)£§žzJO<ñD¾^öaŒQãÆ5`ÀÅÅŹ»·+îîÈ­Ù³gkæÌ™úùçŸÝ] ò‡‡‡¾øâ w—QèxzzºüoÚçß:cŒ|||´~ýz/^\:uÊö˜»Ÿg©R¥ló< üENBB‚nÞ¼™e›åË—ëÖ­[¬%K–è×_U›6mÔµkWùøø8µÿøãÞ¡Ö¤Iµoß>ÃÚ—.]ªäädõïß_‘‘‘Z³fNž<©°°0=óÌ3r8.ÇÄÇÇkþüùÚ³g‚‚‚Ô¬Y3]½zUGUHHˆœ«çWЂ‚‚äíí­råÊI’|ðAeÚ>%%Ek֬Ѯ]»tîÜ9UªTIO>ù¤š7ožaûU«V)::ZO?ý´8 %K–èöíÛÖÏËËË+Óöû÷ï×Ò¥K­öݺus Ï’““µfÍýôÓO:wîœ*W®¬¾}ûªY³fNíÒ¾kÏ>û¬œöEEEéË/¿TûöíÕ¤Ik{±bÅ´}ûvIÒwß}—Í“¼#((HeË–•¯¯¯$)00PÁÁÁ9:¶Ð3ư°°°°°°°°‚ERª$ó¿å¸»ëaaaa)ÈEÒ©´ßýû÷7Ù™>}º©Y³f–mºwïnBCCÍ»ï¾k<==MXX˜)_¾¼‘dÆŽëÒþúõë¦FÆ××ׄ……™–-[IÆáp˜O?ýÔ©íØ±cM¥J•̈#LHHˆ‘dFmž~úik}Ò¤I.×xå•WŒÃá0¾¾¾¦U«VF’©Y³¦9vìX¦÷‘’’bJ—.m$™dûlr*66Ö<òÈ#F’ 1O>ù¤iÓ¦ñ÷÷7ÿýï]ÚO:ÕTªTÉøøø‡Ãa*Uªd-óæÍËðƒ 2µjÕ2[¶l1%J”0^^^¦D‰¦téÒæÖ­[.íkÔ¨aS’y饗2­¿C‡¦yóææõ×_7¥J•2?ü°)[¶¬‘d^}õU—öÑÑѦnݺ¦T©RæÉ'Ÿ4AAAF’)S¦ŒiÔ¨‘yýõ×sñôœM›6Íéyd´ôêÕëžÏŸæâÅ‹¦^½zÖú¸qã\¾›iΞ=kªU«füüüÌÃ?lš5kf$™bÅŠ™o¾ù&Ãcúôéc6lhÖ¯_o<<<Œ···)^¼¸ 0qqq.í{÷îm5jdÖ­[çÔ¾\¹r&>>Þ©íéÓ§MÕªUŸŸŸi×®iÚ´©‘d<<<̲eËœÚFDDIfúôé.×|ûí·$³ÿþLŸÓæÍ›$óÕW_eÚÆcN:eš4ib­¿ð fÑ¢EYs¯vìØaÒý=ÏH`òówj~žœ……………………%ç  ËoyɯÀO’iÑ¢…9{ö¬1æNÈÕ¤ISªT)“œœìrÌÒ¥KMLLŒµ~âÄ S®\9óÀ8µ;v¬‘džþy“’’bZµje‡y饗LJJŠiÒ¤‰yôÑGŽY¾|¹‘d&NœhnÞ¼iŒ1fçΦlÙ²æ±ÇËò^¦NjFaÍ÷jÞ¼yF’Y°`A®Ž›4i’ñõõÍQÛAƒ™òåË›*Uª˜Y³f™¸¸8cöíÛ—í±^^^Ù~’LÛ¶mÍÅ‹1ÆÄÄʇzÈøùù¹´ŸýôÓ,Û§¦¦š%K–80žžž¦aÆ.íëÖ­kZµjå²½aƦE‹™Þ³19üŒ1æöíÛÖ縸¸,Ÿçý(èÀ!½€BoöìÙúãÿh­'&&*!!AþþþÖ¶zõêYCúÒêûï¿·†zyy©C‡Ú½{·¢¢¢TµjU§öO>ù¤Ózpp°ºwï®Ï>ûL7nÜpº^ñâÅõþûï«X±bªS§ŽöîÝ«wÞyGÅŠSíÚµõý÷ß;kÊ”) Ðk¯½f lÞ¼¹¨O>ùD‡Vݺu3¼ÿ±cÇæôQåØåË—%Iaaay~îô®^½ª/¾øB´¶…††æÉ¹ëÔ©£Í›7«dÉ’’$oooµk×N³fÍrùyíÝ»Wµk×VãÆ%I‡C?þ¸þö·¿)..Îe¸jn4hÐ@ 4¸¿›É¡´á§ÒwÎeåîYŽëÖ­«Î;kõêÕJNNVñâ®±PTT”¾ùæõéÓǺF½zõ2½Æ¥K—ôí·ßªwïÞY¶w8.õÔ«WO:uÒ† ”’’â4~èСš8q¢Nœ8¡IÒ©3fdyß¹‘~X¹]Þß'ñ?@СCÍœ9ÓZ_¿~½Ö®]ëô¹ôáN???—ħ…jñññY^3..Îzï™$ýúë¯N×ððð···$©lÙ²*Y²¤À”-[Öi¦Ð””íÛ·O!!!š2eŠÓu¢¢¢$IÌ4ðË}ûöÕ›o¾©¦M›jøðáêÛ·¯Ú´i“á» ïGHHˆSØ——üýý­°/Mf?ßÐÐP­[·NW®\Q… $I?þø£Ê•+w_a_Q«³gϪjÕªJIIÑÍ›7]Þ'ÝyNia_N4hÐÀ ûr#&&FçÎSÕªU•””¤Û·o«L™2ÖþgžyF“&MÒÂ… õÚk¯I’.\(OOÏ|û>Ù  Ð«U«–jÕªe­_½zUÛ¶mÓ€r}®Œ&sHsäȽÿþûÚ°aƒNŸ>-ɹGÕ½:þ¼’’’”ššª={ö¸ìì±Ç2œà#?Õ¬YS;vìÐ믿®¹sçjÚ´iªQ£†ÆŒ£1cÆdùœr#¯ÄìdV÷3Ï<£™3gªW¯^5j”"""´qãF½ûî»÷}Í7ÞxC“&MʲM›6môÃ?Ü÷µrcß¾}zÿý÷õÝwßéìÙ³r8VH™Üþ¼rÓ>22Rï½÷žÂÃÃuîܹ,ë©Zµªºté¢Ï?ÿ\¯½öšŒ1úòË/õä“O:ƒÈºÓîM›6ªZµªþüç?+,,L5jÔЂ ôüóÏß×¹+V¬¨bÅŠ©{÷îš:ujU|ÿzè!-\¸P‰‰‰Z±b…Þzë-7N—.]Ò[o½åîòòÔC=¤9sæ¨oß¾:qâ„‚ƒƒ5gÎ :ô¾Ï=`À5mÚ4Ë6eË–½ïë䯮]»Ô¡CÕªUKÿøÇ?Ô²eKêƒ>Пþô§­EºÓ›²cÇŽªS§ŽÞ|óMµhÑB5jÔÐ{ï½§W^y%Ãc†ªhçÎJJJÒ©S§4{ö쮼h"ð@Ò¿þõ/ݺuK+V¬PÍš5­í±±±÷}nOOOióæÍJMM-ð^oÙ)Y²¤úõë§~ýúéá‡Ö矞eà—’’R€Õåððp9R3fÌÐïÿû<=wpp°‚ƒƒóôœ÷ë“O>QJJŠÖ¬Yãô®Ê˜˜·ÔóñÇË£µkתråÊ9ª§wïÞò÷÷×Â… •””¤ÀÀ@uêÔ© Ê-òüEÎc=¦   <=çµk×T¬X1§‰6oެɓ'K’’’’îëü&LШQ£ôÎ;ïhâĉ¹:öðáúvíZ¾O°‘˜˜¨k×®ÉÏÏ/Ó6>ø âââ´ÿþ<›|£ ¼ûî»*]º´jÖ¬©Ÿ~úI JHHŸŸŸš5k–gC˜ ‹k×®ÉÃÃÃéûœÖ‹Sºÿïs^Ô³lÙ2½óÎ;™ÖSªT) 8P‹-Rjjª^xáÛýœò  È¹û~yáÉ'ŸÔ矮Î;«oß¾úïÿ«íÛ·kĈúðÃuñâEÕ®]ûžÏ?räH­^½Z¯¼òŠvìØ¡=z(99YûöíStt´¾øâ‹ KMMUÛ¶muíÚ5=z4Ïî{×®]š4i’ÂÂÂÔ¸qc=zTk֬ѡC‡4oÞ¼LëÙ³§‡† ¢aÆéøñãÚµk—6lØà4ãiaóì³ÏjÈ!êÒ¥‹Ë¾ªU«ê믿V›6mÜPYþèÓ§V®\©öíÛë‰'žPDD„~þùg1BŸ|ò‰.^¼hMHSPõ¬Y³FíÚµÓã?îTÏŒ3tñâE•/_Þ帡C‡jÆŒr8Y¿>yò¤548mêiÓ¦iéÒ¥’¤O?ýÔV³ðf‡À`K 6Ì0ШZµªÚ·oïòÿ>}úhñâÅš5k–æÎ«6mÚhÕªU ÔÞ½{•ššjµ­Y³¦Úµkç´Þ¶m[k½víÚNëÒÉ V¬X¡3fè‹/¾ÐË/¿¬¤¤$U©REÝ»wÏô>Š+¦þýûëèÑ£ª^½z®ŸCf‡Œ1úøãuãÆ ¨aÆZ½zu–õT­ZUsæÌÑ‚ ôÊ+¯¨FêÙ³g†Ã|zè¡{¾Ü®];§¡Õwkܸ±\¶?ðÀjß¾½Ëì½mÚ´Q½zõÔ¦MuèÐAžžžòôôÔÉ“'5nÜ8M:ÕVß°aÃäáá¡Ù³gkÞ¼y ÓÆåãã£(11Ñå˜ÐÐÐ\½k044TåÊ•ËQÛçž{N%J”ÐìÙ³5þ|………iÓ¦MòôôÔÁƒ3¬G’õ®¿   ,{õ&''ëÒ¥KÖzûöí%ÉÚ–þÏïoÃãî Éáp¤JJ§r“ù¿rÀfÇ)I’Ô¿-Z´È½ÁvBCCÕ¡C}ôÑG.û:vì(cŒ¶lÙRð…!K‡Rhh¨/^¬~ýú¹»œ{¡Ö­[§ß4Гo¿è ×[BòXBB‚Ž9¢%J¸ì[±b…¶oß®¾}ûº¡2d%55U“'OÖƒ>¨Þ½{»»œ"…!½ÀÖJ•*¥Q£FéÃ?ÔáÇծ];%%%iëÖ­ ׸qãôâ‹/º»LüÏ'Ÿ|¢;wê‡~ЩS§´~ýz/N„•ôð¶÷ÁhÅŠªT©’6nܨ={ö¨yóæ:tèÞ}÷]w—‡t«Î;ëèÑ£zä‘GÜ]R‘C< lÏáp¨GêÑ£‡»KA6  ¸»Œ"~€ø6BàØ`#LÚ( Ò>¬\¹Rî¬òTBBÂÝ›jåçõü…WÚ‡ØØX9sƵ@~«Ÿ'gH/`#ôð)úß¿Q½½½U¡B¾v~€• K—.¥ßŸŸ×#ð—$JR¯^½´hÑ"7—y'""B­[·N¿é¿ùy=†ô6BàØ`#~€ø6BàØnôÃ?hâĉº}ûvŽÚúé§:xð Ëö””ýå/Q|||^— ˆ!ðÀvíÚ¥·ß~[111Ù¶:uªÆ¯ääd—}IIIÚºu«ºuë¦[·nåG©Š?@‘³wï^}úé§î.£@-[¶L'NÔŠ+Ô°aC—ýžžžZ±b…¢££5lØ07T ° ð9ÿùÏôü#×Ç%$$èÆÙ¶KJJÒ… r|Þääd9sF±±±Ù¶5ÆèÂ… 2ÆäøüW®\ÑóÏ?¯?üájÛ¶m¦íJ—.­ hùòåZ¸paŽÏ/I©©©¹jŸiÏ'...WÇÅÇÇ+:::ÏÛ'%%ÝS=@QAà°­_|Q¡¡¡JJJÒðáÃU¡B¨~ýú:{ö¬KûóçÏë±Ç“¯¯¯ªU«¦råÊéã?ÎðÜ š7ožZµj%///Ê××W>ú¨.^¼˜á1S§NU@@€uîÉ“'ç(ø{çw”œœ¬É“'gÛ¶Aƒ:t¨^~ùe¥¤¤dÛ^’:vì¨\…kىל9sÔ¢E yzz*00P>>>êÞ½»¢¢¢2<æ¹çžSóæÍ¯Áƒ«|ùò Pƒ tùòe—ö#FŒP‹-2låÊ—zþõ¯©yóæÖÏËÇÇG=zôp9÷W_}%mÚ´Éåšßÿ½üýýõÅ_ÜÇÓòÀ¶bccuóæMýßÿýŸþóŸÿhüøñš4i’š6mªêÕ«;µ½zõªš6mªhâĉZ·nZ·n­Ñ£GkÚ´i.çNNNÖ´iÓÔ½{w­]»Vk×®ÕàÁƒµiÓ&=Ú¥ý¬Y³4~üxµhÑB7nÔìÙ³µmÛ6½ýöÛYÞCrr²>ûì3õìÙSÞÞÞ9ºï'Ÿ|Rçϟ׆ ²m›’’¢_~ùE.\ÈñÄ!9‘˜˜¨>ø@?þ¸Ö¯_¯Õ«WkРAZ»v­ÆŽ›á1i?¯ßýîwúé§Ÿô§?ýI'NT‹-T±bÅLÛ?÷ÜsúùçŸÚW¨PÁ©m||¼¦OŸ®Þ½{kýúõZµj•¨5kÖhüøñNm{ôè¡””ýûßÿv¹æçŸ®¤¤$õêÕë>ž¿Š»»²³wï^…‡‡[ëÛ¶mÓ7œ‚¸òåËkðàÁ.Çž;wNûöíÓ®]»äïïŸé5¦L™¢Ë—/kïÞ½Ö;ò{ì1=üðÚ2eІ ¦2eÊXí}||étŽ®]»êðáÃZ¹r¥RRRäáá!éÎÒüãªW¯žV®\©’%KJ’ºwï®Î;gÚãM’vìØ¡¨¨(uêÔ)«Gä¤C‡’î¼÷¯[·nY¶õððо}û«Ê•+çøÙñóóÓ¾}ûœ¶uïÞ]û÷ï×òåËeŒ‘Ãáp9îØ±cª\¹²víÚ¥Ò¥Kg{£GªR¥JÚ¹sg–íýýý]êéÑ£‡"##µ|ùr§í>>>êׯŸ–,Y¢ØØX+hMLLÔ’%KÔ¯_¿Õ¸ =ü…Þþýû5sæLkÙ¶m›¢££¶}õÕWëãã£åË—göIÒâŋժU+Õ®][ñññÖ2hРݸqC»wïÎQ­={öTRR’Ó°ÞÈÈH={V¿ÿýï­°O’¼¼¼ôÔSOey¾S§NIR®Â8///•-[V'OžÌQ{??¿< û²Ò³gOÅÅŹ ¹Mãïï¯o¿ý6ÇZnÛß­Gº}û¶®]»æ´}èСº}û¶S¸fÍݸqCÇ¿§k…~€Bïé§ŸÖÓO?m­ôÑGúàƒtøðál-[¶l†ÃAÓ‹ŽŽÖ•+WtåÊyyyeØæÈ‘#VϹôΜ9£ððp>|X§OŸÖÞ½{%ÝŠ›æÄ‰’¤:uêd[ïÝΟ?/I ÈÕq+V´Žu§Ó§O[ÏçÌ™3Vpšþù¤—öžÅœªX±b®ÚŸ:uJááá:räˆÎœ9£ÿþ÷¿ÖÓ®];káÂ…8p $iáÂ… V»vír|=Àü¿y ’¤áÇëw¿û]†mjԨᴞššª^xAsæÌQõêÕÕªU+K’<èÔ6mòŒâÅsÿÏ𴞉111¹:.&&F¹¾^^IMMÕÈ‘#õé§Ÿ*00P-[¶Tpp°RRRrÔæµ””ýîw¿Ó‚ ¤-Z(88XIII:räˆK{‡Ã¡!C†èïÿ»®\¹¢R¥JiÕªUzõÕW3Š &~€ß¼Š+Êßß_111jÕªUŽŽ™9s¦fÍš¥·ß~[&LP±bwÞš5kÖ,}ùå—NmÓ&9wî\®kK í®^½š«ã®\¹âÖžh~ø¡æÎ«©S§ê¥—^²žÏôéÓ3~Ÿ¦M›¦ùóçëÃ?ÔèÑ£­ÐnÚ´iZ²dI†Ç<ûì³úë_ÿª¯¾úJ>>>JLLÔСC °jàÞð?@‘S¶lY«7]^騱£¾ýö[?~!00P·nÝÒÌ™3§~øAÝ»wמ={²¬gèÐ¡ŠˆˆÐwß}Çd(2ü¤ 6(99YÍš5SÕªUU±bEUªTI;wvi?aÂ=ôÐCš0a‚|}}Õºuk•(QÂz÷„o¼ñ†Zµj¥áÇ«|ùò*W®œÎž=«É“'g[ÛøñãuôèÑ {ÞíöíÛúè£4bĈlµÝ¶4 IDATg&–î¼ÛnñâÅÚ¸qã= 9ÎÌË/¿¬Úµkk̘1òõõUXX˜üüü¬áÎ=¡ÈĉU«V-=Z¾¾¾jÛ¶­´páÂ,ëéÛ·¯|}}åçç§Þ½{dÉÀ=scÜ]$9ŽTIio?aŒ©éÎz  9ŽS’%©ÿþZ´hQžœ÷È‘#úõ×_sü^>éÎd;vìД””¤*Uª¨K—.™Îô»uëV8p@mÚ´Qƒ ”˜˜¨ˆˆ=ôÐCªT©’SÛ¸¸8…‡‡ëøñãjÕª•š5k¦‹/êøñã S‰%2­k„ Z²d‰öíÛ§Ò¥KgÚnܸqZ±b…"##åãã“£{>|ø°®_¿®6mÚä¨}Ncôý÷ßëðáÃÖ󉋋ÓÎ;U¿~}U¨PÁ©ý¡C‡tëÖ-µhÑ"GçÏm{cŒ¶lÙ¢#GŽ(,,L¡¡¡ŠÕ®]»2¬GºˆÖ¬YS½zõÒ‡~˜£ëw‹ˆˆPëÖ­ÓohŒÉ›_t ð($üü–åWàg'ñññêÚµ«Œ1Z·n¼¼¼\Ú¼ÿþûúûßÿ® 6¨yóæn¨Ò~,X aÆéСCªS§Ž»ËAUÐCz(<==µnÝ:ùùùiãÆ.ûcbb4oÞ={V'OžÔÂ… õꫯêÙgŸ%ìC‘RÜÝ€œñôôÔ²eËäááá²ÏÇÇG{÷îÍprç7ÞÐÌ™3U¼xq 4ˆ¡¼(rü(B² ôûòÆG}¤1cÆÈËËKî.È5?€t<<­_ýUÞÞÞª]»¶»Ë¸g+W®Ô¤I“´oß¾ ÷תUKãÇ׈#2ü»ú©S§œBÁéÓ§kôèÑ9¾þ¡C‡Ô°aCk}îܹ>|x.î ÿDGGë³Ï>Ëô~öìÙ£×^{M›6mR\\œ$©T©R Õ믿®®]»fzî”” ©©©š;w®j×®­3fdØæ³Ï>SÓ¦MµråJ+쓤„„ýüóÏêÖ­›^|ñÅL¯1cÆ íÞ½[’4jÔ¨L¯Ñ¨Q#}þùç†}Ò_-Z´HMš4ѬY³rz‹¶ó /H’Ξ=«×_ÝÍÕ…˜1&׋¤Æ’LÚ2þ|¤wâÄ Óºuk“þ{’ÙÒ¨Q#süøñ ÏÓ¶m[«]³fÍr]G³fͬãÛ¶m{¿·•'bccÍ_þòãååe~üñÇlÛïܹÓ+V̺W_}5Ëöqqq&88ØH2!!!&55Õ¥M||¼;v¬q8Ùþ|Š/n&Ož|Ï÷[ÔݼyÓ”.]ÚH2íÛ·ww9æ‰'žHÿ=8lîá¿,,,,,·Hš–þ¿ß@z={öÌðïyUªT1!!!þ0$$ÄܸqÃé<Çwj3}úô\Õétüܹsóò6ïIÿþý­zêÕ«ç²ïÞ½ÆËËË©îÒ¥K›òåË»<³o¾ùÆåø˜˜S®\9#Éøûû›˜˜—6 ,ÈðçãååeBCC3¼V±bÅÌÒ¥Kóå™v©ÿ{çÕñ¾ý{a©‚€ ‚  €¨À®€‚kDCl±F‰Š5FcýÆc•ØÐØÅÞ{ bCEE, +ˆ" ‚Ô}Þ?üíy÷ì9»¬€¢8ŸëšË=3ÏÌ‘‘‘¨U«f̘ÁTELL :uê¤v¤2AAAˆ 6 ‰„—ž——‡Ö­[cÉ’% ¢BËËÏÏÇßÿ¹sçjìCYÂØØ˜›zöìYìØ±£”=b0 ƒÁМ5kÖàðáÃܵD"ÁøñãñòåK¼xñ>Djj*–-[ÆÛW...Nål´¢âèèˆ7npÁ××·DË/ …Íd\³f ¯ß¾xñb¼|ù‰‰‰Ø½{7Ï6((HãÆHIIüøã‚½ûîÞ½ËÍX“ãíí¨¨(dddàöíÛxõê.^¼ˆúõës62™ “&MÒ¨?_ÖH$èß¿? 77+V¬(eŒ/&ø1J”ÜÜ\ôë×ïÞ½ãâLLLˆK—.áÕ«W¸páfÍšÅÛk!%%~~~ÈÎÎ.Q~ûí7,^¼‹/ÆèÑ£K´ì¢póæM}Š'Ož`Ïž=hذ!/ß”)S§±/e îóüùóKуÁ`0bäåå!**ªØȯ^½BTT”ZåÍ›7ˆŒŒÄ¥K—ðöíÛbÕW^¿~+W® ++K¥Mrr2~ýõW^\HH,X .ÎÄÄ#FŒ@DDôôô¸øíÛ·#::Z#’’’péÒ%¤§§«´Ñ×ׇ››*T¨ Ò–ˆ_¤û,Ï{öìYÜ»wO£vU„††rŸ+W®ŒÑ£GÃÀÀR©?üð¯ÏÉ{vˆ‹/æ®»té"(ÿ?þ@ff&w€ÐÐP¸ººòöÔvwwGhh(œœœ¸¸ ,,¬Ð6dddàÚµkÈËËSksûömœ?žw°Êç&##—.]BZZšZ;E±XY”e0ÿGQ¦‚-é%"]*Yê*Ž“'OæM5®]»6=zôHÔöÁƒäààÀ³_¶lÏFÝ’ÞOqOŠS¦&y×­[Çk¯Ø’Þ°°0jÑ¢…Ê%¶ê–ônذ³[~zýúu^YFFFôßÿ‰–•““C;väÙOœ8±Ð6~ _ËsMDdggÇ݇ .|b¯J¶¤—X`áë øF—ô¦¥¥QŸ>}HOOk{¥J•è?þ ""[[[ÒÓÓ#===ÞòÓ»wïrñzzztÿþ}>|8·´ÕÂÂB°\òèÑ£Ô²eKÞòW‰DBvvvHÿ^¾|É«gÁ‚›Í›7óln޼ɥ]¾|™—öäÉÚ·o¹¸¸p~hkkS«V­(!!APöÚµky}9ooïBïé¨Q£xyÆŽË¥‰-é åõé% ¹¹¹Ñõë×eß¹s‡×ž 6lÞ¿O#GŽ$^™ööö…þî|þü9ùúúR¹råx~êééÑðáÃ)11‘³Ý°aééé ¾O¹o§OŸ&"¢M›6ÑìÙ³iĈ¢K˜úé'ÞR_žedd$oynVV/oJJ éêêr6”žž®¶ûöí#Z±b]¾|YP¦âý=tè-Z´ˆttt8ÿ.\ȳ¿zõ*uëÖ³‘+++úí·ßå˱´´äêmÛ¶ÚµkG–––¤¥¥EÕ«W§®]»ÒòåËE;DDÓ¦Mãê2dˆ¨Mûöí9›àà`^ÚàÁƒ¹´ÊËË£… RÛ¶m©\¹räàà@}ûö¥ÊÍÏϧ *ðþ©“äìÞ½›gïààÀKWüd2-Y²„š7oNdkkKݺu£Ë—/‹–?räH®=¿ýö›¨MBB <˜\]]IWW—ªW¯N¾¾¾¢eÂÃéOŸ>äââBºººT¾|yrvv¦©S§Ò«W¯8;™LF>>>T·n]^{›6mJ>>>4wî\ÎöÇäÙøúúj,øy{{svÊ/sùýP,kæÌ™jÛ—@•+W¦6mÚЄ èÈ‘#¼ô… r÷÷¯¿þ¢ŒŒ êÕ«™˜˜Ðwß}G¿ÿþ;åççóò?~œüüü¨jÕª¤­­MVVVäããC3gÎTÙ¡XµjW¨HçïïÏ¥+ߣ3fpisæÌ!¢<___233#êÞ½;…‡‡«¼cÇŽåî[Ÿ>}ÔÞ·²üX`¾®ð- ~?æ È)‡òö>[²d —7&&F`«œ?&&†³ÿõ×_UÖ#žžžôìÙ3žIII<y?DÅ[tãÆ .-<<\ÐwÓÖÖ­¿B… ‚¾Œr¿òرc…ÞWeQÏÕÕUeZãÆUú£««KÛ¶mã•]Ø~?&gggµ÷¹wïÞ”››+ðûرcdff¦6oÍš5)99™ˆ„b¨r8yòd¡÷*77—WgóæÍyésæÌáÒZ¶l)ȯüÝO:µÐ: C±¼þýû ögT²—/_.ú”ƒ““EEE êQlwÿþýéW®\ᕳuëV.MY¼š>žKW¼NNNôý÷ß‹ú#•JE®ÂíØ±coôP9øùùQjjª _~~>Mš4Ieø0º|÷î]®…u\äÈ;f†††´víZJIIáÙªü222x#„·oßæ¥ðY333•›¦ 0€÷@yF`“&M8ÛôôtêÑ£‡ÚûP³fMºté’ åN¶˜à§øµk׎—¦(^õìÙ“~ûí7Ñúuttx?9~ü8ïÞ©ÝË LðcXøº¾AÁ¯wïÞ¼÷x­ZµhÊ”)4f̪\¹²à=¯Nð“y_ÊÍͳ]¾|¹ ßéááA]»v%+++^Z«V­xý”’ü€3ŸÚ·oOÇü¦pqqáõQûÂÊýlUðfLJ¥R®MÊ‚ða†aß¾}iƌԮ];^š¹¹9½}û–+»0ÁO¹¯ïèèHãǧêÕ«óâ•'-¤§§“¥¥%ïuêÔ‰† B666¼¼Ý»w'"¢;wr3@û‚¶¶¶dkkKçÎ+ô^)‹†3fÌà¥+Æ+ÿî""úã?xù5™pPbϵT*%‰DB&&&”MDD§OŸæ €êׯOÝ»wçÍœ>à¢|ØHI ~òàîîNäîî΋¯X±¢èo²   ΦJ•*žw_2Lðc¡(¡h™¾AÁ/66–›^¬øòòòâF#Åu‚Ÿ˜HtâÄ "ú0…]ùÅ |±S±¶¶œœU’‚Ÿ\ÜÔÒÒ"'''0fhhÈÅ\µj/ý÷ß×èÞöíÛ——Oqº·˜ðiddD-Z´ Zµj Ò"""xe«ünÞ¼)ø.œœœ³{õê%ðyÉ’%‚—¨““¯Ã€ªU«Fïß¿ÿ(ÁoôèÑô÷ßÓëׯ‰ˆ4ü<ÈÙèéé fÖ½xñ‚WNÓ¦M5ú~Ô¡(ø‰=׊ë.]ºÒMMMÿ¯ èÞ½{¼zJRðSíkÔ¨!úƒàâÅ‹‚ò•ïŸò³VÖ`‚ ,°ÀÂ×ð ~‘‘‘¼¾qÓ¦My‚ijgÏÈÚÚZe¿DLð›|8'ÎL:•gײeKî˜ñ´´4úå—_xé?ÿü3¯ž’ü³³37(ö‡_qfâÌ™3yiË—/×èÞ*l)îã§,ø5lØ·÷ÆêÕ«yéÊû’¨ü¼¼¼¸´*Uªp¢kff&-X°€×)Pœ-÷òåKžøéêêÊí™òöí[ àù´zõj""zôèÍŸ?Ÿ—¶ÿ~zôèoù¯2š ~Š zõê Ò•_ºbB&Ñ¥K—ÔÅŽ¢¢à|_´h…††ÒÈ‘#¹ï*$$„gW§NniÀû÷ïiöìÙ<ÁÐÃÃ7:]’‚²´´äퟸzõjÞ§*1T±c3oÞ}úðlBCC6ëׯçÙ(‡öíÛóìÓŒ¹%ÌŠœ?žg÷Ë/¿l ¨^½zœ‘‘ïù)IÁ¯víÚ‚Õ3ÙÙÙ¼Y›5ÔÍ+§,ï³Í?Ф`Jnn.ï({///Œ5Š»677Çš5kàââ™L¦Q™sæÌÁ Aƒ:tðát±5kÖp6–––8tèw𭉉 V­Z…¸¸8ü÷߀uëÖaúôé¨V­Zñ)‚D"ÁæÍ›Q½zu€––æÍ›‡£Gr§umß¾ëÖ­ƒD"Ajj*/¿¹¹¹Fõ(ž|8™I+V¬€¥¥%wíïïÝ»wãäÉ“€°°0¤¥¥ÁÔÔTmÑÑÑ8{ö,w=yòd´k×`hhˆñãÇãÀ¸pád0ð­" IDAT2±~ýzÀÎ;y§ýóÏ?°µµcöìÙˆŒŒD5àââ€*UªÄóÃÊÊ vvvj}Õ”§OŸrŸíííéoÞ¼á]‹–Ó¬Y3µõ$%%¡råÊ¢icÆŒáN‚kÙ²%¿råJî³®®.:„5jøpZÛäÉ“ñüùs,_¾pñâEœ:u >>>j})*‹/FãÆ¹kœ8q{öì\¾|=âž}9Õ«Wçžó„„„OâƒÁ`0ŒÂ‰ç]+¾×åxzzj\^«V­ • ]¹r…wÝ»woMùòåѶm[lݺÀ‡~fAAï„Uu‘Æ~z{{ âlllààà€»wïâââ¸4333®˜ÄÄDT©R¥Ðz=zÄ»633µ»ïFFFøî»ïpþüy?ê¸ÿ>¯ ÅßEŠñïÞ½<~ü “ÉxuÈûÞŠ4jÔ5ÒÈÂ8vìüüüx'ÞΟ??þø#Ï.11‘w-Ö÷¶¶¶æ]?zôˆ×‡..õë×ýM¦És­¥¥…îÝ»ãÆ€wïÞáÑ£G\¾0>æ¹nÑ¢´´´xqzzzpwwÇÞ½{ˆ?GåË—ç]¿zõJã:Œo&øi@BBOÈëØ±£ÀÆÙÙŽŽŽ¸wï^¡åI¥Rôë×OëÖ-¼|ù’»t^¼xkkkñlŸ={¦Q=ŠBðAØ£R¥J¢/ênݺq‚<|ø 6T[§b‡B^vll,/®F¸páðÒóêèè yóæÿ/]º¤¶þOb§Bùå@ ÒÉ;I%ÉÏ?ÿ,ˆËÌÌÄÅ‹¹ëÎ;‹vFÍ ~À‡çúS~ÚÚÚðóóÄ÷ìÙ“ü€ eÁOñ¾²ƒÁ`0¥‡âÀ›¾¾¾hßGq¸0Tõ¥_¼xÁ}644T9詘ÿýû÷xôèvŠ"‘Åä°²²·±±á?Å{cmmÍJâãã üRRRðöí[îºråÊÐÑѵUuïÓ“'OÔÖ'çáÇÜçÄÄD >\­½¼Ü§OŸ"77—‹×tÒAQ8qâºuëÆ«oþüù˜0a‚ÀVñw Þ?W~î”…VàÀsÏž=¹ëììl8p@#5y®¨ñ”óGGG‹Ú~ÊçZΛ7o‘‘ÁN•EÔ´´4ëd0¾˜à§Ê#ˆU«Vµ«V­šF‚Ÿè¢²S«V-Ñüʳ·nݺ…ï¿ÿ^`'6Û0++«Pÿä¨zAÈg³Éyøð!¬­­3õ4ÍS¾¿ÊåærG#>>þ£?±Q-EEIżH$jó~.’’’¸Ïb Å& Þ¡„Â`jj*¯S£±ÙŠÊ,,¬Ð%Î999çÅ)—#G>}_±éß…ñþýûBmILLä^(ŠmÊÎÎþ¨r>%ŠÓæÅ¹9jÖ¬É]ÇÇÇãÁƒ»¤¤$^ÐtYT*…¾¾¾ ^±óVŠmP· FìÙÖ´Sñ±Ïµ"Š>}L» ƒÁ`”,ʃŒ‘‘‘åe‹êPõ^W0•Éd¸}û¶¨|»àà ¢\„RîOˆÍ@ú˜þ¹b=Š(°*NPž°f͵޽{‡™3gòâºvíªÒþÎ;…ú£éPE¿+W®ŒË—/« 3f̘ššòV±Aí””Œ5 ÁÁÁ8þ<¯/©ØTõÛåîÝ»èÖ­×÷—H$V)öÂI Ê3þÀÁÁ 4à®ããã±víZ•e~,š<×À‡•5b(>o‰uëÖ実m±çúc?Uϵ℘êÕ« ~÷)÷ë5—Œo&øi€r‡ByvˆŠ'b¨úë<½^¬@øûî»ï¸ÏŠ/¬âþáUžy'Gy? ù˹^½z¼?²ÑÑÑØ´i“Ú:–-[†çÏŸs×µk×¼€ä<{öLtº¸¦SÒQ^ªyåʼÿ^eÈÌÌä^jŠ¢Ù›7ox£|rÎ;‡K—.©‡?ŠŠäädQÅåøã?J¬þâ>× <ÁOÕs Ÿíììlg¯¦¥¥ ö3T?׊(~ŸŸr¹ƒÁ`0 õ(Îx€3fðú‰/_¾Ä_ý¥qyb}À‡­H”ëQ&,,Œ7‹ÎÃÃû\±bEÞÞÒÊ¢O^^ÞG “[·nôcŽ?Î[âääÄ}nÒ¤ oû™üü|´mÛ–·ÝŠœG¡M›6¼~¤ zõê¥ÒŸÃ‡ V);wŽ'*ú£777îs\\œ@L¼té|}}1mÚ4lÛ¶×w«S§÷ùøñソ–ÿùç,[¶ ÇG‹-xÛ)΂Ì÷îºwïΛ|0oÞ< :Tm{*W®ÌëêêŸO™2…wý믿ò¶¹Q$22}úôQ[¯"ªžëöíÛó²çÌ™#XÑ“€7r×nnn¼vŠKÖÅÚ&öŒ©âüùóÑ1>>¡¡¡ÜµØ*!å™…ª~G2ß*LðÓ[[[Þ‹zãÆ‚Ñ„Õ«Wk¼l@ÕÎÎμéÕëÖ­1_‰®®®Üµâr㜜ÁL6M—Ù:$ÇÄË7N>L¥— MÚÚÚ1bÏö÷ßÇ™3gDË?~ü¸`qìØ±*ýÉÊÊ¡C‡ñ»víâ]+‹yb(w<®\¹}}}^ذa:„øøxÞËRQð#"ìܹ“WVZZ:wî www˜˜˜ðFE•E±’œ!¨(ø‰ À Aƒxš;v`ñâÅ*7Õ=vì˜Ú¥µŠ¨z® xK¬:$º rÇFqÿHåeôÊ‚ßÇ<×x9ŠÏ5P¸à§jƒÁ`0Ÿž-ZðŒ ƒ§§'1mÚ4Ô¯__°OtQðôôäÍj:|ø0ºté‚°°0ÄÄÄ ((]ºtáÒË—/¹sçòÊpvvæ>=zÛ·oG^^…——|||0eÊüý÷ßèÖ­\]]y⣖–Ö®]«²|èËzyyáøñãHKKéS§x¡¶¶¶ÚYpŠŒ9’[©#“ÉлwoNô‹G@@8€Y³f¡ÿþ¼ÕUŠ¢Y~~>Ú´iƒƒ"::sçÎEPP—^±bEÞÌGÅÕW‰‰‰8vìöîÝˉ†‹/ìõ777Ñ ÷KOO÷Ý«êŸwïÞ·½Pvv6Fމzõêaذa ÄðáÃѬY34lØPðÛª(Û YZZÂ××—»¾{÷.<<ŒW¯^áÌ™3èС ¸t±çHq¶jÅŠ5žMÊ`|3åh_nP8zýúõTÖ™iÒ$A¦¦¦‚¸±cÇ |ëÖ­›FmÛ¼y3/ßĉEí¶lÙB999¼þŸ&áíÛ·\ÙŠ¿íúõë'úݽ}ûVcÿC½zõèþýû¼² »Or^¿~M¶¶¶Õ3~üxAþ-[¶ìtuu¹ßxööö¼þõÖ­[¹¼©©©õ\wîÜY´ ŠÏK‡T¶µ, |ÏL¢/àÝ׊–éü^¾|IÕªUSùGHWW—*UªÄ]Uð{óæ Õ¨QCP¾••iiiñâªV­Jééé¼üaaa‚¼uëÖ¥&Mšp| ¹´Â?$‘HÈÑÑ‘ÌÌÌxñåÊ•£/^Úpûöí~1ÚØØP\\œ ,EÁO±cdooO666¼2tttèÁƒ¼ü꿽{÷òòëëëSÛ¶mÉÃÃw¯õôô¾-X°@ІJ•* ^V5jÔ ´´4.ߥK—ùôôôT¾Äˆ4üN:ÅÙ”/_ž Dí ¨_¿~Ý¡@  ÜÜ\®,M¿¼¼>ž·zõj.ßÇ~òz”ƒµµ5;vL4o~~¾J1§{÷ît÷î]^œ:Á/00Z·n-ZÖ¸qã(//Oeîܹ#AÅ‚››;wN´ eÁ/""‚ºwï.ZÎ!C(;;›—¿0ÁO&“Ñ„ T @‰„FŒÁ–“••E£GL*Pìc.Z´Hô¾(þ>’‡¹sç î¿&AQðÛ¾};occ£ò»‘·=00P´oªjÖ¬Iëׯý¾íÔ ~D&+´oß^e=ÆÆÆôï¿ÿªÌÿûï¿‹ækܸ1%''“§Nð;v, 8P´¬~øAð›WŽg·`ÁµmýÚa‚ E ì”^ ±°°ÀÅ‹Ѷm[Á”n###ìÝ»³gÏFXX€¢oèoff†'N W¯^¸~ý:¯¼¿X³fͰqãFÁID^^^7n-ZÄÅɧÂK¥R#$$„óS 6D•*UpðàAÁþ„åË—ÇÆEP¯[·.._¾Œ^½záêÕ«…ÖÓ®];lÚ´©Ð%’C‡EVVÖ¬Y#XÂi``€ÿý—·—DatëÖ ³gÏÆ´iÓ “ÉS§N ÊÝ¿¿`zø˜1cðüùsˆ÷G477Ç‘#Gx'‡5nÜ666xöì—““S"KNš5k©TŠüü|¼}û7nÜàm,GKK ›6mB£F0qâD0©W¯¦OŸ®vÓfuH¥Rìٳđ#G¸xåý>±aÃÁrZ+++¬Zµ þþþ\\BB·ÔbÒ¤IHKKCppp¡¾˜››£W¯^Xºt©`i±T*Å‚ D޹qã÷¹råÊõ¬1 ƒÁø4Ô©S ¸}û6ÂÃÃQ±bE4oÞUªTìlmmÍ}677ÇôéÓ¹k±>“r=×®]CXXöìÙƒ‡";;ß}÷\\\УGÞ@ŠhkkcÛ¶mØ»w/Î;‡ØØX4jÔ^^^èС222x¾(ö H¥J•pâÄ ¬]»çÏŸÇÓ§OÑ AtêÔ ­ZµRÛgggü÷߈ŒŒÄ±cÇpýúu¼zõ ùùù¨T©êÔ©ƒvíÚ¡U«V*—‰V¨Pç«­­-víÚ…ƒâôéÓ¸sç\\\Ю];Áþ‡{_+Ÿ"+‘H0þ| 4Û¶mCdd$^½zsss888`È!¼%ÖÊe¡wïÞ8yò$nݺ…ÄÄDØÚÚ¢V­Zøå—_Dï­³³3"""°lÙ2ÄÄÄÀÂÂõêÕC‡ŸŸÏk¯&( Ø©S'###Ïž=Ãàèè(šO"‘`ܸq3f Ž;†3gÎàÉ“'xþü9 `eeøøøð¶tRFÑßfÍš©õÕÒÒÇŽõk×°cÇܽ{iii¨U«\]]áëë+ØZG‘yóæÁÓÓaaa¸~ý:œáííN:ÁÐÐS§NåöÖTÜŸ[}}}¬_¿;vÄ©S§‹ºuë¢U«VðóóÍ“››Ëí¨­­ýQ{2ß EQ ñ Îð““ššJ[¶l¡€€òóó£E‹qÓ¨›7oÎÝ“ï¿ÿž—oذaäååE^^^4bĈBëÉÍÍ¥ 6P›6m¨B… |˜å×¾}{ R9{‹èÃèЂ ÈÛÛ›ôõõ©jÕªÔ·o_ %"¢±cÇr¾,]º”—Wq†_Ë–-I&“Ñܹs©^½z$•J©jÕªÔ£GÁÔqU>|˜úõëGÕªUã¦tëèèPõêÕiРAôßÿ©Í?jÔ(Î×+VѪU«¨M›6d``@–––äëëKQQQ¢ù]\\xí#""‚~úé'ª\¹2I$ÒÖÖ&[[[:t¨è¬CE¨{÷îäàà@ZZZdddD...ô矪‰ºwïµnÝšŒIOOêׯ¯vôííÛ·Ü=ðòòR;ʦ8Ê5þ|µ¾}X‚L­Zµ"[[[î;233£úõëÓ„ (,,Leþ9sæp~©›¥¨È¨[·ndaaAÀ‡åµÞÞÞôÇPff¦Ú¼›6m¢:Pùòå©R¥JÔ½{wÚ¾};-^¼˜óEyÙâl5KKK®,wwwÒ××'sssêܹ3]¸pAeÝŠË= -- °~,°À _WÀ76ÃïÞ½{4qâDZ±b=z”bcc6gÏžå͈QÕ_ü’Qža¶qãÆÒv©Xœ>}š×žÓ§O—¶KŸœ¡C‡rí-l&é·‚òlµI“&}tÿý÷—ßÇÇçxùeÁfø±P”P´Lß àwìØ1Šˆˆ ääd•6Šûo >¼DëWÞÛNSÔMëCYð+NYb&è| 𸣏<ºG…Úçææ¹ûÉd²¹§Š(.S.Ê>E~ÖŠBQë*((]Ê¡ 1ÁOަß➊KmÊ*LðcXøºÂ·&ø={öŒ÷ãWKK‹nݺť'''ó–¯jkkSJJJ)z\4ÊŠàwîÜ9 æõ§ð¾³²Ê7¸öj:@^Ö) Áï×_åòïØ±ãxùeÁ?ŠØ)½ò矢qãÆ°°°@¹rå§|^½z•·ÌTqÉ@IPÔ%ªŽb/­² KÀ“¨ò';;=ÂæÍ›ñäÉ.Þ¢Ð2uttŠÜÎýŽ$I‰~?Ð¥Kn™õùóç5Z®«ˆ––V‘Ÿµ¢PÔº´´´Št"™š|/^¼ÀÍ›7...pss+‘º ƒÁ` kkkÞûX&“ÁÅŵjÕBõêÕaaaÐÐP.}äÈ‘¨P¡Bi¸Ê0{öl 6ŒëO–aŠmTÖpssãNr>zô(·% £èdeeaÆ >l=¤jÙ/ƒñ­Ã? ñððà>geeaÔ¨QèÖ­ÆŽ ///x{{sû°=J‘ÐÐPÔ¨Qýû÷G~~>¯‰à÷µ#•J¹#ëß½{‡­[·–²GeƒuëÖqŸÿ÷¿ÿ•¢' ƒÁ`0äìÝ»666¼¸{÷î wwwüõ×_ŸÑ3†2×®]ÄÍ;•*U*o>?ÐÑÑL&ÃÊ•+KÛ¯žÍ›7#55°hÑ"hi1YƒÁƒÚ¡!þù'Nž<É€‘‘‘ýû÷‹ÚΘ1Õ«Wÿœî1ëP˜››càÀŸß™R`ôèÑF||<–/_ŽÁƒ—¶K_5yyyX±b€Â?Ad0 ãË zõêxøð!6mÚ„={öàÅ‹HJJ‚––ììì`gg??¿¯úÝmaap×Ê›} dddÀÍÍ ÑÑÑÉdpwwG»ví0lذÒví³áè舱cÇâŸþÁ–-[ð×_ñ÷øÖÐÕÕå=×…š£ÌêÕ«¡§§‡N:ÁÛÛ»„½c0ÊLðÓccc\¸p+W®Ä²eËðüùs^z¹råРAÌš5 žžž¥äeñ±¶¶æNÏRwBØ—Œâ‰Â:::hРæÍ›§ö„©²„žž.\ˆîÝ»ãÆ‡»»{i»UªXXXÀÖÖ–ûü1ìÚµ ‰‰‰H$¼Ó¯ ƒÁ`”>zzzð÷÷‡¿¿i»òI°··ç–.~­ãÔ©S¥íF©3þ|ÌŸ?¿´Ýø"044,Ös}ýúõ’s†Á(ÃHˆèã3I$nnȯׯ_ÿÍÌž’óþý{$%%!==ÖÖÖ077/m— ¼xñÉÉÉpttD¹råJÛR!""2™ U«V-ñ=%¿%âã㑜œ }}ýojï>___8p@~yˆj•¦? ƒÁPD"Y`Œüº(}|ƒÁ`|™¤¥¥ÁÌÌL1j2Í--_l†_100`Ëv¿`ªT©‚*Uª”¶¥JãÆKÛ…2A5P£FÒvƒÁ`0 ƒÁ`0 a»[2 ƒÁ`0 ƒÁ`0e&ø1 ƒÁ`0 ƒÁ`0e&ø1 ƒÁ`0 ƒÁ`0e&ø1 ƒÁ`0 ƒÁ`0e&ø1 ƒÁ`0 ƒÁ`0e&ø1 ƒÁ`0 ƒÁ`0e&ø1 ƒÁ`0 ƒÁ`0eii;À`0 ƒÁ`0Š³â…••UiùÁ`0Œ†ˆ”£œÅì E$"Ná™$’Vþ“_×­[ÖÖÖ%éƒÁ`0J‘7n 99Y~™DDì—#ƒÁ`|ÁH$’ÜJÛƒÁ`|NQûÒv‚ñeSÔ~æŠwîÜÁ;wJÀƒÁ`|T(m ƒÁ`0 ƒ¡9lI/ƒÁ`0 ƒñõ“¥xáääTZ~0 £„‘ÉdxðàbTfiùÂøz`‚ƒÁ`0 ƒñõs€»ü"66¶]a0 FI’––333Ũ«¥å ã롨‚ßCÅ‹%K– gÏž%àƒÁ`0¾ „ãÇË/Ÿ”¦/ ƒÁ`0 ƒÁø8Š*ø(^˜˜˜ÀÒÒ²Üa0 Æ—€žžžâe*;ƒÁ`0 ƒÁ`|yh•¶ ƒÁ`0 ƒÁ`0 £ä`‚ƒÁ`0 ƒÁ`0 ƒQ†`‚ƒÁ`0 ƒÁ`0 ƒQ†`‚ƒÁ`0 ƒÁ`0 ƒQ†`‚ƒÁ`0 ƒÁ`0 ƒQ†`‚ƒÁ`0 ƒÁ`0 ƒQ†`‚£LòàÁøùùáÖ­[¥íÊŦM›°téR@jj*&Mš„ÈÈÈRöêË"-- ³fͰaߟ¯ÖvݺuX¹r%àÕ«W˜4i{æ ƒÁ`0ŒD&“•¶ ÆW üŸ€€Ì›7ï“ÖqùòeìÙ³gΜù¤õ|mìÙ³ëׯðAØš7oîܹSÊ^}dffbÖ¬Y¨^½:¦M›†U«V*øíܹ›6m¤¤¤`Þ¼y¸{÷îçp—Á`0 ã«bĈpttDvvvi»òÅ‘‘¦M›bç΀ÿýîîî¥ìÕ—All,üüü`ii T®\~~~xøð¡Ê<)))hÚ´)öïßX¾|9¼½½?“Ç Æ—Çgü8€³gÏ~®ê_0/^ÄÍ›7?i?ýô"""ðIëù‘H$j¯¿UNŸ>éÓ§£k×®èÞ½»ÆùØýd0 ƒñ5}}}œ>}ú“Ö“¬¬,|Òz¾&òóóqåÊ$%%ž={†Ë—/—²W_ï޽ý{÷àëë‹ ÀÍÍ @“&MðâÅ Ñ›à·jÕ*ìÝ»÷sU÷IÈÈÈ@jjªF¶™™™xòä‰F/´ŒŒ wýîÝ»B󤧧#--M#_€O? :==]ãö‡7oÞ //¯P;---4jÔ:::—ýöí[¤§§j—ŸŸÄÄD^\ff&²²²4®«´°´´„¹¹9pÿV®\Ymž¼¼<®"'33ïß¿/´¾”””BgÉÀË—/?ùýKJJRû|:;;#666l@µjÕ4*Sñ~ZXX(ü~2 ƒñ%žž___jYyyyˆŠŠ ŠÊyøð!¢££‹UÇ›7o…ÜÜ\À£G°}ûvœ={–7x®LAA®]»†;v`×®]¢K333…ÌÌL¤¥¥áСCÜÒÓ7oÞàÔ©SjË¿yó&¶nÝŠóçÏk4(š——‡k×®©õ»¨àâŋغu+vïÞ[·n‰ú”““ƒ¨¨(Ü»w‡¨¨(DEE!..N´ìôôtDEEq÷&>>;wîÄýû÷Eퟧ"¯^½ÂpäȤ¦¦âíÛ·¸~ý:8 6ß—@ùòåQ¾|yX[[¬­­QµjUµy’““±ÿ~;v iii\{<(°MMMåýŸyðàvîÜ©vYlff&.^¼ˆDDD¨\‚œœŒ¨¨(Á3,®Þ¼y#j_PP€ììl\¸p;wîü¨ßì}úôÊÕb•*U‚ªT©¨R¥J¡÷“Á(ÓÑGnHÖ¯_O…Ñ¡C=zt¡všrîÜ9@kÖ¬¤9::’§§g±Ê/(( äââB–––4gÎ £Áƒš2eŠ Ï‘#G¨yóæLááá´víZjÖ¬™è=²··'{{{òöö¦ÀÀ@@îîîÔ¶m[š;w. ]»vñòìß¿Ÿ´´´¨I“&´víZÚ¸q#999‘®®.ݽ{We["##IKK‹5jT¬{¢ÌìÙ³ 6Œ:D·oߦ={öˆ~'DD{÷+W’µµ55jÔˆV®\É…ŒŒ Ñ<ÆÆÆ4`ÀªQ£µk׎ièС´bÅ í³gϸòÆŒCèÔ©S¢åfddrss£*UªÐ‚ èÌ™3Ô¯_?@ÿý· ÏÈ‘#IGG‡–.]JÑÑÑ4nÜ8@ƒ¦}ûöQRRÒGÜ=>ݺu#[[[µA¬Í˵k×èøñãÜõ¼yó(//OÔvèС¤§§GË—/§èèhîžþòË/´oß>JNNä‘J¥äïïOU«V¥N:Ñ¢E‹Èßß_ô™2d 1cÆÐùóçéÀÔ¤I’J¥tòäIžmrr2 åË— Êqww§æÍ›óâ^¾|IÈÊÊŠš4iB!!!töìYòóó#´`Áµ÷iìØ±€Þ¿¯ÖîòåËtúôiîzöìÙ$“ÉÔæùZèÚµ+)ü¥"¼+X`Xø|ÀÅþyaÈß•‘‘‘…ÚjBAAÙØØPË–-iÙÙÙdjjJ?üðC±êX»v- K—.QÇŽI"‘ŽŽ ___Ñ<‹-"kkk@€´µµiΜ9<»óçÏZ·nU¬X‘§§'ݹs‡LMM uíÚUPþÓ§O¹þ¾ÜGGGºyó¦Ú¶ >œÐ¢E‹Š~CD8sæ U«V¡¡!1b„À6&&Fñ]Ï bß#Ñž={EEEQ=xyV¯^-°ïÛ·/ÏÆÚÚZ¥ïK—.åžÉ6mÚD"!©TJ¨OŸ>¢yBBBÈØØ˜ ÉÈȈ$ W—¹¹¹†wMÈóçÏ©gÏž…†´´´"×!§^½ztãÆ "":}ú4µmÛV¥í† ¨\¹r\Plo•*Uö!!!€bccéûï¿ç}›7oØŸ–¦M›’››ÛGçsqqQûGZccc’H$4mÚ´ªãôéÓ ~uëÖ¥„„.þýû÷¤««K:tä133ãuôòóóÉÜÜœúöíûQ¾} Èd2266¦ü‘‹ËÍÍ%3334hÊ|R©”$ Íš5Kmù7nÜ ‰DBcÇŽåÅgff’}÷ÝwTPPÀÅUðkÒ¤ OL.(( zõê‘¥¥%åää¨ôOSÁ¯,Ã?X`…¯+”¶àGD4eÊÒÒÒ¢§OŸòâå"ÑáÇ‹U¾\ð333£ßÿž={FIIIôÃ?:wîœ Ï²eËhóæÍôúõk*(( S§NQ½zõH*•ÒÇ9;¹àgccCçÏŸ§€€î·ÀåË—éçŸ&^ÿ!??Ÿœ©Zµj´nÝ:z÷î:tˆÈÎÎŽ²³³U¶eÍš5dmm-ä,ÙÙÙdllLuëÖ¥[·nQ~~>Éd2Š‹‹£çÏŸ ìsrrèîÝ»Ü÷³fͺ{÷.ݽ{—ž[·nÅĉQPP€;vàÇ„‘‘Q‰Õ£Œ‰‰ ^¿~­2=""W¯^Ž{÷ðüùs$$$º‘¿òáÊ×<L:S§N-#)) –––š4¡DX¸p!bbbпŒ;:tÀàÁƒÑ²eË­ÇÉÉ©DË+ ÁÞZZZhÕªvìØqãÆÁÞÞ‡½{÷0|øðb×)ßÄXmÚ´§§g±ëÒ©T ooolÛ¶ £GF5°wï^ÄÅÅaܸqjójò}=x𦦦Ü~%Џ¹¹q6ß}÷]Ñ †Úµkø°çLÆ K¼|ƒÁ`0¾D´´´xýDùžn+VD¥J•¸øâlpïèè„„„à÷ßðaÏ·#GŽ`ìØ±ÜÁÅ¥yóæ¼ëzõêøp›&´mÛDOú¬Q£€ÿ—òµâÛaaaJ¥033ÃñãǹxhkkãúõëùSR8::ÂÚÚýõÞ¿¾}ûr‡Š•4Ë—/GÅŠ?IÙÊ߯¼o¨üýÊÛ¦¸Wœü7š¾¾þ'ñ­4k¯üy,¬½ÁÁÁ011QkŠ.]ºpÒÉqrr‚‡‡BCC‹â¶åþwåÊ•áååUè¾ôQQQX±bæÍ›§ñ{ Æ·ÎW}dMµjÕЪU+„„„`âĉøï¿ÿðòåK 4¨TüIJJB×®]&MšÀÙÙõë×ÇÇ‹}z­|³Ô 6¨TÌÌÌŠUÇÇbee…ÈÈHœ9sDHH¶lÙ‚Ö­[ãÀeî®àà`´lÙ5kÖDõêÕ‡ü#GŽ,vÙ—.]Â;wÔÚØÛÛ6Áþý÷_x{{ÃÑÑ‘koïÞ½1lذb—™™ CCCÑ4ùs“™™YìzÄÐÓÓNf0 £¬`llŒàà`î:99›6mÂÔ©S9Á¬$4h† ‚[·nÁÅÅ{öìANNÎ'íŸëêêªMˆˆÀÖ­[ƒ¸¸8$%%ø°ÒIZZZj¯ƒ“èÓ§ M*•ª<”îS!•JqâÄ à·ß~Ãøñãáéé‰áÇ£G%&¸(T<*Iä}7e<<<`ee…3f bÅŠJ¥˜5klmmÑ¢E‹bÕùôéSôëׯP»½{÷¢B… ŪKSš7oŽÊ•+cúôé055Ì™35jÔ€‡‡‡Ú¼…}_¯^½Bzz:ªW¯.š^³fM\¹r2™LôÿBq©^½:Ξ=«²üœœøûû£qãÆ;vl‰×Ï`”U¾jÁøÐ©øé§ŸpóæMlÙ²Åþ_T~þùgܼyW¯^å\\¾|=*VÙr‘OWWM›6-VY%‰D"A«V­ÐªU+Ìš5 .ÄŸþ‰þù“'O.m÷J”÷ïߣ|ùòèÞ½;ÚµkWWW4nܸDÊ^³fM‰”S’ÈÛûã?¢uëÖ¨W¯^‰ÍˆsppÀÉ“'‘-‘”Ÿ'vÚmIˆtñññ ²CÃ`0 £èôèÑ£GFHHæÏŸ-[¶ÀÝÝý³¯Ø3vìXÁÝÝÝ»w‡½½=ÌÌÌàíí]ì²Ë•+‡š5kª=yösãì쌳gÏ"!!{÷îEPPúôéƒ`ûöí¥í^‰b``€3gΠY³fhÖ¬€³:TìÕ^úúúýæ*Ll.IŒŒŒ www4iÒP¿~}}:·µIY ??AAA¨T©<‡ÛÞÈÜÜsçÎ-‘²Jм¼<ÁÒÒGŽƒƒC‰ý¾611¹¹9bbbDÓ£¢¢ààà š¦nåŽb3k£££UÆO:§NBxxø'[¢Î`”U>›j´jÕ*L™2¥ÄË500@Ïž=ŒÌÌL 0 ÄëДªU«"44áááÈÊʾ}ûмysäææ"55µXe›˜˜`îܹ¸~ý:z÷îÍíÝ “ÉððáCÄÅÅ©ÌûúõkxzzâÏ?ÿ,–ÊÄÆÆ"66–û£™™‰?ÿü™™™‚½7ñòòÂ… ¸ÑЄ„DDD”¨o%M^^RRR°wï^øøø cÇŽhݺ5<==1pà@9r¤´],QäÏìÎ;Ѿ}{^{þùg;v¬Xå÷èÑ...˜8q"ÂÃØ>}:Î;‡)S¦ |ùòœ½lllpúôiDGGC&“!447Æ«W¯TÖsþüyüòË/xñâ ˆãÇcÈ!‚‘ß””î™–ÿÿºÿ>bccñäÉ“bµ—Á`0Œ/  :”·_I1pà@<{ö LJ¾¾>zöìYâuh‚|Õ€âê™L†ñãÇsŸ‹C¯^½ŸŸ¯ríÂPÞ3úS •JQ¿~}ª÷8”/÷T7`ÿ%²oß>¬\¹›7o†§§''ö•Õm[vî܉իWcëÖ­ðððàľân%§k×®8qâ„`Æêÿý‡[·nÁ××—/ßÃQq¯J™L†ÿýWm=3fÌà]_¾|‘‘‘èܹ³ÀößÿÅ’%KpèÐ!8::~T{ FXÒ |XÖ»zõjøøøÀÆÆ¦ÔüX´hz÷î hiiˆˆ´´4Ìš5 Åš‚ìïïÌÌLL™2»w’’••…þýûcãÆ¢ùbccqñâE¤¤¤”¨è÷÷ßcË–-¨X±"¬­­"ˆ#ÔîÓâç燕+W¢N:¨P¡RRRàáá .”˜o%®®.NŸ>-ZàÆ°´´ä¦ïÙ³7nDtt4êÔ©SÚ®–úúú8uê¼½½¹öêëëCKK ;wîĆ ‹š5k©|---ìÛ·~~~ððð€µµ5ÒÓÓñîÝ;Œ?^ô`ÿýï6lêÖ­ SSSÈd2lذ .TYÏÔ©SŠjÕªÁÈÈéééhÛ¶-fΜ)°]¶l™àÿ‡««+€"uXXX‘ÚÊ`0 Æ—ˆòž~%IË–-agg‡#GŽ ÿþ066þ$õFýúõ!•J¹÷{NN6lØ€·oßB___í ¡&4oÞC† ÁÊ•+ñîÝ;Œ9–––ˆ‰‰ÁíÛ·1`À•3’f̘¿þú 7nDß¾}‹å‡"³gÏF­Zµàî¬,\¼xÓ¦Mƒ££#'ü)S£F 8::bÕªUpuuáÊ•+hÙ²å}ÀYµjÕ ££ƒ¦M›ÂÈȹ¹¹ÈÍÍ…––<==1~üxtéÒ¥´Ý,1ììì •JÑ A^{µµµáéé‰ßÿ;v,rù3fÌÀŽ;àãフ+W¢nݺ¸rå †ŠªU«b„ <û øð›PWWÙÙÙXºt)?~¬¶ž}ûö!;;ƒÆ‹/0|øp öE?uêбcGDEE!**Š—îïïÏ–÷2…P&¿¦M›âìÙ³°³³+±2µ´´pæÌÑ¥‚³gÏæN6S¤}ûöHLLÄÞ½{!‘Hàé鉪U«âùóçhÙ²%oú²|Y¡âµâfª»ví, H$øõ×_Ñ£GDDDàþýû000€ƒƒZ·n­²-žžž8uêT‰ŠÃÏÏ ÈÎΆ••¼¼¼ ýš5k†ääd8p©©©¨S§¼¼¼Dm= ++«ö­Aƒ8sæ 'Ø(#ßóC~òš"‚‘²ÜÜÜÿÇÞ}‡Eu|}ÿ.HW)"( "*vÅBQP,¨ìرÝX0±÷Øk,?%šØÅŠ]c¬€ ìAª ‚´óþá»Ö-,KSs>Ï3îÝsïœ;,»—Ù¹3˜2e <<<°k×.±ù:^½z øûû7~YYY˜:u*úöí‹;vˆ­ýìÙ3Ô®]˜5k–Ø~/^D5ªÃÒÒ7oÞÄõë×]]]ØØØ Q£FRãÇŒkkk\¿~prr‚,,, ¤îS©R%\¸p·oßFHH5j'''‰U°Ï£d½‹{K>cŒ1ö_"0lØ0,X° ÜÓ>ϼsçN,\¸^^^PWW‡››ѲeKÄÆÆ»ŽmÛ¶¡~ýúX°`þøãÑöªU«¢}ûöez bff&¶oß.Öá"àîîŽ7J½þòõõżyóD£!ÍÌ̾úëZ;;;,\¸«V­B¯^½ ££---¤§§ãÈ‘#?~<ÜÝݿʩ”áàà€¹sçbÆ èÕ«´µµ¡¥¥…´´49r&L€«««Ìë☘˜àÎ;ðòò‚»»»h»p‘Ì/dlÖ¬¦OŸŽ•+W¢S§NÐÑÑÁرc±zõj™×ópîÜ9Œ=ööö "˜˜˜àÒ¥K·cŸ:u ¹¹¹8~ü8Ž?.qoooîðc¬0DTäÀ ‹ŸŸ1ö=;yò$ /^H<BèÈ‘#åYé8zô( ØØX‰çnÞ¼I(00°2SÌÛ·o ­Y³¦¼SùfyxxP÷ùHRⳂ .\¸”]°®àõ9ûW~~>………ÑÇKµŽgÏžÑ7èÉ“'”››[è>ïÞ½+•\ž={F·nÝ¢»wïRrr²ÂûåççÓ½{÷èùó生Ÿ_*¹•¤={ö=}úTâ¹õë×zõêU9dV:vîÜI†††ôüùs‰çV®\I(..®DêJLL¤àà`…^£ñññtïÞ=ÊÎΖ·bÅ @)))Dôùz=""â›x­} RSS ^›€Yô|öpùºË÷ñuc¥ÌÔÔ?ÿü³h¾ÄÌÌLìÞ½®®®èÒ¥ËwuË€©©)`öìÙ¢Um333±k×.tëÖ nnnpuu-ÏcŒ1Æ"иqchii•jµjÕBëÖ­aee¥ÐÈ£‚w÷”¤ZµjÁÎÎÍ›7‡Âû X[[˽{âkrøðaXYYIܱ“““ƒ3gÎÀÂÂBá;O¾‡Fýúõ%î¦ÊÎÎÆÙ³gQ·nÝ[´ÄÐЭZµRè5jll kkk¹#H¥122Býúõ¿‰×cߪr¿¥÷Ý»wðööV8^__¿H«¤¦¦¦éVìÚµKáxöß`mmßÿ3gÎÝŽžžŽ*Uª`üøñ˜1c†Øm¾ß:ìܹ³gÏÆîÝ»Eç[µjULš4 Ó§O/ò‡:cŒ1Æc%¥sçΘ8q"F OOO!<<ëÖ­ÃóçÏqéÒ¥òN±DuîÜÓ¦Mƒ<<<`hhˆÇcíÚµxýú5._¾\Þ)2ƾ2åÞ᧪ªZ¤o" ®Ü©•"Ÿçêb² : @TT’’’`jjŠš5k~·_#FŒÀàÁƒ¤¤$˜™™¡fÍš¨P¡Üß6 ¥§§‡3gΠAƒå cŒ1öÍY·n]‘z›2e æÍ›§püš5k°páB…ã§M›†9sæ(Ïþ&L˜€Š+bÓ¦M¨» IDATØ»w/ˆèÞ½;&Ož “òN±DM:•+WÆ–-[°gÏÑùzzzbÒ¤I%6º1öýQáQ_î$X¸'|ìççW¤QzŒ1ƾnžžž >|BDõË3Æcò ‚u& +s/(ß°aC4mÚô«9>ûo"¢ÿÔí¡ßÚùæåå!''šššåÊ7éÝ»w_.êéKDËË+ömøú‡ê0ÆcŒ1ÆÊLƒ Ju”|iŸý7}K_%á[;_UUU^U—±2Æ‹v0ÆcŒ1ÆcŒ1öá?ÆcŒ1ÆcŒ1ƾ#ÜáÇcŒ1ÆcŒ1ÆØw„;ücŒ1ÆcŒ1ÆûŽp‡cŒ1ÆcŒ1ÆcßîðcŒ1ÆcŒ1Æcì;Â~Œ1ÆcŒ1ÆcŒ}G*”we)++ *T@… ŠvNNòòòDÕÔÔ ªªZj¹½{÷FFFPQ)Û~X"§OŸ ÐÐÐPºž¬¬¬"µavv6òóóEK³ýÿ ¾Ööÿôé>|CCCÔªUKf\Qó/ˆˆðøñc4jÔ 8é–‹?"-- ÕªU+ïTcŒ1ÆcŒ}¾Ê~?FTTT‰“ˆ ¥¥…¹sç*¼»»;´´´DeÍš5%šdffbåÊ•¨Q£ªW¯Ž¸¸¸¯£0QQQbç)«+]Gff&´´´°dÉ…÷qvv«Ë–-J×ÿ-yðàž>}Z¢ÇLMM…––V¯^­ð>ŽŽŽbí¿k×®Í)???þø#*W® [[[XZZÂØØ.\ˆMLL„––Ö¯_¯T];vì@“&Mðûï¿3ëâ»wï^¼x¡PlZZæÏŸêÕ«£zõêÈÉÉ)ÝäcŒ±2‹   |üøQ¡øOŸ>!((H¬”ôgcTTvìØ `Ó¦M.Ñã%/ÏUZ‰ŒŒTºŽ×¯_#((YYY ÅgffJÔ_pp+š/^ ((ÙÙÙ ÅøðA¢ý‰¨Äózóæ >ŒeË–áĉHNN–÷üùóbýÞ¸q,Nªe.,, [·nÅ‚ ðÛo¿!,,¬¼SbL®¯r„ߨQ£`llŒcÇŽ•kK—.ÅÔ©S‘žžŽ~ýú•øñ£££Ñ®];¼}û¦¦¦%~|E™™™áÌ™3bÛˆúõë‹uª©©•i^«V­BJJ ’““1xðà2­»>Ç/ñã1»wïÆ?üooo$$$`Á‚èÖ­Î;‡víÚ•X]vvvpuuEË–-Kì˜Êòôô„»»{¡ØÁÁÁpqqAZZªV­Š´´´2Ê1Æ—’’‚íÛ·£wïÞ¨[·n‰÷ܹs1bžy-Z$:×ÌÌL?~pèÐ!±í«V­*³œjÕª%ªwÁ‚eVoy{ýú5|}}ñìÙ³rÍ£Aƒ¢ö÷õõ-ñãß¿={ö„¥¥%¢££ñþý{<|øêêêèØ±#RRRJ´¾5kÖ`ÇŽåÚÙ111ðõõÅ?ÿü#7ÎÞÞþþþˆ‹‹Ã»wïpýúu899aݺuØ¿eËXєٿŒŒŒB/(üýýñúõkÄÅÅáÇbßV 6 úúúû¤§§# ?FRR,,,0räH¹#æÞ½{‡€€ÃÔÔ½{÷Fƒ ”?¹òóóqèÐ!ܹsùùùppp@ß¾}¥Æ¶iÓmÚ´ðùEåååaëÖ­°¶¶†ƒƒC‰ä­¬´´4øûû#<<ÉÉɰ´´Ä¨Q£äÎ5–’’‚€€„††¢FèÓ§Bßê*"77DHHÑ«W¯9viÛ·o’’‚ððp±×ÿ˜1c ­­-±Ï»wïàï¤¦¦¢víÚ5j”Üo “’’€»wï¢fÍšèׯj×®]"ç““ƒ $$*T€““zôè!·sçN~üñG±íÕªUC¯^½àçç‡W¯^Iý511GÅÝ»waaaþýûKûïÙ³g¢‹c¡þýûËýæ.;;û÷ïGhh(ÔÕÕáää$ú&O–\¹rQQQ¨Zµ*š5k[[[XXXˆÅíÞ½©©©HKKÃÇÅ~¾ãƃºººX|=¤¶cŒ1VRRRмysܽ{Í›7/ït¦©©  tî<ÑÖÖFÿþýE[´hÕ«Wãøñã8vì&MšTâuÊòå¼ãÂë555Q”5@ ª[Ñ9ÑYÉ)íöß²e rss€:uêš4i‚Ý»wÃÙÙ{öìÁäÉ“K¬¾öíÛ£}ûö%v¼Òfhh(öwe›6m°téR´nÝ0`@9fǘ DTäÀ ‹ŸŸÆÕÕ•&Nœ(7¦W¯^dllLjjj¤¡¡AÆÆÆ¢-æÌªX±"U«VºtéB€*W®LOŸ>‹ÍÏÏ'Ô·o_ªW¯™››S§NH[[›*UªDW®\‘™Wrr2 _ýUnþÉÉÉÔ±cG@5kÖ¤ºuëòôô¤œœ¹û.Z´ˆÐ?ÿü#7ŽˆèÒ¥K€jÕªUh¬²ªU«FrcŽ;F:::dbbB]»v¥5jÒ××§W¯^‰Å~üø‘Ѐ¨víÚdaaA;v$---ÒÓÓ£7nȬ'66–І äæ“@mÛ¶µ¥¥% ~ýúQnn®â'/E·nÝÄ^ÒÊúõë‹UG§NÈØØ˜TUUISSSìØoß¾•ˆ?tèiii‘™™¹¸¸©©) CCCŠ‹‹‹MII!4dȪY³&YZZR‡HSS“ªT©BÁÁÁ2ózúô) íÛ·ËÍÿÍ›7dooO¨N:¢ßÇ!C†P~~¾X¬››éèèP^^žÔó@ÇmKHH 4lØ0ªQ£Õ©S‡:tè@dhhHwïÞ•8NPP¨ýôôô@¡¯3[[[@VVVdnnNÈÛÛ[""¢ììlš0a 277'WWW²±±!MMMZ¼x±D|›6mÈØØ˜TTTHKKKì盞ž.·m'L˜@(;;[n\Iòðð ïó‘¤Äg.\¸p)»`]Áëó¼}û–Hý JHH êÔ© [[[òðð š1c†Ô}ÒÓÓiëÖ­äîîNõë×§† Ò!Cèõë×±»ví"ôäÉúí·ßÈÅÅ…êÕ«G^^^#7ÿÕ«WJHH(ô\OŸ>MžžžT·n]êØ±c¡×4_ªU«5iÒDnÌùóçÉÝÝ{¸|Ýå«újÆßßÀç[ ™Ã¯yóæ8vì:vì@"ÂÁƒ1`À¬X±Û¶m“ØçÈ‘#غu+ÆŒˆˆˆ@Û¶mñã?",,¬X+xúúú⯿þÂÉ“'áêê "Âo¿ý† &ˆþ- ¶¶¶5jKäxÊjÕªNž<)úf†ˆ°gÏx{{cÕªURX8pàüüü0lØ0ÀÇÑ®];Œ?¡¡¡ÅÊç§Ÿ~Â;wpöìYtîÜùùùX·n~úé'8;;côèÑJ{ìØ±èÝ»·Ü˜âÎ'\¬¢Aƒ Íággg‡³gÏ¢mÛ¶>.ݱc|||°víZ¬X±BbŸ}ûöá?þ}ŠöíÛcÒ¤I¸~ýz±òŸ|ˆ‹/¢C‡ÈÍÍÅÊ•+ñóÏ?£sçÎ2dˆ(öíÛ·066–:Žpq˜7oÞH<÷çŸbÿþý¢Q³ÁÁÁèС&OžŒ«W¯ŠÅvíÚñññ€ãÇÃÃÃCnþãÇGxx8._¾ŒöíÛ#''Ë–-üyóÐ¥K‰oíV®\‰7â×_ÅO?ý$zïÈËËCnn®Äñ…í[³fM…æð+o±±± ‚ùå” cìëð7I®ªÄ¾kÂEÂIùÕÕÕE#Œ¾™.Ô·o_DGGÃÕÕŽŽŽ8uêþüóOܺu ?–:2ÏÇÇqqqèÙ³'êׯ;vàÒ¥K¸sçjÖ¬Y¬sX¹r%fΜ sss 8çϟǘ1cðèÑ#…{ÿþ=^¼x#FÈÛ»w/N:{{ûrBÄÃÃoÞ¼A×®]áèèˆ'Nà÷ßGpp0|8"""¤^«*êÅ‹ Ý¡«ô9_ÿÂ׺"¯WWW¼ÿ]ºt££#±sçNÜ»wÁÁÁR¯}ˆ¤¤$xzz"%%ÿûßÿpåÊ„††{~Ê_~ùK–,AݺuÑ¿?~ƒFTT”ØmÙo߾ŇDW|Ùööö8{ö¬Ô:úõ뇔”ôèÑÉÉÉ¢üïÞ½‹*UªˆÅlÿÿþróŸ5kV¬X+++ôïßÇŽÀ9sæHÄ;v C‡…¾¾>z÷î DFFââŋӋÉûù*:‚8&&°³³S(ž±2§L/!á×§OÒÕÕ• *ºººØ¶yóæIìGDdoo_èè2Yòòò¨jÕªÔ¦M±íÂ~£G–Øgþüù€®_¿.õ˜ŠŒð{ýú5©ªªRŸ>}$êµ°° KKK¹ye„_YPd„Ÿ4999¤««K;vÛ.á7a‰}fΜI(44Tê1áC€,¶=77—LLL¨aÆE>—òR¿~}6l˜Rûfff’ŽŽ¹¹¹‰mŽð›>}ºÄ>“&M"ôøñc©ÇTd„ßãÇ 5Jl{vv6R‹-Ķ›™™Iý‘ˆ(,,ŒÐܹsEÛ„#ü|}}%âüñGÑèYåŽð{øð! ±íYYYd``@vvvbÛ“’’HKK‹úöí+³NYÌÍÍiìØ±EÚ§}šÐîÝ»åÆ½yó†vìØAïÞ½+ô˜ÊPt„Ÿ´»B„×z—.]Û.áçââBŸ>}m?qâÒ¯…áNªªª4zôh±×ÄèÑ£IUU•ÂÂÂäž‹<999ôüùóBKqïò!"ºuë Ë—/+­ýøá$¯C…#üzôè!6ñðáÀæÌ™#³EFøÝ¿ŸTTThüøñ¢öÏÏϧ¡C‡’šššØµshh( eË–I=ÖèÑ£%~ÞÂ~={ökçýû÷Z¸p¡Ì܈ˆzöìIVVV2Ÿ !@@“'OmËÏϧ’ºººÄ(àÄÄD200 ¶mÛé÷Pø^xçÎ…÷òóó#tæÌ™"ï[T<‹2¥ÔFøM˜0AlDÔŠ+P­Z5ÑÈ.%6ožPjj*bccQ§N™“ŠJlëܹ3æÏŸèèhÑœzE†¼¼|h}}}DEE!77÷»žo$)) oÞ¼¥¥e‘ÛýúõˆŽŽFÆ •ªûþýû>ÿ~Ùþ†††ˆŒŒ‰FÁ©©©‰F |I8:NÚ7k²òß¼y3¢££•ž òÞ½{>¯þ'-ÿððp‰øÌÌL :T©ú¾µk×dŒ1öíÎÙ÷¥/G°‡‡‡ë]Úˆ$¬X±OŸ>•ºÏ¦M›Äæ¥svv†½½=>Œ 6(ËÖ­[‘©S§Š¬7n6n܈ƒʽöÌÏÏÇ‚ ФI“Bççª^½:F¥t®%EÚ<ÎX¿~=bbb¤.†·e˱kݺuƒµµ5>Œ_ýUé\¶lÙ‚¼¼hР>Œ… *˦M›@¬ýÆŽ‹={öÀßß_´øGRR@OOO걄sé¿}ûVbUæmÛ¶‰íß¿?æÎ‹C‡I…W”üؼ>>>Ø·o0}út±øÔÔTlÛ¶ ºººJ׫¨OŸ>aÙ²eprrB×®]K½>Æ”Qj½_®ì¹gÏÔ­[^^^%ZOPP6n܈;wî )) êêêPQQ)Ò€pÿ·oß*Ç‹/|^DAØù!T­Z5T«V ïÞ½ûî:üNž<‰Í›7#88ÉÉÉÐÐÐP´ÎÜ’lÿ””‰ö733ƒ™™Þ¿/1¬\Q¸qã†Ü˜%K–`öìÙJ_YÇŽÖ-[Š””hjj"??¿H§%Ñþ/_¾ð¹ÃìÇbÏÕ¬Y5kÖćDÕªU“¹Vrr2€oí-LI柔”„´´4±çjÕª…ZµjáãÇ¢…S"##–––J×ùµ“Ö¹ÊcìÛehhˆ¸¸8Ñ㤤$4iÒçÏŸët)î-„B©©©ˆˆˆÀÓ§OE×i?~”+í6ÓV­ZáöíÛÈÊÊRz‘ŠÈÈH¨ªªÂÇÇGjÂÏsY¶oߎàà`ܸq£T )M)))¢ö‰‰Pôöÿý÷ß‘——'õyEDFFBMM Ç—xNEE¥Ðö—'33·oß.4®M›62o¿-MIIIˆŒŒÄÓ§OEçY”öoÙ²¥hº+e ÛàÀ2Ÿª\¹2H\Ç · ã ú2@€–-[âäÉ“Jå]0?555±…t¾|¾ ÀÌ̬Äɲ|ùr¼zõ 'Nœ(Ö´`Œ•¦oz¸ÓŽ;0f̸»»cÏž=hܸ1LMMÑ«W/8pà6ltèП>}Rø8%ÑþÂU™W®\‰V­Z)*Ö‰&$ü£@ÞJÏ•dþkÖ¬µµu¡ñÂ?†”ù‰`[ÞI0ÆÊœü>öMQQQûlŽx«R¥ŠÂŸ¹ŠˆŒŒÄO?ý„Ó§O£bÅŠ°´´„©©i‘£¯¯"BFF†Ò~ 022‚§§§Äsžžž033“¹ï½{÷0}út,]ºô›šŸëÑ£Gøé§ŸpîÜ9TªT –––Jý|õõõ‘››‹¬¬,èèè(•KBBªW¯.³ý‹3BïŸþ‘:ZñKÅ™ÃO÷îÝÃŒ3páÂèêêÂÒÒR©Nt}}}dee!''GéÎæ„„˜ššÊláJ¼À¿×ÂÂ/Þ¿”œœ UUU…¿Ö××Gfff±:ŒP£F ™ùù·×Ë—/‹õ÷|Q\¾|K—.ÅÖ­[Ëüo@ÆŠâ«íðËËË+4fåÊ•hРD¯zFF†Ì}¾½ü;™~q~Y…ß$œ?þ?Óá·råJX[[ãèÑ£bÛË»ýK£Ã¯¬/ôyý¯ZµJt»KA2o].­övz?^¡?ggg=z§OŸFŸ>}Äž;vì´´´¤þËâõ£H‡Ÿð|Ïœ9#Z´¦(òóó‹¼O9Ë'¢wåc¬lñˆVTééépvv†¦¦&‚‚‚Ð¥KŸï€Þ¨¨ØØXèèè(}wðy$~tt´Ät…yýú5ºu놡C‡bæÌ™J×_ÖRSSáìì ===\¸p;vÄÇǹ#$66UªTQº³øÜþ·oß.rû+¢N:ÈÌÌ,4NÙÎbe$$$ K—.¨V­®\¹'''Ÿ¿Ì®U«V‘Ž%ì¨,ÎÈRKKK|(õùG¡zõêR‘&66fffJwöŸóùò¥Â¯ŸZµjáÚµkJ×§¨ððpôêÕ ³gÏ–:z•±¯‰b¿±%`Ö¬Ybó÷ÉcnnŽ‘ܸäädhjjŠ]Λ7.\9?ضmÛÄVóLMMņ РAØÚ*?€¥^½zèÖ­Ö¯_àà`¥£¨‹/ŠnC,D„””‰[”gΜ‰¿ÿþ[fûoܸ7oÞ=NJJÂæÍ›amm]¬Íš5kggg¬Zµ }ÂùóçK¤ýãââpéÒ¥bGá «…•’TØëÿâÅ‹HJJÂ’%KD}@ÑÛ?##—.]*‘öþü¹èËqy´´´àîîŽ .àÝ;ñïx###ñøñc…o­MOOÇ•+Wú]ž-Z &&F¡[·Ï·A'&&âĉEªGØ— oЊP||<ÜÜÜлwoÌ›7¯Hõ0Vʬï]»v Ï+Ö½{wÄÇÇ£o߾ؼy3FŒ!urüž={âÞ½{pvv†¯¯/¬­­±wï^ôêÕKlŽ’‚ìììЧOŒ1ëÖ­ƒ““^½z…%K–úMöñãÇ1yòdLžþù'–.]ŠbÇŽbñ•+WÆÚµkqÿþ}8::báÂ…7nºuësssÌ;Wj¶¶¶pvvƸqã°jÕ*´k×ïß¿/Ö6'£ IDAT„ÆÂü·oßMMM´jÕ 3gÎÄŸþ‰%K–`À€صk—Ä>¿ýöLMMѱcGüøãøý÷ß±víZ 6L4I²4=zôÀýû÷1bÄlذƒÆ”)S$â>,z ð¹£ÔËË ‹/.Öù2ÆcÂ9ýš4iRh¬¥¥%*W®Œ‹/ж‘Äõ˜p®´‚_Î]½z®®®2;LFŒˆˆÑãE‹áÍ›7×?E5nÜ8˜››cܸqxôè‘Øs ÊÉÉAïÞ½Q±bEìß¿_áÑLÀçü~úé'¼~ýºX9‡p.í‚íþüyÑ-‘²ÚÈ!¢yþ`Μ9HLL,vûOš4 &&&3fŒD§´öÿZ5lØêêêb—yyyÈÎ΋¶¿p188}ú4úöí @vû0@4¥ øúúâÝ»wÅnÿiÓ¦¡jÕª9r¤ØÏ~·‰²³³1zôhÑÏ&++ £G†ŠŠ ÆŽ+µžþýûãÕ«W¢Ç3gÎDZZšÌxEMŸ>>|¸Ä‚?ÒòŸ8q"ÌÍÍ1vìX±€ü×[“&M ªª*ñóýrEFFºu놆 bëÖ­ÊžceK™¥}X£À’Ð~~~T’ÒÓÓiþüùT¯^=Ò××'Ú²e‹Ô¸+V••Õ­[—FŽIñññtðàArrr¢ŒŒ Ql~~>999ÑŽ;èÚµkäææFzzzdkkKþþþróIKK#'''±’šš*5öýû÷4qâDjÚ´)ijj’¾¾>ÙØØÐîÝ»%b$Ž+,Ç—™Obb"9::Òܹsåæ]½zõ¢ŸþYnÌû÷ïiéÒ¥T§NªW¯=šé?þ öíÛ‹-/Ÿ••ENNN´{÷nºt鹸¸žžÙÛÛÓñãÇåÖ“””$Ñ>¶¥¦¦Ò¸qã¨I“&¤©©IdggGûöí+z#”“äädš={6YZZR•*UÈÉɉ~ÿýw‰¸ÔÔTZ¸p!ÕªU‹4h@>>>”’’B;wî¤N:‰Å _Ãû÷ï§³gÏR—.]HOOÚ´iCAAAró‰•hÿÜÜ\©±III4fÌjܸ1ihhµnÝš>,5þرcÔµkW200 +++:t(½yóFê¹:99Ñ¡C‡èÌ™3Ô¹sgÒÓÓ#:wîœÜü‰ˆ ݸqCn\bb"=š5j$Ê¿M›6 5þÇ4}útjÕªU®\™êÔ©C^^^täÈ™u$$$ÐÌ™3©fÍšTµjUêØ±£Ô×çÎ;e¾?L:µÐs..*ð>I_Á²ò\¸p)ÛRðZÀòò·K¡?¯uf%­_¿~€ÚµkG:u¢Ê•+ÓêÕ«Åb²³³©yóæ€êÕ«G–––T¡BÚ³gU¯^FŽ)¿k×.@³gÏ&555rvv&;;;@Ý»w§ììl™ù¬^½šPãÆ©Y³f4~üx©q7oÞ¤5j¦¦&¹¸¸ÐðáÃÉÎÎŽ*V¬(q]°nÝ:@ºººdll,Qâââdæ3dÈ@óçÏ/¬)•DèèÑ£2c233©Q£F€6lH¤®®N }}}úñÇÅâ·lÙBèçŸ&555êÒ¥ µjÕŠPïÞ½e^ï-Y²„P“&M¨Y³f4mÚ4©qýõ™˜˜¶¶6¹¹¹‘··7ÙÚÚ’ŽŽݽ{W¹Æ(]ºt!Ô©S'jß¾=U¬X‘vîÜ)“‘‘AVVV¢×¥¹¹9ihhP@@ikkK´Ñš5kýòË/¤¦¦F...¢ßŸR~~¾Ì|æÌ™C¨Y³fÔ¬Y3òõõ•wáÂ266&rww§aÆQ«V­H[[›ÂÃÃ%âçÏŸOÈÄÄ„ºvíJUªTýé×_%4gÎRSS#WWW²¶¶&4dȹùõìÙ“¬¬¬äÆœ;wŽŒŒŒ¨bÅŠÔ­[7±ü£¢¢$âoß¾MµjÕ"uuuêÔ©9’©R¥J,³GGGÔ¥Kj×®ikkÓþýûÅbfÏžMÈÀÀ@êûCÁ¿}KCjj*}ñ¹<‹¾‚Ï._wý›@` @ôÕ…ŸŸ¼½½‹|Æ+ »w·7BCC‹´‚ñ™§§'…ŸQýò̇1VöAÁ‹ÄDTòq±#Ö˜$|¬Ì5¾<ïß¿G@@Nœ8Š+¢M›6èׯ Äâ²²²päÈ;v 4@÷îÝakk‹­[·BOO^^^¢Øû÷ïãØ±c˜0a"##qäÈüóÏ?hÛ¶-Æ'sNb¸yó&Ξ=+zlee%s5Ò÷ïßcÇŽ C||< кuk 4HlŽÀ¿þúKîí§Ó¦MC¥J•¤>†Ý»wcÒ¤I¨Q£†Ìc(ëéÓ§øã?àåå…úõe$üø‡Ɖ'ШQ#ôèÑ-[¶Ä¦M›P­Z5±¹“CBBpòäIL™2<€¿¿?âââàää¹ó¯]»vMlÄgÆ ѯ_?©±©©©Ø±c=z„„„ÀÁÁƒ ‚žžž­QöRRRpäÈœ>}zzzh×®z÷î ]]]±¸ŒŒ ÏÏwâÄ ØÚÚrgcŒ1Æcå ??YYYÐÖÖF||<ÂÃÃѹsçòN‹±ïZ™-ÚÁce%//¶¶¶ÐÒÒBzz:Æ_ä»cŒ1Æc%ãÊ•+¨X±",,,`nnŽOŸ>{aƘ|<Â1öÝQUU…¯¯oy§ÁcŒ1ÆЬY3œ455¡££ƒ–-[âÈ‘#åcŒ1ÆcŒ±owø•£»wïÂÂÂsæÌÁ˜1c””„#F`É’%åš×áÇqûöm\»v çÏóT¬tlÞ¼^^^HKKÃ/¿ü‚Þ½{ãâÅ‹pttÄóçÏÅbsssqûömÄÅÅ)UWNN>}ú„¬¬¬’H½X¬¬¬0iÒ¤BãBBBаaC,Z´C† ÁË—/Ñ·o_ìØ±£ ²dŒ1ÆcŒ1ö-(3šL X¸'|ìççooo¹û,Z´Õ«WǨQ£Š\ßEVVLMM‘žžŽwïÞA[[»\òhÑ¢ÔÔÔOOOøùù•KìûõîÝ;˜˜˜ Aƒ¸víšèµ~óæM899aÈ!عs§(>11FFFX½z5¦NªTùùùPQ)ÿï8jÖ¬ wwwlÙ²¥Hû%''ÃÔÔÚÚÚHII)¥ìþåéé‰ÀÀ@áÃ'DT¿Ô+eŒ}UAÁ‹ÄD4«Ü’aŒ±/üÿ~ÍþÿáY"r)Ï|cìkSf‹vܼyuëÖU(öåË—xüø1’’’`aa¨ªªJ}üø1tuuaff†?âÂ… ÐÓÓƒ ´´´Š/^¼@xx8’’’P«V-888HtÄÄÄ ;; 6”Ø?%%¯^½B:uP±bE™ç­©©‰ÁƒcÆ ˆˆˆ@Ë–-eÆ>zôfffÐÓÓ“£ŒgÏžáÞ½{XµjîÝ»‡ãÇ#77*H©„……¡J•*011AFF.\¸€*Uª U«VÐÔÔ,v¼0§ˆˆ¤¤¤ˆÚ_ ˆÅDGG#77 4Ø?)) ÿüóêÖ­ %Z¥l=}úHMM…¥¥%Ú´i#q¾B>DÕªUQ½zu¤§§ãâÅ‹022BË–-¡¡¡Qhü… `ll,3øüÚŽŒŒDjj*j×®Ö­[KäóäÉ@½zõ$öOLLDll,êÕ«'úÛ³g233áëë+ֱݺuk¸¸¸àÀX³f tuu¥æ%!Û²eK¨©©IĤ¥¥áÙ³gbÛ¬¬¬ íHùò%BCC¡®®[[[ÉÏÌÌÄãÇ…ªU«¢Y³fR÷‰ŒŒDVV²³³‘œœŒû÷kÚ´i¡‘UªTAÏž=qàÀ¼~ý5jÔÏcŒ1Æcì?ŒˆŠ\X añóó£Â¸ººÒĉ寄„„Pûöí ijjŠŽoccCïß¿—º±±1ùøøÐ©S§HKKK´O³fͤÆÑرcéäÉ“bñÖÖÖ±ÁÁÁÔ®]; kggGiiib±3fÌ @@¯^½’8Θ1cHCCƒRRRäž?Ñĉ ½yóFfÌ;w5iÒ¤ÐãÕ¯¿þJèéÓ§tèÐ!@gÏž•¯««K“'O¦€€±Ÿ™½½½ÔøÊ•+Ó”)SÈßß_,¾uëÖ±7nÜ ‰öwpp ŒŒ ±Ø‰'’ªª*ÅÇÇKgèС¤££CéééElÍ;—ú÷ï/·=zTéã]»vZ·n-q¾NNN”™™)u š5kíß¿Ÿ444Dû´oß^j¼ºº:ùúúÒ¾}ûÄâ;tè û×_‘½½½D>:t ¬¬,±Ø1cƺººÔ×xß¾}IWW—>~ü(ÚæééIêêê?G"¢;w mKHH 4oÞÂ"ëýíK^^^¤¦¦&óõP’<<< æIJ|VpáÂåÛ._¼W-/ï|¸páÂ¥`p¿À{TPyçÃ… ._[)ÿûÛ ÐÔÔ„ƒƒ"""‘‘gÏžaÒ¤IÆÊ•+eî÷äÉ :³fÍBHH<(7>22C‡…¯¯¯Üxuuu899!"">|ÀÓ§O1aÂܾ}«W¯‹õööI,*ÇÃÓÓúúú…¶ÁÍ›7aaaêÕ«ËŒ111A:uàèèXèñŠêÈ‘#hÚ´),--áêê øûûËÝçÑ£G9r$æÌ™ƒÐÐPìß¿K—.•†Q£Faîܹ¢xi󪫫£sçÎˆŠŠBFF¢¢¢àããƒëׯcÆ b±ÞÞÞÈËËÃÁƒŶgffâèÑ£èÓ§ÜÑ•…QWW‡¦¦¦Ü"k¤¢ÔÔÔàêêŠèèhdddàÉ“'5j®^½ŠÍ›7ËÜïîÝ»;v,.\ˆÐÐPìÝ» .”?nÜ8,Z´H¿`Á‰¸ *ÀÝÝ111ÈÈÈ@dd$FŒË—/ã·ß~‹õöö½Ö JOOÇÉ“'1`À±´oß¾…±±±ÔÑv5kÖ¼yóFâ¹Å‹£víÚxýú5ÒÒÒ°aÃܾ}[êœx;vDDD"""ä¶ŸÐÏ?ÿŒ7bÞ¼yxñâ=z„Î;cĈ¸sçŽD|@@z÷î Q¥¥¥áúõë3fŒDü™3gêÕ«cÀ€¢Ü"""~mÞ¼y-Z´9–1ÆcŒ1ÆPz#ü.\H¢bddD–––bÛvïÞ-±ß—>~üHÚÚÚRG }á§¢¢B…‹èó?¥Gcedd¦¦&9;;KmÛ»w/jÔ¨ggçBýã?ÂÜÜsçÎU*·âò÷÷¡F¸uënݺ…zõê!11ýõ—Ìý† www…ë:t(ÜÜÜ”ÊQWW6662Û?88QQQ¢mûöíCíڵѶm[¥ê+ohÑ¢…ÔÑnB£FRèõ%4zôè"ÅTµjUX[[KÍÇÛÛÿý7^¾|)Ú¶oß>4jÔ¶¶¶b±999RçÝ )ùéÓ'‰çZµj%±­ÿþÈÍÍÅ£GŠt.ݺu Ÿ>}B£F$*ÁÁÁhܸ1BCCÅâïß¿ØØXüôÓOe²H^^|||вeKL˜0¡ÔëcŒ1ÆcŒ1öm+µE;Æ/öØÍÍ uëÖÅúõëåî—œœ ???ܹsOžoÞ¼AJJŠÔÅ! €)S¦`ïÞ½hÚ´)ÒÓÓqâÄ L:µÐN={öàâÅ‹8þ¼ÌÅCJ›ðÖ]i+)ûûû£C‡R÷+ÍöOHHÀ®]»"Öþ-Z´ˆ4hf̘!ºE5%%AAA˜;w®ÌE/5~üøB;”FAƒ«žøøxìÚµ ¡¡¡ˆŠŠBll,RSSagg'sŸÒlÿøøxìܹwïÞµjj*Ú´i#;dÈüòË/Ø·o|}}ñöí[\¼x+V¬ˆ566Frr²Ô:…Û«U«¦Pހם_+xV’¢££kÖ¬Áš5k$žÈÈÈ-ú"|-H[¤§4¬Y³ááá¸yó¦ÌŒcŒ1ÆcŒ1¡2[¥W·nÝBçΡ­­aÆ¡{÷î°´´Äüùó‹õǼ²nܸ.]º bÅŠ6lzôèKKKÌ;ññññzzzðôôľ}û°|ùr=zYYY>|¸ÜzþþûoüðÃزe‹ÌNµÒ‡7nÀÇÇ?üðƒØsãÇG@@6nÜX쎳¢¸rå ÜÜÜ §§oooxzzÂÒÒ3fÌ@ff¦D¼‘‘ÜÜÜD~GŽAnn.†Zì\5jTè[¤@[[[j‡ >={öÄ­[·°wï^8;;£FÅÎeìØ±Å>†"u#** ¢íZZZ2Ï·´ó©^½:ž 88X¡?aüíÛ·%nW.I!!!0`V­Z…^½z•Z=Œ1ÆcŒ1ƾ/e¶J¯››[¡E‘‘‘hÞ¼¹XgßÙ³gqöìYäçç—vŠRóiÑ¢…Xß™3gpþüy™ùtîÜ&&&X½z5.^¼ˆ#FÈ<~bb"ÜÜÜàîîŽÅ‹)·ôôt©Êò÷÷‡™™™Ô9Ò<==!päÈ‘«¯0ùùùˆŠŠ‚Xg_`` ®^½*³ý…#ä–-[†k×®Émÿ¯Inn.ž>} [[[±Î>\¿~½Ì_ÿÙÙÙxþü9ìììÄ:û:„[·nȨ́gÏžÐÕÕÅüùóqçΙí?jÔ(¨¨¨H¬ž‡cÇŽ¡k×®R;ð6mÚ$v+pNN¶mÛKKK4iÒD™S8::ÂÌÌ óçÏW¨sµeË–¨_¿>-Z„ÄÄÄ"Õ¥««+6Ϥ,/_¾D÷îÝáãã#sT%cŒ1ÆcŒ1&M™ðûrN?iìììpáÂ,Z´¶¶¶ ‚ŸŸ6lˆ˜˜˜2ÈR2ŸóçÏcÉ’%hÕªNŸ>Ý»w£aÆxñâ…Ô}TUU1dȬX±+VDŸ>}¤Æegg£GHJJB«V­°uëV±çmmm¥ÎSüÛéàà€óçÏëˆå7nœÔ[vÍÌÌ`cc¬]»¶Øõ)BEE6668yò$V¬XfÍšáäɓػw/6l(³“EMM ƒ Âúõ롯¯¯ôâ,e­B… hÙ²%±råJ4nÜ'NœÀþýûÑ Aƒ"w*—ºº:š7oŽ£GbÕªUhÔ¨Ž?ŽƒÊÍGSS^^^ضmŒŒŒd.æR³fMŒ9;vìÀ?ü€Aƒ!>>K–,AVV~ùå©ûååå¡sçΘ5k,--±xñb„‡‡cëÖ­ÅZHìcllL>>> ×addDcÇŽU(6::š\\\DùÔ®]›®]»FK—.%”™™)u¿ˆˆ@#FŒyìׯ_SÁ6ü²,^¼X澤¥¥EÎÎÎ Ga¶oßNèÒ¥K2c–-[FèöíÛbÛuuuiòäÉ ×U¹reš2eŠB±áááÔ¹sg€¬¬¬èÖ­[4wî\RUU¥ÜÜ\©ûÝ»wиqãÎëkðèÑ#êÔ©“è|ëׯO!!!äëëKêêꔟŸ/±††Íš5Ká:ÔÕÕÉ××W¡Ø‡RÇŽEù4hЀBCCiÆŒ¤©©)s¿›7oš6mšÜãçççÓÔ©SIGGGôº777§«W¯JÄ&$$Z½z5ýòË/¢}ÔÔÔhÁ‚…žK``  7nÈ»rå Õ­[WìwQCCƒV®\)5þþýûdcc#_­Z5Z²d‰Ì:ÂÂÂÈÎÎN¯­­M3fÌ‹ •ûþ°}ûöBϹ¸<<< ÖI_Á²ò\¸p)ÛòÅ{Ïòò· .\ ÷ ¼G•w>\¸páòµQÑo Öî ûùùÁÛÛ»ÈÇ‘%66&&&%vÌâøçŸ ¢¢¢p>W®\A‡pçÎØØØ”JNééé¨X±b™.¢Q^^½zuuu…Wm ‚««+ }‡ôRÙ´i"##ѽ{w9r®®®zG©T"44QQQHMMEëÖ­±lÙ2ò'ˆˆˆˆˆˆª?þë·‚üõ×_ú᥶oß>}‡ðR Õw2سg¾Ã """"""Ò‹ú€ˆˆˆˆˆˆˆˆˆ*~DDDDDDDDDÕ~DDDDDDDDDÕ~DDDDDDDDDÕH…,ÚSSÓŠ8UÉÉÉúˆˆˆˆˆˆžR…$üV®\‰•+WVÄ¡ˆˆˆˆˆˆˆˆˆèpJ/Q5„Q5ò´Sz¯èT‘UrCªÕûHÕS,D/Z¶¾ """"""Ý=UÂO‘ ¼‚c!ª´ …w±Mg„wô Q)8¥—ˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨1ÔwDDDDDDDÏ ¹B¡˜¯ï ¨ÒÙ.„ˆÖwDú„UeÍÌÓwTéÄ`Â^ZœÒKDDDDDDDDT0áGDDDDDDDDTpJ/U5Ýè;ªTÚ8ªï ˆ* &üˆˆˆˆˆˆ¨JB<Öw T¹( >Dj8¥—ˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aˆˆˆˆˆˆˆˆ¨aÂH7ú€ˆˆˆˆˆˆˆHLøU#LøU#Lø•_.€L}ADDDDDDD¤ ~Då—#„`ˆˆˆˆˆˆˆ*%&üˆˆˆˆˆˆˆˆˆª&üˆˆˆˆˆˆˆˆˆª&üˆˆˆˆˆˆˆˆˆª&üˆˆˆˆˆˆˆˆˆª&üˆˆˆˆˆˆˆˆˆª&üˆˆˆˆˆˆˆˆˆª&üˆˆˆˆˆˆˆˆˆª&üˆˆˆˆˆˆˆˆˆª&üˆˆˆˆˆˆˆˆˆª&üˆˆˆˆˆˆˆˆˆª&üˆˆˆˆˆˆˆˆˆª&üˆˆˆˆˆˆˆˆˆªC}@DDDDDDÕ‹B¡00RßqÐKűXÝG¡P˜ê%zYý-„x¨ï Š0áGDDDDDDÍÀ:}A/µþWˆ^”v*MÂSz‰ˆˆˆˆˆˆˆˆª&üˆˆˆˆˆˆˆˆˆªNé%"""""¢çj̘1˜4i’¾Ã "ª0ÁÁÁ˜9s¦¾Ã(~DDDDDDô\Õ«Wúƒˆ¨ÂÄÆÆê;„RqJ/Q5„Q5„Q5„Q5„Q5„Q5„Q5„Q5„Q5b¨ïˆˆˆˆˆˆˆª›ììl¯]»¶ÜIÞ—]µX¥÷Ã?,s©p"¢ªdãÆ¸}û¶¾Ã """ª¶rss5FÃåççKûŠ>|~~~%NýMNNÆèÑ£qûömÌš5«Äó?'NœÐ8ß®]»Ùj§e9xð † ‚´´4cž:u ]»vEPP¥}·nÝÂ¥K—¤z«V­ÊL:Õ©S#FŒ° “ME-¢R©ðꫯ"""B¶ýÉ“'øá‡päÈ;vL–@ÌÏÏ—î·¶„àòåË(}7E²²²°oß>¸»»cóæÍðóóÓˆeäÈ‘%¾‹/..#GŽDBB>ûì3…ÉÊâÏDAA ¤[¶l‰–-[j=&ìÚµKVo×®ôY!›âûÚk¯•xœòºví|||’’¢uZZ,X€«W¯j$:Õ}ÿý÷ˆˆˆ%U*Nž<‰:àÏ?ÿDŸ>}tŽ+&&¯¿þºÆÂ*• ±±±xûí·1aÂüôÓOP(²6Ý»w‡B¡€÷ïßGTT””x¥²U‹„ß§Ÿ~ ;;;}‡ADTa>Ì„Ñsdoo¬¬,ܹsGÚfkk‹š5k¢aÆ 4R²¯hD^Ó¦MqêÔ)>|XÚ·xñbL™2¥ÄNœ8 6 VVVصk¢££wîÜÁ”)S°oß>bÏÌÌÄèÑ£eÉ>´iÓ›6m£GŸŸ)S¦ wïÞR"ñúõë²ã”5­ˆú´SRÜÅ)•JDDD yóæxçwŸŸß~ûMú{í… ðõ×_cÑ¢E:766V–ì300À€`bb‚mÛ¶!??ééé=z4dV¯^-KöÙÛÛÃ××))) ‘{sçÎÅk¯½†®]»¢aÆHMM•%Ílll`aa{{û2ãÍÉÉÁÏ?ÿ,ÕÍÌÌÐ¥K©~îÜ9ܽ{Wª»»»ëtt1sæL)n¼ùæ›pwwÇÅ‹*=+›7oÆÜ¹sKLZ†‡‡ÃÄÄ#FŒ@£F‚cÇŽ(L²Nœ8±±±SKòþûïË’}mÚ´AïÞ½eÏÅŠ+гgO¼óÎ;²¾æææhÒ¤‰ôÜ:tˆ ¿òBT¹`&QTRRRQuâåå%Ô~çÂE%øíeaay¹Šúßµ|¥ïxXXXªV`ªþ;2cÆ Q=zTý·NlÞ¼Y¶?))Iôë×O899 …B!6nÜ(Û(ëõêUißš5kdûêÕ«'nÞ¼)íÏÊÊݺu“µ9zô¨´Û¶m²}Ç—öÍš5KÚ^£F qäÈißãÇE—.]¤ýƒ ’ömß¾]vÌÉ“'ëtŸöìÙ#ë÷Þ{ïIûdû<==ÅãÇ¥ý·oßÒ~333qïÞ=iÿ˜1c¤}VVV²óöéÓGÚW·n]‘˜˜(íKNNvvvÒþÅ‹KûÒÒÒ„´ÏÃÃC¤¥¥Iû###e1÷ïß_ÚwúôiÙ¾Õ«Wëtrrrdñ ,µY±b…´ÏÚÚZ§ã¿÷AAAmòòòÄ!C„³³³000ÐøóVüYü믿¤}ááá²}"<<\Ú¯R©dß±bÅ iÿåË—eûV­Z%íÛ¼y³lßÊ•+e1=ZÚçêê*”J¥Æµ½ñÆR›~ýúétÏ^”­[·Ê®€‡¨¿¿E…ïð#""""""ÒÂÁÁ»wïFBB2220bÄi_AAš4i"kŸžž^â±>þøc4jÔHª›™™añâŲ6Ç×).õ•[}}}ñꫯJu L›6Mªoß¾]Š«øâ fff:ÏÔÔTV×6õ¶ÈçŸ ©Þ AY<ÙÙÙ8{öl™çÌÎÎFpp°Tÿý÷Ѹqc©Þ°aCŒ7Nª¯Y³FúŽÔÔT©>gÎÔªUKªwìØß|ó –-[†°°0Ùª»O#??Äþýû¥mÍ›7ÇôéÓeíÔgðè2bPWFFFøí·ß‹ÌÌL,\¸PÚ'„€£££¬}iÏé»ï¾ ///©®P(°xñbÙ º®¦¬þœ:::büøñ²˜çÌ™#Õ/]º¤uè¢Ñ¶Jœ®LÚU‹)½DDDDDDDÏ“……Î;‡àà`iõÓâ ´âï™Sçé驱ÍÛÛ[zG\½zµÌ8T*•ljnvv6V®\)k£žBàæÍ›¨U«–ÆâÅß«V’âíêÔ©Sb[m×Y|!Œ«W¯Â××·Ôs^»vMº/@á%ů3))IöY¥R¡F«'k›:;uêÔRϯ«üü| <ÿüó´ÍÑÑÐH”ª/–––rþâLMMqõêU#44GŽ‘%?‹b.‰¶ïÏÎÎMš4‘íÐå9 ûLLL4¾?!jÔ¨!-Æ‘˜˜¨ñ¬¨ß§û÷ïët^*Ä„Q rss1cÆ lÛ¶­ÌF5j”<‰NÛ ´†††¨S§Ž”)kÅÙ¢6Å9|øp©}’’’àææ¦1ªìÚµkežÐ|÷ŸúHEuÆÆÆZWü-~íº\gñ¤ÝƱqãÆÛçççãÎ;hذ¡FBÊÖÖ¶Ìó=‚‚ :Tc$ÛáÇ5F½¿ïy$ü‚‚‚°fÍ{W\yŸÓ¢íE ?]¾?@þÆÇÇc„ ¥¶WOàQ_ॴ‘‰¤‰ ?"""""""-²³³ñúë¯#,,LÚfooþýûãµ×^Cvv6F%í+-‘òàÁƒÏQD—Ä”úÔJ pÚqYÓC Žt³´´ÄãÇQQQˆ‰‰ÑX”£xßí۷˶uíÚUkÛ¼¼m¶È”)S0vìX,]º”¶»ººÊÚ­X±BVÏÎÎF`` ,X€ÁƒÃÏÏOz_`YÏüñÇøî»ï¤zݺuVæHË HŸ+2á§þœÀ‡~(ûŽŠ?§yyy%ë?þÐxWÞÎ;qçΩ®Ës íÚµ“>Ÿ={Vc*ð¾}û0pà@Ì›7Û¶mÓx× E088'Nœ¦iª/º T*¥QTJ¥ÿý7¾ýö[YõäDqQQQxã7pêÔ)<|ø+V¬­¦Ú¸qc ýôSL:U–üyå•W4’~6l€‹‹ F…   Lš4 ;vĸqãdÉÂW^y¥Æ¶yófŒ?±±±HIIÁ´iÓdɺ>}úh$ä´111Á‡~(Õƒƒƒñé§Ÿ"''ùùùøõ×_ñßÿþk×®Å'Ÿ|"Kêµk×}úô‘êüñpöìYÿüs©]ûöí¥Ï?.uµãò(¾8ÈÚµk¥©°‘‘‘IÛÒžÓÔÔTøøø ,, =®]»0vìXÙ¹Ô¿—Ò|üñÇÒ}ÍÉÉÁ!C¤÷FÆÄÄ`âĉøóÏ?ñùçŸã½÷ÞƒR©Ô8F\\œô¹´‘¥¤…¢Ê3ˆ¢’’’"ˆˆª///¡ö;.*Áo/ ËËUÔÿ®à+}ÇÃÂÂRµ Sõß‘3fˆÊèñãÇÂÔÔTûÍMš4B¢±¯iÓ¦¢^½zۈ͛7KÇ^³f´ÝÔÔTÔ¯__kŸ¢²eËYlÛ¶m“í?~ü¸´O¥R‰Î;Ëö+ amm-ÛV£F ±sçNë.((Ç/5žâ¥M›6âæÍ›Ç ÚØÙÙi½ŸEÅÀÀ@\¼xQÖ̘1Ò~+++Ù¾{{{Ù1 …………l›¥¥¥¸té’¬o\\œ066.óºœÅÇ¥~999Ç êׯ/„bíÚµåºocÇŽ•Ž™™) ¥}QQQÚL5{öì‘/((H£Mbb¢033Óˆ·I“&Zcš5k–Ô7<<\¶ÏÉÉ©Ôë™={¶ìÜ—/_–í_µj•lÿرc5ŽQ«V-m‹-Ò¸.¥R)û.öíÛWæýz‘¶nÝZü:–ºÆcÙ²e04ü¿eîÞ½‹„„aÁ‚èß¿¿´¯¤ç(œ"Þ³gO­û¦NŠ ”+¶+V`ܸq²©ðê«íà³Ï>ÃìÙ³5úÆÇÇKßµ©©):wî\®s¿ì¸hÇ  OOÏ ;Þ[o½%-‰íííõë×—«ÿˆ#púôi@›6m4V]ªiii ÂßÿË—/ËöÙÚÚÂ××'NÔx¡k•J%ûÑ3f f̘¡óù³²²dC¦'L˜€É“'—ó*ž]Ÿ‰‚‚¼òÊ+HLLìØ±=zô(µÏ¼yó333$''ãÔ©S9räSÅùþûïkü%GWÓ¦MÃîÝ»®Êtâĉrõ_°`¶nÝ  ð/BÅß=ñ4-Z„-[¶ÀÑÑ111²•²ˆˆˆˆèå³zõj´k×aaaxðàÜÜÜàáá¥R CCC|ûí·èÛ·/¶lÙ‚øøxÔ­[íÛ·ÇÈ‘#áèèœ9s@aB§Hûöí1oÞ<…«³:99áäÉ“øóÏ?‚û÷ïÃÓÓ”½“¯ˆ«««Ô€F²­V­ZØ¿?BCC±cÇ\¹rppp€»»;>øàƒ2“y“'OÆØ±c±wï^„„„ 11>„……ìììеkW¼ñÆ¥&ú^ýué<Íš5C—.]…;vH -oooŒ1BëqÔ§µPø¾¸³gÏbçÎØ»w/‘——‡Æ£K—.3fL‰§å•WpöìYlÞ¼çÏŸÇÅ‹allŒ¦M›¢G8p Ö…:–-[†6mÚàСC¸{÷.Ú¶m !н{wäbiÔ|0dÈDDD(|÷Þ'Ÿ|Rjÿ-ZÈžƒ.]ºhm7vìXxyyaåÊ•ˆ…‰‰ :tè€ÂÝÝÒs¦P(ðäÉÔ¬Y²›žt IDATãÛÛÛcß¾}Ò÷wåÊ´o߯¿þºÖ‡Ö­[WÖ¿øõbÕªUøàƒðûï¿ãÂ… HMMEýúõáââ‚qãÆ¡yóæZ¯IýÝ„ýû÷×Xý™Ê ï!†OSP…¦ô&&&Š › =®›››tý¯½öZ¹ûwëÖMêß¾}û M!<¨1ôº¤2xð`‘™™©q ¥R)k÷É'Ÿ”+†ÇËúÏ™3§¢.ï™ÜºuK 6L˜››ëÔ~É’%²ë.µý¥K—„‘‘‘ ÆŒ#„âðáÃåv®^žeúŰaäãÔ­[·Üý'L˜ õ766~ê8Ô;vL:æ§Ÿ~Z!Ç|8¥—……EߥØ8¥—……¥\UdJ/韟ŸŸôœ´mÛVßᓦvÀ®]»`ll,kÓ´iSÏ_UŒ;“'OF^^¾ÿþ{|ÿý÷ú‰ˆˆˆˆè¥'[ùµˆ£££¢yñfÏž­[·¢  Ë—//ó•M/£7JïïûüóÏa`` çˆª&ü*!•J%-]ý4„ZßCð,t=æ¨Q£d ­·ß~¿þú«ì]¾¾¾ðõõÅ€пäææÖ­[‡ÀÀ@Ù YŸ%&##£2—Œ/ï1ŸW_uk׮ŗ_~)-W^ÿýï¥Ïê/e­]»¶lYú"Å]½{÷ÖXÒ½$J¥ò™~tõùœ0@zŸãúõë±páBXZZVh,DDDDD¤ú ƒ"vvvX¸p¡¢yñ\\\€eË–a×®]¸uëôV¥²lÙ2€‡‡üýýõMÕÄ)½ÏAdd$úôéƒcÇŽIÛ222ЧOôéÓGc%ž'N`ðàÁprr‚©©) aee|ñÅÒôÌÒ¨T*¡sçÎ077G³fÍ0hÐ œ?þ©¯ãÊ•+=z4ÜÜÜ`bb‚æÍ›càÀøý÷ßµ¶?þ¼lQkkküüóϲdŸ:___Œ7Nª !Êi•™™‰O>ùîîî055E›6m0vìX¤¤¤h´ÍÉÉ‘îyŸ>}ð믿j=æáÇ1hÐ 4oÞ&&&pssÃÈ‘#qüøñRc ÿ¯Ã[o½…æÍ›ÃÐÐõêÕƒ——~þùgäååIí.\¸€>}úàСCÒ¶ÜÜ\)6õÑw³gÏ–’}FFFxýõ×ËŒ£èzûí7©þÆoèÔOW±±±øàƒмysXXXÀÐÐ5kÖDË–-([i©47nD¯^½`mm† ¢_¿~ZGêêÁƒ˜4i<==annôë×Ë–-ƒJ¥ÒÚ§Q£FÒËd322°iÓ¦§>?•¡¡!Ú¶m‹ZµjÁÍÍ }ôöï߆ ê;´fþüù°³³ƒÖ­[§ïp*•°°0\»v ¦¦¦X²dÉ3 ˆz©éû%‚OSPÉíøçŸJ]aãÆRÛ+V”¹hB¯^½Ä£GdçP_´ÃÃÃCôêÕKk_±råJËZ´cݺu¢fÍš%Æ4jÔ(ñøñcYŸÅ‹ËÚ,X° Ì{•’’"jÔ¨!õqtt”ö_´£_¿~ÂÅÅEk}dÇÎÎÎÖÚîСCÒ‹eK*­Zµ ²~ê‹vÔ®][Œ;Vk_…B!¦L™¢qÞ²í  4(1¦îÝ»‹[·ni½¦9sæHíüüüʼW/í`aaÑw)ö›ÊE;XXXÊUÀE;ˆ¨šã¢T¢˜˜HuCCCxyyÁÕÕU6-ñСCøñÇK<ιsçpèÐ!XYY¡{÷î²%­sss1qâD\¾|Yç¸Nœ81cÆÈ¦æ¶nÝZ¶ö¯¿þŠ)S¦Èú]¼xQVå•WÊ<—ìíí¥úÍ›7q÷î]­mÿùçÄÆÆ¢~ýúèÑ£4h í»ÿ>†.Íñ×ŪU«°xñbÙ(°öíÛÃÈÈ@á”ÕyóæáçŸÖè;~üxìÞ½[ª›››ÃÙÙYö½EDD`̘1:ÇSÄÝÝëׯG\\¼½½uî§þ²[ww÷ ›.›™™‰"''@áîmÛ¶Å+¯¼"Ý+ ðyž={v‰ÇIKKÚ5k`bb‚Î;ÃÍÍMŠQ%K–`ÇŽ:Çuûömôïß_6º³qãÆ²ÿ+xøða \Ú·hÑ"é=ƒpàÀìܹSª:÷ïßGll,’’’ЩS'ißöíÛ‡N:!!!o¿ý¶´ÏÔÔTz&† &;¾¿¿¿Æâe9|ø°ôY=™õ¬öîÝ+[AyïÞ½¸páN:…ôôtY¶¬ç´Y³f¸xñ"Ž?ŽóçÏc×®]²¤á¬Y³PPP S\3f̼5kÖÄöíÛqíÚ5$&&bÆ ÒŸŸ'N`ÿþýý]]]¥Ï™™™8uê”Nç%""""""ªì˜ð{ÌÌÌàäässsi›œœœàää$½ÓnÞ¼yÈÌÌÄ™3gŠ-ZHíe‰£´´´ÏW£F ¬Zµ 666Ò¶)S¦ÈF‡íÙ³G§L8sæŒTŸ?>ºwï°°°À¼yóàîîÈÏÏÇwß}WbŒuëÖ-ó|ÚÚedd”ØîÇ”a†††øá‡d×­žˆ+Ío¿ý†‡JõåË—K   `ÅŠÒ÷wëÖ-lݺUj»zõjé³µµµ¬­½½=‚‚‚àíí÷ßË–-ƒ‰‰ LMMáää ©¯B¡ž õíO+))Iú\‘+ë<™™™ˆŽŽÆ¡C‡d‹˜››ËÞXÚs AAA²çü7Þ½€õÊ•+:FÍÈÈÀæÍ›¥z@@ÞyçÀÈÈ£FÂ!C¤ýß|óÆ1š4i"«'&&–y^""""""¢ª€«ôê™™™Ú·o pÔYXX>,ª+¢>ª8'''´jÕJcû€ p”_BBZ¶lYj ðÏž¡¡¡4¢°´‘¥DDDDDDDU ~zöðáCüôÓOØ·o"""J\U´´Uiµn·³³“Õ¯_¿^î„_Y+½ª'ëŠ'÷®]»&%3K£>-XÛqЍ'pÔ©_ç“'Op÷î]Ô¯_¿Ôsª_g^^Z·n]jû¢ëB >>^Ú®þA}ºwïžìÙ©è„_~~>–/_Ž={öàèÑ£²)ÎêJ{Nmmmaff¦±]ÛsZ–âÏé'Ÿ|‚O>ù¤ÄöwîÜA~~¾lú0XZZJ£™ð#"""""¢ê‚ ?=JJJ‚¯¯¯4Š (uäããƒW_};vìÀéÓ§ ÔJZ¨¢xòÅÄĤ̘²³³u ]¢žðóôôÄü!ÕCCC1hРRû_ºt ÷îÝ“êÍš5ƒ­­­Ö¶%]§ú»Ý®³¬‘hÅ©'üÔG–•÷~=/ê1i«?‹ÇãwÞÁÁƒ¥mèÔ©|||]»v(ý9U_FÝ‹xNU*’““áää$Û®þìèr^""""""¢ª€ïð{A´%`¦L™"%û ±yófÜ¿ûöíìY³dSdK9•˜˜¨õø·oß–Õuy¯[ñ÷šÅÄÄ ;;»Ä¢¾PGïÞ½e}׬Y£1«¸â‹‰?†º’F~©_gíÚµQ«V­RÏ Èï…µµu©×˜C‡(üÔWAV_!VÝÎ;qáÂ…G›”«W¯ž,ÙVÒJÇOcéÒ¥²dßâÅ‹qçÎ;v .”¼,í9ÍÎÎÖz¿*â9ݾ}{™ß¡¶EdÔßYR¢™ˆˆˆˆˆˆ¨ªaÂï9R1T<ñ#„@hh¨T÷Ýw1lØ0Ù;êÔGÏ•4Õ(œŠ¢±}ûöíÒg###888”³³³³¬SSSYY½z5öîÝ‹7nȦHº¹¹I |…ï•6l˜ÖÅ„øú믱{÷ni›B¡ÀG}TblGÅ;wdÛ222pàÀ©®ëbê×ùèÑ#ÄÇÇË®±  ?þø#BCCq÷î]ÙuªO‹ŽŒŒÔ˜’†·ß~îîî°°°-QÚ3ñ,ŒŒŒd‹—TdÂOýÙj×®fÍš%KŽéúœÞµ¨N}T( ÛwX|jzxx¸ÆsúçŸbÇŽˆ‹‹ƒB¡Ð}˜››‹¼¼<©^|j1QUÅ„ßsdii)}ÎÎÎÆíÛ·ñèÑ#<|ø²ÑEQQQ²¾?þø£lŠ’V®-2bÄœ;wNªÏŸ?§N’í/>õU›ž={ÊF°}öÙg¸zõªTß²e &Mš„wÞy...˜:uª¬ÿ·ß~ SSS©~æÌ´k×sçÎÅîÝ»Ž•+W¢gÏž˜9s¦¬o@@€ÖÅGŠäççÃÏÏO%–››‹±cÇÊVÛU_ñµ4£F’Å [avþüù˜6m^ýu899aãÆÒ¾ÿ÷ÿþŸôY©TbĈHNNDGGËF-ȪPµ)„Àõë×ñøñã yœú½SŸ&ý¬Ôïo||¼ìY •%mŸ²¶Enܸ!«·k×®ÌóU|‡ßs¤¾ø„J¥‚““”J%~øáÀÃÃCJÊ]ºt mÚ´AïÞ½qèÐ!\¼xQv¬ôôôÏcbb‚»wï¢]»vhÙ²%?~,›:innŽ… ê³æÌ™ƒÑ£G(½åîîŽnݺ!--M–D´°°ÀôéÓeý;t耵k×Âßß_Zý4==_|ñE©çíÞ½;–,YRjDEEÁÁÁ®®®HJJ’Ý—-Zàƒ>Ðé:4h€ñãÇãûï¿üûï¿hÙ²%:w¸8ÄÅÅIm]\\0|øp©îçç‡~ýúáŸþœ8q¨_¿¾ÆÈº·ß~mÚ´‘êÅqqqJ¥ÂW_}¥‘<-¯W_}ÇŽ­£*ŸVÇŽ  ð=ŠmÚ´Á€pöìY?~\cjrFFj×®­qäääàÍ7ß„ƒƒLLL4VU^²dI©ïT÷ÙgŸaïÞ½P©TÈÉɺví ;vLJ<Ö¨QóçÏ×诞 ¯_¿~‰‹ßÑS1V¯|÷ÝwXµj•¾b!"ªpê3Æþ§€sZšêGÑ"U©˜ @•””Q;wN¨ÇYTfΜ)„"<<\jmccc#ÆŒ#Õ …¸~ýºtl777ißìÙ³ÅСCµÇÒÒRìØ±C#¶nݺImÚ·o¯±öìÙB¡Ph=fÑq;Vâµïß¿_4lذÄþEÅÐÐPLžúè#Ù¶Ý»wK}‡ &m8p ˜;w®Öãˆo¿ýVãÜ&LÚkì_¿~½066.ñ;0006lÐzí3gΔڽóÎ;eÞ«ÍËËKýZÂE%øíeaay¹J±ßÔ¯ô KÕ*j•õo–jV†èû·W½p„ßsäîîŽ;v`Þ¼yˆEÍš5áááÖ­[¼¼¼púôiÌœ9‘‘‘HOOG‹-еkW,\¸æææHHH(ú&NŸ>--VбcGiUË–-±páBtêÔ ;vìÀÉ“'akk‹N:á‹/¾Ð:EÒÃÃCZ`¡ø{û`Ñ¢EèÛ·¯ô»ÀÀÀxóÍ71uêTÙb ÅùúúâúõëØ°avïÞˆˆ¤¦¦B¥RÁÌÌ -Z´@¯^½0~üx÷±Q(ðññ‘ê­ZµÂáDZhÑ"#** Íš5ƒ¾üòKÙ;ìH# ‹ÊwcccìܹÛ¶múuëpâÄ dddÀÔÔNNN9r$&NœkkkØìììŒÕ«Wcûöí¸xñ"RRR`kk‹-Z`üøñZ§Q·lÙÿüófÏž˜˜˜™™ÁÝÝmÛ¶-ñ^ÚÚÚÊîC:u´¶ëÖ­jÕª…ôôt>^ÏUNkÖ¬P¸È´iÓô QÅbªõ•„‹tëÖ :uÒC4/Þ’%K`hh!–/_®ïp*˜˜>>X¾|¹^béÛ·//^¬sûùóçÃÛÛ[*?üðC…ƳlÙ2´oß055E«V­°lÙ2(•Êû|÷ÝwøÏþ#Õ;uê„5kÖTh\U~DDDDDDD/±Q£FÁËË빞C©T"//YYYÏõ>hÚ´)òóó…B///XYYÉÚ ::Z¶ÍÁÁ¶¶¶Zã¾|ù2,,,àèèˆììl„‡‡ãÁƒèÒ¥K©÷éÌ™3ˆ‹‹ƒ««+Z·nÛ·o#>> 4€««k™÷KŸ5j{{{©nooGGG­moܸ´´4Ò¿¯¯/|||ðæ›obìØ±ÈÍÍE^^,,,°oß>tíÚUÖþÉ“'xûí·qàÀÔ«W÷î݃B¡€@ᨳ§Mø­Zµ aaa¥¶yõÕWðTÇ/Ò¬Y3YÂËÉÉIö\ª›>}:¶mÛ&ÕÕïí‘#GðꫯÊÚÇÅÅ!00žM•J…Ö­[ãСChÐ@–Zäççc„ X¹r%LMM‘““ƒ&Mš`ÿþýhÙ²å3]+|ÿý÷˜6mòóóahh•J…9sæ`þüù éÒ¨'õÔi»ŸÍš5{Ö°«&!D•+ö,,,,/Iy¬ïß]–—¯ûúJßñ°°°T­ÀTýwdÆŒ¢,“'O}úô)µMË–-…µµµhÒ¤‰Ø·oŸÈÍÍæææÂÛÛ[£ýÇŤI“ÄéÓ§E~~¾¸sçŽ Ä!Cdmûõë'jÖ¬)ÞyçqõêUamm-lllÄСCÅ•+W„¹¹¹˜6mš¬Ï®]»1|øpqæÌqÿþ}1mÚ4@,]º´ÄëÈÍÍ;v½zõ*•ªÌ{£«¥K— bñâÅâÞ½{B!?~,Μ9£µýíÛ·ELLŒðóó..."&&F*™™™ZûxzzŠ:ˆF‰ÀÀ@.víÚ%¶oß®Ñ6??_:Þ‘#G±lÙ²ãwppµjÕ-Z´‡yyybß¾}ÂÈÈHôèÑC£ý”)S„B¡ÁÁÁB!¢¢¢„‰‰‰:t¨HKK999eÞ³’¬]»Vøûû—Z~þùç§>~‘G‰Ó§OKõÓ§O‹GAÜLK IDATim›œœ,bbbD=„‡‡‡ìûÊÊÊÒhŸ””$¦L™"Ο?/ Ä­[·Äܹs1~üxö¶¶¶ÂÀÀ@ôë×OÄÄÄ¥R)þúë/aff&ÚµkWêuˆ©S§–Úfýúõ€7nœˆŽŽ·oß&LÄúõëKí[äõ×_–––B©TjÝŸ––&Ξ=+Õ###K|–ŸÕÖ­[‹ÿ»ÍCT‚ßߢ¢÷ž*h&üXXX^®Â„ Ë /Å~‡˜ðcaa)W ¿5kÖˆþýûK¥iÓ¦¢^½z²m_~ù¥¬OË–-…‹‹‹¸ÿ¾l»°¶¶Ö8GIZ¶l)dÛúõë'lmm¥ä@‡Dƒ Dvv¶BWWWñŸÿüGÖÇÙÙYØÙÙ‰¼¼<Ùv777Q»vmQPP sLÁßß_ÔªU«Üç2dˆðððЩ­§§§ ¾ÿþûr#99Ye'üÜÝÝÅÇeÛ;vì(4h ÑÞÍÍMtëÖM¶­ÿþ¢yóæåŠ­ªyóÍ7E§Nžª¯J¥õë×mÚ´ÑØgkk+<==5¥Ÿ~ú© Ž=ZâqËJøååå {{{Ñ¢E Y’[¥R‰Æ‹Æ—ûÝ»w…¥¥¥xï½÷Êlû"Tö„ß–IDDDDDD/œ••ììì¤R³fM˜˜˜È¶ÕªUK£_«V­4Þ×®];dddè|î^½z!%%Ec{ýúõ¥w•ÙÙÙ¡aÆÒë£ìììððáC©mJJ âââàåå…ìß¿_*nnnHKK+׊§áµ×^Czz:ú÷ïÐÐP(•ÊçržÎ;cÒ¤IÏåØmÚ´AíÚò7Ú”ôýÖ¯__ö…Ó¥MLLžKlÕB¡@Ïž=µ>ÿжm[û7xð`@TTÔSŸ7>>ÉÉÉðôôDpp°ôg%88íÚµÃ7ššZê1¦L™333|óÍ7OÇˤª¾Ã/€ô2cÇŽ•øÒO"¢ªèÝwßÅùó狪Wõ Ñó0hÐ 4Hª"..+W®,÷±J[€àÉ“'Ø´iŽ=Џ¸8$%%áÁƒe³øûĊׯ^-ü+ÚÞ½{±ÿ~þ&&&HLLD‹-t¹„ áïï{÷îáË/¿Äž={P§N 6 “&MªÐ8¬­­+ìXº(éû8p ÆE‹aôèÑøûï¿qüøq|ýõ×Ï|Π  ìÝ»·Ô6¾¾¾˜5kÖ3ŸëyÊÈÈÀ† pâÄ ÄÅÅáÖ­[xðàFRµ4EïÄKJJzê8Šþ¼üñÇؾ}»Æ~$$$ÀÆÆFkÿ}ûöaóæÍضmêÔ©óÔq¼LªjÂ/K½Ò¼ysØÙÙé+"¢ Wl!¢\}ÅADDDT•%$$ k×®xüø1Fމ€€4mÚ›6mºuëžéØæææ w3fLE„[!¦M›†ÀÀ@9rëׯNJ+°nÝ:ìÝ»WcQ‡ªnܸqˆÇœ9s0gÎaúôé˜2eÊ3»iÓ¦ðöö.µ.«1ëÓåË—ñꫯB©TÂßß¾¾¾hÚ´)~úé':tHçãdgg€Ö·º*úóòûï¿ã­·Þ*Wßèèh¼ûî»øüóÏ1pàÀ§ŽáeSU~DDDDDDD¥úì³ÏpïÞ=ÄÅÅÉV>-kä–.œ§NªT ?022B¯^½Ð«W/ÌŸ?íڵ÷ß~[í~gΜÁŽ;0mÚ4Œ3M›6­°é¼¬òÉ¥éÓ§#++ ¨_¿¾´}Ó¦M%öQ©TÛ.^¼%®¬ …^Ê“ð»}û6üüü0dÈÌ™3ç©Ïÿ2â;üˆˆˆˆˆþ?{wVcúÿü}Z´RQ²”¶£…ì´IYÉ’A-ëh”&|-1ƒAY²ÇPÖÈ2ÄŒDŠ¡"íi¡´ïuÿþpóë8§E*>¯ëº¯¯ç~>ÏóÜ÷Óq¾ÍǽBœ……†.Ö{FFFBCC:::üºgÏžáÈ‘#•7©EEEŒ1DTTÔg__XXˆÒÒÒ:?¿¶´´´Ð¦MdggW£¬¬Œ˜˜”••}ñöˆÓœ9s ««‹ 6ÀÈÈ222(//¯×ϵ)PRRÂëׯE&ç*‹ŒŒ„¾¾¾@²ïáÇ8uêT•מ>}/_¾¨óòò‚œœ† Rç6khh _¿~عs'’’’juM^^FŒ®]»b÷îÝu~ö÷Š~„B!„Bœ££#.\(Ö{ššš".. .DPPV­Z…AƒËå‚1V«µüª³sçNÈÈÈÀ‡F\\ÂÂÂpôèQüõ×_U^—›› mmmŠ5é÷èÑ#lݺÁÁÁÈÉÉÁ;w0wî\DGGcâĉU^7lØ0äääÀÃÃ/_¾„¿¿?6lØ ¶v});vÄõëס  HIIAJJ êêêpvv®6ÉÙ”ÙÚÚâýû÷X·n^¾|‰'N`óæÍBq¦¦¦‡››‚‚‚àîî[[[ ;;%%%B×())ÁÎÎ{÷îųgϰpáBœ?ÎÎÎU®¯Ç€€ 77Wèüž={PZZ SSSøùù!>>Ož<ÁáÇqæÌXÆÆˆˆXYYaß¾}سg¿Ü»wï3ßÚ÷‡¦ôB!„Bù&­[·™™™Ø¿?¼½½Ñ¦Mìܹrrr=z4’““¡¦¦Vçûkkk#,, NNN˜5kd‡ÃÁ¸qã0eÊqu¥Vž>}Š+V$rÔÕÕ±eËÌ›7¯Êë ;;;üþûïØ°a$%%amm wwwHH4ÞqB{öìÁÇ¡¥¥….]º@VV222ÇЭ[7±'‘[[[ 6 ÿûßÿàáá))) 6 Ë—/ˆÛ²e ±mÛ6lÞ¼ðõõÅû÷oßò7äà:t(,,,àêêŠÌÌLHHH`úôéØºukíâ%û€ëîuîÜYà|çΊY³faÒ¤Iüz 899aìØ±üºÒÒR\¹ràêê*ô¬E‹Á¢Æ6}Ï8Mq¨+‡Ãq°‘wœ’’B›vB¾)fffæ3ƪ_1˜BÄŒÃáTþ%qc̽ÁCir8Ž,€BÞ±››<==¬=ÈÊÊ‚žžÞ{F~~>"##!!! ‰………’’‚´´´XÛ‘——‡¸¸8äää uëÖÐÒÒªõ3222]]ݯ¾ïçbŒ¡{÷î011Á¾}û„Îkhh`À€8~üx´îëx÷î’““¡««‹-ZT—––†¢¢"hiiÕúÞxñâ455ëµYGUrrriiitèСIî¼ëçç'¸Ѓ1ÖPíùð#„B!„òMkÕªUÓëKAA½zõªu¼œœÜi‡¢¢"Œëtí×xOâ‰gÏžaõêÕBçBBB’’‚þýûý†}Ejjjµ¡Zy ¿Ú’@—.]êÒ¬ZiÑ¢úôéóÅîO(áG¾‘‘‘¸}û6œõó¯-((ZZZÐ××GRRþûï?XYY‰mçªÚJMMEXX,,,мyóãËÊÊ„¶‰8p deeÅÞ¶²²2\¹ròòò°¶¶®6öêÕ«ÐÓÓ—ËEBB^¾| kkk±ÿ«,!„B!¤z\.:::X¼x1annŽÜÜ\\¾|»wïÆØ±c1{öì†n&! †2#ä‹*((€§§'ž={öEŸ³fÍÌ™3ç‹?§©qppÀþýûW®\­­-222ªŒÏÉɧ§'"""ÄÚŽ›7oÂÖÖ111µŠÏÍÍ…­­­@IOOk›ÊËËáëë ØÛÛÃÃãÆkìììpäÈÀ… `kk‹œœ±¶‹B!„R3iiiܹsC‡…§§'ÌÌÌ0iÒ$„††Â××~~~””lèfÒ`¾›~áááÐÕÕ­Õè"">ùùùX±bZ·n®]»~±çüòË/011ù¢CŽ¿YYYX±b´´´`ddÔ`íPQQAaáÇ%_|||°xñb±?ÃÝÝüñ¬¬¬PPP öûB!„B¾¬öíÛ‹\¿òðëÙ³'îܹÓÐÍ _H—.]°téRúœO())ñoåýoc_|—GVV²²²_lºl—.]pëÖ-üóÏ?PUU­Õ5Ÿ¾O úGB!„B!Îw“ð«IXXðüùs¼xñ³gÏFçÎaaaóçÏ‹¼¦¨¨GÅèѣѥKÂÑÑQQQqééépppÀÝ»w±sçNôêÕ ;wîðq›lSSSœ9sFä3îß¿ñãÇÃÐÐ ÀüŠŠŠjûòäÉØÙÙáöíÛuxUcŒÁÏÏvvv022‚¡¡!ìíí±cÇ¡ØÂÂB888à‡~xyyÁÁÁ˜?¾Èû¿~ý Å‹/0gÎtêÔ ãÆC`` PüÑ£Gù÷䕪ÃÁÁ¯^½BXXœœœ`dd„þýûó·úþTII 6lØtïÞóçχ››F ccc”––Öæµ aŒ!22²ÆRTTT§ûW¦§§Çßf][[êêêPPPŠËÎΆƒƒ~üñGÀÖ­[ùïtéÒ¥"ï]PP€ÀÞÞ;wF§N0yòdÄÅÅUÛ¦ÀÎΘ0a"##ëÕÇÊ®_¿Ž±cÇB__VVVصkW•±Ó§OÇ€>ëþŸ¾O 4kÖ¬>M&„B!„BÄî»™Ò[“ôôtœ?jjj¸pᆊ#FààÁƒ;v,^¼xk\\\põêUŒ1fff¸qãÎ;‡[·n!::š?¨  çÏŸGII ²³³Q^^Ž¥K—"!!=B^^&NœˆÜÜ\M |}}áääUUUL:?ÆÏ?ÿŒ‡âôéÓUöåܹs¸|ù2:tèðÙ ê,]ºÛ·oÇðáÃ1iÒ$(**âÅ‹"²²²àp8>®¯Àë[UF|øðçÏŸÇÈ‘#ñÛo¿AFFÆÆÆ¸{÷î§[] ÝóÙ³gÕ®;—’’‚óçÏ£M›68wî†;;;|8úöí‹¿ÿþ~~~¸ÿ>"##En°±téR$%%a̘1044ÄþýûqýúuƒËåÖ«¯;wîÄ’%Kо}{Lž<·nÝ‚‹‹ ÂÂÂàããS¯{ó 8†††>&ÿlllÄr_B!„B!D¬cM®pÀx%%%…}*,,Œ=}ú”_$$$ØÎ;êrssùñW¯^e—Ëe/_¾ä×_ºt‰`›7ozÆû÷ïYYY™@ÝöíÛ¶ÿ~~]ll,ÀzõêÅŠŠŠØåË—fnnÎJJJØÙ³g"poeee6dÈVZZʯ߸q#À.^¼(ÔžÌÌLæããÃÒÒÒªŒù\………¬Y³flÊ”)Ÿu]zz:À8Pclpp0ÀZ´hÁV­Z%p®¢¢¢ÚkÝÝÝÙdzhçÎc˜‘‘{ýú5¿þÔ©S óòòˆŠŠbØÊ•+ùuW®\aعsçjìKu***Xlll¥¸¸¸^Ï©‹øøx€?~¼ÆØôôt¡ŸËêÕ«vúôiú'N0Ì‚åççóëïÞ½Ë0ggç*Ÿ³k×.€ÅÇÇW“Àdee™£££ÀßÉåË—3ìÞ½{ÕöÅØØ˜YXXT󵙚š²JßsY#øî¥B…Ê÷U*ÿ®À³¡ÛC… •¦UÈVþqssc„ò-áýwn¥Ò5‚ï_^ùf§ôöìÙ=zôà—ŠŠ ,\¸P îÑ£GB×íÙ³G`ô•™™8RSS…b[µj%´fœ½½=àÍ›7BñNNN‘‘á$ûñÇ!--Í?®<µÑ××YYYX¼x1¤¤þ ¦³³3¤¥¥áççWeßUTTðã?¢uëÖUÆ|.IIIHJJ"33ó‹ïJ:uêT¬]»V Ž7R°¾8 0’ÌÜÜ„~¾ÉÉÉ€~ýúñëLMM ÞSP9´µµk,}ª¨šššÐÏeÔ¨QDþ`ÇŽ——ç[XXÀÊÊ §NªW[öíÛ‡¢¢"¡uçÎ ‡SíßB!„B!ä[óÍNé}ûö-ï_–|ܽÇ××ÖÖÖüº–-[ ]÷é4Í–-[BVVeeeU>+''‘‘‘ˆŽŽFJJ ¤¤¤DîúÉ»7/áñéqyy9?–—TZ½zµPòKJJJ¬ëžÕ†´´4Ö®]‹Ÿþššš7n0|øp±o”1bıޯ²O¾íÛ·¡ŸoŸ>} --³gÏbèС€€€“ÉõQQQQ«õ{õêÕ$6„ÈÊÊâþ“’’ Ê]oE}Vz÷îÿýYYYPVV®S"##ÁápD®7Ø_!„B!„†ôÍ&üÔÕÕ…êTTTЦM±=#11®®®8{ö,š5kèêê $ë*==ŠŠŠ3fŒÐ9‡:'FêÃÕÕ–––8xð qðàAtèлwïÆðáÿz{¾$EEElÚ´ ?ýô^¼xvíÚ! ãÆ«r¼Ú*,,„••UqâXÃïKŠŽŽ†««+!//þ†ŸCEE——WçÏuzz:TUUEnÜâàà Ö¿÷„B!„BHc÷Í&ü¾´ŠŠ :™™™8yò$FÅ=&''WïûëééáüùóX´h‘ÀȆ֧OôéÓ^^^8uêV¯^ DFFBWW·¡›'V“&M‚——chÞ¼9<ˆ)S¦Ôû¾ (,,¬1®ªÍMƒÂÂB <Œ1ÂÖÖÅÅÅ"7ë¨Nrr2¤¥¥Ñ¶mÛ:·GWW!!!pssÛôoB!„B!¤©úf×ðûÒž?Žˆˆ,Y²£Gæ'ûbcc«þ[[=zôc ǯ÷½¾IIILœ8{öìAii)|8¿­¯^½ªöºO§ÖVTTàÒ¥K066®×Ôðž={¢  gÏž­ó=!„B!„oÅw3ÂïíÛ·ü©ƒâÀÛP!,, Œ1p8<}ú#GŽDEEE½\'NÄ–-[àîî###XXXœ¯¨¨ZŽ'66^^^pvvØ€¤¾ …F/žû9™™™˜7ofÏž-°WAcvïÞ=œ9s¯^½BQQ:vìˆÙ³g ü÷ŸïæBÔš~õahhˆ!C†ÀßßÁÁÁ——GDD~ÿýw?~œ¿Ëk]IJJâØ±c7nKKKhkk#** ÿý÷öíÛWå—‚¶nÝŠÜÜ\øøøÔ«•uïÞÀÇU%$$pÿþ}DDD`îܹèÓ§O•×9»w¹9”””ðèÑ#Ì›7ëׯ[ÛÄÍÊÊ sæÌÁÞ½{qá¡ó}úô¹ËsS'!!;;;9r ——GHH\]]’r&&&033ÃÞ½{ñÏ?ÿbbb°{÷nxzzVùùŸ?>úôé áÁƒ:t(¦M›VcÛlmm!-- ¡Ïµ¬¬,üüüàèèsssXZZBCCøï¿ÿ€Aƒ \3jÔ(222Àápøký¹»»cÉ’%µq„B!„|þù'ŠŠŠÐºukTTT )) Œ1èëë# @¬ðððà‹ú; |ÜÕøÙ³gØ·ož={†¤¤$hiiÁÑÑ]ºtŠwpp¨ò_’ê»3!„Bù|·n݇DnÄö=*??………PUU­õ5ééé••E‹-ª1b²³³qçÎÏn×âÅ‹±cÇܸqChVVcççç‡I“&U®êÁ «*þk£M;¡  ©©©¨¿qã–-[†‘#GR²B!„`gg{{{|øð£F‚ªª*”••ajjŠââb¡øóçσËåBII ªªªÐÔÔĄ↎1cÆTù܃BYY±±±B犊Š`dd„>}ú ¼¼¼~¬Þ‹ˆêïÙ³g¡«« eee´jÕ :tÀ‘#G„↠"r攕•‘””T§6Þ¾}{ö쩶ܸq£N÷®ÌÊÊ 'NDzz:†UUU())¡ÿþ"7Ôô÷÷‡ŽŽTTTвeKhii‰Ü,sàÀ˜:uj•Ïõòò‚²²2ÒÓÓ€¿ ¨é¹ŽŽŽ(//¯r„ß©S§Àår¡¦¦†æÍ›ÃÑÑùùùBqñññPVV(‡®²ðøñctïÞ-Z´€šštuuqíÚµ*ã³³³1{öl¨ªªB]]ÊÊÊÐÑÑÁòåË…bøá(++ãêÕ«xðà@»* ^¨—Ë€zmâHD£)½„ˆ°lÙ2\»v ½zõ‚±±1´´´7oÞ`úôéðòòjè&B!„ò]ÊËËc &L@FF<<<‘‘B#£:„Y³faøðáðööF³fÍpàÀ8;;#%%+W®äǪ««W›¤ˆ‰‰A~~>455…Î!)) rrr(--ý¢É‹””9rúúúÐÓÓ8çããƒ9sæ`äȑسg¤¤¤°oß>̘1©©©pssãǶnÝÏŸ?¯ò9oÞ¼AQQ‘кsµõ÷ß‹L¤U6jÔ¨z¯?—››‹fÍšaìØ±())ÁÚµk‘šš ¡M¼½½±`ÁŒ=>>>àp8Ø»w/¦L™‚´´4,]º”ÛºukDEEUùÜ7oÞ ¼¼­[·ðq£Pü +ëܹ3 11QèÜ•+WpçÎ,[¶ ݺuÙ3g°uëV”••áܹs±-[¶ä”ÍÌñÆî IDATÈÈÀÊ•+QRRRe>|KKKèëëcûöíhÓ¦ 6nÜ[[[üûï¿0`€@|ff&ºw¬,üöÛo077Gqq1ÂÂÂDŽ$2e ÌÍÍáííÂÂB¸ººòÏÕv€Lpp0­v"^”ð#D555}Ê?~úô)ÒÓÓñçŸòë¸\.FŒ!tíÝ»wáàà€‹/V¹t^^V¬Xsss\¸pŸ„³²²BAA6n܈Y³fñZ\.§OŸ®²½‰‰‰ÐÑѹ;.o䟴´4deek÷j)..{öìAyy9þý÷_\¾|rrr8zô¨ÀòC999øõ×_Ñ¿ðÏ 4ùùùX·nf̘ÁßÐ’ËåâòåËÕöWOOOh‰£ÚÚ°a6lØP§k?WPP&Mš„#GŽT¹{ñ‡°jÕ* 4§OŸæ÷ËÚÚ¶¶¶X³f ¦OŸÎŸòÍår«¢š˜˜Èiiiàp8"§PóîÉK V&--7nðd}ûöÅû÷ïqôèQDDDlÌÒ¼ysÌ;+°å§Ÿ~‚¼¼<£QàãôÛ¶mÛÂÕÕUh#ÊåË—#55Ož<X½ª©¶C† \¼xÙÙÙü¶ÕVII nݺ…þýû×8›|>šÒKH8lll°lÙ2üú믘2e %û!„B“û÷ï Lï|þü9êþþûo‘×jkkãøñãÕnþvóæMþ¨­OGÜýüóÏ(((À… øu;vD^^óQ£FAGGñññ„<ŸRSSƒ²²r­û_[Ïž=üyó°`Áœ9sNNNuãÆ ¼ÿ?ýô“P’ÎÕÕyyy¸xñ"¿ŽËåâÇü©£vvvÐÕÕåOáMLL¬Ó†Š ÁÐЇ®2Ù×®]ǰlÙ2÷ÃápàêêŠììl(—ËEZZÝ!C §§‡´´4Âï';;rrr"GwÊÈÈ@RRYYYBçlll„FÃÍ™3Œ1ܼy³v/@„„„|xÍ3{{{œ?wïÞÅ€––&r BuýíÞ½»@ ~²*)) ¹¹¹¸|ù2Ú¶m‹½{÷âÿûajjZçvâÉ“'ÕÆtíÚµÚukKOOÒÒÒÕÆ|îûár¹`Œ!99oß¾Åõëס®®Ž}ûöaåÊ•HLL„ ?^MM ())JDçå塼¼¼ÖfðvÞå%›ë‚×ooox{{‹Œyõêzõêàã¦#eeeèÝ»wŸù9Þ¼yƒõë×ÃÝÝÆÆÆ_å™ßJøB!„BùæðF®ñ¦2V&##)))*'üΜ9ƒ`òäÉX¹r%V¯^¤¤¤ñÖ¯_?ÌŸ?ÞÞÞ8uêÎW×_999HHHTÙߣGÂÚÚ£FÂúõëñ믿âíÛ·õêï‹/ª¡ÉÃK¯6ª{?¼ºÊï‡×÷ÄÄDìÞ½Æ ƒµµ5¶mÛ†eË–!--M`Ä'oªôû÷ï…Ö=|ÿþ=€»òÖ/yY×éÔÀÿoèòûï¿£_¿~"côõõùæ>TTT¬ó3k+++ ööö°±±‡‡ÇÞ÷Š~„B!„B¾9¼dL\\´µµÎÅÄÄ ¬¬L aÃÛÅ4<<ÇÇáÇagg777x{{£¨¨¨Á§¸zzz"00...8p ÔÔÔøçxxÄÅÅ¡}ûö×½~ýýmÕª”•• œ8qÖÖÖøå—_àííÒÒÒzõ×ÝÝîîîu¾^Ü*¿ŸÊï ø8Ò €ÀûiÛ¶-ðèÑ#œ9sçÎCß¾}±jÕ*ìÚµ ï‡wí;w„v?¾wïžÐý«óæÍ€ŽŽÎçtQ€ÿÏfff5ÆóFþ÷ßu~fm”––b̘1PTT„ŸŸíÎûÑ~„B!„BœºººX×Ì611””|}}…Î:t`aa!PÏår±uëV(++côèÑPPPÀ?ü€7@ƒ'ü±wï^¼{÷óçÏ8gff ‰ÏîïüÖ­[ÃÞÞ-Z´À´iÓMÅÉÜܧÊ÷Ãápзo_z===lÞ¼°µµ…ŠŠ &Ož,òýL˜0²²²8vì˜Ðýÿúë/4oÞ\hT&ܾ}>|¨;vì8N•#ójC[[ÚÚÚðòòBaaañjjjèÒ¥ öîÝË‘X[Íš5Czzz­b‘€‹/ŠmIć~„B!„BÜŠ+°oß>±ÝOWWNNNðõõÅþýûÁœ;w[·n…½½=ÌÍÍ®ár¹x÷îfϞ͟V9þ|dffBZZZhsžŠŠ Œ5 S§N[û«2lØ0LŸ>§OŸÆÉ“'ùõúúú˜1c<ˆÃ‡óû{êÔ)ìØ±cÇŽZŸ×ß9sæðGZÍŸ?““û¦6-ìܹ3¦L™‚½{÷⯿þâן8q»víÂĉùk;òðÞϼyóøÓk]\\‘‘-Zð§ñ@Ë–-1mÚ4\¼xëÖ­C^^rrr°zõj\¹r³gÏFóæÍ…Ú•””„qãÆ!22€;wbÈ!;ô~.IIIxyy!>>#GŽDrr2€Ó¨ããã!tÍÞ½{‘••…ÁƒóÛ©©©ÕŽü0`^½z…Û·oø¸±¨Ž=<c¬É¤¤0Bù–˜šš²JßsY#øî¥B…Ê÷U*ÿ®À³¡ÛC… •¦UÈVþqsscâ2`ÀfeeU«Øœœ6~üx€©¨¨°Ö­[3ÌÖÖ–eddůY³†III±äädzf``PåsÒÒÒ˜¤¤$“““cyyyŸ×¡j¨¨¨0{{{¡úŒŒ ¦®®ÎTUUYZZ¿>;;›3†`-[¶djjj 9r$ËÌ̺ÏÊ•+Y³fÍîÁc–––ÌØØXlýø’zõêÅFŒQ«Ø>0{{{€µjÕŠ©ªª2lôèÑ,++K(~ùòåLVVVè³bnnÎzöì)_RRÂ&MšÄ0)))&))ɰ3f°²²2¡xuuu6mÚ4æääÄÿ™`:uú ~*&&†`{÷î­6îÈ‘#LII‰`ÚÚÚ¬yóæ ³¶¶ÈÚµkǰ6mÚ0uuu€™˜˜TÛ–-Z0üÏ\‡bJKKÙ'¿[”… VÛÆèĉŸö£;kß¿¼Âa¿Œ›‡ã`#ï8%%¥Ö‹_BHS`ff†àà`Þa0c¬æ…7!DŒ8Nå_71ÆÏBL„FÃáÈàÏ#tssƒ§§§Xî‡#4«:=BXXÊÊÊЭ[7ôíÛG(.%%ÉÉÉB#á™™ÉßÍU”ÈÈÈ k×®µïL îÝ»%%%‘»˜FGGó7ùt;àà`„……¡¼¼Ý»wçOgýTrr2RSSù;µòÄÇÇ#++ë³ÞqCyòä ddd>k§× <<Œ1þû%)) ïÞ½C=êãââ››‹.]º]ÃÃóçÏ ˜˜˜ÀØØXäû¿ÿ>Zµj<{ö ·oßF‡`ee%r4`eÑÑÑèØ±#öî݋ٳgWûîÝ;#""ÒÒÒÐÖÖÆàÁƒ¡   2>77ÁÁÁˆŒŒ„ŒŒ úôécccHIU½ Dqq1.]º„„„`РA‘‘x/·nݪòz Z¯qØXøùùaÒ¤I•«z0ÆÂª=Ÿ¢„!„4B”ð#„44JøBêãK&ü!ÀÕ«W1lØ0œ|ЩS§†mi´hÓŽïÔ‰' ''Ç/Íš5Cûöí1iÒ$þ–ä¤qÉÈÈ€••TTT`aa555ôèÑ B±gΜœœž>}Z§g999A__111õmv½ãáÇÈÌ̬U|LL ~øá¨ªªbäÈ‘_¸u„B!„òõ¸ººÂÔÔúúúðóóÃüùóѹsç†ni¤h„ßwîçŸF§Nÿý—.]•+Wðøñã&7þ[VXXˆþýû#::üñ¬¬¬ŽŸ~ú  …šššØž7räH4øTù·oßÂÜÜþþþ?~|µ±{öìÁÂ… ¡¨¨HÛ»B!„B¾97nÜ@BBâãã¡££óMí¢LÄï»áתU+5t3!C†`ÆŒX¼x1àããƒììlüþûï Ý4R‰¿¿?"""°mÛ6üôÓOèÑ£f̘'N )) ëó&Mš„€€€&•8{ûö-V­Z…¸¸8hkk7ts!„B!Dì:tè€þýûS²Ôè»á—••…ÒÒÒjcÞ¿cÇŽaäÈ‘hÛ¶-.^¼ˆ›7oÂÐÐ666"çÆ¿zõ ˆŒŒ„††¬¬¬0`À˜ÔÔTøùùa̘1èСƒÈg?~222;v¬Ð¹´´4øúúb„ U^/.cÆŒ‚‚¢¢¢DžŠŠB`` ¢¢¢ ©©‰Aƒ¡_¿~1oß¾ÅÉ“'1nܸ*¿„Ž;Œ=Zì}—””øûûc̘1hÙ²%.\¸€;wî sçθ¹¹‰¼¯MÑÑÑèÖ­¦OŸEEE¡¸Û·o#44”,))‰… VûŽRSSqêÔ)ü÷ßhÛ¶-F%´ƒÕ§.^¼ˆÐÐPÄÇǃËå¢k×®èÛ·/TTTø1ùùùØ·o*ïåË—ñöí[@ëÖ­1yòd¡û®]»¶ÚçB!„B!ß ÆX“+Ü0^IIIa5‘`—.]ª6&<<œ`þþþÌÊÊŠ`-Z´`ØæÍ›…â=Êäå噚š6lãr¹ [´h«¨¨àÇÅÇÇ3쯿þªòÙlÊ”)"ÏýòË/ ûñÇkìgm;vŒ`AAAõ·oßfØÌ™3…®9tè“““c­[·fÆ czzz ûé§ŸúͰ“'OVù|uuu6cÆŒ:µ½¸¸˜©««×X?~\§ûóÜ¿Ÿ`.\`æææŸooo¡x&##ÃÚ´iÃlmm™ŽŽãp8ÌÍÍM îåË— ¨òÙ***löìÙücyyyfkk+2vþüù ËÉÉá×}rrr€+VÀÓÓ&&&˜0a@KK RRRUö·¤¤éééuÞVJJ žžž5Ɖk4äòåË¡¥¥…„„hjjâåË—ÐÒÒˆyùò%æÍ›;;;øûûCVVŒ1¸ººbÓ¦M055åfÔÕÕ…„„D•ï'??>|࿟¼¼¼j×ÓSWWðqdå§£=ŠÇÃÈȰk×.¸¸¸àÏ?ÿÄš5kb׬Yïûé§Ÿàíí]å;),,ÄĉÑ®];ܼyÈÌÌĬY³°páB Œ… BWW¯_¿nÐþÖÅòåËѬY³jcBCCaffÆmÇ#%%…‘#Gb÷îÝ(--…´´4€ï‡÷öïßeË–ÁÀÀüzÞˆOÞ³«Zƒ²¬¬ ø÷®LUUU¨nðàÁضmòóóEþ¬k#,, =ÂÓ§OÎÉÊÊâåË—u÷ïßLŸ>½NÏ#„B!„BHí}³ ?ÞôRž)S¦`À€>|x×JHÔ¼yqll¬Ðf<¼$`ll,ºví àcòæÖ­[oooÌš5 ÇG`` Š‹‹4Llúôé°±±AçÎ1~üx¬X±;v슋ÅСCEÞCKK Œ1ÄÅÅñ§Žr¹\„„„øØ_gggøúúâÊ•+øð်÷·¸¸Xh ¨(<€™™YžQYm?={öyNKK åååHHH€žž€ï'""Œ1ìÚµ ³gÏÆÁƒqýúu$&&BBB‚Ûºuk@FF†Èûóê?M6V…7==-- ºººµºæSqqq——Ç‹/„ÎYZZ %Â###¡¦¦&r³B!„òM*«|лwoXXX4T[!Dì^½z…+W®T®úÐPmå›Mø}ijjjÈÊÊyîýû÷üžŽ;âØ±c¸uë^¼xóçÏ£¢¢ÞÞÞ:t(Zµj%°Ké׿èèˆÑ£GÃËË cÇŽ…¥¥¥Ày555~¢îS¢úËårqöìY\¿~¯_¿Æµk×PXXoooôïßmÚ´©sòGZZúÓ¿T"ñ’_C]Þϵk×påÊ$$$`õêÕøðá¼½½Ñ£Ghhhð“š222PQQALLŒÈûÇÅÅ@•küUÕžªv®¶mÛBJJ ÿýw­âyï§ò(GB!„òMHøY[[×jnBi*üüü>ÍM¤7T[D¡„_éëëãîÝ»(((àoØÁsçÎ(** $T¸\.Þ½{‡?þøÇ‡ŽŽ\\\`bbIIÉF1½ÕÛÛÿþû/fÍš…gÏž L÷Ô××ÇãÇQTT$4ºîÎ;PQQ˜>Ú±cG¤¦¦bÛ¶m°··‡¦¦&\\\`aaòòòzõWBBB`Ó‡Æ@__OŸ>EII‰Ðôß;wî@]]-Z´à×q¹\$''cÇŽ3f Ú¶m X[[£  @èýØØØàÌ™3HKKÉWVV†K—.ÁÌÌŒ?}¼²œœ¡º{÷îASS³ÆiëÕ122Â_ý…ôéÓ§ÆøN:¡¬¬ ׯ_‡­­íg?¯¼¼¼.Í$„B!„B¾K5ÏUüF?~=zôÛýæÌ™ƒ>`Ë–-õÿüó‚‚‚0wî\zÞzl/^„‹‹ €ÃÚûô郫W¯V›+,,D`` rssÅÖ~QÚ¶m‹­[·"&&îîîçæÌ™ƒ÷ïßãÏ?ÿ¨¿zõ*nÞ¼)²¿¸|ù2¿¿æææèÞ½;®]»Ö(œâ4gΤ¦¦ÂËËK þâÅ‹¸ÿ¾Ðû騱#JKKqõêUþû±´´„¡¡!‚‚‚„ÞÏœ9sPQQúmÛ¶áÇü{|jÞ¼yI¿ëׯãÞ½{øá‡êÜWpvv†²²2\\\]cüôéÓѶm[,Z´oß¾­õsZ·n þôpB!„B!„Ôì»á÷éš~õegg‡1cÆà·ß~C\\ €èèhlݺ†††øõ×_â;tèiiihii ¬…çââ‚GU›Û¼y3V¯^ ¡„’¸Íœ9'Nœ€··7ÆŽ‹`oo+VàÍ›7èׯ^½z…mÛ¶¡sçÎX±b…À}tttø#­­­ùõ...pvvþæ~ŽŽŽ8tè\]]ñêÕ+ôíÛضmºv튟þY ^OO]ºtAÿþýùõ...˜?¾Ðû4h¦OŸŽ½{÷")) ýúõCxx8üýýagg‡ñãÇ‹l—¦¦&ºv튙3g¢¤¤;v쀦¦&,XP¯þª««ÃËË ³fÍBÏž=áìì DDDà¿ÿþÚ5kìJJJ8pàÆ=z`öìÙ000@ll,°dÉ÷À£  €AƒáÀPPP€œœ‚ƒƒ1nÜ8L›6M vÍš5ˆˆˆ$$$@ZZ'NŒ?cÆŒ©WŸ !„B!„¦â»IøÕ†¢¢",--Eîl*ÊéÓ§±yófœ>}'Ož„††f̘M›6 ­O'))‰qãÆaèСàp8üú &àøñã055­ò9VVV¸páB•›fÔ…ºº:,--E®èããƒY³fÁ××–––üöž;wžžž8{ö,üüü ©© '''xzz íö*--1cÆ`Ô¨Qõ“'O†¿¿?LLLÄÖ—/¥E‹°´´DË–-kŒåp8¸xñ"6lØ€€€;v ÚÚÚ˜;w.6nÜ(4}VNNB‰èiÓ¦áÌ™3BÓd9>ŒN:áüùóذaôõõáææ†µk× ­‹§¦¦KKKxyyáòåËøë¯¿ƒÂËË«Ö|TgÊ”)066†‡‡|||ðöí[¨¨¨ÀÄÄDä:}¶¶¶xþü9V®\‰³gÏ">>úúúèÝ»wµë9nܸþù'víÚ˜™™A___(.##©©©Àß›wœŸŸ_ïþB!„B!M‡1ÖÐmølÇÀFÞqJJJ­7, „ÔlæÌ™8sæŒÈ5É×aff†àà`Þa0c¬þ[NBÈgàp8•IÜÄs¯2˜B>ÁápdòŽÝÜÜhÓBÈ7ÅÏÏ“&Mª\Õƒ1ÖPíùÔw³†!¤ö^¼xAItB!„B!¤‰¢„!™™™èÝ»7¦NŠ~ýú!$$£GnèfB!„Ò$mÙ²ƒ ªUl~~>FŒÁ_º²øøxØØØ 33SÜMl”F…Õ«W×:~Æ 033ã—O7Õ$ä{F ?Bòóó1tèPäææBYY›6mÂÿþ÷¿†n!„B!MRll,BBBjŒËÉÉ JKK¡§§'t^CCÊÊʰ´´Dzzú—hjL›6 ffâ_q&44ÑÑѵŽ700ÀÀ1pà@„††"..Nìm"¤©¢M;!ÐÔÔÄúõ뺄B!ä;vàÀ$''ã·ß~kè¦|5‹-B^^®_¿ŽfÍš —””ÄñãÇaee…Y³fáâÅ‹ ÐJa%%%())ièf`ìØ±;v,ÀËË«[CHãB ?B!„B! îùó爊ŠSLˆ¨ IDATª1.::zzz(--Å­[·P^^333())‰¼&##OžÍ„B!„Bš”ŒŒ Œ5 'NÄýû÷ˆ½{÷ %û.^¼ˆY³faôèÑxüø1Þ¾} 777lß¾Û·oº¯’’´µµñèÑ#äçç#99›6mBtt4~ùå¡x'''<~üûöíCnn.âââ !!ƒVÛþˆˆÖFyöööˆŽŽÆíÛ·kŒURRB×®]ѧO¡wRžžžˆˆˆ€­­-ŒŒŒÁ/BñÍ›7—ËÅ“'OŸŸ¤¤$¬[·QQQXµj•Ègœ:u xñâòóóqùòedffÂÑÑŒ±zµßßßóçÏÇäÉ“ŽÄÄD,Z´žžžØ·o_½îMHcéï_˜†ÀápÜl䧤¤ M›6 Ø"B/333óƒcâ_™BªÁáp*ÿ’¸‰1æÞ`!„49G@!ïØÍÍ žžž1@`` ÿøùóçÈË˃¹¹9¿ÎÌÌ îî‚_?–––¸}û66mÚ„åË—WÛŽN:!##‰‰‰käõèÑqqqx÷îÐ(?QôõõQTT„„„~]TTŒŒŒ0oÞø8ZŽËåbõêÕððð¨Õ5_Ê„ ðúõk„††Öéz---4kÖ ¯_¿¨×ÔÔ„ªª*îß¿/0…zݺuXµj®]»†Áƒ‹¼§¢¢"fΜ‰;wŠ<_^^ŽŽ;¢¼¼111””äŸ300@NNRRRêÔò}òóóäI“*Wõ`Œ…Uÿµ5Õ)½†•ÌÍÍþ²BHS—””Tù°}CµƒB!äKiÑ¢…ÀÀ˜˜”–– Ô)++‹¼¶gÏž5&ûÒÒÒ‘#GâŸþ8×µkW„……áÍ›7µZsÏÆÆFhØÃ‡ÁÃÔ©Sꥤ¤Ð¥K—j“a¼Ä¡ššZÏæá½—ÊIÇ¦ÊÆÆþþþ"ÏuîÜYh½ÄñãÇcÕªUxúôi• ¿šÄÇÇ#66ŽŽŽ 8×½{wœƒ†®®.öïß³gÏÖú>EEEªñY¼Ïƒ¯¯/&NœXçûÒT4Õ„ŸÀ7ªšší¦Cù¦dff¢´´”wXÖm!„BiŠ ÀápRë„ßÊ•+‘žžŽ¨¨(èêêòë/]º$Û¡CGŽq¹\s5­•¯­­ HOOçÿ¹&iii ´Spc¶bÅ dee!::Z`GÞ3gÎTyMEE…PÝóçÏ@àgò¹ ?® B ?ò]hª ¿ìyÏž=£M;!ß”O6íˆjȶB!„| ÐÓÓÛýäåå1räH:tK–,á'|ª MMM¤Zxx8Ž9"”ij¶¶†¬¬,>ŒaÆñëß¾}[mB lmm!!!›7oÂÄĤVý¹uë`ĈµŠ/,,„””¤¥¥kÿ9”””ƒ²²²j7=‰ŒŒ„ŽŽŽ@²ïÉ“'8~ü¸ÈÄœ?áááèÖ­¿ÎËË Íš5ƒ­­mÛ¬ªª kkkìÙ³ ,hR‰SBꂆÅB!„BipŽŽŽX¸p¡Xï¹sçNÈÊÊ¢_¿~8|ø0bcc†cÇŽáØ±cBñ¦¦¦ˆÅ¢E‹„U«VÁÚÚ;vDEEí=àãL³™3gÂßß¿üò ^¾|‰“'OÂÔÔT`G`QÚ·oÁƒãÔ©Sµî˹sçЫW/×›““---¡¬Lü“Elmm‘ ¼|ùþþþذaƒPœ©©)¢¢¢°téRá—_~ÁàÁƒù»‹ÚÅXEEöööصkž?ŽeË–Áßß?üðC}bbbðìÝw\Våÿ?ð×Í ‚€ ‚¸Á=·fŽJMMÍš_KÍÌO&æJ³Ä2WËYš¸2G©¹æÈ=Š‚ˆ€ˆ€ìyýþ0ÎÃ=¸Qô†Û×óñ¸Ýçœë\ç}‡;}{½Ø»w/RSSUŽ÷ÝwP(èС‚‚‚ƒóçÏã§Ÿ~Ò¸ˆQ•%„¨r@Q\âãã‘!ñöö%¾çN‰JðÝËÂÂòb•’Ö¨ïxXXXªV`Vò{dæÌ™¢¢téÒEtïÞ]çú·oß=zô …BŠG¡PˆÁƒ«Ô}øð¡6l˜055„³³³Ø¶m›Ø½{· .^¼(«Ÿ››+† "µmgg'Ö¯_/&Ož,,--µÆuúôi¡T*Å®]»Ê¼‡¿þúK( qðàAî9--M888ˆ ˆüü|Î)ŒŒ ѯ_?abb"¥R)^yåQXX(«—””$†*ªU«&ñË/¿ˆmÛ¶ ",,LVßÕÕUŒ9RlÙ²EØÛÛK?«áÇ‹¼¼<­1YXX”üÿ–ø÷ßÕÖ‹ŒŒ:u’½FFFbôèÑO÷Pè…Sü—(­E%øþ-. !´Ï-P)Š‹‹·ããã9¤—ˆ J©!½§…ô½xþû‹P±%Bˆ½CDUŽB¡0]¼=sæLê1¢Ç«ï†‡‡ÃÈÈ®®®pppÐX799©©©:1~ðàÑ´iS•Õ|µ™3g~úé'œ={µjÕR['-- ;vD§N°fÍÛ~–Cz‹%''ãÎ;¨_¿¾Ö•““’’‘‘Q®a´B\»v µk׆­­mE„+“žžŽˆˆÃÕÕööö~ 2lÛ·oLj#Jîj#„¸¨¯xJ«ªsøéÌÂÂíÚµÓ©®ìììtnÛÞÞþ‰FóçÏÇÍ›7Ñ»wo„††ª$¶222зo_¸¹¹aÅŠåj»zõê厧¼t}NZ¬ê( †/?©5j }ûöϬ}"}ã~DDDDDDDz`bb‚­[·¢W¯^jç³ËÊÊB›6mðÛo¿=—öð#""""""Ò###|õÕWj9::âÛo¿}Α!`¿*( ‡Ö©î½{÷4výÞµkNžSdd$–,Y‚{÷îéT¿¨¨¡¡¡ ÅÎ;±dÉ<|ø°Bc""""""""z^˜„ߪU«­ï0ž›ììlôïß¾¾¾Xºt©Ú:#GŽÄâÅ‹1hРܸqã9G¨^^^>Œ¸¸8½ÆajjŠS§NáÔ©S˜3gŽ^c!"""""""*&á÷Áàúõëå>ïþýûB”Y/!!¹¹¹:·›œœŒ„„ꦤ¤ ++Kç¶`Ö¬YHKK÷ß~«uYø)S¦ S§N3f un¿¨¨¨\ñùäXYY•Yÿ«¯¾Â©S§°k×®2ëÀŽ;`ccƒõë×ëT_ééé°±±A£F“'O† lllðÒK/©=çìÙ³ðó󃵵5àää'''ìÞ½[ãu²²²àïï[[[¸¸¸ÀÁÁ›7o®{B`Ñ¢E°··‡‹‹ ,--1hÐ &""""""¢çŠ ¿ ‘––†“'ObìØ±ðòò²eËлwoxyy©ÔŸ>}:йsgìß¿sçÎÅ?ÿüƒÎ;«íÝõóÏ?#++ ßÿ=N:…Ï>û Õ«Wǘ1cpëÖ-YÝ´´4têÔ ·nÝš5kpâÄ ôèÑ]ºt)ó>6mÚ…BÁƒëtßÍ›7G£FðÃ?èTÿÎ;HOO¯Ð!Òfff ”†Ï: Äÿþ÷?µçlÛ¶ X·nN:…… ÂÄÄ£FÒ8oáÈ‘#‘••…mÛ¶aÿþýpssÃØ±cqèС§¾‡‰'báÂ…èÙ³'~ÿýwÌœ9ÁÁÁèÞ½{¹zO= c}ð¬¬ZµJ6ìT_ý‘‘‘Ò¾ÁƒÃÍÍMåÜåË—cÇŽxýõ×5¶ޝ¿þ~~~RO·þýû£iÓ¦:t(Ö¬Yƒ÷Þ{OvN`` lÛÛÛîîî5j~ÿýwL™2E:öý÷ß#11û÷ïGÿþý¾¾¾hРüýýµÞûÞ½{ѪU+ØÛÛk­WR÷îݱ~ýzdffÂÂÂBkÝ3f`àÀRo¼Š`bb‚wÞy÷ïßÇG}„nݺ•yŸ¥{]z{{£víÚ?~<:„ &¨œóꫯbíÚµÒ¶——êÕ«‡%K– OŸ>Oÿ… °~ýz¼÷Þ{X¹r% oß¾hذ!üüüðÓO?•y?DDDDD†êøñã˜6mš¾Ã "ª0%óK•‘Á&üÖ¬Y#ëU%„Àï¿ÿ.[ݶC‡j~óæÍÓšì€]»v¡  ãÆCNNŽ´¿wïÞ°±±App°JÂO¾}ûÂÈÈ111²ýû÷ïGÆ ¥d_±±cÇ–™8ŠŽŽ†O™×.ÉÅÅEEEˆ…§§g™õ+2ÙW‘ŠŸWéçYlüøñ²m¼ñÆغu+ `lüd¿;wî„þþþ²÷áÕW_…¹¹9Ž?΄½°þý÷_üûï¿úƒˆè…a° ¿k׮ɶ•J%Ö¬Yƒ~ýú•y®. ¯âUm}}}ÕˆˆP»???GÅÕ«WqóæMÄÇÇC‚‚Y½¨¨(´mÛ¶Ì8JËÊÊBJJ jÖ¬Y®óqqq:Ýe‘——§ò<¨|~~~HMME÷îÝÑ AôìÙ(W;ÅÏÙÈèɧ´ÌÉɽ½=öíÛ§ö¸¹¹ù·MDDDDT ¹ú‚^( ÕJlç(ÒP—èY¨Tï~OÈÃÃÛ·oG½zõP«V­2ëgggcРA¨_¿>Ο?/;çã?V©ïææ¦qá m êÔ©ƒ”ë¼âúîîî徦>ž…âÞv÷ïß×ZïüùóÈÌÌÄÛo¿-KöíÙ³Gëy¿þú«l;''¿üò |}}Ÿª‡_= „ÀÒ¥KŸ¸ """""""¢ŠðÂ$ü®]»†.]ºTX{]»vŰaðbÅ JɵÜÜ\\ºt ÉÉɲúnnnP( ÂíÛ·‘˜˜ˆåË—cäÈ‘°°°@JJЬ~ñ‚cÇŽEZZ„Ø·o† Vfl~~~HIIAHHˆN÷’––†?þø~~~:Õ A»víÊ=tV¶¶¶hÑ¢víÚ…””âÒ¥K¸yó¦¬^:u?ýôbbb/¿üþþþ077WyžÅ–-[†eË–!##™™™ðóóCrr2Þÿý2cKOOGjjª4ü¹¤~ýúáÕW_ÅâÅ‹±bÅ iÁœœ\¸p©©©å}DDDDDDDDOä…Iø= ë֭Ø1c0kÖ,XYY¡aư´´DëÖ­±ÿ~Y]WWWÌ;çÏŸGýúõáââ‚… bÿþýhÑ¢âââdõ=<<ðý÷ßã?þ€½½=ìíí1bÄlÞ¼¹Ì¸:wî oooêtË–-C50nÜ8ꇄ„àüùó8räˆNõËkÔ¨Qø÷ßáêê kkk´nÝ[¶l‘Õ©_¿>púôiÔ«Wµk×F`` > OOO•çYlýúõX¹r%œœœ`gg‡;v ,3®öíÛÃÖÖ½{÷V{|óæÍ2d>øàXYY¡Aƒ°´´DÛ¶mqìØ±ò?"""""""¢' Bè;†rS(oÇÇÇÃÉÉé©ÛÍÌÌÄÙ³gѼyór m½zõ*.^¼ˆØØXØØØ mÛ¶ðòò‚B¡P©›˜˜ˆÝ»w£nÝºèØ±#¬­­qåÊäåå¡]»v*õ¯]»†'NÀÉÉ ]»v…BCCáîîŽzõêiŒ)""mÚ´ÁÚµk1jÔ(õÂÃÃѺuklÚ´I§ÞƒÀãù;†îÝ»Wè0é’îÞ½‹½{÷ÂÌÌ ^^^hÙ²¥Úzñññسg4hXYYáòåË(,,”­˜ûàÁ\½zíÚµƒ‘‘Nœ8¨¨(øøø }ûöZcIHH@xx¸´mmm­q5^¸té.]º„¸¸8ØÚÚ⥗^Rû³%Ò¦C‡8}útñæi!D}ÆCD/…BQò‰K„z †ˆˆ¨ jæðÁ9üèEÆ„Ÿ Â;#}ûöIs–.]º`À€øúë¯õ!i„é~DDT•0áG$ÇUz بQ£`ff†/¾øBmÂoõêÕ9r$/^¬æl""""""""ªŠ˜ð3pC† Á AƒÔûì³Ï T*ŸsDDDDDDDDDô,qÑŽ€¦¤“}DDDDDDDD†‡ ?"""""""""„‘aˆˆˆˆˆˆˆˆÈ€0áGDDDDDDDDd@˜ð#""""""""2 Æú "\»v úƒˆ¨Âdeeé;""""""ª¢ "á׫W/}‡@DDDDDDDDT)pH/‘aˆˆˆˆˆˆˆˆÈ€TÕ!½¿ˆÖwôBy@—ÿ>g¯ÇXèÅó@ßQÕQ%~Bˆ0aúŽƒ^ …¢þÂ/_±]ŸñiÂ!½DDDDDDDDD„ ?"""""""""„‘aˆˆˆˆˆˆˆˆÈ€0áGDDDDDDDDd@˜ð#""""""""2 Lø&üˆˆˆˆˆˆˆˆˆ ~DDDDDDDDD„ ?"""""""""„‘aˆˆˆˆˆˆˆˆÈ€0áGDDDDDDDDd@˜ð#""""""""2 Lø&üˆˆˆˆˆˆˆˆˆ ~DDDDDDDDD„ ?"""""""""„‘aˆˆˆˆˆˆˆˆÈ€0áGDDDDDDDDd@˜ð#""""""""2 Lø&üˆˆˆˆˆˆˆˆˆ ~DDDDDDDDD„ ?"""""""""„‘1ÖwDDDT6…B1€¾ã V…B ï è…r@qEßAUULøU ÿà¡ï è…Õõ¿Bô¼$`ˆˆè qH/‘n„¾ """""""Ò~DDDDDDDDD„Cz‰ˆˆª˜~ýúaË–-úƒˆ¨Âܸq^^^úƒˆ Ë…BÑAßA”rB±GßAЋ ?""¢*ÆÄÄ666úƒˆ¨ÂÔ¨QCß!‘áéû_©l˜ð£ç‚Cz‰ˆˆˆˆˆˆˆˆ {øé&Wß‘FE¨œo3Õwôbbˆˆˆˆˆˆˆª4!ÄefúŽ£4…B‘&ýH8¤—ˆˆˆˆˆˆˆˆÈ€0áGDDDDDDDDd@˜ð#""""""""2 Lø&üˆˆˆˆˆˆˆˆˆ ~DDDDDDDDDÄXß’èèh8p7nÜ@bb"Œáààoooôîݶ¶¶Ï‹‹ÃŸþ)m÷ìÙ:_;&&ÿüó´Ý»woÔ¬YóÉnDRSSqåÊܺu ÆÆÆððð@“&M`aaQæ¹+W®DHHœœœ°zõj­uwïÞ¼¼>þøc­q\¸pƒ–í[¶l>øàƒrÞQÕâêêŠèèh\ºt QQQ¸páŒùW"""CÅ!½DDDDO)44-Z´Ðšì€ÜÜ\¬Zµ ^^^ˆˆˆxNÑé×Õ«WѳgO 2ÑÑÑ*Ç 1dÈ,^¼X%Ù<îõ7uêTL˜0Aã5&OžŒüü|aâĉZãY¿~½Ê¾µk×–}#àÝwßðøgòõ×_ë9"""z–˜ð#"""z ±±±4hâãã¥}ÆÆÆhÓ¦ Ƈ‘#G¢I“&²s¢¢¢Ð§O¤¤¤Th,ÕªUƒµµµT4õˆ{žºvíŠàà`Ç—,Y‚£GJÛ¦¦¦­ôëס¡¡hÖ¬ AAAøä“Oà»Fx} IDATîî®õÙ†‡‡ãäÉ“HNNFëÖ­áåå¥6ÎäädܸqCÚnÑ¢…Ú/²³³qáÂ\¸p999hÕªZ·n {{{­q@ZZ._¾Œ«W¯âÁƒpuuE‹-о}{Y½˜˜ÄÇÇK÷Zß©S§íڵãGpéÒ%éø«¯¾*‹¡[·n¨S§îܹ¸xñ¢ìQQQ8pà€Ç½*ûöí«5ö’ÃyëÖ­‹ H?¿]»vaÕªU°³³S{nÉ÷ÐÖÖÇ¡C‡àææ†ž={ÂÆÆFöNYXX E‹ˆ‰‰ÁþýûaccƒÞ½{«,Ärþüy\¼xñññÈÏχµµ5<<<УG˜™™Iõîß¿[·nIÛ 4P»¨KJJŠlÈxÆ ¥ç:pà@©—ã7ß|ƒwÞyGë3#""¢*JÁÂÂRF°€ø¯¤ê;–¯/þ0`€¨L222Ä›o¾)ŒŒŒD‰ïJ©˜ššŠ-[¶¨œ×¿©ÎÛo¿-6nÜ(;¯Q£FB!&Nœ(íëÓ§8tè066–öY[[‹ÜÜ\!„—.]-[¶TQ¿~}.ÅpôèQÙñE‹©½G???Ùý<|øP!ÄèÑ£eç¯X±¢Ìçåïï/;géÒ¥Ò±={öÈŽìííž}ûTÚÞ²e‹¬Þ™3gTê„„„WWW•6•J¥øä“ODAAÆØW¯^-,--Õ>Û®]»ŠÈÈH©î´iÓ4þˆøøxQPP nÞ¼)Nœ8!¶nÝ*._¾¬rMwwwéœ^½zÉŽ-_¾\:Ö­[7­Ï=22RvýÙ³g‹Í›7küY”6a©^¿~ýÄÁƒ…R©”öÙÚÚŠ¼¼<±`Ái_Ë–-Å¿ÿþ+ÌÍÍ¥}ÆÆÆ"))I!ÄíÛ·EÇŽ5>#GGGqúôi)†k׮ɎOš4Im¬³gÏ–Õ»qã†t,==]˜ššJÇþý÷_­Ïíy +}ÿ~¢|÷²°°°’ïÓ§tìã?ÖÚÖÌ™3em………‰ÌÌLQ£F i_ãÆ5ž_2á׸qcÙyÄèÑ£…B–ðsqqÎÎβzÝ»wBñðáCQ¯^½2ŸiõêÕÅùóç¥8¼¼¼¤cµjÕR› mܸ±TÇÇÇGåxûöí¥ãZŸÛó„ ‹¡&üXôU8‡=±ãÇËæY0`öîÝ‹_ý3fÌö !´ÎvùòeäççÃÙÙ¾¾¾P(xë­·Têݸq=BÍš5ѵkWKõ–-[†¤¤$€B¡ÀܹsŒüýû÷—ÚHII‘022ÂØ±ceq”^=÷øñã²Å5ÆŒàñÜt Ò~;;;Ô¨QCËÓz¬Aƒ²í’C4K+»cÇcêÔ©²ã³fÍBvvv™×€àÿûŸ4§ž­­-fΜ‰={ö _¿~P(€àà`lÛ¶Mvî¡C‡$m{zzâ»ï¾Ã† ðÊ+¯Hûcbb0wî\€——ÆŽ‹jÕªIÇëÖ­‹±cÇbìØ±¨^½ºÖxsss1gÎi[¡P`èС²ã'Nœ¶[¶l©±­‚‚lÚ´IÚnß¾=<==annŽ!C†Hû###¥@´‰ŒŒDzzºì=1b„J½{÷î!>>–––èÞ½;LMM¥÷uíÚµ²aâS§NÅÑ£G±eËÙ\„ÙÙÙ²¹ ýýý¥Ï‰‰‰²gW®\Add¤´]ü¾–Ô¼yséséy‰ˆˆÈ@è;ãÈÂR ØÃ……EÏ•´‡ßÙ³gżyóÄo¼!zôè!²³³eÇ_zé%©ÇN“&MdÇJöðÉ,îYvïÞ=©^É~„§§§ÈÉÉB‘””$òóó…Blß¾]L›6MôêÕKÈ®'ëÙVÜ{P!¢¢¢„B¡Ž-X°@vîøñã¥cööö"//Oºvɸڶm«Ó3+((õftrr’Ž•îáW§N‘••%;ÿ£>’ÕY½zµtL[¿©S§Jû«U«&nݺ%k÷Ã?”Ž7jÔHÖs¬iÓ¦²xÓÓÓ¥cEEE¢]»vxÜ“ÓÓÓSdffJÇmll¤s‡ ¦Ó3ÊË˯½öšì^Æ/«sõêUÙñ«W¯jl¯ôs]¹r¥t,44TvløðájÛ(ÙÀhÚ´©4”üþýûÒ{X²‡þë…—’’"„"55Uz6û÷ï3fÌ}ûöãÆ“]+33SXXX¨ô,nÃÌÌL:6qâDÙ¹sçΕýœ‹‡Ÿ—´dɩޯçö<±‡ ‹¡°‡‹ž í ""¢'Ö¾}{•Å€Ç ;üñÇR¯±â}ÚÂÈèñàgggõ-ZSSS-ð0lØ0 6L¥~tt4BCCannŽŒŒ •Xêׯ®]»J=»~þùgÌ›7Àãža{÷î•ê>\Zô£äb——§õþŠ   @Ú.¾gu&Ož¬ÒnÆŒXºt©ÔSïòåË:]÷ï¿ÿ–>wîÜõêÕ“=z4–.] àqOÊððp4kÖ ééé¸~ýºToüøñ°´´”¶ ‚‚‚’’‚æÍ›ëÔËQ›‚‚ 6 ûöí“öÕ®]‹/–Õ+Ù»€ÖGJ.Öall,ëA×¥KÔ«WOêm·{÷n$%%©] £¤E‹I½µÕ={¶´ˆKÉ…Vú÷ï/ëyZ,>>!!!°¶¶Fff&ùûjmm7Þx[·n•âýöÛo¡T*ÈW2~õÕWÕ.˜SòY¥¦¦"??_¶˜ U}LøÑS+((ÀÉ“'qðàA;v .\PIˆ'¨Ô122B›6mtºV»ví´OKKÃÑ£GqðàA#::Z¥NéXüýý¥„ßµk×pýúu4mÚ!!!HNN–ê•Y³fMYÚ†æ– !„´­iUXàñÐÙÒìííagg' _ŽŠŠÒéº%W’%½Õ„å;wЬY3ÙyÀã©.q>‰‚‚¼õÖ[سg´¯víÚ UIªÅÇÇ˶­¬¬Ô¶‡C‡IÛ:t@VV–ì½èÓ§¾ÿþ{ŸÃÆeCÒÕ)ë=Ôµ^ff&‚ƒƒqðàA?~\6·˜º÷µ8á—””„ôêÕ ×®]CXX˜TOÝp^@õY=xð@k’ˆˆˆª&üˆˆˆè©ìÙ³ãÆCjjªl¿©©)ÌÍÍ¥ùïJöö+­fÍš²¹Þ´Ñ”˜(**ÂôéÓñí·ßÊzГj=B~~>Õ^uC† Á”)Sžžàq/¿O>ù;wî”êxzz⥗^’¶MLLP§Nܹs••…»wïÂÕÕUkü¥h 6ÔXWSËÞÞ^JøÝ¿_ëõ€Çó>zôHÚŽŽŽV›-)66VößbOÛƒO“ÂÂBŒ5JöÌ]]]¢ö•LÄ* snܸQ–|þ믿Tz7–¶víZ|øá‡ZßY]dÚê}öÙgøôÓO‘››+Ûoee…‚‚deeP}_{ôè!{÷~þùgôêÕKÖ»ÏÎÎýúõS{] ÙvqOB"""2\´ƒˆˆˆžØÞ½{1dÈ)ÙW·n]|øá‡ Ejjªl‘mCW‹‡èêBSbpâĉX¹r¥”ìëØ±#¾úê+\½zIII²žt¥c177Ç›o¾)mÿüóÏ(,,” çU×[ªk×®²íåË——ÿÊ•+µ¶QRé^lÅ>|(}vss+óšVVV²!›mÚ´ÁÌ™3µ–â$[íÚµemé’`,/!ÆŒƒ;vHûêÖ­‹'NhLˆ–ìa)„P;d\ 6”;ž›7o"88Xãq…B¡óXMïöüùó1gÎ)ÙצM,Z´çÎCJJЬ×déÄcéÅfvïÞ‚‚Y²´äðóÒJ/ô¢­—)UMìáGDDDOlÓ¦MÒpC{{{\ºtIÖ+íÁƒÒgm ?]“'&&&j{]åææbË–-ÒöàÁƒe½„*=ÂJó÷÷—’Caaaøæ›od«þŽ5J圷ÞzK¶úð·ß~‹ &hâºÿ~Ùª¨ÕªU“%EK;yò¤Êê¯IIIR\€ú!¶¥)•J¸»»ãæÍ›GGG–y4nÜX¶­nÎÀ½{÷bÑ¢EhÖ¬š7oŽ·ÞzKJ–ü¹—îyYì³Ï>“†¨_óøñãZ{K:99ɶïß¿/›#x¼âpÉ•pËcíÚµèÙ³§Úcå™ïNSÝ~øAúܱcGÙ‹@Ù¿;~~~X´h‘ônóÍ7¸víšt\Óp^²ÞžÆÆÆÒƒDDDd8ØÃˆˆˆžØ©S§¤ÏÍš5“%ûRSSeÇ‹‡'ªcl¬Û¿A/LPÚåË—e½–:vì(;, çT{8€¯¯¯,¹õñÇKŸ»u릶']Ÿ>}ds´åææ¢C‡²žjÀãáª_}õÞxã Ù~???¸¸¸¨½' ’ÍÉŸ~ú©lN7]ç’ëÖ­›ô988Xe®¸?üNNNèÙ³'¦N*õä³²²‚‡‡‡ToóæÍ*I´âži?ýôfÏž-ëÕVrøhNNŽJ\!!!˜?¾´mff†   XZZ"55UVJ&ªJ'UÚ.¹X;v ÙÙÙjKjjª,ñµgϽu}_5Õ‹‹ÃÝ»w¥mÙñ+W®àÞ½{Ò¶ºßâÅfŠÍž=[úìáá///1•ì!Z¯^=­C—‰ˆˆ¨jbˆˆˆžXÉáž'NœÀºuëššŠC‡á7Þ%-ÊZ¥·¢â€¯¿þZZpcÆ *½JÏ7XÌßß_ú\2É¢­·ÔöíÛe‰¢´´4 >¶¶¶èÑ£:uê+++|øá‡²¤cãÆ¥Uq5IMME×®]ñùçŸcïÞ½?~<¾þúkéx³fÍTzj2sæL)ašŸŸ^½zaÛ¶m¸xñ"–-[†U«V!11ÁÁÁøã?d‹d|ùå—Òçììlxyyá“O>Á²eËàíísçÎIÇ  [¶äœ¡¡¡X´hÆ/õ`›5k–,™““///ØÚÚª”–-[Jõ<<{{{—;~"""ª„,,,e«ˆÿJª¾ãaaayñ €ðâÊbåÊ•¢Ä÷£J177—m'&&Jçöïß_Úß°aCט8q¢TÏÌÌLc½ž={ê‹···Ú6âââ„R©T9ïÑ£GZŸÃÙ³g…“““Öë—,mÚ´111*íìÙ³GV¯ä3*]ŒÅ‘#GdçoÙ²EVçÌ™3²ãóæÍ+36KKKñ÷ß«Ä6tèÐ2Ïuww ²óÆŒ£¶îÙ³gÅ™3gt~fÅí—ôꫯJÇ>þøcÙ±ÒïæìÙ³µþ …âܹs²s4h ŠŠŠ„BL˜0AöNh²`ÁY>T[oôèÑ:¿¯nnnjÛÈÌÌ5jÔ§P(Ô¾[%µnÝZª¿jÕª2ŸËóVúø‰JðÝËÂÂÂò´@N‰ï¶úއåÅ)ìáGDDDOì½÷ÞÃäÉ“ÕÎ16qâD\¾|YvìçŸ~f±üôÓOèСƒÊþ5j`ýúõXµj•´ïÌ™3ˆ‰‰Q©ëââ‚Þ½{Ëö 8°Ì•iÛ·oóçÏcòäÉ033ÓXÏÖÖŸþ9þþûoÔ©S§¬[¯¿þŠwß}We¿³³3BBBðòË/Ëö !´¶·`Á|óÍ7*sÝkÖ¬Ž9¢2$vìØ+Vh\ ·oß¾8zô(jÕª%ÛÿÁÀÑÑQ¥~ll,Nœ8¡5Þ²tïÞ]ú\z‘ÒÃyÕÍÁXZÛ¶mѺuki;**J6çbEúæ›oTÞ5àñ"_|ñ…ìw%66V6<¾XéÅf€Ç‹Àh{·²³³qýúui[ÓJ¾DDDTµ)Êúƒ! …b5€‰ÿm¦ !8»5=W …"€ðxÈdÉÕc+ƒË—/ãï¿ÿF\\œ4Xñ¼o§N’æn«U«š4iàñ}ºlµÝƒ¢OŸ>:?‹¬¬,üù矸ví’’’ T*áàà€¶mÛ¢C‡Z|xðà4ÜR¡PHs´ÅÆÆâøñãˆÅK/½„Ž;ÊæK,¶~ýzL˜0AÚ—Í¿W2Æ£G",, ©©©pvvFóæÍÑ£G2çsKJJÂ… pùòe„­­­#zö†*­Èüûï¿KɬA~~>6lˆ;wîxœMLLT› .6mÚ4¬\¹°nÝ:¼ýöÛÏ%Ö²0áGD†Š ?ÒÝ—#"""2@ÅÃO:…ï¾ûN¶ìĉ«D²oÞ¼y¸|ù2<(í3111ød¼ûî»RÂoÇŽŸð»qãòòòpåʬ^½ZJö‡-kKöIÿX`kk«2˜ˆˆˆ ~DDDôB[¾|¹Ê|o`mm>ø@•«V­RYɵU«VzŠèùêÑ£^~ùe=z;vìÀÒ¥Ke«š   Y/Îb&&&˜={¶Ösûí7iîÊyóæiMQÕÆE;ˆˆˆè…¦n^4sssìÚµ 66•‡7n¨$ûlll°mÛ6=Eôü-_¾J¥999ذaƒ¾Ãy¦Ô½¯ÆÆÆØ¼y3êÖ­«õÜ⡼ 6ÄäÉ“ŸExDDDTI°‡½Ðúöí‹øøx$$$ÀÎÎM›6Ř1cÔ.èQ™››ãã?Æ7P½zutéÒ/¿ü²N«ŠfÍšaáÂ…øã?d f¢Î;cÒ¤I¸wשּׂ­Ñ¸qcŒ3Fm"°¤û÷ïÃÌÌ ¯¼ò ¦M›¦uñ"""ªú¸h‘¸héí "CÆE;ˆÈPqÑÒé%""""""""2 Lø&üˆˆˆˆˆˆˆˆˆ ~DDDDDDDDD„ ?"""""""""„‘aˆˆˆˆˆˆˆˆÈ€0áGDDDDDDDDd@˜ð#""""""""2 Æú€ˆˆˆtâVüá·ß~ƒ‰‰‰>c!"ªPBˆÒ»ÚØøü#!""2 LøU Òÿ³…(((Ðg,DDÏš™¾ ""ªÊ8¤—ˆˆˆˆˆˆˆˆÈ€°‡QÕ 899ÁÛÛ[ÏáUœŒŒ ?~¼ä®d}ÅBDDd˜ð#""ªØ€··7öîÝ«çpˆˆ*Nxx8š4iRrW˜¾b!""2ÒKDDDDDDDDd@˜ð#""""""""2 Lø&üˆˆˆˆˆˆˆˆˆ ~DDDDDDDDD„ ?"""""""""b¬ïˆˆˆˆˆèù)**‚‘ÿÝŸèI)ŠZbôU¦%>OQ(ïè-ªJ~B¼ù4 0áGDDDTÅäåå¡eË–èÒ¥ Ö®]«ïp*Çcþüùøå—_àêê ØÙÙaéÒ¥úMïvïÞ+VàòåËHOO‡»»;Þ|óMÌ™3–––jÏÙ·o>ûì3üöÛopttĨQ£àææ†Å‹?çè‰*äI"])ÿ+De©ö´ ðŸöˆˆˆˆ*P`` ÆJE)**Bnn.222žéuªš¤¤$œ>}¹¹¹€+W® ""BÏQU7nܼû?>,--±dÉ 8BµçÜ¿§OŸF^^àÒ¥KR;DDDT¹±‡‘:vì¶oߎõë×ë;”Naa!rrržé5ÌÌÌÅa™¥?þ£­R©”þ[üùE7sæLÌœ9SÚž7o^ýuìÛ·GŽÁ+¯¼¢rŸ'‘n¼½½Ñºuk}‡ADUÜŽ;ššZaí1áGDDd€bbbpèÐ!­u¢££‘&Mš °°.\@dd$Ú´iƒ&Mšh><@‹-‰{÷î¡[·n€ððpääähüËQFFΟ?»wï¢Y³fhÕªU™÷ûàÁ$''ÃÃã̺啖–†Ó§O#>>ŽŽŽhܸ1êÖ­«’øHJJB\\âãã/^”ŽÕ®]*mÇÆÆ"==M›6…§NÂýû÷áããGGG•úW¯^EAA´mooWWWµqߺu ùùùððð@AAΟ?¨¨(´k×7Öx¿8þ<ÜÝÝÑ®];¤¤¤ 22&&&ðññÑþ°ôÌÍÍ …ÎÎÎ?÷Úµkk=çáǸtébccáèèX[[«­ƒììlxzz¢¨¨'OžDJJ |||`ooÿÔõKÇS«V-øøøÀÊÊJV'!! hÒ¤ LMå£ óòòpýúuÔªUKzš¼ýöÛØ·o.]º¤6áçææcccé]¬]»6ÜÜÜ´¶Iô"8p ôUq¡¡¡šðƒ‚……¥Œ`5ñ_IÕw<,,,/^^ü=4`ÀQ–õë׋ڵkk­3xð`ѰaCqöìYQ¯^=abb"###ñý÷ß«ÔÏÈÈþþþ¢zõêÂÈÈH˜šš ÂÎÎNœ8qBVwÖ¬YÂÊÊJüðÃB©T âóÏ?ßÿ½P*•¯qìØ1áèè(Hñ 8P¤¦¦j½www@„‡‡—ùlÊ#00PXXXÂÖÖVº—¨Ô]ºt©(ñÿ YùòË/Õ¶?~üxáêê*âããEƒ ¤úFFFâÎ;*õmmmeíN˜0Acì}úô­Zµ'Ožnnn¢Zµj€P*•bãÆ*õ‹ŠŠÄĉ…B¡öööÂÈÈH( éZ~~~åxrrÆ ÓZž¸ýbñññÂÅÅEÚþàƒÄòåËÕÖMNN#Gަ¦¦B©TJÏÇÉÉIœ={Ví9#FŒ7wîÜuêÔ‘ž±±±HLLT©?|øpááá¡Sýˆ#FˆjÕª ¥R)½ÿÎÎÎâܹs²º‡ÄO?ý¤rÍíÛ· bÿþýe>¯½{÷ "((HíñèèhQ¯^=i{Ò¤Iâ»ï¾+³Ý'Vú÷ÆOT‚ï^u€SÉ÷uñâÅ‚ˆèiyxx”üÿà^ñ”ßUBDDô‹‹‹Cÿþý±`Á¤¤¤àüùó¨[·.fÍš%ëI°¶¶Æ‘‘¤¤$lÚ´ 999˜4i’JÛYYYøá‡‰fÍš!00Û·oÇ7РAüöÛo²úèÓ§š7oŽ£G"%%Ë–-áC‡0}út­÷áëë‹&Mš¨í÷¤Ž;†€€ >111xøð!233qéÒ%ôèÑC¥¾¿¿?ÂÂÂðþûïC©T",,L*ãÆÓxüü| 4mÛ¶Epp0Ž;†Õ«W«íIuúôi©MM½ÐJºyó& „/¾ø)))8sæ \\\dC;‹mÚ´ kÖ¬ÁŠ+€[·n¡^½zhÕªRRRðÝwß•y=M”J%ÌÌÌ´–Ò=Õž„““‚‚‚¤íwß}C‡U[×ÊÊ ¶¶¶8vì233‘˜˜ˆuëÖ!99ï¿ÿ¾ÆkdggcàÀèÔ©Nœ8#GŽ`íڵ߽ììl 0;wÖZßÊÊ vvv–âY»v-’’’0mÚ4YÝ—_~µk×–Ýk±-[¶ÀÙÙ}úôÑxÅN:èØ±£ÚãnnnøñÇ¥í÷Þ{,³]"""ªž6cÈÂò"°‡ ‹ž ÊèáwôèQ1`À©´nÝZ˜™™Éö½óÎ;²s ìè IDAT,¬­­EXX˜lÿ”)S«ruÞxã @Ö oÖ¬Y€¸yó¦B???Y¯µáÇ ggg•x”J¥J϶‘#G ###qûömâ©(Å=öbbbÊuÞ¢E‹„R©Ô©îøñãñÿ÷¢¨¨¨\×±µµ-³‡Ÿƒƒƒô3(æïï/ˆäädÙþ‘#G ‘ŸŸ/í›={¶P*•"##£\±Ue={öÆÆÆ²çPlĈ€˜:uªNm >\ï¿ÿþÇÓ­[7abb" dûgÍš%”J¥ˆ—ö%'' ñÑG•Ùn^^žðôô^^^O[Eb?–ªTÀ~Dô °‡©¨^½:œœœ¤bmm ¥R)Û§n9xzzÊöÏ­—––¦Óµ_~ùeÀ½{÷TŽÕ¯_P«V-K½ÖjÕª…äädYÝÐÐP4oÞ×®]áC‡¤R¯^=áÂ… :ÅSQ:wî ### 2»vízf qØÙÙá›o¾B¡¨ð¶]\\РAÙ¾6mÚ=z$Ûïè般¬,Ù}&''ÃÈÈ&&&[eõòË/£  IIIj»¸¸`Ù²e:·W»vm|õÕWOO¯^½ŸŸ¯òû2vìXbûöíÒ¾;w"??þþþe¶»lÙ2DEEaݺuOU^\´ƒˆˆÈøúúÂ××WÚÞ°anÞ¼‰Õ«W—»-mC+ °{÷n:tááሉ‰Áýû÷ ¸×ƒZ¥W“-½’’‚ääd¤¥¥©2hjjŠ;wî”ç6žš——¶mÛ†>úC† ¹¹9ˆ©S§ÂÛۻ®S½zõçšP«V­šÚýƒ ªU«0mÚ4Ì™3çÎÃöíÛ1|øpçèjçÎøöÛoµÖiܸ1Ö®]ûT×)¯¼¼<üüóÏ8vìÂÃÃqçÎ2ßgKKËr­T[žúyyyرcŽ;†ˆˆ­ñxxxÀÇÇAAAÒß­[·ÂÇÇG%‰_Zdd$.\ˆ?þ-[¶Ôù^ˆˆˆ¨ê`ˆˆˆt’žžŽîÝ»ãòåË:t(ÆŒƒúõëãüùó˜5kÖSµmff###L:õ©zCU´7ß|C‡ÅÙ³g±eˬ_¿Û·odž àçç§ïð*TçÎ¥ûÚ°a †Žo¾ùæ©ÛvvvF‡´Ö)k5ÝŠ–œœŒÎ;ãÖ­[1bƇúõë#$$Ÿþùsx¼ÒtçÎ-‹çøñã T{ŽŸŸ&Nœˆˆˆ˜››ãÏ?ÿÄš5k´^')) ýúõÃ믿Žùóç?‹[!""¢Jà…Nø)ŠQê;ªÚ—øl¦P(>ÑW Tå !žÍ8@¢çìÛo¿Å¹sçpàÀôë×OÚŸðÔmW¯^uêÔÁÙ³gŸº­Š¦P(àåå///Ì;]ºtÁÂ… .á‹Õ«Wã7ÞÀ‚ P·n]XZZVHÛ:uB§N*¤­Š²téR„……᯿þ’õŽ ÓK<_|ñÂÃÃñÏ?ÿÈ’£W¯^ÕxΰaÃðþûï#((5jÔ@õêÕ1lØ0õ³³³ñúë¯ÃÕÕ›6mz&Ãȉˆˆ¨rx¡~FxEßAP•c €ÿ$NºZ€ ?zî<==1zôè m3<<ÆÆÆðññ‘ö%$$àË/¿=UûÇG`` öïßW_}µ\ç"''OCYìííQ¿~}iuSu¬­­QXXˆ[·nIsVsçÎŽ{÷pìØ1é9ÿLKÁ6ááá°°°@Û¶m¥}111X¹r%€§ŸŸ$ž5jHsh@tt4¾þúkñX[[cРAغu+,,,0xð`XYY©m¿¨¨£GFzz:<øÔô‰ˆˆ¨r3¼?½|}}±xñâ mÓÛÛ7n>Œ/¿ü;v„ h\ä@WsçÎEÆ 1dÈ|ùå—ˆˆˆ@XXvî܉U«Vi=·cÇŽpvvÆÝ»wŸ*†’îÞ½‹E‹!$$iii8wî,X€àÍ7ßÔx^Ÿ>}¤û¹rå ~ûí7Ìž=û™-úQQ5j„;wîÀÚÚ–––011R©„µµ5Þ|óMܼySß!V(ooodffbüøñ8rä>ÿüsøúúÂÙÙ¤¹óžg<éééxûí·qäÈ|öÙgðõõ…‹‹‹ÖxüüüpëÖ-\¹rãÆÓØ~@@víÚ…`ûöíX½zµTöíÛ÷LˆˆôçEïáGDDD:ú¿ÿû?„‡‡cÓ¦MØ»w/lll0}útŒ15B\\ÜSµonnŽ3gÎ`Ê”)˜5k>úè#éX‹-0eÊ”çÚÓ,22_|ñÒÓÓ¥}–––øàƒ°dÉç5lØ&LÀÖ­[±uëVÀK/½„É“'KÉ›Ê( ÇŽÃеkW˜™™ÁÌÌ 111ضmlmmËœ®*™>}:nß¾­[·bÛ¶m°³³Ãœ9sйsg´oßqqq²ÞÏÚ‡~ˆèèhlÛ¶ [¶læÍ›‡:ÀÛÛqqqhÕª•Êy½zõ‚««+LLLеkWíïÝ»ÔÎOØ£G¼öÚkw3DDO);;‰‰‰ÈÎΆ½½=ììì ²·ye‘››+MiQ¿~}½Å Å¥K— „€‹‹ õÎ3£Ð¶¢ž¡S(‡ðßÞÆãÒ¥KzŽˆˆªº¥K—bîܹ%wÙ !Rõ…BÀ  ýå]²³³qûömxzz>³?|çææ"""yyyprr‚«««ÖúÏjHo^^¢¢¢’’‚š5kÂÝÝÕ«W×éܬ¬,\¿~îîîppp¨Ð¸ž…#FàÞ½{8zô¨ÊpÏW^y÷îÝÕ+Wôݳ“‘‘»wïÂÃãRÌi—žžŽ¸¸8ãIKKC½zõðÑG! à9Døl„‡‡£I“&%wù !6ê)"­ …€øâíÅ‹Wéß¿Ê"55K—.ů¿þª2©z÷îI“&i¶Y³f(,,Œ5 sæÌÑùúùùùhÑ¢…´=aÂüïÿ+ç]<;gΜ———Úc……… ÂîÝ»…ÔÔT4hÐM›6ÅôéÓѨQ#­mÏ™3Ÿ}öLMMq÷î]ØÛÛxüB÷îÝÓx^µjÕP£F X[[£U«VèÛ·/ºtéòä7ùœtëÖ 'Nœ@›6mðï¿ÿVšd²§§'"""Š7B |šöØÃï? …fffúƒˆª8cc~­’á«^½:š6múL¯ajjŠ–-[ê\_©T>“ùûªU«V: ¡3sss´o߾슕@aa!vïÞÙ³g«$ûbccqñâE 2DOÑ=[–––ðôôÔw’5j”+žåË—#;;&Lx†Q=[¡¡¡3f bccÕONNƶmÛ°mÛ6 <7nT»°Txx¸4çiy§fB”L¶<÷©4‰ÇÌ™3±mÛ6äçç«‹‹C¯^½®²ÿ?þÀ?ü€ hLJGFFJó12DJöÀÍ›75þLJ;xð 1|øpA©Têz‹ÏݤI“pâÄ \¸p«W¯Æ¤I“ôÒ3Á¿™½à”J%zôè/¾ø&&&èÒ¥ ŠŠŠð×_aùòåpww¯ð9!éÉ8q·nÝÂ?ÿüƒM›6aÑ¢E°³³ÓwXDDOdóæÍðóóÓy±¤]»v!** þùg…­&_Ymݺ'NDFF†ÚZnn.¬’ì+)//³fÍBóæÍÕ.Š6cÆ äåå@…$¾¶oߎ† âÓO?}ê¶ž•AƒÁÉÉ ˜3g†Žš5kê;¬ W9ú-‘^íܹï½÷Ö­[‡®]»âõ×_Çž={ðé§ŸâÏ?ÿ4øù|ª’þù³fÍBhh(V¯^]©†œ•GLL ¦L™"Kö5jÔ7nÄåË—‘€Ã‡cÒ¤I²i.^¼ˆwß}·Bc166ÆòåË¥Ræ6 AFF†Æã¿üò NŸ>-m÷èÑ@DD>ýôSY’pÆŒ*燇‡K 75iÒ;vÔx-'''çá~ÎyŸóyŸÏ=}ÜÏûó>ïw[téÒß}÷²²²;wîÄøñãÕN°TØ<5440nܸ"éÿ)×®8®û™3gÇ¿ýölllÄ勌ŒDxx8 >>‚x+V¬€$¯ƒ¯¯¯Ês•+WíÚµSØ6hÐ Ô¨QƒÇû{óæ nܸfÍš©³4×^—.]x¶µk×bâĉ_ô6ä<ü‚ ‚ ‚ ¢Œ†víÚ¡]»vèß¿?c˜4i,,,P«V-Œ1)))‚>×®]ÃðáÃQ»vmèêêÂÄÄÍ›7Ç„ ðìÙ3¹sôîÝ›ŸC‘7Ô˜1cx»"Ï» 6ðö.]º ??·o߯ßÿÍe ±qãF¥Ût[µj%7öªU«T^›wïÞaÒ¤Ipvv†®®.ìíí1xð`×Mâ•W½zutèÐ 4@óæÍ¹±OB‹-øgƘ .á‡Â;wî\¨þÊÐÔÔ„ .=]qÞÂ[·naôèѰ¶¶†žž*T¨OOOŒ;ÉÉÉròC† áó^½zµ\ûäÉ“yûÀåÚÿýwÞþÝwßñíË5‚…… )) GŽ)ê´¿|cßlpÀlmmAħÌ$÷•ÿ• ì ¸ßQ)û@œd]ùúú2‚ ˆ¯‰;wîÈþÿ9}÷^*TæÒë588˜•&«V­âºÔ¨QƒM:Uð÷dhhȲ²²cŒåææ²éÓ§3 Ù¿9^*V¬ÈöìÙ#8Gÿþýy{Æ moÞ¼aZZZ¼]SS“½~ýZ ãääÄÛýüücŒ-]ºTpÞéÓ§:×çÏŸ3±XÌûT©REÐ.=¯öíÛ3{{{¥s<~ü¸ ovv¶@fòäÉ‚ö¼¼<Ä455Ž©¡¡ÁfΜÉrssåô~ñâó÷÷WzÍ0WWW–‘‘Ácìüùó*e׬YSèµbŒ1ÞÇÀÀ€åååñ¶ˆˆÞfdd$h“P½zu.cee¥ò\nnnãããíùùùlþüùJ¯ŸDmÛ¶ ú5Š·ËÚm²³³™¾¾¾`Œ§OŸ d<<}ðúõëBÏ)aóæÍ˜;w.òòòx‹‹ ÏnŸŸŸyóæaíÚµr}G…ƒòc===Ô­[W°õÊ•+èß¿¿ÚúÆÓ§O[~7n ÿ78q‚vtt´c HJJÂõë×ñÃ?b Ö®]µk×ôY¾|9f̘!¸~åÊ•C¹råøñëׯ1pà@ìÚµ‹×I¯½øøx|(8Ÿ"^¼x¶mÛ ±sæÌÈHo§vtt,ô¿(iAA” ø/ðèèhøûû—¦.AÅŠ‚,”Šä‚(œV­Zaùòå///^¿aÃwÕæÍ›Ñ¡CÆ=z 33ƒ¤¤¤`ݺu˜6mÊ•+:tpüøqôë×€b£KDDÿ,­U«V022nL”P©R%µæ'+§,¬‰‰ Ö®] YxW¯^ÇscãÁƒ±`Á‚BϹoß>aqÍš5Ü;¬råÊX»v-<ˆŒŒ ¤¤¤`çÎÜÐ*óÐÀÀëÖ­ƒ¾¾>ÀÂÂK–,Á¸qãàèè”+W:::°´´„¡¡¡@KKËBuMKKCëÖ­”}úô‡‡‡@.))‰–5¢~,æææøý÷ßѦMAý¦M›ÆÇ_ýUð[Ö××ëׯG@@ŸÃêÕ«±`Áhii¡S§Nعs'€ƒŸ$–cQÖ^Ó¦Mann.•½ž=Rë—ÈàGAeƒŠ’OŸ>å?¸ ‚ ¾R¬J[‚(« 4HaýÉ“'ùgKKK…I €qãÆñ̹Ò[qýýýùï'N ??ÏŸ?熥š5k";;)))¸~ý:ÒÒÒ``` 0ÊHo©”5f%''£jÕª…ÎïñãÇ‚c‰Q¹sèééÁÇÇ;vìPày–ŸŸ_èvÖ»wïòÏšššÐÑÑA\\œ@¦fÍš¸yó&Ú¤û:;;ÃÄÄDÐÏÝÝ/^Ty~uIOOGÛ¶mqýúu^çêêŠ_~ùE Ç$g‘öìT†H$‚––Oz!MýúõñÛo¿ÁÝÝb±¼™IzíUªT ½zõ’“éÞ½;ÌÍÍy‚Ùµ'1ø9s999ÈÊÊÂåË—ù˜†††xðàœœ AÒÙí¼ŠæýâÅ •× ¬A?¢ØaŒ!::§NBrr2^¾| CCCXXX E‹ðôô„–––ÒþÑÑÑxðà€—oE˜ªˆŠŠBbb"@WWWÎm·¬ÏÝ•­­­Ñ±cG•ò¹¹¹øþûžŽN:qwõ[·nÉÅÇFSSúúú000€••ªU«V|“(AvíÚ…}ûöA__[¶láoî‚ ‚ â[¦V­Z ë%ÏHP·n]…2°´´Ä7€ÿ :u‚X,Fnn.^¾|‰Ë—/#!!··nÝ999رcc8{ö,ŒŒŒ¸ž¦¦&|}}¹¼¬§Þýû÷áææVèü$ÏŠÌÌÌÊU¯^]a½´—Wvv6ž­ÆÆÆðööƹsçРAž4å÷ßGjj*Ž=*×G]ç ekO__mÚ´ÁáǬ=éµÜªU+˜ššrƒ_DDîß¿ÏÛ]]]É^£(z–ÈàG‹-BPP`o¾"žLœ8IIIü?@U,_¾\]¨0ž>}ŠY³f(0„¹»»¬ªxüø1ñäɵâY”öööðòòBdd$‚ƒƒÑ¯_¿¯*ÞA( @h×®¶nÝZÊêA ‚Àõ¢KK‚(ëHÙ¤±°°àYi¥·™J“ŸŸ/ðÆ’MæàïïÏ ~ÇŽÓ'Ol÷lÕª• F`DD„À‹LÖéA6ÎÛöíÛñã?¢^½zJçöÓO? ŒSmÛ¶U*+ë (AÚ`h`` Vì@+«ÿ2 ¯¯ÔÔT•ÎÒ†$\½z@Ás›"ÂÂÂ`ii‰ºuë*5:)3ÊåææÂ××W%×ËË ýõ—RãoÅŠ¡©©É¿/Ù*ê`bb‚´hÑ‚sìØ1¬Y³cÆŒÈZXXðµ"íñ(‹´YÑÚ“üŽ;&Xk­[·†‰‰ ŸSDDLMMy»2‡ÙøÒ}¾ÈàG »wïÆ´iÓuåË—‡‹‹ êÕ«‡¤¤$\¹r…»çÀ0tèP¾¿¸(W®ã ,žÃç$**Š- Æ/^,w- cÙ²eüf5jÔ(•²õêÕã7~Ærrr““ƒøøxÁMSbDSæîÿ%0räHDFF"++ sæÌ!ñµÃÿ@uttJtKAÄç&==]¶J>HAj¡,|’§§'îܹ ÀvôèQž´CÂŽ;F'''A»ŸŸ7æH˜êׯϷÖ:88àæÍ›ˆåí"‘HÎèbgg‡6mÚ <<@ÁVÙ>}ú`ÿþýPðܲbÅ 8p@PÿÃ?(¹ À… œœ,Yôöí[A\7Ùó(ÃÆÆ†~÷înß¾† òºÌÌL¬]»vvv°··d–6ø]»v wïÞŒwþüy¾+MSSsçÎåm¥·ùùùÈÍÍ•Û2;mÚ4³H“&Mpüøq•Ž5033ãHéx~EÁÓÓAAAÜù&OžŒ-Z¶={zzrïÃgÏž!44Tîyÿþý{ìÚûî»ï¸AOzmÕªU‹ocoܸ1ñ%m¸V¶kP«RÂW÷ûš1öÍÇ0ÌÖÖ–Grr2366f’k €uéÒ…¥¥¥ ärssÙœ9s˜X,Èîß¿_ 7xð`Þfhhø9§R"œ?^0ß;v(”‹‰‰a^^^YIéÖ­›Òñ_¿~ÍÊ—/Ï0±X,wÝgΜ)+22Rá8·oßfÎÎÎÙ©S§~üÄ?™™™¬\¹r ÓÑÑaÏŸ?/m•Xpp°ì÷W}÷;*e¿ˆ“¬+___Fñ5qçÎÙÿ?²/àÞK…Š¢À\z½³ÒdÕªU‚¿Ÿ(”»pá‚@®råÊìØ±c,??Ÿåææ²}ûö± *ðöªU«²ôôt¹q5j$÷¼2~üxÞ>a¹vwww…:ÅÄÄ0===lùòåÙŒ3XXX»xñ"[·nkݺµÜ˜C‡•OCCC ãààÀ’““cŒegg³^½z Ú—/_Îûfgg Ú&OžÌÛž?Ο;$óyñâoŸ6mš ï¦M›xÛÉ“'mnnn,))‰1ÆXll,kÖ¬™ ýêÕ«¼ï”)Smqqq,##ƒ?÷DEEÉ]—.]º°~øAa‘œ—1ÆZµjÅû|ÿý÷ ¿ŸêÕ«s+++…2¹¹¹ÌÕÕU ƒ³³3ËÍÍ|Ï"‘ˆ·W¬X‘:tˆååå±¼¼ÛµhÑBå: ’k¯_¿¾B½c,""‚ˉÅb–™™©Tös`kk+­û!ö‰÷*òðûBÈËËì·¯W¯tttŽøøxx{{ÃÅÅEÎm8;;§NBBBÞ¼yƒzõêÁÑѵk×–“}ðàwÕÕÕÕ•³˜¿xñBtÕÙÙYàNœ““ÃßL–ôÊ•+cúô邔꾾¾Ø¿¿Ü555„òåËcüøñ¼~Á‚^Ó¼P IDAT…Æ´ËÉÉÁ•+WpñâEèëëÃÙÙ... ‚&$$ð`›ZZZ‚7/Ò¤¦¦âßÿÅõë×abb‚ ÀÁÁA­-Æ?ÆÍ›7y2ŒêÕ«ÃÓÓS.áELLŒàíPŒöâÅ‹000ào=V¯^-x;¥¡¡‘H$ð¸SƶmÛø›‰fÍšÁØØ¸Ð>а³³Ã„ Я_?®ÊøðáNŸ>»wïâõë×°³³ƒ££#êÔ©# !11‘¿=ÒÖÖ†‹‹‹ ýÕ«WˆçÇŽŽŽ(W®?–ýû¨Y³&ªT©Â³l£>{öŒµoß^e߆ Ê=£?xð@áw5räH–™™É´´´ ½fÒåÊ•+|liï6}}}–““#§·:~dŸƒôõõÙ£Gx{nn.›2eŠœ¦t)_¾<Û¼y³Òs$&& äeŸ-?|øÀ 2ׯ_W:^ÿþý¹Ü˜1cTÎïs@~ßÓ¦MÕ+Wuþþþ öîûúú⯿þRÚ_’1'$$;wP` =z4cÜ;K:ƒí©S§cDDDb:9r„®Zµ*ÜÜ܃—/_òúfÍšÁÖÖVåÜtttЭ[7¬\¹’×ýý÷ß ûeeeÁÕÕ•—•&::ÎÎθuëV¡iÔ¥éÞ½»BD  XiÇŽ±dÉLœ8QÐöêÕ+4lØ>TØ7''ëׯGjj*öíÛ'çåVºººèÛ·/æÏŸ/÷&G™™™8þ]å³}50`ÀžÜ£F‚ïQ,cøðáÜÕÔÔT.ù‡4çÎ㟠Ë]&ùT‹aY.ø‚=ü$ÅÕÕ•ÕªU‹ÙÛÛsÙyóæÉ½™œ×¿zõŠijj ÆuqqèX«V-Þ6zôhÆcýõ— Ï€Ôšïš5ký¤cÅI{øIJ¿~ý؉'Ø¡C‡˜···Êsªòð;xð  ¯[·n[·n«W¯¯×ÓÓcOŸ>ô5j” ¯ŸŸÛ±c[¸p!«[·® íÀŒ1Æ&Ož,÷&ÇÓÓ“ 0@à±ùï¿ÿÊy½©ãáwäÈÁÛ é7kd=ü ÄfÍšÅf͚ŦNʆΘ½½½@ÎÌÌL.fÇŠ+2FFFìÇd«W¯f ¼­144T#¢wïÞ¼>33Sî  à¼¼màÀ‚¶ÜÜ\¦««ËÛ·nݪðZ}.ÈÃJI‡A_1äáG¥,|a~ŸÊ‡X^^^i«!G^^^±ÆTûðáƒÊö””Á}håÊ•…Ž™••Åòóó?Je^ŠÊøðáÃGŸK'Näó7o^±Ž­¥¹öâãã•>–ÅíáWê7ËÒ,_ºÁï·ß~ãíãÓ›7o˜¾¾>—©\¹2KLLŒµcÇÁ8={öämÒÆˆ5jðúÈ_CCƒ'€uÛŽˆˆ`Œ1¶uëVA}PPZó•6T­£²¿Aƒ úæää¶øjhh Kª ~VVV¼­aÆ‚fnn®Àè%íÒ{ïÞ=A´W¯^‚q>|È·kkk æ£nÒYÔ1ø-Z´ˆËXZZ*”‘5ø©SLLLØÉ“'ã¼ÿž™˜˜pcccvïÞ=Œ¬AµS§N¼MÚÈkjjÊoì²Al%åÉ“'Œ1Æ’’’õþù§Ü¥×C`` Z×·¤ ƒ•’*ß²Á/))‰;vLíý·nÝb©©© ÛΜ9SœªQLÁJY*_›Áï[çåË—,!!M:UpRµ­ùkáîÝ»ÜaÈÃã´Õù¬¬\¹’× ,(mucÅoð+ÚžCâ³!që• I}àÀ¼{÷Ž×Ïœ95jÔôíÛ·/Z´hÁ=*ù‰o €¤¤$ܾ}€pK¥¾¾>€‚­Ã‘‘‘| •*U‚§§'€‚m ÒȺA+CÌS‚l‚id·ÖjiiñàŸ=ec("%%<àÇýúõœWSS½zõâÇþù'ÿ|ùòeA Ùôï–––Ç­[·ðöí[lذ¡P}ŠI" ƒš;;;üðȉ‰A›6mmGŽAZZ?ž2e êÔ©#ñóóÃwß}ÇOž<ÉׄŸŸ¿Þ/^¼à[Ö­=8sæ áÚ344”Ó (pÕ– ILCÄ×ÃÑ£GѾ}{¹ÀÖŠˆŽŽFóæÍqá¹¶÷ïßcðàÁ?~<ÿ‘ ‚ ˆo›ŸþuêÔá %$˜™™•’FŸkkkøúúΟ?Ïíß›6mT¬X£F*emJ2ø}¡899)Œ'ûزeK…ý%9  ã$뎭­-ìììxÛñãÇ.ãÆãŸ%Féj¾¾¾<¶@ÅŠçU•ÕUiÛ¢q$hjjÂÆÆF®^6nƒ:ç½{÷®à844={öi#ß“'OŸŸ¯°¯$Ž4^^^°··—‹õP’Hü$Ž cøðá˜6múôé#È„+‹1jÔ(¬\¹RaÜ„Y{999<ûnµjÕàêêÊÛ [{’ìKÒk¯cÇŽ‚ÌѤç.ÉÐLß:iiißÔ6 à^ݪU+Lž<™ÿx•FWWáááØ³g¦OŸ^ Añ¥!;ÜÝÝÏ5_3K—.…¶¶6à×_-em>gΜá1&ƒ‚‚P¡B…RÖ¨d ƒßв€˜Ò 2€‚äŠMú hBÚËïØ±cHJJâ-kkk|ÿý÷¼=""‚Ä]»våŸeƒ¯ÊÆ”!+gmm­PNOOb±|nY¡:^]’ÀžþùçìÝ»WP.]ºÄÛ?|øÀ½I?~,è+”¶4‘ö¸“6Þ©¢OŸ>X¸p!vî܉˜˜nÜËÍÍŘ1c0`À…ž/%±ö^¾|ÉS¡W©RcÇŽå^€ÈÎÎæFg@¸ö¤‘ö ”ö€%ˆo™ƒ¢mÛ¶¥­Æg#// €——&Ož¬T®N:غu+/^¬Ð ‚ ˆo‹{÷îñÏðññÁþýû¹ìk§N:;v,`ÇŽxóæM)kTò¬Zµ `ccƒ‘#G–²6%ü¾P”Ý\$[{%¦N oooXYYÁÍÍ ,P"ã—_~ÁСCÛ·o‡\\\¨pËóš5kxˆíÛ·£mÛ¶*å0eÊ>ÁÁÁrúœ9s~~~ˆ‰‰‘ãáÇðóóáC‡Š|=‚ âs“€û÷ïãÆxõêŽ?^hFÚ¯9sæ ** 'Ožü&ž̘1QQQ8uêÔgÝ¡÷¹!ƒßŠ2ƒŸ¬GÝßÿ­PNÚkÁÈÈHçÏÕÕÕªUPKoáÂ…¼­uëÖÀ½Bc˜3goÿî»ïº‰D"ôìÙSpî‰'âýû÷Jç6sæLÁ[777…[d%üóÏ?ru²ÛÔTõ— {í¼¼¼°hÑ"•E’ŽÞÖÖVÐ÷ÆrãÿðÃhÑ¢Fõë×ó¥l|BÙ¸‡Ÿ‚´NØVŠhÖ¬™à;€iÓ¦áúõ낺Y{:::‚-ÙuëÖå×2??_pÞV­Z€ FßìÙ³ùg'Ÿ4Òë©8bÄ·Ä7pôèQìØ± ÀÛ·oadd„Ç˽(ÉËËC»ví0iÒ$dgg£[·n¸xñ"<==!$¼yóööö¸páš4i‚ž={âÅ‹˜5k&Mš$'…–-["::ݺuƒ¥¥%Fމ;v:7ÂÓÓSî^¥Œ¾}û"66QQQ…Ê2ÆðóÏ?#$$qqqj¯ééé¨W¯¢££Ñ¬Y3àÙ³g˜1c~úé'…}®^½Š'N`Æ øþûï‘ }}}9rDaxŒ«W¯âäÉ“X¿~=†Šœœ¥òiii°··Ç•+Wàáက¤¤¤à§Ÿ~ÂÌ™3²ŽŽŽ8zô(6oÞ,wÎ]»v!,, õë×ÿ„«CAŸ‘H+++888(Ü]ö-P®\9¸»»ÃÝÝ]íPQe™FÁÝÝ]-Ç¡2ͧfý(Ë_p–^eÙnÓÒÒx6XÌ‚%'' döîÝ+«oß¾rãŒ=ZaVÞ—/_2Æä³­JÊäÆºÿ>ÓÕÕÈ9::²ØØXÜË—/Y·nÝäÆ”Íº*›¥×ÓÓS™1++‹ÙÙÙñvöìÙ3Þ®,Kï‡XµjÕ:æææ ÚX:u˜¯¯¯ ÛV\\ÓÐÐà}›5k&H£ž˜˜È´´´:Kˆ‰‰ÌG:û²*ÔÉÒ»xñb.S¡B…2²Yz###ådòòò˜§§§@®^½zìýû÷\æíÛ·ÌÀÀ€·W¬X‘=|øP0Î_ý%ÃÏÏOî\²Ù¯$åñãÇŒ1ÆÂÃöÿþûïJ¯•‹‹ —›={¶R¹Ïeé¥RR…dé}ùò%»ví/AAAÌÌÌLPwûöm¹~L[[›YXX°K—.ñzI&miÖ­[ǰ]»vñº¬¬,fooÏ,--÷ )))ruõêÕc:::‚{0cŒyyy± *°ðºÇó{·ì=GB||<À–-[¦°]L$±Ñ£G«%ñâEªöøê"{}òóóY:u˜¾¾¾ “¼„Aƒ1===VµjUvíÚ5^¯èûbŒ±rù˜˜˜BåéS«V-fhh('ëïïÏ*UªÄ>|ø ¨¯[·.óòòR8>A(ƒ²ôR)K”¥— ˆ€²ô~ã ²÷þ÷ßhР~úé'¬_¿ýúõxÜUªT ?ÿü³Ü8Ò[+%899ñí-Z´{»¡¯¯víÚÉõ³²²ÂÚµku7nÜ€½½=jÖ¬‰Ž;¢~ýú033Ãü!6l:uê¤rÎç΃··76n܈x{{ãÎ;¼}̘1jeP‹Å‚¸N7nÜ@»vípæÌ\¸pC† ÁÍ›7‘€°°0ט­­­ ¶á… àáá+V`êÔ©hÖ¬™`»“´¬¡¡¡@µk×báÂ…‚-ÃK“&MøçôôôÞj¦¡¡­[· âÞ¾} ,àÇúúú3f ?~ùò%œœœ0yòdlذƒFçÎy»‘‘~ùå¹s)Z{666ÜëÔÃÃzzz‚v---Aö_iòòòëÁÍÍ­°éÄWÉÁƒáììÌËܹsñüùsAª¿£}ûö¡qãÆ¼NQâ¨Õ«W£zõêèÑ£¯ÓÕÕÅàÁƒñèÑ#\¼xQ®l\Oèܹ3²³³[†‰¡C‡¢V­Z¼¾Zµj˜6mšÊ¹K’Õ¬YS¥œ4†††¨X±¢Ú{nnnèÞ½»Úã«‹ìõ‰Døî»ïðîÝ;¹ rssÆC Š¿/iùÇ£Aƒ…Ê+ÓçÍ›7rñrˆÔÔTœÒ˜››£aÆjoaa—Ò'11‘__é{-tèÐfffعs':tè `;¯A‰G ‚ ‚ õ!¿2ˆ¶¶6NŸ>Y³fÁÈÈH¡LÓ¦M£Ô›C,ËyÖIb¨IŽ¥(Ï*aôèѸpá‚\?YìííqèÐ!¬_¿¾Ð™–––¸xñ¢Âooo\¹rEÎÄØÿ•Ÿ§««‹cÇŽaÈ!rmy???DDDÈkjjŠ›7o¢OŸ> uÕÕÕŸqãðûï¿ Æ‹Å˜={¶ÜëóçÏ•­WÁCºtFÛa̘1ðòòâÇ999>|8¿¦ššš8zô(‚ƒƒ•&ÇpuuÅÕ«W@ÒH®±4Ÿ²ö®\¹Â?»»»+ŒaEßzzz077çÅÈÈ‚:Ù˜|E!==999ptt„ŸŸŸ ôíÛÁÁÁptt”ë÷ÇpïþýûcëÖ­rYÓàÕ«WðQqc$†Ì¢ÆHÍËË+õX={öì,--1pà@lÛ¶M鋲ÏAHHêÖ­ËõÙ¾}»R}Äb1úô郰°0¼}ûùùùسg”Æ\%‚ ‚ >äá÷… ¯¯ˆˆ~lii©R^bDš:u*Nž<‰¸¸8¼yóõêÕƒ££#ìììTnï 2Jo¹‘õî „··7?Vg«¤››Nž<‰””DDD 11iii000@•*Uàé鉺uëªcòäÉÜCÎÔÔUªTÁ•+Wƒ3gÎ@,ÃÝÝ®®® ½>¤="=8–/_›6m¼yópüøq$&&"775kÖ„‡‡ììì”êV¡Bìܹ³fÍÂõë×qëÖ-èêêÂÊÊ J³95 ˆŒŒDjj*àìì¬2Õ{XXß&¬jËò€øÚ‰ˆˆ ô>xð`AfeEäD"þøãÄÆÆ ê³²²øv_ L:&L@xx8îܹƒôôtØÙÙÁÑÑõêÕ+ÔgÆŒèÖ­?–ÞF(ÑÙÕÕ•K–%<<œîß¿¿ÊóññT¬XFFF°°°ÀÔ©SÕêsæÌôèÑ;wÆ¡C‡`mm   ¸$3°‰G°ì¶Qulå}ñâE‘ú¥¦¦–ªWðñãÇÑ»wotíÚGŽá G6mÚÄ3ñ~NŽ=о}û¢{÷î8vìOˆµnÝ:Œ9RaŸbÅŠ8xð ªU«†'OžÐv^‚ ‚ ˆ/2ø}!hjj Œkꢫ«‹Î; b§©KÕªUn¹”P©R¥Ò (ØnÔ«W¯êkkk+—W$ñTª`Œ bB©2”U©Rå£J¬­­amm-0Z†£££Jc›,²[攀ñãÇãÕ«W¸párrr†DKKËB ÈÒ¨û½kkk£cÇŽ‚-„ê"ñ6R†‰‰‰Z:0Æøöb¹ŒÑA/ÎÎÎ8vìÒÒÒ”zùJ³oß>hhh`Æ ‚í¶’í»ÒØØØ@,#""B€B@i ¡¡„„õ&‚‚piii‚8xŸ›}ûöA[[6l€±±1¯Wt}>¡¡¡<$ƒ´—û½{÷”öqtt„³³3vî܉êÕ«ÃÚÚŸC]‚ ‚ B´¥—ø*xûö-úõë‡&MšâU©“Ì£¬£§§‡(ðÄûóÏ?KW¡ÏÈùóç¹7P=Ìñ­Ó»wo\»v­XÇ Æ«W¯Ð¿dddÚòóóåäutt››‹[·n>|ø€Å‹cÑ¢E„Ù•*UB×®]±wï^ÁVýÝ»w+L>%MÕªUѲeK:tHí¹;v ùùùrÆEeìÝ»óæÍS8ÏEƒõöíÛ Â(ÌŸ?Ë—/ <†_I¡££ƒ÷ïßóD&999˜3gV®\ x÷îÂ~ÄéÓ§JÞ}AA_dð#¾ ®^½Š;wâÒ¥K‚úÒôÜøœL:•o_.,Ê×ÄêÕ«xºÎ›7¯”µ!ˆ/ ==½"'±( www¬^½§N‚­­-zö쉞={ÂÞÞUªT‘“:t(tuuѺuk4iÒ•*U¯¿þŠmÛ¶€ yÌŸ?*T@‹-àïï&Mš`„ 2dH¡º1W®\ U±aÃ4oÞ\e Œ1Œ9AAA¸~ýºZã«Ãðáá­­æÍ›óë³yóflÙ²€üõ)iFŒmmmxxx iÓ¦055ÅöíÛ±iÓ&•úôîÝÈÌ̤РAA_´¥—ø*Pô€çéé‰iÓ¦•‚6Ÿ333Ìœ9“&MÂÙ³g {{ûÒV«Dyüø1<?~Ø¿‘ÂJA("%%Eúðã³íý‘tFÓo ‘Ht€P7N²……({DFFbß¾}HNN†¥¥%š7oŽV­ZÁÐа´Uûläää oß¾ÈÈÈ@=¾úmU¡¡¡Ø²e ´µµ±k×®/æ»^´h‘¬¡Ù˜1–^Zú_"‘(€-P`ô)ÊöÕo…çÏŸÃÕÕ:uÂÚµkÊ\¼xm۶ŲeË0lذϬá×É¥K—àîG¢}ûö¥­QF‰‹‹“õ¸ÄÛVJê„JD"‘#€âsù&‚'…1¦øm«š‡ñUàåå//¯ÒV£TÑÖÖFhhhi«ñÙ@@@@i«AÄ„™™Ο?6mÚ`÷îÝrÉ£Þ¿,Y²„Œ}ÅDrr2~üñG899¡]»v¥­AAñ?ÈàGAÄWC5pîÜ9…^¿ºººˆŠŠ*RærB13gÎÄï¿ÿާOŸÂÂÂýõD"Qi«EAAü2øAñU¡*C;ûŠ‡Þ½{£Zµj(W®zõ걘~RñM‘&}0vìØ¯>œA%¿¿?=z$9¼ù©ãѯ3‚ ‚ ¢HØÙÙ©•ᘠâ+%Wú J•*prr*-]‚øJÐÑÑ‘>ÌüÔñ”§°#‚ ‚ ‚ ‚ ¢ÌA?‚ ‚ ‚ ‚ ‚øŠ ƒAAAAA|EÁ ‚ ‚ ‚ ‚ ¾"ÈàGAAAA_”¥— ‚ Êaaa‰D¥­AAA_(ߺÁÏQò!>>ššš¥© A_Œ1Ù*C饠 AAAñò­ü´¤òóóKK‚ ¾^(tAAAñYùÖ ~AQVØÀ¬´• ‚øLÜ,m¢4‰ˆˆ@vv6@GG-Z´P) ‘H„F©5~NN6mÚ„:ÀÒÒ²HºÝ¸qiiiðöö.R¿Ò$;;›7oF§NP£FÒVQQQÐÑÑ‹‹‹R™gÏžáÚµkü¸fÍš°³³ûê•9233±uëVøùù¡jÕª¥­Î÷nðK` †††èСC)«CDYçÎ;¸qã†tUNiéB|]0Æ~.m‚ ‚ø<ôêÕ Ïž=˜››ãéÓ§*å'MšMMMœ9sF­ñÏŸ?ÀÀ@Œ5 ¿þúk‘t[ºt)"""œœ\¤~Åž}ûðáÃôîÝ[í>gÏžE`` îÞ½‹•+Wª” E^^zõêõ©ª*å‡~@¥J•päÈ¥2çÏŸG·nÝøq`` ~ùå—Ó©¤Ø½{7455Pbç8qâFÿþû ,(±ó”5¾uƒßC6`aa={ö”²:A”u-Z$kðË*-]‚ ‚ ˆ/ƒÇ#??5kÖT»OïÞ½ñ믿BC£ø#Ä4mÚË—/GÇŽ‹}ì’fëÖ­x÷î]‘ ~^^^X¶l:wî\¨ì–-[]¢?uð÷÷GVVÁ£„©©i©êò)lذ:::%jðkÓ¦ –.]Š®]»–Ø9Ê"ߺÁ ‚ ‚ ‚ J”   deeÉÉD[[*TPKÖÈȨH†A]]]L˜0Amù²Žžž~üñÇÒVƒcdd„òåË«”ÑÐЀ®®.@$}µÊ,˜8qbi«ñÅA?‚ ‚ ‚ ‚(ÃÔ®]Œ1•2Ïž=ÃðáÃuhÓ¦Ò>!!!زe ž„““lmm‘˜˜ˆ{÷îaÊ”)‚9}‰PöH‚ ‚ ‚ ‚(Ã4jÔ 6T)#‰ «« ]]]äåå!,, =R*ÿ믿¢OŸ>HKKCŸ>} ‰àããƒëׯ+”OJJ‚‹‹ Ö®]‹š5kÂÛÛ{öì³³³Âó=z€››Ξ=‹6mÚ@GG‹/Æ’%Kääµµµ¡«« MMMîý&)м¥çûáÄ……!))Ié|µ´´øX²ãËzØYYYáàÁƒ “gÍš5ˆŽŽ†Òs5nÜÎÎÎJÛ?…¸¸8ì޽˗//Ô\Μ9777\ºt žžž˜8q"œœœžž.gìþÿûRt=•y£ž|ÈÄb1ëÙ³§œ¼‰‰ ÓÖÖf›6mâuùùù¬B… ÌÙÙY©îíÛ·g^^^jÍSB\\À¶nÝZ¨¬óööV)“——ǪV­ÊÚ·o/¨OMMeb±˜Mž<¹Hú† T[~ïÞ½ìÒ¥KŪC×®]™™™KKK+R?oooæã㣖lÍš5Yùòå™»»;{õê¯ÏËËSÙïêÕ« Û»w¯R±XÌôôôXHH¯ËÎÎf::: ד£££ÀN”““ÃLLLXïÞ½ÕšËÇ`kk+ýyˆ}⽊<ü‚ ‚ ‚ ¢ ‚³³3/‡Ɖ'uAAA¥­¦R>Œôôt̘1åÊ•ãõ-[¶T˜!55{öìA×®][5---Ñ¡CüñÇøðáƒ\¿~ýúaÈ!üX$ÁÍÍ )))Å<£âECCýúõCxx8ž?ÎëCCC‘››‹Aƒ•¢v@@@7n\¬cjkkãýû÷<{tIajjŠ“'O âWWâšáÇ ’±hkk£aÆ ×Ûÿý~¬¥¥…Þ†_*dð#‚ ‚ ‚ ˆbdÕªUxúô)/=zô€¯¯¯ nÕªU¥­¦RîÞ½  `«°,šššruñññ`ŒáÒ¥Kpww”k×®!77÷ïß—ë§ÈSµjUäææÃ,J–"77{÷îåu»víB“&MP·nÝRÔ¬d˜6mD"êÕ«¬]»iiiÅ~///û¸€âõfaa¡p½5mÚýõrrriiiˆŒŒ,±­Ø%%í ‚ ‚ ‚ ˆb¤|ùò‚,¬zzzssóÒR©H¼zõ Í$+AâåÖ¢E Ar iJʈSZØÚÚÂÝÝ;wîĘ1cðèÑ#DEEaýúõ¥­Z‰ààà€{÷îaýúõ C`` &Nœˆ1cÆ`Ñ¢E_]&á¹sç¢U«V°²²BûöíqøðaT¬XsçÎ-mÕÔ† ~AAAApªV­  À§(›ª,VVVwww :´Duû’8p FŒ{÷îaß¾}ÐÓÓC=J[­£R¥J˜1cf̘˜˜Ì;K–,AåÊ•å2æ–uàçç‡ððp¼}ûcÇŽÅÈ‘#abbRÚª© mé%‚ ‚ ‚ ‚coo  3«,Š2îÚÚÚB__;wî,iÕ™™™_Äø={ö„®®.víÚ…tíÚUm¯È²Ž““öíۇʕ+#22R¥lI_%AÿþýqãÆ \¾|»wïÆôéÓË”± ƒ_‰²víZAì‚K—.©”ÿ믿àîî^¤ ˜:uB³fÍŠ¬ÛåË—áîîŽË—/¹oiâããooïÒV°ÿ~¸»»#==]¥\Ë–-ùhÙ²ågÒ®lâéé‰:”¶AAQ¬¬ZµªLmõlß¾=jÖ¬‰%K–àÅ‹€üü|Lœ8çÏŸ—“×ÓÓìY³‰   äçç Úe?'''ÄÄÄâÇeeeëøW¯^<ç)ßÈÈ~~~X½z5bcc1xðàbÓãcÉÏÏÇܹsZ¬ã¾ÿ^®.22Ïž=Sº(¸žÑÑÑxûö-¯+Îï«$`ŒáèÑ£xýú56n܈%K–`þüù˜9s&6nÜXâ‰KŠ 2ø• III‚ ¥FFF*åSSSqéÒ%dgg«}Ž÷ïßãÝ»w’ôðj“‘‘K—.áõë×EêW\üñÇÐÕÕUøvHïß¿Ü(” ]]]$%%}¤†…óìÙ3\ºtIa¶)i<==áíí>”9«„;v@WW·ÄolÙÙÙj}¿AAQ–(_¾|¡Ïƒ_šššøå—_ðèÑ#8::¢G¨[·.Ο?öíÛ+ì3~üx 8óæÍCýúõ1hÐ |÷Ýw°´´T˜Ù÷céܹ3rssagg‡€€ØØØÀÖÖ¶ØÇ¯[·.`mm ;;;¥òƒ «W¯P«V-4oÞ¼ØôøX®_¿ŽY³faĈE¶¨bÞ¼y¨T©|}}1~üxî¬akk‹)S¦(í×¹sgdggÃÆÆÝ»wG:uРAƒbÓ«$‰DX¿~=RRR0cÆ L™23gÎÄüùó1lØ0T­ZUaš/ ŠáWbcc1}útìÛ·ZZZj÷[¹r¥Zr’lGв)ãÔ©S`Œ•¹™ùùùÈÎÎ.ò èìÙ³%:~Q|Ob±ê?£9sæÞ¾}˳]•5òòòŠdˆþX.]ºTæÖ2AAA”5Œ1kÖ,4lØP©L§Npúôi„„„ -- ãÇG¿~ýއÊÉ‹Åblݺ={öÄÉ“'---øùù¡[·nròS¦LQhHóõõU™åÖÝÝÿüóvïÞ¤¤$téÒ¥Pƒ¢©©)fÍš'''•r@AvÖ .`÷îÝHNNF×®]ê/¡Y³f‹Åñññ¸ví,--ÕJ#˜˜$$$ÀÆÆÎÎÎ [ÿý÷^¼xGGG0Æ‹7n nݺpqq‘“ÏÈÈÀƒ¸gß;w¸—¡©©)ªU«&×çÞ½{x÷îZó}ýú5>|ÈÇ¿}û6Ï0%;¾DÖÒÒ*TëæÍ›000Pzé^½:ôôô`ll¬°ýSyóæ >|GGÇb;++ QQQHNN†±±1lll`ee%·®ÓÓÓñèÑ#î)yëÖ-<}ú`ff ¹±SRRðìÙ3þÖ&&&÷ï߇›››Âï7>>^àÒ]¾|yôW–Çãõëר_¿>òóóqóæMÄÆÆ¢~ýú*¯Ó£G 3334nÜ™™™¸wï²²²h›5AAñE’––†˜˜hii©4d| ÆÆÆ˜={v¡rðððÔùûû«ìãããŸBÇžyò„ijj²üQnœ;wî0lþüùJÏÇêÖ­«´]–ÀÀ@f`` ¶|³fÍvúôiµû¨Ã–-[˜±±1ÀŒŒŒøw¼~ýz9ÙÝ»w+½žãÆS8~PP‰D,##ƒ5lØPÐçâÅ‹rò™:(Õýûï¿g+Vd±±±ÌÎÎŽiii1‘Hİàà`…}fÍšÅÄb1366fb±˜Ë`­[·Vóª©Opp°ìµªÀ¾€û*T¨P¡B… •â)Ì¥ï)ûú)T®\™onn^ìãÅÿÿþËÌÍÍåž5‹ƒ?þøCð\Xìç såʀ͞=[PÿøñcÖ¶m[V¹reöèÑ£b?¯­­­ôw~ˆ}⽊bø}AT«V Û·o/Tnçθsçîܹ£Ò¶oߎ¥K—¢wïÞxñâ^¾|‰ñãÇcìØ± å_½z…–-[B__Àëׯ±uëVÜ»w}úôQØ'77M›6Å AƒðüùsÄÅÅ¡Q£FX¼x±\Ì·–-[âÎ;øùçŸlI–Ìeîܹ Çß³g—)l¯ëÖ­qçÎ,[¶ púôiÞWÖâoaa6mÚ`÷îÝrAdCBB ¡¡(=—••6nܨRŸO¡I“&¨U«jÔ¨QlcÞ¾}C† §§'žžŽÌÌLÄÆÆ*Lß±cGܹs .ðìÝyXMkûðï.Õn4JТɔ¡Ìd&㉣ƒ™2Æ1$ §â  ÉœÌs™NˆÄ1‹(*J!J*4hnýþè·×kÛ{׎Âý¹®u]Ö³ïõ¬{­öñ¾Ýž¸víû>-Z$ò> ÃÀÑÑêêêø÷ßqíÚ5øùù B}êÔ)¶ÏfÍšUù 999èÑ£ÜÜÜ™™‰ØØX´hÑ^^^ÈÉÉá‹ ÇòåË1þ|¤¥¥!55íÛ·‡––Þ¿*ïG!„BÈ÷v÷î]$''#99ù‡]ügÖ¯_?4nÜmÛ¶EãÆ±zõj‰ßcРAHKKcooo‰ßƒ×¶m[Ì™3Ë—/‡žžŒÎ;£I“&øøñ#.^¼CCÃÚN³J4¥·¼5ûx²²²TÌñ–’ú_­TRE)))±ùü¼¤  PéðÞÕ«W£Q£Fسg;%×ÕÕyyyB‡Pûøø ;;¡¡¡ì[ggg$''ÃËË W®\Ø%—Ãá 44”R­®®Ž‘#GâîÝ»HLL䛢¬¤¤333B§ òÈÈȈ5,ùk­Y³kÖ¬‘hŸ111` .„±±1€Šµ6,,,„Æ+++ÃÌÌ zzz€¦M›V9åüsçÎc§ ‹zWFFFìŸåääªìSJJ çÏŸg§¦[XX`ذaX±b»¨0Oxx8dee1þ|ÈÉÉAGGãLJ««+²²²Øw@!„BH]b``PÛ)J,]ºwîÜAóæÍѧOŸ¹‡œœtuuk¤oRµuëÖaêÔ©¸té222 §§‡uëÖ ÄRWQÁ¯²²²|ÿ1÷¡££S­5jËLJùóç ¬¿'jîý•+W ­­¼¼<„……±í¼"Ͻ{÷ ~ÒÒÒë'ðŠ1µµ °¸† 555ìß¿Ÿ-øÝºu III쨶Ÿ‰ äåå1qâD¸»»cøðáPVV®‘{×ÈZ—JJJëPòοá§­­’’äææ²k-ò ÷â !„B!äK;wFçÎk; RÚ5k&Ö,´ºŠ ~•hÚ´)ÙóÈÈHœ:u 7n¬tšºâùóçª÷¯CÏž=CNN† &𙜜^½z%V??J1…ËåÂÁÁû÷ïG@@äååqàÀ¨««cèСµžÄ!44³gφ““&Mš„þýûcæÌ™|#¿‡ÃŠŠŠÄú«Š¬¬¬Ðv;;;,^¼nnnðööFrr2¶nÝ [[ÛJGoB!„B!?2ZÃï'¦¤¤ bÇ]q)((`РA(,,zøûû×TºµÆÙÙ¹¹¹8}ú4ÊÊÊpøðaŒ=ú‡)ZVWŸ>}ðäÉ<|øóæÍõk×ЧOŸŸrD£©©)BCC 333 0Í›7Çj;5B!„B!¤ÆPÁï'ÆÁôìÙ3Ͼܤ‚ÇÌÌ ÷ïֶ߯ß?:˜™™aß¾}GFF&L˜PÛiÕ¸–-[bÕªUxñâºuë†U«V¡¤¤¤¶Ó’¨ììløùù¡K—.xðàÞ¿sçÎÑZ„B!„B~jT𫆠`Ê”)ëáÕUòòòèÕ«Ž?Žüü|¶½¬¬ B¯qppÀ«W¯°iÓ¦ÍMUUðôéÓ:Ñ¿³³3°aôlÙRä‡ßÃ0kÒÕeee˜››£¨¨HähP555@BBBç#Ië֭õk×pìØ1´nÝjjj`æ—*hB!„BùõPÁ¯xkúý(?˜?>²²²0dÈܹs×®]ƒîß¿/4ÞÅÅݺuÜ9s°páB}úàÒ¥K;vì7ß¿®JIIA@@V¬XÁ¶ÉÉÉÁÙÙ~~~"¯SQQÁÂ… ±yóf´mÛ`ii {{{èééÕxÞ_ËÙÙ.\ÀíÛ·Ñ¿p¹\ÈËË#-- ‡Fyy9Ξ=[ÛiB!„B!Åa¦¶s¨5' @? bqÿøøx‰öïîî___Ô…w\TT„¸¸8˜ššB^^^¬kJKK‘˜˜ˆ?BKK Mš4‘x^%%%xôètuu¡¯¯_«ý·iÓ¦¦¦8xð Äó€3f`Ïž=ÈÍÍ+žaäææJ|·Û²²2$&&";;ÊÊÊ0229²ïKÅÅň‰‰žž4h ѼjÂüùóñï¿ÿâÆïÑÅÅ'OžDvv¶DïéããƒE‹}Þ¤Î0̉ބB!„Ô‡£ €êâíí ww÷Z̈ò3033û|­P†a†}K4Âï;6¬âgäéé‰Ö­[×JrrrÕ¾w½zõ`ffVCU‘‘aGŒÕfÿ!!!xðàA¬]8|øp ..®Z×q8‰û@ZZ¦¦¦_u­¬¬lþ¼$íÈ‘#èß¿¿À{ÌÎÎÆõë×ѵk×ZÊŒB!„B©9Tð«AmÛ¶…““{þ#­ý÷+ˆ‹‹ÃÕ«WñòåK`̘1èÔ©“ÄïchhˆÂÂBËåJ¼"Zß¾}±sçNhkk£W¯^••Ž{÷°víZÈÈÈ 00°¶S$„B!D@QQ;SŒÃá@NN®Òøââbp8ÈÈȈ}'Ož iÓ¦Õº¨˜EU^^^eNµ­Á=À« IDAT¨¨ÒÒÒuò÷pq~^ååå(..fÏëÕ«W§žåñãÇ033ƒ´´tm§‚¢¢"¼ÿZZZbåSZZŠèèh())ÁÄĤN½WI¢M;jÐÈ‘#ÄÍ›7¯í”ÈgžèׯŸØý߸q–––_5y„ 066®öuß›®®.f̘QÛiÕ¥Kv& ('Ožd¿òòòpssûNÙUíüùóhÞ¼9¼¼¼$Þ÷“'OÄ^n­¨¨þþþ044„žž’’’ª¼fÁ‚PSSCûöíannEEEøûûkÚuÒÏYÆ$D ööö°··¯í4H ’‘‘‡‡<<ˆˆÌ;·ÒøèèhøûûÃÃëV­bÛ{öìYÓ©Öšºõ­!„B!„B~2Ÿ>}BAAAõ߸qã*c>~üˆÝ»wóµõîݻҥ§âããqìØ1¼~ýmÛ¶…ƒƒƒÈØ­[·ÂÄĽzõBdd$ÂÃÃñþý{ØÙÙaÀ€ñ077%vñâE$''còäÉlÛ‘#G //Ž;bË–-ÐÖÖÆ¤I“ðîÝ;¢}ûö8p Ð¼òóóqüøqܾ}ZZZ>|8Zµj%4–aœÍWðóó󃲲2¼¼¼çÏŸƒÃá`Ë–-øøñ#<==qêÔ) <˜¯¯×¯_£K—.xûö-š7o޽{÷Â××ÇŽƒ_l^^ÆŒƒS§NAOOØ´i‚‚‚ EEEgþûï¿Ñ¶m[<}ú³gφ²²2òòòЮ];vtž0ÆÆÆPUUùù·ˆŽŽÆ”)S ¡¡ÌÌL‘EÇêÊÈÈ`¿?¥¥¥ÈÊÊ‚ŽŽŽÈ‚ßüùó1fÌlݺ§OŸF»vípãÆ lÚ´ !!!2d_üáÇììlp8ö^@ÅfŒ_üªëĉèÛ·o½÷ºˆ6í „B!„B~`³gÏ®rJ£¡¡!ÒÓÓ‘žžŽÛ·oW[VV())!::áááxùò%~ÿýw„‡‡‹¼nÿþý(..Æ»wï/^@NN›6múªçúÜ… 0uêT<~üòòò˜:u*æÍ›‡˜˜Ô«WwïÞ¸æÌ™3øã?ðúõk\¸p111¨_¿>\]]QZZÊ»jÕ*œ:u ÄË—/ƒ}ûöáÒ¥KX³fȼîÞ½‹U«VáüùóøðáÞ½{‡ƒVú,îîî˜6mÚ×½ˆ*4oÞ®®®ðòò’X±,,,ØïÏå˗źfçÎPTTd¿‰‰‰’’ÂæÍ›b<ˆôôttêÔ ={ödž ‹oÊýÇÈË˃¡¡!222päÈ 22’Ý ûgD#ü!„B!„ š>}:öïßÏž€a¨©©±m£GF@@€Dî'+++‘~x˜˜ˆ€€˜˜˜°í .Ddd$=z$ôº¡C‡bï޽칶¶6Ú¶m‹/^|sNVVVpqqP±¾¡††ÆŒÐ××GLLŒÀ5ööö|S8 ±`ÁÌž=aaa4h€ŠéÎ~~~°µµå‰8f̬[·þþþðôôš×›7opãÆ všpýúõQ¿~ýJŸEÒ?¯ÏÕ«WObß«o5zôhlß¾=700@óæÍñòåËïšGjj*àúõë011Ann.¤¥¥QVV†N:áðáÃboüñ#¡‚!„B!„"ANNNèÚµ+{ˆââbÌš5‹mgݽÚÂ+ž [{¯²b––––@›©©)ž>}úÍ9)++³ÖÐÐ8ÿôé“À5¦öíÛðìÙ3¶->>………’’‚»»;_¼¬¬,>|ø€7oÞ Aƒý G…K—. |ç’’’à§\à ~5(,, îîîìñùÂDEEÁÝÝ>|ûÞÞÞððð¨vn‰‰‰pwwGbbbµ¯ýžÎœ9ƒ¿þú«¶ÓêÖ­[pwwG~~~¥qË–-c¿Ë–-ûNى篿þŠ+j; ÀåË—1mÚ4„††V[RR‚Ý»wÃÙÙ Àܹsù&„B!¤.quuÅìÙ³k; ± :ÊÊÊðóóCYYÛŠS§NIä:::;ë>|‡’HÿpèÐ!¾|óóó±fÍèëë£W¯^l»––œœœ°{÷n±w ­Ë®^½Zç×ÅÀÀQQQ/úÍš5 ÅÅŘ?>ÛV\\Œ‹/BSS–––½_]@¿tåÊøúú",, aaaìÐaQ=z___¡ÿ !ÊŽ;Xíyñâ|}}‘’’R­ë¾·ª¶@¯M÷ï߇¯¯¯ÐÅa?wíÚ5\¹rAAAX·nÝwÊN<Û·oÇÖ­[%Þïŋѿ±Š×7oÞDÏž=Ñ«W/lÙ²wîÜ©4¾¤¤£F‚‹‹ ¢££!++‹£GÂßß_RéB!„"QÖÖÖèØ±cm§!6,\¸ááá°³³Ã–-[0qâDLž<­[·–È=ú÷ïçÏŸ£wïÞð÷÷ÇСCááá+++‰ôí۷ǤI“0fÌlذ=zôÀãDZbÅ ÈÈÈðÅ®Y³&&&èÛ·/\]]ŒþùÎÎÎðòò’XN5íñãÇèÞ½;ºté"ÑÑ’ßË!C••…ß~û ›7o†‹‹ þøã¸+W®ÀÁÁÌ™3B‹ëX¿~=‚ƒƒ1pà@,]ºÆÆÆHHHÀòå˺©ë­áW-¯_¿ÆÑ£G1sæLHKK‹}Ý—‹~V¥:;Ã\½zEEE?Ýn2?’ªÞ=ï_ˆf̘={ö|”ÄVUqík¥¦¦âüùó(..®2vÊ”)(((€¿¿¿Xÿê9oÞ<„††âСCøý÷ßÙöª ¯„B!„ \.Ý»wºë,ÏâÅ‹!##ƒ   ,_¾Ý»wÇ•+WpòäI¡S};wî SSSvSSStîÜY ý¯¿þBFFΜ9ƒ/^ OŸ> ÂöíÛqóæM¾X+++¾••Ùó¶mÛB]]ïš.]ºÀÖÖ=zô€——–/_###aܸqùhii!::žžž¸xñ"‚ƒƒ!++ ###¾ X>gmm-ô™k“‘‘lmmѪU««(**¢{÷îÐÑÑÓ­[7˜˜˜´[XXTZˆ´³³ƒ——<ˆ«W¯ÂÌÌLhÁ¯  ééé*ÖèëÞ½;òóó‘ŸŸ/rc777hii!((6l@ãÆ±bÅ 8;;WñÄ?(†a~Ù@cjjÊTåÊ•+ ¦   ÊX†a˜… 2¯X<çÎc0Ÿ>}ûšêHKKcJKK†a˜ððpsñâÅJ¯)//g^¿~Í”••I<Ÿ?òÝ'77W ÆÍÍ‘““cÏ ˜wïÞ‰ÕYY“ššÊ”——W+¯ÒÒR±îqüøq¦^½zì;­ÊôéÓ%%¥jåRïý[ˆû}صk€yûöm•}^¼x‘)--e²²²Œ‡‡‡Èج¬,F^^ž™2eJµsÿ^¼½½Þß+ÿ¨1uàï;:è ƒ:è ƒ:$sÐýüÿïy{{3’¦££Ã8;;K¼_òcRRRb¦OŸ^Ûifjjúùï‘!Ì7þ]ESzëcccèèè@^^¾Ò¸BMM=ú÷ï_iü©S§ §§===¨¨¨`ܸq(((»dÉhii¡¨¨nnnÐÖÖ†¾¾>444pâÄ øÅ‹³[\îÆPSSã[ô4((jjjˆŽŽ†……´´´°eË<{ö ¦¦¦hÔ¨‘ÈõRRR`kk UUUhiiÁÌÌLè¿* ³Ž;ÊÊÊhذ!TTT°hÑ"¾µ'>·nÝ:¨©©¡¸¸îîîÐÕÕ…––Œqûöm¡×@“&MаaÃjö¬Ž)S¦@GGqqqí·[·n|ß{{{‘±sçÎ…‘‘>}ú„iÓ¦ASSúúúÐÖÖÆ¹sçâ?555LŸ>дiSö>‹/zÞ½{‹ýwíÚ…‚‚Lš4I¬xB!„B~T€ššÌÌÌj;R Nž< .— .—+±Rȯ…¦ôÖ!FFF˜7o^•qS¦LÁ!Ck×®Enn®ÈØ«W¯â·ß~ƒ¹¹9Áår±{÷nLœ8Qh|aa!²³³Ñ©S'b×®]x÷î–-[†‰'¢oß¾PRRâ‹¶N[YY>~üˆ’’¶­¸¸?~„““ÜÜÜàçç‡%K–`÷îÝX¸p!|||°nÝ:888ðõUZZŠN:ÁÉÉ ^^^HNNÆœ9sУG$&&²[Æë»uéÒ©©©˜4i „àà`¬Y³999ؼy³@®EEEøøñ#|}}±gÏŒ?ªªª¸sçZµj%òÝ6mÚnnn"?ÿV‰‰‰ÈÊʪríÇêš5kÛçßÿ]é¦#Ÿ>}ÂÛ·oaccKKKìÝ»¯^½‚§§'&L˜€””p¹\6¾U«VðññÁÍ›7Œ%K–@YYвeËoÎýÑ£G¨W¯Ú´ióÍ}B!„RWùûû³ƒ4~ƵÅHÕÚ·o   öüó]… ü*Á[³‡·]ó¦M›P¯Þÿ^¤Š>²²²bü†ÊþùÀ"G®ÀŠ+ //Ë—/³…±¾}ûÂÙÙ™]ØòKååå=z4þüóO¶-%%+V¬À“'OD®_ ®Ù³gc„ HNN†6n܈ѣG#66[·nEYY߈¯²²2øúúÂÑÑеkWhiiaàÀؼy3<==ÙØ-[¶ &&ÇŽcG®õîÝ222غu+fÍš%r…½{÷âÖ­[044ë9kt§­sçÎ!==]ì|Ä5bÄöÏâlØQ\\ŒiÓ¦ÁÕÕ•m{úô)Ö­[‡¤¤$¾ÝŒLLL`bb999ÃÉÉ ÚÚÚË=55ººº8qâÖ¯_øøxèëëcذaððð€œœœÄîE!„BHm5jTm§@jYÆ ÃR4¥·oÞ¼A`` {ð¶óÞ¾};_{]UXXˆK—.aôèÑ|£à8ŽÐEJyêÕ«ÇWìÈh3[[[}y^PP€wïÞñÅËÉɱÅ>žÀÐÐP`Ëô#GŽ@OOýúõCaa!{888 ¬¬ ‘‘‘"ó:~ü¸Ä‹kßBNN®N䣢¢ÂWìþ÷}ÈÊÊú®¹¼{÷©©©pttDãÆ1cÆ ÈÊÊÂËË ½{÷®´øM!„B!„ü*h„_%Ú·oøøxö<22=zôÀÇù¦1ÖUÏŸ?Ã0ú«ªª ¼Ej%âËmÐyçâÞÃÔÔ |mÏž=CFF;ôKOŸ>­´?"’ý>ˆCKK ŠŠŠxòä »v¤§§'<<<àíí]étuB!„B!äWA¿Ÿo´Ó—…µŸ…ŒŒŒÀ6ã………èÓ§¼¼¼„^SÙ¶á¤î300@dd$ôôôøÚGooo\½z• ~„B!„B~yTðû‰¨X÷¬&ÕÖ4ʤ¤$4iÒ„¯ÍÔÔ™™™èСC­äDjV»víŒÛ·o£K—.l»¾¾>€Š X!„B!„_­áW 055…”ÔñÚx[¸‡„„|våʉÜCII Ÿ>}ÂóçÏùÚïß¿/‘þŠM#ÂÂÂøÚîÝ»‡øøxtíÚ•¯ÝÖÖ<XÛ|_²²²€·oßJ´_'''(++Ãßߟ¯÷}¶°°èý!„B!„ÑQ¹ª#xkúñŠ?‚Ù³gãÙ³gpwwGii)ŠŠŠ°råJüóÏ?é¿iÓ¦€mÛ¶¡  ™™™X°`<<<$Ò?ϸqãpæÌ”••áÕ«Wprr‚œœœÀfK–,¾¾>þøã\¸pmÿðánÞ¼)Ñœjš¯¯/:uêTã#4kB·nÝAAA(--E~~>"##‘——'øøx<{ö @ÅF ¼¶‚‚¾XÌ;ÇŽƒ··7 ñßÿÁ××\.&L¨ù‡#„B!„Bê8*øýä&Mš„1cÆÀ××PWWǾ}û ‘þGŽ øúúBWW:::xôèöïß/‘þŠÑb;vìÀˆ# ¥¥CCC$%%áðáÃkò)++ãâÅ‹000@¿~ýP¿~}@]]:uBFF†ÄòªiÇŽÃÍ›7SÛ©T›zôèuëÖAGGªªªèÕ«bccùâÊËËannsssvöÖ­[Ù¶{÷î ôíé鉹sçÂÃꪪèÚµ+bbb°mÛ6v;!„B!„ò+£5ü~p6l¨t§TiiicÒ¤IxðàZµj…Ž;¢°°7F«V­øâ]]]1dÈ~Z´hˆˆ´lÙ’¯]NNwîÜÁÉ“'‘Î;£M›6ÈÉɈÀÞÞ-[¶dÏ`ÆŒ>|8ºuë†/^ <<………èÙ³'7n,ô™ÍÍÍqëÖ-ܼy±±±ÈÉɶ¶6lmm¡­­-?fÌtìØ±Îmn‚Ç£OŸ>5v;vTúÜsçÎ…£££@»µµ5"""кuk‘×FDDàîÝ»¸zõ*ôôôЫW/­””"""Döñå÷gíÚµ˜6m""" %%…îÝ»ÃØØXd?„B!„Bȯ„SY±ègÇápÂô*6{ˆ—hÿîîîðõõÅúõëLJ¡¡¡DïAê¾uëÖ¡¸¸§NBLL rssk;%Rƒ|||°hѢϛÔ†ùP[ùB!„Éâp8ºÒxçÞÞÞpww¯ÅŒ!?333$$$ðNC†ö-ýÑ¿¤®®CCCøùùÚ´iC¿_Ðþýû‘ŸŸhÖ¬Y-gC!„B!„|=†aÀápj,žH­áWƒ.\ˆ””öàmd@~-QQQì&QQQµ!„B!¤Ž³³³C‡СC 4¨ÊxWWW̘1ã;dV÷,]ºÇ¯4fâĉ˜3gN¥1‘‘‘ì;ïСV¯^-É4¿ILL ´µµ±{÷îZËáôéÓ6lttt   €-Z ((HäckÖ¬AëÖ­¡¨¨.— KKK¢¼¼ü;gþë¢~„B!„BH…úõë£OŸ>PQQ©2þÉ“'––þ™Õ=‰‰‰¸ÿ~¥1?†ªªj¥1ZZZèÑ£ÀÏÏVVV’Jñ›•––¢¨¨Ÿ>}’xß €´´4Μ9Si\bb" 1sæL",, ãÇG||<|||⣣£Ñ AŒ9YYY8zô(\]]‘’’"4žHü!„B!„´qãFãÏ?ÿûkkkvy¨ªHKKÿ²?qˆó~,,,ØBT``à÷HKlVVVxÿþ=¤¤$?I³¸¸X¬ïΜ9søFI®\¹-[¶„¯¯/æÍ›MMM¾øýû÷ó¯Y³†††X³fÐx"yTð#„B!„BjÐýû÷QPPPcýˆ½FZjj*¢££‘••…F¡Y³fÐ×׈‹…ªª* ðêÕ+ܾ}ššš°±±¼¼¼Èþ322pïÞ=|úô íÚµƒ‘‘Q¥ùðâ Ю]»J×½/,,ÄÝ»w‘––†¶mÛÂØØX¬g600€²²²X±_#!!ÐÒÒ’XŸ………‹B]]]h|LL 444 ¯¯üü|ܸq999èÚµ+´µµâŸ?ŽœœäææBZZÑÑÑìg¦¦¦•þŒyF…˜˜¤¤¤TYÀ“––†³³3V­Z…ØØXv4%©9Tð#„B!„B~`ÆÆÆU®VRR‚‰'bÿþý(++ƒ¦¦&233Ááp‘‘!P°±µµ…äåå.—‹ÂÂB4kÖ .\Z˜ó÷÷ÇüùóQRR‚zõ꡼¼‹/ÆòåË…$ýüü°`Á¾ø%K–ÀÓÓS þõë×èß¿?bccÙ\¦NŠÒÒR±Þ‚‚B•q_#)) æææhР^½z%±Í)’’’¦ïÝ»cÇŽß±cGŒ?­[·ÆÌ™3QVV†ââbhhhàòåËhÕª_üÌ™3qîÜ9öüó{EEE¡M›6Uæxýúu¡mÛ¶b=o½¿ ˆO¾ mÚA!„B!„üÀÆ''§Jc|}}Œµk×"33ïÞ½CNNîß¿/rtÖ¾}ûššŠ¸¸8äçç#$$¯^½‚£££@ìž={àæægggÄÄÄàåË—˜2e V¬X={öÄaΜ9?~lذ.\@^^Ž=Šììl¬\¹R vëÖ­ˆ‹‹ƒ :uêÄÞ+..–––"ïQ^^Ž'OžÀÑÑ×®]ƒØÏ}ëÖ-hjj¢Y³fbÅ“oC#ü!„B!„ Ú¸q#.]ºÄž?xðeee6lÛÖ³gOÌš5K"÷«jÚ,<|øzzz˜={6Û¦¬¬ŒÖ­[‹¼¦mÛ¶8vìäääC‡…››¼½½qÿþ}vXII –,Ylݺ•-mÞ¼çÎòeËàììÌö[\\Œ%K– iÓ¦ ÿyóæÍ›øï¿ÿ°dÉŒ5 `bb‚#GŽ C‡xÿþ}¥ÏÞ¤I“*ßÏ×RQQÁãÇ%Þ¯¬¬,ÌÌÌeeeb]cii‰óçÏCQQ0bÄ#..N ¶aÆEEEHKK³÷ªŠºº:rrr   €[·n¡E‹b]—ššŠ›7oÂÅÅE¬xòíh„!„B!„"AªªªÐÕÕeyyyp¹\¾¶ªv•´=z -- ööö¸zõj•S€ E‹l±ç÷ßܽ{—mKLLDjj*¬­­qþüy„……!,, çÏŸG›6mðòåKdffòÅ¿~ýZh¼••^¼x¬¬,6þæÍ› 0•Ëå¢ú/ã'eeeÅû>oûøñ£Äîáççhkk£ÿþ ëºÙ³gCCC«V­’X.¤r4ÂB!„B‘ qãÆaܸqìùøñãQPPP«»¿NŸ>ÙÙÙX»v-Nœ8MMM8::bæÌ™hܸ±Øýðb“““Ù¶gÏžŽ;†ãÇ \#DðK IDAT''Ç·±/þèÑ£8vì˜ÈøúõëóÝËÀÀ@ìŒÂÂB¡G»víØxÞGŽÿùF¼þy÷&µOVV+V¬@qq1<(2.** ãÆÃš5k`gg÷3$Tð#„B!„B~²²²èׯŽ9‚˜˜býúõBcy»ª~.66ÿºx¦¦¦ø§ùV†·^œ¸ñ¼‘}¼‘Ÿgj2©ÊÊÊ€œœ¡Ÿ¿xñƒ ‚‹‹ þüóÏï™Mé­QOŸ>ÅóçÏÙóvíÚ‰Üý¨Øf<&&=zô—Ëý)ÖYYY¸{÷.¬­­¡¡¡!4æÕ«Wxüø1zöì YYY‘}]ºt %%%iiiôéÓ§FrþQQQ¸rå 8š7oŽŽ;²IÀ»wïÅžËËË£{÷êW{ýú5nÞ¼‰¸¸84hÐ}ûö9ôžaܹswïÞEff&6l[[[ç¬ !„B©9¶¶¶ìï(uI“&M ¥¥%²`sôèQüùçŸ|ëä@NN½{÷fÛ6lˆ.]º`ãÆpuue7„ÅÀÀ;wƆ 0uêÔ*ã )))ìÙ³ÖÖÖl{bb"þý÷_¨©©‰ó¸5&??\.ÒÒÒµšÇ×PUUÅýû÷¿êÚ]»v¨ø~éãǰ³³CçÎE”IÍ¢‚_ Úµk|}}Ùó°°0ôë×Odü… 0a¼xñ5ú)ÖQQQ0`._¾,ô/ 8wî¦NŠ´´4èêêŠìËÞÞž]””Ë墠  Fr®®ƒb̘1——‡††RSSË·åù­[·0dÈöÜÀÀ/_¾¬tùÄÄÄàìÙ³˜9s¦À"°Ÿ{øð!Z·n ‡eeeäää€ËåbõêÕ˜9s¦@|@@f̘‡%%%äææBVV«V­Â¼yójò‘!„Bùn>_ϯ¶ü÷ßˆŠŠBçÎÑ´iS·²²‚M?Û/a˜_ö€À˜šš2UÉËËcC‡ÁÇÇáááÈÎÎF×®]áãã#²x¶k×.=z‰‰‰hÖ¬¦M›VéŽ:;wîıcÇØøéÓ§càÀBc“’’°xñb}ÐÖÖ–HŸGÅþýûT õ–’’°aÃØÏOœ8ñÍ…½‚‚¬Zµ ×®]CZZ,,,àèè(0dž'-- ;vì@dd$RRR ªªŠž={ÂÓÓS`ºîÿý‡þù‡VìììÌT‡ ‚ &T™ï¬E­ã÷¥^½z233ÅŠ'„B!ägtùòe 6 êêêØ½{·DúTQQA‹-ª}””Tµ®SQQAûöík$^GGG`:±$ܸq^^^*Ö䫎ʖ=úQèêêVºt4hÐ@¬ßÝIÝðã !ú‰5iÒ¤Ê &ŠŠŠÐ·o_,[¶ úúú˜>}:ìííÁáp ###ÿøñc„„„ OŸ>8{ö,zõêkkklݺ={öºp¬‹‹ \\\ðæÍŒ5 ?—Ä¿egg³k^XXX`„ ÈÏÏLj#°páBø²²2´jÕ gΜA›6m0fÌàŸþZ¼“––—Ëe¿[rrrlî⌠,..Æ¢E‹ ''‡±cÇŠõL>ZÓB!„ü²F[[[¨©©±3HÍRPP€‘‘ŒŒŒàââ‚nݺÕvJ„|›oü#¨b ?Þš}¼cÇŽ æöíÛ|í¢ðÖ𓤈ˆ³uëV±âçÍ›ÇX3Áßß_è¼µôV®\ɶ•––2ýúõcTTT˜·oßòŇ……1˜U«VñÅ÷íÛ—QQQa222øâœœiiiæÎ;lÛÇ™V­ZU¹†_uÍž=»Zkø=yò„Ù³gÈõ(¾Uýúõ…®© ÊèÑ£«\Ãoîܹ ‡Ãa®\¹Â×>eʆÃá0QQQ×[‡ÏÖÖ–À¼{÷Nè}Ä]Ã',,Œ™1cÓ A¦]»vLtt´X×1 Ãxzz2˜'Ožˆ}M]BkøÑAtÐAtüܾÃ~uo ?BÈ÷!é5üh„_%îÝ»+++ö˜8q"€Š‘GŸ·O¼é”©©©(++ëiiilݺ•¯mÊ”)¨_¿><È×¾aÃÈËËcÚ´i|×O™29998wîœ@¼‚‚‚Xñ¥¥¥8xð †Î7\[EEÞÞÞb=KM277Ǹqㄎ”¬‹JKK€!C† {÷î|Ÿ­\¹rrrذaƒÀu†¿óÖÝKJJ’Hn‘‘‘عs'Þ¼yƒ @SSS¬ë²²²°qãFŒ;æææÉ…B!„R}©©© ¬í4!_‰Öð«D§N––Æžß¼y“ÝZšWxûÞlll0lØ0¬X±ÁÁÁøý÷ß1räÈ*×;ør8999´lÙR Àiii 0€¯7õ7>>^ ^JJJ`í@añ)))(..š«¨Å>‰h)))(,,º±…¦¦&ÌÍÍ~^Ÿ{óæ âã㑜œŒ˜˜ëJÂßÿU«VáÎ;˜6mÌÍÍqåÊ´iÓ¦ÒëæÏŸ‡ƒõë×K$B!„BÈש­ßy !’A¿JÈÈÈð-ZÉÛ|@GG§Fwੌ””Nœ8¹¹¹Ø²e ¼½½Ñ·o_<þ\ì~Þ¼yƒFñµ5iÒŠŠŠpww«êÄëëëÞ¾}+vŽD4ÞÏîóQ¨Ÿ{õê ùÚ>|ˆÁƒ£S§Nxøð!Z¶l 8þ¼À(MIÑÖÖFË–-¦ƒ.22...ضmúöí[#yB!„B!¿ ZÃ溺¬Œ ÀÍÍ ÉÉÉHMMˆ)//dzgÏøÚÞ¾}‹¨¨(­ÕÛ´iƒ»wï"!!A¬ûóâŸ>}Ze¬®®.444!ðYJJŠX÷#ÿc``555œ={Vೄ„ÄÅÅ ü|CBBPTT„€€¶Ø@àûñ%‡ úÛÓÓ»Ÿ?333¡ŸÇÇÇcøðáððð€³³sµû'„B!„B?*øUoM¿ÚšÎËóå:k¥¥¥8yò$”””ø¦ ó0 ƒ¡C‡âÝ»w* €sçÎEqq1\]]ùb—-[8::âÍ›7|Ÿ•—— ô½lÙ2Ô«WŽŽŽ#;Œ—’’ÂäÉ“‰3gΰí—/_ÆÂ… ÅxòšäååÕv*báp8pwwÇ7°cǶ½°°3fÌ€ŒŒ æÍ›Çw oŽÊÊʰ}ûvÌ;€è‚ojöåË—Ù¶ââbcRSSÙ©ÛðúõkLž<˜>}º@¿o߾ŀ`oo¥K—Šýì„B!„B ~Õðåš~µ!-- ªªª°¶¶Æ¬Y³ðûï¿ÃÐÐ111زe êÕœ¥---áÇ£aÆ2d,--qàÀÌœ9ÖÖÖ|±†††ÆÓ§Oaii‰ß~û ŽŽŽ°²²‚¢¢"_1ŒŒŒ°gÏ$$$ÿðá_üŸþ ØÛÛcàÀèÙ³'ìíí1iÒ$É¿¬jZ°`¼½½qáÂ…ÚN…•žžŽÖ­[£uëÖø|öìÙ°³³ÃäÉ“akk ggg4oÞ×®]C`` ŒŒŒøâÇŽ uuuŒ3íÛ·‡®®.-Z„C‡¨(Ð ÓªU+4jÔ®®®2d:tèܾ}›/nÉ’%¨_¿>š7oŽ®]»¢Q£FØ·oþþûo >\ ß?ÿü))) ®®.ßñåz‘„B!„B­á÷ƒQTTĺuëpãÆ $$$@II ŽŽŽpqqAÓ¦ME^·jÕ*ØØØàÌ™3PQQ‡‡…ÆÚÛÛÃÚÚAAAxüø1233Ñ¢E Ìš5 òòòñ#FŒ€ _|Ë–-1{ölÑšššˆˆˆÀæÍ›öíÛcÓ¦M““ƒ‚‚7nüm/è¬^½—/_®±5ä,X 0Ŷ2ööö|?Ssss.—‹Ó§O#88ׯ_Gzz:FŒ¡³† âéÓ§Ø»w/®_¿Ž#Fà÷ßG£Fàéé)ô<ׯ_Ç¡C‡‰–-[bÞ¼y»îþõ×_000ÀãÇQZZйsçbܸq"ŸÛÎÎNäF3***"s!„B!„Bˆh†aj;‡ZÃápÂôSSSÄÇÇK´wwwøúú²Ó'¡¬¬,Ñ{TeþüùX¿~=JKK¿ë}kÓ½{÷PZZŠõë×ãÔ©SS  ©I>>>X´hÑçMê Ã|O!„B~,G»ž‘···Ø›Bˆ(fffŸï§Ê0̰oéFø}VVV€°°0ôëׯ–³ùùõîÝ?~€Z_o‘B!„B!ä{£‚_ Z¸p!¦NÊž×öú¿Š§OŸ²†ðv—%„B!„òs(**ÂÎ;aggCCÃZÍ¥´´÷ïßǵk× //KKK´lÙêêê•^—””„gÏž¡~ýúhß¾ýwÊ–üJ¨àWƒÔÕÕ«ü¼¦ÙÚÚBVV¶VsøÞ´µµk;B!„B!5$22Ó§OGBBüýý%Ú÷‘#GPVV†?þø£ÊØOŸ>ÁÎÎW®\¢¢"JJJP\\Œ¥K—bùòå"¯+))ÁÀñôéSôìÙ—.]’ä#€ ~?½bàÀµ!„B!„ü²^½z…òòòZö³èÚµ+þùç ••…þùçxp¹\HIIAJJн—˺ddd$zôè999±Ÿáþýûؽ{7Ö®]ûË­µO¾?šÒK!„B!„HЫW¯••Åžggg£¨¨ÑÑÑl›††5jÄwÝõë×±qãFL™2°lÙ2hiiáĉ"GÌ]ºt 7ÆóçÏÙ ¿ÝWRRWWWàúõëìæ’sçÎEÇŽáêêŠØØXÔ«WQ"011Áùóç+}>ccc¡…0SSS;v zzz––ÙGuééé±S¢Ÿ>}ŠsçÎUyÍÕ«W±eËL˜0ð×_A]]'Nœ€‡‡_ì¼yóýû÷GQQQ¥Ó¯srr›› }}}„‡‡#$$oß¾………&Ož }}}¡×Íš5 ={öÄ!CÄzfB¾ð#„B!„B$héÒ¥°²²bS§Náüùó|mK—.¸î?þ`‹}<:t@zzºÈ{)**"<<œ-öÝwöìY$%%añâÅl±êׯE‹!!!¯Àgbb‚ÔÔTöÜÓÓ'Nd§§¦¦¢iÓ¦"s²··G§ND~þ½899±Å> â½ØØØTú>ÅñæÍÀÁƒ1`À\½zÉÉÉðòò‚………ЩÆÄ­[·àçç÷M÷&D\Tð#„B!„B$Èßßiiiì1jÔ( :”¯Íßß_àº/ u ¯¯ÒÒR‘÷êСC•»½ÆÅÅ:wî,ðY·nÝñññl›‰‰ ‘™™‰§OŸÂËË {÷îÅÑ£GTŒð«¬àWW|Íû‡’’ÀÀÀ±±±xôèîÝ»‡‡‚ËåbܸqøôéÿéÓ',X°S§N…¥¥å7Ý›qQÁB!„B‘ èê겇¼¼<¸\._›°õïj oD›°Â ®®._ PQð* {›7oFÛ¶m1yòdlÞ¼¥¥¥HKKû! ~5EOOõêÕƒ¹¹9LMMÙö–-[bèСxýú5îݻǶûøøàýû÷˜6mÒÓÓÙ£´´ÅÅÅHOOç+" Tð#„B!„B~b†††€´´4Ï^¾| |ë 6iÒRRRˆ‹‹CPP¦OŸŽiÓ¦áÖ­[8}ú4ÊËË邟´´4LLLðøñcϬ­­¯_¿fÛ:„üü|XZZBOO=’““ñßÿAOO;wîünù“_mÚAȆºø,!„B!„üˆZ´h b-?777¾ÏBBBTŒNã‘••…||| ##p¹\ôìÙ‹/€Ÿºà'Îh»‰'âÿÚ»÷°œïÿàÏ»tQ¨-ÑQ‡uºé„–È)Æ¢d62çóá;‡E¾3ŠÚev-™¯Óæ›/±œB,fæ4‰Q*I,5šHÑáþýáwßëvßÝÝQnåù¸®®Ëçýy½ßŸ×çs]³Ë«÷aþüùHNN†«««¤ý÷ß ý}Ö­[‡ÇËŒ1}út´k×Ë—/—úþD 3üH¥ˆ^½z©: aüøñ°´´„®®.„B!&MšTg¿ .ÀÓÓžžž(,,| ™QS… 6¨ìù}ûö…««+"##‘™™)iOMMÅ×_ ///™—YYYáêÕ«?~<´µµ3fÌ@zz:tuuñî»ïÖú¼+V`ûöíó2ÌÅÅ)))øûï¿%meee2q&L€¾¾>ÆG233ñÓO?ÁÊÊ ÎÎΒؾ}ûÂßß_æGOOíÛ·‡¿¿?,,,ÿåè­Â‚©Tyy¹Üßt¼ª¸¸8hkkK¦§+RTT„Þ½{cÿþýièС¨¬¬„­­-‚‚‚`mm ;;;™¸6mÚ )) ¹¹¹033C¯^½`oo{÷îaíÚµÐÐÐPAöDÿà’ÞzHKKChh(víÚÅÿxȉ'eÜêêj<}ú"‘¨ÎØ3f 77'Ož„‹‹‹ÒÏØ¶mRRR0fÌÄÄļJºDDDDDô–›?¾Üe²|ðÌÍÍåö™6mÚ¶m«ÔøÎÎθté6lØ€?þø‹/ÆÔ©Så#G©g«««cýúõxøða­Ï±³³Cxx8ÌÌÌ ®®®Tnõehhˆ¥K—B(Ö"÷D\…'åöèѧOŸÆŽ;pçÎ 00Pn¬»»;~ÿýwìÞ½çÏŸÇÌ™31nÜ8¥ÿ]9kÖ,•‚©y(Si®A"€`cc#u ¹<'Ož„ÊÊÊ$Sš_ô×_¡°°ÎÎΉD¸víþøãtéÒEj]MEEEÈË˃““ÔÔÔ––†k×®ÁÝÝ]²¹ê‹JJJpéÒ%ܹsvvvptt”)BÞ½{RS‰kª®®Æ•+W$§D½èñãÇÈÎήµÿËÊÊÊBii©äZOO¯ÖßåççãÞ½{prr‚H$Bjj*®^½ ;;;¹¹?|ø999HLLÄ¢E‹pøða¼óÎ;žŸHÕ±cG©øÛ·oÃÒÒ ,@DD„ÒïðøñcØØØ`Ô¨Q000@hh(þüóO˜šš*=5O‘‘‘X´hQͦ¶"‘èïÚ≈ˆˆ¨iÆ$§_DDD $$D…Qs`kk[sÉý~‘Häÿ*ãqIo‹ŠŠ‚‹‹ nÞ¼ 7778;;c̘1pssÃçŸ.·Ï¶mÛ  qïÞ=x{{ÃÁÁAAA033CRR’Lüž={ЩS'øøø`Ò¤IèÖ­„B!®]»&çââRëo_rss! ±wï^¹÷ýýýáââ‚Ç×ó+(6vìX…BÉÏ„ j]µjÜÝÝ‘••¡Pˆ®]»bôèÑèÚµ+¾øâ ™ø'N@(J .ƒ ’ýôÓz½ÃÊ•+QYY)7""""""""UbÁ¯‘tïÞŸ|ò qýúutïÞ«W¯–:šûES¦LH$ÂpæÌ¬^½}ûö•Šùí·ßOOOäåå¡´´çÏŸÇ£G[“Ù'•IDATлwoÉf¡À?§åææÊ}ž¸½¶Ó•<==affVëôñ—µcǤ§§#==]©©ÎèÙ³'&L˜€ÂÂBdffJ6œ-**’ŠíÛ·/ÒÓÓñõ×_Ž;&yÖ—_~)3vvv¶äHõ§OŸ"-- ÷ïßW˜ÏÍ›7ñÍ7ß <<­[·Vþʼnˆˆˆˆˆˆˆ^îá§€xÏ>1q!(((jjÿÔJÅǘ״gÏxyy 0räHœ={YYY011‘û¼G!)) :::ž _‚víÚaïÞ½ÐÕÕð|ß€ØØXxyyá›o¾ÁÒ¥K@²L677xòä add: ~áááWð…^NÍeÊâwUD àÀðôô´mÛHNNÆ7о}{I¬žžlmmqùòe€¥¥e­Ë¢àÎ;hݺ5,X€µk×¢¢¢Àóo¿iÓ&¹{;Ì›7vvv g&© gø) ©©)ÙßÎØØ###©vyÄÅ>1ñL6E››nÙ²Ea¬¼¼gΜÁG}$)ö‰õìÙ666R‡`tîÜ’Â^hh(ÌÍͱ|ùrÏ šššèÔ©S­Ï|´hÑBRìïß§è{*ãÑ£G(..Æ‘#GðÝwßá—_~AHH²²²Ð§OÜ»wO*þçŸF||<¢¢¢¤Š¾DDDDDDDDo ÎðSÀÚÚëׯ—\ŸdȨ©©!%%EÒ¶mÛ6¤¥¥ÁÜÜS§N•´_ºt °xñbØÛÛ¿xB+ÑkÅ‚_bnnŽ-ZÈœÆ ÕÕÕ¸zõ*ìíí¥Ú­¬¬––†äädŒ?íÛ·ÇØ±cñÝwß¡¢¢>>>¯)û7Sÿþý…„„Œ;VÒ^QQêêj´jÕJÒVUUgggäææJ„RPPHIIAUUÕëKžˆˆˆˆˆTÁ°æÅâÅ‹¥ö~'"zÕÕÕ5/…¯: ~õðî»ïbÊ”)hÑB5Ÿ­E‹4hâââwÞyGrïÇD^^/^,ÕÇÊÊ 6l@ee%öïߘ>}:¢££¡¡¡I“&)|fIII“<‰V¼<úúõë íðó󃵵5V¯^   ÉŠÇðÏ^0a¹u¬\¹¡¡¡HLL„©©iC¾½yÔk^ˆD"ˆD"UåBDÍ“fÝ!Š5½ÍÛTH¼§Ÿª ~ªª*øúúâøñã¸}û6bbb0qâD888`òäÉRñVVVxöìüüü`aa°µµ…¯¯/***.éýøãѾ}{œ9s¦Qß©1ôêÕ :::øê«¯pñâE$%%!,,Ljfðüào¿ý™™™>|8~þùgDFFbÞ¼y044”ZºKDDDDDDDÔp†_cooÓ§Oãã?†¯¯/€çE«¡C‡bË–-2ÅHqAoÆŒRí3fÌÀ±cÇší~ººº Áš5kàêê °³³Ã!Cdfá 4ûöíÃôéÓѯ_?Ï—OïÚµ«ÎƒTˆˆˆˆˆè­S^ó¢k×®pttTU.DÔLìÛ·%%%âË¢WOð6O=‰€ 222TœQýܽ{yyy°±±žž^£<£©.髨¨À•+W`ll “:ãsrrPUUKKK‚×!57‘‘‘/ÜÒV$ý­ª|ˆˆˆˆ¨a c‰¯#""¢ÂŒˆ¨9°µµEff¦ør¿H$ò•ñ8ï 366†±±q£>£)û@CCݺuS:ÞÜܼ³!"""""""j|ÜȈˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨aÁˆˆˆˆˆˆˆˆ¨yÛOéí þC^^† ¦Ê\ˆ¨ÈÊÊz±IKy5EK—.Åüøøø:c‹‹‹„~ø¦¦¦R÷ÒÒÒ°`ÁÄÅÅAOO¯±Ò%zc½í?Éß?Vê/"¢zÒVuDDDDDMÅ7’’Rg\QQ|}}acc™ûVVV¨¬¬„¯¯/’’’ ¯¯ßé½±¸¤—ˆˆˆˆˆˆ¨EGGcõêÕªN£Y™8q"ôôô°}ûv¨©É–6´´´°oß>”••aæÌ™*ÈHµÞö~DDDDDDD*%%eeeJÅæççãêÕ«¸{÷.LMMáååMMM™¸´´4´nݦ¦¦ÈÍÍÅùóçÑ®];¸»»CWWW&>55úúúJÇ‹ââÅ‹(--…««+ÌÌÌäÆÕÿÉ“'8{ö,Š‹‹áååccc¹}ÊËËqáÂäççÃÕÕ–––J}£­[·âСCHII‘ûmÄZ¶l‰7¢gÏž€¿¿¿Rã5o{Áï €>Àóé¾§NRq:DÔÔ­]»+V¬¨ÙT¢ª\ˆˆˆˆ¨éÈÌÌÄ¿þõ/9r¨¨¨@uu5ìííqôèQ¼ûî»Rñ}úôÁ€ЪU+¬[·ÚÚÚ(//‡µµ5’’’d s}úôŸŸZ¶l‰ÿüç?’ø.]º )) ;w–ÉiÍš5˜?>***ТE TUUaÉ’%X¶lT¬† oooL›6 xöìôõõqôèQ¸¹¹IÅçååÁÏÏW¯^•ä2eÊTVVÖù­V¬X€€899Õëéé‰~ýúaÅŠ,øÑ[åm/øUˆÿ ®®^ëoˆˆ”%gC`‘*ò """¢¦EOOvvvˆˆˆ€ƒƒîÞ½‹M›6aÙ²eøòË/±qãF™>±±±8p ÒÓÓÑ¥K$$$à£>ÂèÑ£ñÛo¿ÉÄoß¾½Öø'ÀlݺsæÌÁ¤I“0{öl"<<aaa077ǸqãdÆß½{7NŸ>„„ôìÙ?ýô† †åË—ãàÁƒR±£FÂõë×±sçN"''óæÍáC‡dŠ›5>}ׯ_DzeË”ý´2dfΜ‰ÔÔT888(ݨ)ˆDoï¿EA"€`ccƒŒŒ gDDM]dd$-ZT³©­H$ú[UùQÃÆþ_GDD $$D*&::ÇŽ“\_ºt UUUpuu•´õéÓ³gÏVø,‘H 55Uêž‘‘:wîŒS§NAKKKÒ¾dɬX±.\z^‡`ff&Š•+W"99ݺuTTTÀÂÂZZZÈÊÊ’Ìæ‰D°°°@UUþüóO©|ÚµkKKK=z­[·–´;::âÉ“'ÈÎΖ´;wÝ»wGhh(ÂÃÃ%íåååèÞ½;îß¿/3¾Xxx8þýïãæÍ›077WøýÄ.\¸wwwDEEÕù͉TÅÖÖ™™™âËý"‘蕦¤òÐ""""""¢¤¯¯cccÉŽŽ´µµ¥Ú”95V À××ùùùrï;88Hï ((Àó"׋•ŠÏÎÎÆ;wàáá#GŽ 11‰‰‰8rä„B!rssQTT$3¾“““T±\\\PR"½ËÍÙ³g£G–j×ÖÖ†½½½Üw;tè 0®&ñj¾ÚŠˆDÍÑÛ¾¤—ˆˆˆˆˆˆ¨A#88Xr=nÜ8”••aýúõ û•”” &&gΜAff&îܹƒ{÷î)UÏzËÉÉyéø¬¬,Ï—èîÙ³G¦––nݺ…öíÛ×9þ‹ÆšÏ255U*ÇšŠŠŠ ¦¦†–-[*ݧM›6€{÷îÕûyDM ~DDDDDDD*–žžoooTVVbìØ±0`,,,°nÝ:9rDéqž>} JÄäÅ‹ÿ¼sçN 6Lég+K<þÓ§OëU¸€wÞyÕÕÕxøð¡Ò…ÐââbI_¢· ~DDDDDDD*¶páB<~ü999RJÆÆÆÖÚGÞžüâ½þ,,,^:ÞÆÆÀóe¾QðÏìËÊÊ‚‡‡‡Ô½êêj…}Å3‹ŠŠ”.ø€ÌÉÅDÍ÷ð£Fqÿþ}$&&âÁƒJÅçççK¦¿è×_EUUUC¦GDDDDDôÚôîÝýúõS“™™ kkk©bßùóçñã?ÖZÛ½{7ÒÒÒ¤ÚÖ­[MMM¹ÏÛµk®]»&¯¥¥%obb‚÷ßÑÑÑÈÍÍ­óýêËÏÏjjjˆ‰‰‘jÏÎÎÆáÇöÀ† påÊÌ;»ví¸q㤠‡5ã,‰Ÿ3gvïÞqãÆÁÈÈH*výúõ¨ªª‚‡‡vìØÛ·oãâÅ‹ˆ‰‰ÁîÝ»_é{˜››cĈX¿~=V®\‰ôôtüïÿCÏž=ѪU+…}íííáîîŽ]»v)ý¼}ûö¡OŸ>/µg QSÅ%½õPZZЬ¬,8;;KŽ%§Wsÿþ}x{{cÈ!˜;w®Ü˜ÀÛÛÁÁÁعsçkΈˆˆˆˆ¨ñ}ýõ×xòä ¾ýö[¬Zµ ;vDLL Š‹‹qîÜ9äççK–³Šõë×ÞÞÞ˜?>îß¿@€Ñ£G#::Zî3ú÷ï÷ßóæÍদ†Ñ£GcÍš52±öööHIIÁøñãññÇKÚÕÔÔ0nÜ8¾ÒûþðÃxøð!BCC ###DGG#>>§NRØ7<< À‰'лwo…± 8þ|c57,øÕCrr2|||PVVmmmU§Ó,LŸ>mÛ¶­õH`hhˆ¸¸8…BÄÆÆJýφˆˆˆˆˆ¨9022ž={PXXˆ²²2tîÜYroìØ±µöûôÓOŒ´´4˜ššJN¤mˆx[[[œ9s%%%ÈÌÌ„††LMMahh([Û ¸›7oÆæÍ›eÚuuuñÓO?¡  ÅÅ۵µŒ1BaþÀóBçÔ©S1mÚ4œ;w®Öw(,,ĬY³°`ÁtïÞ½Îq‰š.émEEEøê«¯Ð¿X[[C(bΜ9xøð¡LìÖ­[1|øpTTT <<>>>pppÀ´iÓPRR"#‰ C¯^½Æ‹mÙ²~~~°¶¶Æ Aƒ 7î¿ÿý¯ä75ñññ D—.]0tèPdffÊí“Q£FÁÆÆÄ–-[”ùL8zô(âââ°fÍ´h¡¸öüÞ{ïaÖ¬Y˜5kÊÊÊ”Ÿˆˆˆˆˆ¨©éСƒT±Ojjjptt¬³Ø÷²ñ­[·†››\\\äû^…‘‘‘¤ØW«W¯†¥¥%ŒÒÒR™û<@ß¾}áææ†åË—7DªDM ~ÀÃÃ?þø#œœœ0fÌTWWcÍš5øè£dbÓÒÒþýûãàÁƒèÓ§<==±qãFôîÝrãûõë‡C‡Á××Wa<Lœ8ãÇG^^Fމ¬¬, :ëÖ­“‰MMME||ÃÓ§O1mÚ4™X‘HIÅ?{öL&þ‹/¾€††ÆŒƒüü|©{ÕÕÕʼv­&OžŒ_ý”´Ÿ8q .TØWSS#GŽÄÁƒQUU¥ÔóÎ;‡û÷ïKBLDDDDDDDDÒ¸¤·^ÜÓOž   „……aêԩظq#òóóñäÉÄÅÅaèСÈËË“é#SSS 07nÜ@zz:f̘OOO™x555 >;v”ŠŸ9s&<<<¤b;w[·b„ xï½÷àãã===¤¦¦"##yyy’½ _ÆgŸ}† 00¾¾¾(//GJJ &OžŒU«V)ì;yòdlÞ¼{÷îň#ê|Ö† `nn޾t¾DDDDDD éäÉ“ªNˆš4èx,ø5°víÚ!==Û·oÇñãÇ€€€ØØØ ,,¬Öå¨aaapwwGBBZµj…×úœððpxxxààÁƒhݺ5-ZTëÌ·€€¸»»#&&©©©¸wï1{ölèèèHÅöïß_î!:uÂÒ¥KÑ¥K™÷ýå—_°víZ\¾|nnnˆŽŽ†ŽŽtuuann^ë;tëÖ sçÎżyó0hÐ ´lÙ²ÖØÓ§OcÛ¶m8vìX'½.‰‰‰HLLTuDDR"‘HÕ9¨Œ@ H0lll‘‘ñÚsøüóϱjÕ*¥—×.\¸«W¯Vzì›®¼¼=zô€‰‰ öíÛ'9½·¦7nÀÛÛÁÁÁˆŒŒTA–DÊ‹ŒŒÄ¢E‹j6µ‰D«*""""jXÀÀ_ªÎƒˆšµý"‘ÈÿUà~¤RÚÚÚ8~ü8 image/svg+xml +name: string = "objecttree.h5" +root: Group = rootGroupObject +create_group(where:Group,name:string): Group +create_table(where:Group,name:string,description:IsDescription): Table +create_array(where:Group,name:string,object:array): Array +close() +_v_name: string = "/" +group1: Group = groupObject1 +group2: Group = groupObject2 +array1: Array = arrayObject1 +_v_name: string = "group1" +table1: Table = tableObject1 +array2: Array = arrayObject2 +_v_name: string = "group2" +table2: Table = tableObject2 +['identity']: string +['idnumber']: int16 +['speed']: int32 +nrow: int64 +append() +name: string = "table2" +row: Row = rowObject2 +read(): table +name: string = "array1" +read(): array +name: string = "table1" +row: Row = rowObject1 +read(): table +['identity']: string +['idnumber']: int16 +['speed']: float32 +nrow: int64 +append() +name: string = "array2" +read(): array fileObject(File) rootGroupObject(Group) arrayObject1(Array) groupObject2(Group) groupObject1(Group) tableObject1(Table) rowObject1(Row) arrayObject2(Array) tableObject2(Table) rowObject2(Row) PyTables-3.7.0/doc/source/usersguide/images/pytables-front-logo.pdf000066400000000000000000002261741416254111300253510ustar00rootroot00000000000000%PDF-1.5 %µí®û 3 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream xœ•X»Žd·ÍùŒ —,¾C`'ÎÀá@ºÖHA…•¾Ï)¾zºG6ŒÅà.OóÖóT±x¿o%E×{³É{b´/¡t×r¶¿ýdÿjÿi¾™ñK¨©[ã÷2–5¹¢ä`¯wóåoþ—ýòïlÿô«ùj¾Úo&Xïj•¬»ûÙŽ=¦xçc¶ÁGK±ï6ïbŠ“ìÍ ®ønC .·¤E—ƒØÀgÊLÄK05ÔæD:·?¨ÆìC/ahäžuO쪲¸ƒ ¹¸^"^z0æf4Ç àiÈC¬Wç§¼;ïÏf=[N•>Ý©üÄÎ*.Ô‚4×E#’\†ØXâ)6•!VRû]±K^r©Cž$ä7S ÜÛ£“ÿ/CK§¥CðÞw+°8#±;¡YÝ•Ò 4#ÎëϲäaTñµAv+R7•ýËw[AŸ/zqàm®®1-ChûéŽÁùØ\ |üÊÔgÖ;TÕø½Ô̘¥'eq¡¡vk Bë‹Aæ¬#3ÑJ ® =^\Cq‰G,<µÖmIµ¶Hó1OR>„gÆaŠC÷ 9жeú‡gË6‰ëp34äÜcÙTðæ /—ÍIƒhBGðZÉ0êÝUPÌSÇ{rÔ»lÞ@¤6J¦˜Ïê:R`×R  ©ÉÀ/æ( `¡D|³o# ¡€® iž¼.B±ûcT·›p2x¼A"º¥i<½CiÈÅèuçñƒ`cîšÄâ¢$¨•Ì$š˜O Ö%ÍAîÁ ø³žÿ™éà‘·€$¡ö  KÐ7á¾S¡ŠRѤö&¥+N)µh•?Jú/IH\¯Ã½•Æ €nYØÃÐ:B/à{HˆˆÐ„F¡ô ñj e7ØöºCâ±.ZžÌKª$­WF^J*ÒE$#ì"q&XБ lã P$kŒLÀ+àp`½õ®Á`‹) Cæz¢ìb°=6‹,|$yˆw£zçµúC€Ñh\b¾!ENKFù€p®Õ —㺖º )÷‘ò'ÑŸtàBQ5]]Ël#8"êHi‡B¾òè#1 hNQw†nÏûн¯ƒT6Ê¢2&ÒD[a<{—m k‰}d­¯'[oAÂGÏ3bRk­íŸ!½(…nGÙF )¡-~B4]ÍAðÁ­µä4€P\@€»z8X”qº%å˜:8]aËëÉ››67ðÔ€b³â×é‹×ó,tD`ÓåÁ+-kízOÎ1XAã[yÔ«û kà… Ï LMJ:²vèRu6añF°NýAñ)„̦º ÈûD(ó6PQhØêTÎ*$¢sÃ(-4ÐŽõ¤¥‰„€ièŒo³vÑ­¢¹¼VÈfÄnZ6N®qt'”°÷¹ÞÐf– é¤Ï€M"6AMçF̆Î[Kû3²µ ºSbîP¯<©@bɣ 1Œ\G?Ö0Sr¥PQLL2”¾"èo‚1…•o¸öˆ ÁCÃcù]ŠdßiaVɀ왨°Ÿµ ´H­é `z‹­«Œ…°¿•ÉÔö4žLj{å\_ê™¶½cc‹k³´\Oñºi×Ë8"‚pÒ9^H× ‰ÐN…ˆðÔ3qÿ¡?PlÊãÌŒœá` Ú7™Ùðá>Š’íÝ¬Š !~=±e!ÞDÓGl‹†#ðD€1%ÈŒzPo`OíA×x€9M=}ƒ1Mâ½·xFÌOHд_İæÑOšŠŒ%®åQºÏã.0ß6˜®³VĪ…½‰•þŒPºo£Š&„‰þ2d<êV3Ø–Í5áhŒùDŒpxÕ“eŠXë©å:È)Ä¡»BÜPÕÌÜF © mH8 ÀÅÊ\Í—D/GÈEw SަÍ)ž§ýn-A;þõÄCZÒ9%ä\Ps!+{Ú²ûNOgQŸìA‡ö“>œÚ?Öëk}²·]—æ@³ª–ÌUuSé)ËeÔDŽ[ÂZï²|ð1ÇôŒàܵÞcÈé~ áDÞ´Á¸2'Äk×äM’΄´;EgGÄéÇyo±h¯7‹Ì†fòngÓL/úí8Wúž÷ i¼4œÉBpÑê:{d*8‹ºÒ°©'Ì o-íWW”¡âÛ0à{ßî"9¨2ÚçÛд_{FŽèĴɘ䕠±FNˆ›&²s»‘IC®ù)e²2À‘Ãcêi§Kmµûý¹Þ46–ÝÙŠ]3ç¡l®dÕlÅ“ÊÛ²MömûÞ1};"°È¾‘}E8â[ù Ù3Z#!ûIÙ¶ms,ÆÙ!'b"Gfp‰XëÝ…6²;óFf=˜-uÌÒ{*jY¶v,Ë·ˆìš:ÙyÎÜ«U1Ò\FKØ^Ï%ëe?ˆÂkSäÍ\O™×¨X›^'b"¯é¾k=µ\wHÔ·;Äë…vð?Aé†ÌÃŽ<>²àt¯‘GqDcÒÃ:&}ã«€p:ØsÜÂÚxe"üÔæà3ÿ#·s§©D^ KT*!aØXK¨hZ'ÀáºC3åàö€¾>×Hѯ ;¨\ÈÔ¬1ëOjÚü³¶ Mµ%¬åPq-Àð[Üø–·¶À,^Q©C×Èo&X¦ñÔômð©é]ÇpžŠÚòÃ:¿U¬%NòqÕY@©zb Ç'ynPcêXÞø…¬–#]ãÃͱÖ[‡ÙPâ Cî9ýŒLþÝì.þÉ®?Ü»¿5$‰~`žÄã o_8Û`„‚fVBÃàùún¾¼½øoƒ}}3û£÷>|÷ø”ùŒßé#ÍežÏµ.óY?¾föë…6Ÿ?<üöãÝÿ¯z\ò®ùî²ëKÞØ÷–ÿy»“9 ~¿ó÷׿˜?¿š¯æ?«@£! endstream endobj 4 0 obj 2239 endobj 2 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> /XObject << /x5 5 0 R >> /Font << /f-0-0 6 0 R >> >> endobj 7 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 400.131744 244.131226 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /CS /DeviceRGB >> /Resources 2 0 R >> endobj 8 0 obj << /Length 9 0 R /Filter /FlateDecode /Type /XObject /Subtype /Image /Width 500 /Height 212 /ColorSpace /DeviceGray /BitsPerComponent 8 >> stream xœì½u@UÛÖ>üó”‰"J·4*`!H*- ‚HŠ " Ò ¥t©”€ "Ý ÝÝÝ!ö©oεö&Ï9÷¾W}ïû1þ¸Wk¯µö~æÈ9Æ3ÿßÿÛ’-ù¾² ‘ïý[ò­dÛùÞo³%__ ò#üŸ-Ðÿ à?þøÓO?ýüÓO?þQÿÞ¯´%_W ä?þôó/¿lß±cû/¿Ø·@ÿ?.ˆ’ÿôËö»vïÙ³{×Îí¿lþ] –ÿ¼}çûñðöïÃÙ½c ôÿã ÿéç»pö8HDL|ˆßžèßûŶäk ¢å;vïÃ?DJICKKENL€ AßÂüÿ¬` Ç% ¡:Ì|„•š”wÏŽŸÿJÑ·mÛJçÿ‹eÛ6àË䤴̜<§Ïœæ=ÊBKF°o×ö/):&“ÿqUЄ~ ÷ÿÎ|û®}dtlÜgEÄ%ÅEÏr³Ò’àãìüùÇMpD‰üÏ ³ûe;Hì~ù¦ô[°ÿ÷´ì¿ìÜ{€”îÈ !©K*ª*—$Ïñ°PÚ¿{EÇþ3ÈãwݞݻwíÚ rúŸ1°—/±%›Ëæ®uæ8ø$´GNŠÊ©éÜ42о"-pŒìÎŽŸøôbñí;víÁÙò:| xûqAr·kÇv êßò;mÉ—[KÇ8Þmëþ&æ{öQ³ž½¤edmï`g¡§"qæ î®_6Fqh±î$Ç'8HDBJ„”„è îÞÝØJÎêß_ÖJé?aK髸 Î÷ %3ˆ‚¶¹ƒ§€‹•®¢7#TôõÆSžÝ¾sÏ><"R j:zF&ff&Fz:j R"<Ô#º¾ú÷*Äoß¾cû'~Bœ99ÃqA¹k.á1±®š2|G¨âîZÅa”¤ñx„$´Œ¬G¹yOž:u’—û(+- !ÞÞ­úÝÿÁ†\;`%ÖÒwü²¦‹ˆeß œùaN~ 3— ØäŒôä˜{7•DŽ3âïY‹â¶¡^`Î~BƳ€¤N@ø¼„‰ó§y8YS’îÇÙµýç-п£l[Ý:Ù³\àïß·gת.¢ñÛ<"š#§%Uƒâ3r sžDy[iIŸf¥$Ü»š®mC˳»÷áQf=vêÜy™KÊjš×´µ¯iª)+Èœ?wê(ëa "ü}»wlþÝ5ê@ÅwíÙ é$ Þ"!"ÄÛ‡5ÀŽÛwã¢b9q^ÙÀþ^|fQUÍ˲I¡N7/ ¥#Þ¿ÅÅìú\Rf®SB’—Tµ L,­oÛÙݶ¶41ÐV½$)tŠ‹‰†„w ôï%Ø ñ»ppñ ‰É©éA¸ECké;`-ü—]û)¹…/éÚúÆ>-®mio­/ËŠõ¶Ô8ÉLަkÛP-ï?H~øï9IE }óÛξAÁÁA¾ηÍõ5%Îñ°Ñ‘a«¶[ cÁæÑÀ¨ã8D ëè\ܼ¼<ÇØ™hÖPA‹1dô\¯ÝòŒL+ªkïèk¯-HqГà 9„¦kÛ0N`ÿA ÎÓ¢òêú·=Ãb&&§¤$'&Ä„Ýót´ÔW“=ÅAO~ðïJõ[òdÍïÝO@DFÃÀvôÄY!Ñ Ä„ÎpCUÜ·{;ŒÞgNLËÁ'¥næö %¿¦cpl|´¿µ"+ÚÃìª7#&ŠC÷ݸr.>q¥ëæÞ!1‰éÙ¹Å%%ŹÙé‰1!^f×/‹óq1âÂÛo)ú7”57²*b Z&vî3Â/«¨ª^¹$%t‚ŽôÀ^¸=q܈šíäCÇàÇ/ªÛ'ff§G»ëòïݾ.s† ‰â~€Î$ñ„ä Gù%UômîǤ<+(«®klniin¬«.Ë–än­§"y‚¾oâ<¾÷/ñÿÙàÆ’PÒ1sòò‹É\V×½ibff¤ ìïI6"<`~éΜ’™Gô²žü³Š–‰¹¥¥ù©ÁÖ²ŒpW#%ác‡a÷ÜBCçY©«†v>á‰Y…•õ­]½ýƒCCƒý½]­õ•…Yüín^•äãE]ò é¡C$TÌH1Æ/6³¬¹baåÝÇß~ûíã»W3ÃmåO#ݯˆ‡»sÇ.À1r‹*:‡&å×tO/®¼ýð+¸ø÷ßÿ ~æÃÛ•Å©¡Žš¼¤gCeQn2ü›r[òŸò{p ˆ)³"n\M×ô¶«ßƒ8UUÔµtõ  ô´Tç&;è+ g¢¡  a:v)Ƥ7ôŒÍ½ }ÿza¼§6÷‘¿Í5i®Äݳg?TsYm¿øì*Ä" ×þüG ¶UfÇûÙh#›rû`­þ{ÿ,ÿ—ÝÅÙŒ–åèi!Œ¿þðÉ󢪆¶žÁ±ÉééÉѾ–ò¬h/ )>.fzÖcüÒH1¦ ¶ Äoo?üQüíãÛåéÁ–Òô0C¸»FF€»—€”Ḉ²‘kXï'€€×þ‰Bþ€þ~ea¢¯©$-ÌÕHYä= Þî-ãþUeµ6JÏËᚨÏxQò²±£‰±—–fF{ê ’ƒ .‹>ÊÁÉÃ/¥jäüøˆß¦—Þ|øQ\ è+óc]/s|­µdøØiI𠈨YOKiYû%ä¼ìE-Âþ‰Åüj:þ:áǬ4¥N!YÞ–qÿвmÛpƒŒœžó´¨œš¾¥£7êÆk›;ûG&f–_¿y \îôP[eVŒ×--9Q¾Sg¥T îÄgW´Àø … ÷áÍÒÔ@SÉ“.FWÄxYhH‰ˆ)è ^Òwºÿ¤¤y`j ±®ÇüÏÕÁ,OOA€1î[˜=AZ`ë*ÇéóŠÚ¦ö^!±ˆoíYÕ›wï?€èXß^ è!NFjrâbdUôl}¢3J{ÇçWÕEíô¯ïVPõ·Õ‘<ÎBKEEÇvâÂU3ϘgU£s+ÈòøsÄ>Ì€,/ÚÃäŠ,áÈ} ó¯&ЛÃ}pÖ¢Š×-\îE%?+¬lhƒˆø‚ì_?¾…yEV¬ïmõË —Õô¬<ÂRòk»Ff–eÿ “A]œêo.I¸k®!#ÀÍÆÄ\¿¬¶mÀãüúÞÉÅ7~C^‡9ø#üØDo]ÞC?+ q^FRèз0ÿj{öà“æÕ2w ŠÏȯhhGÜøÒ D‰±1y}Aʃ»Ö7¯k]¿iåòèyEKÿÄ<’zaqÔ5wÕä%;«I ž8ÆuüŒ˜’S(0íƒ3Ëï>þ¾)æß-O4%ÙiKŸb!?°ç‹ƒ0[ò?—m?ü¼ç9˜òMû€¸§E5°8 Ýø[`ÔÅ€ù;exôì‡Áî¶æ¦æ¶®÷bÒ k;‡¦^ë~ý ƒúï¿×<=ØZ‘à`¨vQT€_DVÃìnTfEûÆ´¯Fí(ê˜àov¸­,í¾ã‹|l”[AÜW€ùν„”,§$ÔÍæ×´õÍ,¼zýî=4ê¿cckDÑ{Š3bƒ<œìÝýó˺†&ç 9¨£öÆà+óã}¥Ocýí5e$$.^ѳõ‹QûØÂkhÚWe]–n??ÚQñ4ÜY_ÿáÞ-Ì¿¢`0‡ù”•OlVysߨìÒkÄÿþûzëûvyf¸ãeþ“˜?oï€ûqO^”×wôLÎÎ/½BlÆ |x»<;Ú]_”àd~CýÊUs—¤‚úÞ Ô¯}õ¿}x=?ÖY•ézó’;õæ_U mßK€`níÿ¼ª}h–ʰaÙŸ¨ Š>Ñ×\ñ"5.,$ø~äÃ'Ï‹«Û{G&@°=úï¿Aë>3ÜUW”änkj g`vÇ'*½¤©dj7`þÇæßE@ ý9óI [>qÏ«;Ff_aÊj0‡Š>;ÒÝPö<5!:"":!ùiNQEMckgï šÔaAÿøþõâôpg]qÖ£¾®wlo;z†>z^Ù64³ü—˜vVfFlaþ ÛIyÎ_5óˆÎªlž…µ²õIô˜–ÅÛk‹³S¢##£ã§>ÍÎ+®¨ihë›^x…æl˜úôpX)1¡þ>Þþ¡±iHöÕZؾ1nGôvÄŸËŸÝòç_Y`ëîýÄtG….ßt K+mœFÕq昲øÛWsc}-UùOcÂï‡Þ‹ŒIHJËÊ-®¬oë™^Xy÷a-Ž[œéiª,ÈLŠ‹ŠŒNxò¢¢¹o| Ûÿü sÄs¼šj-M uнx†•‚g+WûŠ™"vî;Hu„OöúíÀÄ|L¨µ®&¾¦èo›]ò,)æþ=//o߀ð˜Çi9Å ¿…m˜²;¢é3£}mµeyÏÒŸ¤eæ–Öu M-"uù?ÿ\‡:s¸93ÐT˜h{Mê$39þží[ͯ_O¶¡Œ‰÷‚ª9R!ŦÑë’hŒ¢¿{57ÞÛTž“èé|ç¶íí;Nw}C¢³ŠjÚ&V‹°¨O_šìj®)/.,,®¨àÜ~[ƒüÏuàÿöáÍÂDOí‹_Kµ < [u¸¯+èDÊ~":Îs—ôCS‹‘øz-ˆûDѧÛªsS"ý]lÌ ôošXÚ¹ù‡?~VÚÐÝRý³?úæÕüÔhgKC]m}SGß(’lŽù￾{5 Üyf¤»±’ðQ:d|} ó¯&Ûã¾—€‚唤¦•/R;™ÇÖËÖaŽ†îš®Ú‚ÔHs½kêªjZ7Œm\¢Ÿä× {ªXŸS¶·+‹³“#½]]½£Só ²ÿu3ÌÑ*ýdcQJˆý 9Âmí«}eA§ ñHV2\Åýþ)æhµ ãÒ£|ï˜\WURTTV×5½ã–ô¢äõ«˜c@÷zyavr|tddtbz~ùõ;t3æsËþªyGUvœ¥ºÄ 8ë¶Â}]A£8B*¶3Ò×lüæÖv/¼þDÑWõqij ¥<+.ÀÑôúUEyyÅ«ÚÆ·=Ã’rÌW7ÇÿÄ8õ·¯—çggffç–WÞbŠ{aÇ–k{ Sî#3­È4ÄV÷uIÑñHé‹^1v‹È(o‚`£¼ˆBv¾ÌM u»¥¯qåòå+ê7Ì|#ŸÔvŽÌ®åxXU‡¨¯,//--¿zýæí»÷ï‘~×Õ¢.\¿ÿ _˜„+)Æû–¦Ôi6*­~¸¯.˜tI×îÁÝéÏõ˜c °½%Ñ~Žæú×44®é™Ü¾’UÚ„4OüºN‡‘þF€ú»·oÞ¼~ý#oß d ²,^-L‚ÈðÅã }EaØà¾µ“úõeµ'vÕônT&Z‹Ãà·¾t‚î´ µWç&‡û8Z™™Þ²÷ŠM/¨é¶ŭϿP?¼‡¸¿^yô}hüÊk÷°¿ý0Ksí/óR¸™©Iœb¥Údùý—]¸‡h8øåt탓 úÖu´l¨ºÃ&Çñ¾¦òìĈGGÿñiyU-ýãók%ÛÏQûúÕÒÂìôÔäÔôÌÜÂÒò Tx8Ʋ²4?=Ú×ZŸáyKû¢×a8þ²ùW$]C'Nˆ«YxÅfWub:×6ɪ€¢£;-qá¡ÁÁ¡qÉY…Õ-½c³ËŸ¨9v½ ü›W ‚è_^Z\˜ìjªÌM‰ð¶4cprç–šEÙ†==ΓãÓq bê2è¾çgiºÓ2ÜÕX‘ÿ,-%))%ýY~Ym[ßèÌl\ÿ,[«ÏLt·5766µ´w÷ ܧ¦Ú t·Ô”d'…yÙê)?ÉJá ÝÂükÈ:†åŸàÔùn܃Tl§¥ºLMרüëÍFÙSÃ=-µ%…E¥•µÍc3èxÊç´‹âÕü$øH]eiqQqYeMckGw_ÿÀ@_w{smy^FBè]ëÊN³C~à‹]äS~ðMäߺÛúâï)X‚GȰ ÿvîÚ³Ÿ‚‰GTÙÈ5<­Ë|¶‹ŽEpQÚ¦†úú†¦Öξ¡‰díóz ¦Lr±¡Î†Š‚ìŒ'©O2žå•U×645775ÔV•äe&F¸Xê*]8Ãq¥±ø—Ô|ÕVýü#\>½Û_~hÓ‹ÿù›[A‡!áîÎ]»wïÙƒƒƒ³ÿ 9ÃÑsò:v÷çÕvÔ ímü}C‡-ªŽõ÷õôôö NÎ. ›ÔØí–…©¡Žº’ì”øÈ÷Ã"c¥>}žWXRZZR˜‡òÉØ›j+^8ÃIOŽ¡«ùç?fíbØIÞ\Ö¨Kÿ†RvÛ®ÓMøN?{ô?½ø{ˉÈnœ}¸xøø€"¡dàä“Ò°ðŒÊ,oœZXy‹¤ThOëˆHH¶87=91eb i‹~ÿq=G7f–àlQæÃ°O7W7Oß °è‡I©éé©I Q!¾.Ö†š çÏp1RÚÿ¯‘…­’“[µcÇŽ_ðOºôo9ñìòèÝÐaùå¿pñÏŸ\üãÿN¦bì¸ùn\<ȰLFN„œœ‚ŠŽ™ëŒ¸Š‘Sh2œV˜^€)ZBY× ‰)¯Ük~v„a“S3HeuEÇ´DÎŒtÕ?öt°¶0³°²s¾ët?"*::*â~ ›¹žšüy¾£ÌÔÄöþKüŸXvŒœtïÞ½û¾${÷âàìÁr†¬*?‡o?´kçzÂÓÍ.Þ½=hìçÿ…¤ä({×/DäÀ!ȰÌÀÄÌÂÊÂÂÌÄÄÌÊÁ}V\Iï¶_\VYSÏðäÌ<šJ¿ÿ¸¶9²Z^{³™A¦&ÆÇ'@îG^ÐnøO,;l®éi,ÉŒr³1¾q]ûºž‘¹ƒ«‡Ÿ¿¿Ÿ·‡Ë[Æ:ªòâÌÈÂÆÎÁɵ¹pr°³±0ÒQ‘ÄÃ’Ío‡¡^  ¥gf=ÂÎ~„•CxúY.®HTMIË€¹˜‰ž†œè–õ?†Ú¿-Ødü'”)èÀ!rà»OˆI+(«iiëèê\¿¦¡zEIQQIESßÂÑ7ìQÆ‹¢²ª—u­]ý#æý¯ëÒo46[ž›ìj©Yý²®©½wxrn6Æÿ¾¶ ‡ 2 wÔä%‡yÚh\–“½(ùª¦îM +[Èñkceab £©¢ -&pò5Ù!üÍÔjóï„T’ö ¦b`;Êsú¬À9!!!áÏEHHð?ß).ÖÔˆ÷ØÌ>!-ÇñgÎòóŸ=}%<ý,²Äð4à$§Šs{ñFjR‚ÿ ô¥ÛÖr xü ð@øÉh˜8Oœ—W¹¦obicgï`ç6øù õnèÞÐ7¾ež™“WT^ÓÜ581÷IeQa‚u5Vççæ”V7uŒÃ,è …Zd¤%-Òû¶¡æe9Yy%ÕkzÆæÖ·íœœàc t4®`P§§úçDîHS×Þ$4ÌGO ž—º¨ ¨¤¼¹()*\”;w’“‰ŠÿKœƒ˜9|²ÃG¸Ï ‹ËÈÉËËI‹ ó!„§Ÿ2’"ÅêÝOÏY xñEé BgŽ±Ò’ L–ÿYÿÙ¶š¾þŒw´¦á¤T ì<â j7Lm<|ï‡Þ ô÷¾ëbÛúÖ-+[7ï{ °ŽKxœš™[V×>01ÿêíú:²Ã6;ÚÓT™Ÿõ$9ùIV^Y][ZŽ[% @{!šJŸÆúÙk)]”–‘WÖÐ5¶¼íèz×ÓÛˆ—ç]W'{Û[¦×Õ•åÄ…NCˆÜÿYô¾ %§fáæ«WK×ÀÐØÄt3116Ô×Ѽ"r&ʃ¸› ;‡䤰ôe5m]==ÝkjŠRB'Ž „§ æˆ ²Ã§Dd”ÔµoÀ‹Uá¡Sl4ÄH%ñ»`¾¦Þ¸wïÙ bLB"2*z¶ã|brª7Ìíï<ˆNHJ}’––šü8>*,ÈßËÝÅÉÉÁÄ××Ï?ð~tbfaMûà$¦uf5:û€ôC¾,Èx•š]\Ó @_zí¶@ù@kEvB “É5%YI©‹Jšwܼ‚G p?$(À×ÓÕÑÖÆrR§¸˜¨Iöíþ{в„}äŒÇ¤®è[Û;»yxzz!≠ö/wÝœïÜ2ÒV’8ËIOzdÛê„=Lvˆ!ç ‰È©ë™ÛØ;:ÚÛ˜é©]æe¡:„»a`ÚeBLs䤘‚†¹-¸øŽµé«²BÜÌ߃Ôpmß[^€Ùë>\¼„‡ˆÉ(i?#"sEÇÌÒÆ¤=Ë-,)-++)Ê‘•–u?ÐüF®ÎÀîÚݶspõ ‰Ë(¬í™Y^ë|úÛ×Tö,12Øß/ 4:1«èekÿØìâù!‰Ì̇;_æ&…ºšk_–¿ }IÝÀÊÅ742þqRJê ©)ɉâ¢ÂC¼\í,ô5"whw)ÔZûž,a? ûI}+„­2&6.>>>n½À¿ÇFG>ôr²ÔU:’ Ýžßó‘–o†cç.jÛÝõ òs¿m¤.{î(ÊS¸þ|¸ –Ûq!ùk¦öwýƒÁžn¶7Õ¤ù9éHð¾eÏÇjù…µæ8{aöz¤T4ôLl\<|"ÒJZF¶îÑI™y¥Õu-­m­­Í µU¥ÏÓ“âƒ|=\ìm,Lnêëß4µu Œ{ZÒˆ0>ýºsH"Ò]W”âãîæî™ø¬¸¶q¶Ë­(-ÂÓJGIJLDLFY×Òõ^äãôg/ò ‹ äçåædg¥§>Ž ôr¼¥¯&'r’ý0züæ_÷Ä!ºv€”[DQ×únpLbˆ?r^ ’ƒ ö/Ù™OGºYjËCºí¶ÿ´æÈhë)ñ«Fö>aq‰))‰q|îÜT?ÉB‰¶k­ÃüØXÄvFRÝÄÑ/,>)%åql¨×m}e1^&òßnz~­XŒ{7€{?r˜% óÎã'ø„.È*iÝ´rñxœ™_Q×ÒÙ;04<<<4ØßÓÑR_U’›‘êëvÇÒPGãªÊU-ý[®ÁrÑŵöt´Uu´«¶àIt ‡³ƒ£«wpLJNyc÷È4¤„üí·ß°äÏâünë_‘•U5´÷N}^Tñ²®¾¡±±±¡¡¾¾®¶¦º²¬8ÿyFRL¨·ƒ™¶âùÓÈ^Ëßqz#µ„°E[ÝÌ5$!#·¤âeMm"µ¨`þRS]Qü"-.ÈÉHåÌ%¨pã¶oTò³¼‚‚O?¸k©%s†²˜Ì·­V‹Q¸aˆð ,µ1³qã9uVPLòâeÕëFVÎ>ÒóÊëÛzG'¦gfçfgg¦&F{Û«‹²SbC¼,ô4”@nuIM߯+2£´yÃŽ:–C¤®0=6ÈÓÙÁÁÙ# üpümýãsˆ¢ÃÎÆù±îºüäPgc5¡3§øD.jš»‡%¿(¯oíìîíƒÒÛÛÓÓÝÕÙÞÚ\ÿ²¼¹»Yé*ý³=UlßÙ‹:¶~±…ÕmÝ=½ˆô ‚ùKwGkCe~ZÒbɺž‰ìÌ÷“ÐV2r}ü¼¤º®®º8ûq°ÓÍËÂ(«ÑOë1‡Ýá Üb*&n’sJ_ÖÕUg= ´×»É¿Uç6 ßþv$8G75-=+ÇQž“|çD.HËÁÜØÐÂÎÕ÷~Ü“œÒÚVH–W€¼ZZœ›îm«+ËI¾çj¥¯~IZü‚Ô%MSçÐä5Æ€Ṳ=¥YÂü=\]ܼî…UTÙ܃̭Aî×·ËÓC­Y1ÞVÚò"§yxN Ëk[ûÆf•5tŒŒMLB™À¹?ð´½lÔD×;Ø×C4œ‚— C’r«[ºÁmÇ'&à'PAÿ4>6ÜßÕTñüÑ=„†–’pïjïÕzÌAt€ÇsU-¼¢Ÿ–Ôµvv¶ÖgDy˜]=ÏÍ@‚·>ÚGûɘOHh@ž†Òú¶ÎΖšÂ´p7cH{E„.÷•ñþëN*܃‹Opˆ8ïÃŒ¬ì\Ü'Î_º¨xE]VºíݼÃa”] ÒîñÈþöîÝû÷H›âÊòÂÌø`gCYNr„È®.KŸ¿¨zÓ>ðÚô¾Ö¾ŽtÀN´Vç¥Å‡úùø„F=ÊÈ«hì†3J>~D:d SBT$Îrs=!¬ kø8·¦cò BYX˜Ÿ›ƒ6f™¡ÞöúòÉ^°GŠçï{¤ æÀsÅt K+®_gzvnaaaq½€¿ÏÍN tÔ¤„“bØ©Ö™Þ ˜oßOÆtBBÓÚø²¶ÞÁÁÞÖjHS¨!~‚ic/.ñÛs€œå”´öíÀÇy/Ûû{Z*³c½,TŸ¾Õò]{à äÔ‡™Ø8ñžæ:/){IYíÚ C³[·Ü½@М”‘S\ÝØÙ?†Ì–Àm3DNÅåùI88žýøþ]k}UyÉ ró{›`þá lŒk¬ÈMÉZXXDÌÃÔg…UÍXÌ?À¯«æÅCÿÛº Â'8pð )è9„¤5öC h+ìÍüìôøPOKU^j¸§¶ò/uÀtG…•Ý#2J›zF¦æ–€Íz îŒôU¿~ó<6_‚/U_ô侓>Â8ø%Ì÷à“3Ÿ’ºf”\ÐÐ=<>>”ŸxÏöšäIf²ŸbŽs€‚õŒ¬Ž}hjqcÏÈøøPgm.¤:»ÀÃH²ÿÛŒX®UËÅŒGŽòž9'".£ ¤ª¥{Óä–­ƒ«§_Ѓ¨øÄ'™9…å5Mfné5ºcöûº^sØøÔÕPš•äj¡{UAV‰Spb^]ÏøÂ:ÛŽí~éj¬,ÌNOIJLJÍÈ.(¯ƒqóH¢Ö™#-5eŽae纤ïô ½¬uhòÕ ÐèÀÃÒÒ2쇜›ëo«ÎK~àfúOzž1AÔs·ð´b€Ò$À Íx¨ñ‚›¾3Cuð, ç9PÝÓ2×íCž7÷OO÷5§ ìeÌäŸcN@ÁÆwñüF-ÓÓc½ ‰6š¼Œ$ßh¬ËÔKHJ ðÓçÎK+(«kë[ØØ»xøÞŒ{”r¤‚’ÊšÆ6àù&gW°{¤˜acÌŒ¤ˆ¨/~wÏ„qW¯jê[Ý {´srñÍZ ‡öHÍMu·ÔVçç¾x‘ n]×Ò=81·ô" £ö–²ŒÈ»fj’|GY™Ì œÃ2ʰw ]A¶cæççà Ë«åÅÙñ¶ªœGp¶Ahu¶á‹I¨mçT¼ 7ükÀr›œ…[þïÞ#äðÙ矟îm©zñ8ðŽîE”ÄâK˜S°ž–Ñq¸Ÿ^Ú:89;;9ÐRò$äÎu™Ó,›aNy„ONÏ9üiEÛÐÔììDSQr ­–pß sd —€„š‰ëä9q¹+š7Œ-5÷¹ ÌnrZfN~qyUmcKGOÿðøJñøÉÈ –-` Ø©¢ôØ{®ÖÆúúF·œïÅeU´bè'Öm“¾‡lû#ý]­u/«««kê›Ú{’‘·ï!æðœHÕlrUü4' 3¢KxfeÇ(l¯ü N³­,ÍÏL@kz‰$_-Í÷¯Î0m8s³oŒÄpÔìürºvñY%um=CcÈ{4÷ü†¬*Øc=6ØÝZS”ãcuM!±ø ÌÏÈê:>È(0ÎÍÁ…ÒBíu¾ˆùYy}—82=779Ð ŒÂík’ßs¸ÑCDÉÈyJDFYËÀÂÎÅëÞýH`ÍŸ>Ï+*«ªihnë„[£ãS³ ÅãçC¢ÐaûZmaF\ˆ§ÓmÛ;®þQO ÌÜFf´…˜Í‘Á¾îÎŽööŽ.¸š¦ÑS€½X˜ìk,J}àb¤r`Î1¿ ¡Î18…i«›!{ÿÀÐ(¤„ ÏŽõÔƒ¸ÏÉ@qãù››}ãa~ ¯4,Ü$f£,V±Í{ópU‚ø­§­¡ª0óaˆ‹ÉÕ '6”L¾„y9€q~~z¨íŸ`^Õ123??5ØRòÍ1‡gYÒ9!$£¢cjçæý(53§ ¤âe}€»opé][XOçöç'‚mfì¨-Ê|äçãò¢²u`r•qƒ92w BýÙ鉱ёááax{È £ö°Ù¿C ¯œ?ÅÁ¼Šy‚ùGd2qz´¿«­¥¹¥­ /èÀ±´WeÇyÿƒ™d”À’Œ‘GTIï¶WØ£ôÅUõm½Ã“óÈn?ŒNànïPOk]eÑó´øPkA„~ÓšÌæ˜#kÑÓ /k›ÞANRáÚËúæöî¾áQØñ-èë7oßo$üû\Ó‘ÞäúÒœ´Çq1qÒ^”7aÎbØ8Œ5 :ÌÅù¹Ù8p îºSøsÏŒt¾|ñ(Ð^ÿ²èIv&&6îu˜¿ÿˆŠô´ÔV–•–UÖ6w @`eey~¢Ã=ÀÏþלÞèØ<1-ÇYiUÃÛA‘ÒžV6vM.ÀŽ`ÙAÀ11ÐÑPQð,5!âž»þ‰Ó{ùoÆ{(é!*æãÒWõ­Ý£“² *êš;zFƇ ÔÆ´>`;Y7£ùXDïMUÅ/žefåU5uL£ An3w 3"zà ­²ðŒ•…‰þæò¬X_y!6FVnÔŸÌ?ÿ€,Šî¦Ê‚çO3žfç—×w NÎ/¯¬,!Ñ~Ô]ce‘ct™è"Ð}„LÜ‚²ªú–÷ÂâSŸ—ÖÃ#_^¿GLÍÂ$ÊmûÀÿîó*RGÈ ÖE ÿµ˜oÃ4¹íÄÁ‡›É‚²j7o{ÞO@š{‡€û†ê÷ú fôÓŽõ/>=Ú×Þð²¢¼¢º¾¤¾È);›N …‚y"È€9ü¹VÀšüw©K=ÊLOÏr\ðÃAÌß½GXa›+^ÉKê6a)õ=þàÝ[„H¡Ä¸¡*/!t†›¶cìÚ|Pæ¿óÕ‰”={q$¥fàa;ðçwC2‹ë Äò°þ#¸×ƒcò¹™ééuÍÎù™?°MÐoQÈËž= v1Õ=wœ™†œŒ’žS@Aó7𬞚ƒ]­ŒôõMl= X!ƽûåóXeH†ng­ôK£?¢•GBJ:äg°Øíü㳫;G€qG,IkEf´Í eI¡SÇŽ0Ò’؇éEþä–‰¹,&©_]*ÿ"æÛ>Ú—Ðü×a;ëqöá"¡ a<‚t=é[»Ç?-ª˜CFÎ/hè_ƒŽÆäËKË ÀìþÙÇÞ¾‚`=Ðò`s-9!2""2:Žu˜¿yƒðÁeÅøØhªª^3²ó‹Ë¿Ý,0îÀÔç=ôµT;ÏÃûSàÉÛˆ¬ ®µ…lÇÎfÐ2±ó Êh˜º‡g”A>œ7o_£ÇúÜw1º*ÉÏ}„šœ¶ŸïDŒÀÝЮ˜} óûºðBäp ÿsìÛ"M‰ë·)ÞŸMT~r¤³ž˜œŠŽ‰‹÷,Ús7$þ)ÚÆ¶²ùŒðߢ‡ þîí[HƒIíþÁ§Ð¹ã¡ŽºâÌø g3-9a^VÒƒ„$´üòë0‡JXšæf¦uYNNéš™†šéÚBzÏF ž­Lˆ»{'œ4G Ñß Qƒ}@û`_ åá#'D.ë;„¤7€,ãíëE°xàiì:ò‚Ü,@Ç ðöá ÓE¦±s…›bÞ^žþÀQOžŸƒ†hÿ°Z°ÿ3Ì×5±lxÜçs:Ÿ]ùåyGL¸Ž´á3²qqŸâ‘SÖ2°tò {”U 9V7‰·ÿ蘘üã*ÑÓ?Y(äƒí5…±÷œL5–³Òâí?@Dþó×Ë Ty¸ÃMYqqYHiÒØ?µ³µ¡–$ˆƒ´ÀÝ»gÏn »vîØþ fÄÛÔ‰LŠíÛ‡‹»€ˆ‚žó,<ì'±Ügéõ›•ʼnžÚÜß[’§Ð’îß ‡ÏvÀÁk8̸ ÈNäžèâ›ÄíÃiÚ妢ÐQz2\œ];ЋáÏþ÷˜o[ µÀËb·c³9ÆWîܵs'æ»n6Q ST؆£îÓçD%/*^ÕÒ3±vò‰M}QÞØ…¦ªÿæÔ‘°ì÷/&óŸ|yÛËü´h?cuY `4¶½x‡¨lÀÕæ@;]Eqaá —®Ûø?Ê«ï›\\y ‚¸6 ^Nú—1RÀÇÇÇÃÛ¿Ä Qy¤-dß~<<|DðöãâââS³ž”P¿åû0¿¡îoV&ºkrâ<ÍTÄx)îG߬œ½`Ùî¹{':¹ø#Ü߈ùÌHGUV¤»É•ó'• çÝv ®áç¿Ãü§‰Yd~yÜ~ð–ûöâ ßaã¤äºIKd£ï…™¨üd“ =ÍVΈHÊ_Ѹ®obyÛÉ#àA¦÷mCýw0_ÇêüÏÂ?d†BÞZ—écwSUZà8351þ¾=»÷ं¿Ð:̧óØhË‹ ð‹\Ô´ô‰QÛ3±°òž§]™áf¤,vxa 22R $D ðq÷u‡_¸H—)ø7âC„À’À?DÁ›\¼ráÚ˜ƒ`0;殉’ðÑÃðÐv dÈô5p„‡ˆˆˆ‰‰$8€‡N.ã·G×c¾0;Ú¢Io¸|Œ…–‚ä ô»!Hwû_asƒ1±5ìY‚Ï#$Àß =®gm[„W—‚÷_ }¯OmLVpð‰iX¹$.©#Ón^þ!qIðäËæîá)¤ õ_ áþ-ÁmZ«rS½lõU$ù2Qáºc×>ÂÏ0ïoÈäg­uQ˜OPZÍÜ3öy Š9ˆè«ŸE{˜«I ps°01Ba §£?úýû€9ßà 5=###ÃaZ*2"B|üÉ‹ª˜yÅçÖõb0ïz™íntYqÇ@Çáj!$"¥ ¢¡;|ø0-5%9É¡ûAd·XxDÉæ ³c=uyîÝÑS?ËÍÎt˜š¾X<àâ]{ ÿó=Ⱥ±õúǦ£¡¢ %&Äh®KÖÎ!G®¤¥£££¥¡D÷ìütL[}d<Æ/©¬czÛÅë^hDìÃd„G¿®µ–°±#_r ½,´¤„{‚üHœ‹âúsR²}†9³°lVcñ“0w‹ëJ’Â|¼GÙ™Á“à"0»1«x3ÌÉð÷ìø™ Ä?HJIË€yÜñ£œì¬ŒtTd‡°ó±?`•|7¸’½’ëØ±£è{Q¢c®ëí;:zqˆšíäùË:N¾p#íiv.ÜIk€£…“³«óÁ_ó?0¶ÀöçÔOkÝËNsГ/ íb 7ÃX2‚§OñK¨˜ÜÆb™ÜsúÛé«\=/..~ATèì .:JR"" :N^>AÑ âÄÐÿNABDLÁx\äŠéç˜_€˜ãìÞ³”šž•‹ç4¿ ˆ¨˜˜¨ð9¾“Ç9à<,@a$Q⻈Å|qiaj¨­òY| “ù ÕKÒ„ÏžI> ÙA «ÞjsÌq€QÙ…ƒwpÃãÄDùÏð…·€“˜ˆªÿ°:ßJEÏÊ [“…EDE„Ï=É Þ‹3»®†„ÙY¢ã8+­nâè™øôEQyU-²Q>€ ¿ùžéWÀáío*}íc{Céü)öÃp¶oÇÏØˆÅü駘KŸ;uò¬øc÷¨g«˜÷Öå=r4¹vå’‚Âeå«jêêj*вçxÙi©¨hÙyøÅdUÔ44ÔT.Ëžççf;LIFFõeÌiˆð÷íÅ=@DIÏvü´àiùËW®ª^UQ¾tQR”ÿ3-ÙA|õá»x‹ùòÒìhw]aZL€«­©þ5U%9I‘³<ì ”@ÕAyˆš}SÌaóˆ;öìÃ>î<ú8Õ«W.+Ȉ óñ°3R“ 3Ë?"e%á’f=vê¸RQYEEEYQNJLà$ x¯ý8¡±$Ü\çä´­<ßU5´ttCZŸñÉ™ùEÌÁxßÈ´cø_< t¸yEü4;v “ÙüCÌß`Nã u»e ­©u]ÏÐÔâÖ- ƒkWd„Oq±22²p–Q†µVV–¦×”¥yØSQÒ0s‹nŠù9Zb<|Þ0=-"­¨ª­odjnaanrSWóŠ¼ø¹H ÓÁb>2»ôjyòÐæ¥Å…ú¹;ØXÝÐT–ã;ÆL ³O|"j˜}~†9Ù×P˜æqÛÌЦ!î^>¾^îŽV7ÕåEÏçä<~ZTNÝà–ƒ»·¯¯·»ƒ¥ÞUicÌtÔt,ŸbÞýõçœtd‡‘Ó±ñœ“º¬e`î çb½=\¬Mt2&jRX,d?+‡ÅnìÎÂ3‡ 2“ã#B¼Ý¬Œ´ác¥]­2 Ÿ`Nqo?!ÝtÃyœ¯¯—»³¥¡öY‘Sœ /i'&Ü‹OLÍrœ_BQCßÜÖÑÍÃËÛ ¼—é 5ø^T ÞPöE[?9øåtl!Lc÷rDý+ìÖé—Z#¾æð¬…Þ†‚ä`ÈÎÌÍKhèœá¿ƒyÑ“kK+;ï °è¸ø˜ˆ Ï;Ær¢|'OòÈì<ƒ"bããcÂï¹[ëAOÂ@w˜•gsÌÒS’RÒ³Ÿ‹ÅÊÉ+ðAT\ÂÃp×Pÿ»wÌt¯Hãf¥£$§¤çä_ÃüÍ›•Å™ÑÞÖÚ²‚çOSÇE†øºÚjÈ ó²Ñ’ƒÂ) €b>:³€`ž 1g¡Lˆ‹ òqµ5¾vYüìQX2ÀÙµk79¬ªž¥£ç½û‘àKÅ!ïeoÞ d»T(™Ö'sulg¤×νK”þÛ¿¸·ò?Äžœ ƒ/mÙ³ì4DxX¾…ó ˆy¤ŸË;wÿ°ø”§ÙÙYiÂ}îªÊŠž;'*£zÓÎ'ìQÚ³çÏ3Sã‚Ý,4eøá=˜Êæ¯1˜]:ÆHCIÍÀqJì’¶™£Ïýؤô¬œÜ¼ÜÙ™©#ïÚªÉ÷@OKËtT@^ÅnÓ@ö”±þÎæºêòâüœÌ”ø0_G³kò¼¬´d”ô\›b~Š•šä€üôùË×ÍáãÓàãòrsž¥'LJù»Zé©Hñ# ïÅÙ ; °œ¦É¯èÇi™Ï_¼Èïõ(2èîmð^çŽ3íÙµ¶©‡9‘‰÷ÂUS÷ˆôÒ¦¾ ¸§P‚ôo„ø*æÝ5/âa±ó+%ájwúßbþYÜÞ 1÷wsttó {‡+Ë‹²“ý@(sá‚´²®µgXRvQEUei^z¬¿ÝKBܬ l¼Ìû&—^¿Å`òsan–ÇäŠ:·Ü‚bSž”UÕÔÕ×Õ¾¬(Î}ú8ÂÏÁD㢠Ï&FÖã‚ zNXÌß¿GÛ4û{:Ûšëk*Šž?‰ rµ¸&'ÄÍBBI¸;üæÒ§ÙhÉI)9u^醕{pljvA)|\}]MUYáó´„^v†ªRg¹@Zƒ‡ˆ’鸠œ–™ó½¨ä¬üÒÊ—µÈL%æ½ÔeŽÒ“ÀY×Úƒuè‚—ô‚“ êѶµµãÈ¿ àbËï†ÇeÝÒ>`/Ö ýæ0?¹ÈÏæ5æ¯` W1wrñ Š}’[Q×ÜÚ\Wöüqˆ‹©–âEYEMçàGÙeu-m­ •¹É÷ •DyÙ¿€¹’ïfŽ"—t¬<î?Ê*¬ª‘nOooOd›ÍMƒ[@Ó9Žpð ÃÁ óØ¥´cOŒ!umÕEYC\Í4 Y9|˜v|й¶ôöÃTTtl¼¢Šº6ža×=®»³µñeÉó”(?{ëgØ‘(ƒ‚ž‹_FÓÌ5ž<_×ÜÞÕÝÝÝÙÖTS†¼—±šÔ`3÷¯oÛDNRý¾š·¼c³«@Š9>ç[½ úo¡?¯Ï{äo­%uŠåŸ`žÿÈÏJKVèÌ™sRªfž1«u¸ñžºü”_€¹wèì’ºöž¾ÞŽú’Œho=µË—UoX{E¥×wô ôu6”dD¸›\;q„ñ zn¬,v’‹“Wð¢–ÅÝI9eõí½ƒ#cã@F‡û»ZjŠ2ãao‡”ï±ã§D œÂPÌA΃ EÀe éåP_G}Yö£ 'Cؽͼ®«s=æ2|Œt‡Y€îjßò ‡S¸«ìëlªÊOñ»£¯$vz à$®;?|VRÛ Bðѱ±Ñ‘¡¾®æ—…Oãî9ÜT>Ï h£¢ïÁ‡´—o:ÝOE§M>üë{§ÿÌáQˆSp–#èŽÎE>ô¼¬¿ÄÖÛým®É‰ðŸ’ѰðŽË©íEëíc]5yI¼]]¼ï?~^ÑÜ34:ÒßV ²@Gm5Umc‡À‡9Õmý£ã£}­YѦ*b¼lÀ¶cc¸u˜G»_¹p†û¯€”ª‰KHbNeS÷ÐøÔÌ윎œžè¨+ÉŒó·ÓWç?y‚ï¼ÒMç5ÌQЗ‘iÊÙé‰¡îÆ²¬8?[!^6`oºnÄÜN[æ,3H'ù¤ÔÍÜ$çV·_}ÜÌÔøp_[Maz”·õuyÁã,t´ ðQÔ»ÿ¬´¡s`ä\ÈÈ&¸®½¶øiŒ¯­Žü9.:âõýÙ? „ Š“Ò´òAÛS5¾濾{53ÔZ–ærSQ‹Žhu aşš àæö¥ó‚‚bò׬üн‘•×Ës£Õ9B=ïÀ•s*ÛƦ&GzŠRÃÜoé_»¦oéö µ°¡gtjzr¨£*;ÆÃäŠ(+ýjÜÞ7µ†ù]æÙó—õì²+š{á,ãâ"‹óp”­¾8=Òó–¶¼?ß9 eØÃ«eÌ@ÝlòK w7U“<{œ“ë¤èåÏ0¿.ËŒ•ü“¾CУœªÖ¾Ñ©Ùy8+»'1ç¦Ç:j À71EGºØŽ ÈhYzEe”4t¤ ÎÕÂi]t”¶ðIø]35ñÌëgæ7*zhÊ÷St”af´«:;ÖË\õ/Ùê¸È¦˜#ûçÉAözÊRbbRJ7ì‘}od/u¸V<Ýìlï¸?|^Ù681331ÐZžícg¬§g|Û;êiYËÀäìÜôHg5Te%¦Ñ‚9²Ç07U•äã–Õ0÷ˆL/i잇.#“‘Ë‹s“C5ùI!ÎFª2¢‚"R*F.ë0Gfê–á12¯^-/ÀfÞÜG÷à¨íÉãÇO‹)~†ùÅsÜœœÜ2ÀÙÆd–L &ÏKÈP4¸ÍâÂìÄ`;4YwôEOå™ÁÖ²§Ñ~NÖ–6ÎqÏ*Ú'çæ¦‡;ªŸ'Üs²42²t ˆ^Õw¾fF:«²"]a9Æ œ(÷¦˜«I‹œ’PÒ¿‡ß;ÁÇ–^¬!ÉexPkųXkË’bçeU]ñ12ð¶4?;;‹N9/ÏO4—¦‡»«HðŸàå;¯üæ:r‚'ŽÁÕ`à’\Pß= · §>Cæ ú³ã}ÍeO£<,4dO8-|ñšX³‰YÄÌÂÇÍÐGÑëÌ®žça„óš?nPt<’ÃG….é;„ | ›°ðõA‡Å×™¡¶òŒ7ce‘ãô«ôI0Çö@¾šn+éqKWUIIMÏÚ+:³¬WØì ¢‚â'ážvæ¦ö>Ñ@£æó3£Ýµy‰!î¶æ¶n!‰¹µÝc³‹óŽŠ§aNzò\Œ´tÌÜ"Ÿbžía¦.{^äüE 3÷ˆ 86 µ|iaéîx,/¹‚ä'#59IIyu·UÌ‘“ g&ÇÇÆ'§ç‘yºÑ®—9ñ¾VZ…Ïœâ¿bä¹ó;:ò§xNHkXxÆ ‡¦ÀãÀÓ¦&ÆÆà14tðÒ]µy¢+‰ ò I^¹ VG!H¹fäó³iòÔ ðþ“Ãàº{·¯Ëœa¥$Xw"zZæã›an®)/~^ê²®­_BÎË®‘™EhЧ'FGFÆàdäòòÂÔ`[Ef´ç­ëJe/iš¹E<­@k2ò¹ÉÑÁ¾Þ¾‘‰ÙÅeXïo,J¶×S=>2Ø?0821=;;=ÞŒ ¡5?lÜhEw…öA‰ùu=ãóëéû¾æ ÷æ’'œ±c¥¨¢o‚ùÛ×ÈNJj¸—ý-3skgÿ˜ŒÒæþÉL÷<ÞßÞ䆎¾… 0]ÞÙ…¹ ðdD¸ÚÛ»úGg”4õOÎ/´Z`©ÓltÔ4L›`ãi¡¥ %yQÕÐ1†;sÀ¼ z»»{Ç o_œ6$÷Q€ê%y¥kæî泯޼}Ì8¶775·uŽÏÂÕ2 -Y¸«‘Ф€äUãO1¿qIìì™sW ]ÂÒ…j èjkjj逜¸‹PÑ;¡±°ÑU–•º¨BKHãî±®ìéhkë웚™jõó8/ó«bÜô$ëǸ~º½Ì'ÄUÍ<¢ž–·M¯§ïûv ÃÃ×À±RLYæ ˜¿{³~¤ŠìG|Ý]\=£Sók:Gf—ÈÖòŒHOk}- mc”Ödlv~nòŽ%{º¸x'<ƒçHÌ/B­+xìo¥.~‚…–’šñ¸ˆò*æo°˜[^S”•¹¤M;øm¦ äýíÍ-ý0 [멱(5ÔÙDKYñжÅÝHóåׯa oWSMEyÅËÆÎ¨é`}tVƒ›škÈ‚€ObžµóûÀð ˨›{ÆÀFû™ñX{CuyyemK÷ðÐæ9`³ ’‚Ô.kš8#I6ðæó3ãÍu55õ­`‰MÏL§“ˆtÃ>Œu=ó>DZNyÈу¥ò==B9Ò’õpÐ" À¸¯uc1G§Lºë‹3GÝ ‡ÇòµL.@Ó>Ù×X˜âlzM劚žO,òÓÍÏÁœœÄ0?OO¿°ÄœjؼÀê­ÏMðŠÀDMAÅxL`¿s/Km%99e(¡ :ÚÛZWU^QÝÐ10ÿòÌîZê\U¾ªcéÁ|åœÀj¬,|ñ<§ ¢¡úæ¥98N—àk­­pAXTFÕÄýÌõ.‹ÃÜSÛÚï!äcO ´×•åç<Ï-®nî1Ý"4î t·¸~UYU÷xX f™êj¬.-*.¯i鞘†Æ½äI¨ƒîEì×Z'ä?¯«ºƒ8esBñ7ý×w+³#í•YÑkÁæO?¬ïwÆðLÀi²Å)84ú<-91éɳÂêذ‰˜v%ùÙê]½$¯ •Æv°ñx¤³6?5:ØÏ/…Ú.ˆÞìXw yQ>Æ@ENÅ€bòüõ˜ßº~EáÒUýÛȉÏc³ ³ã„‚ܼ¢ O°`¤£ Dî6ú*jƒ(øÈ¸>ÑßR•Ÿ™š”ò4·¢©|xq~¤&‰ [S”=/«†b>²s% aaqE{&6öƒ›ƒïj(ËIO†ßò%ˆÎç`ueVŒ·¦ºÖÍÛ¾qÙÕª¬Å–ê¢ϲsKkÛúǦ¦'` ŽqAF”ŸÖ·Ë [ª4üunßPÓõI×€êå?¾g«-s† Ãʵæàp8øíë+Šòsó‹*Pò¡•× jDtÀLó’´”¼š±Ëx û Ü{ꋟ&„‡%<-nèAІ¦j—Îq¦ £D0÷Úˆy¬·•ŽŠâeuCû ¤‚†>ÌÃÁªò¼¬ôŒgU`¥?:®üí µÔ4ô¬Í-oìŠ"wàHî+ qÒÚ8º·Ê¸i ÃÓQÎÆï¢èß-ÏÌÒœo* q!ãf?n†ù{„•`v|°«¥¡¶¦¶¡µ Ë÷êõÊ2P]OÁ^SX«QÖ·N.ñ ÜûšÊ²“¢Ã¢“²Ëš!z 3Ãí U“;Ë÷²è7Ç\÷êeeMc§Ð$X‘A{MaVÊãÄ'Ù%õ]HS30Ø {r0ÖV×Ô·öDN Fúá[+s’¢Bƒ#?+k†Ÿ†Ø–> uº©"sAü¢º©{Ô'˜Ã÷–¹ŠvÚ†à½ÇzJ2††Æ¤æ½ì‚ëw¬daANæúº0LM‚þÌÜÌxkU^Fb|BRVQMûàøÔäX‹ñ^f*"Géˆp?Á=â–Îqø?|QÉ_Þ}üÆFqÕÏ@£ºfÜAfñæ¿~|ÿzinrÄÏ] €Ff¦1E2¤Õ梨À91…ë¶ó¡zgõåIBttBZ.tþÐ&µ–¢ÄNl4¤$äôG?Ãüy¬·õ U¥+×Ì\¤•´€O€ûZ*^Á1iE0óVÖ—¸©ÊŠKÈ}޹Ái¨ÿ&nà›Âþà“jòS¢½½üî?|V_>v¹Þ2Ô7´rB¸Þq€ùXoSùóä¸È¨ø'¹•-}£“#@úZ¨Š?LŒ»kæXEç½9A"3µôæÛ+:†á¾§fs ©¿n‚9Bû)xŒŽŽO!B+ËÅŽ—/Øð÷ì©Ó‚2è¬Ãâ@æRS˜™ôðarfamÆÎ6C~.É,T$Ä_Ä\Mùêus`Ë`°•¬±4+1:,<69§¢¥®'Hhêb~CKû¦WtV%¶¿¤“‘þî.n¾aIy5]†Ãm Y3V““”×0½û9æÄåÔÍÀ¯F{¦†Ú«rÝ÷vuñ¸“QÜ„®_„^ÉÝÚø¦ Z‚è˜OB‹ð(òþƒ¨Äg¥(+a]Þ#¿[jç¹é?åÜÀ(:õ>™kÖ~ñ9/á4î¿5§ö?A™`^AvBo_—åcÃP±}Ž9< rü¬,-Ì!•ÍEL=8ÐÂÔ0WUI~ÞcÜ|âhß{×( Üá°+ù’ÓrJºG°ÄSÁOr‡‘‚˜ˆìðf˜ûXë©]QÕ±X9,‹ÁŠ^}QFBxHh4Ø“°ô÷ÀÍRïÚÌGÑ >'{GÏG9°Ü‹ ±U>p3U‡E; ³˜‡Ìe.HÈk𣝠rLàʳâƒî:Ø»øF<õ6ˆ9Rjô°552½í•tn / "–Œ¢ÚŽ‘‘ÁΠ?·Ô/ðlLÐQEÿiÇ|R†ã"J†Î`é nµ|Ë0{Òâ|£.p²gÀ?l†ùïØ£ùÐ tßjr¸»¡äi´·•¶œ /Ç΢È&0’ðÇí…'¤§gåW6÷ÂJ%’7Å{›«ˆ£'':´)æq>6zê*ªØh™Bëª+H‹½‚Á’Æ^Ć Tvî·ôµ¯Úx£˜Ï£µ“G!wímï¸Æ?«„A°Õ•™‘îfòRRŸcî0—PвðŠ}biXWh){àbwÛÁëAr~]7xXcàyž·ÍŒÍì¼"ÒKšW1O»ï^hljnUs÷À@okUN‚Ëþ sÌD ¶3Æþºõo„ùØã_{ÛVõÄÜÊÑ^¨¨Ã/óR¢‚üüCbÓ‹º±˜§GxXlÀ\Y•ÏÕö–“_ r†,T~ˆ¹‡9Ä\a3Ìeæ×nÁ,æ%Q¾N6Öw!,Á6O`º¤€þvŠŽQrÈ 9>ÐQ_úì!<á LSÜm;X$¯€CŸ™š„]JÝm yé±N&êÒü\ Tä´l§$5mîÁâÆ´’ÃÝMU%%ÕMÝ# „[€óáo\äc£&" $¥ãÚs s¸‘0zµW¿HŽ ôõ˜Ö# Es£õ˜Ãü{.6–ÖN~ÑOËPÌG;1˜KKo޹„óñ¹ÍõÁ<bndjë†p]#q{cIfÂ}ß ˆG¹Åå¥yq o½tŽkÃúzEGöTõáÞ\vOõ_œ(þw!ÿðv¤^Ã=­5EYBÝ-G†G×ìqû¦˜ÿ†@¾4ô{d6–6ÕVd%EÂTÙsǘ(Iˆˆ)™x/¨Yú>Ì«ï,SµÖþí}yŽf{•Ö ®X‚3kc0¿<â«|¯h}7Ö?¨¹{ëzIÞùÄH?W+ýòb|Ë—.f`båݪ},V Ž^u·5Þ¿{ûvÕàÃÂak Rv> *ÅÇÆÄ°x"̱ gÍ:äó¾xÞòðVAÆÙp¼Î¯ç9`y¿¹ÿW0w¤`>v{þh»½gÜWâ\¬-­NFfâÕÿrð¨Œf ó÷ñ 9—œššáë|hŸ¢$?Û(fâ1 çT‘ÝãŽëž{† 5L߆+Þ§tXåÀ¬ )¨Ê²¼Œ¸O;Ó}*±–¢‡»L‰É`F±çmMïUV”•äf§ÆGœ°ƒÞ Q;\´ˆž™SxóÞ#> ùÈÓE ½ùaMUUMý8α«v•ð^yXé39øjÈ?÷ŽÉ¾VÓ„üŸö¦ÚWÒ΄‡Fa®{pš0Ç)8€*‹2b‚¼½ý"°N æ]Šów>rÈâ˜GXj< :u€Fñ¹p¿“ž^§üƒBÂý<,t·Ë ¯WÀ /ô…‹ÙÖ«èÛújý3àëVF`¯idšrÈ Íž×Tf'žöq9 öÂ<ì¤dá—˜¿ûãOLÙõ´¾º¢4ÿbVj™È`w; ƒÝÊ2ëx9–Ò-¤¦†ÎOÌGrù6ö’_tµ4<¨©­{ÜÇù«Áž§÷ŠS!2¹jÃø˜CLfŸ¶‘µÇéô¢Û›ZZšÞ½v)%6,8,&åò:œœŸF̰›p."0046½âpC¯HÌÑYbfvÔ5(ñÊmÂ3ÁF\Òi?ãÎÎXOÖÉÎÊD[]„ú£È9ƒÒà ¼IÓÜ-ö‹î—o>|üý#ð±¾.È¿¾dïžÌ1¥ÈKp¤®å¦Æ¸3ÕVß²~-7æÕ£8óß™¤7o—^ÎLŒöõ:îpÔÌ@Km³ñ—óçÐ>œˆ¹xRàãÐô¨®çæ©€ ÄÞÍœÌô´ Ìœ`®»wŸ¥KPÂ¥kwï×××Uß(ÊNŒ ?“1bÝMæœïièÚÙÈÓ1I9W« Œ×zT\ßÐÄÄÒÑ÷,”ð2”DûŸp´9rØâ¹©1(ŠnœH“¨’ZÌÆ/©¬g㟋s¯ß¾ÅÎÐð«Êö)†ü¿þó×ï9ž a'éíR”äbc¢4Ñ’q¸Õ£1ÿãwhK«¿]”p:Ð븽µLÝ,½N€tˆçÍ™3šývc×ÓdryèÏ Š¤±åYï Ô"@t$ÎÛr·ÜšKèhéÇÅòjÚ»wk£Í=2õòÕŠ;•·Ë‹s3â£ÂÂ"㳊€¤¯»iÁ¼wèõk̶Rz)-1!å|A%è¿õãD”ùCSÏH¨nê†8-ÚÜK³Ï°³=¯¼™íðùLæÃoß½~ÑÑX{ûZqqiEÕ£–n²þ«þFNìIcí½:í|ÐæŽµDì 3ÚÇÁ\w§Ò&iqáÕ|«V°.!K'PmŸ9’SuNÊ»qïQSsÓãú÷kàCðõûï&›s\ùˆü‘¼Ä`×CÚjr°­<;åú/1ûñã;œË8íqÔPSi“”(f×acÆßÊlLN1kÞ¢%œ" û­ý’ !Ë‚ŽÁÁÏŸµ·cÕ•7¯‡^´a¾HP:be˜s¨ÔTÞºMMËÐÒÁÃ?42öìÙ3±ÑQQ1q8cÙ†Öâ›WóoñÏÿæÐŠõáý›ÁžöÆUwîÜ­}ÔÒÕÿ šx(Åý†{4÷è“Zf„;Úò-„ ã–z;¤×­æYÉ΂y­&¡/!§,tÈ©î?ìq:íÊõ;ÕÕUwn–—•ݸSû¸­gpÊ‹#)=È5¥™§ÑóªÊˆð.g¦P&þö+¥å Ì?`¸ü„cÍ­Ò"Ü+ØY–ØÈ·•L+ÖÊkYŠ¿r›>Ðiô¼«ëù‹è@ÆQmizˆ=$—ÑÓ,ü„yMë'®°D?[C­rr›•wé<âèvÒ/0$,â4@ž %]¯Þ¢½öÙô`þ v´×=O××?jn{Þ7üöÃG¢¡ãB´§•Þ.u½&öþ/‚Ü>,ôªą̂“ÇìRZó#¾€Â7æŸrªÕ mOE§]*,))ÎÏ͹s¹èFuCû‹áwSÜî€ÛÔ;nçAc†º,¦[HAœø2ÇÁüîi€Þ¶#ÚJÖðpßÞ¢…ÔTø/ -²Xv×!O\rÒ7 EÊýÈ6ÖtN¾ì~rŠH´¡ˆ„náBÚ 0?¦¿]^Zr½Œ‚š–ž©•‹Ç)ÿˆ˜ø”ì+¸N%æd×y=Qy›Qç‘~ä×0GÂÕô`,ÇŠg¿}&Q€Œ8ÀÝ ®E8¨ø i­§¶QˆäßšTÓƒàz¦[ÊÁ³VBF^Aaó&YÙÍ*{:%Þmîzÿçzè˜E¦§:lu)è}.]6 æAvºÛ@#~¬P1Å ˜»qù9àfº|¶ýNQô¢J-µäG³Š™vÁ|*À\a<Ì÷æ+8y1W˜­Wx"òË4AÏ!fÃý÷4bŽÏó?Ab:šŸ£³ýI}²Ó¼j+I­áãX'£¦oã“}KÃöw·‘Gú~%)È®Lª N¼+PZ[ÂÎÅ¿f¨è:áµkE$äTuq+}Cçà”èÈ„{?ÔÝt÷ аmÓl1˜ÏBu™æ8Ë+XâßQ`ä·ÑVXǹdÕ(ÌK¾ÜÛ¹¹x„$¶ì4´öK¼xµòá“g½˜ù¯ùÛÁi¶Û¡£¹íé<š›êªÊ¯¤†cGø+ùEåw9øÇ],«izÖÛ?Ð×ÕRwóJRˆ«º€‡uñÂyãÆ]Ç,ô™s€Sxç*nîU\œ\<«Å7kš¹>½®½ÿÍTÒÍà&†Nö>´‹™þRŠôû0ÿå×ßzu“ã‘Ùå;ˆÌð¿@+˱Ë;¬bùµÈŠ š7 æÛ$…@»s“ºþ‘¡ò»Èz{ñò5©>­˜cõÀW=O?¼_[ƒ¦¥++È:ëïh¢!'ÌÍÁʶR@\a·™sPBnùýfèM„ú_Ìqc„®àb¦›TüRvlÎ<êEô d°l3ó2à£R3tN-©Aúzè„ ×ñ°üüi×Ï”ágü0æØYcáÅYBcåÖ‰Ãn¿¿{ g½,4å„8ΟGE3æ˜+Lh­¸œšÎa·ÄKתµvf6ðáN/æ”8ÑŒ\^†ÆõòëeW‹óÎ'„{Zë©nâd]º”e¥ ”â>‹ã¡Éyžv¾è‡ `ä¥Çx[ë© ÿwÜÜù— Ïž "tôth0,fóýÖ¾Pp2¥:‘!ªU ”%x°øÌ˜ÿòݘϧeæSÔ³J+©Å‡ã¿þMˆ÷L°ˆŸ1Pƒĸ˜o”²Œ¸¨„¬ÊþC®Á —ʪÚ /Gª㆚i܈·w·>ª¾Q’Ÿ›“sñRîåË—/]ÈHˆò#jžp §´ŠŽÕ‰ˆÔü[[º^ô÷u·7TÀð´»3PÕŒ#äæ‚0Ô4ôÌœk7iZxÆ^º9µ:nDí~`t®nåZJ6œO æÈpGÎÚ:mÐíxҲ鸪0ÇáK‚Gúñ'ÁÜFWm“´´œÒS§€ø‹eÕÛ»û1› NèvO“ GÉ«µ7T_¿’šxî\BRJZzzZò¹è`O[cÍ-b¼lô*aÙíúÖ^‘EШÖ×ß÷¼åáÍ¼Ä G㲘ò« Kþ€t ‘à@Ç¢ky ÿ5…˜¿Š9Þ‡÷ȯ]IÔfN!æ4L+×Êï±:Í_ÝC”:^ÌNxˆN†*àßÏs‚CH‡Â&y%M#[ŸXd?nïé944ôô`Ÿ¶˜ ÂüÍÛ׃]͵åyéñQa¡aQ±qçÎÅE‡ù?b¨\®Œ´44tLì¼¢òlOÅd•Ü>à8ºÓØ5uáW0áÞ@º +ÂŇÃÌ™RÌI•;S÷蜊GÏß’7Žƒü½Ä‘²_A„s Íü9b`o¬¥®¦¡mîL°#Èû{»:ÚžiàÕ;PÙœ&Ì{2’û×/%„rwuu÷‹:æçvÔp§¼(; Í"äZs¯Ý¨¢mq<$é2²ãð‘Þú°"7Þ÷,²1PÏ|sÿôòð6\bh¹ì2sºº’SF%†ë%^´ê¦8†:{ŒšüaFÜzV¾õjÆ.èTBÎ…Õ”Œ T^!E•˜Bù ®°øó¤@‡ƒû±:§wl6t0À*ï#號âÀÌ´åR毀t¨ª$3ÆßÝÑÞÁÕ+ ’ÏÅ„xÙÔÚ*!°‚y12³c‰-; m¼"Ó‹î:Ë–ˆ5)¸Ö'ÆG‚®Ÿ?íB¸jTsfM„yJ°ëa##K瀲±t¨¯³ùþ­ÒüË— ­ íîÓTYCö.ÕWäž r·³¶qô >›™WzãfEYaö¹`Wó½ŠÒ"¼<¼ü‚kÖ‰¯—UPÝ­oéìw&ûê=Xè=˜/ÒÛj/Pó|ûæÎÀÊ+©¤‹îðò­úŽ)b”A#ÂÕX>p„‡vj0Ÿ %qk6i‘ŒÙ8œ˜£yÑŸc6@eäªÑNŒyQJˆÛ‘ƒF¦Ö'ÂÒŠp%åð`O[}einVzÆ…‚5Í@X1˜¿ìkxãR| ›­õ1çSI—®Þ¾W[[u£ #Ê먾ºüzQ‘ub’Ò2›¶lSÞ¾k¯Á!;¯ˆ”ü[õèDìëjª.J ²7P‘$I~ÿ>æ„:ÁTbÏ1ñÈïî¤ËTƒ]&(D¤¥úÄ7=E˜ÃßÉ€Ò¥[ àqæ8úGÌkKüùüÙ`^]”ênmv³3]«ÅUòý˜,=!.>%§ônCÁ!:m˜mÉŸ€ã¶Ö¶Çýb2 oÕ>jl|XUv(ãw+ËËÈÈmQTݱKkï>m]}ãCÇ܃À_{ÒÕ78Ð47‘n°‰Žöƒÿæ9(¿pâ÷Þ)1ã×Zc—i|è¾ó_È’8iu‚xàÙ8kD²j…”yiæÍž9k"ÌÃNØ2·rò=ƒWÐfû¢£áNÑy ‹EFÁü‹þó©ÂÜÎÆÞ=(>çZuCK[Û“ºÛ)¡®:*J*êšû ŒÍYX27?duÌÕ7:£¨Y›ƒÐ¨zûò˜x×߯|¤¿eçAg°^ {ñÇ[ZÀe¢˜p»‘ Ç8†nz 0'³,Rª˜x`$'HDÿ€bâÔa-äªy3 æÇ,-ŽºÄ瘽ìi­«ÈK‰ ŽJÊ-kÚ1?àfwÌÁ#$9ßOž=ïjo¬ÆµýöjîÚ£grØÖ鸻»›‹£Ý±cv.ÞáÉ@öß30ˆ¶$d…?Þ;Šú¯ÄBç“Ø¶ÿ°gTÖÕ{#»ûŽÍgÌÐF˜p Ôs¦óùP§D´";HïHWíVn,°âèß„˜§…{ض´9”€i&††qÏ͹0¿S~añ$ÓÃ:w³·uò OÉ¿ý¨­xßj˲c¼mMõut-íÜ}‚ÃOG„ùx¸:9ºz…Äç\¯…â¸n Õ$­:ªo1â0 0.{ßnhë‡{ï§`w'£pÊ2ÃqÛ?6áfŒÌ9%˜ã’¸u$ñÀÓÞWæ#ß° àî$˜{ÚY>æ’4B Ô\]’u&ÈÛÓ;øìù«DÿÈ7cþ5ž‰Îó@w{;gÂÊÇÏÀõn­+Ï9ãëhaltÐÊédHlbZFzr|d·»ëqÏÀ3çK«›ºú^"¯rt"é0'û[èY¸×mÖ4u IÉ¿ÓÐÑ÷ûû(SÊNol]ÔTaNdY(ÄÍÝØYÃI{JÄ+ÌSÍžùÛ¤˜[ÙžMοCò»5Þ-ʈö÷t÷ ˆÍ*½×üí˜ÿm>Ì!Dbî`ïr*2¨ûú–ÀÆçç|ÄÜÜÊñdX|æåÂbä´'Ç{»»yÄdÝ}ü¬˜'È„ñvø´¿s\öÎÈÎ/©¸ßÊ#2¾¢Á·?(ÄFD@›ÑGÙºÆ( ̘"Ì?•Ä‘$æoF%í1‰ÖßÁ÷~¾އ´(ßÇ=ücЛø æ¡ïæj«C¾ZÐ GWŸ¨Œb<ߟŒµ%Ú‚Ïfæ_¿uçvyQNR¤¿× Oÿ¨4dt!#îe_çc¬=² '°F‘yÿ-б·Rh£š­/²Ý‘é2ôîpØþ‹ÒµtóR¬'«,ÿÒ„›ÌqII<ÐÐ ÎÚˆý…¨»ÔÙF7sLUYI`žõÌ+!lêêèâ üpŸñù%æ‡3¶G‡Qõh®0g'7ßhàúŒCÈádDòåëUÖUWfÅ…œòôQ‚Û:°®T#¥oƒpÖþ>æd5,=˪uò»:C‚©¾½ïÕ‡ï<Ò‰ö¿þø@öc¸š¨Ã±:ê;œ̉’8VþõÛM€xàavÖþƒ]5‚b‚²¿LˆyqžÛ¢óøÓHÌa{¸yÆN~ž'`QZ¤›óqo0¿>ñ@úNÆ©¶›àl&0¯¯¸œzÒÕå„?ð@>%È!aoG˜±vöA®ÙíºÆ'MõUe—’Nû{{D¥0¦ $ð W‡ñ[1§ìîÈvßgyâtFqU#4¯}{8ãý‚­õ !,d¯O©‹÷8ÿÌÇ:k#%qØYûϨB -ÒEœ slà ‡éáðyàål¸ìk5O&´Û1­;¤Ä<ŽŸð‹L+Í÷êcc 9ßk…ïÕΟà{&ø^“#|Üݼ‚â.”´g˜ûó¬?ð½:û¡/¡úqk{[óƒ›ùé1Á¾¾AÑi·Q0ÏǬ‡k¾sÊî¾dÅê ªú6§Î\'eèÝ·9lŸðþëÏß?‚"|˃—ÎzÙG$Õ¦s²$ˆj[_¼äß"W ˜9ç&Ã|´¯ö^cuÉù³!>Þ~á ËïO쟷(·¢íÖ;(6³¤ ³W‘¼ÎÖ$¯ó8\ÞÊ*»Ð·Å‡Ï ^ç;…iÑ'½|Â.•? yë®_8ãë|ÔÊâëe5Í]­ wK²ã#BÏdç9H unù=뜴ÝÐ/ã–·äõ·¡7CTž|#ÞMëë¡þç­õwÐKjW¡åL G‹AMæ¿bâ q…’¸§¸ çߨU#D Õ¥Øp–ñ×™ÄÛAÍËÒK/‚ †…ÚjÊ.&ž ‰IÍ#âpÃãa>ŒéÊÑvèq.m  »2Âß¾“àoÿ\§a?Áßîÿ‰¿½ªä|\˜¿_Ptêè>%…ÃÎGŸr°²<âèƒóúÝÝM5×s“cÂÃ"Ïe—`»”á<'3ÆßdÃïèdãWØsÈ-< dlÞü-‡mï‘x¿y54Ð×Ýñäaeiö=e©Ï*9¦óO%q•Mà¬ýëß„«FPLHñµ¶“ancq舳Ü¥ ’/¿µ®âJÆÙȈ¨„óÅ•`—‘ñö¸Ï1­œ[q§C#ΤåUô)«è=ƒ²¯‡ã¼ÿ3‚÷»7Xëïù³Öæúš›ÅâƒHå^,‰ûi²ïÅü g(‰âÛ ÖPȪµÖ–¦‘¸TlbÌCÝ­™v„xûCˆ·¿|ÑÑp·ôb깸ȼëГOÉ¥ŽÅÿçÎÆêk—ÒâÏ ¤c \O´ŽO¢Ç²mÛvíÞ1#z,moe'=s.Ï×õX *ìnmjbfãó^½/ºÛª®åf$%¦dÞ¬#8Æ;U€f zú†˜ åeÂîNÇ̹Vv‡‘½ÿ¹ÜŠº¶ÉÃqcñþHàÝðn{ÚX¿êfi^æÙ ·ÃÚÊë8±ôõÔc>‡š]`â$ µÿú×_¡E®ˆˆCc’I0O u;jvÐÂÞ;:»ìæ‚ÕÄuƒìæG\„ª’„«6óâ‘uõíAX_ÍÈÚ3G+ H|uwuvu÷íÀ|9„oú_¿ÿðáÝë!d³v>ëêNº·oñÖ^˜èp@cËi9åýG>Ó¼Çúç’ë7ï8`ú¬àòaŠ3ô#]@úçÏŸÖ\ÍŠ-!öt°¯§+ÂÛXoï.5ÅÍ2ëÅÖ òrr°Œfvs™‡<8t¨‚ülKMqJ€-¯OŒ9β%qQÀYUrÂUÓ†")Ib.¼3…V?AÇ4È5V^Ið·7Ù»sç^£c§‡•P;àî®aÌÝr½XkËkð8ûÇ#7¶q_ú@ƒƒ/‡€ýçõЋg閭8ܵEJTLzÛÞÃ^gs]~p»U®&›Ä……%äw;¡ìö#ðÍÐöŽ~g8„€¿ôYcui&Zæû”6ˆ‰ˆmTÖ¶òŠ>í²ï^BëäË—èÊa¥xt;?9ØÑH}ƒ ãÂï;Î!w÷y4‹ÙøÄ·îµpH+ªlè ê@óþÞ·ïì´Ä3oû£‡ŒõöíÚ®´Yv=&Ÿ]Éβ”‘óNÈLù svA ó©hïzýjðy3a’*¬ãZ21æ£Xâ"ÎCfí Úp){«ÆD3w&‰9TWì;â›Î¨>ƒcÓçØÝjªÈT M-¬·w˜%Hù½ûðáý›¡ž–û khoarÐÊ%¯×4uôô  Çp½Ä”N¸YTe£èZaI- ÏØ‹7ëÑYLh•‡9«ËŠ®^-,­´ß yF%U¸'ÿõ0!Ùû¬¹¶üR<°fʉ®X-¶IÃÈ! áòûÍÏ`¥ÁÇn¯¿»­¡ª4+Êóð¾­bÜ,ãÉ4| ès PJPZEÏÚ;æ<×¼ ßÃ`_ÏsÀ»þ~õíW /g§#¼Cü¼Ž;X[˜èïÓÜ®´á-²šŸ{%+ó’Åô‹. y>'ëˆ'š !²ây¾ìþÓç°,:îäóù¢Úb<ÌYx$pf Ê _¿ÿ–(¼ô"²öÔÌâ -:÷±· Tvv?ïÑÏGCµÂü¼¢r;ØùÅå\«jhíêEPâÞçmµ7ò’B\͵$¹¹¸WKÂâ OÍ¿y¿©ýy/š n¯¯·«µ¡êZNœŸýõ«ÁjŸ=9ëÀ¤ ÿŠÃqlÑ0²âš{Ûž÷öàÕ3Ä!ñ¾žt6rïýšêÊ[ä¤ÅE„ø¹9—³-CxÓÒP#À猰u~s6~i5cçд⻠m]ÝÏŸ5ß/Ïùd\Æ;º/ç ÌiG9è}¯Þ!Ï ÚŸÉÞ¢bˆbëÉiòŒ¹PVÛÔñ¼ûyûcbݯºYn³ÊS§ÀsÀ2ñ´ù™J`,!<ûûz;Ÿ>¼]îa}ÐÀÐô˜GxJÞõª ›ššŸ¶¶#®§ÝrËãšWRÂÝ-Aò—‡_DNÃÄ9Zwž¶·=©»…i[=åõBܜ܂ Zf.Açr®VÖ5µu>G΋ޞn Œª¾ž—áa¥£,-ÄÅÁƱjÍU=ë“Qéù5èÞ:ÑT=€DS]åÕ‹ A.fZ[DI«ï…œH¥Ï[¸˜•Wl º­àÄܲªúæ–¶Ö–'îWßÁxg$ õ?éæhc |úêÊ rÒoŒ7#Â-ðy$à¿Î˜r„94˜òJ*ØœË-¿×ÐÜò´©îNQz„›ù.Y¡ŸÉ¹±áÈuŽÙ ¯ÞGùÛw¯ú;°4äHûƒÆA×°´‚[Ÿ¶n@¡]ÿÈþmü+9–¯Z¯´ßâxp|vQEuÝc4è ?¬¹EhÈé«Ë ƒà'3;Ï:´»ÛžŠJË+»Sû¨éiKkk 2«+Š.œ >nIá ûjÿùW@'’-RJû-ÝC/–Þª®}p¿¦úNŵ¢<„wÜ'¼µA?AnƒÄº5$Þ €7‰÷ß0ÇÑ4n±­È˜LÏ/¿[óàþ½[¥9ñFÛ‘}²xÌgœÅ¬|’*†Ž¡sä_¿U GD$ñu å¡j`çw6»øfõýµU7 3cN3Pß$¹n”i{PgÏ^]S[¯ðs™—ò ‹¯–߬¬ª©­½Wu«¬ ;!ÜÓÆpÇ&Q¾lì+$µœŒÊÈ/¿Su÷ÖÕÜä°ã滲˜…ÚÉUu,]bÓr‹Ëï ÷[W‡º²¢4/3>ÄkÈñ²/a cXÊÁ'¾EÓØÎ;"ñº·»5÷ï߯EK¯$7ýLàñúª×p2ÓýÐ2ÿ…ØÝç@²eÍF5=ôÎeå_-»VR˜—“™Làítì°©¡¶ÖB?áÍ…ð^ÊÈ@·ho‚÷뀘ÍS»:ùŸÉÈ-¾vyÃ<¬´%xG±MÁhp8CpÃvcçðL`ú~ƒ,®§÷ŠG2,Xœ”åߺÏÒ=äÜù+¥×¯—•äeƹګ(-,(¸Vr³†Á·ÀØ”ìÜü¢‚|4 ‹KËÊoܸ~µðbj´Ÿ³¥þn Pñp:›œš’œ””œšy!÷JQIii 2o’cÝîÜ,Æ¿‚e)¬PyMSçôD…%Åù9)Q§l ·oâdabZÊÎ#,£ª}ÈÑ;,.í•¢«e×/*ÿbƹÓ~®V›Å€´™f! ýÒ’[5mNÅ$Ÿ¿\XrõÚµÒ¢+9éñá§œ,tÔdE¸YIþÏÁ|$'"‡>~·À脴̬Œ´¤¸è°owŒ·Ž–Â{£¤è^.àÓÿ^¼ñ„¸3–Cp½ŠÎa×€¨„ôó²3SΆzÙíÜ$ÂÕú3g|Žù L…ô†Ë@JÊ KåÛû†ß‰we>IBH‰Hâs)¤ Øx„œIÎȾp>#AtD™ÇÜ\<«%6ïгt:—”œ”˜€FRJzVöt3©ñ¾®V†»Õ””¶kX:zø…„‡G9—œšž–šäåh©·c³8ÿŠeŒ‹—­Ú ªku=QjFzJ\„“ùž­Kàêâ‘QÙkb}Ü'4ú\rzfÖù¬Œ”„Øp?w[3uy1þåK騩æS-¤g^)(©°Íê—”–‘™™žr.&Ì×Íæà>Y6FR³ì0 DZñŠÊ£÷àèåìïM °i¨n“Gx#œ‹ÐOÀxÏÿ¼ñ|xçeAËBCÿ°ÓÉ€°È¨¨ˆ`_·c÷(J ,g¢™?Ú&% ÷¿“#ŽîÞ¾þþ~¾¾¾~þA!á§£¢£"Bü<- v«l–“Û¢¢©gvÔÞÉÙÙÑÞÖÖÎÁÅÝË'}AþÞîVÆ{Õ6¡Uμ˜v¼? +ç“¡áaè'ìé¨Ê¬åbaX´ý+÷Zé­;uM­<|CÂ#"ÂC}=]ŽÂr|Ë1ùœ¹ó2,[)(.¿}Ÿ±•ƒ›·phXXH §³™Þ.Å „LÝø<ß :TÇqð‰mÚ¾ßÄÊÎÉÅÅÙÞæ°ÙÝ=o)À{ÕÞÔ#xÿ½üKÌq-ž€ä Ó£ö.nîîÇŽYj)aµÅ/ØÏ0ê¿Îœ…^ýÒå|b›w™8§ÞÅ~7è°àèÇv‚„ð7sjâZ#­¸Û࣫»»›«ƒ5zm[× ­bc^²”u%ÿºÛ4´˜[YÛ³±¶>zÔú˜£óq7w·ãN¶‡Mtv*ÊJ‰‹KÉnSߣwÀÄÄø€žžž¡±™åÑcvv6Vf†ûvlÝ Â»|)= 5P°­@ TC=‘³« ú ãýÛ!ÇÈDKMEEMËÄʵZ\Ny—¶‘ù['ggG»£ƺ»U7K­åáõ¹9³fÍšƒ@g^îm뎽†fV6öŽNN¶èBM•M’èÞ™hLäÄîŽ6ô>EewîÓ726> ¯½g§š¢¼ŒØ àÂ<¢—ðþÛÛ¸˜ƒ§À°Œsµ¤¼Š¦¶‰©™ÙA#½=êèލ-޽?r*†e+øÅ6©ë[ŸŒÉ&R8ƒ]’JÛŽ4qa»í\hm)lߣ{à ™™©‰¡ÎnµÍRkV±¡Í–~13Ï ÙmÛwíÙ¯­£½ÿ¾}ûuô ŒLLMMéïq7Q!AÁ5¢ëå¶©ªkh¨«©())©lß¹{ŸŽž¾¾z=ª 2â«WúÕ¼ù h³p IÊ«¢'221A?±SIíKéÑú;oÁ¢ÅËVð­•”C3îÕ58`dtÀ@wßnuEùõ"œ¬èØ7½Óßtú¥ìÜBâTwîÑÖ304Ô×ݧ©®¸IJ˜% ã¢i.};è³çôÏ’r[U¶«oWSÞ xã+Âp¬—ñCx“³Aˆ‘…SPTZ^½Ð;Ô”d$Öàïý‹ÏxñQR-ZŒþD\^]ÏÊ= —xô á‚¥;WÈhÎHR™øˆi—À»“Ù¢¤¶M¡®ª¸yƒØj9=ÍBd,1±®ä[#&-»YaëÖ­ [¶lQئ¤¢¦¾CCcÇvd¼ Äy¹V®\Å»ZDBz£ŒÌFiI q L°UQIIqëfÙõ¢B<+XiaSžƒPedåÝ€žh»º:zrR"|+–1ЀæÄlôÁÒ/aãÄ3nÙ¦¬ªª¦ª¬¸Enƒ¸°À*æÅ‹ä¿’-#Ë žÕë¤d6oURVQQÞ±µü\ìKAfkÖT@N‰ÁRÓ-aãâ_+&%---%!ºv5÷¼ ½Œ›˜ #²?˜ˆ¸Ô†27¬Gö!?'Ž|ñÏ üI¼¤ycå’˜{ãAK7VCn«+¿}œäž5"ôÏÃÄÊÉ'´Nb=1…„ÈjÞ•,LhYΟOE½ˆa)ÛJ!a »^'""²NT\RJzÃÆ¤%Å„y8ÙY–-ca_ÉÍ'($$$(ÀÇËÃÃÇ¿zˆ¨˜¸¸˜¨°?ˆÑÓPÍCë`Öìy h)O$½=ØZ@“iæL´~©i£¹ù…DD%$¥¤$%ÄDÀåegf¤ÃêY8–… Õèû`Y¾Šoµ°(úÌ$Äñ…+Ù‘Ù@¦¯¦r ¼$ÚÅÌìœÜ|ü|<«È€*°}“þÕÉ0" ÐKg]ÎÅÃ' ¸ZPÎæñsr3FN¡õÛvY#¿x®;û‡(!ކ €=ŠS˜ ³FÏöb/?L!ÀÏõ‚m)Vþš3{Î<ª…´‹—°°/_ÉÉ…'''×*n^>t)º’›s9|ì ô‹™–²°s¬X±œƒ•………•c'7\+‘¿ÊùP¾úí·1O„^ /7‘q"ЄŸ‹fdX²Œm9ç*^4xVq"iÉb:2+EˆÖsç'33+ÇJ.nnòBÚ…TXÏhj ÿ…üÀ¨hè—.cecgc]F®ï)ÆûÓdø01³°²³spÀ„Ÿž~ƘVŸ Ô6L³Ÿt© Òpš¿b8¬þ²Šù› Ig½€†nñf6˜ Lxosˆ…7­uºÅŒK–ÂX²dÉRæeSv4ÐÍÖ*‹hé321112ÐÓÓÑÑ3,f‚ —1/Åñ(2£D-Ž<ñŸ2NøßñŒ4t ŒKÐDh,c^‚M$*Š^Ú âÆ1'3²héñTËÈ ©©&VûnæÌ_°–Ž zºEÓ‚÷§ÉˆW@ ¯qñb0d. †ç›&¼?M†Þš ÍFM™püœæ´#H lNF¦]¹QÓˆ«‡±Šì¥8]% ^Ö1Dy#¯ø¬‰)¨¨æäˆ•‡î`.‚u>î|L}=r3sæÌ†nÆÜ¹$Gò\¸þ„òzȾ üú€>›jÔ ¤$ ÒÝßfâ)ÑP¡?1*G1êq)¤Ìä•]88Ì o Ù£.S8ËèÉÐsQf5á—Ó͘1"ô«DöX¸‡¥äݨmê *Äz@},5ÔÕt„stЖ|Éc§ømDííWâßGO—Î÷ï#WÌ„ÿ?ÿ_øf}ŠOŒÿDø’Ñ(°ãÄX~ã/©Ï¯œðÂ)Áa„ÿù·ß¾=Àö“³‘tÓøýN0!ÑËÀÆ'©¢oë—Sv¯×aI¢Òó1'`†k挶þÈÅ7ÑÜáò>]ûEÞò/¿Žý¿ä“çýì‰FÞàMþÀ—?1öÊŸ®œøÂŸ^¹¦aŽÏf™\|ã^H¤F7¨;CïÐØ¾|ÙßÓÞx¯,'ÎßÞh"&ûñfø"Â7vŒ¹ôkc²'ÿ’/¦üú úú…?8¦†qçœüß R‘Õ²;ÍÜNg–ââ–þþ]­ Õe—pÁW+þ›ŸèçøÑ1ã×YPö"$§yèDtä·uuwwµ7?„Ò‘\Gðw”i~ŽÿCãææî‘™%•›[ÛZšÕÜ,ÊŽ>~X‡RGðašŸãÿРìí2;M]ÃR¯Ü¨~P_ÿàÞ­«—Ócý]-uÔd„W±üxÁÏñ¿i6œÀ†íüqqÚ­›å%y™ç½Ì÷«ÊŠp³1Ò|UQòçø¿4HÁ{>I%#aç2/æåå^H;wÚÏÍÚxÒFa¢täçÎþO3fÌœ1™u›w™Øy…DÇ+úï®ìø9þ'Æß¯*úÿeü?H¹‘ endstream endobj 9 0 obj 36751 endobj 5 0 obj << /Length 10 0 R /Filter /FlateDecode /Type /XObject /Subtype /Image /Width 500 /Height 212 /ColorSpace /DeviceRGB /Interpolate true /BitsPerComponent 8 /SMask 8 0 R >> stream xœí½Án9²5ìwºËos·½˜Ù 0zú¶Õ]ÉÈË@¸Fß­ž§_r~"xþÃÌT©ªdK6c!¤²2“d0â0 ß½›4iÒ¤I“&Mš4iÒ¤I·§ÿlô­k1iÒ¤I“®¥ æBwwwú﷮ݤI“&MzÌÏ#"ßÿÖ54iÒ¤IOlõ Ýÿýßÿ]ÁþÓO?ÝoT/hÀ×p1}5“&MšôšI}/õoEòú·"ü—/_`«× >~âÃߺâ“&Mš4iL´Ø+zº+’Ãbÿ÷¿ÿ l¯õ×þóŸÿó?ÿSïOxŸ4iÒ¤×Lvøa*hÀä˲Կï7ª?UTÿõ×_a½Óñ>á}Ò¤I“^!Ñbz׋ àõú_ÿú×üQÿ=N¿üò ~ª _†ÝªƒÂÄöI“&MzU¤ÀþåË€v…ôŠäe£ßÿ±Qµç+æÓ-;ÿ+˜îÿIô¢ÅMš4iÒÛ¥Šô±®ûí· ÝÕJ˜W$¯7×uÅßJ§êcxïÞ<$R£ëï‰M´Ÿ4iÒ$P±‚3PºBeí ãp³Wë]M÷ ìpÑTªà_¯ýõ×þóŸùú÷úúÒéêaÅÉPL.“9A~Ò¤I?8©I\mõ ’õoöŠäá—`¨Ãå^¯ëO°áë¿°ó«a_/®7ÝÒi ·¼–ÂÚ~ùòÎ<‰Ø{<© 3NMš4iÒ!u³WÛ»ZàÕ¯ÖxEïzQ¼;luDˬð/Üì?~DP þ½ N5®úðë—QCÄaþ*„˜LFõ ö šL„Ÿ4iÒ[!Ëñr±«ÙBÙÕbW ‡s†Ö{ý%®ðâkh ½4×tÁ¤I“&ÝŠhÖÒ_¼ÁL¥Ÿù|/„ŽéЮvr5ÅáŠÁz„'¶ÓžÀÖçº0ÝQÃsÆ5×T_¯ëEe*VÿòË/°Ø1M@Ñ\É…×H'XÞ­/"zM›ûg'MšôªÈ\Ðõ/Wás@t À¶7ž|2…½1øÜì æqlÛaƃèÇÃÛŠ¢ðŸœ³¢jÉ ¢íª#¨@‚2 <†®ðÂq„Ÿê5ï˜Pü±QýT½= šþ™I“&}+RT§ß¸Â0ŠKŠ´´á$çªè±ªë§Có›þszc€í¸ ,=5ÂM” åàÀt·äÜ«~ ´.æbôÑQ†ÿÆ9¿àÌ‚>~Âû¤I“¾2©û…q}¸¨˜©©]*”UKë‰0›á‚€I ït³×W>þ tÅF$Š!„ÂHV[]a“A’õ;ïß¿‡í}<²ÐÄ=­ ¿ä8Rÿ"Ò@M¢‹†¥ë¯x>%ü §}e sâ«l¡4iÒ$K™÷ W'«ÙY³"'¬küàÂb"à9^*\0óÒ*põ±íªc%”æ.±õWÅv8ºëß>ðÉã`Hv8êñA€9ÀYÑ›CÖsíZûøj1–Ý}Å “&MúaÉœêê~©@ï¹9`ì‚;kšp; ØcÚ¦7u%,‰êR)­biÈ**Íuº»Õ=;ÁæðÒ·ƒ„6ˆheX"XÁêqt°±‰Ô"8ë™ð>iÒ¤¥¡SýË—/p¿iQÛ2"þÝL6¹ž‘”ÝìŸ>}úå—_èÑͧ4Ú‡F²óõáß7BVTuñ¾;#µ±¨†ºýyÁj¨ã°}(>£%ñ¼îÊœéxŸ4iÒMhÏ©-ðýû÷H¨KÄSg2dZïÀ.< ;?¶ÀÏ,nv gþ^El#Cx:@xŸ›Xÿg#]Q…Sè±Ãé™.MƒF‹#hëJnnÔÈG•84à]Œ>µ† ï¸`­¾µDLš4é Ó“Nu m%¸Y÷MÄ1 à+ÄÿðáÃ"ðœ®ïhA€7nSâÐÀ G‚¶¹hW£_lÅT»N¸¢ª{¬²;¨^ÑÖ…~cx®ø¯ë¼Šÿ{C’:í1&â]T@ó›MxŸ4iÒt¾S¾tnÌ•ÎUK‚yô.e2ô><<Ð:…óÀ‹®$£Å ¢·úF¢·ØCÜ2µÎ€S„Í«OÛÜAub‚ºx½?Ïd”Ÿ!—8ôpuµ²·>V‹Æº³úަgfÒ¤IçÓ9Nub`àˆ6‹Ô ×H Øð4ÂëG˜}7šÃÎpÌ ¢%ïÕÁ"Yíä!®š÷;¶¡º¢ªÿhÉ rÔ¥îN Y5H×k«Ÿäwòò+VkÑê¢.ûNš4iÒ†)°Žê¸ Éps†˜uªV÷Òöò ûîŸþYÑ쯿þÂW`g e½o14t ú«BèÒoSUd®ŸEÔ=VT5 RMbˆ É`b®Ó!“‹+â„»¾¥¯ ¤c­#Ý•é+'MšôC‘úÕ3ªs¥:"Ý­Ž’Fz‡¸ø0Wa‘âS€Y&<Ç“ð~0âQÃÍæcÙÂÇóº£tF­‡x„ \¥÷Û|û‘f ¹†™4<¯èð¤\åTò8bsÒ¤I“”Ô\G@¼ÜL–‚5Duª«á­†«¡“»^Ø ÀˆÄhmÀEƒ ùü¢B¨bfÆy»Vlç S R«˜Ö;Ìõ:Üh£hok‰zͦÙpi6Q$Ï|鉣ƒ66¶‰M÷03iÒ¤R`a³Ì§OŸ˜Kꚤ+Ä\'­}n.³Tù/3šïñ'!á"8)vôÎsÑìYéÙÌ6d^[0äÒ6 }ùò©ÀÔhg3u¹Ö`ÜÊ*;4‰ôÝ"ÆdHéƒ<‹Þ`êÄeß»v¸É·–£I“&½.¢Ñ~×ÎqÃ:fæNÉ>풜꼯ÿÆŽ]ª˜FÐÆQš#K«kÛ†¯«“Õ‡?eÀŒ~…WyqS·2!Q!'®Ú(ckÄCô>Æv»Ãûê;¢‡i‘š8Z´ÌÜÄ4iÒ$%n±g°ìv¬ª„P“ãúÌŒ7\5 >£ŒRÔ]ö#Rì­!ìë÷ÕÒV„Çàv‘´3H5râGV-wèá?ÆvVϸÁz¢h&7ÅôåË—™:lÒ¤I™˜ ën;‰»>í!Iä nÅv½™ÛBbì×U¼óÀ¡ l¥ëØaP¯d¡,†Æêi)ÍzGpN5Œ𩓈ÒÇ^.ýnÜ=è~Û3ÓôûkËÞ€Ÿ8æb¶…Ñù[‹Ò¤I“^qO=v_F‹9d^tƒhŽBSöºë"`Æ4Ÿ h‹œ­«–D~3˜‡Hh¥h¹Q£ÏheJ;nã·X[ zI9{`|ˆêCn ?ú,Z$=@¨Ì\N4iÒ€íˆôÃ6%ÝÑC¯ˆ!¡a©BP†5C*{Ý%qŸv©V&*àÁøÐÑa•ÔUKý3(P¿#Ä””@ rõ!iÇt[ëY+üÄÛ'Mš”)c{H¼Õè³Á™ýCøÍ )ªpé¹À“tñ¯Bk$‹]á:GõØ¢'ÝA\Òåñv|R·ÍšO&ž²Ï Þ²¨Qã&¨ÏÄöI“&í}2%*h¶+”í™ëêîVŠe`WØ?xrè¨W„q}ì+k¹ ›ê“§g[< Nï<•¸’†Ø¾öA8¬êÄöI“&`Aýí7>|ø°¶ ƒ/CQ¢eFªl·_÷Ü×âÌ1n€¿ŽB‡ðžÓŠßYú”Œú ®ÍŒ¿’X¨•¥¬žvû¤I“Î!‹“©X±lédé Øƒhâük®!ðclJ‚zµÏíË }éCs]kžÃrïEÌx­ù±/(×|È=~ßÞ!†¬•ð·×Ž›Ø>iÒ¤LL#ƒã“Þ¿Ͻ¨ˆ¢k$cõÊt,àEŒ,vÛª£–¹=ÀZcgà“Šö†ùúMó„ä&ÇðùÛí]cQÈlB·Ç"N&Ÿß=iÒ¤IÈ9m÷ú²åBdÎCƒµHö°¢ÐŽ •Ä®¯«oßJ ‰B‡ËZY<Ì?u›’â§ù@¬ž‹œ©¤1™{¯ }Ä™á¯ÇwÖ-$¢ñ2Þ`Î5÷.Mš4I‰I érÿøñ£;°f|$SöÞ ñïÚg­á"fHRÅ|DÑhõ¢wûGoýZõ²ï}Ø¢üc µä‡î3©¤%ÔLÀöhûd™]îK4iÒvÀrcýˆ½†xqª+pñÚV*‡E€¸ãžÏë3Ävµ«cäE§7 `×~ÏÔÐæçõ1€P®˜63$àUB!1\jßZˆ&MšôêHÝ2u‚_ö‡‡€ N‚Ð D9¼0ã“Ø;ƒEörã-îåÁ}˜¯„»=$·ºÅÉyÓVZymÀ~М‹±½Èà)P§ÈŽ]8d°ºÌ<“&MzŠ`º#Ío… xty(†Çèt>¶ë¿ÙX$þpéÏÂДY|w¯JVÖ1ækØØ¼V›ŸÏø|>•Þø×",,Ä#È+Í…ÔI“&“­¨"¾Îv …8(†h9ŒrßÃö!òzˆÛ“ÉÄ¿¬˜=¶ó's¿+´Z%mh³Êï ÑösKKð;í“&Mz’4Ð)‘{T ‰{j[*4‰íÕ‰ÕKŸ_1Z1dÊ‚«)b»¨Q”9?×Äê°‡çy¾ûÇNñNò$’G:mGr`äy^ê¤I“Î# †„³]cf2ñzû}ŒíüˆFÆö }ú$ccÊ~Šz],ºFM÷c£=Wàás³ÅiO«"öp,0Kžn%æl¯7+ÎÃhGïL`Ÿ4iÒ“ÄU;ª±nÙ`x&éÐ`Pi„a\ýÁWEÈ'v{KÞjh–¶Õœ~òØYØÑxA¢Ñ®«Æx §`ÓÓ>öI“&·¦ò\ìŸ~úéË—/õéeà{îc2Ì|„_±ÅGÃpCQö=~–Ñ>¬Ø°eDV™HgjÄ÷†q€@<ÿ¿ÿýoøÛ‘"lzÚ'Mš¤dHN™›±Ù„C*¶ÕáàµÌÜþ9Äç‹M÷Œù›ð«+°gëÚ\ô{ºª¿¥ô³ ºMl±! v|2¶ìy½S9_/~ùåxcpˆ^lÝ4±}Ò¤I„tÎåB¸þ©QÅ FËÀD¬`ŽT×–«ŠÐš÷1©™}`Öî¡hFã½× ªöVŽ‹>¨ÒÞè U%bë(À%à½ojJ[¦ïÅÆÜÿ׿þ-‡ âo-SNÿÙèñkŠ{µ-ý Õ;³/Z‡IßÐûj™+†Wô†û cA8ñ¡Z‰[H8% ÿf˜}ˆÖô¯·fñ*Àæoæë °Ý€]‹Ó$Z+[VÉ26”tdÁÚNEÁÃÈð7;½1¯Çh7›b¦ô,æï‘}ÀÐ×olné^õn °,åÛVcÒ7$¢:ôëqKöX/*˜c©ÿëFªGH0Xýc#ÜD¨†å–ÉË…AÚ±´ÙNÎÈœ§ únþ,ã/Àö½°œÈL~_áÝlou©…ÏêðQyûûï¿Ûä¨Þ„›±1¯Ø)]0Þ7º»Ý ¡ÜM°_¾ˆ¥êº´–æê¡nØ› @Z ˆÍùÕ`OM„ë¤2€.®p o-z™»pÇIbƒ¨ ø{+¶ˆxrèæ-9 ì‘VKõa³ÛCŒvû;ŽÔ‡ _Ñù‚~'£·p¸f‡¿µH?‹çùÊ²Š‚ÇpZJlÞ˜Øú1^¶dðØÛÌâ®ò¯Îdû9„bj yŽÍ>[XŸ›c—ªF™»æ·DeØØzÁêÙÃDøËê0´ÓÀVã_Pz¿ZqÑðŸUújÓœI7'Šk2o½®x‚¸‹Ø òÒ‚î¸ë“¡ÑBÜy$„Å· -íŒlÇð#ïŠ>“ ûUÒؘ’?¾GöîÚ'=°‚òý«>zçÒÛÕåÅOau£ðµƒÀyì#‹Û¿¡õÅù~ŒÌ„ß3%â¯'|ª~¦H-ò\˾½st»oó&hÑò„xóóçÏX‰6A~¼Â™6¬Fl.;Lº÷ª}‡9gƒà­X4é+îèV¢zÕX€ M¥eeÀ‡1ëß>À!C,ÒüºÙc¬8©{E 9àÝޞ̡&ö¯.Ãõ=ÊÑïÛ")òÛD SD>sUEŠuW×=\.œEÃv¸eϧöÍ?C°o¢žô9GËó_«˜­uþôéÓÇ1ãû÷Fhõ/×>‚Å}ð¹Juå ˜yß¶cÜÞÕÛC§ pk}TáÃÄßÚ•x…KZ—á*M5õDUH?¨ÆÃÃnRrêc˜†_9ÊLúʤsd yøj<ÜmaêpÛCê5 }Ùb×!tq0‡ ÂÚVõzéOÇÎ@š¡R±1ÛÀ{`›s\/) ^=ÿCÓßÙƒ÷2ŠcäwÉŠIÏg4z_YDô¦ã£*ÿesÔiÏ‚j7Õ'ïÙÁ¾•zg`9dêu…ô*!µÎÀv´K]IÑ ðÓ˜.õØ7u[åebÕ¦:âÔx áeËq„ŽÃ Æ¾»—u‡ç® ØøRe€c\% |ÃjT-æ°B5ð÷u†ZMz—ÂÔÑSt¬Aû¢YV@õesäV¢ÒÍ¢a0„)þ«P¶gŸ‡øj8Fd·ù7~¶Çf6ñ“¢_ÎßÑÕÕ½gö^/)z‡ø¼ni¢wÈ„0³ˆ+?Ù’4@¡†Ât¿o81íºß"—âj#ðRÀ^©Úê¸+0õ[ÚªñäÂëÜ=›„ÌGﹺɺªŽbw-$¸Ž\0±G˜Ó[5<ØY°‘b;¤³˜»ç¯†CÍî/ºŽt´.Övä"ªã Î=Ô žÂû³ª1é¥IÁü.-Ù«á‹-þ¥§béÙB6S3Õˆo-ÍÝÍ¿¶bÛ/½™½§×ú++ÆÚ²E,ÝÆ}÷Þõ›ú˜ [l,ZÀaMä2ƒ0@SSvmVåODøesõÄhª%² â«I ¥Ž®ö« (†ó$ü^Cd;ŒR†Òám€Øë:æ•l¹ka?peØi«4BÅ¢éjËy1`–aë9uÐ4 uLG’gÌ0;s,¢5Œ6ÚÖjÇæ£ÿ?ZÏÄöoEyVø({Žl«þªSÚ!LCu[[uôÀ^’sƒè´ô~fC?EZRžð•¡Ï<“‚­¾¢DËÐìjCNÃí¥ß]•ùqÞœNœþ„ÌtBÆ/ŽqÌ3¯ßTޱªdÝÅD-e¸ºúRíX®å™ )”×õ,úk@0Z¸HñùóçŠ7ÉÉ šÛ3XÍcJ‡r€-çt˜&ÿüóÏì/~öœ:¨Ñ^¿P¿†ñEÍhÚÁB•W˜ï`P¨wÞ¿o»›¯aѤó)'Øs ÀZ9ÀºsF˜R!¸M+1«…62SmK ¹Ên¦¬È{˜¬6­ "z?úáC E‚u¼ˆ™÷>¥/.#7»½mPS­YÚjÅ"áî! ¾ÈýìÒÇÆ¹æ`,¦Y¬€µF1øjðNþš]ªó&Î\ÈÒ!¼¯#>Æ^ v-²B;Õ.Å…×enª!Œv8Ïñ—îºB@ù,͇‚Àn¯C¦:®žVh€pŒ/œ±8t(‡*cx†u&Â\(“)ƒùpsDHäp4à 7ë5‚ÐÑ@› õ«¸¸WÙꨤ©j•ULíü“¬´µ-È]7E"dbäÁæÅð{,z3›uPÍZålSEÎ=£ÑJ\SعQZ8KÑg”Ã!&“ý ÒqV å— õü üx.Š]¿¼sç[·¶õ‚1TeLay†CÒç³å ]3fÙ"$ÿüóOÜ|¼â´AxB·rhÜ÷[æ ºËT¿²®Qü@p‰˜Íü$¶s¢ô×_ášž}zÊ“=uDÁjªØL·Ì­È’?èº9uÓ<-Ìp×"‡Ñ­˜âÁV‡×Š@ŒêÈÚ{KVYèTC´È“Oªa©Ìʘñ9?f;x÷XtÕô¥šk`§Bý";‰TÚíûúŒ6w4â KˆÞ¶Ì,ZzŠÞ8_“vc•æ¥!ªT5§û”^Ü—‹{l¦ßµ“¹X1¢™ddËžÀ$›}<³+õ³ú¸eª.ÔgªÃT™µ¶×`{Èd\8.mÎÈUí}k5eòÇT³ÃÏÁvÐÿýßÿ-íPEŽ/”"v‡J8«‡ªÂ9Ã9 †˜‰íWÒ0 EŒÀÙæð´ÜÉR ÙÑ¢_J›ôÁ÷xQ‡ÆdIî‹=˜5{i£â´»4_…ÓX¡Ô&‡Jùð´¡ŠU]v–MymSOEÜ1D®|Æ"–¥ÕίÛp³¶Ðw. ÿ;õ9Äb§UÕÇÓŽÕœ‡‡Hƒc×~6Ç ücß{l(']ð¤* ÜÑ×8h‡ÔÄæðÑ~çðªâG“cí§{E–!¸trŽ?¤þú÷ßs]ãýû÷e[>ƒQ¡1WyøS1V\Úº<×U'¶_LDuBúcÛ/›á 0ÙbÆ\.UP«q)%™·‰ˆÊÙ½ÙT]íB_ç}ªoD¡o‘S›×¶‚cŸ-â¨1%NÕ‡ØNéUa6‚eUúõ…u´|i<±›ù#D`B3,„m4Éȯ _ÄØÓ‰-ÒBÕ-ÆÝ |­Ü¿fÊd Þµq³$Ê0¾ö &6hÔ(Æ»z]õ¥v ¸q%¶C[»X±ßWéÒÊè0ÄŸT*Ð_ŸáÎ:'LØŸ?ƒ‚B|t!+ËæRÍ…G¥Hcêærê¤É”"eu §%ÄmޏYô¼+ÑÆeÝ.ZÄ`Й~išlÚ!à˜öéÑOºM¿V1û×Þ™¯¯+Ø*F•~È(;Çgdpà}m/•Ëê2HÙŒ5dìâ[êç)›ÕÄ•åæÊ3{Ö©Á»¡Ÿ5‡qÄ q2s¥OãC^Â3Cÿ@4k6Úî6e²Ê†vÙçß]eÄï30rÙ|eºX—¥•{\Y€ªÒ’«—{Š IâëFOƒír~1"dê_hƒ¯âpÈêA0ÄhNl¿€h®GJ»Á#/13^K6¥ík³zá²Ú:Ú¢¦Bˆ~)®*ôÅñ²”ŸY“»#$v‚éág‡h6Äö|]zK›õª¼¿k>ü~é3T׿·´šý?{u0ä×OÙÃÄL­0ˆ•ÓW9k¯øýF¶|,—UØZú$©Hë‡lYÅÅMG^¼ÞálÇécpõÀÛ³JlLI¬7YÊaýyÑet&¶Ó ™¸™©fÚ:^CâÞ9}@‡^&?,°GËÛƒøó?Ú)iètë°Ï£ß‹·ŠïÔˆ} ÊêÚ;LõØéª}$}Ò¾É>äTaèWž,°zzQ±]!¢ôsÿ%¹ÙùMu¬²®ª…+¢X«|´‘‹Ã+-g›žÄHÝìû{Xg\- '׿_wÎÈ~¹ãöh´Û!®Ñ¶/Äòh«m^dþä® ±ÛC¤‹31ÆÁº¾¶Ã`V‘øC²©è £ÍG×ÔžúðáCiÆÛs±½„ÿû¿ÿ‹1ÇE« …,4gU«¯ûÄöç’fbdj&쟑CÕt\‰ò|Èð üiOû²¢e€R&ú­½›/2t§HƒÒ[Ý”Ì=ŸŒýÑXSd 3‹…ó¾²ÈJ„r;³ÂÆìŽá~%èÈÚ˜jÄÚ¤ioìÐaôIæë  ëã|IqýB¦»a;€T›œí kà°+I YëÈ$άÓ.ƒÝ~slÇÇÑX9Æ"îu=:kí¥¸Ûkë*¶/-l"§"dþ"J/û¢¾…á!w-?ðÄög‘Æ0½R2.mG6XM`¶®=}75)b ¦©¾˜\©Zåçí¦ê”§ÕàåüÔ¶ä ¹æ¥0;|wØF}xOëù}¶NǯÜ|kã°hü¥;—ùÓð/±½ô‹­¹Õ>ƒµlh… ž1ŸèW<6ƒ„!/jºÓ'ƒ©%ƒdl#›²bÈ^k¬Ú$LÐwõaBê h×ûÛ‰íh)¶Œ m¤aëhE`ÒÐ&üt¶sÙz ÑÍÌmÃX\ðMl¿Œû\yð˜akBRdƒ› mö¯âèS£eéW'µsÕXÍ1¢¡ R~øïÒ{• qEœŸªwC<e jJÆÌlš®4M+c×%yz©5ÌìWZ€¥ùáC”Èzm¯u×64ç†ó‚JÊ…•¥…ˆ Û MÖ»[ǺÛZ*íXúF´ 8}3ùɰÇ.k²~Dû…–Â8™›c» Ú¢Ò»VaÜ¿¶3Z&Dø—4”C˜(Û¯¡GÉÃäEºÛ—lÁ7¦]¢ü(T1Œ‡š¢½9D~|8ÄïQF1ã¥ë•¯‡5Ü+]ÁAVîÅS`˜5ÝÔs„ ‰í¤±ŠÑ™Ønõ´Ê³±Ú¬UO«‘>dKé#Þ×m¥kdUg_ E$QñíÜj±È~d–Ü ¹¯ Û³<+¨T îʉÍ'cs®‰<Û­QY+)¢å%±ÝaOžËÄö[У¤Ë ‰xÔÅ/š|ê‹Vy0û<ëEVöŒ]±kÏ¢!Úä S– Ó²° ql¯]Çm·JßÇmµ‚¨/ôjÒ4-mí{ˆíC‡•.Êëq«€~Á~Ò)'º§¼ÝÜ-óØö.q>øÀ™KHZHu•èE$ $°ïuE+›Íœ3ÂK·Ií”+Ó…MlŸ´G L…C† À„4b9uUû6ú9þÞ_sóòŽêš¢Jî÷óÉ@Ø`×*í†ê¦¼C]8(:scï#ÖÌÒ[Ô{eñ~ŒÐYˆsÐý¿Ob»ÒÁ¯kï×â3Ö"¶‹Ë¸¥­$FÛ–rslgd`ýþ§OŸ~ùåÝSCnäáŒÿf——>oÍTÁÎyñ.vàBäãŠëÎ.™Ø>iH)¥?¼îE’°é߬ÂKŠâ0tÚ|ólßÕËUŽdPEïÉØ»?,ÚÆ2-]kUÄ 9ä¯ÍEOæçÏÒP·Õó·§{^£áý\z.­Â,÷r¡Ë²/¶ÈœSËQ©ñí:ƒÓ:ï îúo¶ÞUt æc\ã2wlž™ëW&¶O2²ãJálG|,åÙfü=Å'š Lô`5Ô—ëQ=X4[´Hœá°™ú…óÿŠð‘`3ÛuÖv+eOðWMÐEÖ¾q|gýµv+CX#™åÖwʺEÖÍ3\¨An/jmñ"W7¸yöãLj–Sâ%–S£¹eîZÊÙØòÉ7´ùºÑ¦ì¯G,)|H9Ϲ ožä”ZÿeÞ¼ëó·OlŸDÒˆÇhÛ”˜;Ž›;"a{Œ6‘e|z$Lß©D¹oHVÉ’<{•Ô¨j<·žl].wè³=øÈÒµE;vWÜ@^žVF@b»v´"ØåˆüŽ }¾ˆ[¯VFÅ ¹ÜóBw±ÉjHç /ëG$¾eL`0 %‡­&fò &†1\ƒíw’!dbûIDulA¥H0­r¾ZS˜ÑJïu AHöTF¼¬×Cù 4p³¸2˜G/êö©kjBÚÃL«Áúëð5./[F².m»â4†Ø®¬°á›ÏëäK‡ Çy K×¶œúÒ.wõ7Þ·Ó¨cs†œZ¤Ò¼˜$”Þ]–¡É¢¯u×~âÑYÄtÇÛcbûE¤˜hÙC¢b€õš[xÜN{T/£€2\ظðMhïJê!‰„±ÑCÓKÔ”kB/еÅð™}¿þú+ë ƒC›u€íö¯Þ§{ÿ*Øp l¤É‚<[ ‚G—;ä?=]&ð:3ÕÝÖˆ‡Ä^<{½£xRúÉlo$1 à[O’,6x¯×8–ZùüåË :8‡îñÒœi7Çö¥§ÂYU­çý×ãêÄöoBæZ‡B!6 ìB0ÆH£Sô„»@ EB½I{ôóúoN{(m¢5t¼¼°?Yɵ_§0œ±#––)=Z2™Øbÿp“a‡ÇØž¯9:¯Í¥ó¿8šJ2 ‡z¾®þ= ‘±Á89Å8‚¸w$u¯µ>pÂɈh㗶΀Q™õaž+\ Ð÷ïß×^€5¤¢¡e-ÍM>pXÝ ÛÙÎþt+¶7½l7IPyÈü¼yöÑaé™n[.]ZŠ&D?r:okF!¡¹YÂcgõõ;(›(ŠK¸ŸgåùÝ­áp}ZÄ7®¦xH/ Ë+Ô¡*–SYÊ9v»}0doÔ²EzXÝÔí0ô5åk Yâ‡`EœÌhЉWå¨å09G&Ÿ íèúïŸþ žHR6÷{ôÛ”²äèd¤A4H¯­£­Î†Ð¦bjß')7\ñáJlIÒbÂQfž#Ñ^5¾!¶[Ö\0ó1ž× ëù¥çÇ®ÄyöÊùÏŸ?ßmaÌ–•]{ý®ÑÑÑ´¸$ÏŒþô¤Ð~eêi¦úØŸ[7\/ûñ“lÁ#2aÁÖºÛÂYí]~|ÛòhNS¶¤¼R¬0ÐkYèr¯èÇM£Dòmt:ŽâëOb{Œ?BÂ?ò3ʨUv´é„t‚“˜ÕjWK0þk#í¿7ŠRbñR%õ¸Öijþš߬,¾!|ø ¨h^Ò¬ çcô´s`ýûóÏ?ªÌCN‡Jâ§ÏÝ·)èS">„lwúûï¿+Jðƒçc»aìh¼ÈÙYˆµÛ¤ƒÕxìJµÑ"N_ÛkKßõ áˆrÐgÈ Î‡ 1³ÊÌwžµê¡¨¾W:+û1š%£ýø\„çy[Øvm»¬”*êkÛMáÌLŽ~ã {dϹúʱT-²ìû5Q=Fþ[Eïèá×,ÌRR Û`€©–=‰í¥ßY©ø¶Ê1[¢0¢ŸŽSÑpyÂ鑯Aï$T.¦Ú:hDÕ /ê¾"³´|õõß¶S*ðMŒ ÷rÄ3´Ÿ… Žý&G™X‹òˆ¦î2àJKS' ÍESxýýFÑÂlPCW"ãdW¨÷ÏÄv^p,.’i¿l¾)°t·¹°ÈsTƒÅ *öBØŽp"j´á2ZLëhNfe&ù îUžëdŠC¿€¨iíò.L·´å$5‘uÎýøÜ¹™€¯ÕqG´“’L ¢§ìr ñFjé¯Û_)벜¯²»*Zaâ l¡AwÍŸL‘‹MÌ.Àv«'qÑ’\Á–F Uî}¾kš«ö0ZD— 8¼l¸(¥ü ÙBép¶pytiGQð|‡YÊæÿ©ÜÀ‘õ–ü?þñN4`íœú<Ï:™:p]žÚt:K‚ÑN‹ý®gVQ ^±Y’§vœÿ z]+ŒXVp›aEõ.OpæÂ™Z<…í!ƒ5»ƒCÿe¾¬†d¬ÿrØb5b³N±Ðs[l' b¹“˜ ëÇÌ?£íP®aªX_¬}Á¡Š =„ÙÇt8‡˜\z½ Ý[ZûïZÓžïrø8ÛÅ1ÌßSÉ’ì·²cLò-ÞT•Ÿt@.ÈyÜÉNo<@È‚ݵžªìÔ²¥žÏÁö"™iaËUu®Õ€¢1T«¬íæ¯ñ¾Ù‡¼ƒ´x E€î·ð-¤çÁˆ·€ÌÑP§}¡b½ÐÀ¦„:Àvö‚öTéçéÑB7£Y€ÈCR¶ñ~x>v×וX ³(â l¯Äc=‰„d)Ά3é<4f‚Ÿæˆ@ïC bódb¨R˜¥ŸÍuº)~p–ÎÓŠ)QÚõ_ôã]‹›âÈr¦AÇ՞ɇ‰ï©6²çÉò¯wðÊ$#­Í)ói[ƦP§vв-¤RòC\ œ_ƒí¸VO2DuÐ& ±]‰åªqh¬²®4Coa,Ã@ ¾Y¯î[ðçÚ¶åšÜð1éÀwN-uâ `·—~ŠÏÄvT@±½"O©¾“PLv]²ä–½Ôô8µcg è…¿þúë®ÅH`âvqú±ÝP½ô6X¡ϱ­DÔ Ô¿÷-\?ZvM‚-š9ôÆÕØÓ…¼Â¿KÛ’ó$3—3‘i<Ð>A¨0L¬®u>¦ˆ ußÎ&ø½y¶ÊêÕý?¡[:q8ÛÁvø)Ò:¸G°${Vÿ¬¼&31éT­"ñ“ÝQZ\JôFWHÈ!-O5é€b»UC/ö°õ„šS O-±ð¶«gƒ-²¨K'ïÃ^Ûê­}xW`;áŠâ-¾Ž;¿õgµ/íäV  ñ†ØŽgpã <”•—¹u?µÄ; õ¿µ´Ûõ#€VXøõû°€W¿µ“è#a;9?ü÷ —vj< ÷)WXÁq£•æÕQv= ÛõØV®ÞIô w^ãÂöãXwgËF;Zßîli&ÝÉ&8 d7|‡~0ÕÊ!E’†1«6mi1*èGø¸ïO:g Û+9>i×"Õ3CºöM¬Ü5k¿R6±ýIR¬0ŽÃùwm›Áñ—Ë@Xc ™EÞI4Ú7Äö\„ªvôó‘賓Ԥç‹0Òàœ‡‘IÏ0+ Cƒ‰q´ã8QžÜÁi0kr•lG¡{?}ú¥ƒ7Ї·xÔig? ~V¯ñ:w¢Õš×R&Q¯£™¬{Ø®•W,µ®/rj{Ga#P‘‘·ÎôÀV-‘…f‰Û€øõ®­?b1škLúMe&…\eR¥‹]ÌÉ%“aB:Z†­v±éSõðMµOJKC·Êßt’¨@'<áýÌ$ôÉÀnW™¡P±]Y†µë ²TL`?ŸŒuzS…Ÿ §k[”:víô¯Œíj=Ùd~“ØKíS÷`ˆ÷&D×–µ šå¶6¿:Ÿá§øY%h¡ FæÏ?ÿ|ê#LÎÁöÒÖ¤È âaý,S3áy\£¶¬Ká»ìwEfÅöhûÐj8Þñ”XÚ|äÛ‡´6Ò‘ZkrjÇfÿ"ëEÈvÌ‘{ÊÛ3±{‡ñe@.@õ¾¥¿³uŸÌL«—r«E[À"¼3d=°#1µz?(l ­*À*W!«fÚœ•`u8z¥b»NÒÑ §¶-]¹­âª’¬ú>càöóÉøªºO ¥JWLöW‹í†kŸõ7Fñó%rƒØB4Uc•5PÊ¡‚0J-:6–†û¶Ñ#®ÀöŠ60¥*.1’³ê —@mj"»žm Á%2“ÏÃsÍáÃìa»©|î8“,¨9뉲ªpbØ d2¶È ËBB鎦'„|ˆÑþ ö,ð*!!–6µãè$Á+Ð5v5’ya`¨˜š+ë–É ½æòY<•ÕŸ ôø üŠy ±ÞY7«^X¿,½#kÒ9¤âm|VTt•ó<¸¿flWØÔé*ŸÉ*OðQ°RbÓ†ê=”‘ 4ºJ‹ý€:«+Ck¥0«Ó[ÅvØK;%Ú´‚ ü½eÑ×zê79üͼ/ÍæDp–Qs˜mº¬ÀwùA~vOØXDô¡qZ+Š¿ C[vºÁ Óž…í¬Ü&ŒeÂ+÷ù¯Ã´VÒl J ¶·´?Uà*ö:E[:A¨á}K®HÉŒ~@´‘”ÏX«Ùë–åS-.Ÿ=™~S5e1*LEtÏg$åÒJZ_¡ÜG“hˆí¼Öñ—+nplâšË.=þØ~Aœ á+ìþj* º£5±ÀÝüñ"SïèC4cGàù…!¶GŠCl×IAôón»z°Ù×:S0&K‹8¨A0ïp€ÃŒ8"Î^ý¬™ñÚ.½£Àz²JŠÒl5@ Üyùä³°=š 3„Ž—ëÂõSÆkféÇ&vS4„6è/-‰Ç²-Ußµ}s\ñ§#H%Íl í2•ëÇÒÌÀ2òNG ŠCÓý±¥›P—;„aígv6î(Èÿ‹ØçÒ¯¤!f~B=h§m‡ €Ã~¿9¶ŸÚ"ý«CT¼CjØ–¨E«Ní ¨k†ïB€é¥QŠÞ–³†/½ý¿]íÑÔ–Y%O- çÚ–wíªïÆ—~ðR>ŸÚNR8 tø -±Ý>¢•~ÐÌ LãV[¤@A¢Kül×Á®lÎ Ý×¹ž¾8dæž`[­(Tk‹[ГÓ×Ê.Fÿ–!Uz3‡òÃÁ…a`x÷œ3¹v š[†Q÷mŠÖaØäã^˜t )WMT (] Åöã™Ú5ؽp®â!_[ò®QÚ‹‘„Ê”:z7©Ù<¹ª¥AYˆ…¯¶SÈ0#lÑ&< [—ƒÔõØÍf¦ 7Z0g^E `méoü‚ÉüWÅÈÀ íµ…À)æðSÙ|5XfKù¤6œ•d)§––*LÙ0 RŽQ…¦ì©íµD)´™!¹ªä6*—òÍÖýÞN?„›«"üÃÃ>½HÊüÜ}±¯V% Ü|`ikLPósÎÒe´ &ð†Ñe¤å.âÔ5æO` "WÕ¶‰^Þ…°\m¤~ÒnßÛ»4¬ƒÊX~æ·>|‘TçÑC_)2 Ö1B£îÇŽÊç ÅpÕëldyÖr9u%g¸+6ž‰íüI¾ocm‚³ô¦o~ PCã» ×·‰Ò¶KÙã¿VƒwøA¶‹îkÃ!Âp¶s®Á]ÉKs€£uIécGƒ¹ýÄk‚ªÂ;Vrï·Œx •a¡EB샑ä6WIF;cö41‹Îžénø‹¬¨¢’§þ…ÜöHª1é&d¬VýUtqDÆž¸½ýkWb;‹^ÓrK;x‚©Et>ŽwWqØgÕ8ÔRˆoÑk½V’uæX€8™èáTe¼Uå%"q…Žgvìa»ÖjÛ‹8(>|øð[;Zf/ZK‡*¶ôË‘|’ á_M°nNË(P!×Pë¯?åñÎ Z$ŠFgUZîšÂ÷Ðõ@­Õ| ¼Ê#,7°†ÏÂö"“Dl1Ó‘BÅ&Dú+"¹ÕœjLó‰q}œÊ¥ï jöär)“hf+Ø*…؉¿ÿþûLÓKl&kŸvÀª‘¯'Ý„”ÏJC3®ú—›æ²é~¶Ûð=¬Ì±©âU„(¬Ù±±/TÊ ~¡È^øE¼¬{rËô>J§Ï¤ÚHܾTd˜`ë")£¶H‘pWÚ4‡fÞžjï dZgFú$ ¢´,rZ«ReÛjulñ²æ,…Ævôƒ‚!Ì9Ø®cS4û“æzR±7‡ßÉ‚½H^#-+Ã>?ÎKGX •uÆ«½–f›Ÿ¿2j«'œ8vËhÀ ²JrÛWn+i]¼7›˜t1QMLOñë"+øKKÎü‡RiEõlÓ—Óº,*‘|ÂÃ&h#iGVºHÚÁ'-J<ž¢Ø%&lŒm \ھы±}‘À3å³~3³BÈÝ‘QKÙbU Ùµ4×^͈lì8xŘ©²´¤Ï5ù£ÎÄvm wãI‹b2¶XåµÊg›ÙºP#¿:ÙÅûnöd/WXeCøÔö³Àäˆ8q€íïúX÷»–’j¥½ýø>ÔI7¡áˆ©ÝA/j¯}úô +ªyýblW£TGBNýÂÒG¤Sx–äOˆ-O)ÃÀr8ÅVÅYzŸC.(zôÐ;¼¹´™ï½$*¿ÛµJ`8íg šFd>cLÈÀEVða«vi^÷=´9¾iìÚ{€ÍgqLD©L 'ÏÄvÆíÓòäHÁgr_+9|,z¥ÐšÉQÄ;t’í«„DƒÇ¡ZK‡7µV´0C‡íqìrWÓ‰Ñõ\ÿÕ¨6*ofNîŽI×О¾ë¿ïá&¦ë±=›m!r¨‚Q’»fOˆüÄ´uäž^G¬V¸þC=–Šf*Ï_¹´»‡qã¼ ðVÃoâ9ؾŠË¨ˆ3JȖ̺¬ûvߢӑ‘®¡ÌL¶ÈXd=®ˆ?’o.mv©{JšREo1ê‰-:õû|Ñ×V±=Z‰ׯj½Ð'iPÙÅ ˜•ÔL÷ußÇn½¼ôóVä7Fà\îX0}2ë/Ìú€=iåpcˆç¬€±T•ˆ÷ïÛšü{/.¬²`—ße3gX±’œ¨Ês«-«§¼5SèÖ‰Àü,”—Ý}RDkˆ5'ú<úÕê\a¶W¿¯ Êd…a{6!¬¹2¥’Z ~S%GQ}é×z¢‡PûŽÖST3„2µÐ2Š1+ý¬Êƶn‘¹íÚRô# mŸ‰í0òc;-…=-ÈG«4¬Ì°S&]CäöžêQ‘99e¿ÛRËÅØ®}—Þz§Îê“VsS½¥2C97¬ÓïÛw —ôyåÏcõ_3°ðÆDí1²÷°È³¶Ì½‹$"PÕèEé­Ç"f|n‹=Ï›,î$›C‡øš&D:‡xòÉÞ9“èñ‚—{yønˆ¤…œ‹Ç¥> ýb¥G?îkó­CBÌ’è—tµòE6Ž™@²t~AßÍÊ5Ü&¸89uhºã,*qñ… õk¯_&]CÊáµ÷ìáb‘]ºG5ú¥–˰ý JŠºv‡’ S~]Q¢ô{ L;ö>s)” ㌠äX$mr ’¥ª_çc»â›2 ¶–› LÚ¢¦ûkï!‰Æy€ÐÏf^^ñ³f»jshŸsQ†¼ú]®Õø–s°]å¤ôösBVá5Í)f¸ZøM‹ÔU.Y…Ùm]éWØÉyÖ'ª¼Â“È{9 ùl×3;(Ò\•ÎÊ’åaÙq"MºŒ´£Ÿ<®’z:Z0äÒïn`´ 72< Û‡ j51]سOLmùîÒâ(Övšç‰E&³Z“:ÕV%3‰ YkïÞçfvœÛ(ivõ™ØÎ6.â§]Û®ÿÒ{lŒu¹!2ÿå7£“쥡N ÉæAŒF@ýi¯>ö¤qcéçbœwè¶}°'±]§ÑìãU®›ðkªDö<+¯ ¬aNt å]ÿæ^Ó×ú=D$ôÚdœÞûþý{zWÎÁö¡é®Zq{MÓºa×Lº˜TU©Ú†i*64&¸Û©’¹2 ïöbˆ *d•~êzj´´ ÌK ,Q†¨Ø+7´!V¨5ªˆÆªbÃ#–PÿØÎš-¨à&Ø^6Éð‡¬ÈÜÓ¶³8ÖyM3}ƒ·EÒ ›=GŸaGhåí_³1pGŠ­z.¶ã󃙨X–Î!ù±3¯bÔ§¥ŸmYïGZ/Î_ÓŠñš ª¦+IÕS…BÑ8uWçægb»™îÌðOF•6úgÉT^ŤëHYª7•í:¦¯mC·.µÄ6¬ÿç?ÿ¹ ÛËh~ªd:¢ù-ÕMý—÷ræZ4û¬i6$ÒÒ-¹Ç'WYG‹»ýááÇÀ•~\‹‹°#—"˜êxÙ¡4à(Æù7×! £ÉŒ††“ö`ÈÁ²üU‡eEmæÒ‡÷+&? Ûə軻ô#uMRÈíµ?™ËôhŸCÕSÄΤ=8|Ìd;D˜‹W¼L¶÷s°ý8f‘@ 'êrîc±úÖ‰eÇq4éYDLˆ^sMêì‚KKÌ-à ª.Ãv+N+9ìn{EíüüúÒ6nsççìfºg1­ŒxíÉèÁ&“®0Bqð.Na(W`»–»JXh¨DÄLVoØã¹uZOå€Å¢œÒUúµ1‘ö$+YÚ^û‘1zÑžb‰ôZh–}þlf p+ÙôNFiVÛüü¬ð°ÚR6ŠMSžgÆÚèœ9Æve½ÓÑ™}q%¶ÓtG•†§©f~šÆ ågÒ1iÁ0pŸBE÷ã a{$‹N…ÐZÁëliYÑvd9»¡Z\)‹ÞÈ kòÞØÃV6AµÂ>^dˆ¸ÛËæ–ѨeK$H±ÏªÝ®OhcM É¥’ÆÄèåÊx… ¨(É-f W ?µ#ºÌÌ.ÏÄö±é÷tÂW~‘ò¦bP$årn¦¶%ÄÎ×NW4lÏ\ÍØ^ÒÔÛÔGÿ]eMmi[S¹±.ž‰íïRîß*áççþÍÆÆ¤3É6Dm£—áUH¿°¶0ÈÅvítíë22°UXaÆäãÛåø«}J+½t™îDoUfV< Ý‘ Û¡D,ñl¦ãܸdúk}MÖᎺ/–æ|ȥخץ¹ ÎÁvm#ë ’VÄ%«ÍÔ²ŒÉxË úÙr¶ã1¬qk,¥Š™2Vy¥V·Þ~ ò“ÐGl§ñoˆ—…ÓX¤¢Âªje²ðð È_ǵÔ'Óì™îzlG´Ükº•)l¬ÿ2M÷çPîzÞ^…£_1¤K!š§úáá™ß{CŸŒÕ$W~(Ì*'Ùü {!`€Ö¥EA˜´g±W">ìM42cUÙ±¨DÓ¨^sÍó UÉ °Ë©¥a5}2%Åòe€âO‹8–­;VqSìaû’ÎÊHZSî2 ‡¾Ë‹!¶[+Ê¥ØÎ† ±]™WØí†í”^®þ[ï,â¼RÁ࿹¸U\16˜êGTß—vøeØþî0÷ïI6ÞšâGRÀs(~xÊÂ`ƒ¦>©2¬¢¯u"Æ@rÛÚ•Ønš« ë©Â`ê*ÉUÖͿʠ&Ÿ>}b~?}]Ól0­† a¾©òF¾©ÆaÊÅ&DËp·¯¾xÝ®eE?)Ÿ×Þß®å@{J¹]RtÏÂöèeÖ ò»Æáüµ¸¶súcbf=e<цX¹V!¶“hü[?E±È>Ó\,)t“¥k'ÏÐD°Ä³°ý.–¥1 9Ír˜ié½[?&™æò¦]D2J5°v‘cË`jZ6Ès°]ë“13ChôQ|`éÉê\ÚŽ À8,vÎ?~üˆ&”ѶôÒS†PÞ×dž|^{¿Íºùd@‰Šs+l_úµT2MQ×êYd¦£áÒûdôyÃ}%¾¶Û¿ÚŠr lßÛ,+ö—6‘Ñ0ÈÌ·µŸñ™5åLÈ•7öj—Q)¬±Æ[­¥ÍdùVοþúKáÎÇvEƒ/_¾0÷/B"© Ê’¬¼5SV¨èÚ?e|ÐñÑ„?z©Ó;rÄ5aÂÍ!óî¼}©¬Oî2í¬èÕG+¼7XSõ #8³†À^gŸ7ªUÌ0å„OfIçëY}øÙ"~`3wCàÑ>Â'ù}Ô Ÿ:¸ÛÕ`SóŒÐNÌö!’ýCQÛb]¦Mû°]…ÓúÈ:Å Ô,N¹-ú¢eK‡¼63úÞ±/+i”Í4:8…繦{EŒ*ó§íðY´‘µ"åZY×çÎÒ‹Þ ò}Õ}*&AŒÉýðÜ€Ž`´£ßsÎX¤Ob»™Ê‰'{ý>4làra¶H×?7‚ÅNcÝlú@ŠdÏgû͞ת®-Í ñ¡4· $Ÿg\‰íø>·¸j•ŠüÔ*'Yd 1üÑšðS?,¶çžbT~ÍØ#*3Ñ»w†¬Ó4n”âTJs„ªZ2º2EfBåi±_†íjº#÷/D]‹ÎvˆÉ†6„œÉïõéwOÖpãÌ"s7’ sjO[Ú“úŽb wˆÑ~1¶h™j‡ZA¥ßeÏxt®-cêEfÓU©VÒY€]äªFÌî*½ÓƒÑeUȫюØL"®Ävžà`¶roÈdZûVyEm¾u–=üCa»<ÍE$5™BpÉjÂ;Y Ö¶x¤ü4]°¾PåZ›W‡ï²Q jB‰Ìµ›LVùÄ®óØtü`ן¸Çi~Ù±Ž¢rë£<…ÉR‘¿ð}ÓPÅb4©ÑÞ†Ep­DK WoØ“‡ibû²s6Å/ö;¥ô~¡SŸ`Ïà&Üæ“-«ÑkSKHq¾4û„8Ÿõ=ze±ó,#z!ä7U‘1q¨5©t]ŒíEò@®rnšfÕ#ó£G×üpæƒÕíGÃö¡X;ˆ7Un­ùvÍ]K2d+³Êm ÿÍC qÿBN˜T¿2¿k\g´M÷ØlFÜ ãmîwà¥wv Uò{¥¡$[ˆTøõÔŽÄ¢"ÀÐÅqÀíÚYÑí{ØN5èvZdM*úc¹Ö¶m-bÝ¢í¹¨€Iƒ¹ìb[#ÀÊ– I-^f† lýšÙýEòÖ22ÚÉw@øZÔùl×zòÐRÜ+b)Q‘ù‘Ò|DŠjgæžÊÒõã`{n{ˆØ°9úk™Ù–` µ÷íp@–¢¥œQž,mn‹Ð¬“lÁ愚þ@„FÀÍ«†7_ŒízâžÛ±ÈÁ‹¹ƒ¢ÈØ×Í,¥ñcОƒ(rTFô8þ‹Ì (D8F~¶3 XUâœ%l.-a È À\·"ÚT÷¶ØN E¡¼>|`¿ëòP´ ˰ɥŸ¡èýØcÇ66A:µ”æ±ÅÃy°˜ÏÁ/ÆöawàW=)‰^šô¢ãâxf¿_ýµ6³^üÖÍ?ÆçáO%¹Uµ!K²ši:u1¶—>ûeÓ.$í-+€ûˆüÏßῥش›Û•!ÚF4Í0ÉW»4C=§WL.G[=ú)•®*œ:;Ëͯ_FDMh ¸9°+>@øïZ¶ rÛ˜Æk.:,W“{3KK|×d3š,9fãg°x~øÓõØn˜†¡¼´öhÞ Tì¿þë¿4Ìþœªây„Ú2=u4—¾²H«Ç kÍC€ÅSˆÃµº9Bˆû¶G¯¹ØNk‡ˆ„U¾ÑáÆš«šè<7K‘ÞœØ=žïµ±ì{ öøÀn-2RëäK2™Ôqd¾õˆ~¦û"{ÌãêˆÇ'ñÞ¢‡2R€ÙS‹œu¾¶ð€¡\)O† ùÎȰHÅI‡~DFÁ*ÓîÒ†ÓóûîlW”`U5­‚o뜹wô][N¦‚eÒgdÍpa•´j[‹¬9§vH4¤÷ãÇ!aö±s¶¯’.€%b¤®„;À|Ÿ1ŸVÊšœ<¹ùY&¶›èa<’•½zòƒ¬—æaC ,3eÐʨ~e£] ]·¨ Hˆ:dh)=K§žE„ØWЋª4Ý)Œ/Í«¹Ê¢ƒ¢Š±zعßeYâÄG9Ã3‰ððÝuÙ®ÇöH~¼Ž­ú?ÿüs<ÿ ­aH, Н¢™±YŠtÆÁÑÄäã'äFÆJ±¯ëÞx>¶G;˜²òª~Ÿ9s°®Ê¨HýÎ*9:²ðƒ¹Ü‰íÙnW!a¿°nÆ4rÞÈçiºƒ Ÿ˜âéOü&¯M¡´>ÑË3„ÎX(gë/ä“y' ÄÒmÖŒ^nVvpü²Eøpo Ä2zS/øw$GßéOK[3òÀ;qÁ±æ¹ãnˆí Ø-¥™ t°_\C„Aâ8om(1d¦‘þj`XÒðÔ­ÃÖ©£íÑìI×`;¿ù¯¥`:]à:ž,ýü×ä„ `(Ûþ#c»6œʨ½)ñSßâ+kÛ QZÚUF)sÈÖ¶+·3Ûõ¾Ž XÑÌ`ÛuÏÕA€ bfàu‡Ž—N†‰Ñ³¬!‡$µê‡j»§ËßíQô~iüFH*þ‹{íõc;kˆ00è‹nó‰äXqdù¯Ö|ˆ'Ñ\ܵþŸ?®¨måzÛ—óÖR×䓉-¨Ÿ¥«M¨_Ö¶˜üè7 ËÄö™KZ,2z–DZ¢É;hi~~ü¨>ð¯àu‡ÆíVÿEc+UÑ-[Î%xá±TaÛÉ¥Üp»ø.Éæ}Ä%Åv(>ŒØÊíûvŽÒe½öú±“j….J±n2â±ÚÜ3‚k®hÀ!s=¶k)Ñܪ˜ÉòÔo¬Äq,ˆ».»X–þØn Ìü±1‘åêD‰¡¯³7ùX´uF,Ó(sôô@mΰùŒv“^T!Ä^Åk\²ÏUÃh~~$œ¬úH-¨£  þýFH†ƒ6"dN…déO!1iùþÈÚ¨–†Îûìá+{öõcû»m9•aØ º¶µx¢e*EéÉ"TÖ^¸ep@*XÍ‚ãÛó "c;Þå‰6*ü¼V;Gq†Fæ°Q ÝÛÙ;ёƖ\i‘g|RÙK݈U«•` 9!Û­í¹8«ÿeïóWFçÆ¦\>FFl®ëpj' ë ƒd¿/mº¿x¿“´~?%ºoÉõ@míkS„áÅ÷D‹¬ «>*PPn£‰Äõ°ù†°­&¸N99ÄU‚†ý¢¿ ¨œñùOãRl§zâ¢ð/ƒ) 'I#Æ¿‘¬8Úœì…\™Û÷ŠÎ¥PVÉ÷EfiY":Q˜ÁDZìȸÖSYMž˜Zi%£ŸN.í öèÀ¾ØÌ{–>jäÌc¢»vCÎB“;1DüöxÓôdsðÃGaUb.vÍ2Ê[ÁöÇ–³+Èvð_Œ?WÛ$G›`€ƒøüÒ ±„Š ¸Ûù—Qúk `.½ƒE?•«Û«8`¯Ll¾¯—~UTÇVe>Ëå÷Y+„B1Û+¬ÑqÁpe<ÌøpŽ£‡yÖy•ÔÓ8#^c¡_Ôt'Á†;.¢9Né$åíÞÜê»!Ó\^¨©mÓqi›%¯_"CØÎŒa´ l⬠±§ †“Ñã~zJ[“M\_€íKŸ¿=1BBÙ½KZZ2e„xCŸØž[§³$UvhH/èÖAÑò;Aæ£AÙì§f@8“Tqï°«L*KKX¢^úô®·ônBrì÷¤ÀcUÍP·>Íbÿ¦iOnKïG¥@ÂWÀUŒ‹PÞ¶¿k¡2˜åÕq­ ɇt÷¨ ¿ñsˆímèÞ!8 kÖ<¼-â)!`»êfI ¬«äц¬½‹Æš£H5ì‰íú}m¾öZô–dæ†=©VhÙüxiq#Ñ\º!3Ÿ òS­_e>}†WÂMH1*,ãŠU¼£‡¸Ø±XÞ.™ÔE¯òl8³cqI_ãa{CØÎ±L™Zû-ù†¬›5AÖ›:tÂÒ`(¯wÛuþ~¶³¸¥ù`aÌpÞ1l‚5„KOöï”Þùó}c»rF;bÈUÙ|³ô«6Z÷iÖ+¼ÉëßzMG¸Ð*Yݬ_Àp–ŽC÷5‡Eÿ¯ëþ$!}+cÛ>}ú¤ÌÉr•q&vTàÍ‘XÑ$jßZ4ÝÇŽfKvM?¾!lGAДҶû¡†Œ-Qü4öý!¼††_¹0t1¶“9Y€ulÊö$\12mÚ²×|–bØ®Ž‚½âXP¼elW¸^$ò0Äѧ`«mÉýK!Ä[`kÑ?ÿüsErبHBKL¿ u³ùÂÒ{$”‡K¿FÀ /Û6b0Üýb]» Ñ-ƒÁ-³´Œôœ°”ÞE©»|ž™¬‰ ’Š9kóО¼rŒ~+ØþØg cÆèu6ƒj–Þçë«xÂ˶(öáÃ@"‰e´Ùn\í Y¤|“X#x¤$ ¡˜_ g¬¸ÛµíüøšÝ ÁZÄ™–+¬ãòð0DEc¼ˆÍ›,§Kçé%MêU––”,$ ÉWˆ–yRauìãFˆ]Z(šº¤¬ƒözüÍ‘6Ê.ôš;Ót̓W®ï‚×í-c„¹È–%j¨âaV c©B%îsS  $sCl§§¼¥ª²Q„£¡$è÷Mf†H>lû‹íd>…<$–Cq>“õ¦6’VÃÞ˜ñxç³J<ÂC«a‚1DJ‹^TLP·ÌW‹–9PXÆÀ#Zñ<\NU94H"Ì%Ãc2¤uø—GTp!õÊ.x[Ø®õÄHGBm³úGLÞQ>Sû´ò!éñ¯ÄöÒ¯î‡(xŒ ,Äh7 šëŒÿÑëTü¨Øž¹4¾´ôM$òŠ* Œb)ä<$3Úö4òvWjW–Þíú-½ý¯ŠFãD—‡^‰Ë=ÚAö¸.Ûì&d<Íã¦ý›5úm‘bfô^³ß–˜wÀ|÷¦°=œÊŠ"?íþð1S¨E 3É)—b»Úo˜þ+2X•ŒÏZ–Ý7Ûk5«â°ØnÛ'U˜)*6†–~°ÖøFSgøaÈ^¶t‘SçTó¸o#>/n¾ŸñVDláN.äÉ ©¬Î‚z`ä¼"Œ«Š©‚£u°Q‘‹&×ÎoÛß2†évÎ _ù¢$Š^Ó£©"Ùlq*nŠí1B¼ÜÜšj¥L†`C_ÌÌ)Ûå ¸Ã”/|Œ,Röª†š p3O¹U^ñˋ䋣c_G=ÄS¶°á¢7 Ÿ» =Jn1šî•3ˆ‚(2WRѲVïuý%EHö5‡òSË^u“`§·…팪âÁ©K¿F™‘Ðjž\ArÒn}¬ÎâØ2ÍTÌá¯j%ZIkÏOí!óÄöa‰Ñ‹±šXzu0Kšò[™Š˜ô'ÉDÍ‚È4Sœa%z‹$Øy…èîÛ^nE-ÅM­ÒçÏŸï·´öpRaøc×dÁÓ;ËÛôÌd825Öé¿5Ò“+»ïma;ÊÒŒaElש­Öœº£¬ÉýeCoˆíD€ S1®HଈÁ¶2h{_-¶Û׌K·Åv{xˆí4žÇB–† F”·Üü‚V‰¥ã‚õ…â¿v¥uº¾b\*£ÔvM|sR¯;lª|ÕUNbR ÔA{)oÞ‡ê¼&WV^pé•çè)½!lLâ͂Í6ffxÔÊóNÌ1€j åGÙF·Æv.ái5Ôl39W¨¹Ð׃í Úœå _ƒíksnk‰Š¢ØN†ðWµÕ­u¨¿BýÒVO4„2¿«¯„È¡²Ñ†é\s}·ÞG6|IµF¾9¶¿kžfU¡ÀX`šrÞäÄF·,¯œJO¼oØAÒ¾¸ÅrÉÛÂöǃS ¥ËñW­yˆâÛôVÇЯ‰í6%WÖ9{é³Z¹åŰ]ûš•ͩ£ï1z#a;Gç+±½lÛúž…íѯ}gÔ¬¥—~9ÕŒIg yeMJŸ”¿žd žukô#‚‰ÖÒ"*éH| »SIyQ5ähoæHÊ›å6Þ™ú[Ç™I7€i·ZH}÷±UÕ¨Ø÷ÆäªfZ›³Ê5\–úVØN$Ñw™ì«úG§7Çv³´‰cZ%URÃö"~é+±ÝG É¥÷oD?²ü••Q±Ñæ`{ìLa´Vbé]+Vmýˆ¾¢°ªÌÇõJ Iœöªg†œ4„ Žsg¶¼b…Í,'õ“›ŽËo ÛïäàTœw »¶‰ÇPÀëU<í_6?º!¶Ô‘ÂOÎÄÛY¹¬ýrSlWq]Åa¸H ²¢´•N8 Ñ\úxPì©°ù\l‡­^6lÏò9T(­Œ¶z².¦Ø°GØ^>@0ç¹ÕŸåªThe–ÞÿlÕP©‹–°Ž¶ßkÃöw#ÏÌi;Œé^ÎÙ!¬ÓU‡bð ‰nª—Ãѵˆa†¹Œ£w½?í aû»3Nå3r¬þÚ³'™±GçG·ÅvEEøamÍJŒ^Ç rõù=ã‡w>¶GÚ;£5шÞRÔ5D9¯BÖÏÇvb™ ÕXûiŽö¯ù5޳ÙÔ׿óCöÅh˜ˆ^M†\^8U~´ÚúW»€rûj±ÉÞé™AÌÌÒÎ+9€kí¾×ïOâùprÁgS*„/‘2âÍa;à íàÔˆ#Ç´æÑÿU «¨ËÈoùØnB«­¬Ú6ŠiY¹ôë±]Âo*¶®×”K!פô&=6)ÛÙ„x&¶k -æÄJ7ÅÔ"¢÷á,É9cÐÇf?3(Qïë½o؉ôum£JÝÚÖ^³Ýþ®™ÁÔ»V›nOL¸J?èïÉp|_š²Øä*EêÖ,3K¿˜7:niéÄãFûÎÞ¶?ÊÁ©pR© XÝb_6"©’þ éb )tá¯É'CënM ®°vøü’VÜ"iİu|åLl²—ÍQ£}:r e1 Ÿ›ó$¶GsJðƒHÀ¥(Ay¶h…u”ßÃ\“l{/-|ýÔçá×B#mhÒbˆb{$ãвR¿ªµT§À˜w[Ìgߪ)ƽÒOH‡}t+¢ ïṉwÖV}K1Ae˜ž"n}ºÊÅöh"Á`fun˜™Tĵeõ×>ZÚ& Ü2Äv‘†\€íø•L^Å©b¼ Q<1agEBÂacUbÏÄvýŽ¡¬æL3ˆ"¡É'ÙneP}ñ|lGÍ[ÚtŒFgóX?µö~¶QÇnvßðE­03?© Ô±FŸÉýµÇv¢X|xxxxÍØþN'ÚI%85›©u¬sUZ2ø£®'Ó­‰öÅž„+–á¨ÈÌî)žÝùØr¬ÝÕoÛwNUHÑ UÛaµ‰¨|R÷½¾¶ë+¸&Ê™ ™eOë¯Õ^úÌQå¨Ëyؾ$Ï!ßUÙ(’âÒªguVUe%ÕÞί`»r‰çò¦ER ¿@E3cä×UÀˆüü7+r桲Q[gŸ~™ÜËŒ]7ŸLÞp÷ö.e2ÏL%8WéÄìÇDõ,z{Y·òæ™x®ÿšü«`˜ŠáÉ> áàèø½IǽElGm£91ê÷™»$ì°+yÍÇ8ƒ†·G«}[l_z„ÖäØrS9± 2ÉÌx&¶ë§tнΆ‡W®¾À*Ùž£2Úe|Œí¹’«„?­bQÛ@£#HIv»6!z9ÏmQT_Ûâ>›f¬³rcî mrÃÕ˜©ãš8ðø:ö¥é±?P5Zâ_&F6qd¨”[xf®ÇóÜ5Ú¹”:Ó)ú]y~\½€ø¶«$o Û1èÛ9Œ885F€³§ %Ù™h)—emƒWù¯Çö8%2p'£J8J,‚=•»×v}13m(·%y$xsí×OsY‘²è£k¢p½ça;Áœn­Œ½ÎÊðBaÖŠ3 )ýPU„ìuíMö£ŠÁ^Ûáó*:žj>™xÇs¥hŽehÍioP‚96LÇy¤•ÅÃ>Xú‰RþÕG{M»L?Å:ëš^ÿ…Ñâ%¸!“ß ¶¿;<8UYm|ÎÈÆ;Y£‡ÄnŽí9(=úUÔ¬ò&„ÖL“X{>sÀ°=öÕÄì^>64Dµ¬¶š¯¼Xé&-7ãªÕ û¡€k:©ph–[3Ùkª˜ÃâBÄfi¹°È]Ã/g`Qƒ?zSaÐBY 6oâ­×Œí0–j%a§A­ªN•-nj‘ƒ«(É{ÂyðSŒô=’:X_?‰çŠr¹£µÚ«,¬„lA7j £=nçy÷f±=¶Ã\ôàTÝX=ÎÄ(@"÷&¤†Á7Áöl¾F?¸dˆS´±š "©ZeXP<Û#ŒÖßcï ¼ÀÐ3ê\¯mSç“ØÎO­-Š’•êl§¢÷2ÓøŠrÛ~ˆ¥%H‰Mˆödõ4åµ,> ã£ÊM…£üH=3p±Vk )#˜l™ÍW !wA c]$0ÎôŽ>œçÔì†ïj8‡ÎÝÐ.âjT˜ë¿þú+°÷¶Fû»7ˆíû§j5Öä@XÄka½¬1ɸ“3m>‰í¨ÆÛÉ“ ’À7=@S¿ Rrs†fÆð“ÇØÎ®Œdôê3ÑTߎ‹_ 9@óás°ÏcÁC3Î-5H7>h U$2þÛuž€ŽKÛË·ò¬ÐºfȺI6ÔkÛÀ ™WÜ>¤Ç-½êÝN1ëe‹ <»Ò;w_f ~MŽq•Ÿ¥'í\…Aݯ½6ר- g°â3ò¶K$oÛ%cTIC£K®PFV“jÚ¨Q©åc{ôºlÌYû#ÉûÇÊóù?´|g(½ÖqÖ‰{Ø®CŒV&—~ /YÑHô7‚!LD/²¨z&¶ÛðÁHï¥%à¾ÈzÚ0£¡ÙØhã+LÝasŒ\2¼ÒI\é ^Øc‹ì‚ܾžóRÏ!ݬúåËH¼ão‘ýŠEú¯ÝÜëbí8e»>c¢ø$¤S°a]RxR9Z/àÇ|Û.;ÆvÕ)¶‘íU¦)[¾¶[…ÁL†•Åú·$˜ÿéÓ'뼇í4¹UfX 6•4Î×V9;I…phç™âëE~ؘ£ØŽï0ÜÈÌx>YŒk3õ§a´hµgTw,¥ ¥N/T É:¸ÔtñËs6ÎP”iYN¢·Ò£ŽæË¶:¦psl§e‚*©“–•‚­©µ’¿bHÖ9c;õWENù£`¸$i‘àºú‡r¨ÊnÿZõ_BŠ^ë\òÎü¤~?c£–hwÖ4Ïe¿ í@cˆú* Oª5†½ú:¿iÑm yÎ5´‹"£¼•k…8èhU ­ Áæþzj'áj…“*«ªV¬ƒøÖFÈTSzñúv=3t¿G‹ŠÌ «jµöö³u“ò3³1zX0RHÞý¾ Ò— e÷ñüÃÃ/µƒª„`‹2·Û¬”«Ø/P­ÃÚ²I«™äkcUðø÷¾Ñmåí®e CÐÈ YÏ¡Dþ¯²K^yl¤ŒBCþüóO<‰W–Þ¥ÃB—äo?µ ·œ»éÖZ•Ò!Z[²I3Ä(J¬.:GÃUJ»ÑÒ¯S(åú kK¢VžZB0î¸ÑÊœRJý>ke6-öïXÍi9³álŽÒ†½Öüè‡}’ýŶ¥‚d&¥ÒÒû 3W­sKïóÔ( (p$ÞµcÂÞ¶[1 'ΊÀÑy™«MêØQ »c&:ùluPá~+ $§ó?~üôé“çÀLãë†Ô^¨³ÈUª»J3Ým3QW¸°¶ßü$†Ab@¬ü,’ 2Û6ª8¦&ª§Ì¶ô×_Ù‚±½–X±ÊÔ„àEì£\ÙÙšÊ'©›ñcæNµuYÔµïhSt—æQ¶(…@´~v8M>P=Dw4@óo\Yá»>‹{ˆË1ë©Æ:Z•~ Æ3kl×¶1 ¾²ú&È*¬B“AbwÖ-r¯]´XEu›Vè‹&ÌŠ?‘ðŸ½Ãñ«´a…¯hH¶"U`X«*QTURaÖ,fvѦ]•·l¾µTÿêÆÎ]×ÿížgö)Û¢~ ë  ¶Õ"y´ãbÅ÷ïßë^*5{t´×§:1Éãå â ·ÞØø5I=3!iÄNm³ªbYa=¢&„J ;ôØD7…ækË##¢‘œKïYRp¦RP=¢Ç%“gìh–§nôV<1¸6j•l¢ªð¨ D\"¡Ê¬rzf’+ùer›ã~ºU%ìåa0’y¸…ú¤,?ˆ1(¡îG‡°Ãwr»´ÇU¢È=ðŠ 4õ~ÕÖ:”@V߮Ѣg†›U‘F¬ÈôVíå˜*‚¢ºŽÚ $Jœú¤eó¡a/¤â91ȼ^t¡ñœ^—oÕ)Àvºƒbó†˜aœ€¨º±íj`D´oD}¿Õú¾€lêøre)}2V­­ÂQFx¨L‘]ÛŸ?ÆOZau^qÇ•bB¥¨kVÁÒ"¦¬&Ä7EZÆZP™à,(8hÚ• ÖYS ëC°È>˶Xõb„“ðŠÃ–.›Q VýÅ ¾².×Y²Uæ5§vÚ,l~¬†èL$doÔ"ÛI´¶«ŒÎ6Nñå0þå\&”ÂÌ—ƒ‹vÊ*󫀩•½ÈFÁ»¾l‘9>¸úFvÒãh³*ÏJˆ¶big¯›¬RÒ)'ÑeƒtЦ~(Î"âÏÇóo»êÁ”›w-)ºú ÕL1£^›–q,À5Üwí@õ]_PIºb À …sÅJûwI~KÕ¯%í5PuË‹¿`&8˜‚AHˆT†”¢_(Ä3á¢3…Šaó”êljjY¼“MÐ\%ÖœEñ^Åñ®:BIÐå×èMM³”J[‹ç¿àREr,”@;8@ÃeM”¶†XO-º‰ã216dÛ²vq‡¹Nñtf oOéÄ@¢ºžZ¼Ó±áàuØÒOi@”ö¦2Ó¦ Ëhkª@,QÏçM`á[=3j¿Q/Vqœäy:W™ëQN²DI…â9¡@ñ<ú%Ñ׆çJd&½ ȑŶ+@)=I—o*¼Rjº_¼vÏå€h“5®;£#èRPíÈÓ^«¹öûÒŽ$¶ƒ¢Mʶ^V¶#¹‰<ê×ÕB92ªc„®Á16`iI)uâwBLw5QXãØ,Ñò Èî£ÞÄÀ*¦Í´!äÜدW/h¨àÙò´¶TÙ²n¾,¸D ’uÚ… ~rm|!«պK›¶Ið†B¦¡‘ ­G'¢š:lmY($K¿8«3µÒ[V JªTûåËŽhoÛß휬ÊùžK¿m<\ûÉæÒáuÁ»?~„@îá9ÇkÃóoåB.eNêŠÞ*ñxª!¶ACŸ¯€“qi~3tt£¹Œ`ņ®ènj+!BµI6dŽ¦æ™‚ÈÑÛ£ß@Çõ; gÞ€Hv ¡>ŒÔÄx½bZW…-úµHòœ6jˆéÈo®}xü©E®.ÍkAWßç;àäÚO.8 èúSÖLz} ć]]?X[ÇüH @"L\Íá@YR¾­²€ÏBÓëÇ÷¸ln žÛR/ି¢Ú)ùÍEv¢ÙŒ•æ4M—hc1 ,‚pM§ UUq(¨Z­¥Ê(Ê {'ù¿`=ö!‘´¦Ð×ÜþI dÀ67âq(À«ZŠçÑ–fjïÔQ ¬‚Ú›u¸Œ†çœA¼ÅNÏÿòæI¢ñõHŽØÙi•å 8 Ä@}xÜ*<¿o;ÁLd £8%¡·?z:µ”øýÒø Êâ(LÇ3¼¯X+Œ9)6Ms(GCÀ[Î|¹¨A%âx±´EsüÄ%ƒß[ªÛß[ / aU› 5¬ á1 ¥.ÚCÞ0¿V¤5¸c7E³Ÿ°w-@1ZP=œ,åp¦C09S/>}úÄ´7û” yéårã9ÂïÉR4¹c]‹Fp‘"'|ëø3$.9DV¦Õ.8mᜈAtj£[þ‡[Dï^GúW z?(Š„øHï[8 ±ÄI"ªJþõ¹+U/ðÄÞcK£yŠÕàÉnsb`~‡ yB v¤îa;š£ˆYáBµ’*ZéœéÀXU#ö±ÅXâîښΩ혀[ƒ!G˜ \œ*,@ò17yL3&Z²zŽ8§B°…ƒ®¾ò¯F˜S`üBÑP4þÍaŒÒüHñÕâå¿RG®?¡ ?ú¬ziÕ¢6s…ãQ½‰jtÖÇhECްЦ¾_K6aOTØ.ŒÚ˜—UþׂÁ«‰Ž!E¹hÌ}tpüŽd]I3›ÒQ¤#=TàGÆs#E³ *ǠԕسóGK_ÉØ³z±'ùðF;;5Zîß"xÛÕO«8Ï'ŒXó¢³}XgšdÑFhª£Ö,NíÜöÊ.0JnTOm‚²Á h€ë”ò±­Ùî(ð$J~¤H8F Èdˆ²èí¡O?Ým³Úh.-š&ô}¿_o˜õNg@OJ]ˆ 9Ϊ§ßÇ’r5šŸ\%ëHhËý¶~ÄÎ…4j—‹7+@†³\°š}ZÅæ¡\4ˆsÑÏU«7G*!„Z>ª OlN™xnD6Rª$CÀIM;LÆ2‘…f9#c/c FÝ£&L†@ßòð¯bxŒ¼1º@̓‡ A/Ïš´7Vá7FÒuP`4Î]Oì~¼ö³+()ÂW©f7±&ùÙ’ÑJ¢»¹- „È<óe#\k•þ-tß™îdÇÝN~$cæÔí©°"f[†xZ´õÈ3le‘Ø–8}V3Õ ã Û¨Õ&ŸUô…K6Dªô"Ú ×ºÃh⹑™R€*>9IÊ,¥]# &'pa½r•Ð8mïUâ“õ'uÅ,’{?ÚbÙýA‡42wçm¡%\˜ÂÆfªÈ)£*s¬¬n|°õ£Ç6ïF/¨OÀÆ½É h†’Ï!†’ÏîVBÑ?%b•"‹Pa8S^jµÂ:”ì5ä°UžµRñFq$mÈ^=Kž• ÚÌ\úm‹þ>H5NhD]x|[þ_!e9Üã$‰Ši,½¦èDf¹Aœ@HàñÒïû1†DWJÚ²æÈõ_˜‚lø“•ÌpA„Ü#•=4,KA^Ó,¾ùd7iO‘JAMø<Íc+WGVþî<{éYRg 9³ˆw²@¯ËMІäκr{õ9¥ß¼èïƒT)TM˜'ž“Ùd¦ò3ãí$ßùi;1B΄`EŽ&bÓDß •a8 Ÿg?WQ ò,©0…ÍRw%£´#22ä>²ÎRÊ¿Gç?9,Z}–0KÝAC.^ÐÙk• ¹¬¥_§èRLF]LÿÙhO»ñë ‹Ãgï%¡nô‰|AC׺¾â9‡]u帊zM´ð9\ziñc)_¡›^𾛆Lš4 D-þi‹ýf؆æùÔ­IßH„·˜Ò'cÁ00Œ+s#Lš4iÒ¤'éQV»°™Ñ²Zåˆs¿¬’+•†ýI‡+£]1ßå¿I“&Mz%t€ísXòSúL­ë(¹wâÛ¹ÓhŸ4iÒ¤¥¡O†›ú÷<í!±Ž õš‹‰ù JËTùÝœk0iÒ¤I¯œòZ*öÚk^¬"YÕÁN{^Ý2Ø%Ê­¬ØÂÃX⟾‹s &Mš4é•ö»ë‹HÄ.êŠ*=0ša†Y@yÿ$§´€ôÀšé™4iÒ¤¯@Ø»‡Ì]Û»„ü«4Ý9QCÕzWHÇGöSÓ_Ç÷u®Á¤I“&½Zâᤱåú¸oÙq5†É£ˆçL –}ò $°­ÀþýD6iÒ¤I¯Ÿ4WÖ:‘Ù¹ÖïÛ¹Z´áy<ÏZÚ!hÈ‹|†Ÿ>}R‡ }zc&Mš4éë]îÌñk&–þt鵑f§¹ÑsߎmŠà\ƒI“&MzTQ÷ï¿ÿFhb…b—ÌFÏÀý‚ãgé¯q†¦h~'' M`Ÿ4iÒ¤oEt¹ÿ$ÇÇã …"G&1½Ì½å†c€pÿnKˆýS; ö.€6iÒ¤I“¾&=ögaG;'ÇâÀß ç1dz”[ÈÌ\™½»:ÿð¤I“&Mº†xŠ+ÍòþóŸŸ?Æad!G¹ñ2žÚ£Cy¦fÒ¤I“^ÁÆžw‰ÀH¸Ðq Yœ}”Û¤I“&Mú¶x§Ÿ\O"³CÐîöÏIœ¨>iÒ¤I¯ö\3ŠÑQnߺî“&Mš4éˆÎ?†lZé“&MšôFé?Bߺ.“&½RúÿtòtF endstream endobj 10 0 obj 30764 endobj 11 0 obj << /Length 12 0 R /Filter /FlateDecode /Length1 5904 >> stream xœÝW{xE¶?Õ§kÞ=3ÌäyDBHdä! ‰n" Ñ`ä!¢F‚T|²hÄ B•G ¨DÊS@>Ù•+¬®Èu#â*÷t'è~ûÝûí_÷»ßw§¦Oõ9U]uίίªX |åUÓ«¡üü?¬d„Pùó}Ãw4‘J*«o®š"Ýz@ÙAí›o¾mQe¼ûƒÚ= R2kæôŠ ï;ß°SÈ™Eû6ãý¤¯$½÷¬ªùwM|Òø é­¤ºíöòéi,‡6ߨªéwUn0 },é¾ê¹3«ï¾î[Òg_ *E\É7·Fð¶‚ÌúPGëó&3ñG%ÒÛÚÃÁÙngD«~5àWý•2tÎÃÄÎ3¢Îh¿øÓ\C?m6ä¿ÀS°ÃƒÑÒ^Óž°ŒÃÍ,Ðæì g¶‡3BúÐŽ¡ጠÛÙÁ‡Ña*VBJµ²F±°²hšärieK¤!l‰X|iŸXÌ_úPÊô—]zX›SÒp’¢ .Ýÿ~A»á$l—×Kœ)28MÎΡútam2s±9d®6ךå?&ØÌ*E½vÉ¡ßÖ\â+}ÌÒ®o¸E¾\#ƒ~pÆÌ‡Lí|}·&ê€meR¢[2¹%)Žü$Š«#ܦFåP§;§¢ãœó\nÆŽôä‚d‰•1·ßò2s«v–âÕ ~ðA}RzŒÜÒÙ¢¼ðøgk‹Åâ¨ØÆŠX/fbÃÄÓ C³îwJY•÷Ý7r”g dÙ,–E±<ñîŠÊšsº1§ø1L¾ª08£ÚÍ#à^ë3öæíF‹A“3ÊÙYÔ]RºL]{‡LnÓ9Ýqº³MÍ%Ÿ3X#GÜîáäfL,†ÓËÒ—=«!3z{MÔ•W`zŒûõ—.uÊ¡æ93‘÷`$¿OóZ «ƒWš ïµÂDZG kìGUß!ïÁ¤5)Ô•6H‰Å8ŬX‡yQqå÷!°Ú:™™jnn®îHgGØù·sÎåFéîǧ÷ææ/è{Ì?Ç7ÇŸï>ußÇ}ûWûVû·ø¶øwûvûÝ™ž ïHOÐ;ÉSì-÷„¼yj½+}ÿHUO%V*N÷Ø´êZ-O&¾9çÝýÒ«—n¸=¿bNBJß-Ï_úÜິiFÙ¹Ë~ôðÔó¬—˜N™¦ÕÈ©Q“‡x5¯å=ÔÔiipýîæøT⣞uB &Æ6ÀQ»ÚÀšVÚ°õè’A‘‚ #¬ùÉ:3uÊS†‡§ Ú ž§;žÕ×¥03§;mÜÈf‰•…M Žˆ Ìrdþöuó-š7wÑ"l•J ¯+ŸÊÆ1¤2®¬sÿ¦µk7i—W"ño¢¡*˜`ç&6¨l»©,&«Y2“kÎ({‰Kã £¤¨ÑY2¥¨QÕDTÉ¢¤‚C&mëÚÖ¥ó¡-ÜA{-A Æ@A´D ÃéP‹¥b,v‡¤šË ;Õ“O7”ÏjŠš¥JYìñÔð©ÍâpûëMMüEñnˆÀµƒ»àõvö%6\÷y6åé ÊÓ¾ðU0¨Ø$»5ÇãõpƒÑdæ²%Çëõ,VWvë9ì:än[£Êk”ÃWx,Vo¢&%^gw‹{]s…Æçöði¢®Nèî¬þYËê¨ØÜŒ¢I¥F§ý¬›ÛSMîÕd²2V¼Él6[ÌV«Íª˜<%Á– $Øã©¦4sš%ÍšfKSúùrMùæ|K¾5Ï–§™ -…ÖBÛXe¡m¡²Ë´Ë¼Ë²ËºË¶K Ø v£Ýd7Û-Šu°RÐoZ¿nˆ.“ÉËä·K& úªúNL ŸNû^ö J9vÞ‰i•åEÓ XôqQDn?[së©ù·ÌWUðãÞŽÎò/ˆcç32²²û§YÍ)k_Û¾#%…9 ÊËÍHWLžu/5möüÎ5Âõ_¹¶ý¿çÚéÏ5÷¿áڪ嗹&‡6tSÒE;‡¥sØBY90cÛj´n…'ÌÑŠ‘™œd«‰cW÷a¬v¯Zz‡v¾7›iôÉX)ªŸ©ÝX¥´J‹Y2(Žˆo„XÌ–¯¾ûîj:Œ¿ÿáÒ¥ˆ¼GL«ª¨¸MÏ­xq5aË Žt$ƒƒËV›‚ödÎeL–¹,%3‰kJê•Lï"»Err°¬1}°Ýé°[Í&Ž#ö8íE–.E.3 q¦-VO6‚RÕŽžLãgNSÏÅõ‹Ïtvr/Ü@ÀHN*>)(K3¥ùÒR©VzFzZZ+íõ²—J»tTúšJœÌÌÈ97p#7Å07wÜÆÞ¼·¡·1²ÙÌ1äsí¹ŽÑ0šb¡<š6Ü,Í4,•–ò¥†G”GìÏIu¼Îð¼ò¼}“ô*¾*¿bÅñkÅf¹ÑÜhyÛºSÙiß'TÚ÷;NHg¤\ÊÚh¿™ù™õgÙ~‹g•lQ·Rô³{Õ‘ê)“b-rè’Iºø[N˳?äygéK¹g¥ìŒ64DAƒí@ÔÊ8óÇxáÎׯóò±s.ãÍÁÑñc¢ñŸÎGZh¼üvBRzgaMÍÂ÷Þ»€ü-ZÄ_Å_ÄN6ïy­¡á5íb ö‰0•}lsQШ×.`z=dðQ-S!ËygWIèêrBO¤êµªçö¼ œ¬F0™òØ 6Ph³t€“àÈb•”ã§®Ã`DЫ--T’U«7³Sû ê¹D~ˆ•P]%ÓòSûbù°æË‚pÝä ¬vÁzz {‚å7j½{¢àç¹R.”ÊUò0¹I^"7Qr¥¼IæJÇää{ä#„}©æ¯]ú›i=+d)P/Õ³Q´Œ£¤Ãðù\É X=ËçùA8ÇY1õÜ % ûýD/¥¬‰žº˜—´l)›cß’Ç+á–r ÔÃS,Š´V8L~ŸŸ`ÁZ OñãRbý{p >%;Àl&‘LÆü8•ó° f2§ˆÇ .£_®”.B˜= m”.²&Q‰b^Bó&<,‡äåG©•Ðaf¡GœªõàÇY=yqÊPÉQ?­ÜCó„¥÷¤fŠqœ¤¸hviªtT'ÙV¶‹<xˆm•CÆr"ÔêåR8§aǤÄG±ŽÇcð˜a \ pdz¼IC ü"€ßXhˆ‚:Vh|€" Ú:À~üîB½L†d¨“ûâ‹ä»$Õ\Æ-‚ÃR.΀ô²‚5à h†yZ’bŸ·ŒÚ$©>g£WÑœXêÛ7Ù? õ_TŸÓèk„âFe‘¯¹««¸TNä“yR#Lr åÔÿÔxj@jQq©¯™]1zTϰ£C£È8©”n5Ìd=JoÓfmäú 5úÊgù–9—¥ä-sÎÌ 3Fš0uñƒfÛ4ÇПÁkÒÓu_¿üÞ—ë_ævnu6Ϥ¾&¸Ì1’Æ*Aû­ÓÿËÜ_3‡{ìüTbG¥¡ Z¥=°™WA)þDu%åþ(åM°Y³KûétÙSñH”߃ٚŸ„V>âùb(ík<]{iRZ!FµD[’ ¿¦m€¾=‰úŃtugÔfl0˜÷iß¾ºw*^ ýá.b¿DoA-~VòS-_õ TK«hß@Ö›ù!8ë ÒR€9ú’ê¶õÒûi÷È|z»ZHz ‚d²ÞšDïíÈÁC2A·Äë2N—±ºŒÑ¥›¢²Ó¨n]Óî‘Eë÷Qºt0;ÔP»C×´{¤Ï&p™ÀG>òp€?"ðá.øÀ3ñ:¼_àµñ¸Ø‚÷ ¬x¯À{"xw \xçF¾PàqÁüD¾ ‚óq^çÖà«oOå·§âœVEð¶Þ*p¶À[Î*·ñY™x³ÀÊLœYaá3VX°"(—ϰðrΰàô›O¯ÃSyÈÓ,x“À2A'”ʧ œrc"Ÿ"ðFÒnLÄÉK#ø'7ìºAàõK<8É…×MŒç×Ep"5LŒÇâ ñ¼8‚®Uù„x¼VÅk<8¾ÈÅÇ»±¨PåE.,gç…*޳ãØ޹ÚÅǸñjŽŽà¨‘v>Ê#í8âªÁ«hÌ«,pð À‚áv^àÀáv6TáÃbp¨‚ù˜'0×…CŽÆœìžÀìA.ž€Ù{åA…rá Z9+ÓÆ³\˜”3m80c#(0ƒÆÏ؈é6L‹Æ©y|@SÝžš‡ý+ðÊ ì'ð 7öUy_öñaÀƒ½S€þ½=˜¢b/Px¯úèÊ>z-èñ`rR> stream xœ]‘ËjÃ0E÷úŠY¦‹àG^ CI7^ôAÝ~€,SA- YYøï+ëš*°àxîIw²KóÜX({÷£j9Po¬ö<7¯˜:¾+Š’´Qa¥´«A:‘Es;O‡Æö£¨*Ê>bq ~¦Í“;~D”½yÍÞØ+m¾.-~µ7ç~x`(uMšûØîEºW90eɼmt¬›0o£íOñ9;¦2q+©Qóä¤b/í•E•ÇUSÕÇU ¶ú_½8ÂÖõê[ú$/¢<Ï÷u¢2Ññ Ú }¢S : V€Ž èZ{>‚蜨ÌAµ=¨Ã +)ÐÚSCÉ NtX»ôPâÖE%X_ºD±Ìíž³ºy#NÃMÙ.©Ë÷ù»Ñ-®ôýšl endstream endobj 14 0 obj 294 endobj 15 0 obj << /Type /FontDescriptor /FontName /HALMKU+DejaVuSans-Bold /FontFamily (DejaVu Sans Bold) /Flags 4 /FontBBox [ -1069 -415 1975 1174 ] /ItalicAngle 0 /Ascent 928 /Descent -235 /CapHeight 1174 /StemV 80 /StemH 80 /FontFile2 11 0 R >> endobj 16 0 obj << /Type /Font /Subtype /CIDFontType2 /BaseFont /HALMKU+DejaVuSans-Bold /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor 15 0 R /W [0 [ 600 836 342 678 493 674 592 711 342 348 715 478 595 711 732 651 687 ]] >> endobj 6 0 obj << /Type /Font /Subtype /Type0 /BaseFont /HALMKU+DejaVuSans-Bold /Encoding /Identity-H /DescendantFonts [ 16 0 R] /ToUnicode 13 0 R >> endobj 1 0 obj << /Type /Pages /Kids [ 7 0 R ] /Count 1 >> endobj 17 0 obj << /Creator (cairo 1.10.2 (http://cairographics.org)) /Producer (cairo 1.10.2 (http://cairographics.org)) >> endobj 18 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 19 0000000000 65535 f 0000076211 00000 n 0000002354 00000 n 0000000015 00000 n 0000002331 00000 n 0000039673 00000 n 0000076046 00000 n 0000002491 00000 n 0000002705 00000 n 0000039649 00000 n 0000070667 00000 n 0000070692 00000 n 0000075045 00000 n 0000075069 00000 n 0000075442 00000 n 0000075465 00000 n 0000075746 00000 n 0000076276 00000 n 0000076404 00000 n trailer << /Size 19 /Root 18 0 R /Info 17 0 R >> startxref 76457 %%EOF PyTables-3.7.0/doc/source/usersguide/images/pytables-front-logo.svg000066400000000000000000000434461416254111300253760ustar00rootroot00000000000000 image/svg+xml Hierarchical datasets in Python PyTables-3.7.0/doc/source/usersguide/images/query-time-nhits-cold-cache-float64.svg000066400000000000000000001420261416254111300301420ustar00rootroot00000000000000 10 0 10 1 10 2 10 3 10 4 10 5 10 6 10 7 Number of hits 10 -2 10 -1 10 0 10 1 10 2 10 3 10 4 10 5 Time (s) Query time for an indexed table with 1 gigarow (cold cache) PyTables Pro Postgres PyTables-3.7.0/doc/source/usersguide/images/query-time-repeated-query-float64.svg000066400000000000000000001521261416254111300277730ustar00rootroot00000000000000 10 3 10 4 10 5 10 6 10 7 10 8 10 9 10 10 Number of rows 1 2 3 4 5 Time (s) x1e-4 Query time for Float64 column (repeated query) PyTables Pro O0 PyTables Pro O3 PyTables Pro O6 PyTables Pro O9 Postgres PyTables-3.7.0/doc/source/usersguide/images/random-chunksize-15GB.png000066400000000000000000001710731416254111300253670ustar00rootroot00000000000000‰PNG  IHDRÐß}™SsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwTTW÷7ðïÐ¥J¯‚  ذ;؈]Ñ<¶¨QÔÄjTŒšX¢b7£Ñ$?cL¢&,,øÚPT”&EŠ€4©3œ÷œ‘ë Ã41û³–kÁ¹gî93\‡Í™½ïá1Æ!„B!õ¢¡î B!„Ò˜PM!„Bˆ (€&„B!D@B!„"   !„B‘ЄB!„È€hB!„Bd@4!„B!2 šB!„PM!„Bˆ (€&„B!D@B!„"   !„B‘ЄB!„È€hB!„Bd@4!„B!2 šB!„PM!„Bˆ (€&„B!D@B!„"   !„B‘ЄB!„È€hB!„Bd@4!„B!2 šB!„PM!„Bˆ (€&„B!D@B!„"   !„B‘Є¼Ξ=‹ùóç#//OÝSyçýðÃX¶l™º§ñÎKOOÇüùóqùòeuOå½’M¯+!ï   ‘bÛ¶màñx¢ššš°··GŸ>}pöìYuOpãÆ lß¾………êžÊ;ïï¿ÿÆÞ½{Õ=w“'O°téRÄÄĈ{ñâ¶oߎ{÷î©afﯼ¼¼÷úu-..ÆÒ¥Kß™÷FB”EKÝ ¤±;v,Ú´iƒòòr\¹rW¯^EXXþøãŒ9RÝÓ#Df‰‰‰Ø¸q#Úµk‡6mÚpŽÙØØ 88>>>jšiŒŠ‹‹±qãF€¿¿¿šgCˆòPMH= :cÆŒ}ÿÇ`Ô¨QX»v-Ðä½cccƒÕ«W«{„òN¢BhÈ!066ÆãÇÁµgeeaÕªUèÖ­lmmagg___œø-Z´ÀâÅ‹QYY)6^yy9V¯^nݺÁÂÂÞÞÞX¹r%***8ý:äååaýúõèÒ¥ lllðÑGqÒîÞ½‹qãÆ¨¾¾„×Ô´iÓêýÒh0BHBBBvôèQNû³gÏÇc­[·æ´‡††2[[[6}út¶iÓ&6cÆ fooϰíÛ·K<÷ÇÌtttØØ±cÙ'Ÿ|Âtuu™––{ôè§ÿúõëÖ¥K¶yóf6þ|fnnÎúôéð¤¤$Îülmm™žž›5k[¿~=ëÞ½;ÀfÏžÍ9ïŒ36jÔ(fjjÊæÌ™Ã>üðC€=š9s†éèè°Aƒ±©S§2===¦­­Í¤¾~GeX@@³²²b3gÎdË—/g§NbŒ16sæL€988°E‹±?þ˜ikk3SSSÎù322˜‘‘?~<[»v- b­[·fؤI“ÄÆ6lÀ†ζmÛÆ&L˜ÀìííYûö홉‰‰Ôygdd0¬_¿~ÌÄÄ„õèу͞=›3mmmvíÚ5Ö¯_?fkkË,XÀ<==6nÜ8ÎyJKKY×®]æééÉV®\ÉzõêÅ0///VVV&ê{âÄ fooÏf̘!víìÙ³‡sޚ׎®®.?~<;v,ÓÖÖ–xí¼íÒ¥Kìã?fØÐ¡CYPP bGŽaŒ1vïÞ=€…„„ˆ.꯫«ËFŽÉ&NœÈttt˜µµ5»ÿ>suueláÂ…¬Y³f Û´igì””æììÌx<ëÙ³' f^^^¢çS&&&¬}ûöÌÁÁM˜0mÛ¶ >œ`Æ ãô­¨¨`½{÷fX«V­ØÊ•+Yß¾}ÇcìÕ«Wb¯ë¨Q£˜““›7o[²d »téRóY°`ÀìììØ_|ÁÆŽËttt˜‰‰ {üø±¨ß£GÄ^WÆ[³f ãñxÌ‚͞=›}úé§L__Ÿéëë³Û·o‹=~àÀÌÀÀ€ 0€MŸ>éëë3É:wîÌš7oÎ.\ÈÜÜܶ`ÁÎxyyy¢kÖÛÛ›3€uíÚ• Qß   Ñkb``ÀÙ°aئ¦&300`ÙÙÙŒ1ÆâââØìÙ³æãã#º¦¶lÙR¯Ÿ)! ЄH!)€~øð!?~<À.\Èé_\\Ìø|>§M 0777Ö´iSNÀ$<·³³3{ñâ…¨ýßÿeجY³Dmééé¬I“&ÌÇLJUTTˆÚÏŸ?ψÐÂùýóÏ?¢6>ŸÏúôéÃx<»uë–¨]@÷îÝ›LŒ7Ž`ºººìþýûbc.Z´Hêë'  õõõYZZçØÙ³g6sæLÎ/츸8Æãñ8ÁTee%gnBcÇŽeXTT”¨íŸþ·¦µk×22Ðo¿†OžçÜsæÌaØÉ“'9ÏeÇŽ ;xð ¨M@{{{³‚‚Qû?ü ö’ðÿMPPP½^;B+   ‘Bø Õ‚9991Ñ/ª5kÖ°ÊÊÊZ+Xvv6ËÈÈR<;÷’%K8+++czzz¬_¿~¢6á/¶·Wó355åÐLKK‹™››s~¡3ÆØ6þ|Q›0€®ùK³fßž={rÚKJJ˜¶¶62dH¯\5a=xð`±cƒ bššš,99YìXÏž=™¡¡a­çÍÍÍeì÷ßgØ?þ(:& üÃÃÃ9¿²ÐNNNbÇÆi_±bÀùãÄÜÜœÙÙÙq‚ƪW¦Å^[¡š×Ž0°¬¹’YÛµSRRÂtuu9×Nm@8Ó·  €ihh0 –™™É96jÔ(@ôÇOVVÀúöí+6fBBÀæÎ+uîÂúÉ“'œö+W®ˆ}àììÌ,,,Xii)§¯@ `¶¶¶¬sç΢6áë(uBŒÇã‰Í…1Æú÷ïÏtuuEI  ?ÿüs€EDDˆ=^ø¢0˜>¾C‡bÏÅØØ˜`111œcÂUaá':|>Ÿéèè0OOO±ñ^¾|ÉtuuÙ¨Q£DmÂzëÖ­œ¾ééé ›6mš¨hò_AE„„ÔS§Nàææ†—/_"<<éé騬¬„–÷¿c ;wîÄÞ½{>ŸÏ9žœœŒ>ø€Ó6xð`Î÷ºººpssË/Dm‰‰‰€¾}ûrújhh W¯^œ\Õäädðù|ôéÓÜR‡’’’ÄžãÛóhݺµÄö&MšÀÙÙ™3?i $ÖÌ›7OìXff&Š‹‹‘žž{{{À‰'ðí·ß"**J,×8--Môubb"ôôôðá‡rú¸ººÂÉÉ ùùùrÍ»uëÖ(((@Ïž=ÅÚˆòÆsss‘››‹fÍšaôèÑbçÑÑÑABB‚è{i×NZZÜÝÝ9m’~6îîî2ýldõÑGq¾766†ƒƒ,--ammÍ9&|M^¼x'''<|ø@õÏwøðábçÖÓÓã¼&uqrr‚««+§ÍÇdž††¢ÿ/¥¥¥HNN†­­-§¸&IãIú¹×&..úúúX¼x±Ø±”””——#99-Z´øø‡BKK ß~û-¾ýö[Î1áóHHH@ÇŽEíoÿ 444àááôôtxyyqŽÕü¸¸¸ 99(++«õgðvî5 ~­ÙÙÙÁÊÊJ©×!ï*   ©§‰'r~6 k×®…»»;Æ/j_½z5Ö¬Yƒ#F`Ö¬Yðôô„ NŸ>]k1‘‘‘XÇã_ ‹„ÁdMvvvœï…÷ƒ–Ô×ÒÒºººbÅS’æÁãñê=?iš7o.Ö–ŸŸ}}}ØØØˆ³±±¯¯¯è”Ó§O# ;wÆêÕ«áéé ggg$$$`È!œ×µ¨¨fffÐÕÕ;¯LtmÏ]__šššbíD¯‹p333‰Ï1 úúú¢ïk»vþøã¬X±¢Á׎¢J³¶vàÍk’›› °¶¶†­­­XÿÉ“'ÃÍÍ­^óxûºmmmXZZŠ®ï¼¼<0Æ`ff&q¼aƉý H¾^k#í:îÕ«—ÄkQ(77çgkk‹nݺÁÄÄ„Ó.ÏÏ@Úu9f̘››‹µ«ãZ#ä]E4! tðàA´iÓ ,ÀСCE¿\<üúë¯ÐÑÑõÞµ¡œaaab+ioßC¸Ò%éW®\Ayy¹L‚²¸»»ãéÓ§øî»ï¤ö=|ø0444ðÓO?qVÏŸ?/Ö×ÙÙ±±±ˆGË–-EíEEE¸}û6 ó¤hÞ¼9tttàääT¯çXÛµóàÁ¥ÌOX©’p½[·nøê«¯ä:×íÛ·QTTÄ ì’““‘””$Z-µ³³ƒ¡¡!ììì”¶Ž»»;îÞ½‹½{÷6è5uwwÇ“'O°mÛ¶:mEþâååU¯ëR긦Qº! dnnŽ+V ;;;wîP½Â“““ƒ–-[r >Ÿ/ñVd²¦#\¸pÓž••…ØØXN[Ó¦MѪU+ÄÄÄ ;;›s,44Ð¥K¹æ£¾¾¾ÈÎΖx‹¿·eeeÁÈÈÍš5ã´ÿùçŸb}…¯ÕÛ@\ºtI,-B™455ѳgOüûï¿¢âkÃË/Ä®òòrœ9sF)óóôô 9…AYÜÜÜ`gg‡#GŽ ¸¸X®sñù|\ºt‰Ó&üÿ!¼x<z÷îË—/‹ý?Q___äççãØ±c ~|EE:¤à™Ifdd„Ž;âäÉ“ÈÊÊRè¹­¬¬`ee…ääd…ž—w ЄÈaÊ”)°··ÇÖ­[Q\\ ‡nݺáÒ¥Køî»ïðòåK$%%aâĉråç燞={âçŸÆþýûQ\\Œ¸¸8Œ7¦¦¦býׯ_ÆÆŽ‹‡"??GŽÁÞ½{áêêŠO?ýTî9ÉkÙ²epvvƧŸ~Š}ûö!-- %%%ˆ‹‹Ã¾}û0sæLQß.]º ??AAAHMMÝo[˜S[Óœ9s`nnŽàà`œ;weee¸ví¾øâ ‰¯•2íܹ À©S§››‹‚‚DGGcíڵغu+€ê@¯sçθtéöíÛ‡üü|$$$`üøñÐÖÖVÊÜìííѦMüüóÏ8xð .]º¤´ SHGG»víBrr2úõë‡+W® °°999¸yó&,XP¯{.€©©)¾øâ \»v eee8wîV­ZsssÌ™3GÔoûöíÐÐÐÀÀñûï¿ãÅ‹(**ƒ°aù?Z´hÜÜÜ0cÆ ìÞ½©©©(--Å“'OpàÀ©÷Aþì³ÏСC,X°›6mBrr2JKK#GŽ`„ rÍO’½{÷"??}ûöÅùóçñòåK¼|ù‘‘‘X¹r%öíÛ× óòx<øûû#,, ;wîDxx8nß¾­àÙ¢~@"]]],Y²¹¹¹Øµk€ê€ÉÙÙŸþ9ÌÌÌàâ₼¼<Ñ*µ<Ž?ŽöíÛcÆŒ066†‡‡0{öl±¾Ã† ÃpãÆ |ðÁ055ŤI“СCœ?^iA™,ôõõqåÊôìÙŸ}öš5kxxx`îܹœƒW¬XìØ±ŽŽŽ°±±ÁñãÇE›šÔddd„óçÏCCCþþþÐ××G=0cÆ ±Â?eóððÀÕ«Wa``€áÇÃÂÂM›6E»ví°eËNÎêÞ½{Ѽys|öÙg055EË–-Q^^Ž-[¶(m~ß|ó Z¶l‰¹sçÂ××_~ù¥ÒÆ1bþüóO¤¤¤ W¯^011¥¥%|||pìØ1×ë<={öÄìٳѳgOèëëÃßß8þ<'­ÃÅÅ×®]ƒ¥¥%F +++ÃËË ëׯçä¡7„®®..]º„~ýúaöìÙptt„¾¾>ÜÝÝ1sæL©9ššš8wîF…   8;;C__®®®˜2eŠØÆ&ŠÐ±cG„……¡²² €™™ÌÌÌàíí½{÷Öûg ɼyóзo_|õÕWðóóSÈ!ï£ìBêTXXˆ¼¼Ä ¢ qÿþ}deeÁÓÓ®®®byй¹¹(**‚““çXyy9222`nn.V@”žž ‰…O5½zõ /^¼€µµ5š4iRk¿ôôtÑJ¹­­-Ú´i#öKœ1†Û·o#55žžžpssƒ@ @ZZš6mЦM›rú—––âîÝ»(,,ć~SSSdgg£¬¬ ŽŽŽuÎ[  55&&&b«ÖYYY¨¨¨K'©ë¹VUU!11>DUUìííÑ®];±?dJKKqóæMˆ~¾’ÎÛk§.ÈÈÈ@“&M`ee…ŠŠ <þfff¢ŸCYY233aaa!V¬–––---±¢´º®ÍŠŠ ®_¿###x{{×z1Ƙ˜ˆG¡¢¢öööhß¾=çõ«ëu­çÏŸãáÇÈËË]Ç5 +++‘žžÎy]kÊÊÊ£G••xyyÁÌÌLìñ¦¦¦b……¢k«¦¢¢"äææÂÖÖV,ÇZ  >>=‚††жm[NìË—/QPP€fÍš‰Φ¦¦BWWVVVbÏ¥ªª iiiÐÔÔ”XÐLHcF4!„F§fM!ªF)„B!„È€hB!Ž£££Ä´BQJáSUU~ÿýwDDD ??­[·Æ¤I“¤æ…B!„ƉV åPPP€AƒaúôéHOO‡––~ùå±M-!„BÈûƒv"”ÃÊ•+qïÞ=DFFÂÅÅEÔ.i»]B!„ò~ èÊÈÈÀ¾}û0bÄNð ฿.!„BQ  èîÝ»¨¨¨À‡~ˆ%K– ÿþ Ddd¤º§F!„B”ˆR8(11DÛÆ~ôÑGø¿ÿû?>|ç΃ŸŸŸ¨ï¤I“pëÖ-±ÂBi9BÈ_Zn >Ÿ z›Þ»wïbݺuˆ‡ƒƒ–.]Êé{óæM¤¤¤ˆåF§¤¤ ¼¼üiKHHàü'RÆW®\‘û|)))¢²<6""¢ÞcûÖÕ/../^¼¨µß;wœœ,ñ±yyyxøð¡Ädž‡‡¿s׆²~–uµÅÄÄàñãÇrŸ¯>?Ë·Ûdù¿™™‰øøx©ý¤]çΓù± ¢¹¾KׯÛm‘‘‘HHHhÐc»]kÑ7?˜;ƒ1<º_­ýêzýêÛ&|_‘õ±²ü_¨ÏûPll¬è½ER¿¤¤$DEEI|lff¦è½åídž‡‡7èuQT[xt8ß=ŒŸ¢mo®ðŸåÉ“(?|8¾Ö~™™™¸uë–ÜÏM–ß)B²ü_HMM½·ÔÕOÚõQïÇæää ²²¸yó&Hi£G2ìûï¿ç´Ï™3‡éèè0>Ÿ/jóõõe^^^ªž¢ÌNœ8ÁNœ8¡Ô1&Mš$÷9:OYÆ®Oß½{÷²ëׯ×züèÑ£,44Tâ±{÷ÇZ´hQ¯9ª›"~–u g‡’û< ™§,×X}ç)mu¯íØÒ¥KÙÒ¥K¥Ž­nr½·¬,flZaõ¿…Eµv£÷–jïò{ËУCVƒyìò¨³Ÿ\?KWWÆÆþ÷¿Z»Ð{‹ôc^^^Ì××WêØÿe”ÂÑ@îîî€ÒÒRN{ii)455Áãñ8í*›[Cyxx(}ŒáÇË}ކÎS–±ëÓ·cÇŽ°±±©õ¸§§'ôôô$³±±ÄcÝ»w¯ß$ÕL?˺4oÞM›6•û< ™§,×X}ç)mu¯íX—.]¤Žû. ÷Ùú¾¯ï-q9qws÷:ûÉõ³®Ü[ZÖÚ…Þ[¤k 1‹ºÑF*rðöö†¾¾>ÂÃᥥ…’’´lÙ­ZµÂÅ‹Eý„ùÐaaaêš*iD&OžŒüQÝÓ ÀÉ“'(ÿµZõ ȬªþÚ˜l6Tï|1u¾·ð«øÐÿZ•U•XÜu16õÛ¤øA*+꯿ú XµJñcüGPÜ"å@ËaçΈE‹-”——cÛ¶mêž!„òÎHÊOBeUu»EÝ+Ð –“óæë:V  QJáC×®]qëÖ-;v qqqX´h¦N kkkN?GGG±ä}Bjóv*!µQ÷Gò¤qQç{‹0}žÂÑ`5 /)€–µµ5tuuÕ=w­@Ë©eË–øòË/qäÈ,_¾\,xÊÊÊRñÌHcµaÃuO4œ;Ru¾·ÄåÖ •µM´ÂPÌ"­@«Ýó™Ôå?“úz¯sŸ‰Â©ó½E¸mªg K}%·”¡0³HGô;"22;vìP÷4H#d``€o¾ù&&&êž !„Hô$÷ ÀÍÜMyƒÔ\¦»H%£ŽwÄŽ;è£XÒ Dhh¨º§A!µ¦p(-}x@khææÊ‡Ð ´ÊÔ§ˆ°{÷îôñ=‘ÙÙ³gÕ=¢&9¯?²¦{¶’úxüø±JîÉý¶ÂòBdgPb!ð&€6545•7Î@yy9JA+Ð*B ù„E£"B" uª¤€¨×&*¤~(f‘ŽV U„ò !ŠFE„Dêú„³æ-ìT’M´Ü(f‘ŽV  !„¢4ÂB ž\Í\•7ÐD…(€&„BˆÒS8M¡§¥§¼(€&*D´ŠÐN„„EËÉÉ"ÍãÇÕ2®0…C©„UU@nnõ×@Ëbé(€VJÈ'„(Y¨£ˆáiÞSJÎÎË«¢  €b騈PEäMÈ/((ÀË—/¥ö300€eß<ÊÊÊ™™)µŸ––4!Dq¨ˆÈBE„©©(©, ¢;p@+JGt#1hÐgˆ—þ棯‰‰á cÙ²-øùçÐÔlZg?Æ.ãîÝŸ(ˆ&„R'a! äÚÆ›¨Є@ ‹¬¬ÕRû¹¹Mn𥥕ÈÉ™ yý¬­WƒÏç7xø÷ßQZZŠ!C† ::.\@QQzõê???‰³â¨Ÿ IDATyòä ÂÂÂøúú¢U«VûâÂ… ˆŽŽFyy9ÜÜÜ0dÈXYYqúÅÄÄàÊ•+HNN†››úõëggg±q###áïïââbœ;wÉÉÉèß¿?ºuë TTTàêÕ«‡¥¥%ÆŒkkkÎyN:…¦M›¢{÷î õk×`dd„>ú­[·æô}ô袣£1hÐ äææ"<<÷ïßÇçŸWW%V±Bˆ©üÐÐD%(€VJÈçZ±b233ñìÙ3Ì;æææ¢b¨+V`íÚµœþ;wîÄâÅ‹ÁCË–-‘˜˜@€õë×cáÂ…œ¾çÏŸÇ”)Sžž'''èéé!99»ví½{÷Dý‚ƒƒñõ×_CGGÎÎÎØ±c´µµ±k×.|úé§¢~ÿüó¾øâ üðÃX´hªªªPRR‚uëÖaÁ‚X¿~= „‹/ÂÌÌ ¹¹¹Ø¼y3®_¿ÎY¥Ÿ1cÚ´i[[[;v ...HHHÀ²e˰oß>Θ§N²eËpðàAÌ›7UUUÐÑÑAÿþý)€&"´!‘…:v"êkëÃÁX‰ŸZR­P´¡tTD¨"ªJȯªòóö¯¢B%SIOOÇþýûñ×_!++ ÷ïßG‡°qãF¤¤¤ˆúEFFbÞ¼yðööFVVbcc‘••…nݺaÑ¢Eœ"ªgÏžaĈ066Fll,’““ñøñc¼xñÁÁÁ¢~¡¡¡X³f üýý‘››‹ØØX¤¥¥ÁÍÍ Ó¦M“X­¾hÑ"lذÏŸ?GRRˆmÛ¶¡_¿~°··G\\222°{÷n¤¥¥aóæÍbç Crr2233ñðáC<þÞÞÞ˜>}:Åú/X°?ýô ‘——___y_vò¡"B" u W ]Í\ÁOyÕ  éJ¹Q¡t@«ˆªòããSÓ†ý;tH%SáóùX¾|9 xzzbòäɨ¬¬Ä7Dý6mÚÆV®\‰¦M«ó³Eñ×_-ê»zõj”””àðáÜ´###NÁÕÆëÖ­C“&MVVV ‚@ ¯iРA˜>}:ôõõaoo)S¦ ªª ·nÝ®]»àêê mmm|öÙg066Æ•+WÄÎ!ðå—_Šž‡¹¹9‚‚‚Àçó"Ö¿_¿~6l44ªÿ«ÒŠ©iøðáTHHêME„¢[Ø)3}x@::Êë?€Š¥£š¨žž ÄiëÝ»7 55UÔ ôìÙ“Ó·k×®000ÀƒDmwîܳ³3:uêTçØÑÑѰ´´DÛ¶m9í}úôÇãœSèí@ÅÇÇзo_ŠÚ544йsg¤¥¥‰CWW=zôà´õêÕ ˆŽŽëïïï_çó „wU)¿)ÕŸ&*µ€ MTˆÊQô{ÆÒX¾¼a=~øÿO±ó©‹µµ5ŒŒŒ8mÂï+++EmÏž=C»ví ¯¯Ïé«££oooDDD€1‡ÄÄDtìØ±Îq ‘ŸŸ¡C‡‚Çã~¤hii WWW‰Áo‹-8ßHl«Ó¶m[ÑŠ·©©)\]]94uéÒ¥ÎçB!ïªø¼x00*\¦š¨Ð*¢ª"BSS`þü†=öñcÕÐõeccƒGÏçCKëÍ%[UU…ØØXXXXˆa{{{dgg×y>###èëëãþýûbÇŠ‹‹‘””TëÝ=ä%éy””” )) íÛ·믩©©”y÷Y¨ºˆP˜¾(y€h£"Bé(…CE(!¿á<<ž>}ŠÀÀ@Œ5JÔ×ÛÛ!!!X¼x1\]]ѹsgèééáÁƒ044DTT`âĉ ÅîÝ»†V­ZáîÝ»HIIÁòåËÅ ý¥[·nxöìÚ´iƒ¶mÛ"22qqqX°`:w1 !D„+ÐJ_}.(xsV  ‰ŠPÝHL˜0 & Sê]»z£kWo¥Ž!ˆââb±ö¦M›"88Ý»wç´ïÚµ C† Ahh(âããáïï±»xÕ«Ë~~~8vì˜èn#FŒÀèÑ£E}444pìØ1|üñÇGrr2Fމ¡C‡Šî"äããƒàà`±?ôôô,ºGMcÆŒ‘Ønhhˆß~û ßÿ=nÞ¼‰®]»bݺuœ? {÷î¦ÜVBH£%ÜÆ[eùÏÐDe(€VÚ‰+00Pb{Ó¦M±zõj‰Ç €Ôëüžžžðôô”Ú/ uöñññ‘ ëééÕ:×1cÆÔz>ccc±ÝßÖ½{w±?"yY¨²ˆ0ûU6ò˪Óý”~aú@´‚P¡t”­"”OQ4*"$²Pe¡Z   „béhZE(!Ÿ¢hT@Hd¡Ê"š·°SÙ=   „bé(€&DEþüóOÎŽ…„ò¾®@kkhù©³r£š¨Є¨H×®]Õ=BQ a¡‹© ´4”jh}ýꄨå@«B-''GTHHˆ4?VÙXÂ¥§ooh*¦UŠY¤£ZE(!Ÿ¢hTDHd¡ª"B~‰/«wƒUz!@»*Å,ÒQ ‡ŠPB>!DѨˆÈBUE„‰/QYU @Å+Ð@+ Å,ÒÑ 4!„B¦æ-씾‰ @4Q   !„¢0ÂB€R8Èû‹h¡„|Bˆ¢Q!‘…ªŠ…„MõšÂÊÀJ¹ƒ•”Tÿ(€V ŠY¤£ZE(!_vùùù¸té233ëlËÉÉÁ¥K—( ÿ9TDHd¡ª"Ba ‡JVŸio¥ ˜E:*"TJÈ—]TT|}}qèÐ!Lž<¹Ö¶ˆˆŒ1'Nœ¨WQÕwß}‡³gÏJ<6wî\øùù)ê)¢TTDHd¡ª"Bá ´JóŸ  ˆbé(€n¤áèè]]]¥“œœ SSS˜˜˜(m Y¢mÛ¶033kð9¢¢¢pêÔ)´iÓ<s¬¸¸XÞ)BÈVAy²^U¯^ªôÐD¥(€ndRRRðÉÜOVœ†Wú¯ W¢sMsìX½Ý>ì¦1ŠŠŠ0eþD?‹F‘^´*´`Ì7ÆâÀŘĕ+Wj=>hÐ 4kÖLâ±çϟ㯿þ‚¯¯/ÜÝÅßœ“’’ð÷ß#66mÚ´Á˜1c`nn.×| yW óþ-h6R?†‡‡‡RÇà܃V ­òòr¥~ºý> ZEäMÈ/))ÁôåÓ‘ê—*^ú© d}˜…§v"`PZ´hÑàqþ7ãˆ÷‰Œß:À Û"42¿þñ+ÆŒið÷îÝ“XÌ’——‡¢¢"„††Ö@?yòŸþ9:$@ÇÄÄ`Ú´i°°°€®®.~øálݺÿüóÄ`;##™™™ptt„½½}ƒŸ!ê", ¤\hR6lPz´°€\Í\•:€7´¶6ðޤ¾²²²(Z  UDÞ ñû#ß#Þ%¾Îû¦¤uLÃŒå3p`ßñðþC<ÄCñๆüöùøöûoå  ÇñãÇsÚâããáããøøø4輫W¯ÆñãÇ :¸0`fΜ‰‹/Šõwqq}mccƒ7bâĉ ›u À™ÈBE„ÂGGèi© EŽî­Ÿ--Å\>yyy8p 444ðÏ?ÿ iÓ¦ :OëÖ­EÁ3tïÞ¾¾¾8sæ ¢¢¢Ð®];Õo K–,··7cøóÏ?qúôiLš4 ˜3gŽBž!„ü׈na§Šô €h¢6tèF¢´¬´~îð¤w©U9€z¤< 4¨¨¨c 7***0|øp¤¦¦â¯¿þ‚³sƒ¾¾¾µ¶=xð@Ô¶|ùrlܸü1þ÷¿ÿáØ±cHHH€¥¥%‚‚‚PTTÔà9BÈÃÓܧTT@¼  ©@ª’’lÙ»WÝÓxoÐ ´ŠÈ[Dø¡÷‡K «{…X´²n…%ÃVHø¤Ùl=½åÖuÏÕTÃúúú ãmS¦LADDŽ;ÖàÔ !ooïZÛÒÓÓë|¬ >ùälݺ±±±rÏ…U "B" e¦¤¢”_ €V ßEëvìÀîßÇ`??‰uA5Q¡t@«ˆ¼E„ãñãä‘n]{ hþÀßÌýÃÛ5,/²Ì£ úqü¸Z¯ Ýgºðm/¾ÒÛÁÁÁøå—_°~ýzŒ=ZîóIº½°ÍÖÖVêã…AÈË—/åž !ª@E„DÊ."¦o*ÚD ºžJJJðëåË(\¶ s׭ùŸ~ª³?JG)*"ï…Ø¼ysÌ6 &‘&€„Tè&ÉMÐ]¿;†nø/R===ìY»V—¬¾øqpOpÇ·_}Ûà1„~úé'¬Y³Xºt©Ü瀰°°ZÛZµj%õñ.\¥ßæ‰E>|8Ϥޔ~Uߺ²((¨þšè:­Û±©þþ€¹9¢C\\\ý)x–ŽèFdÙ˰ýÛárÑÆ1ÆÀ3 Il8†;bnó¹øãðrá×Û'¾=">€Ù=3 Ð~¬ Ûk¶( @Ä鹋/_¾ŒÀÀ@ôíÛ{˜õàÁ„††Š¾¿sçÂÂÂеkWtêÔ @õGÞ{öìá¬2ß¾}3gÎDxx8üüüмys…͉Bþ+„+ÐM´š ™‰ä[‘*Ôë&@×A¸úÌoÛ5bæ®[§æY5~”ÂÑÈL7 £‡Fdd$îDßAk·ÖèÔ±LMM6FWŸ®ˆº…˜˜ܸ{ÖÖèÒ© rþC‡¡¢¢qqqðôô;~àÀtïÞ]æóa„ pssƒ®®.nܸ333ìÙ³GÔ§¸¸³fͬY³`mm MMM<þжm[üøãbÛ{B‘N¸ífîž\íõD›¨Ô˺;:`À›ssDWU!..Nj.4©Ð*¢ˆ…ôõõѽ{÷™õ¥¥¥…: C‡ ?÷¸qãD·”“D¸‰JË–-‚Î;‹ŽIjóòòBHH† ‚O?ý'OžDll,üýý1qâDØÙÙ‰ú:88 44HKKCyy9š7oŽ: ô¡ i<¨ˆÈBÙE„Â]Užÿ P] ÑêsP§=käÈ:s¡©ˆP:  UDÞ"Â÷IÿþýÑ¿©ý0þ|©m-Z´à´-YRû]H´´´àïïgMÈ»‡Š‰,”YDXÊ/EJA 5Ü ºb«ÏBRV¡©ˆP:ZnSº !ŠFE„DÊ,"|šûìu…»Êï P-AII ކ‡ƒ_Ë'¾Y#GbÎÚµQÌ"ЄB‘KÍ[Ø©|ZC03S͘Ⱥ;V×§­ææˆy½ MdG)„B‘KÍ[Ø©<Úܼ:ˆ&ç.\€‹±1på JK!` ÆZZ°ÑÑõ)/(ÀÏ'Obí[9ÒD:  UD‘E„„P!‘2‹…+ÐÖÖ0Ñ5QÊbh•:ݽxTV—7_µl‰ùõ¸£JG²©B-""BTHHˆ46lPÚ¹…wàPYú@t=Ý)*}ÝÑȨ^¡˜E:ZVJÈ'„(Y(uï×)*+ (€®'a­É㡽¡a½C1‹t´M!„Ëz•…‚òê-µUº-܉R˜êt»°ÐJ_ššjžÍûƒhB!„4˜Z «ª€ÜÜê¯iºV @dq1€ú§oú¡ZE¨ˆ¢h999¢BBB¤yüø±RÎ˹…ªR8òòªƒh€è:<-)AŸ@¶šbé(Z/^Äßÿ-Ö>iÒ$´oßžÓF ù„E£‰,”µ¡°€P[C.¦. ?¿D´‰J½4¤€ ëƒh9ܾ}û÷ïÛ–úÕ«Wb}éBäº~ý: hKmBä@3‘…²Š…).¦.ÐÒPQXAt½hmmëY@PÌR@ËÉÊÊ 'OžT÷4¯¾ú QQQÈÌÌTê8½{÷–Ø®££ƒóçÏ+ulBù/¦p¨,ÿ ºžn¿ =  G›Í(ÐDJJ ¥ö³²²BëÖ­4F~~>¢¢¢¤öÓÓÓƒOƒÆPµË—/ÃÚÚZi›BÈ¿ŠÄ—Õ¿›Ôrh€èZT1†{T@¨4@ËéÅ‹èܹ3ÊÊÊàáá H .åMÈß÷õ×> :þ‚,p®U+ü}÷nƒÆ¸zñ"ŽLžŒAAý¶áNz:´´Çåãïï¯ÔûŸ¢.´!‘…2v"Lx™~Uu‘šZî Ðmìjñ¨¤¯^ÿ>—5€¦¥£õ|9XZZbòäɘ4i|}}qãÆ tíÚ'Nœë{ýúu9r'Ožäü«oý¼µkñÂÚ“KKký÷J_A[·6øù 9''L¨cŒæ••˜0mšÂƒç¨¨(lذ¡ÖIIIœþX¾|9F…E‹áܹs OcÂã|/麢¶÷³M¸á»0eµÅ=ŽCYYÙ;1—ÆÞ&܉P‘c<É}<PÂ]Vús{ñ'䘘ÚÚª·µÕ, ,¾|¹ÎÇ c’eË–áÈ‘#ˆ©[ãXB|GM:S§N}‚–-[bÞ¼y1b§¯±±±\cYYYÁÂ׆¤bQ-["¨W¯Áãñ0néRüüùç˜üúcŸ·íur¡åË‘‘¸YXˆÎÆÆ¸Ù¡ƒL­OLò_G+Ð –šš …Ÿ»¶UèbQ ”{ €q~0ù­cÊZ}–$##ƒ ‚‰‰ NŸ>-z=·¾NQY°`š4iÐÔÔDPP~ùålÙ²…HŽ5 Æ ƒ³³3bccñÛo¿áÒ¥KèÝ»7bbbàä䤒çC!ï#á ´J 7+ÐT@(Ÿ1DS¡RQ´bbb8ßß¹sGE·nÝÄhEíê³pÓ&lyëþŒ»ÌRÈÙ«ø ¿FÛ%--t3úúú I²’’ 28sæ lmmEÇ=z]]]ôèуó///ØÙÙ‰í´uüøqL˜0ݺuÃôéÓñï¿ÿâàÁƒ(,,ĪU«”þ\Q&Ú‰ÈB; oa§ÒB^÷@KôàÕ+”½Þ©±!4íD(­@Ë¡gÏž077‡‹‹ ž>}ŠgÏžÁÙÙ‡뫨ß^….忆 r~àõ*ôåËøyß>L.-ìutTÉêsUUƇèèhœ>}^^^œãÏŸ?‡»»»Ä@ÞÛÛÿý7 ëÌ9ÿä“O0sæLDFF*|þ„¨íDHd¡èóËò‘ý*€W )WW¢š„@ÓN„ÒQ-‡‹/âêÕ«ÈÌÌD=вeKH¼õ‹"/Ä…›6aYx8~HIÁnssÌÚ¿£xP’€aÃpá&ÄÆ"BK ÆŽUÉêó¢E‹pêÔ)ìÛ· ;Þ¬Y3ïÙ³!!!X²d ¦OŸ.±§§'bbbpéÒ% }àåå…]»vaþüù°³³ƒ‡‡âããQ\\Œ•+WbРA¢ÇܸqÛ·o˜››ãåË—¨ªªB“&M°yófN_B#Ú‰ÈBÑ; W MÑD«‰ÂÎ+ÐuŠyõ •¯ïPÜКv"”ŽhQTaMÊ ž…š5k¦”ó.\¸yyy¢ïÑ»wïZû×|ÓÿüóÏÑ»wo\¸pOž<Áرcáçç‡oÝçrÕªUðóóCtt4222```ggg 2DiÏ‹U¢"B" E W Ušÿ P-…¼„ÖÐ*B"W¿~ý8ßwïÞÝ»w¯÷ã[µj…V­ZÕÙÇÌÌ C‡ÅСC4GBÞu8Y(2xf`ˆÏ‹ Æ;p@Kp»°`¬¥·¦_RÌ"å@B!D&))(åWßæTå÷€Ð@¦Ž4Â膆 òA塚B!2¦o´ ỤD ÀÃ’T@¨l@«íêCQ4Ú‰ÈB‘;Ö¼…Úr )€U\ Áë†æ?³ÔÐ*¢Œ"BBÈ[DD„¨iêºE¨¬„+ÐM´šÀÑDÅù²´w­n×( ”gšb騈PE(!Ÿ¢hTDHd¡È"Bá.„®æ®à©:Ó–V k%Ì6ÓÖ†‹ùá³HG+ЄB‘‰0…Cå„ÐuÐÞ††jžÉûhB!„Ô[Ie R R¨¡€°¤¤ú@ô[Š<)­¾3 *Ð*B ù„E£"B" E>Í{ †êB5µn¢B;prD¡JX@hl,×¹(f‘Žh¡„|Bˆ¢Q!‘…¢Š9·°S×= Z~‹¢ ŠYꃊU„ò !ŠFE„DŠ*"´ á»D˜ÿl¥£ƒfººr‹béhš¨Å™3gðûï¿×«ozz:~üñG$%%)yV„B¤ZXÃD×DµƒS]+aMùϪA+ÐD-Ö®]‹ÌÌLŒ5Jjßû÷ïãÓO?ÅÑ£Gáì쬂ÙÕ-00wîÜ‘xì§Ÿ~‚———ŠgD!ª#LáPyþ3@t-^òùHx]@(Ï*¤þ(€Ve$äçääÀBÉEùùù044„–]*Bñññxòä ú÷ï/vLWÎÍ‘…°€PÙïäýðøñcxxxÈ}Ñ-ìT¾¼  ut9 åÞ'w˜ÿ TÇ,ôû¬n©ˆ¢òóóóÑÂË agÎÀ»C…ž»&ŸÁƒñé˜1š=[ic4FVVV8yò¤iÑå IDATº§Aþã„„” MêcÆ rçAgg¢°¼Ýú]¢ÈB :f¡<èºQ­"оWlÞŒ¢ ðņ ¸òÛo =·Ð_¡¡ÈèÒNŸÆÓ§CGGGácðù|ìß¿—/_†@ €¯¯/>ÿüshhÔ/=ÿÌ™38}ú4ž>}  0býž>}Š .àâÅ‹(((€ z÷îÀÀ@N¿’’ìÛ·÷îÝCjj*lllЧOŒ?MäØÕ‰e À™ÈBE„j- hïZóŸíuua£€ßÕNGGƒuê„GFF¸©”qVíÞB¤øù!dÿ~…ŸŸÏçcìØ±Ø¼y3š4i‚äädÌž=£G{}/˺̚5 ƒFxx8¬­­qýúuŒ5 Ÿ|ò çñÇŽƒ››¾ù怓“RRRðÙgŸqÎOOO,^¼ÉÉÉpttDVVfÍš…¸¸8N_>Ÿ‹/âØ±c¸sçJ„7ö'„÷˜0}Ps4ÐT@¨z´Ý­Ø¼©C†r†Ç”uë°ýàA…ŽñÿþýOÝÝmmTtîŒï7lPø*tzz:rss+ZÝ]°`BBBpìØ1Œ3¦Ödž††bÏž=7nŽ9MMM0Æ0}út8pÄØ±cÛ¶mƒµµ5âââ``` :ÇóçÏE_WUUaêÔ©ÈÍÍÅåË—Ñ­[7ѱììlèéé‰Í½oß¾¢ïÍḬ̀aÃL›6M¾…BÞaÂB- -´0m¡ú P-æEe%RÊÊP¡*Q­"Š*"ÌÏÏÇé¨(T ƒ7##ÄÀ÷øqÀÕU!cví¾üRômêëUhEçBO™2…“1g΄„„`÷îÝuÐ;wîÌž=ššš‡/¾øÀŽ;D´–– Å‚;;;Ñ×ÿþû/"##Ì žê|çšúöí‹iÓ¦¡uëÖÈÏÏÇñãÇñûï¿cúôéÐ××ÇøñãðJ";*"$²PD¡pÚÅÔZj!(€s»°Pôµ¢V ©ˆP:JáPEÖ\}=¨ç=•ëåöm }{@[[ÔTѹ3~øçTTT(n~~~œïÑ¢E ÃÄΛx<¸µm+÷8Ó6mBü‚bíÏ|}º mbbÂYjݺ5Μ9Æx<žÄǾxñnnn¢Õç·áÂTTT@GGpwwGHHöïßÝ»wÃÝÝ‹/ÆÔ©S¼Iç°là›²³³3z÷îÓ§O#;;[lÕše "B" y‹+«*‘”_½¡•Z   kÐÍõô`^cáKTD(ÐÈŠÍ›‘ööêók#Fà§]»põøq¹Æø+4Ù|H¸ïsE§NøaÓ&…åBàÑ£GhÕª•¨1†ëׯÃÁÁ¡Öàš5k†èèh¼zõŠ“×ÌçóqóæMØÚÚræØ£GôèÑ8uêBBB0}útôë×ŽŽŽhÑ¢:—/55•3Yó¤Ë^ç¢BÈû$ñe"øU|´‰Ê»D@Sþ³jQ G#‘ŸŸ¿##QU[þš±1Ê}GŽà={P8`@­ÇŸ)øŽaaaœï£¢¢““ƒ6mÚÔù8///TTTàòåËœö[·n¡°°íÛ·—ø8Lœ8[·nEUU•h;ñN:Ž7ð‚‚\½zFFFhÖ¬YƒÎA!ï2aú æ{@åýž——#ãuj%ÝCµ(€Vy‹WmÝŠT "¢Ö9˜¹jUƒÇ8}î——7nÔ:FEY¶8@ ×óMMMìܹIIÕ aÅŠ€¥K—ÖùØåË—CSS«W¯åjåååaùò倕+Wrú>}úTô}II Ž= ¢`·S§NÀ¡C‡°}ûvÑócŒáìÙ³ÈÈÈܹs_ý5âââPYY‰œœìß¿}úôAVV.\XçÊ9!Š”““#*$$Dššµ! QóvjÝ… è×½Š2vO~ßP ‡ŠÈ›?sÜ8øÕãÍÏvԨѩ];9Sj?]oo‰¹Ç²rpp@`` Z´hæÍ›#==|>›6m»ÆÛ>øà8p³gφ½½½èÞΠÜÇÇGÔwÆ X¿~=LLL`ee…ÄÄDhiiaÊ”)œMW¾ûî;TTT`þüùXºt)‘žžŽW¯^!::¶¶¶ÈÉÉÁŠ+°bÅ hhh€ÇãA @KK sæÌý@ˆ*ÐN„DòîD(\6Ñ5µµ‚f%  ÅÓ7x¼@ÓN„ÒQ­"ò^ˆrß~Hkkk•ý"Þ´iÊÊÊпøùùáòåË`Œ¡oß¾bé:tÀÿgï¾Ã£*Óÿ¿'½iô„š!@è  "F$Š_„ßʪ«¬»‹~×uíŠ"ûuE”^¥H± ¥) ´„–PBHBH/3¿?&'$„0'dfÎLÎýº®\ÎÎLæÜ¸xøð侟gõêÕemЏ¸8ú÷ïÏÖ­[ËN"ìÓ§OY?³âÊ•+lß¾'N““CÏž=‰‰‰ÁÇǧÂû‚‚‚øöÛoÙ±c$%%…F1`ÀÚ¶m ÀàÁƒùý÷ßÙ·o.\Àh4ÒºukzöìI‹-lðoJˆªIpÕQÓ!BeZ“þg¸ ]]¡~}mjp0J€níãCÝ[Ì.Ý) Ï–I€šˆ‰‰){ܹsg:wî\å{CBBª Íš5c„ ·½V`` =ôPµj+__y...tèÐÁb¶BÔ6Ê1Þš´oÀ]8AZå9PKÒ-„BˆÛÊÌÏ$5'Ðh€ä•›œÍÏ'­¨­ Ðv" ùBk“!BQ5"Ô|€$@ßÄV„ ™E Ðv"§ú!¬MN"ÕQ““Ëoa§y´hàFû†‹Á@g??«~¶dˤÚN¤!_am2D(ª£F;p”®@0H€vJ€ŽòñÁ× ;c•'™Å2YB!Äm)„Më6ÅÛÍÛþžn~,ð› jJV „—— .dÛ¶mZ—"œÌ•+WÊŽB[PZ84 ¼zÕ¢A4p:/Ìbó±ê µ!ÚN,5ä¿õÖ[ÿú•œœLÆ U¿ß××—#Fذ"ᨔ 9ÖX¨Gç MFN¦›OsÕ|€$@cÛB0gOOO«nm"ÚN,5äׯ_Ÿ¸¸8û#Z\\Ó§O׺ áä$BQwzá¹kçÈ/Î`€$@s£ÿÙÍ` “•AN"TC´ÈoD¡VMO ú!ÁYTÇÞ[*la§õÐò—²ÝÞ×/ë³If±L†…BQ%e€¤…ÃM&È¡æ$@ !„¢JÊ¡·›7Íêj´2©d0è~:>7—ì’@´–$@Û‰œê#ÔªÉiaB_ä$BQwzoQZ8ZÕo…ƒ5KROY®WÜôÝ}ú«A2‹ íDNõjÕä´0¡/r¡¨Ž;½·”ma§UûÈ!*å(ÚÓÅ…6 É,jèû¯qv$ ùB-"§N¢¨¨ˆ¨¨¨Û¾O†EuÜɽ%·(— Y At9J€îàë‹»Á6?Ìb™¬@ !ìæÕW_%..Ž;wZåó233IJJ¢¤´Й\¼x‘”””[¾öôÓO3tèP;W$De'®žÀ„ hGPl2q(;þg­É ´Â.Ž;Æ;ï¼@zz:½{÷®ñg~üñǼñÆ$%%Vãϳ§þýûÈ/¿üRéµ 8å_ DíS~Íö€ Ð¥ŽæäWz"£hmÉ ´HC¾P«¶Î;ƒÁÀ!Cøî»ïª\}°téRU«ô2D(ªãNî-±4ÜØ…CçºüaW›]G2‹e²m'Ò/ÔºÓÓÂYqq1 , W¯^̘1ƒ~ø ðòË/WzßÊ•+iÓ¦ ;w®ðÚÉ“'ùí·ß2dìÚµ‹#Gްnݺ²ã¬;vìX¡wø?þ`ûöíœ={–ˆˆHóæÍ+|v||<‡bèСdffòã?röìY† Btt4...°cǶoßNhh(cÇŽ­t„vaa!;wîä×_%%%…Fqï½÷Söž’’V¬XÁõë×1™L,]º´ìµ|¶lÙBnn.<ð@¥—{÷îeß¾}œ9s†ÔÔT¢¢¢øë_ÿŠ‹ SµËÜ[”ÂßêyÕ³AU*dfBQ‘ù±h|\]iëãc³ëÈI„*˜„ÍM˜0Á4a­ËB3«W¯6¦/¾øÂd2™L­[·6EEEUz_VV– 0M:µÒkŸ|ò‰ 0ýöÛo&“ÉdŠ‹‹3ùúúšSpp°)44ÔjúôÓO˾gÆŒ&WWW“)22Òäââbòõõ5-X° Âg¿óÎ;&Àôõ×_›êÖ­kjܸ±ÉÝÝݘ^zé%S~~¾©OŸ>&???SPP 0………™’““+|Î}÷ÝgL 40EFFš`7n\Ù{òòòL¡¡¡&WWW“»»{YÝ¡¡¡¦ .˜L&“©OŸ>¦ððð Ÿ““czòÉ'M€ÉÛÛÛt×]w™BCCM€iùòåÕù¿Ã¹ü=ÛdúŸ,ó׋׵®Fwºü·‹‰×1õžÛ[»"Nœ0™Àüµp¡vu8€.¿þjbëVSô6½ŽäËdÉBassçÎÅËË‹1cÆ0~üxŽ?Ξ={îø3¿úê+^zé%öïßOJJ )))L™2€õë×óÆopÿý÷“––ÆñãÇIJJ¢uëÖ<õÔSœ>}ºÒg¾üòˬ_¿ž .pæÌÌ|@ß¾}éÞ½;™™™\¼x‘>úˆ³gÏòÑGUøþ‰'’””Drr2Ççüùó<óÌ3,\¸°l¥ÙËË‹””Z¶lI—.]ÊêNII¡qãÆUþzÿö·¿ñÕW_ñÚk¯‘••ÅüAJJ ¤C‡wüïQˆÛQz b€t½]h4òGN ýÏŽ@´¦RRRøî»ï9r$uëÖÌÚ`0ðÕW_Ùìºÿú׿ðóó㫯¾ÂÛÛ€¦M›òùçŸSTTÄ{ï½Wé{^zé%zõê@“&M˜0a&“‰ëׯóïÿWWW<<<˜2e >>>ìØ±£Â÷?üðÆ›4iÂ[o½…··7«V­ºã_Ëùóçùä“O4ho¾ù&nå’èÔ©†Qk¥d§U8È!è:@ÿ‘“C¡ : é¶iÈjÅÇÇ©uV3þ|Š‹‹yâ‰'Êž '&&†¥K—òñÇ—\k:zô(ÑÑÑ•ú”»uëFHH¿ÿþ{¥ï¹ygîÝ»гgO åö[ussãž{îáÌ™3•>cóæÍlܸ‘óçÏ“•ežžžœ:uêŽ-GŽ¡¤¤„ØØØ Ï+„7ÿ…¸•êÞ[f€P4pÓ¡tAAžžž6½†³“h;‘!B¡Vm;‰pîܹxxxžžÎÒ¥K˾š6mJVVß|óÕ¯™••Åõë×iذá-_oÔ¨gÏž­ô|½z‡¤üJOùºùyåµ"e°©Ô¸qã8p ‹/&--æÍ›‰OY˜¾J­M›6­ð¼œD(ª£º÷e€4ná(¿ÓŒŽÿ²¨è:®®´±Á¢Cy’Y,“h;‘iV¡VmÚãçŸ&!Áü‡ð¸qãnùž¹sç–½¦ì$QXXXé}9¥½jÔ©S//¯*·xKKK£Aƒª?O;v°hÑ"~øaV¬XQáµ7b,ýÑëPj½ù59‰PTGµwà(]vsq£E@ T¤’²íç^^ÚÕ¡1%@w®S@¨Ìb™¬@ !lfîܹüðÃ$&&VúŠeÛ¶m$&&àëë‹¿¿?{÷î­ôY‡ªôœÒú‘™™YáyƒÁ@Ë–-Ù¿?ùùù^;}ú4/^´z›Ìñãǘ4iR…ç/^¼xËVooïJuW¥mÛ¶lÚ´©†U ¡ž²ݼ^sÜ]ܵ+DQ!Ïhä¨ : ÐB›ÈÉÉaùòå´mÛ–Áƒ^éëÉ'ŸÄd2UXëÔ©üñ({nýúõ•Vu²½ü“ÉTáµ^xÔÔTþþ÷¿—½–ŸŸÏ /¼Pöº5);hlܸ±ì¹¼¼<&Ož|ËSÃÂÂHHH¸e+ÉÍÚ´iã>Ê¢E‹X¶lY…×222¸téR «¢2‡Ø$@‡²³).½ÙºÿY¨#ÚNdˆP¨U[N"\¾|9ÙÙÙŒ?¾Ê÷ 8æÍ›Wr_ýu\]]¹çž{ˆŽŽ¦K—.Lš4‰çž{®Ò÷>œfÍšñ—¿ü…àà`"##™3gO=õ±±±¼ÿþû´k׎‡zˆ6mÚ°aÃf̘A·nݬúë2d;väƒ> mÛ¶ÄÆÆÉd¢k×®•ÞÿÜsÏa0/ë•NNN®òógΜI÷îÝyôÑGéС<ò={ö¤Q£FüüóÏVýµˆÚ©:÷–"c‰™æŸ i:@ ©8@hhÉ,–I´HC¾P«¶œDèççÇŒ3˜0aB•ïquueÖ¬Y=z”+W®B¿~ýصk_~ù%)))tëÖñãÇ“’’Bppp…Á@___Ž?ÎúõëIJJ"77·ìB–.]ʘ1cزe gÏže̘1<øàƒôéÓ§B={ödÆŒßô´¯¯/3fÌ(ÛÚ®¼Çœþýû—ýowwwvîÜÉ'Ÿ|Âþýûñóóã¯ý+'NdáÂ…\/÷ Àý÷ßOBBÛ¶m#99™ââb|JO‹‹‹ãÚµkÞÂŽ;˜7oûöíãìÙ³¸»»ÇàÁƒo÷…@õî-§ÓOSl,dÚ(:ÀÍ–6 9‰P ƒéæŸ{ «‹‹‹j×p˜BØÅ?r ¥tÓßïûi[N¬MXËÈ¥æ!ÕmqÛèÖÇÂwØäåÁË/ûïjW‡†Úíßϱœ°©cG›_Or‹e²m%sæÌ!--§žzŠ­ËB!îX…-ì´láÈÉ1‡gÐí tvI ñ¹¹€ô?; ÐVðÍ7ßðì³Ïb4¹ï¾û$@ !„pjÊ¡¿§? ü¬»åcµÈ!*¸~ci³€ìÀá8dˆ°†ÒÓÓ™2e S§N½íû¤!_¨U[†…í¥¥¥U¹×µ7«Î½EÙÚaA·ÚÞ„ ™E Ð5ô§?ý‰¡C‡Ò·oßÛ¾O†…Zµí$Ba;r¡¨ŽêÜ[”‡ Ýè`wwšÙé É,–I G ¬_¿žM›6qüøq¶oß~Û÷Ê4«PK†6„Zr¡¨µ÷–Œü ®äšƒk›À66¬H…ò?aÑy€¶gû†dËdú]»vçž{Ž™3g`ñý`þüù¬Y³¦Â×Í?~•çä9yNž“çnHˆO¨tš¤#ÕWŸ+ Œ‡F.´­¯tz æâR½ï­Ïesªtˆ²Îž=6»®’I¦OŸÎüùó9wîâö$@ß¡_|‘N:«u)B!„Õ(ýÏ­[iX 7Z8ÜÝÁ×WÛZ4ðÛõë({ ·²ÃþÏB=ÙúüñÇtìØ‘wÞy§ìÇ¿þú+|ðo¿ý6Ý»w¯pPC\\,Y²D«’…‰'22Rë2ìjçÎ|ðÁ¼þúëtêÔI³:²³³7n#FŒàÉ'Ÿ¬ñç%%%ñ /0qâD†n… +RV‚‚‚¬þÙCö¶µ÷–×¶¼Æ[;߀ì¿fããîc‡êª0q"Ì MšÀùóÚÕ¡‘wÎãÕ3g¸MC»\wìØ±xzzJKámÈ ôÈÍÍ%,,ŒÙ³góꫯòꫯ2þ|À|Üîܹs+}4ä µô8DxáÂÖ®]Kjjª¦u²víZŽ9b•ÏËÊÊbíÚµœ:uÊ*Ÿw3"Õ¡öÞ¢´p4ño¢mxÝŸB¨ô?7òô´[xÉ,jÈáèÞ½;IIIž[³f £FbãÆ·\A“†|¡–ü¿öhÚ´)_}õݺu³ÉçË¡¨µ÷–²-ì´Þt ÷—h{ "™Å2YBØMQQ999ªÞ›[zò–-Xû³322())©ô|@@qqq´mÛ¶Æ×PûïMˆš0šŒœJ7ÿÄDó= A×:­¨ˆ³¥´r€Šã‘m%íÚµãßÿþ75²üf!tÄd2ñÙgŸÑ®];|}}ñóó#44”_|ñ–ï÷ÝwiÕª¾¾¾4jÔˆÙ³gWx½¤¤„ÈÈHþþ÷¿WúÞ•+WÉÊžûì³ÏˆŒŒäܹs¼ð 4nÜ___ZµjÅÚµkUýNŸ>Mtt4ƒ *ûÑæÙ³gyøá‡ñöö¦~ýúxxxТE Þyç²ïKHH 22’yóæ•=7`À"##où5jÔ¨ ×=sæ #GŽ$((???ÂÂÂxíµ×(,,TU·ÕuöÚYò‹Í¡MV µ¥Å*B=iá°’Ö­[óꫯVùºœê#ÔªmC„?þ8K–,áþûïgÚ´iÔ­[—cÇŽ±eË–Jï}óÍ7ÉÊÊâOú×®]cöìÙ<ÿü󄇇3tèPÀÈHII©ôý™™™$$$Wºí˜í7nîîî̘1ƒ“'OòÅ_ðÐCqìØ1""ª {÷îeøðá4hЀåË—ŠÑhäþûïçܹsL™2…{î¹£ÑÈ‘#GHLL,ûÞ‚‚HOO¯ðï#++«Â5ø¿ÿû?6lXöÜîÝ»4h¡¡¡üãÿ €¯¿þš·Þz‹ßÿõë×ëcˆPXš{‹r„78ÀÐ…… ü·"Ú®×.((ÀÓÓÓ®×t6 íDò…Zo¿ýv­éƒþöÛoY²d “&Mª´’}:ãÇgöìÙ¬]»–iÓ¦ñÚk¯ñÖ[oñ§?ýÉb­3fÌ`ñâżùæ›<úè£^Svܸ÷Þ{i×®]¥ï¥U+Oˆµ’Cnaº Ð2@èø$@Û‰  µjÓaëÖ­Y¿~=ùùùxyyYå3ÝÜÜðòòâÂ… •^»xñ¢U®Ñ¡C>úè#HLL ›7o¾åÿ'Mš4á­·Þ*ì‹åÍ7ßdÒ¤I·ý‹ÃÂ… ùßÿý_ž|òI^{íµJ¯GEEæ ÿÊ+¯Tù92D(ªÃÒ½%§(‡‹Yæÿ†´ \øÙg\(=u€ƒo<ž9Jï!уsß}v®Î¾”mîÑ @Ë¡eÒm'2D(ÔªM':”¢¢">ýôS«~nXX»ví¢¸¸¸ì¹ââbV¬Xaµk´oßžíÛ·c0èÓ§‡¾íû;vìȰaÃHKK+kÁ¸•;w2qâDú÷ïÏçŸ^åµ›6mÊþó®—[‰º™œD(ªÃÒ½åäÕ“˜0ÿGbBXP IDAT‹ŸV¯&úé|mÝÊH0ÍžÍÈ?ÄkÖ,N?n÷ÚìM Э¼½©w‹ŸžÙšdËdÚN¤!_¨U›†'NœÈW_}ÅôéÓIIIaÔ¨QeÛØýð÷<ö^Ç{Œ3f0räH¦L™‚Ñhä½÷Þ«°}5DDD°cÇ @¿~ýøñÇéܹ3?üð3gÎä‰'ž ** 777vïÞÍ_|Axxx•«|ÙÙÙŒ5 wwwþçþ‡Í›7WxÝßߟèèh¼¼¼øüóÏ6lÝ»wçÿøíÚµ#77—“'O²dÉ&NœXißh!nÇâWËíÀ¡A ÇôÏ>cnß¾¼s‹-*ÿgÑ”)v¬JûK·ïÓª}C2‹e …6c0Ø´i/¾ø"Ÿ~ú)~ø!... >üŽ?÷Å_äÈ‘#¬X±‚ 6àííÍÔ©Syì±Çxæ™g¬U>-Z´( Ñýû÷çûï¿'44”sçÎ1vìØ ïŽŽæ‹/¾ÀÕÕõ–Ÿ•ÍÕ«W*}/@§N8Xúcëû￟]»v1yòä =Ò...DGGß²/[ˆšÐz »ˆˆ2;uâò÷ßz‹××yz2ø¹çp·ó–növ.?Ÿ+EE€ô?;2ƒévS7Â*âââ€Úµ²(DuqâÄ £~ýú5þÌË—/séÒ%ÚµkW­aEkIOOçüùóдiÓ ¡XÓµk×8qâ¾¾¾4iÒ¤ÒÖvµÚ?r Åh~ìo€÷ý´­§{|Õã,>¼˜`Ÿ`R_NÕ¤†„„¾ìÛ—wo± =¦M9Rëôª+W}ô(Û;u"¦;ûX‹äËdÚNdˆP¨U›†Ësww¿åŽ5Jhè­Öªì£~ýúVù‹€%uëÖ¥k×®•ž—!BQ–î-Ê)„ZîÀAfÇŽ\NI©° ½ÎÓ“!:X}†ýÏ.5Z–!BËdˆÐN¤!_¨U›†…mÉ¡¨K÷¥…Cë_œ9“÷CB*<7¯Y3&è ÷n éãƒ_í`¶&™Å2Y¶iÈjÉÌ„Zr ¡¨ŽÛÝ[’³“¹^hnZïÁµ† IIM¥ð­‡÷Mš¤‹Õg€ßJ´–ýÏ’Y,“h!„BçÊj±ôÍ^ŒˆàýÒÇóÂÂt³ú|:/ŒÒí9e€Ð±I€B!t®Âv·pDüú+YÀÿ¹¸p¿ŽVŸåBç!ÚNdˆP¨¯u º3{öl~øaŽ–N¾;‹´´´²AB!,¹Ý½EYvsq£e}·HLJ‚3gxøÔß_7«Ïp#@» tòÓnÇÉ,–I€¶iÈjÉ¡ýíÙ³‡o¾ùÆéþò"C„¢:nwoQvàh^¯9î.¯ö–0ýùgݬ>ÃÂv¾¾x»hÑ$³X&C„v" ùB-"´¿&Mšpþüy+©"Õq»{‹Ò¡õ!P iØÚ¶Õ¶;2`€$³¨!+ÐBÝkÚ´)à|Zk(,)$1#pŒB¶l1ÿ³më°³„Ü\®—”Úha™h!„î9ë ´Öp:ã4%&spÓ|€ðÈPÚ ж;+?@ØU´Ã“m'Ò/Ôr¶>ÜÚ@Y¾pá‚Æ•T Šê¨êÞR~ ;Í[8”ö Ðm€öpqá.__Mk‘Ìb™h;‘†|¡– ÚŸ³®@Ë¡¨Žªî-Ê!8À ôO?™ÿÙªè¬W ìà닇†„ ™E "´iÈjÉ¡ýáååErr2%%%¸jt|nuÉ¡¨Žªî-Ê¡¿§? üر¢›ÃŽæÇ:[}.1™8” 8Fÿ³dËdZ!0¯B—””œœ¬u)Bؕҡùáþý•e~¬³},7—\ t* …çm㢦ʶ°Óº}Cé6t·‡ : Ðv" ùB-"ÔFxx8§OŸÖ¶j!BQ·º·¤ç¥“–kþ=ä0„;B` ¶µØÙþÒ•woÚj<@’YÔ ‘ŸŸoÓkHC¾PK†µÑ±cG<¨q%êÉ¡¨Ž[Ý[ÊjÚ‘—»w›ë¬}n¬@wòóÃÍ`иÉ,jèrˆ0??Ÿ¹sç²yófŽ;Æ©S§04oÞœ¨¨(zôèÁ¤I“°Ú5¥!_¨%C„Ú¸ûî»ç Ð2D(ªãV÷¥}4náØµ ”UOè"“‰?rrÇé–Ìb™®V sssùè£hÑ¢o¾ù&EEE<ðÀÌž=›/¿ü’GyOOO¾üòKÂÃÃyíµ×¸zõªÖe !ì S§N:tHãJ„°e€Ð€AÛh¥}ÃÝbb´«C‡³³)0èêï¯q5B-]­@¿üòËúˆÑ£GãæVõ/ÿÇdæÌ™ÄÆÆò“²/¥¢Öª[·.Í›7'11‘¤¤¤²žh!j3eº‰|Ü}´+D Ð÷Þ ÐlOåeZX¦«ýÏþ“úõë«zïàÁƒ|8ëÖ­Ó¸Ëä$BQ7ß[”h/7/šÕÕèGö[¶Üx¬Ã­ Öss£¥ƒ ‚d5t û÷ïÏÖ­[IMMeΜ9„‡‡Ó«W/®^½Š———U¯' ùB-"Ô–3h"Õqó½EYnU¿.â€Ò¾¥{±ë‰²}O:8Æø ™dËtµ]y111xzzÒ¸qcŠ‹‹™={vY?ôO?ýTv¬¯µHC¾PK†µÕ£G9tèçÏŸ§iÓ¦Z—T% Õqó½E9ÆÛ!ûõÙÂ^rJJ8ž› 8^ÿ³dËt»íççÇ¡C‡Ø´i'Nœ`Ò¤Ie¯µhÑ‚¿ýíoV'„Њ««+Æ `ýúõW#„mäåp!ë áaB\0× ÇöƒÙÙK¥ÿÙùè6@+úöíKëÖ­+<÷ä“OÒ§O*BhÍ™Ú8„¸Êê3h¸´²ú º Ðûe€Ð©é¶…C‘’’Â?þÈ¥K—*<߸qcÆoµëHC¾PKN"ÔÞ!Cððð`Ë–-deeáïï¯uI·$'Šê(o©°…V-J€nÚnZÈÒ¥ÿ9ØÝ0+Ï]Õ”œDh™nW 322ˆŽŽ¦Q£F<óÌ3|üñǾ–.]jÕëIC¾PK†µW§N† BAA_~ù¥ÖåTI†Eu”¿·TØÂN‹£Ñ|!èrõ*:É,–évzöìÙ:tˆíÛ·sï½÷âááaÓëIC¾PK†ôiÓX·nü1S§NÅÍÍñn—2D(ª£ü½Eiáö &À+ÀþÅ<ééæÇ: Ð׊‹9é „ ™E Ý®@_»v¡C‡Ò»wo›‡g!„óéׯ;wæÜ¹s¬X±Bër„°*¥…C³B÷?ÈÎF9ëTúŸ“nô€عs'………Z—"„pP/½ôï¿ÿ¾Æ•a]Ê ´æ„QQа¡65hhVVÙc ÐÎI·zÈ!Lž<™Q£F±páBâãã+|={֪ד!B¡–œDè8ÆŒCXX`ëÖ­Z—S‰œD(ª#>>žK—.1gñ®ï¼‰æfÿB AéÝ×áê3ÜènèáA#Ö“Ìb™n´ÑhÄ××—ü‘ñãÇUáK9ÖÛZ¤!_¨%C„ŽÃÍÍ^xpÌÿ_dˆP¨UPPÀƒ=H·qݘ´~„YðÙß>ã?_þǾÅìÞ ¥ý¿zÐ]t‡É,–9ÞTŒ¬[·Ž—_~™?ÿùÏôéÓ‡ÐÐÐ ¯ûùùYõzÒ/Ô’!BÇòôÓOóÖ[oñã?²lÙ2«ÿåº&dˆP¨5â‰$õL¢¨IÑ'C!µc*¯|û ™Y™LŸ6Ý>Å(í®®Ð·¯}®é@®‘˜Ÿ8nû†dËt ·oßN¿~ýøàƒ´.EáÀüüü˜5k±±±L™2…þýû¬uYB¨6oÉ<ö°§bx.çzçëÌZ=‹ÇzŒ°0;´t(ºsg¨WÏö×s0¿É*µ‚n[8ÚµkGFF†Öe!œÀ#<ÂèÑ£IKKãùçŸ×º!ªå¿‹þ˵¨k·}Ï¥¨K|µä+Ûsý:ìÛg~¬Óö 9°vÐm€Ž-[Y²G–†|¡– :¦Ù³gÈŠ+øæ›o´.!B¡NF~€ÛýV …_~ûÅöÅìØÅÅæÇ: ÐJÿs˜—ÁîîWsk’Y,Óm€^µjçÎcêÔ©„„„YáëÑGµêõ¤!_¨åˆÃjBBBøôÓO˜}̇¨è3 ‚d5÷¯?6ЫW/öîÝË?üÀ¿þõ/fÏžÑhÀ`0`*ýÑÒ]wÝÅ|@ll,®Vú\ò…Z2Dè<‚ƒƒÙ±cO?ý4kÖ¬áÁdòäÉ|ðÁxyyÙüú2D(Ô:˜r¬¡Y͵ÿa@‰‰æ/ÐmûÆÙü|Ҋ̇Ù8z€–Ìb™®´bÈ! 2„üü|8~ü8EEEDEEiõc¼…µW`` «W¯æóÏ?gÚ´iÌž=›íÛ·³xñb:tè uyB°ìÈ2\ .<Üöaû ¬>ƒn´ R»è2@+¼¼¼èر#;vÔº!„“{öÙg‰‰‰aìØ±üþûïtîÜ™G}”¿ýíoDFFj]žÐ¹åG—ЫY/Õidÿ” íÛÛÿú@ißp1è,ÚééªÚÚöíÛÇ¿þõ/žzê)žyæV¯^Mvvö-ß+ ùB-"t^QQQìÝ»—éÓ§ãååÅ¢E‹h×®=öÇ·úõdˆP¨±ïâ>3! bÛÅÚ¿“ɼ@ÿþö¿¾ƒPt„·7u¼\2‹e kà7Þ`ÕªU‘™™ÉÓO?MPP[”E9Ò/Ô’!BçæééÉ[o½ERRÓ§OÇÏÏ%K–о}{bccùþûï)*탬)"j,;jnß0ì20ºíhûpäˆyhÐmû† øÍIA2‹“29'ª­  OOϲÿŸŸOxx8­[·fçÎeÏÇÅÅ2&„edd0sæL>ùä222ó#FŒàá‡fРA¶ß×™ý#RÌÃÞøà}™Q©&Â> ã|Öyú7ïÏæ'6[þ&kûøc˜6Íü81ÂÃí_ƒÆNæåÑfï^f¶jÅÿkÒDãŠnOr‹e²]åÃ3˜{ª»uëFqq±F !M@@¯¿þ:IIIüç?ÿ¡oß¾\»v¯¿þšx€&L˜À¢E‹8sæŒÖåŠZf÷ùÝœÏ:hÔ¾7úŸ[´Ðex9°6Òõ¡5¥¦¦òý÷ßóý÷ßóùçŸk]ŽÂÁøûûóÜsÏñÜsÏ‘’’ÂÊ•+Y¶l?ÿü3óçÏgþüù„„„УGºwïN=èÚµ+>>>W/œ•Ò¾áæâ¦MûFq1lßn~¬Óö ¸ ] î–]+è~zåÊ•ü¿ÿ÷ÿ=úÆeíÚµª~âãã1 „††ÇüùóyòÉ'+½oß¾}ÌŸ?Ÿ5kÖTøºyHž“çâãã¦yÎ6ÏíÙ³‡G}”;wrþüyæÎË AƒˆˆˆàÊ•+¬]»–éÓ§Ó·o_üüühܸ1½zõbüøñÄÆÆ2kÖ,¶oßΡC‡HMMu¨_Ûš5kHLLä•W^aíÚµÕèóâÈÏÏw˜_›3=g4Y¸|!䀿¸röŠýkÙ¿JÃã‡ø÷¢ÅsJ€nº?9ééWŸ’I¦OŸÎüùó9}ú4âöt½=bľýö[Ú¶m˱cÇʞߺu+K—.eÉ’%?#<<œÝ»wsþüyÖ¯_ÏĉÉÈÈ`Ò¤IÞ—››kõúEíôöÛoË:Ò¸qcž|òIèÕ«®®®ìÞ½›_~ù…µk×’˜˜È¥K—¸té?ÿü3Ë—//û~WWW‚ƒƒ‰ˆˆ ""‚æÍ›Ó¼ysÎ;GTTAAAvÿ5Õ©S‡§Ÿ~š.]º`2™ˆŽŽÆßߟþ:ÞA »Îí"=ÏÖbÛÇòöÛoÛ¿§Uiß0@§û¢M&”è–ÞÞW£Îµk×´.ÁáévˆpÕªU<òÈ#ìÙ³‡6mÚP·nݲ“7nÜÈsÏ=ǹsçªý¹cÆŒaÏž=œ;wƒÁH3¾¢f®\¹Bbb"III$&&Vø:{ö,………U~¯——õë×§~ýúÜöqùçêÖ­‹‹KÍ~H¹cÇ €ÉdÂ××—€€&OžÌO<¡þøh"¼cÏo|žÙûgãîâÎå—/à`ÿ"úõƒmÛÌáù÷ßí}p,'‡vû÷0»M&5Ò`îj’Üb™nW ùå† F—.]*íÝLª²åN5uïÞ•+W’ššJhh¨5JBè\pp0ÁÁÁtëÖ­ÒkF£‘K—.U°•ÕëK—.Uëš...Ô­[·Ê€}»î]ºÊÃÿû_¦NJVVYYY¼ñƼñÆtîÜ™)S¦0bÄ»{®7%¦V[ Àà–ƒµ ÏyyðË/æÇÒÿ @Wé®5t }}}ÉÊÊ([)VlÚ´‰ÆßöûF#ÉÉÉÞ—››ËgŸ}FãÆ%< !ìÂÅÅ…&MšÐ¤Iz÷î}Ë÷\¿~ôôtÒÓÓÉÈȨðÏÛ=§|U———W…€Ý°aC’’’(...kgÛµkä©§žbÈ!Lš4‰AƒÕèß…¸a[Ò6RsÌ A±í5Ú}cçNP~:":øúj\°Ý蘘Þ|óM8@TTTÙó/^dÁ‚ôéÓç¶ß_XXHxx8111´hÑ‚3gÎðóÏ?ãêêÊÚµk+½_NõjÅÇÇËÑÏBeÈRŸs:u¨S§aaaÕúüââbUAûVÏ%''“œœ|ÛÏÏÉÉ`õêÕ¬_¿ž   ž}öYâââª]«¨hÙóîž®žŒˆhpoQúŸÝÜ &Æ~×u0J€¾Ë×¶EÙËÍç\ˆÊt  À¸qã¸çž{èÛ·/>ø 7n¤Aƒ¼óÎ;·ý~OOO¾ûî;¶mÛFRR­ZµbذaŒ?žàààJï—S}„Zš ú§¤œBh«¡S77·²ö‘êÊÎÎ&##ƒ… òæ›oV9HíZz¤±Éd"00+W®°{÷n4h €ß¡bc1«Ž¯à¾V÷áïéhpoQt·n ÓÖ…b“‰C¥m¢Î´ÿóåË—iÖ¬™Öe84Ýh€yóæ1dÈ-ZD«V­¸|ù2S¦LaÆŒܾ_Ì`00pà@¨êZòQ¨%áY¨åÈ»µøùùñÎ;ïðÑG•…goooÜÝÝÉÏϧyóæDGGÓ§OºtéBTTT‡…ÙæÄÍ\Í» Tlß°ë½%#4?ÖqûÆÑœòŒæ!Xg Ð’Y,Óu€xì±Çxì±Ç´.C!jÂÂBžzê)-Z„‹‹ Í›7§GôíÛ—.]ºÐ¾}{ÜÝݵ.³ÖRÚ7¼Ý¼Þf¸6ElÝ ¥ÁQÏZk/Ýh!„Öc2™X¼x1=zô`êÔ©têÔIZ1쨰¤Õñ«Öf~mû§´oøø@ÚÔà”íåâB; ¬Utýó²+V0~üx6lˆÁ`¨ðÕÃÊÿÁË¡P+>>^ë„“HKK«tº˜Ö qqq<ÿüóÜ{~ílÓ™MdægÛ®âîv½·(ºW/ðð°ßuÌþÒÝÉÏ·›vürd’Y,Óí ôÚµkyä‘G:t(Ó¦M#$$¤Âëw24s;2D(Ô’!B¡–­‡…óQÚ7|Ý}ÖfX…×ìvo¹xÌuܾQh4r¸t§gê"TC·zË–-Ü}÷ÝlذÁ.ד߈B- ÏB- ΢¼‚’Ö&˜·Q1o·ŠÇFÛíÞ¢¬>ƒ®ô99–ö;[ÿ³dËtÛÂI½zõ´.C!„°ŠïN~GVù€°›Û7ìJ Ðp÷ÝÚÕ¡±ò„ζ-,Óm€5jçÎã§Ÿ~Òº!„¢Æ–5·oø{ús_«û´+D ÐýúŽ·&TúŸý\]‰ôñѸamºmáhР«W¯¦sçÎ4hЀ{î¹§ÂëS©iÈjÉI„B-µ'ŠÚ/¯8u ëx0âA¼Ü¼*½Ç.÷–„s4èº}n¬@ßí燋 ‚œD¨†nÿjxæÌˆ››-[¶ÄË˫—‡•§†eˆP¨õöÛok]‚p»ví*$ú¶áÄrŠÌkUµoØåÞ"ýÏä+ ìêï¯q5Õ'™Å2Ý®@/_¾“ÉÄ•+Wðó³ý>™Ò/Ô’!B¡–  …Ò¾QÏ«ƒ[¾å{ìroQtãÆaûë9¨CÙÙ›L€sö?Kf±L·+ÐùùùÄÄÄØ%< !„¶’S”Ææ¥FEŽÂÃU£}—Fó „ ëÕg€ýYYe1@ Ët |ðA~úé'222´.E!„¸cëÖ‘WœÀ#íÑ®ƒAù3UçZé®ëæF+oo ïÎH·-^^^ 4ˆ:0bĺtéRáõàà`† VÅwWŸ  µdˆP¨%C„n´oz2°ÅÀ*ßgó{Kù]­$@pO:8×ø ™ Z¦Û½eËÖ­3O,Ï™3‡9sæTxýÞ{ïµj€–†|¡–œD(Ô’“EVAßü€‡¢ÂÍ¥ê?Öm~oQúŸ#"Ì=Ð:•]RBBžù'Îv€ŠBN"´L·zÊ”)L™2Ånד߈B- ÏB- ÎbmÂZ JÌ?áŒmûÃSlzo)(eG¯>¸~£‚d5tÛ-„B8»eGÌí!¾!ô ï«]!»wC骫Þô~9PtµOJJ ÑÑѤ¦¦rêÔ©*ßëïïOçÎíXB¡^f~&›Îl`tÔh\ ®Ú£´o¸¸˜O Ô1¥ÿ9ÈÝp¯ÊÚˆÚAWúí·ßfÞ¼y$''³råJ¦M›Vå{ï½÷^öìÙcµkË¡PK†…Z2D¨o«ãWSXRXnßß[”}÷Ý`›k8 %@;óê³ Z¦«Ž™3g’œœLHHÏ<ó ÉÉÉU~mܸѪז!B¡–œD(Ô’“õMißhè×ÞÍz[|¿Íî-ׯÃþýæÇ:oßÈ,.æti+‹3hÉ,–éj:99™¨¨(’““iРҞý~ IDAT>>>v»¶4ä µdˆP¨%C„úu5ï*›Í«¾cÚÁÅ`y=Ìf÷–íÛ¡¸ØüXçú×ë×1•>væ-™Å2]­@ !„µÁ7Ǿ¡Øh­šž7Ú7<< W/mkÑØ¯2@¨ …B'³üèršú7%ºi´¶Å(ºG°ãOv‘ xxÐXzˆk5]µp(Ο?O~~þmßãééIÆ ­vM"jÉ¡PK†õ)5'•mIÛsû†AåYw6¹·¤¦Â‘#æÇ:o߀ÚYPQÈ¡eº\îÖ­Í›7¿íרQ£¬zMiÈjÉ¡PK†õi展”˜Jˆmgy÷ …Mî-[¶@é¡!zÐWŠŠ8[º8çìí’Y,Óå ôÇLݺuoûžàà`«^Sò…Z2D(Ô’!B}ZvÔ¼ûFx½pº5î¦úûlroQÚ7êÔnêk©jSÿ³dËt ccciРÖe!„Õréú%v3ÿÔAóáA¸ cbÀM—‘¢Lm ÐÂ2]¶p!„Îh展MF zí6‘˜hþÝ·oÀÝÌË‹«¶&ÚNdˆP¨¯u ÂI¤¥¥•  }PÚ7ZÕoE熫õ½V¿·üôÓÇ kÅ „ É,–é*@7kÖŒ­[·R¿~}»_[ò…Z2D(Ô’!B}9ŸužÝçww¶úlõ{‹Ò¾ wÝeÝÏv2— ¸T:kC€–Ìb™®–|||èÛ·¯&×–†|¡–  µdˆP_–]Ž©ôœ»;é¶ê½Åd‚­[Íû÷ƒº­ôj«ÚÖÿ,™Å2]­@ !„ÎJ9<%2(’¡´-æðaóÐ íÔ¾-,“-„B8¸ÄÌDö]Ü8Àð Ühß ÐÜÐ-½½ Ðùn$z!ÚN¤!_¨%C„B-"Ôeõ ¶ýh«Þ[”-ZXïsPVVÛ>ü¨=«Ï’Y,“m'Ò/Ô’!B¡– êDz#æÝ7î ¹‹¨ ¨;ú «Ý[Š‹aÇócY}fÚ»ï’wé$$Ôš-™Å2ù9ƒHC¾PK†…Z2D¨'ÓOr0å P³ÃS¬voÙ·”ž_謬,6üþ;¼ø"|ò ]b ½Æ $³X&+ÐB!„³Fû†U•ïîß_»:ÀŒ?$õÀÓš5Ø uIÂN$@ !„Li߸»ÁÝ´®ßZãj¸ Û·‡ÐPmkÑPVVß:„)"ÂüÄȑ̘9SÛ¢„ÝH€¶iÈjÉ¡PK†k¿ãiÇ9œz¨ùê³Uî-¹¹°Û|˜‹ÞÛ7f|ø!I÷ßã OOޱgÿ~튲É,–I€¶iÈjÉ¡PK†k?eõjÖÿ Vº·ìÚ……æÇ:ÐÊê³QY}.•>l/ô‘FUYdËdˆÐN¤!_¨%C„B-"¬ý”þ箺Ҽ^ó}–Uî-Jû†«+ôéSóÏsR•VŸåV¡»wíjÿ¬D2‹e²-„B8 Ã©‡9žvpáA¸ »vmkÑHU«ÏŠÚ² -nO´Bနö †·oXEz:4o§§çö*WŸµ¨ZTMZ8ìDò…ZñññDFFj]†pÊaPPÆ•[PÚ7z4íASÿ¦5þ¼ß[¶m£ÑüXÇútRŽåø·ß’WR‚—‹ m}}+¼§¤°­{÷:mGAAžžžZ—áÐ$@Û‰4ä µÞ~ûm郪(„Ò ]ûL9ÈÉô“@͇5¾·üô“ùŸÞÞm•šœÑ·_Íáœ:”®0ÿ½ysþ¦qUÖuùòe郶@´ÈoD¡–„g¡–çÚKißp1¸0¦í«|fï-JÿsÏžæƒCtl~J `\-Ü [2‹eÒ-„B8¥}£W³^4ªÓHãj€ àÄ óc·o”˜L,NM O½z4óòÒ¸"¡ ÐB!„Ùwq‰™‰Ä¶s°Ý7@÷zsF—JçšžhÐ@ãj„V$@Û‰  µä$B¡–œDX;-;jnßp5¸2ºíh«}nî-J€®W:w¶NANj~éL“«+k\mHf±L´È¡PKN"jÉI„µ +Ž® OxB}­×_[£{Ë–-æöík>DE§²KJX]ú—Ö‘AAÔ©¥ÿ.$³X&C„v" ùB-"jÉaí³ûünÎg¬ß¾qÇ÷–øx¸xÑüXçí+¯\!·¤€'jáð B2‹e²-„B8¥}ÃÍŇ¢Ò¸šRÒÿ\FÙ}£¡‡4®FhI´BáŒ&cYûÆ€æòqr”ݰ!DEi[‹†Î°-3€ÇCCq54®HhI´HC¾PK†…Z2DX»ì<·“äìdÀz‡§”wG÷£Ñ|!è~õyáå˘J×öÝ7$³X&ÚN¤!_¨%C„B-"¬]”½ŸÝ]Ü5ÊêŸG÷– #ÃüXçZißèèçÇ]7Ý]ÛHf±L†íDò…Z2D(Ô’!ÂÚ£ÄTÂÊc+Ür0^Öﯽ£{‹ô?°ÿúuâssÚ=<¨Ìb™¬@ !„Û–´Ôóév±íäð¸ [·†¦Mµ­ECÊ곫ÁÀc:ÐÂ2 ÐB!„Æ–1ï¾áéêɈˆWSª ”!¯>™L,-=º{p@ <<4®H8 Ðv" ùB-"jÉaíPl,fÕñUÜ×ê>ü=ýmrjß[v<ócèï®^%­¨¨ýà É,–I€¶iÈjÉ¡PK†k‡Í‰›¹šw°mûFµï-Jû†ÁýúY¿ '¡ÝíïæÆˆ ÙZÐÆ$³X&C„5PRRÂÎ;ùõ×_ÉÈÈ E‹Œ=šzõêUz¯4ä µdˆP¨%C„µƒÒ¾áíæÍð6Ãmvjß[~úÉüÏN 0Ðêõ8ƒŒâbÖ_5ÿåfLp0Þ.úXw”Ìb™>~'ØÈ AƒvßêI€® &pöìY:Ä®]»¸xñ"®®®Lžg¤¤À®]t1iíííPõYû9%“LŸ>ùóç#,“m%‡büøñ<óÌ3ÄÆ:P›B‡´ëœù§u<êp_«û4®¦œ-[n<Ž‰Ñ® íºvÄÒ¿¸=¢q5ÂL77ëŠj;|ø0ýúõcÈ!,X°—›¶¹‰‹‹d{2!„¨¶ä@Šyü ð¾ƒìRQCyÅy¿LNQã:ŒcÁ¨Z—tÃÓO×_š[722Ì}Ð:ó? ÌINÆÃÅ…ä=¨¯³’[,“è:vì  o߾̟?¿RxVHC¾PK†…Z2Dè¼6œØ@NQp£}ÃÖTß[”þç˜]†ç|£‘W®ð@` îÂ3HfQCt œ8q‚pï½÷²dÉ\]]«|¯4ä µdˆP¨%C„ÎkÙQóá)õ¼ê1¸å`»\SÕ½åÌHJ2?ÖéöukÓÒ¸Vº…Ÿ^÷~–Ìb™œDX?þ8)))3f̘ ¯}ýõ×N$”S}„Zò#3¡–œD蜲 ³Ùpb##Gâáêa—몺·(«Ï Û­ì¾èîÎPžÀ(™Å2 Ð5ðÐCѳgÏ[¾v»Õh!„úµîÄ:òŠóûµo¨¦èÀ@èØQÛZ4ZXÈéé<‚»AO»?‹ê]Ó§O׺!„NF9<%Ð;-j\M9&Ó8ú÷†ÇÅ©©—î­ ×ö ¡Žô@Û‰4ä µdˆP¨%C„Î'« ‹ïN~ÀCQáæb¿u,‹÷–Ç¡txN¯íÊÑÝ>>tó÷׸íHf±L´HC¾PK†…Z2Dè|Ö&¬¥ ÄNbÛÛ·}Ãâ½EçýÏGrr8˜ Èê³dˤ…ÃN¤!_¨%C„B-"t>ËŽ˜wßñ ¡ox_»^Ûâ½E ÐÍšA«V6¯ÇÑ(Ã`œÎ´dËdZ!„°ƒŒü 6ÙÀè¨Ñ¸hؼ¸¶o7?Öáê³Ñdbai€î[¯ͼ¼4®H8: ÐB!„¬‰_CaI!`ÿö ‹öî…Òö=èÍ™™\*íû}¢A«Î@´HC¾PK†…Z2Dè\”ö†~ éݬ·Ý¯Û{Kùþçþým_ŒƒQ†}\]¬q5Ú“Ìb™h;‘†|¡–  µdˆÐy\Í»ÊæDsH}¸íøìÿÇïmï-J€nÛ6´OA"»¤„Õ¥D9ÇA2‹ 2Dh'Ò/Ô’!B¡– :oŽ}C±Ñ|<´VíUÞ[rsaÏóc¶o|så 9%%€ì¾¡Ìb™¬@ !„6¦žÒÄ¿ ÑM£5®æ&;wB¡¹7[ZÙ}£¡‡4®F8 ÐB!„ ¥æ¤²-i´{vŸҾáê }úh[‹]((`kf&‡†âªÃÓÅ‘m'Ò/Ô’!B¡– :‡•ÇVRb2·ĶÓn÷*ï-J€¾ç¨WÏ~9€…—/cTŽî–Ý7ÊHf±L´HC¾PK†…Z2Dè–5ï¾^/œn»iVÇ-ï-éépèù±Û7”Ý7:ùùq—¯¯ÆÕ8É,–É¡HC¾PK†…Z2Dèø.]¿Ä®sæ¿ä<ÒîMk¹å½eëV0Íu ½~ã¹¹€¬>ßL2‹e²-„BØÈÊc+1šÌUËö*)힞г§¶µØ™²úìj0ðXHˆÆÕg#Z!„°¥}£UýVtnØYãjnA ÐÑÑ £ã«‹L&–¦¦08 €P+ÎF´HC¾PK†…Z2DèØÎgg÷ùÝ€öíp‹{Ë… pâ„ù±ÎÚ7¾OOçJQ í·"™Å2 Ðv" ùB-"jÉ¡c[~t9&Ì;<8BûF¥{Kùã»u •ö 77Fi\ã‘Ìb™ Ú‰4ä µdˆP¨%C„ŽM9<%2(’¡4®æ÷%@ûûC×®v¯G+™ÅŬ»z€1ÁÁx»ÈZâÍ$³X&¿k„B+KÌLdßÅ}€c¬>ß’ ûô1¢¢ËRS)(ÝyDŽîwJ´BaeÊê38Fÿs%ÇÃ¥KæÇzkß(mO÷ò¢·ÎŽÖ#-v" ùB­øøx"##µ.C8e€0Hz8β#æÝ7Ú‡´§mp[Mk¹víyyyœ:uŠV­Z™Ÿ\½úÆ:v„”êÖ­‹···6EÚÉé¼<~¹v €ñ¡¡Žv¨ºÃ(((ÀÓÓSë2šh;‘†|¡ÖÛo¿-}ÐBe€Pz¡ËÉô“L98Fûƨž=¹'=ï¯]c˜²âšžnþ§‹ <þ8ç iËÿΚ¥]¡v° ÜŸÅãe÷*]¾|Yú -m'òQ¨%áY¨%ÁÙ1•o߈m¯}€žüÊ+äOžÌ{¹¹Pzò^£.]bJ“&<;}º6Ú‘ »ûûÓº–¯¶×„dˤZ!„°"¥}ãîwÓº~k«ÑãÆ±&,Œ’*^OÜ££iܸ±=˲»]×®q&/½ŸEÍI€B!jè÷ßç½OÞcÔ³£8¼å0\sœáAƒÁÀ£¯¼Âb_ß[¾þ~“&¼ôá‡v®Êþ”½Ÿ=\\ˆ Ö¸áì$@Û‰  µä$B¡–œD¨½ÌÌL†?6œA¯ â/GÿšºkÀØg7žÅXº]šÖFdž +­BŸ xê`õn´oº»340PãjŸdË$@ !„w`ù†åäFåÞö=É-’™·|ž*ºìl <ÂX“‰…¥O½ß¸±.VŸS ù¾tÛ¾±!!¸d÷gQs …Bˆ;–­¢ÿ<öÚgûb,yî9HHàÿ³wÞñMUï'i:é¤-»Œ2Ë”-*2E@6‚ã'TŠàÄ Šƒ¡âWÜ \€ÈT”%`¥ì²Z e´¥t¯¬ß'éLšt%i{Þ¯×}åæÜ“{ÒpòÉsŸ1ØLà~Ç5Âû¼2>­AÜ%Õ7$…ÐvBäKlÅYn÷Jœ™DèX<]=­OÊ„:A޹ýê+øñGΊ{ïå¡÷ßg„·wð>C~ëîÖžžtóöv°5U©Y¬#´ù[‘I„[‘I„Ž%´~(¤–<Ç-ÎÁw¶Aæ8v ž~€…žžðý÷Œyä>_·®FxŸOfd‘–ˆÖÝÛšÅ:²¡ù[‘I„[‘ „Žeñ[‹Ù6b©÷YPÑÙÐæFÿ} 3‘žãÆAV¸¸°|Û6 DÜ=`€cl²3¦Îƒ àÿ¤€¶©Y¬#=ЉD"‘”Ý7w“š ë"¹„ÊJBw‡òã²Q©T±Ç‡¨(±ÿöÛp玱ÃAè ~0 è>~~„¸»;Ø"IuBz %‰D")%‡¯fÒ¦IÐ|ûÒ.ªÉ™Éd²ðVyÓ)´K·/ÅÛQ1·_~ ?ý$ö†—_vŒdWr2qÆX^™<(©h¤€¶2 _b+gΜ¡uëÖŽ6CR0%:Ø’šÅÕ´«Œ\5’lm6.JÖ>±–þMû ×ëQ*|s·@Ü3 ÂwßBQãÖ–ÆÚÏž*cdëîR‘““ƒ[‘¦;’ÂÈ;!ò%¶"“%¶"“íO¶6›‘«Fr5í*ú0O<ŽÏéépÿý ..°r%`Õ¤µ%]§c½ñæ¨À@¼FSE‘šÅ:Òm'd@¾ÄVd¡ÄVd¡ý™´i‡¯`j—©Ìè>ÃÁaêT8{Vì¿óN¡¸ç𴶬MH C'z.ÊÖÝ¥GjëH´D"‘H$6°`ï~:.âŠïn|7ˆ,s°EEøâ áq÷üÒKŽµÇ˜ªoÔws£¿¿¿ƒ­‘TG¤€–H$‰Ä ›¢6ñÚÎ×hê×”_Æý‚Z©v°UˆŒ„gžû Â÷ßC mY}%'‡?““x(8U }$•‹ÐvB&JlEv"”ØŠìDhNÄŸàáucÀ@-×ZlzpžN”¸™–&ê=›âžW­‚Úµ‹M«)kË7n —­»Ë…Ô,Ö‘ÚNÈ€|‰­Ô¤DIùI„•Obf"ÃW'=7 ~ý#í‚Û9Ú¬Â<þx~Üó¼ypÇf§Õ”µå;cõNµjÑÞËËÁÖTM¤f±ŽL"´2 _b+5)ÑGR>daå¢ÑkûóX¢“£˜×Ã[ w°UEøüóü¸ç!CàÅ-N­ kË‘´4NgŠ®6Òû\v¤f±Žô@K$‰Db†[f°çÒjÿ¯ÜùŠƒ-*Bd$<û¬ØoÔ(¯ÞsMÆä}V)<ì`k$Õ) %‰D")²CËøâß/èV¿_ÿÚÁ!-­p½g qÏ5 ÁÀªøxPÇÕÕÁIª3R@Û /±•š’è#)?2‰°rؽ“ç¶=@}ïúlx`î.S§Â¹sbÞ<èÕËêKªûÚ²5)‰µŸË‹Ô,Ö‘ÚNÈ€|‰­Ô”DIù‘I„Ïù¤óŒ[3­^‹»‹;Ø@}ïúŽ6«0Ÿ.<ÎC‡–÷\ê¾¶˜Â7|\\!ÛÛ— ©Y¬#“í„ È—ØJMHô‘T 2‰°bIÍIeøÊá$e%ðõð¯éV¿›ƒ­*ÂÑ£…ãžW¬°9î¹:¯-ÉZ-¿Þ¼ ÀýAA¸;º¥zGjëÈO˜D"‘HjÈéÄÓ̺sµÈÁVÁÆzÏ5‘Õññäèõ€ ߨ) %‰DRãyyÇËl9·€á­†3¯ß<[d†)SòãžçÏ·)ð1ä ‰»;wùù9ØIM@ h;!ò%¶RÝ}$‡L"¬¾‹üŽEûÐ.¸?Œþ¥Âɾ?û V¯û÷Ý/¼PêST×µåBVûSRx¤Njv!¿ŠAjë8Ù Q}‘ù[©î‰>’ŠC&–ŸW0õשÔö¨Í¦7áíêí`«ŠPޏç‚T×µåûß¯Èæ)‚Ô,Ö‘I„vBäKl¥:'úH*™DX>®¤^aÔªQäèrP+Õ¬¿–¦~MmVaLqÏ99"îyõj(Ó©ªëÚbзûøÐÂÃÃÁÖT¤f±Žô@K$‰¤Æ‘©ÉdĪÜÈâké¥ÜÝøn[e†‚qÏ Àí·;Ö'coJ ³²Ùº[b_¤€–H$I€ð áD\‹`z·é<Þåq[e†O?-÷’ŠG&–·÷¼ÍšSkè×´‹ï]ì`‹Ìðßðœè†HHH™ãž RÝÖ–l½žŸ¸¯vmü]dTjE!5‹u¤€¶2 _b+Õ5ÑGRñÈ$ÂÒ³îô:æîž @¨(kî_ƒ‹ÒÉ„Wjj~ܳZ]®¸ç‚T·µeSb")Z- k?W4R³XÇÉVê‹ È—ØJuMô‘T<2‰°tDÞˆdÂú 0àãæÃ¯ýJ€Gù…i…3e œ?/ö,€ž=+ä´Õmm1%ªÕ ‘ e*©Y¬#=ЉD"©öÄgÄ3|åp24(JVŽYI›À6Ž6«8Ÿ~ ?ÿ,ö‡ “qψÏÍek’h¹þ@p0êr†·H$¥Ez ËIvv6‘‘‘DDD’’„ ¨_¿¾£Í’H$‰‘\].£W&6%€…2¤Å[esqϳ¬ŒGk0²ú†Ä1H]NÚ¶mËÅ‹ñðð ++‹>}ú˜Ð2 _b+gΜ¡uëÖŽ6CR0%:ØçfÚæi컼€ 'ðb¯l‘ÌÅ=ûûWè%ªÓÚbjÝÝÚÓ“nÞNÖø¦““ƒ›››£ÍpjdG9ùä“OˆŽŽfË–-%Γù[©n‰>’ÊC&ZgñÁÅ|óß7ôlØ“/†}á`‹,0yr~ÜóÂ…÷\ê²¶œÌÈ "- ÞçÊBjëHt94h111%Γù[©n‰>’ÊC&–̶ Ûxaû 4ôiÈúñëqS9¡Wí“O`(«Çðáðüó•r™ê²¶˜¼Ï àÿdõJAjëH´D"‘HªQ7£xà—Ðtx¸x°ñÔ­å„Þʈˆ|Áܸ1T‘[Yè ~4 è>~~4’a!´ˆˆˆà»ï¾cÆ …¶¢M䘓crLŽåu&ŠìììR½69;™á+‡“|42aùÈåt®×Ùéþm~ú‰Ä1c Å=oسÇyìs±]ÉÉÄsŠÚ=êtöUÕ1“&yå•Wøî»ïˆER22„ÃNhÅÞ%kT·na’ÊCv!,ŽÎ cü/ã9{ó,3oŸÉ¸¶ãl•>þLáï¾ =zÀ† •v9gO"|õÕ¸r%˗ϱr埸¹yä3Øš7ÀS¥¢—­­Þh4G›àô( cI¹Ø½{7}ûöåÀô,’üNll,»víru’ªDxxxµ‰U”T.Œb«ZÇB¿ž×õbßG‹j•8ýÙ­ÏòÑ?0ªõ(ÖŽ_‹'¬üñÇ0c†Ø1¢R…³ g_[š5{èè-OðÐúóà®çá:uø¡Öñ®&ôë×§þ¼8é¶2 _b+rÁ’ØJµÎeàëÿ¾ÎÏëtäûÑß;§xŽˆÈoÒ¸1|û­].ëìk‹Ré”§~×up?¦dëîÊEjëÈh‰D"‘TyöÆîåÉÍOäÄÆ7â¥ör°Uf(ZïùçŸ+¼ÞsµeHtMËb€|Ï$Fz ËÉœ9søã?HMM`òÑtøö IDATäÉÔªU‹¡C‡2gÎ['‘H$ÕŸK)—½z4¹º\\U®¬¿ŽÆ¾m–y&M‚ Äþ{ïA÷§ª”·%‹Ý£PsÂ; ’…Ðå¤[·nxy÷r´oß¾ÐsÙ‰Pb+Ξè#qd'BÈÐd0bå2ødè'Ür§ƒ­²À²eðË/bäHxöY»^¾J¯-n€B¤l½à`cª?²¡u¤€.'ÇgøðáVçÉ®>[Y¸p¡ÓÇ*JœSš mÀÀ#ë!òF$Ïôx†I·Mr°Uø÷ßü¸ç&Mì÷\*½¶Ã78_ ë·kK àÆ2Ú R@Û ùA”ØJ•ý‚“Øš*œM¼ñç¬?³€{BïáƒA8Ø" ¤¤ˆ¸çÜܼzÏøùÙÝ g_[,Ök™3Äþöºüû/üð<ü0(d$G¥ 5‹ud¡D"‘Hª?Ÿü™·ÿz€–µ[²zìjT •ƒ­²À¤Ipñ¢Ø—qÏf¹rââ,4yŸu ØLN<òÜvlÝj7%’BH´HHHààÁƒÅÆëׯ/éI$I)ˆ¸Aø†püÜýØôà&üÜíïѵ‰eË`íZ±ï€¸çªÀ0z´(LR Uô3 è#p˵4ˆŒ„Áƒ¡o_ч¦[7»š-©áHm'þø#ž½{oÑâŽYã‹$ÎJ•Nô‘Ø•šD˵ãq$§&ã×ÀŸœ#jþûa²´Y¨*V]E«Ú­m¦yœ î¹ Î¸¶¬X?nÏçñòš…ÍÈÕ^ãfÎf nßøÚ‰GýiÑBOŸ>ðÁžþ)š8Ž óæA‹Žý7Ud¡u¤€¶7ÍÅÆ•Êâ^iIͦJ'úHìJuN"LLLdü”ñÜ{ê.ü\}ÀN$Ÿâ•gßA£ÒÀݰhÈ"…r˜W¯^Í+aZŒ´43rs©ëêŠßÏ?;$î¹ Î´¶èõðÒKB¸¸À‚¿ñÿ—7gøÔ©Ü¼ãEX¿ŸGá¿eãqS(ðññÁÓž|Þz ¾øBx¤×¬õëaÊxã ½VÊŽL"´ŽÐvC~%¶á,_pç§: gƒÁÀ AŸ[Ý!ÝxÀ 45pZoɳo96âÿ¤o|ù ®X×µk[|pXÅ^\bgLô‘8'Õ5‰ðfÖMë“‚àØ©c•oL ( yí5VL™Âä´´bÇÕ©Ã[‹;À2ó8rmYºT„ZètâùË/Ãüù 4ãÆ{úÝwIš2¥ÐXìàÁ¼½x1ïΞmõZ÷Ü Á<{6DG‹*‚=ï¿/*v Xÿªê‹L"´ŽÐvöN„·nÁ‘#b+Š»;4kV\X7o.¼ÕêÒ[µxñ&Î{Ëê¼Îßv¨€ž9s!gÎÄXw×]]˜5kŠÕyÎŒ3%úHœ›êšDèåêe}RÔ ¬[ùÆXaĸqŒ7‰ÇSp Ž|t*ï³#ÖF$û}õ•xîî.ö~ØüüŸvïæ¿ÀÀ|ï³m»vüòþûÌIO§V­ZV¯«PÁ—Ô@¤€¶;ÆóÐCÅ;vè0µÌç¬WOlwÞYüXJJaA]P`ÇÅ•>ùÜ9Ñ2µ,(•–E¶­"üêÕ²]»*"“%¶RÕ“£nFñäæ'Ù½+oìîÆwóé}ŸÒ&° 7½Á߃¶¢¼ ZƒOµOÜõïï|ß1 Néé¢6ÚâÅBuX„-bă2¶cGz?î´Þg{¬-O¿.¾c”J‘(øòË–çÿÏÿ>Æ`@9n“ÝܸÓ××ò 5â¶ÊmgË–¢öô¡C¾ݻşwî\!°çÌ©SË–[TI„ÖQ ²¨KeNll,»ví²>Ùdg‹¬äóçaÊ”pnÜXnëÂ[æU¶]_¡§qãå4l(b >šöëÔ©øj&&:vBF†õ{€aaîlÚô™Ùcááá2Zb6lª^,t¶6›yÏã½}ï‘«zƒ½‚yàûLè8¡ðä×3àºÑ í£€Er ®YÏ?WŒU‹T*xê)àãÀ†Õ«yiêTöœ9㔺2×–¬,ÍL@äÝüô“(Ug‰¯®]ãñ³gÑ ¸)•¬ c¤ƒ~ þþ;Ìš%BOL4o.~ŒWyßÎJ¿~ý ‘ßE% =Ðv™òÝÝEMÌ6mD¨6 ƒ¯¿N—Ü\ñXpßÜXyö‹ŽEGç;|JÂ`È·„Z-âð, ì† …È.KèIvv0.,·:/((Üâ1¹`Il¥ª g€­ç·2}Ët.Þº€R¡dj—©Ìï?w[g†¨(!”ÿø#ìÎ;á㡈'tĸqÔmÔÈ)Å3TÞÚrå Œâyh(lÚ$¾7,ñÁå˼pá^*Ûµ£¿¿ãþþƒàAðÃ~é’p2=ð,Z$*vmöRq&Íâ¬H-± //Ñ=ÊQ„…ÁéÓÖçÕ«}ûÂåËbQ‹+ÞJ£‹£)¹Å&‘mI`›<Ù2¾["±¸´8žÝú,¿œú%o춺·ñÙ}ŸÑ½AwZfÌLá~üàƒüE$8Þ{&L0ë’T(ôìÕËΆ:–`ôh¸~]<ï×O8ëJ¨ 2':šwŒ °¿‹ [:t §Ñ‹ïH”Jñ§?^ü>š?_ä9": ÂĉCÈζ~§±sçV®üŸ,—8) %ÕŠ† áÇóŸ "yÒ$¨M÷Ë*²]\ {²6u¼%I>:ƒŽ¥ÿ,åõ?_'-W4ñqóáí¾o3½ûtT •ƒ-4Ãúõðì³+ž+•0mšÔ~~ŽµÍ‰X±BT¯È1æÂOŸ.ÂÃ],( ð̹s,‹ ®«+Û;v¤½— u¿íˆ››ˆÖ™4Ixž?úHüžÚ¶ ¶o/¯`ÒÓ—[=OIwíAHHw „ÛF:wvgÃó!…ËHm'rr,WÛp$ÍU‚‚Ú03Ãú'D¡žâ:uDÍOsXÙã⊇hµâûÕô[Ò҄Ǧ®™>2‰Pb+ΞDxðÊA¦mžÆÑëù ÔãÛŽçÃ{?¤^-' s8^„klÝš?Ö£|òIµè¸QQk‹^/ï-ÏÕjQÅbj 9ñ:ƒÇ¢¢øÎèªnìîÎŽŽiîáQn{* __á…ž1C$~ó(k¥£¸Ó R…³Ü꼦MËÉ$BëHm'nØhìþúk)))VçN¶:§r‰£Q£'¬ÎrsË.õ™mÙ7nXØ&O¶-qÚ'OŠpOOhÚ4¿\a³f°qãB–,YNӦˡ–›\O\œõÏäm·…1p`ïÊ1BRnœµá­ì[¼²ã¾ø÷ ˆ<õ-øxèÇ læ„=”³²`ÁžarvÔ®-î×OšTm2È*¢aJ <ø Hº u”{—°LäèõÃÃOô1U)*¬Mb»hÄD±>\ü:¶†~¤¦Â޽³®Vç?Ü·4VMÂFG'n>·™[f“€J¡bF÷¼Ýïm¼]K¸â(~ÿ]$ ë£PÀĉ"ö9(ȱ¶ÙÒ&j4¢²Æ—_Šçðí·¢Ì[I\ÊÎf`d$ç²²ÌmÚ ®a‹Bûöbý´ƒÁ²À¶&¾-=¾ùfÙ+CÉ$BëHm'œ5‰PR±tî`Sé¢Þ½;YoÙe Ï¥E¡±Ë®®wÎÅ‹Ë. e¡u¤€¶òƒX3¨ˆ¢ùeMôñõ…Ûn[Q !FMÞê™3Eƒ€ÊD§›=n¾œ<)ò¾Ú¶Íß4¨üë:{ g­^ËGÿ|ÄÜÝsIÏ·æýÝýY0`S»LE“‰¤Ü\QkmÞ¼ü0c†ø•Wƒ°um9v †ϯƒûí"yÎ\ÙÍ‚ü—žÎ ÈHŒåˆf6jÄ¢ÐÐrX,qR³XG h‰¤ PAÙ ð(›:lY#, –,Éo«®Õš¬¬c'OÚ&ÀÓÒD«ù‚øúÔmÛŠOýúe{k:û/ïgÚæi»q,olBÇ ,ºgAžNþðÇB$Ÿ=›?öðÃBP[S‚5˜õëá‘G ÃXúâDøüs‘´\ûRRzü8)Æ`Þ·›6evãÆ•l­c s/÷FIÕG h‰DbÑÂÖQ´jUXÿXÂÛ[„|¼]™’"ª—­`âï/„tQq]M5eÊl.\¸buÞÀ½x啺M8!IYI¼¼ãe¾Žø:¯¦sXPŸý”Þ°Fø•+¢`ïš5ùcaa¢7sŸ>3«*°`HÔëE.å{UÖØž”Ĩ“'ÉÔéPKZ´`F5¾´iSÕèÚ׫W€Ù&)E5j@åS ‘ÚNÈ$B‰­ÈN„¥§m[8p®]˯«}êTþ~rrþÜ[·`ß>±$ ßK]PXשcýúýuŦr{YYá* +3‰Ð€GWðâ/’˜)®ã©öäõ»_çùÛŸG­t²:¾ |ø¡Ï0¹OkÕ‚7ÞñÏU°îpEѲe4š&h4)¨Õ¾ÅŽ "á8#Ãø __Xµ î½×ú¹×%$ðàéÓäêõ¨ ¾iÕŠ ÒÃïüøcÙC e¡u¤€¶2‰Pb+Ñ-Ì99Wðñ±îE1JßÙёԫ'¶E*W¯æ‹é‚âº`Τ$‘0eJš2Q»¶ùPààÊÿ÷X£¬I„YYY¼6ï5üw€ MþþŒºwÏ<ñ …‚“ '™öÛ4þŽÍ3†·Î’ÁKhìë„·åÿüS”‹8}:ìþû… ®ÆžP[Ñë›[:‡ËK˜N‹¢·L«VÖÏ»üúu&GE¡3pS*YÆH'm+/)2‰Ð:R@Û ùA”ØJe‹g€mÛ>æ– éÙ ÞWé¶”„‹KMš„[ççWr2XýúbX¤‹t\\aamׯ¾€ˆÿë/±$00_PôpÛ“Ò ç~ý&påj±™“30 º˱èaïŽ}¼6ÿ<»‘Ôþ,z…(:ÞØ·1K/ax«ám¾EÂï½WEF_ÇWë@–2“U;> [«¥çÀ<{ã¬\™ÿâ–-aÙ²âl %‹gQûÐ!ñh%W®ðìùó/•ŠíÚÑßß¿B¬”8©Y¬#´DRie‹{É 8yòJ=¿)±òž{ _¹b^X§¥åÏIL„={Äf+iiâ<þþb³µ£YEqù²Žó)gàñÔ«¿ôtd†Ü$sÇMèj¥šçož×ï~Oµ§]ílFÿ?þ §^ÜL„Ò¹7!€w½¼èúÅùÕ5<=áµ×à…*¶X ",Ì6ñüö¥K¼nl}îïâ–èéãSÉÖI$΅ЉDR„† Å6hPáñË—Í kc³5«œ<)<Ö&<<òÅ´i °m¬,11ó_myå÷š€ç~þraAa¥¿HðÔܹLÛ¸‘ž/š=žÍÈàeÓÀÈ‘¢èm5­úP^ v0- [J5ϼpÿ]¾ @]WW¶wìH{/¯rX'‘TM¤€¶2‰Pb+2‰ÐyiÔHl“«   kJKV–ØÊÒ0ÆË |}ñõ…  @‹B»àx¶" XQS­¡Öú ‡‰gÂFŒààGÑÓŒ¹ÓA¼ñK–À!v¶Ð9ÉÌ?ÒNœÈ€Öžžüѱ# ¥Àª¶È$BëHm'äQb+R²´YÅ'+à"Ь7å?µ!³ ¼ÜgΈÍ%‹ì ¬xè ˆˆ€Í›Åvø0‹ŒnÔ»€À(ãT ð¡Ñ7wÀM /áÄ•Ïùó:.\Xnuž«k¸Õ9—/ʧO‹Ÿ’P© ys!” n-Zäw*oÞܶΣEIÕjvâËÌÜåëËoíÛãã"¥ƒD"ÿH$I91bhÔ(Üê¿|œ­é¿¢AÍ[$qu !•³ø¡ãîr¿—öæâEèß_ˆecq‰Ô©S\(·m+ZÖ—…îÝhÐ œÌÌ<=‹w"41hP/®æä0ðØ1NñÇóC›6¨m)Ó!©È$BëHm'd¡ÄVd¡ %'…=1{ò¼Ì§NŸ ¾n¾ 6˜þMû3 Ùšø5)ùĆæpëüú xü .ñ i™€®äÞÍ.. ZRxdV–Òqѹäþ±¿}›izj3Á©ç‹ÍÍÆÝôAÈê¡Dg6…Ó :æ™â>ÜH¥1;8C*½ ½þŸD§H…BxºM[IÏËzÌÜ\cA «\»f~®ŸŸÆEÅrE7óûé'ÑÒ9<<ÜêÚr1+‹‘‘Dg‹.¤“ëÕãó–-QJñ\£I„Ö‘ÚNÈ¢ÄV¤x®ºäääðî’wÙµ©Y©ù1vèX¦L˜býµºö_ÞÏ΋ÂÃ|äêt†â jÞ®ÞônÜ›ÍÐZÚ·Cmâ¦A=áFüÀ*ãaa m:_1®^ÅcËZnÞLË;ÌÈnÔˆœC¹Öy(§ëõ#&ÞïËÐ+=Ù—.®&‘MâÑa °]Z­qv”J¸í¶âB¹aßæ²bmm9™‘ÁÀÈH®åæ0³Q#…†ÚÁ2‰³!5‹u¤€–H$’ àøÉã<ðäœ =‹¶£€ömÞÇŠ5+Ø´byóõ=ÿ]ÿOx˜/îdoì^³‰®*Wz6ì)sÓþtoÐeÙ–î]»¾+ë?ÏŒ#Ì0Ä£i+ø¼²ŽED€ÑQ["·ß{÷ZŸçH§¥1øØ1njDhÍÛM›2[6¦‘H,"´D"‘”“¬¬,Æ=1Ž3wŸÉ<PAFXûRöqÿäûùôËOÙ½“wògÌŸ$ewŸ*PЩn'!˜›õç®»*¬•öòÅ‹‰)©¤†‘žpïØ±æ&'ömB0ÿþ{ñ gqƒ ÁÊ…ŽÐ÷4z" ðß©ÿX7`¿}ÿ ­ÔÍÒfq&ñL1¡|!é‚ÙZËQ  ±_c‚ÂhÔ–° 0‚ÂhÔoW!.ïM䎡wpÖë,˜Ë»I‚.Š.ôêÙ«,oE!+‹À¤¤ÂBùÌ8{Œm˜‹ò"°ø´ÀØò€VmÙÝ»‹.!§%Ç$” ˆd“PÖ,ÜIKƒèhxôÑBÃéC†0{éRK-)€L"´ŽÐvB&JlE&擞žÎ¤Y“8×ït4+ »I6‘‘Œ›:Žý[ö‹ù¹éœN<]L(Ç$Ç 7èK¼–R¡¤™³<œ'”ÛX-#÷ðÀÜs%‰;#ÕhUZ *£ˆ1€R«Dš Ï£Ì}ê)ÞûúëÒ¿\¹RX GE±÷èQHLd¤%ÑT//‚Ѫ!­Zá²e чÓøÕÕ•{^z ×=Jo›$äädž™=›Ë–UÈùrõzÎfeå‹d£`>Ÿ•eY(qQ(õð ­—m==9ðÑGìЙù±èêÊ…æÍùý?¤ˆ–ä!“­#´¦E§Nœøçù«NR"öÏ7oÞäà?ùïäÜÙýNºu톗——]m°ÄKo¾Ä¹öEÄsAjÁ#tz¦·šÞârÊe V’]”.„ú‡ÉmƒÛÒªv+Ü]ÜËdçS³gséÑG™žc.ìCäð¤ÓfÏ.ùDééÅB.ˆŠÞäÌÌbÓ‹E+¢Ïv«V…·Ö­¡AƒBžå{Œ…·ßÎ'W¯²<$„•Ï=WÚ¶¤³-bí¹s<òçŸ èÛ׿×i Ιñ(ŸËÊBkE(«LBÙÓSˆeãÖÊÃWc)Á¤¤$¾…yóÌž#eð`f/[&´$)ž­#´8rü8±}ûòÁgŸñê3Ï8Ú‰­VËKs_b;5Ä×'×;ÏÞÅñÊäWxüÑÇíjOzn: ™ $d$ä=þvð7ôJökB5Dî„€Âãj¥š–µ[ó(·¬ÝWUÅV¸oÌFÍËä'ÌFpÄtíJÓ¦ME»K—Ì å¸8Û.èãS\$·j-Z€‡‡M§ AÕ£Kû“'ãZI5j ÉÉÉüIÆóÏ3ëÃ9bF@k ÎeeqªˆGùlf&„r3w÷B"¹­§'­<=q³RsûÕ÷ßçÒ}÷Yž Vs±E é…–HJÐvâzz:š±cùváBf>ñ„ôBW¹¹¹Nû¥ëÖ-ž›ó§cO“š“Š¿‡?½:ôbÞkóœæo?~Êx6+7“Ó'?>?³A&—Ú^â¥__"îZo½úV g(™”œ”BbØÒc|F< ™ dk‹×÷ úî:%Ƀt\qs¡ï̱…„r‹€eny]&ÍË×>Ê“fâŽß¯U‹µZèÐγ­ÿ³J%Zí™ÊÔèâÅÅ‹Áa'÷>gff²óï¿6ö^G›b‘Ù‹qyøppsãbÓ¦|»u+¾]ºò*ŸÍÊ"Wo-”¨ˆPöô$Ìˋ֞ž¸—±9MJj*=¶n-qŽ^§ãäÅ‹ .Ó$’š‡Ðv"£^=b pZ/tdd$[vn!òt$=;õdè=CiÑ¢…£Í*ÄåË—™òâboÆ’¡ËÀGíCÛÆmùüýÏñõõu´yüù÷Ÿ<öÒc\êz C—|¯Ò‘+Gø­ßolþ~3¡Í,÷‚³Gá;oÌ᯷¢ ÉÁÓLx¾–T¾þn)ÝZwbØèÑ0”•d“ NÈL 13‘\]n¹íÌ € 7`²™Æw&õ€&#†³rÌÊr_Ï,:$%ÁÍ›%n÷ݼÉèÜ\&Ö] éé4ýýwóç(nѪ„†Z­³\ÚN„EiРO½õ–Óþ5ñÏÑ£$ººræüyZÓÉî×OÕj¹eÜ’4ñ¨Õr˸=)‰5舺Է† á±÷Þƒ9s,žS©PÐÔ$”"¹­—mÊ!”-±òã™ ,±™Dh) íE®¹]»2wÎvµoO=WWÚøûÓ¡n]BÜÜhì‹}ÿ$§OŸ&::š÷–¾Gdr$ɵ“ÁVÿ¶šw¾}‡»ZÜÅôIÓ ·ŸÄîíÛY¿n ¿ìü…ä&Éy•‹gOp óN&?0™Á÷ §ëí·;ÌÎécÆp`ûf<}rhs¹èQ è£שžÉ3o™÷îVd¡V¯%S“I–6‹,MVÞãËóiR&Ç-¿ö "™'_ú?¦\ô!13Ñjµ [qwq'È3ˆ ¯ «ßª¿åï.ä±TCžúPϸüíá—“gØvñ¬¬ââ71±dqœœ,’ølà1àk`0ßÓ“¹™™¼¼èâ"š˜ó&—£ó[i;åëä…wߥûm·Ñ¡}û2ÛQ™¤§¥s-3m›0û‡V†Ž(ÊP%$K¯Ï¼&ñ›dÆöMÙ´Ÿ¬ÕZCfÅŠÂåáÜÜÄ¡£GQtêDScŒrX¯r//<ìÜî\&(KlE&ZGa0ØøÍ )3ááᬸ~f͇‰ú¬f¾ô|\\hìæFˆ»{ž¨qwÏ«ïꊲKL¿ë.ºìß·R HÞwõäÜLÔz¸„ ýÿýï9pá}°wo†ÿýwžpJžõòâËŒŒ¼_ÿ‰S¦0ï‹/c$пk'ÞŽˆ¤—ñUзV-N¦§‹÷xO ÑScÖü×…¸- l >Z:fN[š¯Õ›wÝ*V£qðuºe;o÷‡ƒ 1%ÿ›½]½-Šà@ÏÀbcµ\kÙü~êõzÚ·kÊóçb™¤…C =<ˆÎÌ$˜â†îÝøjÖ\Û„±¹îzåA¥äÚµó¶ÑÿM×Ü\懄°"*Š}úðɶm`çÇÖÐét´2„3Ó¦ÑïçŸÙùÓOŽ6É,뻯#±VSðò";þ"n/¤ÑgèP‹‚×ÒX¶•Љ²â‘•EîG¡3­ï&rr[¶ŒÃ6àYŽZå‰#dgÜ’p®½¦Ð½;ê×^C?p º"ÕRµZŽkµÏÈ0ûR…‚†F1mNh‡¸¹áUŠÅºí þ\>¼¥Æ0ØGH{ðAn-_ÎF†»x|üýeû·VÙ!µùÍ ~4¾-Süü¸:r$ÿ­^Í‹FQ4·–‚;šM®.N#õšŸÛ2ÇÖçÇ•ç™én ;Ÿ $fäH–ýôÏef’ |éçÿû†Ïã˜70xÀŽ HL‡@àùÀ@.Ý?ËV¬à¹ÌLö+à\ ¨ëQ—a‡•è%vS¹Íg0ˆßÌÌü-! .]ƒÌ âyVVþ±‚ûEž+339â]—1ú+<Šž™uêöÒK¼ö曼•’‚&–ÿ}þZþ7ÅË«¶ióõ-V3yÂêÕL3‡ŒE‹˜>m¾úÊéÄ3ïsl¯^àçÇIwwŽ?^*/t®^O¶^OV‘ÇBc:]ñ1çeëõ¤ÅÇóhJ2ÁuÄ:™Û8„§—M^û t$¸(ø»¸à¯Vàâ‚¿‹ juáG Çg¾þ:ŸŽ[ü¤nn\ eÿ_•ª"‡D"©8ߪ^S:ï)±ô›ŸhuGob³³¹”MlNN¡Ç›šÂe±´1ÙÙÄ”„ä«RPGÁ.Uz•Z”ZüÈÁ<ÈA«Ó ÑkøqÏJ4u ¼ YÀžÐP4cÇòÃöí¼tîqÀ‘&z.|ó›t›Ðéuè :ô}Þ~ÑÇÊ8–v*zõàÚyP4mJîĉ|ù×_Ë¿›Ï¢0€ÂøÅ©0€ ã÷¨Ò ö òÆÆ15b¯)p¼À%O…ŒÖ0;”n~ÄNŸ@ìĉ|´p!*³¸»¸Ÿe$¨  4n*}þ¾Ò`û1T¨*\ J\J\ *T .(Q”¨*T(pA‰ %.·â’H Ìa¡¸Ö nÆ P(ˆ}ä}ø!» 9üzüs}¨·)•Þ ®«3 ÔéQé œÏΡ•J…R§G¡Õ¡ÐéD"›NÚlЦ‹ýrÐ W¦Ó‚ý¸b|?/M˜À³|@†Î)0œÛKòßû1(½Riy_¥ÂàíÞ××¶ùE÷]]Ñ{zbpwÇàæVh?ÄÃéþIê¤IÂÎéÓYûÉ'ÜûÚk\ööFçãƒÎÛ}­Zè¼¼Ðzƒ 3JÞ×ëÑÅÇÛ>¿„}Mn.¿ÿò ú¹sÈyè!†ÎK§wß-•Àµsk"%E<–6vÇ!@M^\__®×ªÅšC‡DåJF¥Pà®Tâ¡TztW*ñP©pW*QܼÉß¹¹àí· ¼xÐ ¾û.ŸO›Fí"ØÏÅ;wPœ½h±%•‡ssãBÓ¦ì(e]èÊB&JlE&ZG èrÁÚµk¹qã½zõbâĉ¨Ìy3E“„‚ ÏÈ>âÕCg¬\%ÉâRÉÕÀ@bƒƒ¹T§±uê~ &ͳp5ƒRMŽwCr¼B°Ûz¾ý5@Ó£ïtî,â¶ÝÜàÑ¢W.;Šâ@anÌ̾Âxý=¦`øê+ ¡¢ŠENïÞ¼¼cÊW_Ee¼Mnzµ¡À—©¹ý‚ÅPÑ_¼‰‰"±¨AagŸ>¼ºk¼ñ;¬ÝÎ_¸0?^¾²IL„c•˜œÞ½ùd×.˜=›·œ)ì *J$âÖñÓ†–-¹R§ƒÝÝ…ç877?ÎÙ‘lÚ÷Ü“ÿÜÓ“” س?„…Uüõ޳@Íxc-¢×Þ=P4‰õþûñúòKZ½ùf¾˜-(lK»[˜g‹Ð}øé§É;¶9 R‘ܵ+©{ö0dôhÛÿÝ•Àµ7èÛŠ™N«åðÉ“N! e¡ÄVd¡uœè²êñ믿rÿý÷s÷ÝwÓ¬Y3^zé%6nÜÈÚµkq) >bcc >}šÞ=ˆd·Vp`9…›J¯§Q|<âã¹ãÄ ³s’kÕ*&ª/Õ­›'º¯`X³Æ/üÂûîƒ_sñ}å ˜5íØú>¬_÷ŽÇ6 ŒnÛ6t#FTŒÁêÕ…ßO…B«mÛ`hÄëV«Wøq…Ç r>;×®…§Ÿ.<6v,¬YÆÿW‰R¡@…ð”Úº¯Ôh¸ôï¿hÞ|³ðÉÆŒÁgÉzÜq®J%j…µBQ!ûG._ÆE¡ O§N6¿ö»~`æ€dý?çëK­à`¾uuuxEŽk×®ñ÷õëy?@‹’Þ¿?óþ÷?ÆU¦ŠÅÚ/¿tص%’Ê&66ÖÑ&85R@—½^ÏÓO?ͨQ£X¹RÔŸ §gÏžlÚ´‰ÑE<#­4Ö$$ <мo×8WZu™wõ)~s_ ¶Ž÷3nÍÌMKOgÚkÓY[¯)ÙFïs]ºàúËOtRi>x$ cÉ¥<¬P”ìÙ5Þ¢·4/o®i^‘×=篛ÖrúZ,ºfÍ Ûyûí¨^~žî!õèÓ»¿°S¡_¬Æ­Ð¾é¸ñ±àñ‚_Æß-Óx¡13û …‚ë/òIj Ú† Ûy績ü¯LžŒ{ ÝâÖx{sÑc%zý:Ÿ)õê>pûíÍŸÏ‹O>‰‹Zc(Báý³G2»qcRj©ÞÑ¢õ6mâûfÍð÷÷/•Ø-i¿¬UnÞ]¶Œ×ï5ÓèÃË —Æy#3“;*¸Ä¢ÁÛ€»ýülš¯Óéøè§ŸÈ´p‡ãƈ<·`Ã+r¼°`—KúA¬RÓ©«×¯ç{¡%IÍD è2²{÷nbbbX°`AÞX= bùòåÅtŒ ÐÁ³>phRA·ÔŽô^òq…f“—oàÊÆõd¦_›nÛhF§sB<¯™e`Æ e®Y{mÍN<4Ѭ Õ;/wéR!×¶eî‘#G¨[·. ‹åq ¢}dDG‹F&/šBbÄH|wïæÁ#ˆ‰‰¡gÏžÅÎ{ÆË‹Yv¸u6þÝwI± >Rû÷Çeûvž{Ür;ïòü-m!&&†äädÞþôSR&N4;çÆˆüòÉ'|ºp¡Åó”ÅÎ3gDX•-ñ¢111$$$ðņ äΞmvNÒˆ<öÜsDo^j°˜ŠxJkgY®]žµÅĉ'pww§yóæÅŽ]¿~ÝâÚRU°×ÚÒ©SùšòØcm±ÅNkvØcm©‰Ø·Š{5âìYQF¡}‘/™°°°¼c‰wHàHpOr§ý¡ö¬ýj­Co?‚¸UzÑ ±Ï±±ùâÔˆ¡{wvœ§-”æýøxÞ¥úìFDD¢•·£×’’Æ"""ðõõ%44´Ô¯»t…:ÁuctJ}©ß?[ÇnÜ}íCBB*m]³eJKK#88˜   ³ó¢££III¡M›6Å^ëêêJRRaFï|Á×®[·ŽÑ£G;Õg£²þ–%]¿~ØØX:vìX®ó•æ;ÅDiþ/( rrròî4”õó·wï^BBBJ|­)Þ911___Ž=JÆ 9}ú4óH]FJ# 'NœÈ¡C‡¨WÄÛ'3\%‰D"‘8š¢ ƒW¯^¥G¬X±ÂA9?2ºŒÔ¯_ŸÜÜ\¢¢¢hÛ¶mÞøÉ“'©_¿~¡¹ò(‘H$‰DR}1Ðe¤eË–7Õa5rêÔ©¼c‰D"‘H$’ê‡Ðe¤OŸ>4iÒ„7æýóÏ?$$$î8Ã$‰D"‘H$•ŠŒ.E©¬Y³†;X#‰D"‘H$IõAz ËÁ°aÃØ¿?]»vE£ÑðÞ{ï±nÝ:›Åó˜1ch֬͛7'<<œ„„„J¶XRÕ‰‹‹Ãßߟ'žxÂѦHœ˜ž={Ò Aš4iB“&Mسg£M’81ôêÕ‹úõëÓºuk’““m’Ä IMMÍ[Sš4iB```¹kiWe¤ÚÄÆÆ‚F£áÙgŸÅ××—ùóç;Ú,‰3räH|||ðôôä³Ï>s´9'¥gÏž,_¾¼Ô A$5ÄÄD:tèÀ’%K5jiiixyy¡.XÞR"1äI“hÙ²%/¿ü²£MqÒí@LeìÔj5 6$##ÃÁIœ™ï¿ÿž–-[Ò¹sgG›"©lÙ²…Ý»w£×ëmŠÄ‰ùúë¯éÞ½;]»våÒ¥KøùùIñ,±Jzz:ëÖ­c¢….±5) ÌsÏ=G§Nؾ};o¾ù¦£Í‘8)7nÜ`éÒ¥ò3"±‰^½z‘””ÄÇL«V­d3‰EΜ9Ãùóç¹ÿþû™4i÷ÜsO‰]%€U«VÑ»woêÖ­ëhS†jîܹsmDU$&&†C‡qóæM4h`vNFF»víbçΨTªbT:vìH=øçŸ¸rå }úô©dË%öÄ`0pîÜ9¶oßξ}ÿßÞGEuÝqÿþ9 Ȱ Xö­ Q49D£¬Ç¡%­¸$RKMQbkMhcB†¤©¦1I•œ5X ‰FQÁF£‚"˜€5™Ê& ¢°È̯ä̫ψ¤0@ü}ÎáèÜwç¾ßÅ{îûùæÎ}ŸÃÃÃFë:t»víBee%d2™ð2X±bþð‡?`úôé(--ÅÕ«Wgªn0éèè@II Ž;†¦¦&øùù­WWW‡={öà£>B{{;üüü •J…ãóæÍCLL âããÑÙÙ‰“'OâñÇ7U7˜‰h4œ={J¥r¹666FëÕÖÖâã?FCC …¨ÞÉ“'ÑÜÜŒ3gÎ`ùò娷od2Ù€cOÃ5·è¥¤¤`íÚµ÷ö21bCrìØ1rqq!€¢££Ö«¯¯'rrr¢ûR)ýæ7¿!Ng´þ_|A#9 €ÌÌÌ}úé§ut:­\¹’ììì(>>žfÍšE666TPP ÔyðÁÉÏÏüüüÈÕÕ•d2¥¤¤˜°'l¤íß¿Ÿ$‰0^š[ÊËËÉÅÅ…ÂÂÂèÉ'Ÿ$¹\N?þ8uww­ÿÁЂ F0r6bccI*• ×"cs QVV™™™Qhh() rww§ÊÊJáøG}D+W®^oÚ´‰6oÞ<Òá3î¹E©T’››ݺuËÑ]œ@ѹsçhÛ¶mtòäIŠŽŽp ÆÅÅQ@@©Õj"":räI$ÊÏÏ'"¢ŽŽ*//'­VK---´jÕ*zî¹çLÕ f"~ø!•——ÓgŸ}6àE®¨¨ˆÐáÇ…²„„òöö&­VkPÿ/ù %''dØl\¸p:DÍÍÍ4oÞ¼ç–|fÏž-\¼*++ÉÜÜœ²³³‰èÛ¹åƒ> ††***¢ððpÚ·oŸ©ºÁLdÛ¶m”——G¹¹¹Î-gÏž%´cÇ""êë룇~˜‚ƒƒ…:mmm4iÒ$º¸ÇXådIDATtéµ¶¶Rpp0UWW›ªÌ†knÑ[¿~=mذa¤Ãó8þ? 4ÈÌÌŒ222DåÓ¦M£Ç{ŒˆˆÚÛÛiîܹäëëKQQQô /P{{»)Âf£ ¤¤dÀ‹\bb"ÙÙÙ‰>ÈÏÏ'tìØ1ƒú‡6˜ÐØË@sË… eee‰ÊƒƒƒiÖ¬YDDÔÙÙI«V­¢Y³fQRR8pàž¿SôC6ØÜ²zõj²²²¢þþ~¡ìý÷ß'T\\,*‹ˆˆ ˜˜Úµk—)Âf£äÿ™[ôÖ®]K/^É0Ç~ÚÇøòË/¡Óé(* Á©S§r¹ÇŽðØSSSƒÀÀ@H$¡,$$D86wî\Q}^Ëz節­‰ÊCBBpäÈ€²³³M{*** Ö°êç–ŠŠ DEE‘˜˜8*1²±ánæ½7ÞxÃdqe¼ Çhjj`| ^½z•¿áÌDššš þ³5uêTØØØc‰1`ð¹åúõëèîî°ØÕÔÔd0VüýýaaaÁs Ñc7þxn1Žè Ñh2™LT.—ËAD<™ˆV«…¥¥¥¨L"‘ÀÜÜZ­v”¢bc‘~<ܹO¯~üðxa·Óh4×! ØÛÛ ×)Æ€ÿÍw^‹xn'Ð#@¿]þ#=¥R L˜0a4Âbc”‡‡”J¥¨¬®®7oÞ„‡‡Ç(EÅÆ"ýx¸s¼TUUÁÁÁööö££ÜÝÝ ®CÍÍ͸~ýºÑmUÙ½‹ç–¡ãzøøø0>õÇÓ›:u*¾úê+QYUU•pŒ1=ýx06^x¬°;ùøø½é1¦§Ÿ?Œž[ŒãzDFFbòäÉ())Ê:::PUU…„„„QŒŒEË–-Ã7PVV&”`âĉ˜?þ(FÆÆšÐÐP„……‰¾€\__¥R‰eË–bdl,JHHÀ•+W R©„²Ó§OÃÞÞ ,ÅÈØX£Ÿ[ …2ž['!"í Æ“k×®áÅ_8pýýýxâ‰'›6m‚««+ ''+V¬ÀºuëwÞy—.]™3gø£³{ÈŸÿügB­V£´´‘‘‘Ëåxøá‡±qãF@?âââpþüy$%%¡±±yyyx÷Ýw‘””4Ê=`¦ÒÚÚ*ü{———C«ÕâdggÃËË PXXˆE‹!&&áááØ»w/d2>ýôS8::ŽZüÌ´rssQVV†––äååaÉ’%ðòòÂüc!áéîîFdd$ -- x饗žžŽôôôÑ Ÿ™Ï-#ƒ·±"­V‹––ÀÌ™3@xÝßß/ÔKJJ‚þþ÷¿ãøñ㈈ˆÀûï¿ÏÉó=ÆÛÛááá€èèh¡üöOÍÍÍqèÐ!¼õÖ[8}ú4\\\pôèQ<òÈ#&—KKKa¬èÿÔ³²²þ>wî\|öÙgسg”J%žzê)¤¤¤ðî£V«…kþ“Í––ܸqC¨cccƒS§NáùçŸÇ›o¾ gggüõ¯Errò¨ÄÌFÏ-#ƒï@3ÆcŒ16¼š1ÆcŒ±!àš1ÆcŒ±!àš1ÆcŒ±!àš1ÆcŒ±!àš1ÆcŒ±!àš1ÆcŒ±!àš1öƒwöìYÑÓØFÚÕ«WqâÄ ÑÞðCuþüyüûßÿƨwáÂ455 ¯‹‹‹ÑØØh²ówÆÌc¦Â 4cl\khhÀêÕ«…§<bÙ²eøòË/…:O=õvìØa²˜NŸ>9sæ ³³ó{·‘ššŠ×_}£˜Z­ÆŒ3DÉèOúSäççÿßmWUU K;Æ=zK–,‘¶cl0œ@3ÆÆ­?þAAA())Á“O>‰Ý»w#%%jµÚà‰[ãÍêÕ«±xñb“œëÍ7ßDpp°ðxßáôÉ'ŸàÙgŸöv`åÊ•¨­­ÅñãÇG¤}Æ?Ê›16.©Õj¬^½³gÏÆD¤MIIÁ¡C‡Œ¾¯­­ MMM½§¯¯ÝÝݘ0a‚¨~GG¬¬¬„º7oÞ„……¬­­lk°˜ÍÍÍagg'”555áòåËðöö†B¡Êò“Ÿ@*•t::::Œ¶éàà Ô€žžÔÔÔ@¡PÀÃÃã;cêêêÂÖ­[ñÎ;ï X§¡¡===˜6mšPÖßßÎÎN8::ÂÌL|/¦³³fffJ¥èééN§1mii [[[¡î­[·PSSGGGøúú=¿Z­Fmm-\]]1yòd¡ÜÞÞO?ý4222ðÈ#|g_clØcŒC[¶l!ôÕW_}gݰ°0JMM¥Å‹“T*%äááAUUUB·ß~›&L˜`ð^ÊÌ̵µnÝ:ŠÚrww§ŠŠ ¡Îþýû ]¿~ˆˆt:¥¦¦’³³3ÑÑ£G) €ÌÍÍÉÅŅК5k„6¢££)99™ˆˆjjj€ÑŸ’’""êéé¡õëדT*% @=ôÕ××ú»ÉËË#©TJ}}}¢r…BAééé4sæL²²²"F*•Šˆˆ._¾Læææ”››+zŸF£!™LFYYY”™™ioBBiµZÚ²e Y[[ ñ‹þMT*EEE‘¹¹9¹ºº’™™EDDˆÎW\\L¨®®nÐ~2ÆØpâ%Œ±q©ººnnn¸«úo½õQ__sçÎÁÉÉ ÙÙÙßëÜÛ·o‡.]º„óçÏC.—¸Æº··?ûÙÏpðàA#** žžŽyóæ¡££mmmhooÇ/~ñ £møùù¡»»[øÑh4X¸p!~ô£ÁÏÏðüóÏ#''¹¹¹¸qãJKKÑÓÓƒU«V Ú—êêjøøøÀÂÂÂàXff&{ì1´´´à_ÿúnݺ…ÄÄD€»»;-ZdÐï}ûöA£Ñ`ùò娏q#233áãã"áÿøàí·ßFzz:¶oߎ+W®@©TÂËË ñññ—/_}õUXYYáÊ•+hmmEWW²²²Dç›:uªÐÆ3N cã’J¥‚··÷]×÷ôôDFF<<<'žx‡þ^çvssÃ+¯¼///„……áç?ÿ9Ž=jPïÆ˜7oT*JJJ0}útß.[¨­­ÅÔ©SaccËå˜5k–ÑóI$X[[ ?úÓŸpêÔ)|òÉ'prrÂÍ›7ñÆo 99ñññ°µµÅŒ3°qãF¢®®nÀ¾TWW Iè&OžŒÍ›7C&“aΜ9HKKCii)Î;X³f Nœ8!Ú-dÇŽX²d &Nœ8èïðå—_ÆÒ¥K±råJÈd2âå—_FMM >ÿüs@ee%¼½½…e5ÖÖÖxôÑGEíLœ8ŽŽŽœ@3ÆLŠhÆØ¸äââ‚k×®ÝuýØØXÑëÐÐP\¼xqÀµÅƒY¸p!$‰ð:""*•Ê ­G}Z­'Nœ€«««Pnaaµk×â™gžApp06oÞ|×Û±íܹ[·nŇ~À×_¾¾>|ñňÇÒ¥K±dÉáû`ÛáÕ××ÃÍÍÍè±;“Uýëšš@LL ¦OŸŽ;w”J%Š‹‹‘œœa¬Zµ »wïÆÁƒ…;¼½½½Ø·oß ñ…††¢±±===Ǫ««ñÞ{ï¡¿¿/^ÄöíÛˆ3fuär9ðÌ3Ï »»III¢6zè!´´´ ¬¬LTž––†þóŸÈÍÍ>EÐét(((¶¼ËÏÏ%ßjµÀÿî`ÀÅ‹!!!ƒö“1ƆÕ(ïÂcß›V«¥W_}•¬­­I"‘——™››“½½=¥¥¥ õÂÂÂè÷¿ÿ½è½wn5GD”––FÈÙÙ™ÌÌÌè…^0ºÝmˆÚ2Öö+¯¼B–––´ÿ~úæ›oÈÒÒ’\\\èþûï'rww§={öõo߯nïÞ½B\ …BôS^^NDDjµš Éd2ò÷÷'rwwôwØÛÛKžžž”““#*W(ôÛßþ–|||ÈÓÓ“¤R)yzzÒÙ³g Ú(--%´lÙ2ƒc:ŽH&“‘¥¥%ýò—¿$"¢¾¾>Z·nI¥R²µµ¥   rpp ¹\NÍÍÍDDäììLöööN>>>äèèhð»_³f ÅÅÅ ÚGÆn¢Ûn0ÆØ8¤ÑhPSS•J///„„„ˆÖ)_ºt vvv¢!:;;ÑØØˆéÓ§‹RQQÿüç?¸ÿþûáíío¾ùNNNËå¶ÕÕÕ…††¡­ÚV©T "L™2]]]8sæ Z[[‚ÀÀ@QŸêëëaee…B›7oø%C___ÑZe•J¥R‰[·naòäÉÿλè[·nENNŽh­ö×_ gggØÚÚ¢¬¬ ===ˆŠŠ2Xÿ |»Ô%<<\´Mß´Z-T*ìììDx¹|ù2ªªªÐÑÑoooÜwß}–þþ~”——£¾¾^^^ˆŒŒ-o¹ví&Mš„ãÇcæÌ™ƒö‘1Ɔ'ÐŒ1vÓh4ðõõE^^æÌ™3¤÷jµZ,Z´ýýý8räÈEh\ff&ŠŠŠøQÞŒ1“ãš1ÆÚÚÚ`aa™Lv×ïÉÌÌÄk¯½­V‹ââbƒ»è#­µµ666ptt4éycŒhÆcßKmm-ÚÚÚ0sæL£O2dŒ±*N cŒ1ÆÞÆŽ1ÆcŒ±!àš1ÆcŒ±!àš1ÆcŒ±!àš1ÆcŒ±!àš1ÆcŒ±!àš1ÆcŒ±!àš1ÆcŒ±!àš1ÆcŒ±!àš1ÆcŒ±!ø/îNå±o‰IEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/random-chunksize-15GB.svg000066400000000000000000003522001416254111300253730ustar00rootroot00000000000000 image/svg+xml Automaticchunksize PyTables-3.7.0/doc/source/usersguide/images/read-medium-psyco-nopsyco-comparison.png000066400000000000000000001775051416254111300306440ustar00rootroot00000000000000‰PNG  IHDRÜÐbåxÒsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î<tEXtTitleSVG drawingLÓ§ IDATxœìÝwxÕþÇñ÷„4JC ½+"(MÊ.‚Eš r/*J•+*‚E,ˆŠŠŠ(ø³aTš"$ ” (QDé=”’óûãìnv“MH ›MàózžyvvæÌÌ™ÍòsÎ÷8ÆD2ˆÖ»"""""…H`m°+!KH°+ """"""r.RÀ-"""""" ¸EDDDDDD@·ˆˆˆˆˆˆH(à Ü""""""" €[DDDDDD$p‹ˆˆˆˆˆˆ€n‘PÀ-"""""" ¸EDDDDDD@·ˆˆˆˆˆˆH(à Ü""""""" €[DDDDDD$p‹ˆˆˆˆˆˆ€n‘PÀ-"""""" ¸EDDDDDD@·ˆˆˆˆˆˆH(à Ü""""""" €[DDDDDD$p‹ˆˆˆˆˆˆ€n‘PÀ-"""""" ¸EDDDDDD@·ˆˆˆˆˆˆH(à Ü""""""" €[DDDDDD$p‹ˆˆˆˆˆˆ€n‘PÀ-"""""" ¸EDDDDDD@·ˆˆˆˆˆˆH(à Ü""""""" €[DDDDDD$p‹ˆˆˆˆˆˆ€n‘PÀ-"""""" ¸EDDDDDD@·ˆˆˆˆˆˆH(à Ü""""""" €[DDDDDD$p‹ˆˆˆˆˆˆ€n‘PÀ-"""""" ¸EDDDDDD@·ˆˆˆˆˆˆH(à Ü""""""" €[DDDDDD$p‹ˆä³M›61gÎ&OžÌÂ… Ù»wïŸkÅŠ¬^½:k—µß~ûùóçcŒÉ—ë‰ 6°hÑ¢lË|÷Ýw¬]»6ŸjtöŽ?Îüùóù믿8zô(óçÏçŸþ rÍ Ÿ;w2þ|Ž9rVç‰'...j•s³fÍâ§Ÿ~ò¼?|ø0;wîÌ´>|8Ó±ÉÉÉüøã,Y²$GÿæìÞ½›ùóçsèС<½‡¼¶jÕ*Þ}÷Ý`WCD$kÆ-Z2.±FDòÜÑ£GÍM7ÝdŸ%44ÔLž<ùŒÎy饗šN:åqMIMM5/¼ð‚yýõ×=Û|ðA˜”””<¿^^4h)W®œçýîݻ̀ÌŽ;<ÛjÕªezõêŒê‘ÄÄD˜—_~ÙcÌÆ `Þzë­ ×¬ð™;w®ÌO?ýtVç¹âŠ+Ìå—_žGµÊ™ï¿ÿÞDGG›x¶uëÖ-Ó¿'€¹õÖ[}Ž}÷ÝwM‰%Œã8ÆqS´hQ3mÚ´l¯÷É'ŸÀ¬X±â¬ëþ矚=zœõyüÙ·oŸ‰ŽŽ6ñññ9¿H.Åšàÿ¯¥€-¡ÁòEDÎG·ß~;sæÌáÑG¥[·nÔ¯_ŸåË—óúë¯3jÔ(FŽäZZ{÷îåî»ïfòäÉžmW^y%EŠ!$¤àvŽêÙ³'õë×÷¼÷Ýw™5k“&M b­òV¹rå7n—\rI°«rÞ4hÆä_OS§N1xð`FMéÒ¥=Û׬YC—.]èÓ§Où:uêxÖ/^Lß¾}éß¿?“&MâСCŒ3†»ï¾›¦M›rÙe—¼þÓ§OçÓO? ȹ£££=z4ƒ bíÚµ)R$ ×9S ¸EDòÁÉ“'™7oÝ»wçÁôlïܹ3­Zµâ믿fîܹYÜÆÇÉÕ5szLNËuèÐ:œÕyÎä>r£G¹>æLë”ÕqþÊ–-Ë#}ú°k×®Lå¦NJ›6mˆŒŒ¤J•*Œ;–“'O²aî¼òJžyæZ´hÀK/½Dll,©©©Œ?žAƒ±téR.¿ürŠ+F:uxòÉ'IKKó¹Ö«¯¾J»ví(V¬Mš4á›o¾¡{÷î<÷Üs~ïaÍš5ÄÆÆ²`Á϶””Z´h‘©5Ï}iiiŒ?Þ§îO?ý4:uâá‡öcŒá¥—^¢~ýú+VŒÖ­[û\ËŸU«VKBB7ÜpeÊ”¡zõêžóN›6† I»víøý÷ß}Žß²e Ý»w§L™2”.]šŽ;úKþ /кukŠ+FÛ¶mÙ°aƒÏþÄÄDbccùüóÏ;&=666Ó¹^{í5bcc9~ü¸§~=zô ..ŽvíÚQ¼xqš4iÂG}ÄÉ“':t(111”,Y’zŽógß¾}ž:tèÐèèhÏçššÊc=Fýúõ‰ˆˆ Aƒ~¿{öìáŽ;î ^½zDFFÒ´iSæÍ›çSfÑ¢E´mÛ–¨¨(¢££¹êª«X·nO™&MšðÞ{ïqíµ×RªT)OPšœœÌý÷ßOÆ )Uª½zõbß¾}YÞ“[JJ ÷Þ{/UªT!,,Œ:uê0lØ0Ž=ê)3hÐ OÐýõ×_›åâ=zÚ´i4jÔˆˆˆj׮͘1c8yòäië4yòdºuëF©R¥<ÛÖ¬Y@Ó¦MÛ žÑîÝ»Y°`ýû÷'22Ò³½bÅŠlÙ²…áÇŸöÚüñ;w¦D‰Ô®]›GyÄóoÀòåˉå‹/¾ÈtÜM7ÝÄСC3f o¼ñ±±±<ÿüóž2Ÿ|ò Í›7§X±bT©R…Ûn»ƒúœgΜ94nܘˆˆ*V¬H÷îÝùõ×_}Ê”,Y’Î;ûôÈ)0‚ݧ]K\4†[$n¿ýv˜ºuëšGyÄüòË/Y–Ý»w¯©X±¢©P¡‚¹ÿþû͇~húôécÇ1|ð§\Æ1ÜsæÌ1Žã˜-Z˜W^yÅÌœ9ÓÔ«WÏÄÄĘýû÷{Ê7Î8ŽcºtébfÏžmFŽiÇ1wÞy§Ù³gyî¹ç `úôécÞ~ûmcLæ1ÜîñÒeË–543fÌ0W^y¥Ìk¯½æ¹Ö[o½eÓªU+óæ›ošž={š°°0aî»ï>¿÷òäISºti3dÈ϶%K–xƼ>|س½I“&æ†nð©“1ÆÄÇÇ›¾}ûÀL:Õ|÷ÝwÆ;†;""ÂÔ¨QÃ<óÌ3fòäɦV­Z&22Òüúë¯YþL-Zdcz÷îm^}õUÓ©S'˜®]»šZµj™‰'šñãÇ›ÐÐPÓ¾}{ϱ7n4Å‹75kÖ4?þ¸™;w®éÔ©“ ÷{úòË/À´mÛÖ¼ù曦cÇŽ&"""Û1ÜŸþ¹<÷çöØcÀ9rÄcÌØ±cMTT”‰ŽŽ6÷ÝwŸyî¹çL½zõLñâÅMÛ¶mM›6mÌK/½dú÷ïoóè£fùYìܹӦjÕª¦qãÆ¦W¯^æÅ_4ÆsÝu×™ˆˆÓ§OóÁ˜ñãÇ›ˆˆŸqÅGŽ1Íš53%J”0wÝu—yçwL‡L‘"EÌâÅ‹=Ÿ·ã8æÂ /4“&M2&L0åÊ•3aaafùòåžs¹ëQ·n]sÓM7™GyÄó] 1}úô13fÌ0 64ááá§Ã}Ï=÷˜°°03nÜ8óñÇ›qãÆ™°°0sÝu×yÊxáÞ¼y³™2eŠgyî¹çÌC=dÇ1­Zµòü¾Ü}÷ݦH‘"¦gÏžfΜ9æ™gž1%K–4]ºtɲ.Ƥáwÿº=ù䓞ŸSóæÍM‘"ELƒ ÌG}ä)oóñÇ›×^{Í\uÕU¦iÓ¦fذa>cÁýqá 3—]v™™9s¦gÌø€Œ1Æ$''›²eËfŸ½e˘7ÞxÃ,]ºÔüûßÿ6€™9s¦Y½zµ1Ƙ_|Ѧ}ûöfæÌ™æ•W^1^x¡©W¯ž9~ü¸1ÆþÎ;Žcz÷îmæÌ™c¦M›fêÔ©cJ–,i’’’|®ùꫯÀüý÷ßÙÞ—H€i ·–LKÐ+ ¥@. ¸E --Í<øàƒ¦téÒžäFµk×6Ç7 >eï¾ûn˜•+WúlïСƒ©R¥ŠINN6ÆøÜÇ7•*U2uêÔ1iiižc¶oßnBBB̈#Œ1ÆlذÁžÀÄ­OŸ>¦B… æØ±cf×®]ðIææ/àÌ'Ÿ|â)sìØ1S²dIsíµ×cŒIJJ2‘‘‘ž?ÐݦNj€,nw}jÔ¨áy?vìXS½zu˜Ï?ÿÜcÌ?ÿücÇ1ï¾û®§NÞIÓÜ×ɘ4­xñâfëÖ­žmîàbæÌ™YÖÇp÷ìÙÓ³íÀ&$$Ä>ç4h 7©©©ÆcºvíjÂÂÂ|ꑚšjªW¯nZ·nmŒ1æÐ¡C&<<ÜÜvÛm>×=ztžÜ€yþùç=e>úè#ø|ÎÆS½zuó¯ý+ËÏÂp×®]Ûœ8q³}áÂ…0cÇŽõ)?yòdŸïóC=dBBBÌÏ?ÿìóY–)SÆ <ØcL:uLÆ ÍÉ“'=e’’’L•*UL³fÍ<ÛS®\9ŸrÙ²e>Ÿ™1öónß¾ýiî‹/¾8Ó½?ýôÓfèСžŸgvIÓ:d5jdjÔ¨avïÞmŒ1fýúõ&$$Ä 8Ч¬;‰›wœÑo¼aŸ‡ ÆÓ»wo˜6mÚ˜_|ÑŒ=Ú”/_ÞföìÙÆ˜ôŸo·nÝLxx¸éß¿¿iÛ¶­çg—1hõæþÈø»ûÀÇqÌÆ1Æ 6Ì„……™½{÷zÊ<ôÐC¦X±bžccÆŒ1¶ÇÚ·oŸ)Uª”iÕª•Ϲ׮]kóÄOcìw644Ô§žK—.5ýû÷ÏôÀÒý@Î}ï"A¢€[K¦E]ÊEDò‰ã8<ú裞nžC‡ÅÃsÏ=G‹-˜5k–§ì7ß|Cݺu)Uª¿þú«giÕªÛ·oÏÔ]à—_~áŸþ¡GlÚ´ÉsÌ‘#G¸è¢‹Xºt)€gZ¡Œã>gÍšÅÎ;)Z´hŽï)$$„®]»zÞ-Z”ZµjqàÀÖ®]ˉ'4hÏqƒ>í¹{ôèAbb"[¶ll×Ýo¼‘5j°xñb¾üòKBCCéÖ­[Žë аaCjÔ¨áyïyóæÓû¯ý˳^ºtiªW¯ÎÅ_ìs¾jÕªqòäI’’’HKKcÑ¢E´k׎ƒz~.›7oæòË/gåÊ•$''³nÝ:Nž<É€|®÷ßÿþ7W÷–›ú7iÒÈ<ö½jÕª9ê~Ý®];"""<ïÝC:tèàó½mÔ¨€ç;øã?Ò¢E 6lè9¶téÒìÚµ‹3f°g϶lÙÂàÁƒ ó”)^¼8 `õêÕ>S{µhÑÂ'™X||<€Ïg’£ï]óæÍY¸p!}ûöåã?æèÑ£Œ=š_|ñ´ SRRèÕ«Û¶mã‹/¾ \¹r€M\–––F—.]|>—ÚµkÊ’%K²<çü@åÊ•}¶ßtÓML™2…E‹1tèPž~úi~ÿýwŠ/Έ#0Æx~—-[Fbb"³fÍbéÒ¥¼þúëüöÛo9J&xÛm·ù¼0`ƾûî;ÀŽgOIIaΜ9€mÈyçw¸þú뉊Šò{ÎU«VqèÐ!O÷p÷AåÊ•=ß“fÍšqêÔ)Ú·oÏôéÓÙ¾};mÛ¶eÖ¬Y>ÉÁ~gÁ¹)H”4MD$Ÿ………Ñ¥Kºtéع´È AƒèÑ£¥J•"11‘'NРA¿çغuk¦}î?4Ÿ}öYž}öÙLǸÇnذÇq¨R¥ŠÏþ3ÉîMxx¸Ï¶¢E‹zÆx®_¿°cF½/^œ .¸ ÛswíÚ•°°0.\HÙ²eY½z5=ö{÷îõ̵ýÅ_СCŸ±­9qá…fª3Ø€étªU«æó>44Ô'+4à$þóÏ?$''óÍ7ßdùóüã?HHH2V­³åLÀÖȶþÙ©^½ºÏ{÷w°S§N~Ëoݺ°ßAɺÜßA÷C–ŒŸµ÷¶ÄÄD7nì·Ü† (Y²$ÅŠóÙžñ;ïÏ /¼Àûï¿Ï{ï½GÑ¢Eéܹ3&L8mfø!C†°xñb¾úê+ŸŸµû¾3æȸߟýû÷ã³ýšk®ÉT¶D‰\{íµ¼óÎ;üù矞czõêE¥J•<åúõëǨQ£X¶lY¶÷™WÜçt?pUçw:t(ßÿ=‰‰‰Ì˜1#Ësº¿'cÇŽeìØ±™ö»n×\s /¼ðO=õwÞy'C‡¥Y³fÜ}÷Ý™L¹¶îÏKD¤ PÀ-"’Þ|óM&L˜ÀâÅ‹3­Zµâ±Çã†nà»ï¾£{÷î”,Y’nݺ1mÚ4¿çó¬–,Y€Ù³g{Zl½¹3üFEEaŒáÈ‘#>­‚iii8Ž“«Lħ+ dþ#8%%Å'™”?¥J•¢}ûö,X°€Š+ÆW\Áž={xóÍ7Ù±cß|óÍMùu6S›¹ƒÔœr? ¸çž{=z´ß2eË–å—_~È”4êĉ9ºNƤYîÖÍŒrLçDÆÏ¢dÉ’DEEeÙSÀý`#**ÊïÏÿÔ©S„††z¾—? °÷å8ŽO°Ÿñž*UªDRR)))>û²KçV¼xqÞxã ¦M›ÆÂ… yÿý÷™7o_ý5[¶lÉòÈ„ ˜9s&3fÌÈôÀ¡T©R8ŽÃæÍ›)Q¢D¦c½{ dän%NNNöy¸åîP¦LŸò]t`ŸÝAhÆÔ©S'GŸÇ¶mÛ¨Y³¦ç½û{åÀ8áÇóûï¿óÎ;ïPµjÕlg4pÿ[µ`ÁÏCoÞÿîºë.†ʪU«øàƒ˜;w.7ß|3©©©>½tÜÉç²jU u)É—\r ‰‰‰¼üòË~÷ïÞ½€ºuëP¿~}V¯^M™2e¨X±¢gùöÛoyì±Çü¶â¸»X®Y³Æç˜òåË3qâDO—õzõêçsüÔ©S)Z´(¿üò‹' 5æìæ¾ôÒK É”]ýÛo¿Í”µÚŸ=z°dÉ.\HëÖ­)Z´(;v$--‡~˜¤¤¤l§sÿá~¶÷q6¢¢¢¨T©ñññ>?—Š+òî»ï2iÒ$RSSiÖ¬`3?{ûñdz=ñâÅÌö¦M›òð.r¦~ýú9r„¿ÿþÛç>:Ä<àÎP¯^=Ö®]Krr²Ïñ­[·¦eË–Ô¬Y“°°0¿-°ß}÷µjÕòmþ\z饤¥¥eúŽŸî³LNNfäÈ‘¼ùæ›-Z”ž={2{ölf̘ÁñãÇùþûïý÷öÛo3nÜ8î½÷^¿ÝÖëׯ1†7ú|.Žãð¿ÿýÏÓ…Úw‹òÞ½{=ÛRSS©S§Žß vùòåž¡uêÔ¡|ùò¬X±Â§ÌöíÛùñǹâŠ+²ý<€LÙïÝ]É/¾øb϶¾}ûÎG}ħŸ~Ê€|jeü÷ÄýoÕºuë|> .¸€qãÆñÑGðÊ+¯0bÄÇ¡E‹Lš4‰Ÿ~ú‰ÈÈH.\èS/÷ {ˆˆ›n‘|ФIÚ¶mË“O>É!CX¶l{öìaãÆLž<™ûî»V­Zyî1cưmÛ6ìé^»råJ† Â×_M… 2]£J•*ôë×_|‘W^y…¤¤$’““™0aÏ?ÿ¼gÌkÏž=©Y³&£GötÍNHH`Ê”)Ô©S‡ Mhh( ìܹóŒï»víÚôíÛ—§žzŠ1cưyóf¦L™’iLwVzôèÁ‘#G˜5k–§Õ°B… 4jÔˆ7Þxƒ-Zø´´eäCÚõ@3f ß}÷cÆŒaÇŽ¤¥¥ññÇ3zôh6nÜHDDUªT¡GLœ8‘Ù³g“––ƲeË5jT¶ç®_¿>¡¡¡L™2…ýû÷“’’Âo¼Á—_~™Ow—î¶Ûn£\¹rüç?ÿáÛo¿Åî]»¸å–[xûí·)[¶,#GŽd×®]Ü~ûíüù矤¤¤ðÚk¯±jÕ*®¾új"##6l³fÍb„ ìß¿ŸÝ»w3vìX¾üòËL݉3êÔ©uëÖåŽ;î`É’%¤¥¥1wî\¦L™’íq,[¶Œ‘#Gzºø=zÔ3ƺeË–™ŽùöÛo>Þ3%[Ÿ>}Œ1ö»çΘ~Á˜ÈÈHbîºë.Ï, ÇŽ3:tðü{æþžôîÝÛ;vÌç#FŒ0111>34ˆ²”kÉ´8Ư›X±Àš`WBä\µÿ~ظq#1114oÞ<Ëq¡ýõqqqìܹ“ºuëÒ±cGŸñ?þø#aaa™ÆA®ZµŠõë×ã8-[¶ôŒëôvôèQ¾ÿþ{¶lÙB•*UèÚµ«ÏXÒ'N°bÅ :D§NØ»w/üñíÚµÃq6mÚľ}ûhݺµÏy׬Yƒã8ÄÆÆúl?|ø0¿ýö 4àðáÃT¬X‘?üë®».ÛÏkÓ¦MìØ±ƒ6mÚxî}ÇŽlÚ´‰K.¹Äg<»¿:íØ±ƒ¸¸8Ê”)C›6mˆ‹‹#**Ê'C¶1†¥K—RµjUŸñªÞ<ÈÚµk3]3>>žâÅ‹û|ÆÛ¶mcëÖ­>u›}õêÕ:tˆFeÙ¥7!!+VpÉ%—мys–-[F½zõˆ‰‰áرcÄÇÇÓ AŸž ¬\¹’J•*Ѷm[’““Ù°aƒ§‰‰‰üùçŸ>-“ÉÉɬX±‚ºuëúôX·n©©©4mÚÔoýRRRX¾|95kÖôIÂæ–””Ä?üÀæÍ›©X±"W^y¥g<¿·mÛ¶±bÅ öïßOÓ¦Mý¶ ¯[·ŽŸ~ú‰%JСCO+¹ÛÒ¥K©\¹2µk×Îtljj*Ë—/gË–-´iÓ† *°fÍš5k–m«ò‰'X°`Û¶m£råÊ´hÑÂ'áÚÚµk1ÆÐ¤I~þùgOwf6lèiáMNNæ‡~`ãÆ”)S†víÚå¨ ô°aÃøì³Ïøý÷ß3åXµjkÖ¬¡xñâ´hÑ"S<·„„V­ZÅ\@ûöíO›´pß¾}$$$кuk6nÜH\\µjÕ¢M›6~Çœÿý÷ßT©R…×^{-Sfs°?ëU«VQ­Z5O+tZZ+W®$!!¢E‹Òºuk¿?ÇåË—³aÃÂÃÃiÚ´i¦1éiiiT¯^NøXW IDATÞ½{óôÓOg{_"ÖX{ÚRr^QÀ-þ(à‘|¸gûÔ©S9r$ëׯ÷L%"þýõ×_Ô®]›O?ýÔgZ·‚d„ L:•íÛ·gÊh ,àšk®aëÖ­Ã-Á¦€[2Q–r ˜Ò¥KÓ¸qc|ðA~þùgš4iB\\sçÎåÖ[oõÛò."¾.¼ðB†Îã?^àîÇœŸþ™ÿû¿ÿcܸqùl<öØcÜsÏ= ¶E¤@RÒ4 ¨ÿû¿ÿcøðáÄÅÅñÄOpàÀžzê)^ýõ³šžKä|2aÂŽ=ÊâÅ‹ƒ]$>>žQ£FùS;Ð.\HJJ ?üp¾_[D$'Ô¥\üQ—r‘ÜQ—rÉDM """"""" €[DDDDDD$p‹ˆˆˆˆˆˆ€n‘PÀ-"""""" ¸EDDDDDD@·ˆˆˆˆˆˆH(à ÜùÈqœ¢ŽãDœ¦LxÎã8Žšƒr¡Žã8¹©£ˆˆˆˆˆˆä ÜùÄqœª@"p‡Ÿ}åÇyÕqœ=@²ã8»Ç™á8Nù åJ9Žóp Hrç{ÇqZû9_#Çq~NûÇùÄqœŠ¹1ñKw>p'ø¨àg_ðpðpðp#ðe†–ìg€k—€žØÀûkÇq.õ:_80¨ †—ß;ŽS"ÏoNDDDDDDü:m·d9;Žã žÍ¦H{à `œ1f‚kÛÇqãNÀ|ÇqÚƒ~Ƙw]ç^Œm5¿èí:ö~ PͳÝUîW`=Ðx9ïîNDDDDDD²¢îr§1ð:°è˜U1`¶UÛÛ·®×®×£ÀÇîƘ“ÀÀµŽãumî ,qÛ®r @Ð÷ŒoFDDDDDDrE-ܵheŒYé8N5Œ1ß’\{»ÁõºÞõz °Ýs›ý’Š– WÖñ¯€À ¯]áØ$i¹·w•!›raŽã„cRNWgžy†ñãÇç¸Þ"""""ç¹÷ƒ]<– v% ;܈ã8íÏ¿€ÎÞã°±-åø9̽íOlv“M¹9 ¶öîÝ À+¯¼’e™òåË~Úià ´M›61jÔ(>þøãB/ùåÕW_e×®]<ôÐCÁ®J&Û·ogÈ!¼óÎ;\p¿_ƒœ›9s&‰‰‰L˜0áô… ±Ù³g“À“O>ìªC† ¡gÏžtëÖ-ØUÉdÞ¼y|óÍ7¼ð g}®»ï¾›N:ѳgÏ<¨YÁ5jÔ(ZµjÅ 7ÜpúÂÂîÝ»8p 3gΤ|ùò§? Ÿ=üðÃÔ¨QƒžÕy8@ÿþýyå•W¨R¥JÕ®à9zô(7Þx#/¾ø"5jÔ8ý—_~ɼyóüþùä“Ä TËÛÀlþ¦Ó¹ÛhçÏh N^Uê|¦€»€pçߨh€®Æ˜½Šü´ôshìÓ§?1)®¹¼ËdQnknëõßÿþ7·‡*ÑÑÑ\uÕUDFf×£FÜ.\P ƒ_ý€Ž;R±âÙM=¿lÙ2Ž9R ï3/ÅÇÇó÷ߟó÷™—Š/ÎÅ_\ ?³Í›7—'u{àhРA¼Ï¼4aÂêÕ«wÎßg^ùã?hß¾=Õ«Wj]üyþùç©U«ÖYÿ}:^˜±=G²Ò¶mÛ³¶`w`g'’Â1Æ»ç×´`#1S½¶¿ R°]Ã3kŒyÖUöàiàì˜íîÀ/@kcLš«Œ|‚ Ä?Ç&Rë ËŸk­\ ÚåÈ8~žx~ýÔó>3Ç¡:°xÏql§ãp%ð‹ã0&˜u“ü§€[DDDD¤©RªW·­Øû÷çìclWôᅦ½{Ïîú‘‘ð¿ÿÁ%—ØÖvcÒ÷%%A|<üô“ ̽¯ð ï6·”»/5Õwû?ÿÀ?À¾}þë‘”«WÃúõö+Ø^Tz»‡_€¯bÀDÝçÜ"""""…H±bðúë°s' vúò+WÚà¸V-hÓÊ•ƒ«¯¶íÙˆŠ‚£GᔫÓô+¯@L ´l —^j÷?ð€ ¤±u¸êªÌçyøa¸ðB8q¾‹ƒ  re¸ür{ÎûîK/oŒ=¦\9hÞÜž·L˜>=÷÷’b¯{ò¤ïöÔT»Ý]'ïk»·§eH)öþû”–bƒm·h >¾q×DÇávÇ¡ˆãé8DzŸÇqpÜÛÇ7^s"\Û54¸PÀ-""""RÈ\y¥íÎ={6|òIÖåöíƒk®cÇl@üçŸ0nœméîÙ3sИS«WÛsÄÆÚDj¿ÿwÞ }úÀo¿ÁöíöaÀĉ¶ë{HÜ|3|÷üñGúyÒÒàwàúë¡xqØ´ þýo(Y>ü0ýœ^oW_ ýuöcÔó@òiöïÉ—ZH €[DDDD¤rw-ï=øè£Ìû·mó?mWT”M~¶{÷鯱t©íV½s§ x¿ÿ^{ êÕó=_|¼íÖ½i“í^µ*tè`ƒ`·íþU«lkùGÙiwkïo¿e?͘û\*dÞW®œ}ÍÉ=XÙÓìVc°Ï ¸EDDDD ±I“l+ñwØn×Þ*Wöƒìvà€MüuñÅyWJ•àùçmP¾r%Œaƒõ!CÒËôêe[æß{>ÿÜf¿å–ôý11þë»aƒm­¯\پ߱#s™; <ÜvcÏ©áÃm]&OöÝÞ¹³ÝþÞ{Pºtúöʕӷ·ní{ÌWð,p 8]gñ÷`3—÷q-½öÿíµý‡ Çqm-·'€ž¬ˆˆˆˆˆbî®å:Ù1ÐÞš6…/¾€5k I“ôí|¾?/¼õŒ_~i»¯·li—¸8;_·w]{õ²]Äwî´ÉÎÜc¾Ýõyï=Û-¼¬«8%ºv…jÕìXôèh˜;×wð´4ÛZ~ñÅ6‰[Nµje—ŒjÕò­—[T”í6ïÏ¢E|å8ü üžÍeÏà à÷Œ;á0Ç߯0/›óJ¤n‘BÎݵ<ã4V÷Üc»h_u<ñ„³zÄ9Ò&»ì²¼¹~óæ6ùÙ!0¾M¢öÒK¶›ù5×ø–8mrw²4·±cí4c:Á»ïÂ?ÂþcÏ=b„ÏýÄ6ðîÔ ,€O?µcÃ7m²؃É>nN‰@S xÖUdp+Ø–ó€Z¸EDDDDÎî¬åÞÓn•, Ë—Û$gcÇÚ%:n¼ÑÎ[›Öàì4lhä‘#mk4Ø1âC†ÀSOù–mÓÆ¶ÿóÜpƒï¾êÕáÛom}ûõ³ÛJ•‚W_µ-ã`Ï÷ßo$8\t‘¦Ë_2µüf Ÿ:=€Ÿa»kóhÇa-ð‚íó‹cŒ v¤à‰:uêš‘#G¢ï‡ˆˆˆÈ¹áðaäÖ­kÖ@HMµ­ÑÇŽÙë)â¿ÜEÙVñ7ßÌú\»vÁþý68÷ÓAÛÛR^¢Dö‰ÖòQ`m°+‘Çy ¸Å“GdÎ_já9”,i—@*RÄŽµÎμy°q£,;*øÏFîæ8és|‹TÃ-"""""÷Ö[6ƒøõ×Cß¾pé¥Á®‘Hà©…[DDDDD®Y3¸ür;}ÙСÁ®HþPÀ-"""""™¥)¶…YŒË>‹.Ê~̶ȹH·ˆˆˆˆˆdö8àoš­@cà. O¾Ö¨`kÎM¬æª\1Ã/« ¸EDDDD$kcwò²SÀ^àm /v¶é[‚T¯‚fƒ2¹8âPÀ}®SÀ-"""""Y»haÛh ð4 ¸E²¡€[DDDDDr§,p0ÛÊì^â±c½/Æv;ÁŽ¿¸á\ßã’ØVôo±mÀ€á@U¯cöÏk\×néºVñ¼¼I‘³§iÁDDDDD$wN+±w8pèLÇvª.¼ –ÿÄ&Zû¸P{{X‚ ¶þÜÄÕ€w€¦@¢kÿï@]ìs€ƒØnïí¹¼÷€O\ëó™®õ8`²k}3ðk}ëZI®ûüåÚ—JÑ g?ìw½ösõÚ8ŒÀ¡.ºÖËáð$%pu­_èÚ7‡Æ®õ;qh—Ë;–|¦€[DDDDD²vÔþæý±¡d?W™å@ð0 x À¹Ê Ĭó½Î½ø¸Õõ~ð&6È^Ì–‡€I®2ÿqw#ð¹ëü+Ø®î¹1Ï«>K€¹®õ5À«®õ­Ø®ó`[ñŸ"=à~ û™¤™áìÑ@QÀø¹zuà¿®õšÀ½®õ2Øþ%°=’ï*»öÝ 4t­ß ´:Ý-Jp©K¹ˆˆˆˆˆd­¥Ÿm¶køx×{wRµç(lx °Ë똫±¡ä, »kÛll`_×ûÅØ®èý¼Ž«üàºÆaW™G€úêx3¶Uý%ʉñZ/´ZHPŒ¯¢ˆˆˆˆˆL《®õàBl—nï1Õ°Að£ÀWØ.å=°-áí]e±õ là\|ws•X®Ó¾š»^ã]¯ ý”iˆ7üwl|°ÒPïâó’~è"""""’µÞÀí®e Ð ß`Ûm¶[÷ àRl÷òØVo·Øñß`ƒëuøf9?‰ MOÇ_™×ë98Þ­2éʃk¤´=GM×z Òø×z(6’ªž‘ÛN¦ZìÈõÃÀo~®~Ûal'õM®õd×ú)ìnÂŽØBúxð?°Ü¥S ·ˆˆˆˆˆœÍØqÔ}€Á®e6ÙÙKÀ0W¹&Ø®æÛ°I׺{§66DÍh6Âõ>û À[6€.Ÿ‹z?ëµ>Êk½·×ù¯À>¨‘¡~Þë¡"Ågîj^ë%Él†á¾Ç~2`HÄ·Ã|úº¡×úM~Î)ŒZ¸EDDDDäìÄa[ªgzm+mÞ«’¡ì@ì”_s±z˜×¾¾À/À뤧Û¼èz üÛõþ{¯ã`3Ž_yö·"’—p‹ˆˆˆˆÈÙ¹¸ ¸ÃõÚÛ¾{x8CÙ~ØŽÒ¿’žÜíVl‹÷` 6ÑZS 0ÑUæì˜ï6@k×þ«°‰Ú^γ;ÉêR.""""yk6qUNE`»KÁÒ;.»Üé b†‹±ã¶W`Gß… Äkg([”ÄËÞB€Ï°S-ÅN=ö˜ë\Å\ej>eØØÎÚ]!ÓLØù)Š_ØïÓ^:{V)0p‹ˆˆˆHÞúÛm8§ªaÓ?IÁÒεäT$0ĵdg° ˜’M™›\KVŠkôsÁ±»µÁ®†, ¸EDDD¤pI!÷¹™+ž¾ˆØzlb³·€âØy¼EÎq ¸EDDD¤pù;^77LÛß–åâ<ÍÛrym±Öcƒì €±A·È9N·ˆˆˆˆœ¿–¯ä¢üApŸ©¾Ø)¶.й."ùD·ˆˆˆÈ¹"¸9—Çø›ó8Úa“`…b®Ä|º®!@õ`WB$)à9W6»YXêz=üÌŠˆˆäÜ""""’·øÙæ>Žº"ðO†ýÛ]KNlð³­äZï‚#|&J`[a÷QÀnàОKDÎ{ ¸EDDD$ol*cƒÖŒB°w(ЀÌ÷PàÓ³¸önÒúïÏâö7í° ¶¢€•@/ vj)oÍ€Õ®õ2®ã¥à™ | ü$a»•OjD¤PÀ-"""ç¾MÀÿrQ>ÿ÷vlÀÕø è‹m•.|„mÑ®‹:j6éYKÒîZøÜi¤ۿ梎’¿R€+€QØÖîŠÁ­ŽˆÃ-"""âOŠëõkà×ú* ?6P¾;Oö/@m <6ÈvH².óZ¿8ൖ@¹x è”Öó9ÕEä¼¥€[DDD$£T kýð2p <'€JÀ…¤Ó­€\ë—¹Ö‘ž,Ms8^û°­Û—%¦Ø¡""ÙP—ò|ä8Î(àGcÌR?ûÊý°ÏÇ×ßc2å*u§!¶#[(ö9úbcLªŸr]°ÿ%–cÖä彈ˆˆze°Ás6Öa¯}öãØ@ú°;Þº*6ÈnLzëõm@oÒÇ_?Dz³F5×"…[$vhBg`,¶[yÙ ÖHD ÜùÄqœ‘À³ØK3ì‹Á>#-ÄÀ¿ÇébŒùË«\Wà3l¦õÀÃÀ§Žãô5Æ${•›ŒÁŽ4; Lqç^cÌ3¼E‘Â¥4éYÁ;cb¹…`»”ÿ´Æfÿp·ÂŽÕxq­÷Ép¼?ÿÆŽýΩ¹(+U›}Þ-›Œ¯Fpª#"…ƒîs'›·4»\§/b'ù—1æ'Çqªßc;°ýÛuž®÷_ÿ5Æìt§3ö¿í»g\åš÷€‰Æ˜Žã< <í8Îbc̸O‘BÇ{Š®L}Ï€ØÖëÖÀ`l4€YØq°ã¸sãB×"…S<0Ûõº;¬`kPk$"œÆpã8€uØ?Ó²(s)pðœ1æ'cÌ6lp}•ã8î禷c;±1Æìt•ûX Üî8Žã*÷(° oŒ9áÚö¶ÓÜy{‡"""…ÄF?ÛJ{­Wð³p­k}4p“k=ÌOY9?$bìnÀ<|3Ì‹ˆø¡€;°*bÿi¾ ÛÜŸË]¯ 3lÿûóiîUn»1&㟠_cS°¸Gµ¾5Ƥ¹ cŽc[Ì[žÁ=ˆˆˆ^Ç\¯»üì ªåHïî­ê.,¾zc÷=ü ÛÍüp¶GˆÈyNw`­7Æ\ušnÜîŽe{2lw¿ñ*—±ŒO9Çq¢°Ïë³*ãg»ˆˆÈ¹i6YÙ?@{?û÷`³ìÁNí%’ïC±ãù£HÏ@/"â‡Æp1ÆËg.¹ IDATä X)ìä#Ÿîw½Vð*·ÍÏñÞåÜë²(í8N˜1&ÅÏ~¿n¿ýö,÷5ŠºuëæôT"""—¼ \ \ŠmÁþ¾ ÍDÎÆû®×ë§Hï‹(RÈ8Žs 6 ¤?—cçk³¤€;ø’°ÙB±ù.Ý"\¯‡½Ê…û9Þ»\’k=«rÇrlÎðþxPj!’Šá›Í›¿xB΀îàûÇõz°Ûk{´ëu«W¹ê~Ž÷”3Ætç¸×¶Œås[¹9sæäö‘üw?ö±ó4`2Лn´-Ð XBî2™Dœ¾ˆœ§öc[¹Wa“¦ýŒ&®zë$rŒ1Óéþö9ŽópKþÖèܤ€;øþv½VÃ7àv§iIô*w¹ã8¡Æ˜SÊ%cöx•«æç:58ƒ€[DD¤À: |\…mž;¶öJ줚#€EÀØäVê)yáð8ÐèçzU–É‚’¦ßþŸ½;—r~ÿ8þú´I”$•"¥Ò&!"K©d+[‹„”} YãûµoÙR_’/áG–’5”HYŠ•B+m´j¡’:ç|~\3ß™s:gÎ953÷Ìœ÷óñ˜ÇÌÜsÏ=×á4ç¾îÏçs]vÊÐ5ßö³°k¨á2.obë¸;äÛïL`JÔó7ŽÎ¹Êá ι:XiÉñ [DD$`§c£‹'c£Ú7„^{[˜ TBɶÄÏÀRìŒëfìbZʼnH!”pÌ{¿x¸Ö9w‰s®®sîZ /Ðß{¿1´ßx`ðŒs®ƒs®sî9¬zù5Q‡ºÓ9w°sîpìOÂàñ$ýX"""‰±h‡•=k†ycèµAÀ'Àt !p/šË'‰ñ3ð$p!Лe!"RýJ ·»b+ÏÊcUËŸòÞ¿”o¿S±f‡ž¯.óÞÏ ïà½_îœk¼Ì mžôôÞoMÜ ""’@‹ @u¬·öMÀ(ì2ó¡ØÔñ°ËËõŠQJW€W±éäa³)DD  „;I¼÷‹)dB[(¾Æ`ÕI¾Â §‰-ë?8«V®ò´"’n †6eñD’ìÁX{¯±X{¯7(FÉP‘$û kQ÷Øò‡«°‹G""Q4¥\DD$S­>,á{Î&yÓd/Çb ·÷:k|Ù¸ø›RÞ%Iñˆ”ıÀŸ@Å ‘T¦„[DD$S-ÂÖ<—Ä: JüCùŸåØÈõE@¬ÚÕXòÒ+Š6x ÍÓÔVë ÿ V=ÿk ðHA‰HªÑŸ2I¼ÜÐý/À¥ÀwXUçs±$;[Ç}-vv¢3Iý€“°YÕ°‚""QôçLDDDk-И´ºa‰5À@, Ÿ†C»Húx ›V>Žýn‹ˆDÑŸ4IŒï@U 1ÖÞ ¬½×wب`m`1”ô´7°ø¸«7ðU ‰HŠÑn‘Ò¤– 4f'ð³þº‹µ÷j¼œ…U"¯z}Æ ’HÛ°ßã\ 9Vøo·@#‘£„[DD¤4Ùõx~Žÿ*ðð 6Ýö`¬RúIXq´G°„û²|¶H²•Ç–C4v 8IIšR.""RšD&ïÇãN Ý7FB¯ÄúkgwSò6e"©®ð6“ã.à‚@£‘£„[DD¤49Ø8ë3°ö^S€–X»¯þ@–€´6` ~å8}¦Hªx[ËÝë#¿6Õ\DM))]&Æé8¹ØèõeX’Ý«<þ ÖÞ«ð2Ðx-NŸ)’Š:b›£3kÙŽ¾DDD$bP)Æë¹ÀþÀ¾X»¯k€/°Ê ‹€IØúm‘L·wè¶»àô Ð8?È D$U(ᑈŽE¼ž´>ÆÖiz·ËCû–¨ERPàu¬Bù¡@Ý@£‘¢„[DD$S•ÇÖk¯þ =ßH5eü­µ.®]°iáö^ý›€3€q‰Z$ýÜü h ” 8I)*š&""’©š+°V`s°êÉ›÷BÛWb£Ó%Q[«}°¸ ŽZ"Iév¶„bð6p+ð{ ‰HŠPÂ-""’©V§`-ºa‰ño@«¨}vd4îߨhù,l½÷‰;¦HÚ[…Õ5¨ \ˆ­ã^d@"’*”p‹ˆˆdªmX‹¢“±5Õ¯‡ž¥^Ôãüm¼¶b½¼Bë´EªuÀ'Xn)õ´†[DD$üŠUú.‰')ü/¹ê#€û€AÀã@·b÷—¨Çæ{-<"®Kö"y…«’/ÁF¸WWŽˆ¤%Ü"""©`%ðß¾g0…ÿ%|Üt†`I¸+ÆqË`í¿ÀFîVF½¦‚P"‰\TŽG ·ˆèú´ˆˆHF:Øè ´Ä ¥'Ù&ôž]€X‹#)Zs¬UÞlýö;Á†#"©A ·ˆˆH¦YƒõÇþ?`!ÐŽ’UL~k#6ïàD2TSlZy#l‰ÈHlj¹ˆ”jšR.""’ªšB=°¬˜ï;¨ŠM'ïŒM/‘Ä»»`µ+PxÖ2LDJ-%Ü"""©ê/`ièqAk§Gcí¹ò»›B~6P[GzÖ¶HD§ ÐK²ë‹ˆ¤%Ü"""©jqÔc_ÀëãË€Ø4W%Ü"‰Õ'tï¹À·Ø…¯]‹HD¦„[DD$UÕ–‡7ÆzüÆ[àïì_ÜÂk"¥Ñ 7ð=ÖRoo¬_}ã ƒ‘ )áIU«B÷•H\¢[¨ c‹”6{a öØ´òºF#")@ ·ˆˆHªÊ ÝofˆˆËnÀ£¡Ç¹Ø¬”µX!C)•ÔLDD$D€7 , ìuøw°áˆH°”p‹ˆˆd‚èiá?…ˆì  ¼„uø,ØpD$XšR.""’ ²€@¬ØflùÁ¼Æú-`[Ôó}¡Çå€ì„E*"±t ÝÀ–…Ìöj‘ˆH ·ˆˆH*Øü¬ç±`CÔó:ØÉý¬ð ˆQDŠç&àK`:Öà1àú@#‘€hJ¹ˆˆH*8¡î\º/‰IÀ¯X1%Û"ÁÊ:£(Ù)Å4Â-""´‹°Bgý€‡€ËeF$";㱨ÇÛ€°å!"Rêh„[DD$h-¦À»À¨H:û¸hU+o¬ 4" ˆF¸EDD‚’ ü…˜ŸÜt¾Ç’ïXö¡àbj…)ɾ"²sr±Y*§w‡Õ‚ HD‚¢„[DD$(£€k°òË€AÀ-X2]”¹‰ KDvÒÀ;QÏ7c-ÂꎈGSÊEDD‚r60ø¶¾s<ÅK¶E$õ.Á¦•÷ 6 †n‘ |üÜ€U'?ø&ЈD$ž~Ö`ËDÆb3ZD¤ÔÑ”r‘dËÆ¦’¯îŪ” 4"‰·B·°ØÔòŠÁ„#"ÁзˆˆH²•f`I÷ X•òßHDâ-x¸[2Rx;ЈD$ቷ5À1…¼¶骊]öÞXüœÐD$IÊÏc…Òz­B7)U”p‹ˆˆÄ[6EWÿ#ßóm ŠED‚35ßs]X)u”p‹ˆˆˆˆ$Â2` Vñk` °(dP"’LZÃ-""""’³€Ç±,—ÐÙ·H)£n‘d8˜¬ÆF·¶Žˆ$ÁÉØ¿ûh¹A""AQÂ-""’ _ak»v 2Iª™ÀçØ´òo°‹oÃHD’H ·ˆˆH2äD=Vá$‘Òã`p$Ö ðø`ÑäRÂ-""’ Ç_„·ÃÖrŠHæ{ ­Û)Å”p‹ˆˆÄÓ@Ù¶OÅNº6½TDJ‡\lIIxJù×À+ÀQA%"É¢„[DD¤ °}±£XÖ'u°i£ùE÷ÙÎ)àuÉLÿ`³ZG·õHD’H ·ˆˆHA>z–`ÿ2À à"¬ Ò׉JDÒÎnÀT,Q¤”ÒŠ‘x(ôǦŒ?p,"’Z<¶”äQ ;p\°áˆHòh„[DD$ö^.ÀÚþüØ·﯑ˆ D$%¼ \4Ū•Ÿl8"’à¼Ðãg±a‘xz¨t& ¡/‘R@ÿÌED¤ôš¼܆U>zþ.ð)п˜ÇRK0)JO¬=ØÁè,\¤”Ð?uÉ,ý(þèôJà= pð4pЫB®Jä"O„nˈL+?èdP"’HJ¸ED$³< üS‚ý/.ÃÖVö6—ˆØLšé@¥ÐãCƒ GDK ·ˆˆ”n½€…Xâý)pq ÑˆH¦ì4BÕ”DJ%Ü""Rº9àe௠‘R¡mè~ ð-ðÖ†°|P‰H")á‘ÌV( lÖ9ù^ŸMäXD$Ñfbk¶W•ð連A%"‰¢„[DD2Ûf`Uèq-à÷|¯7Kn8"RÊÕ‡ P‡‘ §„;Å8ç²¼÷[‹ØÇe½÷ÙEìWÈñÞûxÆ("’Vþˆzœ?ÙI¶ÝsCÁ*•; [`‰H©TC pÎíæœäœ[ üãœ[Ã9W>ß~{:çF9ç¾pε.àx-œs_[€?œso9çª'å‡I5Ñß’•‹BD$â!`/ >p9ðq°áˆHâ(áN ÿ®^NÆbå3Ì·ß   ðp&Öøf‚s®exçÜ.À(lÕb?ìkü0`²s®bB ‘T´;©­Ô8€c€¡À|`-ðL°áˆHâhJyÀB òùÀïýu¡Íãœs+œs·xïsœsí€ Þû×Cï,nÄÛÜÔñÞ/í7ø›À4<9?™ˆHŠ˜õx}`QˆˆD´ Ý{àgàl6N£À"‘Ñwð²B÷ËómŸ‡ýÿ ¿ÞkZónxïý?À›@Wç\x¿À'ád;´ßtìë<œ”‹ˆd¦ß˜U0DDRÄ)ØÌ›&ØÐÉÁ†#"‰¡„;`Þû¿€q@/ç\[ç\ç\¬tÆïýß¡][KBIv´yXR^Ã9WhHÞñœèýöKÈ!"’*ª£¿l"’Nž~Å–¾¨hšHFÒ”òÔÐ x˜„5°©Œ.ŠÚ§*°²€÷†ëïÖ¶au.ÿ(d¿¯^\±bE¡¯U­Z•¬¬¬B_Iš\¬Æ•Øâ™éإʢ¦hª¤¤ˆ!¼0˜º[øî"ñ末 ìZÈË…m—RÂ0ç\lÝuà3`X†M7Ÿ<uÈw±Àι÷±ñž“Þû™Éú¹DDj3ð3p¶^»0»´("’.úF=Þ†ÕHÁÕxD$-i„;xïoÚŸbSÉÿ8Ú{G¾ýÂF´g`Kî:yïs£öÉ:WbíÀÎþ•àCD$9¾öÁú:ƒ•ž|hˆŠ‰Hzù¸h]0lL 4"‰3p§ïýgØécQûM&±O.ð|è&"’9í€K€ÁÀ«ÀýØ‘t³+¶4æTàn,á®hD"gJ¸ED$=äõO€3ùغGÍß‘t6*êñ?ÀO@“€b‘¸Ó”rI}‹æÀרò¯±J…uI3«±š{„îsc¾CDÒˆnI}µ€£°j¯_w’ˆH\¬–g¿¡3t‘ ¢)å""’ºr€'€«€á@cà¬}ÎuÆ%"%²d ì·_äyv6¬^ 5S¬ÐáÖ­°v-Ô¨Ù¶f T¬h·„hº…ý ¬ª%èóD$©týLDDR×à! #Ösáàm }A‰HI Ãĉö<;zö„£Ž‚E‹ -­[¡[78æX¶ fÌ€>€ví sgؼ9þp9ЛV~g?KD’J#Ü""’šÖ`SÇ¿ºé¹Ý9È D¤$„[oµÇ;Bưj•"´m Ÿ~ uë`H8Ù3Æž7heÊØöìlÛÖ¹3¼÷^‚Fº'aßy=±ïºÃð"p‹ˆHêù ¨ ¼Ô&cEÓÆ“ˆ”ÈÓOG’m€Ü\˜3'’l,^lI÷úõI/Þ½#É6À–-6¢N¶ÁFèÏ>;A ÅF¹oƦ—; ;ÖD$](á‘ÔÓë¯}1p Ö«ömlJ¹ˆ¤…ÓNƒ:uŠÞï ¡J•¼ÛfÍ‚Àûȶ7Þ€^ˆkˆÿs饕{Ÿ²eáÊ+óùlž.Z•±Ù="’ö4¥\DD’g%Ð;ÆëÛ€9@# 8ø/P¸/áщHÕ® Ï> 'Tø>ÁgÀ5ר”ó`ÁhßÞŠªÍž W]S¦ØôôÜ\KÂûöÃ{5 zôçlÛÆ0i’M/Žvíìs¼ð}Î;¯øÇÛ!ðÂG‡&ð³D$i”p‹ˆHòü Œ/Æ~¿G=ž4KL8"’XÕŠ¨´]­š­‹~â »o‰íêÕöúØ-ÚÅÛ}ß¾–l_u f öàÁ°iœ~:|õ ž79%º2ù޼¾SvæÛ¶-Ÿ'"I£)å""’Úê¢ö8";hÉ’¼Ï½‡¥K“óÙÛ¶Ù´ðX¦Nµ©â`£Ý'F’íÂxC‡ÚñÃÉ6À3ÏXA³½÷†/¿´ý.¾ž¾èXW¬ˆ=º>þâÅEk‡Í†ça3|D$í)áÉ@Ï?oÕ¶Ã mx4ø°Ãlt"½ü²µ›0!ö~99ðÃöøÄáÁcOA¨T ¦O·Ññp²]ïm}ö‚±÷ëÓÇ’îXÖ­ƒsΉ½ÏN™< äWÏ%ð³D$i”p‹ˆH°Z šh¡“Hœ<ÿ¼înÝ Ý»[;®š5-A]½ÚÖH'*é~ë-8ÿ|X¸jÕŠ½oùòк5T® :ÙìK/ýž ,‘þóÏâMæ8ðÀØûÜv[dýwajÔH\Ñ6®æ¯×bß"’ö”p‹ˆH°¾Ç¦R.ö8‘ ðÖ[–l‡+|{óçÃÊ•‘}ÂIw"¦—ŸrŠ%Ñ¿ÿIvo¸–/‡ï¿‡°ªào¼ak­ÿøÃn°ç±,Zd÷ÕªÁ“OÆÞ·Fâ%åC†ä­ˆÞ§üö¼óì¶›ç½÷à ƒŠ>ÖN™ ÎÅ.D>àÏ‘„SÂ-""ÁÚõø×À¢ÉmÛBóæEï×µ«U· àÝw-©Øc~ôQK\[¶„Ï?·¾×]ºØ>eËB¹r–€ûøëÖÙ}:V -–-[ò&Ò…<8oí´XëÖ…³Î²‘ø,ú8;í¬3CYà Q}¿E$i”p‹ˆH°ÚG=Þ/°(D2FÕªðæ›±÷©TÉ 5º8.´u×Ñ#èÕ«[uñ .€/¾°^ÛÑj׎ŒhGûë/kýU”#´õè§Ÿ{¿õëm¿œœØûÕ®mÉþر6eýé§áÎ;áC`Ä› 0v¬McO¨/€¹Ààj¬E¢ˆ¤5%Ü""¬Ÿ€ê@eàŸ€cÉŸ|9.Ìž{F’í­[¡woK87o.xÿÜÜí_Û¼Ù¦wy¤µóêÒþþ;òzãÆðâ‹V9¼¸úô±V^áØªU³d½I“È>½zY_îêÕ‹ž~ðÜs¶Ž½8‰üÉ'Ü96 ß¡ƒmsڴdz‘ø„Ê>zõ"й‰HjSÂ-""ÉSÐÉür`°!ôXDv˜÷pË-ðóϱ÷[¶,R½ü£à¥— [·í{^ƒ%ª_l#Ò7Ú¶íù=÷DF™¿ùÆŠ¥g w,}ûZÒ}Øa0w®MGŸ=®¸Â’í_´Ä·sç¼É9Ø4ð*U"ϳ²ì‚Â[oÙˆu´iÓlúýìÙy·— {¬µ [´&O†+¯´õÜ µè¼ ìÜ ìšàÏ‘„RÂ-""É31èD2Ot‚ë\ÑmµÂïéÕËútmÛ*V´‚gaë×ÛÈpûö–ôNžlë²Ï8Ãî'O¶í99б£½ç£l„xgõík=º«Vü\C‡Ú…èQæpr^¶¬d/_k×ZrN¶š6µQësαm99V ý³Ï E ¸é¦í׃—)×]û%s©K#à/`:ð,p6ûGDÒ–nI¼\`pZЈdïm´·iSX³&²ýî»aÿ"*þ;g-ÂjÔ°âe`Ó©£Gpß}×’ñO?l[³Æ¶GÞ‹/Zû¯“O¶$¼qãþÑ+¢–?æ2œ¹öíkI~¸y89¿øb{Þ¸±ÄŸ>Œ­ZÁODFëss­ [¿~EÇ´m›U/OlÁzrÄŠ¦õIàç‰HÂeLÇSçÜž@;¬kau °XLðÞÏ 0<‘Òí¬ÐhଠØ{ÀùØ7v,Ý)ÐçŸGŠ‘Ý~»%Ð`£½Ÿ~jk.´õÙ>6Ø´ñ´Qá¾}-q>ã K¢»uË{üð”ó¢8Ço믃’¿Ïv8énÜØúŸ~d ú?@ÿþÛc= >vNLš#GÂÛoÛHù„ ñ?Xk°æÀáÀq ü,I8çwv¡MÀœscMzåC›·`c){b‰7Xâýð´÷~c²ãL3‡ t&µaƒ%ÛºÕ’Ù ¡gOxõÕ¸ýHq÷è£6e¼(:Áûïo?²~ç6BV¶¬rï³O|ãüŸ ØÙë. :¾$RK`FÐAăsn8ÐÛ{_¾È%¦´RîœÛÝ97[åÒ¸hìã½ßÕ{_Ë{¿+P 8ƒ«üêœëTÜ""¥Ê€;*ÀÇÀÁØhÍR”l‹”À–-pß}y?°žÕåË[rýÀy_+[ÖF·£‹ŠU­º}² vŒòQ§Õ›6Ù¨ð^{Ùèu,ÑI~*ºñÆ¢GßëÔwÞÙ>Ù+ζ&¼sg›B_Øhx\T6〻±‹•ï$ðóD$¡ÒyJùWÀ ‰÷~na;yïÿÀ’í1ι€nÀýιêÞû'’ªˆH)Õ[ƒ¸›cô.V}·NA‰ØúÝJ•ŠÞ–*úô±)ÍYYVì¬~}ÛÞ¨‘Þ:·Þ¿Ï«U˦?ü0zhì}Ÿ~Ú*x'4 ÝIÝ»[·Âtì*üÚá‡Ãk¯ÙõÊÉZâÒX‹]¤·}{ IDATlÔJÒçŠHÜ¥í7ÐÓ{ß5V²Ÿ÷~«÷þ 16©QDDa,0 è„ÿ ô ½Ö;¨ D̘1P¯|ýudÛ]wY1­Iîy¼dIÞç[·Ã7ZR½u+ÜpCÞ×î¿ßF¿ѲªgOX¼8ö>‹o¿þ;Õ¼SÄñĉ‘Šæ9çœ$&Û“±jåßC#’øÙ"Wi›p{ïgíÄ{·•$Q‘ |‰•²üÈTB6fŒ%‡«W[ŸåŽ¡Y3«ì=g´k—¼¤û®»¬ÂøäÉö|ëV‹­M¸ürX¹2²ïa‡E ¤½û®LK†{î):Ѭ\yû©î©ä?ÿÿþ7ö>‹ÙûœœâsÃ+>—0õ°î÷€Û±Ù ü<I˜´M¸ósΕqÎàœ«z~sn„sî}­ÙI’_±`ÿt:bë›/bI·H@ÆŽµ¤*<’¹m›U›ŽNœÂIwtÛ«D¸ë.Kò7n„¶m¡I“H‹®_~±ñÚkó¾ç¬'ô“OZRž G ãÇ[R]¦Œûê+»/Sƶoû¥ª† a—b kÒ$oïü6o†×_·5ðûìcëãæc Ð›%t¶®[DÒN:¯áÎï9àB µsnV^¢° 8Å9—ë½`|""™-…iŒ­Ó~+[y6µ8ï>õêA‹РAÞíeÊÀ®»Zá­X#Ü[·Â¿þ;ŽÉ“í‚‚¤¶%K"Uæã® ö=º x¸[@)"i%#F¸½÷ß:çÞF…6Í^ U,ÿ ›^~jPñ‰ˆd¤uÀûÀ‡Àx PHf¯ZÉ([¶X¯épEé lun®«ºóÎȾ‡n½ó'¥‡n÷ÞÃ÷ßGF$ëÕƒ~ý¬HÖ¼y±ã0jÖ„—^*øõ§ž*xûÍ7Û-¿#Ž€¥K­`W­Z¶^ø‹/bÇ gž ¿þ •´,#¥¬Zekñ_{ ¾üÒ~·,HÀmÆ~ª­€3ð9"’P™2 p¶z°7p´÷~½÷~#V4íïý'F'"’I>öÁúkï ,zaÓËEJ`Þ<8äØ}wkÝV¹²­Å†íëðñÅ‹ó¶ðjÕÊ^»ì2KÖ£ýç?VYú˜cìù 'ØèäèѶf,.ɺÞâÈÊ²ŠæõëÛøM7ÙgDzË.6z¯d;õ¼ò \}µ³óÞ*ÇÏHD™¬òÀP¬åZà#¬z¹ˆ¤•´M¸s79çŽvΕðÞçxïß÷Þ¿ä½ÿ#¼Ÿ÷þfïý…IDDJd:иўÔÃúÆŠÄ0{6\|±B/\Ù^³¦µÔÊζQéháëo¿Ý~{Ù²Vý{eT½€Úµ-9úéí§‚ƒ%°ãÆÙˆùøñV«kWKžž|2þÉvA*V„÷Þ‹$Ý·ÝfSØ¿ùÆâßekc•¿À—¤†îÝm&F:Öî»ïì‚QBœu}XŒÍã¼7AŸ#" “ÎSÊî6;ç>Æ&4Ž÷Þ/ 6,‘ ö7Öôä l^Ñ`00Ž4¾„+ÑÂÃÂ6o¶é²%¯…Y¼>ú¦O·VYeËÚöM›¬‡5X’R¿¾=Þ}wëEòÞoô޿罿Ê{ ÐëXØxXëœ뜻Æ9×0Ð`EDÒÝ À!ØzíïvÀ4àü ƒ’xéß¶5©]{ì{î 'Zñ²Nl v´›n²)Ý=zXòNž!ob]¡4kfó'Öç?o¾™w{íÚ6â®öX’Ê~ü>ø ®œ ˆ%ݯ å;"i&­îü¼÷ ¼÷C½÷±‰7YX[°¹Î¹Ý PD$Mn²°©ä§aÉöw@C lp¡I|<ù¤%ÛÑþú+oÿè àÔSó( ·ÚÚ¼~ú)²½iÓÈèsþÄú´Ól ìYgåÝ~â‰pÝu¶¦Z$üú+Üsý¾7kfª¢/<ÅM쬶!p=ðM>CD&×pÿsîà,àïý÷Þû­ÀÄÐíç\Mlõ¡ˆˆ”D.ð_à7l ùóÀ¡Ø\"É^/¾Xtßgçlä;,\Ð ì½Í›Ûãr嬈ÔêÕ°×^yq¯Š>I†øôÓ¼µV­²Gç:«N~Ö&LDÒJ¦Œpga×ÿ¾sÎ-vÎýÇ9w‚sî¼÷˽÷¹Á…("’fæßb%*?ê‘w6WjW”1*U‚^(z¿ìlø!ªïG£FVð `þü¼û~ö™m»ýöøÅ)’JÎ<Ój ´l =‹% Ù«™Ñؘ ¼¬JÀçˆHBdDÂí½ÿ+ Ö x›ðø °Ò9÷’sî,M')¡غí @ `2°ØdP²£²³-)ز%²mõj2Äçó’tô(¸sV mÕ*xà¼û•Wa'ÉpUªX’ýý÷póͰß~ ú  @G`Oà l‰Ïü˜ï‘’SÊáSÈ? Ý®qÎ5Núb%}f`ÍlDD$– @Eà 8x8˜`\²Ã²³­öoXEñ'Ÿ´]}úÀ¼y°t)|õUÑÇé׺uË»-º…˜HiS³f>¤2p$p96­¼N>SDâ&cî0çÜÞ@[¬¤O{¬ÄD66 GDDŠÒ«xñp?6è2 vâ'i%:Ù˜<ÙÖWG{ì±âëÅáŽ;âŸH¦È͵¥‹[]„¸º/tŸü,Æ•D$åeÄ”rç\íкíYÀJàuà8àClÅá^Þûs‚ŒQD$m<ŠMWì¬ÃªâÎAÉvšúðÃH²K™|gåÊåÝV©’µ=ªV-¾ñ‰¤»„«¯†}÷…N°jûÑþãâ  °;¶Äç–8_D&#nlEK? )ðÐÒ{ßÌ{m¨O÷ŸÁ†'"’*q+”K°¢<í±%Yië´Ó¬¿u,ÎÁ[oÁWØã§ž‚mÛ¬Ø1ÇX²=nœ=‘¼æÍƒ¡CaÅ {¾y3̘ç©‚Íß| økz+"iÁya§9çözaã1í°ë³±Âi€Ï¼÷aÚ9dðàÁÓû÷ïO&ü~ˆHˆ+áþï‡îOw ãÿ¹r–`{_|ÇymãF˜;;,ñ1Ф£þZµìßHÖc~Ï=øÛ€±Z ø9²#Zbu£Òžsn8ÐÛ{¯˜;)#Öp{ï×OO†Z‰%ßí+€2ι75­\D¤vÃFT$í­^ûõrå¬zy… y“m°Ñm%Û"…Ûe+Ç’Á&O†¦Má®»"Û† ƒ† a̘Äþ¼y6%¼(3gÂï¿'>‘L÷÷ßðúëðè£q>ðíÀ½ÀiØšîiØôòt3˜ZÀí>ìì=ÿö_ƒ S$^2bJ¹sî8àSl…âà9` 0Õ{Ÿ`h""©k þ†Ô7eBLž 'Ÿlë ï¾†‡òåaÑ"{½[7=ºtI\ mÚXÛ¯k¯µ¶E`ë¹ëÕƒ… íy0i’m‘3s¦%Ùï¼cÿæwÝ.¿Ü–eÄÅ àn,Ñþغo§ã'ËÍÀ»1^?*ßóË€§ŽH¢eÊ÷Zà& ¡÷¾±÷~€÷þK%Û""1D—AÙXköìH²öÛo‘d¬uP·nðùç‰åê«­Êx… •eÏ,°êåádû ƒƒH¦ûãxùåÈ¿ù¬,ûˆ› X÷ˆ³°¢–kI¿d[¤ʈnïýl`¶s®œsî  V»qð½÷~A Šˆ¤¢Ó×C«bëç$n:N:©èØÇ‡žøxN<–/‡uë"½´¯».¼ªTIüç‹dº¶m¡AhÕʪ•Ÿt’%ÝqSx3êù`P7ŽŸ!"q— 7€s® 0 K¶ó¿v§÷þžäG%"’Â^z¬d;îÊ•ƒ×^ƒ÷ß]ŒlàÀ‚ .M ­[GžçäÀ÷ßÛÉ|qääÀý÷C¿~‘EUªlŸ\+Ù‰2e¬^B¬|;m"0ø«RtêÉ-’â2bJ¹s®<ð*6FÓ¨p7w;ç. .B)Ê•³[,Í£¦„nÙ›7ÃàÁpÔQ‘Bk99л·†·ÐÚOÀwÚHû+¯ìPø"RB M¶~Öç`½x¾Hðç%ÃùžW $ ‘„Ɉ„huíà½é½_é½ÿÛ{ÿ³÷¾?VH=¸ED$©}4ïî‚ yüÒKPµ*ôïoÏï¾Û .UªdIsxÍwq’î/¿´ûU«à¼ó[˜MD¶·j  7Üǃž‡µ»hɯãñƒðO¾çª)"&Sî¦Ø×Ík…¼þ4p”s.S~^IqÓ§ÃM7½ßÝwÃøñöxÐ ø'ßÉç¦My§¤‡“îŸ~Š}Ü×_‡Q£ fM{~ôÑÅ]DvÜ´iЩÔªe ‡ ±ä;n†}°³ß=CÏÓYþ‹’å ÜK$meJº +%Ñ¢×ÛkTµ\DJ­l`vY²ÖD±_h[a·ƒ‰4c´l ÷Þ[ô~×]g'ç?ÿ\¼~Ù×_MšØãìl8ÿ|øðÃÈë«W[…ôCµãÞ}wœGÙD¤PeÊÀGÙR€Úµ#-øâb,ð7Ð[Ó}y„¿êX¢Ý¨l8"ñ–)EÓ¾À®Ýëœëé½ß~Á9×è¼Tp""Z´þ\‚-°ù¨ªÛ&Ø¿ÿmk¸o½ÕÖv>ÿ0a"I˜I‰LWb]&¾¾+°QbI9™2¥ïýpàà] Öl-pÐÜ{¿.¸èDD2 ø¸ø[|Ó;æ;d'Ì §žj­»f̈lïÙ3’lƒz÷Î÷ÿ¡|y+l¶dIìÏøãKÎW¯¶Q좬ZóæûG‘T·xÈ®>þÛE$%eÄ·sn¬nãëÞû GD$u„íÁXÓÄ÷±–2’#FÀ† vkÓÆªŽŸ}vñß?v¬M /Êý÷C:[ŒÊ$C‡Â)§?‰¯ ¬€áܹö°ÓZùë=l‹ÃqE$!2e„»V£±mÀqˆˆ¤–qÀeÀ…À)ØÈÈžA”Ùî»yÄŠ&mÞ ·ÝfUÅ‹«sçÈúì°²eíxaåÊÁk¯ÁÌ™ö¼¨µ¡:ÿóE$~¦Nµe X=‡#à—_âtðÙØ…Ôs±>Ö§Çé¸É0›ƒúwè¶ 8 êu‡µ= ¿þd²‰¯LI¸Ã§%8­É`Ó€±êä÷3€]±7’P7Þï½g#Ðï¼YY%{ÿ€‘¤{À[›ýÏ?еk$ÙîÚÕ* fSÑcùì³û9Ddçì½7|÷]äyË–ql6xØû®(NÇM†òX‘“°µè»ƒ€1ØŒ¬ÍÀ@ìïU2d>®”fÎÇ*…š&œså°µÚ½±¯œ9À ú‡Ûâ½/fÕRïÁƒOïß¿?™ðû!R*}‚j—†§MF{ùehØŽ8"ïö­[KžlGûì38þøÈóìløúk›ª°v­UBž5+öq²²¬"zÇŽ;‹ˆì˜= qc«ãШQÐѤÀóÀ+ØtøW€Î¡×VbÝ5 &´ÔûéÒžsn8ÐÛ{¯Îè;)S®µÅÞ³æI‰FD$H›±ÑƒŸÛ±©†±Q‰«O?…¾}mÚ÷óÏÛIuØÎ$Û7ÙÝ'Û•*ÁpW­j£í"’|£F%èÀkŠû ‘j僀ÔŒÌJ¬‡Ð€G°‘íeØÚô…À& û[V6 Eâ(Sî>Eì£2A"’ù~ÃNdn®.ö 2¨Ì®(¾e œ{.üôÜsOœ{î"+ F†nÝ`ÌKú{õ‚ÚµáÙg­ŠyÖž(ºBºˆdtja=znZQñlõ±3÷snÀ,,Ñ®ºÕCɶdŒ´M¸sÕ¼÷k¼÷Ëÿ+b•‘Ì·/ð0pð2ð4Ð&æ;¤fÍ‚[oµÞÙ•+Û¶1c,™7ºw·)Þ_|996 ᤻W/8ÿ|k+ЯŸ¶¦d[$xoEÔF‚~€‰ãpÐß°õÐé$ kcö¶ô`"pyç¡®þvOr|" ¶ 7ð„snˆ÷~j¬œs{ÿÅúrkJ¹ˆd®¯°Q>ØZ¸›€~ÀwDJKJ‰ÍšíÛÛˆñ!‡À5×X_ëgŸµäzÛ6˜2®»ÎÖs'+Ù 'ÝÑÂ#Û"¼¯¾² `‹G¶Í˜aß';e0™È´òÙÀO¤öÈð|ìl|Vué}lÕ3X%¦¹¡}ÖÏ—£Hœ¥sÂ]çœkï½ÿ¾ œsG¯a§ ï&38‘¤»˜‚­×¾AØŒ’íl,Z×_ŸwŸW^±û>°iÝ""ÑêׇeËìq™2pÜqÖy`§}ŠÕèhŽM+äº ÷*l:yS /ppfÔë5ˆL7¯¨ÈœdˆtN¸o¾j>rεõÞÏ¿àœsØ Æû±SÍ;€‚RD$iÞÅŠÐܼ¼ Ô 2 ô÷óÏ6U¼(?üþ {ªÇ¹ˆäS½:\{-Ô­k-ýjÖŒÓ;b-ÓAu`¶ô1àfl4{¿Ðëù[›å$-2‘„JÛ>ÜÞûyXuò­Àç\#ç\u`¶ŠñWàhïý½Þ{ý³‘Ìô5p°èM)<PòGnnñ¶dÅ ›.Þ¶mìý*U²õ˜J¶E¤0=fµâ–lƒ­‡þøb:bJE›þ~p°k_¹_Ô>ã°‘ïã±z$G&7D‘DIÛ„ò$ݹÀ'ιÞÀL 0 hé½ÿ6¸ED’À“°©x¯u°þ¦•ƒ *x6À±ÇF¦|̙͛Ã÷,DÊ?ÅsØ08ï<øä“ØŸÓ²%T«¶óñŠHéâ}rpÖZk?àè83f`gì °‹Ëcòí³KÌ;bk¸E2@:O),évεŦ—ÿ°8Õ{?6À°JÌ9· pÖÐço`lô4ù¨ýšaåÊb%’&yï·¯qÎu¾vןzïg&0| Ê:líÞ líöXÿí;ƒ *x6@§Nðõ×V°hÔ(Øgxí5Ø´ :t€ àÐC-!2ÄÖiÿúkä:X‹¯¢Ìœ ëÖi„[DŠöÏ?ðá‡ö4mš],³3Ã_OaÕŠ²â`¢´–`gêÏ«É;¾gè&’aÒz„;,j¤{9ÖÅoF •s®66ÑæMàdìTy†s®o¾ýNÃFðÿ…]ÏüéœËÊ·ßá×ú—Ós×%úç‘$[…­Ñ¾ÈƦÎ. .¤Tlƒ"½÷ nÉ6X‚Ü¡ƒt¯Yß~kÑæÍ‹§uk¨P¡èÏûóO;Ö† qÿQD$ƒ|þ¹­ç>ã »ø7¾mÛ)û`ˉ>ĦjŸ†u¦H%€°Xo`K¡ò¯=߀]8¾8؈ًH$=dD y’îŠÀDçÜ>ÁFT"ƒ€&À!ÞûÖ@mlÄþ)ç\Uø_{³aؤ¡ú¡ý:gW†äœk…u5¼hà½o‚•¡xÜ9×"i?‘ˆ$^u`0k|ø!VÕuÿ ƒJáéœ'œÙ=}¼|y8üðâ>Õ«»íßøD$³|0lÙb+V„îÝa=vò ›±¿gbk ë•vò˜ñ6‹oàvlývÓösØYîà8à>¬ÏHšs>. H’Ï97 [«_`ìŸkôмŸ¼÷)·²Å9W«ÙØÏ{ÿTÔöVÀÅÀ ïý\çÜ-Øu¿F¡‹ áý&µ¼÷á¢qbÉ{ÝðTsçÜnØXØËÞûËŠÖ!ƒžÞ¿Òõ÷C$ã}Š]š;Ýø7ð6²‘1—Rw܆ P»6lÜXø>};Zâ]£¬Zgo¾ÙgãFõîÐÁFÅkÕ‚‹.‚… aäH+ÀÖµ«V%»ÿ¶ˆ¤ŸíÝi§YÒ³±‹­åãt¼DX ¼ŒM'ÿ Kº3³eeKÒl¦maœsÃÞÞûTþÍJ é|z0‡’•úµè]Ñ ;=þÀ9W Ø*ö]ð­ °(:Ùù¸?4¾[·ýFôºnïý&çÜ—@ëÄý"’TñE(ÿ¦”Æz6(Ù reØøñÇÂ÷iÞÜîƒë®³ûNù.ãVªdë¼'L€›o†×_‡ªUíµ3Ï´ä|Ä%Û"R<$à Í€eÀ´Ðmö7",~NźhôVRx²ý9vö»0t;x$ñaŠ$RÚž"xï3¥$Pml$~WçÜW@+ Ç9÷>p¹÷~uh¿ý€5¼?üz ¬¶ãî1ö;8ž‹H€^ÆÊ,^õÛ~ k&  ?ý{Ÿ·Þ‚+C rn½5ö¾á¤;Z×®vÙ›7ïäh÷³À¥Ø¢ÊÃH­á•o°5Ù•€ó±¶_Mbìÿ6½>vFÜ&ÑŠ$^Ú&Üι Þû-A½?ŽÂ ÷D¬êxO  ÐØ×9wth´º26õ<¿µ¡ûØJžèmù÷«æœ+[’žä—_~y¡¯]ýõ4lذ¸‡‘xø«ú085¸¨dP©eüx¸úê¢÷»æ›vÞ¥Kâc‰¶a¼ý¶-G™6 ~ÿvÙeÖëVÑ ëa“JÎÅ*ÂÚU~‡µ±,Ì’”„…Z*UÈËmÈÔ‰ÿI–¶ 7ð­sî)àYï}vqßäœÛ¸›¨rw¢‚+]€=€ÑÞû‹CÛF;ç–·鮸‘‚›>„¿ž7„ö!Æ~›J’l¬_¿¾Ðײ³‹ýŸ]Dâ¥"ð 6šýpðX ¥œŽ¡qcøùçØû5iGvš!"’ S¦@ûöÖ",lìX[¦²Cö Ý–™VÞœà[l}†Mu? «Jt1Öø6–õXß°Y IDATž…Øtô…Àë¤^!¸ÌQ¨RÈk©Þl.m¤sÂÝ»Vv“sîUlÝrE œs{aÌ{cm·†“:§¨ËB÷/æÛþ*–pŠ%Ü¿5 xh5!¿xï×9ç¶{²ß/% näÈ‘%}‹ˆ$Ê*ì$êk`(Ö^åWàÑ ƒJ=eÊÀ¬YШ‘7+Sn¹<|,°õÛŸ|{ït´"RÚz(deYÂ]£œ}6ìô„ÁS°*å•°iå©°ÄèìïÔUÀ9ØÔòÊxÏO@w¬ èØß¼m Œ±”óÞú m'\4-¹e¦´M¸½÷߇*y_Ž•`ø—sn#v}o vlolísƒÐÛ>ŽõÞ§RW¿¥¡ûüµtÃÓÝÃCÌ¿‡0%¼.°Á{¿.ôüwìgί.©[8NDв;ùè‰õ+è‡5T#rrlzx¯^pt¨EÙ²¶†»MxähÛÖ¶Ÿ}¶H{øa%Û"Œ]w…!C n]8þøâµ,ÒÀÃØúèT)žÙ[³="NÑ wk`šÈ,%UþIîïý6ïýXB}ðöÏ´1ÐK¸ç×zïOK±dlô:[á­GèþóÐý›Ø(u»ðÎ9‡MÔ™õ¾7Nιݣö;)ÞODÒɞطÅçØÈÅ+غí}ƒ *5ôèO=emvfÍŠlÏÊ‚o¿$Û`ÕË_xAɶˆ«Oh×.NÉ6X¢Ú XŒMÁ¾™HYÝ ¼Bdîæ…Øß®'‹ñ¾2Ø|Ìq¡ýûcEAEÒXZ'ÜaÞûïýÞûk¼÷Gxïëzïkxï[xï;{ï‡xïgA¼÷¿#€;s×;ç9çnÅj¿O¤—ßX¬ÓâpçÜ Î¹†Ø†À5Q‡|+™ñ¦s®yhÀèÐ{‡$凑øšÌÂÆÌ®Æ ˨Œ;[K¯u묭ׯšË#"ihMA=fŠk¶ °6÷s—ÐM–¥À Øbȋ鿠ÛYÀéØß¹9Dæ|Ф©ŒH¸3À¥Øu¼±¯–Û±kç„ûi{ï=¶þ|)ð 6r߸$úb‚÷þ7 #Ð[=ó ¡f Þ{­‚IGocsTnÁ’ìÛ±ži»((¾z÷†GCëØ—/·ª¿""é`éR4Ž<êÔ±êå;¤¶÷ÿÙ»ï0)ª¬ãßKN%ƒ #D‚b€.âšQt1¾*`X «°*FTÌŠY  $]@’² Š*È‚ˆ€$ Ã0÷ýãôØ3ã„î™î®îžßçyê™îêêª3>ÒÓ§î½ç,Çí±o‚A¹ [äøtèçÌ(Þ;+®ö=6Ò}^Ì£I(}]K¡DxHh+ì¸ÿ]#8ßg@³ØD'"{»Ýv%6Uð m jÕ*k£“³¢ïu×Á† ¶6ò–[‚‹MD$RsçZ§Ÿ£ÇÛoÃ…ãde°bcX†U*ߊ é$ÚXÝëósB[4jaÕ˜~më°Í")J#Ü""Éj6_eVö¬÷vóc ØwßY!´sÏ…‰s¿v÷Ýpë­ÁÄ%"­ŽÃõ$Z·†áíˆZ±=Uûi,›Vâ‹o6êÞðâÈH=‹Mï…‚›… „JJÓ·ˆH²ª‡Uj=¸+‚sg nÃÛ23-éž<ºu :*‘è•- O<ÍšAûö18áÑÀ“@G`ßœ¯¸nÃÚ}€M)_åûÏÂj–4ÊÇ42‘@(áIF?awø?^ÄÊ(®Áúo—bG ¯¿§Ÿ;wB¿~ðãP©RБ‰ˆDïôÓcx²CB[V*w^èù1¼FQúí°îͧ†¶hÕj` q—cÓÊ»ìšt‘Дr‘d³ûÂr&VyöoÀlÄ úožéˆ'ãÇCíÚðÆJ¶E$½|÷] Þ|.–¬¶nÆ:[$Êl:û£@Sàtl)T´<–tïÍìZ£E² ·sîmçÜÎ(¶AÇ,"‘½°>«1XšRØs{Ü88ì0¸3ÏTúó΃~€£Ž &.‘XZ¸n¾Z´€ƒ†5kŠy¢>ÀóXѱ_KcbÑÊ#±Z•ŠÅ8ʃ~lÇz‹Ÿ£EÊSÊ?Äþ f;8k–3 Ø€}==k‹õT¢‰Ú$  Ð û4{x +€SÊlÛ#GBVÜ~;ì½7 ~½fÍàb‰•ÿþ:tȽoÂ:´'» ô3+V6¸˜È{`W&6íû$l:ù ¡­¸z`#Ý¿`SÊwa£Ý")(eG¸½÷Oyï‡zï‡wxïòÞ_ソÇ{5ÐK¾KqIŸa_4.~c•fãýe) U«fEÑj×¶çƒÃ“ˆH¬µkg#ÛÎYŠG±Y<ŲèTÚñÑîxÛ œ ¼Šlë ¬(ÁùîªbCgÝ€% O$H)›pçÑ›„9*ï ÞûÀ¿€ss.Áq‰ˆDçŸÀ ¬%Ø¡Ÿi*#Ã’èÿý/¼oõjÅÞ¹Óžt|ø!T­jÓÇ»t &V‘x?V¬€™3᪫ ^½bž¨>ö­ø¬È/$¦by%àØšíY@+¬ÓFqŒý‹€?€™% P$8©<¥<'‡­©†fçÕØî½W?INó€!ÀcØ´¼ÿaswÒPFœs¼÷¼ý6Ü{/ìÙcÉöŽVyüÝw­ Z§N0m´i•+¹ˆHìÅìfbMltlÔù ¬g÷??kÃó€¿G†¶’hÚÀÚŠ}…-Ý«„ç @ºŒp‚­ô圫’óç\gàràÝ ‰H Ö?õ@0Œ´ür‘3Ù+4`\x¡%ÛnýpÄJ¶E¤ôÈÈ€Ï>+æ›_ŽÂ¦•wˆYXù«‚ý½š„ú< ØXÂsþ‚u먎Ú,,á9E’ ·÷~ pV m•sn¦sn’sn 0;`ˆ""[Š%ÛobÒ^ŠS,'E,^ S¦}Ü”)m &"’®23ífãEAýúpÌ16ÍXðžs®0¸Ô9×Ý{ÿC1Šˆäòp 6üalAÌ€@#Jˆµkáæ"n*deA0}º’n)=ŽUgŠGr<Þ ,ÀF‹céelÔ¹/–p ìÃóÌó|k Ï-’@i1¥Ü9Wx øp(ð€÷~Öɯ<á& ""ÁˮǾ­Âz—¦©õëÃSÄwí W#/ÌÎv¬ˆHiµy3¼öZ1Þø=08 ›VÞ ØËÈ€þX¥¤±*âÇÇøüK°6c]ØïñkŒ¯!’i‘pc+Fjç{ïå|Á{ÿ!ðo¬f¢ˆHðfaýµ§m±)x£BÓÐçŸÃ¡‡ZÛ¯©S¡Y3˜1Ã~”)'}ú„×u7ib£Û-ZµˆH0ví‚×_‡3Ï´jåýúÁW_Ey’L,9=˜ lJ:M=§õØÈy`*°»‰Ke±›Ó'`­Í>Çæ±Š¤˜t™RÞØüQÀë+ι²ÞûX•r)ž±Û„ÇÿÂF"ÒT:°e‹U$¿àøúkhÞÜ’î^½`Ìû 0m dk¼[j½žˆ”B¿ÿnIvVVxßÛoCÛhnÊ„µ™Ìö°h“a –Ÿ \еìÚ/FçÎÖx.Æç @ºŒp‡}„ô(àõ‹€¥J¶E$p_`£ ã€Oùän}’†Z·†Ñ£íñš5pÅö¸ysëÉl{,|ó’m)½4€îÝ¡vm¸øbøä¸í¶bœè=,n‹ýݹ:†AÞŽù܈µ²;2N׉£´øZã½ÏpÎõ^îÏñÒY€Ç¾Ú>Dl""ªŒpÁ¾ü\ƒ­ÝŽG»–€Œ ×]g~®¹&üÚ¸q6­¼N`bIc1õ;ï¨øVlÝu¥ž÷lª÷©XQ³F%°Šâß×^›ûµ#àŸÿT4‘âª^N: š6…aÃìÆæwFy’±)Ùý±¿AM°êFÅõ>öwlo` ðïœ+YÀÀCØBÑ>ÀÇq¾¦HŒ¥EÂíœk Ü‹•‡h„ýÓÄ{¿ [µ²X€"RzµÎÅÖ¹eWsí T 2¨Ø¸þúðHËSOÁ;ZÌ#"SãÆÁÊ•pÿý6=´å”¸|Ž-Ê=X#Þã°éägPòŠç‘X€kˆÄQº$ÜÙcEÉ¿tCEJ6FD$:»°év•±Ñ…nØ•­AU|·ÝwÝ5j@ÇŽáJäM›Â„ V4m¿ý QD$í-Zo¾ ·Ü*Dø¦¹À€À|¬žÈ#Ÿø¬ÐûŸÁê‘TÄfoÅÛ¬xÚ¡­ íÔz‘¥Å”r`!V²¡_Þœ wit[D¦"ð_ ;p,p)v;°F€1•@§Nösóf0ÀF´³p‚’m‘xÙºÕ P|°Í(9&E35ü,I®ü¸²A<,擟ZŒóÇC¡k=|ÝÈI!i‘p{ïoO;çÞ'´ÂÃ9w V²èl}·ˆHü½Š’Ù ûb2+嘈©w1’•™™áç}úÀ AöxæLxøá`â)m*V„Ñ£áÛoí¹sV@-b¯ßb;¯Š2€ÀK¡÷uÆZe’¸y²·cÕÕ¿ÃÖ‘ß’ ëŠÄHºL)ë0ø6ŽT6´ïìÜùÞû)A&"¥LV¦ñyl:ù‘¡-E¬Yc£ØG£F…÷?ð̘‡_Xx""¥J… pưp!œwôí ûìÅ 2±)åó±iå €·ˆ¼'welIÔ·Xñ²Û€.ÀQÄP•°bm?䨮Áú‹¤€´I¸½÷›€Aι[ÖX ÃÀRïý®ÂÞ+"3Û °¥C±DûMà´ ƒú«±cᬳ A{ž‘>h=_„iÓ,¹îÕ+\‰¼re˜=jÖ ,l‘RéñÇ£X³×vàd¬oOGàV i„ïÝÜ„u×èÜO+[Ø›bl°/–µì‹µ7ÛŒnIi1¥<ßõÀ¯Àr%Û"’0ŸÍç€Ø4¾€ãŒ)#FÀàÁpôÑ6=|Ñ"èÝn¾Î9ÇF²+W¶iåÀ¦Má÷*ÙI¼b'Û5±‚ €Ç±9¡‘¶ÖúûÛv8Öær,V4‘š˰©íßcÍ£/ ´I¸såsc)ßs€-ι/œsm‚NDJ…ŽØ¨ö ¬OéRà jAå6b„ÜøñGèÚÕŠðL›fûÞ{Ï*’ßs=ÿåøôÓ@B‘|ìÙcŸÙW\[¶Dø¦mÀdàn¬W¤7‚auH–½€1بw"•Z`‹D?Å À½ŸàDJ mn`p1Vb pp=P˜åœë\h"’ö¦`k·ocUʇÑ_,_N¤ óÞ{Ш\y¥€Ÿ–dÓáEDJ£­[áê«¡qc8î8xâ x÷ÝßüVéû] !ð·Þ³«Œ4 [¬9 iÞ+úØKìTl¤»ö·õ?Ä RLi±†Û9×k¼s¾÷þå<¯=ŒÕ5†þyŠH€E$ÙyìA>Ánç½€%Ûh½v­µú2 ï¿÷^[ËwÙeá`"""¹´Æ¦•wÂþ¶U.¸¸ûÖ|NÁá7¬/ø1ØÍõá–² ·÷~¾sîlÇÀÀ¥Þû/œsÇ^Ë}«÷~b0‘ŠHRÛtŽò=³âHd¼·uÚ_~ _ ñÇÚkÎÁСÁÅ&""ÁY¶ &L°íé§¡cÇ<ŒžË³/Ø 8¬Yul­ôÍÀ§Ø ædrqhI!)›pxï_tνŒÝßú>»2yÈj`$ð^v."–_tF¶Ê´»vÁ™gZeÚC .& NF†­ß^° ¼ï•WòI¸–FqâÿÅ ¸Xû‰_Ú–ï`ÅÞD’TÊWíöÞgyïçI¶ñÞ/ñÞP²-"é¦[7xî9K¾wí‚ :" J… PµjøyûöpàÁÅW»—€ XÃß[FF$R¤”á) îº þøF ïëÛ×ÚÁ´o]º›ˆˆoð`èÞݪ•ï¿ÐÑÄQ=¬ªºH QÂ-"’Ww¬Ú  °(¸P·ÇÍšÁW„_»úê`b‘äröÙ¶E¥6bœÝÛ§°.¦aÅÇ,¬WÑ¡í0`x ‰*å§”‹ˆÄܧX² ðmÀ…BÆöxð`xÿý@Ñá½mªF8Ù«ž Æa½ŠVyת‹$%Ü""yÕÉñ8ày@ûìcIvÕª°g¼új°ñˆˆHòòf϶ŽMšÀŒ…\>ÏóTY ý,ððppr°áˆE ·ˆH^]€Øt»Æ‰½ô¢E0p ìÞÞסƒ%Ú×\Ï?ŸØxDD$5ìÙcë·>þýoX³^½7쵨ìÿcb3Öø÷q`p&65^$Ii ·ˆH^ïäxœÀ à ÀqÇÁ–-P¦ <ûløµ>}lÉOÙ²pÐAÖ»lY8æëjQ o±Qîr@&¶6:|œ†Ý hÚvð×{‘$¡„[D$Ix NÌŸãÇþûÂí·•ˆˆ¤Šk®N°jõëq°2Š8&uþ\ÀqˆDHSÊE¤tÛQô!‰Rµª­×nÑžÿûß°~}°1‰ˆHê8öX¸êª’íTVXLʧÝ|hD"…Ò·ˆ¤·®\yµ°ê­ ™ #FXq›½÷¶}õêÁGÁ€ðÜsáý"""ű};T¨f_úOVͱ)å‡þ„sY IDATŒHáÒêßžˆÈ_,~âøvÀçES±øádÛ¹Î8&M‚iÓl«TÉ^kÕ æÌ)ù5DD¤tÚ½>þ^yÞ}×~ö9k£©}ã] Ìj£¹º’”p‹ˆätP)þ—©T êֵǟçŸoÕdËè˃ˆˆ”€÷Vä§ŸÂû^{ ú¼XX±Uøø!´m® 4"‘髈HN ,ÂòôÓ¶Þà­·`âÄÄ][DDÒ“sе«=®ZúõƒóÎ 6¦˜{kÛÙ L4‘Bi„[DJ—#%ÀV #0/q—ž1jÖ„víìyùò–hwëÿ;œvZâb‘ô5dœ|²µ“¬R%èhâ /p<Ö,˼DâI ·ˆ”.»€Í¡Ç L¶Ç‡Ë.³iäsæ@Ó¦¶¿F kV¡Bâb‘ôÖ¡ƒmi«^hû•ð´ò.$÷ºs)µ4¥\DJ—oƒ¹ìï¿[›5k woؼ9üš’m‰·uë`GµÂ,‘L ÐK´ÿø&ЈD ¤„[DJ—ò9”¸Ë^{- l¿ùÆZ‰ˆˆÄÓæÍ6ÃêÄ¡Q#xóÍ #Š‘rÀ+À"ààg O ‰HSÊE¤tÙ‰ÝjôØ´8Ù²,E3V®´Q†ûî‹ßµEDDÀj†¬X~þÚkð·¿Nlú¹K¼3°Ñn‘$£n)]2,,áÞŸK¬YcbO: fÍ ï/[^}¦O·‰—ìbœ ÀÕWÃ-·OLj{€túÝ$­h„[DÒ×,±N°ï¿‡%KlÍöi§YŸíV­ìµ´¬+""I骫ìïÐ1Ç@™tf;8hÚjŽHAÒíŸ^ZpΕuÎXFÉ™"o–8çÊ9çØUX$É\lLüe»w·Û6X[–ŒŒÄÇ!""¥[«VУG&Û`m>Ïð%.Eá$­h„;É„éÿÕ€6y^«<œxçÜàzïýœ<ǵ w8°Å97¸Â{¿.þ¿H‚<ü·ˆc°Ös»pfç=¼xá,\íÛ‡Ÿ`ëµÿùOÛT‰\DD’Á?B:Ö–2¥ý œƒÕcÙÚ÷9–ˆ‹$%ÜÉçVì£bq>¯=œ < L†Sœs]½÷ œs×°f WcA÷3sí¼÷Ûãÿ+ˆ$À$àÝ(ŽŸ <4‹}(#GˆðÔSpÉ%áý·Þ ýúAË–±¿¦ˆˆH¤V­‚×_·:"óçÃØ±på•AGUB{c•É[-C?ë‘H¾”p'çÜ‘Xɇ5ù¼Ö¸è뽟Ú7X‰%Þ燽ûØiê½ÿ%tÜ2l¢ÍyÀ¸øþ"Iê\â’l¯] Éçýß“gM¸HZ©…}¢m¿cw»ã`Ý:[ÿV¶¬=ïÜÙîÈŸsŽ}yÈ®H.""’ .¿N?:t:“loÞlϯº*÷ëk×ZÒ=}z#ݵ€+°)å­ã®H‰(áNι3€ €ÎÞû¯ üšÏþì2 €ÝX™¨ × ÚØÖ®][àkµkצ‚*AI²hÌ =^4$. ÷¢E6e¼O»#ŸíÌ3í‹A—.iZ VDDRVãÆ¶%‹aÃÂÉvAÖ®µZ(oä7 •mPžç;KYé✫T.àå‚öK””pÌ9×x¸Ó{ÿE!‡V ÿfÙEЪ†Ž¡ã*9çÊzï÷D_Æ |múôétïÞ=ÒS‰Ä×7yž|¯¨D†µâ3=͛à 7„_ëÖ->׉µyó`Ÿ}liT¢½õrˆÍ+HË–0~|'ZŒÆ*•ÿ€UAú¸-#KCÿòzÄ9ƒL w€B=²ŸÅ>"þYÄákšùì¯ú¹’pŠQÐqÿ‹&ÙX²dI¯í³Ï>ÑœJ$¾2ó<߈dýW”^~:u²–_7Ýd_Vúõ‹ýuDDDbí¿ÿµJå&ÀO?Ùšé[n‰ÿu33aöløðC8ï<8ôP8î¿¿à÷  Õªqb 3uÅJ ·@ã²Ñ¹¸§€×îNO`,iK w°öNÄŠ¡­Î1•¼&PÖ9·xÔ{vÏ®m>稃Ý}úÙ{ŸéœÛHþMê?EàE–ˆI{aŸh;€ƒ°iåQÚ¾Î?ßéNlߊpÙeðüó6 P¿¾}a8úh[ÃÝ,•ÏEDDâáòËmd;Û„ ‰I¸×­ƒcŽ±Ç•+[³jz~Šz€CWJ]é*²üK~¯9犘ô/‘ÒJÃ`ýŒÄúk?žcûØzüyèØEÀ~ιFyÎÑ•P²ã¸.9pΕ:a#é"éi=ð…u±òÏÄöíÖ–äw¬PË7ÂwX±–O>îÝm=ÀAYeòÙ³­`šˆˆH*Èž‘uä‘0z´Ý@Ž¥_…뮳éâS¦„÷7jdI6À¤IðÌ30qbáçzê)û[[¤‡Ë€ã€æ@!£æ"AÐw€¼÷y÷;ç:ͼ÷9_{›ö1 ¸.tÜ¡@Or7Aø7ð–sîï}öGÙ%ØøßC1þD‚ãcwªìd{Ú4{¾cÜw_îc–. WLmкvÝõEDDá ­Èg¬fgýø#Ô«žú]¹2<òìÞ |Ç>¶W/k›9ožýõEüÏÌ„©SáÔS‹bÖççp 6%’D4Â"¼÷káÀPçÜgι—™À G­dÞ>Þpνéœûëí}¿÷þ«DÇ-oŸÆîtëÖÁ÷ß}Ü?Ús‘TT«V8ÙÎÊÊýZÞç…ùé'ØhÑ"÷(tõê¶ä ,áΩW/¨PÁfŒÝ}7œ}vuƒªUs¿çàƒ#æ%àul%ò¥€VCJ’Ñwrz™|ê+zïïuÎ}œŠµøº [ã•ã˜,çÜ)X鈮Ø$Û¡sФ‡@}l½öQ¡ÇY~ ñð‚_jÞÜF®<Ðî¨ä”SlžˆˆH*3&O†×^ƒÏ>ƒV­àÎ;¡iS5*÷±?ý}d-1³“õ&MÂˬ&M²BhÙN: fÌ€eË`ùr«663lÆðhø+¡µ×k×Úù½·B¤ÙêÖ…Úµ#øe–ËBÛ`Fäÿ-DâM wòÞ˜{ï§S z=tLðLhIë€Wk€o±EÞ%;u˖а¡µü*È‹/–ì"""A3®½Öתe#ÛuêX2œ-;éþþ{É»!}Í5ö¸|y8î8xûmKܽ‡ìÚ¿'Ÿl7±{÷¶óg+W.wÕñrå,éÞµ+<²}Í5V?eà@xà‹«Hs§° å­¢úÏ!wJ¸E$y\G¸³|~þLƪt®ýìQòK¯X«W~ÌüùZ»-""©+g² áiä9“í{BM¢F‚Ö­mÔ{Õ*ÉÎN¸Á¦ˆ¿ý¶-ËZ´(\í ƒ"/ÆV®œmÙn¹Ž=6\Ñ<"„6‘$¥„[D’Ç3DV]ü‡ÐÖ [¯UB«VÙ÷¢Ö¯rŠU]íØ±ä×I½zYÅð3`çN¨TÉö÷é?l#Ù-ZÄæZ+þ5ÙÞ½zÈZšåÛ—{0ð”òeÀõ@4I»H©hšˆ¤®õ±9M­Z¹×¤n]«¬*""’І…þý ?¦zõÜë¸{õ²ŸÃÊ•áýÁàÁ±K¶óóùçо= ·ÞZÈgË–äS I$8J¸E$u›ÓT«f[º„:Ø—-kS↠ƒ¡?Ú-[Úš´&MbsM‘ älÕ•Ÿ­[aÓ¦ðó^½¬Ú²eáõ܉0|¸ý]þæ{>}:ddäs`YlvÜ·XM—1@»DE)R4%Ü"’¼š`w©`…PJè믭çv¶5k gÏpÕÔ>²/3fÀâÅpÿý°`µ0Q²-""©nÏ7®ðcjÔ€mÛÂÏ«T WO¤ƒ²¥^+Z!µ ¬­X¾~Å*“? Ü‚µIZÃ-"É«>V(m3àJvª¹sáÄá°Ã`âD«¦zôÑV,­eK›¶vÀ–tç”=²-""’ꮸÂþÞfÓ&4ÈþV©o_+Æ6`@#룀GæXµr8•$¢nI^9ÖŠñKñO“loÞ Ó¦Ùšíúõ-ÙûrÑ®½&""’®ú÷·ë”+gm¹’Á]wý5ÙÞ¸1Ÿ®"wcý·¿>ÂF¹E’„nI^9‹ž4¬ë×Û4ñÍ9ªŸgdصœv튼‰ˆˆH*:öX¹ÎNºO;Íza_j«•Ýû쳃‹±0/¿l³Ñþþ÷€Þ½aÛ6Ûç”-kwų ƒÇæš"""?Ÿ—^ ß|cÏï¸F ½ø;°+¼zn0ñ‰ä¤U‹"œÉÀþÀ‡@{`^hÿüØœþ¥— eKX³>úÈŠ¢ué[¶X"þ裖|cÇæ?UMDDD’ÇŽÖÎ,;Ù>ø`8õT`*P;´ 6t‘ÄÑ·ˆÄÇdì.safÛ6Kº?úÎ:Ëzng«U Ú´… aëVÛ7hõç‘ôV»6[€ƒ¶Yn;•”vJ¸E$öž–ÇïôÞÛÏ„5`Ý:Ø´É’ðjÕ¬5ÈqÇA—.0v,8¿XDDD$94jo\uæBûêÕ6Ôƒ: ÂÇ,\•+ÃÄ9Gg YïXŽgAчIªQÂ-"±“‰M¿k†ùP+˜V”ªP¥ Lœ[]ñã-™xúi›6¾kT­jûêÖµµÞ{í¥d[DD¤49¡°¶7„¾ƒ©_Ãà7 S'X½N: *V„éÓãžtúFqü „;)á‘عøxøx¸[¿½Od§¨RZµ‚ùó >¦cÇÜÏË•³-§êÕ#ŒYDDDÒÇ5ð[›é¶hµíúôœ¿Ö£GB’nM‘º K´~.~ âd¬ù¢E…óâ‹ÅŒODDDÒÛz˜Ô®X“€eÀþù¶v-ôï^¦&/J¸E¤äž^Z³ @g`=Ð òÓlØÝ»ÃÎ*˜g»ýv%Ý"""’íдª ßc€ßó9¬|y¸ä[–‰—^‚ï¾ ?÷þõ/ؼ91KZSÂ-"%· øð P˜ü°wt§©SÎ8£èãZ´°Ä\DDD$—} ÜwðÓ¸ ¬Ëç°Ý»aذܵ^~ùÅ °nܘûØgž kWøðCX²Î?®¿ÞÚ—F˜t/ÎñØ_G÷‹IªÒnÉß uÔ,ê‡#€›ÕÀ¿€K‹wÙÑ£aÇxâ‰Üû6´?„-[Úš«&MŠw~IsŸÁ¾ïÛèvöä»QùÖ®]î0}_l—.…Ö­-Ù¾äÑ^¿N>9÷9æÎµ¤{òdëœRõÐ4Ç8„Û - 9\ÒˆnÉßVlZx4žÞ2€ŠÅ¿ôM7Y•ñuë¬ÅÀÛoÓOÂÈ‘J¶EDD¤`›î€æÓa7Ö¥tqÇÕ©“ûù¼yö³Z5»Á¿iÜpCÑë¼çεžßÆ|Ìz¨±wîçUó$Ü5qœ„õw‰Ôb|¿ž$ %Ü";G`…ÒJ¨ysXúó±|9üç?pÄÖÒCDDD¤0×ï ã¦}ܲe–LgO+Ÿ;×~¶oeÊ@ÍšðñÇödÏž‚ÏS¾<|pžsCÓV9žï ¬êßûýõ4½€.@ã¢#ÿÓH ¾Ÿ IB ·ˆÄNVìOÙ²¥m""""‘xì^h¼ Ö| ­€þÑ~ùví²c:u‚>'Û»wÃO?Ùãß«C _—ßBpÂï­U+üÜ9Ê¿ s&ܦÙçÌÿ4•±ùhnIJ¸E$vT†QDDDVîs1 ÖVƒïvCÛ3á§§à·íÖŸ»J•¿®¹._Þº¥,^lSʳy¿çWæ<}r·@m\\‡ä¿X15I3J¸E$r­•À. 0=ö—xÿ}xè!«BÞ½»Ýe._>öב4u"°öö°ïj¨ÕÌv×­ Ó¦Ù÷Šü œ•- mÛ†Ÿ{W]e#ØEÙ´ 5e{B›ÿEõ"àÐèß&ÉN ·ˆDnV à?ñ¹Ä¤IðÉ'¶H[µ*>ב4TX å~€f??ݰw-K·nµ"®Eq.„ƒ`Ø›ð÷_ʰ§”Ý„qË«dÔ²Vá€y^ÎÀFÊËcKÀ!<3]R„&€ŠHäjæx§Qçºus¯ÙιŽJDDD$"‡Ý€Û™X:¥êÕaêThÓ&¼¯iSèÜ9<ûÎ97Îúr‡<\ æ÷… ÃäP¶V,­A>[m¨àà%<7¿å ¡,áo\MúÑÿ4%Ü"¹#@ y|.1|¸U ]³^}† ‰ÏuDDD$Ív`³ó¦gï4u놓îAƒ`åJ˜=æÌ±bjãÆÁEã€\ºêiÞz´¿­ðKxà›Ð㼃àkó<Ï@Rަ”‹HäÞËñxI|/Õ°!ôíßkˆˆˆHšj ¬Ç¦“ÿl./Þ©êÖµ©å{í®jÞ¡ƒµ.­™=û¯°’3±üêB<–w¼³c±©ás€‰À™ÀaX²}‰÷ôòŠNñ+{aÓÌ«Ä j‰3%Ü"b^Àì;€ÉÀI@O¬ßv¯ø_¾MøòKØ´ >û fÍ‚CÿuEDD$ UÆcÅÌöª&àšÍgEV{Ï-ù¿ÀÉ…¾Ñ1<\g}1¢”SÂ-"¦pvGø>à#à®ÐþªYN=Õ6‘bëú¹ ø kºÏ5ò-|ç( Tð…ŒŽó€ x>Åáp\Šgpq¯+ñ§>Ü"¥ÝûÀ·ØˆöûÀ£À5Ø$¥Q¿ÐˆˆˆˆH†õ± Øí€›Kx¾N@ÙÐã¦%ØNb¦_S¦ÀoXuòîÝ¡AƒÄ\WDDDÒÌ*`uèq}àJ,9nŠ%Þsò_(¨fÌ–<Ïa±³¯SBÎqp=¶.à ç¸Ïû?¯’ϛؘ¼œƒç8ªxv–<*‰5%Ü"¥Õ"¬\Ç3ÀßÀÇXq´Ê;ïÀOØpä‘ðùç‰ADDDÒÀXàÞ(Ž?˜”ãùÕÀ¡À¥ù›3uðg©³â[K8Ùþ‹¾ ’p{~ÂÑÏW¡8úc#Ý£ûK‘Äœ¦”‹”FXòþ@VTäLŠšÈ7;v@ÅÎë×&)…®Æ„—^ =묮 6jÞ8+&W~+[ÛÕ{Úzϓ޳»Èwy¾ÂqŽ©Xíõ'‡c‘Äœn‘ts*ö¢°­:ð ð6ʉ}TPðiþ9²}ÅñôÓV|út>ŒÍyEDDDŠTp’}0“ÜcÏÙ2±¦]k±ïP%ä=[¼çï™YŒ·ß tÇÆÚo6áØcg[‚ç/J6%Ü"é&ØÁ–M…Z \‹}\WÏÿ”3gÂÁÈá}=­[Ã{ïÅ&ìJ•lýöˆpƱ9§ˆˆˆH‘ξÀÊ–…`¿L(ÎQÅ9:GpèB,—«T mår<λíãȶ&qùÅJ1­á)íÎÁî‹`æLèݶmƒ‘#aìX¨PÖ¬ ½ýxýuµñ‘$TköGèyeÈÕxë =е¾øÐ21Ί¡] \ ”qŽÆÞÿu,´"©3Ãë–zJ¸EJ»êØÚbñâp²mýúÜÇddXÒýÉ'Э[ô—6ÌFÊ»w·Ÿ""""1³r¥­y»\¯ ý|hzÜ?ô³^”תT¼>ÜÎÑ ˜NîÙÇ}±Ò¶’â”p‹¤»ƒ±uÛ_bm¾ªk"{ë@¯^Ö²«0]º@ǎчöÛoðàƒàCU>5²õܽzE.‘¿¨lÍñ<ï÷Þ¡Ÿ§åóÞ¯£¾ÚŒ¨ßa>ÇV‡7¦`m¿"¶ vU´UæU€Í»¡byûm%`ZÃ-’î~ÅÖ%y`°¾ðÃs*W^yªT)ü¸‰‹>&?Ë—CÍšáçkÖ@ÆџGDDD$_[—]8Ø'Ïë5Ñ_„*“_ä=ÇyÏÛ…öâÎGE[§]ûkz%ÛIC#Ü"énCžç.º·—+gͶo/ø˜â$Û;Ûõ¯¿†3`î\hÓ¦xçù‹5„göÍ 2ÂyŸ«xÔ¶»öí.þZd=ûRØpL`9–¤k¸#Ž4Â-’îÌñ¸Ð<º·?ölÜXø19«—G«Lh׆µÑô2úT‘RÎ9j8ÇaÕð­À¡Ç»uºK¶ÁJ×K¬è«­&$žA IDATHºû6Ç〥‘¿uáB¸êª¢9&OŽ60ÉÉ9Ú8ÇãÀj` 0(’÷5ö =.± oa·Ì¨ƒ”¨(áIxÈŠÝ)Û·‡#Ž(ú¸îÝáÀ‹<,—Ù³á¾û`Þ<ØÕ*%‘´5¸›ê pt,Ož±!˜]Àä ­õŽ;%Ü"ébÖ9qQìN9gŽ%ÄÙöß~üV­‚>}l_Ó¦¶þzàÀpµñHL˜7Þ:A­Zpþù±‹[DDDJ©»°*ä‘nƒ ³c±á“S¼§{ö ÎÑÔ¹h«ñäVšaà+ä^ú-q „[$ÕmÇšPÔ>"ª*äE©_zö ?_ºÔ’í&Mà’K¬XÚªUöÚŠáÇ‘X¾<üxëVøã‚‰H9¬j¤[ù`Â,Ä+ÀaX!³£ØTs¬ïÌç˜ô[x\’œª”‹¤º÷€ €÷žØ}ÑÁÀÍ@çÞß¼à—öÝ&M‚W_…k¯…ÇnÝìµÆ!#Ã=Õ£¸Gúþû–¤OŸn#äÇù{EDDDÒTeà }èùçøhÝ8 l9™WQHšû5€ –n‘TµûØí‡M#?˜\t%ê`é×z÷†;Âû:v„ý˦”ŸyfñÎÛ¼9üýﶉˆˆˆ”fÎQø˜p² °ppÞcçÁÙóàp¬<îXì[ß:l*zg`€›â³MSÊERÕ` 7Öân /ÐØ ´¥DÉö]wåžò P£4h{ß!M¶ÿøÞx£ø×)¼'x4ÂÃpŸ÷<†rÏZ{Ï߃€gg/±d½ò^0¡26|žßV. =¼+v¿‘€n‘Ôósèç“X²}:VgòIàe FÉN?q"Üv´ic‰wö´ñHÌk•ÍÏ=צ‰䡇à“O`ûö’Å*"""’N¼ç`J‡MÁ^zÏc@WïÙzž\\²Ìó-žÛ ÆN  í7¨†g'^mÂbM ·H*Ù„M¨‹M:Z‚M#/ Ä`ôÂ…P¦ ìÜi‰wûöE'ÆYY0|8}4,[fÕÊŸ}6ÿc7n´õà={B͚Х‹UC~-âõg¼ç4ïùs>¢Ï“({OV(ñÆ9ÎÇ*ýæ.ç8 XÑJ¡”p‹¤ŠÍ@Ml¢ÑµX ËæÀdàÊØ]æöÛ­GvÛ¶öü¨£¬yaʔŋ­Ÿv… pÿý0~|þÇ~ñE¸}ØîÝ0kTVH‘ìjäEUǹ-»zyç+ £èņ5‹#9§DGEÓDRÁDà `6pvßs 6ʇêÞ:Á‚0v, Ù{žx¶l±BjÙÉz~N8~û >ýÔ¦ù¥M_)ÍBÉñ[ØZêÂÜ *êœÞ“é=©„ –‡f‘Ó›# ¶æûÆb„-EPÂ-’̲ôÂ>"Ofb#Ü­bw©¯¿†C ?/WΊ¢Ejï½m]v^?ý+B£Fá}uêX±µâV7I7¡äøoØüŪò,þ/ŠóþæÇý{÷U™·qü{HHH @„€R!HG^tQ^EÖ¾‚eUt_QV]”µeÕem,b¬‹ ,ňéEz!@L!¿÷3™d’ ÈdÜŸëšk2çüæœg†“0÷œç<nèþèaFŠãpîõàŸwfwA—¢¥.å"%Ù¸C^”&ÑÀ€TÜÐ]³hv3n$&ÂàÁî5ÖEåÝwݳÝýûçt#ÿÌø ÷K tâ‡pOÃ,ºšQ¨Olfì÷l«G®ÁÕ–ÍQØ(á.!Ç© \ƒÛµc0Ý̶ø©k\‡Ûýc0ÇÌòý‚8ŽÓh æšÙò6_ ãœrüGÃ½ÒæzÜ«xªÏÿFP¤_•edÀã»øí·á³Ïàå—á¶ÛÎ|›û÷Ã]wÁ矻gÍ‚9s 6Ön¬Cˆ‹+šö‹ˆˆˆœOÌøÉÓ <ÍŒ•žÅ¯8Û€¹… Û¹¶{ÀϲÍgÑT9 :Ã]8Žó¿¸cM¿ t^Öz–ç®»XŽÛ…äZ`:0Áqœ°ÂÂà•W Ô)þÊ\|1üå/E»o‘’@£”—0Žã„â^±Ûèé^î\|yÏnÞªâvϽ,o]%ÇqBÌìÄé¶gÈ!®{衇¨[·îénJ ’JÎW$ q+ÜÅ:6Ö…üßÿvÏ8?þ8Ô¬yv#“ûÓ«tì³g\óÊ+îÀj""""R|Çé´)`õ•¸§ƒä,)p— žÑÆ'7ÃÍìÝ\«a~žî¹OñÔp’ºÔ„m€C‡¸.SÞ¼g€‹áO[l,¼ó C†@Ù²pë­ÙשÎpŸj½ˆˆˆˆDܲþøËr¸KÇq"É@Oà3{#OÉn܉¢òŠõÜo6³dÇq2€ŠÔzž½ &ö)r*y+KöGpÿäÍ÷Œ€iÓ–,À @П>Ýûd~®¼Rór‹ˆˆˆ'OÞÈ›9pg,пx[t~Ò¹¥À3å×t  ÐÏOØwäñ‹Ç ɳ¼bfɞǻq¯÷Ϋ á±ä´=ìȳl&îÕú$Ç‹nw™™pìXÎã_„Y³|kBCÓ¥;#î¾Nœ¢OÅæÍðüóE¿‘`Sà2ÏÈâ_M®f6©€Ò)¸g©;åyîÀyêz8Ž•«.¸"O§mžû;(Ò@}2™™î”[×_ééðóÏ0lt뉉”Øý‡…Á7ßø†yÇÒ¥}ëzõ‚çž l[DDDDD‚A]ʃ¯?î\ÛÿÚ9ŽÓ.ÏúÙf¶˜¬Æ:Ž3Ø Ç,­[®úW€?SÇywŽï7<Ï}5/D pw¤ñW€¸ì<é3ÎZvØž<Ù}œññ`æ>^¾jÔ€  zõÀµ£~}·Ky§NP¡‚ûsÕªðå—îœß={§Ÿºá\DDDDä|£À|·{î»xnyýXdfæ8ÎÕÀ'¸áÜÈÿkf›²‹Íl—ã8Ý€€žÅë[̬˜Î­ à^“½wÚ¯‘¸_ƒ´Êv·f¾a`ï^÷–[Z¼ù&üýïmOýú0w.”/Ÿs¶»W/wY³f Û""""rþRà23ó² ªÝ ´?ºï€šgÓ.)/ceÀý¸3ªßŒÛé?ÄíFýþûn7ë뮃Fà“O`ËˆŠ‚±c¡qcNköôÝ»á·ßÜP[»¶;YîÀíOH´lyv/ñtÕ«—Y›‚&¡9Oèn‘¢6È*ãöa0`Ј¡oÁð÷a°Ö`Ô—pdzðÍwÙÏG ýÿ‚d¼“5ìÝ <âΕ½p¡ï.áŠ+àÿp?ø ôèqòfÞ{¯{¦YDDDDDC[¤(†â^LàŽN^Þ}œ ‹ŸzS‡ÃwßåùnÊ–…-μ™Ù¡;5Õ ø­[»!»C…m‘@Ón‘Ó•å¹OÞ>Æí|ܶ ^{-gµã¸óg¯Z•3:¹ˆˆˆˆˆH6n¹`¥ïÅqü°RÒý× <ól?û,$%嬫VÍòKDDDDD$/n¹pä Ô‘™¾£³ü?-$FvGïÕ 23ý׉ˆˆˆˆˆä¦QÊåü· ¨ –N®Å¿ÙScR ¹Ö;æö*èÙ֯ϙc[DDDDDäTt†[ÎoI@}`:8y®Ñ.ç¹O¶çzœí»ï|+l‹ˆˆˆˆHa(pËùé.à "Ðø[þϴٔ꿻G•*ˆˆˆˆˆˆœ1n9¬Ç ܾãÙ!ûqÏòý…Û\ãÆEÕ0¹)p˹/{³»=??,¾e)0·&dý”Ö‰ˆˆˆˆÈJƒ¦É¹í}àŸÀ"à¯@]`иŽü®Xã™R»7$‡}ûN½ÙÄDèÑ.p͑󟷜{¶ÿž:w_½€ÀÓ¸û (÷+$>¿üýçÂØ±p÷­îä©^æ| h49 êR.玞{xø ¸øð4¬^ ÷ì‚-{q‡¯N7xúi÷i‡ÁªUðùçaaðÊ+°`ÜíéŠ^½:Ì™£ÉEDDDDäìé ·œR€&ÀDàZ`0¸Ô…×o†·ÖÀ'`‹åÌ©}Ýuв%T¬×^ Íš¹¡ÜùµZ·†† ¡G…m):Ã-%ÛmÀg¸swý‰œ‘LJ+p§þª¬†^£ÝU‡ÁK/ùnföl˜6Í Ûàíì°íÞ{¶EDDDD¤è(pKɳøÉósyÜk² xXûÆÂ#à‘Òp$ÂSw)\s{&àÕW!%%g“eË""""""R¬¸¥äHõÜÿ BNÈ^ƒ{½v ` ¤~ÿþ7Œ> £fûnâ©§ kW÷lvùòÅ×t‘¼¸¥d ´Æ ÙÃqCög@ 8ú?p"»+ùëð´há>|õUHJÊÙLÏž0s&´Ó”^"""""d Ü<[€qCö5À¯ÀÜ3Ùw‚€ûî…&ŸÂøDÏs<Ãüe<~äLšT¬­9- ÜRüVY@ð&0¨†;ŸöH¼g¹š°ñgØ|†NÉ&{Ș1#gJ/‘’D[ŠW Ð ø¸¸ x È‚}!k ð£gÝWðÐßݧ9£Gûnê7 {÷bk¹ˆˆˆˆˆH¡(pKñ¸˜„;êø`rÎd?l‚oÿj^ Âà@ýœ§uïmÛº?ÿð˜s»EDDDDDηÎ/À<ÏÏ‘²ÿ؄ە<Ñ-!=Öþ£FùnæÅáóÏÝÀí8ÅÖz‘³¢À-E/{þëqÏfgáNïµ ø EÂO!}ާn ´ íÛ»ÿõ/Ø·/gsmÚÀõ×SÛEDDDDDŠˆ·­·q§÷ÊÂ=“½˜TîŒ' V-h»žÊ3OöÈ‘î}BìÙS|M n9{[!¸!û`îõÚU<ËGzÖ ƒ° qc÷áë¯ûžÉîÐfÍ‚+ I“â} """"""EM[ÎÜàP·ûøD ¸ø;N„ÁšR¸ÝÌã€+੧ܧ§¦Â /øn²K]§-"""""ç‡Ð`7@J‡÷–ÞÇ„÷îÏÕ\ õýÔßÜtþܲŸúâv%þ~ ŒžeÊÀæ ÷„7@§NîÙìíÛ!11€¯KDDDDD$ˆÓÝ»ÃÌ™›6ÁEåÔgfB¨F ‘󛺔K>:Ã-ù$g^>©Pà>9ŽÓÔqœŸ€tà€ã8“Ç©ìv‰ˆˆˆˆˆ\H¸Ï3ŽãDŸÕ{<·VÀ÷ŽãD³m""""""îóÏÀÅ@S3{ËÌ&}€ú@ß ¶¬JJJb„ dee»)çŒeË–ñÃ?»~9r„ &žž즜3V­ZÅìÙ³ƒÝŒsÊ´iÓØ´iS°›á׆ ˜1cF°›qN™5kk×® v3Ω©©L˜0ÔÔÔ`7ůyóæ±bÅŠ`7ãœqüøq&L˜À¡C‡‚Ý”sƦM›Jôå6ŽãÜâ8Nå`·Cr(pŸúßšÙ¾ìf¶ØÜ´V•P¿þú+ýúõ####ØM9g¼÷Þ{¼øâ‹Án†_»ví¢_¿~úàP“'Oæé§Ÿv3Î)=ô3gÎ v3üš6mÆ v3Î)?þ8_|ñE°›qÎØ¿?ýúõcÿþýÁnŠ_Ï=÷üq°›qÎHMM¥_¿~lݺ5ØM9gÌœ9“‡z(ØÍ8™O€Ë‚ÝÉ¡À}q§Pð7¬ìzÜ3ß""""""R BƒÝ)R1žûƒ~Öâ »ÁÉ“'¸.66–°°°Ân²DY½z5óçÏ?ç_KqÙ½{7III%²[ù¶mÛX´h±±±gµ­;w’œœ\"_gQÚ¾};)))çýë,Jiiilܸ±D¾g›7o&55µHÚ–ššÊæÍ›Käë,JGŽaëÖ­çýë,*Ù³˜,^¼˜;w¹5ù%''³sçγþ÷ÿLf_8ŽóPè Œ4³UAm™ˆˆˆˆˆÈDûº†[DDDDDD$¸EDDDDDD@[DDDDDD$4hÚÄqœV@3{¶€õ½€-Z´?~<—]vY¾š´´4>úè#¶lÙBƒ èÚµ+U«VÍW·k×.&L˜@JJ -Z´ K—.”)S¦¨_’H¾þúköîÝËwÞ™oÝñãÇ™8q"k×®¥N:téÒ…š5kæ«KJJâ£>bß¾}$&&Ò­[7Ê—/Ÿ¯nÍš5|ñÅdffÒºuk:wîLHHH¾º™3gòÃ?C‡hÚ´iѼX¹à½ûî»ÄÆÆrýõ×ç[—ššÊ‡~ÈöíÛiذ!ݺu#..._ÝöíÛ™8q"G¥eË–téÒ…ˆˆˆ|u ,`Ö¬Y„‡‡Ó®];Ú¶m›¯&++‹©S§òË/¿P½zu:wîLݺuóÕ=z”?ü;vШQ#ºuëF¥J•Îð]ɱ|ùr¦L™ÂÓO?],ûûúë¯Y´hqqqtêÔ‰Fù­›={6?ÿü3´oßžŽ;KûäÂã8N_ ÄÌ>.†}EâÒœ¬f™ÙÞ\ëÛõN²‰ef¶<°­ "3Óí¸¿ë X?0`SDDÄö7nœå–––f—]v™vÅWX\\œÅÅÅÙ’%K|êÖ¬YceÊ”±ÐÐPkÛ¶­EFFZË–--))ÉDŠÃÂ… -""®»îº|ë²²²¬C‡X£F좋.²råÊÙ¬Y³|êöìÙc•*U2Çq¬uëÖeõêÕ³mÛ¶ùÔ}óÍ7beÊ”±Ö­[[hh¨Ýpà –žžîS7|øp¬zõêÖ°aClÔ¨QEÿâå‚óé§Ÿšã86tèÐ|ëŽ=juêÔ1Àš7on+V´øøx[¹r¥OݲQ QIDATeË,""ÂJ—.mmÛ¶µˆˆkß¾½:tȧn̘1XÅŠíŠ+®0ÇqìÁ´¬¬,Ÿº›nºÉ«[·®%$$XXX˜Mš4ɧæðáÖ`€µhÑÂbccí¢‹.²Õ«WÑ;#ª½{÷Z5¬jժŲ¿{î¹ÇKHH°ºuëZ©R¥ìÍ7ßô©IMMõþ^4nÜØâââ °x XÚ(Å&ÑJÆçþn@&0¾ö¬öäˆ%À>ÏíŠ\5oyÖt{<ØïY@ߣ`7@·bøGvé¶zè|Û³Þ€€P3K2dˆ•*UʶlÙbÙ†n±±±öí·ßš™YJJŠ5oÞÜ.¹ä¬¬,»òÊ+­yóæ¶nÝ:33[¹r¥ÅÄÄØwÞi"öÞ{ïYLLŒ~÷˜1c,,,Ì>ýôS33KOO·=zXll¬ýþûïÞºÿùŸÿ±K/½Ôû…Ò¶mÛ¬FvõÕW{kŽ9b5kÖ´k¯½ÖvïÞmff3gδŸ0½téR µ'žxÂÒÒÒÌÌì‰'ž0À~þùç¢ä‚™™i/¼ð‚…‡‡à7p?øàƒV¥J›3gŽ™™%%%YãÆíòË/÷ÙN³fͬmÛ¶¶qãF33[²d‰•+WÎî¿ÿ~oݶmÛ¬\¹r6xð`KII13³·ÞzËûì³Ï¼uS¦L1ÀÞyç;qâ„8qÂn½õV ³ß~ûÍ[wß}÷Y||¼Í›7ÏÌÌ8` 6´¦M›Ý›$œï¾ûÎ.½ôRŠ,pÿý÷¶`Á¿ëæÎkŽãبQ£,##ÃÌÌî¿ÿ~sÇ6lØà­{þùç ðù¿çÞ{ï5À¾ÿþû"i§”A Ü€Üñ|¶_Û zµ Xÿ t÷<Ž~~%gF¬Š@-?·Ÿm@\0ß·€ÿ»»ºø^ðüÂ͸+s-Küí·ß,$$Ä}ôQ33KNN¶ÐÐPûóŸÿl¹Mž<Ù›1c†™™}õÕWØÔ©S}êî½÷^+S¦Œ%''›H ôîÝÛëÛ·¯U«V-_à>qâ„ÅÄÄØ7Þè³|Á‚x{u,]ºÔ{ýõ×}êž}öYsÇ6mÚdff/½ô’¶jÕ*ŸºnݺYíÚµ½_D]{íµo™™™ÞšßÿÝ¢¢¢lÀ€Eóâå‚’––fÍ›7÷žÙö¸÷ìÙc¥J•²aÆù,ÿý÷}>äOœ8Ñ›>}ºOÝ€¬|ùò–ššjffC† ±ˆˆˆ|g½¬[·nÞÇõêÕ³¶mÛúÔlÞ¼Ù{öÙgÍÌlçÎVªT)ûë_ÿêS7nÜ8 7"'3räHsÇZ¶liW^yåI÷±cÇlÙ²eöË/¿xƒrAÚ·oo×\sßuíÚµ³ºuëú,;xð ………y'ÓÓÓ­jÕª6hÐ Ÿº}ûöÙÀmæÌ™§óòäÜìÀ=Ïó¹ÿmOè’Úò@ vv0. .Ò³Íûý¬«d¯åY~£ç9ÝO²ÝÁž³ð-ƒùžÇMƒ¦ÿj÷q»wøð\sј‘{yåÊ•iÒ¤ .`Ñ¢EdffÒ£GŸçwëÖ À[÷ã?ø­ûý÷ßY¹re¼$ÿöïßÏĉ™0a‚ßkO×®]Krrr¾ã³uëÖDEEÖqlf,Z´È[wÑEѰaÃ|u›7ofß¾}Þº.]ºø\×I»ví¼û)Œ´´4J—.ͬY³x饗üÖ,\¸¬¬¬Óú»]ªT)ºv횯.%%…µk×zëZ·noƒ®]»z·uàÀÖ¯_ŸoŸ \rÉ%Þº œVÛD ã×_eôèÑüðÃT¯^½Àº·ß~›ØØXš6mJbb"•*UâÃ?,ôþ222øùçŸéÞ½»Ïò˜˜š5kæ=Ž—-[ÆÞ½{½Ç{FF©©©ÄÅÅñïÿ;ßïžÈYˆ®5³»pÃl>Žã”qçMà0جv'ñ ö×( |›gùLÏ}ëÚ¼Œ1³Eg°ßsŠ÷ùïf3cf' X;xÞ¼+âââØ³g;vìÈ7˜Mtt4aaa>u‘‘‘ùHË '»N$¾ÿþ{n¾ùæ×oß¾Èg/Û»wïIë²ãÜuþ¶•»îèÑ£$''X—½-‘ˆ‰‰aþüùtîܹÀš‚þng?Î>övìØALLL¾þòï;vì(ð8>rä©©©ìܹÓï>³ëroË_]Þ}ŠÆøñãyøá‡)]ºt5~ø!ƒ¦OŸ>,^¼˜Ý»ws×]wqûí·3cÆŒŸçϾ}û8vìØ)÷ìß‹¸¸8z÷îM… ˆŽŽ¦mÛ¶Þ/´DŠH33ûÏ)jÞúãŽñÔ÷¤Ü,Çq*r5<÷ûs/4³#À1ÜœáÏ¿€ãÀ…Üß9Iû'‘}ªâ`Þ±±±üöÛo¤¤¤x—ª® À['Žãœtý©ŽãìG)))”*UŠ *ä«|êNv¼ïÝ»÷”ûLNN&##ã¤í9‡ò{¡¡¡DGG{ãC‡ò863>|Ê¿ïí3{Yî}ú« §L™2 ÜrFNõÀc=F57nÍš5#>>žÑ£GS§N†À† ¨Zµª÷¶páBfÍšå³l÷îݧýJvà4hûöíãí·ßfèС¬^½š:pð`¾`"gäTŸûÇiˆ;šø'föŒ™í0³5À¸×Y?ì©ì8Î^Çqö[<Œ ¢££(W®ÇŽË·¼uÕÞ:‘`È>ŽýÜŒŒ ïwåÊ•#++‹ãÇæSx»Ô–+W®Àme×jŸ‘‘‘'=#r¦¢¢¢€‚½ìã8**ê”DZã8”-[ö”u…ÙçéÔ‰¥ýû÷³sçNºtéÂ;ï¼ã³.>>žE‹‘••E… øÓŸþä]÷ÑGÆM7Ýä]V¶lYŽ?œú8Îý;2þ|n»í6Z·nÍM7ÝÄk¯½Æˆ#Šò¥Š¤)îÀjGÇ’g]2ÐÄóój`¼ççPÜ þî(äàÎ|99"çÃRŽpÀ_ ~÷ì÷k…lû9K[v{îó}=›””DíÚµ¨V­@¾oa³²²8tèO]rr2fæóMsRR€·N$.ºè" çxÌ-))‰6mÚøÔQQQ>ËÀ‹ÃqœSïÙד÷ïßß§æÚk¯%::š¥K—žÝ 9}yçY·8`f??€w¼§‡¯Ì,oHö›#Ç)…; Úæ<ËÃqG<ŸffþΊŸ—Ô¥ügf©@ P3ﺭ[·zrvÙ¶m›OÍöíÛÉÊÊò©;qâ„÷?µÜÛn ®ìùyãÔÔTöïßŸï ¦¼uyãjÕª±{÷n233óÕ•-[–*Uªxëòn+»N¿(ýÝÎ{_tÑE=z4ߪþêNuW®\™ÐÐÐ|uYYYlß¾ý”ÿ§èÿ ¤ì¿íþóŸY·nß[åÊ•O{{aaaTªT锿Ù;»ÇS¶BCCó]¾$@Ù¹¯™Õ÷sëUÈíeàÏ›#.ÆÍ™[ò,ïˆ;mؤBî眦À-S€^Žãxûµ.[¶Œ­[·rå•WФIêԩÔ)S|ž8uêTBCCiÙ²%½{÷&$$Äo]BB‚÷?;‘`¨R¥ W^ye¾ã󫯾âøñãÞã½G”+W._Ý”)Sˆ¥AƒôéӇÇ3sæLŸº©S§Ò¶m[ïã>}úðßÿþ×{½¸×ô-Z´È»O‘¢Ö²eKªW¯î÷8§yóæÜpà 8Žã·®AƒÄÄÄpã7²bÅ ~ýõWoÍï¿ÿÎôéÓ½ÇqHH×_=Ÿ}ö¹/%üî»ï8pà€·®M›6ÄÇÇûÝgDDW\qE½ "9*W®L5˜2eŠÏñyâÄ ºtéB—.]ÈÊÊÊ÷¼°°0ŸË‹rëÓ§ÿùÏ|.§[½z5ëׯ÷ï­Zµ¢Zµj|ýõ×>Ï5k䪫®*Š—'r:–âN×uSî…ŽãÔrg£ã8# xÞ1üz¾ØüOžå7zêʳürÏý.$Áž—L·â»Óð?wÜ.$S€üSƒ ¬mÛ¶Þy„ÍÌÞ|óMì©§ž²Í›7Û'Ÿ|bQQQöØcYn}ûöµèèh›8q¢mÞ¼Ùþö·¿™ã8öí·ßšHq©]»v¾y¸Í̾üòKìþûï·7Ú×_m•+W¶;î¸Ã§nèСfo¿ý¶mÙ²Å^}õU µwß}×§®iÓ¦V½zu›9s¦mذÁ dááá¶nÝ:oÍž={,""Â:wîlË—/·Å‹[Ë–-­~ýú–žž×/ެ¬,¿óp›™½üòËVªT){þùçmË–-öþûï[™2eläÈ‘>u×]wÅÄÄØÔ©SmÓ¦Mö裚ã8Þ¹ºÍܹããââ,11Ñ~úé'[¹r¥]}õÕV¥J;xð ·nÑ¢EØm·ÝfëÖ­³9sæXBB‚õèÑÃgŸ£F²R¥JÙèÑ£mË–-6~üx‹ŒŒ´çž{®ˆß!¹õíÛ×ï<ÜŸ|ò‰Ö­[7ûòË/mÙ²e6hÐ ïç›ÂÚ¸q£…„„Øu×]g«V­² X£F¬E‹–™™é­{íµ× °{ï½×V­Zeÿþ÷¿­N:V¿~}KNN>«×*%JPçáÎ}ý&{¼Ÿåãqô[@3 °wÔðvg°ŸÁ¸!þI ¸÷ÚígýÔŽ~Jûý)Ö‹`7@·büÇ. p{Ö] ìõüÂXãÆmýúõ–[VV–=óÌ3iÙu½{÷¶´´4ŸºÃ‡ÛÍ7ßl!!!Xxx¸=ýôÓ&Rœ Üffo¿ý¶EEEyãN:YRR’OͱcÇlÈ!Vºti,$$Äî»ï¾|ÛÚµk—uèÐÁ»­òåËÛøñãóÕýøãV«V-oÝ¥—^j‹/.š+´“î'NØO"üÔþ,ö{SÜ7ÇóâErK–»"""""ç¦À/Án„”,º†[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDä4¬\¹’éÓ§“ššêwýÞ½{™>}:ÉÉÉÅÖ¦ùóç³dÉ’bÛߙرcï¾û./¿ü26lvsDDDŠ•·ˆˆÈix饗¸úê«yä‘Gü®Ÿ;w.W_}5+V¬(¶6ÝsÏ=<þøãŶ¿ÂÚºu+‰‰‰ 8#F(p‹ˆÈG[DD¤Þxã æÌ™ìfœ¦M›ÆÁƒùì³Ï8|ø0×^{m°›$""R¬¸EDDNSéÒ¥©Zµ*äèÑ£Ù‡™jy j « m8p€®]»l%u»""" À-""rÚBBBxóÍ7Ùºu+ÿ÷ÿwÒÚuëÖ‘˜˜È·ß~ë³ü‹/¾ 11‘mÛ¶0iÒ$Ú´iúuëèÞ½;QQQ\vÙeŒ;–¬¬,†NÍš5)W®}úôáàÁƒùöõÒK/‘@TT]»võÛ­}ìØ±4iÒ„ˆˆj×®ÍC=Dzzºwý7ß|Cbb" , víÚÔªU‹/¾ø¢À×·hÑ"ºwïN… (_¾<;vä‡~ð®ÿÃþÀo¼À•W^ÉwÜqÒ÷iöìÙ4k֌ʕ+óÚk¯°oß>† BõêÕ §qãÆ¼þúëÞüꫯҬY3Ÿëê¿ûî;½ûÎvà 70|øpÀ½¿GDEEM«V­˜:uj¯UDDäL)p‹ˆˆB¯^½¸í¶Ûxë­·˜5kVuiii,_¾œC‡ù,?xð Ë—/çØ±c€{xñâÅtìØ‘zõê1zôh"""¸ûî»éÙ³'3fÌà/ù  `êÔ©ù®Ùþïÿˈ#¸å–[xâ‰'X±b­ZµbÍš5ÞšG}”ÁƒS£F ÆÏ<À{ï½G÷îݽá599™åË—Ó¯_?êׯÏ%—\B5ü¾¶+VЮ];–/_΃>Èßþö76mÚÄUW]å é¤uëÖÜwß}ôíÛ÷¤ïÓ€ £mÛ¶T­Z•ŒŒ ºté¸qãèÔ©cÇŽ%::šû￟»îº €Ë/¿œ¥K—2wî\ïö¾üòK–/_Δ)S¼ËvíÚÅçŸNƒ øý÷ßéÞ½;ûöíãÅ_äwÞ¡bÅŠôéÓ‡éÓ§øï)""rFÌL7ÝòÞMDD|ôïßß"""ÌÌìàÁƒo5kִÇ›™Ù'Ÿ|b€Í;×ÌÌ–.]j€Mœ8Ñg;ãÆ3ÀÖ¯_offcÆŒ1À† æ­Y¸p¡eÇŽó.oÓ¦]~ùåÞÇMš4±ÐÐPï¶ÌÌöïßoQQQvË-·˜™Ù† ,44Ôn¾ùfŸv|õÕWØ|`ff}ô‘Ö¿ÿS¾íÚµ³øøx;räˆwYFF†5nÜØjÖ¬i'Nœ03³#F`iiin+û}jݺµeeey—=Ú›={¶Oý°aÃÌq[²d‰?~Übbbìþûï÷®oܸ±ÕªUË"##-==ÝÌÌÞ~ûm µƒÚ‚ °)S¦xŸsøða»å–[lÒ¤I§|í""'‘hÁÿ¯[ »é ·ˆˆH!ÅÄÄðæ›o²mÛ6~øá"ÙfÏž=½?7mÚÔ»,,,Ì»¼V­Z$%%ù<¯}ûöÔ­[×û¸R¥J\}õÕ|÷ÝwÌ›7ÌÌLzöìɺuë¼·‹/¾˜ˆˆæÍ›ç³½=zœ´'Nœ`Ñ¢EÜ~ûí”+Wλ¼téÒ 4ˆmÛ¶±yóæB¾zèÖ­ŽãxÿøãÔ¯_ŸN:ùÔÝ}÷ݘsçÎ%44”k®¹†3fðÛo¿±råJ}ôQÒÒÒX¸p!ÿùÏèС1114lØÈÈHî¾ûnFŒÁÒ¥K‰ŠŠâ“O>ᦛn*t»EDDNF[DDä dw-çwò]§}&.¾øbïÏ¡¡¡\zé¥>5ÙËsó×í;>>žÝ»w“‘‘Á–-[·‹wƒ ¼·&Mšžžž/תUë¤íܶmÔ¬Y3ߺìeg¸ónï×_õyO²Õ¨QÇq¼ûèÕ«ëׯgÛ¶mÌš5‹²eË2`À*W®ÌìÙ³9vì³fÍ¢wïÞDEE1{öljÔ¨ÁSO=E³fͨY³&>ú¨Ï5í"""EA[DDä ½öÚkÄÇÇsçw’’’â·&33Óçqrr²ßºÒ¥KŸQ¶oßžoYrr2•*U",,ŒèèhÀ(lÏž=ùnŸ~ú©Ïsý…úÜ*T¨ïÚôìýÔ®]»Ð¯#ïë¯P¡‚ß÷ôСC˜™wÙ½¾ýö[fΜI‡ £sçÎÌž=›yóæ‘ššê Ü­[·fñâÅlݺ•—_~™øøx^xán¿ýöB·[DDäd¸EDDÎPv×ò;v0räHŸuÙÝ­óìõë×i–/_îó8++‹ùóçÓ¨Q#êׯÀªU«¨Zµª÷Æc=vÒßü‰¥R¥JÞ.ë¹}ÿý÷DGGS§N3|59êÕ«ÇêÕ«óu¡ÿþûïœn÷ÑÑÑtìØ‘™3g2gκté¸S‘ýôÓO|üñÇ4oÞœêÕ«ðÃ?0hÐ vìØAÍš5:t( .¤cÇŽEÒSADD$7n‘³Ýµ|×®]>Ë«W¯Ntt4cÆŒaïÞ½œ8q‚/¾ø‚÷Þ{¯H÷ŸœœÌˆ#03öìÙÃ!Cظq#Æ Üé¹5jİaØ:u*™™™¤¤¤0xð`ÆODDD¡÷9lØ0ïèé{öìáСC<ÿüó¼ÿþûÜzë­>×bŸ©‡~˜ôôtn¾ùf-ZDVV“'OfèСԫW-Zxk¯¿þz¾þúk¶oßîó»k×®?~œ>øÀçìvdd$ãÆãÑGõ~²yófÖ®]K«V­ÎºÝ"""¹)p‹ˆˆœ¥ì®å¹EFFòÜsϱaêU«F… 4h/¼ðB‘î»G|øá‡TªT‰Úµk3nÜ8žyæï l¡¡¡Lš4‰ *ЧOâã㉋‹ã‹/¾à™gž¡W¯^…Þç<À}÷ÝÇË/¿L­Zµ¨Q£=öüãùç?ÿY$¯ë²Ë.ãý÷ßgÉ’%´jÕŠÊ•+sÓM7ÎìÙ³}lëÕ«iiiT©R…Ë/¿p¯ ¿ä’KÈÊÊò ÜÍš5cĈLœ8‘¸¸8jÔ¨A:uˆ‰‰a̘1EÒv‘lŽ™» Rò$˂ݑ’dݺuìß¿ŸöíÛû]¿iÓ&vìØAÓ¦M)_¾¼wù¯¿þÊ÷ßOLL ;v$$$„¥K—ÒªU+"##Ù½{76l M›6„‡‡{Ÿ7wî\jÖ¬IBB‚O’““iÓ¦ ‹/¦|ùòÞÂRRR¸êª«ü^C‘‘ÁüùóY³f *Tફ®òv³Ø·okÖ¬¡Y³fDEEö{òóÏ?SªT):uêDµjÕ|ÖoÛ¶-[¶pÕUWQª”ÿïø=ÊâÅ‹iРUªTÉ·~ÿþýÌŸ?Ÿ½{÷Ò¶m[5jä÷ úüùó)[¶,Mš4ñ.[µj䪫®ÊW¿iÓ&æÏŸÏáǹüòËiÑ¢‘‘‘§õºED Ðø%Ø’E[üQà)nÉG]ÊEDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$¸EDDDDDD@[DDDDDD$þu!¨#aøßIEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/read-medium-psyco-nopsyco-comparison.svg000066400000000000000000001357311416254111300306520ustar00rootroot00000000000000 SVG drawing This was produced by version 4.1 of GNU libplot, a free library for exporting 2-D vector graphics. 0 200 400 600 800 1000 1200 1000 10000 100000 1e+06 1e+07 Speed (Krow/s) Number of rows Selecting with medium record size (56 bytes) No Psyco Psyco PyTables-3.7.0/doc/source/usersguide/images/seq-chunksize-15GB.png000066400000000000000000002113751416254111300246770ustar00rootroot00000000000000‰PNG  IHDRÐß}™SsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î< IDATxœìÝwXTGàß"½#UP°ÒA Fņ]cà ‰-hb0v£“¨QQ£1Fcš-cc!"‰ÞC”.½·ùþ€½ë.,K<ïóì“0wî¹»ër˜=3ÃcŒ1B!„BEª½;@!„BHGB4!„B! šB!„ PM!„Bˆ(€&„B!D@B!„"   !„B‘ЄB!„H€hB!„B$@4!„B! šB!„ PM!„Bˆ(€&„B!D@B!„"   !„B‘ЄB!„H€hB!„B$@4!„B! šB!„ PM!„Bˆ(€&„B!D@B!„"   !„B‘ЄB!„H€hB!„B$@4!„B! šB!„ PM!„Bˆ(€&„B!D@BšŒ1†´´4>>íÝBH šÒ$çÏŸ‡±±1ºuë{{{XZZB]]ÖÖÖ¸téR{w¯M?›7oyìîÝ»8tèÊÊÊštm:tEEEÍéb›KMMÅ¡C‡ÒÞ]iùùù8tè&L˜€»wïŠ<ÿôéÓxë­· §§‡I“&áÚµk¸t霜œÏÕÛ½{7¦M›&t~EEœœœðõ×_ »ÿ>fÍš…Þ½{ÃÐÐS§NE`` @¢¢"899á»ï¾C`` fÏž XYYáË/¿DUUWwõêÕ¸yó&€ÿËNNN¸ÿ>à»ï¾ƒ“““@ FLL Ö­[‡B[[FFF˜8q"þùç‘ÏGc-_¾k×®Ezz:–.] sss˜ššrÇÓÓÓñÞ{ïÁÆÆÚÚÚxë­·ðÓO? ]çŸþÁòåËaee…®]»ÂØØóæÍCLLŒÈv=z„É“'COO#FŒ€———DýÎÎÎÆ‘#G0kÖ,èèè@__Æ Ã?üÀÕ‰ŠŠ‚““®_¿ŽK—.a„ ÐÓÓƒ££c½)C999øðÃaggmmm >GY·  6l€½½=´´´0dÈ8pÕÕÕBucbb0kÖ,èëëÃÞÞ»vícL¢{&„tl@B$Ö³gOäççãøñã"ŒWýúë¯ësïÞ=x{{c̘1HIIÁ¬Y³0aÂ@@@¬¬¬ðÃ?@__+W®Drr2\\\°iÓ&ë?~¡¡¡prrÂÖ­[annŽ«W¯¢ÿþˆŽŽ¨ëëë‹‘#G"44+W®Ä¸qãàéé‰Ï?ÿ¼Ñýž5kÜÝÝ¡¨¨ˆ­[·ÂÝݽzõx.rssqãÆ |ýõ×X±b €µk×"33o¿ý6Ž9"pͨ¨(XZZâðáÃÐÔÔ„››²²²àææ†÷Þ{O nrr2ú÷ïýû÷C^^ï¿ÿ>÷þ›?¾@Ýàà` <~~~puuÅÌ™3qöìY¬Y³¦Ñ÷Ké!„H诿þb222 300`|ð»p᫨¨ª›••Å444˜……+,,äÊKJJ˜……ëÖ­+**bŒ1–‘‘Á”••™­­-+--åêÞ»w`X`` W>oÞ<&'''ÔfII ÀV®\É•ÅÅÅ19996räHk0]]]fffƪªªcŒåääpíýþûï\ÝŠŠ ¦¯¯ÏTTTXuu5WîââÂêû8ýðÖ““Ã•åææ Õ+((`ªªªÌÌÌL |Ó¦M KMMyýºLLL¶qãFòªª*fggÇäååYbb¢@ùœ9sX—.]XXXXƒý e˜³³3WV]]Ílll˜’’’@ÿ222˜ºº:ÀŽ9Ò`ŸSSS¶zõj¡cü׃1Æî߿Ͻ&!!!\yaa!366fêêê,;;›+=z4“‘‘aÿý÷Ÿ@—.]Êx<{úô)W>g΀=xð@ ý-[¶0ÌÇLJ+?~<ãñx,::Z ýúõc˜‡‡Gƒ÷Kéhš"±qãÆáÙ³gؾ};ÊËËñõ×_siûöí%þí·ß““ƒµk×BII‰+———ÇÂ… ‘ššÊ¥P\ºt ………˜1cää为000hVŸøá”••aÆ ×VVVƼy󉄄sôôô0sæLîgiiiŒ;ÈÊÊjr_ø£ÓPYY‰ŒŒ búô鈊Šjvù‚ ~þ÷ßñôéS,Z´FFF\¹””ÞyçTUU ŒÀ×í_YYÒÓÓ¡¥¥{{{qÇÂÂÂèééqåÚÚÚpttlT_ÕÔÔ ªªŠøøx¡•F¤¤„E™˜˜ÀÊÊŠûYII S¦LAnn.®^½ÊõË××óæÍCß¾}¹º<K—.c ÞÞÞ€ŒŒ œ?cÇŽ…½½½@[®®®À„gff⯿þ‚µµ5Œú0iÒ¤FÝ/!¤s I„„&éÖ­>ýôSlÛ¶ Ož<ÁùóçqòäIlذ·oßæ~ÞìùóçqåÊk¼|ù@Í×íÇçò›ÇŽ+ÔÞ˜1cDæë6VLL x<Ž;†ï¿ÿ^àXJJ ×>}úpå“&M âlllðÓO?!;;ÚÚÚMêKqq1öîÝ‹S§N!))I( æÅ‹ýD·nÝп2~ÚEHHf̘!p¬²²Ç•¥§§c×®]8þ<222ò{•••¹ÿç¿^£GêÇØ±cqñâE±ýUPPÀÊ•+ñÕW_AWWS¦LÁ´iÓ0gÎÈÈÈÕ¯¯­pýáßodd¤Ðý5tTT ""––&²®ŒŒ ÷Ü4t¿£GÆ¡C‡ÄÞ/!¤s šÒ,²²²6l† †;v GðññA\\úôéƒÜÜ\@÷îÝ!--ø‘£§§ ôêÕ ¸ÍBD6K2ÍDLèÊÍÍ…´´4ºuë&tLOO¶¶¶ÐÕÕ(WQQªËãñêm£±V®\‰_~ùï¼ó·†¶¦¦&¼¼¼päÈ|iI •ñ_}}}¡{ä÷gРAjò¿§L™‚¬Y³–––°°°€²²2ÜÝÝñ÷ßsçñ_/}}}¡kJòzíÝ»óçχ§§'®_¿Ž³gÏbÆ øê«¯„FÓj‹ßþýêè茌ó­X±666þÿGœ®®®È÷ÆÒ¥K¹Ñæ†îWT!¤ó¢šÒb444àää„ . !!}úô‰‰ ÀÝÝ––– žÏ¤ïܹÃ}}ÎwçΡúŠŠŠ(//Gnn.ÔÕÕ¹rQ›Y˜˜˜àÖ­[صk´´´$½µñƒêÆ(++Ù3g0xð`œ8qBàØ««´þk0mÚ4¼óÎ; Ö Á“'O°|ùr|õÕW\yUUž>}*P—ÿzݽ{‹/8&êõjÈ€ðÓO?¡²²·o߯ªU«°qãF8;; | p÷î]lß¾]d[üþðïwܸqX»vmƒíòë>;wîl°nÝûݰaƒÀ±úV”!„tN”M‘XzzºÈòÂÂBøúú—{Êÿº»¾åÃê:t(pËÂñåææâÉ“'Bõ ÁƒŸŸŸ@ù™3g„êJÒIñsrÅÖ}ùò%*++ann.PþâÅ <|ø°Åûƒ†’’¾ÿþ{±«¦ð_[ r???¡¼oKKK¨¨¨ˆ –_} KZZ&LÀܹsñâÅ „…… ÿ矄vuä·Åÿôïß]»vʼn'ÄŽæó—UüùçŸQ\\Ü`]###èëëÃÏÏK}á“ôBHÇF4!DbÆ ÃôéÓqæÌ„„„ >>§N¼yó±cÇ¢gÏžjFö–,Y‚o¿ýîîŽFii)’’’pùòeÌœ9“›t8lØ0Œ?çÏŸÇáÇ‘ŸŸØØXÌŸ?ªªªBý˜:u*à³Ï>õkׄ÷Þ{>>>BuùKº}úé§Ø±câââPZZŠ„„œ?Ó§Ooòó1qâDÀ¶mÛpùòeܽ{—K x•¾¾>ºwgÏâܹs(,,DXX,X &÷¡!:::øì³Ïpÿþ}̘1OŸ>Eqq1RSSñ÷ßcÙ²exô耚o999xyyáæÍ›())ŸŸÜÜÜ ©©)p]555¬]» øàƒššŠŒŒ ¬[·©©©êÛ½{÷°bŠܽ{ÙÙÙ(,,Ä­[·pòäI¨«« Ljr¦.\ˆÄÄDäææbïÞ½ðññÁ´iÓ`kk  &õfß¾} ÃäÉ“ñèÑ#!==þþþXµjnß¾  &é›o¾ABBÆ{÷î!??YYYxðàÜÝÝñ矨 î·mÛ†‚‚¸¸¸ )) 999Ø»w/?~ܬ׈ÒÁ´ë „éý÷ßgÚÚÚܲbü‡ŒŒ sqqXNŒ1ÆJKKÙÇÌ-}W÷aggÇ-cÇXͲw ãñx ›9s&Û»w¯Ð2vŒ1¶sçN®Ö£G,´ŒcŒåçç³÷ߟIII ôAJJŠ9’«Ç_ÆîÃ?ºwOOO€EFF ”ïØ±ƒÙÙÙq}¹técLô2v·nÝbZZZ}X±bûüóÏ…®-é2vööö"UWW³cÇŽqËËÕ}ôéÓ‡…‡‡su?Îäå垟0¡e+**ØòåË®gnnÎþüóÏF-cwÿþ}¦¦¦ÆËãñ˜¬¬,sttdþþþõ°C‡±É“' ¼?Æ'ðüòýòË/"ß§FFFìÑ£Gu/^¼Èôõõ…êêëë³;wî<Û·o诡¡!»sç-cGÈ„ÇmŸD‘\uu5"##‘””„üü|tïÞÆÆÆ ®L‘——‡ˆˆ$%%AKK ¦¦¦"'›UUU!<<qqq°³³ƒ¡¡!<ˆµk×"00›Æàà`ÂÆÆRRRxöìTTT„FMš4Šˆˆ¤¤¤@GGÐÑѸ·¤¤$¨ªªr›¤ðåçç#;;"W‰ÈÎÎF~~>ttt ¨¨Èýlhh(Ë›——‡G¡¢¢ƒ†––òòò““#p휜äåå¡GèÒ¥K½Ï-P“"%%%r2_II "## UUUôêÕ‹Ë®+-- Ož<¢¢" dee¡¨¨H`)<¾¸¸8„„„ÀØØæææ(//Gjj*455ENƬ«ªª !!!HJJ‚œœÞzë-(** Ôyðà†Š#GŽà½÷ÞCBB‚‚‚`ll 333‘KÞ59çQQQˆ‰‰’’zõê333‘uËË˃èèhÈÉÉÁÈÈ–––"sÜŸ?ާOŸÂÐÐÖÖÖ`ŒáùóçPWWÈÇ'„tN@B:„†hÒù½@BH{¢hB!„B$@4!¤CPUU…‘‘deeÛ»+¤ðS*Ä¥ƒBH[èT)%%% B@@ ðî»ï ä5ŠrøðaÀÍÍM`ûZ f;ØóçÏ#99öööpuuبÙLá×_…ŸŸ4440cÆ n)%B!„ÒùtªÚÀÀ)))PPP@II‰Ø\É“'ObÙ²e¨®®FBB·ìøúúbêÔ©4hÌÍÍqáÂXZZâêÕ«——P‡ÂÃùÏQõø»0Š:7--ûlyõ\__ß&=/m]Ö¯eCeiiixôèQ³¯'Éï>Iþ-$''sŸ- Õ÷þó÷÷oô¹YYY¨¨¨@jj*>|Ò€vÙ@¼•y{{3,00P üÑ£GŒÇã1///vúôivúôi¶fÍ€yyy±{÷î1Æ;}ú4ÀþþûoógÏžÍtuu¹ŸmmmÙÀê$$$0ìË/¿äÊFͬ¬¬Zú6[ÜüÁþøãVmÃÅÅ¥Ù×hj?%i»1u9Âîß¿_ïñÓ§O3ooo‘Ç™§§§Èc}úôiTÛ[K¼– ñõõe'OžlöušÒOIÞcí§¸~4t¼¾c›7of›7oÛv{kÖg‹wck þÿø¯Rd5úl©AŸ-âÑg‹øcVVVlôèÑbÛ~“½Q)ÅÅÅ044Äþýû¹²¢¢"Àž={0iÒ$888ÀØØŽ‘#GruCCC¹cзo_\¹rÕÕÕ\4ÿ/ÿºõ@KK«unª™šš¶z3fÌhö5šÚOIÚnLÝBOO¯Þã–––Ü’‡¯ÒÓÓÃ!CDspph\'ÛYK¼– éÙ³'ÔÕÕ›}¦ôS’÷Xcû)® ¯ï˜½½½Øv_Íúl©PRg±¨*ÑÕè³¥}¶ˆGŸ-âu„˜¥½uªeìø|||Ä.cÇwæÌÌŸ?¿U—±sttܹs§î–t6®®®«½RŸK—.hý€£]])®Ôùêy"`Ò¥ýúÓÑg i,Š[ÄëT#Ðk×®Åýû÷‘——X²d ñöÛocãÆ]ëСC˜:u*¹TF… puÆŽ‹… bîܹB©ÐЄB!S§  ‡ ]]]¡òþýû×{Nÿþýñå—_BCCC |ôèÑxðà·•÷Î;…¶òæñxøù矹­¼ÍḬ̀eË¡µ¥ …’÷ ©ÏæÍ›Û» ¤ƒè(_É“×}¶ÆÒÕÕˆwˆ°N™ÂñºquuERR}B…¾f%E)DôÙBËÑц††ô~i@§~њϤ±è‹4V§œI‹£ÏÒX³ˆGôk" ^^^íÝ Ò)))á‹/¾€ššZ{w…By#¼q©¼®¼¼¼e'¤±Nœ8ooïöî!„òÆ è6Ò˜I„ô‘˜O{w´“¬¬,´f+iœ¨¨¨6YïŸt|eee4‰P n#éééíÝBH'ãïïOß\‘FÛ½{w{wt³ˆG#Ðm„ò !-&IÐ7œ¤±(fF  !„B‘ЄB!„H€è6B;BZZVV7‘q¢¢¢Ú» ¤ƒ ˜E<  Û%äBZM"$’ I„¤±(f&¶‘æ&äçåå!''Gl=%%%hkk7©ÒÒR¤¥¥‰­'--îÝ»7© BHË¡I„D4‰4M"èbòä÷k"¶ž¢â]ÄÇû6©-[öã—_2Ñ¥‹zƒõ»‹§O¦ šB!o$  ;ˆª*9¤§"¶ž±±k“Û()©@V–;€ž ÖÓÕý•••Mnnݺ…’’L:ÁÁÁ¸yó& 0räH8::Š<'&&wîÜAll,z÷îÑ£GÃÌÌLdÝüü|ܼyÁÁÁ(++ƒ±±1¦N z!!!ðóóCbb"Œ1nÜ8ôêÕK¨Ý€€899¡°°7nÜ@bb"ÆáÇCJJ ååå¸wï|}}¡­­ gggèêê \çÏ?ÿ„ºº:pçÎüóÏ?PQQÁĉann.P722ÁÁÁ˜dˆ ˜Ø‡±qý×gåJ$ˆmCW׃%$$4¹Æ³··gFFFì믿f<iii1 Û¶m›P}///&''Çdee™¹¹9“——g222lß¾}BuoܸÁ fddÄLLL˜œœ³±±¨·cÇÖ¥K¦  ÀÌÍÍ™ŒŒ STTd'Nœ¨çééɰ~øihh0555&##ð>úˆ•••±±cÇ2Ç455Ö½{w–œœ,p]]]6nÜ8¶dÉ&''ÇÌÌ̘¬¬,“‘‘jóË/¿d؉'˜ŠŠ SRRbÌÛÛ[è~uuuÙéÓ§ýÜ“Î#33“eff¶w7Z×å2Æ–çÿÿUÙÞ=ê°"##Û» ¤ƒpvvn0&!ŒÑ$Â6ÒV ùÕÕ@nnÓååmÒE΋/pìØ1\¾|ééé …­­-öìÙƒ¤¤$®^@@>üðCØÙÙ!==áááHOOÇðáñ~ýzITÏž=ÃÌ™3¡ªªŠððp$&&"** ™™™ðððàêy{{ãÓO?…““^¾|‰ððp<þÆÆÆX¾|¹ÈÙêëׯÇîÝ»‘’’‚„„Lš4 ĸqã```€èèh¤¦¦âðáÃxþü9öíÛ't;wî 11iiiˆˆˆ@JJ ììì°bÅ ÄÇÇ Õÿè£ðóÏ?#??ÙÙÙ=ztsŸvÒ‰Ð$B" šDH‹&ŠGti«„üØX@C£i“'Û¤‹œÊÊJlݺS¦L””,--áêêŠŠŠ |ø0àÃ?l¥»¾ùæTUU¨™HyôèQÀêÕ«[­MÒ9Ñ$B" šDH‹&ŠG)Ä̆¿ÿbëõì)z’ÆX²d*€+bë)(hCOO¯ÉíHÊÉÉ «W¯ÆáÇñäÉØØØ 44ÿý÷–-[†Ù³gsuíììàéé‰ 6 _¿~|wîÜ™™ž>}Ф¤$lݺUh¢_K>|8ž={kkkôïßˆŽŽÆG}„Áƒ·J›„BiY@w‹MÇ¢EÓ[µaÃì0l˜]«¶Á·lÙ2 •«««ÃÃÃåß|ó ¦N oooÄÆÆÂÉÉ žžžB«x5£ËŽŽŽ8{ö,·šÆÌ™31gÎ®Ž””Ξ=‹¹sçÂ×׉‰‰xûí·1mÚ4n%¾!C†ÀÃÃCèyyyxxxp«qÔåìì,²\YYçÎÃ÷߇bذaصk—ÀàààÊm%„B^C<Æÿþš´WWW”••áôéÓ Öè+¶ÎLOO666ðññiñëhïnÔ‹&’Æ¢˜E\øï?,ôõÅØÑ£Û»;Bh4‘4Å,âQMH¹xñ¢ÀŽ…„Î#77WƒƒQ°v-¶xz¾–4!¤åP !mdذa°¶¶nïnBZÁö}û4e '‡x##Üòõmï.BZÐm¤­v"$„¼9²²²¸‰„¤ýdåäà÷À@T×n†”=e ¶xz¶s¯„EEEµwHA1‹x@·JÈ'„´4šDØ~²+*ðkz:œ#"`°~=Ò¦NýÿA99DõèñÚBÓ$BÒX³ˆG9Ðm„ò !-&¶­èâb\yùW^¾Ä?yy¨b (,’’€Å‹êN›†wöìAòk” M“IcQÌ"ЄBˆ•ŒÁ?/¯&hÎÊÂ%%Buþø¥³gChC99<ïÙS~þ—-‚4×&}&„´   !„Z9••ðÉÎÆ•¬,xgg#·²R¨Ž¹’¦ijb´´4Vfd ±6÷YÈÌ™¸¶{7¦öïsPéBÀÒYPÝF(!ŸÒÒÞˆÛÀ%%¸’•…+/_Â?/•¯lÐ+Ããá-uuLÕÔÄTMMôVP|°m[ÍÊõ‘“LLàsçF”—ãš• ääZóVD;’Æ*++ƒ\;¾W;  Û%äK.77AAA055…žž^½eYYY ƒ¥¥%äŸ@H¹Ð’©ª›šñò%bŠ‹…êt•‘ÁÄ®]1USN]»BMZø×å³çÏa ܺ%² !?¹rr¶±}@®YY¡;­¿{÷nʃ&’žžNyÐbPÝFè(¹   Œ='Ož„««k½eþþþ˜9s&þøãFG…ÈckÖ¬££cKÝ!­ŠçÆËå§f¼| ï—/‘#"5ÃXQÓjG™‡«©¡‹˜¼åËF€-ññØ“”„eeˆspêÚµ‰wÒt<“Æ¢˜E<  ;¨ªª*ÄÇÇÃÐаU¿fILL„††ÔÔÔZ­ I(++£ÿþèÚŒ_>AAAøóÏ?amm Þ+¿ ›ÛEBH+¨®f’’l"^lI 7ðžˆÔŒ.<ÔÔ¸Ô cEÅ–ì2€`wïÞè-/Õÿý‡‚ª*L Åá~ý°R_¿ÅÛ#„´  ;˜¤¤$,^³Ï Ÿ£H±òÅòÐì¢ ¯O¼0|èði£  ïº¿‹àgÁ(/€t¹4T+U±aÙ¸.pm‘6šjàÀ j‘k=|øòòò-r-BHëÉÎÎÆµ7°`î<44 ¯Š1ü›ŸÏå3G‰HÍP“–†SmjÆ$MMhˆHÍh +ôõa(/¹áá(¨ªÂ{11ˆ/-ÅîÞ½AësÒñPÝFZbáß÷þÆ’íK<(¨“B÷¬âf휅&}„k66«/^`ÔÜQˆ³‰Qg´†^øùý…ߎþÖ¬6"""àççWïñÉ“'£G"¥¤¤àòåË=z4LDÌ|OHHÀ•+Wkkk8;;CSS³Yý%äuõ¦L"ü7(寯‰ŒÀ 8–WY‰µ©׳³‘]Q!t~oLÕÔÄ4MMŒPW‡L;-)çÔµ+î €É¡¡xQV†½IIH()ÁOff—jý}Íh!i,šD(Ðm¤¹“‹‹‹±bë $;& ï)¤MÇ×~Y“g¡OŸ>MngÞÊyˆ ¨¾r€ä[çÃ;Àg.œó,ç&·(rG¬ììlÀÛÛ»Þ:&&nnn8yò¤P‚åË—CKK rrrøá‡pàÀ\¿~]d°ššŠ´´4ÂÀÀ É÷CH{y&&&&"·‹4ªõõ ‹r$—Wk'ú忢â•Ô )CUU¹Ô s%¥vê½°þÊÊx`k‹É¡¡),ÄùÌL¼(/ÇŸ––Ð’‘iÕ¶i!i,šD(Ðm¤¹oÄïú±½cÜ|ýùÀçX¹u%Žw¼ImD„F ÂÁs¹rñÕ÷_5+€^¸p!.\(P‹!C†ÀÔÔC† iÒu?ùäœ?³fÍP\L˜0«V­ÂíÛ·…ê÷îÝ›û===ìÙ³K–,iRÛ„´‡Î8ó}þ6nÜ;;;0ÆpñâE\½z...ÈËËÃ|Ð"÷Ei:à×##ƒž²²5eÚÚÈ *220’—Ç”ÚÔŒQêêmƒ4ˆ–¢Ò¥ ®ZYaUL ¾OMElI †à’¥%^“IÛ„úQÝA””–4îÕjNj_€F¤„í“'Ø“”ÄÏR<Fª«ãp¿~¸¥¢mm žTª [[ütëV‹,ú:qPSÿ OíDÂã©©˜Š|»&6Ϥ±(xèdËÚ-84ïzßî ÕUà ®C_C¬é¹N]hvŽ£ñÇWÀÂß]» €L” ºýÓ ³JfÁÿª³sŸïÞ½‹eË–aìØ±8räH³ûÌoooîç'OžàÎ;6l  æ+ïo¿ýV`”ùñãÇXµj|}}áè舞={¶XŸyÓ…a{BL=‚Í“'øâÙ3ÄÖÍ<Ô^ýúáùСøÛÆ« °}×.d@r2“óÿGl,ì;z´}o®+*â­-†ªÖ,‡ôWv6‘ÜÉþX ¤££ŽÆe æÌ˜ƒ€€< ~scs 8-ÖÆ°!Ãt3!!!xðôtµta?ÈÝ»wo‘ëŸW·{÷îðöö†¿¿?ž?޲²2ôìÙ¶¶¶˜5k¤:Ð2X„¼N“£Š‹q.#ç23^T$tÜ^Uótt0[[=ÄLNrž9óÿ?\)ªÿÿùÙÁ@À¤¡ ½;y))œ³°ÀƸ8ìKNFJYÞ ÄYssLjâîª4‰4M"dz¸pÇqùòe<|øÁÁÁ(,,ÄñãÇÑ·o_:.\Ào¿ý†ÀÀ@äçç£_¿~pwwǼyó„®wôèQœƒ¥¥%<ˆáÇ#00[Ñ¡¤¤#FŒ€‚‚<ˆ´´4lذ™™™ðòòh—ò !-­=çØ’n¤9XÄg¬­Š æikcŽŽz55h&Üôõa$'‡y(¬ªÂª˜Ä—”`oŸ>-ýOÁi,ŠYÄëTtll,àãド'Ьãç燮]»r?»ººÂÌÌ ^^^\]QQ={öÀÍÍ [¶lP³!‡ŽŽŽ;Ào¿ý†èèhÄÆÆr#Ó%%%غu+¶oßmmíÖ¼]Bi–ÔÔTŒŸ5 ~~ NŽ/)ÁùÌLœÍÈ@ ˆ ÙFYsut0W[›[A‚´¬Iššð0SBC‘RV†}ÉÉH(-ÅÏffP Ô3BÚ\§úW§ÐˆîºÁ3ðx< >ùùù\Ù½{÷‘‘Á­ÜÊÊʰ°°À… ÿ_éâܹsÐÒÒHë4hÊËËqåÊ•æÜ !„´ºu_| =uJèXbi)¾JNÆ §OÑçáClŽž­””°«W/ÄØÛ#pà@l14¤à¹• PVÆ[[X)).dfÂ1(™íÜ3BÞ<*€nм¼<øøø`̘1\YRRÀÂÂB ®••’““¹ŸSRR`ee%PÇÜÜê-;‰B€šI„ü‰„’JMMŽ””/X¯³gQYY‰¤ÒRìON†}@z=x€qqxRPÀc¡¤„={"rð`„ „ŒÐ‚æ6ÕCNþ`\íÊKòó1$ ѯ¬v"JTTTkwt³ˆ÷FÐŒ1¼ûî»(--ÅÞ½{¹ò´´4€‰‰‰@}333äææ¢´v«ÙŒŒ ¡ :´´´ ©©‰ÔÔTò+W®`þüùxï½÷111­qkä òê<`Qï+*ëœeþþþð÷÷oÒ¹ë¾øÏgÌ<=ojŠ~»v¡çƒX‡GŸ|<0UT„GÏž˜ûË/¸¨¦†={´v{í¶¸ßo¿ýyyy­ÚFG+S•–FÏ#G0«6ȉ/)Á°À@L÷ÝÏݽ{÷kuTöú”ñc ÌŸ?~~~ b°NÈÛÛ›` Ö[µjSRRbþþþåß|ó ÀâããÊׯ_ÏdddXuu5cŒ±^½z±Ù³g Ô)++cÒÒÒlýúõ\™‹‹ ›?>+**b%%%ªª*®Ž‹‹KSo™¼ÁtuuÙéÓ§¹Ÿë¾¯¨ŒÊD•¥¤¤0ý™3|}nÜ`¸}›ÁÁáÖ-__ÖïÞ=öql, ),l÷>—ý^Àª—å3¶¼öUÙîÏßëT¶+1‘ñ|}|}™ì͛엔”תTÖ1Êø1INN+**b‹-¢˜DŒN5‰P›6m‰'põêU >\àÝàˆˆôêÕ‹+ E·nݸ%êtuu*pnLL *++ÖYYY(ÖŽÚÒšäE¬|@eTV·ìí;Â_ÁCVÀ3ãž>ÅÞU«Ð_ÄfíÕgYYY€W&¶Þ›Zö±‘zÉËãèh”KKcqt4ËËñ±‘ÑkÑ?*ëeüÿçÿ·K—7k©È¦x#S8¶mÛ†C‡áÒ¥K¹Ï|vvv’’Bxx¸@yhh¨ÀƒFll,—ÒÔÝüc„ò:‰/)Á[·oãÁ‹À+ËT±‘#‘pë,hé¹g®.nZ[CCZ À¶„,ŽFEçÙæ×N§  ‹ŠŠ››‹¢Úý ››‹’’®Ž‡‡vïÞ~øöööÈÍÍå|†††˜8q"Nœ8ììl5KÖ¥¤¤`åÊ•\=777TWWãÀj’î¿ùæXXXjSB>!¤¥5va%cØ›”ËÇqïØ1@ÄÆQàñìà rEòú{K]÷mmÑ»vRç‰ÔTL A^e%W‡&’Æ¢˜E¼N@Ïš5‹Û Þzë-hhh`íÚµ\C‡¡ªª ‹-‚†††À£îHò÷ßYYY OŸ>X¸p!víÚ…±cÇruLMMqòäIxxxÀÐÐÚÚÚHLLÄï¿ÿ.Ô·ôôôV¼sBÈ›ˆ?‰°!O 0èéSlŠGIf&ŸÔ~½ÿªÒ#pèôiTÖ ºHÇa¢¨ˆ¶¶°WUÜÊÉC` ’j·ñ'"Å,âuªèo¿ýV`=g>---îÿïÝ»‡ªª*‘ç×Ý÷½[·nxòä ‚ƒƒ‘’’[[[ôèÑCèLœ8=‚ºº:ìììD®GM»úºÿ>òòòàääÔÞ]!¤Ãjh'¢ª*ìHLÄ¡çÏQUûU¾Ú… È›2± _ò€8zêÞ_º´ÅûKZŸ¶Œ |ml°(233VT„!¸je%°á“€ ´µm¿Ž’×Å,âuªºwïÞb뼺nsCdee6S©ŽŽ¦L™Òèë`çÎ â– l-£FY.++‹¿þú«UÛ&¤½ødgÃ-&‰µ#rRRØfd„ò„…5x¾’ŠJ[t“´))œ77džøxHNFjy9Þ ÂssLÑÔÄ…Ë—ñÎûï#öÉèèè´ww é:UÝ™%%%!>>^l=n3Iåææ"((Hl=yyy 2¤Im´µ»wïBWW¦¦¦íÝBZ]fEÖÆÆâ×:_¿ŽTWÇwÆÆ0QT>ú¨{GÚ’‡ý}ú ·¼<>ŒEQUf„…áPß¾øîèQìØ _~‰SžžíÝUB:$  ÛHsò¿ûüsT:S©úÓÖ Ü03Õ§O›Ôƽ۷ñ“«+&דâÂwPEO^¼€´tÇxû899 |uIHgÁŸ@¨¥¥…SiiX‡—µÛ:«KKã«>}°´[7ðÚ³“¤]­60€¡¼<æGD (!ïÿû/dÍ;}á{ý:222hš)++Hk%Â:FÔ 47!ÿÃÏ>Ãì®Ýf\”=ššØT»"HSLyûmœôðÀ¢ððzßKKcÑòå-<ÁÇǧÞãóæÍX“Ûßßׯ_GLL zöì‰qãÆa„ -Ú'B^wþþþH+/ÇÜÊÉáÊçhkë_?èÕ®ñLÞlS55qׯ#¾ü%EE(ß¾<}:B·€„„L_´ÁþþÜ>]zz:åA‹AtiîQGGZ£G#âÔ)ˆJÐ(Ô·/6Ùä6x<lÞŒ_ÜÜàZÏ$£#FF8¹uk“Û¨Oll,Μ9#TžœœŒììlØØØpôÎ;±sçNèééÁÆÆ.\Àþýû±|ùr|÷ÝwB`¥¥¥¸wï***`jj*´É !Q%cˆ±µÅ'‰‰(© ž{ÈÉá°±1¦jj¶sïÈëÆNEžK—bÕýû¨æ€áÖŸÒ(t3}ôÅøoà@ütî\D-ÙQð,ÐȺ½{±Å×?ˆ…>¬¦†ÕË—ÈanÈ, ÌÒÕÅ¢ÂB¡7ÇßÒÒäìÜ*;*Ξ=›[~/44°µµÅˆ#Ô¬ÞñÉ'Ÿ`üøñ¸téPUU|ÿý÷ptt„³³³ÀuΞ=‹³gÏr?ÛÛÛãÈ‘#0`@‹ß!máIA–GG#¨ö])«õõñyïÞP¡ÄH=¾=yÕîîe)3f`égŸáÊ×_·S¯:¶„„<)-E邨è–ÌÛiF¡IÃ(€î@ê….”—‡MË–5» €~àúʱÖ}%55“'O†šš®^½ %%%à6­ù裸å»té‚M›6á×_ÅþýûèÙ³gcúôéèÕ«ÂÃÃqîÜ9üý÷ß5jBBB`TÏz¸„¼ŽŠªª°=!^/^pKÓõWVÆ÷&&D+g\¸| ––À«éw††¸vþëöîÅþW¾Z9 `u‹\½Æ,—ÔÝJ¡5GŸ_U\\Œ©S§"//×®]C·nݸc‘‘‘““ãF¤ù¬¬¬ ¯¯/´ÓÖùóç±hÑ" >+V¬À­[·pâÄ äççcÇŽ­~/„´ŸìlX>~ ÏÚu¤¤°]]>FF<±>=v &&"±¹sáºs'ö40džKHHÀÃ’ 6eªØÁû~û ¬l¡N;ŠG#Ðm¤¥võyuº@±16íÙÓ"×jG¡ïÞÅ/ß}×ÚmжÉèsuu5,X€àà`\½zUhÝî””˜˜˜ˆ äíììpåÊäççCµv'.Q/^ŒU«V!  ÅûOHKˬ¨€{l,~«ó2FCß#ôÆ v hÆäAQfMŸŽY7obQx8ü¥¥1hþü6}^¿~=þüóO|÷Ýw"WÕèÑ£"##QXXeee®œ1†‡BUUµÁਙ,©¬¬Œ‚‚‚ï?!-éÕ¥éºÊÈ`Ÿ>pÕÓô¡À™4Â'û÷CÎÌ ò}û¿ÿ.t¼’1ä1†êK—pÊÕñ%%¸hi -™vèmǰûáCøq£Ï|eo½…O÷ïïð¹Ð<‹GtÄ…~têT³WÞ¨OÝ9¼uuÛdôùÛo¿…§§'6n܈+Vˆ¬cii‰üý÷ß»? ##Æ ÛNHHÒÓÓ1qâÄë;!-)®¤+cbp»ÎÒtóutp°o_èÐÒtDB>¿ý&vÀàeEÖ¾|‰ÇîååÁ¾vûo³68éHŠ«ªàößøiÏ`áBá <Äç?ý„m..mßAÒf|YL IDAT(€î ÖíÝ »‹qºS7^5káBŒþäLiƒÜgooo¬Y³sæÌÁîݻ뭷yófœ9s{÷îň# ¦¦†’’xxx¶Ö ôÏœ9999Œ?JJJÈÈÈÀüÁMD\½º%3Ç i¾Jư?9;QR] 0’—ÇccLìÚµ{G:*ƒFÕ»W]¥ÑÑø5=ñ%%€³ææ˜@ï=@Tq1f‡‡#<6“}æc#GbççŸcÁܹè];Ùt>@·‘–NÈ×ÑÑÁ§Oѯ_¿½n]<?ûúB[[»ÕÚà;{ö,ªªªàíí ¡ã.\À˜1c`ee…o¾ùîîîÐ×ׇ©©)bccQXXˆíÛ·còäÉÜ9<À¡C‡šššÈÉÉAuu5°oß>º„´·W—¦ëÂãa>ëÕ Jõ,MWw'BBÄ‰ŠŠ‚i+mÈIIá33˜**bGBò*+194ûöÅû Â;«ÓXªªš\ò™3zöK€J õôDÈúõÐí€ßÑN„âQÝFZja]­<óõèÑ£U®»nÝ:dggs?/[¶ £Fª·~Ý}777Œ5 7oÞDLL æÏŸGGGØÚÚ œ³cÇ8::"88©©©PRRB¯^½0uêÔV»/B$%ji:›Ú¥éŠY]Ãßßh!i”Ý»wãÇ[o›‘Lቒêj|ð߈*.Æ¡¾}Ñ¥çõ6EYu5ÖÆÆâHJ €šIöCÍÍÑíáCÔ÷L„!²¸ÚÚp Á]¨¶ðî½­&Š×±^ьވ‚Æ'𳃃}¾™™ÌÌ̬ӵkWL›6 Ó¦MkR imÞÙÙp‹‰Á³ÒR€‚”yÂÏCTU8p`£ç"|Û¯fצ?ÞÍÍ…sD÷ é(€&„Všš*²üÇ´4˜=~ŒÓjFµ~45Å­þýч&‘׌œî €™µyö‘ÅÅ€{yyíܳÖQÉ6ÆÅaFXr+k¶sïÞ~ ‡9ÁR<~13ƒc휞?³²°"&¦UúLÚÐm„võ!äÍñèñc˜ÚÚ äùÇ•”`lp0Þ‰ŠBvíºÎ tu9x0\j×u–TVV7‘q^Ý©µ±”ºtÁKKl®ýZÿeEÆãÇ´´–ì^»K)+ƒcP¾JN &- ðìÛ2MøVHNJ —,-aW;—áDj*6ÇÇ·p¯[Å,âQÝFZc!!äõ´nï^lÚ„÷îE%cØ“”«Ç¹u{ÊËÃÛÚ¿š™A»›Uøûûs §¡%BÅáø²woühj Y))”WWã¨(lŠGu'HM¸“ƒOŸr#딕ñÔÎo7s*•.]àmm ãÚ¥`÷$%arr³ûÛÚ(fï͘ ð „|BÞ ?F´†˜ þÜ¿þ¾¾«<Ô˜¥éþÇÞ‡Çx®ÿf•U{±D"±/¥–hmEm­j„’ªjÑåwôPôT[E‹ª¶Ô¡Å)µW{Gí"Di‚MÈ6óþþL3f“Ì$¹?×5Wå™wÞ÷7wž¹ïç) i"EQ”&Â'U³&ž¶¶ :žÄìlæÆÆréÁÖ4mjïéÒ¦Vf_¿Î§×¯k~¬]›E ¬é¯º•{üüètö,q™™|‹•U±?y* ’³è&3ÐBa@ÌËÜ•_â à|nÒÒÂÁc­ZñMÆe2Ñ"ÏóNNoÕ o{{¶&&òüÙ³Ü,cû'fgÓ'<œ™×®¡Vì-,XÝ´)K76øŠn66ìö󣪥% 06*Š?îÞ5è5Dé’Z! äÄÉ“DV­ ¹«а!f·n1³Z5N¶n­s]g!ÊŠ¶¶mÙR³Kaèýû´;}š“:¶ 7GSRhyê{rû¼íí9Ùº5ÿpu-±kúØÛ³ÃÏ; r…×""8RN›1+I K‰ä QþMúê+[wÜìÕW¹õë¯%²44Š¢(ná“8YZ²Ã×W³Ka|V]ÏžeÃ;½Ž¡}sã]CC53æÿpuåD«V4Í­S.IÏU®ÌF¬ÌÌx¨VÓ?<œ°§ìhh,’³è& t)‘‚|!Ê·ƒÇsºrå¿gŸs©6d×Å‹Z+rŠ4Š¢x–&Â'±03cq£F,iÔËܤðµˆ>»~Ýà×zV)99 >ž¢£ÉQlÌÍYÖ¤ «K¹~»Oµj¬ôò HÎÉ¡WX˜É­­-9‹n’@—)È¢üJW©0kÙƒú|lÿþL›;×à×8p 4 ½¢‰ðI&Ô©ÃN??œrk|gÄÄ0üâE2Õê»fQœ½ŸÖ§O³%÷O[[þlÕŠqµj%žá®®,lØ€„¬,z†…q++Ë(±FrÝ$F±cÇ6nܨױqqqüüóÏÄÄÄ”pTBÝ•Š.ëÖ‘Z³fÙç<%9 -„©èQµ*ÇZµÒlôë­[ø‡†rÛȉᲿþ¢ã™3DçÎò©^3­[Óâ ÿ^KË{uë2ÍÍ x´N|ï°0Rs7o¦O–±FñÙgŸ‘À+¯¼¢óØððpFÍÚµkñðð(…èžnìØ±œ:uªÐçV¯^¯¯o)G$Œå¡ZÍËçÏsféRèØžRNqÝÙ™OæÏgÉ_”b„B”./;;Ž·jÅàˆ%'ógj*íΜá__|sWí(-é*ã/]bMn9‚•™ó<=™T·n©Æñ4³=<¸“Ͳ¿þ"ôþ}^>ž]~~_Dž$Ð¥¤$ òqÉÝ^µ¤$''ãàà€¥¥|«ä¹rå —.]¢gÏžž«T„­^EÙ–©V3ðüùG›£¼þ:-33ùØÍíÉ;–5k†———AcÈk ,éû€("## þ=Xg++öúù1þÒ%V&$p=#ƒNgΰÖÛ›¾ÎÎ%~}x´åø+\HO ¾ ¿y{Ó¡råR¹~QüШw³³Ùtç!ÉÉ »pM>>X”@ã±¾233åç™’•Cä'''ãéëË;hݪ•AÏ_‡~ý=lÿœ8±Ä®QÕ¨Qƒ­[·; a$Yj5ƒ#"4K`õèØ‘m¾¾¥>k”×@(uÐBsæÌ)Ñ:èü¬ÍÍYáåES{{¦^½JšJÅËçÏ3ßÓ“)%<üë­[^ºDºJÀKÎάòòÂùvý,Iæffü·iS’rr8”Ä^ºÄš41ZL·nÝ’:h$.%†þFœ>>iï¿Ï”9s8´~½AÏg[PñíÛ³|ûv¦bmmmðkäää°lÙ2BBBP©TøûûóöÛoc®g"²cǶoßÎåË—iР½zõbÈ!Ž»|ù2{÷îeÿþý¤¤¤P³fMºuëÆØ±cµŽ{ðàK—.åìٳܸqƒš5kò /0|øplsëúDÅ–­(¼zá;s7Að¯R…ß<ƒ$΢hJ+yÎï£zõhlkËð‹IW©xÿÊ.¦§³¤qã'ZSL™j5“¯\áÇ¿þ­ò™‡Së×Çxs¹ú©dnÎÖfÍð åtZ+âãq±²â« Œ$ϺI‘M”œœÌösçPڶ墣#§Ïœ)‘ëÌX²„ÔÞ½‰íÞË–üü999¼þúëÌŸ?[[[®]»ÆÄ‰yõÕWQr·T}šwÞy‡~ýúŒ««+þù'¯¼ò #FŒÐzýo¿ýFãÆù"·öÔÍÍØØXƯu¾ÐÐPš5kÆG}ĵkר_¿>·nÝâwÞ!**ª@ìû÷ïç·ß~ãÔ©SIMkk¶hA@î¿¥ËÒáÌ6úøÐ½jÕ"Ÿ/GQøøêUÍ,­0ÍÍYîîFmÀ{Vn66ìöó£óÙ³$åä06*ŠjVVô/¥L&B}È玥ÄPM„ùgŸ5^}ô\SY/'OBË–¯á"«];þ³s'Y^ϳ{÷îZ_{xxàééÉ¥K—žúº+W®àääD»ví´Æ½½½©S§ŽÖ–µ£F"::šzõê1qâDöìÙS D$<<¼Ðx 3}út†NË–-ñ÷÷çûï¿çøñã888ðÑGé|½([ÔŠÂè¨(~Íý7ÜÆÑ‘Ý~~T6•id'BQ%±aQÙš›³ÎÛ›îî$åîÄ·,ß'‚úˆË̤[h¨&yv±²b§ŸŸyx”éä9½=;üü°³° GQx-"‚Ã))¥v}Ù‰P7ãÿ¨ QŸœœÌgÏþ=ûœ§reœj×f®™›7æëŒ›;—+ï¿_`üº¿¿Ag¡œœ´fóx{{³cÇEÁì 7Â;wîиqcÍìóã¯ß»w/YYYX[[3vìXš4i‚ X¶lK–,¡I“&|ôÑG¼ùæ›ÀßåÕ«W/Ö{ñðð [·nlß¾Û·o˜µe“Œ»t‰U ´tp`Oóæ8™@ò ÒD(ŠÆM„…1>uwÇËÎŽ1‘‘d¨Õ¼ué‘0ßÓs ðÞ¤$†_¸Àìlž«\™õ>>Ô-g3¦ÏU®ÌF„‡óP­¦x8‡Z´À¯6€‘&BÝdº ™>>7Ÿ}Ε2h«¿ûŽnUª<Ó#õÏ?¹íã…$YmÛt:%%…‹/j)ŠÂŸþIݺuŸ˜<Ô«WsçΑž»ÆgžœœŽ?N­Zµ´J6:wîÌæÍ›¹sç¿üò ¶¶¶ €§§'7ž¡a#¯N:##£Øç¦CÆ_ºÄŠøxüØÛ¼9UM$y¢¬{½F ‚[´ÐÔ*/¸y“—ÏŸ'-wù¹¨¨(ê5iBvn¢¬Vf]»Fï°0Mòü~½z„´lYî’ç<}ªUc¥—f@JîlýÕÜ…qI]F$''óÇ™3¨ŸT¿V¹2‘ϼ"ÇÌï¿'µW¯'>ÝÀ+r8p@ëëÐÐPñóó{êë|}}ÉÊÊ"$$Dküĉ¤¦¦Ò²eËB_çääÄÈ‘#ùæ›oP«ÕšíÄÛ¶m À† Šõ>RRR8|ø0ŽŽŽÔ«W¯Xç¦åÝË—5+ûØÛ³¯ys“]GVˆ²ªCåÊœhÕJ3«ºãî]:9ÃõŒ &þ9wúõcÉŠÜÉΦwXŸ^»†ZQp²´dK³f|íéiðåðLÍpWW4l@BV=øeäíÑ…$Ð¥æY›g|ó 7\\mü„G¢‹ fÌ(ö5¶ïÞMdf&;öÄkded°pùrT¹3Ï‚ŋ@ZZÓ§O`êÔ©O}í¿þõ/,,,˜5k–¦VëÞ½{üë_ÿà“O>Ñ:öòåËš¯wòÔ©S|þùçDEE‘Mbb"Ë–-ã…^àÖ­[|ðÁO9eÃä+WX<ÚšxóæT7Áä911QÓH(„.ù{CLI}þײ%ýr›äÂÓÓi½u+'Õj2ûõã›M›hqì{“’håèș֭Xšg'Õ­Ë477¢>¤wX)\ðq%±{ry#ŸE–’g-ÈŸðÆt×ãæWë•WŠ}¶-Z°vÂÇUjݺÐÚ㢪[·.cÇŽÅÓÓwwwâââÈÉÉaîܹVÂxœË—/gâĉԩSG³¶s^RÞ¡CͱsæÌáË/¿ÄÉɉ5jpõêU,--3fŒÖ¦+?þø#YYYLž<™©S§R¿~}âââHOOçܹsÔªU‹ÄÄD¦OŸÎôéÓ177ÇÌÌ •J…¥¥%ï¾û®æQv}Í¢›7hdkË|1›Ù‰PEiîDXTüÞ¬ÿwõ*_߸ÁÝ5k`ôh03ãF—.°môíËøÚµYذ!•*àÚë³=<¸“Ͳ¿þ"ôþ}^>žÝ~~%²‰“ìD¨›$Ð¥äY¿½¼¼J|ù!WW×RûAk?OšÄˆÞÜÔýШw³³Ùt燒“vá›|| ¾òˆ$ϺI-Œ¢K—.š?·jÕŠV­Z=ñØ5j<1Q¨_¿>£Fz굜=+ÉYt“èR"ùB˜–ooÞäÃèh\­­9ТÊPò ÒD(ŠÆÔ›'ΚEüóÏÃS’Àë^^,Y±‚ɼ:› »ýüè|ö,I99Œ‹ŠÂÙÊŠþOø%D_’³è& ´¢Âù>.ŽIW®fqö7oŽ×c«²!JW¯®]ixãän`T({{xx”^Pe€½=;üüxñÜ9¨T ˆ`Oóætvr2vhåš$Ð&ÂÆÆ†5kÖpðàAc‡"ʘ;wîh¶ºýÏÄÜuœ­¬Ø×¼9>ööFŽJñáøñÆ¡Ìz®re6úø0 <œ µšþáájÑB³Ã£0|ø[=jÙ‰P·r¹ Gff&ÉÉɨTª'£R©×¹ÔÝ»w {ê¹EáÂ… ÄÇÇ?ñùFú’p†·öömFGF¢V-,ØíçG›Ü5T˲Jò,ô&÷–ŠaRݺLss úáCz‡…‘’“Ãÿþü7oo²²²tžCrÝÊU=yòdš7o޽½=U«V%<<¼Ðã¶nÝJµjÕhÙ²%Õ«W祗^âþýûZÇ}ZsÌ_ýEïÞ½éß¿?IIIDGG“˜˜È+¯¼RbïQ¡¿-‰‰¼qá*EÁ΂¾¾t’Ît!DðC£F ©^€CGrÎÅ…ìîÝY¾}»^³ÐâéÊU½nÝ:æÍ›GÏž=ŸxÌ?þˆsæÌÁÎÎ___¦L™Â¦M›4%wïÞeݺuLœ8‘–-[bkkË¿ÿýoªV­Ê?ü u®ÌÌL.\ˆ££# 4`úôé„„„pþüy­ëÊ®>B_ea·°²à»wy-"‚EÁÖÜœí¾¾t©RÅØa”ìD(ŠBî-‹¹™ÿmÚ”îU«Â¦Md @l·n:g¡%gÑ­\%ÐúÆÇÇ333͘999š†œ£G’™™‰Ök}}} Ö:—››Žùj)ó^“ÿ8]}„þL}·°² èÞ=^‰ˆ [Q°17çw__üËYò ²¡(¹·T<•ÌÍ™zÿ>–îî»Ö}V‡ü´mÛSg¡%gÑ­Â%Ðýõ¾¾¾ZcÞÞÞšçòÿ·°:£à­[· œËËË sssÍ9òHA¾Ð—4ú<›½II >ž,µšJæælnÖŒU«;¬!M„¢(äÞR1}2>9ƒkÝð÷ê,´ä,ºU¨Z­V“’’‚[nwjžºuëbffƽ{÷4ÿ­W¯žÖqnnn<|ø‡œœ\à\vvv8;;kΑgóæÍ 4ˆ€€­Çã©É˜ŒÉXñÇ$%=ÚHà‹/°¼qƒ>>ô©VÍd⓱¢-Z¸ääd“ˆEÆd¬¬ýïÏ?¹\½:,^ ±±šc²ž{ŽÏ¿üR³ØB@nNÒ°aC $ŸléÁLQÅØAÚ®]»èÓ§gÏž¥E‹ZϹ¸¸Ð¿V®\©»|ù27fñâÅLœ8‘åË—3nÜ8´f˜ß~ûmV¯^­Y±£iÓ¦¸»»¤9&55'''¦M›ÆìÙ³Gߘ ¿ý Q’%'Ó'<œ*–fflðña l0Röý‘ä«ÇüÀšX/!ʃq|ìX(d·Uë?ÿäߎŽüsâÄÏIÞ¢[…š¨S§Nåí"""4Ïåÿï… ´Ž ×<P³fÍçÊ{Mþã@ ò…þ¤Ñ§èþ—’BßÜäÙÂÌŒµÞÞ"y–&BQro©X4³Ï…$ÏðhzÙÖ­…ÖBK΢[…K _zé%.\¸ õ Ž­­-þþþtîÜGGGÂÂÂ4Çäm¼òÒK/iÆúöíK\\wîÜÑ:—™™½{÷Öº®ä }I£OÑKM¥OX÷s“ç5M›òJîÒMå4Š¢{KÅòÎŒÜssƒÐÐ'>bkÖdáO?x­ä,º•«­¼CBBˆçܹsìÞ½›ÈÈH6lH›6m dñâÅŒ3†3fÅüùó=z4Ur»ô7n‹-¢M›64kÖŒ/¿ü’ììlƯ¹Þ˜1c˜3gÌ›7„„f̘Á AƒðððЊM ò…¾ä#³§;rô(ÏwìÀ©´4z‡…‘¦RanfÆJ//†Õ¨aäK4Š¢{KÅ2sÒ$âu%Âþþtl×®À°ä,º•«zÅŠ„„„þòÖl>|¸&öðð`ß¾}Ò´iSœœœøÇ?þÁ¢E‹´Î5oÞ2+h[P×¼¼ÀÊêÑ@­ZT63c ZmÜÀŒLšEQȽEèKrÝdZaòòÏ>çI}å&çnV$„B”&I …&m[PуµµöµjqìÞ=bómO+„B”I K‰ä }I£¶iß~Kz¾ Œò‹<¸BÏBK¡( ¹·}I΢›$Ð¥D ò…¾¤ÑçoÛ‚‚¸TØìsžÚµ+ô,´4Š¢{‹Ð—ä,ºUèu K‹¬§(Dñ4éÚ•Kuë‚•VææT³,Ø÷œÀ † YþÍ7FˆP”8YZˆR'y‹n² ‡Â$e¨Õ˜Ï›`knNh›64¶³3vXB”ºˆˆNž]ùè£333PÌÌÌ”×^{MQE9tèR½zuÍyj×®­œ={Vë<{öìQ\]] Ä4bÄ%55UsÜÀ ýË/¿TEQ–.]ªÊÑ£GµÎáÂ¥M›6^÷î»ïêýÿ¢¢‘¼E7“œ¾uë'OžÔü–,„¨ާ¦òuîÌPG''¦H醨ÀBBBˆŽŽfäÈ‘Œ9•JŪU«Š}Î>}úÀ”)S¸xñ"/^ä÷ßÍÔ¾ôÒKìß¿Ÿo¿ý–¿þú‹ãÇÓ¢E ÆŽ˦M› œsùò儆†rüøqN:Å!Cøí·ß˜4icÇŽeñâÅDGGóÕW_‘’’„ ´^Ÿ••Åøñã5?÷ƒƒƒyçwX½z53gÎÔ÷Ýwß1qâDöíÛ§‰=00ð‰ï7..ŽöíÛsýúuÖ¬YÃÍ›7¹qã[·n¥AƒÅþ{Â$f ³³³•+V(Ï?ÿ¼RµjU­ß•.]º(«V­RrrÊæÌƒü&'„n*•Òôøq…à`Å6$D‰JO7vHÂTàè#F(æææZ3ΞžžJ£F «ï ´¢(JTT”(_|ñEãóf¨?ùä­ñ´´4¥jÕªZ×Λ®S§ŽòàÁÍø½{÷4³Ñ¿ÿþ»Öyòf‘ãããu¾ÿ6mÚ(666ŠJ¥ÒŒÍš5K”˜˜˜Ç6ýÆo(€rìØ1ד¼E7£Î@ggg³lÙ25jÄ¿þõ/š6mÊ´iÓØ¶mgΜaëÖ­üóŸÿ¤aƼÿþû4iÒ„+V”É[)Èúª¨M„3®]ãbnåg²ê†^¤‰°üJMMeÓ¦M¼ð ԩSG3>räH._¾\¬Úw}î-ÿûßÿ7nœÖ¸ƒƒ¯¼ò —/_&!!Aë¹Þ½{ckk«ùºjÕªøøø`mmMß¾}µŽ}þùç¸víšÖ¸¢(>|˜Ÿþ™¹sç2gΜÉÈÈx¦zåЮ];Ú·o_ìsTD’³èfÔu #""øé§Ÿøì³Ï:t(Öí6Ö²eKÍŸ322X»v-ß~û-;vÄËË«´Ã}&R/ô5gΜ ·x½”nO^5pà@#G" mݺuÀÞÞ^ï÷¡áÇsåÊvíÚEjj*7nÜ 44TSï\ØûÒG¥J•°´´,tñt’³èfÔè–š)É× !LOþÒ Ù0EˆGŸÎ?~œ=zðÆox>44”E‹±aÃF €³³3÷îÝ#>>^kF8::ºÀëóf… û˜ÞÃÃEQ§M›6ZÏ………аaÃ⿹Çøá‡x|öÙgØÚÚ²råJÍkò>™Ý¿¿fL¥RñÕW_8íÚµ8{ölç†й¹9_|ñjµZ3~îÜ9¶oßNÿþýqtt4̬­­±²²"&&Fk<,,Œ78>¯´¤°Ø 3nÜ8bbbX¼xqçÊb?•0FÎïþýûüóŸÿÔÔ@¥¦¦òÖ[oñÒK/‘M````e‰ä }EFF–¹ÿâx¼tãg//)Ý(¢¼B#G" %;;›5kÖP«V­'Öî:::Ò¯_?6lØÀåË—iÔ¨o¾ù&Ÿþ9cÆŒaçÎx{{³mÛ6jÔ¨¡y]Þ½ÅÖÖ–_|‘;wÒ¹sg|||¨[·.Ó§O§E‹¼ûî»,Z´ˆ^x—_~™„„~üñGœœœ˜7ožA߯¥¥%C† aíÚµôë×>}úpãÆ –-[F·nÝ LžõèÑ&L˜À† ¨^½:ƒ *0{gÊ”)l߾ɓ'³ÿ~ºv튢(œ>}€µk×ôý”™™™TªTÉØa˜4“I ccc©T©’f]Æ5kÖ`kk˯¿þŠ……...šEY$M„B_¥‰PJ7ž4–?aaa4mÚ”—_~¹@^~ܾ}›'NШQ#\]] aÒ¤Ilß¾ .0pà@&MšÄ AƒðôôÔº·¬[·Ž%K–FTT”ÖlìÂ… iѢ˗/gÖ¬Y¸¸¸ðÒK/1wî\êÕ«§9®R¥JtíÚUÓ\˜_óæÍ©V­ZqWWWºvíJÕªU5c?þø#ÎÎÎlݺ•£GÒªU+ÍìzVV•+WÖ[·n]Ž9Âþó®]»Fdd¤¦Ù°V­ZtíÚ'''Íñ–––8p€ùóç³yóf>ýôS¬­­ñööæÝwßÕõ¿£Â’&BÝÌ”âVçØÍ›7ñðð %%;;;ºuëFãÆY¶l;w&00#F9Ò¢ *^s˜Or"5•ŽgÏ¢Rž«\™#-[Êì³(ÜYðG¾Oð>°ƒ&ON,…ÏNòÝL¦ºnݺxzz²hÑ"8ÀáÇ3fŒæùk×®iýV)„(›2Õjò¯º!¥B!Ê“)á˜6m}ô·oßæ•W^¡C‡À£Žã›7oÒ¶m[#G(„xV3+Ýh"¥B!Ê“™1bׯ_'&&†õë×kÆÙ¹sg¡‹µ—ÒD(ôUžw"<‘šÊ|YuÃ`d'BQåùÞ" KrÝŒž@Oš4‰Ù³gÿüsΟ?ÏçŸnìŠE ò…¾ÊS£”n”,i"“'Oæ×_ÕëØ’¼·üøã̘1Ã`çûꫯøúë¯ v>Q4’³èfë@geeqúôiœiܸ1'OždÍš5DGGÓ·o_Þ~ûmìÊèì•ä }•—FŸLµšÑQQRºQ‚¤PEYº· 0€J•*;Œ KrÝŒž@ß¿ŸÎ; @ß¾}Y´h;wÆÎÎŽœœvìØAvv6ï½÷ž‘£Bèkæµk\Èm–Ò !DQ”ÕO›EÅaôúàÁƒ\¹r…yóæQ«V->ùäúõëÇäÉ“™5k*•ŠÉ“'óûï¿K-D!¥B<»´´4¾ùæΜ9Cll,5jÔÀßߟñãÇS¥J­cïܹ 8räööö <˜qãÆióÓO?qþüy-Z¤5ž••Å{ï½G·nÝ6l·oßfÆŒ 8OOO–,YÂñãÇ©Y³& 0@¯÷°víZBBB4h½zõ $$„~øèèhÌÍÍ©S§;vdÒ¤IXYYðïÿkkk¦N <ª±Þµkׯ3kÖ,jÖ¬©ùzïÞ½¬_¿žððpjÔ¨A×®]yï½÷4çâY½zÿþýôìÙ“?üáÇóöÛoI`` 666ØÛÛÈ‘#G¸ÿ¾±Ã-6)Èú*ëM„RºQz¤‰°ü:þ<¾¾¾Ìž=›œœºté‚££#_}õaaaZÇÞºu‹nݺqøðaêÕ«ÇáÇ äßÿþ·Öq›7ofùò宕““ÃÒ¥K9|ø°f,%%…¥K—²fͺuëFTTuêÔaÇŽ 8µk×ê|Ÿ~ú)o¼ñvvvôèÑ€ 6Э[7Ž=Jƒ hß¾=jµšY³fi-Y»~ýz6mÚ¤ùúþýû$$$x¬]»–¥K—’’’¢9vâĉôìÙ“ÐÐPZµjÅ•+WøðÃiß¾=ršÅÓI΢ÅÈüýý•Ù³gk¾Þ·oŸR©R%­c>|¨Jhhhi‡g£FRüýý†(#FeìžÉÔèh…à`…à`e^l¬±Ã)×¶lÙ¢lÙ²ÅØa”¬m™Š2.õïGdޱ#*q*•JiÓ¦âàà ?~\ë¹ôôt%%%Eó5 Ê’%K4cIIIJƒ ”Ê•++šqwwwÅÎήÀõÒÓÓ@™0a‚fìÒ¥K  ˜™™)»wï֌߸qC±±±Qš5k¦uŽ^½z)5kÖTEQ²³³•Ñ£G+æææÊ‚ ´Žëܹ³R­Z5%))Ik<--MÉÉùûÿ­Ò¦M›'ÿ%)вbÅ P4c›7oVeæÌ™ZÇ®\¹R´ò ñdþþþeþgQI3ú ´Z­ÆÂÂBóuþ??NQ”Ò©DHA¾ÐWYjôyÜÉ´4æå+Ýx_J7J”ìDX>sêÔ)&OžL»ví´ž³³³£råÊZcÞÞÞL˜0Aóu•*UèÑ£©©©\¿~]3Þ¶mÛ"ÇòòË/Ó³gOÍ×uëÖå¹çžãâÅ‹äää8>--¾}û²víZÖ¯_ÏäÉ“µžwttÄ¢ÀÏz‡§þü\pp0o½õþþþ,[¶L3>wî\êÔ©S`E‘#GR»vmYOO’³èfôh€}ûö‘‘‘Àµk×ÈÉÉaÖ¬Yšç ûG*„0-²aІ@·nÝô:Þ××·ÀX›6mXºt) šÕ­ŠÃÏϯÐs“˜˜¨UwüðáC:wîÌ7Ø·o:u*ðÚ±cDzsçNj׮͠Aƒ8p /¿ü2––ú§#QQQ 2OOO6oÞ¬U׉³³3ü1Š¢ ( jµEQpttäòå˨Tª"%ëBÆ$èýû÷³ÿ~­±O?ýÔHÑ!ŠcV¾U7>“U7„(¶¼w]\\ô:ÞÆÆ¦ÀX^R©V«Ÿ)–¢œ[¥RqïÞ=ÌÍͱ··/ô|ƒ "44”¯¿þš-[¶°zõjjÕªÅ;ï¼Ã´iÓtÆ“˜˜Hß¾}±´´dçÎZÍ”$''ckk˹sç ¼ÖÝÝwww233Ëì’¸Ât=>xð ±C(R/ô‰———±Ã()Ý0޼B}-Q6xxxpýúuš7on°ófdd••EFF†Vb|÷î]ƒœßÁÁC‡ñ /н{wvíÚU  yóæ¬ZµŠ¬¬,‚‚‚˜;w.Ó§O§OŸ>´jÕê‰çÏÌÌdàÀÄÅŬù{ÊcccCíÚµñððxêŠB·ÌÌLY‡[£×@W²«ÐWYÛ‰PJ7ŒGv",ŸÚ¶m‹¹¹9«W¯6èyóJ$=ª5~àÀƒ]ÃÝÝC‡Q£F zôèñÔïOkkk ÀâÅ‹زeËSÏ=fÌŽ=ʪU«èСC¡ÇtèÐS§Nqéҥ⿠!9‹Œž@ß¿Ÿääd½*•ÊØá›ä }•µ&B)Ý0i",Ÿš6mJ`` 7ndÚ´i¤¦¦ÖkÞ¼y3ÑÑÑÅ:ï7ß|À¼y󈈈@¥R±k×.ƒnÁ P§NBBB¨_¿>½{÷ÖJÐßÿ}N:¥Y ))‰Ÿ~ú xô¾ŸdöìÙüúë¯Ìœ9“þýû“‘‘¡õÈ;ßܹs177gàÀ=zT3žžžÎ–-[X¸p¡Aßky%9‹nFO ûõëGÕªUõz„‡‡;\!D>Rº!DÉøúë¯yë­·øâ‹/¨R¥ ®®®ØÚÚ2dÈMtQ½øâ‹Œ5Š]»vѬY36lX¡kC?+WWWµ3V~#ÂtHé†%ÇÎÎŽü‘÷Þ{3gÎGÍš5éÚµ+îîîšã<ˆ««k×÷îÝ›àà`Z´h¡5þóÏ?3räHÎ;‡››]ºt¡ZµjS§NÍquëÖ%88˜ 8÷رcéÑ£‡Víýܹsµ6Bpvv&88˜3gÎhêiccc9vì/^äÞ½{T¯^öíÛXIdåÊ•ZùÀ‚ HJJzâß—›››æÏ]ºt!,,ŒcÇŽqáÂ}šeË–ñÛo¿aooÏ?þñžúQNY@ff¦^;7 QVšÿuõ*_ÆÆ0ÏÓ“ëÕ3rDO…h"ü# þÈׄý4‘%ÈŠ£¬Ü[„ñ½þúëTªT©Ì•–&£—p´nÝš¥K—ϼyó8sæ Íš5£C‡üøã$'';Dƒ‚|¡¯²ÐDx2-¹RºatÒD(Š¢,Ü[„iœE7£Ï@&66–_~ù…+VO\\ÎÎÎÆ«Ø€²×&Da2ÕjZŸ>MDz:6ææ„¶i#ƒ¢äÈ ´¥NòÝŒ>ý8EQ¸ví111$&&âìì,; aB>½vYuC¤¤¤°aÃBCCŠ¥Ê$šáÑú”¿üò «V­"..ް~ýz…Å‘””Ä®]»HJJ¢OŸ>c‡G‰|pp0'OžÄÃÞ={jíx”'!!½{÷rëÖ-:vìHÇŽ «¦$éF)ݢ»páC‡å…^`ß¾}ÆGˆRcôè­[·âïïOƒ  â£>"!!ß~û>}úÉYt3ú ôÂ… 9v쯾ú*M›6%!!በ?žš5kûZy+~|úé§š…ã:D×®]Y¶l™&ù]·nëÖ­ãÈ‘#´k׎ЩS'Þ}÷]¶oß®9ߘ1cðõõe÷îÝXZZ²eËLß¾}éÞ½»Öµ¥ _èkΜ9&Yw&¥¦'¯P6Sú(‰{ËÍ›7GËÞ‰òãÖ­[²t°FO °±±a÷îÝìÞ½û©Ç2ä™èÛ·oпÍXçÎqrrÒ<ðÃ?дiSÚµkë×¯ãææÆ‰' ãçŸÆÒòÑ_ã AƒprrbùòåhùFú2Åäù””n˜$IœEQ”ĽEèòIrÝŒž@çŸÑ-iþþþ899ñË/¿àëë‹¥¥%›6m"%%EëÑùóçéÛ·¯Ök}||P«Õ\¸p777 ,þîíí­yNˆòàñ S¤tC‘GJ8DEeôèÒdccÙ3g8yò$®®®xxx0aÂöïßÏsÏ=ÀÇIMMÅÇÇGëµÞÞÞÀߥýõšñüÇýõ×_®}óæMBCC‰ŒŒÔzÆ cذa8;;³fÍîß¿ÏG}¤9îí·ßfÍš5ôìÙ“€€NŸ>ÍÖ­[Ù¸q£æ˜J•*ñÙgŸñÎ;ï~øþûïiÕªÆ +Ñ÷!DIÊzlÕ•²ê†¢ÇŽ$“Ñg K“‹‹ gÏžeäÈ‘œ?žÝ»wóüóÏsúôiºté¢9®zõê=zb¼[§ IDATMÒ¼oß>­õ£áÑÆ.yEøüñ¯½ö!!!ØòQ·ìê#ôe¬ÿ÷矀véÆ¿¥täÉN„¢( }oÉ›Î[ÅJ”’³èf¦(Šbì Ê»€€bcc9pà€±Ce@@@@©×AÿÄð·Þâ×Ý»tû69ŠB‡Ê•ù_Ë–2ûl¶nÝ ”óZè?²à|?Ì?°ƒ&Æ‹§ 3ä½%--*UªàèèHRR’Ô@—3Ý»w§~ýú&Ó“cŠL¦„£¼“‚|¡/cܰf|÷i³g3|æLr&LÒ2¢\'ÎÂà yo9yò$jµšvíÚIò\I΢[…*áBôGPך6…úõIÍΆ۷¥tCñTRÿ,*:I …¨àf|÷ɽz=úbèPªmÚIJê†â)vïÞ @§NŒ‰Æa2 tvv6dggúü„ мk¡)‘‚|¡¯Òl"ÔÌ>[Y=¨U kàæ¥ƒ(>i"Ea¨{K\\GŽ¡jÕªøûûäœÂ´H΢›É$Ð*•Š_~ù•JUèókÖ¬)Ó{³ßºuËØ!ˆ2"oCŸÒ 5ûœ+aà@ÞÿüóR‹Aß‘#G´Öµâi uoY¿~=jµšÁƒcmmms Ó"9‹ne¢‰022µZM“&MŒJ±IA¾ÐWi5þDLþÙç<µjq<%…ØØXù¾5qÒD(ŠÂP÷–uëÖðúë¯ä|ÂôȽ_7£Ï@ïܹ///š7o@óæÍñòòÒ<<<<ðööæÅ_”ßt…0 ß}GÊc³Ïyn ³ÐBˆbbb8qâ5kÖ¤[·nÆG£1ú týúõ6l999|þùç¼úê«XZþ–½½=]»v¥M›6FŒRˆòå  ®zyœ}Î#³ÐBˆBäÍ>¿úê«XXÈzÜ¢â2zݬY3š5kFNN–––LŸ>]+./¤ _è+22//¯½ÆŒyóÈðð€µk¨ji‰åck¹ÞÏÎæÓ øÏ‚%‹(¾¼B#G"ÊCÜ[¤|£bÈÌ̤R¥JÆä™L¦jiiɬY³ŒF‰‘‚|¡¯9sæ”xô´~àÕÜmx‡T¯ÎlB«Q£F‰Æ!žM^¡ÔB }<ë½åðáÄ……áîî.Ûw—s·nÝ’Ou0™:++‹ž={>õ˜åË—Ó°aÃRŠÈ°äQ諤“gµ¢0;- ê×Ç΂…íÚQWfÊ$IœEQ<ë½åóܾˆÉ“' aÊ$gÑÍdh333ÜÝݵƲ³³9pà 2Dš…0€ œ»€êÕ“äY¡Ó©S§Ø½{7Õ«WgܸqÆG£3™ÚÊʪÐߎÕj5‹-bÛ¶mÔ®]»ô¢IS©˜@J•ø¿zõŒ‘¢,øâ‹/˜2e vvvFŽFã3ú2vº˜››3~üxÎ;Ç©S§ŒN±I¡ÐWIîDøÅõëÜÊÊàË °“.ú2Mv"EQÜ{KDD[·n¥J•*¼óÎ;ŽJ˜"ÉYt3ùíR˜““CLîÌYY$M„B_%µᵌ ܼ @[GGþáêZ"×¥Gv"EQÜ{Ë—_~‰¢(Lœ8‘Ê•+8*aŠ$gÑÍdJ8Eáxîªù]ºt‰7’‘‘A»v팙aHA¾ÐWI5þ_t4™j5 6ÄLÇñÂôI¡(ŠâÜ[Î;Ǻuë°··—æÁ DrÝL&ÎÌÌ,tYGGGüýýY¿~=žžžFˆLˆ²ïHJ îÜàµ5èääd䈄¦N¥R1vìXT*S¦LÁÙÙÙØ! a2L&®T©R 333j×®Õ“vKBè¤S®\ÀÆÜœ¯40n@Bˆ2áÛo¿åÔ©S4iÒ„éÓ§;!LŠÉ$Ð…-cWžHA¾Ð—¡w"\À©´4Þ¯W7ƒ[—ìD(Š¢(÷–ëׯóÉ'Ÿ`ffÆO?ý$»ÒU0²¡n&×DÃÔ©Syùå—éÝ»7ï¾ûn™^}#ä }²‰0]¥âãÜOvjZ[ó±Ôµ•+ÒD(Š¢(÷–·ß~›ôôtÆOçÎK0*aŠ$gÑͤèßÿ&Mš0þ|nß¾MNNË—/§mÛ¶šÊ*)Èú2dáÜ7ø+÷ÓÏ=þøc>ùä®äÖq !t»‘™É¼ØXZ:8P³¦‘#B˜ºøøxÍjßÿ½,['ĘL}ùòeRSSùòË/©R¥ŠfÜÖÖ–O>ù„š5k–‹R!JËÇW¯ò0ß²uæf²pâÉ2224h·oßføðáôïßߨ! a²L&vssÃÜÜœ{÷îx.==û÷ï´±ª´I¡Ð—!v"<žšÊ¯¹5lƒ«W§k¾_JEù!;Š¢Ðuo äøñã´hÑ‚eË–•RTÂI΢›É$ж¶¶L:•>ø€S§N¡Î9»xñ"“&M¢W¯^øùù9Êâ“‚|¡/CÔN¹r°67gž,[WnI¡(ЧÝ[æÍ›ÇêÕ«©^½:[·nÅÎή#¦FrÝLf»¬¬,öìÙÙ3ghÛ¶-ÕªUÃÖÖ–¸¸8Z·nMÇŽ5ÇÏ;—.]º+Ü"“‚|¡¯gm"\{û6¦¦0©NØÚ *aФPÅ“î-;wîdêÔ©XYY±iÓ&ÜÜÜJ70ar$gÑÍdhsss:tè@‡ô:¾jÕª%‘eÏCµš©W¯PÃÚšéòƒPñ/^äõ×_G­Vóý÷ßË’uBèÉdhKKK.\hì0„(Ó¾¾qƒØŒ þíîNeK“ù'.„01—.]¢G¤¦¦òöÛoóÖ[o;$!Ê “©.ï¤ _諸M„ñYY|•»l¯½=ckÕ2dXÂI¡(Šü÷–ÈÈHºuëF\\ `Ñ¢EFŒL˜ÉYt3©úÁƒlß¾‰'Ò±cGMIGÞãòåËÆ±Ø¤ _諸M„Ó®^å¾J|À§îîT‘ZÆ %¯Pj¡EaÒÒÒ1b¿ÿþ;ÖÖÖ´nÝš~øÁØa‰2àÖ­[R­ƒQÚ^¼x‘>}ú_µj«V­*0~öìYZ´hQ¡œ|# }é›<ßÎÊâóÜeë¼ííy«víŒJ˜"IœÅ“\ºt‰rñâEj֬ɦM›´vóâi$gÑͨ tÛ¶mIJJÒûø²:û,DIøäÚ5RsrøÚÓKY¶NlÛ¶‘#G’’’B‡Ø´iµål! ʨ ´……UªT1fB”Iáééü'>€>ժѻZ5#G$„0¶¤¤$&Ož¬ùwìØ±,Y²kkk#G&Dùc2“Š¢pîܹ'>oooO½zõ°±±)Ũ G ò…¾"##ñòòzê1S®\A¥(Xš™ñuÆ¥™05y „...FŽDÛï¿ÿÎÛoÿ?{wUÙ>pü;ì à†;*ˆûnjj.å¾—¹d*¾n-šùs×rÍĥĴÒ2—RS[ÕÒ·ÌJ_Ë}WDÅ…MQ\fÎïÃ6²Ì 03Àý¹®s)Ïæ< ž¹y澟ûM¢¢¢(^¼8AAAœcʽEPcGGGKOêYMnbb" 6Ìö¨^½:...ôèуððpKO7פˆP˜ÊX'·o³'%õérå¨)ÛÖYÒ‰P¤îéÜ»wo¢¢¢èÚµ+çÎË<ÃÓw9EÄ,ÆYÍ ´££#7ndÚ´iôë×öíÛðÇðÝwß1kÖ,nܸÁ7ß|Àø÷ß Ô6<’/L•Sa’¢01, O;;f˶uEšmßÿ=o¿ý67oÞÄÓÓ“   † ’íùOÓåTM³g5´F£aòäÉ,\¸Aƒ¥wéÒ…Zµj±`ÁBCCiݺ5/¾ø"¡¡¡T¯^Ý‚3Âü>ˆàb|<3}|(aooá !ÌíìÙ³Lž<™]»vгgOV®\IÙ²e-<3!Š«Y #&&&Ë•—_~™+W®AÛ¶m)]º4W®\±À,…°œÛIIÌMI_ªîâÂÛåË[vBB³ŠŠŠbäÈ‘4hЀ]»váååņ ضm›ÏB˜™ÕОžž$''³~ýúL­]»€âÅ‹cccƒF£ÉÔdÅÚI¡0Uvg‡‡—²mÝ’jÕ°—mëŠ<éDX4×®]»`ccCrr23fÌ`Ĉ|úé§€Úð¥råÊ|óÍ7¼õÖ[Ï4OQ´]zü˜å7nÐÆÃƒ>ò½ÎíÛ·Y·n_}õçÏŸÀÖÖ–nݺ1bĺwïŽÕ¿ !²P –´lmm±µµåâÅ‹Ïô<'Nœà¿ÿý/ýû÷Ï6xØ´i5jÔH žZ·nÍÕ«Wù÷ßøûï¿‰ŠŠ¢uëÖiçT¨P___6mÚôLóbRXIŠ‚FÃRÙ¶Nˆ#,,Œ   Ú·o··7'NäüùóT«VùóçsíÚ5~ùåz÷î-Á³˜ÕüïU… .d¿xñ"[¶láþýû4jÔè™®qòäI|||xùå—9sæ U«Veòäɼøâ‹iç…‡‡Ó©S'ƒÏ­U«VÚc­Zµ"<%75u|8Å‹çùçŸ'00àà`*V¬H‹-ذa±±±lذvíÚ±páB‹OS‹-ýý“1ëKI:wîÌÔ©S9räÂÅJèõzåĉÇÉ“'•û÷ïçÙ5fÏž­ÊÈ‘# Æ+T¨ ´lÙRQEINNVlmm•3fœ¡JPP¢(ЍÊíÛ· Λ0a‚âììl06tèPåÅ_TNœ8¡œ?Þàxôè‘Á¹2V´Ç¾ˆŒTøë/…õë•9!!V7?“1s]ÿ,LIvGQFÞWägz¾àà`%444ן©Ì™3Géܹ³âää¤iGýúõ•Ù³g+Ç7Û÷EÆd,/ÇRc’_ýU9qâ„Ò»woeèСŠÈžFQÅ’¼9­^½š‘#G²cǺwïž6>jÔ(¶lÙ’¶]… hܸ1Û¶mK;g÷îÝtìØ‘ï¾û޾}û²aÃ̾}û ò ;uêDXXXZûU€€€@* EÎètø:DŒV‹“çŸ{'ÙyCu;´°#C Üð·ÍõÓ(ŠÂ?üÀòåËÙ´iåMØòôéÓi©G%õåÒÁÁ¶mÛÒ³gOzöì™';D aM$n1Îjr Sݼy“/¿ü’´Z-~~~ :??¿g~îúõëa0iÐù°^½zœ>}Úàœ³gϦ=–ñϳgÏЧOŸ¦E‹Ï},=%Q¤v!”†*éôz=›7ofêԩܽ{—êŽ;wæ7ÞààÁƒœ={Ngð¹~~~4oÞœæÍ›ÓºukêÖ­k‰/!ßÊ[òÂ$111ÒLūɾví•+WæÐ¡C<÷Üsݾ}6nÜHÏž=ŸùZwîÜa÷îÝ„††R½zuz÷î½½}¦óöîÝË¡C‡¨X±":tÈr•çæÍ›ìÞ½›ÈÈHZ´h‘e VÉ%9Ùï­Oœ`ˆ·7ëe…Hˆt&æ@ët:6oÞÌ”)S¸wïžÁΆ'_êÜÝÝyî¹çhÞ¼9-Z´ Y³f”(Q"ß¾ ! ‰[Œ³š褤$4 ®Yìyëää„““I)mŸ•——ýû÷7z^›6mhÓ¦MŽç”.]šAƒåɼDѣ列³µåà ÛÖ !ŒÓét,\¸E‹Ÿåë„¢(T«VvíÚ¥­0׬Y©3B<%«¹{T­Z•R¥J1}úô´Ý0@ ¬.\H\\Ü3·ñÂÚ|ͱ”·˜'W¬HyÉ9Â$ÉÉɬ[·Ž *0sæLîÝ»—ã"Ktt4^^^ 2„ÚµkKð,„x&VsÑh4¬Y³†;wR²dIZ·nM÷îÝñòòbÞ¼y|øá‡T®\ÙÒÓ|jRD(žôH§cú•+TptdRJ¾YHHˆ%§% ØØØ´B¢D«Õ²lÙ2¶lÙBñâÅqrrÂÆÆÜÝÝñôôÄÍÍ ‡´ÏyôèË–-£N:Ý‹¹·SIÌbœÕ¤ptëÖsçÎñÉ'Ÿƒèß¿?Ç/ð[ÃI¡xÒÂk׈L¹IV­ŠsÊŠ˜úSÕ"B&L˜À„  ÆïܹCTTÑÑÑi^¿~+W®ÁÍ›7¹qã5"00qãÆY¼½¶9ɽE˜Jгš:<<œwß}—/¾ø‚åË—[z:yN~EF×Yrý:ÍÜÝy­L™´ÇäN˜ª¨ÎÆxyyáååEíÚµs|ˆ›››™fgyro¦’˜Å8«Iá°··gûöíYî†!Da3õòeëõ,õõ¥è¬ ayŽŽŽøøø©àY‘·¬&€._¾<5kÖ”ßE¡wèþ}¾MIéXº4-¤£™BQ XM ‡^¯gÒ¤IL:•S§NѱcÇLMSš4i’å6w$ä‹T¢Î66V­šéqé&L%EnȽE˜J:g5+ÐZ­–aÆÃúõë4híÚµ38BSöË-ˆ¤ˆP|{ó&ïß`BÅŠTrrÊtN`` ¹§% ¨ýû÷§ aŒÜ[„©$f1ÎjV ¹’²¥Wvžµ·%IB¾x¬×3õòeÊ9:25›Ÿ Ic¦’"B‘ro¦’˜Å8«  5 >>>–ž†ùæ£ë×¹–Àü*U(f›¹±B!¬ŸÕÐݽ{—Ó§O£OÙ¥ UAÎE“^¯gãwßñRŸ>^»@c77†z{[xfB!„xZV@ïØ±ƒ… rðàAt:]¦ÇOžÂTRD(rCî-ÂT³g5+З/_æÁƒ¬X±???KO'ÏIB~ѳnóf®5k%J ¸»ãp틚77þyRè#L$E„"7äÞ"L%1‹qV³]µjU¹w§"Ä3K]}ŽoÛVèߟr?ÿL•,¶­B!DÁb5´““³fÍâƒ> 22ÒÒÓ♬ۼ™kÏ=6)ÿÅ<=yäêʹà`ËNL!„ÏÌ¢)ÁÁÁ¼õÖ[c‡¢B… 4lØ777ƒÇV¯^¯¯¯9§˜g¤ˆ°èÐëõ,Z·Žø©S ÆoõéøùóùcãÆ?_ }„©¤ˆPä†Ü[„©¤ˆÐ8‹ÐŽŽŽ™ö~Îi/h‡üP>’„ü¢cÝæÍ„g\}NåéÉÎS»V­l??00Pr…IR %Z˜Bî-ÂT111’m„FQÅ’xøð!vvv8âÜЀ€@ 8н^OÍN¸8mZæàÎ^üᣫÐBˆ;´°#Ã;x\À_š ‘Ÿ$n1Îâ9ÐÝ»w—­uD¡±nóf®4mšuð àåÅi\h!„¢³šmì„( >ZµŠd''8~{ï,ÒŽ´wïòÑ_°&(È3B!ij’ÚL¤ˆ°hðþä‚ãâ°ÑhØß°!Ϲ»çú9¤ÐG˜JŠEnȽE˜JгŠ:::š“'O=Ïßßggg3Ì(ïIaá·6:š?ãâ[¾üSÏ …>ÂtRD(rCî-ÂTRDhœUЫV­bÕªUFÏ;qâ 40ÃŒòžü n7µZ&†…PÉɉªTyêç’8a* œEnȽE˜Jbã¬"€îß¿? ÓÈÑÖ IDAT0z^•gJ„ÈOr') €Ïýüpµ•]„BˆÂÊ*è5jÈJŠ(°~½}›-7o0°tiº–(aá !„"?Y|»¢BŠ §‡:o^¼€—½=AyÐ)3$$䙟C ±±±i…„B#÷a*‰YŒ“ÚL¤ˆ°pš~ù2×Sn4U«Fé<è–)û¢ Síß¿?­PcäÞ"L%1‹qOáX½z5®®®–žF¾“„üÂçÐýû| À‹žžx{çÉóJ¡0•¤¾‰Ü{‹0•Ä,ÆY<€ö̓·¼…0·$EaÄ… ègVU¯né) !„ÂL$…Cˆ§°èÚ5Î>zÀlªÐýÉ…B‘{@›‰$äâã™wõ* \]ù¿Šóôù¥ÐG˜JŠEnȽE˜Jbã$€6IÈ/`ÔÅ‹$êõØj4¬ö÷ÇN£ÉÓkH¡0•ŠÜ{‹0•Ä,ÆY<º¨„üÂauTûîÞàÝ hìæ–ç×Ba*)"¹!÷a*‰YŒ“h!L¥Õ29¥]w''æúøXvBB!„°  …0ÑØK—¸›œ ÀÊêÕq‘vÝB!D‘$´™HB~Á¶-6–nÝàõ2eèèå•o×’Ba*)"¹!÷a*‰YŒ“ÚL$!¿àºŸœÌÛ—.PÒÞž¥ù¼w¹úSI¡È ¹·SIÌbœš‰$ä\S/_&"å·ñ¥¾¾”´·Ï×ëI¡0•ŠÜ{‹0•Ä,ÆÉ ´9øçÞ=V¦´ëîèåÅëeÊXxFB!„°4  …ȆV¯gä… (€‹­-+¥]·B!Úl$!¿àùðÚ5ÎÇÇ0×LJ*NNf¹®úSI¡È ¹·SIÌbœÐf" ùKð£G,¸v €Ænn¼[¡‚Ù®-…>ÂTRD(rCî-ÂT³'E„f" ù‡Œ¼x­^]J»nÛÂTRD(rCî-ÂT³'E„FÄÇÇãââ’ã9Z­[[[lmm³=Gò­ÛÝädƦ´ë.íàÀGÕªYl.Rè#L%E„"7äÞ"L%1‹qEvúñãÇ4iÒoooŽ?žéñ P®\9Š+†¯¯/ßÿ}¦s®_¿N›6mpssÃÝÝž={r÷î]sL_ä±ÉaaDkµùúâ•Ïíº…BQpÙúý÷ß'66–˜˜´)SªåË—óþûï3wî\ÂÂÂèׯýû÷çï¿ÿN;G«Õò /””ÄáÇٳg!!!tëÖÍÌ_‰xV{ïÞeuT]K”``éÒž‘B!¬Y‘Lá8tèëÖ­cÅŠ 8Ðà1EQ bÀ€Œ1€?ümÛ¶±lÙ2Ú¶m ÀO?ýDxx8Û·o§nݺ|üñÇôèуC‡ѬY3ƒç•„|딨×3êâEÀÕÖ–Ïýü,=%BBB¨Q£†¥§!r!66–‡=ÏËË ww÷<½.@É’%óì9…éFõì‰6""Çs´:-ûöåí÷Þ3Ó¬²'÷aªÄÄD-= «VäèÄÄD† Æ‚ ðööÎôxHH—/_æÍ7ß4¯[·.?ÿüsÚÇ;wîÄÞÞÞàfÔ Aƒ´Çž  %!ß:Í»z•‹)íº?¨R…JfjדÀÀ@ÉU,`^ïБ‘8åÐpçjR¥ûôaáêÕyvÝÔ– ýÛŽD]¿@Å3å¨\6í±s›.r¯Ì6kFýÆ-2G€ÊÕªÑé×_i¢×g{Î//ê·jeÆYeOî-ÂT111’mD‘  çÎK©R¥1b{÷îÍôxDÊjBêªrªÚµk³eËbcc)Y²$øùùaŸ!W¶B… xxx¤=GFòƒh}ÎuêÕ{Úi=³Õóç3èÐ!<€tÂŽi•ÿç3< c¿­-÷Þ{Ï¢ôØ9s³m_§ìÿ¤xà„ŸSÞ¹´4¹·SIÌb\‘Ê>~ü8Ë–-ã‹/¾@“ÍJÑ­[·ðõõ5¯–²+Cêã·nÝJËÈ××7휌~üñGúôéC@@€Áñ侜2fž±¡¼þÛo$) v _V¯Î† ³šùÉXÁëܳ'{+Wf0ðäN»Àï€Cóæi/J–šóà~ý¸1dޝ† áA†/0xÀ‹~O§ú)§J•¢-ð'à ”M9žÖÊ—gÔ”)™_*wwwÎ:;óÝûÅ þ|êåÅ›)[ÇYÃÏ©ŒÉXvc)1‰¯¯/}úô‘í1M QE±ô$Ì!))‰&MšÐ·o_Þÿ}þþûoÚµkÇhÞ¼9¿ýö;wfÏž=´oß>íó™6mÑÑÑ”)S†víÚq÷î]Nœ8apÒ¥KÓ­[7Ö®]›6ÈoÿÖä“7 À´J•ø°jU ÏHtÿݾ‹ƒóN«Ðo”-Ëôƒ-¾ªsâøq~ìÒ…y7of{Îë>>¬8q3Î,³€6mÚ·:A†høŒ¿ì®rjútÞ3çÙ.’” ðø±z¤þýÉ?sxìþ½{¼½u+ß<‘ÿY³&›þþ¤0Y0·WdR8‚ƒƒ9}ú4vvvüúë¯ÜOy¡1bÍ›7gõêÕ”+W€3gÎÐÁÁÁØÙÙQªT)Ê•+ÇÁƒÑétiû?ÇÄÄpëÖ­´çÈHŠ­Çµ„f¤¼åêçìÌL+k×-…>€N‘‘põ*„‡ÃÕ«t¾z•/õzF3é/ö±±TjÙŠõÏìŽ\<ûèh4&6lÔˆ š5¹só&^Y<~ÔÆ¿îÝÍ<'%V«‰‰™þ>î?ÿ!èôifg±3è*Ö:;Ãä*ØÍ4¦Ó=ó—áÔ£®Ž§úxëüy(S¼¼ fMãF ðñòæóšÜ[„©¤ˆÐ¸"@—+WŽ¥K—Œ…††rþüy:tè¶]³fM¼½½9sæŒÁ¹çΣM›6ؤ¼U×®];6mÚÄåË—ñKÙ¹!õsÚµk—éúRDh=Þ¼t‰‡)/œ_øûãdvÝ9‘BŸt5¢Ll,Ù·(‚X­–]»²dÍš¼»°V ׯÈiGx8ܸ)-ß3 | ŒÍ0¶˜–”¤~Nۯр£#½ÝÝM À]\x·Q#–?μ2=_P‰¬¨QV­Ê2¨Í)àÍÕyZ-yó³!° x¸f?4ÅyÚ´<üN>{{pv''Æ::òVt4’’uõù$09õÜ;wàŸÔ##ggð÷Ï\ûùƒCžM5>>­VËܹsùì³Ï²=ÏÅŇ<¼®(¸¤ˆÐ¸"“‘•¬R8fÏžÍâŋٶm/¼ðk×®å7Þà§Ÿ~J+Úyôè•*U¢uëÖ¬Y³†ÄÄDz÷îÍýû÷ 6ȱ–·B¬Çæ›7 Àð²eYíïoቜl^³†Ä1cúøq¶çL(UŠáýE­ÚµMâøxÀøÉ9:rØY!F]i¬\™W.\`ÓÝ»8W€«Wgy§Nðè‘zÝG²>R3Ã-y(° V¡¿³òýê¦;œ§¯eHáÏg|HΩvvàä”ÌüiêØÓœokøkÝüñãyé“Oh¦×³ÈË‹ç?ÿœVpþ¼á‘EL&vvPµjÖ«Önn¹þ>¶«S‡ºwïæXô©Õâÿê«ÌûôÓ\?¿(|$n1®È¬@çÆûï¿OTT;wÀÎÎŽ?þØ â½X±büùçŸôìÙ“R¥J¡×ë©[·.¿ýö[¶ŠÂ²î$%¥å=—qp`±Ûu Óô  Ï¢E¼~áB–«ÐÑ@\½z™ƒç»w3Åÿž²²Élm¡|y¨\9ýðñIÿ{¥JjPŒÜ¾/fÌýû,.WŽ©»w«›êñãìƒëœoSKI%{5€ž—á²AÀŠìæ¤Ñ¨+¢©‡£cÖÏãÇ:8°wÄw$^G RcCƒÁCq^òFz0kg/ccçÌáÍŸfUx8'ªUcr¿~ê;žxûvæ úüy¸v-ý—§äd¸xQ=¶m3üü 2Ö5kæ˜g=úÿþÆŽe@Ê–Yy§|yFf(ÊBä¬H¯@ëõz´Z-ŽŽŽY½< ,,Œš5kæ˜ téÒ%³}»C~“³ÃBBX ÀÖÚµy5%Ÿ]X·ÍkÖ 3†!Y¬BO,^œÿŒI­„ÀùÞ½Ü]ÄÑ*V̧*ä*PëÛ œ:ŧ}ú°üÇs7—ü¤Ó¥ÓC{÷féáÃx¡æ>ÿ:`³æÌÉ: µ`kûËKƒ‰›¾—Æ ÙãæNËýqªWÌbóÉÉüñã9¹z5ãvì Un·®{ô.\ÈX‡†ªùâÆxy©+ÔOÖ>>è…~uê°õüù,W¡¯½ü2Ë~ø!ws…–Ä-ÆYǯîbccƒS3ÜÜÜÒš£äÄÏ„îuRDhY{ââÒ‚ç%JXuðlŽBŸ¡;£1ÒA->9™]»2ý£òu.îܨ(õˆŒ„¨(úEFò²ƒÀ`:¸sïµ–,1þ¼®®ê*ð“ÁqêÇÞÞyZÌ5bî\º Ä_AAyöœYÉu'B[[pwwwÞýüs–¦ìÈT©+>ý,¼óFVªúúò·ç).Ç$RÆÏ'GË7;ÊÎØ9sXðøqîƒgPsÔ5RŒ’’ ,,s`¢Ý©îÜÿUŒœ±ñ÷§¯»;Kíì˜Eîþâòå™ôD(Ú¤ˆÐ¸"@›“ZÎc½žÑ/àfkËgÕ«[xF93GaÓV­¨2w.ÝrXÙšïéÉ =z<ûÅE}Û:CPœöç“ÏâM`° œa| 01õ/¯Ì«Æƒå%žýëÈ…Î={²a×®|/Ây–N„©;rük¾7žRÝæÍÙ½s'}ë_а$www¬\™·Ojo¯®,ר}ú¤+Ššö‘U:ÈíÛéç=~ 'OÒ¨ ŒÇ°ÄU@ãáA¥3gÔ_"+VÌÛù‹IŠ+Ò)æ"o…X֔˗Ó:.÷ócŒ•t´¤ÄÄDÔªÅO—/gùø`D“&l9r$û'ÑëÕ‚¨¬áŒŠ2í-è츸 /[–W"#ùþñclQWŸ§Õ¯Ïš Ôù) «„º/töí9n½ô-ìH$9Y‡-Lpÿœöeܺ•e`½ùÆ ôŠÂkN LÒB%¨WÏð¨SG]!E†Ä-ÆÉ ´(ÔN>|ÈÇ)­‹[¸»óV{tEŽŽŽ¼8l;çÎ¥kÁíòâÅ;jüòKöÁqLL–[¹™ÌÍ Ê–Urå²þ{Ù²P¼86@ÿ5køvìX^ç£Ò¥™¸q#äfç ‘IÃF8j½Ásvv4›¬T)õxáƒá~÷ïójƒ ¸r Ð8;S)c}ÁÝ»°oŸz¤²±Qw©Wê×O¬«T1ë>ÖBX  E¡¥SF\¸@²¢`¯Ñð¥¿?6r³O3rÒ$®]K×'V¡§îÝcú¨QO÷ÄYÂOɹ\ÑêÀ+‹óbH·ëÖÍݶu"[&çO‹ÏÆÝWß{oÇŽeP|­ØááéÛí=|¨GF•+g^­öõÍ´wö“Žÿû/oîÚENIv?99ýÜs9œ!ò‚'´™H¡y]IH`fx8þ..̨\ٲʅ|-"LJ‚ᅦeËàÐ!F¯‘@?N•+Ç´3 ƒboï<íŒö´ú`ko/«Ï)ž¥ˆP=©÷–~“&ÑóÝwÙ•ÓÎ66jGD??xå•ôñÔÕêŒõ™3pÿ~ú9©ÛIîØ‘>æì¬¦\=™_¡Àwê'Ÿ°¢M‚"#³œ’ì®\™­£G?åw@˜JŠ“ÚLäѼ޸x‘x ð¥¿?ŽVÖ®;'ù<ߺ_|Ÿ}¦æ0§p´³£]Íšì:ž.ÉɬðôdÌÆð4Ûp™ ¯lüÄ"Bg‘©÷–~”(Sæé^—ÜÜàùçÕ#•¢¨+ÓƒêÓ§Õ”ÔŽžÃÑ£ê‘Q¹ri+Õ¾õê‘àçGDdd–«Ð?8;ÓsìX쬤yNa&1‹qòS( 11ü~ç#Ë•£õSæã §N©«Íß~ éã%K¨QðÖ[Œ,Y’AµkÓ:,Œ“¾¾LµÒàY‘7lllèЭ[Þ=¡F£V©½z¥ÇÇùséé©«Õ)÷g@ý…>2víÔ­)k4=±A˜lªTIVŸ…ÕZ*±IIŒOi×]ÖÁEU«ZxF Ó©í?ùöî5|¬~}xçxíµ´\eG m@}yoÑ"óÏWQ8¹¸@Ó¦ê‘Ñ™W«/\€äd|E! V¡ÐhèY¾r„ð„Üíì8ß´)å óoÏŠ¢æ .[»w§o+P«–ºÚý¾üÒ°Ñ€‹ ¢Ì5kZn~BX‚мr÷.ìÜ©¦zü÷¿jMIF 4i’^„X»v–OóÕ’%Ü 3z¹6Ý»Ó6/Ö˜‘Ä-ÆI´(pV®_ϵV­ÀÉ }hNžäËaì"xžúH=J•JkÞ2yÑ"¾>wŽùÙ>ý?U«²aÔ(3}1Â,¿dWDHB~ÞX¹~=á­Z©oµ8:¢õ÷ç¿þ²ìÄRŒ›7 J•²}< ØåìL¯ Ôí—Rƒg''u7S§Y¹úô‘àY%E„"7 Z¡Ùh4ТBHˆÚœ*0š7O­¹uKÝ©W/üŸžÛÉÉd÷?o»“ÝÆŽÅ¶ßÃ%f1Nh3‘„üg—œœÌÒï¾CÛ²¥ÁøÝ®]™¶l™…feÈÃÿîÝ9šM:Éz`ðÍ›hnÞTÊ—W·Xº~¾ú êÕ+:…>â™I¡È ¹·˜¨F ˜2PwïX¹ºtQW­âãÃÇÙ|úæªUXÀWŸ%f1Nh3©”ê¤0Í’5k¸Ò²eúŠ@*GGÂ||¬~: Øô5Ÿnóf‡éÓ¡dz6mSõîݛ޽{[z¢€{ËS([VmLµs§º ½e ¼öþÅ‹s¸õÄéÛé:fL^}‰YL!´(î'&2góf”V­²|<®KëZ…nÙ’£OúëÁ­[£9rþùú÷cû› !„°nnЯlÜ·n1~Í>.VÌà”-Z-å¾^$H-¬žNQh¹h íÛg^}NåèHX•*–_…¾&NdÜÖ­eØ!2 ØU³&½öîU·IBQpÙÛã?lqmÚ¤­Bïº( ¶£FÁë¯ÃÇ–œ¡Ègòk’™HBþÓ{óâEÎþû/( nP!5í :­–ïvíâ¥víÌ !„(Xüýý9~ùrúκ»Ò_©‹* îºÔ¸1œýí³.]àìYõ-6 ì"…>ÂTRD(rCî-VÈÓSÝÉéãÓS:Ƈ޽!.ÎbÓ’˜Å8  ÍDò måíK—µQÊÍÝ(åÊèÓ:t€à`uÌ×vì€;Õ&)"…>ÂTRD(rCî-VlüxØ¿||Ô·mSS:²Èt$f1Nhavîßg@J£7[[vÖ«‡¹¥ÄÇÃûïC­ZðóÏꘫ«ºGç¹séÕÑB!„9=÷œºKGŸ>êÇW¯ªÍX>úȲóY’Z˜ÕÅøxzœ9Ãc½^m”R§ ]]ÍsñÍ›Õmé>øÔ¼æ×_Wóœ§LóÌC!„ÈŠ‡‡ZP¸l™ú𔔤Öãôì )ÅöÂ:Hm&’Ñ)Rn§4JùªF :xzæÿ…O‚6m`à@¸qCkÜþù¾ùÊ–Íÿ9ä‚úSI¡È ¹· ï¼£îUµªúñŽРAúQùLbã$€6“¢žÿ@§£ëéÓ\IH`AÕª Îïý”o߆7ßTƒå}ûÔ±R¥àË/áðahÑ"¯ÿ”¤ÐG˜JŠEnȽ¥€iÜŽ‡¾}Õ¯_Wƒ-R›}壢³˜ÂÎÒ(*ŠrB~’¢Ð÷Ü9N<|À[åË35—ßÛ·oó ¥ÑJN<==)îê +WÂÌ™éoyÙÙÁ˜10{6/žÛ/Á¬¤ÐG˜J EnȽ¥*^\mûýé§jÜÄÄô¶à_­6fÉE9f1•Ð"ß  á÷”@¶OÉ’,÷õÍýstïŽÿåË8ç°str2Ž °ìæM8s&ýÔ}5kÕÊõu…B‹{ûmµíw¿~ªîÕ |û­ºw´0;  E¾švù2ߤ¼Ôò¥¼;o'ûõãÝöÅœèäÄ{ö¤T©¢î­)«tBd+11GGGKOCaLÆpìŒ[¶¨5=íÚÁܹ0uªY~ É6›¢˜ÿiD×®P#¥QŠÓS6JiûÒKªVÇÙ<N§³ô”2‘{K!àî®î(õùçàä¤vÒ>]mvëVž]¦(Æ,¹%´™µ„üŸbcy'4€²)R¼ž±QÊè X™Í®K€ À… ðÞ{êÍ¥’Baª§)"üzË×´ÚŠ?«ýÉ….p­Í5‚_f‹ÝvnÈé3§ói¶¹£×ë™þÁtÆÌ{›ßB~ãÀ­ì<µ“®Cº°ù‡Í–ž^½^ÏÌgR£S :ufÄž´›ÝÿÖþlß¹ÝÒÓ3 ÷–Bä7ààÁô¦_¿ý¦¦t¤Ì?£¢³< ¢äs)§ (:ÿÜ»ÇK§N‘ ×ãfk˾† iG{=hÚ”µGâœa,˜çîÎç¿ü¢n:/„ÈÒõë×iõZ+®½t-ë¡öÿjsâÏØ›³3hú éÃ.Í.:&µ¥ÇÅŽiãµøŒ˜+·™Õmï¾ù®g¨ê;¬/;u;y\í‰÷Çôàu؋ٽg3vôXËLN~ÂèѰi“ú±­­Z,?}:<å;¾Pôâ–§!+Ð"O§çÙ³$¤4Jù±N< žÞèуϟÈóZR¼8Ž‘àY#&͙ĵ¦ÙÏŽp©Ò%6nÙh¾Ieáç_~foÜ^«fñ6²î6½KÐwADFFšrüüËÏüuç¯ÌÁ3€ Üi~‡6}dñyŠBÌÕ6nT·guvNí¶Û¹37ƒƒ 7zõëó¢›69tÙ Õj©9|83/6ãd ‡"@ïÞ½›þýûGÙ²e‰ŽŽÆÕÕ•+V0dȃs—,YÂäÉ“ñññ!&&†Ê•+óûï¿S¡B…´s¢¢¢èÔ©—.]¢|ùò„……ñÁ0cÆŒL×.ì ù÷““ézæ ×24Jy=¯¥ìÝ Àîî|®Ó1áÑ#—-Ë„  ¼¹Ž• ¡F–ž†(R K–,™é±D]"§¢Oq4òhÚq6ú¬ñ'um¼–˜GÌŒÅøû£eáÇ]?ò£ûæ˜QÖ¢0>Ooøì—ÏÐ7Õãë勯—/Õ¼ªáéd†.¬O(H÷–û÷ïsøÈaÎ]8G‹&-hРA–‹X"ƒºuáèQµØ7ßðfr2vqq ÈáS†WªÄÈI“2Ëî<Æ©Z§Ó1sæLðððàêÕ«Œ=š‘#Gò /àã㨅9“&M⫯¾â?ÿù÷îÝ£U«VðǤ=ßÈ‘#ÑjµDDDàååÅ?üÀ«¯¾JÓ¦M騱£Áµ sB¾V¯çåsç8•ò6Иòå™’›°'&ÂŒ°t)èõêX×®´ýê+>ïÞKÇŽñ¸aC|Ÿb_ik(ygÂ$©„ÝzvãLÌÃ`ùæY’ôI†Ÿ zrúî@ãºy®És$ë“3:E—yLŸy,·ç*d(Ç1e7®dÀ6wß/‹ÐAD|ïÿõ¾Á°—³—L{V3¬}½|)S,º´2uêTöþ³—s¡çhݤ5­[¶ÆÛÛ;_®÷4t:SçNeËÞ-Ü*}‹×Ü÷¸S"ª‹¦,¢o¯¾–ž¢u+VLm°Ò®ÇŒáÕøxú‘õù` d»v”.]:Óc111ÒLň"_Dxøðaš5kÆ·ß~Ë€êïiƒæ×_åÎô·æ–/_θqã¸xñ"¾¾¾\»v*Uª°`Á&Ožœv^ÕªU©[·.Û¶mK+ÌÉø ðúùólJùáåR¥øî)÷z6pò$  gSVÌŠƒ>R‹%€¿ÿøƒa½{³ûäÉB@ ‘“d}2Á·‚ ‚åÓ1§IÔeÿ.W ç4.×›Ó6ì½²—Çu²ÛÊþ¯,»—í¦víÚù1ýl)(iÁt߀¾ì¬¸œ Ç…N†E„ÏÆ…aØ„Ù0»Él^òzæçÊâeÍ @Ïá¼Üœ;ò‘ìóÛ9-Ô…‚‡Öƒ{µîeù¼YqupÍ2°öõò¥‚{…,SLŒyðàÿyç?ìÚOLÙO»;ÊÝ(Ljî#xoâ{Ù¦´˜S×z°Ûi7‰•ŸøyVÀó˜'s{Íë1–™\Asî:tÀ.**ËUèá•*±àÈ‘,è·ä•"µ•ÔÔŠŒ¿>|8Ó‹GݺuQ…#GŽàëë˱cÇÐëõ™Î«U«‡Îÿ‰[‰ÉaaiÁsËâÅÙX³æ³Ï:,Z³fARÊêY‹ðÍ7P­ZÚim_z‰Mÿý¯ÏÂê\½z•ÿýû?N]8EËF-iõ|«,S,L¡Wô„ĆË'£Oò89û¸¸cq•mDÓòMiR® MÊ5¡ŠG•´Ç[viÉÁ›Ñ—Ögú\çg^iüŠÙƒgPóŽímì±·±gá´…œxûQí¢²>Y~á~LX9óN4ƒ At›Ô¨VÙϳzxuŽí:†­“-—ã.z'”°;a„Þ UÿÆÕ»WÑ)éûF?Ô>äTÌ)NÅœÊô”޶ŽTõ¬š)°®æY ìl²~Yï1¸ÿ+÷?ô-ÒÿÝ“‹'s­ú5òxþc>|ïÃgû†<£-?náŸÄHôϺx4®I‹Ö/¢O—>”/_ÞüÌÂåË—Ù³wg.ž¡]óv´iÝ///KOKU»6/\àÕŠéwïžÁ*tN«ÏÂ4E:€NNNfÚ´iÔ­[—VZaFFFÒ¶m[ƒsëÖ­›öXÆ?k=ѺvíÚü÷¿ÿEQƒßæµZ-ñññØ<±­ŒƒƒƒÁXBBB[vãK®_­–šîîRžêaa0d ÿþ‹`co¯nÇ3e II8èõŸÛ°IôOŒYÃ÷EÆŠæXBB£ÿo4{B÷U6 }q=˶,Ã{¡7o½úSßšãó=NxÌÕW9}<-X>~ý8ô SRS4P̾ Ë6¤aɆ4«ÔŒ¦šâW šlç¼sóNFþßHþ>ô7·ÊÜ7ÐÜÑP!ºƒÛfÞôyÿžÖ©]‡7º½ÁŠ=+À õ­®ÔïÁc¨p¬+æ­0ž-1ç† 2èùA¬;¼ŽØ&±éï“'Z(ÿoy>™õ ®®®$$$P³DMj—ªéùtè¿NØ0ÎGŸçêë„Å©Aö•¸+êBOÊ¿y¢.‘ó±ç9}>m,•ÞŽÊ^•©V"=°®èR‘“žä˜Ë1ôeôéóËð¹ñuâYóû†¾:ÿ<û^ÙÙÛ‘¬$“¤OB«ÓòðÑC[:´:-Iº$Æ?Do£G‡Ž>ÿ€¸çã2Í/㜯׸Îú­ë™>~ºEN“““5a{®ìáVÙ[(® Ÿoþœ²KÊ2aÐƽ1Îâÿlll°usãåE‹Ø8v,ƒ´Ú´Ñ*Ubö¼yi¯¡ )õKi?“:¶¶!GÊ‚”"ìÍ7ßT\\\”£G¦ét:ÅÆÆF™1c†Á¹:N±··WfÍš¥(Š¢,X°@”œ·xñbPâããÓÆ†ª¸»»+ PFmp\¸pÁàó ÊØÖ›7›¿ÿVøë/Å¥woå¯Ó§Ÿí+W*J±bŠÊhP.øù)ÊñãVóõšsìüùóV33}¬Ã+›6 cQ˜áh‚âÜÉY™<{²Áçî>²[Ù|v³2ñ÷‰J»uí‡fY~nê˜ÓNJ³/›)uºÖQ·*gožUbnÆ(·nÝzª9Ÿ9sFùxÅÇJ:5”ùó•ëׯ[Ý÷ôï}+ƒJôU6”^©|Sé3åÛ²_(ƒ;¼¦¼öÚkV1¿Ô±U«W)µÛÔV*¾TQ)Ù«¤âVÎMiض¡réÒ¥g¾†N¯S ¤¬ß³^ùâØÊäÝ“•—·¼¬”h]Bqù?—l^ ÆüQ˜…˜Ϋ…Rçå:ʨ£”a?Sý0H©Ö¡šòÒÒ—”n»)¾î ´]×V)Ó¦ŒRg^¥þçõ•š+j*¾Ÿø*®Ï»*¥¦”RJ,,¡¸/pWœ>pR4M49þ>ÇÜçTíÛ·§R¥JòZ”ƒ"™Â1wî\–.]Ê?þ˜)x¨T©çÎ3;sæ @Ú>ϩթçÎ3 ƒƒƒ³ÌÍ*,Õ¬ç=¢×Ù³$¦6J©]ûéƒçï¿WÛ‘Þ¾­~\¹2¬[O¤Ï5rÃÊÚ®ßvñÓo?z%”FõÑ¿Gš6ijéi°jã*î6Ì>x¸[ÿ._lúž7·ÕØR«T-ƒœåzeêáhk<+Ôsvvv/înéiåáᇇ‡Y¯éíê·«7-+¶ÌôX\B¡wByuÏ«\åjöÁ3@8sÄËÕ [ìmíÕ?mÔ?³3öqn?gêñ©Ò‚v«³¹aC¯{ñÿíÝwxTeúðñoz% ’@B@ ($€Š(mA•u!ŠŠ,¯.ˆQv­‹bC±ƒ?Á‚iŠô&ZH% „ô>ÏûÇ!“L23Ì$p®k®™9ç™sîgîyÎSÚôkCVQ–Þ#»8›¬¢,ŠË‹ký|¹¦œK…—¸TxÉŒ¿²V3Çfx»x“ùM&# ê,›Ú9••?®ä¹YÏ™½Ÿ†pÿÔ©Œ÷]ºÆÅ™ÔöùFÉYÒM—@¿öÚk¼úꫬ^½š#F,sï½÷ò /pùòeÝÅpÏž=x{{3tèPn»í6üüüØ¿¿n éÂÂBbbbxâ‰',s0–re¢”ËååØ_têÄí×2QÊåËðøãðõ×W—M™ï¿ÿKRXVFF™À!»Cä‡åCwØ–¹¥ó–2,hŸ¿÷9...Æ7d¦Ò íXÈéùédägè½NÏO×{Ÿ“”·Ù` `tnÑY—(÷ èIdËH\¬×Nܸ¼½éЋnÁÝH,H·ÚË:$9ðécŸ2åS,`5Ÿ¾ò)#žAjÿTîtýæ³oêü?_T^¤ŸXe×H¶«&ܕܒÜ:ãË+Í#¯4O;¤±¾ò~°óÀN#…,ÇÎÎŽq³f1ñÙgÙ¶`µÃ¹!ÜT ôŠ+xá…èÛ·/‡æðáúuwÜq‡®#áC=Ä¢E‹=z4O=õ'OždáÂ…¼ôÒK8;;àààÀ³Ï>Ë3ÏÉÉÚ÷¾¾ðé§0vl=F+n†Œà`·ƒPõ·U Èl‘ÉÊ”•”ÿ¿rV}¾Ê¤í•iʸPp¡ÖD¸ê뺚XÔ`JC¸R>‚Ÿfüdúv…¨Îÿ#“<8Ùpè”щþ^sH@KêÞ­;у¢Y¼k1z^м¸‚vñÙüÏŒþ`v±w!°Y ÍÌ©£\SNvq¶Ñ„{ùêh(¯{c%°1a3~™Á¸Î㈠‰ªu„”†Ò¯ß}]½C¬”¢¼y† {F¯\TTÞ{OfÊ4×M•@k4¢¢¢Ø¾}»ÞºU¦‚öõõåÏ?ÿdöìÙLŸ>€€Þ~ûí5ËO>ù$®®®|ñÅ,Z´ˆ>}úðçŸPcßMy&†¿?Îñí-«ÇyÆÜÛ;EE0g,Z¤íÆ0j,^ õ5cá ¢)ÍÖм¿€'ô“ç*4­5lݽ•µ›×j´¶8»(Ûä±xkÓ̱þîþ´to‰¿›öyÛmœÈ;¡1¢vÉvŒ:òºö]]]3 Q)((ˆyÎã…E/páö P%ÿ´¹hCèÁP¾ýüÛF1ê«ϿÊmC˜ýÚl²U6%®%¸ä¹êʲo–5hÓ{[{|]}ñuõ­³Üšm§°(EïïXCT•óÑþøhÿGx;{3ªã(ÆvË]íîžþïšU—žîNBÂR£å<=£k,“™»i;ZRSîD¨€¿Ÿ8Áª ¸××—UæN”rà€vR”¸8í{wwX¸PÛyPÔ ¯ê{O_ööÞ[÷íÒlà0äÚ÷ãæàV#)®ú¾êkCÍ-’““øÀ@’†&ÞA9tÞљÛ×ë—ÒÍÒ‰õU* f»6ÚN„ÝØ±cɱÍ!-?BUˆ—½áá|üæÇo»mŠÂÂBRSSiÓ¦M£Hî+…„Œ#Éq?,,Ú¤zРhvìÐ/'»©j ­©)5È_¿a£î¾€'ÏœÑ%Ï==ùÚœ‰RÊ˵£i¼òŠö5ÀÀÚiFÃÂêþìMÌÒ¬S§N±iû&Ç&ªOw ¾Ãà]”†S’CâåD.'˜“¨÷:ár—R/okè6‰®ÆÅÞÅ`Rl(Avw¼ÆŽ°WñÂÔxyÙˤôIÑŸ™.Âö‡ñå¢/ë½Fç†NœE½[»v­îuS¨atuu¥}ûöÖ£H™ +ÀØ4ýN™6°:2~ ËáwX·ùÖŸZÏÚØµl:»‰¢ò" Ë Y»†5±kp°u`pè`ÆvËß:ýVî­¬uXzšRÎb-’@ =ë6l`Òôéìýå~mÖŒ…)Ú_Ø]]Y¡›(ŨS§´µÎ•³2::jé§žS·!Taa!Ï|˜­‰[¹x¼à‹_¿ ೦Ü1…Wæ¾RcâseejâZ’äËÅu\aÚÀ-íná¹ñÏé%ÅN–íúÈäGÔgÓæL#-/‡\J]èØ‘%?.‘f¢QiìÉs£W<ân‡O× p+…\W(蹟^<}ºù`c3›)ÚPð+ù­×Rø3—)Ó”±9~3›ã7ó¯ŸfàšÕÏ´±xž‹s¡v¦]›«cï«.KO·üŸåf" ´ÐóŸ?¤`Áþþßÿ3cNNüÚ­Þö&œ.JÁ‡Â3ÏhÛ=t릊»[·Œ\˜kÌä1üæ÷šþW§öUžŠÔ¶©¼÷.9srø`Áµ~^¡ÈÈÏÐÕW¯=N¼œHAYÝC=UçdçD°g0!^!„z…r$ö/DùÖÞÒÌåœ ONx’ûÂï3k_ ¡cÇŽl_»€¼¼<š5«£Q´¢I(,„ƒµõAûöAbâ•*²þ„,€<ªw‚(.†3gª.qƒ÷÷‚]„nƒÎk Óàž6ŠÂæ»)l¾›´®ÏÀ…®;bÇBz¤%U˜Ah i ×mØÀ¹ŽÁÏTb"mÛ²!"‚à+£Ô)5U;ÂÆæÍÚ÷¶¶ÚçW^ÑÖ@ “X¢áß|Á>ö]Ú·š¢öE¬Ù½†Q»Gáèf0INÊIªs¼UCš96#Ä+„ÏÝs¨W¨îµ¿»?6UÚläFåÒsdON9­ß#¿R!t½Ü•I&™‡%X"y–N„ÂÒAÙ¸Š 8~üj²¼oÄÄh—×­æÿw__6ìÊôWê*_+å C©a¨Ä¹ä¼›Tµœ÷XKÓYma¿ãÚGÔ˸–†Ò2{,-/Å+w([ÛÔßÏÎÚ$þZ4…&>Ö& ´…dddX;£þóá‡äþûߨ °]¼˜µ_M7S&Jùö[˜1²¯4F ƒeËà¶Û0âÓ믿Þàí ¿üþKr#êó4­]Ã_ƒLßns—æz qÕçP¯P¼Í7ÜÃÃO_ý”Gæ>Bü-ñ¨æWk¢“épº«¿\9Zo ;wjÇ™•¶Ð–¸¶45 úÉòÁƒÚçÚAV˜ps­Cýéjgƒv†¥þÀ›Í8ÊÚ¸µ¬]Ë_Pè˜@¼ÿ»Äû¿‹Ÿ›c:Žalç±Üv;Žv†+¨Â´ÇwUphÔÝ)3##CÚA! ´…4öqÅO?Û¡8h§*ÅË‹f-ZàŸ–uM–’•ÿú¬\yuÙÔ©ðî» ·¯¯IC~Á啿qàüb3b¡»‘Â-€Ì«om°¡U³Vz qõ$ÙÍ¡Ž™®ÑACØ¿~?³æÍâØ®c”àéìÉоCyùã—q¨´'îiã:ãîvwîm÷+x? ޹мÒ\!'. ÎM{ÎÒH-ø:#ƒ‡Þy‡²¹ú©çŒËÌÿý͵ý„Þ¸zΟ׾÷óƒ%K´ã; ««P¿pœ½){Ù›º—}©û8qñ¥:jXtJ GHÞšü!^!yÕZÓÑм½½YúÁR«ì[ˆ¦.66–œœ£å‚‚‚ 4oò‘ú~'å彌–ëÔ)›-[–ÕY¦¸ÒO–ëJz!2R?YîÐáj§õ3kb×ðë™_)(+ ¤¢„ŸNýÄO§~ÂÎÆçþØÆå£¹§¬æÆÜ)©8/IDÄ“õçÍBh iLsÊËùoB¤¦R¡üò <ÿ¼Á²û›~-ôž=0y2œ>­}ïáï½ÑÑ– þ&`JGŸÊ¦•ÉòÞ”½¤å§ÕZÞËÙ‹^½èÓº}ûò@£MÂÐÃ(ƒŽI™:Y&»iÌnÔN„#GÎ&+럌¨ðçž*}]nIá´ö‡a‹ùóOë%|M…“S¹¹q@]×–=Šèú””À÷ß_M˜€üüÚË·j¥Ÿ,÷ê¥mžq­¾ûnѵ¸žx:y2)b“"&Q\^̦³›X·–u'בU”E…ª àT6 ¯c#öPv[>=ûÔl®!“ÚBC'B|•žÎ3ññd\igf·?v=zPZÛužž±³cïŸÒç×_aþü«Ý‘£¢´CB,sMLll,ÛÈÑ“GéK† fR»²ê}êlŠa€ƒ­Ýü»Ñ§uzö¦O`:¶è¨7ºÀ+3^ṟ#¥o T™\Ï&ˆ6ÚðÅ¢/®©¡°s;>òÈ œ=[ËìiUÜygž{îÑëŠíz8;QT @ ¥”sµ¢¤<’¢+ ll¶[!º«ºw¿‡âb?£ån¹Å‡o¿}Çó:°ÔÚA\·à¾ZF­lÖ n½U?a ²l|–ælïÌ莣Ýq4åšr~Oüµqkùô§O)s4Pû\EEë 6ÿ¹™èFë-—N„ÆIm!Ö>ÿÊÏgÆéÓüY¥ÜPoo|ü1šÖ­q:r¤ÖÏäçóϱc9UÙÛÂÉ ^{ fÍ’IQ (((à‘'akÒV.´¼Þ°tëR¿ äþ÷3Þü:§¥}åýWø>ö{Óšb¡^¡ô 죫]¾¥Õ-8Ûvð пg}úQR²S(¢wwº†u哟>Á»®Î£BϨQ‘‘a|¼¨~ý:ðÞ{s–3•¹ÿ=…S§–-WTmÕº©((ðãìÙ¥FËùúF7x,¦YZçÚœm×–²2(-Õ>WÔ¶üZ>S}yewSÙÛk§¨š,wî|s-ÙÛÚ34l(CƲíýmÄS÷  °æ÷‹µs–¦@èÜåòrþsî?¯m®:9ñvÛ¶Lôóãç… 9:e Ó/×>#Ü<­\22R;)J×®–¿I9i$¿·üM_ý JRÚ¥°èô"²gg³xábàú›bô쟛ñ°Ú´iÓ†-ßoç-»;wêš)Ô¥mÛ¶DDDX "ÃN*6)1µ³‹nèPn*ªöùun( äæÖþÈÉ©{½nâ#އáuÝòo$´ÃÃÝr ˜2EÁÍÊ×ÕW{ë¹®Ž©0¸ï` Etc‘ú¥€eéé<Ï…+Í5ll˜ļܯԀŽ=šemÚàtè.¶“+E„vvÁ—^º:Ô¨aé7K9ìpM€áæÅí‹Yµc^ Þ)Þ¤¦½{ëfCM1êKcKž&MúÉÉÆkB{öüû÷kˆDq±¶qa¡v²ÑÊׯæ–5Å®]`g§M¢ª?\\Œ/3¥L]Ÿ3UI =zíIp]í{­ÍÞ^ÛùÎÁÁð£¶u†–ÿ½öx €þýþØšºGþþ‡¿>LÎ-µÀz.”Io4¾‰¨šI -Ä’\i®±«Js;¼½YÔ¾=\]k”ôµ×øtÒ$fVN‚RÅÛÀSÁÁÚ‰RäŠeÔòuËÉéZ÷pQ¹]sY·~] J*›b„U„1ªÿ(“›b\‹'žx…³gS–»ýö¾<ùdtƒÄ` ?Àx3{û> Rªö[ÝU__¸p‰òrpumQk™ª¯³²LÛL ôìi8±Õþ½g5ÍÕø«´5$mû_š!/Œu"ŒˆÐ¦dN"\Ÿ~ÿÝ´Z˜fÒøI|ýý×l=³•ÒvÕÆ×Ö€÷nož‹~?¿šw1ãÉÆFh ±D'ÂËåå¼pîŸTi®ÑÚɉwÚµc|ÃËÝ1|8Ÿ1-;[¯: È ¢ã‰àVÿdÜh.\àtæéºo—4û{†´¢×ѯ²)Ftt4ý'6ì•OsêÔgFËee=fÕÚ’4míjQÑÕ‡©ïMheh‡ÖŠŒ¬=Á­|]Ûð[5í¼ò\¿ªäåigd«/ÎÎàêjÚÃÅE;œ¼ßó5„„À”)WÿŠ‹¯>ª¿¯m™ñ)š–]í‰oåÃÓ³îõÚmÔ6fq»v• tÝ=< oß8Ha5ë¿Yϳ/>Ëêm«¹Ôò…®…øäùàêÍ›Ï½ÉØQc ~N:' ´…4䉨€/ÒÒ˜ÏÅ2m[G[[žlÝšBBp«­ÃZy¹v»eËxôÄ >fVYý¶OmÞ,És-ÒòÓØ‘°ƒ‰;Ø‘°ƒØK±`—>%0ªë(ÖüsÁÕ–™-ÌhÜ+*L¯íLO‡7ß¼¶ä·òaÂh×­ þú«>·Ø03zxÀ AúI­© põdØÕÕüÉ(Ö¬1-nÝZÛªìz”•™–hJÐ,0-Îöíáõ× 'Æ–½¼.µäÎ̦TÚê–lèPnvvv¼õÊ[Ì˙ǡÇ8’>=úÐ¥K—:GY’äÙ8I ›¸CyyÌ8}š=Uî{Ýy¥¹FGÍ5í7øÒ¥°|9\¸ÀÀgÀ4À…+µÏ½{Ó±cdž=€&$97Y/a>uºf!W´ItX8$;pûm·7T˜SR¢½m^P`ø¹®u¦<›Óê)!AÛDßZllLëÐæåƒ×¼%n©×½z]½.áá°~ýuÿYš„Ê¿Q³fÆËVgjM¹ŸŒgþöëKii6¦Œñ¬ÑX·:~ôè.$'¿h´Ü°a#>˜Œ§§'CaÈà!Öå†! t•]^μjÍ5‚y·m[Æj®qñ"|óvÜæêCÖ¹¹Á¸q<Ú¥ Ÿ¾ñ3³³y»U+žZh|F¨YÂå]²¼#qñÙñ˹ػзu_¢B£Øp|/‡×ýNéäÃM9JÁ~›+§›[¯¡ŸFcúmë'´·t %¹Ö¾õ]GÇ«¼\\ôÕ—{oêgºtS§ŒÇÖ©¬]ÛðƒÚX{ZbS•”\¶_y×ìÊ£R&\Z£1¹­ËMí•W #£ö¡J+õìù˜¢©Ý»ïÖßB44I -¤¾:ÖÖ\ã©  žƵjsÒRøùgmmó/¿è7¬´±ÑÞ£ŽÖŽHïî®­…^µŠÑÙÙätï~ÓÕ>ŸÉ:£—0'å$,çæàÆ€àD…D1(d½{ãh§½¶üÑhJSÿ¾š ãR´Ó¥Vº|߆¢Œ•ìÝûA­q˜2¡!YYÚ¦ úÏÕ__¼hzò››«·>ØÙi«¹ºšþüÁÚxéÙ¶o×&´7Ó°7êL„ï¾; =æO豫ëÜnOᲿvÜÚÈÈ[!º¦gÊ”ñÀµ_[ÄÍG:' ´…ÔG'BCÍ5†ûøð~ûö´w©ÒýïàAmMóò噩¿‘6m´SqOž¬L³šG_{aãÇós#¯}ÎÍÍ%!!öíÛãRõØÍw)N/a>Ÿgx'$*$ЍÐ(nmu+ö¶uü×)ñý`ÉTpM—"(lỨ{Ùª3ææÖ W¾¾p¡aÚð6kýú™Ÿøz¾–I ¿ùÆ´ÚÞþælªoîL„J%m´œ››u‡Ú¸ï¾QW߬/…Š«á“"¡cíYRx¸³I“¤ ÙðÁ˜ ú,§BÔF:' ´…\ω˜UVÆóçÎñYZš+Í5Bœy·];ÆVÖ<¥§kG–_¶L;~UÍšÁøñÚîê·ÝVç}Ü;†gÉ?4ºÚçòòrÊËËÙ²m sÞ˜C®C.……¸f¹âkç˧o|J×.]qpp08ËŸBs!F—0ÿžø;†Ôx;{s[Èmº„9²e$v6æ~aÁåMWúÔú¿äKJ´cØJ†32–¦}oêx¸µqpí£eKíÃß_ÛvóJó÷:ué¢KÔÍÛ;ŸÎ£– ñ©×ýš;á©SÛëuÿ7»uë>±vf‘äY˜J’gã$nÄðyZÏÅÇséJs '[[ž bnH.ee°zµ¶‰ÆÆú÷åmmaèPmÒ±±[(7aº;‡úž«WÔ+s;Š››t"¦’N„ÆIm!ùYY<óØcT›s>²o_\dÆéÓÐε Àˆóçyïå—i{ò¤þ†Âï6ÑhÕÊ¡[MÌÅ~9± q¿Qä“iü¶@’—_Š7i.«Jnn5âÚžkkS\éÿ3cǵ°Äœ½½=ööòß¿©“ÄY˜C’ga*Iž“oP q^;šîÚD9ì7èôô?kp{vÅ~øæ¡“ÓPúµJïîí˜ýc4ñ* ùèUX-`“'¤ÿ·ˆ«%ÏþxÝÛŽ>ÂTÒ‰P˜C®-ÂTÒ‰Ð8I -¤ÔÛ€KÓ¦qiõjèӇд4-ZÄÈ3g´ ó”)Ù8f¬ªN££G 'É µÌ‚ç’¡Û¡÷•¤Ù÷„Ám»ÚzÑÃ{0w¶ʸC‰ðïR£Ìܹ@þW°µù³¡U$6‡8/Èšeãèú;h+‘Ž>ÂTÒ‰P˜C®-ÂTÒ‰Ð8I -¥²Ã_Ë–ØTT0àãÙ¬Ñà §NiçÏ(pyü>‡®—À¿RšÁ1Èü/”>Xc;nnھϮRôÜ~#¹â ªT­v²s¢_P?]ÂÜ;°7¶æý`¸çžpÚ´™i`Í>½w·Ý&ã !„¢aIm þþìÊÏgÕªUÜrË-´k×®^7_\¬mbQ™$WM˜ Gí~? [ݪÌCœýòà÷™°ç>ðêüR }öpŽmì»ø{RöPZq%U®2¤°½­==zêæAp¶¿¾qªß~{Îu}^!„¢¾Hm)W&G©téïgÒ¿¿ [«Õ:´ÚìÍUThÛJ’““MŸEÏÎñ<]Žè'Ï•l€¨\¸ø9‘ÎÌHö¦0¾Ð@1º·ì®K˜… ¢™£Œ2q­¤£0•t"æk‹0•t"4NhKÉÎÖïçGEo49e†ËJi›XJ’ããkääµòö† }{ísåëöí¡Uç î¸X÷†ä’› U*Ê;·è¬K˜‡ÆÇÅÇ´`„QÒÑG˜J: sȵE˜J:' ´¥äb³ô ”ÚšÝrð(&%5‘ÌLÃIò™3PP`Úæ]½ò íœEëöÙø…dá…[‹,=³(±É&«(‹¬¢,¶eñ}vY;²ÈÞ˜MAóüšã.Wç¶v<Ø#š¡aC6„Vî7ö,ˆÖ$_pÂT’8 sȵE˜J’gã$¶›³(—*ã;'šs9éVtw_m4à’ ÎÙÚ!àZeiŸ]²À%;÷,Ü}³pñÉÂÖ- å”M±MyåYª2Nºâ2®<êƒÜsZ°dô’zÚ B!DÓ% ´¥¸€nïJ£2©Øõ;ôo«M’rÀ¦öaØ*€œ+@¯ã^]œíñqñ1øX·{§òNAM–mÎÛ0mÊdÓv&„Bqƒ“ÚR %»Þ@i1xÇ›´‰fŽÍjM„«>¼]¼õ޻ػԺÍém§3xê`’'.  ,6Œ¹oÍ5)Fqý¤£0•t"æk‹0•t"4NhK1Ô–¹ì+œ˜q¯ñ¤ØÙ{ÛúÿçjÓ¦ ³ÆÏbÁ Hï®FAë½­yÿ?ïãååUïû†IGa*éD(Ì!×a*éDhœ$Жâi`Y’--‹ºð͸o,NU³þ5‹}2}ît.•]¢È­×|WÝùòó/iÓ¦Uã»ÙÈœ0•$ÎÂrm¦’äÙ8I ­¥øµ-þA­ ½níÅ(..&))‰°°0ÅôâB!„‹­µ¸)åKý ó_h4&ö´ggg:tè ɳB!D-¤ÚBœpÙâŠc¡#>-˜<ýa<=½ÜÚ¡‰FF:úSI'Ba¹¶SI'Bã$¶þ‘ýY±b~~~ÖE4rÒÑG˜J: sȵE˜J:' ´…Kò,L"_pÂT’8 sȵE˜J’g㤠´B!„fZ!„B3Hm!%%%ÖA4qqqÖA4—.]Òu$¹¶SIÎbœ$Ð×éСC<ÿüó<üðÃüßÿýËeddX82ÑT½þúëÖA4;wîÔu$¹¶SIÎbœ$Ð×aýúõôïߟàààÀ3Ï<øqã(/×Û9))ÉJ !„B˜Or—ºÉ(×H£ÑðÄO0vìX¾ýö[¢££éÛ·/ëÖ­cܸqVŽP!„B4©¾FÛ·o'!!1cÆè–õéÓ__ß&;TP\\\ƒ·‘ûᇮ{×§9û6¥ìHII©uýñãÇ9sæŒÁuéééìÙ³Çäx£úø·¬KBBG޹îí\Kœæœc¦Æi,ŽºÖ7ôߺ¡ÉµÅ¼²rm‘k ȵ¥±“ú:u €ˆˆ½åáááºuU•••Y$®ë!_ræ•m¨/¹œœÓ‚´2ù’Ó²æ—\nn.¹¹¹F÷mmrm1¯¬\[äÚÖ½¶4…œÅÚl”RÊÚA4EÿùÏxã7(((ÀÞþjK˜3fðÕW_é}©uêÔ‰ÄÄDzö쉃ƒƒÞvüýýõ¦ËLJJ²Ú²²²2t¨7Ä>4hÐum¯j»,s>»ÿ~hÒ>vîÜÉÀë,—€¾¾¾Ë8p€-ZZ㳤§§^ã³kÖ¬¡gÏžêÜh¨˺–=zGGG½©‡¯e{¦ü[^Ïÿ…ììlòóói×®Ýu7ndðàÁf}öСC€v*ïÆtnT_vèÐ!<==iÛ¶­ÙŸm}È—¡…±³³`C—ì»|Ь¿Ÿ©Ë*;N7ØÿS®Cyyyøùùáëëk°Ü¹sçÈÉÉ¡sçÎ5>ëèèHVVááá5>»fÍÆרΠsÿ/ÔDzôôt’’’èÞ½ûumÏœï”Jæü_°±±¡¤¤„víÚÕYÎØù·sçN‚ƒƒëülå÷ê¥K—ðôôäÈ‘#´nÝšØØX„a’@_#sè)S¦°oß>Zµj¥· ™éG!êæVê‚k‰‹îýe×<Êì¤vLˆúT½ÃàùóçéÓ§Ë–-³RDŸt"¼F”––ròäIºté¢[C@@€^Y9…B!nÒúuèЀcÇŽé-?qâ„nB!„¸ñH}Lhh(?þø£nÙÞ½{¹xñ"ÑÑÑÖ L!„B4(i}Ö¯_Ïøñã‰ŠŠ¢M›6¬^½šðý÷ß뵋B!„7©¾£Fb×®]ôìÙ“²²2,XÀš5kLNžï½÷^Ú´iC»víˆŽŽæâÅ‹ ±hêRSSñööæ±Ç³v(¢ëÛ·/„††ÊŽ;¬’hÄ:Dÿþý  S§N\¾|ÙÚ!‰F(77WwM ¥E‹DFFZ;,«‘h+JJJ"88˜²²2fΜ‰§§'ÿûßÿ¬–hÄþö·¿ááá««+Ÿ|ò‰µÃTß¾}Yºt©Þ€BréÒ%ºuëÆûï¿ÏرcÉËËÃÍÍ­Æ«BT7uêT:tèÀ³Ï>kíP¬Bj ­¨r;Z·nMAA•#ÙW_}E‡¸å–[¬Šh~ùå¶oߎF£±v(¢ûüóÏéÝ»7={ö$11///Iž…Qùùù¬Y³†)S¦X;«‘ÚÊfÍšEdd$›6m⥗^²v8¢‘ÊÈÈ`Ñ¢ErŽ“ôïߟ¬¬,>üðC:vì(“!ˆZÅÅÅqæÌÆÏÔ©S6l¥¥¥ÖK4r+V¬`РA´lÙÒÚ¡XÝ‹/¾ø¢µƒhŠØ·o™™™,SPPÀo¿ýÆÖ­[±³³«1‘ @÷îÝéÓ§{÷î%%%…Áƒ7päÂ’”Rœ>}šM›6ñçŸ@³fÍ –]¿~=_~ù%ýõ^^^ºYÈ|ðAž{î9:tèÀž={¸té#GŽ´Ôa ÉÍÍe÷îÝlÚ´‰ÔÔT:vìh°\bb"_ý5kÖ¬!33“Ž;êfê¸ë®»:t(&L ??Ÿ;vp÷Ýw[ê0„…ràÀbbbðññÁÅÅÅ`¹¸¸8~üñG’““ñ÷÷×+·cÇÒÒÒØ¿?ÑÑѬZµ //¯ZÏ=Ñ4Õ×µ¥ÒŒ3xüñÇoîfbJ˜eÓ¦MªE‹ P€ŠŠŠ2X.))IuêÔIy{{«[o½UÙÙÙ©ÿ÷ÿþŸÒh4ËïÝ»Wuîܹ#ÖУG([[[¨mÛ¶Õ(£ÑhÔC=¤ÜÜÜÔ„ Tÿþý•‹‹‹Ú°aƒ®ÌÀUÇŽUÇŽ•ŸŸŸòòòR3fÌ°à‘ˆ†¶víZecc£;_j»¶>>êî»ïVEEE˯^½ZÝsÏ= ¹°†#F(;;;Ýw‘¡k‹RJ-X°@ÙÚÚªnݺ)ÕªU+õ×_éÖ¯Y³F=ôÐCº÷sçÎU/½ôRC‡/,¨¾¯-111ªeË–ª¬¬ÌÑ7^’@›éСCê½÷ÞS;vìPQQQµžˆ#GŽT;wV999J)¥~ýõWecc£¾ûî;¥”R¹¹¹êàÁƒª¢¢B¥§§«©S§ªçŸÞR‡!,äûï¿WT¿ÿþ{­_r[¶lQ€úå—_tË&Nœ¨‚ƒƒUEEEòï¾û®š6mZC†-¬àäDµ[øIDATÉ“jýúõ*--MÝu×]µ^[¨  ûòú믿”½½½Z²d‰RJ{mY½zµJNNV[¶lQ‘‘‘jÕªU–: a!ï½÷žZ¹r¥Z¾|y­×–(@}öÙgJ)¥JKKÕàÁƒU×®]ue.^¼¨‚‚‚TBB‚ÊÈÈP]»vUÇ·Ôa ¨¯kK¥'Ÿ|R=ýôÓ v£' ôu¨íDLNNV¶¶¶êÕW_Õ[Þ¾}{5lØ0¥”R™™™êÎ;ïT¡¡¡ª_¿~êÅ_T™™™–[XÁîÝ»ký’›4i’rssÓ»;ñÝwß)@mÚ´©Fù_~ù¥ÆMÜXj»¶œªœœœTyy¹nÙ7ß|£µk×.½e=zôPC‡U_~ù¥%ÂVr=×–J?þ¸:}útC†Ù$Èl àèÑ£h4ÂÃÃõ–GDDðÇàããæM›¬žhdbcc ÇÆÆF·,""B·îÎ;ïÔ+/mYo^qqqtéÒEoyDD¿þú+nnn,Y²ÄⱉÆçðáÃ5Ú°V^[>L¿~ý˜4i“&M²JŒ¢q0åÚRéý÷ß·X\™ŒÂÑRSSÃ'â¥K—¤‡³Ð“ššZãÇV»vípqqÑKB@Ý×–ììlŠŠŠ¬–h¤RSSkœ+:uÂÁÁA®-BOåù`¨âO®-†IÝ ðòòÒ[îããƒRJND¡§¢¢GGG½e666ØÛÛSQQa¥¨DcTy>T§·òü‘óETUXXXã{ÈÁÁwwwÝ÷”põÚQý»H®-µ“ºTWWyK¤RLL ...xzzZ#,ÑH£·,11‘¼¼<¬•hŒ*χêç˱cÇhÖ¬îîîÖK4R­Zµªñ=”––Fvv¶ÁaUÅÍK®-擺„„„†OÄÊuBTj×®'NœÐ[vìØ1Ý:!*Už†Î9WDu!!!¿‡*× Q©òúaè|‘k‹a’@7€^½zÆîÝ»uËrss9vì'N´bd¢1šø ³fÍ¢G|òÉ'$$$°ÿ~¹uvyûí·Ù¼y3999ìÙ³‡^½záããÃàÁƒ™3gåååŒ9’#GŽ0eÊRRRX¹r%Ÿþ9S¦L±òKÉÈÈÐý{bçδhÑ‚7rûí·[<^a=ŽŽŽºs¥ò¹’“““îõwÞÉï¿ÿÎ×_MLL ?ü03fÌ/¸›LNNŽî»§òÎfzz:—/_Ö•qqqá?þà…^àƒ> yóæ,Z´ˆiÓ¦Y%farmiR-„B!„¤ ´B!„fZ!„B3H-„B!„$B!„ ’@ !„BaI …B!„0ƒ$ÐBˆÞôfckh—.]bûöízcÛëÈ‘#œ:uª£ªÛÉ“'IMMսߵk)))Ûÿµ¨³BXŠ$ÐBˆ&-99™G}T7Ëcxx8“'OæèÑ£º2?ü0Ÿ}ö™ÅbÚ¹s'C† !??ÿš·1sæLÞyçzŒªv999ôéÓG/7nß}÷Ýuoûرcõ²C6nÜȽ÷ÞÛ ÛBˆºH-„h²~üñGºtéÂîÝ»ùÇ?þÁW_}ÅŒ3ÈÉÉ©1ãVSóè£2vìX‹ìëƒ> k×®ºé}ëÓÏ?ÿÌSO=UïÛx衇ˆ‹‹cëÖ­ ²}!„¨Lå-„h’rrrxôÑG0`?üðƒÞ”´3fÌ`ýúõ?wñâERSSéܹ³ÞgJKK)**ÂÓÓS¯|nn.NNNº²yyy888àìì\ë¶êŠÙÞÞ777ݲÔÔTΟ?Opp0þþþºå£GÆÎÎFCnn®Ám6kÖLW ¸¸˜ØØXüýý 0SAA .ä“O>©µLrr2ÅÅÅ´oß^·¬¼¼œüü|<<<°µÕ¯‹ÉÏÏÇÖÖ;;;Š‹‹Ñh4º)¦quuÕ•-++#66BCC î?''‡¸¸8üüü Ó-wwwç‘GáÕW_åöÛo7z¬BQo”B4A¯¿þºÔ‰'Œ–íÞ½»š9s¦;v¬²³³S€ PÇŽÓ•ùøã•§§gφ„„¨ùóçëmkÖ¬Yj„ ºmµjÕJ>|XWfíÚµ PÙÙÙJ)¥4š9s¦jÞ¼¹Úµk—RJ©7ªÎ;+{{{Õ¢E ¨éÓ§ë¶¥¦M›¦”R*66V»wïVJ)U\\¬ž|òIegg§ n»í6•””TçßfåÊ•ÊÎÎN•––ê-÷÷÷WóæÍS}ûöUNNN PÝ»wWñññJ)¥ÎŸ?¯ìííÕòåËõ>WXX¨¼¼¼Ô‚ ÔüùókÄ;qâD¥”Rêõ×_WÎÎκx»víª÷o¯úõë§ìí핟ŸŸ²µµU=zôÐÛß®]» ë>>µ¶±.))áïÿ;ëÖ­c×®]ôë×€yóæq×]w‘››ËÅ‹ÉÌÌä0¸Ž;RTT¤{2jÔ(Ú´iCÇŽxá…X¶lË—/çòåËìÙ³‡ââb¦NZç±?~œj¬›?>Æ #==ß~û²²2&Mš@«V­3fLã^µj………DGG3gÎæÏŸOHHJ)”R¬X±€?þ˜yóæñá‡ráÂbbbhݺ5&LÐu¾|óÍ7qrrâÂ… dddPPPÀ‚ ôö×®];Ýq!„¥H-„h’âãã 6¹|`` ¯¾ú*ôèуûî»_~ùåšöݲeKÞxã Z·nM÷îݹÿþûÙ¸qcr—/_æ®»î">>žÝ»wÓ¡C@Ûl!..ŽvíÚáââ€ýû÷7¸?œuÿþ÷¿üñÇüüóÏx{{“——Çûï¿Ï´iÓ˜0a®®®ôéÓ‡9sæ°yófk=–ãÇë’ÐêÂÂÂx饗ðòòbÈ!Ìž=›={öpèÐ!¦OŸÎöíÛõF ùì³Ï¸÷Þ{ñõõ­óoøÚk¯1~üxzè!¼¼¼çµ×^#66–?ÿü€¿þú‹àà`]³gggî¸ã½íøúúâáá! ´¢$B4I-Z´ ++Ëäò#FŒÐ{ß­[7NŸ>]kÛ⺌5 Ýû=z_c[wÜqlß¾???Ýrüqžxâ ºvíÊK/½dòpl‹/fáÂ…|ÿý÷têÔ €3gÎPZZÊÞ½{™0aãÇçÞ{ïÕÕ°×5^RR-[¶4¸®z²Zù>66€¡C‡Ò¡C/^ @LL »víbÚ´iuCvv6iiiÄÇÇëÅûÊ+¯`gg§‹wæÌ™|÷Ýwñøãë¬RUË–-IJJªsŸBQŸ$B4IíÛ·'11‘‚‚“ÊWí¸è:¾i4½„¸ª’’’ËÜÝÝëÜV¥ð×_qøðáÛxõÕW9yò$#GŽä«¯¾"44´Fó„ê6oÞÌ¿þõ/>ýôS†ª[^ZZ @¯^½¸çž{1b£Fb„ |ñźf†´mÛ¶Öñž ¾¯lbaccÃc=ƲeË(--eñâÅtêÔ‰¨¨¨:£2Þ®]»êÅ;fÌ–,Y€?~‰½}ÍKsLL ãÇç™gžáÁÔ[שS'lll())!::Ú¬»téÂçŸnpÝ–-[ ¾¯lŠ0eÊæÎË·ß~Ë—_~Éþó½Ï¸ººÖøâïï··7™™™FãmÞ¼9O?ý4O?ý4?üðcÇŽ%..NWûž––FAA]ºt1éx…¢>H ´¢IêÝ»7<ò/½ô , °°ÐÖoذnݺ™µ½~ýúѬY3/^Lyy9‰‰‰¼üò˵ÖL›jÁ‚<õÔSŒ1‚ 6™™É–-[PJéÊQ\\lpŒ1‚áÇóꫯÖXïééÉÔ©SùꫯX·n®†·¤¤„U«VÕ_·nÝHII¡¸¸¸ÆºãÇóÅ_P^^ÎéÓ§ùðà §OŸ>º2>>>Lœ8‘'žx‚¢¢"¦L™¢·Ûn»ôôtöíÛ§·|öìÙüôÓO,_¾\wA£Ñ°aÃÝwß}÷^ò““\­Á8}ú4u§BÔ++"„׬¢¢B½ùæ›ÊÙÙYÙØØ¨Ö­[+{{{åîî®fÏž­+×½{wõì³Ïê}¶úPsJ)5{öl¨æÍ›+[[[õâ‹/Æ®ú¶6lØ ·-CÛ~ã7”£££Z»v­:{ö¬rttT-Z´P={öT-Z´P­ZµR_ýµ®|Õaì¾ýö[]\þþþzƒ*¥”ÊÉÉQ“&MR€òòòR:uR...ªU«Vuþ KJJT`` Z¶l™Þrõïÿ[…„„¨ÀÀ@egg§ÕjlcÏž= P“'O®±N£Ñ¨‰'*///åèè¨þùÏ*¥”*--U³fÍRvvvÊÕÕUuéÒE5kÖLùøø¨´´4¥”RÍ›7Wîîî*22R…„„(ûéÓ§«‘#GÖyŒBQßl”ªR"„MPaa!±±±ÄÇÇÓºuk"""ôÚ)'$$àææ¦72D~~>)))tèÐAo"ÇsîÜ9zöìIpp0gÏžÅÛÛŸZ·UPP@rr²n[µm;>>¥mÛ¶¥  €ýû÷“‘‘ADDááázÇ”””„““þþþäååÕÚÉ044T¯­r||<111”••Fdd¤ÑZô… ²lÙ2½¶ÚgΜ¡yóæ¸ºº²oß>Š‹‹éׯ_öß mê©7L_uÄÇÇãææ¦7ÁËùóç9vì¹¹¹së­·êš°”——sðàA’’’hݺ5½zõÒkÞ’••EPP[·n¥oß¾u£BÔ'I …â&WXXHhh(+W®dÈ!f}¶¢¢‚1cÆP^^ί¿þÚ@6þ|¶lÙ"Sy !,Nh!„\¼x¼¼¼LþÌüùóyë­·¨¨¨`×®]5jÑZFF...xxxXt¿B! ´BˆkÇÅ‹éÛ·¯Á™ …âF% ´B!„faì„B!„0ƒ$ÐB!„B˜Ah!„B!Ì ´B!„fZ!„B3H-„B!„$B!„ ’@ !„BaI …B!„0Ãÿ)­q`NlIEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/seq-chunksize-15GB.svg000066400000000000000000003737431416254111300247220ustar00rootroot00000000000000 image/svg+xml Automaticchunksize PyTables-3.7.0/doc/source/usersguide/images/tutorial1-1-tableview.png000066400000000000000000002571161416254111300255170ustar00rootroot00000000000000‰PNG  IHDR•Ê­zû pHYsaa‰f΀ IDATxœì½w`\ŵ?~ænUYuÉ’lɲܛ\$ÛtŒ&@ ônZh/„Gò ¤ç^ $B€„Žy”x@! Íl‚ÁØ’mŒqC–Ü$Y–¬²«]iwïÞùý1sÎÌ]«™p?k÷–™3gΜó™3s¯XãÞÚD²,+aY/¿ñËì`8pàÀô ÀM#õ¼¯Ÿl†a.—‹í®Û–H$^|kcJzVaY…ÛåúWKéÀ8pðo€XÜ<°gS$¼à”#Ýn7«ÝþÉŸÞÞ”V0%/¿¨­ËŒÄ-3±„õ¯–ó‹ Àè`xÄ–åKú øµÏÏL+~€Bz¸dY¸v§üȸ½œ« ˜&7çÀ˜ü÷ÐHJt¢ZT¡€ ‰8ãÀfÀäq-à²^º‰gœ ð@Ñ&y»øÎTuEb¶6S?r&u$Z*Oªz÷¤Ô‰*ŠÀÚ“ÔŒ1ιØ­óç @5ó^VÒ;—ÌαíJEŒ ¡ì¢l Óì4õs*]Ù†êP½Z¤I&¢÷é½·FzÙNàì[Y'éN“A }©RÊŤ¾Èv„†±VÑAʦ¥1piÚ\o%éC\Æ4ÛBïnE‘mcGVÍ@ $ú°12»Î¨ 6Fö]–¤ .5IŠä\ ;åU 4ë!—¢ô†Å’/£ŠÐ¶eë¹”D>ÆéIºƵjÈIÛZ£)x`$;@­OÈ6À®š$ ¤!»NðÞƒTà!£Ì@…ôÔô¯Ëp» Åkä¦zZZÂ-;Ï=y;‹ùR³Yc¶5uwÇ.5hpÝ•‘s‘AG™,£À„®\„VÍS(6£y"^™v#0àœ1¦‡"Žá/;ÕUYj„ãó˜ ÝV(Ê;Ã2ìqà¯b4ì…Ðù¸Æ«4v"%m‘lHÒ-üÚ<5ÓcæqÀŠVHݨš’úO%-Š—cÀ-éç¤Ããøêæ30¨HñÈKrÅô¦G$p\ö‰ªA jLùsnÉŽ”°@NËF­d0VQ ÜæÕ˜êÔczàQv)ZI¶h+ãȼ´îÂP –% íŽJµ”™’<ÒTˆµi!ÒæÆÑìÔŽ6N‘H ýÅ»~.£Šô’õ8˜t²â>(ç6é^,nåH'CÒ‰”¸ÇÒ­ÇÆ ˆ$q)h¡Üâjt1M4w<*­H‡RPŸtŠ#ËN‘Î8p°5ˆI§ ­±ŒƒE¡˜«± ÈÓD 2¢Ü\k“¢0(–°Hnã(z‰Lë5œ¤aÚ—jçÊY¡xšUعº¡?ìHâ‚4Ã1#)³æMˆQÉK•o•“ ИyRoƒ”ƒké5he}ºUTÞ¯M&{)ÆA?CB|h ÅËs‹áýÑhÔU„Ò f—D$†pj0ÆÔ0µ‰¯b¬VS1š¢'¢ò5‡Gˆ$µEl¦9hÆ4¸NATD`ZÀc¶fk²Ë²T ´¹V§è¤VŒ"ê+9UÔõälé#ÇTÔFÒPGi±ÍnK¨©YƘL-TmÑrmšA©eŠ. ´L5‡i,–zYRô˺UëA @ifTüwRQhcB%¢EtŽƒ•é|Eœf…bRqt«ƒ¤$1dðUæ© e¨¥¤*µ È4´nÝ$ƒ´FÌ‘3rÕ;ÌFw4‚£ÆqF¡((ÎÁ ÅÓôÅ­È”ŠH~mð0¡8‡Ñ Ò•”‰ÊX“ä}8 ­:®>fÁWË0¬ Öeà´d¹šp@ŒŠrÝ4lÄÕ‡‘Qé]§²eä`TŒ„ £âV²¬D"n™q‹[¢DÆŒÁÞ?z…Œ†!Œ™à xjƘ kÞqÇ»Z€±žàý@zWašœr!LåÒAÍ»M–qqš»p€âeÌrIJÌ×´1Ôq´üŒ–›f|,ÝÏ F¬`7ÛÅ9¤ú ?à*Í6г ÔclÝŸöèBÉI%W’àÃù+hÓX¼BT•§æ«Õ$âÎ,\·å\™%IQ@[ äX¨9§æ¾Ôõúì@”}J¢N }¶FºfÙbKÄÁ^1Osu@-çZ^ŒkÁL^ŠI%@N¬2™r6NÁ›4+tÄ´@ Œc¶V ÌC8GÀI°PõŒãD_Y”žÄ’-g”•$Aee8‡6D‹I/äô™ ¡œ+¶¥níǃ÷çØûóVDš´ø.Å—) ýâ#Š<Ñ9•)”Mf´`¥“32ÐÕ(n“q”v TÒ–qœœp9¥Úxžž_Á»„&©K‰TPj Û„4¤Ù—ç¶&óC’ÿsua²þÅ9KFâë²lÌcÚ´ŠAÙ˜ÖâU”–brpÊ$&]ôYI®…½ÇÈqfa‚”‰3ôzR;š»–#‡’>[Ò%Ó5FEœ®£Â¥LwéŒ }Yï'†zîŨx¿ŒJ D€[‰„ZèŸX”™™îs»\f"Ñê©k moޏÜ>fz‹ö¨2Z‰0=&Oó¹â]ÜBw kȤêÅ?>y&¦v.œ=¾dÒ¬®¼æ¤q Sà‰wv4†é8øÁÙå™in¸ëåú¯ÎË«( À¯_©¿á”ÒT_¿Ò7wÄ~û×]Ôxñ±ETÔÁP| 2ØSV“+œO—>¿<06Ç—™æqŠ$>k ¯øø`gÄ\Þ!^%=‡TŸkÉ윙¥é)îH4±£1²âãÖ`Ä´ñ*Eƒ€3àíaÞI•n‡1ˆDyC‚·†M+ã²ÊR×®ƒVýAKãUzÒ×còša”dÊ¡¨¸"‡v€"OÖçèJ9SóNN.ƒ‰h²LôPžð,CW']—l ¨MªqÈ” (Њ y<úšD­0Æb«ñ:ì΀Y4GÅå7ê2†ú"_¤·×ˆ#^¨±+É¡Pµè›¥\Yy] 4‹ù$$X˜©0ŽZïEl¾—zù¶bZ ÅLzfªk¿@£_IªWÒ¨ÞÍíË$¶r•Þl­@þ„ùÞJL’ȨmuJÅ_"R )À~%š-/–ÁET%&rL[õÅ¥(_¨¬ ƒ–Ô°ñbDZ8ÔäLF¦îÈ2lÆ›D§”Ju:Řê<]}¨R©~û1”™dÁʘ¹&³bZ\%ƒµAÊ•ÉÑB8 ;"™˜&»œ=p%/ ÁÕæµh'È÷” ý2*°q?¢®´·5I-MôͨúYõÓUÿ9*QÑÐ@·¢Q¿H$ÌLOlÉü"æò„{ͦÅãc^·wVyÁ̲Ø;›ƒ¦ÇårîBF #ÆÄÍS†èŸ„ngƒÀs¿\¶¯~'ìd¾úQÇš•+¸xC}H|˜;! /+HŒjwKw[×p™Í¿ó4G΃#§dÎ) äex=.f0–™æ®š”ù­SK)nûнŒy=ÆuKKŽ™žæq,⮜˜qÃ)¥™iJKaÚTL`” ׈![˜å:f¢kV‘kQ¹»9È7íKŒÏ1²R .œò`ŒÒää¸qÚÄÕZ †?ô_äæi6Ò³†oÇ0)+àÒÑYKŒ{šËéo(¯%» åL2ZÍ‹“³&ýµ-s¢DÒÛD Ð>Ú§mR%– e4ç |§ÊI2!3¢2\é˜jAH<ú3<D‹ËÒD×K÷ÎP8=¹Zä’û€ÅIF¹‡ˆi¾˜˜7¥E™ë’Å3Æ…çLZ†œepY1Àœ‚:ÄíúôóÓÇ•X‹•“üXĘcQœ(:þÁyÙ2ÉÏqà©Q ˜ÏÃå%Z‚d.Øõ ƒ#ÇX&ʤ<}ÊшÑôÐft“]ÃdPŒ>²yÆ Ù»¢.:w"[ÔLø1'¥€> ’S¶–D^eoc{í\@Ù#ÒB6‹³âbœ&Tê6@– IÇ1;,¶ÚE"Ù9}''&µŠŠáT PI~å0%Ó8èŒ z1*΀ȫÈÐ)¯£çÁ4FÅ{3*†ˆQq6TF…#ÐÁ ‚QYV"ËÛsJUi4ájéŒ茵…â]f[(~ 3ÖÒ‹šîSªÆeºc–Õï¶¢Q)d´0ra(3åZ¼xqúع¡hbðüíݶöý—?oJ×ϯÚY˜Ùñɾ’† ¯(5˜™SÐçõCñc§gËI÷¼¿µ’b‹gåŒËõÀÊÍíûöìŠok¼+ÔÔÝÓÒóñ®Ð†úÐæ=]sÊBâ'ÿѸ¡>´¡>´yoWgØ@ÂÙãc²|ðÁ¶ŽîX ;äC€ôWdŠ €W”öìù[uËëë[¶5„'¦ù½†Ïc˜ kçþn9ýÁè'Ì|éܼ™¥é°z[ûV5uFÌ)Åi>‘•æÙ´+$#70PS99(µ7Ê 3€T›;ÎU½;±¿#ÑÜ 3ЦNK°Ò\£¡]yMl×f¸‚«ámŸ¸tŽÒ¥Iad¤ôqä¤yº4š/K7Ká–Ã¥®;sP-Óx.z ÝÜ8ÔS«$¢âœè‹tÀhIF kâNb'ê µQå,3•)2]Ll’̤yºBÈØGG)0p .@ÝjÊWtM;O]ˆ=$ƒ‘Ô `qQKH‚/>m€a‘açÉÔ E}m¢n—]ûB„‰iTIûHöû)ízýKráÉú³ÉFúb”C¢þÆØ­u#jD19¤õš¹s±qŠÐr¨ª\šˆúÌeDS&nhh¢~d÷c&ÉÆ"¡§<™6–¥Ц" Äa¥Æ¤R`O`&V¸;І-àr5ö£šbЗ$ Gi„nqÁZ+ëdžؽ‰t€ƒ‘Ó';W {íªK©×´Ï  £ ½²D&J¦ÓW‚J¹F-! 0ÈMT2“¯©¹ŽNR±MDc™œ¥ åèædgTŠî!£BŸAÄõPàÜJô„O©,îIáxwÌb ƒ‰Æ žà–Å].câ˜Ô-»nëUì¨2ZÐ…ù¬¶îÏø}AQi #Ks i×_Ÿ(wLI 3{aÜ ø]]‡œUÛ»mí³w,€æ]»—¹{Ù‘®;Å÷õ»¦7ìëîo0·¶ìëªðyŒéãÒ?Ùƒ±Ùã%[Ú´;‹gæÐ’ÜÎýq¯×m`³–+Æþë«%¹oªÏà‚ÝæÎ¦.®ÙæsX‘;½$lk¿ºî@¸§ošYœã[<+gBAJªÏÕ³j›"o|°5gó3¼'ÏË++HIõ¹ Þ‰ïo¾º®%Ô5ªÌ-`/~°¿;&ÿa]=Ý«¶´± r^Ê¡H©ÆæOÌÚxcÃAÓ´Vok?fzvVš{FIZŠ×Õ3iÜPJ‰ãÒ‘ðÒ¶!Ê¡(“µ„¬®(?qº>ÚiN-tmlHL*pg¤@°#=Àµ1DÑ=ˆ ðbps\Yà žÛž'qäJmnLÍ9€6o•bƒô –`mJˆ¤R(Ýä]DZ‚G¾¢…%Ô a“G@9•aRù mߘ\Xá²ÝͤCç”kBvEîý—H81ô”\e°ìM:h®Ü_ÁnQM¢¤òÑÙ[<Ñ“2º˜ºQ†¼ßàœ‹b©I6Ç}@ĤlŒ+ª&#™´'’’L‚ƒ"‘Ôs:½H¦P‡Èž÷¦\¼WኆÛıAV‡QX¥i1Òéld>PU`qe…ŒÔá²_ñ>l¾-ÛkcQ*;='ôjÙÃÃĨ:§—ñÐÊ0KghTj™Oj W´“¹”æoäPÜI ™Dކ&€ÓM@¾D*“ä…‘üÉ×Sf4ÏQýÊ”†…R,À :!æ»D#íÆ"-‰¹JP©”sì9d°Øx• â˜é§>Ñ8%>¢ÃÕ®GÐTº£ìgÉg7Z€¡.UÊå*Ae7oê\À{¥|¨fé×ìÎz ˜f|z¡ÛãõC±x‚»]}ÜOðH4‘ðMãÙÖ÷x|ƒ)dû¦þúì}_¿ô;“g-L!£]˜Uo¼ðÑ?þ´uýª~úHAñø õÿâú®Îƒ)~ߨknŒ0CÞ]_2m¡ø°· ê÷@dú8vï%+jR¸kC]P|i'˜T”šæsÀö†p$:´ä^Iž|~ŠÇÅ:æ™àÙižªI™×µÄãNîÝK/žWžá÷>1§,ð¯Œs}XÀÔ±i7œRZ1>æw‡£‰TŸkî„À§•fùÀ`ìê“ÆÍ*MO÷»ººÍžx"?Ã;{| bßõÅ7ïŽYè/9ø=òʶ®8Gw8ø°¬Twºßí]q3!ýzsgTÔ^œë“^C¬ûɸÚÏ!†`ª‡üL 9ô‘Õ%Öì49cn$LÞÚeå¦á”„Ré¸ð©©mø¡d¼Ji0庵Õ@¿±…G%ŸpQ¨2šNq¢VqÀV€EËR‡Z΀b¡òêÀeB^Ü—Á0Ž2 ̉¤îydÓˆî茼?ÍFõÀÀµ$Ô¤FŽ@)®Z“ÀE©IéÓ¨'0Ä% ”zã8·0 bc™º¸ÈTb ¤"œsnak8€Eªàb=˜¦ô¸”Yöâ€ë„€‡’Zo?ÑçO_P'åª ZÓú›–WejSÈŒW[²§8X-G ô–jÔ*üމ%Ž–(´%åä„éô]³AűŽR ñ™bսص–LKT¤W¤LNݬRœBÊvÕ¤6ãÈ˜Ž¶Ì“Ýô3 å5Òþ¥=©”Ö¬-#ÐR$‹,状¡ÃaºÄ¸æ"#‘¤Ò %3œ&fޤŸ‘’‰&+6ªT€ÏÛJ÷.4¢\\1*¥• RŒŠã’W×qû’ŸƶäG.–Ë/¶‡U±¯¨;)ï>8pv—eEãVÜ´:6½ùâ­n?-ûÞ|ñ`[cÜ´¢qk|QF<Úm€ì4ϬÒ@Ò•íáø/×ÿêÏu»t@qŽ/i_ŒsÄ—Á‚óׯÔßñRÝ}Û7¹Ïc|­*rÒ=™©nøËÚwü©î/Õýü…Ïþ³©«÷K(¤‹`4ãÀ³ÓGÞüÄK°u'€;þþج¶n4-¾iWÜ.6«4Ýåb3JÒ ;fmÙ×E—õ) ô:^ ;'à9µ2ÿÂcŠ.>¶¨4ß/.ÈËð&ݵâãƒm]ñö°ùö'mâȤÂÔ¤ºŠr|©n` Î9rÌÕ';}A°¼òÂÆX¨Ç{ÐÏÊ9çÈ1ÇÍÌ›ëÛ¼§«³Û죱Œq–j¼ ÓwÝÉ%i>—™àϬll$„ëæ–Ø'ÌÀ>‹‰eûê¶tyrhY\,XqΙ…—Æ-¾ó 5.Ë5¹ÀU`&‡•ÛãsK]‹&¸™èêŽóúƒ:¢‘Š®ŠÉ€F8_—Ñ×bôC°,’úg ¤¯àè5@ŠÇP"ÔqŒmÊÔ)éB)ÊɰÈ,à4íç —¦¤¨w“x©!6 Š@áò H\hʆìÊRªàÀ|c€z&—º!‚$Ù35MdÒ,Š52üÙm[:rÎ[H°„È‚T+‘¾JÁ”åÌBÏ Øj‘Õ’Æ&ôŒ»x©– $`‘ФÉid‹sÜÐÎ8³(Äs.LWŸ¦SÏ‚j¯Ê`0Yñ/ޱÜöcñ~ú¼…ë%ÊJ L<ÐædY¾^h^¼ÑR6LZ>ç ‘' ó\dNphØy†Ðµ`H²xaœ*Å-É,7$Å&îÌ1É€L™“Ý24<²7®Z§ÌOö¿%Z§&8˜$N¤špn!—â8Õ!‰!*¥ŠS$ˆ4lå“{yܾÈq…”+AÑ£’Ç`Ò5i.‚äúQã]G®NXp5Œü ׆†rÎÌni\Yæh±÷•Î1d1ùH‹¼—KncѨ¹8†O/3&»R¨(ÙD…<Œ¨Å¸…ôÐ"Óe2²pŒÔYœ5PêIýþÇâ–iÆãq–°¸ÛÅ->cì¸R73_{æW u›^{æWÃ;®lÑ⯹],añxœ›fÜâÖ…tµï§/½ñçT`WGó…ŒÖO’0n+(wæåßÉÎÎÊÌdgeuÙwó ÇŠS #0„=U¡Ýkk«×Â|¸ý xiDM°,Xzüý1(/íÌòg |ûúºà‘S³`hÜò{ ødOÈLðoL¸â„±ÓƦõ>å鵸ÛûÙƒ¸Ý*ÕŸü¦†t<HqRl 1ó{H4ñ꺧Uæg¦ºLʧBÝæòwšÚ£ýJ 0!?õò%Å)^WOÜzê݆úænàr.D©‘ë G¥x~¯Ápœ @¨'îT$„UÐbLM¹°Žlm6'绳S\E™Æ¦óÝqËb‚Yg¤²±YF"!ÿŒí^)(–âLLõ±`OÕPÞ ÷kˆi©ÈMƒ–+šÿÊc3<®åµ96§ÕR èq—‡€¥š/N¾Eþ #”èÜ]£jC=3=i¡v:Ș ZSa6aPOØZuHHuL~•sVÜ‹2”mqÌp´Ê¯Èé*½¨‡ãjŠÍ0gÅ€Éç¥^JY/¡%ÁpÖ.ÓjnÀ0Ý'mE³~•HÕ KkˆmÎ0ZÐÊ$}*EÙD1€þ§<rgÔ‘®r¬Isfb÷Ÿª” ´Y¯¦Y+mÅB““¾‚¤I¶ßh÷@,$¡—‰/õxEo/ª%+€ºŸq›áQ7‚nÆwÏ ƒÐ“†ƒE G9N0Å+k™<&¯'z @TÖJ—Ÿ«2V´úsB*™scÔn†þQ]£ùaÍ(å¨ZC] ™´0™\ÂI޾¾¦›6Xß…²#u™VWê´‰/{…£î”NežX—èh¦ü '•aÊT—¶“sˆV»§¥+ê÷ºÜü¢ó®ýÙ›ÏÝÓî\ýêc^ÏWzò…ÿ/#»zbVkGpí¦¿Bê6­ÖoÌÅwmþ ê„sû+d´$ tlªYñ\f ] Øuo?›“ÿÿ2sÆ Ð"ƒ%U¡ÝkÌ]vM*ÔìÊR¸õ4øá_öí‡9§CÙŒGž3vàö´ö Åsž‰…©nÜØD{­±¹~Á¨öìyfUcGØœ1.ýòŠû¼8+ÍÝŒ€ÈE@¤×Fõ.<²±>ôÜûM}–³fGgÍÎ`Iž??Ã;.׿`rf ŽdvÎßëûz˜]š~á1En댘O¼³oG @þsÀ€%,ËâÐÞevõ$Òý®ì4ÛÃÌ8ñ|¢ÅyS[TÄU\ÇЇ4–Éâ «=Ì>êŠÉ0J²§z„wIXÀ€h±„ts6j’£{ÅDÞ4Óî’5È™•$}°+ô*äÅÑn©+ IDAT1ã¡0g ŸdDEË,’¢ÊŠ.Î,U›pȸ<ª1Ní`´àIñŒb!þ9y-›ôű0\sEM0–ôC³(ÀC¿L ’ÈHâCË{òš$Ê%Ed:Âz”öqž¯Y€"‡Ê-Ù"ïã˜B_w؈Ó ÕV©ÕS¢àoK¶…aõÉ´IkŸè?davòýð'®1r—^ŠÈ‡dQ¸sšÑËWˆc‘ìê •Æ8P—4j´V3e§šã#(œc­I"ÁR4µ‹œí¾WÐÈÉ y’—â'RÅ¥´~“KÙµÁŠ# KdT 9't´¨‹)%û6ytnøV8ݤQaýÒ)5Ò{Ñ)j8Ñ)òֲõíSL{eŒîÞ†¨yÍ’xŠB+ bKÓN ©ÃcÌ8ØÖévyü^Ã`,7¿ðŒËnyû…{fÌíñ}åüo§gæ€Åyw4ÑÚÞɘѫJ[!sŽ81==u⬣Ò2s 7¿ðŒË¿W·ùɳ6 è¿Ñ‚M˜Hg뇯=æó@VqIÕ’ó×½ó=‘àš¿?vüYÿ•š‘;aKªNŒý®?îxþt|)4tÀïW}ÖGŸõ­Á²¾.xÒœ\ƒÁ„1)ÐŽ×7wR½´¡-*Q´ù½7Nš“×ÖÕÄ9œX‘+Ž|¶?yïWc[4Ømf¤¸g•¦O.Jý¬)é~ל²@Nºç¯Õ-.ƒ95kóžP]sw]s÷æ½]ó&dxÜ,ÅÛïëIMÎsÑÑêaÀÁ`C}ð¤9¹ôuc}h~sOKww,‘âu-˜”™™êNó¹Ædyû»8+Íý½3'Ðׯ¶èÆ^ûâ-Îÿôaóe‹‹Ý.võ‰ãÄ;Òü.ødO0§WåŸ^•ß·¢q+ÝïnkC?¨( ˆá”âu]z¼Ê¢Õ5w?òÖl‡Ü?6·O›>&Ë{Ì´ìc¦e‹S¡nóµêô ¸X¯Q+q³ÏQñòT;µ2ì@ˆË—ƒsîv±1Fi®±¹!¡8S åbø‹âŽÊ,‰«´5A ¯ääÈênR'ò³ž¸†¾’–e¾HÆŽ~Ù•àB}箤ïŒ š·×èŠð‚˜—âxq7A‘`ážVÒÎÕò$p òlËdsm @Ƥ$Ž…:PšL¢Yz6«¦EbpŒÓŒò Š-’ᄜ³<†+]jÿ3¥/UWz™ƒnþ}{ùÁx†Þ÷i‚jÅÁÓ⩊Q6Ë¥%-Ë–J~%ðaz¦Ò„@qP¦i9ƒ¤PÄ¢¸²ðþX)Ä#Þ ¥24^ˆÈ ãf\] ^ÕD†ˆìƒvX“¨ZªÆ‚´9K£·úÎ~¸’üÍ´V¢{ ÐWjJ;K-ZXÃWtJ¥¯ä9w®Då$/hyxÀ©"ö}±¯/:e#„It 8zeÒ&³‰Ä§A.í‘kÚ:%g¸<¾Ô {}l¯'Ëãaâ)«‚qåãÊé²Py0lvCµûÚ3 J’þºK…$aàBF IÂŒ™\å÷ûÆNš›’ž ™ÙyGzeãÎY%s)Ì¡I•{óï„ #Y©¿<NÿóÂy&ý&ˆúÁÁP|wKÏxÜW>Œµ?è‰[Ëßi8cAÁØ_ižë¾ðêí]ØçÅXÕxÔÔ¬é%郭ûÂYw Ï?ȳ­!üû×÷,ž™3aLJºß•H@sglwKwÍÎ Xÿp{GIž?7àÉHqÇÖþöèºÁñQ¾¡Aøä9VOÌzèͽ'ÎÉY’F¦æ­[;Â&€z9E rÉÀ ‡ìTÃç†ön‹AF1“{\,ÍÃrÒYY®+åÕõ‰` ¹‘Œ%\x g\@—ä=ZâJÖ¤˜:VJ§Qâ S+ø×%0Ž#ÏЖ™]1¢6v%"˜t?èœÔö ù J™3 ²YŒžY¦%Q`â½ïƒ"XÒWSõøW[Sý£q,9kÖc@Çfè–ÑShÝBîVDUÚšŽkšxƒ-§ÊGcÏ&ó-РR.щ2†YœŒD™G¯Í(\¨RÅý:ÉÖ¹ôÆ`öOð¾n´Q7½FEST˵¤-'i„Išr&à½iS’øš2"O ø‘4hLqêÍà@ûö$ Ó2* œ:‹RG%ýÒRUDžP‚«ˆÔ =7F XJH"R4 µ¼ ZWR©ÝçÂL¹¤«Ú¦F¤>ŒÜG\Y8C‹Á‘δq´Ì'ë¡»1‡¬“«nj*¨¸”(ÛžšJ26tB*a89“t O¨ÉgòÞ)ü¨Ñ)-;¥ y°tŠ£I+:ÅH'£B§_Š;%sw}ɧÅþ1™“î¦ Íñoë2›;£ÍmÝ»?ó¦ez|}¼niT 9,-ÊJ3éXº›Þ¿•ÈΞtlsg´¹-<aØ­·ÞZ¸àІŽXÔìÃE…v¯}0oÙº]ð|5üæ\øsã·½7Æ!;å opÝÐË$[H1À>}QC‹áç Ÿ›e§°€Ÿ¥ú ÍË|n0 æbÀÂ1ˆÆyýA«­ËœYb¾‰øÖ¢+ù9Žõ÷’J~Ãû•ˆ:‰‚Épä"Ä>´Ì¿ÜÀ¤˜GÒÜMû|ŒRW6}"ÏA-ëÒÙû@Ò Z›ST’šJûeEzQÔ>›‚PoŒáÊ!ЭX•¬ ÐáK9¯"ÕQeýêQ ¯B’蘟f½€AUi«C‚ôKã.h•cÓÙ ½øQDr ð»FšˆWÙØ¥ôz«5UØÁ¨uúàµ÷HN`+B;‚¼Y¼*œ¾“õj#~9*’ˆG+ ªCéI-_ÅTaHÖÉ)‘)£¢hÜ€aéŠ@&;8Ý¿ábŒªqÀ•Ð*ëÉm%Ñb¢·ÔYèR4.Eó&™e¦ •¦(ê8U™ÞyzŸU~R]¤­ÿÑÆ)@¯ uV¦JÂvÉp ñþ¤RNÉWl=0ʈöD‚mM)¬{ìø©.oj~†'Ío¸ ô¬pÕŒ[±ðžúϢ̟‘Säó§¾B¾8-ò¹±Yžýëž(SÕüÙÚÇÇ,€eðôGp]Ë3Y“5;å o(FGÈó©¡Åm®^±ú Fáž8ì[ûCê:½6ŽWÚâ'ºQ‡ô.º7£€‹RqE•¸ÂF Ç;9Ñr襳aÄ4´Ü0®|†t^à”’\°|TN|â1¢8Ú"‚ÞŸk\‰ÚƒdGH¨=Ó„ 4"|jŸG'¹&-“=¨æàêf\í`@å4Gf*-Éäy”îlË”´2´'Ùv¯Q ¦l•V/GeÛ‰¢Æk™¢z’:SVÇé=fbÿi)P\Dë…¾ÓPvô“βgÁ°Q$I«'ˆ0uÚj# Ž9M!ꪈëGÕÆÄpÅä-¥U´Ñ“LgìňÎÓºF¡@²[7#£¢4"îÂ^Ò–Z>Äâ„x4¬ii‰ñ°Û *R›8øÛÒŒVñÅÄG†Øº'G¹<ÊKˆ±È„ä?ä¦d2Påæl Éî?mË|d*¨AÌûJ¿/tÅðz-5E)cY2ÍN•àÊÚQ~©LyjSäÿ8#[&SÃÞe\YWïÅ>ì¦Ã¯ÏÈ*èê8°ùãõËÇB,ç€'˜ xb]áÎÖº=MþŒ¼@V×ç?¬…|¡Z$0©â+–¹/Îá'»oL½ê[‡—(~9!#6]£ì %‰•_%£] ¶Ø)¼!Î o—b¶h†žC+YÆJEæ”\IìŠÆ© bý±+Š=¸ x(vE«bÔtA(¥Ÿ> çð”aÈC@Déð™Å8-c k–DĤ¢ÁÒý¹XyQ[¤”\×"Ǥ=œÍ¹FOžl7Q!q+>¦G4‹2g (=¦Mê·0Gj¤î¬@¼…ŽTÏb3ˆÓõC¹ÐĸºM›%c?“Þ0\iJTÐùÄÀðÑñ²µÕ›|%Ñ;{R ÙŽ*KÑÔïã¼ZòåT R§uÌ5x8uw?ü 4Т.“†A½Ât Å”Ùpi6(¥–¨Œ25ô˜£d_H"Ƚèá\L¯LFtAkÄ•Ê.T„DŠfHgh‘޼B•Vó\YF\ŠnèKqäR4/êKqrtÒÛ1$X4u#׊Ó\àM²rºt’NIç‰ 4®7A­_ª’eÒËF§F±¯O0føSÒ Ãíõ¥íÝßÞ³³Ñ4£V"a¸\n·ÏŸÈÈïOËôúü,äJ!_¨ DªâKž¹àñeé_{&o¦³Þw8¡9 À¡ië}ðÉZÛRÆfY\9 =^ȹÍ—qnŠKŠE')Òž}Ê?xv%ÄB~ \‚Ø£)êÀÔ¼¬ŸÈLÑ´)¶E‘†ö7Iâ!f¾œÆ¦ªÒÃY5‰ªúKK,Ñ>ǽ":Çt€4…唾Jê“^Ù,ÜèƒÍ!‡,2µœ–-k&/ÕP2ß¹/G³8Ù_¶†a‚€ ¢Žeöõ/6æ•|, ýù§^ë }Þ;RÕß56ÉûúJ)H˜€™¾T§7’öà ĜôFȸªçŸc,4•lCÃ@Ó‘¥}Ro3åôƒLššG¡§L‘¤ 0)ƒY6{’©Œ,·©^)‘"g$gK4û’I)|1ê,@fÆž4”U(¶"u‡cY’ MTUÚà¸Ãá…^‘ÒL¸©O€‘ìÈÔõ3+Q»pIZæÔh–¥$;|ýFε'qP-ŸR`ÌëOq{½þôŒx4š0£Ü²˜a¸Ü>ÏçöøŒÁì+•BF £$Œ$UÄ‹uŒ›¶¦m‡¾N9}0d=è&qB3G›[–|ˆÞÆŽ™gE , žÊý«lˆÎ®$²€Æ.:&tà–Æ®¸-˜ôfW²AI+ƒônbœyQ6‹àÂÔÓÚ 2w¤/¯;ÊiOŒrî ,9½eô¢$‹‰øÄ-¹J*\1•žRo°Â°q,Ò¶­é!àˆ!' ˜Åi¡MÞÂäßál1.1Ê.ÁæÈfÁ´Çú¥µX@@RþNã[H—©;8 ªSsÔ¸ Ð@£ÑZ(%6¢w0EûÝÙ믠WN÷Æ¡½NW0û ÞmÒI*]‡÷ /*ކRy-ÐN½”b“1IÌ 0wÉò$«fÒˆEòE\žì\D5q…b\³OÁ<Ôòš¨f®XºÎ¢À΢ Uù0*Óì@é@’%ñÞ‹Ø¡dR¯Ô’Ùb’ÍXâê$F;Ü1C$’ò¨ÊàûYãÓ(.%Ã!“}šÄ¥°ãKõ³Ì§«UœèBÍ®,À-zj ȶ‰žËǃN§Há‡?—Ó'Ä£soŠLç mè{+>¯BF ÆÎáꃇŒ 4Ÿâä÷mõk9ŒdÙp6gõE/š ê›9ÈŸb¨SÕ2û•ÊÑÓnbP7£àjJ(îHžpËy˜ý^q%5„÷}¯zy¦ƒ¹0¾Šl nѤSºUƸ¥µÓ9,%`¾™ð˜·±µÑ0×< °@:Sƒæ®´{ô&èÄ ûÅàöεåkpO‚ø-'Ó D Õè88€dEZÄ£ú„[S;@€©·…-‚XÄ<Àà ˜eAI,¦8ÒFl£"MÙf`×/ÅÍu¨ä…‹qîKé-¬›¤¶J÷a-8»•K»¨W9%×:)\0[Æ mhèÛBÀ>u7°¤‘HK:*OJ®_Th({–¢(.HÙ4šãÕi&«H2°$ж>Â^“mÑÏð^½¦gRÉ£ Ôv–TŽQ´?Ò;ÃIfeµ*•,´'Ôe0´WåŽ,` Ä´˜¶‘×l+©ÀäHçÀŒþm9 ¤·äf¡uÆ+bºöR°zµQ(,ù†¥Ò(‰'JÝáƒ*Øù†¤äÍ­ZeÅ@³ ì`HJ ÊH é{½¼‚¡ÿSNHdZ¿“¥1 ;–ª·™ rÈ’°b—Ðbç` ­ÑS¯Ibk1¥|%´¬?锊6çÈ °dý:Ðn¤SöáÃt oÄ#¶BìáÀíö@ŸÏ‚׉µ‘"ÿäV_C8©Hr_ªi šßo}ƒFÚ»†dR•džÃƒÛHþ;ͺèvDÛ}ûžzXd0€24EÓ0²‡Ç^beÒ²¸ÉûdÉmQ_…K²dZ-#yµØÀ•¦Å72w)ôn5µ Ÿt€¶S) rã\?ª=ÄäÎtÕé:·… ôS*Âè1â¿n»”iÒâ›t¯t×¼+^¤H”ÝU*o ÷Š dšbð:Í££Î™ ôʱ(‘U˰šŒzÄç²NÚ«b' tZW°fÉFµ[´ÆjÁIS‚6Ø+%‹B¹AŽNš·ØzMéY©BsÌdú Y ôz꾬5?$Ÿ¤ÆÚ‹"Íâ…zSð8×ÅãºîÁv©‡÷?Ÿƒ^EéC”N¡Z@”½E\÷$\¯×¦`5@Òäråj´FÚ$äŒdÔíÜÖX-€âq;…‘b­ì‰JÛÌÏ6¸5pHJSäÊ”‘ ³f·;ÀÚP׆¼ÒÌè£ñOë³t…r› ç4Ì^Jr[43s-5‹Æ}\X™:Lõ<êYù»B›0¡®5gΨMÒin-)ú¨Òòܦä裆Œ6¨´[lO*Çô¿R;¤ú==ñ‘âw»Á6Ø“‡²2oû´_»E¢ÏW, Œz12ô&U?:#o„u8pàÀ|ð‹W[A#jɤ*Iþ+.8pàÀ‰äu:8pàÀÀCª8pàÀF©ràÀ88¤Ê8pà`@Õ9ôz.Ñ8pàÀAÿàÚ¿N¦Ê8pà`40П©qàÀ8pÐly*•©âƒ%VÏ=÷|EÅœ¤ŸÕï¯~ôÑÇ-:B\£Eˆª/>¡»»›664VTÌyáņWæ5×\û­n%“±vݺoÝpãqÇ_YYuê©§Ýy×]mmm‡©®QÄ믿^Q1§¦¦†Ž<Ø&4¯_öÜóÿWQ1ç³Ïjad=~X»`ˆD"½-œ~Î>ûìá;êƒB ‡úúzý`sssEÅœåË—ÿKDràÀƒ/ìôi˜ûïßÿ~~A>}6}zÓþ¦‰ËG.Þ!ÑÖÖöÇ?>{õÕßøê ž~ú™»ï¾»böìoÝxCNVÎŽÏv<ûìso½ùÖòå—••ý«¥•••PS³^|€ššj¿ßßÖÖV__?aÂqp}MMVVÖ¤I ''ûóéýÏ>Ÿï7¿¹›¾ÞzëÿŒ_zå•WНéé‘\8pàà‹Ža’ª#:’‚«À¹çž{î¹çކH‡ªúÈ#žzê© .¼ žþ9T7<|¼qã=÷ܳtéÒ»îºÓ0 8ñ¤O?ãô‹/ºä替÷ Ï½þ<âÇ<ï¨UPPP2®¤¦¦àq¤¦¦fÑÂEuuu555:©š7o®øKXŸ[ïp¹\'t}½ãŽ;òóóõ#8pàÀAŸµÐ>ÀRÂg;vÜxãMGutUÕ‚e—]¾~ýú‘Ttýu×G"‘gž~º¿ >øàÃeË.«ªZpÔQGÝø­ÿÞ¹³N?ûÖ[o}ýëgVV.8óÌ3W¬Xq˜Dýß'Ÿt»Ý?ùÉuòT2®äÚk¯Ù±cûêV‹#?üá.¼ðBýÆ«®úÆM7Ý4y~s÷oN8á„>úè’‹/©ªZp÷¯ï~÷Ý•s¶lÙªxÍ5×&U1̯šÿñÇ›LÓ_«kÖϯœ7þ¼š)Àž={ZZ[«ªªÄW½÷…`ëׯ¿ô’K++,Y²äñÇm«QwAÝ·eËÖŠŠ9Öo_Ÿ{ö¹ŠŠ9¿ûÝïHžŠŠ9ïýó=Ø»oïM7ÝtôÑÇÌ›7ñâ¾yà ápx¨è[×Àg ‡UÂ$ Ò¤E¯ýóýž{îù•• –.=ùé§Ÿ9L"9pàÀÁ*†IªÂáH1ðŸ ܱcû¥Ë. …‚·ÿâ<ðûÂ1c®¹æÚ¤À?$->û¬³þð‡?tvvö>ûá‡kn¸á†¬Ì¬ûï¿ÿ¶Ûnolj¸ì²ËÅÙ¾ÿÏ[nùÞ”©S~ä¡o~ó›÷Þ{_ÝΣ.*ç|íÚµUU•YYYI§¾²ä+°vÍÚÁ”sHy‚Á®»îºë¿¿}Óëo¼~Ùe—wܱ………/¾ø"]°wïÞµkמÞùCmBUeU$Ùºm„B¡µµóçÏŸ?>…äšê¨¬¬êóö`°ëî»ï¾åûßûÇ?Þ¾þ¿®¿ÿþû‰< Ütß´iSÀGk¥ê>Zû‘Ïç[»v}u»Ý•ó+à¦ÿ¾©±qÿí·ßþÔÓOÝzë­%%%ñx|¨è[×ÀguŒ¢„úH …B]]6r6$“C÷þö¾;î¸íÃWûÛ7Ýwß}Ï<ãð*8†¹üwñÅÓçŠÙ³ÿðÇ?ôwå=÷ü6//ïÑGñz½°pá‹/¾øÑG¹÷Þ{‡W5\{ݵ/¿òÊOüïw¾óí¤Süþ÷eeã{ï=n·**fŸzêiÿûÄÿþø'?€‡|xêÔiwÝy§X´?¾ô¼ó.˜>Ú¢v…Ã]]áqcKzŸ*,*dŒ5íß?˜r)O<ûéO~2wÞ<ºåÜsÏ}â‰'n¾ù»iiiðÒ‹/¥¥¥žrê)C’ªä¶ªšÙ³f­__ãñxf̘‘••ÕÔÔÔØÐX<¶¸¦¦&==mêÔ)}ÞÇþçÖ['O™çŸwþË~ù­·Vˆ´»`€î3 £²rþºµk¯¿þ:˲ª«k.¸àügŸ}.‰¤¦¦®]»nÆôéiii===µµ;o»í¶NX,Ê\¼øø¡6¿? l]Ÿ%Œ®„úHì!™t<ÿá0eÊT8õÔS?ýtË£>váEyÜÃô8pðeÃ03U·ÿâöåO,?I1CG<_·vÝÉ'/>cÇ¿xý†Ã«W ??ÿ‚ .xî¹ç´=L‹Å6úéI'äÆ0ŸŸ¿`á‚êšjˆF£ŸnÙrÒI'ŠpS§N?~üaµOXVâ× F¿ß?gî\ý®sÎ9'‹ýýµ¿‹^ùË«_;ík)))C•p츱cÆŒ©©^5Õë+fÏöx<ãÇÏÉÉ©^_ÕëkæÎ›çr¹ú¼=F%K;®ùÀ8T Ü}°há¢oŠÅbÛ·o…BW^y•ÇãY_³Ö­]·`áB¡“‰Ëzø¡çž{þ³Ï>ã|Ô^2°x‡ž0ºê#qùËï¾û×tj¨&íñxçÏŸO_:òÈÎÎεŸD<8øRa˜sÐÙ³f%mTïÁ`(nšO>ùÔSO©uέDâЬb`\}õU/½ôÒc?vÙ¥Ëôê,ËÊÎÉÕ¯ÌÍÎÙ¶u+„B]–eååÚÎæç纨éiiiiiû÷õ>µ¿i?ç¼°¨è… Fž´´Tb'¹¹9KNüÊ‹/üéüóÎûí·ÛÛÛÎ;ÿ¼¡Ê/PYYùþûïsÎkjjŽ:ú(qpþüy555‹.llh<¯ÿé^¯GÿÊ f™& ¢ è>X¸pA<Û°qã¶mÛ¦L™š››3oþ¼uëÖµµµ-\¸P\öÀ<ôàC?üH{{[^^^zå•W&)jX¼C ¯c%L‰ÍÍͺÀC2é´ô4} ` 3:;ƒÃʾœ8¼‰ý@ Ýår]zÉ%g'óßv IDAT}Öè–œsÉ%?õÔ3'Ÿ´”fd Ãho;¨_y°½-33ScF¨«K? u¥¦¤Ž®¨Œ±… |ðÁ‡¢jÂ»ï¾ G±H|u»\IyŠX4 ô‘ÈsÁùç_uÕ76}òÉ‹/¾4gΜɓ'¯UU•¯½öÚ¦M›¶nÛö­å{¤æÏ›ÿüÿý_uu5àá0p Ü}0iòä¬ììµ}´mëöE‹À¢… ß|óÍÂÂBÇ3ožLÚßvûmPWWÿòŸÿ|ï½÷åçœ~ú׆*mïÂë8L&a¨&Ô Åb1Jkµh€Ì̌ѕÊþƒqxÿL×ë­ªªZ»nmIIÉ;F^øW\á÷yyô½ºÙ³f­X±‚æâ-­­ëÖ®[Pµ|>ßÌ3Ö|´†®oimݹ³öpˆzÅåWÄb±;îø¥¾¸Ó°¯á‘Ǜټ¹§§gþ¼J!OZjêÚuëáÚ_ccã5W_ó‹/¬«®^³fÍm?¿=qÄè¼7|`ñ)üç a†dB)))÷ÿîw/¾ôbuuõ¯î¼óÍ7ß¼öÚkœ]ê8p0’¸Óa÷˜S¦L}î¹g|è¡;ïüu0Ø™‘‘9kæÌ‹.ò›“úIJe—>ûì³täÈ#xðÁzè¡ï|ûÿy<žªÊª»îúuqq±8{ì1ÇÞu×<ðà½÷ÞW8¦ðò+/;|¢^yå•3fÌxêé§sÏ=âíS§N{ôч²³sèšiÓ¦ßvûm=ôÐËŸ()-¹îºëbÑØÈå9yéI¿¼ãŽ3¾~-å •••+V¬˜?_-ó¹\®9s*>üpÍü¡¯ý Üw,\°fΘ‘žž&䩬ª\¹rÕ$UŒŒ1cÆ<õäÓû›š˜aLž<é׿¾ëÈ#G‡² ,Þ!…ÿ$LÂL(55åÎ;ï¼ó—¿Ú¶}GNNöwoþî²eËú¼Òô vë­·.¸bO{4jrøîIiÿj‘þ3ñ?ÿóó—_~ùž{îY²ä„C_=2üåÕWú“Ÿþå/¯ŒÊ2«ƒ/~s÷oþö÷¿‰=8pà`øÍŠ0øÝFI¶wÿº'ïž*„ÿøGG±è?øÁ§Ÿn9|µÔÕÕ¯zoÕ¿àøã;ŒÊ8ø<1´å¿ŠŠ9ýÚ´éã 3̪?O1ƒE½è¢‹èó¨KûË_þr}MÍ슊ŸüäGÃ.dªÖñEPû(¢? |qšùo48pààËgùÏ8pà`8p–ÿ8pàÀF¸üw˜^¨àÀ8pðŸ ;wr2U8pàÀ£‡T9pàÀŒRåÀ8p0 P¯Tp¶T9pàÀ \ûœL•8pàÀÁ¨ ùåŸ55ëÿ%r8pàÀü»aªþ%™T{ì1Ÿ£(8pàÀÿ®x÷•ýk2©ŠD"Ÿ£08pàÀÿ!pöT9pàÀŒôLUßÏÿÕÕÕ@yù„ÏEÿÞp¬ÅÁàñ%´–/a“8øO‡íù¿AeªŠŠŠŠŠÇ6üGÁ±ƒÇ—ÐZ¾„Mvàà˃ä=U½Q^>Áq ÇZ _Bkù6Ùƒ/Mª ©q8‰kƒƒc-/¡µ| ›ìÀÁ—’TñÁ½Q]$®…SHBSkÌ0XÒÁXÜŒÇ≄Õ @UÅØŠûfÅ=»ne=¥Áffe•z&V¹3ò†Zçܲ,—Ë5Ô‰„aŒ%÷ãð0€µüË‘HXZ"==愲¬aÒÖíóø† Ÿ|eIÕ°‹í{÷íëwÓ×”´”’q£óø‚ŒÜZK]}ýòÇ—÷y꿸}rF|‘ˆ‰¤7ª*S%0pâÚ0ØÜY¶Y—ÅyOÔ4MË4­=ûögeª7ítxÕ!Þ™š½6%‹eϾºcWÓÁOW¹*Oc^ÿP‹q¹\{6Tï|ù…`ÛA·ÇË-6s®ÿf Ìx<+LùYç•Ϋä|tþ^Ñ|™c÷ž¶£Žš»zõ'#,gæÌ‰âƒ°óp8lÀìwþQ=º¼jçÎÑhlñâãéÈÊ•«jkk'Mš4Â’¿#wÖòÞ{ïµ´¨×ÃtuuÀÙgŸ¤‹#Ñhtg]ݺµëFKHÎy4M$,:âr>Ÿox“a½7nûÛß³ £ àˆ‹/Ê/+F!88L@RÕªJOSuRå2˜e0—a€ÏëY0úºõ[“w¾ëî;33ó¦™,ƒÇ½¡ºæá‡^±+W®|w媖–6Ëâ–•0M3#¶ø„Ågœþ5Ãò )ZZÂ-m‘„i¹]F0‰ÅÍ„™ð¸]¦iÆb ÓLL÷?ºè¨°·–…V¤¶VÔ¶MsÅ-¿wUpÎckº?oÕßfp\ÊìqV¼Ç²8€Å€0p‹'ܲ8·8çiní¾í;k–žwÄõ7µQ:Fb-Ÿ'\.cwdÓÁƒmá¶¶ÈäIùÃ*qÎÈ$ªª*FΫ,Ë:ÐÒíîé † óä“—öôôÐÙªªÊÕ«?Ø´isfFÀ—â/3f$u%áó¹#±–Ïv|ÖÒÚ’Ÿ';«»§æÌ©ÈÊÊ2Mzzzz¢Ñu0j¤*‰ÔíÜÉ ‡#†Á|>0cê”É~ÿæ<ÃnrëΟ¼úׂhlþ„ ưînËíÞ÷Úï×Õ…Š ¼ø¢¬¢¢¡¶È£{ªjP™ªá¥© Æ\.ƒs° 5ÕŸ–ê™1mBõ¦úáyç·6ý—–Þǯ>Ê8õÈü ¦ü÷7švrÈÜÇ»pÕíÞ½ëÞû²?þ¤Ù³gx<ÑÒÒöÉ'›V®|çÙgŸûþ÷n®¬¬R™­í=“§”.c\aö÷ï|13ÇägüàúÓÄYïŠm¾Þ°ôtZm{ö¿{ *òÇ÷¯»véà«`ŒíY_·êo©ç}=ܼ»uÓ6œ¹]†Ûm%âVÜÆ Ç’êòx9fÌ0<¥FvFö/îYxTéü¡5* £¸¨Q[[ #OÆô†`?]]j5­¡±Ý0ü¥¥Åñá•És¹\‰DbÙ!¯ª­­ÍÏ/ÈËÍ=°q㤉“Z[[“.(++«ÝY;vÜ´`0¸cÇŽ)S¦ ».£>rÀH¬eòäÉçœsŽø,–ÿº»»£ÑhggP â‡Á ×ïªà‚x4ž’šrÄGìÝ»×ãñäåå­^½:‘H Uì!59Ôܼñ/¯¦¶µUN›~QI)‹D ‚DŒîîÒX¬4H®Ý/¼ôîÞ=æÄ‰G]xAZvö!‹%-ilØÛÏ)ÛrjcÃ7_¬_Zö§ÿ(5î÷¹Ü.£¸(;-Õ?¼Yï÷ïâožštpùŠ–«ºú`[[ªeÐÙ! EÙÆV9Ö®[÷øãO^zéåçž{¶Ø›‰Äzz¢™™Y&LXºô”×_ý§ÿßmW\~ÉùçŸ7øb£Ñx¤'æv°ºæ³’œŒMs¹Yÿ¤7¯ ¦BÜk…[#cE³Ö¼]=$Ru¯¼8sñ±áæÝíë«Ýó¸¡'‹u§–fL(w§ÌH¨kÿîžöoz&¸ÝÜ0bí)ÙYñÙåu¯¼4R5*«~½¹Ô¨³«®®H,–…Ââk]}³iºŽ?~òƵc †¼ØJ0 #‘H,]z\0Ø …?ýtûH„4ÍDQQ!$Ì<ØÖûš„™HOOOMMÝ¿¿y$uõÆ(ŽÜþ0 kù`õ|ø„B]ûëëëà«§|555 |ð!°p ;‹ ¾äæÞzëí™3gôwa9<§µµµ¤¤öïßö½©n ©É,ÂØôÉÙ§œâ.*†X D^tŒö¯+,(d„ºÂ<óìI÷Þ3sÞ¼Á‹Ô'¯"FÕØð ñ¹xìr‡W9ppHö§ÿc†ÁÞùÇêwþa;¾cÇgŸ»xHE@]à@»üKÀå'å¤qÆwÖüàd¶s?Ä»Áåιa0Î!nB€•¼$xìÚµûÑGÿ÷替w Ç|üñÖ’’"Ç Ezzb¦i¶·wîÛ×´hÑÑ@ÖÝwÿlüøÒE‹ ²äXÜŒÅÂåçe§O›X4¡T®_XáFà¯1žQô¤t¶zw¤,ê Fâ½4‰PG{ÊìâÖMÛÝ€á÷Yñhzá¸Ig^˜:/¥ „3V"²whÛºú·_L˜1—dž‘ˆÆSòÁ†Z]o-o¼ñ&}>î¸c}>ŸË媭­-//omm ¶¶6###//oT¶ tuuG"Ý]]زu/c¾%KæƒÝ-Œ)(FŒ1—KX»wwwG‡±:Üb%«¼|Âöí}ó³òò ¦iZ–•H ÙNÆèŽÜþ0TßRX\4}úŒ>øÌX,ú•%Krsrýþ”¯,Y2BaŠŠŠ¾~Æ^¯×0 ·ÛmY–iš±XLhøàÁ¶Úµñ¸LdÆãqËÎÄÁ7yÿëo\]\¼áùç#99EååSòòz3*`,ÁyC[[OFFÊ1Ç@kkGW×PEJâU:£‡W9p0 -…3Ô\½a0ιÁØU—Ÿ ‰Db_ÃXÜ䜿ôÒKC ³ ¯°v7L›TÐñæÉ?¿û‘æ6ˆtÇ h#¶Ì¡¸¾D"qÿï~ÙeWüÑ~Xãñ¸×¯ß\^^æñxâq³½=ØØØ4yòØmÛjKKË/¹äÚþèÇo¼þš×;¨}O±˜‹›Â~mÉÜÒâÜ‚ÜçVûJÃW]9`¹]æØy-û ;;ÛMsÈë †ËeÅ£8ó¸­xOî”Ù³¾õK_^1˜±ÐîmáÖ¦@ñ„´âò´âòÀ´ÛžþE´«Ó0<ÌåÎGëÑ?8”µ¼ñÆ›³gÏ¢¯O=ýÌ…\‘˜4iRmmí´iÓÊÊÊÜn7TTTÄb±mÛ¶b¦*ê‡{‚Áð†;}¾´ªªiûöµ¬\YsܱÓGR,cܲ¬H¤Ç0 Ã0æÍ›­?!8Ô§ãñ˜eYPXXXXX¨ŸÒÿF§Ø£=yòäžžž={öŒÊ"à¨ÜCb¾¥|„¼Üœ­[·„B]†áJKKYòÉ¥èÃ𙕕•ùá‡k*+çûý~Á‰MÓŒD"ÝÝݱX<ì ¤§555¹\®¢¢¢ººº‘ÔxÈ& Ã7®dAg'tv†¶nû Jä啎?ÞçÆ,Îtv¶3–¿dÉØ³Îred€ç·'Nž< azç«tþD¼ÊcÔžþëŒ1îr±XÌì w[œ»\†5ÜGÌZÃï¬o|cW±vØH™?æÔÕ»¡»tRp|d§Ú…÷W¯ÎÈÈ9OoÞ¼=%Å_^^ÜÞܲegaaQyíµ×¦ðX,‹› ËjFÎþj%çœsÞÒ‚DwfèoÞìthsAÔ {w¦Íî'º#=úÓFƒ·¸eqævñn3³dÒÌoþ—WÚ±~×ߟîj¨s¥¤efNš=þô«ÒŠË§^úý­OÞžàs€¿\œ|jŒŸ=ÝÄzx´;‘–é~øo{æM+QX¹rUIÉŒxœO˜PRS³9ŒdggVTLª©Ù',˜ž•ˆÅâ6lcÌÓÚÚVV6åoÿû I•'‚]=ë6Õ›fÂ4qÓ2‰|kýq³ ;€C<íÓ†ø«Q³-ê‰F‡±€3° —‹'âåç\í/ܲö“GÖÝÑ:õÜÿ*ùÚU[¿µá£ÝmM3®¾5}Ü”±G²kÕË®”Tñ0àЫS’µ˜ ÓL˜Ùö†O›6uÚ´i^¯WäiÊÊÊD¦j„ ÃŽaIˆDzV¯þxêÔñ3f”íÚµÕªêK.^ìõyï€x1X4åœ'Ѳ‘<Å…†a–e‰ ¬¬lø¼ê`#dæÛ«\cãûVÉ´ŽÜ>1kéïíSMM-­-}žM¤_pÁCÆ0Œ‚‚‚ùóç­[WíñxçÏŸ'T*´Ê¹ÍÍÍÝÝ=Gu„?%<ÏçR-ƒiòöÝ ÷¾¶~ê¢SZÊŽÜòæëÓfÌ4²² ¤:; ³3¿³3­¥eßùçž}¶~—·ýõ¯|õêmÇ[8v˜ûÞ¯ê—rÒT ‡ñé?‹s3a%,nš–eÉu%·Ëÿ?{çÅÕð;3Û+½Šô. ØP±aÅD‰`‹%1Q“˜bI4‰ÝXc+šh4FËgï-*±‹b¯ˆ J¯ l›™ïÑÍJY–&ˆïw8œ™Wï»ûfçî}Ê„WE…ÜÊUÀ¹§@` ¥¢¢¤¡0 J•€q 0‡ÊÏÐ8zs8VÍeÂêU——Wèê*Ù¿ÿTTT;ww—;wxÊdÒ°0šfär1IR§O_ËÊ*æry7o^Ä0¢°ÐÔI ¬§J¥Ö’:Š$)RG‘:Š!Kƒì.pdKÅQP§²ìÓ1J«ÕªTÿU:EÕØ™“"µæ®>æ^!@SÉ{×i”%|s+‘¥ -¬–6%YÏžÙì;Aîοz’b¨‹W¦é½…¡i†ft:]@@‹â—A|¾››[zzºB¡`çT¥¤¤Èd277·”””šù«JRtù ¸2{Goxþ8¹¨È®gÏ6Oždž:uyÜØ>|>·%³PÃ:¨Ø‰8ebk¼*$µF?zôØõë7F)‘Hàåp§N§cÕNÓ4EQ$Iæææ?~<(00ºoŸÚˆ„a˜……å;ï¾³}ûÿ0 kÖ¬YJJJbbâСCårymJf©²É6"†´6§µ$¥ÖòårJ£M+-ÑȈԮ^7ù<ÇgoŸ”oÞTâêêÜ·o·)Sœœœæþþ·§§gXT,¬]…,*¢Jj~L ˜à¸Öû„ÔM38ޱ ñ8`ãr‚fh€~?—h€ÝzF¡À€¢AGIƒNÂéêÉäÜgÉÀÜTMöc•[k³„;ðûø©þþÕÚ‚Òh48Îp8Øóç`ذwe2±V«£iF"¼óN§õë3®]K y ƒ“¤ŽÔ™º]$Iê´$Å×Q:¥TªÕj’RʱÇfP´0¯ ·Xä¤VkKKU¥¥J•RUCßCc\.èÔ@ðù0#Vñ†cpœÃrx| iŒàà—¡k;§Ê*{‹Þ–*.)®0A~~¾……{mnn^Ý U·÷þÜÑ3óêy?t†EûÞŠœ$îƒÏ%þ<½l®·TŸ×z0ô“ݾ”çÔãJªU¸A+^˜Ô¬åT¡…Í® $IŠÃÁiºŸ]û©Ñ¨¬Þ×ÿnaM v´±zm`Ÿ\™%t‰Ã6.Â…DêÑÅ3¬E¥øl®:j‡fjóäVIe½E.—?O¾k×îèè¾fffìšPýê<š¦U*UffÖÍ[·ÂÃÂÜÜÝ%iíå‘J¥¾÷ë¯kGþàСCãÇV'•!•59ÀÉìnòm¡µ3!àñäJ­á©ÅssJ£õ—%­ûñGÐjµE  ÃòK¨{y6ã-ž:æãvµ MTG jI¯þÓhÿ›‚c@Ó ‡ÀÙPÐÑ@ãxMg«Ô©0нœPÅŽâ–f Jq%QP$÷³WÁ—-“:uêhzr™4''ÓÖÖN­Vø-óu:fëÖ}¥¥ê>, ÆŒÁçóÞ'‰ ÄbS j4$IRJ¥æñãÜììb’ÔºK; -ãQ:üfšê‰W©Ÿ•J­Õji£ 7ÙÉÇÐÁãk ²i•ÂÂ;DîÞ¢èé=®X‚\À‚àót¤FâèÎ3³Qf>ÑrB-]ž*SzKii©••UVV¥»VjIm~~>MÓ~þ~5™¢žò?®\›oñ±¿oÀ䞪'”P©£s™ÇI÷h‘湎àýFi™ÔËøa˸–5™ÜMÓ:†Á†f§«—‰5\Èçs†£ÓUÃJfZ­Ö°³¤+©ýjµZ§Ó±³ûM™Þ^†O®Ì‹Ï_:+T°•òÃ)ÚÞ‰Z?¹Æ1Ò[ø|¾«« ‡Ëù'ùDrr²³³³µµµ¡+N£ÑäææåææCûùû[[[ñù5Ì5D*•r88{Êx[TFš<²O—´ßþ>ú¼)„ IDATš°m-siÕÈ!Ÿ+—h sF¸°ix<^‰šþëß¼¿_NHüŸÍµ€¾‚9ã‡?é=¸_tu…Ižæàèå"Q]êxõörCÀ ì»àå ¾5žîZ¢J §dËŠ ¤´Ô‹k @© ¥dM,„ÈÎöí;nmí Kú÷ï) vì8xêÔI’ÔòùÜ‘#ã8|ĈÁwïÞÉÍ-xðàZï^¦îx®%ujV§Õâ8#qH’á”(hÚxÞ™ñøÚ?™üÀh.gšÔ’¤–d'8Æçóù|“Œ`††¢8"IaêƒÂGw,Z´qï3üöŸ 4% RS :J[ª;{7ë:Ô™tZ5!”Ö~?…ê®­r§%(e2™LZ߃%_“ŸËàv/Å{~òç f:òܸîêØü$ß æH0Ï.𛇧ZõÛQÝ*Ø( c0 +?X~U SÀhšV«5$Yé¶àz•V«…—Îà ÓÑ?¹ø“ûXFkQAQ)ÿ¯ ªî© j÷äVˆ‰½E$¹¹ºšÇÄüþûú°°°îÝ»é÷‘¢i¦H¡¸yó¦NGÆÄÄXZYu±çjýaJ“ͤ’%>ú|ån›£çwþ‘ï'²4å’ :3¯ý}âÆÑ³»ÍÍÎ6÷£Ûpôpèç×ûÈþ³V&ÿÀÓcÄ;…†ÿˆêR_«ÿ0 0 †|1HÁ¾Qp¬†_|JÝ‹íÑZÀh½QE޽¦ƒØ50ÕÿaÝ«gÏ-[¶df¦š›[¼Ã! zgçÎ=yyù Eivvê¸qM,\£!‹K”</)Q)IRÇ/Ì)±0Ôأ /f¸r¤º•NGq¹¸VKju$—!¤!ŸÏÃqÌô÷1ÃÐŽá<ÁÓƒäÎÞÏ¿aSžœØ’öï¾ìgI¥Â¦E[çž#xrUÖÓÌ+'¹"Ã` E3LmßI¦÷™\Ö®][vÚ‡Ã!‚Ï糓‘{±iÓf?¿šM)Q‚­T›pvebA¡¸l´ø…ë¾n1+t—¢ „N¿,¡\¾ÐÝù×Ñ.ÁÑ)#÷Q‚Ô½Nͤ¤›ú}†°åWêttµæ?ÇqNÇ*°N6p'ÎᮞÃZT ÍÁ •Dê³~½ ÷¢%R¨Å“[&ö‡cie5hР›·nƯ^=nìXÐjµùù{öìmæãçkieeº+×tT*UÕ‰ªƒ‰MVª˜>­Ûv/-Ö(®Ýu~×o¥Ê–[,ì–o=b.?çÚBÓq ¥­ETÿ QNm88ŽÀYOw÷j £·¨*sV!ˆjQïgÿÕ-ªŠ)¥@“š@Ó@3@Ó  é£8¥„RCä]”YV¯ ‚ &Ošôý´Y‘‘ïÞ½ûè§Ÿ~MKK!ˆóay<âÀ]wïÞÎÊÊQ«©³gþðÃì2³†É¯Ò(KU4—C’:Š¢hš*)Ñž?}ED\KV˜©DŽ4©cg¾cCQ:šÒa8!Uï4: 0Œà K²RíXîû¥Ô+į¹wiÆR!2·Û6‚«)ÈJ;ñEQ„@Àθ†Ú½•ªÕ[Zøû³[,ß8@&•VwŠ:Ë3a§¢œƒ[>á°ã&?—ËÎvqq©+i‘ƒ ¨.õ~ö_òóû²¯vÜXï>UùÏG.Y*x¦y|öY´|§X$æq«·QPPЈáC×®ÛЦM7™ÌœÇÐ4E’†Ñá8!ð32²‹‹KÏž=6hÐ{Ñ}ûš^²J¥Q©´@±k—hš¦i­NýüiŽ];­ÔŠÑQìëÃ!x<EÓ¤ŽâÕ~AÒ4Í0 †ã Nsø’œûWÕëf4ëøŽÔ5@æêÏ0 †¶0·ôé­Ìk§uZ Îà.¥%u¥*†©á|m=&ö–ÇOž<~ò¤ÊÒ¾Sƒ)ê,Ç.9ô·£ôÖ!>—šÙ·âÚêź°Ò"J•¯ƒf/ôÈT8øÂ£êdŒò«1 Ãq†¦«a·77kf‰”ÏÒ´Ž$uÉÉIÙÙ©3gLïßÿÝj«RªK Z^Zª IIê´bGw†sIEÓÀNG©TU±B£,Šªý¶YX+3s¹Î6Ú‚"ÀqŽ@\š—yç*‰­“ØÞãpq`Ty™ê’"ŽPJðÅ8‡K%êÜ'¥éÙfÞup ‹ñÞ2bøû©i©U"‰j¼‹:O$éôát€éå£ ÕöBêá“ ›‡ªNÉ-eš®:;oT)«£R©H’$ˆJRC£J¿°ÆS–q?Ó}4B×'h¨À ž#²è¥?,¬‹Ê¿[„B¡¹™9¼ØäŒKËjú¢«C``@ýn¼ÉïtìèþÐþï‡w)­ZŠ:4·²2s%„ZÐÊÙŸM“­Êÿ߃Ÿ8üäòÓH»³Û ô«Õá•¢–¼ü¾6¾Q˜æ¸þo›Mh†y±œ`Ý'ìæ’ »6»ÚˆÅ"w[-iê<.W*•™¾:϶mÛìÙ½kÓ¦MûÌÏ/¢)†¢hRGJ¥Â^={|üñ33³ê–Ùµ£ß–­ ¤N—r÷ M3 CSMÓ¬ÏêÅæ… ‹)Í×àoò¤AÕ­ÅíÝ÷Ts¿Â-äBs3JCAFJE²0ãpp‚lj¡ghšÒ’êÜ'é§NñòKÝÞ­›ÃÔªì-|>ßÑ¡êÃ4j6ðW% ~8SrDZð響±Â»ýüž?LXõ­ÆØ_•”_XÝ_11vìØY¡XGzªh†‘„Z{ éÿž\* 0/å9»n·ÆOn•Ô`ŽAIIq©²´>„y=˜Òäžž?xz@â­[Û~=~JwÉ.DØÑyÿ½“O±Œ¿Ž\ÿ÷n;3Ïá‚'ޱ²²2ñXw# Qm^ݨªŽWÿñ^|Æ~)S4Í0@Ó ÃvçO `0]Mfîà8.•J¤ÒêOÞÞ¸qÝô¼`ôèÑ£G®VFèÛ7´oßZ"Q% Ã4iu!j ùáíd »ÐJ 4ÍP4ÃÐ8À¼°vIšÒR CÑÀ0:¥º4=›—WRóp«ÚÔnzo!BZ£5}u‚W÷Ñ»â lñ¸çohÅÅØ}l@‡:óÒU¸*°Â‚Ɖ‰Pa¸Z­.cQÕÒÀª×'·2js®è£”ÇÆgñ7NjÖäÐ-B[´`æÂõÛ–½Uxûî‘û½BBç|þ¾u=ýð@ 5 ÎVÿ>yê¼:’ª.¹qãºÞ®ª–Eõ†Â¾¹ÛŒŸÚº]Ê®í…w3‚ËÐÌæ4£ÿ ƒÅ·ðnçöÎ{þÁ­ªµæ¿BjvRäkFliï7bíÆõ_yr¨¸¥nƒÚ ›PW…W¶*°IMMÕóWKöÉ­qo¹u³~5\ԸɆµmÔ¶eTsoñÚÀfΜi6òq®F­£à›žøX7u#M'00èm°¨M · ^oaoy ›Œ@4a.wµäg^Þð&­þ« È¢zm4Þ‚xm¼…½å-l2ñöP½]XÇu=‰‚hb Þ‚0·°·¼…MF šÜÞ¡´k×îµK„@ Ä Î;g<éÇŸWèb-*oÓ³ pïÞ}¨Ð®j×®]pp0²«Ñ œ;w®]»vUÚU&R £Šµ¨0 ¿š”T'u#ÞB‚ƒûôé}àÀÁòFkQÙØØ$''7ˆlx˱±±aÛ׉]U½á?¸š”ôå_`VûºM†a~YºÔÍ͵²OŸ>}"!aEQuUTMŽ©Á0Œ¦éº’Ñ„IKK«2 2Ðâ ??¿f-,,s9µáÕªï©zQ ƒvµB âm!??ßÝݽfy=z¤·c[9uK *¢®@ž*hääååÕØ‚ww÷GYZZ6¶rj\Be O¢AF@4~jÿ]Í–ÐØÊ©[Q…h\ðšƒc[š1˜6È`ÿ]«4´VG+T”¹„/̽hCU=m ñ¦0`À»n®®°zÍo%%%µ/ÐÖÖÖÝÝ RRR23³Œ¤ìØ!"<žž°uÛöà`WWµZxåêÕ«I-ÂÂBeRivvÎ?'Oê[§ïl;vî lÞÜ RRŸt¸OŸÞA}ûô9røcЬ÷ï?¾cÇŽíÛ·ŸúíÔ‚ü|6jÞ¼ykV¯)..öõõõõõŒŒ4žžaX ®]»Ô¥sçõë×ëcçMM}úé§Ÿ¶nݺE‹íÛ·ÿxÌÇ%%%UêÊH®×Œ)} {¾Xnž¹:Û;wG{æv,s6"uc$‚3O~kNߪ;ѶäŸhÍ™þÙ"¹%†¨gôŸT×.]<==ø|^qqñÞ=»ÝÜ\‡äíå% òó¾÷ ¸X‘PÈf´³·spp€¬¬,¥R%“Éüýýú¿Û$I6A``@T÷nfffAXYYEG÷±²´2ìîînƒâb===D"ŽãB¡ÐÃÃ=.v µµuÙ6oþ“-V²éÏöÀcÇŽ¦?n¼¥B¡ÈÒÒJ*‘°·E……¥¥¥†5±&oÎóçé'Oww7Œ¡¥RidçNpÿþ½>úøÒ¥‹U6V/@—.===8ŽD"‰ìÔ±Kd§îݺšÉå8ŽÛÙÙöêÙC¡P”ÉÒ§w/;;[¥RÉãñ||¼{öèN’Z¬ÜK´ÊOQW”ïW5 ²r*´¨Œ„‘'+=…µ¢X*3×ôåÔaëXêx¢º>œF¡(ž?þÔ©SÝÜÜ´ Ã0÷ïß:t˜Ÿ¯ß¼yóD"ÑöíÛ?øàƒÍýåïçÆcËpîܹ±cÇFDtX±b¥J¥Z±bù°¡CvìØéàà0yÒ䈈ˆI“&ǯŽ÷psK$å¥Mø7aÂÄ =zöœ>}F~~þO?ÿ¤R©ü|ýØ”™™^Þ^bÈ$²gÏŸýöÛoŸŽÿlÓŸ›`ܸq4EíÞ³g÷®]€s8 ÃIϪB©T~ýõ7cÆ|ìâìòÏ?ÿ,Z´løˆáÆë€O?ýŒÃáüðÃVVVyyyçÏŸ×jµ"‘ȸ®*ËU³Ïº¾)Ó¡¹80\)¥VjT !sŸ3üf¤2[÷<…æZa´Z““IÛ:2ªbÆŒGàuü< Œ@’ÚOÆ}rûÎ];‚ ºuí‚ãxvvöð#2Ò3üýý~ÿýw‘Htààas ‹”””-ÿ}äÈ.—+‰>úè£Ãß·¶¶V«•Aðx}:¡S§Ž?þø#A={t_´øGGÇfúÄ4ͰƒÅ†oÞü7ŽÁĉ`m1ý®GžFºH$23“;ØÛ³çœ*•Êã'þq÷ðlbMÆq¼ÊæÜºu»yófî£?ÃçñÔjõ×_O‘J¥áááÆËáü÷rÉÎÊþê‹/[}=y2´ n¹mÛ¶?ÿÜ4}Æô°ÐPsssEaT*%BŸ%!!á»ï¾çp8³çÌîÞ­›««+Ðt™M§MùD*û”5 ö_¶XåÃv#÷(øÇßG*L\Y9=;y>ÍÚUìõìô[·ž<ÈS·¼ì÷ÕÚRÝ4HR;cúŒà`}È’%K¬­­×®[ËÎNhݺõ ¸¸5«ã—-[^el–/_îââ²lÙRöÑ èÙ³çºuë¦M›ffnnee 6Ö6ŽŽʶjå*Ÿ/fuêìì¾/b;tèСCö:8$Ø××÷wÞyøà§——\.K%8Ž–l$=¨R©&NœØ½{76A^~îê5«ÌåräU«ÕÉÉÉ?üðC—.]Ø;w®R“Fr½p8R‘óðÆ=‚˽wê9ES"iqa^%±¤ ÎÜ:q‰À1<˜ ‰Æ98²¨^K—.Kºv­c§Hss³N‘f‰0Ÿ;g¶••5A8Ž@H«V³fÏéÕ#==£E@@ÿ,-,p‹^LÁ¶·³¿qófÛ¶íXC?))éàÁƒ"# .?33Óîåè•­­X$€Ë—/Ÿ8ñOç.]›9%''{yyY[[çædÛÛ;T)sPË–úëvíÛûøøIÌòÁ诟¥¥M›>£°°ÐÅÅÅÚÚª)5Ù”æÐ4pÆÞÎA*•²SÂ.\”––Öïwqœ0ÞXÿ^c«V®JÏÈhÈÞΛ7¿E@@Nv"3“gd¤7kæ¤Ï²ió_ö;w),,bCü[ø>|Ô° vv¶U6¡Ju!L§^ýU•n¤Ò £zEz:Yé)¬ueëàÖ+Ò«Æ¢šÄ«[ªsÊ„›ZHÅž*ƒAPË }I’—.]9r—ËÕvŠŒÜü×_ Ã-SV«½uëÖ˜1c‚`c­­­ÃÂÃ/_¾ÌÞ¾ø@i4šÛ·oöÙgú”ÞÞÞ.ÎÎÌË[’$×®]{ôèÑôôt­–ds=~ü˜TQ~öU•é¹\n§NõYºwÚ³gïÃ䇾>¾Fòòù|ww÷•«V•–*ÃB[yxz²}ȸ®*ËÕh)#Ofû^úí¬fÝ™œGw/¹õ²Âžä'åš…î£}x³H)“9dÑ9A4ÒUµM ½†ï޽Ύø8¼|»[[YY¿:šÃá”””@¿~}]]\ÊÈð …P(doÓÓÓmmíØw°F£Õ'Ó¯ƒËÊÊvpppuuÝË#ºDBqAAA9ËxòË÷ê~AËär[;Û(ûb±Xo44&WÙ†ap/..I~”………ûöíkÓ¶­¹¹9†aÑÑ}Œ4ÖP°Ô´ÔV­$/Wê=žÎãñBCÃøÁ‹q޲Ti˜¥°  E‹Ç5 bnfžžžn؉XbJ*l>¢Ô«§ªZF•‘r wgïƒ'_XT½;{W)O-©Ïcj˜W†ÿØ/}HQQI’ë×oذa£A94EQ Ã-SOQQMÓææ†Q–æ÷îÞ}iT½”²"9 MÓ–––†±VÖ6z£pîܹ‡þì³Ï‚‚ZŠÅ"…Bñþûï«5j½­¯škU¦K$GŸÅÌÌ ‹†1žwÕªU«V­ŒŸWoee5ìýaŒú J]U˜«Ñe‡ÿœ¡¨ÒGJžóTÉO´wÓ€ô)¹~ŒLºÇ–ªûÇK®¤hí‚”¹×…ö=8høï5B’:++kVá*õ‹YÜ2ejùÄ––ì÷Ö­Û'NÌÈȈŒì´lÙ26–ÒQjµš½–›É¥R)[¬àå[”*%{akk#‹Ù²—/æü‚<‘D̼ü*ãñxì+Y&ûoÝqY{Ý´É+V¬X¿~C÷îÝçÎ#“ÉfÏšõã’Ÿ1 kbM®²9$I GG‡À€6ÄÌÌlòäIéY†ÙÚÚo¬a½4MËåfúЦ$ Žã8öŸÅÃc˜¥™S3¡H„a˜\&gC Ôµ¡„zuoB…ÍGÔ€7Ũ:ðÏ=Ö¢ÊJO9xútñ1.OÝ¢7ªª7þW¡±Â†—sçH$‚ † Ö@ÿò¥-"•JqÏÏÏ3ŒÊËÏ—ËåvT6Wš}ž‹‹‹ c‹‹‹EB!²ÿþ‘#G:”zøð!0z“‘yÅ|4%}iq±V«år_lîPPr™”aãyíííçÌ™ )))»vîúåç_¬­lzôˆ2®« sEG÷-¯ŠFAà::»È©Óï[ê´V ™™~ÇO§bÌ£ôûÍI\ÂUp ÜÝÐÏÐ×^á99¹ÅÅÅR©´[·ný$²›!‰D"OO¹L–ðï™LƦ¼wï®N§4xHÌ«Ý5??¿´´T,‡‡…[[[€­­µ>AVV6[ExxxhH+‡ãêââèè999÷ï?ðôò)-)e·k׎¢¨  À2é·~pwwËÌ6u'Ì  –¶vögΜŒìÄãñZ‡‡¿p‘ýÒh2M®ò>Ÿß#ª;†aÏŸ?OJJêÛ·ollì¢Å‹…B±ÞX©¬±e(?£ËHbødÜ'4“Ëõ;²^¼pQÀÅBÊÌ̪² ˆ:äþÛâ.kQõíê»ÿd¥§øúv­zÜ¿¼bf¼¾³ÿx<^hhè¥Ë—>ÿâsÃ9Œ¦Ä–OТűcÇØ@ÈÉͽ|érÿw=Õzø|¾¿Ÿß…‹†Άääæ>z”lkc4Mët:±ä¿-øŽ?^¦vÒ`“ž*Ó©Ó:uª{÷îìí‘#Går¹»‡§)yYÜÜÜ&Nš¸eë–äääèè¾&êÊ0—‘d K™ƒÀ€IœÚKž_§\¢ÌtÏR$Ú¸ÆXÑïÝ=#v{×\yãž’q°Îd3yª^+zÏÃ0'OžîݧÇ›5szii)»T ®^Mºpá¼€ÏW©TB¡°ÿþnnnŽÍšYë—è IDAT˜›–Ã0Ì…‹—ºvéÌçóW¬XVT¤°°0×étúß Ü<•лW‡3õÛ)ú¼:nöì¹<ÏÒÒòáÃdv:ùüùó”J¥X,&IR_†aÙYÙ Ã`6xÈàÁCÀê5¿‘$YeK9ÎÃäGá­ÃEBa×®]:(—Ë›R“«üÛ¶m×µKg‰DBÓôwß}ÿìùó°Ð0[;ÛF^¹rUVU­ÒgV~àÒðÖÚÚê›o&ëoïß¿wðСví#ªÛ„ª>gD5¨ðËvßñ;å—×Ù:¸Ew«`…Y}{ªXaôµGwóÛw²ÒSöŸ#òÔ-u8ü÷Ê Xù12˜4iâˆ#?ýall¬M±¢äÖ­› Í|þÅçUÆ–aÜ'Ÿ~òɸñã?<8N­R¯^½šÇã9â¥sȘ§ ÆŽûÙgãÿܸ1nP\~~Á´iÓ¸\.+1†a­[·Þñ¿#;;88;vlû¶m`0CËÍÝ]­VoݺÕÏߟÏåzzyOÏ# þéç"E‘³³óÉ“§öïß?yÒ$A€‘¼ééé3f̈Šêîêê¦ÓéŽ;®ÑhZ·nÍ0Œ]ÉU³Ïº¾)kTqmQFaâe:&%3i*2§Pq%•yX Íƒ¢+Ç5wŠKEçéô©])ŽæTÕ?•½ùRÓÒV¬XåëíÒ*ÄÜÜ\§Ó=Nyœ””´{ïÞÒÒR-IîÚµ' ÐßÇÛÇÝÝ=11ñòåÄ)S¾1,êîÝ{YY™:DØÙÙ+Ö­[×¶]ÛŽ/×m`–ššº*>ÞßÏ/$$X*•+’’®¯[»öö;Ý£¢‚xššºwß¾ÖááÏŸ?[ÿû†˜÷bBCCõ%(Š‹>`goÇzG6¬ÿ=nÐ`½b¤™æÊ•«"ÚÀÀ:‚aXSj²ñOÐßßÏÃÃ6þùgRRRt¿~§þøÞ ‹®]»ž;¡ÊƱ™ ¢÷™aš‰&{XûˆÇæÏ_(“É=<<Ê”i¼ èË¡±QÙ'bëà¶vcG°Ø:¸U«(gϱvUuå© ugT•+7Fžž^›7ÿµzuü¢…‹Š"™Lîïï?xP›ÌxlÚ´i½båÊ5«WõÕ.—ÛªU«‹ÙÛÛ¿2Q½r£ª}ûˆ… ®Š_õËÒ¥¶¶v#F 74 gÏž={ö옘—üãKFŽ©Ÿ¡Ù©SLLÌÊ•+‹ŠŠ¬­­=j<=0  ç/˜¿páÂû÷XX˜O˜8aè°aUÖ%•JmmmþøccFFŽã‹.jÓ¦5Ã0Fte$WÍ>ë׌L@ šw¶T/· JCƒ-ÇšÐé4 ´æ†·£ujëÌÁ’Òp%¾aÒCõ¾¯7âð‘£'¾>þ¨"g'‡Ã¹péÒê5¿fd¤«ÕjÇ¥R©­]`@ älß¾óü¹³¹¹¹\.·yóæ_|ùÕÉþ€ÀÀ ¶„üüÂ… _»v­¤¤X&“={ö|ÑÂÅ©©OõUà8'á߳˖-ÏÈÈÔj5<ßÎζß;ï򯯡 RSŸ|püÄ?údïôë'—Ë6þ¹©ÂôpòäÉŋ޸qƒÇãµmÛnúŒzG; ·zͯ?ÌsûöíaÆ-X¸ÈpPïî; .HLLÌËË333ëÖ½û¬™³,,-+l¬é*e¹{çÎ7ß|sõê¹\þÑÇc¾ü²¬ƒM©T …Â&9ÐŽù4¼uk±X|ãÆõåË–õêÙóÔé[ÛŠ]zo" eTM›6mûöí#FŒ˜:uªaø¦M›çÎÃ^ KKK??ß¾}££¢¢Êx ¯_¿±aÃúÄÄÄÂÂB‰DÜ¢EÀÀ£¢¢êµ!F¸páÂ7>þøãZ–Ã0ŒR© ©,§§Ç¾}û‘¢½üªCmtÿ믿&U²~~Ô¨‘<ž`ÍšÕ5.¡±te"z£ÊÓÓSèææöðákZÎÕ0FÕï—6|=9À9àbé±"­B©U©µ¤†$•Z­š$Õ¤tjµZäæ#4bÞú5åªÚ£Ñh’’’&Lœ¨wƒÙÙÙµˆ8wî¬>P( ¯¬­V»léÒ½{÷¤¥¥iµ$˜œœ\{£J,wèÐAëã뛑‘Á^¿Ü£<222"¢CÏQ¿®Y3múôZVú–£Ñh:Äçó÷íÛ7yòäòÎÑ™3gÚÙÙjµdzzúÉ“'¿øâ‹ˆˆö«VÅóx/ lÚ´yÞ¼ÜÝÝFŒáèèXRR|þü…¯¾újåÊ‘‘_{ƒ.\¸°yóæ:±ø|þÒ¥Kõ·Ó¦Msvv=z4{+‘HõQo¹¢¯KK †-áMá­ÒkTùøø” ÷ôô¼w¯â5õuKÝUó^þfìØÇŒ ñòò’H$&f¬[t:ª‰MWgê #µìÚ½ÛÍÍuð!666»vïz%׋/W² ŠÔ¶mÛm[·ªT*†aÖ­]ËårgÍšE„a2O//???öúþ½{Ÿ}öYÛ6m¢¢z¬X±BŸìÌ™3ƒ 7vÜÃäd}Ôד¿~ï½Ã2‡þé§Ÿ²×‹.ŠˆˆHLLŒ‹‹ êСÚ5¿²QóæÍ[³zMqq±¯¯¯¯¯oddd]éŠeÊ…¿Šb*×Õý{÷>÷IxxxPPÐà!CõQFä11ïùóçãbcƒ‚‚,XÀ†:x¨OŸÞAÑÑÑÇŽ5jÔØ±c†9qℯ¯ï­[· Ë5jTu½*ÓUeÂ3•´7>>>$$DŸýð¡Ãlö¾}ú9|„©¸Û¾B™!HW¦£Weµ}E¥a†ÿ„|¡ÐVH08Cb@ Uq‰F1ªÓû‚óûÉ?‹J|ˆhƒQNöŽATXŸÇ'IÒ0ÄÎÞþè±c4M³«í Å;wlll*LÏçóCBBöíÝ;iÒd¶Š¬¬¬³gÎ 2ÔÄV“$)•þ7ê±oÿ¾jª¡&èt:Ñ©„„„§OŸ xu;Ð7æµ{ª²³³/œ??fì Ãzôì±åï­…EErÙ é 70e‹ŒŒ<þüÍ[7C[…^¼x14,T.“U&üí;w†¿ÿ¾§§ç´éÓ­m¬Ÿ>yúàá6ñ¹sçÆŽÑaÅŠ•*•jÅŠåÆÙ±c§ƒƒÃ‹Ú™WubðË’F¡(^¸pá·ß~ëâârøðáÙ³g;;7ŠŠ7nMQ»÷ìÙ½kàN[YÆ2áHQ†:¹ÿÞСÃü|ýæÍ›'‰¶oßþÁlþë/??ãò˜˜wþüùS§NussÓj4 ÜN8=qÒÄèè¾ÓgÌ(,(Zºt©B¡ðóõc¦cÇŽvvv[·n™5k+[jjêÅ‹gÍœùzžµÊÐ×nDx¨¤½†n¿ &Nèѳçôé3òóóúù'•J¥Ï^yõ¯ÈÐÈAº2‘ƲOUm¨ñN¯8רµŠ¢zõê ½{õþs㟇Š‹‹3ž‹}—çfç—””4slf$ñ‹[YYÿñÇ|>Z…´ÒG-_¾ÜÅÅeÙ²¥lw èÙ³çºuë¦M«`]myHR;göl/ooˆ‹‹Û¹cÇ‘#G¢¢¢är¹X*ÁqÜÁ±÷{{;µdÉkkëµëÖ²“ÉZ·n=(.nÍêøeË–—Çļ3¦Ï ÖW¿*>00pþüì­§§gß¾}Øk‚ bÆ®]·ö믿‹Å°}Ûv±XÜ»OŸ:lom0"C³fYYYúX¨ }VRBY… Eé !IòÒ¥Ë#GŽàr¹úb;EFnþë¯*å1%¯@ j¤Õh4·oßþâ‹/ô!ÎÎÍÝÝÝõ ‰y/&~uü¾ýûâbãH’ܵ{w߾Ѡ1xªª¾|{Á`›ý³Ï>ÓèíííâìÌTõÖáDº×Ò•‰¼ñFUßè~†“¥LßQ¬Í¬Õu«<àÁËIA\·»]­bäc'ձ䧟–üô“a`\\œ¡ƒaÀ€ãé;wîܹsÅ ŽfÍž=köì2&Nœ0q"{mkk»ù¯¿ csró*LÉáp £ÊÐ¥kWÃØòõþöÛZýõøñŸÿyeE5 ^³QuûöíG}ôñG …‚ é¹iÓ¦'Ož8;;ƒÁJÁ2ÒÓÀÊÚJ"‘H$’´çÏ*“¼¨HAQ”¥•Uùì‚ ss Ã(K ó{wï¾ôç¿"íKéÿÕâ¾jàFQ:}l¦Oõ©¸„r¶R”^€¢¢"’$ׯ߰aÃFƒ(š¢¨*å1%/ëpÒgW(4M›™™hff®o”……E×®]·mÝ;0öè±£ù±6ø;’ JáË·à?;˜ÍniiikemSõúæ iÒ•i<~ü¸¡EÐSSî{°f˜nQ€·“÷™gÛiÚž,>­e´€FatÂÝ3ÍäŽgŸ^toî^{‘o¯Ù¨Ú³güöëo¿ýú›a‚={öŒgÇ‚™Šm…S§Nñx<__?†aÂÃÃÏž=[XX(—ËË×%•J8NnNNù¦I¥RÇóóó £òòóår9Â!ð2Uk5jF*©ÌÅòâ›U[¹[Èt*,¡‚_·o½¢ôUH$‚ † Ö@ÿ ‘§y% Žã†EE"¡PûÁ\¿qcû¶íAAAž þŽÔëª á+z£ë-x6{qq±a‚ââböW\û›ã}¤+ Þ´y³é‰ëªÞ—š¡œ:XýgÈþ}{MOÜ3¨g?§h+ʪ…ÈTØH¡@ÈårG´lhõêáÑ).²IÍ¿F4BH’xpçÎ.—×*$äà¡Cêrûòóx¼€-Ž;¦÷Zçäæ^¾t9,4Œ½µ¶µÉÈÌÔ—”<0y;GV²ÇkãíTÇ ½tù’“““ë«ÔG^>ŸïïçwòŸÿNèzúôirò#Ã4¡¡¡î?ÿôSbbâ@ýÐv#Àá«Ì~áâ}HNnî£GÉu,eãéªñSÕ ©–§J.“wjÛ©L —›§—›g…éo ¯ÓSu:!¡°°pâĉ¡­ZÆ3wî—/_ cŹ|ùRjêS­–ÌÌÌ<}úô¥K—ÚµkûÅç/&7~ýõ× .Œ‹ííèà ()¾tñÒ±cÇ–.ý…a˜¯&L9r䨑#ß>ÜÚÚ:-5íþƒûS¦L€qŸ|úÉ'ãÆÿ|ðà8µJ½zõj7bä¶äîݺ­[»nUüªáïÏËË[¸p!‡Czû˹î½ÉÍÝ]­VoݺÕÏߟÏåzzyÕFWeÂÊ EêjÒ¤‰#FŒüpô‡±±±666ÅŠ’[·n24óùŸW)Oõó˜±cÆÿü»ï¾ë? aaѪ+,-,07L6p`Üüùód2Y¨¨ÆàuÐË`\øŠg \2cÇýì³ñnÜ7(.?¿`Ú´i\.·OjÙê_‘¡‘ƒtU%W“’¦}ÿ½‰‰çÌëæV/ǻձQU­9UD…¼N£jßž=b±¸{÷îe*íÙ³×âÅ?îÝ»'44”u}Ïûðù|KK ßÿص[W Ãô äçç÷ç¦?7nÜXTT$‘Hü[´øqÉ’ˆˆ ÃøøøüñdžU«âçΫV«íííûõëÇæmÓ¦õŠ•+׬^ýÕW¸\n«V­,ZdooÏÆz{ûÌž=gõêøß×ýÞÜÉé£1k4ZcãV_‘:ÅÄĬ\¹²¨¨ÈÚÚúèÑ£µÑÕ«A•Ωz›e¨+OO¯Í›ÿZ½:~ÑÂÅ E‘L&÷÷÷<(Îyª "¢Ã‚ âãã>ìÔ¬Ù'Ÿ|ºé¯M‰Ä0Y÷îÝçÏŸÝ/Úp |¢—¡ á+ïll`ûö ,\¿ê—¥KmmíFŒ^¡Ñ_¶ö7aHKOƒëÊðn̘²ä«^XÆfΜi6òA–REÒ0­oX6mÚìããÝïÝú“Ñ$¹sûæ‡ +»˜ƒãû§NNóòò‚—½+éÚõ¯¾ü²¨¨¨ž$),,ܹkÔk-M¤+Ói„º***êÕ«×'Ÿ|2lØ0}àÞ}ûfLŸ¾s×.W—†Ì]U(ü[HãÑÕòeËwïÙ}âĉú«¢–°ºšöý÷&U›6o~/f€þÅtùrâ;ï.e˜“•eÉHVYÔœýE âž6ÂÌË‘§ y¹¢f(•ÊøøøðÖáfr³ŒŒŒ ë× ‚¾}û²±?N{–¿jU§ŽТª ãÂ# yͺR(·nÞ<ñω€õTEÝÒàî´†œSU‡yM‰×9ü‡0¤+Ói(]ñäÉÓ°#ª¡­Zý0¾~iä‚ ®^½8eê”Æóiê%1.<NW×®%Mœ8ÉÏßÂÄ oÄÇÑàB"O¢ÑŒªÆÒ•é4”®¸\îÒ¥¿T&L|||ùÀG/‰qákÌÓÔԸ؊ÏEغm«sóæµ,ÿuÒ€ºÚ¶};««ÆÓs3MÇS¥ÓénÝ~ü0ùYÿw:žNS·ü´dÉÒ¥¿|øàø‰ÿ6·}§_?¹\¶ñÏM0cúôíÛ·mݶmÊ”)ׯ]³·w˜1cFßèèµk×®ŽÏÈÈ ^±b…‹Ë‹ýÇØ‚-Þ IDATô¿¯_?sÆŒ›7o™™É?úxÌ—_~ÉÆ>yòxú´içÎ+))177jÙríÚuÿgï¾ÃšHúÿR 4)Ò»€HQ@PlñD ôì§g=Ôô¬¨`äTûYÎ.XÐÓ» U¹OÅ‚ž…& €JHB’ýþXÍEJØ!ÌûøäÉÎÎÌÎc2™UQQi¡Š;'Ô©’6¨®ˆCuEª+âP]u’©z’‘±aÃÆÆá[·† ÉG½ÅÌ„VÍàU~¨ÕÓd îÞ]]㑘L 8\ #SY\òƒ'¥Cœê@WG«q&Ïž=7vŒMxx„®žî»·ï^f¿ÄwÝ¿ÚTÿáÃ=?}†Á`ìÚ¹cÌhï{÷Eúb0K/þqÁ‚À•ÇŽûñÇù?.XðâùóÐP‹¹)8xá‚ý}›ŸN¯ Þ¸qûŽ×®]]dan>ÎÇfΘA¥ÊEEï×ÑÑ)--ML¼Ïf³E-O§PZZÊ`0Ä”¹’’R¥k@uEª+âP]‡êŠ8~]I¼÷)á9UDÖéj”€ª‚}ŒÕß]—&Oæñ€Ãàp±+i¯>rä“ßÖ¤¼f}¨>·«‰NÕæMÁ::º×â¯ãÏ#4h0×ÎÛ{öìyòÔ)|¶{¿~ýœû÷‹Ú·/,<\Ô¢ÖÖÖîÜæââýú÷·êeyãÆ‡ÊËÓ ¶¦öçŸW}½’Íf³öìÙkÛ»7Ì™3÷ÌéÓ×â¯óñ©««ËÉÉ‰ŠŠöööÆczyy‰Z˜N¡ŸS?âOÄlõ!0Œ'î£t ø3GQ]êŠ8TWÄ¡ºUWëT‰:§ª*.€A’»ýœþ¦ª†Çãq9<êÙ¼·ê>×SU ´ kÉ<YåÔÊ&†sØlÖƒ–-_Ž÷¨±X¬ŒŒŒ•üûõôô\\]ÓÒRE-'(++ã=*PSSÓÕÕ:t(Þ£|%ñ’’~§JMM ïQáLLM‹‹‹@QQÑÊÊ*,<¬¦¶ÖeÈk‰ÔŠòH? ¹=´âcÇù Å;ˆHNJtsŠêŠTWÄ¡º"Õq!![$]i©Ú¾5T0d݆ü|_ù8\ þþ_µ›y73—F©``Ÿ« ¢Š§j¢Ê‹<^] «ò=½àiÁêÚJ§Wq¹\ݦvÑy<ž–Ö7©´µ´^<Nü¤øø’8 …"B¦P€ËáðCäååã“ÉdþÞsç/„íÚ¾¶¼LGGgá¢E˺^×êIFÆŠåËÅw^†íÙ»}B!‚tIh¤Š·fÝú!ü|_ù¸<(¯äÖU³G»j¬¨Ç”)Y¤5¼7…ðá#÷ÓGFYaUeAEwàè«‘œ­™ª¦¦J¥R?}úØÔ.52™\VV*XZV¦¡¡Ñ82•Jið÷c±˜ªÏ]$ÆÆÆQÑûàõë×gNŸ ÑÓÓ÷óóDZ$‹D"µbü’ ‚´À‚ H—%ñNU;/ætãz¼HñyMž„ËÅžåÔ²éVM"½gR¼ä½ÊÅò yùõ¹ï™o³éÅ5µµ•=Œ©=M›¸?N^ž6xðุ¸ººº»h4š““Óõøx.—‹‡|üø15%ÅÅŵq>zúú…>ð \UUõòåK‘N¿zõêµ%$DAA!';[ÜÇ’Llš<\YYÙ’Å‹-{Z˜šûùMyýúuŸo'‚êŠ8‘êJÆ+VÆOŸ¸’’’µk×x{224ÐÖÒ,ÈÏoÕ$Ü¿ÿþýûÂß‹O;wªZ1R%j§ª¦–û>¯Î¹·B!˜r-㱸rÀ£òêÍTU&éhµÔ0•ÛÛ‚id¨Ód&›·„”~úä3nìåËq©©©gNŸ^»v ¾ë—5kÿý÷ßÓ§'$ܾ?e²/Fû)  q&>>>Ÿ?‡…í¢ÓéoÞ¼™?ž˜Vr/((˜4q‰ÇSSSƒ™L¦ûСâ8–¬©¯¯Ÿìë›””¸eKȾ}Q?~ï3®´´´å”²Õq"Õ•ŒW¬ŒŸ¾Hòrs¯^¹¢¡ÑÝÙÙ¹ñ^T“ðu¤j̘1üÒß‹µ’œSåäè¸gϯM† IUô‰Ý§‡’±*UE˜ŠP¥Dª­åÔÖrÊ9ÕÕ$f0k)Ì:ù‡™ì>=9 ´&3±··¿yëÖÎ;W1 ccã©S§á»<<<Î_¸¶kÎìÙrrrC†¸9z¬Éõìì죢÷‡…íÚ·w¯™™Y`P‹É"xî"QSS300ˆÙ¿¿°°L&ÛØØ=zÌÃÃCÇ’9~—••yåê5WWWp0À¹¿è¨¨-!!V†ÎÕq"Õ•ŒW¬ŒŸ¾Hœ ÈÎy‡LIIi°Õ$`æîî^]]o6÷^|$9§ªu7a}*eZéM»“É$àɃ¦™¡Hbª€ ¹® ƒAb²¨÷Ÿ²]™ ɧO»æîS6lذaÚܵ20pe` Óßßßßÿ¿‡PNšäË¿%$¤AS~’ñTp³ÿþ¥eåBâ9r£ªª½?Fȹt1Ù©úëÏ[úúúøg¸¹»ß¼yS¦>†BuEœHu%ã+ã§/á_C5 R0§ªó=û˨Ï+aMøŒaÆfóêõuL6£–YWWϬc3ëØL6W‘D·²4l]IÉêÈÿ999ÖÖÖ‚!6Ö6wïÜa±X4ZÓÜ2 Õq"Õ•ŒW¬ŒŸ~;B5‰KNN777!ïŧó=ûï\‡Bþ$ 0LñËýv`€`€l¨oÓê’ Ô§OŸ>œ™ùâåËl6›}çÎCÃ:Ö•}ììCÔÔÕ1 «¬¬ÔÕmbÑ Y†êŠ8‘êJÆ+VÆO¿¡š9Ut:it¾‘*O­Ëé,Z=RUŸóæM;;{GǾééÿ@ówü!‚ ]†a...•••øfsïŧóT!]^«{B}ûâ“7O:•žþeøÔÕÕüp¡WV’H$55aOã–M¨®ˆ©®d¼beüôÛªIiÀŸõ†}ý×&¢®SÕ^iZšÅÙ$kkëW99‚!Ù9Ù¦¦=?ÂAuEœHu%ã+ã§ßŽPM†a©©©©©©øê&ß·û1»O"T‘H->^'Cô2µKZDJeeN)~;4ú/³ëZ©òòòþã?ÒÒR‡ q€¢¢¢ä¤¤yóç·µ]ª+âDª+¯X?ýv„jæT}þüi´óâŸ×ã…-1%iiÓK§cMæÓøX¾“'ÛØÚ.\°àÜÙ³×®^6mj·nªK—þÔá'Ý  º"Nx]Ý»wOOW'..–Hä.OÆO_$†]¿Ÿ™• w®ÇÇÿ“žŽïE5‰_/ÿµÏ%CA¤Ûÿþ÷?pvvò^|dëî?h§‰ê4eþüøçÏ?ÀÁƒæÍsœ2ÅïTݺõæÖ­7x´ÔÔ‚ŒŒâÌÌ%vv:/^|ÂUTäúéæýû¹ððaaYÙêI“l¬­£Y,.¾÷È‘q&&jùùt Q\\=räïL&îÞ}ÿÏ??nØà>qâ‚çÛµºSE¡P2š—BþAdöuª?â!ͽYœSÕö‰ê••L¼G…{ÿ¾ÒÐP//O úâÅ⪪µLæ†Ç€••?rM ïQáùW'$¼Ã{T] Ý𬆠3»té%Þ£ ƒë×_»ºvñ+€í0§J£¾‚ ˆdaÖ¯_¿’’ü3¿É÷b-@×™S…÷«š{m_ü>ŽÇèÔ/5¹ÿè  !G>ñô<åèxð»ïN€‚Â#‚µµõ‚i¹\L0„ËÅÏMCCAN޼jÕ&sÿßÆîššJ€ ‚ ˆ”éjsªš{Ô`ºðMQMŸn‘¶wï—%níìtZÎâr±={;&[S­Ñ‚ Ò ÿú¹9Uí2Q½9d2I^žRUÅâ‡øúÚ¶:7&“sÿ~î°afk×Þápxm)Xç"ñÿ‚ Hg$ñ¯®6RÕ"±®¨Îãa ï~üÑéÚµœÜÜÊÉ“m.ì'j&‚ÿJIùáÞ½Ù<úð¡J]]aÀC2™´ví¶d+å$þ¿AéŒ$þõÑuæT,ÖÕçνúúuùóç‹ËÊVÏœé0yòÅVgÏž}tv>òáCõž=^wîÌ>rħo_½¤¤¼¶ä‰ ‚ ˆ8ÈÜHUÛýô·`ÈÔ©±ü÷ÅÅ5ãÆÜK"m’¶G=‚› Æ€œœ2ÁüeÄj ‚ ‘Ä¿>dnNˆy¢:ÒF¥¥¥ CL™+)¡'Aº¬®Ö©’þ‘*±NTGÚÈÉÑñô™3â>DrR¢XÑ• º"Õq¨®ˆCuÕ¹ÈÖHÕ¸q/Z}¤Ëps*é"tÉI‰¨®BuEª+âP]wïþ}@#UÒ‘O22V,_N"‘Ä”?†a{öî;ÎGLù#‚ ÔÕ:UR>R…H?‰Äã‰kQ®‚‚1åŒ ‚ "wª0l“½7®Ç·z@Riq{™%ÚU׎ü©‘˜˜xîìÙ‡|üøQCCc¸§çúõôôôø{'ûNŒß½{÷W¯ÿ IH¸½wÏÞçÏŸafeeµyKˆ‹‹K‡•¿#•””ìÝ»çiFÆ‹/X,Ö“'Æ&]ü1”­&j]•••oÜxûößl6{à A[·nëÕ«W‡•V²P»ÉǶoÛž‘ñ„F£ ûî»P}}}IJŠtµ‘ª¶ôl$•‘e‘»wWUUÍœ9ËØÄäíÛ7‡LKKKLLRQQáÇ Ýjhhˆ¿—§É &?yòDP` ··÷ÖmÛ¨jfVfyYY‡ž@ÊËͽz劣£“³³sJJФ‹#ÕDª«úúúɾ¾ee¥[¶„(++Gþ9Þg\Rrж¶vÇ”V²P»"îñãÇ“&Nìׯÿuua»v÷wï~¢²²²¤‹&Þ¿/é"´w§JêGªD½Í”€Žü©±;2²gÏžüM»>vsçΉ¿víûéÓùC=Ü“¿Þ^¯^½ÌÌÌn\¿Ž:U o`<Χ¸¨PßÀˆÈ«¨óRšƒ÷ø=¨vnÍ7®ÇKÚ{v‚&á›HÇÃĦÅC?HKÛoƒ=ÚÛËÔÄØÔÄxÎìY¹¹ÿ /§?|hkkë`o§«£íäØ÷@LŒÄ¯è#NNN޵µµ`ˆµM^^.‹Åj. "›Ølvƒ4-;;[Rå‘*ýúõ#Þ£züø±˜ŠæT‰›Í’—§Ér:RZڃ˗/?~ü¨´´TMMÍÝÝýçŸÖÑÑ!˜¼¢¢bóæMvvö£FÂCTUU—,Y:`à@eeåçÏŸEíÛçíåu?1IWWJJJJJJ"wGoÚlddtõÊ•àà,6{ÅŠâ:C¤+ª¨¨ìc÷Í95uu Ã*++ñ–† 8KKËŒ'O0 ÃGÍ+**Þ¾}Ëf³™L¦‚‚‚¤K'a?&Þ¯êׯ_{T5 ‹#UÃv¿¼Ì'|³±MÁÁ¶6Ö ·=†540èë` &¦ÁÞ¤¤¤Q#Go܈‡¿ÌÊš1}º…¹™‘¡ÁèÑÞ>à'ÉÍ}?k挞æzº:6ÖVS§ú×ÔÔ _¼h‘çðï‹4ÞÇgÖÌ­.€´iõ@ÔÞ¼y3eò”­Û¶Mñ›ò×_Mûþûšš"#UL&sÎìÙµ Ʊß~£P(x ££ã–1cÆxxx,[¶üÂÅKååå‡Â÷òx¼ššš}û¢üýý]\\Â#"¾><:j—Ë{!"{æÍ›ÿêÕ«Í›6UTT|øðá§¥KñO2Y\kûu"h¤J2i\Ú¾Ù:ràÀž=-oܸ¾, €D"-Z¼øëÞª ë×mÛ¾£W¯^,&²²2G{{ÛÛ;DïQVV>uê¤ï¤I7oýéàà3gÌ R墢÷ëèè”––&&Þg³ÙB O´H›V_AÛ¼y³™Yþ¦•õò+þ¼õç¤o—EhŒÍfÍš533óÅ•«×ÌÌÌš‹æäädnnž‘ñßìÞ½û»wïÜÜÝù<†zܽs§°°ÀÔ´GÓY H#êêjt:]0„^YI"‘ÔÔÔ$U$D:}?}zqqñž=¿ÆÄìOÏã||îß»';—#„’‘*Y»û¯}&ª³Ùì;wöîÝ|}'?}ú42r÷¼ùóåää€ÍfEDì0p ?þ–Í›uuucãâh4¸¹¹á¹;âä©ßëêêrrr¢¢¢½½½ñÈ^^^Ð\8"€`ž‚™YÁM§~ýàã§ÂS±Ùì¹sæþ“ž~)6ÎÞÞ^xd‡ËŸ®nemýèÑ#¸Ãƒ‡ñ€LBoX[[gd|³|qvN¶©itAi,0(héO?½÷N]CC__ßÝÍuàÀA’.”Tu¤JQA¾åLE×ΟþÒ?Rí1Q]^ž6hÐ`þæ0a99_f ***:ÀßËf³SRRÆwh€D"åõ0=leevôèÑì—/ùƒ4Í…!R¤PÛ&£ÿçÿûXZörùÃáü8^RRâÙsçœïÜLJJÊËËí׿?¾9f̸sç.?„---C#£ö­¤kóòò.**JKKÅ7‹ŠŠ’“’¼G{K¶TˆÔRPP°±µÕ×׿võjvvöܹs%]"©ðøñc‚=*|¤JLÅèR#Uͽ¶.O!Tº©Þ¬¦®•_öª¨Þ{O§Óëë룣£cbðy<.æÍ¹óÂvíŒ_[^¦££³pÑ¢€€e$©¹ð–‹'b¤M«/ÿ ¢Óé6Ö6C‡º É0((ðæÍ›³fÍ./+¿ÿefž™¹YŸ>v0kæL}}}‡¾ Š/^thsáàãã³wÏž°°]‹/)--]·nm‹]%! Ri¬-#Uõõõ+^ù4#ãСCÖÖÖír%A‘r©Â^ep¤ ¾ö–øý'á›MRVV>|øÈºµk23³´´4·„„ð×ShRïÞ}n'Ü Ûµ~ÝúÊÊ uu GGÇùó瀚ššAÌþý………d2ÙÆÆæèÑcUUUM†€}Tôþ°°]ûöî533 b1[XyYH¤P«{BgÕª ‡écÄØÛÛ£‚ ˆŒ¾‘ªö ý#Uí¥_¿~ý}»qø–&ŸØeiiyäÈÑÆáªªªÑûcˆ‡ãüýýçîð/D¶¢]ÉÖm[ïÞ½7Ùwrå犄Û_þ:Æ&ÆVVÖÂ"‚ šSÕžiEÒö‰êˆXµz„éù³çËœêï¿fíÚö)‚ "•ÐHU{¦%®]&ª#bÕêNUlll“áè: ‚ H׆FªÚ3-aCÛžEs××ö‚ú@‚ ˆH¤d¤ª‹¬¨Ž ‚ ˆÌ’’ÕÛ¹Suãz¼DÒ"]I{=¦¦1IŸ‚ "¢ŽT‰©h¤ ‘:¨S… ‚ˆTµgZ¤+A*AD$h¤ª=Ó"‚ "³ÐHU{¦‡óôÙ¿—âîq8œV$÷4ñûiÓˆÄLNNÞ»wO+!Ö¬º4R… ‚ˆDJFªºÂ:U†ÕÔÔ¤>ü$UCc»;÷^Œî€?lX’““Ž=º|ù ©ÊªË(--e0bÊ\IIIL9#‚ „Ö©jeÚK×iòd  F¸Ù¿þ÷cq©‚µ¥±¶¦“ÉM.ÒxõúƒµqëŠÑ‰°Ù,yyš¤KÑžœOŸ9#îC$'%Šõ] ª+âP]‡êŠ8TW¹¹•†uª$9Ruãzü“Œ Á|ÓÉÑQH>çs,¦ŒÖfr Ž½Ç(¼ð¿ÁNƒÕiòÔúz©—…Æ?ÏÊ5ºÓõtÔ„=þÚµ;wäåå÷èaºfMÃǘ¼ÌÊÚ¾}ûƒi,ËÞÁ!88xРÁ°~ýºÃ‡€¶–&èëë?‘)$>?·]»v>x𠺺ÚÈÈØÏÏoÕêÕÍeuïÞ½ð°]ÏŸ?———›‚ƒ/]ºxðÐám[C³²²f̘±sWáúF¾pso‡5`eArR"ª+‚P]‡êŠ8TWÄIáHU;Ì8i¤êIFƆ ‡oÝ*$õn3Z5ƒWù¡VO“5d¸{wu9ŒGb21àp1ŒLeqÉž”qª]­Æ™$$Üž?Þø Â#v——•…††0 {{|oVVæhoo{{‡èý1ÊÊʧNô4éæ­?‚‚Vñ¸ÜóçÏ'%%…Jž={6nì›ðð]=Ýwoß½Ì~ MfuÿþýiSý‡÷üýôƒ±kçŽ1£½ïÝO46þ2ðF§WmX¿nÛö½zõb1™«º³x’‘±bùr‰$¦ü1 Û³w/º%A¤‹‘ÜŠê˜À«¤çTñxy»|ÇùâG­X1èS)ÓJ‡lÚL&O4åÈ ES TÈuuÀ` “E½ÿ”í:È\H>ã'L?asΜ¹‚{---9ÚdB …²;2rwd$ÁøÐ§]“ …7™Õ°aÆ Öd>[BB:ÑT‰V~NˆÂÂB55õùóæw×ì^^V~þÂù9sfŸ?ÞÆ¦Ù_½ŽŽŽŽ_;ñ®®n^£F>thcp0ñã2™Ì9³g×2q—¯P(”Ö¾‘µóm‹&ëJx«ãñx555Gîé ...ùùÑQûºvm£vÕ¢gÏžMõ÷sv0oþ•ž?aÑ¢EêëàÐ\äÁC_Ž»Ëb±tuuýüüY³FSóËó"¹\î?üwÃêU«`Ĉ‘gÏ€ÇÀ©S'O:É3oÞ¼»ÂÚR~©%kçÛÂëJx«#‘H§~?½mkhDx8N·°°ˆÞãïïïÅx<.—Ë¿¨-''wyㆠ7n`³Ùƒ :|øHçºïµ+â&NœDÒ1K–,æñxfff¡¡[|íŽË84RÕŽ‹H!˯Q>ÜsœÏ¸=¿îÁo?nR@À²€€eÍí¥R©¥eåÍíMNiöql]’¬o[¯+á­ºuë¶sWX“½Šï†oÐ&µµµ:ÔºrJÔ®D2aâDt¯_“¤d¤êëÌGìë¿¶‘TÏõ¨º’öZê“J¥ô´èùáCa‹‹"‚ šÄVTÿ¶û$áÕ;ûqqhu/ŠÃánÒéôçÏŸ›šš¢N‚ H׆VT—Àqo=)º”VH¦Ü0þ{_Çâ±9¼ª:n7ʬa&>Î’YéÊwÒDMŸ¸ÓÉÉÉOž<^¾¼‰…˜»¤eË–ëëë[ÛX)**(¾|õrMMõ‚… $].AD¼¤dN•lTýz5O[ÎD‰««ÂÕSæê)sõT°¯o¸zT¦ŽÍÀÕ¼§¶œÁÆ NÜù îb‹[rrÒ¾½{%] ‘µz¤ÊÅÕåÕ«œÈÝ‘7l<{öŒµ•õ‰'ú9õC#U‚ ]©’ÀqK*˜”¨d.‡C&“I$—Ç¥)`<€¢®LɧSʪyañ¯‹>×É“ÍfÉË7ý„A¤uZÝû™êï?õëMRmÏ Aé,¤d¤J¶îþ³5S<0¯ÃÄ0 € $Æe“(r$ŒË&Sä0ŒÇ‰òËOÒ›ÌdSpð¥K:¼mkhVVÖŒ3ð›t^femß¾ýÁƒ4‹eïà`4£zÆ'·w<9-É*-áébuÕ˜º<…ÜìàØlVDÄn|YmÜ–Í›uuucãâh4¸¹¹á¹;âä©ß`¸§'þÀy0p ½›«KöË—6¶¶Ö§ÝáÃGð‡™[˜óðàç¼sÇöž={ž::ÚÐÀ€ÿo÷ϟñ¿ü²zÿþè3fÆ]¾rïþý«W¯“Å€ªª*§££#˜¿®®?g§¥¥-¸W[K«¢¢¢µUÕ  N‚ "i»ûÃÚ¾žº¤Gªš{å“£1.·öù_5äëÞä²³  ÞºæÙíúŒ¬^³îUBÍãwl=FÙ3EýQBFªPUU¥P( .š>cF“b/]ZúÓOü'4e¿|)˜–L&WUU ƯªªRVV5552™\VV*¸·´¬LCCO¥RôX,&zb9‚ "C$5§ ï;ñ¿†»ÈHA ™Ãb~¢[”´‹^™”°16½è¥a1Ç‘„Õ½2)'Û3«¨Eù@ÂðgÂA£Ñ\\\RR’ÍÌÌ,¿<¯¾¾¾[·nüø×o\ç¿WPPèÛ·obR"?äãÇ99Ùüœœœ®ÇÇs¹\þÞÔ”W|SO_¿ðÃþÓU«ªª^ ôØhò´úúzQkIâÐH‚ ")©ê"sª¢€¢¤bì¢"¯*g>NÝ|p (é˜ùjY þòÊæ4Œû|R5×5[‹ñ0*…èH„„†¾yóf„ñqq±ii©·nÝÚ¶mëÖÐP “ÉîC‡þ~êÔ»wïêëë/_Ž;yâ„`ÚU«Wß½sçàl6«¨¨hÉâEòòÿM ûeÍÚÿýwÆôé ·¯ÇÇO™ìK£Ñ~ À÷úøøT|þ¶‹N§¿yófþüyT»ü¬¬­ëêêŽÿ-##ãeVV[ª®Ã”––ÖŠ’’’¤ÏAiR2§ª‹ÜýG…JaÓ‹+¥0Š 2ã VPW_ZYõ8û·‚]ôÇ ¬—ÕµJxEÝôjÉÍÏ©j¬wï>·î„…íZ¿n}ee…ºº†££ãüùóñ½QQÑ+þÙÝÍU^^~ÀÀÇŸ3f4?­§çˆ#GŽîܹ#44ÄÀÀpéOKsöðð8ábxØ®9³gËÉÉ âräè1þz vvöQÑûÃÂvíÛ»×ÌÌ,0(ˆÅdñÓz{{Ïš5{çŽzzzÒ¿N•“£ãé3gÄ}ˆdqAD8TWÄ¡º"Õq¨®rs* wÿu‘uªR–«W0¦ÉŒÒ\t©Ú‡…Á@¹Cx&4ŒJÂê¹,9g¥»¬&3Ù²%$¤q¸¥¥å‘#G›L¢««{æìYÁÒ²rÁÍñ&ŒŸ0¿9gÎ\Á½Ã† 6lXs'åïïï/°Œø¤I¾ü÷ ewdäîÈÈæÒv¼wïÞ€¹¹™Ëàæ>T‚GïD’“Q]„êŠ8TWÄ¡º"­S%Yyæ^Rç’‡ ¿°‡Pžq̽:¨d²D__oÖw=ÉÈX±|¹à•í ð={÷JsEAZAJÖ©’­‘ª +šïA:†¹¹™¾‘$‰?ï¾Ýˆ)gAD‚¤d¤J¶îþC¤AqQa“ÃT|y÷߬™3´µ4ü9ÂS0NBÂíqcÇšš›ðžšúßÊ«÷îÞ;vŒ©‰±¡¾ÇСgNŸn¯Z’Bÿ¤§ûM™ÜÛÖFOWÇÂÜÌoÊä¾.o‹4©¹–“˜˜Ø ÉYõ²$˜¶¤¤díÚ5Þ^£Œ ´µ4 òó…—¡¬¬lÉâÅ–=-LMŒýü¦¼~ýºÝO³P»"ŽÈç•Ì’’»ÿdk¤ AX¶|Ê?þæûÜ÷¡!!ž#FðCNž<èíí½uÛ6*…š™•Y^V†ïJøpÚ´©NNNûöEÑ.œ?·bÅr ÃfÌœÙѧÑ!ò ò54º,ÓÒÖ*ýTzâÄñ‰'üù×_vvö’.š4Òrp¡¡[ ñ÷ò4y‚iórs¯^¹âèèäì윒’"¼ õõõ“}}ËÊJ·l QVVŽü5r¼Ï¸¤ämmmá ;jWĵøy%ˤd¤J¶æT!Gd¢zG.(åìì,¸¹}û6‰4Õ*¾YŸ¿~ݺ%K–6ykÂåË—àÌÙsøZ¬#GŽtvîñâŮکšQ]H„޼ü'ˆÇã]ºxqРÁ&¦¦xÈ™³g0 [ˆïmŸL!S(•/›d²j7U ¥ÿOI-MMM*UN|à:5á-‡Á`4n–-´:‹À_ÞÒ××Ç{T```àæî~óæMâ9t<Ô®jüy%ã¤d*4§ éPææfæ=…Ç‘T§*%%¥°°pÚ´iüô‡mmmcccìítu´ûˆ‰áç3kæ,¶níÚ>”••íß““½pÑâ6V‘”c2™µµµ¹¹ïW¯^E£ÉϘÑ5‡åÚHxËÁöö251651ž3{Vnî{‘Ò”““cmm-bcm“——Ëb5½^Œ¡v%ªÆŸW2Í©Bd”ðYêíeÙ²€¿ÿ¾=qâ„;vLrþÜ9%%%Ÿñãù!%%%%%%‘»#‚7m622ºzåJpðF›½bÅ °±µ½x)vÖÌ'N…˜¼¼¼Äq:ÒÃ×w>XSSëüù M^ÀB„·UUÕ%K–8PYYùùógQûöy{yÝOLÒÕÕm1­H***ûØÙ †¨©«cVYY‰Kz v%ªÆŸW2NÖçT_‹HøO´”””˜ýû?~TSS£¯o0ÊkÔÏ?¯ÔÒÒ"˜y×¹{÷Þ½{òò 99ùÉ“ÇË—‹üù+=Z÷£\ÐÝ»÷þùçyy9ÀˆæVSSsãÆõqãÆá²Æñx¼ššš£G ÷ô—ü‚üè¨} åÙ³gSýýœÌ›?O^žvíêÕ¥K–€4aâÄ6–_šE„GЫè%Å%¿ýö›¿¿ßÅK± æy ÐRËqtttttÄczxx¸ººyyøÐ¡ÁÁ-¦•àI‰jW"iòóJÆ¡9UýUzzz¶þfzK7Öˆ‰™8aüçÏåk×­;tèð¿)/\æ1ôÍ›7m;N,99ißÞ½’.E³Þ½{ÿîÝ{áqÚx¯¶¶vkhÈÊ• û63!¿v­®®nê´ï»wïnîîü¡t:½°°B¶lVSWÿýôiOÏîîî»##=<†­]»¦kO±±µ4hð„‰ã._*MSž¥‡ð–Ó€“““¹¹yFÆ“V¤N]]N§ †Ð++I$’ššš¨Y‰jW"iòóJÆ¡9U666666´Àß8p 4?¦õ¿þÙ¼y“Ïøñ7oý9gÎܱãÆýòËšÛ ,kþ¼yâøbc³Û4¡É»Œ'ª·ÑÞ½{uõõ}}}[Ž*àܹsÆÆÆü)½8«/óQþëñ0IdxóæMo[[Á'X;ôu(++kð5ÖUÉÉÉÙXÛäççIº ÒHxËiŒÃáò?èDM+„µµõ«œÁìœlSÓ ¢fÕaP»"¢ÉÏ+'%sª$÷ß«W¯ÊËË Ç«¨¨xûöíÓ§OÓÒÒnÞ¼yéÒ¥°°°æFGGÉÉÉ…‡GÞÓ£‡ÙÊ•YY™wïÞÁC/Zä9ü;Á„ã}|fÍœÁß|™•5cút s3#CƒÑ£½>|Àßµ)8ØÖÆ:))iÔÈF†Á7ÞºuK[KóÙ³g‚úNšØàÍ%oñˆ¹¹ïgÍœÑÓÂ\OWÇÆÚjêTÿšš"g[¿~ݯ‘‘UUUø¢pöv}„ä)➨þ"3óüù ›¾V5|;TÕÜóòrÓÓNñókЃ3f ܹs—r'!AKKËÐÈ 333ëëëù{?~¬¢¢¢ªª*r½t\.Wp³²²òÑ£G’*4Þr8Ž`䤤¤¼¼Ü~ýûI+//¢´´/ ‡%'%yöù|Ä µ+Q5÷y%ã¤d¤JÂëT]¸pÁÊÊêíÛ·T*> þqÃår9NnnîêÕ«333'Ä0,99yÈ!øP¹ 1cÆlܸ!9)ÙÓ³å%Ѳ²2G{{ÛÛ;DïQVV>uê¤ï¤I7oýéàà€G Ó«6¬_·mûŽ^½z±˜LC##CCÓ'NDþú+áýû÷ÉÉÉ‘‘¿6™ƒä-qæŒTª\Tô~ÒÒÒÄÄûl6›hm­âq¹çÏŸOJJ •Úö<Û]‹Õ…_§‚ËånÞ4mÚTË^½ðL0b¹?ðÆ÷ÑxzŽpuu]±|Yqñccã«W®$''‡G|éÇÿ¸`Á‚œêï7gÎ\9yùëñ×îݽûóÊ•]uâËŒéÓŒŒììí””” ò NŸ9]]]$érI#á-gÖÌ™úúú}_¼x~âøqCCÃ… I‹aØë× 3+î$hijéêê8îÝ»7mªÿþ˜_ßÉà;yr̘… ¬[·^II)ò×ÈnÝT—.ýIRÕÒ$Ô®DÕÜ界“’9U’¼ûïÝ»w %22ÒÂÂ"??_]]ÿ6ª¯¯¯¯¯ÏËË[µjôîÝ»qÚêêêêêjSÓw‘H¤Â„n1Û²y³®®nl\F77·‘#<#wGœ<õ;ÍfEDìÆ?°p³fÍÞ·ooHh(¾@Ñ©“'UTT&5s±©qr!G¬««ËÉÉ‰ŠŠööþòSRÔ[É444º©ª’Édc<¤íyv¼VwªN:Uþ¹|É’%9´0• qáÂ…ƒõèÑpIR‰tê÷ÓÛ¶†F„‡Óét Û? IDAT ‹èý1þþþøÞ‰'‘€tà@Ì’%‹y<ž™™YhèÖ_¿»žážžq±—bc/ÕÕÕuïÞ}àÀGãO¸F o9ƒ‡ ¾w9..–Åbéêêúùùÿ²f¦¦‘´\.÷‡æò´zÕ*1bäÙsçãñ¸\.òƒœœ\\Üå6lܸÍf4èðá#ÒvßjW"òy%ãdýî?=ztuu5¸¸¸˜˜˜ª¨¨P(÷þýû€€€ .ð?JDÅûvH¹Il6;%%eéÒ¥xÿH$Ò¨Q^GŽáÇQTTt0@0ÕÌY³""Âcc/Í™3—ÍfŸ;wnÊ?%%¥&Ñ ¹ð#***ZYY……‡ÕÔÖº bmcÓöÑ]qäÙﬨÞ:¥¥eû÷ï_»v-ÇÃÛÔ³9ÕÕÕŠŠŠ‚3Ÿ ‘HOžd4··[·n;w…íÜÕôeè 'ví{ýÍŸ?þüù’.E§!¤å, XÖº´T*µ´¬¼¹„ß Þ`¯¶¶öÁC‡—ZP»‰ðÏ+Y&%#U_g#a_ÿµÍØq>ø\õæ^ó÷ ²±±qužlddTSS#''÷îÝ»y󿥧§7X¶NP·nÝTTTòššÌø¡°Ã0"óètz}}}tt´¡ÿßîÝŸ?ó㨨¨4è…hkk;ö䉓pãÆõòò²9sæ4wˆÉ[<â¹ó\† ‰wwwëÓÛvß¾½­¶áGžm!¦Õ?}`2™›6mòU]]ÝÍ[7‡ òðÁCásªANMbsª¾í;µóH|½ØÜkcâ#àæß²³W½zõjÚ´iééé666¹¹¹Í…D"¹¹¹Ý»w¯¢¢òß­[·`èСø&•JiðmÊb1T@UU•B¡,\¸húŒ†3¾…›;wîxŸÇŸAøëæ_Žþü|9]áó©—.ý‰Åb­ùåÁïËü¼¼Ý‘»mlm‡÷ÄCôôõ ?|àO2¨ªªzùò%þžF£¹¸¸¤¤$›™™Y~Kx™‡ q±¶¶Þ²ysZZêì9s…GDüˆ½zõÚ¢  “-ü,Bž&x?š<%¥¸¨P‹ª«¨(÷ÿ…BÑÒÒêß¿¿®Êƒ ‚´£®y÷_+à#Uø«ªêáׯ_L8pРÁÁ![¶~(œþýôîšš/³²:(''wìØoüu|||öîÙ¶kñâ%¥¥¥ëÖ­œ^:fôè ÆÏ;W__ŸN¯zòä1ÆÃ6ðïÉoÆœ¹?¬ùeµººú„ D:_!G,((X¾,ÀgüxKË^çz|<“Ét:´Å³dem]WWwüøo}û:Òä廩ª6—§Ôj×Q%tÕA¤ë“’‘*Éwª˜X[[»æ—ÕÁÁ›Ö¯_×ç#~]ïŒÄ¡ ?ýºuK–,Å›J“†zxØØØˆºwòä)“'OáoNœ4ÉÁÞîâÅ‹x§ŠÈqù„7ï†Ú"ý}eÐû÷ï%]Ô©B:œ‰ê¸ŽìT‘É®€ÿõç-}}}ü+ ÜÜÝoÞ¼‰¢ ö¨`à AP\\,¸}û6CÃé3ft™¯Š®wFâpæì ÃVÇk®™1 EEÅæî̾§©©I¥Êñ§!<.Nxóî`¨]!ÒßWÖˆ´"“££˜Šþ$H‡2773·è)}çÎ]ooo<äNB‚–––¡‘p8*õ¿ÏÞ¤¤¤¼¼ÜI¾“ðMá{¹Üÿ³wßqMœÀŸ öTÁ°‘½‘!‚€ X먶"ÚjÕj]Å.GݨuÕUµÚ*Ί¶U©`uPhý)VPPA•Lòûã4FBŽp‘ÏûÅë^¹çîž{î¹ûÞsÏÝ $/ô”••]¼xÑËËS‘õÖ!óVlWJQêÿÛ2Q1“½½½ô¤{÷î©¡ª@Ý쨮rPehh8rÄH¿}=ý7r7oÞüÑGÃÒÒÒä<ÌP$ýzø0!äzÎuBÈñÇÍÚ˜ñx¼ŽÁÁ„ƒ­]·vì˜1Ó§ÏÐ××OYžbdd?$$dÆâ£.]¼HIMÝ–šºMœÃ¨Q£.Z¬ZùáÁb±R·ï˜?oîÒ%KÊËËœœV¯Y;dÈjj§ÐNûÒö¥¥í­©©áñx|0äëo¾iÓÆL‘©‘‘i{÷ìÝ»§ªªªuëÖÁÁÁ›6mwl—¿^‘P(ĵ’¿yÉÿÿ‘x¢úÿþ÷?ñgµ=iA¨UžOT§‹·Ýõœkræár¹Å%OäÌ`nnþÃúõ2'eVâ•[÷òßµž¤ïÞ7¢—‘‘ÑÂE‹eFØ'Nš8qR} ÊŸ:zôèÑr¯ÐÉYoˆˆ:[»œÍ»¹`»’OÎÿÈÛ¯©¡žÄvóæMu¾»A¨›:Ÿ¨.aááx-Aø)''GÍoDPjÕ¤Õëôð=þüýû÷ûDõAPÐà…ÊÐâ4ÝÕ§L™ÊkÛÖÃËCGG÷æÍ›{~ùÅÂÂ"NâõÆðCPP—Ê KþÇŽþ–~4Ïç›™™ÅÄÄŒ?ÞÔ´ZªZUв(ÒQ]åh䈑#GŒ¤+7Ð,ÍTáá n >Q]å×Ô4üxøû+øšä;w¼~ íÐRj¥HGu¥BvìÜÙàœþþ¥¥¥MQ U nr:ª:zÔ©S§.gg+’uª¡àÌ’æÌIVv‘+ãÏ?›»ƒ–ß*ÀßßÒÊ:00ðÒ¥Kïê°s—®Í^M¾K¿••%ïÒ¥ËM±{§‚$BH``õ¡9%%%ýpä¿©¯Ž]“=vMvჂúþ/Zxèàþ¦»4煮ëW/Z(½EÒíÏŒ’[×áC“““„‚—Šü%'')>3þð§ÉÉI"‘¨ðA†¾cæ۽'''>t°°þèEæŸäéàý„tÕON>Tì4uÓµŽü—””„–*P«<¹Õ/gg'Μ©H>wïÞ®­­¥¥lÍëþýû„¢Âû–V6bøŽ UÛ½+"nذ;w6!—ÿ@ý|¢º²·oˆßetüøqBHdd¤ Ÿ.L8þaˆ!íCjóVv÷Þìwó) A¨•"Õ¥k‘ u/‚*õíÛ·ªªJµÏ@ &ÿ0Äö!µy+»{Ï/6æ@PêVÔÐÕëÔ:{{{éDIªèÒ­[·ŠŠ Õ>]˜püÃCÚ‡Ôæ­ìî]r’ͽ{÷š¦ÚÑA0Žd-rtt”N”3?€¦cÂñC iR›w#wïöööyyyMPíhƒ  ÔJ~GuЏ¹¸¸H'JCK]þúë/BHXX˜ Ÿ.L8þaˆ!íCjóVv÷.=ÉÑÑñÿûÝÕŽ6ª@Ýï¨~óæMww÷:‰ÒTѨoß¾OŸ>Uí3Ђ Ç? 1¤}HmÞÊîÞ¥'ݼy“ÉW'TZ)ÛQ=''ÇËË‹ ¨R‹ààà’’Õ>]˜püÃCÚ‡Ôæ­ìî½Î¤œœ>A¨_‘’Õ¯^½êëë‹  Z&ÿ0Äö!µy+»{—œtõêU†GTA0tµÉÎÎFGu5¸|ù2!$ @…Ï@&ÿ0Äö!µy«¼{—?s ¨µRª£º‚ÐRE£¾}û>xð@µÏ@ &ÿ0Äö!µyãáŸ4ÃÕË××·  @µÏ@&ÿ0Äö!µy#¨ “üŽêþþsçÍ‹6Lñ •}KÃ1áø‡!†´›n÷¾cçÎU+ÍTºÕßFE½sÇΊäCÕ"gÐþþ–V6—.] ÄÃwiHišÝ{€¿?CÞ¦La%%%Yü·ðY%_@™Ý߬¾YwìØéîîÛ¯¿‹ï‚ÜœkGޤÇÅÕ=A±²þgF¢««+!äÌÙ³ywòúönŽ@ËräHº£“ch§N„ .¾×¥H”QßÌrÚ’”Bôu¸n–F/lEK¨U}Õ©-ûÈ‘ôf(´0∊^ª@Ýêë¨Ú©SSlâMç%¿ŠèQŸÙÍ[hiœ›»4xɯzüßõ—üj-U n’mTååÏ.\¸ØŒ…PW[¯m;®¶Î«Ñæ- ´d"Q‹Õ½¹K-œ^êŠ,-þ„  ÔªNGu‘(#44´YKÀgΜF½ HÖ ¢.ÿé»bDTúIvT õ÷÷ÇñàÌ™3¡¡¡Ôñ#44´µK¯ˆÐ°æ.@3;qæ/q½`&\þƒæ$ùDu*¢jÛ¶ííÛ·›·TÍ®mÛ¶âŒÖ.½ lÎ_¹×Ü…hf†6Ä¥Ãã*\þƒæ$ÙQÝÙÙùÞ=9‘xÍ™s;Ë×ñREBÑa1úí®¸üÌÂb±š» až>}ªÚ‚­[·fr>’P/@Y/ž«¶ ¡±9“óa8\þƒæTßÕôôéS'''Õ–½sçŽ8ŽaZ>u ¦¥¨’„  ÔJ²£:¥ÎÁcÙ²~Je˜pˆ†bæh|´AåÀ´|ê`¿xâç¥òŒr¹QeMÓøíMÓöLo>A¨›œ×}Sº/»&þœ‘à#”öâÃÑՄô|¤²­›¢T½ÐÌã‘:Œþ(|h¿ BHm­ ~Ê–â'Ï©ô}?36Ò}ö¼zÀ§ëšµ€*j|BeÀ´|4‚*hf2IT´$>NÈÅU’–†i-LMÔR…zÑDXäÕ/£¥Å‰ÑÝ´¯P[טš@yY[™“™âÝ5¡ù ¨"¦mÏMT/˜A¨•tGõFÖ•çr¹/_¾l̪›3KÅ(LÛé«3¨jdž@!?̇MÝý K/°î,øë±Ùôl‡LˇhX—*U vu:ªË$y-£ÁQBHLßhWWBHÚ¾ýí}}íìl !yyw3þþ¼¸¤äÏŒS/**œúÁàA66Ö„5kÖÕðù’)«×¬ãóù*—ª%kºËv¿eÞ~T˜'='Ïʱwg¥ò!„ÔYDf¢2¯ž(^/ÔßåÛ‰}º†¸B¾ùn_tŸ@_{.—}é꽕›O–?«"„t qéßÛÏ’gbl¨+‰ž–Ufçlß{®äé *‡™“£Ã;:Bæì}¿__»ç/ªÓŽ\Þ쟨îÞÄš·1ÌË/Y½åÏ[yÄëu²7Ò¯ƒ»µ‘‘îóÕÿäìH;ÿàa½5EÜR%¸\î˜1ëvßÐÖk%9›Åj°´LÓt}¡òÿͪ¯^عuV*BHEd&JæC(V~†@PjÕ`Gu"ÕSJþhÅûFG½|ù²²²ÒÔÔÔÝÝMGGû—={µ_?A„Ñ£G›6­ !>?tð@´iýbcØl¶P(|úôi«V­ÜÝÝìììRS·WUW³ÙìÁƒBŠ‹‹Y,¶™Y›V­Z­Y³–dz0003UOOO¼Ò;·¿×€¶¶¶8eÇöÔ©V*~f§¾ÃÌ#ÇëtÁœÃأ¼ß2ß„PT¸Æ³rT¼<ÒiJÖ‹úŠÖTÄ+œ5¥––¶H$ÒÖæ†vpª|V¸hýi®–›ÏÓÕòÅ‹ŠÂÂBSS“¶fF½»zú¸¶=m«ˆ¥'™ÅWc;óxŠïԩüyó !Ý;wøaCêÖÍ«—¥, 33kc¡_P,l­¥¥=et—Ë)**êÛ7ú~Á}/ÿô# âß÷žò›ž¯þb’ßÿÍÃÓ·½¯ç¸QWl½ôÖ,,Vƒ¥m²ŸQEMZ/B"FJ'ž;±U©záàÑ…¼Ž«<ºÜ½‘IÕ *½¾|4‚*`"Å;äÖ±cçO–VVÝ»÷(++§R¼¼½Žû]<ÃÊ•ßgÿóO—®ÝZµ2íÚÍ”ŠŠXlö¼¹sÌÌÌ9›Í&„&Ï™kaa! 9lö¨Ñ£»tí&‰ŠKJnß¾ãíí£««WUY)gªRßW©Rõì%ãÀßr4é]{õe.g¥2'Eus=ú'yT˜GEW<+Ǩn®/£;ª¿^áŠå),®©Cà°{…„Ãáh‰ÊE"Aî­"ŸÆǵ57ãrÙ­L^u´³³:ý†™u{qN‹.¼ó_¡¡•5úôéÓÙ3xa—rËÃÂ!ÄÌÌôîÍ»AÁámZBØlö÷߯60±b±µX,!$4´Ság_:ÆÉ*æ›_æÇÇWúzÆÆÆnN=Àz3a±XŠ—–!š´/T}™ËL—““WWò:®¢ê•"§<A¨UuTgRVZêííÃf³kjj¨”V¦­ Å3ܸ‘Ô±£••!ÄÊÒŠJ473373“Ì–Ëå¾xQQ]SsòDFXX§¶mÛ¶mûêItÏž??xððÓ§Oùµµr¦J—“R'E…R‰D"*Æj™šôŒ\© J~Gõèîn鯎ÑÝÝ,T¢œ%ÖŒ—ÿòóó­\ºp¸Ú/_7s8l!ÿÅœqAíÛI/¨«§[]YÂb½ÉâîÝ<·îVâ 9\][·6÷Õ¹ —í­~bjü*ÐáñxÔåB1.—K^–"d±¤jŠÄ/s!ûæ•ëwÚ{;ÿtˆHô¦=›Í&³¦öm ´ CG½¨7%ë…¼Eœ½»‘×õY~y4‚*P7Ež¨®x‡Ü:‹ÛØÚèéë³X,9—Y IDATc*¥´¬´ºæM?¤ÚÚ—ffæÔRUÕUTbzzú7ß|+]ŒÚÚÚ7oþõpuU¥­——瀌Œ,-ÛÞ¹s»uë6ò§Š^ß¶¢­­M…PÆÆF2K®l©tuuë$rØlj'D½”W|µQ$QW?9!„ˆD!£ßNÚ M ªŽœ¼I9æ¥g¾=Üå—§™'úJtTo|e½^¡H$Ò3hÍ~»«¼«“%£dggòñÈû÷ï÷éÓgûޝ& ’ó …B}C3ñW:ú¦›Ã‘ˆX¢g/^Õ”´´½ãÆŽ•.‘PP£¥­_'Q²XlÙ}øj{o§Þ½{ oê…«#¯ÁÒr8l›Eá×R5ŽÅå° !/B¡PDÑÖâBB‘dÎM§ñ·&P?¸Ì|êË\öÌõçC¹u-C\/!®>Ýå—Gã ¨µ’î¨.M©¹uŒÿlü‘#é¦&&;Q)çÏ×Õy+7ó—<þÜÈÈ(22òý©·…èë뻸8›gfæ°Ù>¾>FFFeee?:ÿ÷…˜˜CÃììì^={É™Yñ¢‚ZQhh¨@ hßÞ×ÈȈÔCñRÉ\ü½÷bmll!?¬ßÈçó½<=»wïJ9q"ãzN޾¾þ§£?!„¤í; Ñ ]qùï×7¨#GL„ǯ'ȣ¼#'IL„GcŠ×˜8Ô@ò²Ú«VX‰"½ n®]»ZöBØ3áÓÏ¿µðÛó³XoõA¬3J¹›ÿäÉÓgmZÇÄÄöx¾¤Öbb¬×¹£3ÏÜxó®¿ê-©D¾¹ÿ+ºøÏ­~®’›‘¡^ƒ¥Mú"¶½§ !$nâÆçµ½»z}ß•²n[ÆÑ?sLŒô·ÿ !äJnÁÌÅØlN=…¡Fœlü{õ$U/Ü|{×שÏН”áTº5iGuss³¯¿þR<úï¿7Ó ¯³Fq+NFÆ©è¾QÚÚÚÉI³***Øl6u×ÞåËÙçÎ ïÜ¥sx—Îá55ü—/kuõô8l6!$3+‹_SÃb³åMe±þ÷¿Ûîîn„ ¾«¬¬400¨­­ÕÒÒ’ùÅ/U§N¡r~ÞŸ~ÚÙ/¶Ÿ8ó³gÎ>, xusSaaáoÇŽF÷‘“ÃÉÜÕ>ž+}ÏÊ16Ò³¾šîàAF¼öØHÏÃÇɣ¼_O9åi0‘ñÕßx}©ûMnÞ~ðüE•‘¡Þ°aqí=,­ÛÙZ½yŠ‹Ô¿Îµré‹%‰Ö¦f~ûyÍë—”•¿`³9ÆFz„ŒÌ‹ÿåuðŽ–U¶ºyþüëµ~oõx»•÷¨áÒ¾ÎçÂïK\‚ƈÇó®yœÿÄÔûUçëò’»7ϧz…~¢Ð×2ÛunüsBf½ðð‹ž¹©ûTQ…¯RÅ“Sƒ  š-rÅ™$|‘7<.,<œÃfgff.X°ÈØØÄÙÙ¹¾ÎLù«W¯õps  hÕªÕË—/ïæÝÍÎÎ>pèPEE!äÊÕk\ÇÖÖÆÐদæîÝÿØ¿k÷ÏTd#g*‹Åº—ŸèðáàŽ[·nýàÁý-?n8h`‡¤‹¡T©è:%m±êûú<+ÇM©¿ÊLW*"ÏQq•Rå‘™·2õ¢ÞÕ5•·‚•º¨¬ªIZvà£7o/—?3Ï®[s:eéwâeëÌ/ÙÅJv ‹°Xäòõ‚q +bº9…têdffV[[{ëÖÿþ>n×î]5U/üX„°XäöÏ^¼Ù©Ã›ë³UÕüF•Væ ML©UÈœùu_¨ºé<+Ç?Ó7JÏϳrT*jOÿ7ñ“ø³œ|4‚*P+éŽêô*-+;söüÆ›ïÝûO$ÙÙÙ‡…‡q8œc¿ý~â«ãwnß–^„ËåžûûïÖo(**¬®®f³ÙFFF< __¡P˜™™u毿?~ôìÙ3>ŸÏápLMMÃÂ;{yyÉŸJežŸ?ýÈÑ+Wþ©®®611å×¾œ1#±¨¨šªZ©d~ñùõðƒ¨Ñë99¿ìùåtV!$¼sçêêê¥KSRS·B¬­­ý37'™!H¿ž^„x522©?Ÿºd&*•­:WAʆãc>ý´øþUBˆ™ ‹Åb-ùá÷Ü¿zò ‡J¹{¿ü›ûò®~ò¢¬ˆÃÕnmáÞgÜ¿÷BlœÃ¥çùRØÔê¿Î&„µ¶e±X¿ŸÊݶuËíBœý^5Ç>­4\»+{zò÷å%wkù,GW¿•q;ç0™?ÂŽ}ççÍSp+“âÑñCjž%ëÿÌ9úéÄ-m}«áÒ&¥üzíôeÅw!Òe{^QÓ/~ÙÙôï!¦æNjøwÈìÃäé­ð›¸_µ0Iå£T&òó‘YBùùhU nMÚQ¢§§×#BFcrddÏÈÈž2‹dffÙSö$‹ÞYÆî™Jññõõñ} 9:95¾T2ÅÄö“õòòöòòêéë÷™â¹1V“öiÆ|êи'ªB<;%‡J¦x“5imëßí­ÐÂÎ_ÎülŽV×ßI¦X;†X;†ÔY¯±©µçÛë•ÏÑ»£wŸ:‰>¡Ãë¤4XÚöGÉ)›¶®aÂ7©Æo‡õ= ½yóÑ8ª@­šú‰êráÝÀ´`¨‰‚*é4†?QšÏ©bb>A¨›ü·þ%&Öß÷¤~¿ÿqü˯¾’y Þ1tEÌLËG~¶Œû·1‹Ã;i' -¶ë'‚*hftÕœ^½z“–ý¨ñ‚i;ý&k©ÒÈ# 4—Æ_,«¯/Tóæ£qTZÑþDuhQÚµkwéÒ%ñ”ʺxñb»ví˜4 íPÍÂÕÙùÔ™KÝÃUÜ3N_tuvf`>šA¨[ŽêJi×®ÝÅ‹U^–±ùÔ˜ ”âæâœqZÅíÐÍÅYò3£òÑ8ª@­é¨ Ÿƒ=ä`Z>’P/@Yî®.ïd>šA¨[ƒOTÔ M„  šÒ4ôm²-‚*P+ÉŽêgÏê,ø®½«ë«·n9{6ïNÝ×T¼ÛC;u¢>_¸pqýú !¡ô}F%.[vùãùÍV>€æÐ3Ò.!áÕsÜg&žù1-«yË£8U n2;ªSõúa€–ãæM*Šê$s*Q]½zMæT€w›8®Ò ª@­¤;ªS¨ˆŠÍæ^º|©1ùŠDÂËÙÙÉ@=üýûö>r$½¾ ŠŠ¨~=‹z-G€¿ÿæËÿ8ž   ar¦péò¥)“'«ÜËJ$­X¹’Ò˜LÔƒÚ\y¹8ê´¯7WÇæ.ˆŠT³°X,¡P¨Ú²Ï@=Ä›«"P/ …Pª^0‚*P+é'ªK‰D_-™0êó±›»ÐâPÕ›»4CK¨U}Õ%áŒ@êó!¨ukð­8xHC½`>UÀ8ô<?~¼aÆëׯåæÞàóù'Nœ°¶¶n|þj†zÀ|èSj•—w—ê«®6ééé&&­üýýÕ¹^&C½h h©u“ùDuIôž‘ûùùýõ×_„Ôm©çÏŸ'"®€€&B½`>U Vêï¨Îb±¨Ï""¢ÒqðM„zÀ|ª@Ýš­£ºèU: ‰P/˜}ªh€–*P«f|¢:.s€FC½`>U njî¨.™Dpð…zÀ|ª@­šñ‰ê"ôM†zÀ|ª@Ý쨠‰TãÐ{F.‰N?N¹yë&!$ëôéÖ­Z™™™ùᙇ QP/˜A¨•ú;ª¿|ù2aÚ4qúüùó !;w^µjUã× 6¨̇  ÔMÍÕ9Nvvv­@mP/˜A¨•"Õ4‚*P·f{¢:€&C½`>UÀ88xHC½`>U VÍøDu†zÀ|x÷¨ÕQ½¹K@3´TZ5ãÕ4êó!¨uCGu ^0‚*`–âââÊÊJÕ–Õ××o|&ê!Þ\z-„Rõ‚TZÉ器cçÎÆä( ™ €z(öNÔ hQüý³³Ë›»*BPê&ç‰ê"‘°‘™‹D˜Ø~1±ý™€dežRd6ºêŘq XYÛbˆ!“‡?ïÚAˆ¦U¬¤¤$‹ ‘ÿ>«ä !³û›Õ7ëŽ;ÝÝÝbûõWcñà]›síÈ‘ô¸¸aÔ(ÕQ½¨ð¾•õð?3]]]Éë­+ûŸ+S&Of±Xª­H$­X¹rÖ¬Ùt• IeežjÓ¦µ¸v\¸pñ½þ+ ! _LMH „ô‰:põêµäd«Æ×‹Öo*|P@cášHVæ©”åùyyåÇŽö'„ÌL<óãëD¢Œf,’œ®ÀÉJ!ú:\7K£‡¶¢¥ ÔM~Gu‹%ªx^^P€c¼›_/QFxÝR¥©ðœ*`‘ª¤³:uêÔ¸±cýÚûZZð<=Ü'NüüáÇ’SÍÍÚHþ¹¹ºÈ)XIIÉøÏ>sqv²·³ýàƒÁ·nÝ¢ÿËÔ£‘õÂÊÚVœÕš5«üý¨Ï›6moÿÖV–¡BV­ú^ ¨VȬ¬¬•+WH¦¤,[fog[ßüÊΦNò÷’¿!ÐHÓ£´TZ)òDu¥,[öìÙ³áÃãmíìîܹ½þ‡Μ9sêT¦¡¡¡xž¹sçY[[SŸµu´ë˪¶¶vÐÀ%%ÅÉÉs R–§¼×/63ë´¹¹y“ €F“¬Z²t©••UuUõ¾}is’“ËJËgÍRa-YY™›7mšÞ><òÐÁƒ &NìÚ­›‡‡GƒY¥¥¥åä\ßà`xx8!$¨cÇ «W­Jž3‡®ÒÈÑÈzAõ®ojXX¸‹‹ !$¶_¿ž‘›7oš>c‡ÃQ<>¿F[[G:=>~D|ü Ì î€.… ¼ƒ™pùÔÊÑÑÁÑÉYþ<4^þ“Œ¨!Á!!„¢¢¢:³UVV6xÄúíØQKKK*¢"„XYYuîÒ%==]þRtid½P𪠋ŠìPQQQZZz#7wäˆxo/OK ž‡»ÛĉŸ?}òD<çìY³<=Ü333{÷êicm5+1qÆŒéËSRž={F],óõñ&R×õrsrFÄwuq¶´àuè°dñb™ÅÈÍɉ6ÌÉÑÁÆÚ*::êܹ³ªÿp¦Èþè"yZ¡¥ ÔM=OT—éì™3„OOÉÄè¨>/^¼ÐÓÓëÑ£GRrr»v²/MÞ¼yÓÝÝ]2ÅÃÝãä‰555::2NÐèEcKUïÞ}êœoHÊÏÏçr¹FFFWþùÇÓËkX\œ‰±É½ü{+W¬?üÈ‘7'ååÏfΘ>ÿ»®®®5ÕÕFÆÆB`÷îÝ™™Y„·îñåÊ•+±1}=<<–,YʳàåÝÉ˽‘+]€œœëÑQQ¾¾íW¯Yk``šºmà€éGµoßžRŸà?iÒdÕ.P*«¾ýƒüßT¦é-Uª@c<~üxÆ ׯ_ËͽÁçóOœ8!îë ˆÒÒÒ¤¤Ù>>¾½{÷¦RŒÇŸÐ18ØÀÀàêÕ+«¾ÿ>ªOŸ?OeòxU=º‹?GFö\¾|!„Ïç¿rå¡C øüZjêíÛ·ÅA•žž^PÇŽ –ŸÏ¯9{öì¤É“©ˆªþÙø§OŸž0a‚¸˜ÅbõîÝgã¦Ô¨­­íÃG\ic`ÿÐ,ÐR •;ª·÷ó;}ú4!$55õüù¿ëëG%ϯ‰~ýúµý:8ÔÏ8::fg_–9ÕÔÔ¤¼ü­‡ü–—•±X,EÊÐH¬òûTý°~½µ–¶¶½½]›6¯ýõ×_Ø¿ÿÛo§w 244,/+‹ŽŽª®©/ehh¨ø#IËËŸ ‚¶mhÖ-//¯­­]½zõÚµëĉB¡@å§<ÐEþþè‚–*%8::POTW›­Jƒ?ŸÏÿxäÇŸ?¿go𝝝ü™_¾ÔwpwwÏÎΖL¹qó†½};ù§ÝMMÁz!¿¥Ê×·=u÷Ÿ¤½{öLøüó1cÇR£7reôRœ‰‰1—Ë}üø‘üÙŒ9ÎØ±ã†ÅÅ5fuMAÎþè¢é-U¸ûÔ­¨ð¾ü¾ê ßÍ$’=›tV/_¾ütô¨ÌÌS?íÚ$=Ur433óÞ½ÿ;tYª>}¢ Ïœù‹-,,ÌÊÌŒŠŽRø{4J#ë…²ÏT …µµµFFFâ”ÿnp)mÚÚZ™“´µu:uê”––VUU%/°°°Ó§³\Þ¦TùO©ýÐwÿЬÁ‹zÔÑC‘ËÓ¦%¤§§ÇÇxRòäð¡CT¢ƒ£ƒ··!$~øpKKËö~íuuõ®]»ºuËkk뱯ÏË322>:dÍÚµ"„ 4híºµcÇŒ™>}†¾¾~Êò##ã >oä—PP#ë…ü–*il6»K×®ÛSS£¢¢mmm>´mëÖ—rsw¯ªªÚ²åG??mmO//É©IÉsbúF÷‹ùlüxÏâ¿»w¯ç\_°`aLæÌÛ7:ºÿ÷>þøcKKËòòg—/_ E3 !A'Nš4cÆLÅ¿Ž äï ‰hzK‚*`œ†{J‰ ª.]¼HIMÝ–šºMœ8jÔ¨…‹B:…vÚ—¶/-moMM Çûàƒ!_ó¸C‰H(â®iii¥¥íKœ931q&ŸÏ Ù°a#îûµid½Páí«V­þbêÔ.õµµ;oÙ²µoßhù‹DEEÅÇX¸`Aii©……ÅÕk×%§úúú¦=ºpÁÂ/§M«¬¬´µµ:ôCéL¼¼¼ÿ8~bñâE3¦Ï(++55måïï?zôhñ×BŠoBTœüý4ô©`®¬ÓÉ™:q⤉'Õ7µGDDqÉÉssóÖ¯§­pjT_KÕèÑ£ßÄ+oãñx;úI2E²F$Ï™#ý:‡³,%eYJŠ8å‹„„/Ä£ÞÞ>;vî”^WÙ\\\6nÜ$³T¶vvu*f‘¿€&¢é-UèSŒÓ`ß‘Â}ªÞ¬šþžZh!4½O‚*`ŽÔeŽºs6wÁšP#ë…¦« …Ðôè—ÿ@cˆD¢ÇBþ½õ/!äô_§[·jeffæ÷ú}-‚õBÓUÐB OÍêkszùòe´iâÑùóçB:wî¼jÕ*5•  ù4²^({÷@³Ðô>Uª€qê;xp8œ:ß”??À»¤‘õhMo©BŸ*`œ†ûŽÔ×¥àÝÕÈz>U 4=úGPðîÓôc´šý#¨ÆAK€4´TAK éÑ?‚*`UÒY/4ýX-„¦Gÿª€qTHkd½Ðôc´šý#¨x÷iú± ZMþTã ¥ @Zª %ÐôèA0Kqqq…ªôõõ›»øM¢ñõ¢ðAWaˆ!󇚋•””d4òßÂg•|!dv³úfݱc§»»[l¿þj,¼ rs®9’7¬Nº•õð?3]]]Éë­‹Åb_–õCÅøû›˜˜4&ujÓ¦µ¸v\¸pñ½þ+ ! _LMH „ô‰:põêµÃ‡ú¢^@‹’²~mU#¥ö\þÆ¡ñ2GmÚ´árµÄ}Svþ´S$}‘@i°ÃÊoÇŽZZZR!ÄÊʪs—.éééû®Šjºz!‰Åbv¨¨¨(--½‘›;rD¼·—§¥ÏÃÝmâÄÏŸ>y"žsö¬Yžî™™™½{õ´±¶š•˜8cÆôå))Ïž=£.&úúx©ëz¹99#⇻º8[Zð‚:tX²x±ÌbäæäÄ æäè`cmuîÜY•~³FQjÿ@AK0ígäÕÕÕ ¸øñÊ•+ut´ãâ†SéçÏóôôÜ»wï÷+WÚÚÚ~úé˜qŸ}&ó6«›7oº»»K¦x¸{œ—Ë522ºòÏ?ž^^ÃââLŒMîåß[¹bÅðøáG޼9‘(/6sÆôùß-puu­©®626 »wïÎÌÌ"„p¸u/W®\‰éëáá±dÉRž/ïN^î\éää\ŽŠòõm¿zÍZƒÔÔm H?z¬}ûö„‚üü€ÿI“&«vRqò÷òCh±TÆ8sæì¾}û.]ºX\\lbbÒ¥K—©S§¶mÛ¶Á@õOoÓÆl÷îŸ=<<¨ô‡>|ø0eÙÒY³“lllìß?kVb Ÿ?eÊéLJK˼}|$SLLME"QYYÇ£ãû¨B…záìì\' ¨ªªª¨¨¨®®JÛ›vâÄñ¨¨(ˆÈȈÈHj†ŽÁÁ>>¾ÃÃnäæzxzR‰|~ÍÒ¥Ë:‹ó126f³Ù¶vv2×›4{VÛ¶¼ƒ‡ëêêBBB:Éœ-9)‰ÇãíMK£NW:wîÜ«gdʲ¥ÛR·B‹ÅápØœ&¿Ì"ÿ ýUÀ@õ‘¯[·îùóçƒ ¶²±þï¿»©ÛRÿ¾páàò3\ºdiù³ò‡EüñÇ!C>øeÏÞ   BˆP(|ñâŦM›©#GXXX~AþêUßOœ8Q©Þ$j@{½Ñ£»øsddÏåËWBø|þ÷+W:t°  €Ï¯¥¦Þ¾}[Téééuì¨à*øüš³gÏNš<™Š¨êŸúôé &ˆ€Y,VïÞ}6nÚHÚÚÚ>|ôXñ¯¦2ì@ª€qê;x$%%98´z¸¹Ož2åØÑcŸ¡øÐ7&&$$xîœ9‡&„´nÝ://¯s—.â9»uívòĉû÷ ìíÛÕÉÄÔÔ¤¼¼\2¥¼¬ŒÅbáí ´× I?¬_ocm£¥­mooצͫ—•}ýõWöïÿöÛé‚‚ ËËÊ¢££ªkªÅK*þHÒòòg mÛšuËËËkkkW¯^½ví:q¢P(Pù)*Sjÿ@APÃÁ¡äh@` !äÑãGŠç ¥¥åáîqýú5jÔÍÝýâÅ‹„¼9V EBB›%ãÊ‚»»{öÛï_»qó†½};ù§ÝMÍÁ¡ä¨ õ‚âëÛžºûOÒÞ={&|þù˜±c©Ñ¹2ú?)ÎÄĘËå>n¨`ÆÆÆgìØqÃâ⳺ÆSjÿ@ÁÆŒ£à=M.\ „¸¸¸Ê¹Ë©ÎÙmYYÙÅ‹œœ¨Ñ¾}ûBNœ8)žáÄñãfffÖ66ÒYõéUXXxæÌ«GÿfefFEGÑô¥@c½P„P(¬­­522§þõpƒKéhëÔÖÖÊœ¤­­Ó©S§´´´ªª*y9èè„……>åàààò6e¿B#)µ  ¥ G‘Ã@yyùÒ¥K=Ü=ºví"gþ¸aÃlll||}ôõõ ò vìÜñüù³„iÓ¨©‘‘=ÃÃçLžTTô­­íý³Ó4_ IDATû³²²–,]Êf³ !²fíÚB´vÝÚ±cÆLŸ>C__?eyŠ‘‘ñ„ ŸÓô¥@c½P›ÍîÒµëöÔÔ¨¨h[[ÛÇmÛºµÁ¥ÜÜÝ«ªª¶lùÑÏÏ_G[ÛÓËKrjRòœ˜¾Ñýbc>?žÇ³øïîÝë9×,XX'“9sçöŽîßÿ½?þØÒÒ²¼üÙåË—DBÑÌÄDBHAAAP‡À‰“&͘1³1_°Aò÷2!¨Æið`PSS3yò”ʪª›6±Ùl9óGDF¦íݳwªªÖ­[oÚ´ÙßߟšÊb±R·ï˜?oîÒ%KÊËËœœV¯Y;dÈWÅ øù4ZZZiiûgÎLLœÉçóCBB6l؈ûþ@mh¬ ZµjõS§vé®­­Ý18xË–­}ûFË_$***>~Ä JKK-,,®^».9Õ××7ýèÑ… ~9mZee¥­­íСJgâååýÇñ‹/š1}FYY©©i+ÿÑ£G¿š, ¡ É%ÿ ‚*Ð0µµüI“'ÿûïÍ›´³màea£G~³/–ÅÈÈhá¢Å Éxaˆˆâ’'’)æææ?¬_¯B™ššRõB’œ:Âãñvþô“dŠdHž3GúQãgYJʲ”qÊ Ôó3)ÞÞ>;vî”^WÙ\\\6nÜ$³T¶vvu*fÓ‘³ A0Žœ3ìÚÚÚ/¦~ñOvöúõëÝÝÝiL(c¡^0‚*`œú /_¾üòËiçΟ[»n­¯¯/ŽТ ^0‚*ÐóæÏ;y2cÐÀAeOKÿñ•hkgëææ.A€wês ¨Æ©ïTûê•«„½i{÷¦í'2ä›o¿USÉšêó!¨Æ©ïà±wï^™é¸Þ-êó!¨ÆÁÁ@êóá!f4@K0ÎȤ¡^0‚*`<¤¡^0‚*`<¤¡^0úTÐ-UÀ88#†zÀ|ª€YŠ‹‹+++U[V__ŸÞÂ0ê€F@P àï/ó öJå•yŠ®ò0ꀦ@PïšÎ]º6w¢Î@õ4…FŸ ¨¹œ=eòd‹¥Úâ"‘hÅÊ•1±ýè-@óB½Ðª€YX,–P(TmÙ‚‚z À¨TÆ©J~¶anÖfâç$Ï;Û/6ÖÖÆÚÙÉñÓOGÉÉ¡¤¤dügŸ¹8;ÙÛÙ~ðÁà[·nÑðmCc½X³fu€¿õyÓ¦Mæfm¨?k+ËÐN!«V}/T+dVVÖÊ•+$SR–-³·³mpAgS§øáqâ_Fü׫g$5Uò7CK´ÇŽû믿´µµ%/]º4àý÷;¬[÷CeUÕâE‹Þë›ñç)éjkk XRRœœ<ÇÀÀ eyÊ{ýb3³N›››«ëK4•%K—ZYYUWUïÛ—6'9¹¬´,qÖ,òÉÊÊܼiÓäÉSÄ)fæfnnnô•T}&NšUÀ8´?§¢¢â›¯¿š5köŒÓ%Ó/^djjúó/¿P÷œûùù…‡…nÞ¼iÒ¤ÉÒ™¤¥¥åä\ßà`xx8!$¨cÇ «W­Jž3‡ÞÒÈԤϩ wqq!„Äöë×32bóæMÓgÌàp8ŠçÀç×hkëH§ÇLjA[AÕ(((Hrô»ïæ³X¬¡C†6Wy@#àò0í—ÿ¾ûn¾•µõ°¸¸:é—.^ìܹ‹ø)>®®®¿>,3“ߎµ´´¤"*Bˆ••Uç.]ÒÓÓéøÆ £½^ÈÄb±;TTT”––ÞÈÍ9"ÞÛËÓÒ‚çáî6qâçOŸ<Ï9{Ö,O÷ÌÌÌÞ½zÚX[ÍJLœ1cúò””gÏžQWÊ|}¼‰Ôu½ÜœœñÃ]]œ--xA:,Y¼Xf1rsr↠srt°±¶ŠŽŽ:wî¬J¿m„Báž_~ édgoß¼%†CK0½gäÿüóÏÖ-[ÿøãé›§ø|¾¶Î[uttnܸ!3Ÿ›7oº»»K¦x¸{œ—Ë522ºòÏ?ž^^ÃââLŒMîåß[¹bÅðøáG޼9‘(/6sÆôùß-puu­©®626 »wïÎÌÌ"„p¸u/W®\‰éëáá±dÉRž/ïN^î\éää\ŽŠòõm¿zÍZƒÔÔm H?z¬}ûö„‚üü€ÿI“&«vR5§OŸ¾ÿþW_}-N‘ÿB‹…  4ÆåË—×®]sóæ¿¥¥¥úúúíÛûŽ?! @Î"à‹©S?õ‰§——ôT—ìË—E"o•––Þ¹s‡ÏçWWWëêêÖ™¹´´ÌÛÇG2ÅÄÔT$•••ñx¼F9©P/œëUUUÕÕUi{ÓNœ8¥££ùª_vÇà`ßÎáa7rs=<=©D>¿féÒeƒƒÅù³Ùl[;;™ëMš=«m[ÞÁC‡©úÒIælÉII<ooZuºÒ¹sç^=#S–-Ý–ºBX,‡Ãæ¨õ2Ëî]»ôõõû½÷ž8Eú7 ª€ê;#¿ÿ¾‰‰éèQ£[·iý¤äÉîŸw9b÷îÝžõeõÃëJJŠ¿þú™SG=yò¤¤Ù³§LZYYùÕ—_R7=±Ù*> éÐX/¤Eôè.þÙsùò„>ŸÿýÊ•‡,((àók©©·oßUzzzA;*¸ >¿æìÙ³“&O–>cy{6þéÓ§'L˜ nf±X½{÷Ù¸i#5jkkûðÑcÅ¿Zã½xñâ×_ÇÆÆÊ¼…@‚*`œú111111âѨè¨=z?žÇ³øïîÝë9×,XX'“9sçöŽîßÿ½?þØÒÒ²¼üÙåË—DBÑÌÄDBHAAAP‡À‰“&͘1³1_PA»wï‰D~ø¡ÖïUÀ8ò‰3Ÿ¿xöøqñîÝ?7î‡õëýÚ·WmEFFF·nÝJKÛËç×:»8Ïÿ#?~S ¡P ˆ/£hii¥¥íKœ931q&ŸÏ Ù°a#îûµQ[½[µjõS§vé®­­Ý18xË–­}ûFË_$***>~Ä JKK-,,®^».9Õ××7ýèÑ… ~9mZee¥­­íС2‚//ï?ŽŸX¼xÑŒé3ÊÊJMM[ùûû· ‹D@(PñMˆJ‰D?ÿüspHH»vjX¼T†qq}Õó#""2¶_ìŠå+¶nÝ¢à²÷òßz³¬½}»Cõ<ê“Ò#"¢¸ä‰dй¹ùë×+Y^uP¹^Œ=zt=W±y<ÞΟ~’L‘¬ÉsæH¿N€Ãá,KIY–’"Nù"!á‹„ñ¨··ÏŽ;¥×Ug6—7É,•­]ŠÙtX,ÖåËÙ Ïð‚*`/[p¹g'çÿ½IïÃB˜ õ€ùTãÔw0’/#{öìÙÕ«W]]]pð€–õ€ùTƘ4i²¥¥¥»‡›žž^у¢}ö½xñ|ÌØ1Í].€æ„zÀª€qê;à ;šžžž~¤ººÚÔÄÄÏßñ¢Å^^^8#‡–õ€ùTãÔw0:dÈÐ!CœàƒzÀ|ª€qp<†zÀ|j}Ñ7À» -UÀ88#†zÀ|ª€qpð†zÀ|ª€qpð†zÀ|èS@´TãàŒ@êó!¨f)..®¬¬TmY}}}z À¨A0H€¿¿Ì7Ø+•CVæ)ºÊÀ¨šA0H``@``@#3±´²¡¥0j›s­ÁyP/ ¥IY¾½¹‹ "tT ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚* ¨ ‚*p›»„âèäxóæ¿NNNϪ´´´ñ™¨Ç‘#éŽNŽõMíiG?!C%h~yyåÔÆ¯qT#„vêD9r$½¹  VŽNŽÔÆ/SBB!äãùj,@óëiGmüA0Eh§NrŽ.-SBB€†]Z ô© Zª 9•—?»pábs—€YJ˪g&žiîR€ÒTA³‰2X¬îÍ] ‰2!¨’¨z¡”ª^ߟu­éJï$9÷7iPUPÔ ¥DP…û³@5òïox7(wù÷gŠ ï7whƒ»ÿh€   ayywóòîb£-p‡  @!––––V6Åh á‘ stt<Æ`£-gÇJJJ²ùoá³J¾€2»¿Ys @$(!„èëpÝ,^تDK•ä¹Kaa¡ÝÌ™3“’’¦Nºnݺ¢¢¢V­ZQSçÍ›·páÂ/^Ôù\Ç´iÓV¯^íââ²råJêêjÉù97jÔ¨'Ož$&&–––üúë¯Ôâëׯ·³³?~¼‹‹KZZÚ† öìÙ3hР§OŸÎž={Û¶mW¯^%„p¹\ooo--­¤¤$ ‹G?~|öìÙmÚ´Q¤TW®\ HHH044ܰaÃΜ9H©/gùKI¯ÈÒÒÒÂÂb̘1‹/¦ óèÑ#kkëE‹%$$ÈÏ-===&&æƒ>7n\qqñôéÓ+**Ä¿PŠ ï_ºtùrvvƒsøû*ñòM/ÿíØ±C  :”2tèÐ+VüüóÏãÆS6Ÿššš~ø!,,LæÔ9s槦¦R£ÞÞÞîîî’3¼xñâû￈ˆ „ôêÕëÔ©STPÕºuk6›Ý®];jÎÊÊÊœœœ-[¶¼÷Þ{TJ¿~ý/ÕW_}eiiùûï¿ëêêBzôèѱcÇyóæíß¿_NÎr–ªoEƒþé§Ÿ.\Èf³ !»ví"„|ôÑG æ–œœìçç·k×.‹Eqqqñ÷÷oà×hy¨ˆ*qæÌçœ;o!Dñ¸JÅŽêÛ¶móõõ¥Bœàà`‡mÛ¶©¾¾~hh¨ÌIUUU/^ìß¿¿8ÅÕÕÕÓÓSrCCÃ=zˆG½½½-'gùKÕ÷õãããyòÄÐÐPµ ííí·nÝJ¹qãÆæÍ›¿ùæ++«áÇ+R‡3eÊ”Q£F)žóàÁƒå/%‹ÅŠ‹‹[±bźuë¶oßnhhøþûï7Xê·*//—L,//Wù·x·©35Hé–*>Ÿ¿k×®àààŒ·ùùùmß¾]þe5¥èééuèÐáÀâ”[·nåää(¸¸ŽŽNmm­ÌIK—.ÕÓÓ»~ýº"YéêêvëÖ-##ÃÙÙÙýmrrV|©:†þâÅ‹}ûöíܹsÀ€úúú –ú­Ž?.Τ¨¨HÁoÐÕi”²±±i†–ª#GŽ//¯1y*ÝRµmÛ6##£Áƒ×IÿðÃõôôTë®^Ÿ¾}ûîÚµëüùó={öœ1cFRR’“““‰‰‰"˾÷Þ{cÆŒILL ŽŽŽ655µ±±Y¶lY¯^½bcc/]ºôóÏ?÷ìÙSÁ’´oßþÂ… ÖÖÖS¦L‰ˆˆøôÓOÿùçŸ.]ºBää,g)ù†þàÁkkkÉU~nÑÑÑ»wï^¿~½±±q·nÝ "^V( ¡P¨à÷x·‰›£¨ˆŠâèèØ˜–*%þÙìÏØ}úô©Ýܹs§NÚ¼% 5gNrâÌ™OŸ>%„Èì“sóæMêCyyùŽ;?]oßhÕþ©~/^¼˜5kVDD„™™Ù½{÷-Zd`` yE @TC”̾Ú*÷agtPÅåroݺµcÇŽ'Ož˜˜˜tëÖí§Ÿ~’~:€Ršâî?FUºººxË Ð®ÅUMA ñðOv÷îݦÈ-UЂøûïØ¹Sñ™ÏAh¶çÏŸ?|øÅb9;;WTThiiikk7w¡ %R=¨:þ|JJJVVVII‰±±qPPÐèÑ£HcáÄNž<ù÷ßóÍ7â”yóæ-\¸ðÅ‹M½¢iÓ¦íØ±ãáÇt­"##cÙ²eçÎ{þü¹MllìôéÓÛ¶mKWþAòß'ý›+îÛo¿]ºtéË—/y<ÞÇíííÇG½Õ»‰¶Ðt—³³gÎTpæ¹óæ(8³Š}ªV¯^š““3eÊ”;v,X°ÀÄÄdÈ!Mô„“'O.\¸P2¥mÛ¶žžžjX½RRRzôèQRR2oÞ¼Ÿ~ú)...55Õßßÿßÿmº•2œÊ¿ù½{÷.\8oÞ¼çÏŸß»wbooOwàT©e»^©ÒRuöìÙÉ“'¿ÿþû»wïær_åðé§Ÿ^½zµ¶¶VzþššÉ—ûÒb̘1cÆŒ¡7ϦvæÌ™/¿ürðàÁ»wï¦^ =pàÀøøøŽ;2äòåË ¾+Zqüå›âG#*êÕ«—¡¡!•‚§š€‚dF,¤ÊQ|ñâÅ:::6lGT__ßÀÀ@BÈ´iÓ,,,Nœ8¬§§—@ÍpõêÕ~ýú™ššêéé………eee‰—½víÚ€¬¬¬´´´x<ÞÇ\RRBMš2eÊüùóËËËY,‹Å²±±!„Ì›7O|(¥V—••¢««kii¹`ÁÉ‚ýòË/ºººÞÞÞû÷Œ‰‰‘þ^2W$.^×®]õôô¤3—ó¥êX²d‰¶¶öºuë$ƒ'''§™3g^¹r娱cTJ\\\‡$ìÖ­[ÿþýY£ô/ðàA‹uéÒ%É ###묢¾Å\ã;wú÷ïߪU+.—Ëãñ¢££Ÿ?®È· ÈüÍëËSÒ‡~Ø»woBHXX˜¡„¹sçÊøé•ù7@KðRÊæ©JKÕÉ“'»téÒºuk9ó”••M™2eåÊ•ÕÕÕ„+W®„……ü¿½;kâè>!! à<@9T”;  TÄ*‡x ""àEUðÁå”Ç*—¢xPäµZÔ¢ÚúØzŸG« T-ˆm=Aä×ûǶḵ٠Q߸›Ù™ùÍìn†ÙÍîTTT²³³'Ož|õêUlVSScbb²xñâ~ýú=~ü8!!aÆŒW®\Amذ¡³³óÀ·oßF ŒäðâÂÃÃwîÜ9räÈãÇ/_¾ÜÀÀÀÓÓ!tæÌooo__ßÌÌÌÚÚÚèèèúúz ×GÅôæÍooï+V¬_¿¾  `ݺuxæÄAñëîîÆÚMø5;3gÎ\µjUII‰‹‹‹ÄÆ—X¢@Ëkii ><+++;;KðÛo¿•””à‹=ì8yyùÜÜÜ!C†<{ö¬¸¸˜ÇãIŒ'²ÍÉä™’’2mÚ4ÿo¾ù¦M[[›àsó>fª(ªuuu‰“µµµeffÚÚÚâk"##‡zþüyEEE„££ã¸qãâããOž<‰š6mÚ´iÓ°”¶¶¶æææFFFwîÜ1660`€šššœœœŽŽAq{÷î511A-[¶,'''??÷lÞ¼yüøñÄR‰|%5BH\AMMM;wîtrrBM™2åòåËxæÄAñklllll1b„p¹ZZZ4­ººZ|s¾%±Dá– LJJJMMe³Ù¡ììl6›=oÞ<‘ùSê¸æææ»wïîß¿ßÃÃK<}út2Qà„ÛœdžšššZZZ!¼CétºÈRÈw€ÏÁ'öœ*%%¥ &à‹<¯´´4""ûVCÑh4ww÷¯¿þO˜˜˜ŸŸ_UU…OKÜ¿ßØØ˜LqýúõÃFT]]ݧOŸ"„ZZZnÞ¼¹uëVü£‘#GR½Ã]EEÅÑÑ_422Â2—%díJ¦D–G-]ºtóæÍyyyË–-ãñx¹¹¹¾¾¾ÊÊÊ"‹ ÔqJJJcƌٴiSSS“½½½‘‘F£·ˆ È6OÙv€OÝû{Nå{ªÔÔÔTUU%ñØl6ÿwa}}}{{û¶mÛùÄÅÅÕÖÖb ‚‚‚RRR–,YR\\|ëÖ­’’„vù‰ û©åää°K¡ ]]]‡ÿSE‰”••ùca0Xæƒâ§ªªÊf³E¶[uuuww76ïBŒL‰-v½B(==½ªªjþüù—/_ùˆs™(//øðaLLŒ»»;Œ¨ôîLöImmíGõ$Ͼ?¨b0<8|øpmm­šššƒƒÃ7ß|#üXó‚Á`½ïRV®\ùÃ?X[[§§§¿ï²€O>¨200ÀWŽ1¢²²Rê<ûþ JQQñ3ÏnqqñÇ®л`ƒ*á 8ÒåÙ÷U°A•ȧ,I}; ªðÙéETøtÁ  @`P ïcP%Í»ÿ0¥¥¥nnnGAAAOO/44ôùóçü víÚE£Ñøo¡/++8p ©©é‹/°5ÁÁÁ4Mê:Põøñã÷‘­”3UÛ·o?~|||¼ººúíÛ·¿þúëüüü’’’Q£F‰ÜäÚµkS§NÕ××/**0`@ê % sóÃyyä“ÏYš™ª«W¯®^½zΜ9W¯^]¶lÙìÙ³7mÚtãÆ¶¶6///‘oIûñÇÇŒsñâEQàc)»uë=%–f¦jÛ¶mL&3##CNîí˜LOO/:::,,ìܹs¯U.))qww?~üéÓ§UTT¤(@Vb¢£I¦Œ‹'Ÿ-噪îîî’’{{{á7½`o.))á_ùý÷ß»ºº~ñÅgΜ>To½¢|8ÿÊmÛ¶ihhlÚ´‰ÅbEEE l2hРaÆQ­@O`c&mmm᪪ª¤Ë“òLFsttüá‡êêê>:uêBÈÑÑQ }VV–ŸŸßš5kÒÒÒ6 úïÿKµ=ÍE=zôH`ý£G¤ž©’æò_DDDkkkPPÿÓ=zoll1@ïÄåZP-‘—ÿdU2ƒ*€A€ À  @`P 0¨TÈ ªdU2ïþë³~ù¥¬ìÖ­]  ·£úŽ?q`PÕg•ݺµtÉâ]  ·Û³7G&ƒ*¸ü 0¨TÈ ª>/^¾455»Uv+5%uÒ¤I»:oáC;~|êÔiffæ¶l%n˜½güxkìÿ×®_ÏÉÉ!_¨¸ôxeŽ9jbbŠýãr-=®^»ôù¸ÚÛyòòÌ^R™¾äcEu¨§§§§§'ÕmMÍL/]*E>tX&ƒ*kk}2)_Õ×Ó 6›Ô_Y}uê«q!¸ü÷™hjzsãú GÇ·3Ø••• .²´´rttÜ»÷ø«WZ°ÀÏÒÒj„ +ƒÿõðá#ü#l¼¢¢ÜÏ?ÀÒÒÊÕÅ­øB1BèÈ‘£..®\®•@À“š'ü¹U>x°reˆí[KK«~þeee"+³pá"„ÐܹÞ&&¦IÉÉX%Ü,,¸“bbb^Õ× G—”œ¼gÏÞ×MMØÜûäÉÎÄE‹K/ÜJ8fblÒÜÜÜÐÐPZzÉÄÄôÞ½rþK—z{{‹k2üïöíù>ó¹\«)S¾>>íØš«W255;rô˜Ä&¥zôõ‡é‚ÖwJ<ÿt(ÿå-’g „_ççLLKKKww·Ä|þw綃ƒãªððÒÒKø> ’ðúÅ-,¸®.n™xJ‚FãGf秺w‰;X(ŽÄe"ÛÃIú>’ ˜©ú,\¹ò£¼¼¼µ ¶ØÒÒé5×{éÒ¥ÅÅÅ;wîÔÖÖÂæT~úéç   ;[»;w¶´´¤§ïöóóËÏ?®¡¡o»nÝzŸ¯–.=zôXdT”ÏÿæU”WDDDðxm))©k"×ä}“‡%~ðàþ‚þ†††ñ[¶()±Nœ(Xº4ðСCcÆ TÌÔØÄÖÎ622jwú.=]=ee„г¿ž9r欙ª*ìš§Osrrþvð@®@t˾úª«³óôéï Nœ@Ñtâ¢E¦n%OŸþÁ`0TTT&NübÈ!ùùù7nÀ>zòäÉõë×7nØ ®ý%ÒÜܲnͺÀÀ¥ÚÚZ¥¥—RRRh4´`Á|ˆÛ3ä_!tº|||ü@ÎÀºÚºŸ~þ©½½]dh¯Óvü;!a‹ŽŽnqqqL̬8‚М&; 7Zï‰ËÌÔLdŸ666%''GFE1‚×Ú&®ƒø566¥¤¤D­Y££­]t¾(>n ~tðÛ»7'===&:z欙ÂL‹ÅJNNœ?Áî]»CCCjkëÖ¯_go?qž÷\2MJé$èëÖâˆ< %žˆ;”äC çLŒŸŸß›7omm'¬ _5|ØpqY³·!&º°°0,,LUUuê—_ºMw716NÉ¿GÝ»W` ¯½^]]½ºªúÁo•X2‰F)LŠ{—؃…ÊéHl&²=$~É ª> KJ&ØN`2ÿ¾öÑÜܼfÍšñãÇ#„&L°¹yóæùó°x÷®]::Ú;Ò¶3 „‰‰±‹‹ëþ}û×G¯Ç·]»n•¥%BÈÄÔdâDûâ‹¿;} »°Òü¦9vÓ¦?ÿüsèС¡íÛwp8œìì,¬èqãÆùøødgg¥¥¥ TŒÉdäpBƒÔÕ54ÿ>Ø}ag÷…ö3sóÑ£ gÍšUYYi``ÀšššŠŠŠ†oH\´Èô­„jmmmnnnkk={æÜ•ÿ\™4ÉûÔÓÓsß¾}áÊÊÊ¡ù'”••¦¹L×þiii [6y²– ¶¶6;{÷¼yòŒwŽP‚ Z[[ûía\\ܤIXb‡·wË „ÖÞÞ¾víš‘#G!„\\\îÞ½‡'.4‹%Üh½'.&“)²OÛÛy1ÑÑfææâºFX{;oSl¬ÁÈ‘!¯9^'ÿï$~t`º»»“’“ Nlß¾]\#ïù£G†††¤¦n·¶Ÿ{à€œ}ó¦M$›”ÒHÜצ ÄyJ<ÿw(É3†Ι**l??3ss%%¥òò{ûöí_àë—"_Ù‹Åò˜áá1ÃãÅ‹gΞ=Sxöè±cZZZîîîn®®šÃ4ñ”ïìQ©©gßþ} ! ‹·©”Øh”¤¸w‰>X(žŽÄq²=$~É\þëûÚÛÛÿsåŠ#߯W”””Ƈ/êè?{þ!Äãñ~½{×ÙÙ™ñÏ W]]ÝjœÕÍ_nòo‹íÁ!6›Íáp¬­­ñ[UFŒÐE=ñ+÷Æõ_~9?xh4š½½CÙ­ÿŠ«˜pÍ33³fÏžmc3˵š;×!ôûï¿“ ™¸h2­„š;×ÛÚÚÆÞ~RRr²­]ìÆØúÙ³g·óxgΞÁ6üöÔi7W7‹%u ò †=ßIÇyÊ䆆†‡ÿü%J&(EEE=½™GŽ­¬¬ä¿ !š¼<“ÿ¤<ÁÆ/ŽRh½-.aŠŠŠ¦ff „±ÙlìüŽÑÔ†˜Î®î¨¨5…ßfgeñOðW†Ìîçëë;ÁvBPÐÊŸ®þ´uë–~ýûãù7)ùCÐ×ü•]@™óq‡J}ÆHÜ9!dd46<"ÜÉÉÑÆÆzÑ¢E™õõõy‡óB¯ùä©®®àìäÉ“S¦8ŸCpÄÉðpâKA:0SÕ÷]¿v½µµÍÞþí‹¥H£ÑðE:ÞÕÑjl|ÝÕÕÕÀ@þÍöPQ^ο-ÿ§t:ÿ—F§#„º::±ÜÚ;:rs8ðö6Žîî.üÁ°eë֢ƒ‚LÌL••”_76,ðóok“|GbÑÂDV&11aè¡ ¦ü0Mþýàëà8Ù)ÿxׯâââúúº9^sê#1e6›æ _ÿ¡††FJAíÞ½;#=#33«¾¾ŽÃèëë»páB&š²Š2ÿ=%l5U¼8J¡õ¶¸„)++ñïêdüt‹&Gëâ»ñ¥ù͛˗/s-¹Æ&ï\¯á¯ ™ÝF£¹»¹]ùñʨQ£±ùŒÄ&%þÝâû÷^»€<2çâ•úŒ!‘¸s¦0c##--­»¿þŠúõîݾo¯´Þ¾ý?‘›4½nlzÝÔÜÒÂß›ïîQMŽºð¶d<ª{ÁÁB~Ÿ!ÈD†‡ƒ_ d Õ†ÐKþ50¨êûJJJ¹\.™›¨ª²åääêëjùWÖÖש©©IQ.›­B§Ó}çÏÇo:¡Z±3…güçûÎÇ+++Å¥¤Z4ÉÊb¿þ6×ËkѢŷïÜÉÏ?ajjJ|}Ab o^¿noo——ÿûäòª¾!¤¦¦J)( ¸ø8„УGOþßÿ¥¥ý[]}»»›phM¯_óx<ü/¶Ï_ðG>´Þ :]àfb^[b«ÜœÍVIII ŽŒŠJNJ§ø+Cf÷{ù²611ÙÐа¢¢"ïpÞ†Rïóâ÷5Ô{êJ­ÝóóÌ[O:ˆFCéëéÈÍ—ì÷ß/,,<{ö\MMÍØ±c–Ns™ŠÿñÆß¶ªª* ãåË™Pj´îü”Èä°•a‡Jñ¥ ÑPa…ßFH‹%\þë㺻»/]*utt$“˜Éd]¸p¼¿xùòÆõV–VRÍd2---¯ß¸>|øpÝw‘©XWWWGG‡²ÊÛ¾¸¸X|Y ü¿£!.Z8=¥VÂXZZêëëíØ¾ãæÍ›ÄÓTdiïè¸té¾XTtAMMMOÿьĠp#Fè†G„+((üöÛo"Cëèè¸xñ"¾øý÷ßó'.4Fëmq TOõÁƒþü믮®.lñuSÓŠgj.—›‘ž~õ?W###±*#1¢îîîèèõL¦üž=Ù¾¾¾Ûw¤U>x€(îó$÷5É ã¨vqk ¶=9ÿ¼Ö#£ãݯ]»VSScbbŒRVV6·0Çÿa ^75>tØÛÛ{útÂÂ3S§M=uêÛ#GŽøÌ÷ÁGTm+/ÏäZXœ=w®µµU tJÖó_ OGâȶCÉïä$ý3¢3U}Üí;w^¼|)ò1"­ Z¾|ù¿V†xûÌmmiÍÌÌd2™ ¤+}õêp?¿€ÅK–Ìõò4hÐë×Mw~½ÓÝÕò/‰“““o=¾àDã¤IC54Š/\ÈÏÏ—XOO¯µµõØñãcÇŒU`ÊŒIP´púæÖVJ­„™ã57aëVì÷;ÉÈÂb±vìHkllÔÖÖ.-½TXX¸zu„À­Äˆ°=ÿøã6:鬫;¢³£ãÂùâ¶¶6kkk‘íÌb±v~ýuÓ›&]Ýâ‹‹ŠŠŠšp#÷ª¸„«'²;¦8OÎÙ›“‘‘áççW[[›˜”Äô;5aææY™Ë–¯X±-%åÞ½{•!Þý:ôóÏ×öîÝ£ªªrãÆÍȨ¨£G*((ßçI’Ø×+Ü“. nmáô=9ÿP:ctwwcßÐ÷ÜGýøŸ+ú÷Wçp°[à¯^ý)((hË–x‰å††„ O’ľ–Xážtqk §ïáù‡|ëuvv†‡Gà‹ØíῘ¸k÷סÎÎN|F‡×ÒâìÙïÏž=Ëãñ8Ž»»ûŠ+øoÁ`anqéÒ%ⓆðéÑÐÐðС»veÄÇÅ·´¶:ÔÃÃûˆ|£Éjç—ˆÒ鈀lâ/òøGTÂ÷TÑbcc‡XÜÿ£±™×‰Ú8Cô¯@Á'gÏÞœ¥KOŸîáææ¸ôcWGP¯ª˜t•9uútLtÌ©SßJ=ü‡–š’Zx¦°´´”`«ÞZØ>2}-µ^uÐWÛ¶7Ç…}cJL&0¢*üîtá-„’cÔPö_7ra¦ª;}úÔÇ®‚h½ªbT+óèÑã'5Õ»wí¶·wèµÃL_ í“Þú†Ï3꣯¶í§—ðˆJ8 ª ,!!¡ì—_ŒML¢£×}ìºÈX ¤&rD÷T {ödì*H/<"<<"\ܧŸth@q_H7¢¾§ © Áˆ û¯O ƒ*ÈŒ¨žÿþkïïç¼3:H<.|Š,ÌÍ÷ìÍùصz; Qoé–8¢ÂcÌ`* „:::=|ñ 赸\ .×Br:¼‹Ìˆj¨Æ°ÞóïvvvÒ¢6lí7Êåijïè&È „:x- & »ö7HׄÅbiªtÕWœ£+ÍTm}ت¨(¾Ëàsƒ¥XªS‘ÅÈ`*vuuuV—º:Ž—C!ƒõúꆙ*" &kŽ1C^!Ä`*"ÔÍj¸ÓMg!ì×7Ÿ´Êñ^±_^–oyJël‘ÀgŒÁTDÑ:[ä[ž²Ÿ—Ò;ÞL²ÐB1hrr49úõÇ mÍVÃ^ÑšžÃU@‰äTÔMŒõ•”” NÿÝápƒÇáIEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/tutorial1-2-tableview.png000066400000000000000000002540451416254111300255160ustar00rootroot00000000000000‰PNG  IHDR•Ê­zû pHYsaa‰f΀ IDATxœì}w`\ŵ÷™»UeÕ%K²%Ëror‘lcÓ   ¦cZh/„GBz¾ð!¡„ ó( ð€BÀ‚`llÉ6Æ€mT\%K²Uvµ+íîÝ;ß3çÌÜU±$Ë„„ûCX»·Ìœ9sæœßœ™{Å÷Ô&–eY Ëzù÷\fààÀ8pà o0n©ç~õÃ0 Ãp¹\lWý¶D"ñâ››SÒ³ Ë*Ü.׿ZJ8pàÀƒÄâfËî-‘pðüSºÝnV»ýÃ?¾µ%­`R^~Q[—‰[fb ë_-çç€Ñ/Àðˆ-Ë—ôðkŸŸ™Vü…ôþ0À5Ø·²NÒ&ƒ úR¥”‹I}‘í c­¢ƒ”MKcàÒ´¹ÞJÒ‡¸Œi:·„ÞÝŠ"ÛÆŽ¬šH!ôacd vQlŒ†m»,I\j’ɹvÊ/ªhÖC.Eé ‹%_ F¡mËÖs)‰}Œ3Ò“t)ŒkÕ“¶µFSðÀHv€ZŸm€]5IHCvཨÀCF™ é?¨9è^—ávAŠ×ÈMõ´¶î ·Ö-=ež;‹ùR³Y£¶5uwÇ.5hpÝ•‘s‘AG™,£À„®\„VÍS(6£y"^™v#0àœ1¦‡"Žá/;ÕUYj„ãó˜ ÝV(Ê;Ã2ìqà¯b4ì…Ðù¸Æ«4v"%m‘lHÒ-üÚ<5ÓcæqÀŠVHݨš’úO%-Š—cÀ-éç¤Ããøêæ30¨HñÈKrÅô¦G$p\ö‰ªA jLùsnÉŽ”°@NËF­d0VQ ÜæÕ˜êÔczàQv)ZI¶h+ãȼ´îÂP –% íŽJµ”™’<ÒTˆµi!ÒæÆÑìÔŽ6N‘H ýÅ»~.£Šô’õ8˜t²â>(ç6é^,nåH'CÒ‰”¸ÇÒ­ÇÆ ˆ$q)h¡Üâjt1M4w<*­H‡RPŸtŠ#ËN‘Î8p°5ˆI§ ­±ŒƒE¡˜«± ÈÓD 2¢Ü\k“¢0(–°Hnã(z‰Lë5œ¤aÚ—jçÊY¡xšUعº¡?ìHâ‚4Ã1#)³æMˆQÉK•o•“ ИyRoƒ”ƒké5he}ºUTÞ¯M&{)ÆA?CB|h ÅËs‹áýÑhÔU<~VÆØE=‰ÎîÄ¿V¾30eýrhËÑ+ŒRÙ£í¼ÂøE í6G¶qø©] |Hó1¯‹Õì6CQp›j”ç¹Æå¹Fe)Ì`¬+ÊYBi…³K"’? C85 cj˜ÚÄW1V+‹©MÑ“QùšÃ£D’Ú"6Ó4cš \§ *"0-à1[³5ÙeYªÚ¿\«SôR+F õÆ•œªêzr¶ô‘Šcª j#i¨£´Øf·%TÔ,cL&Ȉª¶h¹6Í T2EPZ¦šÃ4K½,© úeݪõ N 43"þ;©(´1¡Ñ":ÇA‰Êt¾"N3B1©8ºUAÒF’2ø*óT…2ÔRR•ÚdZ7‹n’AZ#æÈ¹êf£;ÁQc„8£ÎPç`Œ†‹âiú¿âVä JE$¿6x˜Ð œÃhiŒJJ‚Då¬Ir€>œ†VWŸ³àŒ+‹e˜ V뎈2pZ²\M8 ?FE¹n6âê#Ȩô®SÙ2r0*F£âV²¬D"n™q‹[¢DÆŒÁÞ?r…Œ†!Œ™à xjƨMëÞvÇ»Z±žàý@zWašœr!LåÒAÍ»M–qqš»p€âeÌrIJÌ×´1Ôq´üŒ–›f|,ÝÏ F¬`7ÛÉ9¤ú ?à*Í6г Ôc|²?ìÑ…’“J®$Á†óWЦ±x… ¨*%NÍW«HÄ/œY¸nʹ2K4’¢€¶ȱ:PsNÍ}©ëõÙ-€ (û”D@ú6ltͲŖˆƒ½bžæê€Zε¼ׂ™¼“J€œXe2ålœ‚:6iVèˆ1h€8Çl­™‡pŽ8€’`¡êlj¾²(=‰%[Î(ÿ*I‚ÊÊp2m4ˆ“^Èé3B9WlKÝÚïϱ÷ç­ˆ4iñ]Š/SúÄGy¢s*S(›ÌhÁJ'gd: «QÜ&ã0(í0¨¤-ã89àrJ3´ñ<=¿‚w MR—© Ô¶ iH³%.ÏmMæ‡$ÿçêÂdý‹s–Œ8Ä×e٘Ǵi=‚²1­‡E«(-Åäà”I>$Lºé³’\2 {‘âÌÂ!(gèõ¤v4w-G4%}¶¤1*J¦kŒŠ8]/F…9J™îÒú²ÞÃdTœ<ê¹£âý2*- n%ftr¡|QffºÏír™‰DG¨§¾)´½9ârû˜qè-Ú#RÈHáp„é1yšÏïjq Ý%¬!“ªÿðd L(Líœ?slÉ„\yõÉcƦÀoïÛѦã àç”g¦¹àΗ¾<'¯¢,w½Òpý©¥©¾~¥oîˆýæ/;¨ñ¢c‹¨¨ƒ¡ø0.d°§¬&W8Ÿ<&}ny`tŽ/3Íã2 I|Ú^ùÁÁΈ ¸¼C¼Jz:©>׉3s¦—¦g¤¸#ÑÄŽÆÈÊ#¦W)œhóŽH‚|¨t;Œ@$Ê÷%ø°i%`L¶QYêÚyÐj8hi¼J/PúzL^3Œ’L9WDàÐÐ@äÉZâ])gjÞÉÉåb0 @–‰ Þeèê¤ â’­µI5¹ƒÅZ1!@_“¨ÆXl5^‡Á0‹æ¨¸üF]ÆP_äë‘ÔáöÑcÄ 5v%9ª}³”‚Ë +o£ ”f1Ÿ„ 3ÆQë½ÈÍ÷Ro ßVL ´Ø€É@ÏLõ2píhô+ CõJU»¹}™ÄV®Ò›­ÈŸ0¿Ó›B‰ÉAµ­N©ø @D 4"دD³åÅ2r ˆ  ÄD€i«¾¸å •U£aÐ’6^ŒH ‡šœÉÈÔY†Íx“è”R©N§S§«U*Õoÿ †2“,X3×dVL‹«d°6H¹29š@dçA$Ód—³®ä$¸Ú¼íùž’¡_F6îGÔ•¶s £â¶&©¥‰¾U?«~:£ê?G¥3*èVÔ ê‰„™é‰8·ˆ¹<ážDs‡iñ¸Á˜×íQ^0½,ööææ éq¹ÜGº‘Âa câæ)CôOB·³Aà¹_.ÛÛPû#™¯¾ß±nÕÊ.ÞÔf èÇË R£ÚÕÚÝÖ5\fó¯Æ<Í‘sÆ`á¤ÌYe¼ ¯ÇÅ Æ2ÓÜU2¿yZi ÅmZ¸W€1¯Ç¸vIÉ1S³³Ó<.ƒRÜ•ã3®?µ43ÍCi)L{à₊ Œ²áâ1d ³\ÇŒwÍ(r-(w7ù–½‰±9FVŠÁ…S €Qšœ7N›¸Z+Áð‡þ‹Ü<ÍÆ@zVÀð­ñ"e\º":k‰qÏ@s ý 嵄c×"¡üIF«yqrÖÀ¤¿¶eN”Hz›¨ÚGû´MªÄ’¡Œfã”ïTù1I&ÄaFR†+=SM #‰Gÿb†€È`qYšèzéÞ §'÷@ \r°8É(÷1ÍÓ â¦´(s]²xƸpãœI˳ .ë#˜3BPg€¸]ÿ~~ú¸Ë b±r’ëâ‚s,ŠEgØ78 [&ù9<5Šóy¸¼ÄÁBKÌ»žapäËD™”çá¢O91šÚŒn k˜ ŠÑ'P6Ï’A {WÔEçNd‹š©?æ¤ЧA²qÊÖ’È«ìml¯ (›`DZÈfqÖC\ŒÓ„JÝÈRÁ"é8f‡Å¶B»H$;§ïäĤVQ1œ $¢*ɯ¦¤c‡QA/FÅy:åuô<˜Æ¨xoFÅpÃ1*Άʨp„:˜A0*ËJdy{N­*&\­±–ÎX[(ÞÑe¶…â-±ÖÎXÔtŸZ5&Ó³¬~·H!#…Æ2S®Å‹§žŠ&ÿÀßžmëß}ùws&uýüʺÂÌŽ÷–ì úŠRƒ™9}^0?vj¶a°œtÏ»Ÿ´SRlñŒœ1¹~Xµµ}ïÁžƒ]ñmûÂì 5uDw·ö|°3´©!´uw׬²€øÉ¿7njmjmÝÓÕ6pæØÀ¨,¼·­£;ÖGÃyÁ ý™"àe={þZÝúúÆÖmû Óü^Ãç1Ì„U·¿[N0ú 3_2;ozi:¬ÙÖþûÕMsRqšÏcd¥y¶ì ÉÈ ÔTNJmÇrà ÕÃfqUïJìïH4w´b£©“Ǭ4ר׮¼&¶Èk3\ÁÕpŒƒ¶O\:GéÒ¤02Òú8rÒ€<]Í—¥›¥pË€áR×9¨–i< =nnœ@ê©ÕNQñ@Î ôE:`´$£…5q'±uÚ¨rŠ™Ê™. &6IfÒ<]!äì#Š£8P n5å+º¦§.Ä’ÁHj°ˆ¸¨%$ÁŸ6À°È°ódê…¢¾6Q·Ë®}!ÂÄ4ª¤}$û}ƒ‰‹v½þ%¹ðdýÙd#}1Ê!QcìÖº5¢˜ÒzÍܹØ8Åh9TU.MD}æ2 ¢©744Q?²û1„¿dc‘ÐSžLËÒES‘â°Rc R©°'0+ÜhÃp¹ûQM1èK’…£4B·¸`­•ŠõcCìÞDº ÀÁÈiŒ“+…½vÕ¥ÔkÚg† …Q†^Y"“ %Óé+A¥\£–ä&*™É×Ô‚\G'©Ø&¢±LÎÒ…rts²3*E÷Q¡Ï âz(pn%z§V÷$ŒÎp¼;f1†ÁÄcOpËâ.—1~TêÇ;nëUìˆ2RÐ…ù´¶þO¿ÿ]AQi #KÓÒ´ó/Ï?”;ª$™=€0nƒü®®ÆÍCΪíÙ¶þÙÛ—@óÎ]ËîZ¶pÓµ§ú¾zçÔ}{»û[ŒÆ­÷vUŒ ø<ÆÔ1éî €ÁØÌ±’-mÙ€ÅÓshI®nDÜëuØl ¥ÃбÿúrInÀ›ê38‡`·Y×ÁÅ5Ò|®“*r§–¤3€mû¯nh ÷ôM3‹s|‹gäŒ+HIõ¹ºcVmSä­ÅÄÙü ï)sòÊ RR}®D‚wDâûÛ£¯nh uc*sËØ‹ïíÇXWO÷êÛΜW¹/åP¤ ‹TcsÇgm¼±é iZk¶µ35;+Í=­$-Åëꎙ4n(¥ÄqéHxiÛåP”ÉZCVW”Ÿ4Õï×™“ ]›÷%&¸3R Ø‘žàÚ¢èÄ x1¸9®,pPÏíÏ“8r¥67¦æˆ@›·J±AzPK°6%DR)”nò."-A„€#_ÑÂê °É# ƒœÊ0©|†¶oL.¬pÙnb[Ò¡‹sÊ5!»"wˆþK$œzJ®2XvŠ&4Wî/‰`·¨& QRùèì- žèI]LÝ(ÃÞopÎE ±Ô$›ã> bR6ÆU“‘LÚII&ÁA‘Hê9^$S¨CdÏ{S.Þ«pEC„mâØ «Ã(¬Ò´ét6 2¨*°¸²BÆ êpÙ¯x6ß–íµ±(•Àžzµìáab TÓËxhe˜%34*µÌ'5…+ÚÉ\Jó7r( €Ì"GCÀé& _"•IòÂH~Ždë)3šç¨~eJÃB)àŒó]¢‘vc‘Š–Ä\%¨TÊ ‡9ö2Xl¼JPqÌôSŸhœÑáj×#h *ÝQö³ä‡³-ÀP—*år• ²›7u.à½R>T³ôkvg=L3>µÐíñúƒ¡X<ÁÝ®>îŒ'x$šÈ ø¦Œòlk‹{<¾Á²}Ëûyö¾¯^òí‰3æ ¦‘‚.Ìê7^xÿïüdãêëúHAñØ–} ÿ⺮΃)~ßè«oŒ0CÞ]_2e¾ø°§ ö@dêvïÅ+kö¥ pצú ø ÒN0¡(5Íç€íû‘èÐ’{%yþ±ù)ë ›f‚g§yª&d^÷å;¹w/9¾xNy†ßcø<Ƭ²À׿4Æeôa“G§]jiÅØ@šßŽ&R}®Ùã7œ^Z˜åƒ±«N3£4=Ýïêê6{â‰ü ï̱@Š}×CÞ¼;f¡¿äà÷È+ÛºâÝààgÀ²RÝé~´wÅÍ„ôëÍQQ{q®Oz ±î'Sàj?‡‚©ð35äÐG¾_ŸXWgrÆÜ.H˜ü@—•›†SJ¥ãÂ@¦¦r´á‡’ñ*¥Á”ëÖVmüÆ•|ÂE¡Êh:ʼnZ`Ä[-Hj9Š…Ê«— yAJTp\Ã8Ê00s$’ºç‘M#º£36òþ4Õ×BP“9¤@ºjM}¤&¥O žÀ<—4PêsàÜÂ(ˆezèâz S‰0Špι…­á©‚‹õ`˜ÒãRfÙCtˆ®Jj½ýDŸ?}A”«‚hLëoZ^•©M!3^mÉžâ`q´1$Ð[ªyP«ð;&–8Z¢Ð–\”“¦ÓwÍwÆz8J-ÄgŠU÷bÔZ2-Q‘fx\‘298u?h°Jqb)ÛeT“ÚŒ#c:Ú2OvÐÏ0”×Hû—ö¤RBX³¶Œ@wH‘,°œ+Ú†‡éNKàš‹@ŽD’J7 ”Ìpš˜9’~FJ&𬨍JP>o+ݻЈJpqŨ”zT‚J1*ŽK~\]ÇìK~BÛ’¹X.¿ØVž¢î¤¼ûàÀy<Ú]V”[qÓê<Ø´âÅ:4º]Lüt´î]ñâÁ¶Æ¸iEãÖØ¢Œx´;y†d/DÜØÞ²ûOËÿ'ÜÑüÒc·t´î*d¤`æ„Ó/,)-7xü÷÷Ý\ÿñúgî»Ù fiÙøN¿` ™TUn_–oB/™ãÉðG¸kGc$MÀ”Ñi>Úþ*Úq5x|¼·ëžWwþìùÚ;^n¸åÿjÿñQ;d§yf”’®lÇïx¹áWªßÕÒ Å9¾¤}]`0öµ£F¹ Œ˜w½ÒpûKõ÷ýugÜä>ñ•ª|ÈI÷d¦ºàÏë[nÿcý/^ªÿù uÏþ³©«÷K(¤‹`4ãÀ³ÓlG&¦&ÕU”ãËHucðµ…£®:yÌó „啦0ÆB=¦Øƒ¶xFÎ׎:nzöè\ßÖÝ]ÝfeŒ3°äP㙾kO)Ió¹ÌfUc{$!\7°Ä>aöéXL,ÛØW·¥Ë“CËâbÁŠsÎ,¼4nñºƒÖ˜,×ÄWA€™VmÏ.u-ç>f¼«;ÎrèˆBF*º*&C Nà|]F_‹Ñ{ Á°pHZèŸ-¾‚£×)C=ˆPÇ1¶Q(S§¤ ¥('Ã"³€Ó´Ÿƒ\š’¢jÜILâ¥þ…Ø€*y„Ë7 q¡)²+K©‚s ðê5š\ꆒdÏÔ4‘I³(ÖÈðg·méÈ9n!Áf R­Dú*S P6–3 =3`«EVK›Ð3îâ¥6Z2€E*’&§‘-ÎpC;àÌ¢Ϲ0]}šN= ª½*ƒÀ dQÄ¿8ÆrÛÅúéó®—(+50ñ@?d4š“adùBx¡yñFKÙ0iùœƒFž€Îs‘9Á¡açBׂ!Éâ…qª\·$³ÜL›¸3Ç$2eNvËÐðÈÞ¸j2?Ùÿ–hšà`’d8‘jÀ¹…\Šsà oT†$†¨”*NE Ò°•OîYäpû"ÇR®EJƒIפ¹’CêGw5¹:`qÀPÔ0ò,\Ê93 ¸¥qe™£ÅÞW:Ç|Åä#-ò^.¹E£Fäâ>½Ì˜ìJ¡¢dò0" ãÒC‹L—ÉÈÂ1jPgqÔ@©&õ?ø‹[¦ÇYÂân[°øÌÑcJÝÌ|í™_í«ßòÚ3¿òÖè1e Åíb ‹ÇãÜ4ã·(¤«}?ÝxÉ ?§»:š(d¤~’„q»XAј³.ûvvvVfF ;+ëìKoÊ/-N ,ŒÀöT…v­¯­^sá¶3á¥M5Á²`Éåð·Ç ¼´3ËŸ1ðíëƒ 'gÀìqѸå÷ðáî™àߘpù £§ŒNë}ÊÓkq·÷³q»Uª?ùM éx$â¤Øb0æ÷‘hâÕ -§Wæg¦ºçMȧBÝæò·÷5µGû•`\~êe'§x]=që©wö54w—s!J…ˆ\o8*Åó{ †ã\¨B= t§"!¬‚cjÊÅ€uDà“fsb¾;;ÅU”ilÙg¾³#îb`YL0ëŒT6:ËH$dâŸq ÝK åÅR<‰©>ì©zÊ[à~ 1-¹iÐrE@ó_à@yl†Çµ¼6Çæâ´Z*"îò‚c°4B“ãÅÉ·È¿a„=‚»kTm¨g¦'-ÔN30¡Ck*Ì& ê [«Ž ©ŽÉ¯rΊ{1€B†²-ŽÙŽ@ù9]¥õp\M@±æ¬0ù¼ÃK)ë%´$XÎÚeúBÍí¦û¤­hÖ¯©ºai ±ÍF Z™¤OE£(›H#†Ðÿ”§BîŒ:RÃUŽ5iÎLìþA•26ëÕ4 Ò`¥­XhròÁW4ɶãíˆå$ô2ñ¥¯èíEµdP÷3n3<êFÐ͘ãî9aÚCbÒðq°ˆá(Ç ¦xe 2“ÇäõD/h`‚ÊZéòsU&ЊVAH%snŒÚÍÐ?ªk4ÿ!¬™¥Uk¨ 4“#“K8ÉÑ××tóÃë[£P q¤.ÓªàJ6ñe¯pÔÒ©ÌKãÍ”á¤2ìQ™êÒvrÂjw·vRý^·‘›_tî5?[ñÜ=ÝáÎ5¯>æuñ¬1¥§\ðŒìè‰Y:¢€k7ýR¿e~c.¸së{U',í¯‘B’0Ðy°©fås™t1`7¼õlNþw2sF Ð"ƒ%U¡]ëÌ]vu*Ôì†ÊR¸åtøáŸöî‡Yg@Ù´y ¿6zàvè9Šç<ã Sݸ±‰öZ £sý‚Qí=ØóÌêÆŽ°9mLúe'÷yqVš»5‘‹€H¯ê]xdsCè¹w›ú,gݎΚº`Iž??Ã;&×?obf Å}âÌœ?ü£ïë`fiúǹ]¬3b>ñöÞý1møÌm–°,‹C{—ÙÕ“H÷»²Ó*Ã(É6NšìÞ%an !ÄÒÍÙ¨`ð´Ua§V¢ eÓ“^ D#eô¡xœc¾]úHŽîx;Ðetú¨,ï1S²™’-N…ºÍת[Ñ'àb½F­ÄÍ>DÅËSíÔÊL°–—/çÜíb£2ŒÒ\c뾄â@L1”‹á/Š;*³$®ÒÖ1¼’“#ÿ©»IÈÏzâ úHZ”ù";úeW‚ õ»’¾06hÞ^£+ b^ŠãmÄÝdE‚…{ZI8 TË“Àʳ@l,“͵q,“’8ê@i2‰féÙ¬~˜‰Á1N3Ê3`(V´H†rÎò®t©ýÏ”¾PT]fèeºù÷íåãzß§iPªGO‹s¤*FÙ,—–h´,X*ù”À‡é™JÅAM˜^ü ¤å ’BA‹âÊÂûcQ@¤GŒxƒ–Ê|Ðx!"ƒŒ›quzU"²ÚaM¢j©6 Òæ,Þêk|4:ûáRHbð7ÓZ‰î h4@_©)í,µha {\Ñ)•¾’çlܸ•“¼ åá§ŠØöž¾è”&Ñ)àè•I›Ì&cœ¹´G®i{è”TœáòøRëöíñ²½ž,‡‰§¬ Æ”Œ)§ËB=æÁ°Ù ÕîmÏ((Iúë.ý’„ )$ 3jb•ßï=avJz6dfç-:íŠÆºÍY%³)Ì¡I•{ëïZBЬT€_žgüiþÚcž ¤C¿ ¢~p0ßÕÚ3÷•cízâÖò·÷9¯`tޝ4ÏÿÉÞðší]ØçÅ¿_ݸhrÖÔ’tƒÁ'{ÃÞÐÒçäÙ¶/ü»×w/žž3nTJºß•H@sglWkwM],‹¯ÝÞQ’çÏ x2RÜñ„µ¿=º¡.¸åä€OžcõĬ‡Vì9iVîô’4ú35o~p #l¨—SQ¤ — ¢qÈN5|nhïæÑ¸d3¹ÇÅÒ<,'•åºÂQ^ÝÆÉXÂ…×rÈtAÞ£%®dMŠY c¥t%®0µ‚]ã8ò mYÙØ#j`cW"‚I÷ƒÎIm¿Ò`¡”9ÚQ!›Åè™eZ&Þû>(‚%}5Uµ•1Õ?Ç’³f=$q,Ðh–n=5€Ö-änET¥­é¸¦‰7ØrZ |4öl2ß-œ å(c˜ÅÉH”ypôڌ…*Uܯ“lû@o fÿïëFuÓkT4EµPKÚr’F˜¤Ù!gÞ›6%‰¯)s òŠIƒÆ§Þ ´oOR:m £ÂÀ©³(uTÒ/-UEä õ ¸ŠH­Ðsc´€¥D$"EZËÛ Õq%•Ú}.Ì”KºªmjDêÃȈqÄ•…3´éL×IË|²ºsÈÊ0¹ê¦¦‚ŠK‰²í©©$cC'¤ö€“3I§ð„š|&ïÂÒ²SÊK§8š´¢SŒt2"tŠàñ¥¸S2w5Ô›|J<á• 9énÚÐOð¶.³¹3ÚÜÖݸëSoZ¦Ç×Çë–F¤#Ò¢¬”QŽõ§»éý[)ìì Ç6wF›Ûƒ†ÝrË-…ó.ß׋š}¸¨Ð®õæ-Û°ž¯†_/…?5ÎË{C`ì²Sú× ]° @²…ìÓ5´¾qN± ð¹Yv øYªÒ¼ÌçÃ`. ƒhœ7´Úº,À™%曈_`-*°’ŸãX/©ä7ð¸_‰¨“ˆ ˜ G.BìCËüË LŠy$ÍÝ´ßÈÇ(ueÓ'òÔ².½$Í µ9E%©©´ÿXV¤Eí³)õƮ݊Uɺ¾”Sñ*R-UÖ¯Þµú*$ùˆN€‰ðiæÐ T•ö±:$øH¿4î‚V©16 Ú‹A$·¿k¤‰x•íQê@a ·ZS…ŒZ§^{oä¶"´#È;Å«Âé;P¯6"á—£"‰q´¢:”žÔòUL†dœ™2*ŠÆ –®d²ƒÓý~!ƨ\ ­²žÜV-&ªqK….EãR4o’YfÊPiŠ¢ŽS•é§÷)Xå'•áÐEÚúmœô Zg`eª$l—üïO*å”lqÅÖ#ŒhO$ØÖ”ºGìò¦ægxÒü†Û0LË ÷X­Á¸ ïnø4Êü9E>ê‘+äóÓ"ŸÛåÙ¿áÉ2UÍŸ®|Ô2˜WO¿×¶>“5qþP³Sú†r`t„<ŸZÜæêõ«Ï`äî‰Ãþ¸µ?¤®Ókãx¥-~¢{uHï¢{3 ¸8!wQ„Q‰+lz\°“-÷€þW:FLCË]ãÊgHç…a‘N)ÉËGåÄw #Š£-"èý¹Æ•¨=Hv„„Ú3M˜@#§ö‰qtqÒ‘kÒ2Ùƒj®nÆÕÔXNsd¦Ò’L¾‘Géζü@I+CËqpRm÷jÊViõbqT¶(j¼–)º¡ ©3euœþ×c&öŸ–ÅE´^è; eG?é,{ û•Aò‘´z‚SG ] 6Âà˜Ó¢n Š¸NqTmL WLÞRZE=ÉtÆ^Œè< k ${°u32*J#âž!\á%m©åC,NˆGà &&Hÿ» ª"µ‰ˆ¿-Íh_Lb9-žt`.à ˆu…;Ôïnògä² ¼>ÿ-äsÕ"H_¹Ì}p?ÙuCê•ß<²Dñ‹ ´éeo(I¬ü*‘íZ°ÅNá pf€|»³E3ôZÉ2V*2§äJbW4N \ë]QìÁeÀC±+Z£¦ B)ýŒðQ8‡§üC"ÂH‡Ï,Æi]³$" Õ–îÈÅÊ{ˆÚ"¥äº9&ÕèálÎ5z Èðd»‰ ‰[ñ1=¢Y”9@éé0mRϸ…iZ #¬?-Óëó°?"…|®Z$0©ŠŸøÌù/KÿÊ3yÓõ¾# Ía~¤M^íëè€OÖÚ–*0&0ËâÊQèñBÎh¾ŒsS¬XR,:I!öèSþÁ³+!òáÄMQ¦æe-øD`ºˆ¦}L±-Š4´¿I1óåD°0v0U•F°ΪITÕ8XZb‰ö‘Ø8îÑ9 ¤),§ôURŸôÊfáFl9tà`©©å´lY3y©Æ€’ùÈ}9šÅÉþ²5 Tu,³ß¨±1¯äcIèÏ?õZíóÞAªþ¾Ð¨±¹HÞ×/PJAÂDÈô¥:½‘´n æ¤7BÆU=ÿÄã`¡¨dšŽü(í“z›)÷ odÒ Ô< = `"ˆ$e€I†̲ٓ¬Hed¹HõÊHÙˆ9#9[¢Ù—LJá‹ Tg23ô¤¡¬B±©;Ë’i¢ªÒÇ¥/ôŠ”fÂ…H}ŒdG¦¦¨ŸqX‰Ú…KÒ2¿ F³,%Ùáë7r®=‰ƒjù,è”c^ŠÛëõ§gģфå–Å Ãåöy|>·Çg f_ùˆ2R!a$©"^¬cÌ”ù0e;ôuÊÁȃ!ëA7‰³š9ØÜ²äCô6vÌ<+Z`ñ$Pî_eCtv%é”4vÑ1¡·4vÅmÁ¤7»’ JZ¤wãÌ‹²YœèǦžÖ•¹ã }‘x­°ØQN{b”sg`Éé-£%YLÄ'nÉPRá*ˆ©ô”zƒ®p€c‘†´m HG 9i`À,N mò&ÿ`‹q‰Qv 6G6›x ¦%8Ö/­Åz’òwßBºLÝÁIPš£Æe€æÖB)±½ƒ)ØÏèÎ^}½rº7íuú»‚ÙOð¾h“NRé:¼h¬}Qq0”Ê£hv꥘ŒIbN€¹KÆ@'Y5“F,’Ø(âòdçB ª‰Û(ãš} æ¡–ÐD5sÅÒuv¥H¨Ê‡Q œfJ’,‰÷–XÄ%£z¥æÌ“lÆP'1ÚáΈ"¡”G PßÏŸæx@q)™ìÓ$.…‡\ªŸe>]­ÒàDjvenáÐSShD¶Mô\Ž8t:E ?ò¹œ>!óxSd:_hCß[ñY2R¶0tvoTD骤AÙ[ÄuOÂõzm V„!ýHn W®Fk¤MBÎÁHAÝÎmÕ(·S™!ÖzÁž¨´Íülƒ[‡¤4E®LÙ0{av»Ã¬ uÝiÈ+íÁŒ¾1ÿ´>KW(·ÉpNÃì¥$·E39×R³hÜÇ…•©ãÀTÏs ž•ß±+´ êZsæŒÚ$}æÖ’¢ê!-oÀmÚHŽ>jÈhƒJ»Åfð¤‚pLÿ+µÃAªßÓ?ÜBün7Ø{òPVæmŸök·HôùŠ¥!1P/F€Þ¤êGgæf8pàÀ_üâÕ µdR‰$ÿ8pàÀ‡Dò:8pàÀƒaÀ!U8pàÀ#‡T9pàÀŒRåÀ8p0 êz=—èÀ8pà pí_'SåÀ8p0èÏÔ8pàÀ8è¶<•ÊTñÁ«çž{¾¢bVÒÏšw×<úèc %®Ñ? DÕ‹ŸÐÝÝM÷5VTÌzáņWæÕW_óÍëo!“±~Æo^ÃqÇ_YYuÚi§ßqçmmmG¨®Ä믿^Q1«¦¦†Ž<Ø&4¯_öÜóÿWQ1ëÓOkáðzüˆvÁ0‰Dz[8ýœsÎ9Ã+vÄ… úÁæææŠŠYË—/ÿ—ˆäÀ_8ØéÓ0ÿöß¾ÿýü‚|ú:eêÔ¦ýMãÇ—¾x‡D[[ÛþðìUW}ý3¨ëpðôÓÏÜ}÷Ý3g~ó†ës²rv|ºãÙgŸ{sś˗?^VVö¯–n TVV@MÍFñjjªý~[[[CCøqãÄÁ55YYY&Œ€œœìϦ÷?ø|¾_ÿúnúzË-ÿ3vléW\!¾¦§þEr9pàÀƒÏ;†Iª.ZHÁU`éÒ¥K—. ‘Uõ£žzê©ó/8?žþT7<|°yó=÷ܳdÉ’;ï¼Ã0 8éä“Î8óŒ‹.¼ø»ßýÞ /ݱã†nWÌœùû?ü¾¿+ï¹ç7yyy>úˆ×ë€ùóç_tÑE>úȽ÷Þ;¼ªàšk¯yù•Wžxâ¿ýío%zàw¿++û›{ïq»ÝPQ1ó´ÓNÿß'þ÷Ç?ù1<üàÓ'O¹óŽ;Ä¢ÕØ±¥çž{þÔ‘µ+îê ]ÒûTaQ!c¬iÿþÁ”sHyâñØOò“ÙsæÐ-K—.}â‰'¾ûÝ›ÒÒÒà¥_JKK=õ´S‡$?TÉmU53gÌØ¸±ÆãñL›6-++«©©©q_cñèâšššôô´É“'õy{<ûŸ[n™8iœwîy/ÿéå7ß\)VÐÏ0ŒÊʹÖ¯¿îºk-˪®®9ÿüóž}ö¹H$’ššº~ý†iS§¦¥¥õôôÔÖÖÝzë­'œ°X”¹xññCm~غ>KY õ‘ØC2éx<þÃþ`Ò¤ÉpÚi§}ôÑÇ>úØ^èqÓK8pàÀÁ ÃÌTÝö‹Û–?±\ü$Å ñx|Ãú §œ²Døt`Œüâ›6¯^üüüóÏ?ÿ¹çž;xÐö0],ÛúÑG'Ÿ|²Ã@~~þ¼ùóªkª ~ôñÇ'Ÿ|’ç0yò”±cÇQQû„e%yÍ`äñûý³fÏÖïúÚ×¾ÅþöÚßD ¯üùÕ¯œþ•”””¡J8zÌèQ£FÕTo€šê3gz<ž±cÇæääTo¬€ê5³çÌq¹\}Þ£’¥ÓÜ҇ꂻÌ_°ùƒ-±Xlûöí¡PèŠ+®ôx<k6À†õæÍŸ/t2~|ùC?ôÜsÏú駜ثBïÂFVB}$.bùÝwßE§†jÒwîܹôuÑÂ…uµŸŽx8pð…Â0ç 3gÌHÚ¨Þ'‚ÁPÜ4Ÿ|ò©§žRëœ[‰Ä¡YÅÀ¸êª+_zé¥ÇìÒK–éÕY–•“«_™›³í“O ê²,+/×v6??ÄEMOKKKKÛÛ¸·÷©ýMû9ç…EE‡,d0ò¤¥¥;ÈÍÍ9ñ¤/½øÂÏ;÷¼·Þz«½½íÜóΪü•••ï¾û.缦¦fÑÑ‹ÄÁ¹sçÔÔÔ,˜?¿q_ã¹ýïL÷z=úWf0Ë4a]0@÷Àüùóâñئ͛·mÛ6iÒäÜÜœ9sçlذ¡°¨¨­­mþüùâ²xà¡zøáGÚÛÛòòr/¹ä’+®¸"IQÃÀÀâRx#(aÒHlnnÖ’I§¥§é[™ÐÙ†T8pðÅÄ‘Mìé.—ë’‹/>7:`r IDATûœ³G¶äì윋/¾è©§ž9åä%t0##`F{ÛAýʃím™™™BÃ0B]]úÙP¨+5%udEeŒÍŸ?ï½÷ÖvvvŠª ï¼óuÔñÕír%å)bÑ(ÒGžóÏ;ïÊ+¿¾åÃ_|ñ¥Y³fMœ8qx­¨ªª|íµ×¶lÙòɶmß¼A¾GjÏÿßÿUWW. wÁÀÝ&NÌÊÎ^ÿþûÛ>Ù¾`Á<X0þŠ+ =Ïœ92iW\\|ëm·@}}ÃËúÓ½÷Þ—Ÿ_pÆ_ª´IX¼C ¯ãI˜„¡šPW(‹Å(­ÕÚÒ ™™#+•üãÈþ™¯×[UUµ~Ãú’’’qv~á—_~¹ßç}äÑGôêfΘ±råJš‹·8°aý†yUóÀçóMŸ6mÝûëèúÖêêj„¨—_vy,»ýö_ê‹;ûöî{ä±Ç&NœxÌÑLj#ù£ šöï·,K| uuíøôÓÔ§ªªj„ñ¿¹ç7ÕÕÕÃNS¾­jùòåœóY³*ÄÁ9sçîÞ½{ÅŠ~¿ÚŒC-ó]0@÷cl^UÕºµë6nÚ8Á|˜?Á¶mÛß~ûí3gøýþ¤êÊËÇÝôÝ›|>_mmíPEíÅ;¤ð}bd%ì-ðLÈ4Í·ß~›¾¾ñÆ™™™ã' “”;pàÀÁ’T ú…êCÆÍ7ß´s箯_uÕk¯½V]]ýÎ;«îÿíoï»ïþÃ/9##cÙ¥—¾÷ÞZýà7®¿¾¡açßpã?ßýçÊ•+¯»öZ¯×{ù—‹³×}ãº5ï®yæ™gâñXssó~ø#G-T ¨sæÎùÖ·n|ýõ×/»üŠW^~eÕªÕ>úØ…]äq¹î¾û.ZdYròI=ôP(Ú¹sçÍ7ßìv«]JÖçÜóί©©ÉÈÈøò)§ CxqãÆåää¬^ý)S&§¦¦ŠƒâóêÕÿ˜UQ1¼ýËwÁÀÝóæÏûpëÖžžž¹s*…ôÐwÜ vfddΘ>ý ‡üæ¤>±lÙ%Ï>ûlGGY¸ð¨|ð¡‡úö·¾ãñxª*«î¼ó®ââbqöØc޽óÎ;xàÁ{ッpTáeW\zäD½âŠ+¦M›öÔÓOÿúž{ÄÛ&Ožòè£egçÐ5S¦L½õ¶[zè¡'–?QRZríµ×Æ¢±Ã—ç”%'ÿòöÛÏüê™´”3Îq ÇZ _@kù6Ùƒ/Mª ©q/8‰kƒƒc-/ µ|›ìÀÁ’TñÁ½Q]$®…SHBÓ˜a°¤ƒ±¸Å «3€ªŠÑ‡)î<˜÷ì¼…õ”›™•Uê_åÎÈj!œs˲\.×PoL$†a0–ÜÃÃÖò/G"aµ´FzzÌqeYÃ.¤­=ÚçñM›>üÒ‰UÃ.¶?ìÙ»·;ÜM_SÒRJÆŒ@Îãs2rÓZÞ[»véÒóú<Õ¸oÏaÈuñy 8$’Þ¨>¨L•ÀÀ‰kÃ`³gØf]ç=QÓ4-Ó´vïÝŸ•¨ÞRçðªC \—š½>%‹eϼªcgÓÁV»*Og^ÿP‹q¹\»7U×½üB°í Ûãå–F›9×3f<ž•?ªüìsKçTr>2¯ès¾Ì±kwÛ¢E³×¬ùð0Ë™>}¼ø ì<†0óí¿W,¯ª««‹Fc‹OGV­Z][[;a„Ã,ùó0r‡a-<ð`mm-}mim€»îº+/ONB"áÐ{ï­ýóϔœóh4šHXtÄå2|>ßð&!à {6oÞö׿eFÀQ]˜_V6ŒB8pp„€¤ªÿT•ž¦ê¤Êe0Ë`.ß×3oîÔ ?9BÞùλïÈÌÌK˜f² ÷¦êš‡~pxÅ®ZµêU«[[Û,‹[VÂ4ÍŒ@ÚâŸyÆW cÈ/¤hm ·¶E¦åvÁP$7fÂãv™¦‹%L31Õÿè‚E;`O- ­L=PQÛ6Å·üÞ!TÁ9gŒ­{èþ¼ÕqÂq)3ÇXñËâÀ8XÀ-žHpËâÜàœC¤ù@÷­ß^·äÜ£®»a¨Òq8ÖòYÂåò0áp7@6<Ønk‹Lœ?¬çŒL¢ªªâðy•eY-­­ÑîžÎ`(‘0O9eIOO­ªª\³æ½-[¶ff|)þÂQ£§®$|6#÷p¬åwVÕÖÕN/9eg0gžyfqQa,€®PG8úó#&m8©¯«cŒ…ÃÃ`>Ÿ˜1yÒD¿sža7ù@]݇¯þ¥ ›;nܼ‚Q¬»Ûr»÷¾öÆ»õõ¡¢Â…]˜UT4Ô9pà``OU *S5¼4µÁ˜Ëep¤¦úÓR=Ó¦Œ«ÞÒ0<ïüæ¦à]ní}üªEÆi óÇ›ôß7Ü`ÚÉ!p{ìüaT·k×Î{ï{ È>þø“gΜâñDkkÛ‡nYµêígŸ}îûßûneeåÊ<ÐÞ3qR©á2ÆfÿŽc1sT~Æ®;]œåñ®ØÖë ‹A@§Õ¶{ÿ;-U‘?¼{í5K_cl÷Æê¼ÕM=÷«áæ]¶lãÀ™Ûe¸ÝV"nÅM`ÜðxÜ)©.—3`†Á ÃSZ`dgd¿ñâîù‹Jç­QIÁE ‘Š8üdLoöÓÕ¥VÓö5¶†¿´´ >¼2c.—+‘HlÚ$`‡É«jkkóó òrs[6ož0~Â’.(++«­«=fJ0ܱcǤI“†]—޹àp¬å„Å‹ó›{Äg±ü l·ío’¥µìo|iáp¸agÃÄ£ñ”Ô”£Ž:jÏž='//oÍš5‰Db¨b©É¡ææÍ~5µ­­rÊÔ KJY$¡$`tw—Æb¥@Âpízá¥wöì6Ç_tÁùiÙÙ‡,–P<º¤¿åÑâÑËõ¯û¾>øb8øÂâˆ?ýG©q¿ÏåvÅEÙi©þáÍz/¿'_qZÒÁå+[¯ú隃mm©–L@g‡7eCXåX¿aÃã?yÉ%—-]zŽØ›‰Äzz¢™™YãÆ[²äÔ×_ý§ÿïÖË/»ø¼óÎ|±Ñh<Òs» XSóiIaNF€¦¹ÜlxÒ›×S!îµBO"ãcE3Ö½U=$Rõ¯¼8}ñ±áæ]í«Ýó¸¡'‹u§–fŒ+w§ÌH¨kÿ®žöVoz&¸ÝÜ0bí)ÙYñ™åõ¯¼t8¤jDVýzs©gW]]‘X, …Å×ú†fÓtüÄÍ›kG y±•`F"‘X²ä¸`°+ ôÑöÃÒ4EE…0pð`[ïkf"===55uÿþæÃ©«7Fpäö‡aXËc=þøòåÐÒÒúÁ–-ï­] ?ýéOrrràŒ3Ïa ˆètw÷ PTš[ZÞ|ó­éÓ§õwa9<çÀ%%%°ÿþh´ïMu`HM~oùÆ–Ï9õTwQ1Äb Òð¢c´]Á`9@y #ÔþÃYçL¸÷žésæ ^¤>y1ªÆ}_Ÿ‹G/wx•‡Äú1fìí¿¯yûï¶ã;v|zÑÒÅC* êƒ-íòk,—œ_~Ô™ß^÷ƒSXÝ~ˆwƒËœsÃ`œCÜ„+yIðعs×£þïw¿û½N8æƒ>)))òx¼¡P¤§'fšf{{çÞ½M dÝ}÷ÏÆŽ-]°`Á KŽÅÍX@mmmFFF^^Þˆl!èêêŽDº»º"ðñ'{óxâì`°»µ¥eTAé0 dŒ¹\ÂÚp¸»»;:ŒÕáÞ0MÊËÇmßÞ7?+/gš¦eY‰Äíd`ŒìÈíCõ-ÓgL?å”S|9D£Ñp8|Ów¾=®¬,##ó¦ï|û0…)**úê™gz½^Ã0Ün·eY¦iÆb1¡áƒÛjëjãq™ÈŒÇã–5œ ˆƒoòþ×߸ª¸xÓóÏGrrŠÊË'ååõfTÀX‚ó}mm=)Çttu U¤$^¥3*px•CÁÐR8CÍÕ㜌]yÙRH${÷µÄâ&çü¥—^²°Ý‘xÝÀ0€õ»`Ê„‚ާüüîGšÛ Ò70 Ø^0‡âú‰Äý¿ýÝ¥—^qüñG¯][ãñ¸7nÜZ^^æñxâq³½=ØØØ4qâèmÛjKKË/¾øšþèÇo¼þš×;¨}O±˜‹›Â~åÄ٥ʹέöU†¯ºrÀr%º2ÌÑsZ÷vv¶›æ× —ËŠG9pæq[ñžÜI3g|ó—¾¼b0c¡]ÛšÅãÒŠËÓŠËSæm{úÑ®NÃð0—8©GÿàPÖòÆ+fΜA_Ÿzú™ Î??##0a„ÚÚÚ)S¦”••¹Ýn¨¨¨ˆÅbÛ¶mÁLU(Ô÷ƒáM›ë|¾´ªª){÷¶®ZUsܱS§XƸeY‘Ha†aÌ™3SBp¨OÆã1˲ °°°°°P?¥ÿή®.˜8qbOOÏîÝ»GdpÄGî!1Hß²háÂqee+V¬hiiu»Ý99Ù7ÝôqŠ> Ù™YYY™k×®«¬œë÷û'6M3‰twwÇbñ`°3ž^TTÔÔÔär¹ŠŠŠêëë§ÆC69`¾1%ó:;¡³3ôɶ÷B¡D^^騱c}^`Ìâ¼¥³³±üO}öÙ®Œ ð¼ýÖø‰‡!Lï|•ΟˆW9pà``ŒØÓý1À].‹™]án‹s—˰†ûˆÙ0ÀÛߨY¬6RæŽ:mÍ.èî_Ù©öÁCáÝ5k22rÎ=÷«[·nOIñ——··?þ¸®°°(7çÌ™œ››‘‘xãu F~ýÉ'Ÿ¼æškSx,ÅÍ„eµ#ç|¹’sÎ9om A¢;3ôWov:´¹ ê …½ui3;‰îHþ´Ñ Á-nYœ¹]¼ÛÌ,™0ý¿ðå‡vlÜù·§»öÕ»RÒÀ23'Ì{Æ•iÅå“/ùþ'OÞ–àsR›«FväŒ!ù–¢¢¢÷×­]ºô¼’’1´§êð‘™•9göìM›6mÚ´iÞ¼y¦iºÝn–ÅÃáȺuëB]]3RRR8À0^\"pÈ&×ïÛ_‰Â”©ÐÕ`Ç¢ÎNèìlÝöÉ?Âa#˜pÒIy?üaa¾í¹ 3¡oó`•‰C“ªÃyB‡[œsnq‰D{¢1ÓLøý^nÁ°ŸÛß° ‚ïx¾…¿\œ|jŒ=ÝÄzx´;‘–é~ø¯»çL)QXµjuIÉ´xœWRS³5ŒdggVTL¨©Ù'æÍ›š•ˆÅâ›6mcÌsà@[YÙ¤¿þíoƒ&Uf<žvõlØÒ`š ÓLÄMËL$ò­ÇÍØÝÙâií‹¿5Û¡žht;a8Ëp¹x"^þµ«üc‚¯ÿðÑŸuw˜¼ô¿J¾råÇß²ïý•ÝmMÓ®º%}̤ыNݹúeWJªxpèÕ) ÉZÌ„i&LˆÇl{çL™ýgqn&¬„ÅMÓ²þ?{ç×ÔõðóÞË$ aO‘½÷ª{¶÷ª{Ôª]ZµîY[7ÚjµV[­uo­?÷DEq£‚² ëå½÷ûãA +$€¸î÷ÃGóî8÷Ü“›¼“sÇ£‹ç•8PFÜ**ä^:(ó.?   $ ù9ùP¤ŒùYTnºÚٗñ©o.4­¹œœ|wwñáÃg;ujîééöàÁ“  ossIxx M3R©ˆ$©sçn¿zUÀåòîÞ½†aD~¾±‹ØH•R¥!µIR¤–"µC…8\å˜;A6 G‘G}嘆QF©|íTiµA˜p2'Ej,Ýý,}€¦žܬVò-m̬í@he#°¶+|õâʼnQS¥~ü[g(†Ú¸e?ZšfhF«Õ5((Y"àó=<<ÒÒÒär9»¦*11ÑÜÜÜÃÃ#11±zñªÂÄóÚÜ'\sGW7x™ôT&sèÒ¥irrÆÙ³±ãÆvçó¹ÕÌBQ  bâ”É­ö®@’ÔˆTéÃFShšNIIaSªãW½J†»—#è–Ÿj1.ûÉÜøÏbÛ2©Ø.uòO´™¤ÚŸÜʨr´\¾r¥a£pW7w>—Ã!pÇ1 4$eï\_«¥´MR4©¥´­¥hy‘*óERê‹j†[p·³³ˆˆ¸tñÇ ÝʪÔ©YÙÙ:v°··¯É‚¹*»üçñó×rBî¿æÚ퇗­lGå4 ððö K¨çò,öºo×®laZ¥’;vçγ§O“ÍšyNþ2°qòÐùUz)¥–X!¼ÙÝj5¥!µ 08Žq9‚À)Šf¦SZ,-€LÔhiÐR ¡€¤0ºd¢O­a ‹h’œ1&ÍYh4$Ÿ/HHxVTTЪUD½zõnÝzܸ±¿H$dÐh¨Ó§¯çæ* ‚xôèFffªHdCSÆÊ×”†¤hš yZ.­¥(µZkwœ]Ј4 '¦¦ËÌýÄ÷U‘B­ÖÐ4Cj)•RC’$—Ë•H„Fµ„­%EÎî ›Üû×åIxRK†Tßò†!„bù‹'š¼W"[g‘“<ó0PÃH•I£E«¥X§!;;G—¨V©Ù¥*2™œ}!‹år¹\.wr*ž¬u~FêÑïü}lüy å&Aú©ø^Q¡þ¸w£æû÷ŸS©T'ô˜tXi†!I+¡ÂÛmõvjµTe‘¡Ê|ë °k€ÒÓÓïB1cAžEr|ßM‡ájœË=e±mäËùÉ/¤n5ý¤ÚŸÜ 1r´|Ö§dT?©HÈçñyÇò ¹re¡R­Pi ê…ºP©Q¨4OR³®Ûùêñ•j«„a˜­­m›¶­9Æçó}}}q×j©”””÷~þyo[[›š¬;4¦Ëw2´æ)•&Y¡| iåüÈœ·åÙ]÷=Ç:óTÍ‚øº¸<ù÷ßzÖÖOwî¼ôßò† J¬;ÿüS‹æÍÍÌ̪­›ýy@äQ!&aÔcjª±û=”™¢ݬŽcƒëBVÕ£@j€‚$ZH 4ð9 AÈ @$©ñA, €"Mp(ЦiŠ ð„„¤W¯^yyyr¹< à ‚`opZ-óêÕËôôD¹<Ç1š¦ŒŸ¡S«I IJÄ‚N-)ŠV©4ÊB™@vIde `DEyéÒ¡gx+ IÅüyT¡PªÕêÜ\9[ÔRF9U Ã0 †1˜P ©¡i'J‹c@à€ã8—CÓ”–Ôðpçp0‚]3Ÿ À”Ñ GGGÝŽ*} ŠŠG7mÚÄÔ©­ªðÙÎí?ÉËÊâ=?OZ:Òæ!Ï9áRðý3ÒÛ ÝÝ]{ôèðý÷... ~ÿÛÛÛ»V<*Ö¯BQ%ÕL ¸ÖÅ„Tj-M38ޱñ8`ãr‚fh€j~?ªA£+0 hÐÒ@Ò Õ‚šp¹uæiö‹§Àìuf’Ò£‰Åùðû¤é&A©Õjg8ìåËt@0hÐgææ"FKÓŒX,øôÓ6[¶¤ß¾&ò'I-©5ö¸H’ÔjHН¥´ZJ¡P©T$¥Ê—bIæP´0'/»ÀÌE¥Ò)‹ŠJ…²š±†Æ¸\Ъ€àó93ÀpŒ XÃcŽÀq_Èáñ¦1‚ƒ\†®éš*}ª-:_ª ° Â¹¹¹ìùC`iiiꂪûiíq늿ñV-ºÉ³â¸ _Šy $ºÚÝi¨º¢ñbèäýj¾„çÒãŠM®×‹b—šõœ*ô°Ù]$Iq88M›0øÙ½ŸjµÊÀî}Ýïv¢Šm4­ì'×Ü>‰Æ¶-ÃäùDJ²Ùµ‹¬G%Ÿ¸@ÕésÍÔä“[%•GG‡¾}úܹ{oæ·_Íž»ÀÙÑžÏå°³à ÃZš¤è\¹òÙ“„ÿükØà-š5±µµ«¹>‰¤oß>¿þºiäÈÇŽ›4ib­xTúTÖå ‹‡Oï m] 'S*5O%XZRjM yÜæŸ~FCQ”@ À0,·z”c7iÙòécF7®‰Jh¡:QCjy÷ŸZóz Ža@M3gS`@KãÕ]m\ R S` -YPÅÎâÖ^V^ Šp‘'“8*eðUø6mZß„Ô\’••aoï R)ƒ‚ûõë)ñµZf×®CEEªQ£ú›™ ÆŒÊçó?df&ÊÏωŒý¨V“$I)ꤤìÌÌ’ÔÚG Ôе©b1f´ÙØØ¼zUé©•R“››KÓt@`@u–¨'þË•jr­FvLê­L¦„ -Í$]#={š©_j Þo”†I9½š¾šk]ÅÝ4­eŒahv¹z™\ý]|>—a8Z­ ^2ÃÐ*•š]%]IëŨT*­VË®î7fy{Š?¹ævXä$þªo°|9ëQ)¾ø^Ó­/QãO®a Œ‰DÒ¬YS¾€áüÏ—/_ óòôÀq ŒaЦæˆù IDAT ŠÔɉOSžÞjóºuéäáá.‹jE+‰DÂáàìSÆkÝ£2ÐåaÝ?Iýíï“ÿSöMÌÝšrÅæ„Ï•ŠÕùY}[º±ex<^¡ŠþëBÎßÿÅž¿ñ¯Ýí ‚ù×b†$wëß«§©Ê¤½LurvrQ€ 0•ZÞý‡•hÀ”½”<Á·ÚË] Õ@ir¬ ÐÒ@jAC¿ PSPDVÇChۮ͡CÿÙÚ:‰DâÞ½»ˆD†={Žž={†$5|>wذh:´ÿDz³ónwëjì‰çR«Rk´ Ž3ff’d8…ršöž/E¦'Ýþ_?0šËņ&5$©!ÙÅãŽñù|>ß('˜a€¡(Ž™8?%!ÿÙ«M=»¹ÿçu¡œT€V­ÔÉ¥®¾õÚ÷UÆ3­FE%5?OÁÔ½¢Užô£…¹¹¹¹DR e¬ùêÜlw(~(ÞË3¿„ÔÓ’/ þŽ»sýg@’|;€š#Ƽ?Qß=>ݦ×S›`'þ0ŒÁ0¬ü$`ù]Œ)'Ñ4­R©IRc û¯F£’gïè¿0Ý'O~Œ¥§³ÈŠømUvüœ‘˜CÍ>¹bäh±´´lÞ¬™Ë/¿D÷ë5`ÈWÓ¾3ñqÃ1œaàyFî™Cƒ"så/¿¸{¸s9ÕÜÂY7Óe ‰xÅÔQ_®[ÁmzòÊÞ?2sÌ[Šë7æWŸæµ eŽÞ’ý}:þä¥ý–—êÐÍ>wörêÐíÄáK6FÿÀÓa :…¦ÿSyS»ÿ0 0 †,ž¤`ï(8VÍI…¶øxt¹0ZçTQ e_S€Añv5Œé?¬»vé²sçÎŒŒKK›£GOGFv=qâü±cG¸\ǹÇâpˆ~ý>Ý»÷@NN®\^”™™2nÜ6#…«ÕdA¡‚Gà……*™LA’Z~~V¡•¦Àž]Mº–î–Ï‘h •Z-Ååâ ©Ñ’\†ˆ…|>Ç1ãïÇ Cc8†óÏn•ºúнÃ}Ÿ|zgê…C™ñ—H…Ü®A3×.CyR;å«ç7ÏpÍÌc(šaj:¿cüh1—š7oÞŒ]6Äáp‚àóù‡ÃáöÅöí;ª·X¤PöÍùKënä½"äC¯€¿zÇß#r­öz'(¤ÓbÅ”Ûdíƒ Îç]Ò³Ÿ—xšÔŒ‹»«ÛЧOhhû¢ü®@­–6iM¡á¡à8®ÕjYÖÊîÄ•Ü óYŠ¡9X¾‚H¹iÑ«kþc´X5øäV†‘£E xxzlˆYðÐá¨ÞÝÿÙw+T©§d®ünè°Áý;ušìáéAԆʠT*«.d FvYA(#»7iÖ±¨@-¿yrß•}¿)î´rX³ë„¥ô²{uë¾ÖöVz‡tîäÒ”ƒsà\òöô4IGUY° @˜Äö_í¢,¤˜" TY)94 44 Zš.ž Äù /"j"+hY&fmZA|óõ×3gÍmÛö³‡Ÿýü󯩩‰4MSÅãGŽì{øðþ«WY*uéÒÉ… ç•Y5lH¥ZQ¤¤¹’ÔREÓTa¡æÊ¹›fÄí§r ¥™3MjÙ•ïÆP”–¦´N˜™™ö4: 0Œà _¥<Û³Æ+ê+‰OX@}ߢôdµBnfi'²¯W÷*õô_E»âª¿‹ÀÄÑÒ 0=bÑðÁ扩KÔY^ÛȲŽömø„+Àûô¥8‰ùÜ=r*ΫŠ(*•)°aîÖãÖíÙáÞ~¾/’ÒLrª*;=E½ü®@ö4)ã·5PZÊðÃgØET wT•.Ýø^èÐ÷¨ÔÓ–hÝ|Í>í„)4ÄÝxé§]s÷aŒçFbühÁ0ŒC¾¾>M³"0 [¹bi÷Ïú¼LÏ<¼ûßÏztnÚ$ÂÃÝSKg˜•áâ¥Kµ(Íø.[céùi3s‰CçÞ¶-»åå'ß½õ"!v£z ëéÖZÈ)õÍ òEf¦››[mi‹T„©¼ñgÿÕ"¿ 6Ÿ²ç.€ÀvÿÙÊ>rqx¥„ê¤K/zJ÷ŠÌD<®iG…„„ 2pÓæ­M›v07·äñ4M‘¤ša´8NüôôÌ‚‚¢K—Nõë×§gÆKV*ÕJ¥(Z«Õ2 MÓ4­Ñª^>Ïrh®‘Ø0Zнr8Ç¡hšÔR<Âä$MÓ Ã`8Îà4‡/Îz|KµùÇz­?•¸™»2 ƒa ÉÏ.z~/ãö9­Fó8‡KiHm‘’aª¹^[‡‘£%)99)9¹Ji}ûDVc‰:Kë±+Žýí,¹wŒE!ŽM¸7êÎfÉ(e®ê?Ð#CîäÏL !Êï Ä0 Çš6Áo5ìnVÏyª âèNÖ£Ò|½”ìÖ—¦Å¿'̺uÂd2ÎÝxÞå ª.Ýk±9“¾[,,,ZµlimmݧOTƒ†Ó^¾8¹çKÏÛÚÚÕÖ:ª2´k÷‰B©ð®ÕÓüìrsÿà-[w5êæfgo)âŠE<‘—­§CKû‚ÆÄE»(¯RKR 3v=8µ/ù¬5õIˈˆê)†‚UDÍy“ÏþÃ*ØIV“™»…ÛøX=}öôY‘BpÌPÑ’¸X$òðôHLþ…ݧO¤³³óœ¹s%;ww?±XÂç iZK’Ú¼¼¼§Oã23Sæü8»wïÏL«T¨ óå^T$'I-Ij5";µWG†qI-EÓÀVK)•je\­(š™|„’¹•­"#›ëj§É“Žs¢¢œŒÇ{׋í]DŽn‡‹£ÌÉPÊ8B Áá.!«²“‹Ò2-|ká ',†GËÐ!ƒSRSªbffVíSÔyfâ6_̘]>+_å(¤ž$_=\?ŒTr ï)Rµ¦œ¼Q5Æì 4ŒR©$I’ *ýê;Uº=€Õ†ü~5÷Ëhí¨¡ÚîQ@3@‡È0ïÙ¹háÒ7áQécäw‹………K=л5ÒÝý þÌ zs wùÓÖ­=Ÿ8þ½íø>Å ›†fA­êÛØXˆ¸bB%häÈ–ÉTæþ›púïÓÇ“cŸ·uh0¯é€à€=¼@Ô’ïkÃU€që×Çl2@3Lñ>p€ Ÿ°‡K2ìÞl“‰Ì¼<½ìíì4¤±Gð¸\‰ÄÜøÝyú4kÖôÀþ}Û·o?|ähn®Œ¦Š¢I-)‘»véŸïìTõÃ4ª7ñW%r~SøÀ9ÿïÓë °ü‡½^>y*°éaÂÜ_•”ßhê/ŠÈÈÏ÷ìÙ[¡ØG-Fªh†31µé0@Ó¯?¹TPpNâKvßnµ?¹UR5/³ò³s+>‰ã½À˜.7ðö^èí 7îÝûç×ÿÎj¯;„‰ƒ[»~tæ9–þ÷ùw.ÖÝ(@…@˜L郪jy÷¯äÁgì—2EÓ 4Íp0œaOþŠÓVgåŽã‰X"1m~*88Dÿ2>þŽñuÁÈ‘#GŽiR‹èÑ£q5zˆD•0 S?¬ÑÕN}-ï&ƒ=…6 i†¢†Æ¦ØÛ%iJC1 EÃhª¢´L^NaaäˆÀÐF5iÝøÑB„¤Z{újŸŽ#÷ÅœêÛ ©ço¨ØcìóVNµ¥«pW`…; ùy…é*•ªŒGUCë~r+£&ϽyíRaA~-*S7T¯Ë4hÜ Ã0WïÄÿ³êä½üûO<îÖxþ—ƒíìlßÐQ jm÷AàßL_TKZÕ&ññwt~•IÕ{ {çn:vRJ“æ‰ûvç?L'.C3¯ÝiF÷/0 PßÊ·¹Ç§}C™´ç¿Bª÷¤È:Fdí0tÓ¶-S¼¹ yJn‘G¿¦ƒ¦Ö–ðÊvÖ")))ºÇüÕ·ûÉ­öh9µǛЧ¨v—1 kÖ0¤YÃ0ñlQg`sæÌq–”­Viiø®Kq 6LýŽß& ò1xTïÀhAÔáhù»Œ@|À,=^.înÍψÝú>íþ« È£ª3>€Ñ‚¨3>ÂÑòvøx0í¬<6pý†TA|` Ñ‚0žp´|„]F >xjùÙ -ãùGËGØeâc@çTq¦Z€04ZÆóŽ–°ËćH©3jÿQY@ !e§ÿnÞ¼õVô@ xßðÖ¿(ëTµjÕ²UA x_ùoï+ý˲N•B¡¨Ce@ >К*@ ˆZ 8ReÔÞ?@ D e|'©B ¨S…@ Q § @ ¢ÐÛý‡U!@O©ÕQ¤ @ ¢6@N@ D-PöðOÃ\¾r%ñYâRñ¡âáéѼY³òéŽNõ yóæu®@ †¸|ù²îµñ?7Á©b=*??_ÓôB|ôB†Y¹j•‡‡{e¼ÜüoÝK­K•¨>FW£VuSƒaMW§1ÄÇFjjÕrÐâý¥PžU½ŠbsÛwYŽ‘”>QÁôHU±j…¨O…@ ï)²,OOÏêÕ}öì™DjûnʨæáÕtªˆÚEªâ}DžŸéåUM¼¼<Ÿ>}fna÷®É)¾®K§ EªµŽœ*x?©ù¯böð®É©6È©B¼eÊ `ÌÒÚ%ÐÒ_´Ç`¯_+Õ´FKË•”•„—ý4#Ï­3UuÆÂï>kêýÇÿ–•[XsÞîvÍyÀÕ[‰ ‰¯ ”9 e¿^áºK­–ÊÍWܾŸºmÏÕŒLYÍ5©Œ·Øå÷‘™“»µmæ C¿Úò2#ÿm«óñRó_Ŭ€wMNµ©¾S¥P(ÂÂÂ*+àííuèÐáêjU üý÷ß›6ý–žž!‰bcc«,?|ø0O°qã†*K^½z5>>~ôèÑFj²t鲃\ºtÉÈòe~X`¶Þ¶ÜúJRƒÆÖ‡ÇTÚJŒ»Ù b ¾ùÌuî^¢àŽÉçˆ Þ#\\méÙ—+0¯¡owû!}šÀóg·ïd˜™;VVƒR’Ã!ìl$Ú„Øü+Ék¨I•Ô}—ßOŠß¦‡—Ö œ»ó…–oW›–šG†°ZŠ0Õ®œjSý5U|>ÕªUºËY³f¹ººŽ9’½‹%5Q«†dddÌŸ?À€þ={~ÊçskWøÕ«WwìØa¼S…0L™ü8%/-‹ƒqpJ«ÅqÃ0Цœ`€¡i€°)2"»€^v0ááó\4{øöàó8jö7ƒaµøE)Ïzª,x%’:U^´øÿÍ›7oÿóÏúõ]Vüü‹­­µ¯}NüK;‚ï¡2F©[—]~ÁJ½®s!ªŽ×Î@}×ä@5—TÕ R…ãx§Nt)óæÍ³³µÕOÑŸ"$I —Ë«^[Õ 55…¦éÞ½?#'+0²$Œ±2õD£ ÓÊ(­ ô0Û1Ù‡Öª†ÀÃJƒ\Œ¡48Áeš £7|Ý+Z޾Lëˆé“º´iêßÌßóiç° ú9yE_LÛ‚ãOWÛè^ƒüœ%AA¡êöýÔí{®é&eZ7õþ¬sCG{©¹XÀ0Ln¾"î~êŸÿ^ÍÖ›äêÜ60ªg#;kÉ‹ô¼­ÿ\Ñá†c˜‡}t¯Æ¾N¿°H}?!mçºY­å3#ƒü rÔ†"…F?¥÷1‹§÷öór(.ùÓOì‹™ËܨèÔY]Ó™™¯2å:ßåÄ™Ûûv›‹q¬À‡ÔeÃÝ òs^6ós îÞJœ³â0tlå?mlGØwüöÆ?ÏWÙÙ™“»µŒð€ióþíÝ¥axC·‚BÕž#·ö¿Ýµ]ƒ¨žl­Å‰)Ùk·œÕõN7Ø~X²¿{‡ Ð.píVrÌŸçdr%û>•1—áwñ†xsk¡R_x•VÁcñì<êû¶2I”©Ra¢¾ ŒÓ¿lõjÕ¦ÀvU²¥ËZ¶lyåʕ訨%K–°é=?n|DDDHHHÿnܸ¡/Äpn.^¼Ø¿ÿðððqcÇ=yú”MŸ>ýûÁƒ‡@dd¤¿¿ÿ¢E‹*¬~üØñîÝ»‡ôèÞýÄñŒ^·?ž4iRëÖ­ƒ‚‚Z´h1}Æô¼Ü\6kÑ¢E7l,((ð÷÷÷÷÷oÛ¶­áò Ãú`pûöí訨ààOÚµÛ²e‹.×pÝ””ç&LhÒ¤Iƒ Z´h1zÌèÂÂÂ*me VcÌX°R|‘Ô2c+u©[öžÌý(æRË”m!ðt8\l™ü[}úÑ@íéf…ÿ멾ÆŸg&µ.Sý½Á¿’·ìÛ±­[Fx™ yeîÃËú®šÕº©·¹¹ ';Ë\Ìo×ÜwÕÜÈzv\¶¢¯§}€#—`ÒÒÒ”J…¤s›€e3ºã d tý¤Á”Qí,¸\½¾Íì)Ý<ݬôGH³F¿ÌéÛ"ÜÓÂ\H¸Ô\ؼ±ç/sú„úIY :âþ[BSªÒ)‹+üÍùäæŽ‚짺 \¾ØÌÜ+°`/órs5*†ÁÖeÃݹ÷øå¶N@Ó0àúù¶Öâ±CÚÀ½{w'Žèöâñ‰*;«³é·cZµŒðâó86Vâ1ƒ[7¶ù䑟8ÚK9ÂÇÃ~öä”*«Ì»0srg/;…¢H(àµmî3gr; TeÞ&cÞô÷&þÇjôP©œ =*x•–h’¶JÊã º»V™]ïªAm:U¥Ò‘Ë /^üÕ”)'Ož6tÃ0=ì?`€\._´hQLLŒƒ½ýˆ#îÝ¿ÏÖ0œ[†K—.;V*µX»vÝÂ…‹^¦½4pÀË—/†ùæëo~úi9Älˆ9uòä¸qãÊW?wþÜÔiS}|ý~ûí× 'þüËÏÏž=¦XÿôŒ _Ÿ¹óænݲõÛo¿½w¤‰lÖ¸qã ‰Nl(MÓALÕ¹0÷)¦w¥iÃpýøjö_ßϘþþæë¯mm¬mm¬ÿwútÙÁÇv³¤¢H$²±¶ô÷vl×ÜŠŠŠŽ9Âá‰8âCê²1Ýù÷Øãø{à‡ï'OÕJdÆS*•£Gâð-]|;MÓÓÓÛ·ï>R©T$ã8®/Ù@y6Q©TN›6­cÇlœÜì 7ôПË娫R©ž>}ºpáÂO>ù„-Ю]»*-i Ö»I™±ËåpHyÖ“øG—ûèìKЦÌ$ù9a”ؚλxïôuÇðœ«ê2¬'ÎÅ«?ô&Sbéóç_¹ëò9.¶íÜPbm)ÇW¯^+’:a8ÃhÞ¼YÚ¸o¼ zÔxààAv¶6n)5cå¸ÕwºvïaD³öæB¸víêž={<~~;Õ.9%Ý­~ñÂjG+ \¼xñÈ‘£ž¡QZŸ›·ÂÃü$ ९&»¯”âæäÕZwéÖ ‡]ýp¨ðå—“¿ü²81))yò—“2³rƒý½Ül?¤.ÓšfVn¹ºzn=©TÚ0H ?̘ñ,19°ÅXœàî¬sˆ®­¥K–—îª{õhr!æ$`Ýub½=«ìBeo7¢†¼ÑµP• ¯0Ý€ÏÀ6Å¡/Ö£bS èc¥Tç”I7VHÅA½à ¤aˆ.…$Éë×c‡ Êåru‰mÚ¶Ýñ×_ ÃÎ-ÓŒF£¹wïÞ˜1c‚`smmmÃ#"bccÙËâ¡â`‰Z­¾ÿþĉu%}}}Ý\]™’K’$7mÚtòäÉ´´4†dk%%%yy{ë,Å”Z1VEy.—Û¦Mk]•Ž;8pðÉÓ'þ~þêòù|OOÏuë×)Â7òòöfßlöª¬Ö;Kí0 xæ¶aQ}Ò×Ú“ÉzöðªÌ£« –œ—m1²»æÉ]™ÂÜÜéEDõƒ´SÑY:>þŽ‹o©µ8:ÕgííííííõËs8ÐæaýãÔ^á!nå „•"[j^¼Ÿ.55UbY߯9rd¤®‚EÉM:=-ÍÜÚÍÚÑäŠâ\©¹YFAFi‡0¬ôZ½Ò³E`8¶_Q–¥¥…­C=¿_¾@banö!u¹ÊîІgf\¾ñ´SÛÈÍÍýçŸ]®]Í$6³§t7ÐY½¨$%%ÖómGð‹wꥤ¤‹o{œSlÁ!U9úA·ÜœG·68Ž©Ùk+kyN¢~o,¤"cºPqÿ5£æ÷ «TNeÂ+)l¨ŠWƒ¶PâQ±¯ ëc,qªJ´¬HO¹\NÓ´µµµ~®­Î)\°`ÁñãÇ'NœÒP$2“ËåƒV©U:_ J»kU–‰ÅGWÅÂÂòóe î»~ýúõë×ÅÄÄ,Ê˵±±4xЈá#ª´U…µÞYתÌ .3U¢ð%Où4Yó0H¿Â;§È¸G i­|ü_áÍDCˆ"ûŽÐ±3Çjþ; a,%–&IRjéÄZ^^¨d÷ìùwì˜1å+yÖ·dï¸qqq#†{ñâE—.]þܾ£8›¢ ‹TìKKKKÈ‚k.è$ÈJ¼ G''žPʰ±³‰ÙÙYßž.ùpñù|Ãp³µy½/½D‚-Pi/Kr/^´vÍšž={­]·ÎÂÂbýÚ•CÇ/UÐØÖå*»CSj.Ϭ¯SÇ6ÁlŠ••ÕÜy öŸ/ÀpÌÛÝÎpgq=_¦i3±NŠ¢øf„žÇƒ18þÚ«rusË¡D8Ž9ÚIÙ”œÜ-©Ð÷å%æ2Ü… »¨!5?ŠÊ©LxÅ…+— wϰ¯ò ªt§†÷SeÚü_…Î £Ÿ^.œ#‹ ‚4pPïÏ{——f8·LŠD"Áq<77G?+'7W*•êù=PÙ²±XŒãxAA~nAA™PȦ>|xذad³ž·|á  N:ÅÎ@VvvìõØÞŸ•u2*„Ïç\½vuÈ!lJVvö³gOíí쀦i­V+‹tåÿûï¿2­“Ú×góTYH­öìÙ³;vd/Oœ8)•J=½¼©Ëâáá1íëi;wí|úôiÏž=Œ´•~-ÅÞ.e* 3±K ñË;”[' í‹Ä„vî‘6ô“G/Š<>³TÄ?RÐaNŽ/™L†C 5UoÝBNšaÖo;?}b>Ÿ¿yãò|Y!Žæ!œ9#ùþ1‘Ùg…J‰X8pà 7Gg7§×ç4b€Ñ ³cßõ ÃÚ ‚ƒ{·edÉ­4¤–Ï+þBÓLÌŸ¾ŸÐ™Ãá¬úy‘®®V«ýzÚ4œ#K/Æ>e—“ÇlØ /PHÍEjµ†Ïçé´}’œÉ0 †a#G9jô¿I¥&+êšÞk‚à>›Ò½c‘X,êÞ½ûÖHÆúCêr•ï {ƒn‡µ³¶Ó4=aü„„Ä”fÍZÕ«ç8o椑_­NHäî,VÚ«*¿¸LJ™K{{û¿~›©»¼wïîÞ½{]{–ê]uʿшZ¡Â¸ÎÃÛ§Ëïݳwòðoؾ|á7½¦ŠUF×:û/«ž}ªM-îþcJ%—¹d†a¾þzZrrò#¿8rôHìØÿýïÌêÕ«W­\eLnÆŸ””4iÒ—.œ?uòäØÑ£y<ÞÐaCKg*TRÇØqc/]¼ôç¶m:##cÆôé\.—Õð&MšìùwÏóçÏI’lïÞýr¹¼¨¨hß¾½}"ûp…Ö¶.AeF£á.¼õÏ·úå¾ÀuéR™„ ³ì<Îý­üŸ½“‡IrÀÞÉ# ´½.% ´½½“‡} ÷Â0µ·¦ªÌ¤X¹92ðööٱ㯠b–-].—ËÌÍ¥ýûE³Å ç–¡iÓ&k×­Û¸aÔ)S¹\n£F–,[æèèÈè/T¯ü¾Þ¢EË¥K–®Y¿rÕ*{{‡¡C‡€ÞJ©yóæÍ›7/22’Ç冄†þôÓŠaÇéVhµmÓ&22rݺu2™ÌÖÖöäÉ“†ËfBáâ%‹—.]úøq‚••åÔiSTe[‰ÄÞÞî?¶¥§§ã8îååµl鲦M›0 cÀVjUï½~Ó”ùY`. õÛY«Ö8D¥¦ÁžcKhµjšp#šÓZµã` I©¹bÿpÉEªêŒŸýoô¨QY/âÀÆ»T8!W!^ÿwÜŒ¹«eÙI¤¦Ã™¥¹uýz^-0 Kz!û~ñÞÄø…ùé‡gåàÛ²ËØÇ7v@=¯–¬œØ‡ÊC£¦&œ×(ó"«Ëq™¿ú1÷Õc¨ †eŠVn¹öbú2Yv²V«äp„æÖõCZ‘XÖcu8›¹cçôO.ê"¡ØöÑ NZâdYv”?Î\Kùk׿© ç5Š|öãPáÆéí{¯-˜?/5á<¸ùwd 8yoíÚuÉ÷O€…­gPËR— ¿ƒÿ]|´}ûö'qû žW+©û“äœN}~ˆ¿ðÃ0–>\þÃ]¾áäƒë弼ώ­–þ싵—ü+ ÃNž{ðÇÖ-Oo¯†½ôƒ[¹¹¹1;bgÍ_“›ñˆah+¿ Á-? ¿#5ûˆJ©p Sƒ° *}ˆ]YŠ#Låä˜$İœ 54,§Ú`sæÌqö,K©$i˜Ñ­ÒNmß¾ÃÏÏ7îö)_}%“½Á‹">òóó÷îÛ×'òó#GŽ4°LndŸ¨ˆŽ“_¤—:òø¥ÔÆÇÝ]U¬dx@'$%9˲k[kñ–™2ªC‹pOˆolë)YUYQ÷$%'5hX —¯ßvws×ä°¯e¹/‹Òþ»|ù2¤§½¨¬Ê¢£rr O[AFìÖº[S…@TH™_õäÙŠ;FùIZ€zPñxñ^ƒ•žÝC‘¦w–Ú8§ê]”SmjõHÂtPd@”aå¦Ó£FÌL{_ô-ñîòFwÿ½E9Õ9Uˆ· úºD åiÐt4ð¶µ@TAÍÏ©ªl-ÔÛ•SmS…xËÔü3€@ ˆºÇÇËëÜå›íZ6®^õ3oøxy½ƒrjB±Sõý‡@ò©â=Å×ÛëÌÅÕ®ûÎÊ1ž7ù˜ÂtÐô@¼¿øùxrªrªoäT!âà:NUVV–B¡¨ºâ£Ç̬ꇘ¢'"#âàĩ2zQUXhèö;ª.‡@@XhheYW®ð›7.ý®{¹bÅ­Sÿ¥Ô•^@”¢c‡úÓ¦Ÿã>sÖåß÷\¨ºNißÉäHUž½zôìej-ÄÇ̃ûw«,ÃzTññU—D âÍ¡ó«ªA-Ÿ¨~øÐAär!ªëQ>Ôóæ­›o´¡Fa†¾÷F[ù0`£ŒÈVÆ€le<ÈVƃle<ÚjÔÈ/nÞ¼eŒÃBC5 ?áÌ©ÿRÞ!§ªÚ•F£¹ÿðMÓ~þB¡°vµB¼/ܼuó«É“ßÜÒu†aV®Zo´•d+ãA¶2d+ãA¶2žÊluîܹ[qq³fάRÂü jE“·©R©UOSµåêX_Èçï:¶ïfÒcp}Æþ,ʘÎ.\¸uëæäÉ_©›©å+äç+V­Zù<%Õ¤’«V­¼w÷î]µZ}ëVœKýú•UüqöìÝ»ÿyððQ…¹çÎëù¹~Š••Õã„'&öãÝÃ0š¦ßðÔÔÔ:håÃÙÊx­ŒÙÊx­Œ§2[éQÀ ·ïØàQCMÞZ¤êö£»_n›“+—-05#-#§°`Bÿ~í>øßŠÍ1Ó¾W¥_uáÂùÍ›6™âT™V¾Bllm|}}M­õ<9yÿ¾}¡¡aááá/^¬‰,óç/pvvf_óø¼š |§¨›;б ƃle<ÈVƃle<ÈVÆSÞVEÕYëµ|¢ºñ‘ª"eQjîˬìÌ5{7öo×w@¯Î{ò×*Q}ÆÞ8›8{å²)ÃG9;:׆RµƒF£æñøC† 2d¨©uÃ#">z 7l¨§ªMÛ¶þþþ5—ƒ@ ćÍuªjçDu‡,³Œ½ 5ì]á8Žé$•ë: i+ÿ¿r×xðÝwßݺuS*•Ž=櫯ÊØ …P(ü 'ÚQ¤ê]ÙÊx­ŒÙÊx­Œ§ÊH•««ëóçÏßPë5uªnÅÅÍœ9«|ú‚ó 8UEåæzØØ}ýµ½psö’WÊW$I‘$õLñl¡ì»ÑÓ>³íþÃÚŸ&ôâÈá”Õó믿¡)jçÎçÏ_¢\cÊŸ={¶¿èöí;ü¹}‡B¡Xºdq÷n]Ïœ=çââÂÖ’Éä3˜±pÑbµJUFfZZZ@`àÀAƒ¤æÒç)ÏW­\9xÈà#GŽVa2#P(£F>â‹)S§>thá‚ùž={½¶g·®] …Bá'Ÿ|2gî\77÷š7úªw d+ãA¶2d+ãA¶2ÃN•‡‡¸ºº&&&¾‰ÖkaMU5кvêäã“ †Í¸j¶g®ÊTe‘$­&II’$¯ÉZüxÎ$Ïo¾ˆì÷Ûþ]]^¶ü¬K·2,--%ææ8ŽXñ]eù%‹yyyý±më´5jÔ(¼q£5«W/[¾œ- Ñ¨úiED“&Êlß¡CûØ×Mš·jÙâáƒþ¦¤ EEE‹/iݺ5´k×îòåK`*ssóñã'D4i"‰âãï¬Y½ºk—.gÏ··¯8¤÷>ò¶œªY³fíÞ½{èСӧO×Oß¾}Ç‚óÙ×ÀÚÚ: À¿Gž:u*)¼s'~ëÖ-7nÜÈÏÏ‹E õíÛ·S§No´#¸zõj||üèÑ£k(‡a…BVé6coo¯C‡#Cºù™BMló믿ÆU²U~øða<ž`ãÆ Õ–ð®le<:[ÅÄİ/tN•·÷ëÇzxx†Ç+Þ(°}ûŽE‹zzz :ÔÙÙ¹°°àÊ•«S¦LY·nmÛ¶íê¼CW¯^ݱcG­ø |>ÕªUºËY³f¹ººŽ9’½‹%º¬ÜPˆºÁÚÚÊËËëíJx_øhmemm••%N•ŸŸ_™ÞÞÞU¼Ñ¾ÚÔŽSµ¨äç)ËŒ’ Avݺþ¿lº¥Ôrõ‘5B®Ùèþƒ¬0‡/œ¿ŠÐR4ES ÍhhÍ/W8Š\fÿºüUN¦™Ø¼æJ–G&“Ñ4mcc«Ÿhkcs7>^w)‹ ,Zúî»o÷ïÛ7}úŒÆááb±X–Ÿß­[W•ºì,a5033Óo—ÃáPZm…%ÃÂÂ<<<âânÕ¼Ñw‡·©:uêTaaá3f,\´èÂ… mÛ¶}]gãÆ0tèпwþ=oî¼eË—ÿ0cÄݾ½hÑÂö:ü¼b…Î!ëÓ§ï˜Ñ£I­ömE/XÍkÞ:Ã08ŽëG’æÍ›ggk«ŸÂ0 2Tm 1 ’Ôp¹ïåàêÙŠíoß¾Q}ûFU*©Z~Þ1­ŒG§gß¾Qëcb Ä©ºÿ~ùµ¾†ÝäÔå¡iúû?èÿébW¬¥ÿ/ËÊ?/‰^¤(* ç1fõ0¯³§¯9ræÄÑKçOÝòæJp ‚ææå/ÿýš¯ŒÁp8D™w]]¹‹#•JqÏÎÎÒOÌÊζ´´4²¹wï3vìè1cÂÂÂ|||Äb±‘k­–úÀ–«3o ­ìÛ¿ßÃýÿ€vvvûöï+U«øK§T•~Ñýš5köÏ®]J¥’a˜Í›6q¹Ü¹sç¡_ÌÛÇ' €}ýøÑ£‰'6kÚ4((¨S§Îk×®Õ»xñbÿþýCBBÂÃÃÇ÷äéS]Ö·ß|Û§O¤¾Ì!C†L˜0}½lé²–-[Þ¸q#:::88¤U«V7þÊf-Z´hã†þþþþþþmÛ¶­-[±¹L¹ôÓPLå¶züèÑøqã#""BBBúpãÆ ]–}Œ¬{åʕ訨%K–°éÇŽëÞ½[ppHÏž=O:5|øð±cÇ2 súôiÿ{÷îëË>|xsÕ •Ùª2å™Jú¦«~üØq¶zîÝO?ÁTI’ú)ŽŽ'O¢išÝm'—ËŸvèàÁ¯¿þ†mâÕ«W—.^0` ‘½&IR"y=ëqèð!ÍP´Z­þÌÔùóçŸ?Oþ¼ôq ï;LGª233¯^¹2fì Ã:wé¼óï]ù2™Ô¼8DZÆ€²ݶmÛ^¹råî½»5¾víZãðÆRsóÊ”¿ÿàÁÁƒ½½½gÍžmkgû<ùy“¶ðåË—ÇŽÛ²e«µk×)•ʵk× 8`Ïž½NNNÅ­3¥m¢÷Ë’F./XºtéŒ3ÜÜÜŽ?>oÞŸ?qÒ$£”ÇñÖmÚü¹m[×®Ý\\\:øÇÖ­•fæð¡Cpïþ=øïô6Ö6öööìø3gÎôï½nýúÈÈ>U¶;dð`GGdž!ðîÝø­[¶8;;3Æ•qèð!Š¢ºvíݺvûsÛŸÇ‹ŽŽ6\‹½—ggfÖs®g ðOË—ÛØØþñÇ|>…5Òe­Y³ÆÍÍmõêUìp êÒ¥ËæÍ›gͪ`_myHR3Þ<__ˆŽŽÞ»gω':uê$•JE1ŽãNÎoù¼·ÓP+V¬°µµÝ´y»˜¬I“&ý¢£7nˆY½za}Œ¬ûãìCÃBuÍŬ ^¼x {éííÝ£Gwö5AQ}£6mÞôí·ßŠD"ØýÏn‘HÔ­{÷ZìoM0 ÆÔ! Ct¹jµúþýû“'OÖ¥¸ºÖ÷ôôÔ$²Od̆˜C‡EGE“$¹oÿþ=z ‚w!RU¥òåû zËàØê'NÔ ôõõusueªzCkq!]€le<:=™’PÕûäTõèÙK±”ñ'ª€­…­J­jjÓŠ<(Y„aÀåp;:t¢•ŒTlèI5A¬øùç?ÿ¬Ÿ­`øüóHÃåÛµk×®]ÅŽæÎ›7wÞ¼2‰S§M›:mûÚÞÞ~Ç_éç² Ê—äp8úYeø¤}{ýÜòíþöÛ&ÝëI“¾œ4éËÊD}Ô±SuÿþýgÏž=J.—³)íڶݾ}{rr²««+è}¡”Q,=- llmÄb±X,N}ù¢2Íe29EQÖ66å °&,-­ô³¬­,=|XÏ/¥m‰ö¯gµ¸¥ýÃ(J«Ë­Àõ1Š%”ó¡t Èd2’$·lÙºuë6½,š¢¨*õ1¦.pÒU—Ëå4M[XXè ´°°ÔuÊÊʪ}ûöÿìÚÕ7êä©“yy¹Q}û¾õ{$«@•Ê—ï/Àk?˜­nmm­ŸkckWõúNi![C™é¿¤¤¤ºl½ä15徫‡ñøºø^L¸Ô\ÝìLÁ9 £ 0 £Ï?¼XOê|éù5Ïúž5W ñÞQÇNÕà·_ûí×ßô 8p`;ÌTì+œ={–Çãùû0 qéÒ¥üü|©TZ¾-‰DÌáp²³²ÊwM"‘à8ž››£Ÿ•“›+•JÙ—iZ£V1qe!–âoV]nåa!ã©PB¿n?zCéš‹ÅA 8¨÷ç½+,`@ŸjÔ‹Å8Žçååé'ÊdyfB¡.%**jĈwâãwÿ³;$$ÄËÛë­ß#u¶ªBùŠîè:ž­^PP _   @¿ï·þF_­ŒA§'L£Ð°í;vY1,44.NVÍæJ­…Ýú>tÐøÂ]BºôréiCÙ40 >L(r¹Ü¡M†ÚÛqm:{µ‰nûA­¿F¼ƒ$yìØ±à  Í¿ÿ®ÿçëëwøða_"»þÙuíÚµ¨è(@Ç'Irî¼¹ÚÒ‡_NCñx¼Æ_½îâââ^š7Q—Ïçœùßë't=þüéÓgúe7nìååùËÏ?߸q£¯njûÀ嫬~õÚU]JVvö³gOkYËwd«wŸZX¨®I‘*©¹´M³6e}<¼}<¼+,øH¨ËHÕ¹óçóóó§M›Ö¸Q#ýܾ}#,XΪ{=%å¹FCfddœ;wîúõëÍ›7›üeñâ†ààào¿ývéÒ¥ÑQQ=zötvr’\¿výÔ©S«V­dfÊԩÆ >lØà!ClmmSSR'<þþûï`Üø ãÇ›4éËþý£UJÕ† x<ÞÐaCYÉ;tؼióú˜õCÉÉÉYºt)‡CèGûË…î] ÉÃÓS¥RíÚµ+ 0ÏåzûøÔÄVeÒÊO CéÛê믧 :ì‹‘_DEEÙÙÙÈ ïÝ»ËÐÌ—“¿¬RÓ똱c&Múò‡~èýyïü|Ùúµk­­¬0×/Ö·oôâÅ‹ÌÍÍ;wêô.Dt:V¾âÉY½ÌØqc'Nœôç¶mÑý¢ssófÍšÅår+ˆ¤–m¾”ï8ÈVÆ£?ýw+.nÖÌ™FVœ¿`€G [¯e§Ê¤5UD…Ô¥SuèÀ‘HÔ±cÇ2véÒuùòŸ<иqc6ô½`ÁBàóùÖÖV~~þ?-ÿ©}‡ö†é*öë×/ àÏínÛ¶M&“‰ÅâÀ ~Z±¢eËV ÃøùùýñÇÖõëc,X R©{õêÅÖmÚ´ÉÚuë6nØ0eÊT.—Û¨Q£%Ë–9::²¹¾¾~óæÍß°!æ÷Í¿×wq5f´Z­14o¥÷õ×¶M›ÈÈÈuëÖÉd2[[Û“'OÖÄV¥“*˜/C†Ò·•··ÏŽmسlér¹\fn. ìß/Ú}L® вe«%K–ÄÄÄ?~Ü¥^½ñã'lÿk»X,Ö/Ö±cÇÅ‹õìÕS ü[D§CÊW>ØØÄ-Z.]²t}Ìú•«VÙÛ; :¤B§¿lëïç”ÖÛ²•þGøGú]­mÌ>°ÚzÄ26g·ða ¯J’€Y=*XèÀ²}û??ßž½>«•†îß=räè Aesrx£­| [Ï;h+™LÖµk×ñãÇ4H—xðСgÏÞ»oŸ»›ÛÛRÌ[U¨üGÈ»c«5«×ì?°ÿôéÓo®‰R™­~ß²eÖÌ™F:UÛw숋óHL”?öÌœuù÷ßcæ ¤§½¨¬âüÃ20ãÞvŒحïP¤ E¹¢z(Š˜˜˜ˆ&R‹ôôô­[¶‚=z°¹III©/RcÖ¯oÓºÍ[ô¨*ðò}êØVr¹üÞÝ»§ÿw:¨AÐj¢¨ËÛÛ\SU‹uu9ý‡0d+ãy[¶""9ùù‘#GØÕÆ-\¼X·5rÉ’%·nÝ þ~ú÷ïλ©Óİòx{¶º};nÚ´¯§N›ú¾¼åõ|*©BÔäT½k [ÏÛ®T¶ï IDAT²—ûöî;,Š£ðï MŠÔ£ˆ HQ@Pl±ƒ ú‰¨Ñˆ%ؤXQ‰±ÇŠ`ƒ&Vª’D±€¢1 Ò„£^ßïÕËI9¸7ïÃsÏíìÌìì0Ü ³³³ ìo©0¼‡ ,ý6y%\øvËûøÑ×§ùç"\޹lÚ£Gó—$)ÖUÌ•+x]ÉNËLºåì:#Ul6;+ûÃ?ï ¦Nvå:hEîÛwàÀþ¼ùbÊÔ©’=¨®„‡êJxâ®+C*5æJL³»¨ë7…êJxh¤ª£i1 «­­M{\u#Û{^Že?SA‘Od2ÙÄXЃ&ž®]W¸S%‘ª+ IE"††›c\íÞþSZ\¦dea¢«­D§sRŠ´Þ¼-´¶ö~ˆ¬A#U²Õ•ðP] Õ•ðP] O~Gªn&Ä?ÍÌlîèà “K9½fŒ×¥³¡‰}x__pù¯¡ŽöC4)Šd‹ @èÓKëÏçZÝiz-.¯²³wïÞõèÑ£ššccŸu?þˆïzðàÁžˆÝ/^¼PTT:tXðÖ­–––MsX²xñ?ÿ¼½{ï¿Åm'{yih¨Ÿûå<l ¾r%ærL̆ ž?{F¥nݺuâ¤I'OžšÝHf=(5õܪÂ:mưQnÝ50.NÇ€ÍÁ0"™Á!>zZ6Ì‘úz:M3yþüù¤‰¬­­÷ìÙ«o ÿþß÷¯^¿Âw=|øpÖLßQ£FÿrþB}}ýî];'Œ÷|ð0ÉĤÍC_õõõK—,ù~Ñ¢5«×œ:uêûï~¿hÑË/ÂÂà úÖàà€E‹~ÿã/>V´cç®^½zݸq}ÝÚµ½ÌÍ'yyÀ??2YáPÔa==½²²²¤¤‡L&³­åéÊÊÊêëëÅ”¹ŠŠŠŽÒ5 ºª+᡺ª+á ®+y©áÖ9m’€êüZ*Æè¦OQ$r¹Àæ°9صôÊ7¥%Eå”kSßÒ‹ k.în¦S²5XOOÿF|þ<²!C†òvíÚ¹£wïÞgÏÃg»0Àià€CFìÙÓÖ¢ÖÕÕíÚáìì ´ìcqóæÍÇ+*R ®¶nÕª•Æ_¯d3™ŒýûØôí óæÍ¿pþüø“¼¼rrrŠòôôÄczxx´µ0ÂÇÂ?ü²Ý‡À0®¸Ò58:8ª+a ºª+᡺ž€ºêĪ¶Î©jG§ŠÃ¨'(ÜyA{W]Ëår9l.› ,&÷߆Ï,²š¡nA‘«§õ(»‚\ÕÌp“ÉxôèÑò+ð?ƒ‘™™¹zÍÞýƒÎ..ééim-'¨ªªâ=*ÐÐÐÐ××>|8Þ£€>}ú@II ¯S¥¡¡÷¨p=LM‹‹‹@YYÙÒÒ2bODm]ó°aVÖÖ¡å‘}ÖæöÐŽCLœäµhñ²¢Â|C#ô*à5%9ÉÕm8ZèD¨®„‡êJx¨®„×l]………J¸Ò©Ú±-œ?dÓ– ^>ü¯¼lüUãjÞÍÌ¡*ë±ÏÕPYÍUï¡lÒËÐÉ^qßäbyÜù¬Üô_ÓŠókëê02zš{›6sœ¢"eèСqqq vQ(GGÇ„øx‡ƒ‡”––¦¥¦:;»4ÍÇ€J-(,为ºúÕ«Wm:ývèÓ§OhX˜’’RÎë×â>–´`bƒçohôÍ=åååK—,±èÝË´‡‰ÏŒ·oßJã¤eN£Z€’’’7xzŒ362ÔÕÑÎÿøQ*ë’’’ô··£èÛX[þPRR" ¾œ7B9?}áµú7ˆj’_yy9ïýÇ>|(ø½Hˆ¸SÕŽ‘ª¶vªjë8òœú*ЀÎ&ÖÕc\G¸d. ˜tuU‚žQG#9}{ÑôšÍ$$4¬ìÓ'¯I¯^KKK»pþüÆð]ë7lüçŸüfϾ{÷NB|üŒéÞ å‡ÀÀ¦™xyyU~þ±›F£½{÷náÂbZÉ=??ÚÔ)gΜNKKKJJZ»f Nw>\Ç’ü#U,kº·wrrRhhØÁƒ‡JKK'{M*++\N4ÏËËͽ~íš–Vw'''©©‰Ü·ïÍ›7sæø8xhŽ¿B|ü„ ãkkk›,çPÎO¿Mÿ ¢šä‡×þÿz„ ¼ÿ®›¾Õq¥9§ÊÑÁaÿþŸš ªè³_Ou²š*Е¡Z…PWÇ®«cW˜°kjô ×‘è Š³˜ýz³•”(Ífbgg—xëÖ®»Ö­][__obb2sæ,|—»»û¥Ë1{"vÏ›;WAAaØ0ç'O5»ž‚­­Ý¡¨Ã»8`ff¶fíZ!ä¹·‰†††¡¡aôáÃD"ÑÚÚúäÉSîîîâ8–,ûŒLx=†¸¸¸ìì¬k×o¸¸¸€Ó AND:&Ö2Ⱦ¦sªœ zóŽ=ššš*¥ruû"#{÷îÍÛ´íg;þ¼ø7þ7{vÓÈrÞåüôÛDðß ªI~xmŒ50 sss«©©Áwµô^$¤9§jâ$¯vÜÔð©Œn©G4íN$€«Ú Äze] Õˆ P_O¨¯'Ðä‡Ï˜.CÌäÓ¯ŸmK÷©Ž1bĈÍîZ½fÍê5kx›¾¾¾¾¾ÿ=„rÚ4oÞûа°FMùiæ3þÍ–•WˆâÄIüººzÔáhçÒň»SÅ?óûí[T*ÿ CCCW7·ÄÄDùüâ×tNzô“ðø{T0xÈÀoæmJΡœŸ~›þD5ɯ ü}'ž¨.gÿqêYy%Œ_ï~Æ0 c2¹ õ¬:³¾ŽÞÐÀ¢70é L:“£L YZµ¯$ˆtIr¤*''ÇÊÊŠ¯µ•õý{÷ …Òü0§œ@wÿ‰Ð£ôt°¶±iv¯œ7B9?}B5ɯQm¤¤¤€«««€÷"Ñùžýwq-›D,¦üå~; 0À0Œ@4¢Z·»$H'õéÓ§ãÇge½|õê5“ɼwïž‘Q3kþ‘ªÊʪ~¶¶ü{5451 «ªªÒ×ofÑ ùîþ•ÊÊÊ­¶¶vãÆk!‚\7B9?}B5É¿6xs§h4ÒÒûŽë|#U£Ýµ/¤³h÷HUþlj‰‰¶¶vý32þ¾;þøñT!-A#U"A§ÓçÍ[W_wõ‰D’vqDaæìì\UU…o¶ô^$¤|÷Ÿ¨Ò"]I»WL°ïß?55õÈ‘è#F6›ž?JSS£Ñÿ(´ª* ¡!èiÜòõ;;ŽÉdøûÏÉÊzyåJ¬™™YKÑä¼Êù鋪I~MkC2x*ìëO‡´u*Q¥Ez&5ÿ LVVVorrø÷¾ÎymjÚ³é#ŒäMÓuª6a2™óçÍÿ3#ãÒå;;;1å¼Êù鋪I~üµaXZZZZZþßu³ï;p¨oºOh¤ ‘9í©úoD k><þ1Ï¢¢"Þ³‹ŠŠR’“=Ç{Jü¤e©ê6›ýýÂÉÉI¿^¼Øê²^rÞåüôEÕ$?¼6ð÷˜ש’òŠê²‘5ïTa_îbh¾SÅ?ã=}ºµMÀ¢EýõÆõë³fÍìÖM}Ù²¤s沤éH†a ññ ññYÙYp÷ÞÝ„øø?32¤Q:Y·víšÄÄDߊò ¼Òâã³²^â{~ù¯qVxþè¾6a ZB¤kÀ0lРAeeeøA³ïEu¬.r÷_›ÒŽØ÷’ÿ2Ÿàͦ¶ÛX[=~üÈcÜX#Cþ6Öû÷ïçí}ýêÕ¼¹þýúÚP ô­­,ø\QÑ(íË—/&Loldè4pàÍ„8yòäÀŒ 'Nœ›ûÿp¯²³ýfÏîenfld8~¼çãÇ„?SyƒaØÝ;wîÞ¹óæíHMK½{çγÌÌFÑÐŒ0P-!Ò5õŸ¸ˆÈÝœªF—öo¶„F« Ú±sW¯^½nܸ¾níÚ^ææ“¼¼ ¨¨È¦oßÙ~~êyóìß?ÇÎo¿%òÒÖ××/]²äûE‹Ö¬^sêÔ©ï¿_øý¢E/_¼ g0è[ƒƒ-úý;xäìì¬ñžžvvöQ‡£UUUÏ;ë=mZâ­Ûööö”³“jwûf³ÙkÖ®åmnß¾\]]:Ä ­¨. ´¢:‚ ]Æ_ýøú&-½ y»ûO4Õ™LÆþýlúö€yóæ_8þFü ¼S5jôèQ£GãÑ lkkçêâüúÕ+Þ½uuu»vG8;;À€-ûXܼyóñãÇŠŠ¨«­[µjeAA±±1„†„èëëÇÆÅáÃtuu;ftä¾½gÏý"äùvFíîT‘H¤Ì&ãRM3D=*a gÿ!Ò5`_×£*--ÅCZzßqò8§ªãÕ544𮇩iqq1þžÉdîݳÇÍÕŬ§©‘¡á¨‘#àÝ»w¼Èªªªx ÏG__øðáx úôé%%%xV©©©“'Oæ=`œ@ Œçñ¸«¯ $‚9UxÑl!a ZB¤kÀ0lÀ€%%%øA³ïEu¬®3§ ïWµô*ZŠŠŠü›D"‘Ãfãïׯÿñðá(?¿9qW¯=xøðúõë@gÐy‘UTTøÓ’H$þ"‰xn4ÅbEEEò~öíÛ[)Ò•Ê䩪%A¶êjsªZzå×hºàÍ¶Š½reÙ?, À7_¿zÕî¬ÔÕÕI$R@ÀâÙ~~)R§#š…æT Í©B¤k÷w ?¹›S%’‰ê-ár¹,«[·n¼„› íÎB¡8;;§¦¦l "“Eü›’eâþ@=*a 9U‚t ¸S%õ‘ªV‰uEu"‘è6|ø/çÎyzŽ711IHˆ?{æL[3á>aüø)S&ÏŸ?ŸJ¥ÒhÕOŸ>Á¸Ø–  Žd+ãÐH•,@#U‚t ’ìTu9UÂGëŠê‡EõêÕÛÍÕŲELLÌéÓgÚôíÛïÎÝ{T*uó¦ÍÓ¦N]µreÖˬ¡C‡v$Oõ¨„j A¤­än¤ªãBÃÂBÃÂøCNœ8É{¯¯¯á×_ù÷ò?ª©iÚ§™Ïø7ØèÑNüùË4R% ÐH‚ ]©oÚ®¨ŽˆUYYYØà7Zæãë W¯¨ß‰ H× ü‚;G 1pš÷¶¤®Å€ Iš-E=þ‚••å$¯)²“ù‘ª¶N¼ÞžÒ ßz•ýò·ßýüf7 74š³fõª5kÀÃóú‹/â'|ø ɃÊÛœ*t÷_' ÉëßNƒ½ÎyÇŽE*ÁöEFöîÝ›·iÛÏvþüyñ7nüovãû:\]]݆õ?oݼy“€hqqqÙÙY×®ßpqq§Aƒœˆ:t¨Ñšv]›u…Àpwwkkki—BÖåü¸yÓ¦£Gž¿pAÈ$Ž™™´÷K§  |“ÉþH•XSƒˆ„$;UD¢ˆ¯€waü=*¦é8IΩBÚíQz:XÛØH» 2êÙ³ggNŸÙ»go«ß|999VVVü!ÖVÖyy¹ Cœ”!Â×ã==L{˜˜ö0™7×?7W¢×¶:‘ŒÇmll  |êÉ“'?.šS%^L&CQ‘"ϤôôGW¯^}òäï²²2 77·U«VéééI»\]PeeeHÈV[[»qãÆI»,²ˆÃá¬^µê»ßÙôíÛjäÊʪ~¶ßü#§¡©‰aXUU•¾¾¾ØÊ(+ÚTWrN]]}éÒeƒVUU}ñâù¡ƒ==<&%ËC;i«’’’’’’'Ož߯0`À«WGª:ø˜š­ÁÁ6ÖVwïÞq>ÜÈа¿½Ý‘èèF{“““Çcld„‡¿ÊÎö›=»—¹™±‘áøñž?â%ÉÍýà?ǯw/s}=k+Ë™3}kkk„/Y¼xô¨‘üEšìåå?ǯÝ5íˆ:räÈ»wïfLŸ±mûö>3~ÿý÷Yÿû_mm-©-:>oîܺúúS?ÿL"‘¤]Ytôè‘òò²õë7H» ª+á988„†…M˜0ÁÝÝ}ùò—c®TTT?vLÚå’E\.·¶¶T‰=m£K{‚7[B£ÑÂÂÂŽ9Ò»·ÅÍ› Ë Ââ%K¾î­Þ²yÓö;ûôéàÓ ;;k¼§§}ÔáhUUÕsçÎzO›–xë¶½½=Ìñó#“EÖÓÓ+++KJzÈd2„ Q¼¶@Ö´»÷bfÖ“·imiµbåÊÛ·nOóž&Šr!L&ÃßNVÖËk×o˜™™I»8²¨´´t÷®];wîâp84Ú—y¯L&‹F£©ªª’É?u555xÑp´ª* ÏSjk]!üÍÍÍ33ŸJ» ²¨{÷îïß¿—üH•¼Ýý'š‰êL&s×®]}ûöoïéÏž=‹ŒÜ·`áB`2{÷î4x0/~hHˆ¾¾~l\…BWW×±cFGîÛ{öÜ/ 999‡Eyzzâ‘=<< ¥pa´©BæÙ)˜™õäßt0J?•J£,]“Éœ?oþŸWbãììì¤]•ŸŸßÐаr劕+Wð¯^»z5îò嘑£F5Šoee•ùí#/_ç¼65í©¤¤$‰âJU[ë i„Íæ ‰hͲ´²úûï¿%?R%â˲?R¢˜¨®¨H2d(os„ûˆÊÊÊœœ×ø¦²²²Ó A¼½L&355uòäÉx‡¸q32ðÈ–––{"Nž<ùúÕ+Þ MKáÂhSdPG§£õ×_€…EtùO$Ølö÷ $''ýzñ¢“““´‹#»,--¯ßˆçÿ¡P(#F޼~#ÞÁѱi|Ï¢¢¢ôô4|³¨¨(%9Ùs¼§dK-m­+9Çf³ù7“““óòr (­òȲ &À“'O„ìQ}©ê¨.5RÕÒkûò@­›ÿ­øššPYYõe¯šÿ¿4ÅbEEEEGár¹‡ƒ¿¿xérÄî]{÷ìÙXQ®§§°xq`àrÐRxëÅkcdHz?4mïÞ½ÖVÖÇ» Èð› •wïÝÕÑÖÑ××ççCpk×®ILLô÷Ÿ[Q^‘ÿe£™¹Y¿~ènÙotëÖÍÙÙ™?„D"éëéñK½YaááÆŸ2eòüùó©T*Výô錋m ÊÏÏ_±<Ðkòd ‹>l6;!>žN§» ÞR8xyyØ¿?"b÷’%KËÊÊ6mÚØjWI@„©4 ëÈH‹ÅZ½jõ³ÌÌcÇŽYYY¡yT‚ rK#Uß«ŽTÁ×Þ¯ÿ$x³YªªªÇŸØ´qCVV¶ŽŽvhXo=…fõíÛïÎÝ{»7oÚ\UU©©©åàà°páBÐÐÐ044Œ>|¸  €H$Z[[Ÿmgg‡zT‚ ò Í©ê4 ðûwš†‡†…5ûÄ. ‹'N6 WWW:-|8Î×××××—·É»ÙŽt%Û¶o»ÿÁtïéUŸ+ïÞùòÛ1éabii%8!‚ Òõ 9U’¾Øê&"yíazñüÄÆÅÆÆÅògúúnظQ4%CA:4R%v"™¨ŽˆU»;U±±±Í†£ë€‚ rT‰»_5¼ãY´t} ÔBA:­¨Ž ‚ "’_Q]Ī› ñRI‹t%¢zLMSÒ>3ADrÐH‚ N‚ "h¤ AP§ A4R… ‚ "h¤ªýØlö³çÿ\‰{Àf³Û‘Ü{ÚÔÿÍš%LÌ”””ö·ãbͪ+A#U‚ HÇ¡uªÚðÚÚÚ´Ç…@P72±½÷àå˜QöøÃ†Å!%%ùÔÉ“+V¬”©¬ºŒ²²²úúz1e®¢¢"¦œAYƒÖ©j=í•„$Š"ÃÀÍ1®voÿ)-.S²²0ÑÕV¢Ó9)EZoÞZ[™´¯“ÉPT¤H»¢äèàpþÂq"%9I¬‡èJP] Õ•ðP] Õ•ðšÖ•«Ûp9©º™ÿ43“?ßttpÏ¥œ^3ÆëÒÙÐÀÄ>¼¯/¸ü×P¿¿°÷ IDATGû¡NšE2‹Å ôé¥õçó ­î4= G¿qc×®yy{ö4ݰ¡ñcL^egïØ±ãÑ£tƒagoAII x#XÍfµkçŽÞ½{Ÿ=wŽL&À€œ8tð`Äž=x&“±wï¾Aƒ·µÞ: ÐŽV!¤üü|1åŒ ‚ÈÉTIùî?ns'ápê w^Ò/¥Ôž¾SõóïŸþýóÙ;;/ÿÏÅtt H:t=“GïH…Å̦9ÐéôgÏžMš4‰7"Ò¯Ÿm¯^½ð÷L&355uòäÉx ¸q32š-àøL&ãÑ£GÓ¼§á=*Á Fffæ$//¼GÎ..ééi¼8ÊÊÊNƒµšU§&É»ÿ’’’tu´ù,ûXðö–””lܸÁÓcœ±‘¡®ŽvþÇÒ.èooG5з±¶ ü¡¤¤Dìµ#=òv¾¢2Þ\]íÀ– ˆS^^¾tɋ޽L{˜øøÌxûö­ÄŠ'u¨]µÉƒû÷'Nœ`ÚÃÄÈê>|ø…óç¥]"õgF†ÏŒérw÷—Ëݱ-œ?dÓ× ‚ø•DþW<œÍÁà¿j\Í»™R8Re=ö¹*«¹ê=4€Mz™Çm¨eT} å?Ëÿq‘nÓƒVWWs¹\===þ@}}ü Fc±XQQQÑÑGøÊÉáp8Íž‚àø4Z5‡ÃÑÓÓ¦6h4—ËÕÑù¦Ìº::/_¼àmª©©‰ïê˜Ü ßfdd„¿W¤(òÂórs¯_»æààèä䔚šÚ(Uä¾}ÕÕÕsæø›ôèñï¿ïŽ=šžžž””¬¦¦&¹¢K¼¯Hܾ};--MQQQ@‹5ÝÛ»¼¼,44LUU5ò§ÈÉ^“’SRuu›ùøêzP»^ÆãdzfÍttt,<üÝ»wS¦LŽ‹‹MOO»uëÖöíÛ¶…¹LiieÕÐÐpúôÏ™™™¯²³[Vöé“פ‰W¯Æ¥¥¥]8~ãÆ -eµ~ÃÆþùÇoöì»wï$ÄÇϘîM¡P~ lS­vv’ïT÷ô0íabÚÃdÞ\ÿÜÜí+ö£ôt°¶±içiw6òv¾mõìÙ³3§Ïìݳ·Õëõ999VVVü!ÖVÖyy¹ Cœ”Q¨] à?ÇŸB¡lÚ¸±°°°¼¼üð᨜œ×‹—H»\²K¾æT9:8ìßÿS³áR}böë©b¢NVSº2T«êêØuuì vM Þô:½Añq³_o¶’RókcŽ=æÄ‰“»ví 344ZöíÒ¾}ûݹ{/"b÷æM›«ª*55µ.\ˆïõôôô÷Ÿ»kçÎÊÊJƒ/³Ç·³³K¼uk×Î]ëÖ®­¯¯711™9sVKY¹»»_º³'b÷¼¹s† s>qòo=D°§OŸFGÎÉySYY©¢¢boo·té2GGGIÔÕÕ—.]6hð`UUÕ/ž:xÐÓÃãaR²¾¾PÓàx*++CB¶ÚÚÚ7®c'Ñ9ÈÛù¶‡ÃY½jÕw ¾³éÛ·ÕÈ••UýlmùC4451 «ªªjk;ììP»ÌÚÆ&æJ¬ÿ¿3gN€’’Rô‘#Ò.—ì’¯9U'yµcÅ OetK=¢iw"‘\EÐV Ö+èj`¨Flh€úzB}=Î ?|Ætb. ŸÉS¦Lž2…·9oÞ|þ½'Nœl6!‰DÚ¹/2RÈøÐ¯Ÿm³ …7›Õˆ#FŒÑl>¡aaò0ÍBðu: 444.XØ]»{Eyť˗æÍ›{éÒ%këÿëupppøÚ‰wwwwqqõ7öø±cAÁÁ—N§Ï›;·®¾>îê5‰Ô¾Âw"òv¾ípôè‘òò²õë7H» jW­zþüùL_'§A .PT¤Ü¸~}ÙÒ¥ L™:UÚE“Qò5RÕ>œzV^ ã×»Ÿ1 ØLnC=«Î¬¯£74°è Lz“Îä(h–Fâ. "íîTMœ8qâĉ¼MÏñž#G޼q#Þʪ™IèÍrtt477ÏÌ|*üA™L†¿ÿœ¬¬—×®ß033k[‰;!y;ßv(--ݽk×λ8FÙLFSUUå-˜Â£©©Á‹†£UU AÏ„èbP»FXhˆ†¦æ/çÏã­ÈÍÍ­¸¸xãÆ ^“'‹ïq·š|TµÏŵl±øú¥¦  €†¯hŠa$¢UØïQ¤«ÒÒÒ"‘Èm]J”Íæ¿h“Éœ?oþŸWbãìììÚ^ÆNFÞη}òóóV®\±rå ^àÕ«qW¯Æ]¾3rÔ¨Fñ­¬¬2¿}`×ëœ×¦¦=…YÜ®k@íJHïÞ½³··çï—Û÷·¿sç&o7‰ Tµn´{_úi÷HŽÁ`p8œŠŠò'OQ(ŠÞÞÞ2d³ÙüŸPÉÉÉyy¹Ó¼§ s 6›ýýÂÉÉI—cbœœœ:RæNAÞηÝ,--¯ßøæ–_ŸÜW¬XiÓÜükÏß~û-==mØ0g(**JIN^ðuRf—‡Ú•ðŒŒŒ²²²X,–‚‚òäÉ555uuuéLq8‰„Fª¤£ª àÿ÷kiu?räHïÞ½dè?g•Jµïo¯¤¤üòå‹3§OðJr3!²²³àî½»:Ú:úúúøc‚Ö®]“˜˜èï?·¢¼"!þË—¨™¹Y¿~¶-­s“·óm·nݺ9;;ó‡H$}==^àƒfÍô=íí=¼§O>°hѦM›UTT"ŠìÖM}Ù²¤Pti@íJxß/Z´èûïgúúÌ›7_AQ1!þƃû÷W­^¦ 5å7{¶±±ñž½{ÑH"ï:Ø© ÚTS[ýéSÙ¥K—/^|ôرþöö-E:lèÕ¸«qq± C__ßÇÇwý† ÚÚ_žÉáp¾ûî¿;~\·ÆŒûëÅ‹ðäï¿àܹ³çÎåÅY°`Á®Ý)¿Ì’·óŒËåp8¼ Ó qqWƒ¶l ÚÂd2‡ rüø ù¹ïµ+áM:„#G¢—.]ÂårÍÌÌÂ÷-úúO ÂoÔèÑq±W:ÁH*`oB¼Ã$¯LÄDZÇE„‘%ÔÕ4Q±øúð¾Q£FOòš´ÿ§ýøíÇÍ \¸¼¥½d2¹¬¼¢¥½)©i-íê’äí|E(ïã7Oò9jT£v¥««{ôØ1ÉJV vÕ&S¦NE÷ú cáÂ…øÂFRZQûúÓ1ÒêÙ UW"ª¥>ÉdRï^½ Z]üAéz$±¢ú·Ý'ß„™/hÝNñ‘Öqqhw/ŠÍfóoÒh´/^˜šš¢N‚ ˆêôwÿÉøH•No­þ£Ì9œ¿\,.›ƒÕ3¹ê*äWɪÞW‰®Œmp÷®?Ξ8ñ׿3r¤Ù AF»v5~pWµ|ù *•jem©¬¬\\X|õúÕÚÚšE‹¤].AD :Áœ*Ád|N•ñÀžd]&° @À;Vàã&›«®BÔÓTø§°aÚ½‹ñÑ{ô[¥häH³~Ôé:UíRrvq¾•˜˜˜øN×ÔÐèïà±;¢oß¾hŒ AD¡‘*ñ÷cQÍg‰@"p9H ã D øDU ©¬ŽXÝÀû³0·°F˜<)ƒÁéXñ‘o´»4Ó×w¦¯¯¨rCA:54R%Þãö0PZ?Íã²0  0.›@$Æe‰d ãr@À€¼ïzUqs™ìÝ;ÖÏÏnöì¸;FÙÙéŸ:•ùÉ`g§¿mÛH77S …ôôiñ† wSR>âIlmõBCG b¬««òùsCbâ?ëÖÝ)/¯çå9c†MXØ33­÷ï+ƒƒ4:âØ±½BBÜ ˜LNrrÞúõw_½*Ãw??ÍÊJgàÀã¼ÈΫª¢O™riÿ~+†m€ÂÂcãHè P7Aé84R%Þã*PTÔªîÑ î×TÖu3ֺʲ¢j=K¨*þ”W¤gkÏ­(abÝH Ê*®•Ñ-壩©´¿ÇŠ·_¿.SR"€½½~ZÚ‚§O‹çνV[Ë\´hÀÝ»þÆzò¤ŒÕ_¼(=uêiUÝÌLkãF—ë×gº¸üŒç6~¼ÅåË3bb²—,ùMWWeçÎQªªŠOŸ~éÑcž˜8ûÖ­&O¾¤ªª6"-í»þýæåÑZ*.,,‰D"ÌÛßÎî°Ùm{Z ‚ ‚tjh¤J¼Ç%#)sX Xœê Œ¬Ífи%\’:ËdÑ*¹šÚ$f¦J&[|…BZ¼øfZÚkÏDDŒ).®;ö: ÷ïøóÏï·lq›:õ2ܺõîÖ­wxÌ´´üÌÌ⬬¥¶¶z/_~€­[‡?{V2kV,>@óÏ?Ÿ33ÿ[Ì-<|ä›7åS§^Æ{E…ïÞ-_¿ÞeéÒߟìçÏ 4ƒËÅrs¥3ݾÝÐH‚ Òq’©ñ’ 2>RE"‘8õ´¢ôúò‚—ï ?±0vC] µNÉã0òž¿-«áVå¿)z×@ H-?T·¾ž•žþ_JQ‘4b„Ù•+¯ð`$$¼uqéÁ‹<üåË%ÕÕéô-Ož€¥¥(+“4Œ‹{ÅëH<{Vòöí—…•”ÈNN†±±¯xãLEE5ænÚ†ÚélÚ½¤B«¤}f‚ ˆäHbªou©uªZzå!—¬¢ÑÛÕYÝȲ÷X7‹¡V%uƒF½ ¨Úgüèžv½´-íÌG™s¹\bËuSSÃäÿ‚ÖÒRRP ®[7ŒNßÂû rÓÖVÁ#><~íÚa'O>=úœƒÃÑ‘#Ï~ÝPCC‰H$””Ôòç_\üeSSS‰H$|úTÇ¿÷Ó§ºîÝ•ÛYSêT!‚ 'Å9UÖñõÔ¥=RÕÒ+‰HÀ¸\ú‡§ô 2³{)+¿ØÆô™ì÷ÀîÆ,xÖðo [ËŒQýAQË‘Ôòå¿Fh4‡ƒíßÿøÔ©æOöl»½{ÓÈÀ7mmõøÒÒ¹\LCC‰?¾†¥¶– UUt.ÓÓSåß«§§úùsþžÍæ¾-&ÞWCA9'9Ux߉×ê"#UB"‰³ªžJ hTèV±Í]_‘¯]É1àTêÖz2ëIŸË¨@Àa;Ut:ûáÃÜ#ÌÞ½ûœ“SÎÿD"AQ‘T]ÍàÅ÷ö¶á½oh`ÿýwÑèÑæ¼*U­_?=^ÎþY8}º éëÅH*UÍݽçǹøfaau¼é_þƒÁVPñ¯XÐH‚ ÒqhN•xHe]k%² Ù`š¾UP4 †©XÑ€¬d0DMÇ”¦b iàD ~¤ Ö¬ùÝÒRûÁƒ¹ÿûŸíðᦓ'[nß>rçÎQÀåbwï¾ÿþ{G ‹î ÄY³ú|sá644Éã÷ªUC(’±±ú/¿Lã_ø*8ø••N|ü¬ñã-¦O·ùã9 ;"âË#Hcc_ik«„„¸kj*YZj_¾<ƒÿ.¿ìì2…%K:9ÚÙuŽ‡Þ—••Õ‰ŠŠŠ´ÏA‘ÉÏ©ê"wÿ ‰H"²ê?×ýóŠñêÞaåLvumý»2¬¨–]õïž±ò蔬¢VY‹NhK‡óùóR'§!!îû÷{tï®üùsÃ_FEý‰ï?ÿúñã“^¼XÂdrÒÒò§OIMýŽ—61ñŸ™3cÃÂFìÚ5:?¿zïÞtþœïÜyïéy!$Ä=.·Åâ&%åúúÆòÖSÈÌ,™7ïzHˆû† .ïÞ}Oâ¿üwãFÎñãOÂÃGvï®\TÔ Ö©rtp8Ⴘ‘’œ$ÖCt%¨®„‡êJx¨®„‡êJxMëÊÕm8Z§JŒÔ(E]»nÌÍ>Àea IÒ r8, ,É}ˆ\#Ø‘›Ë"+™X¨V²›ÍdíÚ?Ö®ý£ixNNùÌ™±Í&).®4é"Ê¿““ÍÛüÂùó¢ª%ôgF†ÏŒé}m¬ ôõz™›ù̘þgF†´ %ÓZj9IIIšœe !Ó–””lܸÁÓcœ±‘¡®ŽvþÇ‚ËP^^¾tɋ޽L{˜øøÌxûö­ÈO³ƒP»ž0ŸWÿ+C#U"QËW̘áÃÛüû!<,lô˜1¼³gϬ]³ÆÓÓsÛöíd9+;«¢¼ß•ñøñ¬Y3}osê´iöv¶111è˯)Á-7ÜÝÝÚÚº­i zóŽ=Új§*...;;ëÚõ...xZ§¢P*ÉCíJx‚?¯¦:ÁH†mm-šŒ|ãUöKþMÕqÒZ¥“Ëå^‰‰2dhÓ/V¼ðë ÃV¯Yƒï%~ûè""‰H"‘ÔÔÔ¾l‰êÝÔI¤Î·Újûhkk“É â›×© n9<õõõÊÊÊîÌh¥Õ x~V¿ß¾E¥Rñºº¹%&&ÊT§ªÔ®„Ôôó iªÓÏ©º™/Ú ‘.ÆÜÜ̼WoÁq$9§Š_jjjAAÁ¬Y³x!ÛØØÄÆÆÚÛÙêëé::ô?ÍËÇŽ?…BÙ´qcaaayyùáÃQ99¯/é`É8:^WW—›ûáÇ×Q(Š~~]sX®ƒ·ÜxOÓ&¦=LæÍõÏÍýЦ´BÊÉɱ²²â±¶²ÎËËe0-%‘Ô®ÚªéçÒT'© Ý¬Ž´Jð,uQY¾<ð?îL:eçÎ]B&¹tñ¢ŠŠŠ×äɼ’’’’’’È}{ƒ·†_¿v-88ˆÁd®\¹¬mlb®ÄúÏñ;sæ4())E9âáá!ŽÓ‘ÞÞÓðyÄÚÚ:—.]nö"¸å¨««/]ºlÐàÁªªª/^üäÉßµµµTªá8q«V­ÖÑÑ2ó®!rß¾öç}Ì€”””§OŸ¬XÑæÏ_ÙÑñË÷ï?øóÏ?6·ÚÚÚ›7&Mš¤ªúß㫹\nmmíÉ“§F ÎÎÎó?F:H"‘ž?>Ó×ÇÉiЂ… )7®__¶t)S¦Ní`ùeÙÞ={iÕ´’â’ŸþÙ××'æJl£y´Ör¾Ü(íîîîââê1nìñcÇ‚‚ƒ[M+Å“+Ô®Ú¤ÙÏ+„ﯬs?û¯M#U¯¿ÊÈÈx͇·™ÑÚµG¢£§N™üùsÅÆM›Ž;>ÃgFÌåË#܇¿{÷®cçщ¥¤$bãÆ ]{:ˆµÍ!C§Lwõjwmípž#E‚[N#ŽŽŽæææ™™OÛ‘V0MM ÆB«ª"mÍJÜP»j“f?¯¦$ÿì?)Ï©²¶¶¶¶¶èÚšV++«799ü!¯s^›šöTRRjkVƒÚ•0šý¼Bš’üœ*iŽTáÞ¼ySQQQ__Ïår+++ÿý÷ßgÏž¥§§'&&^¹r%""¢¥„QQ‡öìÙË;LÏžf«W¯ÉÎκÿ²dñâÑ£Fò'œìåå?Ç·ù*;Ûoöì^æfÆF†ãÇ{>~üˆ·kkp°µUrrò¸±cŒ ƒƒ‚nݺ¥«£ýüùsþ ½§Mmtˆ–’·zÄÜÜþsüz÷27Ð׳¶²œ9Ó·¶¶V˜³ÀmÞ¼é§ÈÈêêj|Q8;Û~ò” qOT™•uéÒå­_«¾ªjéˆyy¹gøø4êÁO˜0îÝ»Ï ¹w÷®ŽŽŽ‘±1eee±X,ÞÞ'Ož¨©©©««·¹^:‡Ã¿YUUõ÷ß÷êÕKZå‘e‚[›ýÍsE“““óòr (LÚ6ñðð,**JOÿ²phQQQJr²çxÏ6Ÿ8¡vÕV-}^!üð¿2ÉTImNîòåË–––ÿþû/™LÆŸO‚W‡Ãa³Ù¹¹¹?þøcVVVÓ„†¥¤¤ 6 *ç7a„  -)É)£G·¾$ZvvÖxOO;;û¨ÃѪªªçÎõž6-ñÖm{{{<V½eó¦í;vöéÓ‡A§=s&ò§Ÿð>|HII‰Œü©Ùü%oõˆsüüÈd…CQ‡õôôÊÊÊ’’2™Lak`íÚu\çÒ¥KÉÉ)@"“;ž§Èµ:Q]ðu:8NHðÖY³fZôàYšâ IDATéƒg‚ —Û¥K—0 kzÍèÑc\\\V®X^\¼ÁÄÄäúµk))){ö~éÇ¿hѢ￟éë3oÞ|EÅ„øîß_µzuWøâ7{¶±±±­­ŠŠJþÇüóÎ×ÔT¯Y»VÚå’E‚[Žÿœ9T*Õ¾¿½’’òË—/Μ>mdd LZ Ãn&$@VvܽwWG[G__ÐàÁðàÁƒY3}GG{{OïéÓ£D,Z´iÓf•ÈŸ"»uS_¶ìiUK³P»j«–>¯~ø_Ù¾ÈH9ºûïýû÷$)22²W¯^?~ÔÔÔÄ¿X,‹ÅÊËË[·nôíÛ·iÚšššššSÓžMw„‚B¡n1 Ñ×׋£P(àêê:vÌèÈ}{ÏžûÀd2öî݇`áüýçï@?®[cÆŒýõâEÀ¸\‡Ã›ü   w5hË–  -L&sÈ!ÇŸµûþP»jŸW?ü¯LŽîþ€ñãÇ×ÔÔ€³³s= ÔÔÔH$—ËýðáC``àåË—y%mÅývH¹YL&355uÙ²exÿ¸q'NžàÅQVVv4ˆ?Õÿ½{÷ÄÆ^™7o>“ɼxñâŒ>***Í¢QrÁGTVV¶´´ŒØQ[WçR…¿ª«ûö­ ZPX0û³»kk¿ÊÎ>v쨂‚©S?óÖYðòò:°DÄî%K––••mÚ´‘zMXxø„ñã§L™<þ|*•J£U?}úãb[x÷ä·`Þüï6¬ÿQSSsÊ”)m:_GÌÏÏ_±<Ðkòd ‹>l6;!>žN§» ÞêYð³´²jhh8}úçþý(ŠŠÝÔÕ[ÊSf‰tT ]õC‘SþÙ7}útá#.··ï}8$dkee%ôëgË»lmíEŽˆØ}ðÀ33³5k×2èÿ-ÂÙ·o¿;wïEDìÞ¼isUU¥¦¦–ƒƒƒ0³&'Ož¼aý¾3gò¦œ IÀ544 £.(( ‰ÖÖÖ'Ožrwwoõ,øyzzúûÏݵsgee¥AjZzKyJ…0ÕEÕ JOOan‚ Hç"Å»ÿD1M½JJJ>~üÈ‚?—†Á`¼~ýÊÊÊZÍÄÍÍÍíëóV¯ZuáÂùŒŒ?ÇÏÇ××—ÿ.ÂiÓ¾YþÀÂÂâĉ“ÍfÚÂÓîÞ½ sçÎP¶–’·tDuuõ¨ÃÑ-å&à,V¯Y³zÍü=‰DÚ¹/2’·W@žROTpuƒAŽ3¤êßLˆšÙúý’Ží½ø‡ñ½Ju¤ŠJ¥¶‡µôVEìÙSP¿8`Q|ÂÍþýûw h‚¼}û67÷î;ÆóhuöÒˆ0ÕA¤ãžá€y(ssªd_ÜÕkÒ.B‡:U‚ ˆ¨p„X \TP§ ‘(INTGAIvª¤¿N"oZ]QAAD…ó-cccN¢:©B$J|+ª#‚ HSü}&sss055}ÿþ½8Ž…:Uˆ¤ ž¨^VVV__/¦C·ôÜkA¤«âuªøoØ777ÿçŸD~,Ô©Bdˆ£ƒÃù Ä}ˆ”ä$±¢+Au%rÄÃÃCÚåê|ä¼!µ$hË!c†oÛÖñáN"sÚ}ù 6¬ß@¥à! ŠŠBæF§ÓçÍ[W_wõ‰Dj_º¶£G”——­_¿AÚé¸\nmmíÉ“§F ÎÎÎó?F:ˆZW#ÏŸ?Ÿéëãä4hÁÂŠŠ”ׯ/[º”„)S§J»hˆÕÔ+Ô©B$J¼Õ1 †Êÿ€'arc2þþs²²^^»~ÃÌLš«½Ë¬ÒÒÒÝ»víܹ‹ÃáÐh_¦0™,¦ªªJ&£O’otïÞýýû÷®nn¼÷áî÷ïÝ+(È75íÙ(²¦¦¯Jq´ª* ¡¡!¢J]XhˆÆÿÙ»ó¨&®6À7 »¬Š²£ D"*¸ãˆ»ÕVE­Öݪu©UQ@­+Ô}·*®Uq­ØEå,ÖªP­Å * ‹¬$ßÓ¦iBC˜Èï9œœÌ;wn†y3ofn&¦¦GŽ¥ö¢=zäää|õÕ’ÁC†(BRšøŽ¤îS2j zƒ®¢¼¼\ùÌŒÏçOš8é·Û·O~wŠËå6hÇ4WVVVyyù¼ysÚ:ReeegÏÆ:µuL¸ß&“Öîï¡-ÿî„B‘ÂfÉyËuuu}òß {œþ¸uë6zzz ÚI†xöì™{ûö’yy‡Žòòò¤ò¨Uß‘À}ªàƒÕÐÕ !Ÿ|ò‰·····÷çŸÏÉÌ|¡x zuuõgS&'$Ü8~â„Ü[¿¥]»vç/\”üÓÕÕíݧÏù y^^Ý;Æ &„\»v]\ríêUsss[9' ÌÎÎNJúûÖ ÙÙÙ‰ Aêéj£³±±IMM­ªª—Ü»wÏÐÐÐØØ¸{¥‰šøŽ¤•6ÙʃûTÆ«u ºÊ—ÿ 'N˜ØÑ‹g oðøñ£|òÉØØØØ–-[Ö´ÈÂ… âââBC'äçå_ºx‘*tptððð¬i‘¦ÉÈÈÈÏÏO²„ÃáX´j%U”€€~þþþóæ~ž“³ÄÎÎîü¹s‰‰‰7m¢®gÅÇÇ›:uêgŸýÑĉ“´ut.]¼ýúü/¾Àà3Y"‘èûK—!©i©„«×®š·0·°°èìëKšüŽ$E¼­È?—ÿ222%ëdddà•áçrRåîîîþÏÓºvíâãÓyìØOŽ9:o¾œ¯\QîݽK‰‰9sX\8yòäuë7¨ÖB‹ÅŠ9rtÍêU›6n,**jÛ¶íö;GMÍ …@ü—ÚÚÚ±±gÖ/ [Îçó»té²wᆭóu­aƳk×®3gÎ …«V­ž:mZc÷‹‰äœ/ZDéׯÿñ'H“ß‘¤PÛjÎ矉1Uüñ‡xÄíüÑc­TZ)3P.žžööö©iÔI¼Yãϱb/2ñëÔŠ­[¿AnvÞ§oßܼ|É’–-[îÞ³G]]cœ¡Ã†á»~ÊÐÒÒ’Ús¤4ñIµ­"##ȪSwóJOOo ÑëHª@ÝÔyGu@@‹Æ@³HåOiii ÷}@$U VŽŽµ~õO娺ºZò;D·oß~ùòåÀÀHªš,uÞRI¨[ÃÝQ}Þ¼ù­Z¹¹»éêꥧ§Ÿ>uÊÒÒr~  CRMšÊ'–¼¼y?\ù1îJŸÏ7774hÐÌ™3MMÍp¦  ÉBR¬½£úÄ 'N˜HWkðÀÕáCVëÕësóÏZo M„§äÏ$=v̋ǫÿq¦ ÔJ™êõçííE9zìX­5½xRûy]ßó¥f¥¥¥1$£"Hª@ýrê8PýÁƒ\.I4A~ÌÃ#ô‘ÚÏëúž/9ëÁƒÌɨ’*` ÙIIIÁ@uµINN&„xyy©ðèÕèÇ<<â±A©ý\å÷|ÅÕ’*P«: TWÎTÑ.88øÕ«Wª=5ú1xlÐGj?ÇÍ?T‡;ª3—ËÍÊÊRí9ЫÑyxÄcƒ>Rû9’*)¨îÅã­Z½zÜØ±Ê7XׄР~ÌÃ#ô±áÞó;æÅã©yªCRê–Só9*êÇ/;¦L;TÀ(Y@ãxñxVÖ¶÷îÝóööÆ#?ÐdžzÏ÷âñÔÿkÊ„Vxx¸¥ÏÄ'ÙÅe|!dåPóšª=zÌÕµ]Èà¡jì|¥=¼|9nÜ8éÏ"Ö6ã|1Á/BHTTòÏW3M£ƒ$#£¨_€=uTZ–ôí·»D¢x¢ð\@ÄùŸ¯r¯š²†»<ñc³7Ù²5-¬ôpªS;„©EäÖU]ãBÄ;ó™3g=¹mÚ´á°Ùýõâç«×¨lçâÂãu011Õ××#„”””de½ü%éVII ÕBÈ `gg'BÈw§N{ñxm***îÞKNNNñôôðñédldôömîõøø×¯ßˆ×«r¤--­ÀÀÉ)¿IÍ­µ·LÓ”ã‚QTZÕ:PÈŒQ<)µxpP`uuuYY™©©©«k;]]S§ÏèüsBHß>}Z´hNyýúýÅ ç,\88d›Í …ïÞ½333sumgoos¤¼¢‚Íf9ÜÐÐ’››Ëb±ÍÍ[˜™™íرÓ²Y³f æêëë‹WzìØ‘!C‡ëèèˆKމ>b¤j½Rb3ƒ"5~ä9þ)—ó¦¯à0ö&;ãÇ„ÔaÉÂÚQùþÐꨣ£# µ´´œœÚæçå]ÿßÿôôô-­,­­­KKËÞ¼ycddlllìîÞ¾U«–ß<,„>}z·47'„öêÙÃÔØ¸#¯#5ËÒÒ"pà€-[·›˜˜BÚÔ5RÄ[æûË—‡ øã?KÕQ¾· ˆ æCRê&uŽJ6rê4 Wjñ„„„eË–kiiE®Šìààà@„B¡P(®PUÅŸ9cfÚ£ÇÖÖ–' o6›ýöíÛÐ r²sÜÝÛûí·ú^¼—ã~ppt¤r¦µ_}âäwFFFyS>411±¶¶V0×ÀÀ@¼R¡PÄf³¥º*9Y§^™iÔ\h× —'&|<@¶ðð‰ä=²³ötþáÆßÇ=¸ñuäØSú2“d;µÖ'.Ô)5-mþ¼ùZZÚ§OײeËN>6lÜØ½GÏŒŒŒ“'NüøãÚÚÚúŸ}öÙ„Ðñ-[¶¬¨(ãp8ZZÿŒÞ¾y;îD$ú÷|6›Í VÜÛÚ·ˆz5‘¸`>$U nÊÜQ]ù¹R‹ÛÚÙê°X,cª¤ ° ¢òßÑUUÕææ-©¥Ê+ʩ¸¸¸%K¾’íFUUÕãôôKß_ª(/³µµswo?|øpc##+«Vþù¬yóŠçŠþ9ªèèèPÇ cãÇÃþ÷ò_Ýz¥§§'UÈa³ ‹EþùñQñÕF‘HD]ýäp8„" $.†j"M9x\¾žN9ÞdgÄÅ“à>®Šû£L¡Êq¡N"‘ÈØØXª–V–TŽ’šš¶`Á‚œœœ^½znݺ•š+¨HÖ …&&¦âP`hhÈf³Ù¬3$©)R’SRzöì%9HÀ¢U­½eZÄ5‘¸`>$U V²ÕeՌà IDATi@®”™3f^¾gjbBÝßrû×ÛzºÿycxÍÍÍ{ÿþ½‘‘Q@@À°áw©_E000pvv216NH¼Éa³=¹žFFF………oß¾¹ýÛAƒéêê63LIIé߯¿‚¹}û”–”R+êÖ­›@ èÐkd$ý%#z%wñ!CBlmm !»÷ìãóùîíÛ÷îÝ“ríZ|jZšÁgS>%„deeÅž=¯Ñ'º4â2Ç÷×SGŽA}ݾ¿FÞdg\¾NõuS¹·¤~qÑèÄÉMzúãêêê12bø0õ¥¶ªÜ=VµH‘’óÇÏœ$W¡Lo™qM6.˜I¨[ƒToÙÒüË/‰'Ÿÿܹ ž\w×v®mÛ¶½{÷î;w—,ùRj‘š&‰¼WÄb±TˆÙµ¼ÍÍ}òäI»vÿŽïáWUÕÚ[å·Œ õ…FÄó!©µ’¨N¯‚¤[·÷í;ðâÅ_"‘ÈÞ¾µŸ¿‡ÃùáÇŸ®-¾úç³g²‹hiiýúÛo»÷ìÍÉÉ®¨¨`³ÙFFF––\O®P(LHHLúå—·oßóù|‡cjjêçßÝÝÝ]ñ\ªñÌÌ—q—¯Ü¿ÿ{EE…‰‰)¿ªzÙ²°œœlj®j½’ûÂÏ_¸tùûK¯^½¢&SÓÒN>u31‘âß½{EEŦMÑ11‡ !666õÞÌIî[íà~׳©¹ir ?$²;ó•~üù矞gü}¦ä]AÁéÓgo%ý’——§­­moo?wÞüøë× !\nÙú`ËÖíöï#„P_‘Ú«©ju”¤[¿nÞ¼å÷ßS!ýúõ§ ¾z}áÂÅ/^üEþ¹öWko™qˆ †@RêÖ Õ !úúú}úö•m3  _@@?¹]27oÐOþ,‹%~û®ë\Š'—ëÉý÷ýݱmÛú÷J®A!ƒ%'ÝÝ=ÜÝ=Ä“úÓ¦ÏP¾5Æ¢ëDÓÚQ¦áÕewæþÿ½©……ÅÐaÃ%K\\Ú)¨¯¥¥%µÓJíÕ”ºFŠo—.¾]ºH ”*©µ·ŒŠ8¦íÏMö„:’*P«†¾£º‚Bø00íM_mI•¦ÜQÓög$Uj¢øWÿÂÂj¾Æ^³Ÿ~¾ºhñb¹×ÑàC×[-ÓÚQLµ¸€¦ƒiû³†¦Dõ‡¤ ]±×¿ÿÒ´o5ÞD0í“´:/ÿÔ„iû3ÎT¨íwT‡&¥M›6÷î݃²®îÞ½Û¦M¶# qÊcÚþÜpqÁ|Hª@ݤªÔI›6mîÞ½«ò²Œm >˜¶?7Ù¸@Rj¥Ì@uÅè¹%ÓÚ‘„¸€ºbÚþÜqÁ|Hª@Ýj½£: .4’*hd8xÈB\h"$U V’ÕoÝÒ]ûujVÒ­[JÿLÀ‡Í±­c·®]©çwîÜݳç !qMœÜ¸ÐHª@ÝäT§ŽÔÏ4ééO!âã‡Ä4MŠã‚ÉTZÉT§PG6[ë^ò½ú´ïíå- “SRêÓ€zxñxÁÁA—/ÇÕtð@\@Tk\0’*P77S¸—|oÞܹ*&‰D›·l!„Ô§õ vWe~\qM‡òqÁLHª€YX,–P(TmÙ¬¬¬ú7 âÝUˆ h"ê „¤ ÔJöŽê²D"QýWDK#̸`>vcwšj zc÷€f8SjUÓ@uIøD qÀ|Hª@ÝjýÕ?<d!.˜I0½·oßîÝ»75õá£Gù|þµk×lllêß>€š!.˜cª@­22žScÕÕ&+++..ÎÄÄŒÇã©s½L†¸h8Sê&÷Žê’èýDÞ±cÇ_~ù…s8æöíÛD$ÂÐDˆ æCRj¥þê,‹z.""ªÐDˆ æCRêÖhÕE—ãàšqÀ|S@œ©µjÄ;ªã2h4Äó!©uSó@uÉ"‚ƒh,Äó!©µjÄ;ª‹0v4â€ùTºÕ:P@!©Æ¡÷¹H$ºvõ*!$ýi:!$ñæÍæffæææqÏCÐ(ˆ æCRj¥þêÕÕÕ .—¯Y³†Ò½{÷mÛ¶Õ-jƒ¸`>$U nj¨ÎápRRRh-jƒ¸`>$U VÊ TÐDHª@ÝíŽêš qÀ|Hª€qpð…¸`>$U VxGu†¸`>üö¨5P½±{@3œ©µjÄ;ªh4Äó!©uÃ@u .˜I0KnnnYY™jËÔ¿õï®Ê@\@Q§¸` $U VŠª{{y=v¬>í{{y‹DÂz6 ^Êý& âš%ã‚™Tº)¸£ºH$¬gã"‘pPÈàA!ƒëÙ€$&ÜP¦âš%ã‚™TZ)¨žœ’2oî\‹¥Zã"‘hó–-8rÀq )Tº)¨Îb±„B?—gee©¶ Ã!.4îSŒ#R•lS¯_¿þê«%ØÚX·4o‘•™©xÕyyy3gÌpvjÛÚÞî£F=}ú´a^"@Ñ;vl÷âu¤žïß¿¿¥y êÏÆÚª[×.Û¶mªu211qË–Í’%ÑQQ­ííj]PÉjjöÛíÛéÞÞÍÒ¢U[G‡Füíömñ\ÉÍ@ARj•‘ñœ«®/þúëü¹sffÍ}||j­\UU5rĈ„„‘[·n{óæÍÁ!¹¹¹jè'@ãÚ¸iÓ±ãÇwíÚíââñõš5ªµ“˜˜°uËÉó–æíÚµ££ 3+Ó̬ùœ9Ÿoß±cáÂE/^¼6lèÇ»_À\¸üê¦` :…Æ[éøtîü8ý !dÏîÝ7oÞT\9666--õÜù þþþÔ²>¼·oÛIWTÖ ·˜òóówvv&„„ Ü/ ïû—.[Æáp”oϯÔÑÑ•- :¶Žª×È‘£FŽ%ž6|x®ç©S§<=¹Ø+`2œ©µrttpl뤸—9Øì:ìá?þpÅÊʊʨ!ÖÖÖÝ{ôˆ‹‹S¾€†Cc\(Àb±¼½;•––<~ôhâ„P÷öV–n®íæÌ™ý.?_\såŠíÝ\ôïgkc½",lÙ²¥ßDGS¹žDæºÞ£´´ ¡ã]œ¬,-|:uÚ¸aƒÜn««+WW×ï¿¿¬¸Ž¶¶¶›«[jêCõt 4’*P7ª‹þ®F/_¾œ”ôK·n~„ìììÄ„„ÉS¦Ð¸ •©9.„BaUU•‘‘‘¸äÒ÷µŸžÑÕÑ­ªª’;KGG·k×®±±±ó硯¯_c ºº~~~7o&. Ó’êÞp }»t‘*’÷•(,,¼{÷®»{{µõ 4’*`œZ """Ê‘+{ðŸ~:I<¹xÑ"BH¿~ýŸ8A …@|×mmíØØ³aË—‡…-çóù]ºtÙ»w¾÷ Ac\(iÛ¶í_̟ߣ»¿ŽŽNg_߃)^$0004tºµk ,--$UÀ85<8ŽÔÍ9×ø .˜I0²̧¾úø€áL0>‘ÈB\0’*`<d!.˜I0²̇1U4À™*`|"…¸`>$UÀ,¹¹¹eeeª-k``@ogq TƒxñxrÁ¾N-$&Ü «?L€¸ÐHªàCÓ½GÏÆî€RÔ™è .@Shô$UÀ É))óæÎe±Xª-.‰6oÙ2(d0½½h\ˆ M¤ ˜…Åb …BÕ–ÍÊÊ¢·3 ¸Ð¸¥0ŽHU²Mݸqcú´i;p­,-Ú»¹Î™3ûõë×5­÷õë×_}µ$pà[ë–æ-²23¥*äååÍœ1ÃÙ©mk{»>õôéSÌeÔÜZÿƒƸرc»¯#õ|ÿþý-Í[P6ÖVݺvÙ¶m«@ P­“‰‰‰[¶l–,‰ŽŠjmoWë‚JVS3;›ä6CR²è¨¨'OžŒºeë¶ñ¡¡—.^ *))‘[ùÅ_?wÎ̬¹ìܪªª‘#F$$܈ˆˆÜºuÛ›7o† ÉÍÍÅ\æÌUü6nÚtìøñ]»v»¸¸DFD|½fjí$&&lݲE²Ä¼¥y»víè裺)ÞÙäÂå?`ïÇíää$žôôðœ4iâÅ >;V¶²OçÎÓŸBöìÞ}óæM©¹±±±ii©çÎ_ð÷÷§*ûtòÞ¾m[Dd$æ2d®âÿ ¦kÐûTùùù;;;BBîÐ÷ÀýK—-ãp8Ê·ÀçWêèèÊ–‡†N @[GÕHñÎ ÎTãÐx™C2£"„øvéBÉÉÉ‘»^6[Q8üøÃ+++êí•bmmݽG¸¸8ÌeÎ\ÅÿAMGc\(Àb±¼½;•––<~ôhâ„P÷öV–n®íæÌ™ý.?_\såŠíÝ\ôïgkc½",lÙ²¥ßDGS¹žDæºÞ£´´ ¡ã]œ¬,-|:uÚ¸aƒÜnŸ;—ʨj®Æ¿yóæ¬Y³¨ŒŠÂb± ¸oÿ>jÒÎÎîõ›·Ê¿4uBRŒSÓÁ#<<ÜÁ¡xÒ­ëÜyó~¸òÃðÃ7ÈçW††ŽOM}xîüÕzejjRTôŸKE……,ËÄÄs2÷ÃF{\HÚ½g­­¶ŽNëÖö-Z˜S…_~¹øü¹s_}µ´“¡¡aQaaPP`Ee…x)CCCåoIZTT,Zµªå„bQQQUUÕöíÛwîÜ%. *ßåAeMyg•!©áàÐFrÒËÛ›òæíÅKñùüI'ývûöé3±\.Wåµ»ºº¦¤¤H–úht~^þ¥‹©¿ÔÔ‡ÔÜøøxK‹V±±gÄëý»BZ*!ä굫—.^üíömj#ÝÚ·Ÿ6uê‰ãÇ/œ?ÿñÇcŒŒŒgÍš¹Ì™«ø?¨éhŒ e°Ùì={‰‰ÉÈȨªª:{6öð¡Cµ.ÕÎÕµ¼¼üàÁoSRR¥¥IÍ ˆÌ}ûvpÈ ³gcùå—cG~õÕÙF"W­zöìÙСCbcÏ$%ýråÊ•5kV¯^µŠš›••eiÑjÍšÕª½.å)ÞÙäÂå?`œZ•••sçÎ++/ß·?›ÍVPÿÞÝ»„˜˜Ã11‡Å…“'O^·~!D$ ñOª ‚O?$®¶xÑ"BH¿~ýŸ8AÑÖÖŽ=¶|yXØr>Ÿß¥K—½{÷‰¿q†¹L˜«ø?¨éhŒ %mÛ¶ý‹ùó{t÷×ÑÑéìë{ðà¡àà Å‹††NX·vmAA¥¥åƒ‡©’s¹\nÜ•+ëÖ®[´paYY™Ý˜1Ë6âîîñóÕk6¬_¶tYaa©©Ç›2eÊß³E"@ ¨øKˆÊS¼³ÈÅ ·ô™ø$»¸Œ/ „¬j^SÕ£G¹º¶ ‘ÈB\0’*`–ÜÜܲ²2Õ–500 ·3 ¸ÐHª€A¼x¼£ÇŽÕ³…Ä„tõ€ šI|hº÷èÙØ]PŠ:Äh þ€¤ $9%eÞܹ,KµÅE"Ñæ-[… ¦·W q )T³°X,¡P¨Ú²YYYôv€!·TÆ©J¶©7nLŸ6­c®•¥E{7×9sf¿~ýZÁªóòòfΘáìÔ¶µ½ÝGzúôiƒ½J€º¡1.vìØîÅëH=ß¿KóÔŸµU·®]¶mÛ*Tëdbbâ–-›%K¢£¢ZÛÛÕº ’ÕÔìÆâCýµsqϕ܌œ©‚YtTTqqñøñ¡vööþùlÏîÝIII7n$ÊV®ªª9bD^^nDDd³fÍ¢¿‰28$!ñfË–-ÕßsuÚ¸i“µµuEyÅÙ³±‘……a+V¨ÐNbbÂýûçÎ'.1oiÞ®];úzÚV­ZmccC=×ÑÕiÜÎÃ!©Æ¡ñ~}Â#"Ú´q ÊoFhšTÆHNNÞ¹sGzú“‚‚ƒ¸3gÎòòòRrñ‚‚‚ð𕞞ÜÔP¡ÐÃÓS²ÄÄÔT$ZXXÔ·÷ C…¸prr’ÊÊËËKKK+*ÊcÏÄ^»v500PWW·o@@߀ªBg__OOnw¿Ç‰?–ðù•›6Euöõ·cdlÌf³íìíå®7|åŠV­,.\¼¤§§GéÒ¥«Üjáágbc©3Ý»wïß/ :jÓá˜#„Âbq86G—YŒgΜÕÙ×·Y³fÜß¶ukàÀÿ»‘@½!ÈnF$UÀ85}"ùò¥‰‰é”ÉSš·hžŸ—ò»“'N8yò¤››ü3O’***&N˜PZV{ö\FŠ0DCÄ…Xß>½ÅÏú}óÍfBŸÏߺeËÅ‹²²²øü*jî³gÏÄI•¾¾¾OçÎJ®‚ϯ¼uëÖçsçRUÍÕø7oÞœ5k–øô0‹Å0`à¾ýû¨I;;»×oÞ*ÿÒ”$JJJÄ“&&&„Çãñ¨’^½zùûw8 ÿÞ={Ôv’ 4’*`œšƒ 4hx20(°OŸ>.\tu­e )Ÿ_:>5õá¹ójªfjjRTT$YRTXÈb±¨·W€ÆE{\HÚ½g­­¶ŽNëÖö-Z˜S…_~¹øü¹s_}µ´“¡¡aQaaPP`Ee…x)CCCåoIZTT,Zµªå¤oQQQUUÕöíÛwîÜ%. *ßåAI)))ÿ=‡›—/[ÇËËËÑÑ1%%¹A{ Ih*333G«Ö;"òùüI'ývûöé3±\.WAMWW×””É’Çé[·n£øƒ5£(R¸ÜÔ·ÿ$9}zÖìÙS§M£&?’3þIy&&ÆZZZoß¾Q\ÍØØ˜ÃáL›6}ì¸qõY]]¹ºº~ÿýåZ«UW T¾µ=4Hª€qÈ­¬¬ùùyûöÐÕÕ1b„‚úÕÕÕŸM™œpã»S§|||¯wàÀÀË—/'%ýÒ­›!$;;;1!aò”)ª½ zÑÊ …UUUFFFâ’Kß_ªu)]ݪª*¹³ttt»ví;þúúú5¶ «ëççwófâò°0-™¡î ÇÐÐúv°¤êêjÉ>$$$¼xñ×ðÃÕÖ+Ð8Hª€q &OžLO23k¾k×.'''õ.\:!?/ÿÒÅ‹T¡ƒ£ƒ‡‡'!$>>þã1£wìÜ9bÄHBȈ‘#wîÚ9mêÔ¥K—Dmdd–mÄÝÝãç«×6lX¿lé²ÂÂSS37E|ÎX$BŠ¿„X']»u={66öLee¥……ÅGþrÉñ˜3YHª@Ã8ÿóÛ[}û„ ÙüÍæC‡ÖT9ñæ/ šêÓ·¯ÔpÔ–-[îÞ³‡–~¨SâBÒ”)S¦ÔpÛÂÂâØñã’%’ñ)ûc'*::*:Z\òÅ‚_,X žôððgÎçêY|Tã(yÙBK‹ãÔÖéÉ“tÜéšÄó!©Æ©é` $o1U\\üàÁg< )@\0’*П>×ÊÊÊÕ­¾¾~Ϋœ³çÏ–”¼Ÿ:mjc÷  1!.˜I0NMŸ°ýüý®ÄÅÅÅ]®¨¨051éÈãmX¿ÁÝÝŸÈ¡)@\0’*`œšcF3z´’•>0ˆ æCRŒƒã€,Äó©ã‡¾>x8SŒƒOä²̇¤ Yˆ æCRŒƒƒ€,ÄóaL p¦ ŸÈd!.˜I0KnnnYY™jËÐÛ†@\h$UÀ ^<žÜ_°¯S ‰ 7èê .4’*`oo/oo¯z6bemKKgÔàQÚÃZë . ©Q&.˜ Õh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €Hªh€¤ €ZÝBql똞þ¤mÛ¶õoª   þ¨ÇåËqŽmkš‹¸€¦Iq\0’*`„n]»B._Žk쎨•c[Gjç— qM“â¸`2$UÀݺvÕÐ(h8ˆ ‚1U4À™*hLEEÅwîÜmì^0 â@C!©‚F#ųX½» "ÅB’¨¸ÐuHªþùÊÆë ||CƒB@mªI¾‡ªÑÜïq(¯n—ÿð= QNöËÆîmðí? ©¨]FÆóŒŒç˜Ädœå!©PŠ••••µ-&1Ù'@I¸¥@í$1˜ÄdÓ™å±ÂÃÃ-}&>É..ã !+‡š7v—4@Äù=räÈwïÞ­\¹òðáÃ< „hiiÙÚÚzxxhkk‡‡‡[ZZ¾yóæêÕ«+W®lÑ¢…2½ºÿ¾ŸŸŸ——ׂ ÷îÝ{þüù¤¤$oooBHM-+^JvEVVV–––S§NݰaÕ™7oÞØØØ¬_¿~Á‚Š[‹‹‹4hÐG}4}úôÜÜÜ¥K—–––Š·Pr²_Þ»—œœ’RkM/ÏÛÛKù–U¼üwôèQ@0fÌBȘ1c6oÞüÝwßMŸ>½®íTVVîÞ½ÛÏÏOîÜÈÈH__ߘ˜jÒÃÃÃÕÕU²BIIÉÖ­[ûöíKéß¿ÿ7¨¤ªyóæ&&&l6»M›6TͲ²²´´´ƒ2„*ùä“Z[‹ˆˆèرã‰'X,!ÄÙÙ™ÇãÕ²õš*£ [¾¼Öš«V¯&„(ŸW©8PýðáÃ\.—Jq|}}>¬B;ݺu“;«¼¼üîÝ»C‡—¸¸¸´oß^²Ž¡¡aŸ>}Ä“¯^½ªiEíÛ·ˆˆØ¾}ûÇE"‘ò½âóùñññ£F¢²B‹Å ¹y󦂖/UÓË }õêÕõëשÉ#GŽôíÛ×ÊÊJqkÔ¶1b•QB:vìèââ¢à54MJfT„qcÇ*sBKL•¤êîÝ»= )üÇàÁƒýõ×§OŸÖµ)###q ¥¨¨H(š›ÿgŒ—Ôd³fÍ$×ÒÒª®®®i]qqq={öŒŒŒär¹ÖÖÖëׯ¯)µ’êUAAAUUÕÆõ$¬Zµ*??_A˵.%÷åûûû·iÓæÈ‘#„Ç'''‡††ÖÚj[YZZJ6eeeUÓ¦hâJ¨k›ª\þ£NJ­Y³fÍš5’å111«W¯V¡A¹¨ëwyyy’…ùùù†††ª5غuëC‡B?~|àÀ%K–X[[?^™žp8œyóæMžPÛª¨¨H²°¨¨HåmðaS!gªUÏTñùü'NøúúÆÿWÇŽ9¢ø²ZèëëwêÔéüùóâ’§OŸ¦¥¥)¹¸®®nUU•ÜYnnn›6mÒ××OMMU¦)==½^½zÅÇÇ;99¹þ—‚–•_JÊøñãKJJΞ={ìØ±áÇÔÚj[]½zUÜHNNŽ’¯  ’:)ekkÛgª._¾œŸŸÕ«W/ÉòiӦ͘1ãÿû_ïÞ½ëÚfMV¬X2aÂñ·ÿ,,,¨ܵrww/++ÛµkW§NtuuMLL>ýôÓQ£F¹ººVWWŸ9s¦¼¼< @ÉžDEEùûû÷îÝ{ÆŒ666………¿ýö›P(\»ví‹/jjYÁR Öåâââëë»dÉ’W¯^Q×þjí!dåÊ•ƒ úæ›ofΜ™››;qâD]]]ñ²?ýôSPPPLL 5æ ‰“Ì™ !­[·ÎÈȨO›u>Suøða##£Q£FI•üñÇúúúª W¯Ippð‰'n߾ݯ_¿eË–…‡‡·mÛÖÄÄD™e‡ 2uêÔ°°0__ß   SSS[[Û¨¨¨þýû‡„„Ü»wï»ï¾ëׯŸ’=éСÃ;wlllæÍ›×·oßÏ>ûì÷ßïÑ£!DAË –Rlüøñ¯^½²±±‘ÌP·tòäÉ={ö÷êÕkäÈ‘]ºt/+ P(Tòõ|ØÄ§£¨ŒŠâèèXŸ3Uu¸ùg£ßc÷Ý»wööö«V­š?~ãö4TddDØòåïÞ½#„È““žžN=)**:zìØgSj­úÍ?Õ¯¤¤dÅŠ}ûö577ñâÅúõë›5k&yE @Ô‰(¹cµUÃÎè¤JKKëéÓ§GÍÏÏ711éÕ«×ñãÇeïP' ñí?F'Uzzzø• ]“Kª’*0âæŸíùóç Ñ,ÎT@âÅã=vLùÊÊ·Œ¤ 4Ûû÷ï_¿~Íb±œœœJKKµµµutt»SЩžTݾ};:::111//ÏØØØÇÇgÊ”)#FŒ ±sbׯ_ÿí·ß–,Y".Y½zõºuëJJJzE .|øpøðáÖÖÖÚÚÚ“&MÊËË£fÍ›7oÍš5EEE,‹ÅbÙÚÚBV¯^->”R«KLLìÒ¥‹žžž••ÕÚµk%;vêÔ)777===sçÎ 4HöuÉ]‘¸{={öÔ××—m\Á‹’²qãF]»vI&OmÛ¶]¾|ùýû÷øáªdܸq:u’\°W¯^C‡Uf²[þÂ… ,ëÞ½{’ H­¢¦Åk]ãŸþ9tèP333--- ‹   ÷ïß+ó*(r·yMmJúøã @ñóó3”°jÕ*9›¾.ÿ&h ª•P×6U9Suýúõ=z4oÞ\AÂÂÂyóæmÙ²ÅÍÍ­¢¢‚rÿþ}???//¯Ã‡îÝ»7 ))‰ÊÃ^¾|Éår'Ožljjúüùóµk×:ôæÍ›„+V‚Ç?xð€"•ɉW·`Á‚­[·º¸¸œ:ujÆŒÎÎÎ#GŽ$„\¾|y̘1ãÆÛ½{w~~þòåË ¼¼ä\­iE¥¥¥cÆŒ™9sæ²eËbcc—.]*n\ñ‹’$‰¨í&û3;Æ ûâ‹/®_¿TëÆ¯uR[ÞÞÞÞÎÎnÏž={÷î¥*<{öìúõëâÉzþㆠ¢­­}èÐ!KKË7oÞ\½z•Ïç×ú*ÄänseÚÜ´iS``à„ Ž?.þ9Ì®]»VVVª°Ñ ©iˆ3UuNªŠŠŠŠ‹‹W«¬¬Ü½{·ŸŸŸ¸dñâÅVVV?ýô“žž!¤OŸ>;w^½zõ¹sç!TM???çááñðáCOOÏæÍ››˜˜°Ùì6mÚ(XÝþýû¹\.!dúôé8}ú4•÷DFFúúúÆÄÄP5=<<äþ$5!¤¦•””lݺµoß¾„þýû߸qCܸâ%©¸¸¸¸¸ØÑÑQv½ööö,+33³æÍù¯Z×(»å§Nº~ýú¨¨(###BÈÞ½{ŒŒ>þøc¹í×éWVV–––vðàÁ!C†P•¬Ì«“ÝæJ¶icccooOiÓ¦øÊápä®Eù4vŸ*ƒnݺ‰'ù|~||üÂ… ©£!„Åb…„„lÛ¶M\aݺu§OŸ~ñâ…ø´Ä“'O<==•Y©©)•QQ^½zE)//¿{÷î×_-žåââR×†}úôOzxxP×ú¢êD™[»*³F©-Oùì³Ï"##;6}út>ŸèСqãÆ5kÖLî*êô300hß¾}DDDIIIÏž==<|øž={!±±±¹¹¹Ó§O¯iuýÇÅÅÅõìÙ322’ËåZ[[¯_¿^$ÕúB£·Í:ý›àƒçíí¥dªäÅã)?¢Ú™ª>}úüøãïÞ½S<¬J’‰‰ ‡Ã™7oÞäÉ“åV8vìØÂ… çÎKM>|øP…ŽÉ]/›Íy§äç狹׳qÅ/J‹ÅêÓ§ÏO?ý$»Ý.\¸@  &µ´´¤rqrY§5Jš1cF¯^½nß¾½gÏž®]»*yþO™5¶nÝúСC„Ç8p`É’%ÖÖÖãÇWð*jUS›J.^×—Mʽ{ÉJÞªjÕêÕ„†½OÕâÅ‹+++§N*50þÁƒRß2ÓÓÓëÕ«W||¼“““ëB„B!ŸÏ766ו\\WWWµeúúú:u:þ¼¸äéÓ§iii5Õ¯ÓŠ¿(Y .¬¨¨˜={¶d¶ñüùóÕ«W{zzЇ”ÙØØdff …Bj²¨¨HœbÖub={ötww_¼xñ7œ¦ªÏktssÛ´i“¾¾~jjªâW!EÁ6—jS5*o4ø )óÏqcÇ&§¤(ß²*gªºvíºyóæ¹sçòx¼ÐÐÐ6mÚ^¿~ýôéÓÔI¹¢¢¢üýý{÷î=cÆ ›ÂÂÂß~ûM(®]»–ÍfìÛ·oÈ!mÚ´9sæ u­JÌÝݽ¬¬l×®]:uÒÕÕ•>U«+V„„„L˜0aòäÉùùùaaa5ݪ®+Rð¢d+ûûû¯[·îË/¿ÌÌÌüôÓOÍÍÍqâÄÊ•+Ož<Ù¶mÛÈÈÈÍ›7›˜˜È­\×)~Q²/^ìííµhÑ¢wïÞB:vìøÓÿÛ»ï¸&Î7àoHH¨š(¢‚¨8XE,q0Ô:T¬,P XQA©e9«Uê@‹E°Õº°Øj¥¥Pke•!;ðûãì3.—dø|?þA.ïÝû<ï{÷æõîr¹r…ÃáàeLLLŽ9¥§§ÎáLÚq‹-òóó[¶l~¿6I5~ôÑGC‡‹‹+))¡R©ãÆ;uꔽ½½Ä,ø ´ùãÇÅmS‹Åâr¹ÊÊÊøccã!C†H•B¨½½Çãá'Õˆ_èºbRE‰ˆˆÐ0÷zú¢¶¡…‡Ú2OìM܃5‡Š{«©ªªÒÒÒÚ¶m[```wÇ‚V­ZuèС³gÏ ?SîŽ=êå啟Ÿ—½|ȾøbkxXXcc#ÿBmmmì‡:ø½|ùòxjªÏJ±·äný®!¤Ì Ìz™{¤«©ÐsÔ××oÞ¼ÙÎÎŽÍf—””DGG«¨¨xzzvw\!”””TRR²dÉ’~øAä#Îå"??ÿùóçáááÎÎÎ0£лgª°§Hjkkwf›}RE£Ñ ?^YY©®®nmmýí·ß ?Ö¼[Ðh´ÌÌÌ®®eíÚµ?þø£……ERRRW×ô ø¤J___8bĈ¢¢"™·Ù÷'UJJJøïìfeeuw@Ï‚Mª„/àèëëȶ;?©€MªD>eIæ{ØaR€Nz¤@ï“*9€I€tŤJ–ßþÃäää899±Ùlƒ¡««ðêÕ+þ  …ÿú¼¼¼•——cKüüü(ŠÌ1Hë÷ßïŠÍÊx¦j÷îÝAAA'NŒŒŒäp8>üꫯÒÒÒ²³³G%r•»wïΚ5KOO/33³ÿþˆ@F¦&&ÇSSÉ&¿eYÎTݺukݺu‹-ºuëÖêÕ«,X°uëÖÜÜÜææf‘¿’vãÆ {{û1cÆ\»v fTè.ytQaYÎTíÚµ‹N§'''+(¼“éêꆅ…^ºtIàg•³³³'NœxþüyUUUj—ð°0’%·EF’߬Ôgª:::²³³­¬¬„éûUàììlþ…—/_vttœ:uê… `F€ÞBÚ[¯¤>SU[[[[[«££#ü–––…B)--å_¨««›žžÎ`0„W¡Ó銊ŠÒÆÐ=ëÛ$9::>þ|çÎ"ße±Xjjj]? ÒnSê3Ujjj,Kä ±?ÿü³££cذaü wíÚ¥©©¹uëV&“*°ÊÀ‡*m Í™´µµ…ß*))‘m›RŸ©@=ò”_IDAT¢P(¶¶¶?þøcUU•À[ééé![[[òûöíóôô\¿~}||¼À*¾¾¾¿üò‹´1tv.ª¸¸X`yqq±Ìgªd¹üÜÔÔäëëËÿô„âââÈÈÈñãÇÏž=[ <…B9|ø°‹‹K```JJŠ 5È>s***½×Ë¡)S¦DEE…††–””xyy±ÙlìáŸt:ýÔ©SüÏYÀQ©ÔÔÔÔ–––5kÖ0 oool¹¿¿bbb[[› aȆÎTPP0zôè‚‚‚NÞ½.ãÕCBB¸\n\\܆ êëë‡ âáá±aà ±5Ñh§NúøãW®\É`0ÜÝÝÑóDc‰ÀôãñãÇŸÈþƒÊvvvvvvüüüüüüø—Ðéô .ð/IHHHHH9tÅ9Ù'U½Lªä&UrÐ+Ÿ¨Ðs˜š˜ü™äã©©¦&&ä· gªðárMBÇSS%–451Á “$Ťêïeä ôL\®©T³%’àò€À¤ @`R 0©˜TÈLªä&Ur“*9€I€À¤ @à·ÿzŸÎË{𠻣ú&iãO˜Tõyø¬\ÑÝQ}Óƒ‡ä2©‚Ër“*9€I€À¤ª÷)¯¨022~÷ .6ÎÆÆ¦»Ãy  !têôéY³f›L¶œ,qÅýûLœhý}÷Þ½C‡‘¯T\y<˜'Naÿ¸\³¹sç}ýõ×<`› ø*ÂÿæÏŸ/nEâáO³3øÛY6Ò6ò{ÎKÚðd ñØé|#¿OÝÞré2òYð—,//ßµÔc©™™¹¡¡Ñ‹¿^¬HÜïwîÜ8اM³’*{”Ï*?ßµd6"ÇCCŽ›zŸ ù¶"Öuƒ ܨÞûääd÷ë×ÏÈØ(;;»»cyØ?ÿü³sÇNWWG'']QªÜ»{÷䉓+V½1_\y<˜‚§OBaá› ÔÜÔ|éÒ¥={âkjjüÅm“Á`ÄÅÅâ/#"¶jkky{{c/UUYRe$wxj2oAÚF~?ð¼»=¼Î7ro$sÈeêß¿Ÿ®îi×*+-˼œ9nì8c#£{¹¹ ³n]°††ö7]Êá‹€\ö(9¹rÜTozhmmûyPPNÎu|I`ºråÊܹó¸\óyóæ]½zU0<)‡;⑹¨°0 `êÔi¦¦\G§ää‚MIÛeHÔˆ$\† B‰MÜV˰ÇÊœ©êenÞ¼¡¨¨8Ébö²±±)$$ÄÅÕÍÇÇ'++kïÞ½ÚÚZØ9•Û·ïøúúN™d}ê·©Xá§K—.300ˆÜ¾]Y™yæÌYŸUÇŽ3Æ@ 0£ñ†“§L MLJÐÕÑUQUAýóòŸ‘#G~<ÿc5UVÙ_:tè³€ÀoŽÈnõ'Ÿ´óxçÏöÌ„•F%®ZdyáVð×_/h4šªªê´iS544ÒÒÒ¶lÙŒ½UZZzïÞ½-›7‹k‰‰444n\¿qÕ*mm­œœë±±± Zºt©ÀvˆÛÓÿ3*U122r{@UeÕí;·[[[E¦V[[¿çË;·®“••¾«Ž 5»évÂÖsò262Ù§µµõ111!¡¡#FŒhij×AüjkëcccCׯ®­y%3rÛvüèàwðࡤ¤¤ð°°ç,ÜÈ1™Ì˜˜¨%K–&&$øWVVmÚ´ÑÊjÚb7W2M*ÕHÐ×ï­ ÄyJˆ;”äˆ!‚1ãééùúõk%%¥É“-?ú|ØÐaâ65Á|Âæð°ŒŒŒÀÀ@55µY3g:Íq6?^¸$ÿuãæuëBfÌœV]UÿeSc£Á¬¤´Ãqw?y’ïå奯§¶‰ÃáüYògá³"$×.‘ËGHÜÑÄm%1`qÄ}pÈLªz™kÙÙ–“-éô7çTÖ¯_?qâD„¥å¤û÷ï_¹r †×Þ¿›F£!„ Ç;88~}øëMa›ðu7lÜhnf†242œ6Í*ëÚµïϧc'l^7DlÝú÷ß<!´{÷6›½ÿ>¬ê &¸»»ïß¿/>>^ 0:>€ÍF äp4‡¼Ù¹§L2eêìoc“Ñ£ æÏŸ_TT¤¯¯ÏŸºººªªª…‚¯H\µÈò­„jjjjhhhnnºxáÒÍŸnÚØXcï.\¸ððáÃÁÁA***¡3igTT”g;Ì×þill ümmm  361×5ÂZ[[¶FDè‰rYärîçð£ÓÑÑsöÌÙÝ»w‹kdâ=ôhƒ€ÿ¸¸Ý=ª @ýbëV’M*ÕHÜ×ï§ ÄyJˆ;”äˆ!‚1SU•µÌÓÓØÄDYY9?ÿÉáÃ_/õðL;“Æa³EnŠÉdÎ7wî¼¹ååå.^¼qñä©SZZZÎÎÎNŽŽC†ÁKòïQ)I)£FމަP(!mm­E‹\ñû$¤îˆ÷ÏÝqql6ûðׇ BÈÔôÍÃ-åØeB#’`â‰;š¸­$,ޏ¹€Ë½IkkëO7oÚò}{EYYy„ øK=}½^½Bµ´´üöø±½½=í¿—Ãá˜O0¿ÿó}þu±!Äb±Øl¶……~ |Ä„Ыòr¬ÞÜ{¹3gÎÀ …bee÷àq Gž’²oÁ‚“&Yr¹æ®®n¡?þøƒLÊÄU“i%„««›…Å$++›è˜˜)“§DlÙ‚-_°`AkKË…‹°¿K?ïäèÄd2eND‘F³âû ²Ÿ1½¦¦æù³"òI)))éêŽHNI>qâdQQÿeáÔéø@‰²œ4 ¯NªÔzZ^”””ŒŒ c±XØŒ 3dÈPìèÀðÚ;BC×g|Ÿ±ß>þ¹0dv?ËÉ–¾¾koߺ½cÇöúõ÷Cܤä@ A_óßu]@™ñ‡¸Ce1$7f"„Ædgg;i’ÅòåË“S’«««S§"„x<^mr8¯eËÒÒN;wnÆ ûsçÎ98:nÜŽç‚·mssóã'Oìí§c³„ШQ£µµµñ’R wÄå[[[~ÎË›={6£"&[— ï6eÈ|vˆëhâ¶"p·€3U½É½»÷ššš­¬Þ~0™Jø‡¢R©ímm¡ÚÚºööö~ýð¯> _ÿ‚ü|þuùߥR©üŸ¸*!ÔÞÆÃ¶ÖÚÖväÈÑ£GßÞÆÑÑÑŽ?˜@80Ûwìȼœéçëkhl¤¢¬RW[³ÔsYs³ä‹8«&2˜¨¨ƒ5ÓèŠC‡höë×_>`@Ûévi§Ïº,rÉÊʪ®®Z䲈 ‰‰¨°Xüg>ê×!TSS+UR‰‰‰ÉIÉ))ûª««ØìÞÞÞ E85Uþ{JXêjxuR¥ÖÓò¦¢¢Ì¿«“!ðÕ-Š¥ïÆ—†×¯øá®w¼á;×køƒ!³ûQ(g'§›7nŽ5;ÿ‘ؤäÀ7- ¾¯q]Ú䑈;TæC"qc¦°ñãÆiii=þí7„Ðo/õx{¥õáÃ_E®R_W[_WßÐØÈß›üm[WWßÞÞÎðNËp8ìi‡;âòµµõ<Íæˆk MÉÐe"F¤wËḦ £%¶•Ā߃ÁšCªà_“ªÞ$;;‡Ëå’ù²‰šKAA¡ºª’aeu•ººº õ²XªT*ÕcÉü¦i»qÁËkÙ%ØË¢¢"q%¥­šd0Ø·ÿ„¹º¸,_¾âá£GiigŒŒŒˆ¯/HLäu]]kk«¢â›ó««BêêjR%¥©©¹-rB¨¸ø÷sÿû_|ü—Î@gg'áÔêëêZZZðÿ–¿*篎|j=-/2hTªÀÍÄ-Í͈¥JruK566Ö××/$44&:ÿÿ.0dv¿ŠŠÊ¨¨ƒ‚‚‚Ôã©xʼϋCÜ×xR]ÔRµvçǹ·žlx<¢PBzººGWì?þÈÈȸxñRYYÙØ±cV¯Z5ÛaþŸ7=JAA¡®¾žõººze¦2’~¸#.¯¦¦J£Ñ**Ê…ßUX–.“¸ÛḦ £‰ÛJbÀȬ94ãûóiñ/„˽FGGÇõë9¶¶¶d ÓéôñãÆ]½zÿAyEEî½\s3sª¦Óéfff÷rï 6Lç]dkoookkSQ}»7gee‰¯‹Áÿ=⪅ËKÕJ333==Ý=»÷Ü¿Ÿø4™DZÛÚ®_¿Ž¿Ì̼ª®®®«÷ÎlFbR¸#t‚‚ƒ ƳgÏD¦ÖÖÖvíÚ5üååË—ù«—š@£õ´¼Â‡3hàß/_¶··c/ëêë ¥üèår¹ÉII·~º‚Õ(ŒÄŒ:::ÂÂ6ÑéŠì÷ððؽ'¾¨°I¹Ï“DÜ×$ÆIÛÄ­-|ØvfüéŠÖ#£íÝ”ïÞ½[VVfh8!¤¢¢bbj‚ÿà ÔÕ×?vÜÍÍmΜ¹fÍž•žþ݉'Ü—¸ã3*¶e0cÇŒ¹s÷^KyEÅóçϰ¿¥îˆË+*Ò¹¦¦/]jjjN¶ó]Ff°%ޏ£%¶qÀRí±2øoF%ÎTõ=*¯¨°õ˜‘Öøú~úé§Ÿ­õwswmjlJII¡Óé^Þ^²Õ¾n]§§×Š•+]]\XWWÿè·GíþþŸI LAAa¢ÅijgÎÚÚØ ÖÔ̺z5--M\a]]ݦ¦¦S§O3–AWÔ9’ jáò MMRµf‘‹ëÎ;°ïï#““Éܳ'¾¶¶V[[;'çzFFƺuÁ·#Âö|ñâÅ–Í[ìgÚëèŒàµµ]½’ÕÜÜlaa!²™LæÞ¯¾ª]¯3\'ëÚµÌÌLêD¦&ÜÈ=*/áðDvÇ ûé‡JNNöôô¬¬¬ŒŠŽ¦Iÿ-S“})É«?]³.8xWlì“'O‚!Þý¾9vìλPSS ðÏͽzòäIƒA~Ÿ'Ib_K ¸3]@ÜÚÂå;3þH5bttt`ŸÄO Ÿ"„nüt³¿~6»WúÖ­Û¾¾¾Û·G:88H¬7À?pà cÆ0J§OŸÖÐÐðøï$а¼¼Ÿ“’“¦ÛÛ›q¹"¯f ·íê5«ý|×;vÌÍ͵ªª:,,?Œ$íoÒŸ}¾l™—···§§'›Ã)û³´ ðé†õëEnJÚ.#ù‘D¡ÄŽ&n+‥Ýc‰³€Ï¨¸\ÓŒR¸ü×;ådçŒc0hÐ ’å'M²HJJJNN ø\QQÑŒk³KâwMÅ9rÔ‰ß&%'GG節­QSS7vìâÅn$Û¶mÛ_ÌŸ¿€NW461Ùç¹ÌKdIë… &&$ÖÔÔp8œ¬¬«U —wrr’ª•03gØïܱcÎÜ9ü_”-eefTtTôΨ‚§…ýû÷ þÒ;"lO–šÚ AƒŽùæåßSôõõv튙4É">þKáÔ”•™ÑÑÑÕ‰LM¸‘{T^Âá‰ì‹Ñ£ ¶EnKNN>|èð0­aŸ|òIKs‹È’ÄŒŒSö¥|ºzupP¶öp`2ÊÏÏßûåÞ+–›™™!„cb¢\\\wÅĆ…o"¿Ï“$±¯%Ü™. nmáòÈ·Ç Æ_b·‡O›:-!ñ+„PG{;ÇÃOWãš™^¼xùâÅ‹---l6ÛÙÙyÍš5ü·` 051½~ý:ñ !<þKA˼=ùËK5ÜI Ž;š¹-²±©iðàÁsçη)i»ŒäGq„ÄMÜVÄK»ÇgÁF%|O%""BÃÜëé‹Ú†BhË<ÑßÝèÀÁC>+WÌ™3×ÉÉiÕ*ŸîGP L¶`ÒÏŸ OOÿNÜ}W=pjq±q2rrrÖêù©õýç= Ó×2ë±Y÷}¸mûXjØç¬Äb3ªŒïÏg”j!„”´QƒY/sÀ™ª^ãüùôîA´˜´Áÿ^ZögbB¢••uv`újj½zÿé>̬ß>ܶ}85q„gTÂe`R>h;wîÌûùçñ††aa»;9ëéÀ{&rF÷TðŽöww²  ÷n¯N  îk@—7£¾§ © ÁŒ û»­¥/ “*Ę̀^ýñ[[Ë›ÇýÓÚ:÷ü+𘚘8x¨»£ú&SQ¿í-qF…ÝS5pøxjkk£ü†è!¸\S.×Tr9È™Õ`Í¡m-¥4ú›ßyäñx”ÐÍ;>åðW=jmë Ø:@µµ4ÒèLìÚß@C&“9Dµ½ºà’¼Ù6E9­­¤8 ð!{sUk3Î8|`4ºBˆÂkTlü‹õ*‡ÚöÚÆT !D£R©4ÍÚDkΜ9k×m¡Ô¿ñCÛà] ªÃñzÊÊÊ4J¥þ'û«¦qÒÒIEND®B`‚PyTables-3.7.0/doc/source/usersguide/images/tutorial1-general.png000066400000000000000000001371201416254111300250040ustar00rootroot00000000000000‰PNG  IHDRjyí pHYsaa‰f΀ IDATxœì]w|Eûf¯·ÒC ¡'@ éÕ." -‘òŠ ˆXà ¨T¥* ‚‚"HôB@¥C©!”´»Ë%×v~ìÎìÞ%¹$g0¼¾óýh¸yæ™gf¾óLÙ]túôiÞívó<Ïó{9ªpY »8}·)Ç)8Žã8¥Ëét»Ý»žÑƒê¶ì­T(jZK†û§ëÀ‰ã¶"sÏ6M”J%:vôhú¡³†ð&¡aQyV—ÍÉ»Üàpó€`—rH„Kúd?JG(_Š,—‹aÏhᲄÊ3#‘©¢ƈê& C#,–‹&Ô'ш‰=2Àœ,IC§ÊzHxHF ‰j…B!À˜š!ŒÅ˜ÉòD€‹™`RNA>BT}$J+­5¢v@XV.¹±Ë*©BŠ'[®› 4ã‘<…›%¼K‡1±BÆä–ÜDX24‰ ˜—äcáñR–¯¨-ÂäºTIïYÕ°GI©ra„±wÆ R¨z)oyÆ^}FÖþ½;…W(«s•îw^Y•“§W*Á¬Ô N©š Ñ«îܹQtçb÷Ô†J‡Ã¡Ñ™‚"ÎÞ,.vòP@€Ä¶€Ø:1„Ѐˆ¥’ ‘%HãFbû:„ð‡4zbX±ñérÊ,vuÒ§=(ˆ€<_ ­Il@’e‘ y1ˆ¬MÐî(Ë—]ó€in¼¬K‹ V’'&Å”D"žd/äÎKùP&¯ä„ „_Aê|b.´‰z„¬ ^b’×" Š`wc— ™í¸ÈÎ[ЉjB‘ïÑ–É?Ëj–c¤†0¸ÅzñèðcL‰@T –ˆË(Žæë]q²Ò! nÀÈÉ󤯋$Nl„0`Ò¾‘Üz²¾ €@›§ìŒcLLI•LläHd\„…ÁCÒMO깉t"$”MHI스>ˆ¡4eU Eà~ܱ8„D¹‹rìv»"6.) ¦}A‰»°ØýW¤Ë!4C„¨¸|¤z¨È HbLâKž‚T+²Þ!Æ ùHy":Ê „H˜P{È+6Á•èðMþÇDȲCBöȘdޤæ@z*M*”B´@‚‚b;‹F› ¢ cZ:ƒHHüÓÜ1ÉEŒMK…dBh«“Ê^i jÒ©AÅ¡ÓÙî"péP`T'…Á¨FA±K¬Lʺ˜ºRÇE”G°ÌÚXL‰ ÉD!Ò-(£ŠE#c ¾PG‰ZàÅŒ“aƒ=%Úx„þÉó¼Ûåv¹cѵ"VGÛ³‚C ¥¤ž¬Àµ<Æ„4ÄÒ STi-Ÿ˜’dØír¨ì]£BUTâ¾UàⱓCH­T·ˆ oãØ÷û­B·J¡PRORlA”ê¸Ü® ¥£l! ›×wüøû-³K¥T*#,έ¥i ¦J7Jr2d2ieIpðY>ò¬N·Û¨rtKª@…BYfp‘E¥Pn죵”ï¦å¸ë«c娷.H¾žÞ¶KOyÑ•}ú UÈ|y/£2¥âÈ$‰ÞŒA@Ö‰Ý síêT­‘ꉯ@µÐLäSÊy2M„R¶nJQ&BH< ‰ ™0Ù  Ò]2ÆF©:Ä€µÄ“oÇÄù•‹ƒØ”éüÓ¡žæDJ!Sß T2¢n¢d,A3Á•7¢a¨Ä…µJtê_`ÃM#Qa g.ÆdZNˆÃ“5„9%5Y›ÀÄå ɰ$•8àÊœ©Jë"Bޤšeõ&ŠÌc>@]Ü;©¾¹˜·Xùæ¢Cû¾Ë¾’Ó°Ej—Ç “NÙ;¥ÎG¯™ÝHÁ)j"ë$b3äÝ®@µ½wRŒ ÄZâæÉ€ÍqÈhW˜tʇSêìʼfá9qÆ<ÂÒ¬®‹PÃfDiÂcÚ"÷G¼üJžw©KJ®_¡2f7âÊòA¨·Q6»øÆµ³WÏd¤´»õÒG¯Ä®ýµãѺÚNzø „Î1Ÿÿç¦B…¥u޲_÷S¶Ð¥K\<ÈW¦1eÀdyJ¬YŒ©o ›vxµtqÊTšA€z²Åk:cy×!K]¤ÕÈi¨ÈÉ‹2ÈPš s$v ‘Fd¦¡ýœzéâ¨H‡¡Ç‹> uF(Èí¦²(ŸºÏœ¸jA,65›D`ÔBdX”ó‡Ì^•.£XêhÐTÄ3ÆX£‚a܉뼋ÇJ×8¿…¯çCýtüOÓ SÑX¤4@”Èn &EîÒø^u‡ÉE&g¶rú‘¼ÈR›,t] cì*¶vn[ÛjçÍ6§Íîþ`úÈ+NÀ¯é_þq8}Ìë‹1ƧêÒ2ò›ƒ×9½!ެ™ˆÌÊón·ÝÖ%1ZRìà9œBÊÔfw Bº&D~}èºJkDÀÑ‘KœŽ“Î@ÛY¹ÓB$eN[0æÝ%E]k[íü…‹—~øn]燇EÕîÞʾüõ®MõÚ¸Qî ‘_¼ŽtFK!U¦kg3>' n]¾’ÖîJZ»ßÆ>¬yrnÜëÅ”>hm 3…‹·lB·Ð(9R8—]$LUêÇö®bRë5Æ`)v]Ì)þñÜ‚b—°|BË®×($„4«cDçnmϼc³»°WÝ è`MÇæµb#t:5Wìà/åØöžÈ˵8¤9e Ü¿}d‹|¶ïF›Æ±z8Ÿ]´óèÝ"»ô{0²E=#|úãõš5ŒÔ[Š]K¿¿ŠÔ®¥é_«^¨V§áJüÕ;%ûOçÝÌwõ÷t»ÈæõŒ°áçìäF "tp>Ûöñ»6»èóE©Œ®¦Ó«¹bÿçíâÿœÌͳ:+>Õ."¾®ÖíËNm¡³–¸íNwt-­PÚ‡’ÂJ €Mûo^ʱµm$Ä_±ëZžÅ‰0Ž Ò´‹ ª*Zãòíâ§ós-yCLÊÎ-Bê†iuj…‹Çf›ëN¡=ý·»ÖÏ¥tºš#Ÿ@ˆ½—îD@DʵòV'îÐP¿]ЏÓ9|L2hÁZ‚yâø!²ÑI=ÙTƒÇ€¢ya¹c'zï䯌×äÄ!ó!鎈+‚_À «ž"sÑÍ.Œ°Óéh¡R©µf‹ÃéÆJ²šóUjMʃ½?¼÷ôoûÿu×]·ÙÝ!&MÓÕ¹|§Z©¡ì,äër¹šE(äBàÜñÃÛ>_üäÐI[¤€Ó!ÍÂUgóœjµF.8„$ ì ŽžÜ!í¶ˆö²ƒ#Õ¸\θH¥ ÌÏ»¾<¼÷«3Ç~ÿÖÊðè˜Û7þ\1{œµ0W§ÕÔ=%Ĥi¡:›çT©4¥º˜_K¬e¢n³6Âk7áÏk`‹«ƒ I?zCGãÈ'`¢W%4/¹oMê³N˜6&L§R Â"—˃ ªä†cz×U+È(ÁàNQ­c´*N£âê›Fv¯Íq\éq²I´~ìCu[Æõ…ÍîÖk õMÿz¨nD Fl³¢=‰{)0¨ctÝPË5*®eŒiX×h'î ú?_רQqB56ŒêY'®ŽÁ Upé5Šfu £zÖimÀ èU7Dëtaµ’kQÏ8¤KÇnex¶gøºFƒFQdwë5ŠõŒ£zÖ Ô€4@ômѬŽA£â{Z4¼ü‚ÇÇFê†w¯WG°¯×(š×3Žè^;

šQpçº( ±cqv ã [ò˜¸erÆdåŒîþ—0vÚ‹©2]T·^;×/žrétƺÅS”àªW¿a×GÊ•)¯µÞGò¹4£¬€wVÀG³,÷J|,@k“70² …°´Œê± B¦+øìµ¢Ì æ;vŒ€x()¬c|pA_×øÇŸ,KTPäZ¹û`Ð!2&L¬iUßxì¢E®B¨Ï ™m®v_Ë·9Ã5ÿê]O£âN]óã a&Lý/Çõ|vÑæ_sÀÓí#[Ô3Fkê›~»TH#¸y¼æÇw F%Ç¡ÇRÃ8„x [~É9Ÿ]Ô8Ú0àÁH¡ÇRBe[åëÑYÙE_¼žn_פic:~ÙòXj˜‚C–bךo¹Â‚Ô£zÔѨ¸ž­C6ütS¾ÝìæñúŸ²o:‚ Êì|{b¬éÑ”pØyôα‹ÙÑ ¹5¸GSÂùŸí»QXä PìQG£âº· ýü?7‚ j“N ?üv÷è3èÔ\l„Þæp{Í ËhBcÀ Mù;V¸eÁ.7\ËãÛÄ*ÌØ\ jr‹0`((ÂaFtÈÌNÜ 9N¢uDû.ˆô+ 3¢#"ãzUèÒÇ «DtbixäˆÑ°¸éF—ƒxÌ;íÅAAÆ¢Ìq¢o p:ì‹ÿoDö• AAMâo‚ǸVP€Ó~Õ­àœÐ©17%6¹Ü[׿Z5]¯Õt:Œù¯VM4~V­ðÚ’’+¼>@aò+ÍPš;ÈF&ñ;È."A¾S¦LDTíqo,Þ¸tšÕœ·ý³÷”×­WÐøÙÁaÑ^ÊpÞK"Dú(5” ÓÒú'e|× 6ød ¼ü,Ä5¥ó@í T*.P!Ì‹6!,xð.ݱÅÕ6¦4 0i•Jµ¢J¡j^<­#"ýxn~‘cØ{"od·ÚÐ0Rì¢E–/Žª¥Ð+!èÓ6‚† ØâïéÌaàeKipð\›ÇPÆùa¶¡ûí’™²Ìž?rÿ¼] V»»NˆVè{—oÙN_³À™ëÖ?oÛDèM:eD°æfžö…Cç „ª#Y…Âä"6Bw»À.H@¥†0Š ×!ɶ½Çsÿ¼]Œl%.zx„]ê@PdJ”У)áÔ'@1áZqV;Ïó˜ãPûfA‘Aš»ÇíÇÙÖ vâÄ®ŽÀíÆ×òqýPN§†‚"¸kŇþt·U”8±V… løZ>`@V;ĆŠÝè(*GœØ#ž:…²mLEè¨C¯1Yz–܉0Ù𖥓x²¤$+/º4#Ìïr9œNäæy¥l©â·#?Ü ¨]¿Ùn¹yìt‚Ëéà1Ï'ð»\N*¤àîÍï×½§âøÚuêwëûÜÞ¯WY ¾_÷^ŸQÓ‚B"E!.'yaEŒ—M!x„O¸ƒ@$¦¸Xª@Ù®y¹2U§ÏðI;Ö/À<÷xÚä°ÈÚB:Oe<¦)Tv¼Ë•Œ ™³ž€-¿Ý<½FÀŽ A½Â m‰d 2‘דÀ¡‚a]j7«m(J‰À³r ‹\Â4¶P\½FéYûÈ@ØÇ¤S =‡‚CH«âl7`^l¡´¿Ž*²»…Ë"‡8ó7hòùõÍ<;Hw£ÍÅ.q0Ä`¶‘„Z–1¦­Dl™T²N­ ÚuJc)mÕJ®Øé¦åË)pˆûFÒé2(P¾¸JÃyF-Ê×*ŒZìpi”œÍéþá÷»ÝBL:e«X“pËZâÞ´?û–°\&d>ÂPP ïð1µP Ž 3á³9üÁKn¼[8LŠMZȹy:A<&SwâAЮ,8"ÂÈÉÓr¾‘ ˆåv¸Èc$œ^J"è.®=z¶H‡?&ÜWïXMz­Z)õkþ­ €€Öízõî7––8ø»v qòMp„8 ¹tüµÕ©×{àËÁá!c¦ÿ°qaqQáå“¿¦tí'! 0Ò ýEVêR~™©!¹•d>õÏä%*̽y4}c É(LuŽìù¼VØËµ"h‰@¾h^ •¥Ë•Œe!i£õpô*$׃Âß\ÏVCýøÔvO×–ª€LØ<„ޤš0D‡j[²á?7ó‹\ñµ úFÓhrÕ Ê»fƨÕ.²»¼ô," ~\¶|ùK5"Ý…›¤Šô”Ô2ªîš€!Ø "Y¸åÓ¥ç6ŠJÄÜtJêUPÝJÜòÞlTæZp°^”ls¸­DÂÉ+Ö¯åxLÂA:&À%œ€–†#2œ"Éñ¹‰²ÙEkœ¼jýöÐ-© Šò18vÑ|ü²%º–6Ä¨Šª¥Il`Ô*lüõÁ[rÃz4mØ€Ë ùE8¿ÂŒ\Tz°¡Èª. 0'n¾#‡[ìéÂ|ÓNà! ñbfâm²xAXB\À@˜¦ e–Ü *è®2Ð…YÒ8©¥1YDAt„Ìr8¤ÈÍ+T*TZ5Gø„¤öæÜk‰t¡. q±Ý}7¿!N\OÏò`„BÒªm£Qß°E{C`„„E>1üÕK'6lù Ç$¤T—ý©ÒÜA§jÒ±&R29wˆvñPÆVx÷à÷iT]7¥Û€#?~Qb3ÚñQç§þ¥‘•¨\þ¨ìÒiÇ0®#¼³àµ^ðBgñÖƒO½8àõuu·¢‘=NmI«žd1FÈ ›Zvž=¿È‰ê›<¬&[›è™Rˤ 1ª»· B.Ü´y” ãì¼s± š×56ŒÒ Ì Q´môHrm6d1 N!E·–µj™TÁFU×µ„‹96ùÄ_ÒÃÍ|»¥Ø±úæõŒ*j^Ï®K±+'ßcôîÒ²V°QU˨îD$_ʱåä; Íê„ëùz­"µqPïÖ!žŽ•8_Ž­ðN—x7,@͉} K^=æá&•_Û®VôEjã ^‰¡â¸ÔÆ:µâòíâc—ÌûNä9Ý´j&SZ\jbËcñ?q^€D£Ü±âã×܇.¹Nçð—sñÍ|-.ÜæýÉ_ÉãK2%iX  Ñù&UNÈĉ ö ’¢ú¨c)­ô/Æ€y’µ˜X: *¤$ ¸€‰»‚@©Ñ˜ÍÅê©T œ«g]E·®ž¥!%¾ Ø_˜§Ôh×ì8w£H«Vè4 ƒFÁʵ8Ï\·fd™ÅEzn›øá-êÑ,n(•\AÅc|úšuó/9vÍëŠf”Ø%öÉ5;²nÚ …^«P*ÍÁ_ʱ}søöŸ·Š…<š×3…ª`ݾJhPñŸ¹^ôÕÁ[v—åZ\糋´*…NÃé5 „PžÕyîzQæÅBs±0Ä×5 2ΛmN7à”8xk‰;,@eÐrJŽsñ|Q ÇìäEWÿÌr ­©V”Fkò¼¼‚ØÇ€‘½¤ÈœŸ£Cŵc| ±#m@­(V/o Ò3øÄ*5a¡¼".›çDâÙóþ`/±™ónú¡ …FÉÕRåYãkòr++ããˆ4H­Ÿ†±wÖ5ncò‘@VŸXæ"Zÿä´9qD¥IŽGå‘EoqPl#{¼…ZIb_ºJ+Û—÷¾d3Ú2±œ\ä K-O:«(9Ô„­°lÉRÞJÈŠ´ÊÈ<]¨ZR™ÈC-™õpÂq5"‹ž —vÃ%S“®)ª&™ˆôWi#“®!c¯üË„LIj4‡ ß±à;A ì©GBÚ¨÷A4iTñX%™ â<×:8*b F¡gGdÅ.¯jÄpº€KÙ+kWk4¦ pKÁí“kØ 68jÝV)»Áa-*¼{éêM­)Ô®Öhx ç IDAT¦%µVg ·ú@…x™Ž¼¿C  êi?Œú$Xt«0mÄÖU¢ÖhýT¦|ÑNOSŒaÚ•õϾP •i3I  ²Ü…è1eiˆÇòáJ!óbñYXÚþe’¥…"i‘s Æ ’¾è-P"Io ’Äu>© Ò4A`Ú‰&´¿ËŽ& é@j˜Òa8ºYàM%Ôy“õ ™k!w¤m B$'ÙˆO™EL&fâIo¾!£™ éŽtVÔ®¬éFädØÓ¿é!÷?„_^”! ÉWA‘¸Z"múI¬AØDJ.½{d¶–X !itFÄ)Õõ[%³].;ïvsœB¥Òh Æ€°­!P­Ñ€°g+ÒH»ÎqZ‘„ääKB ¥R£5˜ˆ­×ž‹àaˆå¢mFQ= &9ûÒ`0öª‘¿¤Liøš¼\;›áþ!ÍøØºÐ†ÎV<…ÊZ-!Mâf¢È6ýE”d_J²´AvØNRÉŒ‚’ˆ4%ÎñRÊmÈ£çz8ÓR øSvH{ÞÊ,ªB4“œd)Gõ¦C™²"Òúe¾‹'!Ú®‘|½¤òÐ]./î’«¥Â½ÃJ§¤éeqÊ% ¯ûR¡$5=\±8ž&õHH¼:á‰dƒuð=ŒO„áÒ•%™”V„±¸Í,ÅE¼ÛårÚ»ÛeÇ<8N©Ô(Õ¥JÍ•õ`;‡ ôá;žw»œv§]¢PjTR¥)óÙV„hË'—׬XN¹;dJ+Ÿ° /{û§ …÷ä—Õ ê4kÍÎAåF&9—‹Q<›À“~ÎÓ¹<3/4$¼Hxҵȡ:Ò8r˜N¢"˜ td\ú+/V†@1„ÏÄ{ô2Dmá©wà…wyð ;5,k|Äõ:ŽŠïüÀèkl<ÊJg§˜DFÒX @KO\‘l„‬¹Wp‚‹#n/ѵ4Áîr•)Á‘FÅ{0†/rêR”(ú>ò™E©Í!a‰žŒÐâ<x„8ù (‘rc0¦ o%ý“Y `,Û°Ý;á¼-/Xžü‘¯b©ÝKvƒØv°LS±!z¶@z ‘´5qW hÅ‘ 0ÐÓˆb÷LJ0Obžê„Éòäý#ññÉ%L|5’mð”~°øl­\Ò}<ADé‡öj¢„¬Re#<'6bHxñâˆq%²Bd4¤ì€.sÊæ2ž>À¦bÝx¾ãI ÄÑ鿸p. ^.® Sb<’¢e!µ%æŒ1ViôRÕÈÎ. c[ezˆÃaW–÷¬*B*%Åét`bƒ§ÝFhM<¹–Z’UAèQ„ˆ€ól5þ@«V"Òl0ÂJ̇ÉYAÉ¡ÄÀ!2Ö“#‰%˜Ä‘žˆ‡yŒ$Ú¡ÞbùIO¥®&ñ€Iš7“z"žíy¤SŠº@{'µœÔºd»*Äùݡ͑¤&Ò0bï¦Bdêí<²FCR‘2Ié°¬O1‡LÑ}ò  U [~• lb©h,É; ³skÈäˆ#1–$aAâ«Qf”ˆ\HC: "Çé$eE¥¤¦,¥£ë1ˆäC=²%…á¦ÙIt—V A†Ü"ïSÑUEIO,JßüEÕ^•ÌÀÀð¿ ßCš¼0þ```¨žüÁ&/ ~ŸoÜþzðो—ª]•û 6hß®]MkÁð?cÇ~?zìh…Ñ’“’“’Zû¿J¨2}Üñè£T5á?;v|ŒAj Lš8±Â˜ï/Z$ü¨Rüª2H•éCàŽàààª&ü Y³¦gÏžcôÁPS¸ãŠŸøÌ3›¾øªÿžÓÃúõjZ†¿ 6¸ß¦ê²/2Ü“ø•£ªA£ÕŽ~î9y}xU ½U^¸×-ú.пžD^•$ {ƒDÅ·* ‡Ò͵2Iä—žÉq9I¼³)Oò’TNügÿ7/¼ðB™ÕZ•@(§øÞ‘}®XùÜO`ôQ5`;åüµê¿å©Ro|ªHž_·ÊÅÏ?WCÓšý U,¥dÞÇ+0à{W ÷!îÃÿ_¡S§N¯[·îرc¹¹¹jµº~ý˜N: øLpp­{š¯Xµê£Õ«W>|Èw4Œ1ÏÿÕ—¾1üÕ¸ˆÇÆð=8wºqã¦!C†œ={f@ÿþïÌžýú¯·lÑrýú &¾\­ùü½ÀUÀ®]» ”˜˜Ø¼yó.]ºŒ÷¯ýûTE€<¸råJ¿“ûiÓ¦5mÚôwÞ©PŸJj¸lÙ²Ö­[ ¿ß{oNûöíýSìï1H%küÍ7ߌ÷ üå—_›6mºk×®jS¦Òm°ªm¶²Íß“%îÕ©Ó?~ÿ}Μ9=zôزeËs£ŸëýPï'Ÿxbê›SwíÚÙ£{÷{”iyp:«mµ‚ǘ¯¶lÞ2a­V;íÍi .>|¸Ãé8p`%“—ÆÁƒ?úè#¿“û‡âââ;wj4šmÛ¶9ßúTRÃàZÁ6~ «‚þéö7¤r5ŽË*ˆ°cŠùʶ™ á£e~ýõ7¶Þ¦M›US?q¯&/Ÿ|ú©Z­ž>ýÿ”J,L&Ó°aiô2ëüù%,=v4ÓîpÄÅÇOš8!))I¸µ`þ‚í;¶/X°`á‚…gΞ 4 <ä¹çFU>í{ï½·dñ’sçÏ?Ý÷©7¦NÍÊÊZ¶lÙñ?ŽçtìØaòË/Uõ ®ìLrÍÚ5ñqq«V­¢ïÀOKKs:•gúÒ9ýœÇ–‰={Ò­Vëo¼ñî»ï8°¿sç.>ô©PC§Ó¡R©û÷ëß¿_,n'ø_¨¿É •«qñY²6ª}õ¤´´o¾ùj…„åÞ½í;~Ó¦ÍΞ=S]šÜïcœ‘q$9%ÙdòõIÜóçÏ Mf±˜gÍž½t金£G9}Z*›Ùl?þ”×^Ý»wϸ[²dIzzzåÓÎ;÷¥‰vîÚ9lØp¸•s«I“&ÓßžþÉê_yeòÉ“'_š8©ÊE«ôì%77/,<<]G¥R‰1þñǽ͛7?uê”üÖ¨QÏöïßc|õê•_|¡]Û¶ ;v;nœÕj}÷½wW­\e±Xš7oÞ¼yón]» ©Î=7~üø¶mÛ&&&:äèÑL*pÞÜy:u:}æôСCzèáÝ»wcŒ7lø¼wï‡Z·NLKK»zõŠïR|ûíw±±±ƒ ûæÛïhxi}ÊÔPÐáàÁCLLLœ3g.ÆxÅŠ)))‚¡ÛýþûïlÝ:±{÷îkÖ¬¡¹¼öêk‚M(†ñâ‹/”©@…)Ó°¾‹/¨XÐÏ)xËB}(Pa= 6y"îðºåÕå͚ŕ¥¿?¸'Þ‡µ¨¨¨¨¨vTmy Íf£¿u:BháÂ÷CCCW­Z©V« M›6ƒ^µjå"rÞÖét¼=cFã&M`@ÿß|ýÍîÝé={ö€Ê¤}kÚ´Ö‰‰4Ó;tèØAøÝ:1±Y³¸¾}ûfee5nܸòEÑÊÄlÙ¢Å/¿ølíg½ê&¿Õ±c‡ÈÈÈ/¾øbúôéBȵk×ΘþÓyžñÅ ÕÌY³BBCórs:h·ÛÇŽëv¹·nÝúÕ–- P*yž?þ\ZÚ°¸¸øY³féõú-[¶Œõܺuëâããc\\\üÆë¯fÌ蘘˜}û~š7o¤¥¥ñ/< Ž1žçý0H™†Õé*ø2I%k\è€^1…É O_ËS Âz¤6<~ü¸W¾ß~û]…º•F\\ü™3§ýHè…¿iãÖf³µm+õÞ»wo``À‘Œ#ÃG ú? „:wîòùÆ4šÉd¸C@íÚunݾ N§³Â´Z­¶Uk¸N§sõêOÒÓwggßt8„ÁåË—«F•vD§¾9õ•É“çΛ;wÞÜðððĤÄ>Oöiß¾=p×ïé~Ÿ|úÉäÉ“ lÞ¼Ù`Ð?ôðCÅÅÅ.\œùï™]ºtätîÜYøa0BQÑÑ¢&/\¸044tåÊ‚RSS‡ ¼jÕÊ÷ß_$¨j³ÙÞxcjJJ ´LhÙ¹sç=?îÙúÝw*•ŠŠlo¿ývvvvTTT™Eضm›ÛíîÝ»7Æø¡Þ­_·~×®ýû€€€/}ÊÔv:ÓÞ”xǾ¸¸xâË“„±V­[ßͽ»jÕª*•J:¢Ë+@)­€oƒ””””iØ k³Ò5^ÆLJ¼ÄcìC ë‘ÚP šËwßmõV¢,UË Œ‹‹?}úT%Êå ÷„>ŒƒÁ`ÈÎɦ!Z­víš5°cÇ÷_nþÌf‹ÓåZ³fíÚµëh4Œy·ÛM/ÕjOõ ñ.W%Ó z¯ïÍ~çvýðÂøñ ­[ô‹¹0mØp»Ý^¥¢áJoãEDD¬[¿þäÉ“G2œ:yòÀþ?ìúaÔsϽ0~<<Õ÷©+Wlß±½¿þN§ó»ï¶>òÈc4h°lÅrkQQJrRÃFh)¼Æ7§Ó™‘qdø°aJ¥’vêÔyã›èÀ®×ë“’’„KƒÁúÀ(büúõëÀ­[·#""Ê,ÂÖ­[7iÃó|óæÍ£k×ÞºuëÓO÷+SŸrB°V«m™à2ïC©TvêØ‘FèÞ£û¶mÛÎgoÖ´&¾†”€†TÉ jµº<ÃúF%k\^( Ợ‚÷Qž•©ÇÒ6ðä“O”fJâ¯sÜ£×!„RSS:l±X„åŽã“àØoÇ„8&“Q¡P 2䩾OUU¾iwlß1bÄð!C‡—YYYUÍ@I*]˜™€Åbyá…ñk>ýô™BCCƒƒƒ»uïöÕæ¯ú=ÝoÏž=ùùyýžî+HþàƒV¬X¾jÕÊ9ùù!!!C† >|ùœ­4’˜Íf—˵ö³Ï>[·^¦ïv»É Z­V®-ÇqZ­Ž† Ž·«ìÕÜÓ§O_ºtiÔs£Ìf³Ò¹sçŸ~ùò•˜˜zBnà=²• Á ×ë½=–<1M&…BA#@aA¡—Ÿ"ËBZT¨’AÊ7¬OT®ÆœBØ‘ t;]Àq  T¦½l(×ç‰'ߺu›\Ù2KàrêÔI?×;<Ý«ÉËÈ#þù?ÿþ÷Ìwß}ÇkóE€Z­NIIÉ8’ñÒ„—ÊŒà~¤åyÞårŒF²gÏž*e*WzçÅ F£ñ±Ç?qâäÕ«WCBB _¿~cF9~âÄW[¾JHHhب‘ 922rÆŒ·àÏ?ÿüöÛï–,ù 44üÑGñêKƒA¡P 4¨OŸ>¥””œ(¥­¬3`ì£DÛ¶m€Õ¯^ýñjyøöíÛžþy ”šYx1EY:xL^Àj±8•Jô4óóò À„1V(8¯…=‡ÝF#íWU2H¹†õ‰JÖxp­`ŒñíÛ·ÃÃÃi`έ ©åCž={Tµ½ôyüñǶmÛ^æ­2Ož<á÷Z©î}$&%¾:eʼùó³²Î?öØãuëÖ —¯\ÙúÝV“ѨѨ`Ê”ÉÆõÜsÏ n±XOœ\"9~üú{̘ÑcÆŒö¡ª€ÊoÜ>?þùŸöý´iÓ¦»wï*•Ê:uëŒ3fÈ!òäݺu7oî#}V¬Xi6›CCC·mÛÖ°a£O׬ùøã,xßb1›Lññqýûõ§KnàÓû·Ë*ÑŽíÛõz}·®]½nõìÑsÑû‹vlß–”˜XZŸÒ!åé [þÄ:þß3ÿ½pÁ‚óY‚ƒƒ^zé¥g>#Ümܨñ[oýßG´víÚ:uê<;êY{‰U2ˆÃVWÏ™óÞÚµŸ¥ïÞ½eó…BѸqãÙ³fuïѣš­j=–G7³¯—]„ê£ ,û hÆŒ‘©#®æÙK\^éeð~ýú ÿ³oËÌÌüãø‰ÏªõÛ>þ7OÍ1ܮҧ$+?33sǎçÑ˃שÓ{õªd†<îÉû>þ™ðt?˜÷ÁÀÀà'}000ø F ~‚уŸ`ôÁÀÀà'}000ø F ~¢fècãÆM ­„ÿÚ´yࡇž4iRzzzåŸí9œ‘±zõêŠã,˜¿ k×®~)ËÀÀP6jòÐú´·ÞŒˆˆpÚ7snþüÓÏ“'¿Ò¾}»%K–ÐOOú@ÆáÛ6n5ª‚W3200T#¼­×ä#s©)©±±±Âï´´´/¾ürö¬Ù ßÿõ×^«A­*‰ûè‰Ûg øqÏ[6o™8a‚V«ÍÊÊZ¶lÙñ?ŽçtìØaòË/Àœ¹s7¬ßäÌááá{ö¤ûˆOñÇñãsß›söÜùZC‡6,M÷öÚõkóçÍÏÌXzìh¦ÝላŸ4qBRRÒßi4†Ä}DÐ¥sçC‡:u*99ùVέ&Mš<Õ÷©£éú«W¯~iâ¤ÏÖ®€qcÇòn÷Ö­Û¾Ú²Jøˆ/Àf+žúúÔ1cFÇÄÔÛ·ï§ùóç#iii¦ðÒ…B5k֬мܼƒ‡:N8þ\ZÚ𸸸Y³gëõº-[¾=z̺uëâããþf»10Ôî/úˆŠŽ€»wî@‡Ž:t_`Ý:1±Y³¸¾}ûfee5nÜ800Ðh4rE׎¦i}Ä‹‹‹'½<©GîB„ÜÜÜU«>8hJ©ô‘¶¤¤äÂ…‹3gÎìÚµ‹¡K—ÎÂ… ß ]µj¥°XÓ¦M›Áƒ¯ZµrÑ¢E÷ÖL ÷î/úßI‹œNçêÕŸ¤§ïÎξép8…—/_¦tà… ã«”ÊΤç@Ï^=¶nÛvñBV³fq>ÒjµÚ† ,_±Üf³¥¤$7jÔHø„ºÓé<’qdøˆát¡!Ô¹s—Ï7n¬f£00ܯ¸'ôñëÁƒ—.^ò ¬ðM$só&„……ÀìwÞùa×/ŒŸÐº•Ao°˜ Ó† ·Ûí奭0¾ÁdR)¥ò×€ÂBs…i—.]º|Ùò+Væçç…†† :täÈ‘f³Åér­Y³víÚuT&ƼÛí¦—ë×o¨°È 4l ¼g³zQýô!pGeÈ¢4~úùgµZ;¶ï1bø"'++ËwÚ ãY,N§S¥R —ùyPaÚèè虳fÀ¥K~óõ׋- ïÝ»—B¡:dÈS}Ÿ*O%ÿŒÀÀPíF²jgrl WÛÙ¿¹ã‹/¿<|øð€ghµZžç].—Áh¤w÷ìÙ#¬Vkœ.½¬0>8]®Ÿ~ú‰^þðCz```ÃF+“V@ƒ±“_™¬Ñh.\¸ V«SRR2ŽdÔ­[7Ö~”ážbèÐ!¥'þÀ“(jríãHæ‘«×®:íΜœœŸþùpFFûöí&N˜Ç=Ðö¯¶|Õ­kרèè=éé›7o–§mذaIIÉ_~Ù<¾¹F­jܤ‰ïø ÓéÞ‘Ùlމ‰Ù·ï§íÛ·O™òŠ0ñ‘6;;{úÿMïÙ»gll·Ë•¾{ÝnoÛ¶-L™2yذ£ž{î™ÂÃÃ-뉓'0'LxéžÛŽá>@MÒǬ™³@£Ñ„„„ÄÅ5[°`~=„…I˜9sæ¿gü»oß§ÕjUëÄÄ…  >‚¦íÚµK¿~ý–~¸´°°0,,lÏžtßñ@¯×½7ç½9ï¾wöÜùZµ‚'¿2Yصõ—) ""bíšÏrnÞD׸q£yóæ¶kך4iºqãçË–/Ÿ3gžÙ\Ø¢yóAƒÞk»10Ü'@3f̈Lñç]{‰‹€×2úN°~ý†G}ÄÇ«Æ×¯ßÀæü ÷|wÌÌÌ̳gÏUØsçì²€VÅņhrެaOÜ200ø F ~‚уŸ K§ìC/ Âã‘}æ}000ø F ~‚уŸ`ôÁÀÀà'}000ø‰š|U2üX‚y ~‚уŸ`ôÁÀÀà'dì³ÅßðüNó>ü£?ÁèƒÁO0ú```ðŒ>ü£?!ÒGõ}æ¥RظqSBB«.]ºÓÀìÙ ­¾ÜüåߥÅýˆÑ£Ç¼0þÅ¿.çpFÆêÕ«ÿº9¼ˆ¢&½¼¼¼ >¯AþÁÈ8|ø“ÕŸÔ´ ÿpÔ$}´k×víÚµ«µuø+p:5­CM¢&écÜØq6›mÝgŸ•y7++kÒ¤IÝ»uOJJîÒ¥ë[o½UŸOï.˜¿ k×®gÏž6|DJJê£<¶'}lܸé‘GMNN>bĵë×<ž?ÿâ‹lÿ`JJjÚ°áÇŽ«ªÂB¦‡2xHJJêüyó+”ì»°{÷î'Ÿì“œœÚ§OŸôôt¯ýõ`ZÚ°””ÔöíÛ¿øÂKeß|ã©z|’êÙgGM˜0æÌûÑG[¬Ö„„V ­zôèYÕ’20T5Iѵ£û>õÔúõë Kß½•s«I“&ÓßžþÉê_yeòÉ“'_š8I¡¸¸dêÔ7Ÿxü±Å‹5hÐàÕ×u3³A IDAT^›?þ{ö¼òÊ+³gϼqýÆë¯¾N#Ÿ?nhÚ0‹Å߸Q¸ŒŽŽþí·JÍe´Zm«Ö­åJú–ì£v»ýÔéÓãǧŸõmÚ´YLLŒðÛápœ¡u+ƒÞ`1¦ n·ÛiN+¯P(t:½D ð.7˜Í§ËµfÍÚµk×Ñón·»ªÚ zÚÛ+#ÙG),+Ïó¡!!rùaaaT2ÏóÁµ<î†×:{æLUuf`¸G¨yú®5dÈàµk×õîÙK¾cûŽ#†!ßìÍÊÊò; “ɨP(†òTß§þ’®U—ì£&“‘ã8¯'‹Åª×é ÀÄq\~^®ünn~^`` ð[©P`σ:»L|ᜡq_œ:1b„V£^¹j% áyÞårŒRgسgßòÕjuJJJÆ‘ŒºuëÆzâ/é]‘dߥÐh4Íãã>DCîܽ{ñâ*¹e‹éééÔ‘¹s÷#©)©ÂeXDøÍœžç…K‹Õz^ÆMjµÆérýÅÒ10ø¡¿ùØ©'Ò† ûõ׃’Z÷@Û¾ÚòÕÕ«W.×Î;7oÞüW²˜2eòåËWF=÷Ü÷ߟ™™¹oßOK>ø`ñâ%ÂÝìììÄĤ%|P½’+,ŸçÇýrà—uëÖ9Ž[·nM}cªJ¥¢wŸ?þÏ?/¿ôâ„ýö§§§;V­V9B¸Û«g‚‚åË—[,–Ë—/O™2E©Tд 6,))ùâË/Ož<•uþ¼åb`(žDÁy…×ÒÒ†ÉCfΜY?¦~ß¾OwîÔiÛöí ,ø+ò›4iºqãçááásæÌ=zÌŒoŸ;s.9)I¼Áívón¾Ú%û.EÇçγyó–¶mÛ?;rTÏ^=èÝvíÚ.[¶Ìl1Ošøòÿýßôè¨ÚŸ}öYt´¸™Ò¬YÜÌY3·ïØÑ¥s—‰'>ùÄÍšÑMèÚµK¿~ý–~¸tÈ!ÿz~¼åb`( /7͘1#2uÄÅ;%ÅN¦>bò~ýú >úHpp°CÉTŸá>™™yöì¹ {î;ß[@§â†isެ¡K§ìK/ ƒ%î‹¥S†ÿF0ú```ðŒ>ü£?ÁèƒÁO0ú```ðŒ>ü£?!c‡Æ*„ç²™÷ÁÀÀà/}000ø F ~‚<2Ç?*„'K0ïƒÁO0ú```ðŒ>üDÍÐÇÆ›„ï'&$´JNNyòÉ>Ÿ~úie>›PúÃñ‡#eàÎÝ»­ZµþíØo¥o=æ…ñ/VFH邸jå£hÕŽÊÛÊ7ª±ø UEMzÓÞzóƒ—¼ûî; ľÿþ¢>ø°Â$÷Çã÷íÛܪu«¿"¤ R¢ª¥h3î‡&ñ? ‘>jäEë©)©;uîÕ«×Â… ããã6mÚäÇw›þ~ìÝ»¯s—Î÷™÷9ú»¼¢Éã0ü/Ë(î‹>€Jh™`³Ù }|’ÞLJ㳲²FŽ|6%%µ[·n|]Y«µèHÆ‘nÝÄéÒîÝ»Ÿ|²OrrjŸ>}ÒÓÓ½"g?ÿâ‹lÿ`JJjÚ°áÇŽó]òâÓ»'NìØ±SRRò£<¶|ù ¢~ýõ`ZÚ°””ÔöíÛ¿øÂK/^¢r„éÞáLJ ’’’:ÞüÒE+/Ž }T\elåCá7Þ˜:pà@yägŸ5aÂÅgø{Pó_™pãF¶R©4§OnÒ¤ÉS}Ÿ 0š®ß¸±zõê—&Núlí7v,ïvoݺí«-[@A>kR\\òꫯxfàèÑ£÷ìÙ³dÉ’˜˜z={ö€ìÙ=üð³Ï>;qâ„jÑóÀý*•ª]Ûv°ÿÀþ)S^íÕ»×´·¦åçå-Z´¸¤¸8..^ˆyþü¹´´áqqq³fÏÖëu[¶|5zô˜uëÖÅÇÇ•Yñàôé3#FŒhܨѴio†……]½rõü…¬òlrðà¡ñãÇwx°Ã’%KŠ‹‹—-[:lذ͛¿¤y0›­sçÎ}õµ×4hà(±—.Z™q|kx+çVyW¡­*T¸<”×$þÔ$}”””Øl6»½äû;ür k×.jµÚÇ'éËûp¼Íf{ýõ×xàhß¾]ffæîÝé}…BÁ)ªÍÉúqïÞö¶>ˆ½bÙŠ¦M›Í3GøêmLL½þýŸ¡ŸZY¸ðýÐÐÐU«V ‘Û´i3xðàU«V.Z´¨Ì‚øˆ , ýäÓO4 $‘ïÈ”)jé‡Ö¯óþ¢…ÂçµZ>òÈ£Ÿ~òé›ÓÞ"8Ž·¦Mk˜X^ÑÊŒã[CW¡­*T¸<”×$þÔ$}<óŒä‘vìÐqÆôéàó“ôåÉÑëõmÚ´¡—7ºuû¶ð;::ú·ßŽ•“®Êp:¿80uêT°Ûí§NŸ?~<ýbvÓ¦ÍbbbhÌ#G†N{#B¨sç.ŸoÜXždñNÇÑcÇž}v¤À¾áp8Nž:5fÌh¡+@XXXj›ÔÌ£™4ŽV«mÕºuyE+3N…%òQq¾mU…îOÔ$}¼÷Þ»Q‘QJµªNíèààZB OÒ—N+ÿê½B¡àïÍç]3g””Ø;wî ‹•çùÐy„°°0á‡Ùlqº\kÖ¬]»v½‹1_ÞÚ°ïøf³Õív‡††UFI³ÙÂó|p-ÅB‚k=s†^ z¹Å¼ŠVfœ Kä£â*´U… 3ÜŸ¨Iúˆ‹‹+ý‘jŸ¤¯qìÝ»/99Ùd2€Édä8ÎbµÊ#X,V½N/ÜU(C‡ yªïS•‘ì;~@€Q©TÞ½{§2¢LÇåçåÊsóó}¤’Í ÁgÅù¶U… + ì¹)è°ÛÁd†šÆ}±óBáû“ôP£ŽÇÿôÓ¾nݺ —¦y|ü¡Ã‡h„;wï^¼xAø­V«SRR2ŽdÔ­[7Ö$‚GA|ÇW©ÔÉIIßïÜYRRRZ±Ò¢Z¶h‘žžNý‚;wïÉ8’š’ZÉ¢• ßú®¸ må[á°ˆð›99*ü$}U?Ÿ˜˜´äƒþºnÇOœ¸s÷.ݲ€qÏûåÀ/ëÖ­s:·nÝšúÆT•JEïN™2ùòå+£ž{îûï¿ÏÌÌÜ·ï§%|°xñ’ò â;þË“_ν›;räÈ;wÉÌüæëoÞ}ï½òD=?~üŸ^~éÅ ûìOOO7v¬Z­1rDå‹V&|hXaÅù¶•o…{õìQXP°|ùr‹Årùòå)S¦(e;,Um Õˆûeã–bæÌ™ÿžñï¾}ŸV«U­.X0løz—~8¾°°0,,lÏïãÞÀàv»y7ÿ×Û·w_||\DD 騡ãܹs–.]¶hÑâȈÈá#‡Éã7iÒtãÆÏ—-_>gÎ<³¹0 °Eóæƒ ,¯ ¾ãÇÅÅ­[·ö×Ϛ9«¸¤$**êÉ'Ÿ,OT»vm—-[¶|ùòI_V©T)É)sçÎó± Zºhe·†¾+η­|+ܬYÜÌY3—/_þÉêOêÖ«;vìX‡]:ÆVå&ÁP}@3f̈L‘u»¸ØÉÀ´G|'X¿~ã>ì#B…ßéþoÄO<ùØc3º¦©~üƒ‹Æ@á»cfffž={®Âž;k‡tjEã0mΑ5äUÉcÌÞä [·~WÓ*Ü+üƒ‹ÆP½Y‚pÅýµöÁÀÀð_F ~‚уŸ`ôÁÀÀà'}000ø F ~‚уŸ`ôÁÀÀà'}000ø F ~‚уŸ`ôÁÀÀà'}000ø F ~‚уŸ`ôÁÀÀà'jàe… å~¹Q£†_ýõß©L•p8#ã䉣Fªdüólß±}ß¾}eÞ]µê£Õ«W–½@˜á¿ 5@ ̧¿gÌx;&¦ÞÈ‘#…K£±ÜÜÈ8|xÓÆM•§†6j€>ÄÏGÀ;ï¼&a``øoÁ}´ö±oßO ­NŸöø¶ØèÑc„¯« ß|ß`¿~’“S{õêýÙgëä1}žþ¯£Ì¹WøYyøãøñ!ƒ‡”©óß©?Cµã>¢N:FFFÊ¿ríÚµŒŒŒý—f³eÑû‹ßygæÁƒ¿Lœ8añâÅëÖ‰½ñüùsCÓ†Y,æY³g/]úadDÄèÑc(eßÈNHhµhÑâ¿¢Þ¸±c h0víܹkçÎ Öù¬üô·§²úãW^™|òäÉ—&N’§²ÙЧ¾>uÀ€«?^õðCÍŸ?Ÿê,Çÿ³wßqMœoÀŸ$VÁZe*ˆì- ´¢Ö… ÊtU¥ ¸7CDÄ‚ (­`‡Õ*‚u†øSÁ*8p”!(’ +!ùýqôŒ†qQßïÇO›»{ïî¹7äá½»pàøD4‰P …âââ’œœ(##™™22Ò“œ'a X,Öºukuu‡€³³sqñƒÄÄCsÜÝÅÅÄP(2¥G¹²Ãbî‚ËÊ@ccãÊU+LJ5xóæ 3ïÆ»ˆAD’>`Ö¬Y¬––3‹õû§¦Lž"%%…-§šššâmmlêêêž<.ÅŠ¿Oœ8¯ø{áÿa“êêêwî®X¾¼×f±X gÍšecckffáæ6ÊÊÊðâbb£?Ôvš0‹™o#‚ãGÑ$B£PRR;~\Ɖ“®³]srrjkßÎv/•¡ÉÉò¬¼ÔÕ1>µœ}/PV¾-fYYÞÆE,fÞ?‚ôþ“͉2Qn®®óç/¸wÿ~FF¦‘‘~õLfKK þ+ºúu5ÈËË}j9û^$ ¬<æ=“Éb±ðz®ïj߀¼üG¥üŒA>—ç¿"vòæææÚÚZÑ{¢ x‡Àf³/\¸€Ož;wN^^^K[§Ërö½…¯˜»à²ò›}éÒ%|2++‹ùãÍöSüÒ»Dëä3ÛÕmGx¸œœÜw'òΗ’’ŠÝ·¯þ}½ÆPœ ²²²‚ƒƒ°Sƒàà@//Ÿ º¹ºª¨¨0™õ÷‹îs9\ÿP^^>yòßù¾=¼üs¡?B‚*®£«‹••;fŒšºzNv6_Yy,æèèƒ1dÈ‹/>}™—àøD4‰bú˜8ÁiGxø÷Ó¾ÇÏS0ÒÒR;v>|T¢¨¨èéé‰-\ü¸ÐÚÚÊiåô0°öÅÜ—•ÇbÞ±³Ã˜yu?‚ˆ$‚ÓG‡rõÚ5˜íâÒ~‘áȑǎëpSQ‘‘.R¤~ïÞ݄نB¡lÞ¼ióæMøº²ò¾ý±¼mxwƼxñ"Þ¢öâGÑô_ú+§OŸ>{ùêEÜþ¸Ñ£Ñ™?‚ˆœ®œ=úà³cÇŽÂÛ·GnܸžèXé‚h¥C‡;[„Ÿ ""DîÆ-‚ Ÿ ”>J‚ ¥A„„Ò‚ BBéA!¡ô ˆPú@DH(} "$”>J‚ ¥A„ÔûéCSK3-­ãGr Bˆ´´cšZš½¾ÙÞÿ‹[[@AD‡¦–&öÁì]}òû¶66}+‚ "]û@DH(} "¤¶“.p¹¢ð°SAD–%¸"[& AÏJ‚ ¥A„„Ò‚ BBéA!¡ô ˆPú@DH(} "$bŠT¦§ÿ¼cÇ쵤¤¤¢’¢ÞðáÎÎÎãÇ'‘HÝÙBn^^Ñýû ,èË0„'}ôû—N7nÚ ªªÊjfUTV\¾t900ÈÖÖ&66–J¥v¹n^nîÏé?£ô ýŠËó_bKd[˜[hhh`¯===9q",4lOtôÚ5kŒ An"2}ðqsu½s!3#3Àß_RR²´´ôÀ÷îÞ«}÷NNNÎÞÞ.pÕª y,퀊ŠJNN¶€öðòÕË]Q» n744ÈËËëЊŒ”‘‘Ù½k÷é3§·‡lß³ïÙ³gJJŠ^^žØZ‚· ¥%%qܾ]øþý{µjS¦NYºt ¾(v_\áí‚æ–=}ý•þ¦¦¦ýÙŸÒ×D(}€ãèÑ·nÝ*..633«ª¬ÒÕÕ1s†MöÕ¿ÿ&%%­X™rô,ùáNkë©SžÌÌŠ´ÿþŠxhh¨’²ÒÛ7ooÞºÉb±°E 3&zïŽaC‡jääälÚ´™DOOÏ.·ùàÁ?>>>:ÚÚ7n Óé/ž¿(y\Š-*)yäéé­§§&--•™yrѢũ©©úúzýØÒ·D+}¨©«@Mu ØÙÛÙÙÛaóML†×›9sfii©ŽŽŽ¼¼œ,!!¼ƒ‹={¢•••b×q,--çΛ˜x0&&¦ozA ZéƒÃáX,VRRrvöùòòŠ––¶‘BYYöÑmO@{III--Íø„ø††ss3mmmÞû;ââTÞO¾­Mjjê“Ç¥Ã‡ë Ø&‹Õr»°pþ|_,wðE’Ÿ—ïíã_&‘H£G;OOïi!ˆ(é“ôqãæÍ§OžòÍôð˜×劕@§Ó ,<<ë\Ö~~†ÆF2Ò2LF§—wsssgë n >!á`mí[ee%___,‰ÈÐdÈäß‘•—€º:†àm2õ­­­ÊÊôö‘0L›}äÈÑ£GSñ™\.§µµŸDÏ‚EúÓgó¬S,wt'Y´wéòe*•ª¯¯gNŸñññž÷ßvJKK¯+¸½ººzHh<}úì·_‰ÙK§«L:ê™Ì––|¤PýºäååoSNŽ&&&VSSÝ>YY…Bñ˜7oÆÌE+\ÿ ˆp°_W½žAzÿ[§BçŽ_NœÈÍÍuus•””äp8l6[†F׿ääð6¦R%Xl6>Ùe{œ¦¦F`P „„ÄãDZ9l6ûÂ… xƒsçÎÉËËkiëÞ¦¸8ÕÌÔô¯³g›ššøvA¥RÍÍÍóòó¬ñ±î÷‚ô"yíOzŽÈkùù/^¾`5³*++/_¾œ›—gkkàïd2ÙÊÚêdæÉ±cƨ©«çdggdd𮫥¥ÕÔÔôˉ#ôGHPÅutu´///ß²y‹ÓD' ÍV6;û|Nss³µµ5¶TJJ*vß¾ú÷õC5r.\ÈÊÊ Á1¬ \åííãëëëåå¥L§¿zñòaÉ£uk×@pp ——Ï‚… Ý\]UTT˜ÌúûE÷¹®¿ÿоîUé7D¦Ð0PRRÒÓ¾{÷.Þ/­‡„„lߺ}æÌYTª¸±‰ÉžÝ»½¼}ðuÇŒqtqq‰ÛWWWG§Ósr²´—•“SUU=z$¥²¢‚D&ëèhGEEÚØ´¥ii©ˆˆˆˆ;>*QTT ÄîÚvƒžž^jêÑýûãCCB›šÔÔÔ¦M›†-ÒÕ–ž~ü@||DDƒQ'''o0b„»ûœ¾ìNéo¤­[·´ðyTYߨ€Íß+^!-íØäÉÎ 6KK;öØc_»xñ"Ñ HßüÁ,((xøðQ—ŸÜí§j@šJÑ(S™ýÅ-‚ BBéA!}íé#0(¹ ˆpx/¢2Q‚öÑ_ìí£A„†Ò‚ BBéA!¡ô ˆPú@DH(} "$”>J‚ ¥A„„Ò‚ BBéA!¡ô ˆPú@DH(} "¤ÿþ`Ÿ‹þ^A®|ô÷úhô ˆ°ˆIéé?aÿ,-­¾›4iåÊ•ÙÙÙ\î‡!Pbâ!++kBÂë¨Å#@uuõŽ;==<ÍÍ- Êÿ-':"äËDd¡†›6¨ªª²šY•—/] ²µµ‰Å ¾)**hiiQ‹G€W/_eË2a`ld”—ŸOt8È‹Èôaan^óôôüåĉ°Ð°=ÑÑk׬Ãã#jñ`dltéÒEHKMCéé;"tíÃÍÕÕÚÚ:3#+ûÈ{²°{×î1cÆ<|ø—·¹¹Ådç)9Ù9žþ³³ód33 oŸ—¯^òn­´¤dùrÿQ¶£ÌÍ-<½¼ ñEØÖ =æy˜™YŒ;ö§Ÿ’ð¥/_½ô÷÷5ÊÎÄÄÔÑqÌ2?¿÷ïßC»“—7nzzz™›[ØÚÚ.ÿqÅž €Âm¿ñVüF¾#Z?gŽ£G·´´·_ÔØØ´~ý†ï§NÙ£©©¹zÍš]»v]ÈÉ ù÷Õ¿kW¯Å—”<òðôb2¡aaqqûªª.Z´øÁƒð Fý®]»‚׬þûïœ%K—ÄÆÆfggc‹üWø——W†††M9ºuëÖÁƒ³X,¾`nÞ¼åçç7@~@lllHHhyÅ¿^^^ååå=Ù~ù¿å††F11{{©/¤ÏyòÒžšº:ÔT×´_ÔÐаnýz ss042tps៧þ§@Ãû†­Û¶UTT¨©©Àž=ÑÊÊʉ‰±Ë(–––sçÎML<ƒmÅjÙ¶u«Ž®.¸Îvýí×ßΟÏvrrjjjzüøIHHȘ1ŽXKGÇÑ탉ۿèÐ!Ñ1{ÄÄÄÀÐp¤³óäÃɇ7lÜ üöI@¡PÈÑJè"€h¥‡@ê`‘´´4–;@VVVYYÙÚÚË ©©¯««ÕÔÔX,V~^¾·7–;€D"íx<=ßš¬¬,öÙÆ ôMÕë× ))©¥¥ŸßÐÐ`nn¦­­×Üŵ´´/^¼Ë@§Ó-,- nôdûêêêwî‚|>D+}TVTNo¿HJJ’w’B¡HIIá“$ 8ìV`0˜,6ûÈ‘£G¦â ¸\Nkk+>I¥ŠónD&qØlìu\\\üø„„ƒµµo•••<<<|}}y“ƒÁäp8 ŠJ¼[PRP|øÏ‡“£žlA>xúàrEàk§—._¦R©úúú=Ùˆ¬,B¡xÌ›7cæ !VWWW €§OŸýöë¯11{ét•©S§à äädÉdríÛ7¼k½©}+//ß+ÛG‘…e Dž¼ää¿xù‚Õ̪¬¬¼|ùrn^ž­­M€¿Ï·èåå³`áB7WW&³þ~Ñ}.‡ëï¿BðŠååå[6oqšè¤¡¡ÙÊfgŸÏinn¶¶æÿ²é2?¿¥K—®Xî?g®[ScSBB•Jõñõé20Û///Ÿêò“»í7 M¦F«Ì?"B×>ù¼ ô *½k- IDATˆPú@DH(} "$”>J‚ ¥A„„Ò‚ BBéA!¡ô ˆPú@DH(} "$”>J‚ ¥A„ôßジ :ED´}œ%ÐèA!–>²³³½¼}¬­mLMÍ&L˜¸|¹ÿ7±EX‘G¢ûb„‡…/_ÞöàØêêê;wzzxš›[•ÿ[Î×øíÛ·ë×o°³³·²²^²téÓ§Ï:ÛìÑ”WÞªÈW‹˜ôñûo¿IIJ¬[·.**ÊËÓ“Ån¹qã:!Á|‘ÊÊÊ223—-[‚M¾zù*ë\–¼üc#£öYlöâÅKroÝ Ü¾}[uuÍüùóß¼yÛá–縹UWWŸ>}¦£G>Ä<*9%5EOO/!!0²‡§GûR²ˆÐ9b`` §§‡M]ºtÒRÓòòóùÿõ×_%%~JúÉÒÂŒ'9|80(°ý–%$$¦OŸ–œœ% bN^F\¿q=-5­ºººÃM«W¯ž0qbll¬£ãÞ õU•Uººº[¶mINú)((°¨¨hEÀJÞu fLôÞðð›7¯øïÝ»75µ-ƒ””<òðôb2¡aaqqûªª.Z´øÁƒ¶â’_Lûü‚üÆÆFK«î–­züø‰¶¶ï-íWÿ¾jiié°½žÞpYíò•+= ùÌsò²aãúÀÀÀȨ¨È¨(S“Ó¦ÛŽ²Å444¬]»ÖÊÊ lmm ° õ`goggo‡5361>\oæÌ™¥¥¥:::ØL‹µnÝZ]Ýaàìì\\ü 1ñÐwwq1±={¢•••bÕ³---çΛ˜x0&&àË©qÿ ø‰¤¡¡ÙÍöuuŒaÇñΑ•“ãr¹u ]Y¹}{2™¬©¥UTTÜ ±"Ÿ3bÒ‡ššÚñãÇïåçå]¿v-ë\Ö¢E —ÿW`MZZÚÒÒo¯­£U¨‹•””œ}¾¼¼¢¥¥írIYYž>ÄÅ©¦¦¦øº¶66©©©O—jiiççå{ûxc¹H$ÒèÑŽÇÓÓ±É/¦Æýëª×²²²|'ƒ½KIIñÙ³²¾Û>òY ²ÊÜHƒ‘Àd2—.Yšœ|xŽ»;öëNJJ’÷Ê…BÁ+Ô‡…‡gËúÑÏÏÐØHFZ†É¨óôònnnÆËÐdxOõeåå ®ŽÁ`0Ylö‘#Gýp5„Ëå|y÷ Ù6™Bé~{99Y&“É;‡É`H$9YÙÎV!S(ìÿÞä«EdúÀÉÊÊ~?ýû{÷ï¿|ñ¢ÃÑ2¯3§ÏøøxÏû¯Vii)_ƒz&³¥¥bT¿®yy9YY…Bñ˜7oÆÌ½}¢EQAñ=“Éår»YñS[›ÿL¤ôÉão}#!!ÑÙ*Ì:†¢¢bOE>sÄœç¿|ù’oΓÒÇ ¢¢"xE‡Ãf³eh4|V š›Í¾pá>yîÜ9yyy-m*•jnnž—Ÿ7xð`õè`D––&‹Í®(¯èf{GÇ1UUUØdUUUî­Ü1c¬Röü9~¶ˆ|µˆ},X°pÈ·ßÚ9ØRÄbµÜ¸qãÔ©?ÇŒqüæ›o¯H&“­¬­Nfž;fŒšºzNvvFF_))©Ø}ûêß×k Õȹp!+++88H\L ‚ƒ½¼|,\èæêª¢¢ÂdÖß/ºÏåpýýW@¯Ö¸'–••‰Dº{ï®ú ¶ªÝ\.˳JÀÕë×èÊÊÆ&&à<Ù9%%eÍšµ+–/—”’L<ô&ëíロûúõë… ¹¸¸xyybs*++«ªªÚWG¾6Ĥ€ÿœ Ž¥«©©¡P(ß~û­ŸŸŸwwÖ Ù¾uûÌ™³¨Tqc“=»w{yûð6––ŠˆˆˆØ±óá£EE…À @O϶Ÿ{]ÝaééÇÄÇGDD1urrò#F¸»Ïi[³÷jÜ‹N§[ZXü}ñâ¤I“°9­­­Axƒ°Ð0p°wØ·ÄÅÄ£¢¢"£¢X,–©©IdD~ùþýû²²26û×ú.^¼$K£988ôß!!"‰˜ôáìììììÜÙÒÀ @¾ï;FEFâ¯éÊÊûöÇò.½wï.ß GŽJ‚ ¥A„„Ò‚ BBéA!¡ô ˆPú@DH(} "$”>J‚ ¥A„„Ò‚ B"æöÓÓÞ±£íÙV’’’ŠJŠzÇ;;;?¾›ÏÍË+ºÁ‚ÂÐÃÕ‘OµhÑb ªölDä‹Aäèc㦠ûödž‡…Í›;—É`-]º´³Âˆ|òrs““’…ÞuWGˆ}\…¹^$ÁÓÓó—'ÂBÃöDG¯]³†À¨¾<,V‹¸8õËÛB8ºöáæêjmm™‘ÙÔÔ¥¥¥+W®7vœ©©™£ã˜M›6½«­ÅZFDF:ô³¾ÞÐÐÈÐÐhüx'Áíù´_ýâÅK††Fx­lÌ¢E‹çÌ™»wí3fÌÕkW]\\ÍÌ,&L˜˜’òÑ3DKKJ–/÷e;ÊÜÜÂÓË»°àJ—XÀ¹¹¹óæÎ37·Øµ«;q––”ØÛ;˜ššMvžŸ€/ºq㦧§—¹¹…­­íòW`0ê###W¯Y£©©ÙÒÔÜeœüãã㣣­½qã:þâù‹’ÇmåûnÞ¼åççg7Ê.66¶±±ñÀ8//¯ŒŒêêêíë굫ÁÁ«'Lœ°qÓÆÚ·ocbö656êéé÷g ý@´Ò‡šº:ÔT×€½½6ߨÄdøp½™3g–––êèèÈËËÓh42‰„—AÜžo/®îââ’œœ(##™™22Ò“œÛꤰX¬uëÖêêggçâ≉‡æ¸»‹‹‰íÙ­¬¬œ˜x+‹iii9wîÜÄă111$ P(dJòX¬–M7bU 0‚ãܳ{·²²ròád¬üoñ¸ýû‡³GLL G:;O>œ|xÃÆ í+á@°aÃ##"° áC†|;{¶›^7Ò¯Dèä8 €Åb%$œ5k–­™™…›ÛðèðOmÏgÖ¬Y¬––3Á6õû§¦Lž"%%…-§ò~¢lmlêêêž<.e±Xùyù'NÀKê’H¤Ñ£ ïü›TWW¿s§°ÿËÖIJJã“‚ãd±ZnNšô]ûÒ----EÅÅNNNXî:naiQp» ³}577?xàäôá&Ú°aÇ ÒG‰L´F•@§Ó ,<<ë\Ö~~†ÆF2Ò2LF§—wsssgë~j{>JJŠcÇË8qÒu¶kNNNmíÛÙ®³ñ¥242ùCª••—€º:ƒÁd±ÙGŽå­¨ÂårZ[[?ñÐ{™ŒŒ4ï-pÁq2õ­­­ÊÊôöÛa0˜GAQ‰w¦’‚âÃ>\'âÛ“YÏáp”•>Z{O‘/Œh¥K—/S©T}}}8súŒ÷sæ,*UÜØÄdÏîÝ^Þ>øºcÆ8º¸¸Äí«««£Óé99Ù‚Ûói¿:6â§ááßOû?OÁHKKEDDDìØùðQ‰¢¢B`P v×tu‡¥§?Å`ÔÉÉÉŒáî>§mM.´¶¶rZ9½Ôg§žž^jêÑýûãCCB›šÔÔÔ¦M›†-²±±>pà@||üÊ€Uâââæfæ‘‘Qø]ÛÙÛÙGFFÄň‰Ù;Pu ·¯WŸÒ/ĨR*CGŠQÛ.±“¶nÝ:ÐÂçQ9£¡¥¶LW¼~Zڱɓ4ðøïÄgçS§6mÜôÇ¿óž}ìÞµûô™Ó/^$00é!ÁÌ‚‚‚‡uùÉÝö{ »¥QNVv˜šleþѺtJ §OŸ½|õ"nÜèÑŽ¢våADvò"=Ì@DíÎ vìØQxûöHCÃ× ‚ˆ(¾“”>Ú:”ØÙ¢À ÀÀ Àþ AD%B7nq|w^Pú@¤»øN^Pú@äˆQ%ñ×(} "$”>J‚ ¥A„„Ò‚ BBéA!¡ô ˆPú@DH(} "$”>J‚‰ÈôqïþýààÕãÆŽ335sp½tÙ²œlAÏ7þ|%&²²²î·Ý¥§ÿlhhäè8¦±ñÃSmËÿ-744:‘q¢ßÂ@¾x„¥ôôŸ½<½žòòòÌÌÍT*êºZ^ñ¯——Wyy9Þ ±±iýú ßO²7&FSSsõš5»víº“òï«×®^Ë»Aƒ½7<<äæÍëþ{÷îMMMåYZ¹"Àÿì¹³^^ÞPRòÈÃÓ‹Éd„†…ÅÅí¨ªºhÑâÚ*3ú¯ð//¯ =šrtëÖ­ƒf±XæÒ¡1õ»ví ^³úï¿s–,]›ÝVG»~³·;¬>H}æŒiiiuuuBô°€0wòõ àä…Y___ÿþõo´é²ª{CCúõë-ÌÍÀÐÈÐÁatÎ… žú;ïhxß°uÛ¶ŠŠ 555¬=‹ÅZ·n­®î0pvv..~˜xhŽ»;Vêé“êÑ755=~ü$$$dÌG¬±£ãhèlþ§‹Õ²mëV]]píúÛ¯¿?Ÿíää@ …B¦t7é/þaño¿ÿžœ|xåÊ€^ C@çt30äË Š7n»SÕ]ZZË ++«¬¬lmm_³ÐÔÔ€×ÕжЋSMMMñI[›ººº'Ûêà~R=zIII--Íø„øôôŸKKK¹\.¾‘çê¡ÉÊÊbZÌ AßT½~½VWW¿s§pÅòåÝëH ÓénnnéééoÞ¼í­0wòU!`ô!K£Ñh2¯Ê_uÖ ;UÝ¥¤$y—R()))|’D¡‡ç*¬ M†Lþ+eåå ®ŽÑ¶ôSêÑ@\\\üø„„ƒµµo•••<<<|}}I$Rgó?éШÔê9’È$N»r¼Ý·páüÌÌÌC?òòðì•0ºìäëAÌKKËë×oÔÕÕuxùCˆªî]ªg2[ZZð_˜Õ¯«@^^®ÃÆ‚ëÑ€ººzHh<}úì·_‰ÙK§«L:¥³ù}zh‚)((Λ7÷èÑÔ‰Nz%Œ.;ùzsòâëëËb±¶mßÎþø÷jiIɃÿQÕ½Kl6ûÂ… øä¹sçäååµ´u:l,¸=/MMÀ @ ‰Çwg~_Z—|||$%¨öJÝïä‹GÌèÃÈÈhÍêÕ;#"ÜÜܦL:H]ÉdæææeggïÐ[æç·téÒËýçÌukjlJHH貪»`RRR±ûöÕ¿¯×ª‘sáBVVVpp¸X§‡èåå³`áB7WW&³þ~Ñ}.‡ëï¿¢¼¼|Ëæ-N444[Ùììó9ÍÍÍÖÖÖÍçÛrO­¼¼|òä)¾ó}»ùäää<½¼8Ð[aèœîG…|«2ç>×]ß`DjJjjJÊ»wu4Í`¤Áž={ì@¨ªî‚IKKEDDDìØùðQ‰¢¢B`P §§§€öêÑËÊÉ©ªª=’RYQA"“ut´£¢"ml¬™õõÎçÛr ­­­œVΧ¾§§ÇñãÇß½{×+aèä«BÚºuë@ ŸG匆–VØ2]Yð iiÇ&OvVPPРË:Ýýl÷®Ý§Ïœ¾xñ"Ñ a0 >|Ôå'wÛï5 -!6LM¶2ÿˆ(Þ¸Eä³€Ò‚ Bú*ÒG`P :sA^÷U¤AúJ‚ ¥A„„Ò‚ BBéA!¡ô ˆPú@DH(} "$”>J‚‰˜?Øß½»0;ç!»F/ÓøoM»n×ÛHXî¸wï~ÿïA¾`ýŸAHXîøóÔäÂ;wúï¢ÆÔÄPWêŠÿ˜š˜˜™™Þ¾]Ø®À/ó»˜óâ«H˜Â;wV,ÿ‘÷)ä_!.—»o? ®@]Áû¡ðÎM7vÙ>$4´ïƒêaéH$‡óÉÏÝû’¼|ÙVIuê ÖÝÌà1o^Ú±cý]€Cdú€k)}PWàPW`D¿tÁéAΠôÑô{‡º‡º×>† òüùs¢‚éJ¢uu†7}hjjÀ!Cž>}J\DüOúúú¼3,*.>tèÐíÛ·»ÜBBB‚€–óçÏ—ˆï`ûV¯|dÌÿ«Þž¶¶ö©S§za7}Lè®8ŸuþhjÊ£‡[ZZèÊÊÇëÍ7wÔ¨QuêÏSW¯^íÍ@ûž>tt>”CÔÔÔ,--%("~„_ûàÀúuëTTTðYzúzå•ÚZZØÒîoGØ¥"¢‚” ÆDGã“›·lòí `“4YÚÜ¿žüuã¦M¶¶¶7l ÉÊ–——_½võúõk£FÙòló³8ü6Xú>|8ß|‡?ÂG\°e«ùq…ÔÙ..³]\º5ˆårAÐp— ÜÏc0Ü+A’Éd§ NødHh(]…Î;ç îŠ#Gêëé%&Ä¿6âééÁb±°­q{°e¢`飸¸¸³E„#øOæ¸\.—Ëmû„„ƒææøä£‡üü~´¶¶611çáqûvÁ‡¦<Ûár¹Y粦L™jll:uê÷ç³Î@[çr¹\nTd”ƒÃèÛ· ÜÝÝMGvLL<„/-yTâ¿ÂßÑÑÑÈÈØÞÞaý† µoßò­ûàŸž&&¦ß}7éüùó\.÷رã'N266õôôzñâïîø'öº¢wñnùÂ…¿GŒ0(..æm°`Á‚Ù³]ñ#ºråÊÌ™³ŒMÇäÈQñôô^¨=éŠ7oÞÒUTøVkÛ>— %J¼¼|LL„y»û§sxû¡µúî³Ù„§àr¡¾þ=ƒÁÄþ½ßÀå¶ý²À–>|ôhž‡ƒÁ ;p@UUuÁ‚EÅÅÚ~$xZ^¹r50(hذaô[æýäÉ|)ÿ?ƒ¹kõêµ/þ½téÒ½{÷žÏÊÆ–VVVê ÓݺuÛáääàà ¢ûE?®ðç]·±±qÝÚõß?5vï^MMÍààÕ‘QQ99ÙÁÁAaᡯ^½Z½z5Þ¾ý!ü÷óÌß½ûKŸÀ傃ƒÃÀù%_úüÅËÜÜDâÚ‡»»;>mh8òرc¼gª{vï¡++:tJ¥€•¥…»»ûÁƒ÷îÁÛ`ÿ?0lØðÈÈlø:dè`W==è쌗ÅjÙ¶m«®®.¸ºÎþõדYÙYNÆ€½½ÖÌØÄxøp½3f”–”èè¶]ÄjhhX¿~½……9ÚÛ;ää\8ýçââThxß°uëÖŠŠr55µ¡£ún\Í …<ÛÅ%)9988PFF232dddœ'a X,Öúuë†éêÀdgçââ⃉ç¸Ïëê-è“€?ÕÆV­Z©¢¢bjj:cútÛ¶  k×­µ¶²[[›‚‚üî¿Ý„táÙ¡K"qí#4,û˜MFÃq¹\‹•Ÿ—çíã-..ŽíFv<žžŽMâ-›››‹‹øùùá›ÕÕ6dÈN¯}p¹²²²:::øÒAƒ¾©ªzM²X¬¤¤äììóåå--,¬Á³²gÚ:ÚØºÒÒÒææfXc¦¬¬lmm%&Ö¤¦ÆP¨zýzàÀBû:²§¸Ø¹61sÖ¬„„„ÓgN»Îve±X¿ÿþǔɓ%%%±±½¸8ÕÄÄolkm“š’úøq‰––¶à· ‚f³<~üøý¢¢ü¼¼¢¢¢k×®ž;wnÑ¢…Ë—/Ç6*--miao\[G»ûo7!ƒÒG°6a Ásé”ËsJRWÇd±ÙGŽ=z4•§§µµœc-Ìz‡£¤¨Äû®Ñ•éÜNnr¨TqÞE$2©•ÍÆæ„……geeùùùIKË0u^ÞÞMÍÍØR.€””$ïº EJR ŸC"S •ÕÚÙ!äwØ}ß²¢¢âØñã2NœœíâšS[ûvöìÙøÉÐdH$2ÞXVNÞ½ctýôYÀB0a`0˜LæÒ¥K““ϙ㮬¬Œ½e$|ã ¥ûowÿwγgÏz´~¿ <}`÷Møáp¡í~ &C¡PæÍ7}ÆŒŽ×Å[ÊÈÉdf=“wSÌúz)i©NFÀ?0á~Øì™3g¼½½ç΋-yŒÝiÇ/nµ_÷ãßóxlB?>ÚÅé:{ö‚ ïÝ»—‘‘idd¤¥­Q=“ÙÜÜŒÀàõë× ''×å[ÐûQ÷Æfi4Ú÷ßÿþýç/ž+))õðíîçÎ151I;v¬ûïÜ©z_=AøÃ ¹ük[J¥Š›››åçç  %%µo_\}ý{¡C/\øû|ÖùÀÀ@ …Âår»x z=l¡ºbÑ¢EßþÖÎÞN]}‹ÕrãæÍÓžvt­®>낞¼ÝýÜ9Ýÿƒ}h{äÇWúûØÿÚݺãYª££›–––p0!*jƒÁ““¡?ÂmŽ~·5ÊnçŽñññ±±±ªª½¼<ÿû¡épß<´ iÛÖm!!¡³g»Š‹‹ò?ŸË0ߺ|;£êìF¾ÐqWô¾-;99íܹsÊÔ)ââÔ‹¸ %%µ#|GTTÄ£G¥ŠŠ +W­œ7o^wÞ‚¾¸›–¯Xñ÷…¿?^Só†B¡ þöÛ¥K—zyyuö–ñî«Ë·›ÎéN-++ëénz€´uëÖ>Ê -­°eº²àÒÒŽMžì¬    ‡Ç<[ønÒï÷îÝß¶MÝÅòº:†pqÞ½«ýýSП]ñçéÓ[·l9yòäСCñ™ÑÑ1ýu&;;»bh®è¦þì¼6mÜØÇ&=þ<íØ±;w4Ÿ>­;wvº€–‚?˜>üÉ€m¿×€´„Ø05ÙÊü#"ñ½ú¡+ž={öêÕ«„†òñy~GODÂÀ‰TçˆÑ8yAú¥+"## ïŒ4¹fíÚN¾ Óç1t‡ˆ„ÁG4£"áé½'mú¡+âât¶;y;D$ Q#jýÐJ¢uuÅç]û¨+p¨+>„>ˆÝ¿A]C]ýQáéCÔ;¨ß ®À¡®ø\™>ª«kÞᤥe°¨+PW`ð~€Ï!–>LMLާ§µwÑvE]¨+þcjbò¹ú%,}ÈËË'üévþM3 ›¯ù¿¿¤§ikk™™õwqcôøñÔðøñ¢Cè.¾´~åò\mm-á‚þ’`Ÿ¢£ ¨+0?¹xéÒ¦Zºlüï¿ÿø¥uÂÿ`ÿ«ffaCt"<”>ˆt;ÿ&Ñ! ˆðPú } tXɱ¢ôA$4ú@>k(} >ÐèãÓlݶÍÐÐ(2*Šo~zúφ†FØ?KK«ï&MZ¹revvvûî»wÿ~pðêqcÇ™™š98Œ^ºlYNv'7//))©³¥Ÿ:úHL•™òË#vFz{ybs¦ ·›^uøÈ===lRBBbúôiÉÉÉÓ¦}Ol`ýƒðs“.‰ÐèãS§444æ¸Ï¡Óé§þø³Ëön®®ÖÖÖ™™MMMpøðaqqñ-›7ã¹££««¯ßöóWZR`oï`jj6ÙyJ||ÞìÆ›žž^ææ¶¶¶Ë\ñäÉS|Ѻuëç̙ûÍùóøû·¨wïÚ=f̘ÂÂByffcÇŽýé§¶³†ˆÈÈC‡~bÖ×c§]ãÇ;ÁÇÚ>ï‹÷äåS÷K& z¯ûý7.—»ø‡ÅÐGlöƒ¦¦¦sgÏMœ0wæ„ ž={v÷ÿ#**„—¨Œ>ª««soå.^¼ˆL&Oünâ/?ŸÀZ-x-ÇÑ£oݺU\\lff–——gfn&//ßYãþñññÑÑÖÞ¸qNñüEÉãRlÑÍ›·üüüìFÙÅÆÆ6668çåå•‘qB]]½;Á3õ»víZ³víÐ!C²Îg…†„ ò­““Ó’~à´¶ž:õçÉÌL ˆQøVüÔÑGoí·½;…wtuuNŸù+9)©ªªJ]]}îܹžžXµ`Bää766ZZYðÎÔÓ.K£]¾rÅÈØ˜¨Àú }t×éÓ§[[[¿ûî;øî»ïX¬–sYçº\KM]jªk˜õõõõï¿QÿF@ã=»w+++'Nž8q¢©©éôÓWc‹âöï:tHtÌ[[›qãÆ&$$´´´N>ÜÍàY¬–m[·ÊËË»Îv1Bÿüùl——§ÑhdI}ºú uUUU¾{xçEèý¶÷ººº¬ìù¡ÄƒËW,OJN²e·k×®¤Î¯ÚôƒÅÿH$ ®õÉdM-­¢¢b¢¢Bx‰Jú8uꔎ®.viÃpäÈAß ú³ç/mÃìnü‚d±ZnNšô„„ߢ–––¢âb'''ü¬‡N§[XZÜîî}YYY]]|rРoª^¿îΊ=üÞ‡ÐûmÃá¼ÿ~û¶íßOjan¾qÓ†Qv£>LàmŽ×U¯eeeÅÅÅùæ+))VUUÂG$ÒGqñƒ'Ož:Žv`þÇÑÑñî½{ÏŸ?¼beEÐétYF“yUþª³– F}kk«²2½£EL‡£ ¨Ä;SIA±®®»uC©Ô~ÄId§£;Gíõpô!ô~ÛS0¬¬­ð96Ö6L&³¢¢¢'ö›Ã&S:8í"S(Þ˜ûò ï}tË©SÀ¡C?e‡ý;–v N:%xÅK—/S©T}}}°´´¼]p»³Ï¼œMLL¬¦¦º£E²d2¹öíÞ™ojßâ—QÄ(¾·©¥¹¹›‡&XûÑGßíK0Ͷ?uý°o—da?!Š Šï™ÌöŸfCQQ‘>ħ‹uöì9Ñ#“’“xÿ 6üôé3òë/'Näææºº¹JJJ€¯¯/‹ÅÚ¶};߯¦Ò’’þ§š™šþuö,v›†•Ji`Ô«kjòóò-ÌÛ.ÚÑUU**+ñûÌúú’ÒÒn•*ÁêüWeûÑGOöÕýý¶7nÜX¸ví>çúÕkŠŠŠÕ ±÷^¡¥¥Éb³+Êù‡?eÏŸëèè‡ø;/W®\}÷î]`P …¹9ïüÙ®³BCÂò ,-Ú>Æùù/^¾`5³*++/_¾œ›—gkkðßMM##£5«Wpss›2uê uu&“™››—½76@oUà*oo___///e:ýÕ‹—K­[»–ùù-]ºtÅrÿ9sÝš›¨Tª¯¶å Nã“~JŠ÷òòzóæÍΈ±nÜËÀhii555ýrâÄýTqÞKÐÑ—žìKð~¹\nNN<*yW¯_STP ++›˜€½½¥…Å–-[ª^/SWWË:—•›—·qÓÁ·{û”••‰Dº{ï®ú ÷¿*++«ªªllºõÕÛÏáç&]">}œ:õ‡ŒŒ ßí}˜4É9*rןœÂÓGhHHHH())éé ß½{×øñãyï,ºÏu×7‘š’šš’òî]F3i°gÏ{ÐÓÓKM=º|hHhcS“ššÚ´imß²±±>pà@||üÊ€Uâââæfæ‘‘Qø]ÛáÃõBBCâãã““’;ø‡~hiîú9.˜1c]\\âöÇÕÕÕÑéôœœê¤bOãÓ“} Þokkk``Þ ,4 ìöÇ퉳wï¾Ø½ æÐ¡CBÃB¿Ÿ:Uˆ]÷:niañ÷Å‹“&MÂg^¼xI–Fspp 00‡ž6F0ôˆ-\û®¸zíj€ÿªœœ,…¶‹³g»eàOD€ýÚ“Éì²qMM zÚØW ýÅ­`övö††#MÅ&¯]½VYYáããMlT¥"¡ç}tiçÎø•ºŠrRÒ¡‚CéƒHhôÑ%UUU+«¶o£ 6\Ww±ñô'.—{åÊ•+W®`_ñèð5±¢ôA$4ú@º4yòä._¥"¡Ñ"˜½½=ƒÁüš@(} >ЗÖAÐèìÚµk×®]üš@(} >°ÁÅäÉ“ñFû×ÄFHdúÀ~÷¢ÿ"HgFõîÝ;Á¯ Dä·N…‹A¾xØ·N»“ jkk üÖ)‘󂾬 èKë^QYµoß^8š’òç©Ó¿ü’N騀ÒÿN»wïÂ'·nÝ6dÈ·¾¾¾Ø$&‹/Ú¸iƒªª*«™UQYqùÒåÀÀ [[›ØØX*•Š5HOÿ9""BSSÃÃÓC]]½¾¾þVnnðêÕ1{£ÛWÛîy¹¹?§ÿÜ‹éCQQAKK³ëvíúNá¿400ؾ}›„„ä©SlÙº•ËåΜ5³·Âëueee™™ÇÃ&縹%'>}úÌ×S";77°'žtöš@§ …âäô¡ptxx8N烳0·ÀjЀ§§ç/'N„…†í‰Ž^»f ܽ{7""b츱‘x±¸Y³f•–”°Ø„ÕIëu......­{öìYØ¿V¿ÆÁÁ~ò”)ž>-Êéãð‘#zzmÎ%$$¦OŸ–œœü•¤ÌäÉ“kjj¿&ÊçzíÃÍÕÕÚÚ:3#«Ûrøðaqqñ-›7ã¹££««¯ßöÃWZR`oï`jj6ÙyJ||ÞìÆ›žž^ææ¶¶¶Ë\ñäÉS|‘à’÷ŸZæ¾K‚w—˜xÈÊÊZ¸]“(d2™,-#ƒµ!“É4Y quºÔÔÔtîì9¾GðO˜0áÙ³gwÿ÷?¢¢êgVVVxŽèì5>ã¯9Ž}ëÖ­ââb33³¼¼<3s3¼.\{üãã㣣­½qã:þâù‹’Çmå—nÞ¼åççg7Ê.66¶±±ñÀ8//¯ŒŒx¡Áz±Ìý§ú¤]»Ìšõç©S;".Z@¥ROýùç“'÷ìÙÓëQõ–ü‚üÆÆFK+ Þ™zzÃei´ËW®X¿!üÆJ—>ãô¡¦®5Õ5Ìúúúú÷ߨ# ñžÝ»•••“'c%²MMMñEqû÷:$:f6r14éì<ùpòá 7t' ¬Ì=VÿÉu¶ëo¿þvþ|¶““o™ûžfoíZGG'>!!ÀßÿDÆ st$æªPw<(þ‡D"ih|t­‡L&kjiÂë3Nm•I]µ`±ZnΟï‹å^---EÅÅ‹/ÂÏzètº…¥EÁí‚n†Ñ‹eî?Õ'íúÁƒ–-]fldеA„„Ò‚ BB'/"¢ÐÉ ‚ _,”>‘'/x)à¯êê ^¢òBdú@_Öô¥u¨+0ŸQE'/‚ ÝyA%ú'/hô ˆÐèAD} òÅB£QÔÐÐPSSƒ?~UaÀ€?Ÿ Aˆ‚F"'&f¯ý”)S§M›ŽýKMKëùfsóò’’’ÚÏ¿zíª¯••µ¥¥•»»{~AnV­Zehh´i㦞‡ñ©Â×/o{¶ØÑ”W<«~ñУ’‘OS^^žœœüã~·nÝ,(È/(Èï­§ãäåæ&'%óÍÌÈÌð[ö£¼¼ÜêÕÁÖ¯761©}û–¯Í¥K—óó Ú—kêeee™™Ë–-Á&縹UWWŸ>}¦ÿ#A:ôu¼°X-ââT¢£¤¢¢lmm¤¥¥ûz_åÿ–GìŒôöò ÄæLkצ¡¡!<<|e@@Ddd_ÇÓÞá#G ôôÚŠœKHHLŸ>-99yÚ´ïû?˜þGøà¢K¢2ú(-)Y¾Ü”í(ss O/ïÂÂB|‘€RòÝ\777wÞÜyææ»¢vaó³²²¦M›nff1sæÌ þ^´hñ~ËàâÅK††F|TbnÑ¢Å|UïûHÈöÐ~XžžÞVVÖÑÑ1íÛܸqÓÓÓËÜÜÂÖÖvù+žGFFæÜÙ³çΞ=v, îÞÑÕÕ9}æ/'§ ÆÆ&ß}7)%%•÷7^qñƒ¿dlÞ¸· F¿É/Èoll´´²à©§7\–F»|åJÿǃ´''/{öD+++'&¤R©`ii9wîÜÄă11m¿~;+%ßÍu7mÜhlb‚ïî`üAÑ#ðIíï¿o¶S(—äää  @ÈÌÈ”‘‘žäü¡ÔHß¡++«©©À AjíÄíß?tèè˜=X­ICÑÎΓ'Þ°qØÙÛÙÙÛa-ML†×›9sfii©ŽŽŽ¼¼”x0`åJ55µ¬³Y»víjiiY¸p´¶¶nÛ¶}Î7Þ*ÜýéAñ?$ICC“w&™LÖÔÒ***&$¤~†N^ºÆb±òóò'Nœ€}þ€D"íXxçõ³RòÝYWRRÒÈØŸlnn.~ð`ÌØ1øœ!C†hi}ø5k«¥åÌ_g°íÿþÇ©)“§HIIõîQ ¡¥¥¥¨¸ØÉÉ ¯SK§Ó-,- n·Ý+a±X gÍšecckffáæ6ðÇ”·ÇápÞ¿¿}Ûöï§Nµ07߸iÃ(»Q‡Ænm¤¦¥½}ûfÙ²¥}~`x]õZVV¶ý%[%%Ūª*BBêOÏž=#:„®?ú`0˜,6ûÈ‘£x9àr9¼÷ç:+%ßued¤yÇÞLf=‡ÃQðÑ×(ðL*))Ž?.ãÄI×Ù®999µµog»Îî…ãì1ƒÉáp•xg*)(>ü§íL-,<<ë\Ö~~†ÆF2Ò2LF§—wsç¶ xñâ…•µ>ÇÆÚæúµë’’â¬[»–Ãáà5´[X,&“)%%Å[g»ï°9lrG4É û+vLMLÒŽë~ã;w:.ß׈O²²4 …â1ož×ç„XWV–F&“kßÕòά«{'-õáN‡›«ëüù îÝ¿Ÿ‘‘idd$"™åädÉdríÛ7¼3ßÔ¾•——Ç^Ÿ9}ÆÇÇ{Þ…ÂJ»ª­­©­u÷Þ=€#d—d¹¼¼üÿíÝ{ï×¾vgžyΙϙu>óœçœ9Ÿ¶¶¶Ý»#"ø¯^¾|ùòåËÇmíl¥±A}ÐÔÐ|ÓÔÄãñf^šXlMMM9 @º£F‡/\TWûDwÔQþœr®ï•Ê€âÓ•Jµ²²*¾Q¼%x‹¸_k,K£Ñ¦Lžœw5ï‹/¾ÀZjjjó|AÁç`-ÿ—_ ©©9RwäPuõä”Nom ØhŰZ¿~ý$y%S}—[W[‡Ÿ¯AU×ÔØÛÛË'E±´´=w”””(*Nŧ„Ðöí¡ÞÞ>ë}}WxxŒ1¢©©ùÎßwx¼àà-²XÖ£ÿæ -Ì]Ì%K—46²Ž®I"0 ´ÜcÅþ¨¨¡C‡Îwq‘ÂJɦÀÀ7nÙ¼rõжֶãÇS©TŸu>!2™l=Óú׌_ç::ꎕ›“ƒ?„200hkkûé矧LžB£*Nšdog?ƒÁؽ{÷³ç›FÒͺ’UT\Ì ÛE&“étU†•~q%%%íáZ2emmM"‘ÊÊËð飾¾þÙ³g663å†B”””ˆžA,--º§8?uŠš4É臾1bDttì† ~{*ÿ©´´°Ñ²ö1ÑÑåwîøùù9òÝÆÇŒ§¦FÇ÷qqvB-Z¼ˆ?)K663ÙMìm[CÂÃwÒ}öìÙQ£ºö®½{÷Ž×¿té²Ù¿]¼x0>¿¬£ãww÷„£ žžž7"„H$ҡÇÌw9qüø¶­!‘û"åsŠZÚÚÚ3Œ«yyøÆ¼¼kjtºƒƒƒ¢¢’}ˆg„ ±½\×úe(ÿ²HŒ@O±–ŸÌwq™ß5¬`±XLf˜Ó¼Oñò BËÝÝEÞé`XY•——á[®_ÿÿtÖ,›Y³lz\V[KëÈÑïð-øU)))…‡‡…‡ð»:]õ›;¿Ù¹³ÏÀŠŠ ûì#uÞ>Þ[ƒC^khtMvdfž_îáA£ÑäŒ<õ—ÑQÒ‡<µ´´$$$ZÏœ1l˜F]mmJÊi•… b¯>zôøOþM8š0{öœ/¾rcogof655õÜÖ­Á¡‚ü‚úú:ŸµŠŽKæýgÆIDAT`ôA\ ¥¦ºæâÅK,V#Ng0¬¢£ð™¶ÿþÒ’’©ffLfßßÉ@ÖØÏ¿tE{„Vrr’´~CHd0ú .*•z4áHo¯&%”g0@8챑‘±bƒ‘›þ2ú ÄÔ)¯¤¤DÄÜñnô¡> }$£€„`ôŒ>ê/£Ež¸íG¥€e >>ø(0pÝGß œ:‚²ò8ðQ`&*WWWWWWþÓ æ;99ÿöÛ%cc„PxxØøñãù¯‡„†ædç¸-qû¸eèô™3¦¦¦&&&ØSææ¶8%%eñâEŠ LÖúËU§D}ôVò!ïèèXTTä¹ÚÓÊŠ‡º ò³³³±ÅÝÜÜøÕ³…#þÁ ¶á¥¥¥^ž^––Œ¹sçž:Õu´“”tª©¹ÙÌlš™Ù´y󜺯j˜††’«ü„Â焹…B«õILmmmW._qqvÆ7:;;?~ü¸ìöíÞ–`ô!¬äý矻†…1YGa³Ø&&“ùØì昘˜_}¥¯¯ßÞ&Xu1¿ ûöÎ.ÎÌ0fÃëׇnkmÅ/Þ±ÙÍqqq_}ýõx=½¬ì¬È½ûôôÆ999øûwvtüç?¿ýš‘R¢¼/æøöíÛŽŽŽW¯_%'§ÐhÊK—,íqÍ%%7B) ×£7o´¶¶Î°fàMLŒÕèô?¯_Ç×-xúËèƒéCHÉ{ ‡ÓÆdN77ïqñã‰ÇŒŒc¢£±j†zzã–/_a"ë å‚Ãiß•÷Xîq>ó|vvŽ“““ºº:N'“HØBüü±S ÍÄ„CÉÝWËb±üÖØØdölâL¹w÷‰4aÂ3Ad2YßÀàï¿ï***ùè/£Å¼ôYò!ôÉ'Ÿôömƒ-îä4_ ÕÈÈXOOOvË“šš–;0£Géóp#Œ¹ë̙ӱ11úú6n ,++èðöíÛmÛBZZ[ããc•zªAMÏŸ=WSSSVVh>\óÙ³g InúË™Å>ú,yRU"P'Y`q­áÔ×ÖÖ–zœ A¥~°óȤξŠËóGæÎûtÑ¢E‡>}úý §=xëÖÊŠŠSɧƎ+õ€¥ˆÛÉ%÷”ÝÈJJܾ>„þF¢ê­ä½X‹757㛚š{ë?x(S( Ÿ>}Êoáp8!Û¾¼}ëvâ±Dþé ÂÒÔÐ|ÓÔÄãñÚ›XlMMM…„$7ýeô¡øôÁ/yÏoÁJÞ‹µx!®„â‹—/>| å(‰‡J¥q>ü¸žŠÍf———óã¸\îöíÛ ‹ =2mÚ4ù*)}—[W['Ð^]SCä_©è/£Å¼ ÑJÞ °) (pó¹sçV®\ñúu“Öý€yà100hkkûé矧LžB£*Nš´es°®®®±‰‘ŠŠJmm]æùÌææ&?¬ÿÞÈÈ«WóÜÝÝøç¶ÇKØÚKÖÖÖ$©¬¼ ?=\__ÿìÙ3Âq—.8ó"¬ä}Bbâå+WÆŽx.-] ä½övö11Ñ ‰‡©3rí:o™FKŽŽsÜÝÝŽ&°X,mmíÜÜ;{»ß/]ºôû¥¶¶¶aÃÔÍÍÍccbMM§`ýï”—#„222222ø+Yµr…(õ±B[[{ƒq5/oÁ‚üƼ¼kjtºƒqOIE}"""F2|*kÙ-í¡ÝnZÂHKKwuýLCCCH//O!k˜¿àByùë®îíÎt,ËÙÙ%((pÍš5"lBÿwèãëþQääo ÉÍÍÒÐèšìX¾|…­í,¬bö@õàÁÃ!ªt±2È’%ç=b]¹,ìbá;æÍ›7+**…﹡=^"„†Ð(Fºjõ7Î(~î!ÔÒÒw=ÿzù;YYY~~þø’÷`в·³73›ššz{Z_P__çã³V±QÉA}âàExÉ{0˜8°¿ºº{¬=B+99i0üaÀ܇„—¼—XMMÍÒ¥=ÿü,33cÀ\Z6°éèèèèè` ;Ë+u0úP¼Q£G_¸ÙãK#uuå ¢ƒÑ‡â)S(¿°€õ—Ñ!¦NxpÕ)@B0úHF õ—ч"§N¡œ:>>ø(0pæ¥op±6‚‹Öqà£Ààเ„`ôŒ>‚Ñ@B0úHF²%¼Ä$¾Æ¥dkPÑ£**.NNNƷܾu+ `îܹææ¶³l°zQ˜Â¯¿þÆÙÙÅÂÂÒÑÑ1,,ìÅ‹RŽ^¢öEmÞÜuo±Ô³gÝÝ=î= ÁèC¶D¯D/»5È‚èQ¥$§à[žÖÖª«[·n]dä^ÿ€€'Ož®÷ÝPQñöêÉ“I>Z¶lÙž={–-[–““ë½ví›7o¤¿ ÒS]]ýKFƦMØÓ•+V¼xñââÅKŠJúËè£ÿý`ŸÃiWV¦~L%zÌǯA>&*WWWWWWþÓ æ;99ÿöÛ%cc„Pxx¾J¶±‘qHhhNvŽÛa7ÈT¬ÓgΘššòKÒÐh47·Å)))‹/Rl`²VWû¤¤¤´ôTrŸ=-ÌÍ---ãU§xYYY‰‰Çž>þ`üÁ**ÕÕÕV¯öôõ]ŠŽ‰IOKG™™MC1"77G`UÃ44””(¼Nì)>w „Ì-,B}–¼T ¶¶¶+—¯l Â7:;;Ÿ>}ºìöí^"»´ôÖ­0&³Ïž{##åOo‘>®ç_ß±ã«Ï?w c66²Ž=Âf±ML&ó;°ÙÍ111;¾úJ__¿½í­ÀâùùÛ·ïpvqf†1^¿>tèp[k+~ñþ‹ÍnŽ‹‹ûêë¯ÇëéeegEîݧ§7ÎÉÉ)Àß¿³£ã?ÿùí׌ „å}1Ç·oßvtt¼zý*99…FS^ºdik.)¹‰p- èÆÍ­­­3¬øFc5:ýÏë×vú1w „¼<=ÓÒÓRÌa8!Òljc'̦NÝ·oöÔÐpâ¢E‹ñ8œö0&sº¹y‹OéíÛ[ÜÉi¿†¶‘‘ñ€¹ ²šš–;0£Géóp#Œ¹ë̙ӱ11úú6n ,++èðöíÛmÛBZZ[ããc•zªAMÏŸ=WSSë^0pøpÍgÏž)$$9ëb#T|úhjjîììÔöAÝ©a>UUÂÏ=.®5|8¾Q[[[êq*•úÁÎC"“:û*.ohhhaaá2ßådÒÉaÃ:Œ•ÃiÞºµ²¢âÄñc¿,·“Kî)»‘•”¸}}ƒ@¦3f ¤Ajjt2™ÜÐØ€od±ÅZ¼©¹ߨÔÔÜ[ÿÁC™B™h`øôéS~ ‡Ã Ùöåí[·%òOg–¦†æ›¦&'ÐÞÄbkjj*$$9ç l@­§§éã4mÊäÉyWóø-555¢ß6[¼wåÅË—>r”ÄC¥Ò8~ ü1±Ùìòòrþq—Ëݾ}{aQáÑ£G¦M›&¿@%e` ÏárëjëÚ«kjˆ<ã+Eü¡¯ÿþX^__Ÿ8éƒS§þý7maîb.Yº¤±‘•ptøpMYÔÔ°) (pó¹sçV®\ñúu“Öý€yà100hkkûé矧LžB£*Nš´es°®®®±‰‘ŠŠJmm]æùÌææ&?¬ÿÞÈÈ«WóÜÝÝrrºÎòŽ7–°µ—¬­­I$RYy~z¸¾¾þÙ³g66„»\X°ì`l,ø?ÈÐа¢¢B "Dúp°wˆ‰ŽNHL¼|åÊØ±cÏ¥¥«©ÑE\ÜÞÎ>&&:!!ñСÃ#uF®]ç-₽ͧô ŽŽsÜÝÝŽ&°X,mmíÜÜ;{»ß/]ºôû¥¶¶¶aÃÔÍÍÍccbMM§`ýï”—#„222222ø+YµrÅ7;w*fú¢­­=ƒÁ¸š—·`Á~c^Þ55:ÝÁ¸'Œ¤KwïöpšIáã !ÒBÈe¾‹Ë|ì1‹Åb2Üæ}Š= ý24ôËPþ~~üü6¼_ÜÅÅÅÅ…ÿÔc¹GŸïØÚÚª¢òÉÇÆ-møíê¾á±11üÇJJJááaááaü–U«V®Zµ²·5gfö\pȼ}¼·‡44¼ÖÐèšìÈÌ<¿ÜÃF£)60ù HŽ‚飥¥%!!ÑzæŒaÃ4êjkSRN«¨¨,\¸PFoÇf³ï”ßÉÍÍ555“Ñ[©°·³73›ššznëÖ`„PA~A}}ÏZEÇ%'>DB¡Pjªk.^¼Äb5ÒétÃ*:ú€ì ©ßº}+dkÈdSÓ;¾”Ñ[i9p`uu5öX{„Vrr’ìþ0ˆÒ‡H¨T*öó骩©Yº´çŸŸežÏ0—– l::::::ØcÂÎòʤE5zô… =ðÔÕ•s0ˆåñãÇŠ¡o9}(S(¿°€Y˜›§¥§‹ÞùÖ-–Lãéâ/(ÅÝ&Nº¥k >è¿DüÁ>Rè-?`ô@?¦Ø)}@PpæE(§ŽÏ> z¼@©¦¦F®1õæ> (ì'ù=hôèA~°é‚âçˆû÷ïóïß¿Oœû}@ú€ ði»ÁGEE¡î6g^ (ìp÷î]…ç > (¢%‹î }@PÄO„˜û^V~ÿ ÀÍòŒïèèØw?©þQà''÷P5¿ ßÇgµõÌ3¬W­ZuãæÍî}BBBÌ̦…1ú¿D4Qû¢6oƧž=ëîîAüJZ‹2ôD±"}³Ø½BˆþQ¥$§4þ’ñKঠuõ¡;vlßµsçtsó†×¯ú\»öç7ûÅݤ«««ÉÈØ´){ºrÅŠ/^\¼xI±QÉ ñÓ!^ˆYì^Š8œveeª(=?棨}Z} f­·7ÿ©‹»õiii‰ŠŠÚ¶uk4î¶©„uúÌSSS~Iææ¶8%%eñâEŠ LÖ,ÌÍ÷FFzyzöÙ3-=}°ÿ`_`Äž½x±›¥%ÃÍÍ_RƒP”––zyzYZ2æÎ{êÔûü7ßì\¹òƒ{ñÅúàà`ü²ÿx¯õ±²b¸~öynN.Bè‡~üì3WKKÆZŸÿ=ùŸ@leååž«=--ÎÎ.gϞÿt¿ªjóæ`ÛY¶VVŒ5ÞkKKKâ,**ò\íieň‹“ࣲ±Ñ11II§šš›Í̦™™M›7Ï !tþÂyççï‡êìììqýGŽÕ9rÉÒ%"Æ£@mmmW._qqvÆ7:;;?~ü¸ìömEE%––Ø-?úüÇÂÜÜÒÒBQq’"""F2|*kÙ-í¡ÝnZÂHKKwuýLCCCH//aY3>¾4'÷_}}uÉ"xôˆå4o\h¨°<"|ǼyófEE¥ð=!´çÂK„ÐÅHW­þƼ`™“û¯ü߀©ÏÜ!#Š™û µPÈÖ¤ˆs€þÒ@B>‚ô¤€„ }$é !H I?}èè§¥‰Zž iiéú2øQ»ô¯:ecƒ‚ qèèc;¦tÉä¢õY66²ˆ@(0÷¤€„ }$é !H AúHÒ@B>û²1}ýK—~766’E4…¨¨¨”àªv±Óv9iEE¥¸ K²«Ú%¹h®I ˜ûH Ò@B>‚ô¤€„ }$é !H AúHÒ@B2¹Ó:ÀNJÎ/(PtÒgogç뻾Ïnÿýë¯GÉ!9“ßo^À …åŽ+—/+:雿`BHxÁrGhh¨¼‚’Ÿøøxôî±¢ƒôÄ_Pùë¯MMMŠDúRÏœYëã#<}€Øäþ#úF òÍǃôÄ6È÷ŸA¾ùx>€xêè]Ää›/ÒÛ€ÜD7È7.âáÉ@aQQ@@€Í433''稨¨W¯^á;¤¥¥›˜˜<|øˆßr÷î=›™3»¹½~ýªÛú$$ÅÍ/++Û²ÍÁÁÁÌÌÌÆÆfƒŸ_vV–´â”À±cÇ,,,„÷‘àF@l’ý©õ&õLjLlŒ™™Ù–-[454+ïW~Ÿ–ž•••rúô„ñã»Þñ°co]^^îïï7n츓IIêêêR‰Gô•ôÙ3ýûïì߯¯¯¿Æ{ÍèQ£›››ÿ*, =rä»Ù³ç|l ’á!$4rH@N¤˜>nߺçìâK&“Bóœæ-Z¸pÕªÕ!!Û2~ÉÀ±¿~ì«¿´´dãÆM†“ ;N§Ó¥›ËD!üoß.;°ÿ§Ÿ~K¡tí_Ë–-«òÝÀíàÊ?ZLWþ•ö»ÃÁ tÕ)§S(J“I"‘øcÆŒõ÷󫪬ÊÏÏ7°îúë/,,òóóŸ2eò‰ã'TUU¥ŒT6?%%YYYy÷îÝJJJøvÃI†&&&<¯ª²*88ØÑqÎôéÓv1w5¼~Íï;{öì’’›«W¯27·˜3gÎɤ$üz*++·lÙbkk;}úô $&$â_ ²±™iaaáååURróý¦ñú^‚?}±Iö§ÖãzŠ‹‹­VÝ@£cbŠŠŠìíí±®¡‚ü‚C‡Y1¬:L£Ñ¤?©ô,..¶´²:thoÝêëë'Mš´dé’¡tµ'OŸ&''o>›šúníˆÍnŠýêëoÆëéeegEîÔ7ÎÉÉ !tïÞ=É™L¦¶¶Ö¿5ÿV=¸½QUUåš5Þ&&“##÷ 2$##Ã×wùsç&OžŒD}@úò ñ7UwÍÍÍÍÍÍ£Gé¾B‘#I$R]}=öözLl̘1c¿;|XY™*ÝÜ!:á›/d‹ølílmíl±ÇÓ¦O726^¶téýªª‰††!âq8í»# 'MB-w_~þ×̬ììyóæ!„âã㵆k%§¤Ðh4„¹¹z·çyòÄ·ßB¨ï¹É@úb“âèãÝW(ðöåiooŸŸŸêTr@@€Tèé?ª§-âãp8)))999uuuíí¬ññãjƒ‰Bˆ‡ÔÔÔ&ò×0jÌèçÏŸñx<§½´´tݺuTª`öäp8ÅÅ7Ö®õVVVæ¿ä0{ö?þˆÿaôOZécÈ!ªªCž<}Ú}…õuu<OGgd×K<„Ú¶m›¶¶öñãÇ©4ê:ŸuR‰O*éCUUUUUµÇ-â‹Ú••µiS ™™™ªª*›Íöññi{ÛÆßÏñ)!D"‘;¸<Ínêèè>\«ûÊÙl6—ËMM={öl.ÎÎŽŽüé(˜^B 㯿 ÕÕÕñíyyy¡ +ü—'Bh×®]íííßþŽJ¡®ö\-­0D×çæÏ˜Áøïÿê¾E|¿_úÝÛÛ{ÕªUØÓ÷ï#Ü9éžösö¶tº*…ByùêE÷TUU•””V¯Zí¶Ä­{Àμâ>/–5^ÞíííöÀ¾'1Ožü/)ùÔDÉ66³ºvWÜNîäì÷KÆ/RŒ„'ÎèCoïµgod$‡ÃÅ·WUUÝ»w¯££ƒËåâÏå^ý¡wùƒ‡;øáGÅëz•BQ677¿|ùJkk«À›*++[ZZÞ¸ycôè1zz¿8óˆ@²?µM›>-hsБïŽÔÕ×}¾ðóaêÜÿé§Ÿ(Êþ¨ýØÙ\„ûòäñxd2yïÞHN;çÀþT uᢅR‰D¬ô!äÕ©S§†††ÆÅÅy®^õÙ箣tG557Ý(¾ùǹñññÆÆÆŒ3233fÏ¥«ûÇüšñ+Â>º xàà`__ß ¾¾ž^^ZZZOþ÷¤ê~ÕöíÛBÛ¶m]ÿÅz?ww÷#´›ššÿ¾û7¯“„`î„ÄßT½ñ^ãmll’žž–p4¡¥¥E{Ĉù æ¯óY7|øð÷oÄ{Ÿ>BJdrTTÔö/·ïÜK¡Ræ»Ì—b<‰²ù&&&éߟž–ÞØØ¨¦FŸ`ôäaÞùæ €ôÄ6 ÷Ñ òÍǃôÄ3È¿~ùæ €ôÄ6È÷ŸA¾ùx>€Øäþ#ºA¾ùx>€ìíì¼Ö¬Ù©è@¤o“iog'¼¾~jjêœ9sä‘\]»vMß@_Ü¥ }1`%`w1™ŠDúìí세Eï*H_»vMÉ—¾¾¸n¤ ._ßõ}îfØ, v³ ~°¤€„ }$é !H AúHÒ@ ÜöVþcHQqÛ[ŸWÿÍm‹=%s¹\Åè/(T•ã§R¨4„—Ë%wtt(:$@ÿÀmo¥P?ÁwttP:•Õ9]/cwR>n{+…ª‚¹Œ˜`Æ¡:(jdû¦/ïçqøùpºæ;8o»Ž\”inÇ«ª«3§O"#„‰¢ÒtÁ] Ýð³BˆBý!ž ëOIag^fk‘ÛÕ^þ©Üú”ÔÑÚÇʃ 6ßAêhUn}ªö0À5Ø·²NÒ&ƒ úR¥”‹I}‘í c­¢ƒ”MKcàÒ´¹ÞJÒ‡¸Œi:·„ÞÝŠ"ÛÆŽ¬šH!ôacd vQlŒ†m»,I\j’ɹvÊ/ªhÖC.Eé ‹%_ F¡mËÖs)‰}Œ3Ò“t)ŒkÕ“¶µFSðÀHv€ZŸm€]5IHCvཨÀCF™ é?¨9è^—ávAŠ×ÈMõ´¶î ·Ö-=ež;‹ùR³Y£¶5uwÇ.5hpÝ•‘s‘AG™,£À„®\„VÍS(6£y"^™v#0àœ1¦‡"Žá/;ÕUYj„ãó˜ ÝV(Ê;Ã2ìqà¯b4ì…Ðù¸Æ«4v"%m‘lHÒ-üÚ<5ÓcæqÀŠVHݨš’úO%-Š—cÀ-éç¤Ããøêæ30¨HñÈKrÅô¦G$p\ö‰ªA jLùsnÉŽ”°@NËF­d0VQ ÜæÕ˜êÔczàQv)ZI¶h+ãȼ´îÂP –% íŽJµ”™’<ÒTˆµi!ÒæÆÑìÔŽ6N‘H ýÅ»~.£Šô’õ8˜t²â>(ç6é^,nåH'CÒ‰”¸ÇÒ­ÇÆ ˆ$q)h¡Üâjt1M4w<*­H‡RPŸtŠ#ËN‘Î8p°5ˆI§ ­±ŒƒE¡˜«± ÈÓD 2¢Ü\k“¢0(–°Hnã(z‰Lë5œ¤aÚ—jçÊY¡xšUعº¡?ìHâ‚4Ã1#)³æMˆQÉK•o•“ ИyRoƒ”ƒké5he}ºUTÞ¯M&{)ÆA?CB|h ÅËs‹áýÑhÔU<~VÆØE=‰ÎîÄ¿V¾30eýrhËÑ+ŒRÙ£í¼ÂøE í6G¶qø©] |Hó1¯‹Õì6CQp›j”ç¹Æå¹Fe)Ì`¬+ÊYBi…³K"’? C85 cj˜ÚÄW1V+‹©MÑ“QùšÃ£D’Ú"6Ó4cš \§ *"0-à1[³5ÙeYªÚ¿\«SôR+F õÆ•œªêzr¶ô‘Šcª j#i¨£´Øf·%TÔ,cL&Ȉª¶h¹6Í T2EPZ¦šÃ4K½,© úeݪõ N 43"þ;©(´1¡Ñ":ÇA‰Êt¾"N3B1©8ºUAÒF’2ø*óT…2ÔRR•ÚdZ7‹n’AZ#æÈ¹êf£;ÁQc„8£ÎPç`Œ†‹âiú¿âVä JE$¿6x˜Ð œÃhiŒJJ‚Då¬Ir€>œ†VWŸ³àŒ+‹e˜ V뎈2pZ²\M8 ?FE¹n6âê#Ȩô®SÙ2r0*F£âV²¬D"n™q‹[¢DÆŒÁÞ?r…Œ†!Œ™à xjƨMëÞvÇ»Z±žàý@zWašœr!LåÒAÍ»M–qqš»p€âeÌrIJÌ×´1Ôq´üŒ–›f|,ÝÏ F¬`7ÛÉ9¤ú ?à*Í6г Ôc|²?ìÑ…’“J®$Á†óWЦ±x… ¨*%NÍW«HÄ/œY¸nʹ2K4’¢€¶ȱ:PsNÍ}©ëõÙ-€ (û”D@ú6ltͲŖˆƒ½bžæê€Zε¼ׂ™¼“J€œXe2ålœ‚:6iVèˆ1h€8Çl­™‡pŽ8€’`¡êlj¾²(=‰%[Î(ÿ*I‚ÊÊp2m4ˆ“^Èé3B9WlKÝÚïϱ÷ç­ˆ4iñ]Š/SúÄGy¢s*S(›ÌhÁJ'gd: «QÜ&ã0(í0¨¤-ã89àrJ3´ñ<=¿‚w MR—© Ô¶ iH³%.ÏmMæ‡$ÿçêÂdý‹s–Œ8Ä×e٘Ǵi=‚²1­‡E«(-Åäà”I>$Lºé³’\2 {‘âÌÂ!(gèõ¤v4w-G4%}¶¤1*J¦kŒŠ8]/F…9J™îÒú²ÞÃdTœ<ê¹£âý2*- n%ftr¡|QffºÏír™‰DG¨§¾)´½9ârû˜qè-Ú#RÈHáp„é1yšÏïjq Ý%¬!“ªÿðd L(Líœ?slÉ„\yõÉcƦÀoïÛѦã àç”g¦¹àΗ¾<'¯¢,w½Òpý©¥©¾~¥oîˆýæ/;¨ñ¢c‹¨¨ƒ¡ø0.d°§¬&W8Ÿ<&}ny`tŽ/3Íã2 I|Ú^ùÁÁΈ ¸¼C¼Jz:©>׉3s¦—¦g¤¸#ÑÄŽÆÈÊ#¦W)œhóŽH‚|¨t;Œ@$Ê÷%ø°i%`L¶QYêÚyÐj8hi¼J/PúzL^3Œ’L9WDàÐÐ@äÉZâ])gjÞÉÉåb0 @–‰ Þeèê¤ â’­µI5¹ƒÅZ1!@_“¨ÆXl5^‡Á0‹æ¨¸üF]ÆP_äë‘ÔáöÑcÄ 5v%9ª}³”‚Ë +o£ ”f1Ÿ„ 3ÆQë½ÈÍ÷Ro ßVL ´Ø€É@ÏLõ2píhô+ CõJU»¹}™ÄV®Ò›­ÈŸ0¿Ó›B‰ÉAµ­N©ø @D 4"دD³åÅ2r ˆ  ÄD€i«¾¸å •U£aÐ’6^ŒH ‡šœÉÈÔY†Íx“è”R©N§S§«U*Õoÿ †2“,X3×dVL‹«d°6H¹29š@dçA$Ód—³®ä$¸Ú¼íùž’¡_F6îGÔ•¶s £â¶&©¥‰¾U?«~:£ê?G¥3*èVÔ ê‰„™é‰8·ˆ¹<ážDs‡iñ¸Á˜×íQ^0½,ööææ éq¹ÜGº‘Âa câæ)CôOB·³Aà¹_.ÛÛPû#™¯¾ß±nÕÊ.ÞÔf èÇË R£ÚÕÚÝÖ5\fó¯Æ<Í‘sÆ`á¤ÌYe¼ ¯ÇÅ Æ2ÓÜU2¿yZi ÅmZ¸W€1¯Ç¸vIÉ1S³³Ó<.ƒRÜ•ã3®?µ43ÍCi)L{à₊ Œ²áâ1d ³\ÇŒwÍ(r-(w7ù–½‰±9FVŠÁ…S €Qšœ7N›¸Z+Áð‡þ‹Ü<ÍÆ@zVÀð­ñ"e\º":k‰qÏ@s ý 嵄c×"¡üIF«yqrÖÀ¤¿¶eN”Hz›¨ÚGû´MªÄ’¡Œfã”ïTù1I&ÄaFR†+=SM #‰Gÿb†€È`qYšèzéÞ §'÷@ \r°8É(÷1ÍÓ â¦´(s]²xƸpãœI˳ .ë#˜3BPg€¸]ÿ~~ú¸Ë b±r’ëâ‚s,ŠEgØ78 [&ù9<5Šóy¸¼ÄÁBKÌ»žapäËD™”çá¢O91šÚŒn k˜ ŠÑ'P6Ï’A {WÔEçNd‹š©?æ¤ЧA²qÊÖ’È«ìml¯ (›`DZÈfqÖC\ŒÓ„JÝÈRÁ"é8f‡Å¶B»H$;§ïäĤVQ1œ $¢*ɯ¦¤c‡QA/FÅy:åuô<˜Æ¨xoFÅpÃ1*Άʨp„:˜A0*ËJdy{N­*&\­±–ÎX[(ÞÑe¶…â-±ÖÎXÔtŸZ5&Ó³¬~·H!#…Æ2S®Å‹§žŠ&ÿÀßžmëß}ùws&uýüʺÂÌŽ÷–ì úŠRƒ™9}^0?vj¶a°œtÏ»Ÿ´SRlñŒœ1¹~Xµµ}ïÁžƒ]ñmûÂì 5uDw·ö|°3´©!´uw׬²€øÉ¿7njmjmÝÓÕ6pæØÀ¨,¼·­£;ÖGÃyÁ ý™"àe={þZÝúúÆÖmû Óü^Ãç1Ì„U·¿[N0ú 3_2;ozi:¬ÙÖþûÕMsRqšÏcd¥y¶ì ÉÈ ÔTNJmÇrà ÕÃfqUïJìïH4w´b£©“Ǭ4ר׮¼&¶Èk3\ÁÕpŒƒ¶O\:GéÒ¤02Òú8rÒ€<]Í—¥›¥pË€áR×9¨–i< =nnœ@ê©ÕNQñ@Î ôE:`´$£…5q'±uÚ¨rŠ™Ê™. &6IfÒ<]!äì#Š£8P n5å+º¦§.Ä’ÁHj°ˆ¸¨%$ÁŸ6À°È°ódê…¢¾6Q·Ë®}!ÂÄ4ª¤}$û}ƒ‰‹v½þ%¹ðdýÙd#}1Ê!QcìÖº5¢˜ÒzÍܹØ8Åh9TU.MD}æ2 ¢©744Q?²û1„¿dc‘ÐSžLËÒES‘â°Rc R©°'0+ÜhÃp¹ûQM1èK’…£4B·¸`­•ŠõcCìÞDº ÀÁÈiŒ“+…½vÕ¥ÔkÚg† …Q†^Y"“ %Óé+A¥\£–ä&*™É×Ô‚\G'©Ø&¢±LÎÒ…rts²3*E÷Q¡Ï âz(pn%z§V÷$ŒÎp¼;f1†ÁÄcOpËâ.—1~TêÇ;nëUìˆ2RÐ…ù´¶þO¿ÿ]AQi #KÓÒ´ó/Ï?”;ª$™=€0nƒü®®ÆÍCΪíÙ¶þÙÛ—@óÎ]ËîZ¶pÓµ§ú¾zçÔ}{»û[ŒÆ­÷vUŒ ø<ÆÔ1éî €ÁØÌ±’-mÙ€ÅÓshI®nDÜëuØl ¥ÃбÿúrInÀ›ê38‡`·Y×ÁÅ5Ò|®“*r§–¤3€mû¯nh ÷ôM3‹s|‹gäŒ+HIõ¹ºcVmSä­ÅÄÙü ï)sòÊ RR}®D‚wDâûÛ£¯nh uc*sËØ‹ïíÇXWO÷êÛΜW¹/åP¤ ‹TcsÇgm¼±é iZk¶µ35;+Í=­$-Åëꎙ4n(¥ÄqéHxiÛåP”ÉZCVW”Ÿ4Õï×™“ ]›÷%&¸3R Ø‘žàÚ¢èÄ x1¸9®,pPÏíÏ“8r¥67¦æˆ@›·J±AzPK°6%DR)”nò."-A„€#_ÑÂê °É# ƒœÊ0©|†¶oL.¬pÙnb[Ò¡‹sÊ5!»"wˆþK$œzJ®2XvŠ&4Wî/‰`·¨& QRùèì- žèI]LÝ(ÃÞopÎE ±Ô$›ã> bR6ÆU“‘LÚII&ÁA‘Hê9^$S¨CdÏ{S.Þ«pEC„mâØ «Ã(¬Ò´ét6 2¨*°¸²BÆ êpÙ¯x6ß–íµ±(•Àžzµìáab TÓËxhe˜%34*µÌ'5…+ÚÉ\Jó7r( €Ì"GCÀé& _"•IòÂH~Ždë)3šç¨~eJÃB)àŒó]¢‘vc‘Š–Ä\%¨TÊ ‡9ö2Xl¼JPqÌôSŸhœÑáj×#h *ÝQö³ä‡³-ÀP—*år• ²›7u.à½R>T³ôkvg=L3>µÐíñúƒ¡X<ÁÝ®>îŒ'x$šÈ ø¦Œòlk‹{<¾Á²}Ëûyö¾¯^òí‰3æ ¦‘‚.Ìê7^xÿïüdãêëúHAñØ–} ÿ⺮΃)~ßè«oŒ0CÞ]_2e¾ø°§ ö@dêvïÅ+kö¥ pצú ø ÒN0¡(5Íç€íû‘èÐ’{%yþ±ù)ë ›f‚g§yª&d^÷å;¹w/9¾xNy†ßcø<Ƭ²À׿4Æeôa“G§]jiÅØ@šßŽ&R}®Ùã7œ^Z˜åƒ±«N3£4=Ýïêê6{â‰ü ï̱@Š}×CÞ¼;f¡¿äà÷È+ÛºâÝààgÀ²RÝé~´wÅÍ„ôëÍQQ{q®Oz ±î'Sàj?‡‚©ð35äÐG¾_ŸXWgrÆÜ.H˜ü@—•›†SJ¥ãÂ@¦¦r´á‡’ñ*¥Á”ëÖVmüÆ•|ÂE¡Êh:ʼnZ`Ä[-Hj9Š…Ê«— yAJTp\Ã8Ê00s$’ºç‘M#º£36òþ4Õ×BP“9¤@ºjM}¤&¥O žÀ<—4PêsàÜÂ(ˆezèâz S‰0Špι…­á©‚‹õ`˜ÒãRfÙCtˆ®Jj½ýDŸ?}A”«‚hLëoZ^•©M!3^mÉžâ`q´1$Ð[ªyP«ð;&–8Z¢Ð–\”“¦ÓwÍwÆz8J-ÄgŠU÷bÔZ2-Q‘fx\‘298u?h°Jqb)ÛeT“ÚŒ#c:Ú2OvÐÏ0”×Hû—ö¤RBX³¶Œ@wH‘,°œ+Ú†‡éNKàš‹@ŽD’J7 ”Ìpš˜9’~FJ&𬨍JP>o+ݻЈJpqŨ”zT‚J1*ŽK~\]ÇìK~BÛ’¹X.¿ØVž¢î¤¼ûàÀy<Ú]V”[qÓê<Ø´âÅ:4º]Lüt´î]ñâÁ¶Æ¸iEãÖØ¢Œx´;y†d/DÜØÞ²ûOËÿ'ÜÑüÒc·t´î*d¤`æ„Ó/,)-7xü÷÷Ý\ÿñúgî»Ù fiÙøN¿` ™TUn_–oB/™ãÉðG¸kGc$MÀ”Ñi>Úþ*Úq5x|¼·ëžWwþìùÚ;^n¸åÿjÿñQ;d§yf”’®lÇïx¹áWªßÕÒ Å9¾¤}]`0öµ£F¹ Œ˜w½ÒpûKõ÷ýugÜä>ñ•ª|ÈI÷d¦ºàÏë[nÿcý/^ªÿù uÏþ³©«÷K(¤‹`4ãÀ³ÓlG&¦&ÕU”ãËHucðµ…£®:yÌó „啦0ÆB=¦Øƒ¶xFÎ׎:nzöè\ßÖÝ]ÝfeŒ3°äP㙾kO)Ió¹ÌfUc{$!\7°Ä>aöéXL,ÛØW·¥Ë“CËâbÁŠsÎ,¼4nñºƒÖ˜,×ÄWA€™VmÏ.u-ç>f¼«;ÎrèˆBF*º*&C Nà|]F_‹Ñ{ Á°pHZèŸ-¾‚£×)C=ˆPÇ1¶Q(S§¤ ¥('Ã"³€Ó´Ÿƒ\š’¢jÜILâ¥þ…Ø€*y„Ë7 q¡)²+K©‚s ðê5š\ꆒdÏÔ4‘I³(ÖÈðg·méÈ9n!Áf R­Dú*S P6–3 =3`«EVK›Ð3îâ¥6Z2€E*’&§‘-ÎpC;àÌ¢Ϲ0]}šN= ª½*ƒÀ dQÄ¿8ÆrÛÅúéó®—(+50ñ@?d4š“adùBx¡yñFKÙ0iùœƒFž€Îs‘9Á¡açBׂ!Éâ…qª\·$³ÜL›¸3Ç$2eNvËÐðÈÞ¸j2?Ùÿ–hšà`’d8‘jÀ¹…\Šsà oT†$†¨”*NE Ò°•OîYäpû"ÇR®EJƒIפ¹’CêGw5¹:`qÀPÔ0ò,\Ê93 ¸¥qe™£ÅÞW:Ç|Åä#-ò^.¹E£Fäâ>½Ì˜ìJ¡¢dò0" ãÒC‹L—ÉÈÂ1jPgqÔ@©&õ?ø‹[¦ÇYÂân[°øÌÑcJÝÌ|í™_í«ßòÚ3¿òÖè1e Åíb ‹ÇãÜ4ã·(¤«}?ÝxÉ ?§»:š(d¤~’„q»XAј³.ûvvvVfF ;+ëìKoÊ/-N ,ŒÀöT…v­¯­^sá¶3á¥M5Á²`Éåð·Ç ¼´3ËŸ1ðíëƒ 'gÀìqѸå÷ðáî™àߘpù £§ŒNë}ÊÓkq·÷³q»Uª?ùM éx$â¤Øb0æ÷‘hâÕ -§Wæg¦ºçMȧBÝæò·÷5µGû•`\~êe'§x]=që©wö54w—s!J…ˆ\o8*Åó{ †ã\¨B= t§"!¬‚cjÊÅ€uDà“fsb¾;;ÅU”ilÙg¾³#îb`YL0ëŒT6:ËH$dâŸq ÝK åÅR<‰©>ì©zÊ[à~ 1-¹iÐrE@ó_à@yl†Çµ¼6Çæâ´Z*"îò‚c°4B“ãÅÉ·È¿a„=‚»kTm¨g¦'-ÔN30¡Ck*Ì& ê [«Ž ©ŽÉ¯rΊ{1€B†²-ŽÙŽ@ù9]¥õp\M@±æ¬0ù¼ÃK)ë%´$XÎÚeúBÍí¦û¤­hÖ¯©ºai ±ÍF Z™¤OE£(›H#†Ðÿ”§BîŒ:RÃUŽ5iÎLìþA•26ëÕ4 Ò`¥­XhròÁW4ɶãíˆå$ô2ñ¥¯èíEµdP÷3n3<êFÐ͘ãî9aÚCbÒðq°ˆá(Ç ¦xe 2“ÇäõD/h`‚ÊZéòsU&ЊVAH%snŒÚÍÐ?ªk4ÿ!¬™¥Uk¨ 4“#“K8ÉÑ××tóÃë[£P q¤.ÓªàJ6ñe¯pÔÒ©ÌKãÍ”á¤2ìQ™êÒvrÂjw·vRý^·‘›_tî5?[ñÜ=ÝáÎ5¯>æuñ¬1¥§\ðŒìè‰Y:¢€k7ýR¿e~c.¸së{U',í¯‘B’0Ðy°©fås™t1`7¼õlNþw2sF Ð"ƒ%U¡]ëÌ]vu*Ôì†ÊR¸åtøáŸöî‡Yg@Ù´y ¿6zàvè9Šç<ã Sݸ±‰öZ £sý‚Qí=ØóÌêÆŽ°9mLúe'÷yqVš»5‘‹€H¯ê]xdsCè¹w›ú,gݎΚº`Iž??Ã;&×?obf Å}âÌœ?ü£ïë`fiúǹ]¬3b>ñöÞý1møÌm–°,‹C{—ÙÕ“H÷»²Ó*Ã(É6NšìÞ%an !ÄÒÍÙ¨`ð´Ua§V¢ eÓ“^ D#eô¡xœc¾]úHŽîx;Ðetú¨,ï1S²™’-N…ºÍת[Ñ'àb½F­ÄÍ>DÅËSíÔÊL°–—/çÜíb£2ŒÒ\c뾄â@L1”‹á/Š;*³$®ÒÖ1¼’“#ÿ©»IÈÏzâ úHZ”ù";úeW‚ õ»’¾06hÞ^£+ b^ŠãmÄÝdE‚…{ZI8 TË“Àʳ@l,“͵q,“’8ê@i2‰féÙ¬~˜‰Á1N3Ê3`(V´H†rÎò®t©ýÏ”¾PT]fèeºù÷íåãzß§iPªGO‹s¤*FÙ,—–h´,X*ù”À‡é™JÅAM˜^ü ¤å ’BA‹âÊÂûcQ@¤GŒxƒ–Ê|Ðx!"ƒŒ›quzU"²ÚaM¢j©6 Òæ,Þêk|4:ûáRHbð7ÓZ‰î h4@_©)í,µha {\Ñ)•¾’çlܸ•“¼ åá§ŠØöž¾è”&Ñ)àè•I›Ì&cœ¹´G®i{è”TœáòøRëöíñ²½ž,‡‰§¬ Æ”Œ)§ËB=æÁ°Ù ÕîmÏ((Iúë.ý’„ )$ 3jb•ßï=avJz6dfç-:íŠÆºÍY%³)Ì¡I•{ëïZBЬT€_žgüiþÚcž ¤C¿ ¢~p0ßÕÚ3÷•cízâÖò·÷9¯`tޝ4ÏÿÉÞðší]ØçÅ¿_ݸhrÖÔ’tƒÁ'{ÃÞÐÒçäÙ¶/ü»×w/žž3nTJºß•H@sglWkwM],‹¯ÝÞQ’çÏ x2RÜñ„µ¿=º¡.¸åä€OžcõĬ‡Vì9iVîô’4ú35o~p #l¨—SQ¤ — ¢qÈN5|nhïæÑ¸d3¹ÇÅÒ<,'•åºÂQ^ÝÆÉXÂ…×rÈtAÞ£%®dMŠY c¥t%®0µ‚]ã8ò mYÙØ#j`cW"‚I÷ƒÎIm¿Ò`¡”9ÚQ!›Åè™eZ&Þû>(‚%}5Uµ•1Õ?Ç’³f=$q,Ðh–n=5€Ö-änET¥­é¸¦‰7ØrZ |4öl2ß-œ å(c˜ÅÉH”ypôڌ…*Uܯ“lû@o fÿïëFuÓkT4EµPKÚr’F˜¤Ù!gÞ›6%‰¯)s òŠIƒÆ§Þ ´oOR:m £ÂÀ©³(uTÒ/-UEä õ ¸ŠH­Ðsc´€¥D$"EZËÛ Õq%•Ú}.Ì”KºªmjDêÃȈqÄ•…3´éL×IË|²ºsÈÊ0¹ê¦¦‚ŠK‰²í©©$cC'¤ö€“3I§ð„š|&ïÂÒ²SÊK§8š´¢SŒt2"tŠàñ¥¸S2w5Ô›|J<á• 9énÚÐOð¶.³¹3ÚÜÖݸëSoZ¦Ç×Çë–F¤#Ò¢¬”QŽõ§»éý[)ìì Ç6wF›Ûƒ†ÝrË-…ó.ß׋š}¸¨Ð®õæ-Û°ž¯†_/…?5ÎË{C`ì²Sú× ]° @²…ìÓ5´¾qN± ð¹Yv øYªÒ¼ÌçÃ`. ƒhœ7´Úº,À™%曈_`-*°’ŸãX/©ä7ð¸_‰¨“ˆ ˜ G.BìCËüË LŠy$ÍÝ´ßÈÇ(ueÓ'òÔ².½$Í µ9E%©©´ÿXV¤Eí³)õƮ݊Uɺ¾”Sñ*R-UÖ¯Þµú*$ùˆN€‰ðiæÐ T•ö±:$øH¿4î‚V©16 Ú‹A$·¿k¤‰x•íQê@a ·ZS…ŒZ§^{oä¶"´#È;Å«Âé;P¯6"á—£"‰q´¢:”žÔòUL†dœ™2*ŠÆ –®d²ƒÓý~!ƨ\ ­²žÜV-&ªqK….EãR4o’YfÊPiŠ¢ŽS•é§÷)Xå'•áÐEÚúmœô Zg`eª$l—üïO*å”lqÅÖ#ŒhO$ØÖ”ºGìò¦ægxÒü†Û0LË ÷X­Á¸ ïnø4Êü9E>ê‘+äóÓ"ŸÛåÙ¿áÉ2UÍŸ®|Ô2˜WO¿×¶>“5qþP³Sú†r`t„<ŸZÜæêõ«Ï`äî‰Ãþ¸µ?¤®Ókãx¥-~¢{uHï¢{3 ¸8!wQ„Q‰+lz\°“-÷€þW:FLCË]ãÊgHç…a‘N)ÉËGåÄw #Š£-"èý¹Æ•¨=Hv„„Ú3M˜@#§ö‰qtqÒ‘kÒ2Ùƒj®nÆÕÔXNsd¦Ò’L¾‘Géζü@I+CËqpRm÷jÊViõbqT¶(j¼–)º¡ ©3euœþ×c&öŸ–ÅE´^è; eG?é,{ û•Aò‘´z‚SG ] 6Âà˜Ó¢n Š¸NqTmL WLÞRZE=ÉtÆ^Œè< k ${°u32*J#âž!\á%m©åC,NˆGà &&Hÿ» ª"µ‰ˆ¿-Íh_Lb9-žt`.à ˆu…;Ôïnògä² ¼>ÿ-äsÕ"H_¹Ì}p?ÙuCê•ß<²Dñ‹ ´éeo(I¬ü*‘íZ°ÅNá pf€|»³E3ôZÉ2V*2§äJbW4N \ë]QìÁeÀC±+Z£¦ B)ýŒðQ8‡§üC"ÂH‡Ï,Æi]³$" Õ–îÈÅÊ{ˆÚ"¥äº9&ÕèálÎ5z Èðd»‰ ‰[ñ1=¢Y”9@éé0mRϸ…iZ #¬?-Óëó°?"…|®Z$0©ŠŸøÌù/KÿÊ3yÓõ¾# Ía~¤M^íëè€OÖÚ–*0&0ËâÊQèñBÎh¾ŒsS¬XR,:I!öèSþÁ³+!òáÄMQ¦æe-øD`ºˆ¦}L±-Š4´¿I1óåD°0v0U•F°ΪITÕ8XZb‰ö‘Ø8îÑ9 ¤),§ôURŸôÊfáFl9tà`©©å´lY3y©Æ€’ùÈ}9šÅÉþ²5 Tu,³ß¨±1¯äcIèÏ?õZíóÞAªþ¾Ð¨±¹HÞ×/PJAÂDÈô¥:½‘´n æ¤7BÆU=ÿÄã`¡¨dšŽü(í“z›)÷ odÒ Ô< = `"ˆ$e€I†̲ٓ¬Hed¹HõÊHÙˆ9#9[¢Ù—LJá‹ Tg23ô¤¡¬B±©;Ë’i¢ªÒÇ¥/ôŠ”fÂ…H}ŒdG¦¦¨ŸqX‰Ú…KÒ2¿ F³,%Ùáë7r®=‰ƒjù,è”c^ŠÛëõ§gģфå–Å Ãåöy|>·Çg f_ùˆ2R!a$©"^¬cÌ”ù0e;ôuÊÁȃ!ëA7‰³š9ØÜ²äCô6vÌ<+Z`ñ$Pî_eCtv%é”4vÑ1¡·4vÅmÁ¤7»’ JZ¤wãÌ‹²YœèǦžÖ•¹ã }‘x­°ØQN{b”sg`Éé-£%YLÄ'nÉPRá*ˆ©ô”zƒ®p€c‘†´m HG 9i`À,N mò&ÿ`‹q‰Qv 6G6›x ¦%8Ö/­Åz’òwßBºLÝÁIPš£Æe€æÖB)±½ƒ)ØÏèÎ^}½rº7íuú»‚ÙOð¾h“NRé:¼h¬}Qq0”Ê£hv꥘ŒIbN€¹KÆ@'Y5“F,’Ø(âòdçB ª‰Û(ãš} æ¡–ÐD5sÅÒuv¥H¨Ê‡Q œfJ’,‰÷–XÄ%£z¥æÌ“lÆP'1ÚáΈ"¡”G PßÏŸæx@q)™ìÓ$.…‡\ªŸe>]­ÒàDjvenáÐSShD¶Mô\Ž8t:E ?ò¹œ>!óxSd:_hCß[ñY2R¶0tvoTD骤AÙ[ÄuOÂõzm V„!ýHn W®Fk¤MBÎÁHAÝÎmÕ(·S™!ÖzÁž¨´Íülƒ[‡¤4E®LÙ0{av»Ã¬ uÝiÈ+íÁŒ¾1ÿ´>KW(·ÉpNÃì¥$·E39×R³hÜÇ…•©ãÀTÏs ž•ß±+´ êZsæŒÚ$}æÖ’¢ê!-oÀmÚHŽ>jÈhƒJ»Åfð¤‚pLÿ+µÃAªßÓ?ÜBün7Ø{òPVæmŸök·HôùŠ¥!1P/F€Þ¤êGgæf8pàÀ_üâÕ µdR‰$ÿ8pàÀ‡Dò:8pàÀƒaÀ!U8pàÀ#‡T9pàÀŒRåÀ8p0 êz=—èÀ8pà pí_'SåÀ8p0èÏÔ8pàÀ8è¶<•ÊTñÁ«çž{¾¢bVÒÏšw×<úèc %®Ñ? DÕ‹ŸÐÝÝM÷5VTÌzáņWæÕW_óÍëo!“±~Æo^ÃqÇ_YYuÚi§ßqçmmmG¨®Ä믿^Q1«¦¦†Ž<Ø&4¯_öÜóÿWQ1ëÓOkáðzüˆvÁ0‰Dz[8ýœsÎ9Ã+vÄ… úÁæææŠŠYË—/ÿ—ˆäÀ_8ØéÓ0ÿöß¾ÿýü‚|ú:eêÔ¦ýMãÇ—¾x‡D[[ÛþðìUW}ý3¨ëpðôÓÏÜ}÷Ý3g~ó†ës²rv|ºãÙgŸ{sś˗?^VVö¯–n TVV@MÍFñjjªý~[[[CCøqãÄÁ55YYY&Œ€œœìϦ÷?ø|¾_ÿúnúzË-ÿ3vléW\!¾¦§þEr9pàÀƒÏ;†Iª.ZHÁU`éÒ¥K—. ‘Uõ£žzê©ó/8?žþT7<|°yó=÷ܳdÉ’;ï¼Ã0 8éä“Î8óŒ‹.¼ø»ßýÞ /ݱã†nWÌœùû?ü¾¿+ï¹ç7yyy>úˆ×ë€ùóç_tÑE>úȽ÷Þ;¼ªàšk¯yù•Wžxâ¿ýío%zàw¿++û›{ïq»ÝPQ1ó´ÓNÿß'þ÷Ç?ù1<üàÓ'O¹óŽ;Ä¢ÕØ±¥çž{þÔ‘µ+îê ]ÒûTaQ!c¬iÿþÁ”sHyâñØOò“ÙsæÐ-K—.}â‰'¾ûÝ›ÒÒÒà¥_JKK=õ´S‡$?TÉmU53gÌØ¸±ÆãñL›6-++«©©©q_cñèâšššôô´É“'õy{<ûŸ[n™8iœwîy/ÿéå7ß\)VÐÏ0ŒÊʹÖ¯¿îºk-˪®®9ÿüóž}ö¹H$’ššº~ý†iS§¦¥¥õôôÔÖÖÝzë­'œ°X”¹xññCm~غ>KY õ‘ØC2éx<þÃþ`Ò¤ÉpÚi§}ôÑÇ>úØ^èqÓK8pàÀÁ ÃÌTÝö‹Û–?±\ü$Å ñx|Ãú §œ²Døt`Œüâ›6¯^üüüóÏ?ÿ¹çž;xÐö0],ÛúÑG'Ÿ|²Ã@~~þ¼ùóªkª ~ôñÇ'Ÿ|’ç0yò”±cÇQQû„e%yÍ`äñûý³fÏÖïúÚ×¾ÅþöÚßD ¯üùÕ¯œþ•”””¡J8zÌèQ£FÕTo€šê3gz<ž±cÇæääTo¬€ê5³çÌq¹\}Þ£’¥ÓÜ҇ꂻÌ_°ùƒ-±Xlûöí¡PèŠ+®ôx<k6À†õæÍŸ/t2~|ùC?ôÜsÏú駜ثBïÂFVB}$.bùÝwßE§†jÒwîܹôuÑÂ…uµŸŽx8pð…Â0ç 3gÌHÚ¨Þ'‚ÁPÜ4Ÿ|ò©§žRëœ[‰Ä¡YÅÀ¸êª+_zé¥ÇìÒK–éÕY–•“«_™›³í“O ê²,+/×v6??ÄEMOKKKKÛÛ¸·÷©ýMû9ç…EE‡,d0ò¤¥¥;ÈÍÍ9ñ¤/½øÂÏ;÷¼·Þz«½½íÜóΪü•••ï¾û.缦¦fÑÑ‹ÄÁ¹sçÔÔÔ,˜?¿q_ã¹ýïL÷z=úWf0Ë4a]0@÷Àüùóâñئ͛·mÛ6iÒäÜÜœ9sçlذ¡°¨¨­­mþüùâ²xà¡zøáGÚÛÛòòr/¹ä’+®¸"IQÃÀÀâRx#(aÒHlnnÖ’I§¥§é[™ÐÙ†T8pðÅÄ‘Mìé.—ë’‹/>7:`r IDATûœ³G¶äì윋/¾è©§ž9åä%t0##`F{ÛAýʃím™™™BÃ0B]]úÙP¨+5%udEeŒÍŸ?ï½÷ÖvvvŠª ï¼óuÔñÕír%å)bÑ(ÒGžóÏ;ïÊ+¿¾åÃ_|ñ¥Y³fMœ8qx­¨ªª|íµ×¶lÙòɶmß¼A¾GjÏÿßÿUWW. wÁÀÝ&NÌÊÎ^ÿþûÛ>Ù¾`Á<X0þŠ+ =Ïœ92iW\\|ëm·@}}ÃËúÓ½÷Þ—Ÿ_pÆ_ª´IX¼C ¯ãI˜„¡šPW(‹Å(­ÕÚÒ ™™#+•üãÈþ™¯×[UUµ~Ãú’’’qv~á—_~¹ßç}äÑGôêfΘ±råJš‹·8°aý†yUóÀçóMŸ6mÝûëèúÖêêj„¨—_vy,»ýö_ê‹;ûöî{ä±Ç&NœxÌÑLj#ù£ šöï·,K| uuíøôÓÔ§ªªj„ñ¿¹ç7ÕÕÕÃNS¾­jùòåœóY³*ÄÁ9sçîÞ½{ÅŠ~¿ÚŒC-ó]0@÷cl^UÕºµë6nÚ8Á|˜?Á¶mÛß~ûí3gøýþ¤êÊËÇÝôÝ›|>_mmíPEíÅ;¤ð}bd%ì-ðLÈ4Í·ß~›¾¾ñÆ™™™ã' “”;pàÀÁ’T ú…êCÆÍ7ß´s箯_uÕk¯½V]]ýÎ;«îÿíoï»ïþÃ/9##cÙ¥—¾÷ÞZýà7®¿¾¡açßpã?ßýçÊ•+¯»öZ¯×{ù—‹³×}ãº5ï®yæ™gâñXssó~ø#G-T ¨sæÎùÖ·n|ýõ×/»üŠW^~eÕªÕ>úØ…]äq¹î¾û.ZdYròI=ôP(Ú¹sçÍ7ßìv«]JÖçÜóί©©ÉÈÈøò)§ CxqãÆåää¬^ý)S&§¦¦ŠƒâóêÕÿ˜UQ1¼ýËwÁÀÝóæÏûpëÖžžž¹s*…ôÐwÜ vfddΘ>ý ‡üæ¤>±lÙ%Ï>ûlGGY¸ð¨|ð¡‡úö·¾ãñxª*«î¼ó®ââbqöØc޽óÎ;xàÁ{ッpTáeW\zäD½âŠ+¦M›öÔÓOÿúž{ÄÛ&Ožòè£egçÐ5S¦L½õ¶[zè¡'–?QRZríµ×Æ¢±Ã—ç”%'ÿòöÛÏüê™´”3Îq ÇZ _@kù6Ùƒ/Mª ©q/8‰kƒƒc-/ µ|›ìÀÁ’TñÁ½Q]$®…SHBÓ˜a°¤ƒ±¸Å «3€ªŠÑ‡)î<˜÷ì¼…õ”›™•Uê_åÎÈj!œs˲\.×PoL$†a0–ÜÃÃÖò/G"aµ´FzzÌqeYÃ.¤­=ÚçñM›>üÒ‰UÃ.¶?ìÙ»·;ÜM_SÒRJÆŒ@Îãs2rÓZÞ[»véÒóú<Õ¸oÏaÈuñy 8$’Þ¨>¨L•ÀÀ‰kÃ`³gØf]ç=QÓ4-Ó´vïÝŸ•¨ÞRçðªC \—š½>%‹eϼªcgÓÁV»*Og^ÿP‹q¹\»7U×½üB°í Ûãå–F›9×3f<ž•?ªüìsKçTr>2¯ès¾Ì±kwÛ¢E³×¬ùð0Ë™>}¼ø ì<†0óí¿W,¯ª««‹Fc‹OGV­Z][[;a„Ã,ùó0r‡a-<ð`mm-}mim€»îº+/ONB"áÐ{ï­ýóϔœóh4šHXtÄå2|>ßð&!à {6oÞö׿eFÀQ]˜_V6ŒB8pp„€¤ªÿT•ž¦ê¤Êe0Ë`.ß×3oîÔ ?9BÞùλïÈÌÌK˜f² ÷¦êš‡~pxÅ®ZµêU«[[Û,‹[VÂ4ÍŒ@ÚâŸyÆW cÈ/¤hm ·¶E¦åvÁP$7fÂãv™¦‹%L31Õÿè‚E;`O- ­L=PQÛ6Å·üÞ!TÁ9gŒ­{èþ¼ÕqÂq)3ÇXñËâÀ8XÀ-žHpËâÜàœC¤ù@÷­ß^·äÜ£®»a¨Òq8ÖòYÂåò0áp7@6<Ønk‹Lœ?¬çŒL¢ªªâðy•eY-­­ÑîžÎ`(‘0O9eIOO­ªª\³æ½-[¶ff|)þÂQ£§®$|6#÷p¬åwVÕÖÕN/9eg0gžyfqQa,€®PG8úó#&m8©¯«cŒ…ÃÃ`>Ÿ˜1yÒD¿sža7ù@]݇¯þ¥ ›;nܼ‚Q¬»Ûr»÷¾öÆ»õõ¡¢Â…]˜UT4Ô9pà``OU *S5¼4µÁ˜Ëep¤¦úÓR=Ó¦Œ«ÞÒ0<ïüæ¦à]ní}üªEÆi óÇ›ôß7Ü`ÚÉ!p{ìüaT·k×Î{ï{ È>þø“gΜâñDkkÛ‡nYµêígŸ}îûßûneeåÊ<ÐÞ3qR©á2ÆfÿŽc1sT~Æ®;]œåñ®ØÖë ‹A@§Õ¶{ÿ;-U‘?¼{í5K_cl÷Æê¼ÕM=÷«áæ]¶lãÀ™Ûe¸ÝV"nÅM`ÜðxÜ)©.—3`†Á ÃSZ`dgd¿ñâîù‹Jç­QIÁE ‘Š8üdLoöÓÕ¥VÓö5¶†¿´´ >¼2c.—+‘HlÚ$`‡É«jkkóó òrs[6ož0~Â’.(++«­«=fJ0ܱcǤI“†]—޹àp¬å„Å‹ó›{Äg±ü l·ío’¥µìo|iáp¸agÃÄ£ñ”Ô”£Ž:jÏž='//oÍš5‰Db¨b©É¡ææÍ~5µ­­rÊÔ KJY$¡$`tw—Æb¥@Âpízá¥wöì6Ç_tÁùiÙÙ‡,–P<º¤¿åÑâÑËõ¯û¾>øb8øÂâˆ?ýG©q¿ÏåvÅEÙi©þáÍz/¿'_qZÒÁå+[¯ú隃mm©–L@g‡7eCXåX¿aÃã?yÉ%—-]zŽØ›‰Äzz¢™™YãÆ[²äÔ×_ý§ÿïÖË/»ø¼óÎ|±Ñh<Òs» XSóiIaNF€¦¹ÜlxÒ›×S!îµBO"ãcE3Ö½U=$Rõ¯¼8}ñ±áæ]í«Ýó¸¡'‹u§–fŒ+w§ÌH¨kÿ®žöVoz&¸ÝÜ0bí)ÙYñ™åõ¯¼t8¤jDVýzs©gW]]‘X, …Å×ú†fÓtüÄÍ›kG y±•`F"‘X²ä¸`°+ ôÑöÃÒ4EE…0pð`[ïkf"===55uÿþæÃ©«7Fpäö‡aXËc=þøòåÐÒÒúÁ–-ï­] ?ýéOrrràŒ3Ïa ˆètw÷ PTš[ZÞ|ó­éÓ§õwa9<çÀ%%%°ÿþh´ïMu`HM~oùÆ–Ï9õTwQ1Äb Òð¢c´]Á`9@y #ÔþÃYçL¸÷žésæ ^¤>y1ªÆ}_Ÿ‹G/wx•‡Äú1fìí¿¯yûï¶ã;v|zÑÒÅC* êƒ-íòk,—œ_~Ô™ß^÷ƒSXÝ~ˆwƒËœsÃ`œCÜ„+yIðعs×£þïw¿û½N8æƒ>)))òx¼¡P¤§'fšf{{çÞ½M dÝ}÷ÏÆŽ-]°`Á KŽÅÍX@mmmFFF^^Þˆl!èêêŽDº»º"ðñ'{óxâì`°»µ¥eTAé0 dŒ¹\ÂÚp¸»»;:ŒÕáÞ0MÊËÇmßÞ7?+/gš¦eY‰Äíd`ŒìÈíCõ-ÓgL?å”S|9D£Ñp8|Ów¾=®¬,##ó¦ï|û0…)**úê™gz½^Ã0Ün·eY¦iÆb1¡áƒÛjëjãq™ÈŒÇã–5œ ˆƒoòþ×߸ª¸xÓóÏGrrŠÊË'ååõfTÀX‚ó}mm=)Çttu U¤$^¥3*px•CÁÐR8CÍÕ㜌]yÙRH${÷µÄâ&çü¥—^²°Ý‘xÝÀ0€õ»`Ê„‚ާüüîGšÛ Ò70 Ø^0‡âú‰Äý¿ýÝ¥—^qüñG¯][ãñ¸7nÜZ^^æñxâq³½=ØØØ4qâèmÛjKKË/¾øšþèÇo¼þš×;¨}O±˜‹›Â~åÄ٥ʹέöU†¯ºrÀr%º2ÌÑsZ÷vv¶›æ× —ËŠG9pæq[ñžÜI3g|ó—¾¼b0c¡]ÛšÅãÒŠËÓŠËSæm{úÑ®NÃð0—8©GÿàPÖòÆ+fΜA_Ÿzú™ Î??##0a„ÚÚÚ)S¦”••¹Ýn¨¨¨ˆÅbÛ¶mÁLU(Ô÷ƒáM›ë|¾´ªª){÷¶®ZUsܱS§XƸeY‘Ha†aÌ™3SBp¨OÆã1˲ °°°°°P?¥ÿή®.˜8qbOOÏîÝ»GdpÄGî!1Hß²háÂqee+V¬hiiu»Ý99Ù7ÝôqŠ> Ù™YYY™k×®«¬œë÷û'6M3‰twwÇbñ`°3ž^TTÔÔÔär¹ŠŠŠêëë§ÆC69`¾1%ó:;¡³3ôɶ÷B¡D^^騱c}^`Ìâ¼¥³³±üO}öÙ®Œ ð¼ýÖø‰‡!Lï|•ΟˆW9pà``ŒØÓý1À].‹™]án‹s—˰†ûˆÙ0ÀÛߨY¬6RæŽ:mÍ.èî_Ù©öÁCáÝ5k22rÎ=÷«[·nOIñ——··?þ¸®°°(7çÌ™œ››‘‘xãu F~ýÉ'Ÿ¼æškSx,ÅÍ„eµ#ç|¹’sÎ9om A¢;3ôWov:´¹ ê …½ui3;‰îHþ´Ñ Á-nYœ¹]¼ÛÌ,™0ý¿ðå‡vlÜù·§»öÕ»RÒÀ23'Ì{Æ•iÅå“/ùþ'OÞ–àsR›«FväŒ!ù–¢¢¢÷×­]ºô¼’’1´§êð‘™•9göìM›6mÚ´iÞ¼y¦iºÝn–ÅÃáȺuëB]]3RRR8À0^\"pÈ&×ïÛ_‰Â”©ÐÕ`Ç¢ÎNèìlÝöÉ?Âa#˜pÒIy?üaa¾í¹ 3¡oó`•‰C“ªÃyB‡[œsnq‰D{¢1ÓLøý^nÁ°ŸÛß° ‚ïx¾…¿\œ|jŒ=ÝÄzx´;‘–é~ø¯»çL)QXµjuIÉ´xœWRS³5ŒdggVTL¨©Ù'æÍ›š•ˆÅâ›6mcÌsà@[YÙ¤¿þíoƒ&Uf<žvõlØÒ`š ÓLÄMËL$ò­ÇÍØÝÙâií‹¿5Û¡žht;a8Ëp¹x"^þµ«üc‚¯ÿðÑŸuw˜¼ô¿J¾råÇß²ïý•ÝmMÓ®º%}̤ыNݹúeWJªxpèÕ) ÉZÌ„i&LˆÇl{çL™ýgqn&¬„ÅMÓ²þ?{çÅÕð;3[ØewaéE”Ž‚‚ l±÷Þ[>“¨I즘آÆ]“h4Æ£Ækì%6boˆ(J¯ Û¦~ ®+eÙ]@ßïp8»¯Þ¹ûfæÎ}÷½a æ•0fÜ*ŠåN2h³.>’††Š…œL€Pk@N:“•¬÷ œj*$–u—™™ãã#;xðt»vMýü¼ïÝ{\¯^€B!oØ0„e9;;[ŠbΜù/55O(ݾý/†99æ1ðž*­Ž¤h†¢Šf(šá(u˜ÛeÂ2(`šlætª{Æ$©Õ¾6ªhš! væd(RéS[,·ƒ^“/V:I]@âàdãè’ŸúâÅÑ­¾ý&ÙÕn$¾qŠá8([¦ù£…cYŽåhš®W¯nÞ«@±Ø××7))I¥Rñ1Uñññ …Â××7>>Þ:U~üY:ë1ƒPánãß ^>ËÍuëСIBBÊéÓWÇŒî, ­h™‡a8ÞAÅâʵzU E‘&Ù)9J_pËç8ŽÈT/“Ù©¶Îž¶Nª´ÀA=Ušfx£!##Ó¨×éùP•Ü\ÿA&“©T*•JåáQØ=Y*úœ”ÄÃ_Õ zäTGv‚ü§|ü±¿ð:x@ƒ¦ýuF§Ó}þYW‹¶{Žã(ŠÂ^QìíÖºU4Í”ä*ɶ®[·.”œœlþ!pÿ*¨²A­Â÷®#Û|¨Ç…â«Çí7/‚•8á…݃ÿÔMZ[}æ‹™£¥GŸ½û p²³•ˆEb‘€À±œ"<ÂßÏÇ1 0Žã–ÍSëâãžÇÝ•ÐÙ:®O IDAT´óõõ‘ÉlËE*¹\.àü[ÆËÝ¢2qÈ#:·Nüeû±t„kc…w¡LAHÄB;™>'½o´7_F$åëØmç2·Ÿ¸zöÚŸ..ÿÕëb3çßµÃ: ìÖÕRa’^&zxzA‘€ÈA…@XJ9¯þÃ^mhÀ¾¼zƒ¯Õá®ùz`H€L ß0Ð,P4LÁg`@C‚ž5e…вU‹N8;{ØÚÊzöì`kkÃq°{÷áÓ§OQ) GŒè/àǼÿ^FFö£GÿuêhîŽç$Eëô$M’8ÎI¥Šâù*–õQC%?ýïŸq=ÀX¡ç8–")ФøàqÇÄb±Xl–ÌqÀ1Œ@*Ëyþ(çÉ=‡ºMü:»ûû}¾ŠÒ«€ÖkIµÊ®VP€.å Mꉼìû)XºV´Ô~4 Q( ¹Ü aÅú¬ w+x)ÞËS?†Õ ©pë¦gÍ'@Qb€LV ÃZëo™êÔm·¥]ðÆaVt°èª@Î’ÀX–ÕéôEš(Àÿ'I^½{ÇøƒùÎ\<á!–œÌ[T«oÛ¤mÛ‹“+ lgn±˜9Z”JeÓ¨(¯ì?``¿AÃ&LþJa+Æ ÇpŽƒg)Y§lMÚ²ôñõ ¬\Âùv0çíå²%“>·z‰°É±K{~KË –ºGËjF $6—ã²[…s‡oän?yëØ…¿”öj³Q½<ý=zöîtôà'³ð ˜ðN¡é?ÂR*jõ†Ç†Çñ€“üǬœDÐÐÛ£«HÀXƒQÅÍfƒ‚v=œåÖ;tرcGJÊs¥Òéðᓽ{wbèÀvíÆûúùå¡„BhµÚÒ Y‚™‡¬!´½;7Žj«ÎÓ«®Û{iï/jMýn+wUÚ]ô©«oÞ×ÑÕ¡]ϰöí¼šp… ~~ c°¨JrV!‹¨ðwÿ•/Ú|†S3 Kž À²ÀrÀ²@°lÁl .•šÐè‰ô`sÓ0G˺ â‹)S¦Mÿ®eË÷ï?YºôçÄÄx‚–e†‰ˆC‡öÞ¿755]§c.\8öý÷³ E ›’_«×¨µ¬P@Q4Ã0,Ëäç“—Î\—ÿÅ©ìµRO–¢ùÈw ã†fà ©Ô²·Ñap€a„X’ŸúüÉî•þý&È#‚k©“ô•TébëZ¡>;5ñä6†a>â¬_E`áh©Âo±hzã…\niˆ:Ï I‹ÜôÃ}ë?Épƽ”=åzùôž„‹e:5Ã$ryÿSxwxwzr°gí O“YdT•´-¿‹zÑUünRæ/k`hÆôËgø *0ÚªÊnþQ0¶¨ô“ÐÞAÒîí0 Iܾe×½cÖžCœÙãÜLÌ-† "((°Iz# Ö-YعGŸ—ÉiwýÙ£Kû&ùúøÊi³Bœ¿p¡[3ÿ1ûäœ$¹Tá&wkßÓ9º³:[“pûÆ‹G) qù¸Ûˆ®ÞÍ%‚7® $P/ÒÒ¼½½ËKZä B ,¥ÂßýWŽü8T1q÷mç¿N—üø(Ä!U /ôO/¼èj·ÇVj+Z¶QXXØðaƒ×oØÔ¤I…B)Ù°,CQzŽ£qœ°±''§åå©/\8>`@Ÿ®]º˜ß²V«×jI`Xš¦9ŽeY–%iÝËgénMI¹G3üíP D"ò͈‹o,Ër‡á8‡³±,ýá ݆™5šw—ûÔSø„p‡a@æd¨ŸÝIùï Mêq‘ .2$E«µge¼¶3GËÓ„„§ ¥¶Ö·Oo+BÔyš^ò÷vOù¿Å§¶so<ȧAg>KËh³h¨QðB•Gxbù LQtU †a8α¬v«isÓ:ã©$ˆÃ;x‹Šœ²êÔ—e9ÍŸG¥Úa¹¹‚Û·DÏé:t.Çîx,º¶ØÛÛÇDG;::öéÓ¯nýȤ—/ŽíþíÂù³ÎÎ.åGUˆV­Zk´š€rÝÍßÌCnZ't㦠:y»¸*m…2[‘­¿³Ÿ[´k^dqÞ¥Ÿÿ!‰ù);ïß›pÚ‘iݨ‘u‚!gQv*òÝX1+Éʲ#s§†NqO➨5€¿M}å—ÙÚúúùÊå?a÷éÓÛÓÓsÖwßÉå.>>µe2¹X,aYš¢èìì츸ش´ç³fÎèÙ³‡EÍj5ºü)ÀÕjEÑE“¶.zÿ¶a+¤h†eŽšf´Z½6O¥×¨%R‹·PR88kR2„µ\Èì\Àq­:3åáž52W/[woL ÄÓf¦èòs9!¶ÅBB"Óe$¨“ÒìƒÊá '<¦GËðaCŸ'>/µ©Tjõ.ê"©¬ÅÈ3ŠfåèÜ%Ìã„ˇBkFPÚ|AþM"mÉÎ¥cΪ@ÓhµZŠ¢¢Ä“ÔØ¨2¬´êëÂqýé‡Óû˦:pTѵ½úû…aQcæµÅÞÞÞ«†Ã/ôñ©ÀǼÐÐz׸éCîÞ¼¹ßc÷í›ìÕ\sª/­SÓÉÉÞV(#t6 j…ðeÒ´Y>:¹ýä‘„«ÏZºÕÝdPhp™^^‰@ ÊÈ«ëµéªÀ<Çõëm69`9®`8À»OøÍ%9~m¶ÅØÚJýýü]]\HÊÜ- DB¡\®0už1QQMöýµwË–-ÎÊÊeŽaXЦärIÇí?ùd“½½½¥m~Ðd âê‡EÕ ÷æ@ o lÖ¬Yn G<ÍÐëh¾êPŒˆwSWòÛ¤iBCÃÞ‹ª2P F â­ñŽ–÷ðˆjÌÂ#ù`#Ä}Å)W7U¥ÕeYToj0Zo÷p´¼‡‡Œ@¼?X¶W︮ QÕ 4ZæóŽ–÷ðˆjO9¿ûàA£a>ïáhyx0Uf쩀▀F Â|ÞÃÑò2QycO…òU@ Ä{Háé¿ë×o¼9@ ªÆ_ U11ÑoQ@ ˆªÊ‰=©Æ_ Uæ- ƒ@ QM@1U@ å@§Ê¬µ@ ˆW²§ @ ¢@F@ D9€Œ*@ ˆrÀhõ ªB 0Ÿ76TGž*@ ˆòU@ å@áÍ?MsñÒ¥ø'ñ$ ¢ºâëçÛ4*ªhº»G hÚ´é[—@ ï/»ÿüãúõ7bcK-Þ A„ù-[`TñUíÚAæWA àÁƒ‡P¬]Õ´iÓððpdW!â­Á[TÓ§M+µäœ¹sÀ|»Ê£Š·¨0 7ǸC x"ÂÃ;wîtèÐá¢FoQ¹¸¸ÄÅŽÙñ‚a`ŽECÞ²uk…U<7bc'Œa˜¥ï!Ç-[¾Ü××§¤þþþÏž={›"!â=ÇÁA Ô{ËÖ¼¦Ã0–eË]Dõ#11±Ô2È@G ˆjCVV–uÞZ;åhT½¹£‚垪‚V8´«@ ˆ×deeùùùYW÷É“'{¨âÛá ˆQU«V­²ÏœXiT!åòT!D5 33ÓjKüüüžût´D"))7##sÓo››ÇD7jÔÐÈqœV«MJJþ÷ß+É))eÀÕÕÕÏÏâããSRR é]»t €õ6æää˜hÁü’<¾>>ÁÁu\]]är9Žãjµ:!áÙ…‹—òóóKªRÍ4PFИ1gÌ ”×CrE·Ã›0¼QU»víB¹<°®Gë*FQâ"ÀÿZ×x¹°}ûöõëINN±µµ½zõj©å?üp„HdóÓOëJ-yùòå[·n}òÉ'fJ²pá¢ýû÷]¸pÁÌòï…ý a£paM-Eâ€ñ–;†œ€Žb•¶¸·«ÍÕGy_ô¨õÝËøã]51}ÊÉÉÙ¿ï¯Íc U‘J¥þþ~>>Þ¿mþ=;»LwqWW—¦QMàÑÇYYYŽŽŽ… ìݳ»a£Ær¹¼Ô¦Ì,Y?<Ì×çõâS¹\^¯^Ý5jlÜ´©¤ÃBZªê(#h̘3fªŠ§Š‡7ªîÞ½[R–XS%‹—/_nø:}úôZµj}ôÑGüW™¬bOuÓ¤¤¤Ì™3gР]»v‹…åÛøåË—·nÝj¾Q…0M¡AÿäEfRºà Mã8ŽaÃ2NpÀ±, @ØÛÏs‰Œ^› ªw-qÕáyb¢Hl#ÅbqAÇ^ºxéƒÚ€Tbëáîa(œ™‘Žã˜ggΞmÚ4 üýoÞºéáéfgg/‘Ø@~~~bâ ãù‘.;À»þ ¯_¿fÍšju¾N¯÷pwç L›þ-ÿaÏÞ¿ž5j9dð@ø~Þ|þBáääØ02ÒË«†D*eh:#3óüù /^¼,zPÎÎÎEÖð¬a#±Ñiu‰‰‰/]6Ìò9zìïÇ„BaÃF•Jå¿W®vïÖ<ÜÝOŸ>njC¿ê¡¥Rݬ©§§‡ ˲yyyé§OÉW«ÀÕÕeð pçÎÝcÇOm‹Æ ¢â¦íŽžKM*&„ÜÕ÷}s‹Ú€ÖQ5¡²UÅ$¥/^´xÿý‹/^öã>ìÓ§÷´iÓàÑÇ˗¯¸výš^¯ ™ßÿ½““Sffæ¥K—H’”J¥¦uUR-ë~늦ÐÐâÀ åŒN£×€žÊxɉkPš4úe<+tÂX>=…uõä´yœ½ˆÀ+|ú£ZR4l¨hG'çZµjÕ¨áÙ¨aA òãGH’Ä0,>>~ÇöíG …©ôã?>l¨³³³N§!B x}U¡(òÓ1ŸÞ½wßÃÃíÅ‹—=zô˜°}ÑÄß¶5ñtT4«C‹€#g >W¤QeÑ–êæAQäÌ3Ã# )K–,qvv^¿a=ÿhÒ¸qãýûÿ´níŠ+KÍ-ÄÊ•+½½½W¬XΟ¡¡õ:tè°aÆéÓ§Û+•NNÎàâìRÒ\þšÕkj×®ýÃâżÆkÕªÕ»wo¨SSðÄ^§NîÝ»?~ô( 0ÐÎÎÎV.ÃqܸeåùD­V;yòä¶mÛð2³2Öý´nà B¡ÐD]N÷ý÷ß·nÝš/ЪU«R5i¢V•@ PªôÇ·BáƒÓ/–‘Êór2#™#›}þÎÉ+Žá™—õyTDW\€£ \E1p@㯠Ã,]¶Léà@DRRrÝzõzöêåèà€¸­´À êîæ~ëömÿ׋“—/_ûßÍ[´T*íýüÃê×7d5mÖ¬ví:P77WÞ¸yëÖÚµëÂÃ#‚CB‚HINÆ_ù! •Äp|îœÙNNÎAà8 |7{NÛv¯/ÁŽŽ=ºw“H$$INš8);;»e«ÖÕ^nnn Ë8>ò£š·hÉq\zFF\Ü“ºuëÙØH --}ÖwsØÏ²¬ovª6+ã˜AT誽’7ÿa€§cË@½^÷ôéS«Å{ƒ7·TJ7·‘â=UÜëtlllÂê‡R(ŠºråêˆÃ…B¡!±EË–[·mã8Îtn¡nH’¼sçΨQ£‚às6jtõêUþkÁcyŒÐëõwïÞýüóÏ %ƒ‚‚¼kÕ2L_Rµ~ýúcÇŽ%%%‘$Å×zúô©?¿•E‘è«RË …Â-šª´mÛnß¾ýãש]ÇD]±Xìçç·z͵ZÓ0²@?>L몤Z•–ÂOÀÀˆÎýú$ÝM­ÑÜKrÿr®oG',!+6þÑGÉÇ·s5 …G*›ÎÚæÊLx e±,«Êͽyëæú oݼٮ]{Ç»víìãí]´Y±X¥R;<î߿װQ#~þH")ì+53îTf[0³“àââñjRÛ«fÍ’J:;9æhxA~¾šýOO®]:‹Åâ|µzÜØq±±±íÚwào®¥*§Jk@§×ÿsòT³fQ......|–*/oß¾†ww(•ÊAƒ‡@ÿüó°°ú¶¶R•J5tèP^g°ÕàMs­Ôò¶2™@ 0T±·W@NN.Çq¦ë®Y³fÍšÕk×®—åää4dèÿ}ø¿RuUl­Jk|žþ#pŽaÔ·Žæ¿iãÈû‰@ÕοyœŠ}ÀQŽÚ‡'ò¯Ç“našŒ›÷ö4ýgæLå¬ZµêçŸásEb±«‹k×®Ý\\]]]]ø»ã;w'OžœœœÜ²e‹+Vðµš1n¢h''ç’.yŦk´þƒ½½½‰ß×PòðáÃ_]LÜ$EQ666þþ~íÛµ%"--m̘1Ïž=ïб£»QxuIT ÜðàÀÁ:­¦F ¯à^½z)ärww—'Oâ1 Ãqœ¿:™~£+3æŒDU1ª’’“Í))9¹Äàî"Œ*ËæÿJ T/êΑÉdA <¤g¯žE[3[(…ß“-++Ó8+3+ËÎÎÎÈîámªbä”Éd8ŽçååçæååI%>åàÁƒ#FŒ>~ïž½Ë~\æìäÒ¾};Óº*¶V×®]Šª¢B8­×¥åúaR]ÒCGšt⨔¤{Á4-Á¸'IkR¸L¨ädûùymš™ÂÌ™;×dþfÆÛ{÷ŸH$ŠŒŒ¼rõʸñãŒÃÍÉ-Z¸^ݺÇçg =#ãê•«={˜:Q ˆÅâààËÿ^6lŸ’ž‘ñäIœ«‹ °,KÓ´­ìõZ¹'ÞXi,‰¨Wç¹9倢éÓ§O·mÛ–ÿzôè1;;;?ÿsêòøúúNž2yÇÎqqq]»v1SWƵL{·ô„TæÕLöò&ãÝΞ~ÿèš‹Oo'öñƒûçm}{(5·hØ÷—\' §ÊÌñ:””•š’ªÕj%IÏž=}}}=kÔp0,t-Η`œ’–šÆq†a 8h ¬û銢ŒËswêô™N;…Â!CÒ÷íÛŸ””T¸ä©3:w‰DßÍš¡V«qç·ºq#öòåKQQMüùÞå ÅÒ%K uŸ>Mø}ËV¥‘äÕRÑÑ1Íc¢›ÇDëõ$MS6 ãpöÜ9R¯7–“_ý×¼y 3õ€Æ ¢6¢Ï IDATXŠNÜ+ºvÏÕ÷k›bV¡U´§ê•05Á¼­ŽJ-cL9Nÿ½1)VtŽ ¦L™<|øˆ‘ìׯŸ‹‹Kž*ÿÎÛË?®ÔÜBŒùô³O?3vì¸ûë´ºuëÖ‰D¢á#†¿r™òTÀè1£?ÿ|ìï›7÷Ð?++{úôéB¡—ðÆïþsw«–­<<<Ž?¾ë?À(BË×ÏO§Óíܹ38$D,š.Ï'‘H~\úc®*·V­Z§N>xðàS¦LÔMJJš9sf»vm}||iš>~ü„^¯oܸ1Çq&te¢–u¿uESبdnrεóš$ȉãµTzŽêúsîq6™ ¹×Oèïå©¥—ؤl¹›G1UVaf|L±³-$EíÝ»¯^hHí Ú~~~×®]»zõÚ×_UR-㯪¼¼#GŽ……Össwã½›6þÚÀÀBå_¬^½& 2²££#EQOžÄïÛ·O$¶)TòybâªUkêF4ˆP*•4M?û×þýjµÚÄØÈÌ̸pþ|×nÝL+§ªknÞº- /¯2™L¯×?}š°ï¯½Ûw쌊jZ¬5S’ÆÐ˜11fåHI?«‡ïúÍż¦ÅÕÃ×¢vLT)ÊϨ*4)VdŽ ·nݶnÝÚE «T¹ …]HHÈÀýùb¦s ѤIãU«Wÿ´nÝĉ“„Baƒ ,ZäîîþF zÉFU³fÑ ,\³vͲåË]]݆flΞ={öìÙ½{÷ …aáá?ü°dć# Z-[´èÝ»÷êÕ«sss;fº

ȨB¼cP¤9@T*‡ yªï)ȨB ˆj€··÷õë×###­«~íÚ5ooïÊÓŽuUðê?@ ïÞÞÞ×®]³ºî[kÇϯÜvU¨È×Ô –ƒHWæƒte>]݈>mÚœ¹s+¨£wæ©ÒéuqÏãi†©å^S"ïü{ïõ§ ºkh~æ8Ÿ;wîÆëãÇO0S6KËËÒ%K–/_öìy¢E%Ïœ9³}Û¶Ë—/¥¦¦*•ÊÚ´ùöÛinnnÅVœ9cÆ®]Ü»ÿ ØÜ3gÎôéÝË8ÅÁÁáá£Ç–J%Ã0–eßU‰•AŒ*Ò•ù ]™Ò•ù ]™¯+Þ¢ªÐŽÞ™§ê¿·Çmž•¥Ê;hRJRJf~Þg`¶kÿ‰%ÖN9¦T»êܹ³Ö¯·Ä¨²¬|±89;Y÷³tÉ•J5tè0¯š5Ÿ<‰ûiݺ‹/ž9sV&“Y'Æœ9s===ùÏ"±ÈºF*-•dÃŽJ"F•éÊ|®ÌéÊ|®Ì‡a˜Šk¼œwT7ßS¥Öª³^¦g¤­ÜóÓÀV}uk¿;gU¾VÓ¯Ïèk§ãg,[4ñÃ=Ý=ËC¨ò$õ"‘xذáÆ ·´î’¥Kýýý _ëÕ­÷á‡#öïÛ7hð`ë„iѲe:u¬«‹@ Ä{KùUå³£ºƒöŠùâ¿F„‡›¶®pÇ€ò°³ÑqP“˜:Û²V>Ñ<ÐèÉUê¹Ã£&؈$s7-›1rŠ»‹k±Õ¿ýö›Ÿú œÀÝÝýÖí;cF~üøÑ‰“ÿŠuïÖÍÎN±ù÷-Å–€S§N-^´ðÖ­["‘(*ªéŒ™3 Ž(~nÝO??wÎÝ»w‡ ²`á"ãI½û÷î-\¸àÚµk™™™ööömÚ¶ýnÖwŽŽE¥5¶¨ q“&œœlB?÷ïÝûꫯnܸngg÷ñ'£&L(ì`Óh4‰¤ZΣW’G®J"F•éÊ|®ÌéÊ|®Ìçmxª¬æFlì´iÓ‹¦Ï;Ç„QÅ0Lv~–¯“ËWý§¸J6d,HÕ¦RCQÌÍ“ïs¿ú$xrçÎß®úá³¾CÂê„…åœ2å –avìØqöì9 Š0§üéÓ§èÿÁm~ß²U£Ñ,\0¿s§Ž§NŸñòòâkåæª¦}ûÍ÷óæê‹lk‘””2xÈ;…ݳçÏ–/[6tØÐC‡—¢2€K/@àà’ h4š?þèÃÿœ8iÒÁ¾Ÿ;ÇÏ×·k·×úìÔ±C~~¾D"iݺõ¬ï¾óö~«ÛðW4•äêPIĨ ]™Ò•ù ]™Ò•ùTj£ ¬ˆ;þïñcÍ1˦¹%myš.¢X=Eé(Š¢Ø2}þÃYcý¾Ù{À/íìð2ºG‡N…ZP*•r…Çq¯š5Í鱨ò æÏó÷÷ÿmófÞhkРAÃÈ+W¬X´x1_€$õ?ü°¤QãÆÅ¶ùA›6´iÃnÔ¸q½z¡1ÑÍîß»gÂZ€ìììY³fÖ«Ú¾}û’ʨÕêyó4oÞZµjuñâ…}û÷ñF•B¡øôÓÏ5nlkk{ëÖÍ•+VtìÐáô™³®®Å»ôª"•äê`cíÚµ?ÿüs¬«p/_¾|ëÖ­O>ùÄb~Ý*º* UZ«•dxW ®ÌÇ «¢W¡*MEN…UxÙ›`‹Ãt•_¯lú¨ÝȨ (’!sI•†Ôæéóóôù*}~®>/ŸÔäèrt:]˜oíO ßqöDÙ…,Š^¯íÚ­›Á æææÖ,:úâÅ †2‰¤a£F%µ@’ä‹7‰öñ®åéáñAëÖg¢SN7bøpµF³á×_ ‚(©˜­­mLLŒákí:u s…áááß͞ݹsç–-[Ž7~ç»233ù™MÄ;çòåË¿üò‹qŠ££C¡É_DÙAZE J¢èU¨JS‡c0ª83°´ñòñTÍ›;Ç8å›W‚|ܺñ>]i§\qh¥D(ýdàÌm¤ç×À4Ã2,ñÉ’?>\ânë5ãçÅ©™iR™¢ìB%77—eY''gãDg'§Û·n¾Êd2AK_}õå_{÷NúMdÆ2™,7'§S§Ž:}‰›ß“¤~ذ¡wîÜÞû×>ÓïÍ–J¥Æý †¦‹-áëë{ÃDkUŽJòxúZ îͯ&ªW¨dß¾ýúöíWIލâ°BWe¡JkµŠŠýN(‹®ôz½X,.GaÊ…Š“Ê «¢W¡J E‘Ba)«×Ëx8ÅvQ|0xðà:ÁóæÍ“J¥»víúßÿþ·uÛ¶“ ï#]0vìX©T*‹³²²>ô$þÉŽ;„B¡¡Àĉ333ÀÙɉã¸sçÎ;–¢(‚ 333:xúÌé-¿ÿHÓôÈÿ}˜’š ®®. Ã>}šðôiÂ'âääd:÷ ± ¢r¯¿ßÉ-’ª,º*ö*dú×·t,qÀ©Tª%K—.\0ßÇÇ÷øñcß~; lØðaPÚHãÇüüùó§NêëëKêõÇYzQå€îÍÓßè‚Pl…tU *\€ëu¤ˆ“"‘—·€ˆÅ"ŽåD  ãhÆDàQPíÚZ­vãÆ_ë׋DÁ!!ݺu[¾lÙ¢E ÇŒù4==ý›o¦ÛEËõõÔþýú¾>uëÖ€S§N Ðõš5½{÷)µßaC‡º»»‡Õ³±‘ܾ}kÓÆžžž£F2GfDÙ‰‰‰1„»…G„שS§{÷î=  ´³³³•Ëp÷ð¬Dû«½CV®\éíí½bÅrþ4 ­×¡C‡ 6LŸ^@QäœÙ³ƒ‚ ÿþ{vï>zô(oR¬]³644tþü|É€€€.]:¿£ã¨@LhÀÄHã5Í´iÓø‡´Ð°°èèfÇŸ8|è ?Ù¡QkfÌœ™œœìîîK–,qvv^¿a½H$€Æèßÿ§ukW¬Xù.ŽÛzZ´h±xñbøòË/9òàÁƒC‡õèÑÃP@(nÚ´ÉßßïeR2MÓ3gΠ(ÊÕÕeËï[<<=ãââú÷Ÿ¿pÑ¢õë׿|ù’·™¾6mÐÀ››{éâE0k ÿo‹¤*‹~н •úë[4–€¢¨o¿ý&(¨6tîÜåÎÝ»ë~Z7pÐ@¡PXj_EΜ13<"Ü ^¹_T‹vQˆJmTE„‡/[öc±é¥ÖµKUÚ|` Ž}eHb8p‹¦Ñë‚-*èØ±ã°aÃÌŸŸíæævëözõBW®Z½hÑÂË—ûøøLž2E¯Ó›(ß²eË;ÿX¼háˆáÃ…BaÓ¦Í~Y¿Á°ŸB©¬\¹jÒĉÍc¢E"Q£Æ7nÜÔ¹sáUŠ<ׯ]€Í›Û¼ù7CâÈ‘#,\Ë2 c¦mÕ4jÏî=»wÿ©×ë]]]ûõëÿÕ×_;::™)s• ’x ¸âb(ŠZ¿~ý±cÇ’’’H²ÀñùôéSÿ€¾(¼)Šo( EuE’ä;wFE“òÎÎÎ 5ºzõªá™^!—êzÔ¨‘ššÊqœ^¯¿{÷îøñã YµjÕôóóãŒF¡ÊjÕØ«W’ÀŒ‘&•J###ùÂr¹ÌÉÉ)*ª‰@ äS||} 55ÕÍÍ¢¨+W®Ž1\(újѲåÖmÛ*¹Ä+ø0dÈÞ‚0 ÿ‘#Gàò¥ËÝ»w7˜0qBdd$ØÛ+ïܹ“šš,ËMŸQ`ÊóÁW¯^¥(J©T …BŠ¢Öÿüó½{w}}|ƒj·þà^Q¦s_ËôÊ/U\Š5RÝBÈ2]½y*ý×·d,ñå…BQDx„¡µ¦QM7ÿ¶ùqÜc?ÿRû²±± «f<ð,½¨òçÿ›—Ù•—ÔE!*µQÕ¥k7ã`)ówTg{g^×Ä)F"x„a ÛºµcµœÌÔ›j‚X²té’¥Kû÷ïß¿Ã×^½z›.ߪU«V­ZÛþw³g7{v¡ÄI“'Oš<™ÿìêêºuÛ6ãÜôŒÌbKž;J¦õW,Úï/¿¼~v;vÜØ±ãL´V ¨$úb/RsçÎ=räÈçŸVßÖVªR©†ªÓëŒnö\¡ó*ÍUEuÅ/Q*ŒÝÑAùàþ}ƒ®„"‘q.Ža Cs§R©X–µ··7ε·W_7¡ÊjÕØ-I`ÆH“H$Æu ‚ؼNÁ0šæ Š¢6nÜ´iÓf#1X†a*¹ +0Œ ;;{>%+'Û0Ïuj×1ÔâgÜ ====ýðYš¦óòòìíí¿þúë%K–¤¦¥íݳ—ÏrvvZ»n]P`L&3‘[HN¾SóqAеRY¦¦W=|xó*Tê¯oÑXâËÛÊl1 3Ë““kN_¶¶¶ðæÉkéE•{óx ©&º0æéÓ§f)ÔZ^½¦æù_ë1ߢ€ ¯ ó.4ÕGÊ;Cr$`€1{öþùvžžýëWÓ¯ì"!ª•äB_¬§êàÁƒ#FŒüêýB?Î0ÁϽ>·‹Ö­ÆÕ•\.Çqœ#6ËÌʲ³³+É0*¸"rœL&Ãq<;;Û8777[úêÒ_¥µZ’GŒ4挴"àŒî=Æ+d2ACéÙ«g‰ÂTJŒ§€Ä牵jÕ€/_ð)J{{ã»—€eä³”ót:uZ°`A±í÷íÛ·[·n·oßNHH¸{÷îž={ÒÓ3~ùù~’Ñt.8ôz½D"€””dCËe‘Ê%©õæU¨ô_ß’±ÄÏÏËÓëõü¤¥¥€Bn]_–^T^Èb!õ:N.3ç‰+"<|ËÖ­ÅfYÍ«~ z,‡ÕÆ<°ßüÂÂ:tóêêÄ8Õ•†|Øp„ÄF" ‡7îê"tjïߢËj¨ê°,KÓ´­ÌÖrâÄ›¨‰D"ª„Í/Þ7D"Q½ºu?nð´§gd\½rµadé 5ÄbqHpð©^¿oêÙ³gqqO*JÖÊG©#Í"D"Qddä•«W¼¼¼|Þ¤<„}«¬Y»:111ñEâOëÖñ)ü[¿Š¥ví ggg8qâÄåË—ùĬ¬¬íÛ¶/Z¼(šÞòû–¬¬¬ÈÈÈ>}úŒ7–ßï 77·Ô\pq-XuñâEøkïÞ¤¤¤R¡T©ÊH¡«PEüú4MŸ<ùz@9ò·Ÿ€}YqQuvuINI18óòó=~lõ±”;å¨nŒEž*;…]‹¨…}}ÊU(D£’<=õ(`Ö¸qãÝînÕ²•‡‡ÇñãÇwýñ=Õùúùétº;w‡„ˆ…€ÀÀ*=Qe>Åz_Æ|úÙ§ŸŽ;vÜÀýuZݺuëD"ÑðÃKœ*Ž{UwÔèQcÇŽûöÛo{öꙓ“»fÕ*G Ç«Ùô_I(u¤W÷Íz¾Ø«”)S&>bäG#ûõëçââ’§Ê¿sç6ÇrãÆWêX£,øžžÑ¹óë% AAµ;vèÀE/û0‚˜9sÆ„ I’üä“OxQvv´iÓ†ã8–a-^´hñ"™ÌV*µÍÊÊ¢ibbbJÍ€:8p¦Nº`Á‚œœ±X¬×ëİFª2êªèUÈô¯oéXâ§ W¬\™ŸŸïíãsòÄÉ£G~1eŠ€ 8޳´/+.ªmÛ´Ù°~Úµk† –™™¹páB€Ó§Æ+nÄÆNŸ6 æÌkžK¥œ*‹bªˆb©$7Ëb§ÿfÏž={öìÞ½{‹„°ðð~X2âÃðêŠÙ²E‹Þ½{¯^½:77×ÙÙùرcUz¢Ê|ŠÕU“&W­^ýÓºu'N … 4X°h‘»»{I^}cS)::fÁ‚k×®=räˆWŸ~úÙ–m[d2Yu›þ+Y¦GZ1u îzœq/†”€€À­[·­[·vÑÂÅ*U®Ba2p@ÿJ®ÃBÆ:üðÃ;vìÑëI(yº°(¨ŽÍš5Ë­áˆG©-ÅÀô.v%ݲekíÚA]»õ(©Q,÷îÞ>tèð!…7óðzúÔôÀÀ@x5ºbÿ»9qƒwýí“““³gï^x·bT Þ¦®rss;vìøé§Ÿ2¤B;ª и2ŸbuõÕ×_;zöïßoþíjÏ;WK—.=tèÐÉ“'ßNwå…AWÓ§M£izþ‚Ó§M3Ǻzþüù–­[?þhdIæÌ©ˆp‘¤\Ýô.cªÊ±.¨Nh4š%K–œ;îöíÛÇŽ3z´ÄƦK—.ïZ.Qå)´HÓ4–6þ.cªÊ±.¢:QI¦$*‰U‚r×A Ï:”››+“É"4ø~þü×+«2ÕàÞFº*&8 aÌÛSKUŽhä©Pá+QLŠÇBðT’Óµ’ˆQ%(w] …ÂåË—Ut/ï„êqoƒ®æÏ›?ÞüB‰cÞšZ&Lœ0aâ„*ý+T%£êzªhš¾s÷éã¸=»Ç”º#íúõë§~ýU¡D©TúìybYd0͹sçnܸ>~ü„Šë¢zPIN×J"F•éÊ|®ÌéÊ|®ÌÇêwë™CuðTq—ŸŸáòKÀž^õNžºÝöƒ°R_ÆóæÍ/Øw«^`>çÎݰ~=2ªxW OÕì:pF,Â98hÚÆ„>zœšœnS;ÀËÙÑF§cÎ%)>zY§vé+DZ¶j€öĪtT’G®J"F•éÊ|®ÌéÊ|®Ì§*Uy›Ø#6¶hzDx¸‰Fv<ðëÛÉYGƒ–äžÆk^ì¼ÕÐ^,P €ú)¯ÜÌT:亹”¸=„ öïÛ7räÿΜ9bH0 rrò™3gàÞÝ»óæÍ»té¢^¯ ›1cF“&Q|±™3fìÚõǯ7Κ9óöí;ööv2j„ ðí·ßüüÓOàìäîîî·nßIHx:cúô‹/æçç+•ʰúõ×¯ß “ɬ»:QI®•DŒ*Ò•ù ]™Ò•ù ]™OUšþ³ÈSu#6vÚ´éEÓçÎc¢{9áSSœ§as^ªÝõM?hî`/äXL§ã€f8èüÒô¦¸º8•e–ri IDATÔT~~¾ñÞB¡P*•¶ïÐA¡PìÚµkæ+£*==ýô©S3f΀»wïtêØñÿìw\×Àg÷íŽÎÑ{U(X± ˆMT0Q‰[bvÅ{‰Æ®£±F1Ñü¢FÔØ@TŠˆ ¤ƒp”ë»ûûãô<Žã8Ž£ˆûýð¹Ïíì{³³sËÞܼ·ó¼¼¼wíÞ£««{ôè‘Ñ_|qñÒßÞÞÞ’ÆNUôòåëÖoprr:þÜü¨('GÇ¡¡QQóq ;uêTbb¼jœ8a•JÛ¹k·™™Yiié×…B¡êì”––r¹Ü¶:ºŽŽN{0㣀ô•ê¾RÒWªCúJu¤¾‚œ©µF' *¯Æ‚|Ö—Í £8b@ŒܪxZŒiѵ“ž×$gó ò«Onl0¨<(HvsРÁ'Nžd0¡#GÆÇÇ-Ž–LÌ:£G€U+W²Ùì¸øxÉ"P}úô<(hÛÖ-GŽþ.Q" ¶oß!ÉrMš4ùø±cçÎ 544d²X(ŠÚØÚJZòx¼¬¬¬;w…„„H$C† iª7:~¾~_íR os3> |}|€ô•*¾RÒWªCúJu|}|$ƒcSPÕÔ9UjUÀEh—qrªjpÇĸX "!þ<Ÿ÷VDÕ³4}S‹âf†·ÓË©•Ê?»vï±¶¶–nJÞ„‡‡ûý÷¤¤¤ÀÀ@8sætß¾}Ùl¶P(LNNž9s¦$¢A‚ƒ‡Œ9(U¢¯¯/;nhkgWXX¨ðèÚÚÚnnn›6oª©­íÕ³§»‡‚ MõFƒ Z0+«º ÃG„’Õ=T!)ñFŸ¾¤¯Tô•ê¾RÒWª“”xCòæcþS#Sµî§5²’%Ë–KõȾJˆ1þ¹WÝÇ‘é`ÉÀ” .ñ¶ *ªp–­>ˆ)_ã¼AåKNÞüÓL•Ý××WáDuÿ[ÛØ3§³³³=z´wß>àp8"‘h×®]{öì•9L¶Ô=N—U…¢(Vw…mYNž:½iã†-›7/./333›>cÆ?üø)‡V)©©³gÍjC±}ÇòEBBBÒQéà™ªEK–ÊI¤zd_¥`8”Wb¼jáÐÞ¦Å"B—R,@Škðœ7_Œ•sËÞTUæUØBéæÎWã,;vìþ}û6oÙ{æ´®®î°aÀÅbQ(”éÓgŒ×Ðd666;wí€ìììãÇŽ­Y½ÚÜÜ",,L#Ê?RiÑŸÊÉËkÁBe$$$$$mN‹Um¼ö®å]0ŒH˪ ð`V#ÈK>õvþôñú ž›'zõ’ÿ<“S˜WS[KP°·¡:Û©ù$]XXxmmíŸ^ˆ‹‹>|¸¶¶60Œ^½z%''988¸ÔE :C$)Üåêêºjõj--­¬ÌLõ îH¨²S ¡Ðž²²²ï¾ýÖÅÙÉÎÖ&,llvvv+;äc¡¨¨hñâE!C‚­­,MMŒórsåž”²{÷._Ÿ®’÷111¦&Æ’?+K‹ž=vîüE•¥^’””$WŒ~ÛÖ­v¶—˜Q±YëóÃ÷3%Ή˜8¡!I}4x±}D×-é+UèÈkÿ©‘“¨©Å^¾æMhð†|1ZËãŒ8ÏÒEôTL%ëì$¶¶2S¢êúµkrALð`:NNN~~~kV¯.,, 'm°zÍšaC‡Ž5ròäÉNUJÊ'–-Wð£nîî<ïðá_»võaÐéLkÖ?„Žéââ*‹/$$ðùü¾MuI‹"‰ÆŒ]VVºjÕj]]Ým?o:"1)ÙÔTÙÈò§ÉëW¯Îýñ‡o·nÝ’““åö’žTÎæ-[,--ù<þÙ³ñ«W­ª¬¨\­†žúE†MLMÜÜÜ4giàêêzêÔim™¸êKdÑàÅöÑ]·¤¯EPIuÚrN•¯Ïöí?+”+éUP"ìb¯câêé_ªtÚZqm­¸ÜF\]ðyÀ¯¥ðyôÿž»8‹µ´JT-Y²XN’ž‘ifö.¾há ‹Þ½{KtîÜåò•«›6m\ºdiee…¡Ï”)ST9߈ˆ¯7¬__QQannž|ó–¥¥åžÝ»ß¼yƒ¢¨‡‡GLÌ¡~ýú©¢ªcÓ¢W|S‰OOòǹó’Ë [÷îÝ>óÛµsçªÕ«ÛÚ´vG·îÝ3³žÀþ}ûêU¤'•Ó«WoIÎ{Dhè  ‡Å,Yº”B¡¨®A(H~ÊñuDÄ×3´- ÑèÒ禒ȢÁ‹í£»nI_5ÊÇT5)S¥Þ3V%¥|73ÔÎEÀé`LC¹Ú_,õP¸\„ËEøêõ‡ÂÞŽ )™2eJ£‘Pdddddd}¹‹‹ËÁƒ1 »¬Z½Zîê‘mI¡P¶nÛ¶uÛ6©d×î=Êmø4iWAÕÿþ¾$X[ZZöéÛ÷âÅ‹íí6ÑP¾6éIAÄÏï³´´´ŠŠŠÒ’’7Ü¿¿¼¼ÜÀÀ hРU+WKZJª ïÛ`íOkÒÓÓ'L˜@¡RëÞ¶uëŽÛ¥ ›f¤§oܸáöíÛÕÕÕÖÖ6aaaó,¨o†’*Çí ^lþºý}Õ‘ŸþS£/ƽ.œ¸ò– B(Äy\/äÖòy<Ÿ'äó„|!¦pÜ\¬Ô³„¤miWAUVV–»»»¬ÄÃÝãß«W´²‰*ž”%8xˆ³³sC{sss©T*“ÉL{ø°SçÎã'LÐgé¿Î}½cûö‰ÿú뢴%‡Sµlé’µëÖ»ºº ø|&‹U¿È°,iii#†óððؼy Ûœýâù‹ŒÌŒú(¯rœ—›ëëëóã³Ô l4x±uøëöôU‡ÍT©×÷d”˜‚æ@Ah° €  jeá¡¶%$•’’’Ÿõꕃ10(h`лÅÝýý==½úôî•™‘áÑ©“D( ¶lÙÚÝß_ªG®È°+WD›™±Ï'\ÐÒÒ€†’OT9F …‚R4üœ“ÑàÅÖá¯ÛOÐWSPÕ ™ª ~ÝÕÓOò±ÐrW|^nîÅ‹==½||ºÞ¹sÔz¸ƒ„¤%8 ¿ô}PРŸÞB¡ð—;Îçåå …ïžÎÉÉ‘UÚÚÚݺ«zK ·oßþqÖ,IDÕp³FªÛØØ—4åäHHÚÓð_+dªH:<-èxwí*™@}ôèÑ;wîªòĬ¾ì‘À©¬DD__åº?eHO*gßþýÖVÖ4:ÝÎÎÖØøÝâZ .8÷Ç‹/ù¬[7===NeåС!|Á‡ò{zzzªËåpª0 33k$g J•ãvŽ/¶Ý~‚¾jL!Nk&­©"!Qå3©âîîžšš*+ÉÌÊ´³³Wþ[Ÿ¤>¤'•ãåå]¿â]\lìÌ￟6}ºd33CÁü'ÕÑ×gQ©Ô’’båÍ4^å¸õÑàÅÖá¯ÛOÍW/_¾Ô´JBæUÓÅ?ÉLIóÑX)O%õÜŪoÌ!!·nÝ”l$%&† iM‡t HO6ÇE"“É”J.üy¡Ñ^JŠ ÓéŒ=zÄÇÇóx ª´´´~ýõ°tsÎœÙŽŽŽ?üð£d“ÉbIw­[·ÞÜÜ\ºY¿Ÿf©¿Ú—òUØÚ9­ðC™SÕÒÇ"!!i&™™v¶6ÁÁC<ØDm’““§Ï˜ahhØl35©JmH_)¡E2Uuæ©b™ª¢ þ_¥yÅÄbEÁpŒ‚R p èRr9”²j|SBvÁ[ù¹œ eD臣,\¸ÀÜÜ\V"¥_ÿþm;©Sù*líœÖªÞÍ©j_ÕÛIHHê³b媹ó¢@WW·!IsسwoãZ]•z¾RN+fª4D;ÏTurÐÞ銋ùA € &D(4„À„(…F8‚}ÖNã›BÂùó‘‘ßܸ‘Ø©sg©pܸðÂÂÂ7Aéb[’E¾~=|xåŠ?10П:múìÙ³`éÒ%õWûR£vÀ§AW¯\€§ÙO ùf²‘¡¡‰‰IW¥kx“´!&&&&&&Ê%$H_)‡œS¥á¾ ]}âßJž}[XiìnœÂÜìr»>þP”óêIŽ}¿Xþ aDcè±Âtô¿SϨ©©‘­F£Ñttt‚‡ a±X±±±+ÞU¥¥¥×¯]‹^±[l 8œªèåË×­ßàäätþü¹ùQQNŽŽ#BC£¢æ+_í룣å²Gb±x^T”tsíڵЧOŸ;w¶ÐIHHHHÚ d¦JÃ}i(4&Æç x •å k·Dœÿ§™ 8_PZ„³­^5a@§ ªÖ)®ÏàAA²›’  FèÈ‘ññqË££%™¤³ññ0zôht±-¡P°}ûI–kÒ¤ÉÇ;Ÿp~Dh¨¡¡¡òÕ¾>:Z.¨¢P(rµéZúˆ$$$$$í2S¥á¾T*UTUúìQ…F˺žá˜³º²ÜÓ3Æ+’Ÿ\½KA´ü?AµÈwJmFPµk÷kë›Ñû¹xáááÇ~ÿ=)))00Μ9Ý·o_6›Ýèb[ ¯¯/;nhkgWXX¨¶…í2Ä!!!!!Ñ8d¦ª }%qUC¯’f`t–©oؘ‚ôbë¾NDéóÌÿ8Ž!&È«·©eݧ >{Ìá²X–Åx)F¡¨møúú*œ¨îï`ck{æt```vvö£GöîÛª-¶E§ÓeU¡(ЉëÔí !!!!!!i2SÕä¾ ½J QPÃjý¯&ŸÎËy%ÌÌ‘{MÚeQj!2æ=½Róà…ÐÜ›[–¦mÜœLUC 2vìØýûömÞ²5öÌi]]ÝaÆC‡XlKƒ™*Cfª4Ü—BAÅ~ Ç Ñá<5 MQQAF'±X!ž<µ¡z´*je…““%ÑBÐ………oÛºõÏ?/ÄÅÅ >\[[dÛZ¶|9µé3Í•¬öõ1BU$$$$$‡ÌTi¸/ŠŽžM/½ü4Ì~°øÍ‹ìûf£MðgY™ÉºŽ£ ¹²¸¸¯¥E>QBP)êgª®_»–•™)+ L§3ÀÉÉÉÏÏoÍêÕ………aá㤠V¯Y3lèÐQ£FNž<Ù‚éJIy@àIJåË=œtµ¯®]}tz§Î ‚øó®ÂfblÂf³»ûû«}R­T‘h¹LUK|×|b™**EÈ)¬¼ŸÌ-€ÊÔ"'*­¬zK<«–çÁAFu­Îm¼ ‚i^‹6cNÕ’%‹å$é™fff’÷cÃÂ-\`aaÑ»woiƒFÛRJýÕ¾”¯ÂFBBBBBò©Afª4ÜW—&Ò²íoÌßiÞ0lª)E,àOëÞóq¤?!D˜€¦çÑMç_rm™Yõ…J–ñ’Y_®d±­ú‹|ɶ¬¿ÚW£«°µgÈL ‰Æ!çTi¸oúëüɱ¨|` ¤‰¹ü|WµM"QÒÒR.—ÛVG×ÑÑi«C“´(d¦JÃ}OÏ~’ÖÁ×ÇçØñãmnCRâ¶µá#‚ô•ê¾RÒWªCúJuÈLUkô%!‘£OßÀ¶6áã )ñé+!}¥:¤¯T‡ô•ê$%Þ 3U­Ñ—¤ý’š:{Ö,Ñ|…0!bûŽäµDBBBÒñ 3U­Ñ—¤] Žãmuô¼¼¼¶:4 I‹Ò ™* ×·üóBÂG×—¤½A´õ)**Z¼xQÈ`k+KSã¼ÜÜÖwÈGÄÿÝ1ÂÆÚÊÙÉqêÔ)uyÊæ³{÷._Ÿ®’÷111¦&Æ’?+K‹ž=vîüEv‰ª&‘””´cÇvYɶ­[ílmí¨b³Öç‡ïgJœ1qBC’ú”••}÷í·.ÎNv¶6aac³³³Õ;úÇuPÃW<Áöï«XXZ·h¦JÃA™©"é`¼~õêÜuëÖ­­miïh.ÿûû’ì’–––}úö½xñ¢\ eUèðw ž`û÷UKdª$±“4‚ú´žþûùÜk+žHˆ"q‚ |!n¦…ÚÛjÝË®ž?ÊnÕÙrA•––Ö¯¿–nΙ3ÛÑÑñ‡~”l2Y,é®uëÖËF0”¦¯‘Ü$’’ÅÄÈUÛ¶n­ªªš81ÂÆÖöùóœýûöݺuëÆD==½µD#´« ŠDu„B!A—•0ŒÌº‹`’HâììÜÐÞÜÜ\*•Êd2Ó>ìÔ¹óø ôYú¯s_ïØ¾}bÄÄ¿þº(mÉáT-[ºdíºõ®®®>ŸÉbávêÔ©ÄÄ$PtçIKK1|˜‡‡ÇæÍ[ØæìÏ_ddfÔ7 =ýÉÐ//ï]»÷èêê=zdô_\¼ô···7äåæúúúüøã¬åÑÑšq‡¦ÉÊÊrww—•x¸{ü{õª@ `0meI›C®ý§á¾Eü¿Jó*ЉÅ(Š"‚á¥@à8@1Ð¥är(eÕø¦„ì‚·<¹î eDè‡-\¸ÀÜÜ\V"¥_ÿþ...jŸNóÙºm›ì-Û³‹çäÉ“Οÿjüø6´ªÍ¹uëöÙ³g<¸_ZZª¯¯ß·oß9sæH×d$i&...©))AHŠbTTT<þ\(òù|--­¶¶®}áìì,Tñx¼ÚÚZ>Ÿõꕃ10(h`P¤AwOO¯>½{efdxtê$ …‚-[¶Ê.”Îd±Pµ±UœS_¹"ÚÌŒ}>á‚äi(ù´jåJ6›/ Aúôé3xPж­[ŽýA( Ji¿i‰ŠŠÊ.žž²}‚ *++Ùlv[YEÒæuª4Ü·“ƒöÞHW\Ì'!0!B¡ &D)4€Àq@賎pÔ3¦!ΟŒüæÆÄN;K…ãÆ…Þ¸‘Jóí’<ÿ¯‡¯\±âñã'úS§MŸ={6,]º¤~Â_î~íË£X-—©Ú»wouuõØ1c-­­^½zyôÈÑ»÷î?wNWW·…ŽøI9eÖ¬W®X1{Î.—»`þ|ɬ m³Âc|Xï!(hÐÏ?o¡PøËŽ çóòò„B‘doNNŽ4¨ÒÖÖîÖ½»Š‡ ·oßþqÖ,å1®P(LNNž9s¦4©ƒ Hpðƒ1%›666EÅ%M99’vY§JÃ}ºú†E¿•<=ú¶°ÒØÝ8…¹Ùåv}ü¡(çÕ“û~°ü<ˆÆ(Ðc…éè§¶=555·˜ŒF£éèèÂb±bccW¼ªJKK¯_»½b4–o§*zùòuë7899?n~T”“£ãˆÐШ¨ùÊþpûÖ-ÞˆÛ9-T­\¹ÒÁÁ^ºéáæ>köì¿/ýýÅè/Z舟__XX¸}ûÏ{ö쀠 A#BC¯_»F§“.³oÿ~k+knggklünNñÂ… ÎýñÇâÅK>ëÖMOOSY9th_À—öÒÓÓS½X.‡S…a˜™Y#©‡#‰víÚµgÏ^©Ç1µ«<´>ú²w`àTV"¢¯¯ßV&‘´ÈL•†ûÒP hLŒÏð@ *Ë'Ö"n‰8ÿN3Ap¾ ´g[¼j€NiÆÏëÁƒ‚d7 |âäIƒ:rd||ÜòèhÉ„¾³ññ0zôh4ß  ¶oß!ÉrMš4ùø±cçÎ 544Tžð¯¨¨X¹r…§§Wpp°ÚgÔ1pp°—Ýôõó€â’â¶°¥c2/*jæ÷ß¿|ñÂÀÐТoŸÞþþmmÔÇ——wý9q±±3¿ÿ~Úôé’ÍÌ óŸTG_ŸE¥RK»àY,…B™>}Æø Vj縻»§¦¦ÊJ2³2íììÉaèO2S¥á¾T*UTUúìQ…F˺žá˜³º²ÜÓ3Æ+’Ÿ\½KA´ü?AµÈwJmFPµk÷kë±°‘¡¡äMxxø±ßOJJ €3gN÷íÛ—Íf7šo}}}ÙqC[;;U†óø|þ¤¯¿®årãÏþA¡PÔ>£Ö¤Õ&ªß»w\\\É©ñDKKK’=î\ffæŠ+ÛÚ¢ÇE"“É”J.üy¡Ñ^ :C$)ÜE§3zôè?gÎ\mmí50½zõJNNZ¶|9µ…²i!† ù믿nݺٳg/(((HJLŒœ2¥­í"icÈ:UMëÛЫ0:ËÔ7lŒ©k€ß”/»}ÞÕ3q 1qõTÛ û·“<øZ ðùº+Ža͉@|}}{É wó÷°±µ=s²³³=z2ùv+KKéßÖ­[*Þ¾•ê¤Óë>N(9»8¯]·nÒ¤Éw#i˜;wÍ3§oŸÞt:½»¿ÿáÿ 6Ty—ˆˆ¯7¬__QQannþèñÙ½^^^/]Ú°~Ãü¨(.—kcc3nÜ—õ•tîÜåò•«›6m\ºdiee…¡Ïi¦‡ 0 Ã1Åktau&`aXŸ8cÖ¢ÿæ4->þìòeË–/_& 8(}îÀq äkŒÖÔÔäääˆÄŠÓ{ÊïMRÕÍaÂDµÏ¨u¨—f™÷ÞßÙÖÀÏ‘iD…ZüÝËmçžãDi™¶ž& ={ð[S«¡½¯Þ”O‰::å«ÞãB»I…ATUóÓ³ Nž»›™SÔL\Ìzø9À)/²_KåËf í×à ¾ž}8¿¨R‰Õ[Jð÷qØÛÃÕÑÌÔ˜I¥ o+kï?z}4övéÛšfžKfÁ·Áƒ;I7g¯<ó$+¿³›å„/üímL õu ‚ÃÍxZpâüÝç¯ÞÍ:ŠÝ?]² j¹‚‘ßìiêqþähíM I«Ñ2sª™×O,SEC 11>WÀ¨,Ÿ`X‹¸%âü8ÍÁù‚Ò"œmEðª :Uÿ?~ð  ÙÍAƒŸ8y’Á`„Ž·<:EQ8£G€U+W²Ùì¸øxƒ}úô<(hÛÖ-GŽþ.Q" ¶oß!ÉrMš4ùø±cçÎ 544d²X(ŠÚØÊO¢=ú‹»w±É©S§=<<Ô>£ÖAî·/CO·×°À‘}lìt0`À‚ƒA]mÌÖ,øfê|'!—ÆÐ‘yÖ ™‡W¶“_Súô¿ýÈW}ä Ögi÷üÌÉßÇ>jåá'9ÍŠE\Øcàõó‡iÕE:, 9Ë2oîÖ²ÆÐ6lX‡ê-B{ûû8H7M™!ý»tó´œ6ÿ·j¾†—[è8¼¿NîݽKDÊ¿;Å:Ý\ºvó¶—615Ò ìáàk?eÎî¢ ²rЍø[WW}}}L,xr}³gÿM;¬‚Íd˜4«‡„¤Õ ×þÓp_*•*ª*}ö(«†“—u=5ûµTW–»pôâ¢Ú'Wïæ–cEÿ=K©FP”ÚŒ j×î=çÎ'Hÿ–/_.‘‡‡‡&%%I6Ïœ9Ý·o_6›- “““GŽ)‰¨Aàà!ÿݹ#Õ©¯¯/;nhkgWXX¨ÜŒ-›·\øóσc\]]ÃÃÃîÝ»§öµH] -Í;;Yé€.<1ÄÀ¯Kqm*Ú½PqI¢!~Úqñ«ñÃÃþŽx—Ò‰Dááa’¿yóæ"ðá õСCýûõ 1âÚµk@¡P¦}Õ«¦ü…z‡ÖbÐ$o$Ê«JsxÕÒ½g.ÜŸ>sÑø¯¾***BäTYg*o‰ ‚`8ñÇùKŸéáî:bD~~>˜˜õñ¢b¢ZõÎE¤çÛ®PÕª÷ÁçŸ ?=·°šJÓÎ/ª\³éPÏînnÆ }úô)0t?W”_[† ÈŠ­¾Œø!íQzCW{#Ç@Q¤9ð>ˆiWz¤g§„ß~E+bŹsã’4Š™™îï¿þöíšš%ÿ=ÁÃC3Iî  ÇãÇ¿ÈÍ#-/*Š:|x¤¥e[NKøáû™¦&Ʀ&Æ'4$QÂäI_›šÿðýÌæ[¢AU¤Öþë8™*É`C¯’f`t–©oؘ‚ôbë¾NDéóÌÿ8Ž!&È«·©eݧ >{Ìá²X–Åx)F¡¨møúú*œ¨îï`ck{æt```vvö£GöîÛG$íÚµkÏž½ÒÆ8Ža&ݤÓ鲪PÅÄbåfxtz7H1løð€ÿ5«W'\¸ öIµr >n.LÀ€'1Aˆ1aPÉ%Jj* aÆL?ÖBÈ£kiæ.ö(ãMõ[Ç´µß…¶_»vÝÙçÝÛ®S'éjIIqI•ÎòÜõÛÍþýû€‡‡Gy^²¾©sß—QÁ]-Øú,=-‚ ÞVrSÓó~û¯ìý˜Ú↸Àü5ñ#ƒ½}=mË+jk¹|wçw©©Í[¶HÞ,Ûtþ~ÚëðÐϾ€±“Vqøz(‚Ø[‡ðóêdm ¯#‰_¿y{èäÍÇYù²É6APq²3 ýÌÓÝŠÉÔª®á?LÏ;G:2¸mÿå´ä#*ÃØaWË4öÂÝÙ3>;;«²SÇ,û»:šý²füïzúϯ*ô›ôt–n87,ÈÓ§‹ ÜIyµ÷÷œ*^Cç9ï0ŠR•›gma8)¬Gw+–ž–HŒ•–W¿Ì-Û{4ñmem£{7/íéa£§î«å e%ŸGîåñEj[%ëd·nJ€ÔÇyUeÏ…tws[]ûjò3777 Ñ(•Eu]€³ï—ºúÒŠ‘¦Ÿ5|4JCs¡rŸ&¼¨ßžméhëÖ§¾\‰ë¢P(«°úûê’™Y6tèq.W¤D¢ ý矉ffºQQÿÔÔ—.ísýú$/¯½ÅŵªtW²e}õõ>xõªÒÕÕxöì€À@{oï½ÕÕÂfjVWW×S§Nkëè(‘(äï¿ÿ¾yó¦ÜzhP•f!Ÿþk’¾ ½J QPÃjý¯&ŸÎËy%ÌÌ‘{MÚeQj!2æ=½Róà…ÐÜ›[–¦mÜœLUC 2vìØýûömÞ²5öÌi]]ÝaƋŢP(Ó§Ï?¡ñj@£Ñ<Ü=ž|¸¶¶60Œ^½z%''988¸ÔE :C$ªó‹M6Å•••÷ïßW»¨L«!7äÁ%ÐWUð´ ñ–xY†?+ÆŠ+ðÚZœ[×TbB>@(*r5›êè—~£š™±Ý]<=¬üf€D’‘ž.P¹“újäØïìm»uëÞÉÓwÏž=`iÁv6)!p1‚ Òïe¡P8fL¸»›ÛŒéÓBG]´d¥D>?*J’±ÿ÷êUi 2gj$¢Ú´i£‹³“­õ¸qáþÇ«.‘mI¥¢³§ ¤R)………Ý>óóìÒ%hPÇÓÓÓø¼ ¿¦DrF4ºŽä Û”õù®PUUuøð¯ ƒz®hÈWïÚ\þçŸÎܽ½}þwçΖ‚Àžo£æ™›éKb¦E xvéìãÛ½Sgï©S§¼Èº#Õ*ß+ûIá8Ž ¨¬ÞeYÔ± ©ȹâô¾iÿáÀ¦‰.f¥¥ecÇŒNM}hdÙYæúAêÛèÙ¤ö iP¨Nªÿ×Pã†ô8xôe[:¼x••„ ˆ4¢rðè«üŒº!hŠÐP·üüjIDoÞT]½úrÔ(÷æk–¨ 99¬¬>¾“×­[kie¥‘_õT¥qÈL•†¡ @Ñѳ饗Ÿ†Ù6¿y‘}ßÌa´ þ,+3Y×q”!÷Q÷µ´È'J*Eýõë×®eefÊJ‚‡Óé prròóó[³zuaaaXø‡ «×¬6tè¨Q#'OžlaaÁáT¥¤< pbÙûùXJpswçñx‡ÿÚµ«ƒNïÔ¹ó„ñã­­­=½Üõl­ŒVÏe1µÁäI_—¼õìû%‚ Ï_—q+Ã0C /Å®~/Û`?ÓØÉ©ë˜›’òmþݽ“öþȰúçÜ•©ÜEÑêZDbld\UþBáùZXÚ*7¯–Ëßwäêäðž¡ïFËßV­ØzáÕ› .O¨d¯œ+$άc}ŸB£VÔ)öV7÷Sæ}xmK—¾lÙ’¯Æwppˆø¢ëÎ#)tƒú^kêuÛüo)‰…zRÞ@ce]œ»ôƒ÷•ä½r{Z##í‡ëT<©¨à!jj¬zˆ‘‘öæÍƒSS‹.\xª)­†asçÌù&òÙ¡Ú\U Afª4Œ.M¤eÛߘ¿Ó¼;`ØTSŠX, ÀŸÖ½'.æãH*Bˆ0MÏ£›Î¿åÚ22³ê §L™Òh$)—åx‹‹ËÁƒ1 »¬Z½zÕêÕ²Ù– eë¶m[·mk’í¹ß¾L¦QÉÝäuÕB_ÿîNÎ|.Á«%*+×3oÛ ÏÙC0ŠCfGþþ”1EK[_“fÈç9d3ïÞ¬_¿nÛÖ­€ …®Ã4°éÜ󦑭‹ƒ™$¢JMMýfò¤7oÞ 2ä÷cÇßuÃ0ýð}.‰ô -?è—9.Š 2òÆTUq%o\\\tôL䳊ZÆÇÇ͘>½þi☀F×éù™ÓÜiƒè4JaaaxØØg9/;L”Ì-“XD£RhF` }P;{ûrLE ³wGùÛr±ˆ«ð|«jxš÷ORö™ØXc­2Gû®>]'L˜hlÄ Ðßñ[–Ë\ù^ü}­dƒ!q¦©É‡AQü)¨b•ìÕ¢R÷#ð8n>~n®Î¹O7ºú…Éù izæ©ùu¡$GT¨§!åŠ7¬²_“DT’|•«gåöt ´µ©gφëêÒ‚‚ŽbØÇ´ìξ}{ËÊJ.\Ô®Tµd¦Jä¿ÎŸk€¡Êÿ• Jš˜ËÏpmi“Hd©ÿÛ—måö6çaÂý.ØùŠ|œ@q®@·èò†þ©…Ç,ûYK®ß|(™*ͬhÜ ¤T•­{µ«üÐSïݤïÇUÖ~ƒæMý~¬loDvä E©RýBố77·›iR¹lûœWE¥åSc}??¿SÂï>¥ŠÄ˜‡‹†áiod[>]\þ¶Ê؈5|øˆÐÑwÊD ÏÒîÓÝ™mÊ:tò&vú6"AÊÊÊ%K™üµ–®—'Ì+¨g{³­ÑcàrbúÎ_¯!Š&Jºpá¢Í{þbŒùnà2ñÆ ]Wáù¾Ì-Wn…‚ày;…Y\Z™ò#"¿ìùe/.OÀã‹ XÚ —/ÿƒ yT ªd/‚ É÷rú÷t€½ûöUUsõYºÁ KÍPÏ*‡.Cå&¼K:vïj?°·üÄg.—»uË†Ž‘ÌáêdJœY…ẏWë?»Ç¶tôè:°~ã–žS%1FztÉ«Ä<%ö´éé¥ÝºYÊJºt1{ñ¢‚ÇÓÀ„*:Ö«—ÍàÁ¿§¤4R;°½‘——ÇãñfÏž5{ö,©ðìÙø³gãOŸ>3` ‚O­TµäÚ$Ÿ Ýcit-[‚x÷H#¨¾‘eÂS„šò?€_N‰šõÅö=ŸëÌk 3¼ó+šÃã Wn=÷Õp7OÏ.ž]®'ÞÞ»;yÛ–uuºÔùVþ ¤¸´jסK£w±²¶–<|úß_«½úÏ“kŸ–ñfÆÜCû9ôìÙÓÌÌL(>}úôß‹¿ñj åZ¦<É›1oûð~N=z˜˜˜ˆD¢ììgwïüwòÔI¯FÉ7Z-§àEÚÏ>‘*¹â½ð›É“¦Mÿ.(h…BùçŸÿ-^´˜¦mljãÙÐù*7Ãñ‹ÿ>¶4Âìíí õY|>ÿÙ³g'O?tè£çPå{Rçîœ]»î“lFGFGvî¼[®pƒa2™½zõ’•P(¶™™TxíÚµ/Ç…ïÞ³G²œZaa៊ˆøúÛïäׯՠ*ÈÉÉéݫ眹s¥3´úÀõ7$›[6oÞ²esòÍ[ÎÎÎõ»79§ŠäÓ¢©Sq-Ý^ÈF°¤K,=½”CÔYc÷h”ÆübÂ]Ž]†8v¢¼»¾‘O¿oe%æ¶u½ìÔ}tW¼Â†•ƒ¿•ƒ¿œ°~{¦¾¹G·0¨Gý–,«N  ¼û(xfB}Cë†\Qº–žŠV©dBqéÚðmAù^°vîiíÜSºifíÙ|«¤¡@@@(zv™'¶ÖÕ7wè4ȡӠ†,qs27`iËJÚOEõ¶ÕÓ ˆDø AG·m Þ¶-˜N§$'ç~ùe¼ô¹?E(”Ï…0™ 77cM¥:…Ö0mšß´i~RáîÝ÷¾ÿþb£š¡Pɰµ*µÎA%ÝÛ¶6ã†áïR©©©ÉÉɉU*dßUA`X5E0¬ÎX-Nà†5uüŽÌT‘|Z¨q÷°tpñ…ZV(Ö²°î8Ï}LÔ™gÖôJ»»öÃ÷3‹^§H%>Ó=ë©óÞúsPðJS\f] š¨SÕõ´ÅŵãÇŸU¸ëï¿sd•t³gO›ª*ÁÁƒ) ËÑ¥Ë%{•kÎÊ*“Ý[_ÛÊ•×W®¼®Šäunžìæ€KË>äÉîݽËd2'NŒhiU...² )ù¦ìæ‚ ,X¨Š²´b¦ŠÐÄ„-2SEÒÔûMfå$Ÿ×!iM¶Ç\:uJIÞ#`»µÞ˜NâÑm¬G·±·«K¯K›sÐ}ú¯ õ¨‚§§YMÍ’ ž~ùe|CMÑ¿¿ýöíÿ½}ËÓ¬ÚÕ,Kff†­Mpð6$Q›äääé3f6Þ´U©N‹dª™W2SEÒ®ø¾;$]¾‚€¯ÚÚŠNóËš Õ¶zeÁ‚Ë?ý”55†$dâÄ?4®³¥5KY±rÕÜyQ )ë¯PÒöìÝÛ|%W¥:䜪& ‹Ÿ¤¿|–óæó‘}-‰³xÑ»Ì!N·³³ûò«¯¾ûn&…¢Nõ¤¤¤””³fÍ–J¶mݺcÇv¹äg}TlöéÐjs,HH>.\oÜzп÷gêu¿–|ßÕÙ¹êQ…’’Ú’’Zå &&&&&&Ê%Ÿ2äœ*• ¢¦¦ææù€°¬l<¯^{^ÙÏŠ KµÜ]lLµø|,©Àðiv¾‡»M£ªzõêíââ#BC KÿuîëÛ·OŒ˜ø×_¥-9œªeK—¬]·ÞÕÕUÀç3Y,ÃN:•˜˜”zÓ¹ÒÒÒF æáá±yó¶9ûÅó™õ HO24$ÄËË{×î=ºººGýÅ/ýííí 'L Ri;wí633+--½qãºP¨ùšmNGZ`•„„„„¤]!—©ú–©iê¼(¼¡uï•u! *¯Æ‚|Ö—Í £8b@ŒܪxZŒiѵ“ž×$gó ò«Onl0¨âñxµµµ|>/>.þêÕ+!!! c`PÐÀ w%€»ûû{zzõéÝ+3#Ãã}¡}¡P°eËÖîþãg²X(ŠÚØÚ*<ÊÊÑffìó ´´´à}ÁÀú¬Z¹’ÍfÇÅÇ3 èÓ§ÏàAAÛ¶n9rôw—••µsç®Iã!C)>ùÑqû6£gí ßÕÞݺ5åò•ܶ5‰„„¤åd;oÞ»:îË–ßú5>©mí!ùèà™*P+¨Âp.B»üˆ“SUƒã8&ÆÅb ñçù¼·"ªž¥é›Z73¼^N­T–Î8àÃúÊAAƒ~þy;…Â_vìHH8Ÿ——'¾+öš““# ª´µµ»uﮢµB¡àöíÛ?Κ%‰¨n&LNNž9s¦$¢A‚ƒ‡Œ9(9¢››Û¦Í›jjk{õìéîáѱ‡É$Õ£GÛÚ’–EW‘´|Nà8¾î§5²’%Ë–KõȾJˆ1þ¹WÝÇ‘é`ÉÀ” .ñ¶ *ªp–­>ˆ)_ã¼AåKNÞüÓL•}ßþýÖVÖ4:ÝÎÎÖØø]BkáÂçþøcñâ%Ÿu릧§Ç©¬:4„/àK{ééé©Óp8U†™™±kƉD»víÚ³çCéÿP¤ÿä©Ó›6nزyóâò233³é3füðÃ5´’DT^ñ åASûúùú®p®‰¾>>@úJH_©Ž¯Ÿ_ã¡Òw3¯]¾’KU$­IkfªB%ÕÕÈT-Z²TN"Õ#û*á¼ãU ‡ö6-®º”bR\ƒç¼üb¬¤˜[ö¦ª2¯ÂÄúH7w>4Œ——·äé?Yâbcg~ÿý´éÓ%›™ æ?©Ž¾>‹J¥–”+oÆb±(Êôé3ÆO˜ °ÍÎ]» ;;ûø±ckV¯67· S°ÈZ‡áAʃٳf5)p$bûŽÐÔŽŸ ¤¯T‡ô•êH|¥JPEBÒú´D¦J;Iã§¶ÏT5õF¤eýŸ½óŽj*éø¤Ð‹ô&R•ª`¡ˆ‚¬+l«k]{WPÁ.ÈZ{]ë 6¬Ÿb²*M‘& E-=ïûã¹1›FIf~‡“ó2oæÎ}—ð¸¹ïΖþMÌg ¾²„UGÂ~©d•–Ñ˾Ð*K[ZX-ˆ:èaŽïi!Þú8‹E§Ó544Ø-É·’ÛE$étþ»BÄ$%%-[¶\EE…o‘HôòòJOO‹ˆŒ^¹ÔÖÖ6*:úäÉùùm*ÖÕÁ`0b}BÊÊÊÚ7P¶h+ÑaÛ ‘C`Nš[˜ŸKÈáþÚå$@a`[Z,*S °ð,: Q4Õ0ê,`0Ì^Ö 3S±„c±XßAƒþ<{600ÈÜÜ<9ùæ™Ó§ÛegoO&“O:éêêF${õâ<»9*zÔÈ 1£GÍ_°ÀÐШøóçœÜœ;vr ‰Þ²edPиqcgΜillL"5¾y“…°ˆÈȲ²²%‹;ÖÆÆ–Á`$ß¼I¡P| ëÒº(í^!e?+ÐV¢mt]~òœ*w7·½{ÿàÛ.dTE5­wUsM¼º ¨€FULK £¥…QgÎhjÂPÈ€Ò‚£ Ïsh½{2”•ùTæÎñË—-óõñ&žýú:uzäÈ áC§M›¾sÇŽúúz##£wïs8Ï:;;ß¹{w玫V®lmm577Ÿ8q¯^½z?x˜²{÷® ë744Ôkk븹¹Íž= ¥¥ebb’pð`yy9‹upp8~ü„ŸŸŸ¸—@ rKZêSöqêS¿ÎS¤‹Ái7ˆp~òHÕ¨ÑcÚQ½º†bg€µè†Åb‹t•°­*Š:0QÇ’É µÓÚŠ¡PñOþ¦y÷·$döìÙ¨¿Â‹¡¡áù 8[jjëØÇQÑÑQÑÑ\Cp8Üž¸¸=qqì–å+V,_±‚ý¶wo§sçÏóÎÅÕÍÆÆæØ±ã¼Ý455ã&º–®EQÑg€••¥ˆýa¤J@[‰´•ôðñý}OK}Ê>†ÚJtÒRŸþ䑪öÁl¥—TQ/<ü† Æ"·ÒÉZk …L¦SÈ4 ™F¡1U0$;S©jiÆÆÆÆ&f"~W€N• €¶h+¤ëò“GªÚÇÅ• ö À Ùö¢€&h>‚kjì mM íÀÊÊR¬o ]Ú©ª®®>zôhNÎû¼¼|–’’bj*޾<Ø*##óêÕ«YY¯kjj´´´|}}—-[f` ^N¤ [½yó&!á`AÁ‡úúzUUUç º»ÃwHÈ R…•¬¸[É7%+—?ÏÁ¾ƒ}<ûz öõâë9dçAžþƒúùûõó÷ë0¸_ÀàþöÖÒÖÒ>*+Ê¥úEA~(++»s玖–Ž›Ð4AàСC………&Lضm{hhèýû÷'OžÜÒÒÒÙzÉ#_¾”kiéÌž={çÎ ,(++Ÿ1cF>¿Í¯~ý¾P_OW_OwÚÔpA-mR[[»`þ|›žÖÝÍCC'|üøQ"º½|ñ"tBH/G#Ck+ËÐ !/_¼ˆäöÑ>[=ž9fôhs3ÓžÖV¿ý6»²²²Ý HP”4ÈÊÊý9Iûèz‘*ˆBÑ¥#U®®®Ïž=œ=söÅ‹AäA+^äA«¨¨Í––?2íìí/YrïîÝñÁÁ¨/ò`«Q£F5šý6((hðàÁ×oܰ·ÿiÃó¶¶¶—.]VQUÒ":\[[­¦¦÷GÜØ1£SÓÒõõ…U‡…Ò²Rn‹-ÖÓ׫©®9}úÔ/¿Œ»wÿ¾““s%·qm•••5þ—_úôé{èÐáV2y÷®]cÇŒ~ü䩚šš¸SKP””øù÷þƒ( •¨ŽÁ`P5¾W‡ƒN•`zôèÁ©†›»; êëWyÐyÓ ­­ÃáYL–ê&)””\›«ò¶!)))77çÚõÞÞÞOO¾}âà]u$.!!BB&°ßþ2~¼‹³Ó_ýÕ‰N•¸¶Ú½{—¶¶öå¿þRUU¸ººz{ /D\¬¬,­¬{v¶¹†D"ÅÆÆ:88øùÁµâ™5{–‡‡ÇˆR>t˜wÇ-›‚‚{{{Î{‡’’b*•*ù¨ƒ[\üyõêUD"!<|ªDÄÊF 8[ˆDb~»vì ()sªä}^H;ëÝîï¾rõ¥>þ*•ºxñ’V2ùøñX,V®tòd«‘MÍMÕ_k.^º4wÞÜ#GŽºººt¶RrJ}}Co''Î-mmA ÛØí^‚ƒÇ£ù麺z—.]vpèJÉm666ÙoÞ ‚îkY__ÿéÓ'F¡P”••;K””€uª$<ïÝ7W2ʱ8Ž-Q0?ŽÉTÁj$35”qÓwãñŸð---=,>™¶··OKvüøñuk× -ÁÂÂbÒäÉ ,Äápb_iiioÞd-Y²”Ý·gϾ}{KJÛØ`KÄn€—/^ÄÆÆäææÖÕÕ©©©õéÓgåÊUžýúµC[iðs8UN•HÐé´E‹øâÄIss3ùQŒü¨ÔóßÐÔÿÑcFÿñǧOŸê\•–ؘXR#©ª²êäÉ“aa¡]Iôððèl¥DeÖ¬ÙK–,Þ¼iÓÒeËZ[[W¯ZÅd2X¬Ø{‡KP””€uª$<ï×KÜL-Ét`Ð[#` ÐXÊØÝ•_}lZ5Î"êj—S¥¬¬|òäÛÖ²eK­¬¬-ZŒ¾ÕÐÔdŸŠ‰511¡)W¯&EGE5Ô7DnÜØŽëJKK=qü8§S¥§¯ggg×Q‚ñê•"EJTgƒpäTu¶.|­ètúòeËÿÎÎ>r䈽½½œhÅ…j…ÇãzZ÷üð¡@u“´µµH$g ©¡ƒÁhiiID¾ƒ£#z0rÔ¨þýûm‰Ž¾)O‰D™Æ“Éä¼™466¾{÷ÎÖæT ÄÞÞ>;;›³%¿ ߢ‡ÄŸI)))9Ø;ää¼—¬Xi³båÊ…¿ÿþ¹¨H[GÇØØØ×Ç»_¿þ.JÀH•„çu´T94Ë–Å  `0“†Á)€A˜4,N a±Ã„%gHmKl Ó§Oß·oßÖ×××TWïÚµóõë×uuuÚÚÚC‡FmŽê¦«‹öÜ´qã•+>rtÛÖ-¹¹¹ááá8<þè‘#}=]€±±ñ»÷9\ÏõòrswíÚ™™™ÙÔÔdffºjõj^5òrs·oßž™™A¥R]\6nÜØ¿ÿ¾ K{õŠBUTG$åáC@ÁÇ@Zzz7===W9«*¶Ú²uË£GB‚Cê¿}{ð¿ÿ¡æÝÍíìì…”1ò`«E‹Û;Ø©¨¨T~©¼zýjssÓœ¹säA7ùdĈÀÛ·ogd<8Ð PQQ‘–š:KÀö¯bÁåà644¼~ýºW/ÇŽK–1ÊÊÊh¼íÆõëùùù›6m–QFª$='å%‹ÁÖ=§6ÑÝGcñpªü‡ f ýã½¶ß¾›7o”••Ñhß?I………l§JEEÅÃÓSÄ)h4jffæâ%K„§Ðh´ôôô… ¢ƒÁ >âØñcœÝd¶zEÜDué8úúú‡á{jˆ¿Mmûí«—/544¦N&ŠØÙ³gÏüQ¸dγ€´ôgœoW¯^³zõQÔè=„¤Õ‹e Š’’qd©B„ÇÖ‰‘*Ô¯ôŠvÃ&ASß=4¤"÷«™¯5Ró)ÿ9É*PSü-»VÛsöHÚ?ïI­šš&_Y5ÌvÕ@øÎá#GÌLÍ” ‹îººzhãš5«¯_»¶nÝú¾êê꤆†  @ •Â¥®®Ž–÷©‘Éd´Qd…D"Ñéôøøø„„CìFë?߀lW¯ˆ•¨Þ¥#U]h+Ѷêòóó,º›>âè±c‚Z$EzzúÜyóttt$+Vª’9‘ª­$x ²±RÉ©úï-ág‹T zEQÂa&³åÝýæ/ra1-¿ Ðí›ß> g t]ò‡‡ÍYE4#—ÖÚ·*ÆÃ;©rvvá-pœxåÊÂߟ3w.ú6?¯C»Êkiiâñøêê¯Â»ijjâp¸¹sçM i;wi¯^Q¨Dõ®´•è@[ÉžM›£–¯X `ïËËÛ"Aj»“œIf#m[Iðd` ^`N•„çÅá° *¥šdQ¥T|ÐeÐôzUEž#ƒ¡‚A>U|èNǪ+5âê­­M,V’{ø°X,:®¡¡ÁnI¾Õv(ˆH Òéü9Ä$%%-[¶\EEE "ÑËË+==-"2Ï“ê:cõŠâ$ªw ­DÚJöèééééé o @[ ®þ“ð¼8 À©ª›{©yËì1L›Q^ôñµe°럂üt5«q:­ï ZYî&Æ_j“dX,ë;hПgÏ™››''ßÞÁ³_¿S§N$|H``à´iÓwîØQ__oddôî}çYggç;wïîܱsÕÊ•­­­æææ'NâÒ«WïSvïÞµaý†††zmm777vr¥ðÕ+G1+ªË9ÐV¢mt]d©’L¦ºœGªÔ”èÊÝëRy&• ñú8ƒŠ€~JžY 3AèLª’ºƒ‡ê£660ÏË/àm²ÄÐÐðü… œ-œ«¢¢££¢£¹†àp¸=qq{ââØ-ËW¬X¾bûmïÞNçΟ狫›Í±cÇùj%|õŠ4€‰êò´•è@[A ]éDªŽW‹Tå–|™yE›‰$üÁî-£•ò[‰¨a#V¢zMMMkk«èÂUUUÛ7Pô}æ IDAT¶h+ÑaÛ ‘C`N•„ç½¼tp›} ÒFÄo }Üûð µ9 AXí¨€¸»¹ ­DÚJtÜål&„ Ì©’÷y!ÒAÚ³ÿ ‚°F3gÞŠ/e&¦æðUÈkZêSßAðoG ­D'-õ©T#H»‘*yŸ".¢'ª¿ÉÎ^ºd‰èÕP‚ìÝ·õ¨ðUÈ«‰©ùå‹çD·-tud©’d)&#U@ÕEé‰Á`XâPZZ þõ ÂV‚@ ŠïÞm"îbGª0˜(!g“oºÃgYiÓYóBD!7g<ûXÕÑg[âŽR4`¤ (]/RÕYž ô¨º•åRýLCJ • ˆ¢Ñõrª`¤ "Y`¤JJÀHDÞHK}Ê÷"h+Ñéz«ÿä~ü(d:±:wi¸¬xúôé¼¹s]]œ ì-ú½ªªJˆű•pfΘ®¯§»è÷…Bú@[A ò@VV–èûy´ÅŠT•V4}#á08 ‹ÉÄ`° `!,,‹|¯Š„U#âjZ°dVÒË/Å_¸£5L&’˜˜Ç~TQÑÄÙÂæÉ“â‚‚Z@bbÞ«Ws.ôذ!E¬`‘ˆ£R™¼íGf=š%ºœ6™;7ùÇ»åüýwUbbè„ ½Nž”ʯRÜDu6ÕÕÕGÍÉyŸ——O£ÑRRRLMMùöäŒTÑéôààÚÚš¨¨h55µ¸?âÆŽ𖮝¯Ï;P¬Î]ÞHUÜž=S§N3ïÞýÓ§Â#‡gdd<}šª®®Î;\¡l%„{÷î={öŒ@ö%Ú ‘`¤JÂóv7RÞ5ÝdÇd½á;¦lŸb°c²Þö);¦올·+Ü r‚î‚aZkÇu›í¯c¨#h ‚€/ÊÕÕ ººªNNW¯†UT¬ Ó#¿~]yêÔX=½[:ÄÆ«ªZéïoùâÅl2yÞ=Ã÷î±aƒ–A6!Ȧòò倈ßææõìQÎΆ׮…ÕÕ­¦Ó# oÚÄ?ììlx󿤆†µdò†gÏ~õñéÎ>ÅéQÒÓK¦¦¿vAˆ•¨ÎŽB••–Þ¹sGKKÇÍÍõ?'„Fª’’’rss9:yÊ”±ãÆ]¼x©±±1þÀ¾s‰Õ¹«Ã©Ú÷øÉ“+W†††®[·>>þ`iIÉÍ7øW([ ¢¥¥eíšÕ7nÂã…};…¶ê ‹~_¨¯§«¯§;mj¸ –6‘A°P”˜¥´i‡­ªªªÖ­[8b¸™©‰¾žnYi)oŸ‡Œ5Ê¢»yws³¡þÏž=k‡n2›H0R%áy•ˆªê )¤òGMõ-fz ¥¾¦¢ÑÀÑ4TV—T8¹°êªhˆN©NEÕ‡¨ ÝzôÐf0XÔ>}Œß½ûzâÄ›†Š¥¥ÎºuÞׯOôö>Éî©­­¼wïˆ%Kîåç×(+ãI$*‡™>ÝÕÙù€Áà®0Þ§qjêÌœœêùóoWV6ÙØè:9ð*àâbøìÙ¬7o*§O¿ÖÜL›3§ÏÇÓ<‘•UÉÛÙ××ðþ}µD®½ã°]%W×ôôtÀÙ³g_¼x)¤‚g¤êþ½»ÆÆÆÞÞß¡š˜˜øøúÞ¹s‡wïjq;wux#U={öä|Û¯@e%Ÿ P0[ bûöm&¦¦SÂÃ7lX/¤´UDZµµ½té² ÇÆ‚¼-BA°P”˜¥l×V%ÅÅׯ]sss÷ðð@ï±\œ9szåŠ[·mÃãð9¹9uµµíPLf ®þ“ð¼8,@p*L:•NЙu^—A%±êªX8M ‹F'Õ³´uq42¢†ÇbŨåÍ…ªª’º:AE?y²S` Í ãîÝ»w ÑÏž•egWæä,pr2`{0D"nÞ¼[Ïžýð H$*‹…7𙀘˜aUUÍ~~§Éd -ãؽ{heeÓ°aR( À£GŸ_¾ü-"Â÷—_.sõìÖM%&fXvvUrò‡v_»pÄMTgƒÅŠUå\ýWPP`ooÏyÖÁÞáQJ •J%‰\ÅêÜÕisõ_fFÀÁÑ‘ïY…²_þþûïÓ§N?xð Í¢ÿÐVGI‰`Þ½»ð! ÁÂk×o ®­‡§§Gß>ñHʯeÇ,…»×²A\[yxzæ|9|˜××)+-ݰ~ý‚ ;n+™M$X§JÂóâp8f+©âs9¥µ¶ü}Ñ—j: ·4·(» LjÉÛ5M¬†²…d ƒk¿O²²æ45­«®^µwwÿùí·d€Û¸qÐû÷ó×Q(YYsvvzìQ­­ôŒ QW®‰8__‹‹sPJnð`Ë+WòP € 9ù£·7÷ß˜Š þêÕ055¥ÐÐ+í[«("LTÿvvàŠ3RU_ß ¥­Í)PK[A†>~ªX»:Â×HÖ××oÞ¼ÉÉÉyøðá:(­xa2™Ë—-ûu֝޽zµÙYÁm% JJ>;f))²Dø÷ÕóÎ#²|Å ‹ÕžýXe?‘`N•xó zeƒ,¼ªVO/MS»žÃ|mØc”5úh™ö‚šmP@gk];g++‹%rd„S¦\õñ9Õ¯ßqƒ˜‘#/ÔÖ¶ Z¹ràñãoκ¹2ä @YùG°°©‰&zmmeSUÕ,¼›ŽŽ²’vÕªJû'2ÒWW÷?¡`"wíÚDWW£áÃÏ~ëbÅÂÊÊÒʺgÛýþ…Ÿë„ðo°úÂ!V¢P(3¦Ooim=qò$‡“¥V]…ÇÕÖÖ¬Y³¶³ˆß`aII1•Jí¸p4f+ÖF¥]…ÏŸ;::&&&º8;è»»¹JH”z!ÿUV”ge½‰Ž¶1Lù©rª½²Áa1‹Eùü†R‡§uûJ/« 3ÊçlFQ9`hÐÊÿ&ªbèXR?tÜqxü÷æM%ºú“)Sœcc3öí{¾å›ÿ$: ƒedÄge'$•ÉDöî}~â„À_ €KL õò26ìÏ7oøçÐH±B¯|þ¨Nß!œ1mm-‰Äy–ÔЀÁ`´´´xŠÕ¹«#¨NF6mjNÎûk×oXZ |J«P¶ââëׯ»vîܱc'“ÉdF£“H$555Þ¤uE¶•œP_ßÐÛɉ³…,444ìˆd±b–]‘ªªªªªª¸=±7m633»~íÚÆ‘TméÒ¥]q¢¬¬7o²³##"¶lÝ ÚU±M~ªHU›`±X&ÖÐjŒ!jÕ•ë70¬£µ®L·ži³î‹~¦­÷­Æ`É~íÀb1®±ñÇ£à`þÙ*œP© %%þ¿#*•™šZ2y²“ªª’  ãÉ“âÁƒ- ¿Ôrþ ðxìåË!þþV£F]ÈÌ”â“f™Áƒ±··ÿPPÀy6¿ ߢ‡²²2ï@±:wuøFªh4ÚÌ3_¾xqéò_ÎÎÎB†+”­¸(++#“ÉK—.éim…þ´¶¶^½šÔÓÚ*õ)?U‘mõÓóÓÇ,Y,Vssóþý¼¼¼bbc‡øûÇØÏdò©ø#ÿ¡•ò¢X9UXpD}e¼*ÞÈSÝоµŒjÙ“^Ù¨¿ºžIÕHÛȃ¤#‘*^X,äáâß~s·±é¦¤„4©÷ܹ}Ú•›[£ªª4~_ggîoT«V=04T{útƤI½ýüzÌšå¶ ¯+îÛÙé>~<}òd§Aƒ,ÆŽµÛ¶mÈŽßë‘>V·sW‡7§ŠÁ`ü6{VjêÓ /zxx®P¶âÂÎÎîú›œ?D"qð!×oÜtswçí¯È¶’¤,Dc–kÖ¬Ec–èhÌ’Á–êÚ…èÖ­ÀÇ×—Ýâ7ÈD"•—Kxã ™M¸Gȉ‚EªpXzë·–ò¨ÕÅ-EoÉŸ¾1¿´f“ ëMßZ ÿ¦|®'W´Ö# F¶3g^ÿø±îÝ»ùµµ«§Nu ù«Í!7n=šµeË/~»sg ×Ù7o*½¼NVV6:4êÁƒ©k×zWW·ð yûö«‡Ç±/_šöî‘’2ýر1®®F©©%èÙþýÍsæô¹reûgöl>ÿ$EG+ªÏ©â—Ãøo &8$ÄÁÑqîœ9/\¸qýú¤I544.ü=ûøñc#Cƒ¤¤DQ:ÿdðFªV®\qçÎÐаºÚºä›7ÑŸœœ÷èYE¶^ÿ‡Ãxyyéèèh+ùCJÁBqc–]»ï¹h?“±+é2›HÙ©ú©rªÚDÈ$è;kÐæjÛÚ8-,“IG€ÞËbÒŒ3ƒ0Xt¼²¹Z}_5ŒŒbyãã_ÆÇ¿äÛ¿²²yô苜-Ìt¹•+ÿ·råÿ¸†0™Èܹ·æÎ½ÅnÙº5uëÖTöÛ¿ÿ®3æ"à«[AAíĉ‰|µêÝ;o»”hwEuAR>|øøþ,½›ŽŽžžž«·?̓QRRJJºA£Ñú÷ïôè1vÂb1™LöJá2xsª²^¿œ={æìÙ3ìÆY³fíܵ(¶­ÄÚJÞ1"ðöíÛÏôÿ gÍžÝA±hÌ’³%,tÂ@/¯%K–: ¨EÒå9räùsçRR~­¦<|¨§§gj&át2›t-§JÎëT½Î-k ˜ÐYŽÂì!(}B ?Á}y¥BûÕ ÆŠ•+ÙíÛ¶møøøà©LÍY§  ¯¯øÈ¾Â‡øû×Ôþ§ ¼Î?¼uªÒÒ…/Vd[µIIéîÐVòFpHH¡„¹sæ¬_¿AUU5î8®ˆõ¤‰a‚ƒC•••ã7mÚôù ‹Æ,9[Ø1KQ$z{ \¶|9;%ËoÐ À“£\±11±±1éÏ2¸ óJAn%'rrsSêéêzöëêíí½tÉâÊÊµæææ×¯]KKK‹‰Eë#ˆeºŽL$Y[u%§JÎ#UeÊËü ¹ØŠÛ©ÂápÙÙ|~˼Ë7„W`‚ Zýü|ˆ±nnn.,,¤3èŸW¸dA˜L&ç?x&ó?GX‹ÉdJc…'L&ó×_g²ß®^µ 0tè° /0ÌÙ?ÏmÛº%6&†D"Y[[ÇL û~ ☮CIÔV]É©’óH¤Ó)³¢z;n(\‘*_Ú¬¨üLˆ±~õò¥††ÆÔ©ÓÚ1‹ð˜%—d®ˆ&W´xõê5«W¯i‡bÇã¹ÔàBCCcç®Ýhb™®#IÖVœNÕ•ÄDÀ„!Çb¡X«ÿ ò€X‰êízT¢­éBäççYt7ŸóÛoBZ$EzzúÜyóÐ5]E2'²´²¹ÀŽÃ©ú#»NбèÀHD¦ˆ›¨#URFª ]…M›£–¯X PSSÔ"A’¸LiKf#c[q!ƒ ”œNÕ„ö†Q‚ŽÅBl§ A6µÕaL;ôè85/¤Mòrßs¾íhEõ¶€•(Àœ*HWAOOOOOOx ÚJºRª[É7Ûî$:k^ˆ´á[äSè¸÷Ÿ(@+A Å„+§ MŸr,vªFîœpQgÍ —"1+ª·©h%¢˜°*ô«øo³g³¿–ó‹+\Â9U·’ovŠ#â¼wßT\É(Çâ8ÊT!`~“©,ƒÕHfj(㦠î>ÆÃ”sxKKK‹î‚„ÛÛÛ§¥?;~üøºµß×  ‹I“'/X°‡Ã‰}U¤¥¥½y“µdÉ%ãöìÙ·o/×^D쨪ªÚ·oïßÙÙïß¿§R©oÞd›wxMTñ! Ì©’0§ ( Ÿ?ÿç[}Hpp}}½ðc±P¬HÕ×Kô•º«2 Õ™FjL#5¦‘:òïÓO10 šx[õÔW2‰çy:å ×peeå“'O±´´´ÜÜÜØo·lÝÆî{þÂ…C‡ÛÚÚFGEmß¶ ´‹´´Ôýûöq¶èéëÙÙÙµO_JŠ‹¯_»¦£Ó­ÍíÞ$‚••¥•µ¨…ìjjjZÄAUUPñ¥ }¶_…¼B¿(înnçΟ?wþ¼ô¦ÀlÞ¼ÙÈcÆ‡ŠÆV°iœÀŒ¶sçÎÛÛÛ3Nˆ89T9/zÌb¨cðX&ƒÅb1 “ÅÄaq@X,8m5œq7\mˬñSuõó݃„Hst°ïÛ·ïÙ?ÿóuTed>·±± 24À¿°°ðSÑg±‚U4•@ nß¾íÄñãŸÄ^&z¤ŠÅb¡%k>±A‘ª¼Ü÷·oß çÞ»ÐÄtê“Ç‘¶¶¶èñŠåËV¬pŒ¼þîÝûä›#ßð«ö)w7·n’ @:ˆ¯°;'Ê/¿üYTDºww "2ãäÉCò=•–úT m%i©O?yÂ~Q__Ÿ˜” ð=nhh8wþüo³g u½ JÄÛkT½:-áÇr©r´T94Ë–Å  `0“†Á)€A˜4,N a±Ã„%gHmKl Ó§Oß·oßÖ×××TWïÚµóõë×uuuÚÚÚC‡FmŽê¦«‹öÜ´qã•+>rtÛÖ-¹¹¹ááá8<þè‘#}=]€±±ñ»÷9\ÞR^nî®];333›ššÌÌÌCCCW­^Í«F^nîöíÛ333¨Tª³‹ËÆû÷€žB=ªŸ x‹x;h+Ñ«J!òLdD`ËÖ­ì–ßfÏf?ét,:Š•SETÓÒ©:]ýáì·Ê]{s@ª,ýXgáÓTçöðÂüRDFº)+Ô5CUµÚØøIDJKKñx¼††ÆÛ¿ÿvìÕkJx¸–¦VIiɾ½{§N›zûövO©1bÃúmÛwØÚÚR) MM“yéÒ¥ÔÔ4ÏýËzûöíèQ#bbb ‹>ååçñ*››èìì0AMMíìÙ3ÁãÇß¹{ÏÅÅE"(¢WT“½tÉ Fø>ÿA½ûöÁU ‰çT)V¤J % &¥•JPéµ_¢½µšñ¥ˆ¥¤‡aQ¨5U,CS„Ü„hpX1þsA&“[ZZ(rRbRJÊÃÀÀ@"‘èà€vðì×ÏÉÉÙÇÛ+?/ÏáßýÌi4jlìtwI MM,+èyÜæM oÜLVVV°ƒO\DmÞlhh˜˜”D$>>>ÆÄí‰=söÏv_`G=QƒÁ°w•…²2˜'@ at¥½ÿä>>!áÇ,Sª®ºp΍@ ˆáÝûæTñWÐ+Š‹0™-ïî7! ‹iùe€nßüö=»¡ë’?ÞÁ³_¿S§N$|H``à´iÓwîØQ__oddôî}çYggç;wïîܱsÕÊ•­­­æææ'NâÒ«WïSvïÞµaý†††zmm77·Ù³g£g™L毿Îdw^½j`èÐa.^ìèóCÜDu6™W¯^ÍÊz]SS£¥¥åëë»lÙ2ƒ6ž9szåŠ[·mÃãð9¹9uµµ|{ÒéôààÚÚš¨¨h55µ¸?âÆŽ𖮝¯ß…»/ž?Ÿ4i¢»»ûþýˆÊÊ—/]\ºt ‚ áS§òvVp[¡û¸¹¹{xx¤§§sË8 nIDöðæT}ûöMø±èü<9U¢ ¦DWî>X—rÀÈ0©,`ˆ×Ç1TôSòÈbPX˜Áx BgR•ÔÚa«§OŸ^¼páùó̯_¿êèèølØaddÄ>+© ”ÙDmÂéTÏöœ‹…bEªrK¾Ì¼¢ÍÄþ`÷–ÑJù€þê íTmÞ¼ÙÒ²»ÝÁÎ~ÉÒ¥÷îÞÿß¿C.Î_8 ê_²7äÄý{wÑÿm_ß;wî(È¿7,‹ÃáÔÕÕ¿¿Åb554q8þSt[Ià–[[ÛK—.«¨ª i‚´Ã[¶l555E D‚Dd¶qm·gOccãÔ©ÓÌ»wÿô©ðÈáÃOŸ¦²o@B(³‰ÚÖ©’——n»Dšˆ›¨ÎÆÒ²ç[÷>}_ÛZüøâùsGGÇÄÄÄýûöVTT˜››ÿöÛœyóçó-]QPP`ooÏÙâ`ïð(%…J¥²«|ýÄL›:íò¥Kë×­[ºl‘H¼|ùRAAþ©ÓgøvVp[ G,ã@KŠ‚’«2o‹¤äççàà QG\[퉋ëÙóÇ&÷N½fΜqóÆÉS~ìÖ*‘ ”ÙDm"U§J‰êòìQAä‰$ª¿zõ `cc+P¤q2›ˆw77έ”¥båTAäö%ª³¡R©K–,m%“?ŽÅb…§éÖ­[QQ‘¯/»Åoߣ””òò2 ‹\µµµH$g ©¡ƒÁhii‰®p×%:j³–¶öŸçΡõÌ|}}+++×­[;fìXÞ"·•pÄ2´¤ ¨¯oèíäÄÙ¶±)…p455,XèÙ¯ŸššÚ»woìß8bÄ“§©ÛYÔ××oÞ¼ÉÉÉyøðáh‹”.Pfqѧ;€ïê.I¡X9U®N[¼dɇù'NœìnnÞf;{ûׯ_ðÃñb!,Ã'FkooŸÍÙ’_oaÑCøîŠ? ………...œb]\]<ø‰DB×r¢à¶ŽXÆ–d“—û=ÐÕíÆ>ÌŸ?ó,ßá\½šÈÕØPÿaCýëj«ëj«;¢3‘€ ›€ø ðè¸XÑ‘¸­Îûð¡àû†Ò»@™MÄFW·[}}½••¥••ebR{jP‰ŒTAdJG*ªÓéôåË–ÿ}äÈ{{{Qž»9òü¹s))Ñ–”‡õôôLÍø$uxûö파gz***ÒRSg (ñóajjš““C§Ó•””Ж¬¬,uuuMMMÞÎ n+áˆehI6·oK2É é`¤ "kÐDu²='ƒ±jÕÊç/ž'Jpvv1“1 `¨··÷Ò%‹++ך››_¿v----&6}žõøñãIÃ&$‡‚CB%Ì3gýú ªªªqÄihh.\ø{{/´‹ñÛœ9s~ûmbXèŒ3•„ä›7?z´lùr ­þ ‚ ·’“ì}ôtõ =ûõmZRááSÚîH‚cÇOHPƒFÄï{ÐuZ¤Šïšv¾ÿ÷™žžžpð`VÖëææfcc“á#†/[¶\OOODá?q{öìÛ··¤´ ––öæMÖ’%K;[)þ´»¢úÖm[=zÒð­þáƒh£yws;;{!£0ÌÙ?ÏmÛº%6&†D"Y[[ÇL CÏ",“Éd±Xè[%%¥¤¤«‘‘‘4­ÿþG뢹íà—_ÆcæÐ¡„ æ³X,KKË-[¶²÷ÿ†¶âDø>Â- È YfÈ ‘«‹sTíÜЩÊÏÏG91°ß666öë×Oˆ„C 7FöéÓgÝúõzºz¹y¹Ç»yãÆµë7¸Vo*ii©'Ž—[§ ü7QDj|õêµÎìý»·ï‰I‰‰I‰ì³ÃÂÖ®['|: »vïܵ›÷Ô®jòúúú‡ië ~ZÆýò˸_~á{ ÚŠ“6÷!bhIA¿@ ò ž bÐà Oø^U®“sªD)ó…Á`øº“¯^¾Ü¼yÓ˜±c;Ž>Í5ztXXذ¡CgÏšõèñcáUÛF%Ú_ޝƒÃ>ä1ÓF9Vö¯>11QxÒ¥;Ž9DâDEùITÞCŸSõáÃ===eee‰ôíÛ·¦¦¦ÖÖÖ†††–––ÏŸ?¯^½šïÀøøJJJ11±œÎS–Ë—¯ˆŒŒxô(% `(`þ¼yÿüóñaÊ#vŸ±cÆhii²wAÊËÍݾ}{ff•JuvqÙ¸qcÿþÐS›6n¼rå¯ÃGŽnÛº%777<<|ßàiSæpà@77·ffòßï( .ìl ŠÂ§O…’%ÝÇbEªŠŠŠp8\\\œµµuii©¶¶6ºÎˆN§Óéô’’’U«VzõêÅ;¶©©©©©‰·~#ÀÔÌ ƒÁ”iqYÔæÍ†††‰IIè6[>>>ÆÄí‰=söO´FÝãÉ‘Ú5mÚôýû÷EoÙ‚{Ξ9£®®>>8˜¯|ÞáBf$“Éij×ÿ Ú$D:::ššX,–½ÓSÇeJÎDuÔ£200(,øù†‘*DAr€@$‹È+åDDjÿÄŠT555¼¼¼ºwï^^^®®®ŽÃáX,ÖçÏŸ-ZtùòeöB-qa‰° FKOO_¸p!{ãR 3|øˆcDZû¨¨¨xxzrŽš:mZllLbâ•3fÒh´‹/N˜ª*`p®áÂgTQQ±³³Û³»¹¥Åkà@{Ñ×H B2;g¢zÏž=KJJ„t†N¢ Ô××w¶ E¡[7î‚ÆíF«ÿ½ò6fåå›±Þ!fffåååJJJEEE³fÍzñâ…šÂêêê%¥|þ)/G„ouG.H$NOH8Änd±˜œé««sy!úúú#G:súÌŒ3oÝJ®««1c† )¸†·9ãÅK—wïÚ³®®ÖÀÀ`î¼y‹-î $ ™D¸&Щ‚@ù¹)AÚäÛ·oíÈ•±ÓYr$èTÉbõŸ W¾\¾ ¸“r2?•™™Ù‡&MšôâÅ ‡ââbA£0ŒÏãÇëëë¹öи{÷.`РAè[<Çõ™J¥  ÐÔÔÄápsçΛ.ÖeΜ9sì˜1YYYgNŸñððàÚRmÎhnn~ þ àãÇçÏÛmdd*ä*ÚDLu–,âVT‡@ ˆ\ñíÛ7kkëöýôéÛêT9’üºŽ'üI¸è@;³}ݼæÝ6-µ <ŸzáÂß©TêÚ5k8½Ò’’=q{ýýÐ#cãò/_Ø¥öóòþÝlˆHôòòJOO³´´´ù/Âu8ÐËÞÞ>jóæŒŒgÓgÌÞ™Ñg´µµŠŽVVV.ÈÏ~ÜSˆt:ï).™š¨Î~‹Á`„|CEÄA&êC ©€t:â ¬­­¿}û&?r$Ž„#UíT¡¯ššG?~ü(âÀ~ýûGnÜUþ¥|Êä)Ýtuórs9¬¤¤tâÄIv…1cÆìÛ»w÷î]óç/¨©©Y¿~ç–±Ñ[¶Œ 7nìÌ™3I¤Æ7o²)|ö3]»fµ¶¶ö¸qãĺ^!3–••-Y¼hÌØ±66¶ #ùæM …â;hP›WÁ‰½=™L>u꤫«‘@ÐÐÔ$³Sà­¨ŽìQÕÔÔ´¶¶Š.\Pf‘„Ü rEÇS¨„N”#½/áïTq"zçE‹»¸¸&$ܼyšäØ»·SbR¢®îmjœœœÄܽ{×þ}û,--W¬\I¥ü¨†Ò«WïSvïÞµaý†††zmm77·Ù"ìf:vìØµkV‡MœÈN9!3jii™˜˜$aaaœ«ÇÿOù›cÇøIŠŠŽŽŠŽæ{êáÇ€éÓgÑMÐpA3jjjÆL$MÈU,_±bùŠè1‡Û·'.Ž}VˆLy@ÐßCŸ>î}ú¸·C`ûö„@ ï­ÀÐÐ`à€þ€>|ûöMWWmwus±²ü‘—©¡¡áäÔÛÌÌìÔéÓ0 @ü‘*iÄ«:Ó©266n³X[Í쎉)//›7wÎÍä[®®®PM?~,.þ¼sÇöáÃG´™}á‚7QÆü!àw+`·”—•×ÕÕêé}°G).\¸Pøé“¥¥åŽÛŒŒtt´µ´4«ªªUTTdª·âÑéÎÄåHNsª¤â!âñ]á¿CœY·vMfffŸ>}wÇÄH{®ŸΊê‚²pá|[\\òàa ™Lž4)ÌØÈ=¹=¸zízqqɽûÿ»{ç¶’’’‡g?/_3`blüäÉS7·öD¸!¢#½Çv÷S ¿Vñö44±îÛS,9®!|%ŽÜåTÉ?IW¯u¶ ]±Õ!ˆâ€Å`ðx|ÏžÖuµµž<áÛçÁƒÿ©©ª›š™yûø²óYõÿ`})ÿÒÔØo)ò ß_êßv>Îßõ׊¢û©?\(Ô]34±_Yñ€NDÖpŨà†~åÊe}}ý¾}wÇĨ¨¨2ô+–¶nÙöו¿ÐnÇÀ`0ìÒКššnn®€¦ææËýehdo)ÒFªí¦OÎÛxæâ}¾É1ÈæÞÓï~ÕˆA6÷žþƒzT#I=c:Ué|FŽEjlÔ××Çáp­­­L&Ó…#5v ——½½×]ÝnãÆŽQQQ¡ÑhË—-¯¯¯÷Þ]ù·ÕÌ™3å#Gw¶"ÐVܰ*v£••Õ?ÿü#Õy¡S‘)¢$ªË'T*õîÝ»D"199yÕªUx<÷ßÎæÍ›Œ i4zEEÅãÇ—,Yâíí•pˆ@  Î;¿}û6kk«éÓ§›šš677ef>_¶lÙÁƒñ~~ƒe~AðüùóóçÏKÄW ‰ûöíc¿ŒŒ´°°˜={6úV]]ƒ}JÁ @dêTÙÛÛsµÛØØHo^èTAdM›‰êrò•‹K477oX¿~Ûöíiii~~~?zàáá™Ó§O¿xébtTô ëײÿþ{ûömþq{ö°² sçÌ¡3uɨæŸA,ËIŠŽŽ6Ð×çlAJRBù±NSR"ð9 zʃ(rb+Ô©ÊÍÍtJJ`¥'áÅÊÊÒʺ§ð>Hç!Dkׯ[YYNš<ÙÀÀàÚõkÿõýfñŸ!Ã&0à¯Ë—Éd2‚ 'ŽWRRŠŠŠÂápœÝllmÑã¿ÿþû€þýœœ† Ïî–žž>iÒ$ùóæÿSXÈ>µzÕê`N™Ó¦M[¸p!z¼{×nooïׯ_‡……9;»øøø9r=µ}ûö#‡455988888øùùIÊVèY„§]1 …°•ùøáâE‹|}}œœ¼¼¼Ö­_Wÿíר¼¼ÜÉS¦¸¸¸ 6ü÷ï#rîÜù¡C‡9;»L /--áÔáCAÁ‚ù <==]\\&Mžüúõkq¯B6ðÚªµµ5((0t†¶¤§§;::ž?}۾σ𳨅333ÃBC]\\vî܉¶ß»{oäÈ gg—Q#GÞ¿wáÿñ—¼¶JIIqppÈÉÉåì6sæLö'_l…„)ÿaµ©‚Èš®˜¨^]]ý<3sî¼¹ føˆá—.^n ‘´45¿÷DÃáÒÜÏÏ/33ó}Îû¾}ú¾xñ¢¯G_-MMAW—›—7mêT›Èõ ôKŠK>þó휑‘1oÞþ@ø”ÉIIWMLL¾ÏŽü×h߀466íÚµkýúõ=zô¸wï^tt´…E÷aÆ͟?ŸÅd^¿qãúµk,ßnË ÈÕ Åi!3*«ªlílÇ×T×,ÿR~ìØ±…‹~?÷ç9öX2™¼fÍÚðððysæ^¸xqÅÊ•S§äçå¯^½šF£ÄÄĬ\¹êÒ¥Khÿ ¦L wtpܾ}»ªªê•+W~ýõ×ó.ôrtl÷…ÈÔVÊÊÊ11±“&M:°ÿÀ²åËêêêÖ®]ëçç7iÒDAÚýyhóÓÒØØ´cÇŽuëÖYYYѨTARÓR—¯X>|Ĉ7}ûö-î82™ìèà(·,T___##£Ë—/EEE¡í¥¥¥/^¼ˆÚ¼YÑl%Šç$ e S´Mò­d&“ úóìŸ÷îÞ > ½ãÔV×455577›™ «%£§§æÌ"‘èãÞ‡}êÀ=zôØ¿ú8ÌÙÙiĈ'NœˆŒŒEy:¶%:ÚÖÎv5)éþýûÆ ÓÒÒRÓPÇb±&¦¦¢È‘ k(A3||||||ÐnnîncÇŽýçãG[[´±µµ5""ÂÃÃàìââííõàÁÃ;·o¡O^Z[Z7nÚTYYill سg¾¾þñÇÑĵ~ýúM ;røÐþý${ERÂÁÁaù²å1±1ýô?uú‡ÛºåûЇvÚü´Ðé´M7¹¹»±&L°··‰A×UXXXAp8\è„Ðã'ޝ^½ZMM på¯+jjjA#G³UgÕ©‚ÿ 2¥¨èsQÑgá}¤ˆ 5n\¿akgkÙ£‚ NNN¦ff7nÜø1 á¯7óß#¾KF^FÍÊÊ $\§¨TjNNΰaÃØÃôõõ=<=_½zÅžûA‡6š6¶¶ì“&ff_¿~åÔ\°=„!üWÆ·Y1 …°•ði4ZBBÂÿÙ»óx(ó?àß1¹¹YW JÎô[…¶EÇʱ›¤¥{K§Ê•P©°µ+´i+Ñ!*¶Ý­äH×b‘m»TìRŒD†™ùýñ´³ãcæ™áó~yõšç;ßyžÏ|zf||Ÿï|gÞ¼y'N455[°À !ôìÙ3öc%%%---±MiEEÅÿýo’¨¨Ö¢£«‹ÂöF§ÓïÝ»ïà0SLLŒ½}aQq¿Ÿïôt^y-òúÜÆfåÊU· víÚ-'/ÏÀù€çl‘˜`:½Ã?–••͘1ƒØØ±c?ÓÖîz™›oºÍ•ë×¶¶¶K—/±X,:~þÂgg ‰á“+,!pù B7Q½¬¬ì¯¿þZºlicc#Ö2ÕÞ>%%åùóçÚÚÚˆcs§È«ß¼A)*)JKKKKKW½~ÕÓS£Ñ Æ(EÅ®h4“ÉTPÉyר‘ å嬧.¡®Iû÷: ±ÄÄÅ9ï!‘Œvö½¨ËŸ~è~¬Îí(θ122òêÕ«ß~ûí„ ¦RR’‹-úØú‘ψ#8K&“GHü×B"‰ „íí,‹F£µµµ=zìØ±dŽ0˜ C@^k=éž‹‹s~^ÞØ±Tkë‰Ø]ý>pœ-,l¤‡Ý¡±±‘ÉdŽ5Šó!ŠJ£ëÄ v #GŽœ6mZÚ™³înî¿þök}ý;w77‹5¬rõìY/ºóU€¯„qEõÌÌL„Ðáþñ0g‡ÌÌÌ5kÖ`]QwµÂÍ›7ÅÅÅ X,ÖĉoݺÕÐÐ ''×õX22Ò¢¢¢uµµ]Ÿ»ŒŒŒˆˆÈ»wo9ïzûÖ"JéthzëG–Œ4ëßQÔ1¥ìiMŸ6º”>ýÐíXÝT0Ã=QÁõˆ—/_öññY¸p!v¶¸‹3žî¦¬qVlèßTKKK“Éd¯…^ó¿œßc0‰3¼ºº·Q»÷VTT¤œHYèµ à|èõléšaiii‘÷ïßs6¾ÿ^²cuKÎÜÝÝ¿ùæ›%%gÓÎN˜0Aß@ŸÅb Ÿ\™›™¥¤¦òú(=ˀߪ߼¢/þkkkûùçŸMÆ?òÓOœ?cÇR/_¾Ìå âLÚ™»wïº{¸KHH „–,YÒÖÖ¶#|G{{;g·?++=z$&&nanžõóÏ?~ì´qqññãÆýöÛoì!ëÚººû÷î[YZa›JÊ£«kjØßQÿ¾©©÷êvâââmãá?HTWL&³½½]JZŠÝríÚµ~ïM\\ÜÒÒòÞý{ššš: F°üÀb±‚ƒƒÄÅÅ>ìååûÝwVV"„ú}>ôz¶tE¡PŒŒîܽÃn©­«ûë¯'ƒûL…¥¥¥¾¾Þw±±¿ÿþ»›»;Ö¹â©GþìCÿ†‘“›ÛÐаqãFK Î{ÝÜ\##wÞ¿ßÊÊ ‹÷þý{/_¾ ÓÛjjjrrrîÝ»÷ùçÿó_ëíÇÄÄ$ `Ïž=îîÎ..êjjMïïݽ÷Ûo¿8°ŸÅb­ß°ÁÇÇg‰Ï"oo%%¥ª—U+oݺ!´rÕêU«V®Y³ö«¯<>¶|LLL_ì³ÛóŒéÓ$‰Oˆ÷^äýöíÛ={öˆŠ’ÙÃìÝ]·b±Ç«²¸R IDATtõô>~üxæÌ#ccŠ˜{tÿrÕ©­ëP?$Š3W\ŽH"‘¬­­3Ò3¦ÚOUSSûí·ßΦ¥!ޱ¨î/GrŽ¥}üÔ²iÓÆÅ‹}|ý|ÝÝÝGý¾±©´ô!‹ÉZë¿¶ßO„ØO'99ùÎ;‡“ËÈÈøûûß¿ÿ{À–€S§NS(”~ŸÜïí6Ã+V®øöÛ5'’“=<=Þ½« ëfD–bpsóؽ{—¬¬¬Ã̙컆I®ŠŠ‹C‚ƒ¹÷‰ˆŒäÑÑ¡¨|õôé3ÔÛŠê‚ð…þ ãRf¦””ÔŒ3:Eåèè´o_ôÅ‹™–––Ø¥–ÈÈ! …2jÔH*Õ0z_ô´éÓH$ûžžžFFF'RN$''Óh4iiiãqã¢cb&OžÂb±¨TêñãÇâã"##?~ü¨ªª:gÎ챓&YÇO±§®))©TêX—9óø •=¼r%ËËk!BèéÓgªªªºzúÕo^©©/º™2fÌôïÙUüǃõëÖÑh4¢Bmhh8wþQ½¨¸x¿?‰DâgHœX,Öþà „ŒTá‚ûDu‰DÔ_¡ªª*¢ `°UTÁ:U@àp__„§º§®®nÕÊ•úzÚZšîîn•••|Nˆ\átð`œ¹™)v;))IIqT§m-Mž——wàÀ~žbõ鼿'!ä ÃäŠwÇ…‘*ÀWÜ'ª  ¶¶¶®®uuµ;v„KIIÅ~;wŽKn^¾’’Ñ¡ ÈÕ@ìÚµ[EE…½Iåí›s^^$ÿu<=Ê èÓy5ÌOBÈ\þ׉ê¢^ ÝÊÈÈ(++=!sòäÉ!«‰­,-â~øaGx8Ñ¡ ÈÕ@ØOj``@t‚¨OçÕ0? !Wlx~ðâw \þ|¥««£«§Ï½@]þûåêϪªªØ›BHMMmŠ­mVVÖà§FøA®ÝÅÌL%ÅQÊÊ8===ììl±ÛÊʼ.ÔÓÕÑPW›5ËéÎÛìnÛCC ©wîÜvt˜©®¦fld¸ÿ§ë}AAßÅÆ666b—MÆãÛ3ê‡>WÃü$„\±uùŠ*ÀoÕo^qŸ«.PEUEE•Jål1¤¾xñ¼µµuó"ü Wø988âlijj¢qÀÖrtpt”••={ö,»[mmíÍìl„PYY©““#F‹;òÔi555×/¿|ðà»3Ö¹sWiYÙæ€€‘—.^DmÚ´ÙÏÏOZZº¨¨¸¨¨øê/¿"„ª^¾TR!x£}:¯†ùI¹bëë[ý`¢ #ÿüóOdd¤§§‡‰‰ •J}ýúu¯©¯o“—çl‘“—g±X < SXA®ðÓ××wppäl™9cº¾ž.ûÇÏ×!D¡PæÌ›‘‘ÎþÛú\FBÈÕuBhGX˜²²rzF†“““­­íáÃIFFF±1Ñì}Òé­û÷°´´TPPðñYbjjšy1!¤   #++""¢©¥¥©¥¥¦¦†B$™L! Ü/…>WÃü$„\±UTÁœ*ÀWOqLTçÝ_õòeVVÖøñ&ff¦wïÞC<~u€_ÜÁx ÿ–p© €ÝðððH9q"//ÏÎÎ!”–vÆÖÖVYY™N§ççç¯^½šB¡`=I$’ƒƒãá¤ÃìÈÉɳ7µ´µ«««{ @SS³æï÷I@Xü NTŸ`jšŸŸJNN¾{÷ž?Yäååh4g ­¡D"ÉÉÉñ(Há¹ssón'ª[[OÒÔÒ:›vÆÎή²²²¤¤$!1!D£ÑÚÚÚââââãØ™LƒÁ`oŠ‹‹sîJDD„ÑÞγgÀ+}:¯†ùI¹b#êf(ª_q_Q×DDú|iƒJ¥s¶”W”kk&!!1xq +^ ‘Hnnn‡÷EÇœM;#%%5{¶3BHVV–L&/_¾b¡—Ñ1òVŸÎ«a~B®0Ïž=#êÐPT~ã>Kñç/ Ö§õz,GG§+W®Üúüs„Л7oòrs}ýüx¡‚\ñˆ»»GlLÌåË—ÒÓÓGŒ¢P(666ùùyÁ!!¢}_ÔŠ"NikkãA°ƒ¯OçÕ0? !W!s33¿aŠ* pøPT± á+ª\,ˆOˆ_¾lY``¤¤dìw±22²«WËë…äj nfgW”—s¶88:ˆ‹SBzzzááÕÕÕîžìá³gÍš7oî’%KTUUi´Æ¢¢B“ÒëáÆR©---GþdjjF726®ªª²²´X³vmPPðŸË“'O&Û|¾~Æ-[¶b-övv¡›99Øfô¾}ÑÑûòoèë÷²À êí¼ÊÎÎþÊÓã`|<6y˜Ÿ„+„ªšz¨Ëœê7¯°y&Ýþ‹}é2/@Q_FªðUbbbçB‚ƒCB‚étú¤I“~üñ°²²2Ï#B« ÜÖ©¥ìQùèÑ£±Ûnî[·p®*„26÷Ûµë{÷î jh¨——W033óÃ7Òàäääí½8j÷îúúz•’‡¥ˆÅb0LÆ Lïe±X F‡Ù] F‡¹\L“Á`à|¥s?¯XL&ƒÁ`ÏJæ'!ä !daaÁ½¢êõjÉ@@Q@/”””": á¹ê??¿^+!_____ß®í‡'uûáá–ÆæìI&“cbccbcÙ-šZZµuoûwÏ :í*/ÿçf@À–€€-øwÈå¼úbÚ´NÇæ'!䪰°°×º ëÉ‹?ࡨ‡—ÿpÏ© D`¤ €ø8§J°¾gÀá©â(ªÀ0Âb±®_»†z\ù!”+¤‚‚¢¢¢©™Ñ¡0R@¼=joo߸i{sçΡ)S¦üðÃ<:"~‚‘*:à]QE&“;-vÇë#à'© (qô±#U÷…äý#Ut#UúæTÐUúFªèŠ*ýsªŒTÐŒTèøî?þS[[ÛÜÜLÔÑ%%%‰:4€ƒ‘*>173KIM%<†¼Übc"+ü WøA®ðƒ\u")% Ÿþ@€L±µ#:á—›¹Â r…ä ?ÈU·`¤ „**.^çïO"‘ˆ €Åbí?pÀÙeQX§ €ÿH$&“IÔÑ«ªªˆ:4€#vN,©‹8]ƒÉÉÉY±|¹éUe#Cêš5ßÖÔÔð?'B¡¦¦fÛ¶­NŽêjJŠ£ª^¾$:"Áuð`œ¹™)v;))IIqT§m-Mž——wàÀ~žb°ôõ¼ª««[µr¥¾ž¶–¦»»[ee%â+„Paa!÷Š Fª LlLLccã¢EÞšZZýõäPbbAAANN®´´4Ñ¡ œÏŸ_8ÞÌÌÜÊÊ*??Ÿèp„Ì®]»UTTØ›dQÞ¾9çååIJò÷_ÇÓ£ Š>Wmmm \]ëêjwì—’’Šý.vî—ܼ|%%%þDK,È"z¤ Š* pjª˜ØX}}}öæøqã—,ñ¹˜™ùõÂ…F%˜¬&N,¯xŒ:”˜EU_ÙOj``@t‚¨OçUFFFYYéù ™“'OÆkei÷Ã;ÂÃù+Ñ Wˆè9UpùºüÇYQ!„¬'MBUWWó)BEDÞOÙÅÌL%ÅQÊÊ8===ììl±ÛÊʼ.ÔÓÕÑPW›5ËéÎÛìnÛCC ©wîÜvt˜©®¦fld¸ÿ§ë}AAßÅÆ666b—MÆãÛ3ê‡>W¿\ýYUU«BjjjSlm³²²xšÀ\!¢GªàM*ª:¹]P€242â}ÀPæàà¸;*г¥©©‰Æ[×ÁÑQVVöìÙ³ìnµµµ7³³=<<Bee¥NNŽ4-î`üÉS§ÕÔÔ\¿üòÁƒìÎ4ZchHHäÎ]¥ee›vFF\ºx!´iÓf???ii颢⢢⫿üŠªzùRIqT„RTTTP©TÎCªá‹Ï[[[‰ I` Õ\;§ Š*0ŒÜÞ´ióÔ©SÇgcc³mÛ¶þùÿÃëëëöoâààÀ» Áp ¯¯ïààÈÙ2sÆt}=]öŸ¯/BˆB¡Ì™;7##ý‘Øs!WסaaÊÊÊéNNN¶¶¶‡'ÅÆD³÷I§·îßÀÒÒRAAÁÇg‰©©iæÅL„‚‚‚Œ¬¬ˆˆˆ¦––¦––ššB‘Hd2Y„,Ü¿êëääå9[ääåY,VCCQ! ¬¡š+˜S@xFŒú'!!áýû÷n ÜÔ4ÔŸ?–|<ùÞýû™.HIIõúØ?ú,^ü¡¹9ãÜy2™Ì£Á°w0^Cã¿? G*(`7<<<!SGG‡GááÌÜܼۉêÖÖ“4µ´Î¦±³³«¬¬,))IHLDÑh´¶¶¶¸¸¸øøvg&“Á`0Ø›ââ✻a´·óìyy9ÆÙBkh ‘HrrrD…$°†j®`¤ >ÑÑùŒsÓÜÂ!ô÷?sN_â³äÞÝ»gÓ3LLLxÝ!‘Hnnn‡÷EÇœM;#%%5{¶3BHVV–L&/_¾b¡—Ñ1 *•Z\\ÌÙR^Q®­ý™„„Q! ¬¡š+øôðmZúýû÷Bc¸LTooo_êç›››sòÔ)+++¾'äîîñáÇ˗/¥§§;;;1!D¡Plllòóóttt :³OŠ8¥­­ÇÀÑÑéÍ›7·°Í7oÞäåæ:Ír"6*Á4TsŸþ þTT ÑÑцTC;;[.EÕ¦M³²²ÜÝ=ÞÖ½½tñ"öSZúÿi|,ëS~ÊJB×®_»tñâ½»w‰ŽK8ÜÌÎfŸ`ØþéCXzzzáá¯^½r÷ðd?$<"âÉ“'óæÍÍÈH/(¸õóÏ?ïÜçpc©Ô–––£G*..Æ–l¨ªªRQ½sgäÀŸË“'OT”GïÙóßÇííììíþûÞßè}ûT”G?yòÏÞ¸ŸWÙÙÙ*Ê£32Ò±M× Œ–/[vêäÉÌ ¾úÊSFFvõêoþ¤„ä ýé?¸üN·ÅÍàjmmõ÷_×ÜÒr8)IDD„Ë ÿ!”œ|<9ù8»Ñ××7jÏ^^)t Æ7ß,aolÞŒš1cæÉS§ˆ JhnëÔRö¨|ôèÑØm7w­[8WBûíÚõ½{÷54ÔËË+˜™™ùùùá9œ“““·÷â¨Ý»ëëëUTTJ–"‹Á`0ƒðÍ›,‹Áè0»‹Áè0—‹Éb2 œ¯tîç‹Éd0ìOGЉ‰edœ  ¦Óé“&MúñÇÃÊÊÊRBr…ˆ©‚¢ ;mmôµþþ—9ò“–f/ß°–—‹?Q ¢¢¢µuo‰ŽBøøùùõZ ùúúúúúvm7008|8©Û‡ìï´46gO2™ËnÑÔÒ¬ÿ>ƒN»êô: ذçÞ¸ŸW_L›Öé^%%¥ÄC‡p;¤@®Ñsª ¨‡§#UmmmÖoø£¸øÐ¡CT*•£bøFªè€w…N{{ûæÍ›îܽŸobb 10RŸDq#{낆wõ×~û kÔÔÒ;–Êý„ŒTÐïJ” „Ò3ÒÓÿýü BÈÓÃcë¶Îs„#© ÞUéééݶÃu@`¤ € ÄÐ?Ä~÷,þ €!FªèFªôÌ© (ªôŒTÐUú‡Ø‘*˜S€!FªèFªôÌ©à?µµµÍÍÍD]RR’¨C8©às3³”ÔTÂcÈËÍ!6!¹Âr…ä ?ÈU'’RÒ0R€™bkGtÂ!/7r…ä ?È~«nÁH!TT\¼ÎߟD"‹ÅÚà€³Ë¢0Ä®¨E,$‰Éduôªª*¢ `àˆSK*Ã"÷À–ø,VRµæÛÕüɃÐÉÉÉY±|¹éUe#Cêš5ßÖÔÔ”€:x0ÎÜÌ»””¤¤8ªÓ¶–&OÈËË;p`?OÁ x^ƒuuu«V®4Ð×ÓÖÒtww«¬¬ä[x„«©©Ù¶m«“£ƒ†ºš’⨪—/»ö¹ví7ggm-M-Mӧݺu«§½ i& ¹WT0§ â]½zõÖ­[âââD"¸bcb-òÖÔÒúë¯'‡ rrr¥¥¥‰MìÚµ[EE…½Iåí›s^^$ÿu<=ÊàÂólkk[àêZWW»cG¸””Tìw±sç¸äæå+))ñ-N½xþüÂùóffæVVVùùù];?~lÓÆNNN‘;wŠ’EKËJßÖÕu»+áÍ$|ú€pª>lݺ=((èXWLl¬¾¾>{sü¸ñK–ø\ÌÌüzáB£öS§…àÂùÌÈÈ(++=!sòäÉ!«‰­,-â~øaGx8¿"%’ÕĉåB‡»UU/_®ZµO6„7“°¢:àå¿]»vª©«/ôòâg„gE…²ž4 !T]]MP8CÁÅÌL%ÅQÊÊ8===ììl±ÛÊʼ.ÔÓÕÑPW›5ËéÎÛìnÛCC ©wîÜvt˜©®¦fld¸ÿ§ë}AAßÅÆ666b—MÆãÛ3ê7œ¯Á_®þ¬ªªŠÕ!55µ)¶¶YYY¼P ˆˆpûžz2•Åbmظ!ÔëÔUáÍ$Ì© A+ªþøãcGEï‹&ðc‰ÂèvABÈÐȈè@‘ƒƒãî¨(Ζ¦¦&l \GGYYÙ³gϲ»ÕÖÖÞÌÎöðð@•••:99Òh´¸ƒñ'OVSSsýò˰;Óh¡!!‘;w•–•mØqéâE„ЦM›ýüü¤¥¥‹ŠŠ‹ŠŠ¯þò+B¨êåK%ÅQ9ÿ5XQQA¥R9[ ©†/^>‹OŸ>mhØãhJbbB]]í–-[yÒôñãGŸÅ‹?47gœ;O&“‰G8ÄŒ×Ðøïè‘ Ø ”'òòòìììBiiglmm•••étz~~þêÕ«) Ö“D"988N:ÌÞ‰œœg!¢¥­Íåj¬¦¦fÍßÿ î“ð,L&³©©))éÈ´éÓB666/«^Æýðýš5k†Òë¾û€xWT9;;;;;³7f9}ñÅ™™©TÃnûÿý÷ß{¢¢vïŽb04 k¤ÓÛh4š”””(?Ÿ%¤èôVoïE¥¥Ï_ÈÔÑÑ!:¡annÞíDukëIšZZgÓÎØÙÙUVV–””$$&"„h4Z[[[\\\||»3“É`0ìÍN”a´·óìðD__ƒòòrìnZC‰D’““ãSÄläÈ‘OŸ>bkËn±·³¿qýú«WUÚÚŸuê,¼™„9UCAALå2a³ªªª¥¥eÝ:öE™æææsç2ôõtssàû¶ºA§Ó—ø,¹w÷îé3i&&&D‡3H$77·Ë—/·´´œM;#%%5{¶3BHVV–L&/_¾âfNû'7/ÿVÁí^÷)Dúú¤R©+*8[Ê+ʵµ?“àWÈ‚kì§9RÿýÕÊd1B"¤n*áÍ$¬S@¼©Â´¶¶2Œ·oë'¡PÄ]]]{:âØ±c/d^älñpwûÜÆÆß̿½}©ŸonnΙ´4+++¢Ã:ÜÝ=bcb._¾”žžîììe†ÒÑÕ7n<± …›ÙÙååœ-Žâ℞žž……EDxxuuµ»‡'»CxDÄìY³æÍ›»dÉUUU­±¨¨Åd‡„ôz¸±TjKKËÑ£?™ššQÄÅŒ«ªª¬,-Ö¬]<ÀçòäÉ“É6Ÿ¯ß°=ÊÞÎ!tóß±¥è}û¢£÷åß*è´ GW½¾³³³¿òô8MÞw]° >!~ù²eA’’’±ßÅÊÈÈ®^ýퟑ°`±X—/]B•–•"„®]¿¦8JQYYy¢µ5Bhúô“'O^ç¿¶ºz«¦¦æ…óçóòòöEGc 1 ™LÂwÿЯ‹ªà÷MÿüS{úô™+V$:d:aO8Lþþ;B(9ùxròqv£¯¯oÔž½Ä%4·uj){T>zôhì¶›»ÇÖ-œë!„ŒÇývíúÞ½{‚ƒêååÌÌÌüð%899y{/ŽÚ½»¾¾^EE¥äa)b± “1ß¼Éb±Œ³»Œs¹˜,&ƒÁ”W:‹Éd0ì‹øbbbçB‚ƒCB‚étú¤I“~üñ°²²òÀ$ Æ7ß,aolÞŒš1cæÉS§B$)ùDÊÎȈè}ûh4šžž^ÜÁxly4„2 #Uð•Á˜OÓ§M›î2Çeÿwû;Šó±/^Â7.÷(/¿Ç/\øùùõZ ùúúúúúvm7008|8©Û‡ìï´ø5gO2™ËnÑÔÒª­{Û‡¸{f``ÐiW΀€-[ú·óN¯Á/¦Mët,%%¥ÄC‡ú·sa'**Êý?QFF&jÏÞnÿÎ2™„OÿЯGªØDEÉúzúWðíˆx Fªè€w%ƒÁà\Ž¥±±±¤¤dÌ(ª`h€‘*ødíZUUUªáØ#FT¿®>wá\SÓûeË—€Á#Ut鯮u IDATÀ»q#›É6?geee]ùøñ£¼œœ©™ÙÞ={a¤ †© Þ•8žžÿ~Ô…‡Àg0R@PåèbGªàkj0DÀHÀH€þ9UtE€þ‘*:€¢ @ÿûÝ0§ CŒTÐŒTè˜SÀjkk›››‰:º¤¤$Q‡0p0RÀ'æff)©©„Ç—›Cl Br…ä ?È~«N$¥¤a¤ 2ÅÖŽè„C^nä 'È~+ü WÝ‚‘*B¨¨¸x¿?‰D"*‹µÿÀg—9D` `Nÿ!‘HL&“¨£WUUuhGìœ*XRqz éÚµß\œµµ4µ45fLŸvëÖ-~&DXÜ»{×Ým±‘¡Šòh=]w·÷îÞ%:(uð`œ¹™)v;))IIqT§m-Mž——wàÀ~žbݹs{Ž‹‹¦†º¾žîÒ¥~ÕÕÕ\:×ÕÕ­Z¹Ò@_O[KÓÝÝ­²²’oq ‚ì7œgkkiª«©ÚÛÙ¥¦¤°ïª©©Ù¶m«“£ƒ†ºš’⨪—/¹ïJH3YXXȽ¢‚‘*ˆtüø±M7:99EîÜ)J--+}[WGtP‚èeÕK…‘kÖ¬UTR¬ý§öر£óçÏ»úË/ãÇ›šصk·ŠŠ {“,ÊÛ7ç¼¼Ü#IIþþëxz”AQXXøåüù– ‰Í--{÷ì™;Ç%ûfŽ””T×Îmmm \]ëêjwì—’’Šý.vî—ܼ|%%%þGÎwïÜùê+Ossóï¿ÿ"!qæô©uëüY,–×¢E¡ÏŸ_8ÞÌÌÜÊÊ*??Ÿû®„7“ðé?:¨uªª^¾ \µjõŽðp¢ct ¸-XàÆÞœÿå—LƧ¥¥AQ…‡ýÔ©DG!ˆöîÝ#//&- [ñÄÔÔt²ÍçGŽ$­]ëßµsFFFYYéù ™“'OFYMœhei÷ÃÃäõ{îÜ9„PêÉS ¡™3gZYY¦¥¥aE•ÕĉåB‡{-ª„7“ÄΩ‚Ë@àÔå¿Ô“©,kÃÆ!'{ £Q£F‰ŠŠAÒâbf¦’â¨Geeœžžvv¶ØíGee^ êéêh¨«ÍšåtçÎmv·í¡¡F†Ô;wn;:ÌTWS362Ü¿ÿÓõ¾  Àïbc±K&ãÇñíõCáï¿O™bË^Cn̘1:::—/]ê¶ó/WVUUÅꄚšÚ[Û¬¬,>ÅJ4²™L–––þ´)""+#K&‹°7ñïJx3‰¤ª¿¸ƒ¢ N}{ýܽsÇÈÈ(==}‚ÉxåÑJæf¦ ññýx¥ ?~üðáÃóçÏ6S(â^^‹ˆŽH988âlijj¢qÀ–Àupt”••={ö,»[mmíÍìl„PYY©““#F‹;òÔi555×/¿|ðà»3Ö¹sWiYÙæ€€‘—.^DmÚ´ÙÏÏOZZº¨¨¸¨¨øê/¿"„ª^¾TR!xãt:]œ"ÎÙB¡PÊËË»í\QQA¥R9[ ©†/^~üøñãÇ®ëëääå9[ääåY,VCC?b%š¡‘QÚÙô+W.›N01¤ŽÚ½;>!ÁÑѱ÷Gv!¼™„9UtÀ‡q 7²ïÝ»'..†X½ŽÉd655%%™6}:BÈÆÆæeÕ˸¾_³f ™LæuœÂ(z_4­‘VS]óÓO?yx¸§M·²²":(!w0^Cã¿? G*(`7<<>Ý™Éd`F\¼ÃU3F{;Ïž¯|½pauuõþýßÅÇDMŸ>ÃeΜ›ÙÙââ”®åååh4g ­¡D"ÉÉÉñ)\B…ï““—?‘’"**вµµ­®®Þ¶m뜹sû4¡ s&a*øêÀʪª®®®x:ý4«à¿Â‹Éb"„DHðÚé…˜˜˜!ÕðåËD"ÜH$’››ÛåË—[ZZΦ‘’’š=Û!$++K&“—/_q3'‡ý“›—«àv¯û:7múóÉ_¹¹y%KO>ý¸¢ÂÚzR·=©Tê㊠ΖòŠrmíÏ$$$ø)Áž-ÏûæÍ›¼Ü\§YN|Œ”Hêêꥥ¥œÿ³………ÒÒÒ²²²}Ý•ðfæTÐï.ÿ1Œ°Ðí_}åi0f vVo‡›>}ÆäÉ“×ù¯­®Þª©©yáüù¼¼¼}ÑÑ}K¼.ÔÐÐo2^RR²êeUJjÊû÷7m":.áp3;»¢ã'Ú°+\zzzááÕÕÕîžìá³gÍš7oî’%KTUUi´Æ¢¢B“Ìþ›¡gc©Ô–––£G255£ˆ‹WUUYYZ¬Y»6((x€ÏåÉ“'“m>_¿aÖ-Ÿ>bog‡º™“ƒmFïÛ½/ÿV¾¾~¯{+-}xìè1SSSQ1±û÷î:uÒÛ{16Ç!”ý•§ÇÁøxlò¾ë‚ñ ñË—- ’””Œý.VFFvõêoøŒ„ÅÒeË–-]êéáîã³DL\üÒÅÌì7ÖoØ€Me±XØR¥e¥¡kׯ)ŽRTVVžhm†P&aNð®¨JNN~ûîíªU«8ÑËœ*‰”|"egdDô¾}4MOO/î`<övÐÉ´éÓ3ÒϦ§Ÿmii9r¤µµuRÒ333¢ãÛ:µ”=*=z4vÛÍÝcë–ÎuƒBÆÆã~»v}ïÞ=AA õòò fff~~~xçäääí½8j÷îúúz•’‡¥ˆÅb0LÆ ¬+Æb±Œ³»Œs¹˜,&ƒÁÀùJ—‘‘©¬¬ÌÈH§ÓÛô ôwîÚåãóß0‹Éd0ìOGЉ‰edœ  ¦Óé“&MúñÇÃÊÊÊRBaþü/Iˆ”¿jÕJ&“©££¹lùrì^ƒñÍ7ÿ¥.`óf„ÐŒ3Ož:…†P&a¤ ~¨­­;xðà¶mÛ˜Læû÷ï±Æ6zûû÷ïGŒ!Úó·‚ÈÈÈDíÙµg/¿"V~~~8£NxòæëëëëëÛµÝÀÀàðá¤n²#<¼Óâל=ÉdrLllLl,»ESK«¶îmâî™A§]ååwøºÌ€€-[pîM[û3.ŸuøbÚ´NÇRRRJýŽ }ú/ûÆ gçÙÚZšêjªövv©))|Nˆ0Zâ³XIqÔšoWˆ€:x0ÎÜÌ»””¤¤8ªÓ¶–&OÈËË;p`?O1Xjjj¶mÛêäè ¡®¦¤8ªêåË®}®]ûÍÅÙY[KSKScÆôi·nÝêiouuu«V®4Ð×ÓÖÒtww«¬¬äeìü–““³bùrÓ &ª*ÊF†Ô5k¾­©©á¼·Ói6vŒA§= ™L2¸ÂÓ‡³'~0R7wïÜùê+Ossóï¿ÿ"!qæô©uëüY,–×¢ED‡&¸®^½zëÖ-qqq¢&»víVQQao’Eyû朗—{$)ÉßO2(^<~áüy33s++«üüü®Ž?¶iãF''§È;EÉ¢¥e¥oëêºÝU[[ÛW׺ºÚ;Â¥¤¤b¿‹;Ç%7/_II‰ÇO‚Obcb-òÖÔÒúë¯'‡ rrr¥¥¥Ù}"""ÕÕÕ±Ûâ”/Ò¡”É~ŒTikk¿xñb€Ç…¢ Z/êܹs¡Ô“§B3gδ²²LKKƒ¢ª'>|غ% 4t{PP ѱû©S9?û ج&N,¯xŒ:”˜Øµ¨ªzù2(0pÕªÕ;ÂÃ{ÝUFFFYYéù ™“'OÆölei÷Ãx+bbcõõõÙ›ãÇ_²Äçbfæ× ²íìí »>v(eòÙ³gxºqUººº!mmí§OŸäÐPT#PE•Y„L&³ÿΑ•‘%“áºyvíÚ©¦®¾ÐË Šª»˜™éëûMNN®‘±1»ÑÓÓ£ºº:''!ô¨¬l×®]·o´¶¶šL˜:iÒÿ°nÛCCÏžMûéèѰíÛ>,•——[ºlùºuëBAA?:„RR…RUU-yXJÀÓÃGD„ÛË-õd*‹ÅÚ°q#BˆÉdrïüËÕŸUUU±:!¤¦¦6ÅÖ6++K@J㬨BÖ“&!„ª««;uknn1bD§ ™Lâ_š‡ûZVý¿€À¨9UÞ‹¼)Jà¶m¯_¿®««;x0®¢¢|ùŠ•üO‹Pøã?Ž=½/šÀp ÇÝQQœ-MMM4Ø¸ŽŽ²²²gÏžew«­­½™íáá*++urr¤ÑhqãOž:­¦¦æúå—<`w¦ÑCCB"wî*-+Û°32âÒÅ‹¡M›6ûùùIKK_ýåW„PÕË—JŠ£"à—bŸÜ½sÇÈÈ(==}‚ÉxåÑJæf¦ ññ=ýaVQQÑiá"Cªá‹Ï[[[ù,¿Ý.(@q6ÎrrÔÖÒÔÖÒôYìýüù#:C&“EÅÅ8{b³¦ºŽÀœ*zWPpû›o–p¶((Èß¾}‡ËC ŒÒΦ{/ò:vì(BHBB">!ÁÑÑ‘· 'ƒ±aýúo|¿áVÝÒ××ï4¨0sÆtÎÍ3fž>KRSR2/fºÌ™£   #++""¢©¥õß!I$2™,"l£°555555±1Ñ¡ÛÃ444.œ?ÒJ§ccrÔ×7Œ?ž³EN^žÅb544(+ã]2[XÔ×ׇ…m?ÞÄÁÁk‘••]µjõDkk))©’’?|ÿ½“£ãÍœ\ì¹¥Löº\¶ú÷µ¬úwh(ª€ÀáÝå?b!„¶nÙªªúiF°˜¸8÷Ã=xðÀÓÃÝÊj¢¯Ÿ¯¸8%óÂ…Õ«V‘iÞüù< Rx%&&ÔÕÕnÙ²•è@„RÜÁx öæH솇‡Gʉyyyvvv¡´´3¶¶¶ÊÊÊt:=??õêÕXE…"‘Hއ“³w"''ÇYàjikw½Ħ©©Yó÷?ƒû¤ø€Éd655%%™6}:BÈÆÆæeÕ˸¾_³f ™L&::Â|üøÑgñâÍÍçγó`fffff†Ý¶··Ÿ}útŠ­-»ÅÞÎþÆõë¯^UikÖ©³¼¼ûäÄÐH$’œœBå:½ÕÛ{QiéÃó2uttzêfnn®««[\\„m±LÂ:UðOKK ÎÒíÉ“'ÆFFœ5ÁÓ uuuÞP@UUUKK˺uþúzºØOssó¹súzº¹9ðÝdýG"‘ÜÜÜ._¾ÜÒÒr6팔”ÔìÙÎ!YYY2™¼|ùŠ›99ìŸÜ¼ü[·‰™¯Æ~šÙóßË™Éb"„DHÝüv£R©+*8[Ê+ʵµ?“àiüD§Ó—ø,¹w÷îé3i&&&Ü;··3ØÓ‡X&‰Z§ Š* px=ý믿¶°°°°°X»vÍË—/¸OTWWW/--mkkc·JKKËÊÊò/#Â`ìØ±2/rþP(”©_|q!ó¢™y¾ŽtåîîñáÇ˗/¥§§;;;1!D¡Plllòóóttt :³OŠ8…ó¬^³gÏF]¿~ƒÝrýÚ5EEEuŽk©lŽŽNoÞ¼)(ø´ å›7oòrsf9ñ'T>hoo_êç›››sòÔ)++«®÷rnæææ¾xñÜÂÒÛb™ÄJ"îÀâŸ`xáÝå?iiiŸÅ>¦æf’#$ËË9räë¯fddpY°né²eË–.õôp÷ñY"&.~ébföë7lª“ úMFFÆÆÆ†³…L&+Ý©ôäfvvEy9g‹ƒ£ƒ¸8!¤§§gaa^]]íîáÉî1{Ö¬yóæ.Y²DUU•Fk,**d1YÁ!!½n,•ÚÒÒrôèO¦¦fqq#c㪪*+K‹5k×áúN4.žé‰ÒÑÕ7nÅî¹–U_AQïŠ*cccãçíþ¬&.\øuʉ”uë{\Wzþü/Iˆ”¿jÕJ&“©££¹ìß·!K`à¶N-eÊGÝvs÷غ%€se „±ñ¸ß®]ß»wOP`PCC½¼¼‚™™™ŸŸžÃ999y{/ŽÚ½»¾¾^EE¥äa)b± “1ß¼Éb±:ý•Ï`t#a²˜ ç+Á`p~h7`ófôï§#B$)ùDÊÎȈè}ûh4šžž^ÜÁxlÉ „‹Éd0ì¯ËÈ8L§Ó'Mšôã‡áÓjƒ¥ð÷ßBÉÉÇ““³}}}£öìEýïóÿË8—‘‘ÞÚÚª¬¬ìîî±eëÖQ£±nC,“ìÓïÏ?ÿdßþùçŸX;þµ¬°ÎøK S±òyü¦±™Î@mŸ§ØS×””T*u¬Ëœyø÷BèQÙÃ+W²¼¼vjWS_t3;d̘1èß³«øë×­ûðáßbsvvVQQNJ:‚mÖÕÕ;>4t;ßjy¹9Sl툎B8@®ðƒ\á¹ê*<|GHpð»wï8©TjÇ$°ƒññ}ÚçR?ßžîÚq¡!$I«*SsÿŒTÃÃOÿuÁ`0"ñóˆx­Ót¨²²²N-½®eņ-j…U@àð®ÄiooçüßÝ»w_½zåèäE %ƒµ\B_§^AQ†‘uëÖ+mhlH¡HTTTœMKSQQñZØùº$¡FÔ:UPTûq#s ³«?ÿ’õsNWTTtvv^µj•¼¼ŒTÀPEŸð®ÄñYìã³Ø‡o‡@üE•¶¶v×»^¼xÑ¿ãÂâŸ@àðtñOîˆ~êÄÜÌ çÔrlá§OŸvjÇÖ²‚Å?0¬YX˜#„ð,CÅ}-«þ¢ ¥~¾999EÅÅx:c ²áìÜ'áá;}ŸCUöÍ›D‡ 4 Wø c®ÌÍÌTÕÔ-,, ùö¯¤”4BˆŸGø¿Õo^ñâ}ÞÜÌ «¨,,̱=9œtu¼DXQQ­e5À‰VPT‚½Òp.‚ ðâ_kx'"22ÔeNõ›WXÝÿöô/ïÞç±ÎÜË)N½®eÕPT‚ÿ•ƹvÈÐø^X€ðzõêB¨úÍ+U5 ø—û¿EIGúñ>‡×Â…)©©ý.ªU@àôõDg\\»v !4}úô~ÜÂë¡øËU_ßçyQAQ†…®'º††ö‡`· ¨8Íž=»¥¥¥·èáõŠPü‹åª¯ïóìþÜ»õ U`Xèt¢c‹ˆp9û¡¨xØÛÛ³¿«»¯·ÀƒðzE(þÅrÕ×÷yλ444ú½ŽTOû,PTÃy¢ëêêvmäÒˆBx½"ÿb¹àû¼¶¶v×õ¥ðÃÖ²Âóe)©©Ø'q‚¢ ö‹‡½p‚‘*0`·nÝBÙØØôã6x^¯Å¿X®úú>ßõ.]]Ý?ÿü³ÿSø×²b/Ó€),,LÅÊçñ›Æf:!´}žbO]SRR©Ô±.sæáß;¡Ge¯\Éòòêæo‚>­Vò¦ºº¦¦f°£À+***–}]ÎJMU™×kY –êB’ѱª25÷ÁH ¬JC[Ddd?–³º|颀¬eÕWPTÂÀªT0„õ{ù®þývÀ£¯kYõU€`°*>’Oœ@y/ZÔÛ©A™Ìއ |h Š*@0X• +KýüØk_õõ6Âhà“ÙÙø¿–U_AQ«Ráã믾b¯}Õ×Û©Á©"d-«¾‚¢  V¥€!lPFªø¿–Uÿ@Q«Ráãlz:BÈmÁ‚~Ü@H |¤Šµ¬úŠ*@0öË ¢¢‚J¥vjì Š* Ô–úù½{÷®·F©ÂùÛ¡ë]|¾¸E ç_VVfllŒ ¨CÔüyóêêêúw!5(sªðüvètWYYÿ§‹@QÖé¤/))111¢ ††Áúô_¯¿8ï*))!d.U€`]Ïûââb˜¨†¤ËW® „œgÏîÇm„Ô ®S…ó·÷n<E ,þ †•¥~~¯_¿îßm„Ñ ®SÅ üÉ E U`øptp¨ªªêßm„¬¨Þ=]=ÝŠŠÇzzy ’®\ÉÒÕÓíÚnnféµp!þ]õõkž«#U¼ûí’šjnfÖß§Ò»>UŸÿï¡+W²x štõt±“§ì+-SRSñìÄÜÌìMu5ÎÎŠŠŠªæ´%Å IDATšFaa¡……þ-B}ùíЧμû6e„),,LÅÊçñ›Æf:!´}ž"ï0dì¸P‡’¤ˆŽU•©¹L„èx†(ªèÝÓ§Ïž>}›°9 7øAQ.ªªªìϤÀ&l«MN°¤½ÓÕÕáü›°9|6øÁDu€þè4Q½#Uœ»¼yóFKK+888,,lýúõ ÕÕÕ Ø½‘‘‘QQQMMMnw²iÓ¦¸¸8ƒ~üøQGG‡³ÿ•+W\\\¼¼¼|}}ß¾}R__onn~ùòeìá‡ÒÒÒZµj•AFFÆ?þxöìÙ ¼{÷nûöíÇ/))A‰ŠŠjhhŒ7NLL,,,LEEåï¿ÿ¾víÚöíÛG…'ªØØØ˜››oܸQZZúǼpáBAAöÁÏžöÌýQ]¤ªªª¢¢²lÙ²½{÷bÁüý÷ßêêê{öìÙ¸q#÷½eee9;;»»»¯X±¢¶¶600ðÇì\SýæUaaQQqq¯=ûºC?/ÿ¥¤¤0 OOO„§§çþýûÏœ9³bÅŠ¾î§µµ511ÑÆÆ¦Û{ÃÃí­­“““±ÍqãÆQ©TÎMMMßÿý´iÓB3gÎÌÉÉÁŠª‘#GÊÉɉˆˆ|öÙgXÏæææ²²²£GÎ;k™3gþ¨TUUýõW „Ð_|1qâÄÈÈÈóçÏsÙ3—Gõt 77·“'OFEE‰ˆˆ „N:…úúë¯{ÝÛŽ;LMMO:E"‘Bf¼\ß RXEÜkψÈHô慠xôs¢úñãÇMLL°ÇÚÚZGGçøñãýؤ¤äçŸÞí]---¿ÿþû¼yóØ-cÆŒ122âì#--ýÅ_°7Ç×Ó—dIJJíØ±#..îáÇ, Tt:=;;ÛÍÍ «fB$ÉÅÅ%??ŸËž¹?ª§§ïííýúõë7n`›'Nœ˜6mšªª*÷½a¹ruuÅ**„©©é˜1c¸ûsù”Ú¹sçÎ;9Û“““###û±Ãna×ïêêê8ß¾}+--Ý¿jkk;v !T^^~äÈ‘­[·ª©©-Z´O$d2yݺu¾¾¾ø÷ìææÆýQÝ"‘H^^^û÷ïOHH8qâ„´´ôüùó{ËFãl¤ÑhýÎ0´ñâ ˜û;`ê4(¥¡¡AÀHÕ•+WÞ¾}cooÏÙ¾|ùò•+WÞ¼ysêÔ©}ÝgOBCC]\\/^ÌþôŸ²²26ƒ»WÆÆÆÍÍÍ ––– ENNî›o¾qss£R©íííééé---Ó§OÇILLÌäÉ“§NºråJuuõ†††{÷î1™ÌÝ»w¿xñ¢§=sy—c3ÆÚÚzëÖ­¯_¿Æ®ýõBhûöíÎÎÎß}÷ݪU«jkk}||( û±¿þúë¬Y³’““±9ïÀ0ÇY3éêê"„´µµŸ>}:}öy¤êøñã222nnnÚ¿úê«#FôoºzOfÏž}êÔ©»wïΘ1#(((,,LOOONNÏcçλlÙ²kkëY³fÉËËkhhÄÄÄÌœ9ÓÅÅ¥°°ðÌ™33fÌÀÉ„ îß¿¯®®¾nݺiÓ¦-]ºô?þ°µµEqÙ3—Gq·hѢׯ_«««sV¨Ü÷6kÖ¬Ó§O:tHVVÖÞÞ~Á‚“&Mb?–Éd2 &“‰óùC{8 «¨0ººº©êÃ⟄¯±ûîÝ;--­ˆˆˆõë× „TxøŽààwïÞ!„º“SQQÝ Ñh)©©KýzœÝÿÅ?ù¯©©)44tÚ´iŠŠŠ/^¼Ø³g””ç1€~À¢º«Ýï9ì]T‰ŠŠVVV¦¤¤¼}ûVNNÎÞÞþäÉ“]×@è^|úO ‹* ø– ºaWTðUƒ@ ÿjÏž=ãÅna¤ ȹ™YJj*þÎø÷ Enïß¿¯©©!‘Húúú>|':(ÃQÿ‹ª»wïÆÆÆæååÕÕÕÉÊÊZYYùùù¹ººbpl7nܸwïÞÖ­[Ù-‘‘‘QQQMMM¼>ЦM›RRRjjjëÙÙÙ111wîÜyÿþ½†††‹‹K``àèÑ£kÿBó¿¯kÎñÛ¶m[ttt{{»²²rMM¶¶öŠ+°oõæÑ@؇ãìiaa޳s?çTÅÅÅ}þùçeeeëÖ­KIIÙ½{·œœœ‡‡V@¸qãFTTgËèÑ£ŒŒøp ÁûÅ_ÔÕÕEFFž}-´«««··÷ĉ=<<ŠŠŠp~W4~Ì\°\]]] …òôéSÑÍù–Ø[>000)))55•Åb!„rrrX,Ö¼yó„æ/Qǵ´´Ü½{wïÞ½XâéÓ§“‰'Øæ$óÔÑÑÑÕÕEéëëãJ¥R…–B¾›| >°çT)**Ž?ÉårËÊÊ"""°O5„…Bqwwÿæ›oð‰‰‰555ø²ÄƒÌÌÌÈ7`ÀlF…100øý÷ßB­­­7oÞܲe þÖÈ‘#%½Ã]YYÙÙÙijjŠe.6(‰y´+™ùZ!´dÉ’M›6åçç/]º”Ëåæååùøø()) -B¢ŽSTT=zôÆ›››MMM)Š„q ©€ló”m7øÐ½»çTI|O•ªªªŠŠŠØ)‹Åâý,lhhèèèØºu«¸¸¸ºº:,APPPJJÊâÅ‹KJJnß¾]ZZŠÂ.?‘Áw?µœœv)´±±±»»[MM÷]¾—b)))ñÆB£Ñ°ÌÅÅKEE…Åb m·§OŸöôô`ë.ÄÈ”È×ò! Y³fegg#„ŠŠŠ^¼x±téRQEHÚq§NrttÜ´i“¹¹¹¶¶vRRROOØ@ˆÉ6O‰º ÀGͶ&9U²¶²"ÿ<$ÝJ•³³óÙ³gëëë‰o«â¥ªªJ¥RCCC-Z$4A~~~DDDHHöòÎ;RTLh¹rrrø=º:ü&÷^fN/ …âìì|îÜ9Áv+..F¹¸¸`/i4ߟ\JT"¯eË–999]»v-;;ÛÎÎŽäú™õôôòòòB÷ïßÏÍÍ]½zµ¶¶ö‚ ¢KTž$w—4Ÿ”[·*H>ª*.>!ônŸSÙÞÞÈwc|ee%ß·Ìp NNNeeeFFFÆÿ†êîîær¹***xú¢¢"ÞÝ †t7”1™L‡óÝwßá[>|x÷î]Qé%*ˆ8(AmmmÁÁÁ¼³'OžÄÇÇ›™™á·”éèè<}ú´»»{ÙØØˆO1%-çèè8f̘ÈÈÈK—.,Sõ&F“””&“ùË/¿GÁ‡ Íùò”ŽÔà£DþáŸ>óçWܾM>giVªìììÒÓÓCBB¬¬¬|}}õõõ_½zUZZZPP€-º•ššêàà0qâÄeË–éèè¼zõêúõëÝÝÝ rrr...»víòððÐ××/,,Ä®UáÆŒÓÒÒ’™™Éáp ïíSb­_¿ÞÝÝÝÏÏoÑ¢Euuu111¢ž%iAA &vppHLLŒŠŠzúôéÂ… ÕÔÔ*++ÓÓÓétúÑ£Gñ*yzz&$$ÄÆÆ®\¹òùóç!!!¼_x”¨D^Ë–- 8p ——qJò1ÖÔÔ,\¸pöìÙÆÆÆ………­­­Ø’q¼øÚ\UUUTžR#n´sçιººîß¿ßÛÛ[ìK2·2KJÊÕW¬Xacc“–––––öòåKUUÕ±cÇM›6MÔ.7n܈ Å.ÙØØcïîÝ»700ÐÜÜœN§ÛÛÛ:88àûzxxÆÄÄÔ××kkk×ÖÖ’¯ê´iÓ:´aÆÇnÚ´)==]UUUhbI "JPdd$›ÍNMM]µjU}}=BÈÒÒòܹsêêêx++«¼¼¼ØØØÄÄD##£˜˜Þ g’–ˆ›={vpp°ŸŸ~¿6I%0`èС©©©555T*ÕÔÔôÈ‘#“'O/¾6¿{÷®¨<ù°X,6›­¨¨ˆo±´´ÔÑÑ‘(„PwwwWW¾¨FüÀÇá]Lª(±±±š6þþhjáv!„6Ìy·–öPQo}@êëëuuuãââÂÂÂúº.(00077·¨¨Hð©˜2·oß>ÿû÷ïÃe/Ÿ²M›6ÆDG·¶¶ònÔÓÓÃ~¨ƒ×_ýu0?Éb‘·änüî%BH‘A¥ÅúëFÞ»z¤BÿÑÜܼ~ýúI“&©©©ÕÔÔ$%%)))ùúúöu½B(##£¦¦fþüù—.]úˆs™¸ÿþ£GbbbÜÝÝaF ¯TaO‘ÔÓÓ{üøqoòüø'U4íáǬ««SUUurrúöÛokÞ'h4ÚÙ³gßu)+V¬øá‡lmm322ÞuYÀŸT1ß8|øðêêj©óüø'U Ÿøïì–””ôu€þ›T ^À1bDUU•ty~ü“*>ؤJèS–¤¾‡&Uøäô£G*|¸`R 0©w1©’æ·ÿ0eeennnjjj ÃÐÐ044ôï¿ÿæM°cÇ …Â{ }EEÅàÁƒ-,,^¼xm ¦P(R×@ROžnܸøøxuuõÊÊÊo¾ù¦   ´´tÔ¨QBw¹víÚ_|addtöìÙAƒõ¢ÎR²¶²:˜ŸO>1ùœ¥Y©*//_µjÕìÙ³ËËË—.]úå—_nܸñÆííí^^^B%íòåË“'O=zô… `F€¾Rqûö;J,ÍJÕÖ­[étzff¦œÜÛ9™¡¡atttXXØéÓ§ù~V¹´´ÔÝÝ}ܸqÇWVV–¢DY‰‰Ž&™2.>ž|¶¯Tõôô”––::: þÒ ö«À¥¥¥¼Ïœ93mÚ´Ï>ûìäÉ“0£À‡BÒ[¯$^©jjjjjj200|KWW—B¡<{öŒwcXX˜¡¡aqq1ƒÁÜ…N§ËËËKZ€Þè_ßþ#iÚ´i=JHHú.‹ÅRQQy×uàÕE‚¤yJ¼R¥¢¢Âb±„.ˆ=}ú´§§gذa¼·nݪ­­½qãF&“Å·Ë!C†*iz›3ééé ¾USS#]ž¯TQ(ggç~ø¡¾¾žï­ââb„³³3_úììl__ßÕ«W§§§óíôÓO?IZ€ÞÀÖ¢?~Ì·ýñãÇR¯TIsù/""¢­­-((ˆ÷é ?Ž733›:u*_z …²gÏ//¯°°°¬¬,)J!|æT]]o¬®®~¯—ÿB‰‰‰QQQ555þþþjjjØÃ?étú‘#GxŸ³€£R©ùùù\.wùòå # Û²sçÎÎÎN)ª Þ9SUU•±±qUUU/ï^—ò‰ê‘‘‘l6;55uÍš5ÍÍÍ:::>>>kÖ¬ÑÔÔYväÈ‘™3g.^¼˜Á`x{{£ÿÍ¥¬;€Tø¦wïÞíý„DúTž4iÒ¤I“ón¡Óé'OžäݲcÇŽ;vH])¼‹5é'U(˜TÈLªdàƒ|¢:@ÿameEòg’æç[[Y‘ÏVªð a³­BóóŦ´¶²Â“$Á¤êÏ?jÉ'èŸØlk‰fK$Áå?€I€ À¤ @`R 0©˜TÈLªd&U2“*€I€ Àoÿõ[·**nßîëZ@âßø&U}£âöí%‹õu-€víΕɤ .ÿÈLªd&U2“ª~áÅË—–·+n§¦¤Nœ8±¯«ó^1„Б£G¿øbª¥¥•ýx{±;æäì7ÎûûÚõë¹¹¹ä •¯Ì¡C‡ÍÍ-°l6ÇÃcÆÞ½{»ººòliiÁwü7kÖ,Q;÷o˜½ÁÛÎÒ‘´‘ßs\’VO bÏÞ7òûÔç] “.#oÊ«W¯®^½fʔϭ­Ù'NŒ‰‰yñâ…¨‰ûýêÕ«|'û„ Ž’F!JoF$‚-O²e{̈0?¸¨%:®¤7ª÷ ee¥´°´(--íëºü ^±çÏŸ'lI˜3Çkš›ƒ./Q&ׯ];|èð¢EdoÌ•¯LÕƒ¡è˜uímí§OŸÞ¶-½±±)44DTž #55»QOO7 {©¬Ì’("™ÃC“:IùýÀãÚ¹sgŸW¯÷ü!’º drD 4ÐÐp¸¤{åäìzýºùË/¿ÔÖÖ®©ùíàÁü›·n())IWU«"455±¿é_z3"lyéšQjX\k׬¡ÈQ°-æµÌ+¡`RÕ/”––9:9ÊÉõ»…C¼bµµµÝÝÝ3fÌ011éóÊ`/m86¡É“'Ï›7ïðáÃ+VS©T¡ûR©ÔÉ“'ã/·lÙ¢®®Î»¥oõÛ —ÈÇÕÑÁ•—§÷“Ê|Lú*j¬C======%Ýwýú}}}ü¥ñ(ã•ááçÏŸ1s†t•±µµ1ˆLÊW TÅ"õ¿¬ÞŒHB‰: ¤kF©aqMù| ¾…`Àü€¢–ùq%Ô§5¾ôOÍÍon\¿áìüv»ºº: `!‡cãìì¼{÷¿VDËË\°À—ñ?~üŠàÿ\±"Ä~¼=‡c³Àׯ¢¢BhÅb¢c"„æÌ™knn‘”œŒU2,,l’ó$kk¶“ÓĘ˜˜W ‚Ñ%%'ïÚµûus3¶Jìâ2™¸hQé[ G¡PÌÍÌ[ZZËÊ.š›[Ü»wŸ7Á’%sçÎÕþdù¹²r¾÷|6ÛfÊ”Ï÷ï? 2+Ñíù¬öYHHˆ½½ƒ••µ“ÓÄåAAoÞ¼ ëÄËW.{zzñGšÐFë?q ­éµk׿{ÏçplR¶¦ „Ö¬YË×S . ùç?¾Ø.>ó}Øl!g¯â­Ù»víld‚ˆZ[[§O÷ðööîèìĶ”—ÿhaayèð±M*é HÐ×ï§ Z[Ôi(vüáëPÞK3$G „ï'BÈÊÚ!ôüï¿E5"31­­­===9`~¾Séää¼2<¼¬ì"~ ErD"sÌð6šÐ–ç»ÂUýðahhègŸM°¶fOsuËÌÌÞ&½Ž$Òÿ£–⸒¬Tõ½+W.ËËËÛÙÚa/[[Û"##½æÌ]²dIIIÉöíÛõôt±ÿ"üøãÕ   {‡íÛ··¶¶fdìôõõ-(8ª­­ï»ví:ooﯖ,9|øHdT”÷ÏóªîWEDDp¹í))©«#Wç›%~øðÁ‚~&&&ñ›7+*2 ‹–, XM !4D]][çŸÔá3‡Ï°¿-­¬ŒMfÍšU]]ÍwŒªªª*++ËQ(øŽÄE M/ØJ¡¶¶¶–––öö¶S'O_ù¿+':aïzzzîÙ³'""»R^XP¨¤¤8Õuª¨öHkkkØÊ0—IX‚ºººœœ]sçÍ“§ýëô!ª­­í×_ÅÅÅMœè„%vrz{·,_hkÖ¬9rBÈÕÕõîÝ{xq¢Bc2™‚Öâ¢ÓéBû´£ƒmie%ªkutp7ÆÆŽ9!ä5ÛëØág¦§§')9¹¨°(--MT#ùÆÆ&¡¡!©©i¶¶ãòöí““£nÚ¸‘d“JÉŽW šIDATt÷õûéQ„ž†bÇâ%9bðillLKÛfllâè8AT‚1SY™åçëkie¥¨¨xÿþ½={ö.ðñ-(,PWSš“Éô˜áá1ÃãÅ‹'O:yâÔá#GtuuÝÝÝݦMÓªƒ§$9"‰Z°Ñˆ{*-5UMMmÏÞ= !dm-üy•²ŽÄúP¢æKI渒LªúXGGÇÿ]¹²víZ|‹¢¢âرcñ—F#Œ°õI.—ûËÝ»KhÿpÕÕÕmÆÚܼu“w_l@G±X,555[[[ürõðá¡¿_¼ÐÒÒêèè¸qý†Ÿ¿~òP(GG§oU1Ášçæî9þÜüÉåþó¿œß~ûMìÄ_lÑdZ !4gÎÛ+Ÿ9|»aö÷—_~™•uòÔI¯Ù^ßw›æÆd2¥DžFsät&Oq9þý÷~­666áÍ„ (CÃá™Y™---ÛÈȈB¡ˆ M^žÎ;dŒ·³;pàVœD¡õ·¸)((XXZ$Äb±°FGg(ïê}WwOTÔêòòòœìl+ë·ã5oeÈ~>>>?^½´¢³³#''{ÀÀx>ÄMJþĶô5oåß]Gfü!îP)FŒööö°°•-­­»våܦ#jÌD™šŽ15ƒýmggk3v¬Ï|Ÿüƒù¡¡!]]]---ø^|÷Q©««ûûùùûù=zôøäÉÇŽËÈÈðš=›A’‘ÄF-ÑYÐÑÁ½UQ±pa6·ìý3JÔ¼HWÒIU»~íz[[»£ãÛÏ6&S?ÄBT*µ»³!ÔÔôº»»{à Á¼»8¨êþ}Þ}yߥR©¼Ÿ¸*!ÔÝÙ…åÖÑÙ™—·oß¾··qôôtã_ˆ¬ŸÍ[¶œ=s68(ÈÜÒBIQéuSã_¿övñqÄ-Hhe´4µhtù¡:Ú·<ÈÙeRÁÑ"¯Ù^%%% õ³½fÔGl J,ïÊÁ€ƒBMµsçÎ̬̌¬ì††z5µÁ>>> E04%e%ÞÛŠYª*xq…Öß⤤¤È{¨“Á÷Õ-Š¥›çÆ—–7o.]ºÄæ°ÍÌÿu½†·2d? …âîævåò•Q£Œ±õŒØ&%þÓ¢û÷N»€<2ãq‡J:bttpCBCTUíÎÝ=l˜È vHô˜)ÈÌÔTWW÷î/¿ „~¹{wÏÛ+­••? Ý¥ùuSóëæ–ÖVÞÞ$?"‰? %9 ššš»ººÔÔÔÅ%{ÇÌ5†üqE†–öP„^ònIU+--c³Ùd¾l¢¢Â’““k¨¯ãÝX×P¯ªª*E¹,–2•Jõ™?¿éDÒŠOÛ(j$ëãJK{è‰ï#¤Ë»¾ý×—zzz.^,svv&“˜N§›™šž?Ÿw¿xùòÆõ6)ЦÓéçúëÆ 3ø72ëîîîììTR~;þ–””ˆ.‹Áû=â¢ÓKÔJ‡cdd¸-mÛÍ›7‰—©ÈÒÑÙyñâEüåÙ³çUUU þ5›nøpƒðˆpƒñ믿 ­³³óÂ… øË3gÎð'*4¾FëoqñUOu!þõWww7öòusóC ‡c6›™‘Qþå‘‘‘X‰|•QOOOtô::]~×®Ÿ´méÕ" y’ˆûšd…q’vqk ž¶½$j½ÎÎÎU«V]½vuÇŽo,,zõ\±Î‡|íÚµÚÚZss3„’’’•µþKðº¹ùàƒsçÎ>Ýãĉ“_Lý¢¸ø»C‡yÏ÷ÆgTäG$鎂“E^žÎ¶¶>uút[[a2ޤÐo£–áq…ÞΨøÁJU_ª¼sçÅ˗俼º<(hÙ²eÿY2×{N[k[VVN÷ð—®ôU«Â}}ý-^<ÇËkÈ!¯_7ßùåNOwOHÈÄVLNNnœí¸¢Â"牵´µKΟ/((•ØÐа­­íÈÑ£cFaÐåGŒIP´`ú–¶6‰Z 3ÛkN–-Ø÷w’‘ „ÉdnÛ–ÞÔÔ¤§§WVvñĉ«VEðÝJŒÛó?þذ~ÃäÏ' ïêì<®¤½½ÝÖÖVh;3™Ìíß|Óü¦Ù@ß äÂ…³gÏò'44ÁFîWq VOhwL™ì’»;733Ó××·®®.1)‰&î{j‚¬¬­²³2—.[¾*"bkJʽ{÷ø*C|øí?pàêÕk»wïRQQ ¹qãfdTÔáÇ ùcž$±}-¶Â½éâÖLß›ñG¢#.>¾´´ÌÓÓ³¡¡áüùóØF]Ýa£F#„ÊË Ú¼9ÞÕÕUl¹¡!aC4†ŒmÂ`(TUU=zTSSÓç+(‚**nedf¸LžÁa³…^¢"?nK5ŽødY¾ÒÏÏ? À××WM]½ö鳪‡Ö¬^Í—‰¬†#)ôϨ‘¸ãJ"øŒŠÍ¶>ñ .ÿõe¥e£G›hhhLogg›‘‘‘™™ºR^^žÃæ$'oÅ¿Ï,©‘#G:ômFffRÒÖ¦¦FUÓ1cæÍ›K²bqqq›b7Íšõ%.oie•–šêëç/4åĉNžžž;wìlllTWW/)9OP´`z777‰Z óù”É [¶L÷˜.ök,bQTd&&%&%$V=x8hÐÀðˆpÁ/½#Âöd©¨hhhìËÛÿןRääFŒ0Úº5ÙÎÎ6=ýkÁЙIIIÅ M°‘ûU\‚ÕÚÆÆ&qñq™™™{r÷ ÓöÕW_qÛ¹BS³°´ÌÊÎZ¶tiDx¸žž>_e"ºÿþö¯·/Z´Ãá „äåå““½¼ælMN‰ŽYGþ˜'Il_‹­poº€¸µÓ÷rü!ßzw*+B………………øÆysç¬Y»!ÔÓÝÝÕÕ…/°cs¬O:sêÔ).—«¦¦æîî¾|ùrÞ[0ùX[Y_¼x‘xÐhÜ–â˜!>YLLLØ·cGf|\|k[›–––‡‡‡`&²ޤÓ£FâŽ+òxgT‚÷TQbcc5müüÑÔÂíBm˜!ü‹¦@¶víÎ]²xÑôénnnKúº:üúUŤ«Lññã1Ñ1ÅÅ߉ºïª? -5%õÄÉeee{õÿÐ>‚ãç= Ó×Rë·Q>Ö¶ýXã" ûP›ŒoFuâûã'žé"„´QZ¬¿näÁJU_:~¼¸¯« \¿ª˜¤•yüøÉ³Ú§;wìtttê·ÓÌÇÚ}ü|>ͨߵm?Ö¸dHpF%˜&Uàc“Pqë–™¹yt´lžÐÓ|Ä¡@&tF÷Tß®]9}]é…G„‡G„‹z÷ƒ ð!îk@ÿ!jF%xOÅÅÅ1cÆ 6lØb_||<3fÌ`æÌ™cRì_¹r%?ÿüsªÇ>{ö,3fÌ`×®]©¶Ù»w/3fÌàôéÓ—vÙ`ûöí̘1ƒøøøTÛ¬\¹’E‹ecT—oÖ¬YlÚ´ c 3fÌ`Ë–-~Ž*ðœ8q‚3fpàÀË:Îúõëùõ×_3)ªô›>}:³gÏö¼>}ú4û÷ïO±9r$Å{/\¸Àúõë™={6{öì¹è¹N:ÅŒ3Ø·o_¦^CfÛºu+~ø¡¿Ã¹ª)‰!"">…††Ò»woyä‘û-ZDëÖ­‰eõêÕ)öwéÒ…Aƒ¥zìƒÒºuk¦OŸîÙöÍ7ß0tèPÏëÙ³gÓºukþúë¯Ë¼’¬óÕW_ѺukO¢åôéÓ<óÌ3,^¼ØÓæùçŸ÷ys²6mÚ0fÌÀ&¬Z·nÍĉýUàÙ²e ­[·fþüù—uœ·Þz‹»ï¾;“¢JŸíÛ·Ó½{wÊ”)ãÙ6pà@J”(‘béСƒ×{ýõWJ•*EÍš5iÙ²%åÊ•càÀižoÇŽ´nÝš9sæ\vìGŽ¡OŸ>lß¾ý²•\Ù²eyçwøæ›o2ýØ""’>JbˆˆHªš7oÎòåˉ‹‹óÚ>cÆ Ê•+G®\¹˜1c†×¾Ý»w³}ûvZ¶l™êq###¦OŸNtt4Û¶m`ܸq´hÑ‚7rë­·’?~Ê”)C¿~ý8{ö¬WÜß}÷mÚ´!oÞ¼T«V &ðÈ#Я_?Ÿ×yøðaêÔ©ÃèÑ£½¶·k׎† z}£;mÚ4êԩáC‡7nÑÑÑœ:uН¿þšÇ€ûî»/ŃÞ÷ßOtt4¹sç¦nݺŒ?>Í{àÀ¢££™7o}ûö¥dÉ’/^œx€ .ðí·ßrýõד+W.êԩòe˼ÞðàAºuëFÉ’%‰ˆˆ ~ýú>ç)ùꫯhÑ¢yòäá†nH1÷B||<ÑÑÑ|ôÑGüõ×_DGGóÃ?xµ›5kÑÑÑž¹4~øáêÔ©ÃÖ­[iÓ¦ ùóç§Zµj|ðÁcx饗¨X±"´k׎ƒ¦y?n¹åÆÇ=÷ÜCþüù½îï'Ÿ|BíÚµ §bÅŠ<õÔS)>gÏž¥ÿþDGG“+W.ªV­šâ÷½fÍÚ¶mK¡B…ÈŸ??5òšìgbĈ<öØcäÏŸŸvíÚ‘À°aèW¯Üzë­lݺ5Íkr{óÍ7©T©¡¡¡”+WŽ=zxÝÁƒ{þ7nÜHtttªËúõë=ï›:u*7Üpyòä¡L™2<ðÀéê=0bÄjÕªÅu×]çÙ¶uëVNžø€W^y€Ûn»Í+ɰpáB7nLþüù)V¬:uòêývÛÍ7ßLž|Ø”(QÂDEE™˜©S§š»ï¾Û8Žc¦L™â9ÇÌ™3MPP©U«–3fŒéÒ¥‹ 7€yúé§1Æœ?Þæ…^0ƳqãF˜qãÆyÅûå—_À¬^½Úc̸qã `J–,iúöíkFŽin¼ñFdZµjejÕªeÞ~ûmÏ=»÷Þ{S½Æ“7o^S¶lYS±bEs÷Ýw{~çýû÷7AAA¦mÛ¶æ‹/¾0o¿ý¶)X° iÔ¨‘IHH0ÆoÚ·ooÂÂÂL=ÌäÉ“MçÎ `ÆŽkŒ1fóæÍ&wîܦpáÂfРAæÝwß5åË—7€ùâ‹/>ÞT¬XÑ”*UÊÄÅÅyÞwøðajúôécŒ± ZÀüþûïž6›6m2]»võú›6ÆþΓ^»Hoüÿox-Zrä¢á$""’¦-ZpðàAþüóO.\¸ÀÏ?ÿLll,`»U·lÙÒ3Á]…!ù|7Þx# È”xÚ¶mëYw‡êÕ«sôèQÀ¡Ø¶m½zõ"((ñqmÚ´¡dÉ’i·}ûöœ?ž¹sçðóÏ?Cýúõ=×·hÑ"Ž;FÇŽ3s¸å–[<¯¯»î:¢¢¢Ø¼yóEßCXX˜çu:uÈŸ??M›6õl+_¾<€§ÔåìÙ³©R¥ ‘‘‘lܸѳԯ_Ÿ={ö°eËöïßÏîÝ»éÞ½»×Øÿ^½zeê\îÏ @5 ¥yóæDDDxÅøðá‹+::Úk˜Â¼yóˆ'66Öë:Ë–-Kxx¸g~–•+WR´hQ¯ÏNpp0ëÖ­óTÃX´h]»v¥`Á‚^mxàöïßÏüáÙ^±bE*W®ìy½råJèÙ³§W¼÷ÝwßE¯é†nðT1ùì³Ï8zô(Ý»wgìØ±éú›y衇˜3gS§N¥Zµj,[¶Œ“'OÒ¶m[¯ûATT”ç3îËŽ;(Uª”×ö-Z0lØ0-ZÄSO=Å /¼À¶mÛ¨T©¯¾ú*‡òüΜ9“åË—óÝwßñý÷ßó믿räÈ‘‹V(èÝ»·×ß®ûóéþ]vëÖ°°0&Mšäi3qâD4hÀ5×\ãó˜Û·ogÛ¶mÜ~ûílÙ²Ås?<Èõ×_ï¹uëÖÅq:tèÀðáÃùóÏ?©R¥ “'O¦Q£F^Ç,Q¢!!!žû%""ÙGI ISLL AAA,\¸%K–pôèQZµjåÙß²eK¶lÙÂáÇ™?>åÊ•óªh‰Ù™¡D‰^¯sçÎí—¾nÝ:Š/žâ}¥K—Nó¸uëÖ¥téÒÌœ9c ¿üò -Z´ iӦ̛7 .ðã?R¡B…4'õ%i™Ê¤q'¯úâK¹rå¼^S®\9¯ÄFHˆ÷<ÝÛ¶mcóæÍ\{íµ^Ë¿ÿýoÏ~÷½òu?£¢¢ÒwaéP¶lYϺã8¥xØLj’ß w ÍÞ½{{]gíÚµ9{ö¬gNŠõë×ûü6ùuâĉÇOzN÷Ü,òóœÚç®B… ½¦gŸ}–þýû³lÙ2zôèA±bÅhÑ¢Eй8|:t(Ÿ~ú)ü1Íš5ólwÇ:`À€Ÿx]KrîDXòÏEÓ¦M0`yóæõls‡îÝ»sþüyÖ¬YãyOÆ ©S§Ž§]“&M¸îºëÒUj6éçì|… ò$ .Ìí·ßÎ_|Á… صksçÎåÞ{ïMõ˜îëýøãSÜ%K–°cǨ]»6S¦LìUªT¡zõêŒ1c¼+S¢D Ïý‘ì£ê$""’¦‚ R·n]Ï?ö .Ìõ×_ïÙß²eKÇaéÒ¥,X°ÀgU’ô>¤¦GZ½Üߢûz°8tèPŠo—“k×®³fÍbݺu8p€-ZpâÄ ȪU«øñÇéСC†cNúÍrF…††fø=‘‘‘´iÓ†?üÐçþ‚ ²eËÏ·çI;wî¢çHž€ñu¸´øS“üXùóçl’¢páÂ)Ú»=ùòå㯿þJ±?>>žòçÏã8>'½t_WÅŠ=Û’žÝ½|Ž;æU8=÷188˜×_¡C‡2wî\¦L™Â7ß|CË–-Y¸p!õë×÷ù¾Ï>ûŒçŸž¤èñ À/¿üBõêÕS¼7­¿Ç|ùòùŒýرcœ;w.E‚Ë}ü„„O¢°víÚ)Ž[³fÍt%f’ÿž.\¸À‰'¼zRõîÝ›©S§2gÎV¯^MXXwÝuWªÇtßQ£FÑ®];ŸmÜ£:u¢S§Nlذ©S§2yòdžzê)Ž;ÆË/¿ìõžsçÎyî—ˆˆdõÄ‘‹jѢ˗/gþüù´lÙÒ롼X±bÔ®]Ûó🴗FF\΃¾[ÕªUÉŸ??¿üò‹×öÍ›7§«Ûw»víØ¸q#“&M¢téÒT©R…ºuëR°`AF͆ ÒJâþf?ù·¶Ù­Zµj¬X±‚Â… S¼xqÏ2gÎ^}õUŽ9BµjÕˆˆˆ`Ñ¢E^ïݾ}{ª ÀóMü±cǼ¶»«’d'÷ð‰õë×{]ghh(ô|ªV­ÊÎ;Ù¿¿×û{öìIñâÅ=½[ÜC’š?>aaa>“n7Üp`«_$µråÊ‹^ÃàÁƒyã7 ¡E‹Œ5ŠŸ~ú‰„„„TúçÎËý÷ßO§Nxíµ×Rìwß—ß~ûÍë¾(P€_|‘o¿ý6Õxܽ):äµ=&&†êÕ«{*…¸-\¸àà`jÖ¬IžÈ¬Y³ˆŠŠ"88˜¾}û2vìXÞÿ}Ξ=˺uë.:CTTEŠáã?fÏž=$$$ðÓO?¥(Wšn»í6jÔ¨Aÿþý™6mñññ;vŒ¾}û2~üxrçÎ À#IóoÁ=/ÄÚµk½¶ß{ï½>|˜!C†pòäINž<É{ï½ÇÈ‘#éÓ§ça~РA¬X±‚AƒqþüyÏ\'Nœ G½“&MbÅŠ€M =üðÔ)S†»ï¾ÛÓ&88˜=z0eÊ–.]š¢œ±ûïpùòå;vŒB… ñàƒòé§ŸòþûïsüøqâââxóÍ7>|¸'iwæÌ>ýôS^}õUO²fÉ’%ìÛ·/ÅïbýúõÄÇÇ{X""’ü=³¨–«bQu‘wæÌOéͽ{÷¦Øï.7X¿~ýûp•LÊWu’¾}ûzJšþïÿKµ:IÒ*'Ƥ¬Üožzê)bOÕˆ"EŠ˜ûî»ï¢×ê.Ûê®laLbÅŽäïO^äøñã¦téÒ0¥K—6ÆØªµk×NqžòåË›öíÛ§‡»:IÒ–ÆÓ¦MS³fM¯m_ýµÌÊ•+=Û†nråÊeÇ1Å‹7€©T©’Ù¸q£§Í… L§N ` ,hÇ1;w6QQQ©V'1ÆVq 5Žã˜|ùò™ÈÈHóÁø¬N’ô|Æ“+W.ó¯ýËk[ß¾}MR½ÆØê$÷ߊí6l0µjÕ2€)R¤ˆ 1¡¡¡fèС^í¾üòKé©–˜&Mš˜'NxîÅÓO?m‚‚‚Lhh¨ÉŸ?¿Lçν*ZT®\Ù´mÛ6E‡65jÔ0€)T¨Ìc=vÑê${öì1U«VõÄ•'OOåwÕ˜¤Ÿñþýû{þN|-C† 1ÆØÏOÆ =¿Ûððp”âÞûR¯^=sÇwxmKHH0}ûö5Žã˜Ü¹s›|ùòÀtïÞÝœ9sÆ«Ý!CLPPÉ—/Ÿ‰ˆˆ0@ŠßGrîê$}ûö5!!!¦dÉ’žòÌ‹/NÑþ÷ß÷”ðu—vÛ¹s§)X° ÌM7ÝdŒ1æØ±cž²ºžßïwÜá©<“`ºwïî© åùœ:tÈëï½÷ž õüí‹dñÆÿÿ†×¢%G.Ž1þíò*W…À¥õ/‘cùòåÄÅÅÑ AƒûâââX¸p!%K–L1©ç¼yó(Uª”W5‡sçαxñbªT©âë~á–-[ÆÞ½{iܸ1 üñÇÜxãäÉ“‡]»v±uëV6lè5¦Ó¦M>|8E\gΜaãÆT¬X‘ÈÈHÂÃÃy饗xöÙgÓ¼Î;w²mÛ6n¸áÏЉãdzzõj¯x“¶mÔ¨‘§ ûÑ£GY´hAAA´nÝšuëÖqîܹߨ.]º”ˆˆjÔ¨á3_÷ðy¼¿ÿþ› 6xÅ °k×.–.]Êþýû©R¥ Í›7÷ęԶmÛ˜={6×\s 5bÙ²eDEEQ±bEŒ1Ì›7òåË{Mh¹mÛ6æÏŸOÞ¼y‰‰‰!<<œåË—{bØ¿?7n䦛nòôˆûíz©R¥¨T©’gÛæÍ›ùûï¿Óìå²`ÁŠ-JÕªUSì;þ<‹-â÷ß§@4nÜØçD®ÿý7 .d÷îÝ\{íµ4kÖ,Å+þù'Ë–-#!!˜˜˜ÇY¶l¹sçöÞÔªU«X±bõêÕ£zõê,Z´ˆêÕ«{Í•‘Ü… øå—_زe‹gΙ¤“Ÿ&ýŒoÛ¶;w¦z¬ *x&#MHH`ñâŬ_¿žÜ¹ssË-·xÝ÷Ô¼ûî» 4ˆ½{÷z ÓÛdÙ²ec¸þúëSävË–-,]ºÇqhÖ¬™ÏÉv“:}ú4Ë—/§nݺ8p€yóæQ¼xqš4iâsÞ‰¸¸8 *Äã?î™°6©ƒ²xñb"##½*ù¬ZµŠµk×ráÂêÕ«çsþŽ5kÖ°jÕ*âãã‰ŽŽ¦nݺ)æiܸ1EŠaÚ´ii^—Èe˜ôòw"9‘’’”Ä‘lS­Z5Ú´iÈ#<Û¾ûî;:vìÈÔ©S¹ãŽ;üHÎ÷Ï?ÿP¡B^|ñE}ôQ‡ãÓçŸν÷Þ˶mÛRT4Éj›7o¦Zµj,_¾Ük’c‘L¦$†H*TDDD®(-Z´à½÷ÞãàÁƒ4hЀõë×óå—_Ò¬Y3Z·níïðDr¼ø ÛÏ-""–†“HvÐp‘ôÓp‘T¨'†ˆˆˆˆˆˆˆ%1DDDDDDD$ (‰!"""""""AI  JbˆˆˆˆˆˆˆH@PCDDDDDDD‚’"""""""”Ä‘€ $†xq',â8Ž“YÇ‘«›’‚ã8EÇí8ÎßÀ9Çq8Žó‰ã8Å’µ»ßqœ>–éÉÚÕtgp8Ü´iÓ:ÈÆ+‘+Qˆ¿ÿr'˜ÔÆ?->@´ã87câ]Í›E€éɳ'Éñ€¯€BÀÿ7mÚ4¡aƬY³†ˆˆˆ¬¼¹‚)‰!M†À`cÌË®m3Ç9 b€Ÿ]ÛësŒ1½Ò8Þ³@U ¬1fÀ‚ þÕ¨Q£Ÿ}ö}ûöÍ‚K‘«†“ˆ| |–lûl×Ï2Žã„Õ€U9Þ]À¯î@Æ OÖ¨QƒÏ?ÿï¿ÿþekôèÑ8p€^x!"ËÙ2ó¿!W“¶mÛ2lØ0jÖ¬éïPRÈÌÿÆmÚ´‰~ýúñí·ß^ÿJË÷ßÏ/¿ü’)ÿ ${÷îýÀ\Î?§2)¤Ì6X LIGÛ xûGa+(ÊUBI Àqœ¶À7Àz µ1æP:ßú 6‰QÄç8Îßøèîuøða*V¬˜á¸rêjfúí·ßøã?hÓ¦¿C (£G¦|ùò9ö¾½üòËT­Z5SâË•+ÑÑÑ9öZ3ËŽ;hÚ´)åË—÷k,äĉäÊ•+Ç~>~ûí7V¯^)ñÍš5 Ç^kfÊÌÿ†\mnºé&š6mêï0RÈÌÿÆ*dÿ©KxxøeF–³mÞ¼™¥K—^ »ûôéóßK}³ã8=cƘK>FVrçE`SfÄç8NêßzÊI{ ŽãÜ |‹­HÒÔWÃqœ Žã¬q'yºÿ×ÏM®Ÿë±%[=Ο?ï,Y²„J•*erä""""""r5QOŒ«œã8¥O± ŒöƘ ©4Ýôzc»lá8NU`°pOúðƒã8Œ1ß >¼Ô±cÇxâ‰'²îBXll,U«Võwçá‡&_¾|þ#U  B… þ# .\˜‘#GR¸pa‡PêÕ«Ço¼áï0RKåÊ•ýFÀÑC.ÍÈ‘#©R¥Š¿ÃðIÿ»4Í›7§téÒþ#Îù;ˆ4¼l÷w˜cŒ¿c?ru¿zˆ|4yÎó–ã8¹°‰Ž[€åÀQàVà/ ­1æw×ñà{ ˜„9ŽÓö™gžáõ×_Ow\=ö~ø!ú|Š@ñâÅyçwèÚµ«¿Cñ«ýë_ìØ±ƒï¾ûÎß¡ˆøÕ’%K¸ùæ›9sæÌ?œä*6èåï ã8˜oŒiâïX${¨'†,ö¥± €1æœã8M°½1áÀÓÀĤÃOŒ1ÆqœöÀ½@ dĈëžx≜7Öˆˆˆˆˆˆ%1®rƘ±h{çZÒjg€ñ®lÕ%1DDDDDDä²(‰!9’Ê+Š$zã7¨W¯ž¿Ãñ»;3“'Oú; ¿«X±"#GŽLwéz‘+\pÐßAHöQCr¤üùóû;‘£gÏžþA$GhР¿CÉŠ+ÆC=äï0DrŠ฿ƒì£$†ˆˆˆˆˆdªmÛ`ôh¸ñF¸ãßmž¢£¡sçË?ß³Ïz¿vÈ›ŠƒÛo‡âÅ/ÿ"’3(‰!"""""™ê¯¿àõ×!<V¯†jÕR¶yóMèÖ-s’¯¿¡¡'Oâ¶Ó§!>BB`øpø¿ÿ»üó\‰‡²Æ°3Éëp ¿1¢!9S¿‘+Óٳл7\¸õçêÔ ŽK\Ο‡µk¡jUè×Ö­Ëúãðð›ãp“ëu8ð0ÏqPÿÉ‘”Ä‘,Ѱ!,Yo½•þ÷œ8K—† ¶'Å¥r¨U † „øõWïý[·Âÿþ»w{o?sÆ&A|9yþùÇ{[\œ÷÷ßíy’3vì€ à`êÛàJ`< D ‡ À~ P øU‰ ɉ”Ä‘,ñè£Ð¨¼ø"üñGÚmãâà©§ H¨_jÔ€¨(˜4éòbÈ—Ïþ<îšúñàA{üÊ•¡qc(SÆžkíZ»âD(XæÏ÷>ÎÑ£P´(Œa_'$ÀƒÚ¶õëCõêP³¦wÕ«¡^=¨PÁÞ‡¨(hÙÒ·ÉÇÁqÂ]KP²}¹\ÛC’mumK¶=Øqx›Àp ®Ã&4Ü܉ŒÜ—xŽpWÏ‘L¥$†ˆˆˆˆˆd‰  7‚ƒ¡W¯´‡•¼ö¼ótïn 3gÚž={Â?^ÚùãâàÃíú7ÚŸýúÁæÍ0{¶MLÌ ‡Á}÷Ùý]»BîÜ)“'_}e‡¨ôèa_?ö|þ¹ý¹};Ì™c{‘tìh'OB‡öØï¿;wÂС6±Ñ¦M†{™Ôθ–vÉöýåÚþA²í“\ÛK¶ýà•tžw”1œ¹ÄsœΨ7‡d6Mì)"""""Y¦R%› ø¿ÿ³“y&¯$pê”}ÀïÖ ÆŽµÛjÕ‚fÍl‡çžƒ¶mÓ>ϪUð¯%¾Þ´É9}Úö~hÕ*q{åÊv¨KX4i_~ {÷Ú$Kd¤MDL™b“ᮾ'BL ”+g{sŒi{˜ b÷—/o'8ÑQ™<ví‚+ n]ÛfÐ {=:ØȽ÷^òmÍ«á!’œzbˆˆˆˆˆH–züq;œâ¥—ì\ÉmÚd{9´oï½=$Z·†õëíþ´ìÜi“_~i{MìÞm“#GÂÿ›Øî®»`ùr›ÈxüqÛ#£aC¸çÛcìd¤ÇÃþc_oÙ‹'&ÜCObc½c¸ë.Ûk¤lY;¬¤téĆ[Û¶ö<«V¥}=9Àa â‹’"""""’¥Ç{XIò¡;]>‹KùÞbÅl‰#GÒ>G‡°¿]öí³I„ÿüz(19ðôÓðÙgP¥ |ü1´haÏñÑG‰mš5³‰÷’‰!o^[àÏ?S7é5ùÚbçÑÈI“|¦¢‰»j‰HN¢$†ˆˆˆˆˆd9÷°’+àõ×½÷•.mîߟò}{÷BáÂP<gVèÖ ~ù€1c dI; éÊ•vP‹cÆ [©äË/¡sgˆˆ°ûK–ôï™3vBУGí5ùºžóçáða;™hìîv-Ë“íëëÚ>&Ùöw]Ûû%Ûþ3ð}:Î |ë8ä¾Äs¸ãM¥Ö‹È¥QCDDDDD²…{XÉË/ÛI7ݪV…þŽE²‡zbˆˆˆˆˆHŽdËŸfµÈH¨Y3õýçÎÙá"·ßžz , ªWOû\ùòÁµ×^Zœ"âMI Éz§\Kz…Yø-¼HZ®¿vì°½,>ýÔßшHRJbˆˆˆˆHÖ{xý¢­5æfM("Ó©“­,Ò«ÔªåïhD$)%1DDDDD$°%/âé¹.ýp™U ED2Ÿ’"""""âH™ŒHËàvÀ¹}ìŠw’—àÂ!ˆÍÀ;cø$«ÂÉ(%1DDDDDÄÿÎç2ÐþB²×uI^ŸVóE@ÄåxŨ¼–ö›@I É9”Ä‘ìW¨,¢€k°š9Uµ ¶ŸܘHªªÿò±ýYì|,³€ŽÙ‘ˆd%1DDDD$ûºÖ÷ùýKzlÊ`û²$ ¹°IŒ5$&1~¾ö°ƒ+:'Ù7x(’ìXƒ°=>Ümÿ¾þä:wbçäpû˜ ìªÝ€[2åÊD®JAþ@DDDD®Bù’½>î—(²NOlïô,ýcN²ËǶV$Îuák΋ôúÕõÓ='Æ  ðPØ t¸öÞ¾LvœEØAá®×«›€¡ØÞD€»“àQ =ð PÎuÌFÀ° ^ƒC †áŽCˆk½Œkßs8Ôr­?‚C×úÝ8tp­ÇâÐÛu´ >Îp;ç,¾Rp=‡ÃM8ôs­WÁá• ^±Hª”đ엨îZw°€W’]ØÞéYöù)Æìööa>Û§=ö>%•]?“Ú œq­ŸáâÎÇ’,Kcù€v®v °Ã—ÞÁ&9º¸b»€ML\ LJvü‰ØdE¬ë\­±I]Àx`Ðý<Ï>ú;€O]×tðð{:®)Q6=ŽíM?(åÚ÷8pk½'p³k½=‰x6îr­—ñqüì] '±TR=GàA×zEà™Ô/M$c”Ä‘ì÷°Áµn\KR{²7œ «Š}¤+‰}˜•DÛ±Ã'À&ÜsƒìnÅþnóÿÅ&q‚°¹É?{3xÞ©@Á$K}à9ìcù—ØyXÀ¦Ö#¿]Û¾VÁ®×½€eÀf×ës®6ݰû`ý±=7ÜÞÁÎó |ä†`+¥àz= ˆ¦eðú²VÒ«¨á·(DÒAI Ézñl_û­øÑ,ˆ%3lÃ~“¾;Ìàja€Ó®õ_o\ë-]ë«€û]ëyåÀ~ì †`ìð×ë­®v×2‰‘tÞˆæéˆ­611ÛÛâ`6QÒ&I»7°Ÿ¯G€âØá#ðÒÔë»7ưŸÅ^®×k]?«&‹¡˜+Žp`#öÓµ)fc:®)û$­ “Ñô‘H¶ÒÄž""""’õ–d°}ðö!ty&Æqûí|F õ±-éW›Ï£0vØÁ"ìm>ÒßÓä}{¬¤G,¸f(¸<;±åH c{TnÃ>ÜçÃ& æs±“\Áön›8*aïÕVl ¬k½©ë˜î$FàÇd1ü7ÉúìtÄ\x(íª`{R,Âö†øx ›øXÀ&9bÏW°ÉŒ:Øß#@œëgò!0É¥¶?Û[$ýÎbÓ1\GÝDâ ›?®õØO9ØOÙI×úAgI>ç…`{`Db{%—ÑsŦúN‘ñ©qER¥$†ˆˆˆˆd½úøiŸš]Ø¡?»^ŸÂ~«¹N£2øžâ>¶ÝFâ‰2øž˜Ò­öaì\ ¾Só#¶RFz ýIŒxlràflÏ€ÎÀÓØßU[lBàQìÃþVì5»Ëâ‚MVŒI²¾{+cç9ÙŠ_¢´kýW;wââ6ŸFþ›°9Ÿk½qØê#Q@C×òvÂÎç°Ÿ·.®¶½±÷äØò¬¯'9Ne×Ï?Hœ)l/”§±‰JØÔ@òÏîVlú¡nâ6lÄ»Ðoµ$û%Yïšdý©$ë#’¼w‰}eÀþ¶’öu¹ÖÇù3vÃWÀW®õ@íǹDN"""""Yg%¶,åÓØ¾!@öî?RY†cçÌø û¹(ïÚçïûØö=v¸A0ï|.ÙëPŸ­²ÎA`´k}/ö¡û ëõmØo°ÃcÖ¸Ö¯Á~÷~±õ=ØïõÝõ[°Iš2$NXú¤«-Ø /¹Ö!qêÇ2x—%Í*¡Ø„ÉÝ$ö¦p°ýÀ&\ÜnÇöDyÛÿáž$ûêc‡Š¼MÞ€íÏðolÒ£¶×Êìõ»û0ÂöúCeVE.‘’""""’uNc«2tÅöxû|©—>:a¿µn Üý~8«¿©÷Å׿˜ã±¶î%-%’#/‰ÌYa¥ëç kWü ôÅ(‚íØ¿Û ¢¾URÙ~ ¶×É9×z9ì—î!&î^+ÛHìò/kYÃ>ÀûÓp쀈RØdÆ-ØÄFlrÂ- û9]ƒý I²/vøÉØ{Ð ÛKeðvÂ×›±UH>Å&i:a‡Óü;„¥J\›ÈU@ÃIDDDD$óìƒlcì7ý]°s |ObµŠÔ4w-cç%¸û€|Þõº-$éÄ~ùªbKrþ÷d“n}°%.Õ ì¿ºƒ°ÿë/ãX…±=ÎcÂÿÄ{ÒT÷PØ^ǰ×6qq3¶·ÁŸØ^.•°½'À&%6'YwWϨEâà…šØß¡ƒMJmOrî¤óQ“qÿÁwB¨öþ5bH¬2âŽáƒIœ¯âb:« Ø?×cç_éJÊÞ ·c{âôòqœ–ÀoØòª°Ujz˜°ÛóèVì=Û ¼Šý '‚’ý`k¥×Ž,ŠCä’(‰!""""™ï[ìÝH '0û­´¯ù%Rsû-ùGØ@l/‚ ®ý d^¿â®ŸÉ‡~tçò’†Ä¡ ¾d¤rK>)Ïc cL²ÿ4v^ŠŠ®×b«e”p­ßŒM\¸{V$MbÄ8Ùä@×z]'ÝŒÀAÉ ·¦²} ‰ŸƒP´*¶W‡ÛKØÉ ß#㉀RØo¹úÓ±ó ÇNÄ8;ãÅæR(A⹯$EÒº »}ìÀ&cÒk«ÆrøâM<’&<Î’²|'ØE]ìCþfl£*v>€°½(ÀÞG÷D£m] ØdFƪgdâØÊ'Ÿ_`çáMje÷y>ÂNÒy)=KD$K(‰!""""™gð,¶ þc؇é.Ø.û}/ñ˜ ØÉ¿Á~ƒÞ;é'xO¶˜šVØ!"õñ]$„Ää@0¾‡4¤§t§Û2–ĈÂN™;mKãhÉ2âV[ ì¼"u\ës’´¹;ÉzVÎÏ‘™z`“UO`ã/•vóËöv¨I _ŸKD2DI É\›±É‹¯€Øy ]Æñ‚€‰®õÕÀ×—pŒñÀ2l‚%¹ (ö>7ö[ÿËq/ÞD&·[¶´6ar;dáMR·<–ìõZW›CØIB&Ûß; $– ½ÌÇ&lÀþ¾~Åå¨Eç[®Sìb E$»)‰!""""™c%¶ÇÅ"l/Œ¦ØÒž½2ñk±C#2šhx[Îóqû°“zþ}y¡yĸ–´ 7‰ó{Ü—ŽcŸ!±ÔlfÅòc÷Ÿ“±×>ЬKb¸xñ•X‘Ë÷;¶÷A?ì×dc°%(keòyzaçÛȈxlEŠÜäœé lI¿'±55^Ä/êå×hDÄOÔCDDDD.ßuÀ/À]Øá_“¾ž™¡4¶"ÇqlrÀÁö®pÛ „a'õ5±§¿TÁ[ô™ý=‚­öò¶ K ¿E$"~ $†ˆˆˆˆ\ºØÄÅëØ «;7H_ ËÌpŒÄÉ, )ûŸtý|; !£CQ".=´4ùª,’–¢@³‹´©}‰±‚0ìP¥‰Àl9Ù·QCä*£$†ˆˆˆˆ\º`ìüõ±ÃGzb']ôUá#«œOöºp ÉëêIÖ‹_fyD—¦6¶Üênlo’0¼{Ž4#çÆž]ÞÁö¸y èHàTW‘L£91DDDDäÒÌÇö&˜Œ­¬ñvÈF+ãȃD´ p —W ÅŸNX>ՠʾ|ÌÄ·ŠMê,òg@"’ݔđŒ;…-áÙ ;DãQ`ÐܱæG…$Vï4‡“½Îë—(r6ÛÓ§'…­8³Æ¯‰H6Ópɸ¼ØJºØ9 êû5¢ÀW [B5ÛÃE_7úö6q5h‡­:#"W ý§QDDDDÒ/Ûûâ  <¶çCàe?Æt¥Ø‚½¿g ~Œ%'û[™ä.ì}ú[¹DD® ê‰!""""éÄ`»ó/ÂV EÊÉ5³R^ìP‚ô Ô92$u?£ÿù€Á@¬_#‘l¢$†ˆˆˆˆ¤Ï\ &ð,vèÈÝÀo®íaÙÇÓ®E®^‹±“Ç~ƒM^„ú7É>JbˆˆˆˆÈÅ` °˜Š­²X‚(KöK:|éol2£v²Y¹¢)‰!""""08žÊ>ƒ­ Q8Ü„Mh¼tÈ–è®LŸ`çwH¯à¬ $@ý|ü”Àö‘+ž’""""€h? xìFr¥ Eà .ÇV 46‰Ñ›h‘+ž’""""’qCQCüë‰$ë»É@~ ¯Â‘졌""""’qÚ 9ÁtlµœrØá9üŽˆd=%1DDDD$¥’@lýÒ~ŽE$5ÇÚØ f7ø7ÉzN""""")ý ĹÖwû3‘4ts-[€Ï±óŒ ò[D"’ÅÔCDDDäjæî~>ÙöøìDäMÇV̹øÛƒHD®XJbˆˆˆˆ\­Ve€S¤¬’Q4Ézžl‹H$ã°ÕIVБ+œ’""""W“mÀuÀ! :p˜JÊò”!®me€ºÙ HµÞ¢±‰Œ§'ý‘ˆd!%1DDDD®t€[€=Ø*G€/±%R»|¼g/`€]À‚ì Sä’ý€MÎÕæÕüŽˆd%1DDDD®D'ÎÀv 6ñ¶4ê=ÀDW»žÀ\TšR[!  ° Xôõo8"’u”ĹRœž6ù€õÀ$ì°î®u°‰‹å®v7?¢J`k ÁNî¹x›¬‘+ŽJ¬Šˆˆˆ2¼ÜŽãâ@ðмˆM\ Öb瘔p£5Ð8–ó–»üÐE2ÕÀcÀN )p¯_£‘,¢$†ˆˆˆˆ¿üŒM>¤W8‰‰†/€@-à?Øy/ÞzoÿÆö¾xX Ü |q½¿{²cœáèEr–ÒÀ£ÀÝ@)?Ç""YFEDDDüå¶:Hz—°CDÀö¤øÈµ~/0ˆÇv¡ßç¢6™QÐÕîaôp'W®ÚØÊ$%±“{>Äú5"ÉJbˆˆˆˆŠ Àp×zOàklrãNà80 (Ší}‘ËÕîYT©A®s°É»[±É¼^~FD²€’"""""˜ œ:`{^üž\í^ø#@?« öa‡YuÅÎ#"W ͉!"""¾ýè—öE±“J^ÌÀ‰ w ¶òÀÕ¢#ð“k½ö÷à„ßbç´xÛì°‘«]yìß…;Á÷9¶ Ï*?Æ$"™JI ñí°)íÓ[Ùb3‰Þéq*mÍFÛ~ÀøÃÇþ{Ý®õ׳"(‘7è‚ýoXà9lo Ç1‰H¦QCDDD$;} üŽ}°Úæcÿ…$ëG|ì‘A‰\Aª¯bçŠqOj{Û‹Ig¬©C IDATDžæÄÉ*î±øcÇ\ëGw]ûnðñž*®Ÿ!@ž,NäÊTè ä¦`穈b""OI I¿ÒØíú™tÌ@- Ð:“Žé/øÛµþÐÙµž[5{‡€•@1ÇØìúœPD2n ôòbçÖÑ“ÈAÃIDDD$ý–%Y¯‘IÇüƒÄ!§3é˜Ùé86ÙP xûð4›èy[µöÚh`¾{bˆÈ嫌ڮm'€ü~‹HD2‰’"""Wƒ%ØÉîÒ«z‘6;’½> KDZÏ%{œq­g¤j‰?m`«‡ ~1®×'æØqøó[›±UGqñû+"—./p6É8ø Xìuí‘€¥$†ˆˆÈÕ`.00í‹/úØ^ ØŠí–ýO²}§3x·ÖÀ4×zÒ_å$»ÍÃö¸è |ƒ s  ¼†»ö_W¿`K¥6À–H½èOâÄ‚º~ÎȦØE®F+[°Ã¹º/‘Ø+CD–’"""’Ò?ØRŸÉíÆ–)4>ö]ª€`ì’Œ”^Í_a¿¹};”f 6‰Ñû@´;7H>lâ¢3¶7ƱIŒWHüÖ÷vÇ¿Û;#½‚3z"W±ÚÀרD£;x(ì·ˆDäÿÙ»óx™ëöã¯Ç…THE„Bº[)QJ*´ïîö”¶[Û}ׯ»¤í›¥Tºï’²¤„$ZHZ(KÙ÷õ8Îç÷Ç5sÏÌÙÏ13ß9gÞÏÇcgæûýÎÌ5œe¾×\ŸëŠ%1DDD$·zä½ü$çRh%MnDO ˆgr¤¸<öžÃ–Ê Äšt>…%1Nê)bɇ}ñÀeÀÉØ’’žÀ`"c/ä9kc'X"@w¬Jjð&ð–˜T"C¤ÔRCDD$‘ÆP¼>í°%‰V;Þ€¸—#Ò— VèöÒÝxŽêXŸ €Ê9?d뱇€¹ØIN%àu¬¿GWàÆÐ¾Ã€±% ×aIù¡Çz[ pHr‘"˜‡M?ª œ%•À)Õ”ÄI¤[°7ÑEõÉIbÔÀ–Œd†nŸ€5 Œv ðÐn>Ç_¡ëÛwãqf¿j…õXuES /ð0K< Á]+™@[ìÿc 6}äb`gè±^Á@ “"‘TÕø«Š XýhP<"²Û”ÄIG»tã»<޹8 y#±KC–D]Ïk™È XuC´µX߇ ¡ËÇÀ¿6XeG~ZaÍûò2«†9ëcñ6µ–¨Ùœz¾¯±­°ÄEÛPœµCõ@Ôã:D¤4è€õ¯U[Ía•U"Rê(‰!""’²sÜ.ÚN.äœ4VPœª›‰MbìId)MerWct#ï ‹ DzJ„RÈsÏŽˆºý)Vrvâò6]ä4àl™Kg¬e*6µ¶TähàlIMøu‰Héµ8h€-)y %0DJ±r…""""¥^ÎBøcŒšØI{Ãáæì5A¤’%ÚïXE}lyH9l’HxÍ{XŸ ˆŒB‘ôQKrÎîÅ–¹M!w¿ Iyjì)""’L{a åi|Èhº=›¤Aèz l ÈŽb<×ëXÆËDzSì[Äû–dYÅß(^|ÅI&|Ddl^“N¦=°±©á†œGí×$iŠUÀ½ ¼,f`•["Rj(‰!""’(£ÈÝPó«¨ëó¸O-lZÆ*l²ÆN,‰ñD žÿàUàrì$ÿ0û—r{gÔõ*Øä‘hÍB_oO` "Rºý…%[›ý€ó€zF$"% å$"""‰ò_`Y î÷6E£66*t м+ Òx>t¿Ã)Ý]œU±ÔÆšvæÔ:¹áˆH)Tkî9¸¨|Fîd³ˆ¤4%1DDDâ)›x±x‘¼û7欗X/‡ÎØ ü–ǕÚWKî$ʼnXuDYXJ1 «\YKéïï!"Á©‹-!¹Ø8 ›d$"¥FiþLFDD$õ¬ÀzY|‹õ¢8›8RëQ˜ªD`ÝóÏ ]Ϲ„ÂëB×§‘;aâ°FŸ""bvݰ¾?ÿôDŠDJ%1DDDâa%öi^7àC¬ â`¬Æ"ìMrI¦bD÷x¨”c_eb«3rŽk‘X°%%á¤r&VéÕM.)%´œDb8ç*áçœ+4æœËpΕ…"f‘ÂÎÆFöý ë~/0› 7ǵrÜnKd©H |žCD¤¬«Žý®¾ë‹q!ZR"RŠ(‰!8çöqνèœ[ìpέrνäœÛ7Çq5œs#€­ÀfçÜTçܱy<^KçÜ4¬MÒºŽ;¶^µjUR^‹ˆHÒ} ¬®®Ã&Šüú:8:Ï=…H³Ï_‰àQšÕ¶órx ‘ŠHiäË€%ÀSØ4¨“‚ HDŠCIŒ4çœ+‡õÏ?t†½sT\ ÂÞ–?‹­ÐÞ Lpεz¼ŠX_ýFÀMÀÕóæÍ«Þ¾}{¶lÉÙ‘ND¤”óXÿ‹3±´íc@{¬"#›Ä&0ʺÊż¨îODŠª0« »ûý1XdP"RTJbHGì-÷ ï}_ïýxïý­Ø[ñ#±UÝ8çN®.÷Þß꽜ŽõŠ^±Ýh ´õÞ?ã½9räÈYóçÏgøðáÉ{U""‰äñØßÿbý0. Ý~ xý…IeqÀÅÀ~ØÒ’ïHDŠHo±ÄocÕÑ> }= ôµ7Ö>îƒðÞûLløßÙιpÿûsϽ÷ËÂǵoßþ¯-Zðæ›o& |‘ÌÎ^ö>>öÚšˆˆÑ]À&l’Ô*lܪˆ¤ÏusKfÀäœÖ´iS¾ýöÛø-"”ÕÀZàlaݵ@  V™Ñ$ÁÏ?Ø\ŒãKT ""eÀ7D"¯^:‡‘ˆ’’‹s®Ö¢n‚÷~zhs-ì×{NëB_ëbo­]^ÇÕªU‹•+W;–‚îS«V-*V,t˜ŠˆHü Þf}€ùXÊ÷{l"I¢µOÂsˆˆ¤‹òXMñp`,6võ%”ÄH1ιòÀ>AÇ!©CI ‰š62KD\µ«"ÖÈ3§peÆ¡cÈ븪U«’™™ÉÎ;©P¡B‘ã©[·n¾û>ÿüs:vìXäÇÉe50±Ç-ZßÝ€/€ØTŒƒ ˆˆ$ÔËØD¤ÿ`UuE›*ÉsðKû}û¤ RCþÇ9w"08ÕÙ{¿4j÷J fw oû;ðy÷çŸR·nÝb%0~ù%ÿßW 4(Öc‰ˆäò3p~1ï³8ëh?ª÷‘Ô16êú ¬SÜñX{{I ±%ëùù1YHjPCpÎuà ê~NõÞ¯ÍqÈrò.–Þ$ø»÷~§sn ¶ô$ƺuëhÔ¨Q±ã:ôÐC‹}‘„ª‰½é]t ""o¯Ÿõ€†(‰‘BBÃææ·ß9§JŒ4£é$‚s®7ð>Öà³c °äÆÎ¹ú9¶, ýr ³j;33ÓMŸ>ÆÇ9r‘ŒÂ–œt ""°ßë±ÚⳂ GD ¦$Fš %%^Ægzï·äsè Àvàö¨û¶ºbCÆMœs= ´ÿ† èÛ·o¼ÃI¾cƒ@DDâêUàEàlIÉc­_’ˆBËIä^ ö™âç\Îýw{ïóÞ¯qÎýxÄ9×ë›Ñ ëÉÿTÔña}5ÞuÎ}TtÎvûí·Ó¦M›D¿‘Ýw(‘¢Õs°foÑÔ]D¤ìy›L2 8x0ÐhD¤JbÈ ,真ðˆU¼÷ƒœs33±‘ªÏzï³£Žñι3K@Æã?>§oß¾-½ˆH¼E¯ºX""’L³VØ(mõÃIiJb¤9ïý+Å<~"…Øyï=ðZèv $†ˆ¤žyyl« üº^Èo‘ˆˆ”¢®ÿ¼‰Õ>3˜pD$ê‰!""éÉû¦5¬ ¶d¤&p@R#‘ Žš#Ì‚‘`(‰!""ée0Ö ÈWå±ÿ3` V13‰q‰ˆH°VbÍ=g?½‚ GDò¦å$""Röe_c î®D“FDD$âQ×gæ@Ÿ`‘¼©CDDʾqÀÉÀb '¶d¤ ‰ˆH*z hLö 4Ƀ*1DD¤lZ œ ¼tǴݼ<œ ìT€""’r²°d÷ûÀ!Ç""yRCDDÊ–_ZXsÎMXÿ‹WG€ŽÀ-À1ÀçØÚçK‰RDDRQx鈾À¦”DìR ”–“ˆˆHÙr.ð/¬qç ¬òb6pZ&4:‘´§$†ˆˆ$ßÀÕÀmÀœÐ×ûÈ?‰!""’,ÿºd°8 ø8 è 4Klh"¢å$""’lŸaŸXµ¦¯bk‰•À‘Ò¢.–À˜tΆcU…}€‚ M¤¬SCDD’ë=àHà 쓬ÞÀ@#)™ý€€W€, 0e‰$~¼DD$¹ž&`ÕM€Oƒ GDD¤ÄÄšzž--™´>~ ,2‘2KI IŽuÀyÀ\ ðð4Ð&È DDDâ`°K`4ê1º£ŒI¤ŒRCDD’c3ð'Öµý:,©q)P+È DDDâ 0«0|è Ü Üü…5‘¸PCDDop 0ûtê  [ ‰ˆˆÄßfààaàn`Öøóª ƒ)[4bUDD¯7–6ˆ ëŒ>•‘²§ðPX‹ý½Û |ÚŸ T &4‘²B•""’x÷›°Fg}±O¦ˆˆH‚Ô}톟ԞŽv–HY¡$†ˆˆ$N6ð pð¶Nx"ªÂ‘²ï ìo^màQàVà |A‰”~JbˆˆHâLÁ>uê ,Â>•šƒ%5DDDʲ£À¬?ÆÛØ”®EØ8ÖÁ…&Rš)‰!""‰Ó˜Žk†ujwA$""’d­€‘À9À| ÖT½1DJD=ED$1Þš퀩ظåF$""’|ái\ó€NXRã.`6ð^h•÷YÅ>ôàè¨-ßàÕMC”Ä‘D™\œ<ô 6‘@ÍŽþ%øÃËóèÓB—°šX[l‘´§å$""’/c󱊌o‚ GDD$P=±ªD‰ìý‰ˆH|Í:³€c€¯€@› ƒIJê‹ì%1DD$¾ª•#˰>=ÐH9€¿€Hé¦$†ˆˆÄÏz 10øø¸1ЈDDDR_"Ý ›ˆHêScO‰ì N¾6”ˆˆH)ð1º¾8È@DRŸ*1DD$>ð.Ph ôÖ5‚ JDD¤ðQ×õwS¤@JbˆˆÈîÛ|†}š4ø È2(‘R¢9P¨U4ŠH¾”Ä‘Ý788˜t&û”ˆˆH)ñ°X |›çj ¢$†ˆˆì¾s™À6 ðP°áˆˆˆ”1ƒ@$U(‰!""»çßÀ4àpl"Éû@Ë@#)k¶âè…SE†ˆ’""RrÙØò‘ãÞÀBàtlY‰ˆˆˆÄKà ì#‘´¦$†ˆˆ”\9ì-Õ `Ö˜lI ‰ˆˆ¤¶Ã±å—]61¨ TnzàùÇÁ8-/‘ô¥$†ˆˆ”Ì»XÆ×À‘X#ÏIÀÆ$""’êP¹KuváÙ ܆%0Æáh‰-â¼.ˆÐER’""R2GacàŽ.þÀÞj‰ˆˆH|x&-¢Ÿ….OáÈÀQ)ÐøD $†ˆˆßB !ð6ð%–Àx&È€DDDÊ(Ï|µ±äÅçÀE¡=o/—H@ÔÝVDDŠgð7l”êãXõÅ—@fA‰ˆˆ”ažµ8®F…¶¼ tN ,&‘€¨CDDЧðPë…q°M°I$Ï€]Àë@'à$ÖÈóI j€1‰ˆˆ¤ Çñ5ðp(qðð!ÐW¤Gùž ŒR$¡”Ä‘¢›\ œ€-%麈ˆˆHrxžÀÑëNU8't)ŠšX ¥H©¤å$""Rt}€9ØÔú6X"CDDD‚Ð x-è D’MI )œ.Åú¢ |ŒCíÄDDD‚â : ‘dSCDD ·(œt~:‡”ˆˆˆˆ¤%1DD¤`;=€W€™Ø(Õ£MA%"""ùðQ×…H‚(‰!""€R„U^Lf{“ˆˆˆä':‰Ñ8°(DDI )Ø@K¬ÿÅ™À<ô–HDD$u­º¾9°(DDI Éßt >Öûü;`+09È€DDD¤_ìÐõœÚá„Ã%7,‘øÈ:IQ«€NØ’ÁXŒ Ä©ŠˆˆHª9=êz§<ö?ü†Çã…Í+ê_÷Çð¼TäHí¡E>vàÕ6\ ¦$†ˆˆäm?l ÉÀ±À¹XEF¥c‘ÝÕ èºÞ‘âu¹Ú»˜ÏU hZŒãwóñ% i9‰ˆˆä6 4FÓ€ýQCDD¤ô{Ï8NªŒHq©CDDr› ü8x [Jrt ‰ˆˆHnYØТª ÂQx$tß:‰L$QT‰!""¹õÖ’ÔÚ GDDDòà™§N1.5ð,šcõ•Ër<âÀ/XŒ©À–D=X ̾HÀãK§$†ˆˆDlÂVÇŽþƒ--é`L"""_ž9À!X%G´L à°zÌœûãád öNãà<¾”qJbˆˆH„ZgÙ@{¬É§ˆˆˆ”žì<¶uÝ{$à™«D]×R)6%1DDÄlÄÚ{=…“V΂<ß∈ˆHY´5êú& |ŽýÍŠùxyoFWwŒ)æã‰¨±§ˆˆ„ÜÌÂyž ŒÖ£t·ˆˆHú¨ƒ-))GÞ£WÿÄÑ8 Ø\„ÇkǶ `WèÒ¥„qJSCDDÌ#À¿°þ]€Á@“@#‘ä*Oîê‹h+›ü<"ùÒçk""ïµç€°U°+HDDDROpdÐAHzS%†ˆHºû¸ëþ8Ð ø(ЈDDD$5u"±ç.-e„’""é®ð+p76‘ä,`$z!""Rö ¤è3Èp0¿¶k^;¿Ì&¶â¶h5¯Çk tǦ Œ&#vISJbˆˆ¤³a@%à\àUàFàk”À)ÀóÏÃé§Cýúv;+ { nº *W66‘bñ¼_¬ã/oåØ: KF”Ç&älY0 Ïóy<–&/ñlÀQG]¼µJþÔCD$-.޾Ú׈Hj{øa¸öZ8î8˜4 ~úÉýûC°}{Њ$ÏsðúþDštfåØŸ™Ïcy ž;-8®wÕ†Èÿ(‰!"’®<¶„d.pp<0:ЈDDRÚÃÃwÚõÅ‹áÄ¡E ?Þ¶Ÿ;‘±t)L™û8Ó¦Áï¿'%d‘d¨’ãöÊ"ßÓãqtÂ:t ž®_hR)‰!"’ŽæÍ÷€†ÀlI×cIa‹Ã~ÜøñðÞ{v}éRKtœz*Œgɉ¡KèØQ‰ )3¶?ËϚżÿ&ìI#<ÿ²p\ߥ,QCD$ít.ÂZsMǦU2(‘ÔÕ Œ å y÷üÀEY£cG˜?¶nµDF•*pÒI°y3üñGl"cãFøå—ØÇúõWX¿>/F$¾öê'ûëÞžoñô6Ḡ˜ Áq@¼•²AI ‘t³ë>+Þlô4"‘”—™ ¿ý{îYðqýC¿Oûô >ö?à²Ë,Ñ¥ tèsæØ¾¹síöÉ'+‘!iÀQ ˜ƒ½;y«ÊXlP’ª”ÄI7—-±þaKIÆ‘ˆH`/.Ú¶~ýàšk`Æ‚oùrû:tháUUªÀSOYcÆ X³Ž8jքÕ+aÖ,%2$-œ4Æf¦Ý¬Å±G&Žíy\ ùI”²LI ‘tóààlàd¬x¥@# ĸqд) Ù6x°m£Ññ5EœÜtƶ”¤bEÈÎkŽC”ªU­Q茑mÙÙ–(‰¾ï¬YлwÑž_$ ~fã²°Y[ÔZ{WR ›tR!êvôEÃŒÓXFЈˆH’lÃÚf]< \}Ö¡¿"’†Æ³I";vÀ-·À!¶=\…ѽ;<ÿ<\}µÝ®]Û’[·ü¸K—ÚØUç ¡vm0&O¶>ù©RÑÐIIž{{ƒCÒ—*1DDÒÅl /И4Ū2 2(‘ä›1#’À[¼8÷2’뮋TIÔ­ ï¾»D¤qch×Ê—·Û+ÂÈ‘¶+<Ž[o…®] >FD¤,P#Áœsgou¼÷Wxï?ôÞÿæ½ß>Î{¿Ë{ÿƒ÷þiïý‰À!À`¶sn7 ‹cçÜcÀT »€C[½÷—å¸ÜuLìsß¶Þûg¼÷#GŽ9kþüù ><¯BDr™…u>z«¾8x-È€D$?óçÛòŠqãìùAƒàÆáŒ3`øp8ÿü²“ÈÒ²eVõ1c IdœrŠ% /¶ë`û H^lµjA«V±Ûš7‡:uì{£e®ÙX‡–¸ØDDR‰óÉZx˜¦œs€UÞû•!8çš¿zïwÅ7²˜ç¸ x$tùx 8Â{]aQø ¸×{ÿpõ°Â{rÔæq-[¶ìR³fM¦L™R¤˜† B¿~ýÐ÷§H ylQØf` pnÔvuªI)áÆÒ¥׳'ŒQøDTwÍ5ð ùïoÕʦÄ»«Vf¦%0~üöØÞ:w¶žçž™’™ ï¼c0©båJhÑÖ­Ëÿ˜¶maÚ´Øé'Rª . :ˆ¤qtÆã;ðTpÎí†yïû$$6I9ªÄH0ïýÂ’&0B÷ÿ%‘ Œ/€C¼÷÷ù}ÎÓûLw£s®¿sn¢sîuçÜqáB#ͰÏ|c4mڔŋ' tɓÖŽô.ŽÃR”J`ˆ¤œ_…Õ« ?îÇaÆÄÇ“h…-‹Øg:Ô¦qÄSÅŠpóÍP¾öóÏ­ÄÇÛdxºòJkžyÁVé±ï¾ñ}üD(WΖµôè;vä\ݺÖCã¿ÿ…?þ€{ïM^Œ"»Í³–È4’ιòäõ.ÆQ'ÁQIŠR%F’9ç÷‡nŸ <~_ ŒvÎ¥âäÍÀ'@ï}WïýiÀÀRàIçÜ>XsR€­9ï\µjU233ÙYÐ,³<Ô­[7ßË´iš´$’§9ØHÕðdF#à4a]$EmÞl} rðÁ‘c¦O‡­¹þÒ¦® ¬ŸÃÀwØí‚d‡ºs]tÑî?·÷¶4%ÚÙgØ1ðî»püñ»ÿÉе«ýûUªd•"ƒÃW_Áµ×Úþzõ ZµHSØý’Ö^$)Vp)\hUb$ßýÀ2àÎÐí>ÀZl^ÀØÖ“€ÑD—ïýÛÀÛ9¶-sν.eO=P3çýÿüóOêÖ­K…b[ÿå—_òÝ× Aƒb=–HÚ8 X€ý¶é\<hD"’÷Þƒ¾}aÛ¶‚[°ÀNP÷ØÃOfgÃÂM7%'Î’úë/8ïü°dûÞ{v"Þ©“Ulßn¥ðç—^Z¶;vÀ]wA³fÖÏ Ì9«.pÎ8‚Mç¸ë.«(Zéݽ»Mø1T4}þù±5FŒ°Šƒ¯¿Î=b33“¤øïmòÈ»ïZD˜sðÜs–°™=ªVµ$ţƞp_q…]öÜ3öqï¿?R}2p`ì}žz Ž<Ò¾FŒ°ï¡o¿µª•“N²ÆäÉV¡rå•VRV9gU,a¿üçœcß[¶D¾¿DDJ=ï½.I¼í€XïÜÚþ@èöDÀße¡8ϱ½05´ïk`ÄN©þ |äœówÜq‡/ŽÁƒ{ûö‘"yÂ{_×{ÿ²÷~WhÛºà‘²cçNï{öôÞ>ËÎûR±¢÷£FïqGŽô>#£àǽðBï³²óº’-+Ëû–-íuU¯îýбûß{Ïû™3½ïØ1vߨQöï¿c‡ÝÞ¹Ó¶ýòKìý:Ê»Q£Øíóç{_£†÷W]åý‚ñ]Ѷm³çï÷Ü3÷k,©)S¼o×Îû.]b·ÏŸï}ùòí³÷?üàý²eeç{©(²³½?âˆÈ¿Ão‘”Àk>Î]JÃØ :]’wQN6ɼ÷ß` WÇ{ïŸ íú¸èêC?ùk3ÛÔ{¿è\Í>X Ü´óÞÿuœÎÄ^ßFàÏÇ|΀’½HºêƒRí ´¦Gw‘â{úéȆüdfZÉúÚµE{Ì+àâ‹c—äåÍ7­J¡´Š^&S¾¼õu«*¸ë®ØcÏ9Z·¶¥ Ñ“Jºw·eáꊌ ÛÝ2ê?¬±#äžè1|8lØ`Ë8¶Ç¹?ÎŒ¶\#¬råHƦMVµÇoÏ5bDìöîÝ ¯ÖY³Æ*{êÔ‰Tµ¤çà7¬ ê½÷æþ¾x÷]«Ð)”ÄH0çÜqιKsÿ{Kâ½ÿÝ{ÿ’÷~jÔ¶qÞûg½5® Œ÷þ{ïý}ÞûUyìÛå½Õ{¥÷þBïýãÞû\oY½yÍ{¹÷þâ›o¾yy9Õ0Š$ÎgØp±¿m€oH’`åÊÜË/ŽÿóÜpCá%øåÊY¡ví¢=fݺv‚½, /=zX?…ÒfÑ"[zÓ¼¹Ì‡|²-¿ëÙÝcwx üö›¬^rIì¾pƒÌ6m,ž°ï¾ƒã޳$Ñ–-¹sÅŠÜI¦èï¯;î€cޱå›7G¶Ÿz*œu–%eî¿÷^W4ç fÔì³ß·Æ…©RÞ~;=—R´h3gæþxþyë[Ò¥ üùg0±‰ˆìŽ4ü•žtհʆåιYι‡ssÅ›5*"’—Ø$’¦À ð2pKAI¢-]jŸN÷êIdLj£ï»/¾Ï•‘=Vð1Û§âÅѳ'4mZð1ƒ•ÎOÏ¿ÿÞª'V­‚ˆÝ7h¼ü²UD÷/Ø]ÃÿýŸ} ûê«È‰~ÎäÆoÀ´ipÍ5V©í÷ß-Aqþù‘DÆÄ‰ÖÏãá‡íöa‡ÙB…eËrŸ$¿ý¶Uïì¿Ü^^.{íe±g2g¯[7k$š®öÞ;6Y8nœ%&~þ96É&"RZ(‰‘`ÞûñÞû†@s`¶”d °Þ97Ê9ws®ø³GEDÀºÕüˆµÜí ¬/ðRÊ-] 'žh'§~heòÍ›CÇŽö‰øý÷Ç?‘±qcÁû³ó˜©ýé|ÎmK–ØÉåÂ…?n¸™cªó>¶4ÿ¬³ìÿàÉ'­J"¬iS«^HFe@›66é¥[7[R–•YšqÒI±É†wßµi)üa‰ˆýö³äEçÎ6æõÎ;-‘qÉ%–Hxâ ;!ËÙX4jÖ´çý׿ >nëÖÄÇRZ¬XaB»vY…ʇZˆHi£$F’xïçzï‡xïOÅVªŸƒÍ¸XàœûÍ9÷”s®›sn@ƒ‘Òa*ð¶”ä_À¯À¨F¶ys$öçŸ6… º7Àý÷Ã!ñyÎ5kàòË >fÁ‚HâäÃmŠF»v±É”3 Q£Èþ1c ŸšqÑE¹Ç¯¦š/¿´I=»}ð`KTdf–|zËîªTÉNZ?úÈ’aŸ|bcK!¶BcÃK°D'Ö¯‡¹scUwÞ /¼Ï>k•]»Bµj‰}-y™3Ç&œdܸÂ{º¤‹ºumºK… V‰{ –ØúôÓàb)%1à½ßî½ÿÄ{‹÷þ0 0¨ ¼ ü;ÐE¤tXƒýæ8x¨Ä©‘ž¤¦jÕlLdaêÔ±~ñðá‡ð믅÷ V%rÍ56uî\K¦´haŸâ¼%Dzõ²“©¢,ùé'Kv¤*ïáúë­©æ£Âòå‘}‡À”)pë­ÁŘ—fÍ «à9ë¬Èöòå‹6޵AëíÑ¢…%>üж%Û‹/¾";ÛþoòªJG×^kIÐsΉÝ~ÝuösúÿL\""Å¡$F’9çîtÎ]蜫Þæ½_â½ê½ï ì–ˆˆäÏgós±å$J`”i»vÁgŸY‚¢U«‚}öYhØ0>Ï{å•v2­B…Ø%uêØTgŸµ’õh?ýdŸð†—[dfÚ‰ÔÍ7ÇW¹²U D¯ß<8÷T… EÏs.Rñ²eKî‰#wÜYr‘J:ÈþOú öˆªý9²h+† ƒúõízô””d2Ħℕ/oÉ”è^#­[ÃØ±éÙØ3?9N<™4ztü§ØˆˆÄ›~¥'_ `8°Æ97Á9w½s®~xghÈÊüï."ioÖ]gPx˜‹-N“”–×§ÁEý„øÊ+mºÅÍ7[³¾‚uJHQõïMšDnïÜi{öŒ$0jÖ„§žÊÿ1¢í½·UlÜr‹Ýï m©Ìʕ֓!#Ã9AòÞNÞ[´€uë"Û;v´°ØWåšíUzÔ«:ÞÓ¢F‚÷'Kùòörá…°Ï>0k–õòX¾Ü’­[[­––ØåküxûùK|~ô‘%EDR™’I潿hÜ da+Ú—8ç¾qÎÝíœ;,ÈøD¤ÈÀšxþhŒÄ–“HÊÊÌ´Òýðt°“ö¶mmÝ~XV–õ,xòÉØûŸvš}ݸі(ä¶, 8æÎ…ÿüÇ)y¤5‹1¦O·Oã+T°e$°çž?ÖˆpðÁ6õdæL; //éÙÓz|\yel³È Mž —]f1ýóŸ±û}.¸ÀöE÷ž(mºvµïèþ*yéß?÷øÕ „_™BR«–%/”À(ÜñÇÃgXbj̘Øïßµk‹6ÆVD$Ùœ®‹”¤ 5ñ<è\ TzyïËRªq@—âÜaÈ!ôë×}Šä°K?×6ï?ÕŒK ”™i½  ;t°®#ìSýJ•àƒì$ò¦›,Q±¢}ªþÔ{Ëû´yÛ¶¢=ç}÷>¹!–,±×QPbÑ¢¼—»ìÜi¥ÿo¾ieíM›Ú§ë©¢KK0•/ocT[´:¢øÚ°Á–šä¹š—‡†ü#ñ1Iâeg[ß›è¥AÛ·[UÎo¿Á¨Qpì±ÁŗƆ—DiàœÛ óÞ÷ :IUbÄ9WÎ9׸¸[Õ^X¬ 26IaOÕ€GßP#–.µ“ï¥K#Û¦NµäÃî¬Ï™ÀûTÿé§#Ëvì°FœãÆA÷î‘ûEßg=¬£bÅ›bvê·ß^ò˜wÇòå0o^ÁÇä79eð`8óL[–±m›% ‚¨ÆØ²Å’@9›>ö˜ýÛïÚÿ.ƒ-¸kÔ°ª†Â–“tébÉ6)Ê•‹M`x_ _}eÕÇ›ˆH^”ÄH2çÜιQÀ:à[àŸ@6Ö’¯¥÷¾Ž÷~R€!ŠH*û? ?¡ßÀ$ RAw¢XºÔF—ŽmÚXÓÃÛn³ï½g †’&2–/‡Å‹ ?.\xÖ±c¤§ÅÈ‘±Ç l‰>²êúõ­Œ~ÅŠHSÉN, PµjÉâÍ);Û*CŠ’LX±ÂNp [ŽðÄð꫹·Ÿ{®5ˬVÍú|ôrHÉâÞ]d“UzȪFÂZ´°i$wÜa“1Ê¢îÝíû®bE«Ê˜9Óþ_ÃVºt±ª!õM(»î¹'2–öÄíçUD$¥xïuIâ;åðÀ2¬/F• cJÂeœ/¦Áƒ{ûö‘ÿæ½ßº¾Ú{­÷þãàÂ)+–,ñþàƒ½·4Bþ—.]¼ß¶Íûo¾ñþÝw½3&öq òþÀ½ßwߨíãÇþØàýàÁ‘ûüýïÞW­êýä÷„ Þ/Z¹½p¡÷½zy߸±÷ýúíî¿JÄ×_Gb<à‹mÉ’ü0 ð×Ú¾½÷ý•÷ý?ùÄû-[âI̘á½skÏžÁÆ”qãrÿ?¿ñ†ý HÙ6s¦÷õêyß´©÷ë×Çî[´Èû¬¬@ÂJG¯ùàßÃ—Š °tº$ï¢JŒä»ŒUb V9çF;çnrΕ±Õµ"R, €·ó¹¼ôêc Ð>NN $Ò2eáB«–(ÌܹVZ}Ûmл7<ø`ìþÌL›Œ°zulߊ¢6z<úèÈõ°ÇyóÍü?ùäH_‰ÛnƒÆíô ì~ñj¼øÅ‘ëK–ÀË/\åÑ¿d$ë¾ûZLS§Z…@ûöÖ÷¢Zµ¼ïß¹süªHŠbÉ¸âŠØ©"GíúÞ{0cFòâI]ºDƨ†]t‘*0ÒAëÖÖ°wÌ›¶l™ýüvë›6ŸˆHFФïýÀι:XSÏÎÀ­ÀçÜ*à<¯%%"égÖ%§0/†.MP#N8ÁlžyfþÇT¬“&ÙIݾûÚ¶Õ«c©S'r}ÍhÐÀ®‡/̰a‘DFq'\~xd9J›6¶ž=+Ë&…ì®[nÓO·~ãÆYÿŽÂ&>ôïÕ«Û‰ðÁÛ¶ñã-ñsë­ù'0ò²b…%N<±ä¯!? Ú‘mÛìß*z‰ÈÃ×_Ú2víâÿÜ"©ì€booÞl¿–-³ËÿkÓzDD‚ $F€¼÷+sc±¨, '°P;ÐÀD¤tpAPvœpBÁû<0RõN0”ÄX½:’ĨRÅú<„“ ùyþùHÏ…â:ë,;Ù¾ðBhÞ¼ø÷/L“&véÛ·è÷Éù:22Š>-eÃçúÖ[–<ª]Ûªe khZ\Y’±c­Â䆠U+ÛW¯žMf(§šUIsÙÙpÞyÖhàºë”À‘`éOs’9çÊ;çŽqÎÝ¬Þš€Ã|Ù¯*"’òÆŽ-xÿâÅ‘©%áÊŠ¬, Ö¬™œ|üq;«QþùÏÂc¨_ß*J¢Z5«rHD#cÇBŸ>0q¢@­^»¬¥¤¦Mƒgž‰Ý6x0T¨`Ïn^¦†ˆýœt’}=í4kômÅŠ`â‘ô¥?ÏÉ7˜ÜŽS½ ¨ç½?Ö{ÿ°÷>€ar""éë“O"ýò³c‡-gX±úõ³ÒêÍ›íä7¬aC[‚Я_ll\g¸ ¡B[nñÆж­m«_>ÿ<²ô"Â=:vÇ;ïÀ”)±ÉšdèÞÝúbì·Üx£zìØq÷sà@8î8ûÿùí·Èö¦Mí9Z·†»ïÞ½ç)«úõ³ådo¿[5mšõãyôÑàb‘ô£å$É÷1ð 0Á{¿­°ƒE$Uva‹ÍÎþ\8eÕØ±ö)|a4€½ö*yÓÉûî³%G ]»Ú¶Þ½íúöÛã—Àøî;xí5ëóÑ´©õu(‰ìl[^±v-ì¹§}Û·ïî'Šb=¬±`óæñ[BrÊ)¶äfçN«¸øðÃȾ´¾'ª¼É_çα·,°^BÛ¶Y°[·²S &"©M®“Ì{ÿ¤÷þC`çÜUιGC“INpΣՙˆ”yÙD`š%î ‚sÏÜ.WÎNrûô‰TZtê£GïþÔŒ{î‰$0ÀNœ_x!~ ŒÇ³$ÉÓOúuö)itÕAqüü3¬_o×7m‚÷ßà‘h-[–<á½}b^Viqé¥v}ôh˜0!²¯re%0DŠãÏ?-i±v­Ý~î9%0D$yô';ιû€Ÿ±¡‰·C€ÉÀιF†&"©&ú·´j·¢|y;á½þzkÀùüó–Øxé%+Ÿ>ýôø$0’áÔSíkùò–,1"÷”¢jÑÂ’o¾iÓNêÕËýIl2Ík¯§0[·Â1ÇÀùçÛ²h>hýCÎ<ÓJàE¤d*Tˆ$_û÷‡+¯ŒÝ¿aCòc‘ô¡$F’9çÎþŒZ{u€îØ)Êdç\•à"‘”±£Zhl8eÉ“OÚxÍhO? ?þÿ{d[¼*0’¥ys›²±d‰-“9ï<«2(©Úµá‚ àõ×m¬ba£UãmåJxä«¢hÖ .¿Ü>.HÕª‘É0o½3fDöÕ­ ¿ü|`“ID¤dªUƒQ£àÕWᡇb÷½ü2z(|ûm0±‰HÙ§$Fò],.ñÞÏñÞoõÞ¯òÞ®êÇ¡ˆ¤Ž¹ÀŸÀ7¡‹ì¶§ž‚›n²&³gÇî+ åÐW\a'ëeÁ¼y6ñ%<ÚqÇ›X61l=~Îé2Û ‹ˆÄ›’É×xÑ{¿+çïý$`p\²ƒI«WÛו+¡C›¾Q–mÜS§ï>³fYÊ–-‰‰©¨N8ÁFÚ–+g Eçχsαõ÷MšØ'À‡ gœ?ý¹_ÆpË-Ösä„’?]E$Í›={ÚÏ[¥JV U±bÐQ‰HY¤$Fò­:äµÃ9w Ð8tŒˆˆ$ÀÀ!öéáÆ0lXÐÅßΖ„èÝêÔ±¾Û·ýþÏ>kcN÷ÞÛú` š¸X òå—–HÉΆI“,étÐApÝuV•qß}¶Ìe×.íÎ;-±1p N¤D’᯿¬W†s6!©}ûȾìlû9‰%1’o,ÐÅ9wzôFç\UàaÀƒLD´˜ìº„gÕŠÚ–ó²OòÃ,­²²boßt n'êÏ?LL‰4j”½¶‘#-y±q£%5ŠêóÏíëŽðé§‘ÛÉôãÖ¨4ºdÙ2øý÷Èíè ‹ b_cµjñ›ú""…;òHüâ‹Ö'Z¿~ö;ióæ`b‘²%#èÒÐ@àT`´sîg`Öܳ%vJr»÷þ×ã‘D˜ ô)`ÿ&`%PH’bÐ0á‘•yóæÙ(À¡CmIBØØ¥,:ã ¨YÓNòÏ:˦‹œtRÑï?{¶%.ƳË)§$.Öüz¨MXyb;öX˜322‚_þ"’î:ÈÆSG{â k¦ pà V¥!"²;”ÄH2ïý_ι¿7€C-À$àIï}1W.‹H©°ëxS˜M¡KE”ÀˆƒíÛíDø÷ßíë›oZO…²®R%K>vì±Gñï¿Ç¶åôPÍ`tcÌdÉȰ‘ªcÆØØÔüŒ3gBË–°×^É‹OD ÷ÁÖŸ¬©nÎI&""%¡å$Iæœë´öÞöÞŸá½oâ½oí½ï­†ˆH|U® XO„;¬GDº| xÔQ%K`ä%zú@2ed>"¶jU[{¯†HêY°À’ Õ«ÃÇC½z‘}ÙÙÁÅ%"¥›’É÷ ðrÐAˆHŠ è¤±,:ï<{ó\½º]Ú´ :¢`,[¯¼Rð1›6Á«¯Âòåɉ©0Ï=ë×|Ì}÷%%hÿÜ· IDAT)[o…wÞ±K«V‘í;v@§N6òZD¤¸´œ$ù ©Ù"m5ØÒ‘JÀO.E³s'|ñ…½A;ùd›n±ukìé²nëVë)ñÆ0q¢}òyôÑмyÞÇO˜W\a×[µ²%8÷ÜcÉŸd›5 ®¿¾ðã9ºtI|L"R|½zåÞvÅ0y²]jÔ°¾=""E¥JŒä»¨êœ{Ó9×Û9w”sA)"Ið°˜RÊqâ=\~¹}æ™Ø}mÚÄŽüK+VÀ¥—Ú„‘péöرù=…ä‡ॗl¹FZ·.Úúù›oVC¤4ùç?á­·ìzûö¶ÌOD¤8ô¶9ùžš….ùõÅ?x'i‰HÉ-¢h?­yÍŠÉÂø„“îæÎµFrÙÙÖõjû¤>]5n Ç¿þ 矗\bÉü<ù¤}"žJrÐAP¾|òâÍ©ûzçv²óúëP¡\{-|ô‘%0.>)ž¬,øê+»~ðÁöûºR¥`c‘ÒÇù Zާ1ç\W N!‡MòÞÿž„p’eP¬ÏɆ B¿~ýÐ÷§¤¼qØÐä’ø0#tý`IÔ¾JÀö݈+}óT]³ÆnkË"ÒÕ²e°ß~Ö$³¸¼®©g´#àÌ3#U!YY6iæÒKƒKDŠ/+ ¹š4‰l_±Î>ž?<¸øRÈ0ಠƒ( œs;aÞû‚†ÙK¢JŒsÎí lóÞïðÞ+äø:ÀÞɈMD6¨º¾"È@Ê–ví`êT[bУGz'0öß¿ä÷M…XI´Œ %0DJ«Œ xüñØm[·B÷îðí·V=6s¦UjˆˆäE=1¯ð–s®Ð‚\ç\`6Ð"áQ‰HðvF]²Ž¥ûøc¸ûîØmMšØ›áœo”~þÙþ½6oŽÝþÞ{ðÛoÁÄ$"éË{[Æöí·v»W/K`  ¿ÿ9n×.8ÐÉ4w. »mÜ8K–‹H0T‰‘x»€žÀ0çÜ%Þû\S±s€‡€[±É%K“¢ˆÄÕÞXb"Ø\ȱ²[¦M³7¼Û¶Yÿ‹çŸôpØ[5mÿ³};¼ð‚õ”˜9Ó¶zhd"ÀæÍpÁ6Ù¥Q#«^¹áhÖ,¸˜E$=,ZdSJÀš2¿ð ýúA½zö{«Në3z4|ò‰}MFÓá¹sáÄaÕ*øã8ç˜2n¼*V´åŠéÖ0Z$¨#Á¼÷s€À…À‹ÎÅç:ç_·?Gyï¿Lz "?€MX£AÀ±”qõêE–K  çž ;vS*ÊȰIá†s0{vdÿ´i–ÀX¸ž}Ö­K~œ"’~5²fŸ=zXEØÓO[`ùr‘Ý¢…%.ÀÆEŸqFâ+2 Œ•+­Zäž{,±{õÕ™iÉßSOUE†HT‰‘Þûþ¡ÜÅ?€mÀιó€€êÀãÀ]Þ{½ý)ívE]ßc_ðTC¿‹¤aC{Ùµ+|ÿ½-•زEÝîsÊÈ€ /´“€K.‹.²É#a§œóçÛÒœ1c,ÁqÌ1ÁÅ+"éåCàý÷­¹ç½÷~üÿ³wßaRV×Ç¿—"(Š€ŒĆX# ±aKì¢þ¬±GcÁ‚AchÄhl(6,؂ذ`Gƒ lQ,QA^ïï;ëì.ËØÙòý<Ï<óÎûÞ™9«[Μ{Î /À¹ç¥=a¼ñF:>þølUÞÔ©ðÈ#éx¯½àW¿Ê>ç_ÿJF·ÙºuËžÿ÷¿SS¦”Çœ9pöÙðÖ[eÇ,©òøkrŽNdd:è¶Ž%Í#èc›Ïø$U¢5È&/~ ¼_èZ}à¤\T»Ìœ «¯žýµMxñÅ4vóšk U«¼†Wm]qEé=B6ÚN?=Ý–,ÉïhUIuS»vi‹Æ®»¦QÙ¥y碟~:m90 û=ì‹/ÒÏH¯]8‰qê©©zï¼óŠ&1þþ÷T¹Ö¤IÚŽ·"k¯ ?^®/MR%2‰‘CÅ*2îN‰1ÿ¬VRM¶0 ˜Kê‹¡J3gNªhÛxš6Mç[¶„{ïÍolÕ]Á«òX™q¬’TzôH[¿­âÚµK=;VdÀ€ôóHRnùkJŽKd|dCª…ž+tì¿ðJuÑEðöÛéx=Òöˆ5ÖÈoL5QŒiÎðái$mçΰãŽ&/$åßÒ¥¥W?äÒüù¥_ÿøãÜÄ!©({æAŒñ|R³ÏËCçä;I«`v¾¨[NÖ ýÞ§OÙ%Ç*ê²ËÒÖ‘]vIûÁO<zöLeÑ£Få;BIuÕÒ¥pôÑð㥯 !%c kÐ õCjÜ8]/P¯^ö|ñmrMš¤ó =ߨQ:÷Ã¥ÇñÔSi*–¤Ü2‰QÅB[†f¿‘Ým ×ÈgÌ’*ÀJ‹œZ}õ´§yÿýÓ/©çœ“~AUù½ür¶<ºaÃì¿Y³àá‡í´/)fÌ€7ß,{]ýú0p`Ñs眓*8,(ÚÜy‡²çûô)úœ™3ÓùÁƒ‹žüñÔG£x¢¤¸S/Ž'ŸL½„$内£Uo.ðFŸã`;©&ø ØØø/©*c#`ÍRžãÔŒ >öÝ7»m¤qc9ÆŽM£÷T1G “&¥¤ÅSOÁæ›§_À/¼0}ú¹ÓNùŽPR]ÕºuúÞÞ»wš˜i’RëÖ©‘çÒ¥©ââ¾ûÒÏ…ª4th==lXzܼ9tí ~˜’-bØ}÷4òµ¸ÿ~§cI¹`£ŠÅ¿úæ;I•ì#`àr`<©çe@ O)ÏS…üóŸpÒI°ÅéÓ®õÖKçë×7±²Þ{/1„ô©ä¦›¦z?ÿœÎ ”¶—´n·%ÕaíÛgW^™Î—’·ÞšÎWµ²#\_y%umÝæÍƒ½÷N•Ç“ïÛoÓv=IUË$F !4‰1®t{¢B#`qŒe´IÊ©Í;€€7;II Uª_L÷~˜*ÆŒNòR6hPút±À’%0qbÑ5|Do¿ö…KR®µoãǧÉSzôH[árÙ̹ ‘1{6´h‘Î5k–ªØ>ý¶Û.õejÑ"ý|2!å†;‰«ÞQ!„ÇB+ò¤| øk¤”K§Vpë¬AÚ62Xx¸0ÖË–¥O¶ ›;7í51N9%ûúëì'rZ9‡k–¶Ý)㤓L`Hʯ Œù˜FB6Q iSØzk¸ôÒ”ÔxòÉ´Õ¤@ŒÙê6I•Ï$FÕ»xBB8"„Т¤…!„z!„­B—_W'Ææ0^Iߟ¬àö)0 ø"óx2°)¡¡ [¶ þïÿ`Ï=aΜtnÖ,øÍoàÈ~ 6hPªÄ° üªéÔ ®¾ºô5={¦†v’¤Ò]tQê1Ô³gÑó§ž Ý»§í%’*ŸÛIªXŒq1pYa$p)©Q/„ð=ðMæÖhtZÓ€ëÄà(UwHÕª‚Æw¤Çl>Ùz饴͡ Cý?þ‘>íºà«*Ã:ë¬ÚuIRV‡EŸwÜtS:>þø4Q«¦z1²¬¬sR¾Y‰‘#1Ɖ1ÆÃ€ ³IÕ-€]€­HmG{bŒW™ÀT›pB6i2ÆóÏS7lœvZ:6±êæÍK ¡Ò<ù$¼ûnn⑤Úä/k®IÇn·Ý–ßx*"êÃCàÊBçZ¯‡@Z©Jåg%FŽÅ¿þ–¹Iª ~ ´&’†&/œ¿ÊvÚ n¿=í^‘RI®*ÇÀi:Ii.„ý÷‡Ï>3q$I±í¶©GÆkÀsÏÁºëæ;¢òÉ$0îÏ<îü4s­ p_#å-H©+1$©,SqÀ `!Ð0¿áÔÇ Púš~ýàðÃsO]0htëVúšæÍSCU’T1{î Ï>›nl=¿h|pÕZÝO`dìHJ`@úàû>+2T]˜Ä¤²oÅkÿ‹Jq×]ðôÓ¥¯Ùe—ÜÄRW´l™Ævë–ª\.º4HU1묓£G§Q†’¤ŠÛi'è\h&áÒ¥i2ÔCAß¾ðÖ[ù‹m…| 0‰!IeÙ¨Ÿ9n”0öM÷ùçi”jin¹%;µD•£ ‘ñàƒ0xpú¥ú°ÃRCÕ§Ÿ6!I•%ÆTuøè£éñvÛÁ–[æ7¦âbd)p4ðYK¯r;‰ª “’TØ4Ò–‘ÂÆ’’õ2×>ÉuPµCñQso¼Qös¾øÂí$U¡eK8(S|ôÑ)‘Ñ©ì¼s~ã’¤ÚdêÔ” †Ô/ã‰' iÓüÆT’L"ãµ2–ËE,Ry˜Ä¤ÂNÞ.vn°(s¿(¥¥–7{6œ{.l¼qúô¿Àå—§h¥YsͲ§ihÕì¿?l´Q¾£¤Ú§M7ú÷Op- mO6­úL/ÉL!Ù«ŒeÇTÂû4 óBÈ—ö!pÒª¾¶ê§“HªÝÆ?•cݧÀ¦ÀéÀcUQ# ž¦^üéO°ï¾Ð¤IêËð лwºvÉ%©)Ú“O¦1uÝÝ»vÍ÷WP»Õ¯_öIÒÊiß>»¤À̙ЧOšõÍ7©ér¾„@#àY`í2–þ.Þ‘+Vò}š£€=Þ!ðG 5©©h‡X#F®Z™×VÝc£Š…¶*ÚÂçÈãȪˆGªsÎÊ)YÄ`{ v¯I~þ9û‰S‹©êâ„૯àúëá ӵnÝ`ìXøòËÔø `ÇaÓMa‹-L`H’j—9sÒä­‚q×_ W_ §–­Nœ1¦ó!UOŒ, EåXú#ðøÊ¼G±™û=‹-»23ÆõªÌú3€¿Æ˜†Ú‡@{`ï¹yebPíb£êM‡åþ±Cêðû0ði`ã6¤2®æ2@I…| îÖ6)Çs6¯Òˆj”É“áœs`Ò¤ô ZƒÌO™ãŽƒü#{é¥lR"£øØÏ£ŽÊYÈ’$åÌWd{Bx`ªD<ÿ|5 † K9&Mbû¡C#gUeÖ'5²¹ Ø5ÆxWÓj¿O_“ú\ïŠmtËaÙ2¸ãØuWXThÛ‘G™í9Æ¥&e’$©l3fÀî»—¹¬ pðfô©¤·n|ž9ÞøOìRÖ“B QœCúMê`pyß0FÆC‘Q®‹€/ =þŠônY#T[÷™È¨ÛLbäAŒqIŒñï1ÆCbŒ›Å{ÄÏŒ1¾‘ïØ¤Zo!0˜¼Ù¢I­Èƒ±ÇÂË/ÃÐBEž!¤Ç ï¾›o/I’J÷ùçðÙgå^þ0®2Þ7F¾z’¶±GÒ4’òôÜx$óœ™Çë†Pþ¿%cäqR"c&°°}Œl\FJ`ôZë•ã嶇 U‚¨–1‰‘'!„߆þBx*„°Q¡Oa¿|Ç%Õ:V¬²ƒ†-¶HÇ—_?ü½Ö½{Jr¬¿~~b“$©&Úa=š7OC€.] óò›)–‡ÇÈü‚!Ð13þt¥ÄÈâùÐÔ8ôçr<ퟙû€=cdßâ[MJ7TzdÄÈ“1ò~æÜ  KŒL.¡bc)ð6ðßB/ùÐ+FÜ~_‡™ÄȱB½ƒ¤Ÿ'‘Æ©6vF…®Ég|R­²Œå{`¨TsæÀ…ÂðáÙsõë§³gg{aH’¤•×£GJd¬±Fú@à?ÿ>‚›n‚æÍY¼ü=F^-xN´žÞ íËó>!°V 6…ÏÇȘy«<¯#Oû[ÇÈ3å|ߣS±!p}4‰‘™%¼öÌBÇ…+6öˆ‘bdc UlÄÈäò¼¿j/÷åÞ™À¾ÀÑÀXøe<Е¤†7ç„îŽ1~§ø¤Úap i„jR.iwii‰ÕJ¹Vì±¼þ:´iûï«gFÐöé:Áùçç7FI’j‹=àË/S"£ÀÉ'Cÿþ<²îºÎò¯] ü*sëBªT(ËÀÞÀ!pTy“ÅÅȨâç2A¿Š‘Å%<¥©&¶p°glW¸ªdïóxlP,¹1(†–”QÝc%Fî 1fœŒ1þ ü ”ÝæGRé6 í˜ü¸žÔc6)‘1¥”Ûñù¶ú8ãŒtÿÃiëHa#GÂàÁ°ZOôH’T™ '0 ´kÇ¢ÌØÓ_’!°ÙßTž‰‘[‹?¯xŸŠhÌÍ'5=V=y"ÛIòaiTÒ°âB;›eÖHZŸ—zh?@ÚÀµi˜±~ñöÛг',\ë® —\’½6thÚJ2x0tè¿%IRVŒÜ¯cüeK:!°!©î´iæÔK!ð ií@ýÌñÀæ1òÜ*†².Ùé 〳cL­ÔC` /psŒÄLÜË(áoieY‰‘{ÿv !< ìš9×<„иøøw¾‚“j´™¤ÏÆ?’¦Ÿ@j›»Q㪆¶Ý6õ·¸æøî»ìµí¶KU@=JH’TmÄÈ—1òTÁãÀýdjO$5ȬŸ9× ¸ x¢¸xøppH¡XnnÞö•ð^Òrüõ4ÇbŒ“ºëî ¿4ÇŒ&Í]>$Æ8+OáI5×ç¤j‹á™ûW`•?k¨%^}y$û¸^=2$Ï ]ßÀ:=I’j‚¡|SJ*KŒ,Œ‘ÝIíÓÿœÏÝ3˶> ßæ26Õ þšš1ÆA!„á@ÒçÃóOGcŒóòœTSm Žþ\Gú\Bœ>\}5´j½z¥{€Þ½Ó¶‘É“á¸ãò¡$IZ1òPœHêO± +îþµè#S*ã}CàXR£àývËÜ kFjìi•¹*•IŒ<‰1~AÚZ"©43§K¹¾¸4¹|à<²».‡Tmh5EÏž)‰1c\z)ÜpCöÚw¤1ªn‘$©fŠ‘[Bà_ÀBVü÷ÝœÊJ`dìDùÚ¥¯W‰ï)&1ò"„°:p1°'éóãH…ï cŒå=$Õ “C˱nt¡ãwɶ›ªcb„{ïMÍ2}Â÷Ú úôgŸ…aÃÒD’Í7O×Z¶Ì_¬’$©ÒìAéÛµ n1òf%½ß @`ÛRÖ¼ œVIï'ýÂÏÞr,„Ð’Tì~)[Z0ŽhMÒË­¤UµÔÅVRÓ¦A·npÄ©Ygaý+Ô¯-Zä'>I’TùB`g²½öV¸ +ã=3G)cÙýJ¤Êd#÷“:oHʘc¼†4ï¹W¡Ožb“Tƒ­½6„Laç5×À7ßd¯m¹%|ò <ø üêWù‰O’$U‰÷€·Ë±î%R+ôU–™D²O˪Œ÷’Š3‰‘{»CcŒ“‹_ˆ1þ øš´µD’J5g¼üröqð·¿¥ûùóá¼óŠ®ßÈ1³’$Õ:12èGšxðp2p0#sîqààYTIo{#°ckv ¿VÒûI¿0‰‘{ÍI•Ë !4 íe³ìJ*Í:…Žûæ-Š*5oŒ*V:y2¼öZ:~ì1ØtSØ{o˜:5»fÇÓv’Æ¡C‡Ô#C’$Õn…wÛÆÈÍ12èÜBå&0>¤ì¿Y"ðQ%¾§˜ÄȇÿLJ–pm_`]RI˜¤™Vèøù¼EQeæÍƒ}öIý+þþwX° mÙuWØsO75‚ᅦٳáâ‹‹>ÿšk`Ò$¸êªìöI’T»ÅÈœ9¦ð’ù}à‡RUÆ–[ºëÂ>ûÀOÀË/›À$IRÝe#÷Ž%5õ¼˜Bø „ð9ðp ©`þ!„™™Û˹ .„ð[`&°e ×Ö!ÜÌæ„Æ…v*aÝ–!„×€Àô^½zmóÃ?TuèªéGï»o_Gå3¨ò{óMØsOøè#èÚN<~û[8xèׯb‰Œµ×†Ë/‡÷߇wß…úõÓøÕvíªîk$I’ª;{æÞD`IÖSUBèNê˱¢äÖuÀþÀ?H­ÿ<BØ%ÆøNæ5­€3€©Ÿ|òÉ]=zô`„ ¬¶ÚjUýe¨¦Š@}Rÿ‹»þÀëÀù ª| ³f¥Ç³gÃ-·]3n\JdŒ Í›WìõÛ´©œ8%I’¤šÎ$FŽÅ¯Ïw Å…š—g’Š÷KZ³ ppXŒñ¾Ì¹±¤F¤ç‡d–žt:Äÿ0nܸ3{öì¹Ó=÷ÜÉ'žX•_Šjª‚žwƒ€ÁÀ@‹ü…U1®úk,^ Ç {í ü-I’$áv’j „°i¡eC88ø )!Q’ßs)Ô¯#ƸˆÔ½à€B“Ìéßc =zô˜½Å[0bĈªˆ]5Ýà` /0ƒÔÐó`Í|U1ݺÁ}÷•¾fõÕË®ÂxòÉ4bµXo=8ç˜1£rc•$I’j2“9·_5 IDATBØ=„p{a¿Bç¶ !|EjæùcáñB³<„÷ °IŒñbV¼Õe+à›ãübç?íB Í€O‹?¹cÇŽ|ýõו²j…e¤Fž¯S€nÀ$RCÏ“ò×JèÚµôë¿þuÙÛH¾ý6»fêÔ´%¥qãJ O’$Iª,VÎÂnÀÓ¤ÿ/fε&Ë_4!õ¸+„ð»+£8½|bŒï–cY+ÒçäÅMÏÜ·#}¦JZתU+¦L™RáØJ{N«V­hÔ¨Q…_SÕÄ\ '©öçPRÿ‹Ã2ŸÌc\+©¬JŒO>ISK6ÞxÅkN; Ž=FŽLSM6Û l##I’ê²B} u¾ãPõa£ŠeF•Þ|cœ¹t°>ptŒqxfí÷À ÀæÀ‡y·4HSIŠ+¨ÌX-³†’Ö5kÖŒE‹±xñb6lXî7mWÊ(†±cÇÒ«W¯r¿–rh&)mW–NÀáÀƒ¤v°‘Ra5ÌÈ‘ðÇ?–¾fÑ"ØqÇ4½¤´F«­¤ÛÒ¥•¥$IR´ ©VwEröᯪ“Uo`à’B €½H®þüö!Rc{ª_c %w)(8÷0•ôMd¹u?ýôíÚµ«P`Ò¤¿êСC…^K94™T]Q^£Hi½©1< ̘Ûn ;ÂÇ—¾völ˜;·ü¯]¿þªÅ&I’T |AÚ²¾"Õíï&U1“U¯Kæþ…‚!„æÀ®Às1ÆÅ…Öþ,6Ì]xåö©cAqk‘:LŽ1.!L#m=)búôél¸aÅ¿¬N:Uø9ª¡*’ô¨&æÍKSD–.…‡†L‰ŒŽáüóS#ÏÌ®?óLXýå_gÜ880m%9è ·H’$È XáGE!+1ê{V½Ÿ2÷ë:·ÐxªØÚHM2—kŒY |¬Bh_ì|OàÛÌ7—‚u= /X´hQxã7Øh£r¦j¬Þù ân¸ÞxÞ~ö߆OÛ@ÆO÷·Þ :@Æ)™qõÕ%WWÜy'¼ürzN»vpÊ)¹ý:$I’¤šÂ$FÕ+ØBRøO´ÃHÛ.F[[0¹äƒªj%üX@¡¬!„-Hƒ1¯/´n(°i¡Á‰ë®»n½™3grúé§ç*V)'Î9N8!úiªÄ¸é¦ì„‘-`̘0>¸èsçêýÑ¡C¶OÆìÙ°=p%I’¤:Áí$U,Æ85„08?„Іôß|Oà‰ã÷™Ñ¤ûÆå+Þ‰1N !\\Bè |Cêë1ÔÇ£À“ÀÀƒ!„'F!„ÍÏ=÷\º–5ƒRµ[àÙÌq;àû<ÆRÓ¦ÁÏ?CáB¢>JÛBš7‡þÚ¶…Áƒáφ¦M‹>¿¤Q£GÃa‡¥êŒ>}à’Kàì³S寨±ÙĈ$I’¤¢¬ÄÈc€±ÀñÀq¤DÅQ…®?CšÍð#°_±>¹6¸ŒÔȳˆãuÀîÀ»¤dÌ_€>1Æe…ÖDREÉ À,à§ë¯¿þƒ+¯¼2¡«Z{®Ðq ©4˜6 ~óèÕ+G4vÞúõËVS\v¼óNÚRšS²c¯½`æÌ´eæLX° õ¸äøÓŸ oß*ü¢$I’¤ÌJŒˆ1Îú†Z«Å'[ò8ð/à©ãÌ\ÇWXf‚Ê„R®¿@¡&¥+X;37HÃ6·¬œUcn¹´!ðß|R> Œ2›»:u‚ÆS3OHÍ8ûõKUÍ›Ã6Û”ýšƒÃ¥—fÏ›*:,H#X!ûš=z”ü’$IR]f%Fŧ•À Æ8$Æ8"ß ©Ju©ÅmÇ<ÇRM`@š@RÀ(0n\ÙÕ…µ(a|ìÏ?gª;úõƒïkÈvI’$)—LbHÊIÕÿþçXÊáºëRåEi4€Ë//ÿkžp”§5ÌàÁiJ‰$I’¤¢LbHª\‹WòĪëÖ Ž<²ô5çž[rãÎiÖ z¨ô5ë¯gžYþ×”$I’ê“’*×+@-ø#|áBx¥ŒdLñI$åQ¯Œïº!Tü5%I’¤ºÂ$†¤Ê± ˜ ì\9·:Цœ·jöÝèõ×áÓOK_3dHvjIy,XPvÉ“aذò¿¦$I’T—T³?$ÕX×;“s€QÀ¢Ìý”rÜJhz™O¦‘¨¥ùé'8ï¼ò¿æÝwË/–½îÜsmì)I’$•Ä$†¤Êq*°° ð9°ð!)±QCÜsÌŸŸŽ~xù~ Š ¥îÖ n¿½ü¯üñË÷»hРè“fÍàñÇmì)I’$•Ä$†¤Usp:Ð xØØ•T]±q㪀… áè£S#Ïÿû¿t®m[;6%2ÖZ &M‚Å‹á7 eË”À3&WÄ!ÙDÆ!é5gφÝvK Œ'žHÇ’$I’–× ì%’TŠ=€½%À?€G€{¶ù ªd\:ÀÉ'§Ç ¤ÄÅ©§Â×_§s÷Þ Ûn gŸMdÌš;¦ëݺ¥-!lPñF!C Øu×ô¸ yñÑG°ýö«ô%J’$IµšI I+ç:RÅÅ.À³@_ÒxÕ[€£ó× \p\uUšþñÊ+н;Ü|3LœO? ÷ß_~ _}úôì™ mÛ¦[a]º¬z< ŒÍš™À$I’ÊâvI+çC`w`Ð KÚRRF3Ì|(H`@jÖyß}púé)0gr\ziJ&œvl³Mþâ•$I’T2+1$UÌhà7À@SRÆc™sËc\+°h¼õVÙëæÌ¹sS>.I’$Ig%†¤ò›œ@š<²œÜ–Ï JרQù¦}üá©7† I’$©ú2‰!©|¾š/“€½€¹À_»óVif΄«¯†­¶J·ÒtPnb’$I’´òLbH*ÛD`C`$°ð2ð5ðçÌõú¹ cÉ’””X° {nÆŒ4í£$óæÁEÁ_¤fž¥yì±Ê‹S’$IRÕ°'†¤²u.æG¯«ç.„%KàÐCᡇàÉ'áÆS2ã˜cR‹Ï>ƒM7M>vÞ9=gÝuaï½aÔ¨”Ð(Íßþ›m'œPõ_‹$I’¤•cCÒŠ½ü x"sß8X 80waN`Œ·ü˜ÓaÃÒýf“ú\ŒUö{4m o\9ñJ’$Iª&1¤ºèg ŒÊÖ&õ½èIê…q°.iI=ýt6Q–Q£`ÊhÛ6=Þm7Øw_˜=ÆŽMçöÞvØîº þûß4Võ‰'ÒZI’$IÕ—=1¤ºèO@»rÜ:Ó€]ÿ‘*0ÖÈm¸{ï C‡–oíÒ¥pÇÙÇ!¤~/¼gž™úg<ñ\|1¼ÿ~zm’$IRÍ`%†¤²õZ óÂg¤$ÄŠ¬½6´o³fÁ:딼¦xЂ I’$I5ƒI IekÜ–ß~ü±ôësæ¤-'n˜ª/$I’$Õ>n'‘T¶Eù{ë©Sá‡ÊÞî±`Aš,2~nâ’$I’”{&1$ÁÀ@š±|V£œGÀK/ÁÖ[Ã9礜eùàøöÛªK’$IR~˜Ä”v¾DÒÔ’&ù `Ù28ýô4idÄ80õ°€´]d›m`³Í²ë[·†çŸ‡M7ÍO¼’$I’ªžI IЪØã6y‰¢ˆzõàÁaõÕ!F¸î:¸õVXc4FõÝwaâD¸é¦ÔÈóùçaË-óµ$I’¤ªdCRJb´Ï¯´ÈO}”¦‹èØn¿=7k–ù%ì»ovÍÉ'Ã'Ÿ˜À$I’ê“’`"PÐKb&ðŸÜ‡pï½Ð­}tª¼(pÐApË-©òbÛmS%Fq%“$I’Tû˜ÄêšÙÀkùby#GÂܹðØcpå•E¯|Ú2"I’$©n3‰!Õ5³€¯òÄòîº+Û”óâ‹á¹çò$I’¤êÇ$†TWÌž&õ¾Ø·Œµ•hÊè×&OΞ{óMØyç´E¤@‹ðÈ#°Új°ÕV°á†¹‹Q’$IRÍÐ ßHÊ‘1ÀAÀ0à:àdàÿ€3€]ËxîJ6úœ2z÷†?Ný,Î>~þ† Å‹¡gÏÔ¨³`«Èæ›Ã³Ï¦ñ©MªÁ˜WI’$IÕ‹I ©¶[BjÖÙŸ”À8™T•q:ðP¿jÞ¶p`Æ ¸è¢¢kæÍKSE¾ü2MèÞ½jâ‘$I’Tó¹Dªíþì|Ü ü˜@•%0 mùöÛ2—1ujªÌ$I’¤²˜Äj»³€v@Oà à`Ð¥jßvÇaôè²×õè'Tµ±H’$IªLbHµÕYÀ@`u`4Б”ÈøØ87!ôèQúõ֭ᥗ`­µr$I’¤šÍžRmµ7i É<`ð80X;w!¼ùfé×ú ¾þ~ý뜄#I’$©†³Cªm®Þ~CšHr;p"Ј´•$GƇ=÷,}Í’%Ы|óMNB’$I’TÙÄj“eÀûÀnÀx ðй ¥mÛ´]¤,¿ú¬¹fÕÇ#I’$©æ3‰!ÕÏìì¼l\“ÛPÞ~b„±caãLÿ–-á„àÄ¡Q£t®GÔü³yóÜÆ'I’$©f2‰!Õ³€£€ßKÛ€Ã[rÊäɰ×^°ýöi›ÈرiËÈøñðÏÂÍ7ÃóÏCß¾&0$I’$UŒ=¥šîGR³ÎI}0úÿ %4rìÑGaÚ´tÜ»wJ\Œ[tMA†$I’$U„•RM6Ø€ìÕW2çþœ¹^?÷!uÜr 4l ÂÅÜ9¹C’$IRícCªÉ:’ª/%%4^.ÊgPpüñ0fLjÚùøãn‘$I’T9LbH5ÑK¤­#³€ €«€ß‘*2Öšå6œ€W_-z®woøüsèÒ%·±H’$Iª½LbH5Ñ&À÷¤DÆtà,à.`Ç܇òÚkpôѰÛnpûíE¯L!‘$I’¤Ê`CªIÆ7“ª-^–½€€Ã€5sÒ7ߤûE‹à¸ãROŒ¥yh(*I’$©ö3‰!Õ$Ÿ§×­±ÀÀ²ü…ôûßà /À:ë¤ÇãÆ¥„†$I’$U6G¬J5Á$ 9p8Ð8˜ îË_XvÚ Þ~N8n» š6ÍwD’$I’j#+1¤šà/@Oà Rχ€áÀù gÐ ¸õÖ¢ç:t€§Ÿ†õÖËKH’$I’ê+1¤êl:°p+p)‘ñ<°/ÐÈCãÌ#à²ËÒñÀõ×C¿“H’$IÊ+1¤êjÐ8h < ì¹–§É›líÚ¥ãn€~ý`îÜüÄ"I’$©n1‰!UWõ€›€À±™Ç÷‘×¹;ìo½]»¦Ç-[B³fù‹G’$IRÝaCªn~vÆ=€çH‰‹€úÀ&¹©ø´‘öíá•Wà¼óà®» „ÜÇ$I’$©îq'»”K#¥e¬‰¤í#»w“F©Î¬ÚÐVN„ßýZ·†›n‚† ÓùfÍફò“$I’¤ºÉ$†”KG +°þ °WÕ„SW_ =–Ž?û ~ÖZ+ñH’$Iª»ÜN"Ug‹ó€°ãŽéø¥— [7˜3'¯!I’$Iª£LbHÕÙ‘@ÿü†Ð¶-¼ø"ydz|è¡Ð¼y^C’$I’TG¹DÒr¦MƒÕW‡&MÒãÆaøpØèŸç¤Š$I’¤ºËJ )Ÿö'MÈÓ„)S`ìØìã  wï´…dÊ”¢k÷ßßI$’$I’òÇ$†”OO‘VsÿöS¦¤„E¿~ðøã)qÆðÑGðÞ{еkº—$I’¤êÀí$RuÑX»·+H`|üqz¼ß~˯ùþû´æë¯íƒ!I’$)ÿ¬Äò©;i„jvÊí[ŸtR6QšµÖ2!I’$©z0‰!åÓKÀ<ÒV’rûÖ7ßœ&”fõÕaôèÜÄ#I’$Ie1‰!ÕQmÛÂYg•¾æÄaãs$I’$•Å$†”+ÿåïí§N…+®€ÎaòätnúôÒŸSÖuI’$IÊ%{J¹²#Ùæ-ÆåxNyÖ”Ó?ÂE¥ã{î>}à†JÎÝwÃï~}ûV^’$I’´²¬ÄªÚ<àV 0¸˜ \L)ãÖ»âo·t)üûß)ùðî»Ùó;§‘©Ã‡Ã7Âüù¥¿Ö’%ð׿V<I’$Iª VbHUí=à,Òv’»HIŒÀvUóvS¦À>ûÀ²e©ïEAâàÈ#Sbã³Ïà¶ÛàçŸáñÇÓµ `ÝuÓóe¶½ôè>Z5qJ’$IREY‰!U¥ù¤1ªoI‰‹€ß¶”¬¢ï¾ƒk®×_Ïž[o=Øm·t|ß}°xqöÚ¡‡¦¤Æõ×§ÊŒ‘#aß}¡}{˜4 ¾ú*Ý:uJ ŒÑ£¯*I’$©ú°Cª*ï{·¿ÞŽ&%1¶\õ—Ÿ?6ÝæÎ…#Ž€îݳ׎: ž{.õÁxê)èß?oÓÞy§èëŒ ?ü¿úUzܶ-¼ø"¬¶š I’$IÕ‹•RUÙœ”´Ø4«øKÅ/½¯½–=×´iÚ6ðÈ#0{vöÚ¤dÄGd“+Ò¨ÑòkÚ´1!I’$©ú1‰!U¶¥À•¤)$WC€Ù%‹Á!‡À /dÏM™ýúeÇŸÖ½;ôêƒ=?`@ºŸ7z({~µÕàÛoÓt‘m·]Õ/H’$I’ª“Re›BÚBÒ øØœš./Z <’g•úZl³ <ý4ì² ŒSô%wÜ1Ý?ÿ|êƒQ OŸÔ£cGh\lkýúUñÅI’$IRþ˜Ä*ÓL`=RÒbR#ÏG€MÒù‚FÁDE‹`èP8ï¼T‰ðÍ7©Ùfኌ£ŽJ÷Ë–ÁˆÙóõêÁøñðñÇpØJlS‘$I’¤šÄ$†TY~6"m!i <üø"»dÆ ˜8±ì—Z´¨èV“®]aóÍSÿŠ©S‹®mÛvU—$I’¤šÁé$ReY¸8†4RõNàÒ¢KÚ¶…±ca“M`Á‚Ò_îóÏ‹>¾çèÐZµª¬€%I’$©f±Cª ƒ€ÉÀ¤QªŸG–¼´}{Øpò_ò•WŠ>îÒņ$I’¤ºÍ$†´ªæ“*/¶F3ÿVòò)SàÓOKÉË.ƒ—_®Ì %I’$©æ3‰!­Šï¦ÀS¤é#û¶¬üzùåS§BïÞ°dIé/{ÕU&1$I’$©8“ÒÊZìÌ.ŠŸÒ¢¬¿~Ù/½ÖZitª$I’$)Ë$†´²ï;{‘úc³h _|£FÁž{f^¢ì·œ~J\@ê™1v,l¼qÕ ’$I’T“˜ÄVÆ•ÀsÀVÀxRŒ½IÕÅ,[¿ÿ=ÜuWÚJòÙg)‘±ß~ðÄéøÊ+aÂèÕˆ$I’$­ˆI ieÌú’’-€G(qhq½z°Ã鸠'ÆÇ§äEß¾ÙuV`H’$IRéLbHñ-°¸ x(s¿æ\‡?í‚ `Èt<}úòãS%I’$Ie+áscI+t )‘ñП´•ä6 ÉòK'M‚Í6Ë>>óLhÖ ¦MƒÓNËE°’$I’T»X‰!UÄ]@;`[àI`R5F1gÛn Ï.áöx±u[…^Ó{÷îÝeêÔ©ù ze=DÚB²€4}ä!àÀË/Ýl3hÑb„“O†nÈe ’$I’Tw˜ÄPEôÖÞ(v{¿`A¡1ð ðkà4àĉ'¶ìÑ£óæÍËu¼+oÒWÖ øØŸÔcëå—vï/¼k­•?ñ,[–£8%I’$©1‰¡Šè<cPìvQ¡5]cŒÃbŒ#GŽùîgŸ}ƈ#òuEÌÆ‘ú`¼t¶'5õlž]6iRѧuí /¾ £FA=ÿeI’$IR¥óO-•K¡)Ðx·Œ¥¿^ˆ1~_pb—]v™Ý¹sgî½÷Þª ±rŒ"Õ›\ ¬ <\Njê™1f l³ œ}vѧn±<ø 4k–£X%I’$©Ž1‰¡òÚ¨üB¸ „06„pW¡{Á‚ÌV’ޤ Elºé¦|õÕW¹‹veDààn`0pð3iSL¦ céR8ã X¸† ?ü!õÂ$I’$U=§“¨¼¶ÉÜ^$µ¼<8<„pLŒñnRíB¦òZk­Å”)S*ü¦¥=§U«V4jԨ¯Y¢qÀÙÀ¤†ž[/“&“dÔ¯Ÿ*1~óøïᦛ`ÝuaàÀÊ C’$IRV¡>Ð:ßq¨ú0‰¡òš < Œ1Ž!´^n !Œ 2 ËuðlÖ¬ .déÒ¥Ô¯_¿ÜoÚ®]»^;v,½zõ*÷k•j`5R/Œ@?Ò$’r$ë¯/¿œ!À‰'VN’$I’–³ 0©”ëÖE×1&1T.1Æ{{‹û6„pp!°ð|æÒšÅŸÿÓO?±ÞzëU(0©xÍB:tèP¡×*ÑàaR'çH_É>À¤:àý÷aèP¸ùf((üXw]xé%X²Ö^{ÕÃ$I’T¢/€ÍJ¹þa®Qõ`C«êyÒŸþ­cŒ CÓIcX‹˜>}:n¸a…_¼S§N«aiÞ·’zîLJÉŸ|»ïÓ¦Á”)ðÈ#ФIº¶Î:Uš$I’T×ůèzÁJŒ:ÆÆž*—Â=!„wCÅ7Xl’¹ÿ$sÿ!Ð£ð‚… †7Þxc¥’Uj )Yñ*ð:ÐôUì ´MKÚ´‚°G†½ö‚¹só«$I’$É$†ÊísRsÏ£ N„:çïofN:…~i‡yÍ5×´Ÿ={6§Ÿ~zÃ-ÃgÀ¦¤:’®¤¯àWÀ³E—­±<û,ì²KzüÁðÝw¹ T’$I’TÀí$*¯«€=€[CÇ3>ÀdàÈ4úððpá  I¡óyçG—.]òö üØ Øø3p)êz)I±ÚjвeZºúê© ãØcá a“MVðš’$I’¤*e%†Ê%Ƹè GÚ22 8Ø>Æ8©ÐºHÚq"ð30}È!ï_qŹzEþIÚJr0ø pP~øvÛ-ݦÛ¬Ü?lµU>–$I’$•ª€ãRàöÌ­´uË€;27€§êñçÿ÷ÀåÀ0àà0`k 34åÚkS3O€]w…瞃¶mó¨$I’$©8+1TwÌÚ‘&’¬ lGÚB²9‚rÕUpÈ!éø£ o_X¶,±J’$I’–cCuÃR¢âo@k` p20¶è² `Ä07†k®zþ+‘$I’¤jÁ?ÏT74®¿'Ue\s/ƒÃ‡ÿþ7»´^=¸ývxóMØc¼D+I’$I*I Õ~7߇ÁNúâE°÷Þpï½i„ê¤IÙ§„[o—h%I’$I+`CµÛ"RÏ®Àó¤þoC¡Q#èÑ#-ûî»ÔÈó½÷ò¨$I’$©,&1T{ÍÏ’Ãö®Z[¤%ƒÃå—§ã„ÿü'qJ’$I’ÊÅ«ª"°Ð¸‰”¼èñßðé'бcvéÀФIjä9`@>‚•$I’$•‡•ªpð"Ðø–íÀöÛÃË/]þÇ?©§æp lÙ:oWLƒ)Âȑиqžc”$I’$U+1TýM~| ^ƒFïÁ3'A÷îiÉ{ïÁ´iy‹P’$I’”Vb(ÿŽÞ*zê蟎¦ý i ÉwÀ¯aY[¨w>ð4kÏ'žÿßÞÇWQŸ}ÿNö@ $BXRKØwPˆ¢ jE¬ˆ (ÅG´´w)XÑJ_h½ŸÚÖ}©r›¢DET  ( AH€ ,!²Èz=„œ›““°™p2ðyûšfæ:ó»æ0srΗ™9=&5kv¡\H„ð¿LI›¼gÕ?ù_ùù»¤¢¤ ÿSúsD„4gÎéàg\N×™·F:xÐß].4B ¸Nú>éºëüÝàB#Ä@³EÒ1Ç”«|ý'D:RnyHˆ4k–?:ø÷Ä@sTR-sTK¡ê] í•yÊò ¤¨Þ~jà7œ‰'ªÜÏuË//_¸$b ÆI;åÿs%òW#€…ËIPã ‘T É‘TKR}ÿ¶¨!1PãH ñw€‡ËIàwfþîà„ð;Çñw7àrøßI7K_|!¥¤”ÎjÕ*K?þø±Ú¶½S›7KµjI÷ß/5o.©›?›ø !üoDé&J? "#¥ýûßÑs¿ý­Š7Þ©)S¤_ß&5ïâß6þEˆeâÄÒ?Ÿy¦ôÏ€€ÿýÀ¥{bW Ä®@ˆ\¸!pB à „À1€+bW Ä®@ˆ\¸!pB à „À1€+bW Ä®@ˆ\¸!pB à „À1€+bW Ä®@ˆ\¸!pB à „À1€+bW Ä®@ˆ\¸!pB à „À1€+bW Ä®@ˆ\¸!pB à „À1€+bW Ä®@ˆéèÑ£þn¨1æÏŸ¯ÌÌL·øÝÚµkµ|ùr·ø]NNŽRRRTRRâïV€šÀ‘éï&páb Ú8Žä8Žs>ÍÎήêv׺çž{´råJ·øÝ믿®Ù³gû» Àï¶lÙ¢1cƨ  Àß­5A ¤h7 ‡UÎqœ$ÇqVKÊ—t`РAIû÷ï÷w[—#Ä@•r'LÒ\Iq’î•4iÆ uû÷ï¯ãÇû·9€«b ªM—ÔRRg3{ÎÌþý?ÿó?k7mÚ¤””÷V#mܸQŸ|ò‰¿ÛpåË—kíÚµþn£RŸ|ò‰6nÜèï6\%//O)))ÊËËów+®’™™©ùóçû»JmܸQ‹/öw®ÃkÈùIIIѾ}ûüÝF…x;?›7oæ5ä<8ŽÓÛqœnþî£2Žã$;ŽÓÎß}À1PÕFKZbf{Ëf <øpÛ¶m5gÎ?¶UsÍŸ?_Ó§O÷w®3{öl½þúëþn£RÓ§O¯Ñ,k¢ýû÷k̘1âò³s³råJÝsÏ=þn£RóçÏ×Ô©Sý݆ëðr~ÆŒ£~øÁßmTˆ×¸óóá‡òr~î—4ÞßMœÆLI#ýÝ܉UÆqœZ’ZKÚ\~YÛ¶mùvÀÏäïpQipòÏœò 6lx^ß8òÎ;ïT>Xƒ 9çuÖ4Û·oב#GøÚÀs”““£û¼9rDÛ·o¯’þ ´iÓ¦»­U¥ì5bÍš5ÊÊÊòs7î±iÓ&ÔØýcûöíÊËË«’þvïÞ­œœœ»­U©*_C.5ßÿ½‚‚jÞ[ܪ|Û°aƒ¤Ò3±.†÷B§“‘‘Qe¯!n²wïÞÆ7Þxc¿Ÿ±ŠF’ÂÇù9ë¨N‘’âϲ¿@•n IrÌÌß=à"á8N‚¤­’þËÌNý¼E“&Mòâ‹/ª  @gó­«úÓŸôØcUW«.ï™Ù(7 £æÅÔp³²S-ê•_››«¸¸¸³ 0$iòäÉJLL<íͯ7n|Ñÿë.=»wïþt„ Oû»"HRôi–çKZzzA @ˆ*cfÇÇ9$©aùe999jÙ²åY¯+**J¿þõ¯«²=À-²Æÿ¡¿›j"n쉪–&ÉëÚ¶cÇŽ|õÕWjÕª•ŸZ\ 1PÕž‘ÔÑqœ«ËfÌš5«Ù±cÇtß}÷ù±-€Ûq9 ªZª¤E’Þug¡¤ZŽã´{ðÁuÙe—ù¹5€›q&ª”™•H.i’¤£’ü÷ÿ÷w3fÌðoc×ãL T¹“AÆË''©ôÌŒ_ú¯#ÀÅ€31€+bW Ä®À=1pÁ|õÕWZ²d‰|ðÁ —/X°@_ýµš4i¢Aƒ©}ûö>5Çל9s´mÛ6ýâ¿Ð•W^©èèhŸºÝ»wë­·ÞÒ¡C‡Ô­[7%''«V­ZU¾MÀ™”””è±ÇÓu×]§N:ù,ÏÊÊRJJŠŽ9âÙWÃÃÃ}ê¾úê+}üñÇ Rß¾}Õ¿Ÿ3Ó{ï½§o¿ýV1114h}êòòò4gÎíØ±C:tPrr²7n\5 œôÅ_è믿Öï~÷;Ÿef¦ÔÔT­]»VÍš5Ó AƒÔ¶m[Ÿº£GjΜ9ÊÌÌT‡tå•WªQ£F>u;vìÐܹs•——§îÝ»+99Y¡¡¡>u+W®Ô§Ÿ~ªÐÐPõë×O}úôñ©)))QjjªÖ­[§¸¸8 4HmÚ´9Ïg¨ØÒ¥K•––¦É“'WûXÅÅÅš;w®ÒÒÒ¯Áƒ+!!Á§®¨¨H‹-ÒÚµk¤!C†¨K—.ÕÞ.mŽã4”ô ¤GÍìЯ»¤!’Š$­0³/OYV[Ò¯Nóðb3{£š[ÄÙ03&¦êžeffZ“&M¬uëÖV‘ &˜$KHH°6mÚX`` ½ôÒK^5'Nœ°:˜$ëÒ¥‹5jÔÈ¢¢¢lÍš5^u7n´Úµk[`` õéÓÇÂÃí{÷îvàÀ ǪӔ)SL’½óÎ;>ËÒÒÒ,<<Ü‚‚‚<ûjïÞ½íàÁƒ^u/½ô’I² X×®]Íq›4i’{Õýú׿6IÖºukKHH°   {óÍ7½jòòò¬M›6&ɺuëf 6´èèhûî»ïª~ãqÉÚ¼y³Õ¯_ߺtéRáòÑ£G›$kÛ¶­µlÙÒ‚ƒƒ-%%Å«æÈ‘#–àÙW4h`111–––æU÷Í7ßXhh¨[Ÿ>},44Ô `‡òª{öÙgM’EEEY—.]Ìq›2eŠ•””xÕÝpà &É->>ÞBBBìí·ß®‚g(µaëS§ŽõéÓ炌7tèP“díÛ··¸¸8 ·÷ßß«fïÞ½6`À °îÝ»[dd¤9ŽcÏ<óÌézÍüÿ¾Z'IÁ’–I2IÍ.Àxwžë ¤¯%•Hú‡¤€“ËãN.¯l:îïçŒéäߥ¿`ºø§¹sç~Ý¢E χ«ò>ùä“dO?ý´˜™Ùo~ó °ŒŒ OÝôéÓ­~ýú¶xñb33;|ø°uïÞÝZµjåyZRRbýúõ³®]»Zzzº™•~P¬_¿¾Ýyç>cÕå§Ÿ~²»îºËó‹¯|ˆQ\\l½zõ²=zØæÍ›ÍÌlýúõV·n]»çž{Q¸$Í›7Ïš4ib’ª,ÄX´hQ¥Aô«¯¾j6gÎ+))±ÂÂB9r¤EDDx}&L°[µj•™™:tÈFŽi–™™Y%}✽f5à}|uM’Z`TIˆ!©£¤a•,k*)WÒ+’êœ7öäØ7Ÿü9PR|Óí'ë¦øûyc:ù÷éï˜.îIÒŸ%Yÿþý­[·n†={ö´öíÛ{ÍÛ¿¿Ùþð3+ý@l÷Þ{¯W]jjªI²>úÈÌÌ>øàƒ ?0Þwß}îùT§7ZLLŒÕªUËî¹çž ÷É÷Þ{Ïç”™Ùĉ­víÚž7—÷ß¿…„„øœI”˜˜h ðü|Ùe—Y·nݼj²²²Ìq{ä‘G̬ô_Úí¿þ뿼êÞ|óM“dŸþùÏÛp\òî½÷^“dW^y¥uìØ±Â£]»vÖ«W/¯yÛ·o7ÇqlæÌ™ff¶{÷n °iÓ¦yÕ•x+V¬03³·ÞzË$ÙÇìU7nÜ8«W¯ž;vÌÌÌî¾ûn ·Ã‡{jJJJ¬E‹6dÈϼ6mÚX¿~ý¼Öõã?š${òÉ'Ïõé¼”ÛÇ·ÄÄÄÓ†yyy¶zõjûá‡|κ+/&&Æî»ï¾ —5mÚÔ†ê5oýúõ&Éž}öY33Û³g…††ÚO<áU—––fãÇ·µk×žÍæ¡ê½f5à½|uL’FJÊ“´SÒÜÓ…*½‡c[I=%Õ>ÃzŸ–´·’e•T )ªÜü’¾8Í:#$m“”êïçé'nì‰êÖꡇÚøÙgŸ©I“&> Oœ8¡o¿ýVC† ñ𥤤$­ZµJ’ôõ×_«°°Ð§îÊ+¯”$OÝŠ+$©ÂºãÇkýúõU³UÀiddd¨_¿~Z»v­n½õÖ kN·¯æåå)--ÍS×µkW5lØÐ§nõêÕ’¤Ã‡+--MW]u•WMll¬Ú·oï9>V­Z¥ââbŸº²ŸËê€óµcǽð Z¼x±êׯï³üàÁƒÚ¸q£Ï>Ø¢E µiÓÆ³þç?ÿQIIÉY½æiðàÁ>u?ýô“ÒÓÓ=u½{÷Vdd¤§Æq%''{Öµÿ~mÙ²ÅgÌV­Z)!!ã?[VV–^}õU-\¸PuêÔ©°¦¸¸X3fÌPݺuÕ³gOµoß^qqqZ²dÉ9·mÛ6íÙ³ÇgŸþå/©Æ{öé¥K—*??ßSwìØ1åçç«C‡zñÅ•””tÎcgÐAÒó’:IZ]Y‘ã8ƒ%í´IÒ*I‡Ç™á8NàyŒÙWÒ7fv Üü%* H*3[RIÿç<ÆD5!Ä@u=cÆŒÌÀÀŠ_k²³³UXX¨¨¨(Ÿe5Òž={$I;wî”$Ÿºˆˆ………);;[’”™™©ÐÐPEDDø¬«l< º 6Lÿþ÷¿+¼Qa™ÌÌLEDDøÜ|°ü¾š™™Yéñ‘ŸŸ¯ÜÜ\eeeÉÌ*¼áaEÇQùº† ÊqOp¾,X  &Èqœ —geeIòÝËæé5¿ìq§ÖÕ¯__g¬«ì8:tèŽ?^é˜euüþÀÏõÑGéöÛo¯ôø¤Ç\?ü°î¿ÿ~¥§§kÛ¶m8p †®~øáœÆ;Ó>]þx ÑÀU¯^=EDDhøðáÚ½{÷9 œ¥?›Ù3;XYã8$-’ô£¤a’JºWÒTI<1ã$•0$i¿¤“7-ßCI÷œì—ƒ¡!Ä@µ²“çaUæ§Ÿ~’$5hÐÀgYƒ ´wïÞ³ª+{syèСJk$B \§{ƒZæl÷Õ³©;t¨ôfÞýË÷©ÇQY]ùõ¨^½zž:à|i߯l,›w¦}588X^¿Nw|ìÝ»W%%%:räÈëÎö÷ p¾ÎæøxòÉ'Õ¯_?Íž=[‰‰‰Š×K/½¤àà`=üðÃ’¤%K–(::Ú3eggëå—_öüçYŸtæã-++Kaaa:t¨jÕª¥7ß|SãÆÓ§Ÿ~ª«®ºJ………Uù4gü|pÒc’I·™Ù"3;hfÏKzWÒÿu'B’ÇÙæ8N¶ã8Ù* ¢Ê~>9]}r}uUzCÏòÊæùž2.M;¹üïg¿u¸øŠUøUÙ©½>Ë <§[ž©®nݺ’JĮ̈¬F’§ð·ÊöÕüü|I:ã>}j]QQ‘¤Šüü|ÏqTv†RÙc+«ªËéöÁS_ó϶.22R™™™ÖHR:u ZµjñwCÙ˜gú=T—ôôt?~\±±±zî¹ç¼–ÅÅÅy.‰‹‹Óí·ßîYö÷¿ÿ]‰‰‰JNN–$•ýz¶ÇQAANœ8¡Þ½{ëí·ß–$ÝtÓMjÓ¦þøÇ?*%%EcÇŽ­Úά‹JïE1¼\X$©–¤6’ÖJzCRÈÉeƒTz©Êk§Ôï8ùçQI¾ß½ý¿=|êLÇqUzÈŸÍìÈùnª!ü*&&F’”““ã³,''Çó=æ•Õ™™rssÕ²eKI¥÷ÈÍÍ•™yý‹GÙãÊꋉ‰ÑÁƒ¾ÿ P6ïÔ}¿²º°°05mÚÔsIJeuå£òu'NœÐ±cÇ˶oßîyZöæ²|ÝÎ;U\\ìõFµ¤¤Äs}ç©ë’ć4Ô±±±*((ð9E½l_=5˜«ìøˆ—ã8jذ¡ÂÂÂ|êÌL;vìðZ—ä{q|àBiÔ¨‘‚ƒƒ+ÜW333Ïøš_~_ÕáÇ•››{ƺ3ýžiÒ¤‰}êÊ~§p| º•í÷³fÍRzzºÏt®7'/ 1ÊïÓÚ³ggŸ. 1N½ñ­Tz Çq8 þ²[Ò23ûE%ÓÒs\ß.I-*˜/i{—¸Œ”´ÁÌÎíf4¸ 1àw7Üpƒ.\èu ïºu딑‘¡¾}ûJ’.»ì2µiÓFóæÍózì¼yó¤ž=Ko*|Ýu×)00°ÂºøøxÏÀßF¥€€½óÎ;^óçÍ›§V­Z)::ZRéñ‘žž® 6xjòóóõÁxŽIºþúëõÞ{喝¸Ø3oÕªUÚ½{·úõë'IêÞ½»š7oî3fjjªBBBÔ­[·*ßNàT5j”Þ}÷]•””xæ/_¾\ûöíóìÓ={öTll¬ÏkyjjªÂÂÂÔµkWI¥û}ÙüSÍ›7O:tð|øºþúëµnÝ:mݺÕS“——§Å‹{Æ Òˆ#”ššªSßË.[¶L999^ÇPÚµk§Zµjy.é(søða%%%ùœ)Q&,,LAA¾'WGFFꪫ®ò9Ž/^¬¼¼<Ï>}õÕW+,,L .ôª›7ožJJJ4`À€Ÿ³YÀùúFÒåo¸é8Îÿsç{Çqb*xL¡JϪ¨ÈjŽãØ¢E‹|ƪۗ_~i’ìwÞñYvà 7Xݺumîܹ–‘‘a=ô9ŽcŸ~ú©§æÄ‰Ö´iSëØ±£­\¹ÒÒÒÒìÚk¯µ¨¨(Û¿¿§nݺuæ8Ž=Ú6nÜhŸþ¹µiÓÆ®¸â ¯1ŸyæsÇžxâ ËÈȰ7ÞxÃj×®m>úhõ= ¸$õïßߺtéâ3Íš5æ8ŽÝrË-–žžnË–-³„„KNNöª›={¶جY³lÛ¶möúë¯[xx¸=þøã^uÇ· Ø»ï¾k[·nµ©S§Z@@€-_¾ÜS“——gQQQÖ¹sg[½zµ}ÿý÷6dÈ‹ŽŽ¶ÜÜ\OݪU«L’;Ö6mÚdK—.µøøx6lX?;¸ÔuïÞÝúôéã3æÌ™&ÉFmË–-³+Vص×^k’ì7Þ8çq>ýôS“dwß}·mÞ¼Ù/^l±±±vã7zÕ=ðÀ&É{ì1KOO·§žzÊ7nl—_~¹œ÷vâgyÍüÿ¾Ú'I¿“d’š•›ßNR¤õ’î’”¨Òmš¤…ç1NˆJÏÆH“Ô[¥!Å|•~cI£rµ-OŽó{??L•ü}ú»¦Kb:mˆaföÑGYtt´|Á°Ë.»ÌÒÓÓ}êžxâ ÷Ô9ÒŽ?îUsäÈ=z´š$ åüæt!Æ¡C‡ìÆo´€€Ï¾ZþšYiÐשS'Ï~m|ðOÝüùó­Q£Fžº.]ºØÖ­[½jJJJìᇶ°°0Oݯ~õ+ËÏϯº¬òÃÌ,55Õ¢¢¢<û`×®]mÛ¶m^5ÅÅÅöàƒZhh¨§îæ›o¶ÂÂB¯ºÜÜ\1b„ç8 ·Ù³gûŒùý÷ß[‡<늵%K–øÔ½ýöÛÖ°aCO]=,33óüŸ •…ÅÅÅöÔSOYdd¤glÒ¤‰=ÿüóç=Ö¿þõ/«W¯žg}}ûöµììl¯š¢¢"›6mšçwChh¨ :Ô+äÃ÷šùÿ=|µO•…'—õ‘ôCÙ¾«Ò3-^•Tû<ÇJTéÍ@ËÖ—-ixuÃN.êï燩âÉ9ùT§E’†ø» À%^—t»¿›j"î‰\¸!pB à „À1€+bW Ä®@ˆ\¸!pB à „¸Ô®]»´hÑ"eeeU¸|X©©©úË_þ¢eË–ù»p1p©?þXÆ Óµ×^«ÂÂBŸåÔ°aÃ4gΜ ÖÓ?ÿùO9ò‚w® uå•Wê†nÐŒ3ôÑGù»%p1p¹uëÖéñÇ÷w®°yóf}õÕWúÝï~§Ã‡kÖ¬Yþn œB \®yóæzâ‰'´nݺ :®™]°šsUÙ:8 IºâŠ+ðóÞUGßÕ¹^.„¸ÜSO=¥ððp7®ÂËJNÕ¯_?ýùÏöš·k×.%%%)55U’´uëV%%%iÕªU7nœš4i¢ØØXMžÝDZ5kÖxÍŸ8q¢Ù¦M›ÌÌì³Ï>3I¶hÑé5EFFZ:ulïÞ½žy«W¯6IöðÃ{Õ~øá‡&Éþú׿š™ÙðáíS§Nžå÷ÝwŸÅÇÇ›$ûòË/ÍÌ,==Ý$Ùûï¿o%%%j“'OöZï”)SlÆŒ§íÀEë5óÿ{x&¦9q&‰_|QµjÕÒ¸qã*½Lâ\$''+00P’¤Ë.»LÑÑÑêÙ³§§&>>^Ré7¡” ÖØ±c=?;Ž£[o½U;wîTFF†¶oß®ŒŒ ]sÍ5úñÇ•žž®ôôtíÛ·O]ºtÑçŸîÕÇå—_®ÓöºbÅ õèÑC]»võš?qâDéË/¿<çíOJJRãÆ=?¯\¹R’4iÒ$¯ºaÆ©yóæúì³Ï$I#FŒÐwß}§ììlI¥ß"3~üxEEEiéÒ¥’¤?üPJNN–ã8êÒ¥‹ž{î9Mš4IK–,Qaa¡žzê)ÎÄ B .qqqzúé§•––vÚ{Gœ­-Zxý¨V­ZÉqϼŠ.Lj‰‰ñ™_v9ÆŽ;”‘‘!Izî¹çÔ®];¯iõêÕÚ¾}»JJJ*í£"[¶l©°®l^Ù˜ç¢üú¶lÙ¢°°0¯`ãÔÚ²1®½öZI¥÷Ù¹s§6mÚ¤ääd 8Ðb|ðÁ:t¨BCC%I©©©2dˆ^zé%]uÕUjÔ¨‘ÆŽësé—:B ."wÝu—† ¢Y³fiÍš5>ËÇñ¹ùçO?ýTẂƒƒÏ«‡={öøŒ‘››+©4à¨S§Ž$é…^О={|¦ÌÌL¯o9›>êÕ«Wáv”›pÎÛQ~Üzõê)??_'Nœ¨pœ²1š6mªîÝ»ëã?Ö'Ÿ|¢ºuëª[·nJNNÖªU«´wï^}ùå—5j”çñÑÑÑZ°`öíÛ§7ÞxC пÿýoõèÑCEEEçÜ;+B .2/¾ø¢j×®­ßüæ7>Ëj×®íóa?==½JÇ/((ÐÆ½æ-_¾\aaajÕª•Ú¶m«€€}óÍ7ŠŽŽöšžzê)½üòËçÿ :e 3ó¹4%;;[[¶lñcäÈ‘Z²d‰–.]ª+®¸BJNNV~~¾¦OŸ.3ÓðáÃ%•~«Ê½÷Þ«Å‹«^½zºõÖ[µ`ÁM›6M{öìQZZÚÏ!™²ËJvíÚ峬cÇŽZ¸p¡V¯^-IÚ°aƒ¦M›Vå=üáÐñãÇuüøq=óÌ3zóÍ7õÀ(44T 4ÐÝwß­W^yEûÛßtøðaÏW—Ξ=Ûëþç2^NNŽF­uëÖ©¨¨HsæÌÑ´iÓ”””¤Ž;þìmºé¦›Ô²eKMœ8QóæÍSII‰V¯^­1cÆ(88ØëÌŠ#FhïÞ½š7ož’““%I­[·V‹-ôÊ+¯èòË/Wݺu%I5RJJЦL™¢;vH’8 åË—«N:j×®ÝÏ!¡²ËJÊ{ä‘GT«V-õêÕKuëÖU§NôÐC÷¥#iÙ²¥Õ A5kÖL¿ýío5jÔ(MŸ>ÝSóä“OjĈš9ãAÏFXX˜æÍ›§àà`Ýxãjܸ±zõê¥o¾ùFï¿ÿ¾:uêä©íر£tüøq <Ø3ðàÁ*))ñ SVG drawing This was produced by version 4.1 of GNU libplot, a free library for exporting 2-D vector graphics. 0 50 100 150 200 250 1000 10000 100000 1e+06 1e+07 Speed (Krow/s) Number of rows Writing with medium record size (56 bytes) No Psyco Psyco PyTables-3.7.0/doc/source/usersguide/index.rst000066400000000000000000000045761416254111300213430ustar00rootroot00000000000000===================== PyTables User's Guide ===================== ------------------------------- Hierarchical datasets in Python ------------------------------- :Authors: **Francesc Alted, Ivan Vilata, Scott Prater, Vicent Mas, Tom Hedley, Antonio Valentino, Jeffrey Whitaker, Anthony Scopatz, Josh Moore** :Copyright: |copy| 2002, 2003, 2004 - Francesc Alted |copy| 2005, 2006, 2007 - Cárabos Coop. V. |copy| 2008, 2009, 2010 - Francesc Alted |copy| 2011–2021 - PyTables maintainers -------- Contents -------- .. toctree:: :maxdepth: 1 introduction installation tutorials libref optimization filenode datatypes condition_syntax parameter_files utilities file_format bibliography -------------------------------------------------------- Copyright Notice and Statement for PyTables User's Guide -------------------------------------------------------- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: a. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. b. 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. c. Neither the name of Francesc Alted nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 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. .. |copy| unicode:: U+000A9 .. COPYRIGHT SIGN PyTables-3.7.0/doc/source/usersguide/installation.rst000066400000000000000000000501221416254111300227210ustar00rootroot00000000000000Installation ============ .. epigraph:: Make things as simple as possible, but not any simpler. -- Albert Einstein The Python Distutils are used to build and install PyTables, so it is fairly simple to get the application up and running. If you want to install the package from sources you can go on reading to the next section. However, if you want to go straight to binaries that 'just work' for the main platforms (Linux, Mac OSX and Windows), you might want to use the excellent Anaconda_, ActivePython_, Canopy_ distributions. PyTables usually distributes its own Windows binaries too; go :ref:`binaryInstallationDescr` for instructions. Finally `Christoph Gohlke`_ also maintains an excellent suite of a variety of binary packages for Windows at his site. .. _Anaconda: https://store.continuum.io/cshop/anaconda/ .. _Canopy: https://www.enthought.com/products/canopy/ .. _ActivePython: https://www.activestate.com/activepython/downloads .. _`Christoph Gohlke`: http://www.lfd.uci.edu/~gohlke/pythonlibs/ Installation from source ------------------------ These instructions are for both Unix/MacOS X and Windows systems. If you are using Windows, it is assumed that you have a recent version of MS Visual C++ compiler installed. A GCC compiler is assumed for Unix, but other compilers should work as well. Extensions in PyTables have been developed in Cython (see :ref:`[CYTHON] `) and the C language. You can rebuild everything from scratch if you have Cython installed, but this is not necessary, as the Cython compiled source is included in the source distribution. To compile PyTables you will need a recent version of Python, the HDF5 (C flavor) library from http://www.hdfgroup.org, and the NumPy (see :ref:`[NUMPY] `) and Numexpr (see :ref:`[NUMEXPR] `) packages. Prerequisites ~~~~~~~~~~~~~ First, make sure that you have * Python_ >= 3.6 (PyTables-3.5 was the last release with Python 2.7 support) * HDF5_ >= 1.8.4 (>=1.8.15 is strongly recommended) * NumPy_ >= 1.19.0 * Numexpr_ >= 2.6.2 * Cython_ >= 0.29.21 * c-blosc_ >= 1.4.1 (sources are bundled with PyTables sources but the user can use an external version of sources using the :envvar:`BLOSC_DIR` environment variable or the `--blosc` flag of the :file:`setup.py`) installed (for testing purposes, we are using HDF5_ 1.8.15, NumPy_ 1.10.2 and Numexpr_ 2.5.2 currently). If you don't, fetch and install them before proceeding. .. _Python: http://www.python.org .. _HDF5: http://www.hdfgroup.org/HDF5 .. _NumPy: http://www.numpy.org .. _Numexpr: http://code.google.com/p/numexpr .. _Cython: http://www.cython.org .. _c-blosc: http://blosc.org Compile and install these packages (but see :ref:`prerequisitesBinInst` for instructions on how to install pre-compiled binaries if you are not willing to compile the prerequisites on Windows systems). For compression (and possibly improved performance), you will need to install the Zlib (see :ref:`[ZLIB] `), which is also required by HDF5 as well. You may also optionally install the excellent LZO compression library (see :ref:`[LZO] ` and :ref:`compressionIssues`). The high-performance bzip2 compression library can also be used with PyTables (see :ref:`[BZIP2] `). The Blosc (see :ref:`[BLOSC] `) compression library is embedded in PyTables, so this will be used in case it is not found in the system. So, in case the installer warns about not finding it, do not worry too much ;) **Unix** setup.py will detect HDF5, Blosc, LZO, or bzip2 libraries and include files under :file:`/usr` or :file:`/usr/local`; this will cover most manual installations as well as installations from packages. If setup.py can not find libhdf5, libhdf5 (or liblzo, or libbz2 that you may wish to use) or if you have several versions of a library installed and want to use a particular one, then you can set the path to the resource in the environment, by setting the values of the :envvar:`HDF5_DIR`, :envvar:`LZO_DIR`, :envvar:`BZIP2_DIR` or :envvar:`BLOSC_DIR` environment variables to the path to the particular resource. You may also specify the locations of the resource root directories on the setup.py command line. For example:: --hdf5=/stuff/hdf5-1.8.12 --blosc=/stuff/blosc-1.8.1 --lzo=/stuff/lzo-2.02 --bzip2=/stuff/bzip2-1.0.5 If your HDF5 library was built as a shared library not in the runtime load path, then you can specify the additional linker flags needed to find the shared library on the command line as well. For example:: --lflags="-Xlinker -rpath -Xlinker /stuff/hdf5-1.8.12/lib" You may also want to try setting the :envvar:`LD_LIBRARY_PATH` environment variable to point to the directory where the shared libraries can be found. Check your compiler and linker documentation as well as the Python Distutils documentation for the correct syntax or environment variable names. It is also possible to link with specific libraries by setting the :envvar:`LIBS` environment variable:: LIBS="hdf5-1.8.12 nsl" Starting from PyTables 3.2 can also query the *pkg-config* database to find the required packages. If available, pkg-config is used by default unless explicitly disabled. To suppress the use of *pkg-config*:: $ python3 setup.py build --use-pkgconfig=FALSE or use the :envvar:`USE-PKGCONFIG` environment variable:: $ env USE_PKGCONFIG=FALSE python3 setup.py build **Windows** You can get ready-to-use Windows binaries and other development files for most of the following libraries from the GnuWin32 project (see :ref:`[GNUWIN32] `). In case you cannot find the LZO binaries in the GnuWin32 repository, you can find them at http://sourceforge.net/projects/pytables/files/lzo-win. Once you have installed the prerequisites, setup.py needs to know where the necessary library *stub* (.lib) and *header* (.h) files are installed. You can set the path to the include and dll directories for the HDF5 (mandatory) and LZO, BZIP2, BLOSC (optional) libraries in the environment, by setting the values of the :envvar:`HDF5_DIR`, :envvar:`LZO_DIR`, :envvar:`BZIP2_DIR` or :envvar:`BLOSC_DIR` environment variables to the path to the particular resource. For example:: set HDF5_DIR=c:\\stuff\\hdf5-1.8.5-32bit-VS2008-IVF101\\release set BLOSC_DIR=c:\\Program Files (x86)\\Blosc set LZO_DIR=c:\\Program Files (x86)\\GnuWin32 set BZIP2_DIR=c:\\Program Files (x86)\\GnuWin32 You may also specify the locations of the resource root directories on the setup.py command line. For example:: --hdf5=c:\\stuff\\hdf5-1.8.5-32bit-VS2008-IVF101\\release --blosc=c:\\Program Files (x86)\\Blosc --lzo=c:\\Program Files (x86)\\GnuWin32 --bzip2=c:\\Program Files (x86)\\GnuWin32 **Conda** Pre-built packages for PyTables are available in the anaconda (default) channel:: conda install pytables The most recent version is usually available in the conda-forge channel:: conda config --add channels conda-forge conda install pytables The HDF5 libraries and other helper packages are automatically found in a conda environment. During installation setup.py uses the `CONDA_PREFIX` environment variable to detect a conda environment. If detected it will try to find all packages within this environment. PyTables needs at least the hdf5 package:: conda install hdf5 python3 setup.py install It is still possible to override package locations using the :envvar:`HDF5_DIR`, :envvar:`LZO_DIR`, :envvar:`BZIP2_DIR` or :envvar:`BLOSC_DIR` environment variables. When inside a conda environment *pkg-config* will not work. To disable using the conda environment and fall back to *pkg-config* use `--no-conda`:: python3 setup.py install --no-conda When the `--use-pkgconfig` flag is used, `--no-conda` is assumed. **Development version (Unix)** Installation of the development version is very similar to installation from a source package (described above). There are two main differences: #. sources have to be downloaded from the `PyTables source repository`_ hosted on GitHub_. Git (see :ref:`[GIT] `) is used as VCS. The following command create a local copy of latest development version sources:: $ git clone --recursive https://github.com/PyTables/PyTables.git #. sources in the git repository do not include pre-built documentation and pre-generated C code of Cython extension modules. To be able to generate them, both Cython (see :ref:`[CYTHON] `) and sphinx >= 1.0.7 (see :ref:`[SPHINX] `) are mandatory prerequisites. .. _`PyTables source repository`: https://github.com/PyTables/PyTables .. _GitHub: https://github.com PyTables package installation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once you have installed the HDF5 library and the NumPy and Numexpr packages, you can proceed with the PyTables package itself. #. Run this command from the main PyTables distribution directory, including any extra command line arguments as discussed above:: $ python3 setup.py build If the HDF5 installation is in a custom path, e.g. $HOME/hdf5-1.8.15pre7, one of the following commands can be used:: $ python3 setup.py build --hdf5=$HOME/hdf5-1.8.15pre7 .. note:: AVX2 support is detected automatically for your machine and, if found, it is enabled by default. In some situations you may want to disable AVX2 explicitly (maybe your binaries have to be exported and run on machines that do not have AVX2 support). In that case, define the DISABLE_AVX2 environment variable:: $ DISABLE_AVX2=True python3 setup.py build # for bash and its variants #. To run the test suite, execute any of these commands. **Unix** In the sh shell and its variants:: $ cd build/lib.linux-x86_64-3.3 $ env PYTHONPATH=. python3 tables/tests/test_all.py or, if you prefer:: $ cd build/lib.linux-x86_64-3.3 $ env PYTHONPATH=. python3 -c "import tables; tables.test()" .. note:: the syntax used above overrides original contents of the :envvar:`PYTHONPATH` environment variable. If this is not the desired behaviour and the user just wants to add some path before existing ones, then the safest syntax to use is the following:: $ env PYTHONPATH=.${PYTHONPATH:+:$PYTHONPATH} python3 tables/tests/test_all.py Please refer to your :program:`sh` documentation for details. **Windows** Open the command prompt (cmd.exe or command.com) and type:: > cd build\\lib.linux-x86_64-2.7 > set PYTHONPATH=.;%PYTHONPATH% > python3 tables\\tests\\test_all.py or:: > cd build\\lib.linux-x86_64-2.7 > set PYTHONPATH=.;%PYTHONPATH% > python3 -c "import tables; tables.test()" Both commands do the same thing, but the latter still works on an already installed PyTables (so, there is no need to set the :envvar:`PYTHONPATH` variable for this case). However, before installation, the former is recommended because it is more flexible, as you can see below. If you would like to see verbose output from the tests simply add the `-v` flag and/or the word verbose to the first of the command lines above. You can also run only the tests in a particular test module. For example, to execute just the test_types test suite, you only have to specify it:: # change to backslashes for win $ python3 tables/tests/test_types.py -v You have other options to pass to the :file:`test_all.py` driver:: # change to backslashes for win $ python3 tables/tests/test_all.py --heavy The command above runs every test in the test unit. Beware, it can take a lot of time, CPU and memory resources to complete:: # change to backslashes for win $ python3 tables/tests/test_all.py --print-versions The command above shows the versions for all the packages that PyTables relies on. Please be sure to include this when reporting bugs:: # only under Linux 2.6.x $ python3 tables/tests/test_all.py --show-memory The command above prints out the evolution of the memory consumption after each test module completion. It's useful for locating memory leaks in PyTables (or packages behind it). Only valid for Linux 2.6.x kernels. And last, but not least, in case a test fails, please run the failing test module again and enable the verbose output:: $ python3 tables/tests/test_.py -v verbose and, very important, obtain your PyTables version information by using the `--print-versions` flag (see above) and send back both outputs to developers so that we may continue improving PyTables. If you run into problems because Python can not load the HDF5 library or other shared libraries. **Unix** Try setting the LD_LIBRARY_PATH or equivalent environment variable to point to the directory where the missing libraries can be found. **Windows** Put the DLL libraries (hdf5dll.dll and, optionally, lzo1.dll, bzip2.dll or blosc.dll) in a directory listed in your :envvar:`PATH` environment variable. The setup.py installation program will print out a warning to that effect if the libraries can not be found. #. To install the entire PyTables Python package, change back to the root distribution directory and run the following command (make sure you have sufficient permissions to write to the directories where the PyTables files will be installed):: $ python3 setup.py install Again if one needs to point to libraries installed in custom paths, then specific setup.py options can be used:: $ python3 setup.py install --hdf5=/hdf5/custom/path or:: $ env HDF5_DIR=/hdf5/custom/path python3 setup.py install Of course, you will need super-user privileges if you want to install PyTables on a system-protected area. You can select, though, a different place to install the package using the `--prefix` flag:: $ python3 setup.py install --prefix="/home/myuser/mystuff" Have in mind, however, that if you use the `--prefix` flag to install in a non-standard place, you should properly setup your :envvar:`PYTHONPATH` environment variable, so that the Python interpreter would be able to find your new PyTables installation. You have more installation options available in the Distutils package. Issue a:: $ python3 setup.py install --help for more information on that subject. That's it! Now you can skip to the next chapter to learn how to use PyTables. Installation with :program:`pip` -------------------------------- Many users find it useful to use the :program:`pip` program (or similar ones) to install python packages. As explained in previous sections the user should in any case ensure that all dependencies listed in the `Prerequisites`_ section are correctly installed. The simplest way to install PyTables using :program:`pip` is the following:: $ python3 -m pip install tables The following example shows how to install the latest stable version of PyTables in the user folder when a older version of the package is already installed at system level:: $ python3 -m pip install --user --upgrade tables The `--user` option tells to the :program:`pip` tool to install the package in the user folder (``$HOME/.local`` on GNU/Linux and Unix systems), while the `--upgrade` option forces the installation of the latest version even if an older version of the package is already installed. Additional options for the setup.py script can be specified using them `--install-option`:: $ python3 -m pip install --install-option='--hdf5=/custom/path/to/hdf5' tables or:: $ env HDF5_DIR=/custom/path/to/hdf5 python3 -m pip install tables The :program:`pip` tool can also be used to install packages from a source tar-ball:: $ python3 -m pip install tables-3.0.0.tar.gz To install the development version of PyTables from the *develop* branch of the main :program:`git` :ref:`[GIT] ` repository the command is the following:: $ python3 -m pip install git+https://github.com/PyTables/PyTables.git@develop#egg=tables A similar command can be used to install a specific tagged version:: $ python3 -m pip install git+https://github.com/PyTables/PyTables.git@v.2.4.0#egg=tables Of course the `pip` can be used to install only python packages. Other dependencies like the HDF5 library of compression libraries have to be installed by the user. .. note:: Recent versions of Debian_ and Ubuntu_ the HDF5 library is installed in with a very peculiar layout that allows to have both the serial and MPI versions installed at the same time. PyTables >= 3.2 natively supports the new layout via *pkg-config* (that is expected to be installed on the system at build time). If *pkg-config* is not available or PyTables is older than version 3.2, then the following command can be used:: $ env CPPFLAGS=-I/usr/include/hdf5/serial \ LDFLAGS=-L/usr/lib/x86_64-linux-gnu/hdf5/serial python3 setup.py install or:: $ env CPPFLAGS=-I/usr/include/hdf5/serial \ LDFLAGS=-L/usr/lib/x86_64-linux-gnu/hdf5/serial python3 -m pip install tables .. _Debian: https://www.debian.org .. _Ubuntu: http://www.ubuntu.com .. _binaryInstallationDescr: Binary installation (Windows) ----------------------------- This section is intended for installing precompiled binaries on Windows platforms. Binaries are distribution in wheel format, which can be downloaded and installed using pip as described above. You may also find it useful for instructions on how to install *binary prerequisites* even if you want to compile PyTables itself on Windows. .. _prerequisitesBinInst: Windows prerequisites ~~~~~~~~~~~~~~~~~~~~~ First, make sure that you have Python 3, NumPy 1.8.0 and Numexpr 2.5.2 or higher installed. To enable compression with the optional LZO library (see the :ref:`compressionIssues` for hints about how it may be used to improve performance), fetch and install the LZO from http://sourceforge.net/projects/pytables/files/lzo-win (choose v1.x for Windows 32-bit and v2.x for Windows 64-bit). Normally, you will only need to fetch that package and copy the included lzo1.dll/lzo2.dll file in a directory in the PATH environment variable (for example C:\\WINDOWS\\SYSTEM) or python_installation_path\\Lib\\site-packages\\tables (the last directory may not exist yet, so if you want to install the DLL there, you should do so *after* installing the PyTables package), so that it can be found by the PyTables extensions. Please note that PyTables has internal machinery for dealing with uninstalled optional compression libraries, so, you don't need to install the LZO or bzip2 dynamic libraries if you don't want to. PyTables package installation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ On PyPI wheels for 32 and 64-bit versions of Windows and are usually provided. They are automatically found and installed using pip:: $ python3 -m pip install tables If a matching wheel cannot be found for your installation, third party built wheels can be found e.g. at the `Unofficial Windows Binaries for Python Extension Packages `_ page. Download the wheel matching the version of python and either the 32 or 64-bit version and install using pip:: # python 3.6 64-bit: $ python3 -m pip install tables-3.6.1-2-cp36-cp36m-win_amd64.whl You can (and *you should*) test your installation by running the next commands:: >>> import tables >>> tables.test() on your favorite python shell. If all the tests pass (possibly with a few warnings, related to the potential unavailability of LZO lib) you already have a working, well-tested copy of PyTables installed! If any test fails, please copy the output of the error messages as well as the output of:: >>> tables.print_versions() and mail them to the developers so that the problem can be fixed in future releases. You can proceed now to the next chapter to see how to use PyTables. PyTables-3.7.0/doc/source/usersguide/introduction.rst000066400000000000000000000355221416254111300227500ustar00rootroot00000000000000Introduction ============ .. epigraph:: La sabiduría no vale la pena si no es posible servirse de ella para inventar una nueva manera de preparar los garbanzos. [Wisdom isn't worth anything if you can't use it to come up with a new way to cook garbanzos.] -- Gabriel García Márquez, A wise Catalan in *"Cien años de soledad"* The goal of PyTables is to enable the end user to manipulate easily data *tables* and *array* objects in a hierarchical structure. The foundation of the underlying hierarchical data organization is the excellent HDF5 library (see :ref:`[HDGF1] `). It should be noted that this package is not intended to serve as a complete wrapper for the entire HDF5 API, but only to provide a flexible, *very pythonic* tool to deal with (arbitrarily) large amounts of data (typically bigger than available memory) in tables and arrays organized in a hierarchical and persistent disk storage structure. A table is defined as a collection of records whose values are stored in *fixed-length* fields. All records have the same structure and all values in each field have the same *data type*. The terms *fixed-length* and strict *data types* may seem to be a strange requirement for an interpreted language like Python, but they serve a useful function if the goal is to save very large quantities of data (such as is generated by many data acquisition systems, Internet services or scientific applications, for example) in an efficient manner that reduces demand on CPU time and I/O. In order to emulate in Python records mapped to HDF5 C structs PyTables implements a special class so as to easily define all its fields and other properties. PyTables also provides a powerful interface to mine data in tables. Records in tables are also known in the HDF5 naming scheme as *compound* data types. For example, you can define arbitrary tables in Python simply by declaring a class with named fields and type information, such as in the following example:: class Particle(IsDescription): name = StringCol(16) # 16-character String idnumber = Int64Col() # signed 64-bit integer ADCcount = UInt16Col() # unsigned short integer TDCcount = UInt8Col() # unsigned byte grid_i = Int32Col() # integer grid_j = Int32Col() # integer # A sub-structure (nested data-type) class Properties(IsDescription): pressure = Float32Col(shape=(2,3)) # 2-D float array (single-precision) energy = Float64Col(shape=(2,3,4)) # 3-D float array (double-precision) You then pass this class to the table constructor, fill its rows with your values, and save (arbitrarily large) collections of them to a file for persistent storage. After that, the data can be retrieved and post-processed quite easily with PyTables or even with another HDF5 application (in C, Fortran, Java or whatever language that provides a library to interface with HDF5). Other important entities in PyTables are *array* objects, which are analogous to tables with the difference that all of their components are homogeneous. They come in different flavors, like *generic* (they provide a quick and fast way to deal with for numerical arrays), *enlargeable* (arrays can be extended along a single dimension) and *variable length* (each row in the array can have a different number of elements). The next section describes the most interesting capabilities of PyTables. Main Features ------------- PyTables takes advantage of the object orientation and introspection capabilities offered by Python, the powerful data management features of HDF5, and NumPy's flexibility and Numexpr's high-performance manipulation of large sets of objects organized in a grid-like fashion to provide these features: - *Support for table entities:* You can tailor your data adding or deleting records in your tables. Large numbers of rows (up to 2**63, much more than will fit into memory) are supported as well. - *Multidimensional and nested table cells:* You can declare a column to consist of values having any number of dimensions besides scalars, which is the only dimensionality allowed by the majority of relational databases. You can even declare columns that are made of other columns (of different types). - *Indexing support for columns of tables:* Very useful if you have large tables and you want to quickly look up for values in columns satisfying some criteria. - *Support for numerical arrays:* NumPy (see :ref:`[NUMPY] `) arrays can be used as a useful complement of tables to store homogeneous data. - *Enlargeable arrays:* You can add new elements to existing arrays on disk in any dimension you want (but only one). Besides, you are able to access just a slice of your datasets by using the powerful extended slicing mechanism, without need to load all your complete dataset in memory. - *Variable length arrays:* The number of elements in these arrays can vary from row to row. This provides a lot of flexibility when dealing with complex data. - *Supports a hierarchical data model:* Allows the user to clearly structure all data. PyTables builds up an *object tree* in memory that replicates the underlying file data structure. Access to objects in the file is achieved by walking through and manipulating this object tree. Besides, this object tree is built in a lazy way, for efficiency purposes. - *User defined metadata:* Besides supporting system metadata (like the number of rows of a table, shape, flavor, etc.) the user may specify arbitrary metadata (as for example, room temperature, or protocol for IP traffic that was collected) that complement the meaning of actual data. - *Ability to read/modify generic HDF5 files:* PyTables can access a wide range of objects in generic HDF5 files, like compound type datasets (that can be mapped to Table objects), homogeneous datasets (that can be mapped to Array objects) or variable length record datasets (that can be mapped to VLArray objects). Besides, if a dataset is not supported, it will be mapped to a special UnImplemented class (see :ref:`UnImplementedClassDescr`), that will let the user see that the data is there, although it will be unreachable (still, you will be able to access the attributes and some metadata in the dataset). With that, PyTables probably can access and *modify* most of the HDF5 files out there. - *Data compression:* Supports data compression (using the *Zlib*, *LZO*, *bzip2* and *Blosc* compression libraries) out of the box. This is important when you have repetitive data patterns and don't want to spend time searching for an optimized way to store them (saving you time spent analyzing your data organization). - *High performance I/O:* On modern systems storing large amounts of data, tables and array objects can be read and written at a speed only limited by the performance of the underlying I/O subsystem. Moreover, if your data is compressible, even that limit is surmountable! - *Support of files bigger than 2 GB:* PyTables automatically inherits this capability from the underlying HDF5 library (assuming your platform supports the C long long integer, or, on Windows, __int64). - *Architecture-independent:* PyTables has been carefully coded (as HDF5 itself) with little-endian/big-endian byte ordering issues in mind. So, you can write a file on a big-endian machine (like a Sparc or MIPS) and read it on other little-endian machine (like an Intel or Alpha) without problems. In addition, it has been tested successfully with 64 bit platforms (Intel-64, AMD-64, PowerPC-G5, MIPS, UltraSparc) using code generated with 64 bit aware compilers. .. _ObjectTreeSection: The Object Tree --------------- The hierarchical model of the underlying HDF5 library allows PyTables to manage tables and arrays in a tree-like structure. In order to achieve this, an *object tree* entity is *dynamically* created imitating the HDF5 structure on disk. The HDF5 objects are read by walking through this object tree. You can get a good picture of what kind of data is kept in the object by examining the *metadata* nodes. The different nodes in the object tree are instances of PyTables classes. There are several types of classes, but the most important ones are the Node, Group and Leaf classes. All nodes in a PyTables tree are instances of the Node class. The Group and Leaf classes are descendants of Node. Group instances (referred to as *groups* from now on) are a grouping structure containing instances of zero or more groups or leaves, together with supplementary metadata. Leaf instances (referred to as *leaves*) are containers for actual data and can not contain further groups or leaves. The Table, Array, CArray, EArray, VLArray and UnImplemented classes are descendants of Leaf, and inherit all its properties. Working with groups and leaves is similar in many ways to working with directories and files on a Unix filesystem, i.e. a node (file or directory) is always a *child* of one and only one group (directory), its *parent group* [1]_. Inside of that group, the node is accessed by its *name*. As is the case with Unix directories and files, objects in the object tree are often referenced by giving their full (absolute) path names. In PyTables this full path can be specified either as string (such as '/subgroup2/table3', using / as a parent/child separator) or as a complete object path written in a format known as the *natural name* schema (such as file.root.subgroup2.table3). Support for *natural naming* is a key aspect of PyTables. It means that the names of instance variables of the node objects are the same as the names of its children [2]_. This is very *Pythonic* and intuitive in many cases. Check the tutorial :ref:`readingAndSelectingUsage` for usage examples. You should also be aware that not all the data present in a file is loaded into the object tree. The *metadata* (i.e. special data that describes the structure of the actual data) is loaded only when the user want to access to it (see later). Moreover, the actual data is not read until she request it (by calling a method on a particular node). Using the object tree (the metadata) you can retrieve information about the objects on disk such as table names, titles, column names, data types in columns, numbers of rows, or, in the case of arrays, their shapes, typecodes, etc. You can also search through the tree for specific kinds of data then read it and process it. In a certain sense, you can think of PyTables as a tool that applies the same introspection capabilities of Python objects to large amounts of data in persistent storage. It is worth noting that PyTables sports a *metadata cache system* that loads nodes *lazily* (i.e. on-demand), and unloads nodes that have not been used for some time (following a *Least Recently Used* schema). It is important to stress out that the nodes enter the cache after they have been unreferenced (in the sense of Python reference counting), and that they can be revived (by referencing them again) directly from the cache without performing the de-serialization process from disk. This feature allows dealing with files with large hierarchies very quickly and with low memory consumption, while retaining all the powerful browsing capabilities of the previous implementation of the object tree. See :ref:`[OPTIM] ` for more facts about the advantages introduced by this new metadata cache system. To better understand the dynamic nature of this object tree entity, let's start with a sample PyTables script (which you can find in examples/objecttree.py) to create an HDF5 file:: import tables as tb class Particle(tb.IsDescription): identity = tb.StringCol(itemsize=22, dflt=" ", pos=0) # character String idnumber = tb.Int16Col(dflt=1, pos = 1) # short integer speed = tb.Float32Col(dflt=1, pos = 2) # single-precision # Open a file in "w"rite mode fileh = tb.open_file("objecttree.h5", mode = "w") # Get the HDF5 root group root = fileh.root # Create the groups group1 = fileh.create_group(root, "group1") group2 = fileh.create_group(root, "group2") # Now, create an array in root group array1 = fileh.create_array(root, "array1", ["string", "array"], "String array") # Create 2 new tables in group1 table1 = fileh.create_table(group1, "table1", Particle) table2 = fileh.create_table("/group2", "table2", Particle) # Create the last table in group2 array2 = fileh.create_array("/group1", "array2", [1,2,3,4]) # Now, fill the tables for table in (table1, table2): # Get the record object associated with the table: row = table.row # Fill the table with 10 records for i in range(10): # First, assign the values to the Particle record row['identity'] = f'This is particle: {i:2d}' row['idnumber'] = i row['speed'] = i * 2. # This injects the Record values row.append() # Flush the table buffers table.flush() # Finally, close the file (this also will flush all the remaining buffers!) fileh.close() This small program creates a simple HDF5 file called objecttree.h5 with the structure that appears in :ref:`Figure 1 ` [3]_. When the file is created, the metadata in the object tree is updated in memory while the actual data is saved to disk. When you close the file the object tree is no longer available. However, when you reopen this file the object tree will be reconstructed in memory from the metadata on disk (this is done in a lazy way, in order to load only the objects that are required by the user), allowing you to work with it in exactly the same way as when you originally created it. .. _objecttree-h5: .. figure:: images/objecttree-h5.png :align: center **Figure 1: An HDF5 example with 2 subgroups, 2 tables and 1 array.** In :ref:`Figure2 `, you can see an example of the object tree created when the above objecttree.h5 file is read (in fact, such an object tree is always created when reading any supported generic HDF5 file). It is worthwhile to take your time to understand it [4]_. It will help you understand the relationships of in-memory PyTables objects. .. _objecttree: .. figure:: images/objecttree.png :width: 100% :align: center **Figure 2: A PyTables object tree example.** --------------------------- .. [1] PyTables does not support hard links - for the moment. .. [2] I got this simple but powerful idea from the excellent Objectify module by David Mertz (see :ref:`[MERTZ] `). .. [3] We have used ViTables (see :ref:`[VITABLES] `) in order to create this snapshot. .. [4] Bear in mind, however, that this diagram is *not* a standard UML class diagram; it is rather meant to show the connections between the PyTables objects and some of its most important attributes and methods. PyTables-3.7.0/doc/source/usersguide/libref.rst000066400000000000000000000031351416254111300214650ustar00rootroot00000000000000.. _library_reference: Library Reference ================= PyTables implements several classes to represent the different nodes in the object tree. They are named File, Group, Leaf, Table, Array, CArray, EArray, VLArray and UnImplemented. Another one allows the user to complement the information on these different objects; its name is AttributeSet. Finally, another important class called IsDescription allows to build a Table record description by declaring a subclass of it. Many other classes are defined in PyTables, but they can be regarded as helpers whose goal is mainly to declare the *data type properties* of the different first class objects and will be described at the end of this chapter as well. An important function, called open_file is responsible to create, open or append to files. In addition, a few utility functions are defined to guess if the user supplied file is a *PyTables* or *HDF5* file. These are called is_pytables_file() and is_hdf5_file(), respectively. There exists also a function called which_lib_version() that informs about the versions of the underlying C libraries (for example, HDF5 or Zlib) and another called print_versions() that prints all the versions of the software that PyTables relies on. Finally, test() lets you run the complete test suite from a Python console interactively. .. toctree:: :maxdepth: 2 libref/top_level libref/file_class libref/hierarchy_classes libref/structured_storage libref/homogenous_storage libref/link_classes libref/declarative_classes libref/helper_classes libref/expr_class libref/filenode_classes PyTables-3.7.0/doc/source/usersguide/libref/000077500000000000000000000000001416254111300207315ustar00rootroot00000000000000PyTables-3.7.0/doc/source/usersguide/libref/declarative_classes.rst000066400000000000000000000122271416254111300254670ustar00rootroot00000000000000.. currentmodule:: tables Declarative classes =================== In this section a series of classes that are meant to *declare* datatypes that are required for creating primary PyTables datasets are described. .. _AtomClassDescr: The Atom class and its descendants ---------------------------------- .. autoclass:: Atom .. These are defined in the class docstring Atom instance variables ^^^^^^^^^^^^^^^^^^^^^^^ .. autoattribute:: Atom.dflt .. autoattribute:: Atom.dtype .. autoattribute:: Atom.itemsize .. autoattribute:: Atom.kind .. autoattribute:: Atom.shape .. autoattribute:: Atom.type Atom properties ~~~~~~~~~~~~~~~ .. autoattribute:: Atom.ndim .. autoattribute:: Atom.recarrtype .. autoattribute:: Atom.size Atom methods ~~~~~~~~~~~~ .. automethod:: Atom.copy Atom factory methods ~~~~~~~~~~~~~~~~~~~~ .. automethod:: Atom.from_dtype .. automethod:: Atom.from_kind .. automethod:: Atom.from_sctype .. automethod:: Atom.from_type Atom Sub-classes ~~~~~~~~~~~~~~~~ .. autoclass:: StringAtom :members: .. autoclass:: BoolAtom :members: .. autoclass:: IntAtom :members: .. autoclass:: Int8Atom :members: .. autoclass:: Int16Atom :members: .. autoclass:: Int32Atom :members: .. autoclass:: Int64Atom :members: .. autoclass:: UIntAtom :members: .. autoclass:: UInt8Atom :members: .. autoclass:: UInt16Atom :members: .. autoclass:: UInt32Atom :members: .. autoclass:: UInt64Atom :members: .. autoclass:: FloatAtom :members: .. autoclass:: Float32Atom :members: .. autoclass:: Float64Atom :members: .. autoclass:: ComplexAtom :members: .. autoclass:: Time32Atom :members: .. autoclass:: Time64Atom :members: .. autoclass:: EnumAtom :members: Pseudo atoms ~~~~~~~~~~~~ Now, there come three special classes, ObjectAtom, VLStringAtom and VLUnicodeAtom, that actually do not descend from Atom, but which goal is so similar that they should be described here. Pseudo-atoms can only be used with VLArray datasets (see :ref:`VLArrayClassDescr`), and they do not support multidimensional values, nor multiple values per row. They can be recognised because they also have kind, type and shape attributes, but no size, itemsize or dflt ones. Instead, they have a base atom which defines the elements used for storage. See :file:`examples/vlarray1.py` and :file:`examples/vlarray2.py` for further examples on VLArray datasets, including object serialization and string management. ObjectAtom ^^^^^^^^^^ .. autoclass:: ObjectAtom :members: .. _VLStringAtom: VLStringAtom ^^^^^^^^^^^^ .. autoclass:: VLStringAtom :members: .. _VLUnicodeAtom: VLUnicodeAtom ^^^^^^^^^^^^^ .. autoclass:: VLUnicodeAtom :members: .. _ColClassDescr: The Col class and its descendants --------------------------------- .. autoclass:: Col .. Col instance variables ^^^^^^^^^^^^^^^^^^^^^^ .. autoattribute:: _v_pos Col instance variables ~~~~~~~~~~~~~~~~~~~~~~ In addition to the variables that they inherit from the Atom class, Col instances have the following attributes. .. attribute:: Col._v_pos The *relative* position of this column with regard to its column siblings. Col factory methods ~~~~~~~~~~~~~~~~~~~ .. automethod:: Col.from_atom Col sub-classes ~~~~~~~~~~~~~~~ .. autoclass:: StringCol :members: .. autoclass:: BoolCol :members: .. autoclass:: IntCol :members: .. autoclass:: Int8Col :members: .. autoclass:: Int16Col :members: .. autoclass:: Int32Col :members: .. autoclass:: Int64Col :members: .. autoclass:: UIntCol :members: .. autoclass:: UInt8Col :members: .. autoclass:: UInt16Col :members: .. autoclass:: UInt32Col :members: .. autoclass:: UInt64Col :members: .. autoclass:: Float32Col :members: .. autoclass:: Float64Col :members: .. autoclass:: ComplexCol :members: .. autoclass:: TimeCol :members: .. autoclass:: Time32Col :members: .. autoclass:: Time64Col :members: .. autoclass:: EnumCol :members: .. _IsDescriptionClassDescr: The IsDescription class ----------------------- .. autoclass:: IsDescription Description helper functions ---------------------------- .. autofunction:: tables.description.descr_from_dtype .. autofunction:: tables.description.dtype_from_descr .. _AttributeSetClassDescr: The AttributeSet class ---------------------- .. autoclass:: tables.attributeset.AttributeSet .. These are defined in the class docstring AttributeSet attributes ~~~~~~~~~~~~~~~~~~~~~~~ .. autoattribute:: tables.attributeset.AttributeSet._v_attrnames .. autoattribute:: tables.attributeset.AttributeSet._v_attrnamessys .. autoattribute:: tables.attributeset.AttributeSet._v_attrnamesuser .. autoattribute:: tables.attributeset.AttributeSet._v_unimplemented AttributeSet properties ~~~~~~~~~~~~~~~~~~~~~~~ .. autoattribute:: tables.attributeset.AttributeSet._v_node AttributeSet methods ~~~~~~~~~~~~~~~~~~~~ .. automethod:: tables.attributeset.AttributeSet._f_copy .. automethod:: tables.attributeset.AttributeSet._f_list .. automethod:: tables.attributeset.AttributeSet._f_rename .. automethod:: tables.attributeset.AttributeSet.__contains__ PyTables-3.7.0/doc/source/usersguide/libref/expr_class.rst000066400000000000000000000014671416254111300236360ustar00rootroot00000000000000.. currentmodule:: tables General purpose expression evaluator class ========================================== The Expr class -------------- .. autoclass:: Expr .. These are defined in the class docstring. Expr instance variables ~~~~~~~~~~~~~~~~~~~~~~~ .. autoattribute:: Expr.append_mode .. autoattribute:: Expr.maindim .. autoattribute:: Expr.names .. autoattribute:: Expr.out .. autoattribute:: Expr.o_start .. autoattribute:: Expr.o_stop .. autoattribute:: Expr.o_step .. autoattribute:: Expr.shape .. autoattribute:: Expr.values Expr methods ~~~~~~~~~~~~ .. automethod:: Expr.eval .. automethod:: Expr.set_inputs_range .. automethod:: Expr.set_output .. automethod:: Expr.set_output_range Expr special methods ~~~~~~~~~~~~~~~~~~~~ .. automethod:: Expr.__iter__ PyTables-3.7.0/doc/source/usersguide/libref/file_class.rst000066400000000000000000000050471416254111300235750ustar00rootroot00000000000000.. currentmodule:: tables File manipulation class ======================= .. _FileClassDescr: The File Class -------------- .. autoclass:: File .. These are defined in the class docstring. This is necessary because attributes created in a class's __init__ method can't be documented with autoattribute. See Sphinx bug #904. https://bitbucket.org/birkenfeld/sphinx/issue/904 Attributes ~~~~~~~~~~ .. autoattribute:: File.filename .. autoattribute:: File.format_version .. autoattribute:: File.isopen .. autoattribute:: File.mode .. autoattribute:: File.root .. autoattribute:: File.root_uep File properties ~~~~~~~~~~~~~~~ .. autoattribute:: File.title .. autoattribute:: File.filters File methods - file handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automethod:: File.close .. automethod:: File.copy_file .. automethod:: File.flush .. automethod:: File.fileno .. automethod:: File.__enter__ .. automethod:: File.__exit__ .. automethod:: File.__str__ .. automethod:: File.__repr__ .. automethod:: File.get_file_image .. automethod:: File.get_filesize .. automethod:: File.get_userblock_size File methods - hierarchy manipulation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automethod:: File.copy_children .. automethod:: File.copy_node .. automethod:: File.create_array .. automethod:: File.create_carray .. automethod:: File.create_earray .. automethod:: File.create_external_link .. automethod:: File.create_group .. automethod:: File.create_hard_link .. automethod:: File.create_soft_link .. automethod:: File.create_table .. automethod:: File.create_vlarray .. automethod:: File.move_node .. automethod:: File.remove_node .. automethod:: File.rename_node File methods - tree traversal ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automethod:: File.get_node .. automethod:: File.is_visible_node .. automethod:: File.iter_nodes .. automethod:: File.list_nodes .. automethod:: File.walk_groups .. automethod:: File.walk_nodes .. automethod:: File.__contains__ .. automethod:: File.__iter__ File methods - Undo/Redo support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automethod:: File.disable_undo .. automethod:: File.enable_undo .. automethod:: File.get_current_mark .. automethod:: File.goto .. automethod:: File.is_undo_enabled .. automethod:: File.mark .. automethod:: File.redo .. automethod:: File.undo File methods - attribute handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automethod:: File.copy_node_attrs .. automethod:: File.del_node_attr .. automethod:: File.get_node_attr .. automethod:: File.set_node_attr PyTables-3.7.0/doc/source/usersguide/libref/filenode_classes.rst000066400000000000000000000044661416254111300247770ustar00rootroot00000000000000.. currentmodule:: tables.nodes.filenode .. _filenode_classes: Filenode Module =============== .. automodule:: tables.nodes.filenode Module constants ---------------- .. autodata:: NodeType .. autodata:: NodeTypeVersions Module functions ---------------- .. autofunction:: new_node .. autofunction:: open_node .. autofunction:: read_from_filenode .. autofunction:: save_to_filenode The RawPyTablesIO base class ---------------------------- .. autoclass:: RawPyTablesIO RawPyTablesIO attributes ~~~~~~~~~~~~~~~~~~~~~~~~ .. autoattribute:: RawPyTablesIO.mode RawPyTablesIO methods ~~~~~~~~~~~~~~~~~~~~~ .. automethod:: RawPyTablesIO.tell .. automethod:: RawPyTablesIO.seek .. automethod:: RawPyTablesIO.seekable .. automethod:: RawPyTablesIO.fileno .. automethod:: RawPyTablesIO.close .. automethod:: RawPyTablesIO.flush .. automethod:: RawPyTablesIO.truncate .. automethod:: RawPyTablesIO.readable .. automethod:: RawPyTablesIO.writable .. automethod:: RawPyTablesIO.readinto .. automethod:: RawPyTablesIO.readline .. automethod:: RawPyTablesIO.write The ROFileNode class -------------------- .. autoclass:: ROFileNode ROFileNode attributes ~~~~~~~~~~~~~~~~~~~~~ .. autoattribute:: ROFileNode.attrs ROFileNode methods ~~~~~~~~~~~~~~~~~~ .. automethod:: ROFileNode.flush .. automethod:: ROFileNode.read .. automethod:: ROFileNode.readline .. automethod:: ROFileNode.readlines .. automethod:: ROFileNode.close .. automethod:: ROFileNode.seek .. automethod:: ROFileNode.tell .. automethod:: ROFileNode.readable .. automethod:: ROFileNode.writable .. automethod:: ROFileNode.seekable .. automethod:: ROFileNode.fileno The RAFileNode class -------------------- .. autoclass:: RAFileNode RAFileNode attributes ~~~~~~~~~~~~~~~~~~~~~ .. autoattribute:: RAFileNode.attrs RAFileNode methods ~~~~~~~~~~~~~~~~~~ .. automethod:: RAFileNode.flush .. automethod:: RAFileNode.read .. automethod:: RAFileNode.readline .. automethod:: RAFileNode.readlines .. automethod:: RAFileNode.truncate .. automethod:: RAFileNode.write .. automethod:: RAFileNode.writelines .. automethod:: RAFileNode.close .. automethod:: RAFileNode.seek .. automethod:: RAFileNode.tell .. automethod:: RAFileNode.readable .. automethod:: RAFileNode.writable .. automethod:: RAFileNode.seekable .. automethod:: RAFileNode.fileno PyTables-3.7.0/doc/source/usersguide/libref/helper_classes.rst000066400000000000000000000053751416254111300244710ustar00rootroot00000000000000.. currentmodule:: tables Helper classes ============== This section describes some classes that do not fit in any other section and that mainly serve for ancillary purposes. .. _FiltersClassDescr: The Filters class ----------------- .. autoclass:: Filters .. These are defined in the class docstring. Filters instance variables ^^^^^^^^^^^^^^^^^^^^^^^^^^ .. autoattribute:: Filters.bitshuffle .. autoattribute:: Filters.fletcher32 .. autoattribute:: Filters.complevel .. autoattribute:: Filters.complib .. autoattribute:: Filters.shuffle Filters methods ~~~~~~~~~~~~~~~ .. automethod:: Filters.copy .. _IndexClassDescr: The Index class --------------- .. autoclass:: tables.index.Index .. This is defined in the class docstring .. autoattribute:: tables.index.Index.nelements Index instance variables ~~~~~~~~~~~~~~~~~~~~~~~~ .. autoattribute:: tables.index.Index.column .. autoattribute:: tables.index.Index.dirty .. autoattribute:: tables.index.Index.filters .. autoattribute:: tables.index.Index.is_csi .. attribute:: tables.index.Index.nelements The number of currently indexed rows for this column. Index methods ~~~~~~~~~~~~~ .. automethod:: tables.index.Index.read_sorted .. automethod:: tables.index.Index.read_indices Index special methods ~~~~~~~~~~~~~~~~~~~~~ .. automethod:: tables.index.Index.__getitem__ The IndexArray class -------------------- .. autoclass:: tables.indexes.IndexArray :members: .. _EnumClassDescr: The Enum class -------------- .. autoclass:: tables.misc.enum.Enum Enum special methods ~~~~~~~~~~~~~~~~~~~~ .. automethod:: Enum.__call__ .. automethod:: Enum.__contains__ .. automethod:: Enum.__eq__ .. automethod:: Enum.__getattr__ .. automethod:: Enum.__getitem__ .. automethod:: Enum.__iter__ .. automethod:: Enum.__len__ .. automethod:: Enum.__repr__ .. _UnImplementedClassDescr: The UnImplemented class ----------------------- .. autoclass:: UnImplemented :members: The Unknown class ----------------- .. autoclass:: Unknown :members: .. _ExceptionsDescr: Exceptions module ----------------- In the :mod:`exceptions` module exceptions and warnings that are specific to PyTables are declared. .. autoexception:: HDF5ExtError :members: .. autoexception:: ClosedNodeError .. autoexception:: ClosedFileError .. autoexception:: FileModeError .. autoexception:: NodeError .. autoexception:: NoSuchNodeError .. autoexception:: UndoRedoError .. autoexception:: UndoRedoWarning .. autoexception:: NaturalNameWarning .. autoexception:: PerformanceWarning .. autoexception:: FlavorError .. autoexception:: FlavorWarning .. autoexception:: FiltersWarning .. autoexception:: OldIndexWarning .. autoexception:: DataTypeWarning .. autoexception:: ExperimentalFeatureWarning PyTables-3.7.0/doc/source/usersguide/libref/hierarchy_classes.rst000066400000000000000000000131641416254111300251630ustar00rootroot00000000000000.. currentmodule:: tables Hierarchy definition classes ============================ .. _NodeClassDescr: The Node class -------------- .. autoclass:: Node .. These are defined in class docstring .. autoattribute:: Node._v_depth .. autoattribute:: Node._v_file .. autoattribute:: Node._v_name .. autoattribute:: Node._v_pathname .. autoattribute:: Node._v_objectid (location independent) Node instance variables - location dependent ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoattribute:: Node._v_parent Node instance variables - location independent ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoattribute:: Node._v_attrs .. autoattribute:: Node._v_isopen Node instance variables - attribute shorthands ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoattribute:: Node._v_title Node methods - hierarchy manipulation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automethod:: Node._f_close .. automethod:: Node._f_copy .. automethod:: Node._f_isvisible .. automethod:: Node._f_move .. automethod:: Node._f_remove .. automethod:: Node._f_rename Node methods - attribute handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. automethod:: Node._f_delattr .. automethod:: Node._f_getattr .. automethod:: Node._f_setattr .. _GroupClassDescr: The Group class --------------- .. autoclass:: Group .. These are defined in the class docstring Group instance variables ~~~~~~~~~~~~~~~~~~~~~~~~ The following instance variables are provided in addition to those in Node (see :ref:`NodeClassDescr`): .. autoattribute:: Group._v_children .. autoattribute:: Group._v_groups .. autoattribute:: Group._v_hidden .. autoattribute:: Group._v_leaves .. autoattribute:: Group._v_links .. autoattribute:: Group._v_unknown Group properties ~~~~~~~~~~~~~~~~ .. autoattribute:: Group._v_nchildren .. autoattribute:: Group._v_filters Group methods ~~~~~~~~~~~~~ .. important:: *Caveat:* The following methods are documented for completeness, and they can be used without any problem. However, you should use the high-level counterpart methods in the File class (see :ref:`FileClassDescr`, because they are most used in documentation and examples, and are a bit more powerful than those exposed here. The following methods are provided in addition to those in Node (see :ref:`NodeClassDescr`): .. automethod:: Group._f_close .. automethod:: Group._f_copy .. automethod:: Group._f_copy_children .. automethod:: Group._f_get_child .. automethod:: Group._f_iter_nodes .. automethod:: Group._f_list_nodes .. automethod:: Group._f_walk_groups .. automethod:: Group._f_walknodes Group special methods ~~~~~~~~~~~~~~~~~~~~~ Following are described the methods that automatically trigger actions when a Group instance is accessed in a special way. This class defines the :meth:`__setattr__`, :meth:`__getattr__` and :meth:`__delattr__` methods, and they set, get and delete *ordinary Python attributes* as normally intended. In addition to that, :meth:`__getattr__` allows getting *child nodes* by their name for the sake of easy interaction on the command line, as long as there is no Python attribute with the same name. Groups also allow the interactive completion (when using readline) of the names of child nodes. For instance:: # get a Python attribute nchild = group._v_nchildren # Add a Table child called 'table' under 'group'. h5file.create_table(group, 'table', my_description) table = group.table # get the table child instance group.table = 'foo' # set a Python attribute # (PyTables warns you here about using the name of a child node.) foo = group.table # get a Python attribute del group.table # delete a Python attribute table = group.table # get the table child instance again .. automethod:: Group.__contains__ .. automethod:: Group.__delattr__ .. automethod:: Group.__getattr__ .. automethod:: Group.__iter__ .. automethod:: Group.__repr__ .. automethod:: Group.__setattr__ .. automethod:: Group.__str__ .. _LeafClassDescr: The Leaf class -------------- .. autoclass:: Leaf .. These are defined in the class docstring .. _LeafInstanceVariables: Leaf instance variables ~~~~~~~~~~~~~~~~~~~~~~~ These instance variables are provided in addition to those in Node (see :ref:`NodeClassDescr`): .. autoattribute:: Leaf.byteorder .. autoattribute:: Leaf.dtype .. autoattribute:: Leaf.extdim .. autoattribute:: Leaf.nrows .. autoattribute:: Leaf.nrowsinbuf .. autoattribute:: Leaf.shape Leaf properties ~~~~~~~~~~~~~~~ .. autoattribute:: Leaf.chunkshape .. autoattribute:: Leaf.ndim .. autoattribute:: Leaf.filters .. autoattribute:: Leaf.maindim .. autoattribute:: Leaf.flavor .. attribute:: Leaf.size_in_memory The size of this leaf's data in bytes when it is fully loaded into memory. .. autoattribute:: Leaf.size_on_disk Leaf instance variables - aliases ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following are just easier-to-write aliases to their Node (see :ref:`NodeClassDescr`) counterparts (indicated between parentheses): .. autoattribute:: Leaf.attrs .. autoattribute:: Leaf.name .. autoattribute:: Leaf.object_id .. autoattribute:: Leaf.title Leaf methods ~~~~~~~~~~~~ .. automethod:: Leaf.close .. automethod:: Leaf.copy .. automethod:: Leaf.flush .. automethod:: Leaf.isvisible .. automethod:: Leaf.move .. automethod:: Leaf.rename .. automethod:: Leaf.remove .. automethod:: Leaf.get_attr .. automethod:: Leaf.set_attr .. automethod:: Leaf.del_attr .. automethod:: Leaf.truncate .. automethod:: Leaf.__len__ .. automethod:: Leaf._f_close PyTables-3.7.0/doc/source/usersguide/libref/homogenous_storage.rst000066400000000000000000000046211416254111300253750ustar00rootroot00000000000000.. currentmodule:: tables Homogenous storage classes ========================== .. _ArrayClassDescr: The Array class --------------- .. autoclass:: Array Array instance variables ~~~~~~~~~~~~~~~~~~~~~~~~ .. attribute:: Array.atom An Atom (see :ref:`AtomClassDescr`) instance representing the *type* and *shape* of the atomic objects to be saved. .. autoattribute:: Array.rowsize .. attribute:: Array.nrow On iterators, this is the index of the current row. .. autoattribute:: Array.nrows Array methods ~~~~~~~~~~~~~ .. automethod:: Array.get_enum .. automethod:: Array.iterrows .. automethod:: Array.__next__ .. automethod:: Array.read Array special methods ~~~~~~~~~~~~~~~~~~~~~ The following methods automatically trigger actions when an :class:`Array` instance is accessed in a special way (e.g. ``array[2:3,...,::2]`` will be equivalent to a call to ``array.__getitem__((slice(2, 3, None), Ellipsis, slice(None, None, 2))))``. .. automethod:: Array.__getitem__ .. automethod:: Array.__iter__ .. automethod:: Array.__setitem__ .. _CArrayClassDescr: The CArray class ---------------- .. autoclass:: CArray .. _EArrayClassDescr: The EArray class ---------------- .. autoclass:: EArray .. _EArrayMethodsDescr: EArray methods ~~~~~~~~~~~~~~ .. automethod:: EArray.append .. _VLArrayClassDescr: The VLArray class ----------------- .. autoclass:: VLArray .. These are defined in the class docstring VLArray instance variables ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoattribute:: VLArray.atom .. autoattribute:: VLArray.flavor .. autoattribute:: VLArray.nrow .. autoattribute:: VLArray.nrows .. autoattribute:: VLArray.extdim .. autoattribute:: VLArray.nrows VLArray properties ~~~~~~~~~~~~~~~~~~ .. autoattribute:: VLArray.size_on_disk .. autoattribute:: VLArray.size_in_memory VLArray methods ~~~~~~~~~~~~~~~ .. automethod:: VLArray.append .. automethod:: VLArray.get_enum .. automethod:: VLArray.iterrows .. automethod:: VLArray.__next__ .. automethod:: VLArray.read .. automethod:: VLArray.get_row_size VLArray special methods ~~~~~~~~~~~~~~~~~~~~~~~ The following methods automatically trigger actions when a :class:`VLArray` instance is accessed in a special way (e.g., vlarray[2:5] will be equivalent to a call to vlarray.__getitem__(slice(2, 5, None)). .. automethod:: VLArray.__getitem__ .. automethod:: VLArray.__iter__ .. automethod:: VLArray.__setitem__ PyTables-3.7.0/doc/source/usersguide/libref/link_classes.rst000066400000000000000000000027621416254111300241440ustar00rootroot00000000000000.. currentmodule:: tables Link classes ============ .. _LinkClassDescr: The Link class -------------- .. autoclass:: tables.link.Link .. These are defined in the class docstring .. autoattribute:: tables.link.Link.target Link instance variables ~~~~~~~~~~~~~~~~~~~~~~~ .. autoattribute:: tables.link.Link._v_attrs Link methods ~~~~~~~~~~~~ The following methods are useful for copying, moving, renaming and removing links. .. automethod:: tables.link.Link.copy .. automethod:: tables.link.Link.move .. automethod:: tables.link.Link.remove .. automethod:: tables.link.Link.rename .. _SoftLinkClassDescr: The SoftLink class ------------------ .. autoclass:: tables.link.SoftLink SoftLink special methods ~~~~~~~~~~~~~~~~~~~~~~~~ The following methods are specific for dereferencing and representing soft links. .. automethod:: tables.link.SoftLink.__call__ .. automethod:: tables.link.SoftLink.__str__ The ExternalLink class ---------------------- .. autoclass:: tables.link.ExternalLink .. This is defined in the class docstring ExternalLink instance variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoattribute:: tables.link.ExternalLink.extfile ExternalLink methods ~~~~~~~~~~~~~~~~~~~~ .. automethod:: tables.link.ExternalLink.umount ExternalLink special methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following methods are specific for dereferencing and representing external links. .. automethod:: tables.link.ExternalLink.__call__ .. automethod:: tables.link.ExternalLink.__str__ PyTables-3.7.0/doc/source/usersguide/libref/structured_storage.rst000066400000000000000000000127641416254111300254250ustar00rootroot00000000000000.. currentmodule:: tables Structured storage classes ========================== .. _TableClassDescr: The Table class --------------- .. autoclass:: Table .. These are defined in the class docstring .. _TableInstanceVariablesDescr: Table instance variables ~~~~~~~~~~~~~~~~~~~~~~~~ The following instance variables are provided in addition to those in Leaf (see :ref:`LeafClassDescr`). Please note that there are several col* dictionaries to ease retrieving information about a column directly by its path name, avoiding the need to walk through Table.description or :attr:`Table.cols`. .. autoattribute:: Table.coldescrs .. autoattribute:: Table.coldflts .. autoattribute:: Table.coldtypes .. autoattribute:: Table.colindexed .. autoattribute:: Table.colinstances .. autoattribute:: Table.colnames .. autoattribute:: Table.colpathnames .. autoattribute:: Table.cols .. autoattribute:: Table.coltypes .. autoattribute:: Table.description .. autoattribute:: Table.extdim .. autoattribute:: Table.indexed .. autoattribute:: Table.nrows Table properties ~~~~~~~~~~~~~~~~ .. autoattribute:: Table.autoindex .. autoattribute:: Table.colindexes .. autoattribute:: Table.indexedcolpathnames .. autoattribute:: Table.row .. autoattribute:: Table.rowsize Table methods - reading ~~~~~~~~~~~~~~~~~~~~~~~ .. automethod:: Table.col .. automethod:: Table.iterrows .. automethod:: Table.itersequence .. automethod:: Table.itersorted .. automethod:: Table.read .. automethod:: Table.read_coordinates .. automethod:: Table.read_sorted .. automethod:: Table.__getitem__ .. automethod:: Table.__iter__ Table methods - writing ~~~~~~~~~~~~~~~~~~~~~~~ .. automethod:: Table.append .. automethod:: Table.modify_column .. automethod:: Table.modify_columns .. automethod:: Table.modify_coordinates .. automethod:: Table.modify_rows .. automethod:: Table.remove_rows .. automethod:: Table.remove_row .. automethod:: Table.__setitem__ .. _TableMethods_querying: Table methods - querying ~~~~~~~~~~~~~~~~~~~~~~~~ .. automethod:: Table.get_where_list .. automethod:: Table.read_where .. automethod:: Table.where .. automethod:: Table.append_where .. automethod:: Table.will_query_use_indexing Table methods - other ~~~~~~~~~~~~~~~~~~~~~ .. automethod:: Table.copy .. automethod:: Table.flush_rows_to_index .. automethod:: Table.get_enum .. automethod:: Table.reindex .. automethod:: Table.reindex_dirty .. _DescriptionClassDescr: The Description class ~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: Description .. These are defined in the class docstring Description instance variables ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. autoattribute:: Description._v_col_objects .. autoattribute:: Description._v_dflts .. autoattribute:: Description._v_dtype .. autoattribute:: Description._v_dtypes .. autoattribute:: Description._v_is_nested .. autoattribute:: Description._v_itemsize .. autoattribute:: Description._v_name .. autoattribute:: Description._v_names .. autoattribute:: Description._v_nested_descr .. autoattribute:: Description._v_nested_formats .. autoattribute:: Description._v_nestedlvl .. autoattribute:: Description._v_nested_names .. autoattribute:: Description._v_pathname .. autoattribute:: Description._v_pathnames .. autoattribute:: Description._v_types .. autoattribute:: Description._v_offsets Description methods ^^^^^^^^^^^^^^^^^^^ .. automethod:: Description._f_walk .. _RowClassDescr: The Row class ~~~~~~~~~~~~~ .. autoclass:: tables.tableextension.Row .. These are defined in the class docstring Row instance variables ^^^^^^^^^^^^^^^^^^^^^^ .. autoattribute:: tables.tableextension.Row.nrow Row methods ^^^^^^^^^^^ .. automethod:: tables.tableextension.Row.append .. automethod:: tables.tableextension.Row.fetch_all_fields .. automethod:: tables.tableextension.Row.update .. _RowSpecialMethods: Row special methods ^^^^^^^^^^^^^^^^^^^ .. automethod:: tables.tableextension.Row.__contains__ .. automethod:: tables.tableextension.Row.__getitem__ .. automethod:: tables.tableextension.Row.__setitem__ .. _ColsClassDescr: The Cols class ~~~~~~~~~~~~~~ .. autoclass:: Cols .. These are defined in the class docstring Cols instance variables ^^^^^^^^^^^^^^^^^^^^^^^ .. autoattribute:: Cols._v_colnames .. autoattribute:: Cols._v_colpathnames .. autoattribute:: Cols._v_desc Cols properties ^^^^^^^^^^^^^^^ .. autoattribute:: Cols._v_table Cols methods ^^^^^^^^^^^^ .. automethod:: Cols._f_col .. automethod:: Cols.__getitem__ .. automethod:: Cols.__len__ .. automethod:: Cols.__setitem__ .. _ColumnClassDescr: The Column class ~~~~~~~~~~~~~~~~ .. autoclass:: Column .. These are defined in the class docstring .. autoattribute:: Column.descr .. autoattribute:: Column.name .. autoattribute:: Column.pathname Column instance variables ^^^^^^^^^^^^^^^^^^^^^^^^^ .. autoattribute:: Column.dtype .. autoattribute:: Column.index .. autoattribute:: Column.is_indexed .. autoattribute:: Column.maindim .. autoattribute:: Column.shape .. autoattribute:: Column.table .. autoattribute:: Column.type Column methods ^^^^^^^^^^^^^^ .. automethod:: Column.create_index .. automethod:: Column.create_csindex .. automethod:: Column.reindex .. automethod:: Column.reindex_dirty .. automethod:: Column.remove_index Column special methods ^^^^^^^^^^^^^^^^^^^^^^ .. automethod:: Column.__getitem__ .. automethod:: Column.__len__ .. automethod:: Column.__setitem__ PyTables-3.7.0/doc/source/usersguide/libref/top_level.rst000066400000000000000000000011151416254111300234520ustar00rootroot00000000000000.. currentmodule:: tables Top-level variables and functions ================================= Global variables ---------------- .. autodata:: __version__ .. autodata:: hdf5_version Global functions ---------------- .. autofunction:: copy_file .. autofunction:: is_hdf5_file .. autofunction:: is_pytables_file .. autofunction:: open_file .. autofunction:: set_blosc_max_threads .. autofunction:: print_versions .. autofunction:: restrict_flavors .. autofunction:: split_type .. autofunction:: test .. autofunction:: which_lib_version .. autofunction:: silence_hdf5_messages PyTables-3.7.0/doc/source/usersguide/optimization.rst000066400000000000000000001514271416254111300227600ustar00rootroot00000000000000Optimization tips ================= .. epigraph:: ... durch planmässiges Tattonieren. [... through systematic, palpable experimentation.] -- Johann Karl Friedrich Gauss [asked how he came upon his theorems] .. currentmodule:: tables On this chapter, you will get deeper knowledge of PyTables internals. PyTables has many tunable features so that you can improve the performance of your application. If you are planning to deal with really large data, you should read carefully this section in order to learn how to get an important efficiency boost for your code. But if your datasets are small (say, up to 10 MB) or your number of nodes is contained (up to 1000), you should not worry about that as the default parameters in PyTables are already tuned for those sizes (although you may want to adjust them further anyway). At any rate, reading this chapter will help you in your life with PyTables. Understanding chunking ---------------------- The underlying HDF5 library that is used by PyTables allows for certain datasets (the so-called *chunked* datasets) to take the data in bunches of a certain length, named *chunks*, and write them on disk as a whole, i.e. the HDF5 library treats chunks as atomic objects and disk I/O is always made in terms of complete chunks. This allows data filters to be defined by the application to perform tasks such as compression, encryption, check-summing, etc. on entire chunks. HDF5 keeps a B-tree in memory that is used to map chunk structures on disk. The more chunks that are allocated for a dataset the larger the B-tree. Large B-trees take memory and cause file storage overhead as well as more disk I/O and higher contention for the metadata cache. Consequently, it's important to balance between memory and I/O overhead (small B-trees) and time to access data (big B-trees). In the next couple of sections, you will discover how to inform PyTables about the expected size of your datasets for allowing a sensible computation of the chunk sizes. Also, you will be presented some experiments so that you can get a feeling on the consequences of manually specifying the chunk size. Although doing this latter is only reserved to experienced people, these benchmarks may allow you to understand more deeply the chunk size implications and let you quickly start with the fine-tuning of this important parameter. .. _expectedRowsOptim: Informing PyTables about expected number of rows in tables or arrays ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyTables can determine a sensible chunk size to your dataset size if you help it by providing an estimation of the final number of rows for an extensible leaf [1]_. You should provide this information at leaf creation time by passing this value to the ``expectedrows`` argument of the :meth:`File.create_table` method or :meth:`File.create_earray` method (see :ref:`EArrayClassDescr`). When your leaf size is bigger than 10 MB (take this figure only as a reference, not strictly), by providing this guess you will be optimizing the access to your data. When the table or array size is larger than, say 100MB, you are *strongly* suggested to provide such a guess; failing to do that may cause your application to do very slow I/O operations and to demand *huge* amounts of memory. You have been warned! .. _chunksizeFineTune: Fine-tuning the chunksize ~~~~~~~~~~~~~~~~~~~~~~~~~ .. warning:: This section is mostly meant for experts. If you are a beginner, you must know that setting manually the chunksize is a potentially dangerous action. Most of the time, informing PyTables about the extent of your dataset is enough. However, for more sophisticated applications, when one has special requirements for doing the I/O or when dealing with really large datasets, you should really understand the implications of the chunk size in order to be able to find the best value for your own application. You can specify the chunksize for every chunked dataset in PyTables by passing the chunkshape argument to the corresponding constructors. It is important to point out that chunkshape is not exactly the same thing than a chunksize; in fact, the chunksize of a dataset can be computed multiplying all the dimensions of the chunkshape among them and multiplying the outcome by the size of the atom. We are going to describe a series of experiments where an EArray of 15 GB is written with different chunksizes, and then it is accessed in both sequential (i.e. first element 0, then element 1 and so on and so forth until the data is exhausted) and random mode (i.e. single elements are read randomly all through the dataset). These benchmarks have been carried out with PyTables 2.1 on a machine with an Intel Core2 processor @ 3 GHz and a RAID-0 made of two SATA disks spinning at 7200 RPM, and using GNU/Linux with an XFS filesystem. The script used for the benchmarks is available in bench/optimal-chunksize.py. In figures :ref:`Figure 1 `, :ref:`Figure 2 `, :ref:`Figure 3 ` and :ref:`Figure 4 `, you can see how the chunksize affects different aspects, like creation time, file sizes, sequential read time and random read time. So, if you properly inform PyTables about the extent of your datasets, you will get an automatic chunksize value (256 KB in this case) that is pretty optimal for most of uses. However, if what you want is, for example, optimize the creation time when using the Zlib compressor, you may want to reduce the chunksize to 32 KB (see :ref:`Figure 1 `). Or, if your goal is to optimize the sequential access time for an dataset compressed with Blosc, you may want to increase the chunksize to 512 KB (see :ref:`Figure 3 `). You will notice that, by manually specifying the chunksize of a leave you will not normally get a drastic increase in performance, but at least, you have the opportunity to fine-tune such an important parameter for improve performance. .. _createTime-chunksize: .. figure:: images/create-chunksize-15GB.png :align: center **Figure 1. Creation time per element for a 15 GB EArray and different chunksizes.** .. _fileSizes-chunksize: .. figure:: images/filesizes-chunksize-15GB.png :align: center **Figure 2. File sizes for a 15 GB EArray and different chunksizes.** .. _seqTime-chunksize: .. figure:: images/seq-chunksize-15GB.png :align: center **Figure 3. Sequential access time per element for a 15 GB EArray and different chunksizes.** .. _randomTime-chunksize: .. figure:: images/random-chunksize-15GB.png :align: center **Figure 4. Random access time per element for a 15 GB EArray and different chunksizes.** Finally, it is worth noting that adjusting the chunksize can be specially important if you want to access your dataset by blocks of certain dimensions. In this case, it is normally a good idea to set your chunkshape to be the same than these dimensions; you only have to be careful to not end with a too small or too large chunksize. As always, experimenting prior to pass your application into production is your best ally. .. _searchOptim: Accelerating your searches -------------------------- .. note:: Many of the explanations and plots in this section and the forthcoming ones still need to be updated to include Blosc (see :ref:`[BLOSC] `), the new and powerful compressor added in PyTables 2.2 series. You should expect it to be the fastest compressor among all the described here, and its use is strongly recommended whenever you need extreme speed and not a very high compression ratio. Searching in tables is one of the most common and time consuming operations that a typical user faces in the process of mining through his data. Being able to perform queries as fast as possible will allow more opportunities for finding the desired information quicker and also allows to deal with larger datasets. PyTables offers many sort of techniques so as to speed-up the search process as much as possible and, in order to give you hints to use them based, a series of benchmarks have been designed and carried out. All the results presented in this section have been obtained with synthetic, random data and using PyTables 2.1. Also, the tests have been conducted on a machine with an Intel Core2 (64-bit) @ 3 GHz processor with RAID-0 disk storage (made of four spinning disks @ 7200 RPM), using GNU/Linux with an XFS filesystem. The script used for the benchmarks is available in bench/indexed_search.py. As your data, queries and platform may be totally different for your case, take this just as a guide because your mileage may vary (and will vary). In order to be able to play with tables with a number of rows as large as possible, the record size has been chosen to be rather small (24 bytes). Here it is its definition:: class Record(tables.IsDescription): col1 = tables.Int32Col() col2 = tables.Int32Col() col3 = tables.Float64Col() col4 = tables.Float64Col() In the next sections, we will be optimizing the times for a relatively complex query like this:: result = [row['col2'] for row in table if ( ((row['col4'] >= lim1 and row['col4'] < lim2) or ((row['col2'] > lim3 and row['col2'] < lim4])) and ((row['col1']+3.1*row['col2']+row['col3']*row['col4']) > lim5) )] (for future reference, we will call this sort of queries *regular* queries). So, if you want to see how to greatly improve the time taken to run queries like this, keep reading. .. _inkernelSearch: In-kernel searches ~~~~~~~~~~~~~~~~~~ PyTables provides a way to accelerate data selections inside of a single table, through the use of the :ref:`TableMethods_querying` iterator and related query methods. This mode of selecting data is called *in-kernel*. Let's see an example of an *in-kernel* query based on the *regular* one mentioned above:: result = [row['col2'] for row in table.where( '''(((col4 >= lim1) & (col4 < lim2)) | ((col2 > lim3) & (col2 < lim4)) & ((col1+3.1*col2+col3*col4) > lim5))''')] This simple change of mode selection can improve search times quite a lot and actually make PyTables very competitive when compared against typical relational databases as you can see in :ref:`Figure 5 ` and :ref:`Figure 6 `. .. _sequentialTimes-10m: .. figure:: images/Q7-10m-noidx.png :align: center **Figure 5. Times for non-indexed complex queries in a small table with 10 millions of rows: the data fits in memory.** By looking at :ref:`Figure 5 ` you can see how in the case that table data fits easily in memory, in-kernel searches on uncompressed tables are generally much faster (10x) than standard queries as well as PostgreSQL (5x). Regarding compression, we can see how Zlib compressor actually slows down the performance of in-kernel queries by a factor 3.5x; however, it remains faster than PostgreSQL (40%). On his hand, LZO compressor only decreases the performance by a 75% with respect to uncompressed in-kernel queries and is still a lot faster than PostgreSQL (3x). Finally, one can observe that, for low selectivity queries (large number of hits), PostgreSQL performance degrades quite steadily, while in PyTables this slow down rate is significantly smaller. The reason of this behaviour is not entirely clear to the authors, but the fact is clearly reproducible in our benchmarks. But, why in-kernel queries are so fast when compared with regular ones?. The answer is that in regular selection mode the data for all the rows in table has to be brought into Python space so as to evaluate the condition and decide if the corresponding field should be added to the result list. On the contrary, in the in-kernel mode, the condition is passed to the PyTables kernel (hence the name), written in C, and evaluated there at full C speed (with the help of the integrated Numexpr package, see :ref:`[NUMEXPR] `), so that the only values that are brought to Python space are the rows that fulfilled the condition. Hence, for selections that only have a relatively small number of hits (compared with the total amount of rows), the savings are very large. It is also interesting to note the fact that, although for queries with a large number of hits the speed-up is not as high, it is still very important. On the other hand, when the table is too large to fit in memory (see :ref:`Figure 6 `), the difference in speed between regular and in-kernel is not so important, but still significant (2x). Also, and curiously enough, large tables compressed with Zlib offers slightly better performance (around 20%) than uncompressed ones; this is because the additional CPU spent by the uncompressor is compensated by the savings in terms of net I/O (one has to read less actual data from disk). However, when using the extremely fast LZO compressor, it gives a clear advantage over Zlib, and is up to 2.5x faster than not using compression at all. The reason is that LZO decompression speed is much faster than Zlib, and that allows PyTables to read the data at full disk speed (i.e. the bottleneck is in the I/O subsystem, not in the CPU). In this case the compression rate is around 2.5x, and this is why the data can be read 2.5x faster. So, in general, using the LZO compressor is the best way to ensure best reading/querying performance for out-of-core datasets (more about how compression affects performance in :ref:`compressionIssues`). .. _sequentialTimes-1g: .. figure:: images/Q8-1g-noidx.png :align: center **Figure 6. Times for non-indexed complex queries in a large table with 1 billion of rows: the data does not fit in memory.** Furthermore, you can mix the *in-kernel* and *regular* selection modes for evaluating arbitrarily complex conditions making use of external functions. Look at this example:: result = [ row['var2'] for row in table.where('(var3 == "foo") & (var1 <= 20)') if your_function(row['var2']) ] Here, we use an *in-kernel* selection to choose rows according to the values of the var3 and var1 fields. Then, we apply a *regular* selection to complete the query. Of course, when you mix the *in-kernel* and *regular* selection modes you should pass the most restrictive condition to the *in-kernel* part, i.e. to the where() iterator. In situations where it is not clear which is the most restrictive condition, you might want to experiment a bit in order to find the best combination. However, since in-kernel condition strings allow rich expressions allowing the coexistence of multiple columns, variables, arithmetic operations and many typical functions, it is unlikely that you will be forced to use external regular selections in conditions of small to medium complexity. See :ref:`condition_syntax` for more information on in-kernel condition syntax. Indexed searches ~~~~~~~~~~~~~~~~ When you need more speed than *in-kernel* selections can offer you, PyTables offers a third selection method, the so-called *indexed* mode (based on the highly efficient OPSI indexing engine ). In this mode, you have to decide which column(s) you are going to apply your selections over, and index them. Indexing is just a kind of sorting operation over a column, so that searches along such a column (or columns) will look at this sorted information by using a *binary search* which is much faster than the *sequential search* described in the previous section. You can index the columns you want by calling the :meth:`Column.create_index` method on an already created table. For example:: indexrows = table.cols.var1.create_index() indexrows = table.cols.var2.create_index() indexrows = table.cols.var3.create_index() will create indexes for all var1, var2 and var3 columns. After you have indexed a series of columns, the PyTables query optimizer will try hard to discover the usable indexes in a potentially complex expression. However, there are still places where it cannot determine that an index can be used. See below for examples where the optimizer can safely determine if an index, or series of indexes, can be used or not. Example conditions where an index can be used: - var1 >= "foo" (var1 is used) - var1 >= mystr (var1 is used) - (var1 >= "foo") & (var4 > 0.0) (var1 is used) - ("bar" <= var1) & (var1 < "foo") (var1 is used) - (("bar" <= var1) & (var1 < "foo")) & (var4 > 0.0) (var1 is used) - (var1 >= "foo") & (var3 > 10) (var1 and var3 are used) - (var1 >= "foo") | (var3 > 10) (var1 and var3 are used) - ~(var1 >= "foo") | ~(var3 > 10) (var1 and var3 are used) Example conditions where an index can *not* be used: - var4 > 0.0 (var4 is not indexed) - var1 != 0.0 (range has two pieces) - ~(("bar" <= var1) & (var1 < "foo")) & (var4 > 0.0) (negation of a complex boolean expression) .. note:: From PyTables 2.3 on, several indexes can be used in a single query. .. note:: If you want to know for sure whether a particular query will use indexing or not (without actually running it), you are advised to use the :meth:`Table.will_query_use_indexing` method. One important aspect of the new indexing in PyTables (>= 2.3) is that it has been designed from the ground up with the goal of being capable to effectively manage very large tables. To this goal, it sports a wide spectrum of different quality levels (also called optimization levels) for its indexes so that the user can choose the best one that suits her needs (more or less size, more or less performance). In :ref:`Figure 7 `, you can see that the times to index columns in tables can be really short. In particular, the time to index a column with 1 billion rows (1 Gigarow) with the lowest optimization level is less than 4 minutes while indexing the same column with full optimization (so as to get a completely sorted index or CSI) requires around 1 hour. These are rather competitive figures compared with a relational database (in this case, PostgreSQL 8.3.1, which takes around 1.5 hours for getting the index done). This is because PyTables is geared towards read-only or append-only tables and takes advantage of this fact to optimize the indexes properly. On the contrary, most relational databases have to deliver decent performance in other scenarios as well (specially updates and deletions), and this fact leads not only to slower index creation times, but also to indexes taking much more space on disk, as you can see in :ref:`Figure 8 `. .. _createIndexTimes: .. figure:: images/create-index-time-int32-float64.png :align: center **Figure 7. Times for indexing an Int32 and Float64 column.** .. _indexSizes: .. figure:: images/indexes-sizes2.png :align: center **Figure 8. Sizes for an index of a Float64 column with 1 billion of rows.** The user can select the index quality by passing the desired optlevel and kind arguments to the :meth:`Column.create_index` method. We can see in figures :ref:`Figure 7 ` and :ref:`Figure 8 ` how the different optimization levels affects index time creation and index sizes. So, which is the effect of the different optimization levels in terms of query times? You can see that in :ref:`Figure 9 `. .. _queryTimes-indexed-optlevels: .. figure:: images/Q8-1g-idx-optlevels.png :align: center **Figure 9. Times for complex queries with a cold cache (mean of 5 first random queries) for different optimization levels. Benchmark made on a machine with Intel Core2 (64-bit) @ 3 GHz processor with RAID-0 disk storage.** Of course, compression also has an effect when doing indexed queries, although not very noticeable, as can be seen in :ref:`Figure 10 `. As you can see, the difference between using no compression and using Zlib or LZO is very little, although LZO achieves relatively better performance generally speaking. .. _queryTimes-indexed-compress: .. figure:: images/Q8-1g-idx-compress.png :align: center **Figure 10. Times for complex queries with a cold cache (mean of 5 first random queries) for different compressors.** You can find a more complete description and benchmarks about OPSI, the indexing system of PyTables (>= 2.3) in :ref:`[OPSI] `. Indexing and Solid State Disks (SSD) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Lately, the long promised Solid State Disks (SSD for brevity) with decent capacities and affordable prices have finally hit the market and will probably stay in coexistence with the traditional spinning disks for the foreseeable future (separately or forming *hybrid* systems). SSD have many advantages over spinning disks, like much less power consumption and better throughput. But of paramount importance, specially in the context of accelerating indexed queries, is its very reduced latency during disk seeks, which is typically 100x better than traditional disks. Such a huge improvement has to have a clear impact in reducing the query times, specially when the selectivity is high (i.e. the number of hits is small). In order to offer an estimate on the performance improvement we can expect when using a low-latency SSD instead of traditional spinning disks, the benchmark in the previous section has been repeated, but this time using a single SSD disk instead of the four spinning disks in RAID-0. The result can be seen in :ref:`Figure 11 `. There one can see how a query in a table of 1 billion of rows with 100 hits took just 1 tenth of second when using a SSD, instead of 1 second that needed the RAID made of spinning disks. This factor of 10x of speed-up for high-selectivity queries is nothing to sneeze at, and should be kept in mind when really high performance in queries is needed. It is also interesting that using compression with LZO does have a clear advantage over when no compression is done. .. _queryTimes-indexed-SSD: .. figure:: images/Q8-1g-idx-SSD.png :align: center **Figure 11. Times for complex queries with a cold cache (mean of 5 first random queries) for different disk storage (SSD vs spinning disks).** Finally, we should remark that SSD can't compete with traditional spinning disks in terms of capacity as they can only provide, for a similar cost, between 1/10th and 1/50th of the size of traditional disks. It is here where the compression capabilities of PyTables can be very helpful because both tables and indexes can be compressed and the final space can be reduced by typically 2x to 5x (4x to 10x when compared with traditional relational databases). Best of all, as already mentioned, performance is not degraded when compression is used, but actually *improved*. So, by using PyTables and SSD you can query larger datasets that otherwise would require spinning disks when using other databases In fact, we were unable to run the PostgreSQL benchmark in this case because the space needed exceeded the capacity of our SSD., while allowing improvements in the speed of indexed queries between 2x (for medium to low selectivity queries) and 10x (for high selectivity queries). Achieving ultimate speed: sorted tables and beyond ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. warning:: Sorting a large table is a costly operation. The next procedure should only be performed when your dataset is mainly read-only and meant to be queried many times. When querying large tables, most of the query time is spent in locating the interesting rows to be read from disk. In some occasions, you may have queries whose result depends *mainly* of one single column (a query with only one single condition is the trivial example), so we can guess that sorting the table by this column would lead to locate the interesting rows in a much more efficient way (because they would be mostly *contiguous*). We are going to confirm this guess. For the case of the query that we have been using in the previous sections:: result = [row['col2'] for row in table.where( '''(((col4 >= lim1) & (col4 < lim2)) | ((col2 > lim3) & (col2 < lim4)) & ((col1+3.1*col2+col3*col4) > lim5))''')] it is possible to determine, by analysing the data distribution and the query limits, that col4 is such a *main column*. So, by ordering the table by the col4 column (for example, by specifying setting the column to sort by in the sortby parameter in the :meth:`Table.copy` method and re-indexing col2 and col4 afterwards, we should get much faster performance for our query. This is effectively demonstrated in :ref:`Figure 12 `, where one can see how queries with a low to medium (up to 10000) number of hits can be done in around 1 tenth of second for a RAID-0 setup and in around 1 hundredth of second for a SSD disk. This represents up to more that 100x improvement in speed with respect to the times with unsorted tables. On the other hand, when the number of hits is large (> 1 million), the query times grow almost linearly, showing a near-perfect scalability for both RAID-0 and SSD setups (the sequential access to disk becomes the bottleneck in this case). .. _queryTimes-indexed-sorted: .. figure:: images/Q8-1g-idx-sorted.png :align: center **Figure 12. Times for complex queries with a cold cache (mean of 5 first random queries) for unsorted and sorted tables.** Even though we have shown many ways to improve query times that should fulfill the needs of most of people, for those needing more, you can for sure discover new optimization opportunities. For example, querying against sorted tables is limited mainly by sequential access to data on disk and data compression capability, so you may want to read :ref:`chunksizeFineTune`, for ways on improving this aspect. Reading the other sections of this chapter will help in finding new roads for increasing the performance as well. You know, the limit for stopping the optimization process is basically your imagination (but, most plausibly, your available time ;-). .. _compressionIssues: Compression issues ------------------ One of the beauties of PyTables is that it supports compression on tables and arrays [2]_, although it is not used by default. Compression of big amounts of data might be a bit controversial feature, because it has a legend of being a very big consumer of CPU time resources. However, if you are willing to check if compression can help not only by reducing your dataset file size but *also* by improving I/O efficiency, specially when dealing with very large datasets, keep reading. A study on supported compression libraries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The compression library used by default is the *Zlib* (see :ref:`[ZLIB] `). Since HDF5 *requires* it, you can safely use it and expect that your HDF5 files will be readable on any other platform that has HDF5 libraries installed. Zlib provides good compression ratio, although somewhat slow, and reasonably fast decompression. Because of that, it is a good candidate to be used for compressing you data. However, in some situations it is critical to have a *very good decompression speed* (at the expense of lower compression ratios or more CPU wasted on compression, as we will see soon). In others, the emphasis is put in achieving the *maximum compression ratios*, no matter which reading speed will result. This is why support for two additional compressors has been added to PyTables: LZO (see :ref:`[LZO] `) and bzip2 (see :ref:`[BZIP2] `). Following the author of LZO (and checked by the author of this section, as you will see soon), LZO offers pretty fast compression and extremely fast decompression. In fact, LZO is so fast when compressing/decompressing that it may well happen (that depends on your data, of course) that writing or reading a compressed dataset is sometimes faster than if it is not compressed at all (specially when dealing with extremely large datasets). This fact is very important, specially if you have to deal with very large amounts of data. Regarding bzip2, it has a reputation of achieving excellent compression ratios, but at the price of spending much more CPU time, which results in very low compression/decompression speeds. Be aware that the LZO and bzip2 support in PyTables is not standard on HDF5, so if you are going to use your PyTables files in other contexts different from PyTables you will not be able to read them. Still, see the :ref:`ptrepackDescr` (where the ptrepack utility is described) to find a way to free your files from LZO or bzip2 dependencies, so that you can use these compressors locally with the warranty that you can replace them with Zlib (or even remove compression completely) if you want to use these files with other HDF5 tools or platforms afterwards. In order to allow you to grasp what amount of compression can be achieved, and how this affects performance, a series of experiments has been carried out. All the results presented in this section (and in the next one) have been obtained with synthetic data and using PyTables 1.3. Also, the tests have been conducted on a IBM OpenPower 720 (e-series) with a PowerPC G5 at 1.65 GHz and a hard disk spinning at 15K RPM. As your data and platform may be totally different for your case, take this just as a guide because your mileage may vary. Finally, and to be able to play with tables with a number of rows as large as possible, the record size has been chosen to be small (16 bytes). Here is its definition:: class Bench(IsDescription): var1 = StringCol(length=4) var2 = IntCol() var3 = FloatCol() With this setup, you can look at the compression ratios that can be achieved in :ref:`Figure 13 `. As you can see, LZO is the compressor that performs worse in this sense, but, curiously enough, there is not much difference between Zlib and bzip2. .. _comprTblComparison: .. figure:: images/compressed-recordsize.png :align: center **Figure 13. Comparison between different compression libraries.** Also, PyTables lets you select different compression levels for Zlib and bzip2, although you may get a bit disappointed by the small improvement that these compressors show when dealing with a combination of numbers and strings as in our example. As a reference, see plot :ref:`Figure 14 ` for a comparison of the compression achieved by selecting different levels of Zlib. Very oddly, the best compression ratio corresponds to level 1 (!). See later for an explanation and more figures on this subject. .. _comprZlibComparison: .. figure:: images/compressed-recordsize-zlib.png :align: center **Figure 14. Comparison between different compression levels of Zlib.** Have also a look at :ref:`Figure 15 `. It shows how the speed of writing rows evolves as the size (number of rows) of the table grows. Even though in these graphs the size of one single row is 16 bytes, you can most probably extrapolate these figures to other row sizes. .. _comprWriteComparison: .. figure:: images/compressed-writing.png :align: center **Figure 15. Writing tables with several compressors.** In :ref:`Figure 16 ` you can see how compression affects the reading performance. In fact, what you see in the plot is an *in-kernel selection* speed, but provided that this operation is very fast (see :ref:`inkernelSearch`), we can accept it as an actual read test. Compared with the reference line without compression, the general trend here is that LZO does not affect too much the reading performance (and in some points it is actually better), Zlib makes speed drop to a half, while bzip2 is performing very slow (up to 8x slower). Also, in the same :ref:`Figure 16 ` you can notice some strange peaks in the speed that we might be tempted to attribute to libraries on which PyTables relies (HDF5, compressors...), or to PyTables itself. However, :ref:`Figure 17 ` reveals that, if we put the file in the filesystem cache (by reading it several times before, for example), the evolution of the performance is much smoother. So, the most probable explanation would be that such peaks are a consequence of the underlying OS filesystem, rather than a flaw in PyTables (or any other library behind it). Another consequence that can be derived from the aforementioned plot is that LZO decompression performance is much better than Zlib, allowing an improvement in overall speed of more than 2x, and perhaps more important, the read performance for really large datasets (i.e. when they do not fit in the OS filesystem cache) can be actually *better* than not using compression at all. Finally, one can see that reading performance is very badly affected when bzip2 is used (it is 10x slower than LZO and 4x than Zlib), but this was somewhat expected anyway. .. _comprReadNoCacheComparison: .. figure:: images/compressed-select-nocache.png :align: center **Figure 16. Selecting values in tables with several compressors. The file is not in the OS cache.** .. _comprReadCacheComparison: .. figure:: images/compressed-select-cache.png :align: center **Figure 17. Selecting values in tables with several compressors. The file is in the OS cache.** So, generally speaking and looking at the experiments above, you can expect that LZO will be the fastest in both compressing and decompressing, but the one that achieves the worse compression ratio (although that may be just OK for many situations, specially when used with shuffling - see :ref:`ShufflingOptim`). bzip2 is the slowest, by large, in both compressing and decompressing, and besides, it does not achieve any better compression ratio than Zlib. Zlib represents a balance between them: it's somewhat slow compressing (2x) and decompressing (3x) than LZO, but it normally achieves better compression ratios. Finally, by looking at the plots :ref:`Figure 18 `, :ref:`Figure 19 `, and the aforementioned :ref:`Figure 14 ` you can see why the recommended compression level to use for all compression libraries is 1. This is the lowest level of compression, but as the size of the underlying HDF5 chunk size is normally rather small compared with the size of compression buffers, there is not much point in increasing the latter (i.e. increasing the compression level). Nonetheless, in some situations (like for example, in extremely large tables or arrays, where the computed chunk size can be rather large) you may want to check, on your own, how the different compression levels do actually affect your application. You can select the compression library and level by setting the complib and complevel keywords in the Filters class (see :ref:`FiltersClassDescr`). A compression level of 0 will completely disable compression (the default), 1 is the less memory and CPU time demanding level, while 9 is the maximum level and the most memory demanding and CPU intensive. Finally, have in mind that LZO is not accepting a compression level right now, so, when using LZO, 0 means that compression is not active, and any other value means that LZO is active. So, in conclusion, if your ultimate goal is writing and reading as fast as possible, choose LZO. If you want to reduce as much as possible your data, while retaining acceptable read speed, choose Zlib. Finally, if portability is important for you, Zlib is your best bet. So, when you want to use bzip2? Well, looking at the results, it is difficult to recommend its use in general, but you may want to experiment with it in those cases where you know that it is well suited for your data pattern (for example, for dealing with repetitive string datasets). .. _comprWriteZlibComparison: .. figure:: images/compressed-writing-zlib.png :align: center **Figure 18. Writing in tables with different levels of compression.** .. _comprReadZlibComparison: .. figure:: images/compressed-select-cache-zlib.png :align: center **Figure 19. Selecting values in tables with different levels of compression. The file is in the OS cache.** .. _ShufflingOptim: Shuffling (or how to make the compression process more effective) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The HDF5 library provides an interesting filter that can leverage the results of your favorite compressor. Its name is *shuffle*, and because it can greatly benefit compression and it does not take many CPU resources (see below for a justification), it is active *by default* in PyTables whenever compression is activated (independently of the chosen compressor). It is deactivated when compression is off (which is the default, as you already should know). Of course, you can deactivate it if you want, but this is not recommended. .. note:: Since PyTables 3.3, a new *bitshuffle* filter for Blosc compressor has been added. Contrarily to *shuffle* that shuffles bytes, *bitshuffle* shuffles the chunk data at bit level which **could** improve compression ratios at the expense of some speed penalty. Look at the :ref:`FiltersClassDescr` documentation on how to activate bitshuffle and experiment with it so as to decide if it can be useful for you. So, how does this mysterious filter exactly work? From the HDF5 reference manual:: "The shuffle filter de-interlaces a block of data by reordering the bytes. All the bytes from one consistent byte position of each data element are placed together in one block; all bytes from a second consistent byte position of each data element are placed together a second block; etc. For example, given three data elements of a 4-byte datatype stored as 012301230123, shuffling will re-order data as 000111222333. This can be a valuable step in an effective compression algorithm because the bytes in each byte position are often closely related to each other and putting them together can increase the compression ratio." In :ref:`Figure 20 ` you can see a benchmark that shows how the *shuffle* filter can help the different libraries in compressing data. In this experiment, shuffle has made LZO compress almost 3x more (!), while Zlib and bzip2 are seeing improvements of 2x. Once again, the data for this experiment is synthetic, and *shuffle* seems to do a great work with it, but in general, the results will vary in each case [3]_. .. _comprShuffleComparison: .. figure:: images/compressed-recordsize-shuffle.png :align: center **Figure 20. Comparison between different compression libraries with and without the shuffle filter.** At any rate, the most remarkable fact about the *shuffle* filter is the relatively high level of compression that compressor filters can achieve when used in combination with it. A curious thing to note is that the Bzip2 compression rate does not seem very much improved (less than a 40%), and what is more striking, Bzip2+shuffle does compress quite *less* than Zlib+shuffle or LZO+shuffle combinations, which is kind of unexpected. The thing that seems clear is that Bzip2 is not very good at compressing patterns that result of shuffle application. As always, you may want to experiment with your own data before widely applying the Bzip2+shuffle combination in order to avoid surprises. Now, how does shuffling affect performance? Well, if you look at plots :ref:`Figure 21 `, :ref:`Figure 22 ` and :ref:`Figure 23 `, you will get a somewhat unexpected (but pleasant) surprise. Roughly, *shuffle* makes the writing process (shuffling+compressing) faster (approximately a 15% for LZO, 30% for Bzip2 and a 80% for Zlib), which is an interesting result by itself. But perhaps more exciting is the fact that the reading process (unshuffling+decompressing) is also accelerated by a similar extent (a 20% for LZO, 60% for Zlib and a 75% for Bzip2, roughly). .. _comprWriteShuffleComparison: .. figure:: images/compressed-writing-shuffle.png :align: center **Figure 21. Writing with different compression libraries with and without the shuffle filter.** .. _comprReadNoCacheShuffleComparison: .. figure:: images/compressed-select-nocache-shuffle-only.png :align: center **Figure 22. Reading with different compression libraries with the shuffle filter. The file is not in OS cache.** .. _comprReadCacheShuffleComparison: .. figure:: images/compressed-select-cache-shuffle.png :align: center **Figure 23. Reading with different compression libraries with and without the shuffle filter. The file is in OS cache.** You may wonder why introducing another filter in the write/read pipelines does effectively accelerate the throughput. Well, maybe data elements are more similar or related column-wise than row-wise, i.e. contiguous elements in the same column are more alike, so shuffling makes the job of the compressor easier (faster) and more effective (greater ratios). As a side effect, compressed chunks do fit better in the CPU cache (at least, the chunks are smaller!) so that the process of unshuffle/decompress can make a better use of the cache (i.e. reducing the number of CPU cache faults). So, given the potential gains (faster writing and reading, but specially much improved compression level), it is a good thing to have such a filter enabled by default in the battle for discovering redundancy when you want to compress your data, just as PyTables does. Using Psyco ----------- Psyco (see :ref:`[PSYCO] `) is a kind of specialized compiler for Python that typically accelerates Python applications with no change in source code. You can think of Psyco as a kind of just-in-time (JIT) compiler, a little bit like Java's, that emits machine code on the fly instead of interpreting your Python program step by step. The result is that your unmodified Python programs run faster. Psyco is very easy to install and use, so in most scenarios it is worth to give it a try. However, it only runs on Intel 386 architectures, so if you are using other architectures, you are out of luck (and, moreover, it seems that there are no plans to support other platforms). Besides, with the addition of flexible (and very fast) in-kernel queries (by the way, they cannot be optimized at all by Psyco), the use of Psyco will only help in rather few scenarios. In fact, the only important situation that you might benefit right now from using Psyco (I mean, in PyTables contexts) is for speeding-up the write speed in tables when using the Row interface (see :ref:`RowClassDescr`). But again, this latter case can also be accelerated by using the :meth:`Table.append` method and building your own buffers [4]_. As an example, imagine that you have a small script that reads and selects data over a series of datasets, like this:: def read_file(filename): "Select data from all the tables in filename" fileh = open_file(filename, mode = "r") result = [] for table in fileh("/", 'Table'): result = [p['var3'] for p in table if p['var2'] <= 20] fileh.close() return result if __name__=="__main__": print(read_file("myfile.h5")) In order to accelerate this piece of code, you can rewrite your main program to look like:: if __name__=="__main__": import psyco psyco.bind(read_file) print(read_file("myfile.h5")) That's all! From now on, each time that you execute your Python script, Psyco will deploy its sophisticated algorithms so as to accelerate your calculations. You can see in the graphs :ref:`Figure 24 ` and :ref:`Figure 25 ` how much I/O speed improvement you can get by using Psyco. By looking at this figures you can get an idea if these improvements are of your interest or not. In general, if you are not going to use compression you will take advantage of Psyco if your tables are medium sized (from a thousand to a million rows), and this advantage will disappear progressively when the number of rows grows well over one million. However if you use compression, you will probably see improvements even beyond this limit (see :ref:`compressionIssues`). As always, there is no substitute for experimentation with your own dataset. .. _psycoWriteComparison: .. figure:: images/write-medium-psyco-nopsyco-comparison.png :align: center **Figure 24. Writing tables with/without Psyco.** .. _psycoReadComparison: .. figure:: images/read-medium-psyco-nopsyco-comparison.png :align: center **Figure 25. Reading tables with/without Psyco.** .. _LRUOptim: Getting the most from the node LRU cache ---------------------------------------- One limitation of the initial versions of PyTables was that they needed to load all nodes in a file completely before being ready to deal with them, making the opening times for files with a lot of nodes very high and unacceptable in many cases. Starting from PyTables 1.2 on, a new lazy node loading schema was setup that avoids loading all the nodes of the *object tree* in memory. In addition, a new LRU cache was introduced in order to accelerate the access to already visited nodes. This cache (one per file) is responsible for keeping up the most recently visited nodes in memory and discard the least recent used ones. This represents a big advantage over the old schema, not only in terms of memory usage (as there is no need to load *every* node in memory), but it also adds very convenient optimizations for working interactively like, for example, speeding-up the opening times of files with lots of nodes, allowing to open almost any kind of file in typically less than one tenth of second (compare this with the more than 10 seconds for files with more than 10000 nodes in PyTables pre-1.2 era) as well as optimizing the access to frequently visited nodes. See for more info on the advantages (and also drawbacks) of this approach. One thing that deserves some discussion is the election of the parameter that sets the maximum amount of nodes to be kept in memory at any time. As PyTables is meant to be deployed in machines that can have potentially low memory, the default for it is quite conservative (you can look at its actual value in the :data:`parameters.NODE_CACHE_SLOTS` parameter in module :file:`tables/parameters.py`). However, if you usually need to deal with files that have many more nodes than the maximum default, and you have a lot of free memory in your system, then you may want to experiment in order to see which is the appropriate value of :data:`parameters.NODE_CACHE_SLOTS` that fits better your needs. As an example, look at the next code:: def browse_tables(filename): fileh = open_file(filename,'a') group = fileh.root.newgroup for j in range(10): for tt in fileh.walk_nodes(group, "Table"): title = tt.attrs.TITLE for row in tt: pass fileh.close() We will be running the code above against a couple of files having a ``/newgroup`` containing 100 tables and 1000 tables respectively. In addition, this benchmark is run twice for two different values of the LRU cache size, specifically 256 and 1024. You can see the results in :ref:`table `. .. _optimization_table_1: .. only:: not latex .. table:: **Retrieval speed and memory consumption depending on the number of nodes in LRU cache.** ====================== =========== === ======= ==== ==== === ======= ==== ==== Number: 100 nodes 1000 nodes ---------------------------------- --------------------- --------------------- Mem & Speed Memory (MB) Time (ms) Memory (MB) Time (ms) ---------------------------------- ----------- --------- ----------- --------- Node is coming from... Cache size 256 1024 256 1024 256 1024 256 1024 ====================== =========== === ======= ==== ==== === ======= ==== ==== Disk 14 14 1.24 1.24 51 66 1.33 1.31 Cache 14 14 0.53 0.52 65 73 1.35 0.68 ====================== =========== === ======= ==== ==== === ======= ==== ==== .. raw:: latex \begin{threeparttable} \capstart\caption{Retrieval speed and memory consumption depending on the number of nodes in LRU cache.} \begin{tabulary}{\linewidth}{|l|l|r|r|r|r|r|r|r|r|} \hline \multicolumn{2}{|l|}{\textbf{Number:}} & \multicolumn{4}{|c|}{\textbf{100 nodes}} & \multicolumn{4}{|c|}{\textbf{1000 nodes}} \\ \hline \multicolumn{2}{|l|}{\textbf{Mem and Speed}} & \multicolumn{2}{|c|}{\textbf{Memory (MB)}} & \multicolumn{2}{|c|}{\textbf{Time (ms)}} & \multicolumn{2}{|c|}{\textbf{Memory (MB)}} & \multicolumn{2}{|c|}{\textbf{Time (ms)}}\\ \hline \textbf{Node is coming from...} & \textbf{Cache size} & \textbf{256} & \textbf{1024} & \textbf{256} & \textbf{1024} & \textbf{256} & \textbf{1024} & \textbf{256} & \textbf{1024}\\ \hline Disk & & 14 & 14 & 1.24 & 1.24 & 51 & 66 & 1.33 & 1.31 \\ Cache & & 14 & 14 & 0.53 & 0.52 & 65 & 73 & 1.35 & 0.68 \\ \hline \end{tabulary} \end{threeparttable} From the data in :ref:`table `, one can see that when the number of objects that you are dealing with does fit in cache, you will get better access times to them. Also, incrementing the node cache size effectively consumes more memory *only* if the total nodes exceeds the slots in cache; otherwise the memory consumption remains the same. It is also worth noting that incrementing the node cache size in the case you want to fit all your nodes in cache does not take much more memory than being too conservative. On the other hand, it might happen that the speed-up that you can achieve by allocating more slots in your cache is not worth the amount of memory used. Also worth noting is that if you have a lot of memory available and performance is absolutely critical, you may want to try out a negative value for :data:`parameters.NODE_CACHE_SLOTS`. This will cause that all the touched nodes will be kept in an internal dictionary and this is the faster way to load/retrieve nodes. However, and in order to avoid a large memory consumption, the user will be warned when the number of loaded nodes will reach the ``-NODE_CACHE_SLOTS`` value. Finally, a value of zero in :data:`parameters.NODE_CACHE_SLOTS` means that any cache mechanism is disabled. At any rate, if you feel that this issue is important for you, there is no replacement for setting your own experiments up in order to proceed to fine-tune the :data:`parameters.NODE_CACHE_SLOTS` parameter. .. note:: PyTables >= 2.3 sports an optimized LRU cache node written in C, so you should expect significantly faster LRU cache operations when working with it. .. note:: Numerical results reported in :ref:`table ` have been obtained with PyTables < 3.1. In PyTables 3.1 the node cache mechanism has been completely redesigned so while all comments above are still valid, numerical values could be a little bit different from the ones reported in :ref:`table `. Compacting your PyTables files ------------------------------ Let's suppose that you have a file where you have made a lot of row deletions on one or more tables, or deleted many leaves or even entire subtrees. These operations might leave *holes* (i.e. space that is not used anymore) in your files that may potentially affect not only the size of the files but, more importantly, the performance of I/O. This is because when you delete a lot of rows in a table, the space is not automatically recovered on the fly. In addition, if you add many more rows to a table than specified in the expectedrows keyword at creation time this may affect performance as well, as explained in :ref:`expectedRowsOptim`. In order to cope with these issues, you should be aware that PyTables includes a handy utility called ptrepack which can be very useful not only to compact *fragmented* files, but also to adjust some internal parameters in order to use better buffer and chunk sizes for optimum I/O speed. Please check the :ref:`ptrepackDescr` for a brief tutorial on its use. Another thing that you might want to use ptrepack for is changing the compression filters or compression levels on your existing data for different goals, like checking how this can affect both final size and I/O performance, or getting rid of the optional compressors like LZO or bzip2 in your existing files, in case you want to use them with generic HDF5 tools that do not have support for these filters. -------------- .. [1] CArray nodes, though not extensible, are chunked and have their optimum chunk size automatically computed at creation time, since their final shape is known. .. [2] Except for Array objects. .. [3] Some users reported that the typical improvement with real data is between a factor 1.5x and 2.5x over the already compressed datasets. .. [4] So, there is not much point in using Psyco with recent versions of PyTables anymore. PyTables-3.7.0/doc/source/usersguide/parameter_files.rst000066400000000000000000000077521416254111300233750ustar00rootroot00000000000000.. _parameter_files: PyTables parameter files ======================== .. currentmodule:: tables.parameters PyTables issues warnings when certain limits are exceeded. Those limits are not intrinsic limitations of the underlying software, but rather are proactive measures to avoid large resource consumptions. The default limits should be enough for most of cases, and users should try to respect them. However, in some situations, it can be convenient to increase (or decrease) these limits. Also, and in order to get maximum performance, PyTables implements a series of sophisticated features, like I/O buffers or different kind of caches (for nodes, chunks and other internal metadata). These features comes with a default set of parameters that ensures a decent performance in most of situations. But, as there is always a need for every case, it is handy to have the possibility to fine-tune some of these parameters. Because of these reasons, PyTables implements a couple of ways to change the values of these parameters. All the *tunable* parameters live in the :file:`tables/parameters.py`. The user can choose to change them in the parameter files themselves for a global and persistent change. Moreover, if he wants a finer control, he can pass any of these parameters directly to the :func:`tables.open_file` function, and the new parameters will only take effect in the corresponding file (the defaults will continue to be in the parameter files). A description of all of the tunable parameters follows. As the defaults stated here may change from release to release, please check with your actual parameter files so as to know your actual default values. .. warning:: Changing the next parameters may have a very bad effect in the resource consumption and performance of your PyTables scripts. Please be careful when touching these! Tunable parameters in parameters.py ----------------------------------- Recommended maximum values ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autodata:: MAX_COLUMNS .. autodata:: MAX_NODE_ATTRS .. autodata:: MAX_GROUP_WIDTH .. autodata:: MAX_TREE_DEPTH .. autodata:: MAX_UNDO_PATH_LENGTH Cache limits ~~~~~~~~~~~~ .. autodata:: CHUNK_CACHE_NELMTS .. autodata:: CHUNK_CACHE_PREEMPT .. autodata:: CHUNK_CACHE_SIZE .. autodata:: COND_CACHE_SLOTS .. autodata:: METADATA_CACHE_SIZE .. autodata:: NODE_CACHE_SLOTS Parameters for the different internal caches ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autodata:: BOUNDS_MAX_SIZE .. autodata:: BOUNDS_MAX_SLOTS .. autodata:: ITERSEQ_MAX_ELEMENTS .. autodata:: ITERSEQ_MAX_SIZE .. autodata:: ITERSEQ_MAX_SLOTS .. autodata:: LIMBOUNDS_MAX_SIZE .. autodata:: LIMBOUNDS_MAX_SLOTS .. autodata:: TABLE_MAX_SIZE .. autodata:: SORTED_MAX_SIZE .. autodata:: SORTEDLR_MAX_SIZE .. autodata:: SORTEDLR_MAX_SLOTS Parameters for general cache behaviour ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. warning:: The next parameters will not take any effect if passed to the open_file() function, so they can only be changed in a *global* way. You can change them in the file, but this is strongly discouraged unless you know well what you are doing. .. autodata:: DISABLE_EVERY_CYCLES .. autodata:: ENABLE_EVERY_CYCLES .. autodata:: LOWEST_HIT_RATIO Parameters for the I/O buffer in Leaf objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autodata:: IO_BUFFER_SIZE .. autodata:: BUFFER_TIMES Miscellaneous ~~~~~~~~~~~~~ .. autodata:: EXPECTED_ROWS_EARRAY .. autodata:: EXPECTED_ROWS_TABLE .. autodata:: PYTABLES_SYS_ATTRS .. autodata:: MAX_NUMEXPR_THREADS .. autodata:: MAX_BLOSC_THREADS .. autodata:: USER_BLOCK_SIZE .. autodata:: ALLOW_PADDING HDF5 driver management ~~~~~~~~~~~~~~~~~~~~~~ .. autodata:: DRIVER .. autodata:: DRIVER_DIRECT_ALIGNMENT .. autodata:: DRIVER_DIRECT_BLOCK_SIZE .. autodata:: DRIVER_DIRECT_CBUF_SIZE .. autodata:: DRIVER_CORE_INCREMENT .. autodata:: DRIVER_CORE_BACKING_STORE .. autodata:: DRIVER_CORE_IMAGE .. autodata:: DRIVER_SPLIT_META_EXT .. autodata:: DRIVER_SPLIT_RAW_EXT PyTables-3.7.0/doc/source/usersguide/tutorials.rst000066400000000000000000002764501416254111300222640ustar00rootroot00000000000000Tutorials ========= .. epigraph:: Seràs la clau que obre tots els panys, seràs la llum, la llum il.limitada, seràs confí on l'aurora comença, seràs forment, escala il.luminada! -- Lyrics: Vicent Andrés i Estellés. Music: Ovidi Montllor, Toti Soler, M'aclame a tu This chapter consists of a series of simple yet comprehensive tutorials that will enable you to understand PyTables' main features. If you would like more information about some particular instance variable, global function, or method, look at the doc strings or go to the library reference in :ref:`library_reference`. If you are reading this in PDF or HTML formats, follow the corresponding hyperlink near each newly introduced entity. Please note that throughout this document the terms *column* and *field* will be used interchangeably, as will the terms *row* and *record*. .. currentmodule:: tables Getting started --------------- In this section, we will see how to define our own records in Python and save collections of them (i.e. a *table*) into a file. Then we will select some of the data in the table using Python cuts and create NumPy arrays to store this selection as separate objects in a tree. In *examples/tutorial1-1.py* you will find the working version of all the code in this section. Nonetheless, this tutorial series has been written to allow you reproduce it in a Python interactive console. I encourage you to do parallel testing and inspect the created objects (variables, docs, children objects, etc.) during the course of the tutorial! Importing tables objects ~~~~~~~~~~~~~~~~~~~~~~~~ Before starting you need to import the public objects in the tables package. You normally do that by executing:: >>> import tables This is the recommended way to import tables if you don't want to pollute your namespace. However, PyTables has a contained set of first-level primitives, so you may consider using the alternative:: >>> from tables import * If you are going to work with NumPy arrays (and normally, you will) you will also need to import functions from the numpy package. So most PyTables programs begin with:: >>> import tables # but in this tutorial we use "from tables import \*" >>> import numpy as np Declaring a Column Descriptor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now, imagine that we have a particle detector and we want to create a table object in order to save data retrieved from it. You need first to define the table, the number of columns it has, what kind of object is contained in each column, and so on. Our particle detector has a TDC (Time to Digital Converter) counter with a dynamic range of 8 bits and an ADC (Analogical to Digital Converter) with a range of 16 bits. For these values, we will define 2 fields in our record object called TDCcount and ADCcount. We also want to save the grid position in which the particle has been detected, so we will add two new fields called grid_i and grid_j. Our instrumentation also can obtain the pressure and energy of the particle. The resolution of the pressure-gauge allows us to use a single-precision float to store pressure readings, while the energy value will need a double-precision float. Finally, to track the particle we want to assign it a name to identify the kind of the particle it is and a unique numeric identifier. So we will add two more fields: name will be a string of up to 16 characters, and idnumber will be an integer of 64 bits (to allow us to store records for extremely large numbers of particles). Having determined our columns and their types, we can now declare a new Particle class that will contain all this information:: >>> from tables import * >>> class Particle(IsDescription): ... name = StringCol(16) # 16-character String ... idnumber = Int64Col() # Signed 64-bit integer ... ADCcount = UInt16Col() # Unsigned short integer ... TDCcount = UInt8Col() # unsigned byte ... grid_i = Int32Col() # 32-bit integer ... grid_j = Int32Col() # 32-bit integer ... pressure = Float32Col() # float (single-precision) ... energy = Float64Col() # double (double-precision) >>> This definition class is self-explanatory. Basically, you declare a class variable for each field you need. As its value you assign an instance of the appropriate Col subclass, according to the kind of column defined (the data type, the length, the shape, etc). See the :ref:`ColClassDescr` for a complete description of these subclasses. See also :ref:`datatypes` for a list of data types supported by the Col constructor. From now on, we can use Particle instances as a descriptor for our detector data table. We will see later on how to pass this object to construct the table. But first, we must create a file where all the actual data pushed into our table will be saved. Creating a PyTables file from scratch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Use the top-level :func:`open_file` function to create a PyTables file:: >>> h5file = open_file("tutorial1.h5", mode="w", title="Test file") :func:`open_file` is one of the objects imported by the ```from tables import *``` statement. Here, we are saying that we want to create a new file in the current working directory called "tutorial1.h5" in "w"rite mode and with a descriptive title string ("Test file"). This function attempts to open the file, and if successful, returns the File (see :ref:`FileClassDescr`) object instance h5file. The root of the object tree is specified in the instance's root attribute. Creating a new group ~~~~~~~~~~~~~~~~~~~~ Now, to better organize our data, we will create a group called *detector* that branches from the root node. We will save our particle data table in this group:: >>> group = h5file.create_group("/", 'detector', 'Detector information') Here, we have taken the File instance h5file and invoked its :meth:`File.create_group` method to create a new group called *detector* branching from "*/*" (another way to refer to the h5file.root object we mentioned above). This will create a new Group (see :ref:`GroupClassDescr`) object instance that will be assigned to the variable group. Creating a new table ~~~~~~~~~~~~~~~~~~~~ Let's now create a Table (see :ref:`TableClassDescr`) object as a branch off the newly-created group. We do that by calling the :meth:`File.create_table` method of the h5file object:: >>> table = h5file.create_table(group, 'readout', Particle, "Readout example") We create the Table instance under group. We assign this table the node name "*readout*". The Particle class declared before is the *description* parameter (to define the columns of the table) and finally we set "*Readout example*" as the Table title. With all this information, a new Table instance is created and assigned to the variable *table*. If you are curious about how the object tree looks right now, simply print the File instance variable *h5file*, and examine the output:: >>> print(h5file) tutorial1.h5 (File) 'Test file' Last modif.: 'Wed Mar 7 11:06:12 2007' Object Tree: / (RootGroup) 'Test file' /detector (Group) 'Detector information' /detector/readout (Table(0,)) 'Readout example' As you can see, a dump of the object tree is displayed. It's easy to see the Group and Table objects we have just created. If you want more information, just type the variable containing the File instance:: >>> h5file File(filename='tutorial1.h5', title='Test file', mode='w', root_uep='/', filters=Filters(complevel=0, shuffle=False, bitshuffle=False, fletcher32=False)) / (RootGroup) 'Test file' /detector (Group) 'Detector information' /detector/readout (Table(0,)) 'Readout example' description := { "ADCcount": UInt16Col(shape=(), dflt=0, pos=0), "TDCcount": UInt8Col(shape=(), dflt=0, pos=1), "energy": Float64Col(shape=(), dflt=0.0, pos=2), "grid_i": Int32Col(shape=(), dflt=0, pos=3), "grid_j": Int32Col(shape=(), dflt=0, pos=4), "idnumber": Int64Col(shape=(), dflt=0, pos=5), "name": StringCol(itemsize=16, shape=(), dflt='', pos=6), "pressure": Float32Col(shape=(), dflt=0.0, pos=7)} byteorder := 'little' chunkshape := (87,) More detailed information is displayed about each object in the tree. Note how Particle, our table descriptor class, is printed as part of the *readout* table description information. In general, you can obtain much more information about the objects and their children by just printing them. That introspection capability is very useful, and I recommend that you use it extensively. The time has come to fill this table with some values. First we will get a pointer to the Row (see :ref:`RowClassDescr`) instance of this table instance:: >>> particle = table.row The row attribute of table points to the Row instance that will be used to write data rows into the table. We write data simply by assigning the Row instance the values for each row as if it were a dictionary (although it is actually an *extension class*), using the column names as keys. Below is an example of how to write rows:: >>> for i in range(10): ... particle['name'] = f'Particle: {i:6d}' ... particle['TDCcount'] = i % 256 ... particle['ADCcount'] = (i * 256) % (1 << 16) ... particle['grid_i'] = i ... particle['grid_j'] = 10 - i ... particle['pressure'] = float(i*i) ... particle['energy'] = float(particle['pressure'] ** 4) ... particle['idnumber'] = i * (2 ** 34) ... # Insert a new particle record ... particle.append() >>> This code should be easy to understand. The lines inside the loop just assign values to the different columns in the Row instance particle (see :ref:`RowClassDescr`). A call to its append() method writes this information to the table I/O buffer. After we have processed all our data, we should flush the table's I/O buffer if we want to write all this data to disk. We achieve that by calling the table.flush() method:: >>> table.flush() Remember, flushing a table is a *very important* step as it will not only help to maintain the integrity of your file, but also will free valuable memory resources (i.e. internal buffers) that your program may need for other things. .. _readingAndSelectingUsage: Reading (and selecting) data in a table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Ok. We have our data on disk, and now we need to access it and select from specific columns the values we are interested in. See the example below:: >>> table = h5file.root.detector.readout >>> pressure = [x['pressure'] for x in table.iterrows() if x['TDCcount'] > 3 and 20 <= x['pressure'] < 50] >>> pressure [25.0, 36.0, 49.0] The first line creates a "shortcut" to the *readout* table deeper on the object tree. As you can see, we use the *natural naming* schema to access it. We also could have used the h5file.get_node() method, as we will do later on. You will recognize the last two lines as a Python list comprehension. It loops over the rows in *table* as they are provided by the :meth:`Table.iterrows` iterator. The iterator returns values until all the data in table is exhausted. These rows are filtered using the expression:: x['TDCcount'] > 3 and 20 <= x['pressure'] < 50 So, we are selecting the values of the pressure column from filtered records to create the final list and assign it to pressure variable. We could have used a normal for loop to accomplish the same purpose, but I find comprehension syntax to be more compact and elegant. PyTables do offer other, more powerful ways of performing selections which may be more suitable if you have very large tables or if you need very high query speeds. They are called *in-kernel* and *indexed* queries, and you can use them through :meth:`Table.where` and other related methods. Let's use an in-kernel selection to query the name column for the same set of cuts:: >>> names = [ x['name'] for x in table.where("""(TDCcount > 3) & (20 <= pressure) & (pressure < 50)""") ] >>> names ['Particle: 5', 'Particle: 6', 'Particle: 7'] In-kernel and indexed queries are not only much faster, but as you can see, they also look more compact, and are among the greatest features for PyTables, so be sure that you use them a lot. See :ref:`condition_syntax` and :ref:`searchOptim` for more information on in-kernel and indexed selections. .. note:: A special care should be taken when the query condition includes string literals. Indeed Python 2 string literals are string of bytes while Python 3 strings are unicode objects. With reference to the above definition of :class:`Particle` it has to be noted that the type of the "name" column do not change depending on the Python version used (of course). It always corresponds to strings of bytes. Any condition involving the "name" column should be written using the appropriate type for string literals in order to avoid :exc:`TypeError`\ s. Suppose one wants to get rows corresponding to specific particle names. The code below will work fine in Python 2 but will fail with a :exc:`TypeError` in Python 3:: >>> condition = '(name == "Particle: 5") | (name == "Particle: 7")' >>> for record in table.where(condition): # TypeError in Python3 ... # do something with "record" The reason is that in Python 3 "condition" implies a comparison between a string of bytes ("name" column contents) and an unicode literals. The correct way to write the condition is:: >>> condition = '(name == b"Particle: 5") | (name == b"Particle: 7")' That's enough about selections for now. The next section will show you how to save these selected results to a file. Creating new array objects ~~~~~~~~~~~~~~~~~~~~~~~~~~ In order to separate the selected data from the mass of detector data, we will create a new group columns branching off the root group. Afterwards, under this group, we will create two arrays that will contain the selected data. First, we create the group:: >>> gcolumns = h5file.create_group(h5file.root, "columns", "Pressure and Name") Note that this time we have specified the first parameter using *natural naming* (h5file.root) instead of with an absolute path string ("/"). Now, create the first of the two Array objects we've just mentioned:: >>> h5file.create_array(gcolumns, 'pressure', np.array(pressure), "Pressure column selection") /columns/pressure (Array(3,)) 'Pressure column selection' atom := Float64Atom(shape=(), dflt=0.0) maindim := 0 flavor := 'numpy' byteorder := 'little' chunkshape := None We already know the first two parameters of the :meth:`File.create_array` methods (these are the same as the first two in create_table): they are the parent group *where* Array will be created and the Array instance *name*. The third parameter is the *object* we want to save to disk. In this case, it is a NumPy array that is built from the selection list we created before. The fourth parameter is the *title*. Now, we will save the second array. It contains the list of strings we selected before: we save this object as-is, with no further conversion:: >>> h5file.create_array(gcolumns, 'name', names, "Name column selection") /columns/name (Array(3,)) 'Name column selection' atom := StringAtom(itemsize=16, shape=(), dflt='') maindim := 0 flavor := 'python' byteorder := 'irrelevant' chunkshape := None As you can see, :meth:`File.create_array` accepts *names* (which is a regular Python list) as an *object* parameter. Actually, it accepts a variety of different regular objects (see :func:`create_array`) as parameters. The flavor attribute (see the output above) saves the original kind of object that was saved. Based on this *flavor*, PyTables will be able to retrieve exactly the same object from disk later on. Note that in these examples, the create_array method returns an Array instance that is not assigned to any variable. Don't worry, this is intentional to show the kind of object we have created by displaying its representation. The Array objects have been attached to the object tree and saved to disk, as you can see if you print the complete object tree:: >>> print(h5file) tutorial1.h5 (File) 'Test file' Last modif.: 'Wed Mar 7 19:40:44 2007' Object Tree: / (RootGroup) 'Test file' /columns (Group) 'Pressure and Name' /columns/name (Array(3,)) 'Name column selection' /columns/pressure (Array(3,)) 'Pressure column selection' /detector (Group) 'Detector information' /detector/readout (Table(10,)) 'Readout example' Closing the file and looking at its content ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To finish this first tutorial, we use the close method of the h5file File object to close the file before exiting Python:: >>> h5file.close() >>> ^D $ You have now created your first PyTables file with a table and two arrays. You can examine it with any generic HDF5 tool, such as h5dump or h5ls. Here is what the tutorial1.h5 looks like when read with the h5ls program. .. code-block:: bash $ h5ls -rd tutorial1.h5 /columns Group /columns/name Dataset {3} Data: (0) "Particle: 5", "Particle: 6", "Particle: 7" /columns/pressure Dataset {3} Data: (0) 25, 36, 49 /detector Group /detector/readout Dataset {10/Inf} Data: (0) {0, 0, 0, 0, 10, 0, "Particle: 0", 0}, (1) {256, 1, 1, 1, 9, 17179869184, "Particle: 1", 1}, (2) {512, 2, 256, 2, 8, 34359738368, "Particle: 2", 4}, (3) {768, 3, 6561, 3, 7, 51539607552, "Particle: 3", 9}, (4) {1024, 4, 65536, 4, 6, 68719476736, "Particle: 4", 16}, (5) {1280, 5, 390625, 5, 5, 85899345920, "Particle: 5", 25}, (6) {1536, 6, 1679616, 6, 4, 103079215104, "Particle: 6", 36}, (7) {1792, 7, 5764801, 7, 3, 120259084288, "Particle: 7", 49}, (8) {2048, 8, 16777216, 8, 2, 137438953472, "Particle: 8", 64}, (9) {2304, 9, 43046721, 9, 1, 154618822656, "Particle: 9", 81} Here's the output as displayed by the "ptdump" PyTables utility (located in utils/ directory). .. code-block:: bash $ ptdump tutorial1.h5 / (RootGroup) 'Test file' /columns (Group) 'Pressure and Name' /columns/name (Array(3,)) 'Name column selection' /columns/pressure (Array(3,)) 'Pressure column selection' /detector (Group) 'Detector information' /detector/readout (Table(10,)) 'Readout example' You can pass the `-v` or `-d` options to ptdump if you want more verbosity. Try them out! Also, in :ref:`Figure 1 `, you can admire how the tutorial1.h5 looks like using the `ViTables `_ graphical interface. .. _tutorial1-1-tableview: .. figure:: images/tutorial1-1-tableview.png :align: center **Figure 1. The initial version of the data file for tutorial 1, with a view of the data objects.** Browsing the *object tree* -------------------------- In this section, we will learn how to browse the tree and retrieve data and also meta-information about the actual data. In *examples/tutorial1-2.py* you will find the working version of all the code in this section. As before, you are encouraged to use a python shell and inspect the object tree during the course of the tutorial. Traversing the object tree ~~~~~~~~~~~~~~~~~~~~~~~~~~ Let's start by opening the file we created in last tutorial section:: >>> h5file = open_file("tutorial1.h5", "a") This time, we have opened the file in "a"ppend mode. We use this mode to add more information to the file. PyTables, following the Python tradition, offers powerful introspection capabilities, i.e. you can easily ask information about any component of the object tree as well as search the tree. To start with, you can get a preliminary overview of the object tree by simply printing the existing File instance:: >>> print(h5file) tutorial1.h5 (File) 'Test file' Last modif.: 'Wed Mar 7 19:50:57 2007' Object Tree: / (RootGroup) 'Test file' /columns (Group) 'Pressure and Name' /columns/name (Array(3,)) 'Name column selection' /columns/pressure (Array(3,)) 'Pressure column selection' /detector (Group) 'Detector information' /detector/readout (Table(10,)) 'Readout example' It looks like all of our objects are there. Now let's make use of the File iterator to see how to list all the nodes in the object tree:: >>> for node in h5file: ... print(node) / (RootGroup) 'Test file' /columns (Group) 'Pressure and Name' /detector (Group) 'Detector information' /columns/name (Array(3,)) 'Name column selection' /columns/pressure (Array(3,)) 'Pressure column selection' /detector/readout (Table(10,)) 'Readout example' We can use the :meth:`File.walk_groups` method of the File class to list only the *groups* on tree:: >>> for group in h5file.walk_groups(): ... print(group) / (RootGroup) 'Test file' /columns (Group) 'Pressure and Name' /detector (Group) 'Detector information' Note that :meth:`File.walk_groups` actually returns an *iterator*, not a list of objects. Using this iterator with the list_nodes() method is a powerful combination. Let's see an example listing of all the arrays in the tree:: >>> for group in h5file.walk_groups("/"): ... for array in h5file.list_nodes(group, classname='Array'): ... print(array) /columns/name (Array(3,)) 'Name column selection' /columns/pressure (Array(3,)) 'Pressure column selection' :meth:`File.list_nodes` returns a list containing all the nodes hanging off a specific Group. If the *classname* keyword is specified, the method will filter out all instances which are not descendants of the class. We have asked for only Array instances. There exist also an iterator counterpart called :meth:`File.iter_nodes` that might be handy is some situations, like for example when dealing with groups with a large number of nodes behind it. We can combine both calls by using the :meth:`File.walk_nodes` special method of the File object. For example:: >>> for array in h5file.walk_nodes("/", "Array"): ... print(array) /columns/name (Array(3,)) 'Name column selection' /columns/pressure (Array(3,)) 'Pressure column selection' This is a nice shortcut when working interactively. Finally, we will list all the Leaf, i.e. Table and Array instances (see :ref:`LeafClassDescr` for detailed information on Leaf class), in the /detector group. Note that only one instance of the Table class (i.e. readout) will be selected in this group (as should be the case):: >>> for leaf in h5file.root.detector._f_walknodes('Leaf'): ... print(leaf) /detector/readout (Table(10,)) 'Readout example' We have used a call to the :meth:`Group._f_walknodes` method, using the *natural naming* path specification. Of course you can do more sophisticated node selections using these powerful methods. But first, let's take a look at some important PyTables object instance variables. Setting and getting user attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyTables provides an easy and concise way to complement the meaning of your node objects on the tree by using the AttributeSet class (see :ref:`AttributeSetClassDescr`). You can access this object through the standard attribute attrs in Leaf nodes and _v_attrs in Group nodes. For example, let's imagine that we want to save the date indicating when the data in /detector/readout table has been acquired, as well as the temperature during the gathering process:: >>> table = h5file.root.detector.readout >>> table.attrs.gath_date = "Wed, 06/12/2003 18:33" >>> table.attrs.temperature = 18.4 >>> table.attrs.temp_scale = "Celsius" Now, let's set a somewhat more complex attribute in the /detector group:: >>> detector = h5file.root.detector >>> detector._v_attrs.stuff = [5, (2.3, 4.5), "Integer and tuple"] Note how the AttributeSet instance is accessed with the _v_attrs attribute because detector is a Group node. In general, you can save any standard Python data structure as an attribute node. See :ref:`AttributeSetClassDescr` for a more detailed explanation of how they are serialized for export to disk. Retrieving the attributes is equally simple:: >>> table.attrs.gath_date 'Wed, 06/12/2003 18:33' >>> table.attrs.temperature 18.399999999999999 >>> table.attrs.temp_scale 'Celsius' >>> detector._v_attrs.stuff [5, (2.2999999999999998, 4.5), 'Integer and tuple'] You can probably guess how to delete attributes:: >>> del table.attrs.gath_date If you want to examine the current user attribute set of /detector/table, you can print its representation (try hitting the TAB key twice if you are on a Unix Python console with the rlcompleter module active):: >>> table.attrs /detector/readout._v_attrs (AttributeSet), 23 attributes: [CLASS := 'TABLE', FIELD_0_FILL := 0, FIELD_0_NAME := 'ADCcount', FIELD_1_FILL := 0, FIELD_1_NAME := 'TDCcount', FIELD_2_FILL := 0.0, FIELD_2_NAME := 'energy', FIELD_3_FILL := 0, FIELD_3_NAME := 'grid_i', FIELD_4_FILL := 0, FIELD_4_NAME := 'grid_j', FIELD_5_FILL := 0, FIELD_5_NAME := 'idnumber', FIELD_6_FILL := '', FIELD_6_NAME := 'name', FIELD_7_FILL := 0.0, FIELD_7_NAME := 'pressure', FLAVOR := 'numpy', NROWS := 10, TITLE := 'Readout example', VERSION := '2.6', temp_scale := 'Celsius', temperature := 18.399999999999999] We've got all the attributes (including the *system* attributes). You can get a list of *all* attributes or only the *user* or *system* attributes with the _f_list() method:: >>> print(table.attrs._f_list("all")) ['CLASS', 'FIELD_0_FILL', 'FIELD_0_NAME', 'FIELD_1_FILL', 'FIELD_1_NAME', 'FIELD_2_FILL', 'FIELD_2_NAME', 'FIELD_3_FILL', 'FIELD_3_NAME', 'FIELD_4_FILL', 'FIELD_4_NAME', 'FIELD_5_FILL', 'FIELD_5_NAME', 'FIELD_6_FILL', 'FIELD_6_NAME', 'FIELD_7_FILL', 'FIELD_7_NAME', 'FLAVOR', 'NROWS', 'TITLE', 'VERSION', 'temp_scale', 'temperature'] >>> print(table.attrs._f_list("user")) ['temp_scale', 'temperature'] >>> print(table.attrs._f_list("sys")) ['CLASS', 'FIELD_0_FILL', 'FIELD_0_NAME', 'FIELD_1_FILL', 'FIELD_1_NAME', 'FIELD_2_FILL', 'FIELD_2_NAME', 'FIELD_3_FILL', 'FIELD_3_NAME', 'FIELD_4_FILL', 'FIELD_4_NAME', 'FIELD_5_FILL', 'FIELD_5_NAME', 'FIELD_6_FILL', 'FIELD_6_NAME', 'FIELD_7_FILL', 'FIELD_7_NAME', 'FLAVOR', 'NROWS', 'TITLE', 'VERSION'] You can also rename attributes:: >>> table.attrs._f_rename("temp_scale","tempScale") >>> print(table.attrs._f_list()) ['tempScale', 'temperature'] And, from PyTables 2.0 on, you are allowed also to set, delete or rename system attributes:: >>> table.attrs._f_rename("VERSION", "version") >>> table.attrs.VERSION Traceback (most recent call last): File "", line 1, in File "tables/attributeset.py", line 222, in __getattr__ (name, self._v__nodepath) AttributeError: Attribute 'VERSION' does not exist in node: '/detector/readout' >>> table.attrs.version '2.6' *Caveat emptor:* you must be careful when modifying system attributes because you may end fooling PyTables and ultimately getting unwanted behaviour. Use this only if you know what are you doing. So, given the caveat above, we will proceed to restore the original name of VERSION attribute:: >>> table.attrs._f_rename("version", "VERSION") >>> table.attrs.VERSION '2.6' Ok, that's better. If you would terminate your session now, you would be able to use the h5ls command to read the /detector/readout attributes from the file written to disk. .. code-block:: bash $ h5ls -vr tutorial1.h5/detector/readout Opened "tutorial1.h5" with sec2 driver. /detector/readout Dataset {10/Inf} Attribute: CLASS scalar Type: 6-byte null-terminated ASCII string Data: "TABLE" Attribute: VERSION scalar Type: 4-byte null-terminated ASCII string Data: "2.6" Attribute: TITLE scalar Type: 16-byte null-terminated ASCII string Data: "Readout example" Attribute: NROWS scalar Type: native long long Data: 10 Attribute: FIELD_0_NAME scalar Type: 9-byte null-terminated ASCII string Data: "ADCcount" Attribute: FIELD_1_NAME scalar Type: 9-byte null-terminated ASCII string Data: "TDCcount" Attribute: FIELD_2_NAME scalar Type: 7-byte null-terminated ASCII string Data: "energy" Attribute: FIELD_3_NAME scalar Type: 7-byte null-terminated ASCII string Data: "grid_i" Attribute: FIELD_4_NAME scalar Type: 7-byte null-terminated ASCII string Data: "grid_j" Attribute: FIELD_5_NAME scalar Type: 9-byte null-terminated ASCII string Data: "idnumber" Attribute: FIELD_6_NAME scalar Type: 5-byte null-terminated ASCII string Data: "name" Attribute: FIELD_7_NAME scalar Type: 9-byte null-terminated ASCII string Data: "pressure" Attribute: FLAVOR scalar Type: 5-byte null-terminated ASCII string Data: "numpy" Attribute: tempScale scalar Type: 7-byte null-terminated ASCII string Data: "Celsius" Attribute: temperature scalar Type: native double Data: 18.4 Location: 0:1:0:1952 Links: 1 Modified: 2006-12-11 10:35:13 CET Chunks: {85} 3995 bytes Storage: 470 logical bytes, 3995 allocated bytes, 11.76% utilization Type: struct { "ADCcount" +0 native unsigned short "TDCcount" +2 native unsigned char "energy" +3 native double "grid_i" +11 native int "grid_j" +15 native int "idnumber" +19 native long long "name" +27 16-byte null-terminated ASCII string "pressure" +43 native float } 47 bytes Attributes are a useful mechanism to add persistent (meta) information to your data. Getting object metadata ~~~~~~~~~~~~~~~~~~~~~~~ Each object in PyTables has *metadata* information about the data in the file. Normally this *meta-information* is accessible through the node instance variables. Let's take a look at some examples:: >>> print("Object:", table) Object: /detector/readout (Table(10,)) 'Readout example' >>> print("Table name:", table.name) Table name: readout >>> print("Table title:", table.title) Table title: Readout example >>> print("Number of rows in table:", table.nrows) Number of rows in table: 10 >>> print("Table variable names with their type and shape:") Table variable names with their type and shape: >>> for name in table.colnames: ... print(name, ':= %s, %s' % (table.coldtypes[name], table.coldtypes[name].shape)) ADCcount := uint16, () TDCcount := uint8, () energy := float64, () grid_i := int32, () grid_j := int32, () idnumber := int64, () name := |S16, () pressure := float32, () Here, the name, title, nrows, colnames and coldtypes attributes (see :class:`Table` for a complete attribute list) of the Table object gives us quite a bit of information about the table data. You can interactively retrieve general information about the public objects in PyTables by asking for help:: >>> help(table) Help on Table in module tables.table: class Table(tableextension.Table, tables.leaf.Leaf) | This class represents heterogeneous datasets in an HDF5 file. | | Tables are leaves (see the `Leaf` class) whose data consists of a | unidimensional sequence of *rows*, where each row contains one or | more *fields*. Fields have an associated unique *name* and | *position*, with the first field having position 0. All rows have | the same fields, which are arranged in *columns*. [snip] | | Instance variables | ------------------ | | The following instance variables are provided in addition to those | in `Leaf`. Please note that there are several `col` dictionaries | to ease retrieving information about a column directly by its path | name, avoiding the need to walk through `Table.description` or | `Table.cols`. | | autoindex | Automatically keep column indexes up to date? | | Setting this value states whether existing indexes should be | automatically updated after an append operation or recomputed | after an index-invalidating operation (i.e. removal and | modification of rows). The default is true. [snip] | rowsize | The size in bytes of each row in the table. | | Public methods -- reading | ------------------------- | | * col(name) | * iterrows([start][, stop][, step]) | * itersequence(sequence) * itersorted(sortby[, checkCSI][, start][, stop][, step]) | * read([start][, stop][, step][, field][, coords]) | * read_coordinates(coords[, field]) * read_sorted(sortby[, checkCSI][, field,][, start][, stop][, step]) | * __getitem__(key) | * __iter__() | | Public methods -- writing | ------------------------- | | * append(rows) | * modify_column([start][, stop][, step][, column][, colname]) [snip] Try getting help with other object docs by yourself:: >>> help(h5file) >>> help(table.remove_rows) To examine metadata in the */columns/pressure* Array object:: >>> pressureObject = h5file.get_node("/columns", "pressure") >>> print("Info on the object:", repr(pressureObject)) Info on the object: /columns/pressure (Array(3,)) 'Pressure column selection' atom := Float64Atom(shape=(), dflt=0.0) maindim := 0 flavor := 'numpy' byteorder := 'little' chunkshape := None >>> print(" shape: ==>", pressureObject.shape) shape: ==> (3,) >>> print(" title: ==>", pressureObject.title) title: ==> Pressure column selection >>> print(" atom: ==>", pressureObject.atom) atom: ==> Float64Atom(shape=(), dflt=0.0) Observe that we have used the :meth:`File.get_node` method of the File class to access a node in the tree, instead of the natural naming method. Both are useful, and depending on the context you will prefer one or the other. :meth:`File.get_node` has the advantage that it can get a node from the pathname string (as in this example) and can also act as a filter to show only nodes in a particular location that are instances of class *classname*. In general, however, I consider natural naming to be more elegant and easier to use, especially if you are using the name completion capability present in interactive console. Try this powerful combination of natural naming and completion capabilities present in most Python consoles, and see how pleasant it is to browse the object tree (well, as pleasant as such an activity can be). If you look at the type attribute of the pressureObject object, you can verify that it is a "*float64*" array. By looking at its shape attribute, you can deduce that the array on disk is unidimensional and has 3 elements. See :class:`Array` or the internal doc strings for the complete Array attribute list. Reading data from Array objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once you have found the desired Array, use the read() method of the Array object to retrieve its data:: >>> pressureArray = pressureObject.read() >>> pressureArray array([ 25., 36., 49.]) >>> print("pressureArray is an object of type:", type(pressureArray)) pressureArray is an object of type: >>> nameArray = h5file.root.columns.name.read() >>> print("nameArray is an object of type:", type(nameArray)) nameArray is an object of type: >>> >>> print("Data on arrays nameArray and pressureArray:") Data on arrays nameArray and pressureArray: >>> for i in range(pressureObject.shape[0]): ... print(nameArray[i], "-->", pressureArray[i]) Particle: 5 --> 25.0 Particle: 6 --> 36.0 Particle: 7 --> 49.0 You can see that the :meth:`Array.read` method returns an authentic NumPy object for the pressureObject instance by looking at the output of the type() call. A read() of the nameArray object instance returns a native Python list (of strings). The type of the object saved is stored as an HDF5 attribute (named FLAVOR) for objects on disk. This attribute is then read as Array meta-information (accessible through in the Array.attrs.FLAVOR variable), enabling the read array to be converted into the original object. This provides a means to save a large variety of objects as arrays with the guarantee that you will be able to later recover them in their original form. See :meth:`File.create_array` for a complete list of supported objects for the Array object class. Commiting data to tables and arrays ----------------------------------- We have seen how to create tables and arrays and how to browse both data and metadata in the object tree. Let's examine more closely now one of the most powerful capabilities of PyTables, namely, how to modify already created tables and arrays [1]_ Appending data to an existing table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now, let's have a look at how we can add records to an existing table on disk. Let's use our well-known *readout* Table object and append some new values to it:: >>> table = h5file.root.detector.readout >>> particle = table.row >>> for i in range(10, 15): ... particle['name'] = f'Particle: {i:6d}' ... particle['TDCcount'] = i % 256 ... particle['ADCcount'] = (i * 256) % (1 << 16) ... particle['grid_i'] = i ... particle['grid_j'] = 10 - i ... particle['pressure'] = float(i*i) ... particle['energy'] = float(particle['pressure'] ** 4) ... particle['idnumber'] = i * (2 ** 34) ... particle.append() >>> table.flush() It's the same method we used to fill a new table. PyTables knows that this table is on disk, and when you add new records, they are appended to the end of the table [2]_. If you look carefully at the code you will see that we have used the table.row attribute to create a table row and fill it with the new values. Each time that its append() method is called, the actual row is committed to the output buffer and the row pointer is incremented to point to the next table record. When the buffer is full, the data is saved on disk, and the buffer is reused again for the next cycle. *Caveat emptor*: Do not forget to always call the flush() method after a write operation, or else your tables will not be updated! Let's have a look at some rows in the modified table and verify that our new data has been appended:: >>> for r in table.iterrows(): ... print("%-16s | %11.1f | %11.4g | %6d | %6d | %8d \|" % \\ ... (r['name'], r['pressure'], r['energy'], r['grid_i'], r['grid_j'], ... r['TDCcount'])) Particle: 0 | 0.0 | 0 | 0 | 10 | 0 | Particle: 1 | 1.0 | 1 | 1 | 9 | 1 | Particle: 2 | 4.0 | 256 | 2 | 8 | 2 | Particle: 3 | 9.0 | 6561 | 3 | 7 | 3 | Particle: 4 | 16.0 | 6.554e+04 | 4 | 6 | 4 | Particle: 5 | 25.0 | 3.906e+05 | 5 | 5 | 5 | Particle: 6 | 36.0 | 1.68e+06 | 6 | 4 | 6 | Particle: 7 | 49.0 | 5.765e+06 | 7 | 3 | 7 | Particle: 8 | 64.0 | 1.678e+07 | 8 | 2 | 8 | Particle: 9 | 81.0 | 4.305e+07 | 9 | 1 | 9 | Particle: 10 | 100.0 | 1e+08 | 10 | 0 | 10 | Particle: 11 | 121.0 | 2.144e+08 | 11 | -1 | 11 | Particle: 12 | 144.0 | 4.3e+08 | 12 | -2 | 12 | Particle: 13 | 169.0 | 8.157e+08 | 13 | -3 | 13 | Particle: 14 | 196.0 | 1.476e+09 | 14 | -4 | 14 | Modifying data in tables ~~~~~~~~~~~~~~~~~~~~~~~~ Ok, until now, we've been only reading and writing (appending) values to our tables. But there are times that you need to modify your data once you have saved it on disk (this is specially true when you need to modify the real world data to adapt your goals ;). Let's see how we can modify the values that were saved in our existing tables. We will start modifying single cells in the first row of the Particle table:: >>> print("Before modif-->", table[0]) Before modif--> (0, 0, 0.0, 0, 10, 0L, 'Particle: 0', 0.0) >>> table.cols.TDCcount[0] = 1 >>> print("After modifying first row of ADCcount-->", table[0]) After modifying first row of ADCcount--> (0, 1, 0.0, 0, 10, 0L, 'Particle: 0', 0.0) >>> table.cols.energy[0] = 2 >>> print("After modifying first row of energy-->", table[0]) After modifying first row of energy--> (0, 1, 2.0, 0, 10, 0L, 'Particle: 0', 0.0) We can modify complete ranges of columns as well:: >>> table.cols.TDCcount[2:5] = [2,3,4] >>> print("After modifying slice [2:5] of TDCcount-->", table[0:5]) After modifying slice [2:5] of TDCcount--> [(0, 1, 2.0, 0, 10, 0L, 'Particle: 0', 0.0) (256, 1, 1.0, 1, 9, 17179869184L, 'Particle: 1', 1.0) (512, 2, 256.0, 2, 8, 34359738368L, 'Particle: 2', 4.0) (768, 3, 6561.0, 3, 7, 51539607552L, 'Particle: 3', 9.0) (1024, 4, 65536.0, 4, 6, 68719476736L, 'Particle: 4', 16.0)] >>> table.cols.energy[1:9:3] = [2,3,4] >>> print("After modifying slice [1:9:3] of energy-->", table[0:9]) After modifying slice [1:9:3] of energy--> [(0, 1, 2.0, 0, 10, 0L, 'Particle: 0', 0.0) (256, 1, 2.0, 1, 9, 17179869184L, 'Particle: 1', 1.0) (512, 2, 256.0, 2, 8, 34359738368L, 'Particle: 2', 4.0) (768, 3, 6561.0, 3, 7, 51539607552L, 'Particle: 3', 9.0) (1024, 4, 3.0, 4, 6, 68719476736L, 'Particle: 4', 16.0) (1280, 5, 390625.0, 5, 5, 85899345920L, 'Particle: 5', 25.0) (1536, 6, 1679616.0, 6, 4, 103079215104L, 'Particle: 6', 36.0) (1792, 7, 4.0, 7, 3, 120259084288L, 'Particle: 7', 49.0) (2048, 8, 16777216.0, 8, 2, 137438953472L, 'Particle: 8', 64.0)] Check that the values have been correctly modified! .. hint:: remember that column TDCcount is the second one, and that energy is the third. Look for more info on modifying columns in :meth:`Column.__setitem__`. PyTables also lets you modify complete sets of rows at the same time. As a demonstration of these capability, see the next example:: >>> table.modify_rows(start=1, step=3, ... rows=[(1, 2, 3.0, 4, 5, 6L, 'Particle: None', 8.0), ... (2, 4, 6.0, 8, 10, 12L, 'Particle: None*2', 16.0)]) 2 >>> print("After modifying the complete third row-->", table[0:5]) After modifying the complete third row--> [(0, 1, 2.0, 0, 10, 0L, 'Particle: 0', 0.0) (1, 2, 3.0, 4, 5, 6L, 'Particle: None', 8.0) (512, 2, 256.0, 2, 8, 34359738368L, 'Particle: 2', 4.0) (768, 3, 6561.0, 3, 7, 51539607552L, 'Particle: 3', 9.0) (2, 4, 6.0, 8, 10, 12L, 'Particle: None*2', 16.0)] As you can see, the modify_rows() call has modified the rows second and fifth, and it returned the number of modified rows. Apart of :meth:`Table.modify_rows`, there exists another method, called :meth:`Table.modify_column` to modify specific columns as well. Finally, there is another way of modifying tables that is generally more handy than the one described above. This new way uses the :meth:`Row.update` method of the Row instance that is attached to every table, so it is meant to be used in table iterators. Take a look at the following example:: >>> for row in table.where('TDCcount <= 2'): ... row['energy'] = row['TDCcount']*2 ... row.update() >>> print("After modifying energy column (where TDCcount <=2)-->", table[0:4]) After modifying energy column (where TDCcount <=2)--> [(0, 1, 2.0, 0, 10, 0L, 'Particle: 0', 0.0) (1, 2, 4.0, 4, 5, 6L, 'Particle: None', 8.0) (512, 2, 4.0, 2, 8, 34359738368L, 'Particle: 2', 4.0) (768, 3, 6561.0, 3, 7, 51539607552L, 'Particle: 3', 9.0)] .. note:: The authors find this way of updating tables (i.e. using Row.update()) to be both convenient and efficient. Please make sure to use it extensively. *Caveat emptor*: Currently, :meth:`Row.update` will not work (the table will not be updated) if the loop is broken with ``break`` statement. A possible workaround consists in manually flushing the row internal buffer by calling ``row._flushModRows()`` just before the ``break`` statement. Modifying data in arrays ~~~~~~~~~~~~~~~~~~~~~~~~ We are now going to see how to modify data in array objects. The basic way to do this is through the use of :meth:`Array.__setitem__` special method. Let's see how to modify data on the pressureObject array:: >>> pressureObject = h5file.root.columns.pressure >>> print("Before modif-->", pressureObject[:]) Before modif--> [ 25. 36. 49.] >>> pressureObject[0] = 2 >>> print("First modif-->", pressureObject[:]) First modif--> [ 2. 36. 49.] >>> pressureObject[1:3] = [2.1, 3.5] >>> print("Second modif-->", pressureObject[:]) Second modif--> [ 2. 2.1 3.5] >>> pressureObject[::2] = [1,2] >>> print("Third modif-->", pressureObject[:]) Third modif--> [ 1. 2.1 2. ] So, in general, you can use any combination of (multidimensional) extended slicing. With the sole exception that you cannot use negative values for step to refer to indexes that you want to modify. See :meth:`Array.__getitem__` for more examples on how to use extended slicing in PyTables objects. Similarly, with an array of strings:: >>> nameObject = h5file.root.columns.name >>> print("Before modif-->", nameObject[:]) Before modif--> ['Particle: 5', 'Particle: 6', 'Particle: 7'] >>> nameObject[0] = 'Particle: None' >>> print("First modif-->", nameObject[:]) First modif--> ['Particle: None', 'Particle: 6', 'Particle: 7'] >>> nameObject[1:3] = ['Particle: 0', 'Particle: 1'] >>> print("Second modif-->", nameObject[:]) Second modif--> ['Particle: None', 'Particle: 0', 'Particle: 1'] >>> nameObject[::2] = ['Particle: -3', 'Particle: -5'] >>> print("Third modif-->", nameObject[:]) Third modif--> ['Particle: -3', 'Particle: 0', 'Particle: -5'] And finally... how to delete rows from a table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We'll finish this tutorial by deleting some rows from the table we have. Suppose that we want to delete the 5th to 9th rows (inclusive):: >>> table.remove_rows(5,10) 5 :meth:`Table.remove_rows` deletes the rows in the range (start, stop). It returns the number of rows effectively removed. We have reached the end of this first tutorial. Don't forget to close the file when you finish:: >>> h5file.close() >>> ^D $ In :ref:`Figure 2 ` you can see a graphical view of the PyTables file with the datasets we have just created. In :ref:`tutorial1-general` are displayed the general properties of the table /detector/readout. .. _tutorial1-2-tableview: .. figure:: images/tutorial1-2-tableview.png :align: center **Figure 2. The final version of the data file for tutorial 1.** .. _tutorial1-general: .. figure:: images/tutorial1-general.png :align: center **Figure 3. General properties of the /detector/readout table.** .. _secondExample: Multidimensional table cells and automatic sanity checks -------------------------------------------------------- Now it's time for a more real-life example (i.e. with errors in the code). We will create two groups that branch directly from the root node, Particles and Events. Then, we will put three tables in each group. In Particles we will put tables based on the Particle descriptor and in Events, the tables based the Event descriptor. Afterwards, we will provision the tables with a number of records. Finally, we will read the newly-created table /Events/TEvent3 and select some values from it, using a comprehension list. Look at the next script (you can find it in :file:`examples/tutorial2.py`). It appears to do all of the above, but it contains some small bugs. Note that this Particle class is not directly related to the one defined in last tutorial; this class is simpler (note, however, the *multidimensional* columns called pressure and temperature). We also introduce a new manner to describe a Table as a structured NumPy dtype (or even as a dictionary), as you can see in the Event description. See :meth:`File.create_table` for different kinds of descriptor objects that can be passed to this method:: import tables as tb import numpy as np # Describe a particle record class Particle(tb.IsDescription): name = tb.StringCol(itemsize=16) # 16-character string lati = tb.Int32Col() # integer longi = tb.Int32Col() # integer pressure = tb.Float32Col(shape=(2,3)) # array of floats (single-precision) temperature = tb.Float64Col(shape=(2,3)) # array of doubles (double-precision) # Native NumPy dtype instances are also accepted Event = np.dtype([ ("name" , "S16"), ("TDCcount" , np.uint8), ("ADCcount" , np.uint16), ("xcoord" , np.float32), ("ycoord" , np.float32) ]) # And dictionaries too (this defines the same structure as above) # Event = { # "name" : tb.StringCol(itemsize=16), # "TDCcount" : tb.UInt8Col(), # "ADCcount" : tb.UInt16Col(), # "xcoord" : tb.Float32Col(), # "ycoord" : tb.Float32Col(), # } # Open a file in "w"rite mode fileh = tb.open_file("tutorial2.h5", mode="w") # Get the HDF5 root group root = fileh.root # Create the groups: for groupname in ("Particles", "Events"): group = fileh.create_group(root, groupname) # Now, create and fill the tables in Particles group gparticles = root.Particles # Create 3 new tables for tablename in ("TParticle1", "TParticle2", "TParticle3"): # Create a table table = fileh.create_table("/Particles", tablename, Particle, "Particles: "+tablename) # Get the record object associated with the table: particle = table.row # Fill the table with 257 particles for i in range(257): # First, assign the values to the Particle record particle['name'] = f'Particle: {i:6d}' particle['lati'] = i particle['longi'] = 10 - i ########### Detectable errors start here. Play with them! particle['pressure'] = i * np.arange(2 * 4).reshape(2, 4) # Incorrect #particle['pressure'] = i * np.arange(2 * 3).reshape(2, 3) # Correct ########### End of errors particle['temperature'] = i ** 2 # Broadcasting # This injects the Record values particle.append() # Flush the table buffers table.flush() # Now, go for Events: for tablename in ("TEvent1", "TEvent2", "TEvent3"): # Create a table in Events group table = fileh.create_table(root.Events, tablename, Event, "Events: "+tablename) # Get the record object associated with the table: event = table.row # Fill the table with 257 events for i in range(257): # First, assign the values to the Event record event['name'] = f'Event: {i:6d}' event['TDCcount'] = i % (1<<8) # Correct range ########### Detectable errors start here. Play with them! event['xcoor'] = float(i ** 2) # Wrong spelling #event['xcoord'] = float(i ** 2) # Correct spelling event['ADCcount'] = "sss" # Wrong type #event['ADCcount'] = i * 2 # Correct type ########### End of errors event['ycoord'] = float(i) ** 4 # This injects the Record values event.append() # Flush the buffers table.flush() # Read the records from table "/Events/TEvent3" and select some table = root.Events.TEvent3 e = [ p['TDCcount'] for p in table if p['ADCcount'] < 20 and 4 <= p['TDCcount'] < 15 ] print(f"Last record ==> {p}") print("Selected values ==> {e}") print("Total selected records ==> {len(e)}") # Finally, close the file (this also will flush all the remaining buffers!) fileh.close() Shape checking ~~~~~~~~~~~~~~ If you look at the code carefully, you'll see that it won't work. You will get the following error. .. code-block:: bash $ python3 tutorial2.py Traceback (most recent call last): File "tutorial2.py", line 60, in particle['pressure'] = array(i * arange(2 * 3)).reshape((2, 4)) # Incorrect ValueError: total size of new array must be unchanged Closing remaining open files: tutorial2.h5... done This error indicates that you are trying to assign an array with an incompatible shape to a table cell. Looking at the source, we see that we were trying to assign an array of shape (2,4) to a pressure element, which was defined with the shape (2,3). In general, these kinds of operations are forbidden, with one valid exception: when you assign a *scalar* value to a multidimensional column cell, all the cell elements are populated with the value of the scalar. For example:: particle['temperature'] = i ** 2 # Broadcasting The value i**2 is assigned to all the elements of the temperature table cell. This capability is provided by the NumPy package and is known as *broadcasting*. Field name checking ~~~~~~~~~~~~~~~~~~~ After fixing the previous error and rerunning the program, we encounter another error. .. code-block:: bash $ python3 tutorial2.py Traceback (most recent call last): File "tutorial2.py", line 73, in ? event['xcoor'] = float(i ** 2) # Wrong spelling File "tableextension.pyx", line 1094, in tableextension.Row.__setitem__ File "tableextension.pyx", line 127, in tableextension.get_nested_field_cache File "utilsextension.pyx", line 331, in utilsextension.get_nested_field KeyError: 'no such column: xcoor' This error indicates that we are attempting to assign a value to a non-existent field in the *event* table object. By looking carefully at the Event class attributes, we see that we misspelled the xcoord field (we wrote xcoor instead). This is unusual behavior for Python, as normally when you assign a value to a non-existent instance variable, Python creates a new variable with that name. Such a feature can be dangerous when dealing with an object that contains a fixed list of field names. PyTables checks that the field exists and raises a KeyError if the check fails. Data type checking ~~~~~~~~~~~~~~~~~~ Finally, the last issue which we will find here is a TypeError exception. .. code-block:: bash $ python3 tutorial2.py Traceback (most recent call last): File "tutorial2.py", line 75, in ? event['ADCcount'] = "sss" # Wrong type File "tableextension.pyx", line 1111, in tableextension.Row.__setitem__ TypeError: invalid type () for column ``ADCcount`` And, if we change the affected line to read:: event.ADCcount = i * 2 # Correct type we will see that the script ends well. You can see the structure created with this (corrected) script in :ref:`Figure 4 `. In particular, note the multidimensional column cells in table /Particles/TParticle2. .. _tutorial2-tableview: .. figure:: images/tutorial2-tableview.png :align: center **Figure 4. Table hierarchy for tutorial 2.** .. _LinksTutorial: Using links for more convenient access to nodes ----------------------------------------------- Links are special nodes that can be used to create additional paths to your existing nodes. PyTables supports three kinds of links: hard links, soft links (aka symbolic links) and external links. Hard links let the user create additional paths to access another node in the same file, and once created, they are indistinguishable from the referred node object, except that they have different paths in the object tree. For example, if the referred node is, say, a Table object, then the new hard link will become a Table object itself. From this point on, you will be able to access the same Table object from two different paths: the original one and the new hard link path. If you delete one path to the table, you will be able to reach it via the other path. Soft links are similar to hard links, but they keep their own personality. When you create a soft link to another node, you will get a new SoftLink object that *refers* to that node. However, in order to access the referred node, you need to *dereference* it. Finally, external links are like soft links, with the difference that these are meant to point to nodes in *external* files instead of nodes in the same file. They are represented by the ExternalLink class and, like soft links, you need to dereference them in order to get access to the pointed node. Interactive example ~~~~~~~~~~~~~~~~~~~ Now we are going to learn how to deal with links. You can find the code used in this section in :file:`examples/links.py`. First, let's create a file with some group structure:: >>> import tables as tb >>> f1 = tb.open_file('links1.h5', 'w') >>> g1 = f1.create_group('/', 'g1') >>> g2 = f1.create_group(g1, 'g2') Now, we will put some datasets on the /g1 and /g1/g2 groups:: >>> a1 = f1.create_carray(g1, 'a1', tb.Int64Atom(), shape=(10000,)) >>> t1 = f1.create_table(g2, 't1', {'f1': tb.IntCol(), 'f2': tb.FloatCol()}) We can start the party now. We are going to create a new group, say /gl, where we will put our links and will start creating one hard link too:: >>> gl = f1.create_group('/', 'gl') >>> ht = f1.create_hard_link(gl, 'ht', '/g1/g2/t1') # ht points to t1 >>> print(f"``{ht}`` is a hard link to: ``{t1}``") ``/gl/ht (Table(0,)) `` is a hard link to: ``/g1/g2/t1 (Table(0,)) `` You can see how we've created a hard link in /gl/ht which is pointing to the existing table in /g1/g2/t1. Have look at how the hard link is represented; it looks like a table, and actually, it is a *real* table. We have two different paths to access that table, the original /g1/g2/t1 and the new one /gl/ht. If we remove the original path we still can reach the table by using the new path:: >>> t1.remove() >>> print(f"table continues to be accessible in: ``{f1.get_node('/gl/ht')}``") table continues to be accessible in: ``/gl/ht (Table(0,)) `` So far so good. Now, let's create a couple of soft links:: >>> la1 = f1.create_soft_link(gl, 'la1', '/g1/a1') # la1 points to a1 >>> print(f"``{la1}`` is a soft link to: ``{la1.target}``") ``/gl/la1 (SoftLink) -> /g1/a1`` is a soft link to: ``/g1/a1`` >>> lt = f1.create_soft_link(gl, 'lt', '/g1/g2/t1') # lt points to t1 >>> print(f"``{lt}`` is a soft link to: ``{lt.target}``") ``/gl/lt (SoftLink) -> /g1/g2/t1 (dangling)`` is a soft link to: ``/g1/g2/t1`` Okay, we see how the first link /gl/la1 points to the array /g1/a1. Notice how the link prints as a SoftLink, and how the referred node is stored in the target instance attribute. The second link (/gt/lt) pointing to /g1/g2/t1 also has been created successfully, but by better inspecting the string representation of it, we see that is labeled as '(dangling)'. Why is this? Well, you should remember that we recently removed the /g1/g2/t1 path to access table t1. When printing it, the object knows that it points to *nowhere* and reports this. This is a nice way to quickly know whether a soft link points to an exiting node or not. So, let's re-create the removed path to t1 table:: >>> t1 = f1.create_hard_link('/g1/g2', 't1', '/gl/ht') >>> print(f"``{lt}`` is not dangling anymore") ``/gl/lt (SoftLink) -> /g1/g2/t1`` is not dangling anymore and the soft link is pointing to an existing node now. Of course, for soft links to serve any actual purpose we need a way to get the pointed node. It happens that soft links are callable, and that's the way to get the referred nodes back:: >>> plt = lt() >>> print(f"dereferred lt node: ``{plt}``") dereferred lt node: ``/g1/g2/t1 (Table(0,)) `` >>> pla1 = la1() >>> print(f"dereferred la1 node: ``{pla1}``") dereferred la1 node: ``/g1/a1 (CArray(10000,)) `` Now, plt is a Python reference to the t1 table while pla1 refers to the a1 array. Easy, uh? Let's suppose now that a1 is an array whose access speed is critical for our application. One possible solution is to move the entire file into a faster disk, say, a solid state disk so that access latencies can be reduced quite a lot. However, it happens that our file is too big to fit into our shiny new (although small in capacity) SSD disk. A solution is to copy just the a1 array into a separate file that would fit into our SSD disk. However, our application would be able to handle two files instead of only one, adding significantly more complexity, which is not a good thing. External links to the rescue! As we've already said, external links are like soft links, but they are designed to link objects in external files. Back to our problem, let's copy the a1 array into a different file:: >>> f2 = tb.open_file('links2.h5', 'w') >>> new_a1 = a1.copy(f2.root, 'a1') >>> f2.close() # close the other file And now, we can remove the existing soft link and create the external link in its place:: >>> la1.remove() >>> la1 = f1.create_external_link(gl, 'la1', 'links2.h5:/a1') >>> print(f"``{la1}`` is an external link to: ``{la1.target}``") ``/gl/la1 (ExternalLink) -> links2.h5:/a1`` is an external link to: ``links2.h5:/a1`` Let's try dereferring it:: >>> new_a1 = la1() # dereferrencing la1 returns a1 in links2.h5 >>> print(f"dereferred la1 node: ``{new_a1}``") dereferred la1 node: ``/a1 (CArray(10000,)) `` Well, it seems like we can access the external node. But just to make sure that the node is in the other file:: >>> print("new_a1 file:", new_a1._v_file.filename) new_a1 file: links2.h5 Okay, the node is definitely in the external file. So, you won't have to worry about your application: it will work exactly the same no matter the link is internal (soft) or external. Finally, here it is a dump of the objects in the final file, just to get a better idea of what we ended with:: >>> f1.close() >>> exit() $ ptdump links1.h5 / (RootGroup) '' /g1 (Group) '' /g1/a1 (CArray(10000,)) '' /gl (Group) '' /gl/ht (Table(0,)) '' /gl/la1 (ExternalLink) -> links2.h5:/a1 /gl/lt (SoftLink) -> /g1/g2/t1 /g1/g2 (Group) '' /g1/g2/t1 (Table(0,)) '' This ends this tutorial. I hope it helped you to appreciate how useful links can be. I'm sure you will find other ways in which you can use links that better fit your own needs. Exercising the Undo/Redo feature -------------------------------- PyTables has integrated support for undoing and/or redoing actions. This functionality lets you put marks in specific places of your hierarchy manipulation operations, so that you can make your HDF5 file pop back (*undo*) to a specific mark (for example for inspecting how your hierarchy looked at that point). You can also go forward to a more recent marker (*redo*). You can even do jumps to the marker you want using just one instruction as we will see shortly. You can undo/redo all the operations that are related to object tree management, like creating, deleting, moving or renaming nodes (or complete sub-hierarchies) inside a given object tree. You can also undo/redo operations (i.e. creation, deletion or modification) of persistent node attributes. However, actions which include *internal* modifications of datasets (that includes Table.append, Table.modify_rows or Table.remove_rows, among others) cannot be currently undone/redone. This capability can be useful in many situations, for example when doing simulations with multiple branches. When you have to choose a path to follow in such a situation, you can put a mark there and, if the simulation is not going well, you can go back to that mark and start another path. Another possible application is defining coarse-grained operations which operate in a transactional-like way, i.e. which return the database to its previous state if the operation finds some kind of problem while running. You can probably devise many other scenarios where the Undo/Redo feature can be useful to you [3]_. A basic example ~~~~~~~~~~~~~~~ In this section, we are going to show the basic behavior of the Undo/Redo feature. You can find the code used in this example in :file:`examples/tutorial3-1.py`. A somewhat more complex example will be explained in the next section. First, let's create a file:: >>> import tables >>> fileh = tables.open_file("tutorial3-1.h5", "w", title="Undo/Redo demo 1") And now, activate the Undo/Redo feature with the method :meth:`File.enable_undo` of File:: >>> fileh.enable_undo() From now on, all our actions will be logged internally by PyTables. Now, we are going to create a node (in this case an Array object):: >>> one = fileh.create_array('/', 'anarray', [3,4], "An array") Now, mark this point:: >>> fileh.mark() 1 We have marked the current point in the sequence of actions. In addition, the mark() method has returned the identifier assigned to this new mark, that is 1 (mark #0 is reserved for the implicit mark at the beginning of the action log). In the next section we will see that you can also assign a *name* to a mark (see :meth:`File.mark` for more info on mark()). Now, we are going to create another array:: >>> another = fileh.create_array('/', 'anotherarray', [4,5], "Another array") Right. Now, we can start doing funny things. Let's say that we want to pop back to the previous mark (that whose value was 1, do you remember?). Let's introduce the undo() method (see :meth:`File.undo`):: >>> fileh.undo() Fine, what do you think happened? Well, let's have a look at the object tree:: >>> print(fileh) tutorial3-1.h5 (File) 'Undo/Redo demo 1' Last modif.: 'Tue Mar 13 11:43:55 2007' Object Tree: / (RootGroup) 'Undo/Redo demo 1' /anarray (Array(2,)) 'An array' What happened with the /anotherarray node we've just created? You've guessed it, it has disappeared because it was created *after* the mark 1. If you are curious enough you may ask - where has it gone? Well, it has not been deleted completely; it has just been moved into a special, hidden group of PyTables that renders it invisible and waiting for a chance to be reborn. Now, unwind once more, and look at the object tree:: >>> fileh.undo() >>> print(fileh) tutorial3-1.h5 (File) 'Undo/Redo demo 1' Last modif.: 'Tue Mar 13 11:43:55 2007' Object Tree: / (RootGroup) 'Undo/Redo demo 1' Oops, /anarray has disappeared as well! Don't worry, it will visit us again very shortly. So, you might be somewhat lost right now; at which mark are we? Let's ask the :meth:`File.get_current_mark` method in the file handler:: >>> print(fileh.get_current_mark()) 0 So we are at mark #0, remember? Mark #0 is an implicit mark that is created when you start the log of actions when calling File.enable_undo(). Fine, but you are missing your too-young-to-die arrays. What can we do about that? :meth:`File.redo` to the rescue:: >>> fileh.redo() >>> print(fileh) tutorial3-1.h5 (File) 'Undo/Redo demo 1' Last modif.: 'Tue Mar 13 11:43:55 2007' Object Tree: / (RootGroup) 'Undo/Redo demo 1' /anarray (Array(2,)) 'An array' Great! The /anarray array has come into life again. Just check that it is alive and well:: >>> fileh.root.anarray.read() [3, 4] >>> fileh.root.anarray.title 'An array' Well, it looks pretty similar to its past life; what's more, it is exactly the same object:: >>> fileh.root.anarray is one True It was just moved to the hidden group and back again, that's all! That's kind of fun, so we are going to do the same with /anotherarray:: >>> fileh.redo() >>> print(fileh) tutorial3-1.h5 (File) 'Undo/Redo demo 1' Last modif.: 'Tue Mar 13 11:43:55 2007' Object Tree: / (RootGroup) 'Undo/Redo demo 1' /anarray (Array(2,)) 'An array' /anotherarray (Array(2,)) 'Another array' Welcome back, /anotherarray! Just a couple of sanity checks:: >>> assert fileh.root.anotherarray.read() == [4,5] >>> assert fileh.root.anotherarray.title == "Another array" >>> fileh.root.anotherarray is another True Nice, you managed to turn your data back to life. Congratulations! But wait, do not forget to close your action log when you don't need this feature anymore:: >>> fileh.disable_undo() That will allow you to continue working with your data without actually requiring PyTables to keep track of all your actions, and more importantly, allowing your objects to die completely if they have to, not requiring to keep them anywhere, hence saving process time and space in your database file. A more complete example ~~~~~~~~~~~~~~~~~~~~~~~ Now, time for a somewhat more sophisticated demonstration of the Undo/Redo feature. In it, several marks will be set in different parts of the code flow and we will see how to jump between these marks with just one method call. You can find the code used in this example in :file:`examples/tutorial3-2.py` Let's introduce the first part of the code:: import tables # Create an HDF5 file fileh = tables.open_file('tutorial3-2.h5', 'w', title='Undo/Redo demo 2') #'-**-**-**-**-**-**- enable undo/redo log -**-**-**-**-**-**-**-' fileh.enable_undo() # Start undoable operations fileh.create_array('/', 'otherarray1', [3,4], 'Another array 1') fileh.create_group('/', 'agroup', 'Group 1') # Create a 'first' mark fileh.mark('first') fileh.create_array('/agroup', 'otherarray2', [4,5], 'Another array 2') fileh.create_group('/agroup', 'agroup2', 'Group 2') # Create a 'second' mark fileh.mark('second') fileh.create_array('/agroup/agroup2', 'otherarray3', [5,6], 'Another array 3') # Create a 'third' mark fileh.mark('third') fileh.create_array('/', 'otherarray4', [6,7], 'Another array 4') fileh.create_array('/agroup', 'otherarray5', [7,8], 'Another array 5') You can see how we have set several marks interspersed in the code flow, representing different states of the database. Also, note that we have assigned *names* to these marks, namely 'first', 'second' and 'third'. Now, start doing some jumps back and forth in the states of the database:: # Now go to mark 'first' fileh.goto('first') assert '/otherarray1' in fileh assert '/agroup' in fileh assert '/agroup/agroup2' not in fileh assert '/agroup/otherarray2' not in fileh assert '/agroup/agroup2/otherarray3' not in fileh assert '/otherarray4' not in fileh assert '/agroup/otherarray5' not in fileh # Go to mark 'third' fileh.goto('third') assert '/otherarray1' in fileh assert '/agroup' in fileh assert '/agroup/agroup2' in fileh assert '/agroup/otherarray2' in fileh assert '/agroup/agroup2/otherarray3' in fileh assert '/otherarray4' not in fileh assert '/agroup/otherarray5' not in fileh # Now go to mark 'second' fileh.goto('second') assert '/otherarray1' in fileh assert '/agroup' in fileh assert '/agroup/agroup2' in fileh assert '/agroup/otherarray2' in fileh assert '/agroup/agroup2/otherarray3' not in fileh assert '/otherarray4' not in fileh assert '/agroup/otherarray5' not in fileh Well, the code above shows how easy is to jump to a certain mark in the database by using the :meth:`File.goto` method. There are also a couple of implicit marks for going to the beginning or the end of the saved states: 0 and -1. Going to mark #0 means go to the beginning of the saved actions, that is, when method fileh.enable_undo() was called. Going to mark #-1 means go to the last recorded action, that is the last action in the code flow. Let's see what happens when going to the end of the action log:: # Go to the end fileh.goto(-1) assert '/otherarray1' in fileh assert '/agroup' in fileh assert '/agroup/agroup2' in fileh assert '/agroup/otherarray2' in fileh assert '/agroup/agroup2/otherarray3' in fileh assert '/otherarray4' in fileh assert '/agroup/otherarray5' in fileh # Check that objects have come back to life in a sane state assert fileh.root.otherarray1.read() == [3,4] assert fileh.root.agroup.otherarray2.read() == [4,5] assert fileh.root.agroup.agroup2.otherarray3.read() == [5,6] assert fileh.root.otherarray4.read() == [6,7] assert fileh.root.agroup.otherarray5.read() == [7,8] Try going to the beginning of the action log yourself (remember, mark #0) and check contents of the object tree. We have nearly finished this demonstration. As always, do not forget to close the action log as well as the database:: #'-**-**-**-**-**-**- disable undo/redo log -**-**-**-**-**-**-**-' fileh.disable_undo() # Close the file fileh.close() You might want to check other examples on Undo/Redo feature that appear in :file:`examples/undo-redo.py`. Using enumerated types ---------------------- PyTables includes support for handling enumerated types. Those types are defined by providing an exhaustive *set* or *list* of possible, named values for a variable of that type. Enumerated variables of the same type are usually compared between them for equality and sometimes for order, but are not usually operated upon. Enumerated values have an associated *name* and *concrete value*. Every name is unique and so are concrete values. An enumerated variable always takes the concrete value, not its name. Usually, the concrete value is not used directly, and frequently it is entirely irrelevant. For the same reason, an enumerated variable is not usually compared with concrete values out of its enumerated type. For that kind of use, standard variables and constants are more adequate. PyTables provides the Enum (see :ref:`EnumClassDescr`) class to provide support for enumerated types. Each instance of Enum is an enumerated type (or *enumeration*). For example, let us create an enumeration of colors All these examples can be found in :file:`examples/play-with-enums.py`:: >>> import tables >>> colorList = ['red', 'green', 'blue', 'white', 'black'] >>> colors = tables.Enum(colorList) Here we used a simple list giving the names of enumerated values, but we left the choice of concrete values up to the Enum class. Let us see the enumerated pairs to check those values:: >>> print("Colors:", [v for v in colors]) Colors: [('blue', 2), ('black', 4), ('white', 3), ('green', 1), ('red', 0)] Names have been given automatic integer concrete values. We can iterate over values in an enumeration, but we will usually be more interested in accessing single values. We can get the concrete value associated with a name by accessing it as an attribute or as an item (the later can be useful for names not resembling Python identifiers):: >>> print("Value of 'red' and 'white':", (colors.red, colors.white)) Value of 'red' and 'white': (0, 3) >>> print("Value of 'yellow':", colors.yellow) Value of 'yellow': Traceback (most recent call last): File "", line 1, in ? File ".../tables/misc/enum.py", line 230, in __getattr__ raise AttributeError(\*ke.args) AttributeError: no enumerated value with that name: 'yellow' >>> >>> print("Value of 'red' and 'white':", (colors['red'], colors['white'])) Value of 'red' and 'white': (0, 3) >>> print("Value of 'yellow':", colors['yellow']) Value of 'yellow': Traceback (most recent call last): File "", line 1, in ? File ".../tables/misc/enum.py", line 189, in __getitem__ raise KeyError("no enumerated value with that name: %r" % (name,)) KeyError: "no enumerated value with that name: 'yellow'" See how accessing a value that is not in the enumeration raises the appropriate exception. We can also do the opposite and get the name that matches a concrete value by using the __call__() method of Enum:: >>> print(f"Name of value {colors.red}:", colors(colors.red)) Name of value 0: red >>> print("Name of value 1234:", colors(1234)) Name of value 1234: Traceback (most recent call last): File "", line 1, in ? File ".../tables/misc/enum.py", line 320, in __call__ raise ValueError( ValueError: no enumerated value with that concrete value: 1234 You can see what we made as using the enumerated type to *convert* a concrete value into a name in the enumeration. Of course, values out of the enumeration can not be converted. Enumerated columns ~~~~~~~~~~~~~~~~~~ Columns of an enumerated type can be declared by using the EnumCol (see :ref:`ColClassDescr`) class. To see how this works, let us open a new PyTables file and create a table to collect the simulated results of a probabilistic experiment. In it, we have a bag full of colored balls; we take a ball out and annotate the time of extraction and the color of the ball:: >>> h5f = tables.open_file('enum.h5', 'w') >>> class BallExt(tables.IsDescription): ... ballTime = tables.Time32Col() ... ballColor = tables.EnumCol(colors, 'black', base='uint8') >>> tbl = h5f.create_table('/', 'extractions', BallExt, title="Random ball extractions") >>> We declared the ballColor column to be of the enumerated type colors, with a default value of black. We also stated that we are going to store concrete values as unsigned 8-bit integer values [4]_. Let us use some random values to fill the table:: >>> import time >>> import random >>> now = time.time() >>> row = tbl.row >>> for i in range(10): ... row['ballTime'] = now + i ... row['ballColor'] = colors[random.choice(colorList)] # take note of this ... row.append() >>> Notice how we used the __getitem__() call of colors to get the concrete value to store in ballColor. This way of appending values to a table automatically checks for the validity on enumerated values. For instance:: >>> row['ballTime'] = now + 42 >>> row['ballColor'] = 1234 Traceback (most recent call last): File "", line 1, in File "tableextension.pyx", line 1086, in tableextension.Row.__setitem__ File ".../tables/misc/enum.py", line 320, in __call__ "no enumerated value with that concrete value: %r" % (value,)) ValueError: no enumerated value with that concrete value: 1234 Note that this check is performed *only* by row.append() and not in other methods such as tbl.append() or tbl.modify_rows(). Now, after flushing the table we can see the result of insertions:: >>> tbl.flush() >>> for r in tbl: ... ballTime = r['ballTime'] ... ballColor = colors(r['ballColor']) # notice this ... print("Ball extracted on %d is of color %s." % (ballTime, ballColor)) Ball extracted on 1173785568 is of color green. Ball extracted on 1173785569 is of color black. Ball extracted on 1173785570 is of color white. Ball extracted on 1173785571 is of color black. Ball extracted on 1173785572 is of color black. Ball extracted on 1173785573 is of color red. Ball extracted on 1173785574 is of color green. Ball extracted on 1173785575 is of color red. Ball extracted on 1173785576 is of color white. Ball extracted on 1173785577 is of color white. As a final note, you may be wondering how to access an enumeration associated with ballColor once the file is closed and reopened. You can call tbl.get_enum('ballColor') (see :meth:`Table.get_enum`) to get the enumeration back. Enumerated arrays ~~~~~~~~~~~~~~~~~ EArray and VLArray leaves can also be declared to store enumerated values by means of the EnumAtom (see :ref:`AtomClassDescr`) class, which works very much like EnumCol for tables. Also, Array leaves can be used to open native HDF enumerated arrays. Let us create a sample EArray containing ranges of working days as bidimensional values:: >>> workingDays = {'Mon': 1, 'Tue': 2, 'Wed': 3, 'Thu': 4, 'Fri': 5} >>> dayRange = tables.EnumAtom(workingDays, 'Mon', base='uint16') >>> earr = h5f.create_earray('/', 'days', dayRange, (0, 2), title="Working day ranges") >>> earr.flavor = 'python' Nothing unusual, except for two details. Firstly, we use a *dictionary* instead of a list to explicitly set concrete values in the enumeration. Secondly, there is no explicit Enum instance created! Instead, the dictionary is passed as the first argument to the constructor of EnumAtom. If the constructor receives a list or a dictionary instead of an enumeration, it automatically builds the enumeration from it. Now let us feed some data to the array:: >>> wdays = earr.get_enum() >>> earr.append([(wdays.Mon, wdays.Fri), (wdays.Wed, wdays.Fri)]) >>> earr.append([(wdays.Mon, 1234)]) Please note that, since we had no explicit Enum instance, we were forced to use get_enum() (see :ref:`EArrayMethodsDescr`) to get it from the array (we could also have used dayRange.enum). Also note that we were able to append an invalid value (1234). Array methods do not check the validity of enumerated values. Finally, we will print contents of the array:: >>> for (d1, d2) in earr: ... print(f"From {wdays(d1) to {wdays(d2) ({d2 - d1 + 1} days).") From Mon to Fri (5 days). From Wed to Fri (3 days). Traceback (most recent call last): File "", line 2, in File ".../tables/misc/enum.py", line 320, in __call__ "no enumerated value with that concrete value: %r" % (value,)) ValueError: no enumerated value with that concrete value: 1234 That was an example of operating on concrete values. It also showed how the value-to-name conversion failed because of the value not belonging to the enumeration. Now we will close the file, and this little tutorial on enumerated types is done:: >>> h5f.close() Nested structures in tables ---------------------------------------- PyTables supports handling of nested structures (or, in other words, nested datatypes) in table objects, allowing you to define nested columns of arbitrary depth. Why is that useful? Suppose your data has a certain structure on the column level, which you would like to represent in the data model. You can do so by creating nested subclasses of IsDescription. The benefit is the ability to group and retrieve data more easily. Example below may be a bit silly, but it will serve as an illustration of the concept:: import tables as tb class Info(tb.IsDescription): """A sub-structure of NestedDescr""" _v_pos = 2 # The position in the whole structure name = tb.StringCol(10) value = tb.Float64Col(pos=0) colors = tb.Enum(['red', 'green', 'blue']) class NestedDescr(tb.IsDescription): """A description that has several nested columns""" color = tb.EnumCol(colors, 'red', base='uint32') info1 = tb.Info() class info2(tb.IsDescription): _v_pos = 1 name = tb.StringCol(10) value = tb.Float64Col(pos=0) class info3(tb.IsDescription): x = tb.Float64Col(dflt=1) y = tb.UInt8Col(dflt=1) NestedDescr is the root class with two *substructures* in it: info1 and info2. Note info1 is an instance of class Info which is defined prior to NestedDescr. info2 is declared within NestedDescr. Also, there is a third substructure, info3, that is in turn declared within substructure info2. You can define positions of substructures in the containing object by declaring special class attribute _v_pos. Creating nested tables ~~~~~~~~~~~~~~~~~~~~~~ Now that we have defined our nested structure, let's create a *nested* table, that is a table with columns which contain subcolumns:: >>> fileh = tb.open_file("nested-tut.h5", "w") >>> table = fileh.create_table(fileh.root, 'table', NestedDescr) Done! Now, to populate the table with values, assign a value to each field. Referencing nested fields can be accomplished by providing a full path. Follow the structure defined earlier - use '/' to access each sublevel of the structure, similar to how you would access a subdirectory on a Unix filesystem:: >>> row = table.row >>> for i in range(10): ... row['color'] = colors[['red', 'green', 'blue'][i % 3]] ... row['info1/name'] = f"name1-{i}" ... row['info2/name'] = f"name2-{i}" ... row['info2/info3/y'] = i ... # Remaining fields will be filled with defaults ... row.append() >>> table.flush() >>> table.nrows 10 As demonstrated above, substructure's field can be accessed by specifying its full path as defined in the table hierarchy. Reading nested tables ~~~~~~~~~~~~~~~~~~~~~ Now, what happens if we want to read the table? What kind of data container would we get? Let's find out:: >>> nra = table[::4] >>> nra array([(((1.0, 0), 'name2-0', 0.0), ('name1-0', 0.0), 0L), (((1.0, 4), 'name2-4', 0.0), ('name1-4', 0.0), 1L), (((1.0, 8), 'name2-8', 0.0), ('name1-8', 0.0), 2L)], dtype=[('info2', [('info3', [('x', '>f8'), ('y', '\|u1')]), ('name', '\|S10'), ('value', '>f8')]), ('info1', [('name', '\|S10'), ('value', '>f8')]), ('color', '>u4')]) What we've got is a NumPy array with a *compound, nested datatype*, i.e. its dtype is a list of name-datatype tuples. For every fourth row in the table (note [::4]), we get one resulting row, giving a total of three. You can make use of the above object in many different ways. For example, you can use it to append new data to an existing table object:: >>> table.append(nra) >>> table.nrows 13 Or to create new tables:: >>> table2 = fileh.create_table(fileh.root, 'table2', nra) >>> table2[:] array([(((1.0, 0), 'name2-0', 0.0), ('name1-0', 0.0), 0L), (((1.0, 4), 'name2-4', 0.0), ('name1-4', 0.0), 1L), (((1.0, 8), 'name2-8', 0.0), ('name1-8', 0.0), 2L)], dtype=[('info2', [('info3', [('x', '>> names = [ x['info2/name'] for x in table if x['color'] == colors.red ] >>> names ['name2-0', 'name2-3', 'name2-6', 'name2-9', 'name2-0'] Note that row accessor does not provide natural naming feature, so you have to specify an absolute path to your desired column in order to access it. Using Cols accessor ~~~~~~~~~~~~~~~~~~~ We can use cols attribute object (see :ref:`ColsClassDescr`) of the table to conveniently access data stored in a substructure:: >>> table.cols.info2[1:5] array([((1.0, 1), 'name2-1', 0.0), ((1.0, 2), 'name2-2', 0.0), ((1.0, 3), 'name2-3', 0.0), ((1.0, 4), 'name2-4', 0.0)], dtype=[('info3', [('x', '>> table.cols.info2.info3[1:5] array([(1.0, 1), (1.0, 2), (1.0, 3), (1.0, 4)], dtype=[('x', '>> table.cols._f_col('info2') /table.cols.info2 (Cols), 3 columns info3 (Cols(), Description) name (Column(), \|S10) value (Column(), float64) Here, you've got another Cols object handler because *info2* was a nested column. If you select a non-nested column, you will get a regular Column instance:: >>> table.cols._f_col('info2/info3/y') /table.cols.info2.info3.y (Column(), uint8, idx=None) To summarize, cols accessor is a very handy and powerful tool to access data in nested tables. Don't hesitate to use it, especially when doing interactive work. Accessing meta-information of nested tables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tables have a *description* attribute, which returns an instance of the Description class (see :ref:`DescriptionClassDescr`) with table meta-data. It can be helpful in understanding the table structure, including nested columns:: >>> table.description { "info2": { "info3": { "x": Float64Col(shape=(), dflt=1.0, pos=0), "y": UInt8Col(shape=(), dflt=1, pos=1)}, "name": StringCol(itemsize=10, shape=(), dflt='', pos=1), "value": Float64Col(shape=(), dflt=0.0, pos=2)}, "info1": { "name": StringCol(itemsize=10, shape=(), dflt='', pos=0), "value": Float64Col(shape=(), dflt=0.0, pos=1)}, "color": EnumCol(enum=Enum({'blue': 2, 'green': 1, 'red': 0}), dflt='red', base=UInt32Atom(shape=(), dflt=0), shape=(), pos=2)} As you can see, it provides very useful information on both the format and the structure of columns in your table. You can use natural naming approach with the description attribute to gain access to subcolumn's meta-data:: >>> table.description.info1 {"name": StringCol(itemsize=10, shape=(), dflt='', pos=0), "value": Float64Col(shape=(), dflt=0.0, pos=1)} >>> table.description.info2.info3 {"x": Float64Col(shape=(), dflt=1.0, pos=0), "y": UInt8Col(shape=(), dflt=1, pos=1)} _v_nested_names attribute provides names of columns as well as structures embedded in them:: >>> table.description._v_nested_names [('info2', [('info3', ['x', 'y']), 'name', 'value']), ('info1', ['name', 'value']), 'color'] >>> table.description.info1._v_nested_names ['name', 'value'] Regardless of which level of the structure is accessed, the output of _v_nested_names contains the same kind of information. This is because even for nested structures, a Description object is returned. It is possible to create arrays that immitate nested table-like structure with _v_nested_descr attribute:: >>> import numpy >>> table.description._v_nested_descr [('info2', [('info3', [('x', '()f8'), ('y', '()u1')]), ('name', '()S10'), ('value', '()f8')]), ('info1', [('name', '()S10'), ('value', '()f8')]), ('color', '()u4')] >>> numpy.rec.array(None, shape=0, dtype=table.description._v_nested_descr) recarray([], dtype=[('info2', [('info3', [('x', '>f8'), ('y', '|u1')]), ('name', '|S10'), ('value', '>f8')]), ('info1', [('name', '|S10'), ('value', '>f8')]), ('color', '>u4')]) >>> numpy.rec.array(None, shape=0, dtype=table.description.info2._v_nested_descr) recarray([], dtype=[('info3', [('x', '>f8'), ('y', '|u1')]), ('name', '|S10'), ('value', '>f8')]) Last but not least, there is a special iterator of the Description class: _f_walk, which returns different columns of the table:: >>> for coldescr in table.description._f_walk(): ... print(f"column--> {coldescr}") column--> Description([('info2', [('info3', [('x', '()f8'), ('y', '()u1')]), ('name', '()S10'), ('value', '()f8')]), ('info1', [('name', '()S10'), ('value', '()f8')]), ('color', '()u4')]) column--> EnumCol(enum=Enum({'blue': 2, 'green': 1, 'red': 0}), dflt='red', base=UInt32Atom(shape=(), dflt=0), shape=(), pos=2) column--> Description([('info3', [('x', '()f8'), ('y', '()u1')]), ('name', '()S10'), ('value', '()f8')]) column--> StringCol(itemsize=10, shape=(), dflt='', pos=1) column--> Float64Col(shape=(), dflt=0.0, pos=2) column--> Description([('name', '()S10'), ('value', '()f8')]) column--> StringCol(itemsize=10, shape=(), dflt='', pos=0) column--> Float64Col(shape=(), dflt=0.0, pos=1) column--> Description([('x', '()f8'), ('y', '()u1')]) column--> Float64Col(shape=(), dflt=1.0, pos=0) column--> UInt8Col(shape=(), dflt=1, pos=1) See the :ref:`DescriptionClassDescr` for a complete listing of attributes and methods of the Description object. Well, this is the end of this tutorial. As always, remember to close your files:: >>> fileh.close() Finally, you may want to have a look at your resulting data file. .. code-block:: bash $ ptdump -d nested-tut.h5 / (RootGroup) '' /table (Table(13,)) '' Data dump: [0] (((1.0, 0), 'name2-0', 0.0), ('name1-0', 0.0), 0L) [1] (((1.0, 1), 'name2-1', 0.0), ('name1-1', 0.0), 1L) [2] (((1.0, 2), 'name2-2', 0.0), ('name1-2', 0.0), 2L) [3] (((1.0, 3), 'name2-3', 0.0), ('name1-3', 0.0), 0L) [4] (((1.0, 4), 'name2-4', 0.0), ('name1-4', 0.0), 1L) [5] (((1.0, 5), 'name2-5', 0.0), ('name1-5', 0.0), 2L) [6] (((1.0, 6), 'name2-6', 0.0), ('name1-6', 0.0), 0L) [7] (((1.0, 7), 'name2-7', 0.0), ('name1-7', 0.0), 1L) [8] (((1.0, 8), 'name2-8', 0.0), ('name1-8', 0.0), 2L) [9] (((1.0, 9), 'name2-9', 0.0), ('name1-9', 0.0), 0L) [10] (((1.0, 0), 'name2-0', 0.0), ('name1-0', 0.0), 0L) [11] (((1.0, 4), 'name2-4', 0.0), ('name1-4', 0.0), 1L) [12] (((1.0, 8), 'name2-8', 0.0), ('name1-8', 0.0), 2L) /table2 (Table(3,)) '' Data dump: [0] (((1.0, 0), 'name2-0', 0.0), ('name1-0', 0.0), 0L) [1] (((1.0, 4), 'name2-4', 0.0), ('name1-4', 0.0), 1L) [2] (((1.0, 8), 'name2-8', 0.0), ('name1-8', 0.0), 2L) Most of the code in this section is also available in :file:`examples/nested-tut.py`. PyTables provides a comprehensive set of tools to work with nested structures and to address your classification needs. Try to avoid nesting your data too deeply as it may lead to very long and convoluted series of lists, tuples, and description objects, which can be hard to read and understand. Other examples in PyTables distribution --------------------------------------- Feel free to examine the rest of examples in directory :file:`examples/`, and try to understand them. We have written several practical sample scripts to give you an idea of the PyTables capabilities, its way of dealing with HDF5 objects, and how it can be used in the real world. ------------ .. [1] Appending data to arrays is also supported, but you need to create special objects called EArray (see :ref:`EArrayClassDescr` for more info). .. [2] Note that you can append not only scalar values to tables, but also fully multidimensional array objects. .. [3] You can even *hide* nodes temporarily. Can you think of a way to do it? .. [4] In fact, only integer values are supported right now, but this may change in the future. PyTables-3.7.0/doc/source/usersguide/usersguide.rst000066400000000000000000000055011416254111300224000ustar00rootroot00000000000000:orphan: ===================== PyTables User's Guide ===================== .. raw:: latex \listoffigures \listoftables \clearpage :Authors: Francesc Alted, Ivan Vilata, Scott Prater, Vicent Mas, Tom Hedley, Antonio Valentino, Jeffrey Whitaker, Anthony Scopatz, Josh Moore :Copyright: |copy| 2002, 2003, 2004 - Francesc Alted |copy| 2005, 2006, 2007 - Cárabos Coop. V. |copy| 2008, 2009, 2010 - Francesc Alted |copy| 2011–2021 - PyTables maintainers :Date: |today| :Version: |version| :Home Page: http://www.pytables.org .. raw:: latex \clearpage .. rubric:: Copyright Notice and Statement for PyTables User's Guide Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: a. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. b. 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. c. Neither the name of Francesc Alted nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 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. .. |copy| unicode:: U+000A9 .. COPYRIGHT SIGN ------------------------- The PyTables Core Library ------------------------- .. toctree:: :maxdepth: 1 introduction installation tutorials libref optimization --------------------- Complementary modules --------------------- .. toctree:: :maxdepth: 1 filenode ---------- Appendixes ---------- .. todo:: check why "latex_appendices" config option doesn't work with parts .. todo:: try to use raw latex \appendix .. toctree:: :maxdepth: 1 datatypes condition_syntax parameter_files utilities file_format .. raw:: latex \bookmarksetup{startatroot} \addtocontents{toc}{\bigskip} .. toctree:: :maxdepth: 1 bibliography PyTables-3.7.0/doc/source/usersguide/utilities.rst000066400000000000000000000466531416254111300222510ustar00rootroot00000000000000Utilities ========= PyTables comes with a couple of utilities that make the life easier to the user. One is called ptdump and lets you see the contents of a PyTables file (or generic HDF5 file, if supported). The other one is named ptrepack that allows to (recursively) copy sub-hierarchies of objects present in a file into another one, changing, if desired, some of the filters applied to the leaves during the copy process. Normally, these utilities will be installed somewhere in your PATH during the process of installation of the PyTables package, so that you can invoke them from any place in your file system after the installation has successfully finished. ptdump ------ As has been said before, ptdump utility allows you look into the contents of your PyTables files. It lets you see not only the data but also the metadata (that is, the *structure* and additional information in the form of *attributes*). Usage ~~~~~ For instructions on how to use it, just pass the -h flag to the command: .. code-block:: bash $ ptdump -h to see the message usage: .. code-block:: bash usage: ptdump [-h] [-v] [-d] [-a] [-s] [-c] [-i] [-R RANGE] filename[:nodepath] The ptdump utility allows you look into the contents of your PyTables files. It lets you see not only the data but also the metadata (that is, the *structure* and additional information in the form of *attributes*). positional arguments: filename[:nodepath] name of the HDF5 file to dump optional arguments: -h, --help show this help message and exit -v, --verbose dump more metainformation on nodes -d, --dump dump data information on leaves -a, --showattrs show attributes in nodes (only useful when -v or -d are active) -s, --sort sort output by node name -c, --colinfo show info of columns in tables (only useful when -v or -d are active) -i, --idxinfo show info of indexed columns (only useful when -v or -d are active) -R RANGE, --range RANGE select a RANGE of rows (in the form "start,stop,step") during the copy of *all* the leaves. Default values are "None,None,1", which means a copy of all the rows. Read on for a brief introduction to this utility. A small tutorial on ptdump ~~~~~~~~~~~~~~~~~~~~~~~~~~ Let's suppose that we want to know only the *structure* of a file. In order to do that, just don't pass any flag, just the file as parameter. .. code-block:: bash $ ptdump vlarray1.h5 / (RootGroup) '' /vlarray1 (VLArray(3,), shuffle, zlib(1)) 'ragged array of ints' /vlarray2 (VLArray(3,), shuffle, zlib(1)) 'ragged array of strings' we can see that the file contains just a leaf object called vlarray1, that is an instance of VLArray, has 4 rows, and two filters has been used in order to create it: shuffle and zlib (with a compression level of 1). Let's say we want more meta-information. Just add the -v (verbose) flag: .. code-block:: bash $ ptdump -v vlarray1.h5 / (RootGroup) '' /vlarray1 (VLArray(3,), shuffle, zlib(1)) 'ragged array of ints' atom = Int32Atom(shape=(), dflt=0) byteorder = 'little' nrows = 3 flavor = 'numpy' /vlarray2 (VLArray(3,), shuffle, zlib(1)) 'ragged array of strings' atom = StringAtom(itemsize=2, shape=(), dflt='') byteorder = 'irrelevant' nrows = 3 flavor = 'python' so we can see more info about the atoms that are the components of the vlarray1 dataset, i.e. they are scalars of type Int32 and with NumPy *flavor*. If we want information about the attributes on the nodes, we must add the -a flag: .. code-block:: bash $ ptdump -va vlarray1.h5 / (RootGroup) '' /._v_attrs (AttributeSet), 4 attributes: [CLASS := 'GROUP', PYTABLES_FORMAT_VERSION := '2.0', TITLE := '', VERSION := '1.0'] /vlarray1 (VLArray(3,), shuffle, zlib(1)) 'ragged array of ints' atom = Int32Atom(shape=(), dflt=0) byteorder = 'little' nrows = 3 flavor = 'numpy' /vlarray1._v_attrs (AttributeSet), 3 attributes: [CLASS := 'VLARRAY', TITLE := 'ragged array of ints', VERSION := '1.3'] /vlarray2 (VLArray(3,), shuffle, zlib(1)) 'ragged array of strings' atom = StringAtom(itemsize=2, shape=(), dflt='') byteorder = 'irrelevant' nrows = 3 flavor = 'python' /vlarray2._v_attrs (AttributeSet), 4 attributes: [CLASS := 'VLARRAY', FLAVOR := 'python', TITLE := 'ragged array of strings', VERSION := '1.3'] Let's have a look at the real data: .. code-block:: bash $ ptdump -d vlarray1.h5 / (RootGroup) '' /vlarray1 (VLArray(3,), shuffle, zlib(1)) 'ragged array of ints' Data dump: [0] [5 6] [1] [5 6 7] [2] [5 6 9 8] /vlarray2 (VLArray(3,), shuffle, zlib(1)) 'ragged array of strings' Data dump: [0] ['5', '66'] [1] ['5', '6', '77'] [2] ['5', '6', '9', '88'] We see here a data dump of the 4 rows in vlarray1 object, in the form of a list. Because the object is a VLA, we see a different number of integers on each row. Say that we are interested only on a specific *row range* of the /vlarray1 object: .. code-block:: bash ptdump -R2,3 -d vlarray1.h5:/vlarray1 /vlarray1 (VLArray(3,), shuffle, zlib(1)) 'ragged array of ints' Data dump: [2] [5 6 9 8] Here, we have specified the range of rows between 2 and 4 (the upper limit excluded, as usual in Python). See how we have selected only the /vlarray1 object for doing the dump (vlarray1.h5:/vlarray1). Finally, you can mix several information at once: .. code-block:: bash $ ptdump -R2,3 -vad vlarray1.h5:/vlarray1 /vlarray1 (VLArray(3,), shuffle, zlib(1)) 'ragged array of ints' atom = Int32Atom(shape=(), dflt=0) byteorder = 'little' nrows = 3 flavor = 'numpy' /vlarray1._v_attrs (AttributeSet), 3 attributes: [CLASS := 'VLARRAY', TITLE := 'ragged array of ints', VERSION := '1.3'] Data dump: [2] [5 6 9 8] .. _ptrepackDescr: ptrepack -------- This utility is a very powerful one and lets you copy any leaf, group or complete subtree into another file. During the copy process you are allowed to change the filter properties if you want so. Also, in the case of duplicated pathnames, you can decide if you want to overwrite already existing nodes on the destination file. Generally speaking, ptrepack can be useful in may situations, like replicating a subtree in another file, change the filters in objects and see how affect this to the compression degree or I/O performance, consolidating specific data in repositories or even *importing* generic HDF5 files and create true PyTables counterparts. Usage ~~~~~ For instructions on how to use it, just pass the -h flag to the command: .. code-block:: bash $ ptrepack -h to see the message usage: .. code-block:: bash usage: ptrepack [-h] [-v] [-o] [-R RANGE] [--non-recursive] [--dest-title TITLE] [--dont-create-sysattrs] [--dont-copy-userattrs] [--overwrite-nodes] [--complevel COMPLEVEL] [--complib {zlib,lzo,bzip2,blosc,blosc:blosclz,blosc:lz4,blosc:lz4hc,blosc:snappy,blosc:zlib,blosc:zstd}] [--shuffle {0,1}] [--bitshuffle {0,1}] [--fletcher32 {0,1}] [--keep-source-filters] [--chunkshape CHUNKSHAPE] [--upgrade-flavors] [--dont-regenerate-old-indexes] [--sortby COLUMN] [--checkCSI] [--propindexes] sourcefile:sourcegroup destfile:destgroup This utility is very powerful and lets you copy any leaf, group or complete subtree into another file. During the copy process you are allowed to change the filter properties if you want so. Also, in the case of duplicated pathnames, you can decide if you want to overwrite already existing nodes on the destination file. Generally speaking, ptrepack can be useful in may situations, like replicating a subtree in another file, change the filters in objects and see how affect this to the compression degree or I/O performance, consolidating specific data in repositories or even *importing* generic HDF5 files and create true PyTables counterparts. positional arguments: sourcefile:sourcegroup source file/group destfile:destgroup destination file/group optional arguments: -h, --help show this help message and exit -v, --verbose show verbose information -o, --overwrite overwrite destination file -R RANGE, --range RANGE select a RANGE of rows (in the form "start,stop,step") during the copy of *all* the leaves. Default values are "None,None,1", which means a copy of all the rows. --non-recursive do not do a recursive copy. Default is to do it --dest-title TITLE title for the new file (if not specified, the source is copied) --dont-create-sysattrs do not create sys attrs (default is to do it) --dont-copy-userattrs do not copy the user attrs (default is to do it) --overwrite-nodes overwrite destination nodes if they exist. Default is to not overwrite them --complevel COMPLEVEL set a compression level (0 for no compression, which is the default) --complib {zlib,lzo,bzip2,blosc,blosc:blosclz,blosc:lz4,blosc:lz4hc,blosc:snappy,blosc:zlib,blosc:zstd} set the compression library to be used during the copy. Defaults to zlib --shuffle {0,1} activate or not the shuffle filter (default is active if complevel > 0) --bitshuffle {0,1} activate or not the bitshuffle filter (not active by default) --fletcher32 {0,1} whether to activate or not the fletcher32 filter (not active by default) --keep-source-filters use the original filters in source files. The default is not doing that if any of --complevel, --complib, --shuffle --bitshuffle or --fletcher32 option is specified --chunkshape CHUNKSHAPE set a chunkshape. Possible options are: "keep" | "auto" | int | tuple. A value of "auto" computes a sensible value for the chunkshape of the leaves copied. The default is to "keep" the original value --upgrade-flavors when repacking PyTables 1.x or PyTables 2.x files, the flavor of leaves will be unset. With this, such a leaves will be serialized as objects with the internal flavor ('numpy' for 3.x series) --dont-regenerate-old-indexes disable regenerating old indexes. The default is to regenerate old indexes as they are found --sortby COLUMN do a table copy sorted by the index in "column". For reversing the order, use a negative value in the "step" part of "RANGE" (see "-r" flag). Only applies to table objects --checkCSI force the check for a CSI index for the --sortby column --propindexes propagate the indexes existing in original tables. The default is to not propagate them. Only applies to table objects --dont-allow-padding remove the possible padding in compound types in source files. The default is to propagate it. Only applies to table objects Read on for a brief introduction to this utility. A small tutorial on ptrepack ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Imagine that we have ended the tutorial 1 (see the output of examples/tutorial1-1.py), and we want to copy our reduced data (i.e. those datasets that hangs from the /column group) to another file. First, let's remember the content of the examples/tutorial1.h5: .. code-block:: bash $ ptdump tutorial1.h5 / (RootGroup) 'Test file' /columns (Group) 'Pressure and Name' /columns/name (Array(3,)) 'Name column selection' /columns/pressure (Array(3,)) 'Pressure column selection' /detector (Group) 'Detector information' /detector/readout (Table(10,)) 'Readout example' Now, copy the /columns to other non-existing file. That's easy: .. code-block:: bash $ ptrepack tutorial1.h5:/columns reduced.h5 That's all. Let's see the contents of the newly created reduced.h5 file: .. code-block:: bash $ ptdump reduced.h5 / (RootGroup) '' /name (Array(3,)) 'Name column selection' /pressure (Array(3,)) 'Pressure column selection' so, you have copied the children of /columns group into the *root* of the reduced.h5 file. Now, you suddenly realized that what you intended to do was to copy all the hierarchy, the group /columns itself included. You can do that by just specifying the destination group: .. code-block:: bash $ ptrepack tutorial1.h5:/columns reduced.h5:/columns $ ptdump reduced.h5 / (RootGroup) '' /name (Array(3,)) 'Name column selection' /pressure (Array(3,)) 'Pressure column selection' /columns (Group) '' /columns/name (Array(3,)) 'Name column selection' /columns/pressure (Array(3,)) 'Pressure column selection' OK. Much better. But you want to get rid of the existing nodes on the new file. You can achieve this by adding the -o flag: .. code-block:: bash $ ptrepack -o tutorial1.h5:/columns reduced.h5:/columns $ ptdump reduced.h5 / (RootGroup) '' /columns (Group) '' /columns/name (Array(3,)) 'Name column selection' /columns/pressure (Array(3,)) 'Pressure column selection' where you can see how the old contents of the reduced.h5 file has been overwritten. You can copy just one single node in the repacking operation and change its name in destination: .. code-block:: bash $ ptrepack tutorial1.h5:/detector/readout reduced.h5:/rawdata $ ptdump reduced.h5 / (RootGroup) '' /rawdata (Table(10,)) 'Readout example' /columns (Group) '' /columns/name (Array(3,)) 'Name column selection' /columns/pressure (Array(3,)) 'Pressure column selection' where the /detector/readout has been copied to /rawdata in destination. We can change the filter properties as well: .. code-block:: bash $ ptrepack --complevel=1 tutorial1.h5:/detector/readout reduced.h5:/rawdata Problems doing the copy from 'tutorial1.h5:/detector/readout' to 'reduced.h5:/rawdata' The error was --> tables.exceptions.NodeError: destination group \``/\`` already has a node named \``rawdata``; you may want to use the \``overwrite`` argument The destination file looks like: / (RootGroup) '' /rawdata (Table(10,)) 'Readout example' /columns (Group) '' /columns/name (Array(3,)) 'Name column selection' /columns/pressure (Array(3,)) 'Pressure column selection' Traceback (most recent call last): File "utils/ptrepack", line 3, in ? main() File ".../tables/scripts/ptrepack.py", line 349, in main stats = stats, start = start, stop = stop, step = step) File ".../tables/scripts/ptrepack.py", line 107, in copy_leaf raise RuntimeError, "Please check that the node names are not duplicated in destination, and if so, add the --overwrite-nodes flag if desired." RuntimeError: Please check that the node names are not duplicated in destination, and if so, add the --overwrite-nodes flag if desired. Ooops! We ran into problems: we forgot that the /rawdata pathname already existed in destination file. Let's add the --overwrite-nodes, as the verbose error suggested: .. code-block:: bash $ ptrepack --overwrite-nodes --complevel=1 tutorial1.h5:/detector/readout reduced.h5:/rawdata $ ptdump reduced.h5 / (RootGroup) '' /rawdata (Table(10,), shuffle, zlib(1)) 'Readout example' /columns (Group) '' /columns/name (Array(3,)) 'Name column selection' /columns/pressure (Array(3,)) 'Pressure column selection' you can check how the filter properties has been changed for the /rawdata table. Check as the other nodes still exists. Finally, let's copy a *slice* of the readout table in origin to destination, under a new group called /slices and with the name, for example, aslice: .. code-block:: bash $ ptrepack -R1,8,3 tutorial1.h5:/detector/readout reduced.h5:/slices/aslice $ ptdump reduced.h5 / (RootGroup) '' /rawdata (Table(10,), shuffle, zlib(1)) 'Readout example' /columns (Group) '' /columns/name (Array(3,)) 'Name column selection' /columns/pressure (Array(3,)) 'Pressure column selection' /slices (Group) '' /slices/aslice (Table(3,)) 'Readout example' note how only 3 rows of the original readout table has been copied to the new aslice destination. Note as well how the previously nonexistent slices group has been created in the same operation. pt2to3 ------ The PyTables 3.x series now follows `PEP 8`_ coding standard. This makes using PyTables more idiomatic with surrounding Python code that also adheres to this standard. The primary way that the 2.x series was *not* PEP 8 compliant was with respect to variable naming conventions. Approximately 450 API variables were identified and updated for PyTables 3.x. To ease migration, PyTables ships with a new ``pt2to3`` command line tool. This tool will run over a file and replace any instances of the old variable names with the 3.x version of the name. This tool covers the overwhelming majority of cases was used to transition the PyTables code base itself! However, it may also accidentally also pick up variable names in 3rd party codes that have *exactly* the same name as a PyTables' variable. This is because ``pt2to3`` was implemented using regular expressions rather than a fancier AST-based method. By using regexes, ``pt2to3`` works on Python and Cython code. ``pt2to3`` **help:** .. code-block:: bash usage: pt2to3 [-h] [-r] [-p] [-o OUTPUT] [-i] filename PyTables 2.x -> 3.x API transition tool This tool displays to standard out, so it is common to pipe this to another file: $ pt2to3 oldfile.py > newfile.py positional arguments: filename path to input file. optional arguments: -h, --help show this help message and exit -r, --reverse reverts changes, going from 3.x -> 2.x. -p, --no-ignore-previous ignores previous_api() calls. -o OUTPUT output file to write to. -i, --inplace overwrites the file in-place. Note that ``pt2to3`` only works on a single file, not a a directory. However, a simple BASH script may be written to run ``pt2to3`` over an entire directory and all sub-directories: .. code-block:: bash #!/bin/bash for f in $(find .) do echo $f pt2to3 $f > temp.txt mv temp.txt $f done .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ PyTables-3.7.0/environment.yml000066400000000000000000000000461416254111300163310ustar00rootroot00000000000000name: PyTables dependencies: - hdf5 PyTables-3.7.0/examples/000077500000000000000000000000001416254111300150605ustar00rootroot00000000000000PyTables-3.7.0/examples/Single_Table-vs-EArray_Table.ipynb000066400000000000000000001666621416254111300234120ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Using a single Table vs EArray + Table" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The PyTables community keep asking what can be considered a FAQ. Namely, should I use a single Table for storing my data, or should I split it in a Table and an Array?\n", "\n", "Although there is not a totally general answer, the study below address this for the common case where one has 'raw data' and other data that can be considered 'meta'. See for example: https://groups.google.com/forum/#!topic/pytables-users/vBEiaRzp3gI" ] }, { "cell_type": "code", "execution_count": 156, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n", "PyTables version: 3.2.4.dev0\n", "HDF5 version: 1.8.16\n", "NumPy version: 1.11.0\n", "Numexpr version: 2.4.3 (not using Intel's VML/MKL)\n", "Zlib version: 1.2.8 (in Python interpreter)\n", "Blosc version: 1.9.2 (2016-06-08)\n", "Blosc compressors: blosclz (1.0.5), lz4 (1.7.2), lz4hc (1.7.2), snappy (1.1.1), zlib (1.2.8)\n", "Blosc filters: shuffle, bitshuffle\n", "Cython version: 0.23.4\n", "Python version: 2.7.12 (default, Jul 1 2016, 15:12:24) \n", "[GCC 5.4.0 20160609]\n", "Platform: Linux-4.6.4-gentoo-x86_64-with-Ubuntu-16.04-xenial\n", "Byte-ordering: little\n", "Detected cores: 8\n", "Default encoding: ascii\n", "Default FS encoding: UTF-8\n", "Default locale: (en_US, UTF-8)\n", "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n" ] } ], "source": [ "import numpy as np\n", "import tables\n", "tables.print_versions()" ] }, { "cell_type": "code", "execution_count": 157, "metadata": { "collapsed": false }, "outputs": [], "source": [ "LEN_PMT = int(1.2e6)\n", "NPMTS = 12\n", "NEVENTS = 10" ] }, { "cell_type": "code", "execution_count": 158, "metadata": { "collapsed": false }, "outputs": [], "source": [ "!rm PMT*.h5" ] }, { "cell_type": "code", "execution_count": 159, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def gaussian(x, mu, sig):\n", " return np.exp(-np.power(x - mu, 2.) / (2 * np.power(sig, 2.)))\n", "\n", "x = np.linspace(0, 1, 1e7)\n", "rd = (gaussian(x, 1, 1.) * 1e6).astype(np.int32)\n", "\n", "def raw_data(length):\n", " # Return the actual data that you think it represents PM waveforms better \n", " #return np.arange(length, dtype=np.int32)\n", " return rd[:length]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using Tables to store everything" ] }, { "cell_type": "code", "execution_count": 160, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class PMTRD(tables.IsDescription):\n", " # event_id = tables.Int32Col(pos=1, indexed=True) \n", " event_id = tables.Int32Col(pos=1)\n", " npmt = tables.Int8Col(pos=2)\n", " pmtrd = tables.Int32Col(shape=LEN_PMT, pos=3) " ] }, { "cell_type": "code", "execution_count": 161, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def one_table(filename, filters):\n", " with tables.open_file(\"{}-{}-{}.h5\".format(filename, filters.complib, filters.complevel), \"w\", filters=filters) as h5t:\n", " pmt = h5t.create_table(h5t.root, \"pmt\", PMTRD, expectedrows=NEVENTS*NPMTS)\n", " pmtr = pmt.row\n", " for i in range(NEVENTS):\n", " for j in range(NPMTS):\n", " pmtr['event_id'] = i\n", " pmtr['npmt'] = j\n", " pmtr['pmtrd'] = raw_data(LEN_PMT)\n", " pmtr.append()" ] }, { "cell_type": "code", "execution_count": 162, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 220 ms, sys: 104 ms, total: 324 ms\n", "Wall time: 323 ms\n" ] } ], "source": [ "# Using no compression\n", "%time one_table(\"PMTs\", tables.Filters(complib=\"zlib\", complevel=0))" ] }, { "cell_type": "code", "execution_count": 163, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 3.16 s, sys: 16 ms, total: 3.17 s\n", "Wall time: 3.17 s\n" ] } ], "source": [ "# Using Zlib (level 5) compression\n", "%time one_table(\"PMTs\", tables.Filters(complib=\"zlib\", complevel=5))" ] }, { "cell_type": "code", "execution_count": 164, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 596 ms, sys: 4 ms, total: 600 ms\n", "Wall time: 599 ms\n" ] } ], "source": [ "# Using Blosc (level 9) compression\n", "%time one_table(\"PMTs\", tables.Filters(complib=\"blosc:lz4\", complevel=9))" ] }, { "cell_type": "code", "execution_count": 165, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 42M PMTs-blosc:lz4-9.h5 550M PMTs-None-0.h5 17M PMTs-zlib-5.h5\r\n" ] } ], "source": [ "ls -sh *.h5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, using no compression leads to best speed, whereas Zlib can compress data by ~32x. Zlib is ~3x slower than using no compression though. On its hand, the Blosc compressor is faster but it can barely compress the dataset." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using EArrays for storing raw data and Table for other metadata" ] }, { "cell_type": "code", "execution_count": 166, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def rawdata_earray(filename, filters):\n", " with tables.open_file(\"{}-{}.h5\".format(filename, filters.complib), \"w\", filters=filters) as h5a:\n", " pmtrd = h5a.create_earray(h5a.root, \"pmtrd\", tables.Int32Atom(), shape=(0, NPMTS, LEN_PMT),\n", " chunkshape=(1,1,LEN_PMT))\n", " for i in range(NEVENTS):\n", " rdata = []\n", " for j in range(NPMTS):\n", " rdata.append(raw_data(LEN_PMT))\n", " pmtrd.append(np.array(rdata).reshape(1, NPMTS, LEN_PMT))\n", " pmtrd.flush()" ] }, { "cell_type": "code", "execution_count": 167, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 120 ms, sys: 132 ms, total: 252 ms\n", "Wall time: 250 ms\n" ] } ], "source": [ "# Using no compression\n", "%time rawdata_earray(\"PMTAs\", tables.Filters(complib=\"zlib\", complevel=0))" ] }, { "cell_type": "code", "execution_count": 168, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 2.72 s, sys: 24 ms, total: 2.74 s\n", "Wall time: 2.74 s\n" ] } ], "source": [ "# Using Zlib (level 5) compression\n", "%time rawdata_earray(\"PMTAs\", tables.Filters(complib=\"zlib\", complevel=5))" ] }, { "cell_type": "code", "execution_count": 169, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 224 ms, sys: 36 ms, total: 260 ms\n", "Wall time: 258 ms\n" ] } ], "source": [ "# Using Blosc (level 5) compression\n", "%time rawdata_earray(\"PMTAs\", tables.Filters(complib=\"blosc:lz4\", complevel=9))" ] }, { "cell_type": "code", "execution_count": 170, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "9.0M PMTAs-blosc:lz4.h5 4.1M PMTAs-zlib.h5\t 550M PMTs-None-0.h5\r\n", "550M PMTAs-None.h5\t 42M PMTs-blosc:lz4-9.h5 17M PMTs-zlib-5.h5\r\n" ] } ], "source": [ "!ls -sh *.h5" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "We see that by using the Blosc compressor one can achieve around 10x faster output operation wrt Zlib, although the compression ratio can be somewhat smaller (but still pretty good)." ] }, { "cell_type": "code", "execution_count": 171, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Add the event IDs in a separate table in the same file\n", "class PMTRD(tables.IsDescription):\n", " # event_id = tables.Int32Col(pos=1, indexed=True) \n", " event_id = tables.Int32Col(pos=1)\n", " npmt = tables.Int8Col(pos=2)\n", "\n", "def add_table(filename, filters):\n", " with tables.open_file(\"{}-{}.h5\".format(filename, filters.complib), \"a\", filters=filters) as h5a:\n", " pmt = h5a.create_table(h5a.root, \"pmt\", PMTRD)\n", " pmtr = pmt.row\n", " for i in range(NEVENTS):\n", " for j in range(NPMTS):\n", " pmtr['event_id'] = i\n", " pmtr['npmt'] = j\n", " pmtr.append()" ] }, { "cell_type": "code", "execution_count": 172, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 0 ns, sys: 0 ns, total: 0 ns\n", "Wall time: 1.79 ms\n" ] } ], "source": [ "# Using no compression\n", "%time add_table(\"PMTAs\", tables.Filters(complib=\"zlib\", complevel=0))" ] }, { "cell_type": "code", "execution_count": 173, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 0 ns, sys: 0 ns, total: 0 ns\n", "Wall time: 1.74 ms\n" ] } ], "source": [ "# Using Zlib (level 5) compression\n", "%time add_table(\"PMTAs\", tables.Filters(complib=\"zlib\", complevel=5))" ] }, { "cell_type": "code", "execution_count": 174, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 4 ms, sys: 0 ns, total: 4 ms\n", "Wall time: 1.52 ms\n" ] } ], "source": [ "# Using Blosc (level 9) compression\n", "%time add_table(\"PMTAs\", tables.Filters(complib=\"blosc:lz4\", complevel=9))" ] }, { "cell_type": "code", "execution_count": 175, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "9.0M PMTAs-blosc:lz4.h5 4.1M PMTAs-zlib.h5\t 550M PMTs-None-0.h5\r\n", "550M PMTAs-None.h5\t 42M PMTs-blosc:lz4-9.h5 17M PMTs-zlib-5.h5\r\n" ] } ], "source": [ "!ls -sh *.h5" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "After adding the table we continue to see that a better compression ratio is achieved for EArray + Table with respect to a single Table. Also, Blosc can make writing files significantly faster than not using compression (it has to write less)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Retrieving data from a single Table" ] }, { "cell_type": "code", "execution_count": 187, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def read_single_table(complib, complevel):\n", " with tables.open_file(\"PMTs-{}-{}.h5\".format(complib, complevel), \"r\") as h5t:\n", " pmt = h5t.root.pmt\n", " for i, row in enumerate(pmt):\n", " event_id, npmt, pmtrd = row[\"event_id\"], row[\"npmt\"], row[\"pmtrd\"][:]\n", " if i % 20 == 0:\n", " print(event_id, npmt, pmtrd[0:5])" ] }, { "cell_type": "code", "execution_count": 177, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(1, 8, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(3, 4, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(5, 0, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(6, 8, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(8, 4, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "CPU times: user 24 ms, sys: 80 ms, total: 104 ms\n", "Wall time: 99 ms\n" ] } ], "source": [ "%time read_single_table(\"None\", 0)" ] }, { "cell_type": "code", "execution_count": 178, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(1, 8, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(3, 4, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(5, 0, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(6, 8, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(8, 4, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "CPU times: user 576 ms, sys: 24 ms, total: 600 ms\n", "Wall time: 593 ms\n" ] } ], "source": [ "%time read_single_table(\"zlib\", 5)" ] }, { "cell_type": "code", "execution_count": 179, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(1, 8, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(3, 4, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(5, 0, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(6, 8, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(8, 4, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "CPU times: user 776 ms, sys: 16 ms, total: 792 ms\n", "Wall time: 782 ms\n" ] } ], "source": [ "%time read_single_table(\"blosc:lz4\", 9)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As Blosc could not compress the table, it has a performance that is worse (quite worse actually) to the uncompressed table. On its hand, Zlib can be more than 3x slower for reading than without compression." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Retrieving data from the EArray + Table" ] }, { "cell_type": "code", "execution_count": 180, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def read_earray_table(complib, complevel):\n", " with tables.open_file(\"PMTAs-{}.h5\".format(complib, \"r\")) as h5a:\n", " pmt = h5a.root.pmt\n", " pmtrd_ = h5a.root.pmtrd\n", " for i, row in enumerate(pmt):\n", " event_id, npmt = row[\"event_id\"], row[\"npmt\"]\n", " pmtrd = pmtrd_[event_id, npmt]\n", " if i % 20 == 0:\n", " print(event_id, npmt, pmtrd[0:5])" ] }, { "cell_type": "code", "execution_count": 181, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(1, 8, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(3, 4, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(5, 0, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(6, 8, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(8, 4, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "CPU times: user 4 ms, sys: 80 ms, total: 84 ms\n", "Wall time: 82 ms\n" ] } ], "source": [ "%time read_earray_table(\"None\", 0)" ] }, { "cell_type": "code", "execution_count": 182, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(1, 8, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(3, 4, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(5, 0, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(6, 8, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(8, 4, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "CPU times: user 992 ms, sys: 104 ms, total: 1.1 s\n", "Wall time: 1.09 s\n" ] } ], "source": [ "%time read_earray_table(\"zlib\", 5)" ] }, { "cell_type": "code", "execution_count": 183, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(1, 8, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(3, 4, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(5, 0, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(6, 8, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "(8, 4, array([606530, 606530, 606530, 606530, 606530], dtype=int32))\n", "CPU times: user 168 ms, sys: 4 ms, total: 172 ms\n", "Wall time: 171 ms\n" ] } ], "source": [ "%time read_earray_table(\"blosc:lz4\", 9)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, the EArray + Table takes a similar time to read than a pure Table approach when no compression is used. And for some reason, when Zlib is used for compressing the data, the EArray + Table scenario degrades read speed significantly. However, when the Blosc compression is used, the EArray + Table works actually faster than for the single Table. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Some plots on speeds and sizes" ] }, { "cell_type": "code", "execution_count": 184, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's have a look at the speeds at which data can be stored and read using the different paradigms:" ] }, { "cell_type": "code", "execution_count": 188, "metadata": { "collapsed": false, "scrolled": false }, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAe8AAAGxCAYAAABLDT5KAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XucVXW9//HXe7h5YQaYER0QGBSlvPyM1LyUFwbvKYql\nJuQtLU3jB5bHvKQC2eF4tGOaxwx+CUEkpWiiYeJRwaJOXjA08oKR3ERRGDBA5fr5/bEX02YuzIXZ\n7L2G9/PxWA/W5bu++7NnNvPZ3+/6rvVVRGBmZmbpUZTvAMzMzKxpnLzNzMxSxsnbzMwsZZy8zczM\nUsbJ28zMLGWcvM3MzFLGydss5STNkHRpvuPY0XbW920GTt5mzSbpGEl/lLRK0nJJf5B0WL7jyiZp\nhKSJOaj3eEmLW7reXJH0tqQB+Y7DrKW0zXcAZmkkqRh4HLgCeAhoDxwLrMtnXDuQgGY/4UlSm4jY\n1ILxmO1U3PI2a56+QETEg5GxLiKejoi5AJIuljRL0j1Jy/y17JafpBJJP5O0VNJiSbdKUtbxS5Nz\nVkj6naReWcdOkvS6pJWS7iGTSGuRdApwI/AVSasl/SXZ303S1KTueZK+Xt+blPRFSX+T9M8kzu9I\n2g14Auie1PtPSeWS2ku6S9I7kpZI+pGkdkk9xyfnf1fSu8C4ZP8Zkv6SvJdZkv7PNmKp931L2lfS\nM0kPyPuSJkkqSY5NBHoBjyex/luy/0FJ7yb1zZR0YH2vbVZonLzNmmcesEnSzyWdKqlzHWWOBN4C\nyoCRwCNZ5SYA64F9gc8CJwFfB5B0FnA9MAjoCvwBmJwc2wN4mExS3gOYD3yhrgAjYjowGvh1RBRH\nxGeTQ78GFgHlwLnAaEn963mfPwO+ERElwMHAsxHxEXAasDSptyQi3gNuAo4ADgE+k6zflFVXOdCZ\nTCK9XNJngfuBbwClwBjgsS0JP5uksgbet5L3Wg4cAPQg8zMnIi5K3u8ZSaw/TM55AugD7Am8DPyy\nnp+BWeGJCC9evDRjAT5FpgW5iEwingp0TY5dDCypUf554KtkksUnQIesY+cDzyTrTwBfyzpWBKwF\negIXAn+qUe9i4NJ6YhwBTMza7gFsAHbL2jcaGFfP+QvIJNfiGvuPBxbV2Pd34JSs7ZOBf2SV/wRo\nl3X8J8CoGnW8ARxbRxxNfd9nAbOztt8GBmzjd9kZ2FzzfXrxUqiLW95mzRQRb0bEpRHRi0yrtDtw\nV1aRd2qcsjApUwG0A96VVCVpJfBTMq1skuN3J8eqgBVkri/vnZxfc6BYUwaOdQeqItN6zo5r73rK\nfxk4HViYjO4+qoG6F9Wot3vW9gcRsSFruwK4Zsv7TH4OPWqck113ve9b0p6SJifd9auASWRa6HWS\nVCTpNkl/T8q/TeZnXO85ZoXEydusBUTEPODnZJL4FjUTYi9gKZmk8wlQFhGlEdElIjpHxCFJuUXA\nFcmxLcc7RsSfgXeTerL13FZoNbaXAqWSdq8RV80vGlve1+yI2NJ9PxV4sJ56SeqoyNquSF6vvlgW\nA/9ex/v8dR11N/S+R5NpOR8UEZ2BC9h6LEDN1x4CDCTTGu8M9E7K1zl+wKzQOHmbNYOkTyWDt/ZO\ntnsCg4H/zSq2p6T/K6mtpHOBTwNPROb68FPAjyQVK2NfSccl540BbtwygEpSJ0nnJMemAQdKGiSp\njaThwF7bCHUZ0HvLYLiIWAL8CfgPSR0kHQJcBvyijvfYTtIQSSWRGRm+GtiUVW/ZlkFhiV8BN0na\nI7k2f3Nd9Wb5f8A3JR2RvN7uyQC53esoW9f7Ls86XgysAVYnv5Nra5z/HpnxBdnl1wErk9f7D7Zj\n9LzZjubkbdY8q8kMSHte0moyCfFV4N+yyjwP7A8sB24FvhwRK5NjF5G5vew1oIrM7WblABHxKHAb\n8KukS/dV4NTk2Aoyg8z+M6m3D/DHbcT5EJnW5ApJLyX7hgD7kGkVPwzcHBEz6jn/QuDtJI7LyVyz\nJyLeJDOI7h9Jl3c58APgpSTeV5L1f68vsIiYTeZ6+n8nlwfmkRkrUFfZut73rKwio4DDgFVkbuF7\nuEYVtwE3J7F+h8yAwUVkegvmkvn9maWGInL3ZVNSD2AimZbBZuD/RcSPa5Q5nkx33D+SXY9ExA9y\nFpTZDiDpYuCyiDiuwcJmZk2U64e0bAS+ExFzJHUEZkt6KiLeqFHu9xFxZo5jMTMzaxVy2m0eEe9F\nxJxkfQ3wOnWPavUgETMzs0bKabf5Vi8k9QZmAgcniXzL/uOBKcASMtfgro2I13ZIUGZmZim0Q55t\nnnSZTwGGZyfuxGygIiI+knQa8CiZR0/WrMMjQc3MbKcTEbV6p3Pe8pbUFvgt8LuIuLsR5d8GDouI\nqhr7Y0f1ErSUkSNHMnLkyHyHYbZd/Dm21iKNn2VJdSbvHXGr2DjgtfoSt6S9staPIPOFoqqusmZm\nZpbjbnNJXyBzX+hflZnRKMhMLFBBZkamscA5kq4k87zlj4Gv5DImMzOztMtp8o6IPwJtGihzL3Bv\nLuPIl/79++c7BLPt5s+xtRat6bO8w0abb680XvM2MzPbHvVd894ho83N0q53794sXLgw32FYHlRU\nVLBgwYJ8h2G2Fbe8zRoh+fab7zAsD/y7t3zK52hzMzMza0FO3mZmZinj5G1mZpYyTt5mZmYp4+Rt\n1oo98MADnHrqqS1SV2VlJePGjWuRuhpr+vTp7L///vUeHzx4MKNHj96BEZkVBidvs2bqXV6OpJwt\nvcvLGxXHrFmz+MIXvkDnzp3ZY489OPbYY5k9ezYAQ4YM4cknn8zlj6HB2IqLiykpKaFjx44UFRVR\nUlJSvW/JkiUN1iF5xmCzmnyft1kzLVy2jFzeQKRlyxoss3r1agYOHMiYMWM499xzWb9+PX/4wx/o\n0KFDDiNrvGOOOYbVq1cDsHDhQvbdd18+/PBDJ2Sz7eSWt1mKzZs3D0mcd955SKJDhw6ceOKJHHzw\nwQBMmDCBY489trp8UVERY8aMoW/fvpSWljJ06NDqY5s3b+aaa66ha9eu9OnTh3vvvZeioiI2b95c\n52uPGzeOAw88kLKyMk477TQWLVrUqJhr3jM9duxYDjjgAEpKSujbty/jx4+vVX7kyJGUlZXRp08f\npkyZUm/dv/nNb/jMZz5Dly5dOP7443n99dcbFZNZ2jh5m6VY3759adOmDZdccglPPvkkq1atqlWm\nZit32rRpzJ49m1deeYUHH3yQp556Csgk0enTp/Pqq6/y8ssv8+ijj9bbQp46dSq33XYbjz76KB98\n8AHHHnssgwcPbtZ76N69O9OnT+ef//wnP/3pT/nWt761VdJdsGABGzduZNmyZYwdO5aLL764zqfd\n/fnPf2bo0KFMmDCBqqoqLrzwQgYNGlTvlw+zNHPyNkux4uJiZs2aRVFREZdffjl77rknZ511Fh98\n8EG959xwww0UFxfTs2dPKisrmTNnDgAPPfQQw4cPp1u3bnTq1Inrr7++3jrGjBnDDTfcQN++fSkq\nKuL6669nzpw5LF68uMnv4YwzzqBXr14ADBgwgOOPP55Zs2ZVH2/Xrh233HILbdu25YQTTuDEE0+s\ns/U9duxYhg4dSr9+/ZDE17/+ddatW1d9/d+sNXHyNku5T33qU4wbN45FixYxd+5cli5dytVXX11v\n+b322qt6fbfddmPNmjUALF26lJ49e1Yfy16vaeHChQwfPpzS0lJKS0spKytDEu+8806T43/sscc4\n8sgjKSsro0uXLsyYMYPly5dXH+/atSvt27ev3q6oqGDp0qV1xjR69OjqmLp06cLy5cubFZNZoXPy\nNmtF+vbtyyWXXMLcuXObfG63bt22Gv29rWvYPXv2ZMyYMVRVVVFVVcXKlStZs2YNRx11VJNe86OP\nPuK8885jxIgRLF++nJUrV1JZWbnVdfHly5ezfv36reLq3r17nTF9//vfrxXToEGDmhSTWRo4eZul\n2Jtvvsmdd95Z3bpcvHgxkydP5uijj25yXeeddx533303S5cuZdWqVdx+++31lv3mN7/J6NGjee21\n1wD48MMPtzmQbIuag9U+/vhjNm7cSNeuXYFMK3zmzJlblVm/fj233norGzZs4Nlnn+Xpp5/mnHPO\nqVX35Zdfzj333FPdTb5mzRoef/xxPvnkkwbjMksb3ypm1kwVe+3VqNu5tqf+hhQXF/P8889z5513\n8uGHH9K5c2cGDhxYb+KtOQAte/sb3/gGb731FocccgidOnVi2LBhPPfccxQVFdUqO2jQINauXcv5\n55/PokWL6NSpEyeddFKdSXVbr19WVsYPf/hDzjjjDDZu3MjZZ5/N6aefvlWZffbZh7Zt21JeXk6n\nTp34+c9/TkVFRa36Pv/5z/PjH/+YK664gvnz57P77rtz3HHHcfLJJ28zJrM08pSgOdSrVzmLF+fu\nj3tL69lzLxYtei/fYRSknXFayCeffJIrr7ySt99+O9+h5NXO+Lu3wlHflKBueefQ4sXLmDEj31E0\nXmVler5oWMv75JNPmDFjBieffDLvvfceo0aN4ktf+lK+wzKzOviat5kBmevRI0aMoLS0lMMOO4yD\nDjqIUaNG5TssM6uDW95mBsCuu+7KCy+8kO8wzKwRnLzNzGynkLZxSNvi5G1mZjuFtI1DAqisrHu/\nr3mbmZmljJO3mZlZyjh5m5mZpYyTt5ml3uDBgxk9enSdx9atW0dRUVGdk5mYpVVOk7ekHpKelfQ3\nSX+VNKyecj+W9JakOZL65TIms5bSq1c5knK29OpV3uhYevfuzW677UZJSQnFxcWUlJQwbNi//rvN\nnDmToqIi7rjjjlz8KLbblVdeWR13hw4daN++PSUlJZSUlNR6XGpz1DcvuVla5Xq0+UbgOxExR1JH\nYLakpyLijS0FJJ0G9ImI/SUdCfwUaNrURGZ5kOuRq0154p0kpk2bRmU9Q1MnTpxIWVkZEydO5Npr\nr623nk2bNtGmTZsG9zXXwoUL6d+/f61Hrt53333cd999AIwaNYr58+czceLEFnlNqD0hilna5bTl\nHRHvRcScZH0N8Dqwd41iZwETkzLPA50kNTwjg5ltpb4E9dFHHzFlyhTuvfde3nrrLV5++eXqYwsX\nLqSoqIhx48ZRUVHBCSecUOc+yMw61q1bN7p06UL//v2rZxR76aWXKC8v3+r1H3nkEfr1q7sTrTmt\n4E2bNnHOOedQXl5OaWkpJ5xwAvPmzduqzHvvvceAAQMoKSnhpJNOqreb/JNPPuHqq6+mV69edO/e\nneHDh7Nhw4Ymx2SWTzvsmrek3kA/4Pkah/YGFmdtv0PtBG9mzfTwww9TXFzMueeey8knn8yECRNq\nlfn973/PG2+8wfTp0+vd98UvfpH58+fz/vvvc+ihh/LVr34VgMMPP5w99tiDp556qvrcSZMmcckl\nl7To+xg0aBBvv/027733Hp/+9Ke5+OKLtzo+adIkbrvtNpYvX85+++1X6/gW3/72t1m6dCl/+9vf\nePPNN5k3bx633XZbi8Zqlms75CEtSZf5FGB40gJvlpEjR1av9+/fn/79+293bGatxaBBg2jbti0R\ngSTuuOMOLrvsMiZOnMj555+PJIYMGcLw4cO58847q7vCJTFq1Ch23XXX6rrq2pedjG+55Rbuuusu\nVq9eTXFxMRdddBG/+MUvOOWUU6iqqmL69OnV3eDZmtt93aZNGy644ILq7ZtvvpkePXqwfv162rdv\nX/3+jzjiCABGjx5NWVkZK1asoGPHjtXnbdq0iXHjxrFgwQKKi4sBuO6667jqqqu4+eabmxWbWUua\nMyezNCTnyVtSWzKJ+xcRMbWOIu8APbO2eyT7aslO3ma2talTp9a65r1kyRJmzJhR3bI888wzufzy\ny5k2bRpnnnlmdbkePXrUqi973+bNm7nxxhuZMmUKy5cvrx5Ut3z5coqLi7ngggs48MAD+fjjj3nw\nwQc57rjj2CuZj3zy5MlcddVVSGLTpk2sXbuW0tLS6i8Zr776ap2vn23Tpk1897vf5dFHH2XFihXV\n03SuWLGCbt26AdCz57/+jHTp0oWOHTuydOlS+vbtW71/6dKlbNiwgYMOOmir97blC4BZvvXrl1m2\nqKOjDNgx3ebjgNci4u56jj8GXAQg6ShgVUS0jofPmu1AdbVqJ06cSEQwcOBAunXrRp8+fVi3bl2t\nrvO6rkNn73vggQd4/PHHefbZZ1m1ahULFiwgIqpfs3v37hx99NE8/PDDTJo0iQsvvLD63MGDB7Ny\n5Uqqqqp49dVX6dWrF1VVVdX7GkrcAOPHj+eZZ57hueeeY9WqVbzxxhu13vPixf+6+lZVVcXatWvp\n3r37VvV069aNdu3aMX/+fKqqqqiqqmLVqlW8//77DcZgVkhyfavYF4CvAgMk/UXSy5JOlXSFpMsB\nIuIJ4G1JfwfGAFflMiazncnEiRMZOXIkc+bM4ZVXXuGVV15hypQpTJs2jZUrVwJ1J/2a+1avXk2H\nDh3o0qULa9eu5YYbbqiV8C+88EJuv/125s6du815wJvTdb569Wp22WUXunTpwpo1a/je975Xq8zU\nqVN58cUXWbduHTfddBOVlZWUlZVtVaZt27ZceumlDBs2jBUrVgCZpP/00083OSazfMppt3lE/BFo\n8B6TiBiayzjMcqFnz72adDtXc+pvioEDB9KmTZvq7ujDDz+cRYsWcdVVV22VxAYOHMj+++/P5MmT\nOf300xtsdQNcdNFFTJ8+nb333puysjJuvfVWxowZs1WZs88+myuvvJIvf/nL7LLLLvXG2ZzR5pdd\ndhnPPPMM5eXl7LnnnowYMYLx48dvVecFF1zAddddx0svvcTnPve5rW41y37Nu+66i1tuuYXDDz+c\nlStX0rNnT4YOHcqJJ57Y5LjM8kVpuf9RUqQl1i0kpWoGm8pK3w9bny3XWG3b9ttvP8aOHcuAAQPy\nHUqL8e++9Ujb32So/rtc6xuvH49qZi3i4YcfpqioqFUlbrNC5fm8zWy7VVZW8vrrrzNp0qR8h2K2\nU3DyNrPtNiNtfZFmKeduczMzs5Rx8jYzM0sZJ28zM7OU8TVvs0aoqKjwnNA7qYqKinyHYFaLk7dZ\nIyxYsCDfIZiZVXO3uZmZWco4eZuZmaWMk7eZmVnKOHmbmZmljJO3mZlZyjh5m5mZpYyTt5mZWcqk\n6j5vPyTDzMwsZck78h1AE/mrhpmZ5YK7zc3MzFLGydvMzCxlnLzNzMxSxsnbzMwsZZy8zczMUsbJ\n28zMLGWcvM3MzFLGydvMzCxlcpq8Jd0vaZmkV+s5frykVZJeTpabchmPmZlZa5DrJ6yNB+4BJm6j\nzO8j4swcx2FmZtZq5LTlHRGzgJUNFPNTRM3MzJqgEK55HyXpL5KmSTow38GYmZkVunxPTDIbqIiI\njySdBjwK9K2v8Mis9f7JYmZm1lrMmZNZGpLX5B0Ra7LWfyfpJ5JKI6KqrvIjd1hkZmZmO16/fpll\niwkT6i63I7rNRT3XtSXtlbV+BKD6EreZmZll5LTlLekBMr3bZZIWASOA9kBExFjgHElXAhuAj4Gv\n5DIeMzOz1iCnyTsihjRw/F7g3lzGYGZm1toUwmhzMzMzawInbzMzs5Rx8jYzM0sZJ28zM7OUcfI2\nMzNLmUYlb0nnSipO1m+S9IikQ3MbmpmZmdWlsS3vmyNitaRjgBOB+4H7cheWmZmZ1aexyXtT8u/p\nwNiImEbmYStmZma2gzU2eb8jaQyZJ6A9IalDE841MzOzFtTYBHweMB04JSJWAaXAtTmLyszMzOq1\nzcejSpoNzAJ+BzwREZ8ARMS7wLu5D8/MzMxqaqjlfSTwGzKTizwn6QlJwyXVO+e2mZmZ5dY2W94R\nsRGYmSxI6g6cCvxA0n7AnyPiqhzHaGZmZlmaNKtYRCwFxgHjJBUBR+ckKjMzM6vXNrvNJe0haYSk\nYZI6SrpP0lxJU4F9IuKPOyhOMzMzSzR0zfsBoAOwP/AC8A/gHOC3ZB7UYmZmZjtYQ93me0XEjZIE\nLIyIO5L9b0j6Vo5jMzMzszo01PLeBBARASyvcWxzTiIyMzOzbWqo5b2vpMcAZa2TbO+T08jMzMys\nTg0l77Oy1n+Y/Bs1ts3MzGwHaih5dwZ6RMS9AJJeALqSSeDX5Tg2MzMzq0ND17y/CzyWtd0eOJzM\nE9e+maOYzMzMbBsaanm3j4jFWduzImIFsELS7jmMy8zMzOrRUMu7S/ZGRAzN2uza8uGYmZlZQxpK\n3s9L+kbNnZKuIPPQFjMzM9vBGuo2/zbwqKQhwMvJvsPIPHVtUC4DMzMzs7o1NKvY+8DnJQ0ADkp2\nT4uIZ3MemZmZmdWpUbOKJcm6yQlb0v3AGcCyiDiknjI/Bk4D1gKXRMScpr6OmZnZzqSha97bazxw\nSn0HJZ0G9ImI/YErgJ/mOB4zM7PUy2nyjohZwMptFDkLmJiUfR7oJGmvXMZkZmaWdrlueTdkbyD7\nPvJ3kn1mZmZWj0Zd8y4UI7PW+yeLmZlZazFnTmZpSL6T9ztAz6ztHsm+Oo3MdTRmZmZ51K9fZtli\nwoS6y+2IbnMlS10eAy4CkHQUsCoilu2AmMzMzFIrpy1vSQ+Q6d0uk7QIGEFmcpOIiLER8YSkL0r6\nO5lbxb6Wy3jMzMxag5wm74gY0ogyQxsqY2ZmZv+S79HmZmZm1kT5HrBmZmYp1Lu8nIXLPEQpX5y8\nzcysyRYuW0bkO4gmqm/kdBq529zMzCxlnLzNzMxSxsnbzMwsZZy8zczMUsbJ28zMLGWcvM3MzFLG\nydvMzCxlnLzNzMxSxsnbzMwsZZy8zczMUsbJ28zMLGWcvM3MzFLGydvMzCxlnLzNzMxSxsnbzMws\nZZy8zczMUsbJ28zMLGWcvM3MzFLGydvMzCxlnLzNzMxSxsnbzMwsZZy8zczMUsbJ28zMLGVynrwl\nnSrpDUnzJF1Xx/GLJb0v6eVkuTTXMZmZmaVZ21xWLqkI+G/gBGAp8KKkqRHxRo2iv4qIYbmMxczM\nrLXIdcv7COCtiFgYERuAXwFn1VFOOY7DzMys1ch18t4bWJy1vSTZV9OXJM2R9KCkHjmOyczMLNVy\n2m3eSI8BD0TEBkmXAxPIdLPXMjJrvX+ymJmZtRZz5mSWhuQ6eb8D9Mra7pHsqxYRK7M2fwbcXl9l\nI1syMjMzswLTr19m2WLChLrL5brb/EVgP0kVktoD55NpaVeTVJ61eRbwWo5jMjMzS7WctrwjYpOk\nocBTZL4o3B8Rr0saBbwYEb8Fhkk6E9gAVAGX5DImMzOztMv5Ne+IeBL4VI19I7LWbwRuzHUcZmZm\nrYWfsGZmZpYyTt5mZmYp4+RtZmaWMk7eZmZmKePkbWZmljJO3mZmZinj5G1mZpYyTt5mZmYp4+Rt\nZmaWMk7eZmZmKePkbWZmljJO3mZmZinj5G1mZpYyTt5mZmYp4+RtZmaWMk7eZmZmKePkbWbb1KtX\nOZJStfTqVZ7vH5tZTrXNdwBmVtgWL17GjBn5jqJpKiuX5TsEs5xy8jbbwXqXl7NwmZOLmTWfk7fZ\nDrZw2TIi30E0gfIdgJnV4mveZmZmKePkbWZmljJO3mZmZinj5G1mZpYyTt5mZmYp4+RtZmaWMk7e\nZmZmKZPz5C3pVElvSJon6bo6jreX9CtJb0n6X0m9ch2TmZlZmuU0eUsqAv4bOAU4CBgs6dM1il0G\nVEXE/sBdwO25jMnMzCztct3yPgJ4KyIWRsQG4FfAWTXKnAVMSNanACfkOCYzM7NUy3Xy3htYnLW9\nJNlXZ5mI2ASsklSa47jMzMxSqxCfbV7vo5TT+Izlysp8R9A0Uhp/yumTtp9y2j7H4M/yjpDGn3Aa\nP8t1yXXyfgfIHoDWI9mXbQnQE1gqqQ1QEhFVNSuKiDR+TszMzFpcrrvNXwT2k1QhqT1wPvBYjTKP\nAxcn6+cCz+Y4JjMzs1TLacs7IjZJGgo8ReaLwv0R8bqkUcCLEfFb4H7gF5LeAlaQSfBmZmZWD0Wk\naWZhMzMz26mfsCZps6Q7sravkXRLE+s4TdKLkuZKmp1dn1mhkzRI0l8kvZwsf5G0SdI3Jf01KXOY\npLuS9RGSvpPfqM0yks/qy5LmSHpJ0lHJ/ootn9/WaqdO3sA64EvNvTVN0sHAPcCQiDgYOBz4ewvG\n19Drt9lRr2WtU0Q8GhGfjYhDI+JQ4CfAc8CTQCRlZkfE1fmM06wea5PPbj/gRuC2rGOtult5Z0/e\nG4GxQK2WRPLN7ZnkG93/SOpRx/nXAj+IiLcAImPMts6XNF7ST5JHwf5d0vGS7pf0mqRxWa+/WtKd\nSYv+fySVJftnSPqRpBeAYS3+E7GdlqS+wC3AhWT94Us+o49nFe0n6U+S3pT09R0dp1mW7LuQOgG1\n7lSS1EHSOEmvJr2j/ZP9B0p6Pqvl3ifZf5GkV5JeqAk16ysUO3vyDuBe4KuSimscuwcYn3yjeyDZ\nrulgYHY9dW/r/M4RcTSZLw2PAf8VEQcCh0g6JCmzO/BC0qL/PTAi6/x2EXFERPyosW/UbFsktQV+\nCXw7Imrezglbt2L+D9Af+Dxwi6Ty3EdoVqddk+T7OpmG2K11lPkWsDkiDgGGABOSu5++CdyV9Dgd\nDiyRdCCZFnz/iPgsMHyHvItm2NmTNxGxhszjWWv+ko4GJifrvwCOaWLVNc//QtaxLa2YvwLvRcRr\nyfbfgN7J+mbgwWR9Uo3X/3UTYzFryA+AuRExpRFlp0bE+ohYQebWziNyG5pZvT5Kus0PAE4j87e2\npmPI/A0lIt4EFgB9gf8Fvifpu0DviFgHDAAeioiVSflVuX8LzbPTJ+/E3WQmSNk9a1/N6yV1XT+Z\nS+YbW122db1lXfLv5qz1Ldv13b6XXd/abdRt1iRJN+LZZFoojZH9WRSt/NqipUNE/BnYQ9IeDRRV\nUn4yMBD4GJi2pTudlDw4bmdP3lt+iSvJtHIvyzr2J2Bwsn4B8Ic6zv8hcIOk/SEzi5qkK5pwfnUM\ndSgCzknWvwrM2uY7MWsGSV2AccBFEfFRI087K5nKtww4nszDmMzyofrvZzJjZRGZ54Vk+wOZv6Fb\nxnX0BN6pdaseAAAgAElEQVSUtE9EvB0R95C5fHkImZ6kc7YMYk7+fxSkQny2+Y6U3WL4LzItjy37\nhgHjJf0b8AHwtVonR/xV0tXAZEm7Juf+toHzt9Wir9m6PkLSzcAy4Cv1nG+2Pa4AugL3Jc8C39KS\n/tU2znkVmAmUAd+PiPdyHKNZfXaR9DL/SuIXRUTUeK79T8h8vl8FNgAXR8QGSedJujDZ9y7w7xGx\nStK/A89J2gj8Bbh0h72bJvBDWgqUpNURUXMQnZmZ2U7fbV7I/K3KzMzq5Ja3mZlZyrjlbWZmljJO\n3mZmZinj5G1mZpYyTt5mZmYp4+RtZmaWMk7eZq1AMttcQT5MoiHJHOF1PZO6vvKbJe2by5jMCp2T\nt9l2kHSMpD9KWiVpuaQ/SDos33FlS5LjxHzH0YCm3LPaqLLJtLybJfnvnLU6O/vjUc2aLZlG9nEy\njxh9CGgPHMvWk820OpKKImJzPkNoQrloQnmz1PA3UrPm6wtERDwYGesi4umImAsg6WJJsyTdk7TM\nX5M0YMvJkkok/UzSUkmLJd2qrIcyS7o0OWeFpN9J6pV17CRJr0taKeke6klQkk4hMz/xVyStlvSX\nZH83SVOTuudJ+np9b1LSeEk/kTRN0mqgfzIxyQ8lLZT0bnK8Q1K+s6THJb2f1P+4pO5Z9fWWNFPS\nh5KmA9ucBUrStcnPaImkr5HV8pb0xWQ+5w+TWLLnvX8u+XeVpH9KOlLSvpKeSXpJ3pc0SVLJtl7f\nrBA5eZs13zxgk6SfSzpVUuc6yhwJvEVmEo+RwCNZ5SYA64F9gc8CJwFfB5B0FnA9MIjMxCF/IJkf\nPpny8GEySXkPYD5bzxdfLSKmA6OBX0dEcUR8Njn0a2ARUA6cC4zOmhKxLoOBW5Pn7f8R+E9gPzIz\nMe0H7A3ckpQtIjNTWU+gF/ARcG9WXQ+QmYlsDzLziF9c34tKOhX4DnACsD9wYo0ia4ALI6ITcDrw\nTUlnJseOS/4tiYiSiHiezJec0cn7PgDoQeb3YpYuEeHFi5dmLsCnyCSqRWQS8VSga3LsYmBJjfLP\nk5mecE/gE6BD1rHzgWeS9SeAr2UdKyIz01xP4ELgTzXqXQxcWk+MI4CJWds9yMyktFvWvtHAuHrO\nHw/8vMa+NcA+WdtHA/+o5/x+wIpkvVfyc9o16/gvs+Orce79wOis7f2BTcC+9ZT/EfBfyXpFUrZo\nG7+/s4DZ+f4cefHS1MXXvM22Q0S8STJlYDJX8C+Bu0jmDwbeqXHKQqA7mcTSDng3aypOkfkSQHL8\nbkn/lWxvuX67d3L+4hr11tzelu5AVWw9f/dCYFsD7arrl9QV2A2YndXLX5TESDI97l3AKUDnZH/H\n5JJAN2BlRHxc47V7bCPWl2qUzb60cCTwH8DBZMYctCcz/qBOkvYE7iYzNqEj0Aaoqv9tmxUmd5ub\ntZCImAf8nEwi2WLvGsV6AUvJJMNPgLKIKI2ILhHROSIOScotAq5Ijm053jEi/kxm7uFeNertua3Q\namwvBUol7V4jrppfNOqrYzmZrvCDsuLrHJmua4BryLSQPxcRnflX97WS2LskCT77tevzLlu/t4oa\nsfwSeBTYO3mtMfwrudc1Kn00sDmJvTNwAR7QZink5G3WTJI+Jek7kvZOtnuSuTb8v1nF9pT0fyW1\nlXQu8GngiYh4D3gK+JGkYmXsK2lLohsD3CjpwKTuTpLOSY5NAw6UNEhSG0nDgb22EeoyoPeWwXAR\nsQT4E/AfkjpIOgS4DGjUvdYREcD/A+5KWuFI2lvSyUmRYuBj4J+SSsm6phwRi8i0pEdJaifpGGDg\nNl7uQeASSQdI2o1/XVffoiOZlvwGSUcAQ7KOfUAmUffJ2ldMpst/dfJ7u7Yx79ms0Dh5mzXfajID\n0p5PRmH/CXgV+LesMs+TaYUuB24FvhwRK5NjF5Hp5n2NTNftQ2QGUhERjwK3Ab+StCqp99Tk2Aoy\ng8z+M6m3D5lBZPV5iEzrcoWkLV3QQ4B9yLTCHwZujogZ9ZxfVwv2OuDvwJ+T+J4iM/oeMl3muyWx\n/YnM9ftsQ4CjgBXAzWQG7tX9whFPJvU9S2aA4DM1ilwF3CrpQ+AmMgPxtpz7MfDvwB8lVSXJfRSZ\nywOryNzm93B9r21WyApiPu/kWuGv+dc9mfuS+WPy47wGZrYdJF0MXBYRxzVY2MysCQpiwFpyrfCz\nkHkABLAE+E1egzIzMytQhdhtfiIwPyKaMnrWzMxsp1EQ3ebZJN1P5r7Ln+Q7FjMzs0JUUMlbUjsy\nA2gOjIgPahwrnEDNzMx2kIiodTtjQVzzznIamVb3B3UdLKQvGo0xcuRIRo4cme8wzLaLP8fWWqTx\ns5z1IKStFNo178Ekz282MzOzuhVM8k4ewHAi8Ei+YzEzMytkBdNtnjxnuWu+42hJ/fv3z3cIZtvN\nn2NrLVrTZ7mgBqxti6RIS6xmZmYtQVIqBqyZFaTevXuzcOHCfIdheVBRUcGCBQvyHYbZVtzyNmuE\n5NtvvsOwPPDv3vKpvpZ3wQxYa4169SpHUmqWXr3K8/0jMzOzRnDLO4ckMaO+eZoKUGVl+u6l31Hc\n+tp5+Xdv+eSWt5mZWSvh5G1mZpYyTt5mrdgDDzzAqaee2iJ1VVZWMm7cuBapq7GmT5/O/vvvX+/x\nwYMHM3r06B0YkVlhcPI2a6be5bkdkNi7vHEDCGfNmsUXvvAFOnfuzB577MGxxx7L7NmzARgyZAhP\nPvlkLn8MDcZWXFxMSUkJHTt2pKioiJKSkup9S5YsabCO+p7tbLYz833eZs20cNkycjmMScuWNVhm\n9erVDBw4kDFjxnDuueeyfv16/vCHP9ChQ4ccRtZ4xxxzDKtXrwZg4cKF7Lvvvnz44YdOyGbbyS1v\nsxSbN28ekjjvvPOQRIcOHTjxxBM5+OCDAZgwYQLHHntsdfmioiLGjBlD3759KS0tZejQodXHNm/e\nzDXXXEPXrl3p06cP9957L0VFRWzevLnO1x43bhwHHnggZWVlnHbaaSxatKhRMdccuT127FgOOOAA\nSkpK6Nu3L+PHj69VfuTIkZSVldGnTx+mTJlSb92/+c1v+MxnPkOXLl04/vjjef311xsVk1naOHmb\npVjfvn1p06YNl1xyCU8++SSrVq2qVaZmK3fatGnMnj2bV155hQcffJCnnnoKyCTR6dOn8+qrr/Ly\nyy/z6KOP1ttCnjp1KrfddhuPPvooH3zwAcceeyyDBw9u1nvo3r0706dP55///Cc//elP+da3vrVV\n0l2wYAEbN25k2bJljB07losvvrjOp939+c9/ZujQoUyYMIGqqiouvPBCBg0aVO+XD7M0c/I2S7Hi\n4mJmzZpFUVERl19+OXvuuSdnnXUWH3zwQb3n3HDDDRQXF9OzZ08qKyuZM2cOAA899BDDhw+nW7du\ndOrUieuvv77eOsaMGcMNN9xA3759KSoq4vrrr2fOnDksXry4ye/hjDPOoFevXgAMGDCA448/nlmz\nZlUfb9euHbfccgtt27blhBNO4MQTT6yz9T127FiGDh1Kv379kMTXv/511q1bV33936w1cfI2S7lP\nfepTjBs3jkWLFjF37lyWLl3K1VdfXW/5vfbaq3p9t912Y82aNQAsXbqUnj17Vh/LXq9p4cKFDB8+\nnNLSUkpLSykrK0MS77zzTpPjf+yxxzjyyCMpKyujS5cuzJgxg+XLl1cf79q1K+3bt6/erqioYOnS\npXXGNHr06OqYunTpwvLly5sVk1mhc/I2a0X69u3LJZdcwty5c5t8brdu3bYa/b2ta9g9e/ZkzJgx\nVFVVUVVVxcqVK1mzZg1HHXVUk17zo48+4rzzzmPEiBEsX76clStXUllZudV18eXLl7N+/fqt4ure\nvXudMX3/+9+vFdOgQYOaFJNZGjh5m6XYm2++yZ133lnduly8eDGTJ0/m6KOPbnJd5513HnfffTdL\nly5l1apV3H777fWW/eY3v8no0aN57bXXAPjwww+3OZBsi5qD1T7++GM2btxI165dgUwrfObMmVuV\nWb9+PbfeeisbNmzg2Wef5emnn+acc86pVffll1/OPffcU91NvmbNGh5//HE++eSTBuMySxvfKmbW\nTBV77dWo27m2p/6GFBcX8/zzz3PnnXfy4Ycf0rlzZwYOHFhv4q05AC17+xvf+AZvvfUWhxxyCJ06\ndWLYsGE899xzFBUV1So7aNAg1q5dy/nnn8+iRYvo1KkTJ510Up1JdVuvX1ZWxg9/+EPOOOMMNm7c\nyNlnn83pp5++VZl99tmHtm3bUl5eTqdOnfj5z39ORUVFrfo+//nP8+Mf/5grrriC+fPns/vuu3Pc\nccdx8sknbzMmszQqmIlJJHUCfgYcDGwGLo2I57OOe2KSHPPEJPXbGSenePLJJ7nyyit5++238x1K\nXu2Mv3srHGmYmORu4ImIOAD4DOAbNM12oE8++YTf/e53bNq0iXfeeYdRo0bxpS99Kd9hmVkdCiJ5\nSyoBjo2I8QARsTEi/pnnsMx2KhHBiBEjKC0t5bDDDuOggw5i1KhR+Q7LzOpQKNe89wGWSxpPptX9\nEjA8Ij7Ob1hmO49dd92VF154Id9hmFkjFErybgscCnwrIl6SdBdwPTAiu9DIkSOr1/v370///v13\nYIhmZma5NXPmzFp3XNSlIAasSdoL+N+I2DfZPga4LiIGZpXxgLUc84C1+nnQ0s7Lv3vLp4IesBYR\ny4DFkvomu04AXstjSGZmZgWrULrNAYYBv5TUDvgH8LU8x2NmZlaQCiZ5R8QrwOfyHYeZmVmhK4hu\nczOz7TF48GBGjx5d57F169ZRVFRU52QmZmnl5G3WTL16lSMpZ0uvXuWNjqV3797stttulJSUUFxc\nTElJCcOGDas+PnPmTIqKirjjjjty8aPYbldeeWV13B06dKB9+/aUlJRQUlJS63GpzVHfvORmaVUw\n3eZmabN48bKc3k1QWdn456ZLYtq0aVRWVtZ5fOLEiZSVlTFx4kSuvfbaeuvZtGkTbdq0aXBfcy1c\nuJD+/fvXeuTqfffdx3333QfAqFGjmD9/PhMnTmyR1wTfRWGtj1veZq1EfQnqo48+YsqUKdx77728\n9dZbvPzyy9XHFi5cSFFREePGjaOiooITTjihzn2QmXWsW7dudOnShf79+1fPKPbSSy9RXl6+1es/\n8sgj9OvXr854mtMK3rRpE+eccw7l5eWUlpZywgknMG/evK3KvPfeewwYMICSkhJOOumkervJP/nk\nE66++mp69epF9+7dGT58OBs2bGhyTGb55ORt1so9/PDDFBcXc+6553LyySczYcKEWmV+//vf88Yb\nbzB9+vR6933xi19k/vz5vP/++xx66KF89atfBeDwww9njz324Kmnnqo+d9KkSVxyySUt+j4GDRrE\n22+/zXvvvcenP/1pLr744q2OT5o0idtuu43ly5ez33771Tq+xbe//W2WLl3K3/72N958803mzZvH\nbbfd1qKxmuWak7dZKzFo0CBKS0vp0qULpaWl3H///UCmy/z8889HEkOGDOFXv/oVmzZtqj5PEqNG\njWLXXXelQ4cO9e675JJL2G233WjXrh233HILr7zyCqtXrwbgoosu4he/+AUAVVVVTJ8+ncGDB9eK\nsbnd123atOGCCy5g1113pX379tx88828+OKLrF+/fqv3f8QRR9C+fXtGjx7NM888w4oVK7aqZ9Om\nTYwbN467776b4uJiiouLue6665g8eXKz4jLLl1Ql71wODsrFYrYjTZ06laqqKlauXElVVRWXXXYZ\nS5YsYcaMGQwZMgSAM888k48//php06ZtdW6PHj1q1Ze9b/PmzVx//fXst99+dO7cmX322QdJLF++\nHIALLriA3/72t3z88cc8+OCDHHfcceyVzEc+efLk6i8Un/nMZ1i0aNFWXzKWLFnS4HvbtGkT11xz\nDX369KFz584ccMABRMRWyblnz57V6126dKFjx461us6XLl3Khg0bOOiggygtLaW0tJRBgwZVvw+z\ntEhV8o6ULWY7Ul2t2okTJxIRDBw4kG7dutGnTx/WrVtXq+u8ri+b2fseeOABHn/8cZ599llWrVrF\nggULiIjq1+zevTtHH300Dz/8MJMmTeLCCy+sPnfw4MHVXyheffVVevXqtdWXjLq+ONQ0fvx4nnnm\nGZ577jlWrVrFG2+8Ues9L168uHq9qqqKtWvX0r17963q6datG+3atWP+/PlUVVVRVVXFqlWreP/9\n9xuMwayQpCp5m1nTTJw4kZEjRzJnzhxeeeUVXnnlFaZMmcK0adNYuXIlUHfSr7lv9erVdOjQgS5d\nurB27VpuuOGGWgn/wgsv5Pbbb2fu3LnbnAe8OV3nq1evZpdddqFLly6sWbOG733ve7XKTJ06lRdf\nfJF169Zx0003UVlZSVlZ2VZl2rZty6WXXsqwYcOqW+2LFy/m6aefbnJMZvnkW8XMmqlnz72adDtX\nc+pvioEDB9KmTRsiAkkcfvjhLFq0iKuuumqrJDZw4ED2339/Jk+ezOmnn95gqxsy17SnT5/O3nvv\nTVlZGbfeeitjxozZqszZZ5/NlVdeyZe//GV22WWXeuNsziWlyy67jGeeeYby8nL23HNPRowYwfjx\n47eq84ILLuC6667jpZde4nOf+9xWt5plv+Zdd93FLbfcwuGHH87KlSvp2bMnQ4cO5cQTT2xyXGb5\nUhCzijWGpJRE+i8CzyrWSnhmqcbZb7/9GDt2LAMGDMh3KC3Gv3vLp4KeVczM0u/hhx+mqKioVSVu\ns0LlbnMz226VlZW8/vrrTJo0Kd+hmO0UnLzNbLvNSNP1IbNWwN3mZmZmKePkbWZmljIF020uaQHw\nIbAZ2BARR+Q3IjMzs8JUMMmbTNLuHxEr8x2IWU0VFRV+5O1OqqKiIt8hmNVSSMlbuBvfCtSCBQvy\nHYKZWbVCSpYBTJf0oqRv5DsYMzOzQlVILe8vRMS7kroC/yPp9YiYlV1gZNZ6/2QxMzNrLWbOnMnM\nmTMbLFeQj0eVNAJYHRF3Zu0rwEi3zY9HNTOz7VHQj0eVtJukjsn67sDJwNz8RmVmZlaYCqXbfC/g\nN5KCTEy/jIin8hyTmZlZQSqI5B0RbwP98h2HmZlZGhREt7mZmZk1npO3mZlZyjh5m5mZpYyTt5mZ\nWco4eZuZmaWMk7eZmVnKOHmbmZmljJO3mZlZyjh5m5mZpYyTt5mZWco4eZuZmaWMk7eZmVnKOHmb\nmZmljJO3mZlZyjh5m5mZpYyTt5mZWcoUTPKWVCTpZUmP5TsWMzOzQlYwyRsYDryW7yDMzMwKXUEk\nb0k9gC8CP8t3LGZmZoWuIJI38CPgWiDyHYiZmVmha5vvACSdDiyLiDmS+gOqr+zIrPX+yWJmZtZa\nzJw5k5kzZzZYThH5bexKGg1cAGwEdgWKgUci4qIa5fIcadMJmDEj31E0XmUl5PvzYGZm/yKJiKjV\nqM17t3lE3BgRvSJiX+B84NmaidvMzMz+Je/J28zMzJom79e8s0XEc8Bz+Y7DzMyskLVYy1vSuZKK\nk/WbJD0i6dCWqt/MzMwyWrLb/OaIWC3pGOBE4H7gvhas38zMzGjZ5L0p+fd0YGxETAPat2D9ZmZm\nRssm73ckjQG+AjwhqUML129mZma0bHI9D5gOnBIRq4BSMk9NMzMzsxa03aPNJc0GZgG/A56IiE8A\nIuJd4N3trd/MzMy21hIt7yOB35B5Wulzkp6QNFxS3xao28zMzGrY7pZ3RGwEZiYLkroDpwI/kLQf\n8OeIuGp7X8fMzMwyWvwhLRGxFBgHjJNUBBzd0q9hZma2M9vubnNJe0gaIWmYpI6S7pM0V9JUYJ+I\n+GMLxGlmZmaJlrjm/QDQAdgfeAH4B3AO8FsyD2oxMzOzFtQS3eZ7RcSNkgQsjIg7kv1vSPpWC9Rv\nZmZmWVqi5b0JIDITQS+vcWxzC9RvZmZmWVqi5b2vpMcAZa2TbO/TAvWbmZlZlpZI3mdlrf8w+Tdq\nbJuZmVkLaYnk3RnoERH3Akh6AehKJoFf1wL1m5mZbbdevcpZvHhZvsNoES2RvL8LnJ+13R44HNgd\nGA881AKvYWZmtl0WL17GjBn5jqJpKivr3t8Sybt9RCzO2p4VESuAFZJ2b0wFyQxkvyeT+NsCUyJi\nVAvEZmZm1uq0RPLukr0REUOzNrs2poKIWCepMiI+ktQG+KOk30XECy0Qn5mZWavSEreKPS/pGzV3\nSrqCzENbGiUiPkpWO5D5UhHbKG5mZrbTaomW97eBRyUNAV5O9h1GJgkPamwlyXPQZwN9gHsj4sUW\niM3MzKzVaYlZxd4HPi9pAHBQsntaRDzbxHo2A5+VVELmy8CBEfFadpmRWev9k8XMzKy1mDMnszRE\nmQejFRZJNwNrI+LOrH0FGOm2CVI1srGyEgrx82Bm1hIkpepvMlT/XVbN/S1xzXu7JTOTdUrWdwVO\nAt7Ib1RmZmaFqcXn826mbsCE5Lp3EfDriHgizzGZmZkVpIJI3hHxV+DQfMdhZmaWBgXRbW5mZmaN\n5+RtZmaWMk7eZmZmKePkbWZmljJO3mZmZinj5G1mZpYyTt5mZmYp4+RtZmaWMk7eZmZmKePkbWZm\nljJO3mZmZinj5G1mZpYyTt5mZmYp4+RtZmaWMk7eZmZmKePkbWZmljIFkbwl9ZD0rKS/SfqrpGH5\njsnMzKxQtc13AImNwHciYo6kjsBsSU9FxBv5DszMzKzQFETLOyLei4g5yfoa4HVg7/xGZWZmVpgK\nInlnk9Qb6Ac8n99IzMzMClOhdJsDkHSZTwGGJy3wrYzMWu+fLGZmZq3FnDmZpSEFk7wltSWTuH8R\nEVPrKjNyh0ZkZma2Y/Xrl1m2mDCh7nKF1G0+DngtIu7OdyBmZmaFrCCSt6QvAF8FBkj6i6SXJZ2a\n77jMzMwKUUF0m0fEH4E2+Y7DzMwsDQqi5W1mZmaN5+RtZmaWMk7eZmZmKePkbWZmljJO3mZmZinj\n5G1mZpYyTt5mZmYp4+RtZmaWMk7eZmZmKePkbWZmljJO3mZmZinj5G1mZpYyTt5mZmYp4+RtZmaW\nMk7eZmZmKePkbWZmljIFkbwl3S9pmaRX8x2LmZlZoSuI5A2MB07JdxBmZmZpUBDJOyJmASvzHYeZ\nmVkaFETyNjMzs8Zrm+8AmmJk1nr/ZDEzM2st5szJLA1JbfI2MzNrbfr1yyxbTJhQd7lC6jZXspi1\nar3Ly5GUmqV3eXm+f2RmVkNBJG9JDwB/AvpKWiTpa/mOySxXFi5bRkBqloXLluXoJ2FmzVUQ3eYR\nMSTfMZiZmaVFQbS8zczMrPGcvM3MzFLGydvMzCxlCuKat5mZpUvv8nIPZswjJ28zM2uyLXdNpElr\nuhfZ3eZmZmYp4+RtZmaWMk7eZmZmKeNr3ma2Te3agZSuq4U9e+7FokXv5TsMs5xx8jazbdqwAWbM\nyHcUTVNZ6VHQ1rq529zMzCxlnLzNzMxSxsnbzMwsZZy8zczMUsbJ28zMLGWcvM3MzFLGydvMzCxl\nCiZ5SzpV0huS5km6Lt/xmJmZFaqCSN6SioD/Bk4BDgIGS/p0fqMyMzMrTAWRvIEjgLciYmFEbAB+\nBZyV55jMzMwKUqEk772BxVnbS5J9ZmZmVkOqnm2erqkRMior8x1B06RtAoq0SttPOW2fY/BneUdI\n4084jZ/luhRK8n4H6JW13SPZVy0i0vg5MTMza3GF0m3+IrCfpApJ7YHzgcfyHJOZmVlBKoiWd0Rs\nkjQUeIrMF4r7I+L1PIdlZmZWkBQR+Y7BzMzMmqBQus3zQtJmSXdkbV8j6ZYm1nGapBclzZU0O7s+\ns0InaZCkv0h6OVn+ImmTpG9K+mtS5jBJdyXrIyR9J79Rm2Ukn9WXJc2R9JKko5L9FVs+v63VTp28\ngXXAlySVNudkSQcD9wBDIuJg4HDg7y0YX0Ov32ZHvZa1ThHxaER8NiIOjYhDgZ8AzwFPApGUmR0R\nV+czTrN6rE0+u/2AG4Hbso616m7lnT15bwTGArVaEsk3t2eSb3T/I6lHHedfC/wgIt4CiIwx2zpf\n0nhJP5H0v5L+Lul4SfdLek3SuKzXXy3pzqRF/z+SypL9MyT9SNILwLAW/4nYTktSX+AW4EKy/vAl\nn9HHs4r2k/QnSW9K+vqOjtMsS/ZdSJ2AqloFpA6Sxkl6Nekd7Z/sP1DS81kt9z7J/oskvZL0Qk3Y\nIe+iGXb25B3Avf+/vfsJlaqMwzj+faQiCcHIoE1gi4SkLgQhmELSzkVIIEZpRrpQCKwgghANNHdJ\nipC7RMqu/dkUtY3KSBFUUEtc1aLICFEoDTXu0+J9jw3HuXPvXLneO93ns5n3zHl/Z87LzJz3/N5z\n5h1gtaQ5rXV7gH31jO7Dutz2MHBslG33ip9rezHlpOFzYKfthcCQpKFa5y7gaM3ovwXe7Ii/3fYi\n2++Mt6ERvUi6DTgAvGr71y5VOrOYR4BlwOPAVkn3Tf4eRnQ1u3a+ZyiJ2PYudV4CRmwPAc8B++uv\nmjYCu+qI02PAL5IWUjL4ZbYfBV6+Ja2YgJneeWP7L2A/N75Ji4HhWn4fWNrnptvxSzrWNVnMKeCc\n7R/r8g/A/FoeAT6u5Q9ar/9Rn/sSMZa3gNO2Px1H3c9sX7V9HviKMr1xxFS4XIfNHwKWU461bUsp\nx1BsnwV+BhYAh4HNkl4H5tu+AjwJfGL7Qq1/cfKbMDEzvvOudgPrKdluo329pNv1k9OUM7Zuel1v\nuVIfRzrKzfJoP9/r3N6lHtuO6EsdRnyakqGMR+dnUfzPry3GYLB9BJgnad4YVVXrDwNPAX8DXzbD\n6QzIxHEzvfNu3sQLlCx3fce674Fna3kNcKhL/NvAG5IehPLvaJI29BF/fR+6mAWsrOXVwHc9WxIx\nAZLuBt4D1tq+PM6wFZLuqPdhPEGZZCliKlw/ftZ/opwFnG/VOUQ5hjb3ddwPnJX0gO2fbO+hXL4c\nog9LaEQAAADaSURBVIwkrWxuYq7fj2lpWkzSMoU6M4adlMyjeW4TsE/Sa8AfwIs3BNunJL0CDEua\nXWO/GCO+V0bfzq4XSdoC/A48M0p8xM3YANwL7K1zgTeZ9MEeMSeBr4F7gG22z03yPkaM5k5Jx/mv\nE19r26157d+lfL5PAteAF2xfk7RK0vP1ud+AHbYvStoBfCPpH+AEsO6WtaYPmaRlmpL0p+32TXQR\nEREzfth8OstZVUREdJXMOyIiYsAk846IiBgw6bwjIiIGTDrviIiIAZPOOyIiYsCk846IiBgw/wJt\n8i91O5CzigAAAABJRU5ErkJggg==\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=False, sharey=False)\n", "fig.set_size_inches(w=8, h=7, forward=False)\n", "f = .550 # conversion factor to GB/s\n", "rects1 = ax1.bar(np.arange(3), f / np.array([.323, 3.17, .599]), 0.25, color='r')\n", "rects2 = ax1.bar(np.arange(3) + 0.25, f / np.array([.250, 2.74, .258]), 0.25, color='y')\n", "_ = ax1.set_ylabel('GB/s')\n", "_ = ax1.set_xticks(np.arange(3) + 0.25)\n", "_ = ax1.set_xticklabels(('No Compr', 'Zlib', 'Blosc'))\n", "_ = ax1.legend((rects1[0], rects2[0]), ('Single Table', 'EArray+Table'), loc=9)\n", "_ = ax1.set_title('Speed to store data')\n", "\n", "rects1 = ax2.bar(np.arange(3), f / np.array([.099, .592, .782]), 0.25, color='r')\n", "rects2 = ax2.bar(np.arange(3) + 0.25, f / np.array([.082, 1.09, .171]), 0.25, color='y')\n", "_ = ax2.set_ylabel('GB/s')\n", "_ = ax2.set_xticks(np.arange(3) + 0.25)\n", "_ = ax2.set_xticklabels(('No Compr', 'Zlib', 'Blosc'))\n", "_ = ax2.legend((rects1[0], rects2[0]), ('Single Table', 'EArray+Table'), loc=9)\n", "_ = ax2.set_title('Speed to read data')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now, see the different sizes for the final files:" ] }, { "cell_type": "code", "execution_count": 190, "metadata": { "collapsed": false }, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfIAAAFCCAYAAAAKW1+lAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XucX/O97/HXZxIJKnckQi4aotRBe2xFSzKoU5dUONii\nLim910Evjth716XdJ0e1u0WrLmeLJrXdSutStjgqo7V3qbpEFYk6zZ0Qk2gSJCSf88dvTfwymZlM\n0vxmssbr+Xj8Htblu9b6rF9+5v37rrV+a0VmIkmSyqmuswuQJEkbzyCXJKnEDHJJkkrMIJckqcQM\nckmSSswglySpxAxyaQNFxMkRcX8N1ntDRDRGxKObet2dKSKGRcTqiGjX35viffh2reuSugqDXGpB\nRHwiIv4jIpZExKKI+G1E/FeAzLwpMz+1qbcHHAoMzsz9N+W6W9jWtIg4o5bbaEFNbljRUfvSSe+Z\n1C7dO7sAaXMTEb2Ae4AvAj8HegAHAStquNnhwKzMfHtDF4yIbpm5atOX1Or2Ir2TlLTZsEcurWsk\nkJl5W1asyMwHM/NZgIg4PSJ+WwyfFxFLI+KvxWtlREwq5vWOiH+NiAURMTcivhMR0XxjRU/v/wAH\nFOu4qJj++Yh4sTgicGdE7FC1zOqI+EpEzARmtrDOnhHxs2LZxRHxWERsFxH/TOVLyY+LbV1ZtD8w\nIn5f1faAqnVNi4h/johHImI5sHOxb9e3tG8RURcR34+I1yLiz8BRbb3ZEfGRiHgiIt6IiFuALavm\n9Y2IeyLi1Yh4vRgeXMxrbV8uj4g5xfoeL452NK3v74ppb0TEyxHx/ap5+xdHYRZHxFMRMWo92/lh\nRCws1jU9IvZoaz+lmslMX758Vb2AXsBrwE+BTwF9m80/HfhNC8vtBMwDDi/Gfwn8hEowbQs8Cny+\nlW2utU7gkKKGvYEtgCuBh6vmrwamAn2Ani2s7wvAXUBPIICPANsU86YBZ1S17Qc0AidT+XJ/UjHe\nr6r9LOBDxfzube0b8CXgOWAw0Bd4CFgF1LVQ5xbFus8GugH/HVgJfLuY3x84ttiPDwC3Ar+sWn6t\nfSmmnVxstw74GvAy0KOY95/AZ4rhrYH9iuHBwCLgvxXjhxbjA1p5zw4HHgd6FeO7AQM7+7Pr6/35\nskcuNZOZS4FPUAnL64BXI+KuiNiutWUiYivgTuDyzHwgIrYHjgC+lplvZ+Yi4HJgXDvLOBm4PjOn\nZ+Y7wAVUeuxDq9pMzMw3MrOlQ/7vAAOAkVnxVGYua2VbRwEzs3Luf3Vm3gK8AIypavPTzHwhM1dT\nCdeW9u2kou0JxfuwIDOXAP+7jf3cH+iemVdm5qrMvINKQAKQmY2Z+cusHBVZXqzr4DbWR7EfS4p9\n+SGVLwG7FbNXArtExIDMfDMzf19MPwW4NzOnFuv4NfAH4MhWNvMOlS98exSnGmZk5sK26pJqxSCX\nWlD8YT4jM4cCe1LpsV3exiLXA89nZtOh2mFUepsvF1eiLwauodJ7bY/BwOyqepYDrwM7VrWZ18by\nU6j02G+JiHkR8d2I6NaebRVmN9vW3Krh1vat6YvO4Gbtm6+7+bbnt7BtoPIFKSKujYhZEbEEeBjo\n29IpiqplvhkRzxWHyBcDvXnvfT+TSqi/UJxCaDrsPww4sdifpn36ODCopW1k5jTgx8BVwMKIuCYi\ntmljP6WaMcil9cjMmVQOs+/Z0vyImADsQiUkmswF3qZyaLZ/ZvbLzL6ZuVc7N7uASrg0beMDVHrY\n1eHd6gVnRe/2O5n5YeBA4GjgtFaWW0DlYrtqQ1k7YKuXWd++vQwMqWo/jNa9zNpfGJq23eSbwK7A\n32VmX97rjTcF+Vr7UpwPPw84vqirH/DXpvaZ+VJmnpyZ2wGXAbcXR1PmAlOK/Wnap16Z+b2WtlOs\n68eZuS+wB5UvB+e1sZ9SzRjkUjMRsVtEfD0idizGh1A5JP67FtoeAfwP4NjMXNk0PTNfAR4AfhgR\nvaLigxHR5mHhKjcDn42IvSKiJzAReDQz565nuaa6RkfEnlH57fYyKoeCm65sXwh8sKr5fcCuEXFS\nRHSLiL8Hdqdy5f462rFvtwFnR8SOEdEPOL+NUn8HvBsR/yMiukfEccB+VfO3Ad4C/hoR/YGLmy3f\nfF96Ffv6ekT0iIgLi2lN78tnIqKpd/4GlYBeDdwIjImIw4uL9baMiFFNF9Y1305E7BsR+0VE96K+\nt4v1SB3OIJfWtRT4GPBYRCylcoHUM1R6h82dSOWw7fPx3tXrPynmnU7lp2vPUbl47Oe0cqi2ueIc\n7beAX1DpGe/Me+egYf2/yx4E3E4lrP5E5WKtG4t5VwAnFFeBX56ZjVR67N+kcoHXN4GjMnNxG9s6\nrY19+z9UDutPp3Ke+Y429vMd4Djgs1ROHZzQrP3lVC5KW0Tl3+G+ZqtYa1+A+4ttzwT+ArzJ2of5\nPwX8KSL+CvwQ+Pvi/Ps84BjgH6hcZDi7eB/qWtlO72I/G4vtLAK+h9QJIrO2PweNiD7Av1I5LLka\nOIPK/2S3UjnkNgs4MTPfKNpfSeVCmuXA+Mx8uqYFSpJUYh3RI78CuC8zd6fyU5oXgAnAg5m5G5Wf\nplwAaw5TjsjMXancjOOaDqhPkqTSqmmPPCJ6A09l5ohm018ARmXmwogYBEzLzN0j4ppi+Nai3fPA\naH/WIUlSy2rdI98ZWBSVhyA8GRHXRcTWVG6csBDWXDgzsGi/I2ufz5rPule0SpKkQq2DvDvwUeCq\nzPwolfPeE1j34hnv2yxJ0kao9UNT5gFzM/MPxfgdVIJ8YUQMrDq0/moxfz5r//50J9a9WQQRYfBL\nkt5XMrPFGyHVtEdeHD6fGxEji0mHUvkpzN3A+GLaeCr3hKaYfhpUHmAALGnt/Hit7127qV8XXXRR\np9fgy9ff+vJz7KurvMr2WW5LRzzG9Gzg3yJiC+D/Ufm9aDfgtqg89Wk2ld/ikpn3RcSRUXli0vKi\nrSRJakXNgzwzpwN/18Ksw1ppf1ZtK5Ikqevwzm4dZPTo0Z1dgvQ383OsrqIrfZZrfme3Wqg8NbB8\ndUuStDEigmzlYreOOEcudSnDhw9n9uy2nsyprmrYsGHMmjWrs8uQ1mKPXNpAxTfjzi5DncB/e3WW\ntnrkniOXJKnEDHJJkkrMIJckqcQMcul94qabbuJTn/rUJllXfX09kyZN2iTraq+pU6ey6667tjp/\n3LhxTJw4sQMrkjYPBrm0CQwfNIiIqNlr+KBB7arjkUce4eMf/zh9+/Zl22235aCDDuKJJ54A4OST\nT+b++++v5duw3tp69epF79692Wabbairq6N3795rps2bN2+964ho8Vof6X3Nn59Jm8DshQtr+gi/\nWNjiIwfWsnTpUsaMGcO1117LCSecwMqVK/ntb39Lz549a1hZ+33iE59g6dKlAMyePZsPfvCDvPHG\nG4az9DeyRy51ETNnziQiOPHEE4kIevbsyWGHHcaee+4JwOTJkznooIPWtK+rq+Paa69l5MiR9O/f\nn7POeu/uyKtXr+Yb3/gG2223HSNGjOCqq66irq6O1atXt7jtSZMmscceezBgwACOOOII5syZ066a\nm/+U67rrrmP33Xend+/ejBw5khtuuGGd9hdffDEDBgxgxIgR3H777a2u+5e//CV77703/fr1Y9So\nUTz//PPtqkkqG4Nc6iJGjhxJt27dGD9+PPfffz9LlixZp03z3u+9997LE088wfTp07ntttt44IEH\ngEqgTp06lWeeeYYnn3ySO++8s9We81133cWll17KnXfeyWuvvcZBBx3EuHHjNmofBg8ezNSpU/nr\nX//KNddcw1e/+tW1AnjWrFm8++67LFy4kOuuu47TTz+9xZvzPProo5x11llMnjyZxsZGTj31VMaO\nHdvqFxGpzAxyqYvo1asXjzzyCHV1dXzhC19g++2355hjjuG1115rdZkLLriAXr16MWTIEOrr63n6\n6acB+PnPf84555zDDjvsQJ8+fZgwYUKr67j22mu54IILGDlyJHV1dUyYMIGnn36auXPnbvA+HH30\n0QwdOhSAQw45hFGjRvHII4+smb/FFltw4YUX0r17dw499FAOO+ywFnvl1113HWeddRb77LMPEcHn\nPvc5VqxYseZ6AakrMcilLmS33XZj0qRJzJkzh2effZYFCxZw7rnnttp+4MCBa4a33nprli1bBsCC\nBQsYMmTImnnVw83Nnj2bc845h/79+9O/f38GDBhARDB//vwNrv/uu+/mYx/7GAMGDKBfv35MmzaN\nRYsWrZm/3Xbb0aNHjzXjw4YNY8GCBS3WNHHixDU19evXj0WLFm1UTdLmziCXuqiRI0cyfvx4nn32\n2Q1edocddljrKvK2znkPGTKEa6+9lsbGRhobG1m8eDHLli1j//3336Btvvnmm5x44olcdNFFLFq0\niMWLF1NfX7/WefRFixaxcuXKteoaPHhwizV9+9vfXqemsWPHblBNUhkY5FIXMWPGDH7wgx+s6XXO\nnTuXm2++mQMOOGCD13XiiSdyxRVXsGDBApYsWcJll13WatsvfelLTJw4keeeew6AN954o82L0Jo0\nv9Dtrbfe4t1332W77bYDKr3zhoaGtdqsXLmS73znO7zzzjs89NBDPPjggxx//PHrrPsLX/gCP/rR\nj9YcSl+2bBn33HMPb7/99nrrksrGn59JXUSvXr147LHH+MEPfsAbb7xB3759GTNmTKsh3Pziterx\nz3/+87z44ovstdde9OnTh7PPPpuHH36Yurq6ddqOHTuW5cuXc9JJJzFnzhz69OnDJz/5yRYDtq3t\nDxgwgO9///scffTRvPvuuxx77LEcddRRa7XZeeed6d69O4MGDaJPnz789Kc/ZdiwYeus78ADD+TK\nK6/ki1/8Ii+99BIf+MAHOPjggzn88MPbrEkqI59+Jm2glp6ANXzQIGa347feG2vYwIHMeuWVmq1/\nfe6//36+/OUv85e//KXTatgc+PQzdRaffibV2KxXXiEza/bq6BB/++23+fd//3dWrVrF/PnzueSS\nSzjuuOM6tAZJ7VPaHnln17ChttyyjrffLs9vWIcMGcicOZ3XA9ycvR96ZW+99RajRo1ixowZbLXV\nVhx99NFcfvnlbLPNNp1dWqd6P/zba/PUVo+8tEFetqoDmDats6tov/r6dS9GUoV/zN+//LdXZ/HQ\nuiRJXZRBLklSiRnkkiSVmEEuSVKJGeSSJJWYQS6pyxg3bhwTJ05scd6KFSuoq6tr8SErUpkZ5NIm\nMHToICKiZq+hQwe1q47hw4ez9dZb07t3b3r16kXv3r05++yz18xvaGigrq6O733ve7V6K/4mX/7y\nl9fU3bNnT3r06EHv3r3p3bv3Ordr3RitPVNdKjPvtS5tAnPnLqzpfQLq69t3+9eI4N5776W+vr7F\n+VOmTGHAgAFMmTKF8847r9X1rFq1im7duq132saaPXs2o0ePXueWr1dffTVXX301AJdccgkvvfQS\nU6ZM2STbBO+NoK7JHrnUxbQWVm+++Sa33347V111FS+++CJPPvnkmnmzZ8+mrq6OSZMmMWzYMA49\n9NAWp0HlyWg77LAD/fr1Y/To0WueevaHP/yBQYMGrbX9X/ziF+yzzz4t1rMxveNVq1Zx/PHHM2jQ\nIPr378+hhx7KzJkz12rzyiuvcMghh9C7d28++clPtnoo/e233+bcc89l6NChDB48mHPOOYd33nln\ng2uSOptBLr1P3HHHHfTq1YsTTjiBww8/nMmTJ6/T5je/+Q0vvPACU6dObXXakUceyUsvvcSrr77K\nRz/6UT7zmc8AsO+++7LtttvywAMPrFn2xhtvZPz48Zt0P8aOHctf/vIXXnnlFT70oQ9x+umnrzX/\nxhtv5NJLL2XRokXssssu68xv8rWvfY0FCxbwpz/9iRkzZjBz5kwuvfTSTVqr1BEMcqmLGTt2LP37\n96dfv37079+f66+/HqgcVj/ppJOICE4++WRuueUWVq1atWa5iOCSSy5hq622omfPnq1OGz9+PFtv\nvTVbbLEFF154IdOnT2fp0qUAnHbaafzsZz8DoLGxkalTpzJu3Lh1atzYQ9zdunXjlFNOYauttqJH\njx5861vf4vHHH2flypVr7f9+++1Hjx49mDhxIr/+9a95/fXX11rPqlWrmDRpEldccQW9evWiV69e\nnH/++dx8880bVZfUmQxyqYu56667aGxsZPHixTQ2NnLmmWcyb948pk2bxsknnwzApz/9ad566y3u\nvffetZbdaaed1llf9bTVq1czYcIEdtllF/r27cvOO+9MRLBo0SIATjnlFH71q1/x1ltvcdttt3Hw\nwQczcOBAAG6++eY1Xy723ntv5syZs9YXjnnz5q1331atWsU3vvENRowYQd++fdl9993JzLWCesiQ\nIWuG+/XrxzbbbLPO4fUFCxbwzjvv8OEPf5j+/fvTv39/xo4du2Y/pDIxyKUupqXe7pQpU8hMxowZ\nww477MCIESNYsWLFOofXWzpvXT3tpptu4p577uGhhx5iyZIlzJo1a82jVgEGDx7MAQccwB133MGN\nN97IqaeeumbZcePGrfly8cwzzzB06NC1vnC09CWiuRtuuIFf//rXPPzwwyxZsoQXXnhhnX2eO3fu\nmuHGxkaWL1/O4MGD11rPDjvswBZbbMFLL71EY2MjjY2NLFmyhFdffXW9NUibG4Nceh+YMmUKF198\nMU8//TTTp09n+vTp3H777dx7770sXrwYaPkLQPNpS5cupWfPnvTr14/ly5dzwQUXrBP+p556Kpdd\ndhnPPvtsm88w35jD60uXLmXLLbekX79+LFu2jH/8x39cp81dd93F448/zooVK/inf/on6uvrGTBg\nwFptunfvzhlnnMHZZ5+9pjc/d+5cHnzwwQ2uSepsBrnUxYwZM2at35EfcsghzJkzh6985Stsv/32\na15jxoxh1113XXNeeH29caicAx86dCg77rgje+65JwceeOA6yxx77LHMnj2b4447ji233LLVOjfm\nqvUzzzyTbbfdlkGDBrH33ntz8MEHr7POU045hfPPP5/tttuOGTNmrHXUoXqbl19+OYMHD2bfffel\nb9++ay7ik8rG55F3EJ9H3nW09EzqoUMHMXdu+37rvTGGDBnInDmv1Gz9m9ouu+zCddddxyGHHNLZ\npWxSPo9cnaWt55F7QxhpEyhTyNbaHXfcQV1dXZcLcWlzVfMgj4hZwBvAauCdzNwvIvoBtwLDgFnA\niZn5RtH+SuAIYDkwPjOfrnWNkjaN+vp6nn/+eW688cbOLkV63+iIHvlqYHRmLq6aNgF4MDMvi4jz\ngQuACRFxBDAiM3eNiI8B1wD7d0CNkjaBaWU6fyR1ER1xsVu0sJ1jgKYrUCYX403TpwBk5mNAn4gY\n2AE1SpJUSh0R5AlMjYjHI+JzxbSBmbkQIDNfAZrCekdgbtWy84tpkiSpBR1xaP3jmflyRGwHPBAR\nM6iEe7UNvgz04qrh0cVLkqSuoKGhgYaGhna1rXmQZ+bLxX9fi4g7gf2AhRExMDMXRsQgoOl2SvOB\nIVWL71RMW8fFtStZatOwYcN8rvX71LBhwzq7BL1PjB49mtGjR68Zv+SSS1ptW9Mgj4itgbrMXBYR\nHwAOBy4B7gbGA98t/ntXscjdwFeBWyNif2BJ0yF4aXMxa9aszi5BktaodY98IPDLiMhiW/+WmQ9E\nxB+A2yLiDGA2cCJAZt4XEUdGxJ+p/PzsszWuT5KkUvPObh3EO7tJkjZWW3d2817rkiSVmEEuSVKJ\nGeSSJJWYQS5JUokZ5JIklZhBLklSiRnkkiSVmEEuSVKJGeSSJJWYQS5JUokZ5JIklZhBLklSiRnk\nkiSVmEEuSVKJGeSSJJWYQS5JUokZ5JIklZhBLklSiRnkkiSVmEEuSVKJGeSSJJWYQS5JUokZ5JIk\nlZhBLklSiRnkkiSVmEEuSVKJGeSSJJWYQS5JUokZ5JIklZhBLklSiRnkkiSVmEEuSVKJGeSSJJWY\nQS5JUokZ5JIklZhBLklSiRnkkiSVWIcEeUTURcSTEXF3MT48Ih6NiJkRcXNEdC+m94iIWyLixYj4\nXUQM7Yj6JEkqq47qkZ8DPFc1/l3gXzJzJLAEOLOYfibQmJm7ApcDl3VQfZIklVLNgzwidgKOBP61\navIhwB3F8GRgbDF8TDEOcDtwaK3rkySpzDqiR/5D4DwgASJiALA4M1cX8+cBOxbDOwJzATJzFbAk\nIvp3QI2SJJVSTYM8Io4CFmbm00BUz2rvKjZ9VZIkdR3da7z+jwOfjogjga2AXsAVQJ+IqCt65TsB\n84v284EhwIKI6Ab0zszGllZ8cdXw6OIlSVJX0NDQQENDQ7vaRmbWtpqmDUWMAr6RmZ+OiFuBX2Tm\nrRFxNTA9M6+JiK8Ae2bmVyLiJGBsZp7Uwro6qOpNJ4Bp0zq7ivarr4eO+mxIktoWEWRmi0epO+t3\n5BOAr0fETKA/cH0x/Xpg24h4ETi3aCdJklrRYT3yTckeee3ZI5ekzcfm2COXJEmbgEEuSVKJGeSS\nJJWYQS5JUokZ5JIklZhBLklSiRnkkiSVmEEuSVKJGeSSJJWYQS5JUokZ5JIklZhBLklSiRnkkiSV\nmEEuSVKJGeSSJJWYQS5JUokZ5JIklZhBLklSiRnkkiSVmEEuSVKJGeSSJJWYQS5JUokZ5JIklZhB\nLklSiRnkkiSVmEEuSVKJGeSSJJWYQS5JUokZ5JIklZhBLklSiRnkkiSVmEEuSVKJGeSSJJWYQS5J\nUokZ5JIklZhBLklSiRnkkiSVWJtBHhFbRsTpEfHpqDg/In4VEVdExLYdVaQkSWrZ+nrkU4DDgTOA\nBmAo8GNgKfDT9a08InpGxGMR8VRE/DEiLiqmD4+IRyNiZkTcHBHdi+k9IuKWiHgxIn4XEUM3ftck\nSer6uq9n/h6ZuWcRtPMyc1Qx/f6ImL6+lWfmioioz8w3I6Ib8B8RcT/wdeBfMvPnEXE1cCZwbfHf\nxszcNSL+HrgMOGljd06SpK5ufT3ylQCZ+S6woNm8Ve3ZQGa+WQz2pPLFIYF64I5i+mRgbDF8TDEO\ncDtwaHu2IUnS+9X6euQ7RcSVQFQNU4zv2J4NREQd8AQwArgKeAlYkpmriybzqta1IzAXIDNXRcSS\niOifmY3t3SFJkt5P1hfk51UN/6HZvObjLSoC+yMR0Rv4JfCh9pdHtDbj4qrh0cVLkqSuoKGhgYaG\nhna1jcysbTXVG4v4FvAW8D+BQZm5OiL2By7KzCOK8+cXZeZjxTn1lzNz+xbW04FVbxoBTJvW2VW0\nX309dORnQ5LUuoggM1vs3LbZI4+Iu9uan5mfXs/y2wLvZOYbEbEV8EngUmAacAJwK3A6cFexyN3F\n+GPF/IfaWr8kSe936zu0fgCVc9Y3UwnXVg91t2IHYHJxnrwOuDUz74uI54FbIuI7wFPA9UX764Gf\nRcSLwOt4xbokSW1q89B6cXj7k8A4YC/gXuDmzPxTx5TXal2lO+jroXVJ0sZq69B6mz8/y8xVmXl/\nZp4O7A/8GWiIiLNqUKckSdpA6zu0TkT0BI6i0isfDlxJ5epzSZLUydZ3sdsUYE/gPuCSzHy2Q6qS\nJEntsr5z5KuB5cVodcMAMjN717C2VnmOvPY8Ry5Jm4+N/vlZZvqYU0mSNmMGtSRJJWaQS5JUYga5\nJEklZpBLklRiBrkkSSVmkEuSVGIGuSRJJWaQS5JUYga5JEklZpBLklRiBrkkSSVmkEuSVGIGuSRJ\nJWaQS5JUYga5JEklZpBLklRiBrkkSSVmkEuSVGIGuSRJJWaQS5JUYga5JEklZpBLklRiBrkkSSVm\nkEuSVGIGuSRJJWaQS5JUYga5JEklZpBLklRiBrkkSSVmkEuSVGIGuSRJJWaQS5JUYjUN8ojYKSIe\niog/RcQfI+LsYnq/iHggImZExNSI6FO1zJUR8WJEPB0R+9SyPkmSyq7WPfJ3ga9n5oeBA4CvRsSH\ngAnAg5m5G/AQcAFARBwBjMjMXYEvAtfUuD5JkkqtpkGema9k5tPF8DLgeWAn4BhgctFscjFO8d8p\nRfvHgD4RMbCWNUqSVGYddo48IoYD+wCPAgMzcyFUwh5oCusdgblVi80vpkmSpBZ0SJBHxDbA7cA5\nRc88mzVpPi5Jktqhe603EBHdqYT4zzLzrmLywogYmJkLI2IQ8GoxfT4wpGrxnYpp67i4anh08ZIk\nqStoaGigoaGhXW0js7ad4YiYAizKzK9XTfsu0JiZ342ICUDfzJwQEUcCX83MoyJif+DyzNy/hXXW\nuOpNL4Bp0zq7ivarr4dafzYkSe0TEWRmtDSvpj3yiPg48BngjxHxFJVD6P8AfBe4LSLOAGYDJwJk\n5n0RcWRE/BlYDny2lvVJklR2NQ3yzPwPoFsrsw9rZZmzaleRJEldi3d2kySpxAxySZJKzCCXJKnE\nDHJJkkrMIJckqcQMckmSSswglySpxAxySZJKzCCXJKnEDHJJkkrMIJckqcQMckmSSswglySpxAxy\nSZJKzCCXJKnEDHJJkkrMIJckqcQMckmSSswglySpxAxySZJKzCCXJKnEDHJJkkrMIJckqcQMckmS\nSswglySpxAxySZJKzCCXJKnEDHJJkkrMIJckqcQMckmSSswglySpxAxySZJKzCCXJKnEDHJJkkrM\nIJckqcQMckmSSswglySpxAxySZJKrKZBHhHXR8TCiHimalq/iHggImZExNSI6FM178qIeDEino6I\nfWpZmyRJXUGte+Q3AP+t2bQJwIOZuRvwEHABQEQcAYzIzF2BLwLX1Lg2SZJKr6ZBnpmPAIubTT4G\nmFwMTy7Gm6ZPKZZ7DOgTEQNrWZ8kSWXXGefIt8/MhQCZ+QrQFNY7AnOr2s0vpkmSpFZ07+wCgNyY\nhS6uGh5dvCRJ6goaGhpoaGhoV9vI3KgcbbeIGAbck5l7FePPA6Mzc2FEDAKmZebuEXFNMXxr0e4F\nYFRT773ZOmtc9aYXwLRpnV1F+9XXQ60/G5Kk9okIMjNamtcRh9ajeDW5GxhfDI8H7qqafhpAROwP\nLGkpxCUwHMWmAAAGY0lEQVRJ0ntqemg9Im6ictR7QETMAS4CLgV+HhFnALOBEwEy876IODIi/gws\nBz5by9okSeoKahrkmXlyK7MOa6X9WTUsR5KkLsc7u0mSVGIGuSRJJWaQS5JUYga5JEklZpBLklRi\nBrkkSSVmkEuSVGIGuSRJJWaQS5JUYga5JEklZpBLklRiBrkkSSVmkEuSVGIGuSRJJWaQS5JUYga5\nJEklZpBLklRiBrkkSSVmkEuSVGIGuSRJJWaQS5JUYga5JEklZpBLkv4mwwcNIiJK9Ro+aFBnv22b\nTPfOLkCSVG6zFy4kO7uIDRQLF3Z2CZuMPXJJkkrMIJckqcQMckmSSswglySpxAxySZJKzCCXJKnE\nDHJJkkrMIJckqcQMckmSSswglySpxAxyqROV7R7VXen+1FJX4b3WpU5UtntUd6X7U0tdhT1ySZJK\nbLML8oj4VES8EBEzI+L8zq5HkqTN2WZ1aD0i6oAfA4cCC4DHI+KuzHyhcyuTJHUl3bpBRHR2GZvE\nZhXkwH7Ai5k5GyAibgGOAQxySdIms2oVTJvW2VW0X3196/M2tyDfEZhbNT6PSrhL2gzU1ZWvFzNk\nyEDmzHmls8uQamZzC3JJm7HVq8vViwGor/dKe3Vtm1uQzweGVo3vVExbR7n6BBVtHRrZHJWt51VW\nZXuXy/Y5Bj/LHaGM73AZP8sticzN51esEdENmEHlYreXgd8D4zLz+U4tTJKkzdRm1SPPzFURcRbw\nAJWfxl1viEuS1LrNqkcuSZI2zGZ3Q5jOEhGrI+J7VePfiIgLN3AdR0TE4xHxbEQ8Ub0+aXMXEWMj\n4qmIeLJ4PRURqyLiSxHxx6LNf42Iy4vhiyLi651btVRRfFafjIinI+IPEbF/MX1Y0+e3qzLI37MC\nOC4i+m/MwhGxJ/Aj4OTM3BPYF/jzJqxvfdvv1lHbUteUmXdm5kcy86OZ+VHgJ8DDwP1QuSV8Zj6R\nmed2Zp1SK5YXn919gH8ALq2a16UPPRvk73kXuA5Yp4dRfKP7dfFN7/9GxE4tLH8e8M+Z+SJAVlzb\n1vIRcUNE/CQifhcRf46IURFxfUQ8FxGTqra/NCJ+UPT0/29EDCimT4uIH0bE74GzN/k7ovetiBgJ\nXAicStUfweIzek9V030i4j8jYkZEfK6j65SqVF843wdoXKdBRM+ImBQRzxRHTUcX0/eIiMeqevQj\niumnRcT04ujU5A7Zi41gkL8ngauAz0REr2bzfgTcUHzTu6kYb25P4IlW1t3W8n0z8wAqXyDuBv4l\nM/cA9oqIvYo2HwB+X/T0fwNcVLX8Fpm5X2b+sL07KrUlIroD/wZ8LTNb+vlnde/mvwCjgQOBCyPC\n55yqs2xVBPHzVDpl32mhzVeB1Zm5F3AyMDkiegBfAi4vjkTtC8yLiD2o9OxHZ+ZHgHM6ZC82gkFe\nJTOXAZNZ9x/sAODmYvhnwCc2cNXNl/941bym3s0fgVcy87li/E/A8GJ4NXBbMXxjs+3fuoG1SOvz\nz8CzmXl7O9relZkrM/N14CG8E6M6z5vFofXdgSOo/K1t7hNU/oaSmTOAWcBI4HfAP0bE/wSGZ+YK\n4BDg55m5uGi/pPa7sHEM8nVdAZxJpRfcpPn5lZbOtzxL5ZtcS9o6P7Oi+O/qquGm8dZ+Hli9vuVt\nrFvaIMWhxmOp9Fzao/qzGHTxc5Eqh8x8FNg2IrZdT9Mo2t8MjAHeAu5tOuROSe5zY5C/p+kfdDGV\n3u+ZVfP+ExhXDJ8C/LaF5b8PXBARu0LlSW4R8cUNWH5NDS2oA44vhj8DPNLmnkgbISL6AZOA0zLz\nzXYudkxE9Ciu2xgFPF6zAqW2rfn7GREfovJ38/VmbX5L5W9o03UgQ4AZEbFzZv4lM39E5RTnXlSO\nMB3fdAF08f/HZmmzuiFMJ6vuSfwLlR5J07SzgRsi4pvAa8Bn11k4848RcS5wc0RsVSz7q/Us31ZP\nv3mve7+I+BawEPj7VpaX/hZfBLYDri5uadrUw76ljWWeARqAAcC3M9Onk6izbBkRT/JeoJ+Wmdns\n9rw/ofL5fgZ4Bzg9M9+JiBMj4tRi2svA/8rMJRHxv4CHI+Jd4CngjA7bmw3gDWFKICKWZmbzC/Ak\nSfLQekn4bUuS1CJ75JIklZg9ckmSSswglySpxAxySZJKzCCXJKnEDHJJkkrMIJckqcT+P2gS6mtp\nr0N8AAAAAElFTkSuQmCC\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig, ax1 = plt.subplots()\n", "fig.set_size_inches(w=8, h=5)\n", "rects1 = ax1.bar(np.arange(3), np.array([550, 17, 42]), 0.25, color='r')\n", "rects2 = ax1.bar(np.arange(3) + 0.25, np.array([550, 4.1, 9]), 0.25, color='y')\n", "_ = ax1.set_ylabel('MB')\n", "_ = ax1.set_xticks(np.arange(3) + 0.25)\n", "_ = ax1.set_xticklabels(('No Compr', 'Zlib', 'Blosc'))\n", "_ = ax1.legend((rects1[0], rects2[0]), ('Single Table', 'EArray+Table'), loc=9)\n", "_ = ax1.set_title('Size for stored datasets')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conclusions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The main conclusion here is that, whenever you have a lot of data to dump (typically in the form of an array), a combination of an EArray + Table is preferred instead of a single Table. The reason for this is that HDF5 can store the former arrangement more efficiently, and that fast compressors like Blosc works way better too." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The deep explanation on why using an EArray to store the raw data gives these advanatges is because we are physically (not only logically!) separating data that is highly related (like the result of some measurements) and also is homogeneous (of type Int32 in this case). This manual separation is critical for getting better compression ratios and faster speeds too, specially when using fast compressors like Blosc." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Finally, although meaningful, this experiment is based on a pure synthetic dataset. It is always wise to use your own data in order to get your conclusions. It is specially recommended to have a look at the different compressors that comes with PyTables and see which one fits better to your needs: http://www.pytables.org/usersguide/libref/helper_classes.html" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 0 } PyTables-3.7.0/examples/add-column.py000066400000000000000000000041511416254111300174560ustar00rootroot00000000000000"Example showing how to add a column on a existing column" import tables as tb class Particle(tb.IsDescription): name = tb.StringCol(16, pos=1) # 16-character String lati = tb.Int32Col(pos=2) # integer longi = tb.Int32Col(pos=3) # integer pressure = tb.Float32Col(pos=4) # float (single-precision) temperature = tb.Float64Col(pos=5) # double (double-precision) # Open a file in "w"rite mode fileh = tb.open_file("add-column.h5", mode="w") # Create a new group group = fileh.create_group(fileh.root, "newgroup") # Create a new table in newgroup group table = fileh.create_table(group, 'table', Particle, "A table", tb.Filters(1)) # Append several rows table.append([("Particle: 10", 10, 0, 10 * 10, 10 ** 2), ("Particle: 11", 11, -1, 11 * 11, 11 ** 2), ("Particle: 12", 12, -2, 12 * 12, 12 ** 2)]) print("Contents of the original table:", fileh.root.newgroup.table[:]) # close the file fileh.close() # Open it again in append mode fileh = tb.open_file("add-column.h5", "a") group = fileh.root.newgroup table = group.table # Get a description of table in dictionary format descr = table.description._v_colobjects descr2 = descr.copy() # Add a column to description descr2["hot"] = tb.BoolCol(dflt=False) # Create a new table with the new description table2 = fileh.create_table(group, 'table2', descr2, "A table", tb.Filters(1)) # Copy the user attributes table.attrs._f_copy(table2) # Fill the rows of new table with default values for i in range(table.nrows): table2.row.append() # Flush the rows to disk table2.flush() # Copy the columns of source table to destination for col in descr: getattr(table2.cols, col)[:] = getattr(table.cols, col)[:] # Fill the new column table2.cols.hot[:] = [row["temperature"] > 11 ** 2 for row in table] # Remove the original table table.remove() # Move table2 to table table2.move('/newgroup', 'table') # Print the new table print("Contents of the table with column added:", fileh.root.newgroup.table[:]) # Finally, close the file fileh.close() PyTables-3.7.0/examples/array1.py000066400000000000000000000024761416254111300166420ustar00rootroot00000000000000import numpy as np import tables as tb # Open a new empty HDF5 file fileh = tb.open_file("array1.h5", mode="w") # Get the root group root = fileh.root # Create an Array a = np.array([-1, 2, 4], np.int16) # Save it on the HDF5 file hdfarray = fileh.create_array(root, 'array_1', a, "Signed short array") # Create a scalar Array a = np.array(4, np.int16) # Save it on the HDF5 file hdfarray = fileh.create_array(root, 'array_s', a, "Scalar signed short array") # Create a 3-d array of floats a = np.arange(120, dtype=np.float64).reshape(20, 3, 2) # Save it on the HDF5 file hdfarray = fileh.create_array(root, 'array_f', a, "3-D float array") # Close the file fileh.close() # Open the file for reading fileh = tb.open_file("array1.h5", mode="r") # Get the root group root = fileh.root a = root.array_1.read() print("Signed byte array -->", repr(a), a.shape) print("Testing iterator (works even over scalar arrays):", end=' ') arr = root.array_s for x in arr: print("nrow-->", arr.nrow) print("Element-->", repr(x)) # print "Testing getitem:" # for i in range(root.array_1.nrows): # print "array_1["+str(i)+"]", "-->", root.array_1[i] print("array_f[:,2:3,2::2]", repr(root.array_f[:, 2:3, 2::2])) print("array_f[1,2:]", repr(root.array_f[1, 2:])) print("array_f[1]", repr(root.array_f[1])) # Close the file fileh.close() PyTables-3.7.0/examples/array2.py000066400000000000000000000017141416254111300166350ustar00rootroot00000000000000import numpy as np import tables as tb # Open a new empty HDF5 file fileh = tb.open_file("array2.h5", mode="w") # Shortcut to the root group root = fileh.root # Create an array a = np.array([1, np.e, np.pi], float) print("About to write array:", a) print(" with shape: ==>", a.shape) print(" and dtype ==>", a.dtype) # Save it on the HDF5 file hdfarray = fileh.create_array(root, 'carray', a, "Float array") # Get metadata on the previously saved array print() print("Info on the object:", repr(root.carray)) # Close the file fileh.close() # Open the previous HDF5 file in read-only mode fileh = tb.open_file("array2.h5", mode="r") # Get the root group root = fileh.root # Get metadata on the previously saved array print() print("Info on the object:", repr(root.carray)) # Get the actual array b = root.carray.read() print() print("Array read from file:", b) print(" with shape: ==>", b.shape) print(" and dtype ==>", b.dtype) # Close the file fileh.close() PyTables-3.7.0/examples/array3.py000066400000000000000000000021531416254111300166340ustar00rootroot00000000000000import numpy as np import tables as tb # Open a new empty HDF5 file fileh = tb.open_file("array3.h5", mode="w") # Get the root group root = fileh.root # Create a large array # a = reshape(array(range(2**16), "s"), (2,) * 16) a = np.ones((2,) * 8, np.int8) print("About to write array a") print(" with shape: ==>", a.shape) print(" and dtype: ==>", a.dtype) # Save it on the HDF5 file hdfarray = fileh.create_array(root, 'carray', a, "Large array") # Get metadata on the previously saved array print() print("Info on the object:", repr(root.carray)) # Close the file fileh.close() # Open the previous HDF5 file in read-only mode fileh = tb.open_file("array3.h5", mode="r") # Get the root group root = fileh.root # Get metadata on the previously saved array print() print("Getting info on retrieved /carray object:", repr(root.carray)) # Get the actual array # b = fileh.readArray("/carray") # You can obtain the same result with: b = root.carray.read() print() print("Array b read from file") print(" with shape: ==>", b.shape) print(" with dtype: ==>", b.dtype) # print " contents:", b # Close the file fileh.close() PyTables-3.7.0/examples/array4.py000066400000000000000000000030561416254111300166400ustar00rootroot00000000000000import numpy as np import tables as tb basedim = 4 file = "array4.h5" # Open a new empty HDF5 file fileh = tb.open_file(file, mode="w") # Get the root group group = fileh.root # Set the type codes to test dtypes = [np.int8, np.uint8, np.int16, int, np.float32, float] for i, dtype in enumerate(dtypes, 1): # Create an array of dtype, with incrementally bigger ranges a = np.ones((basedim,) * i, dtype) # Save it on the HDF5 file dsetname = f'array_{a.dtype.char}' hdfarray = fileh.create_array(group, dsetname, a, "Large array") print(f"Created dataset: {hdfarray}") # Create a new group group = fileh.create_group(group, f'group{i}') # Close the file fileh.close() # Open the previous HDF5 file in read-only mode fileh = tb.open_file(file, mode="r") # Get the root group group = fileh.root # Get the metadata on the previosly saved arrays for i, dtype in enumerate(dtypes, 1): # Create an array for later comparison a = np.ones((basedim,) * i, dtype) # Get the dset object hangin from group dset = getattr(group, 'array_' + a.dtype.char) print(f"Info from dataset: {dset!r}") # Read the actual data in array b = dset.read() print(f"Array b read from file. Shape ==> {b.shape}. Dtype ==> {b.dtype}") # Test if the original and read arrays are equal if np.allclose(a, b): print("Good: Read array is equal to the original") else: print("Error: Read array and the original differs!") # Iterate over the next group group = getattr(group, f'group{i}') # Close the file fileh.close() PyTables-3.7.0/examples/attributes1.py000066400000000000000000000011741416254111300177040ustar00rootroot00000000000000import numpy as np import tables as tb # Open a new empty HDF5 file fileh = tb.open_file("attributes1.h5", mode="w", title="Testing attributes") # Get the root group root = fileh.root # Create an array a = np.array([1, 2, 4], np.int32) # Save it on the HDF5 file hdfarray = fileh.create_array(root, 'array', a, "Integer array") # Assign user attributes # A string hdfarray.attrs.string = "This is an example" # A Char hdfarray.attrs.char = "1" # An integer hdfarray.attrs.int = 12 # A float hdfarray.attrs.float = 12.32 # A generic object hdfarray.attrs.object = {"a": 32.1, "b": 1, "c": [1, 2]} # Close the file fileh.close() PyTables-3.7.0/examples/attrs-with-padding.py000066400000000000000000000022731416254111300211500ustar00rootroot00000000000000# This is an example on how to use complex columns import numpy as np import tables as tb dt = np.dtype('i4,f8', align=True) # Create a file with regular padding print("attrs *with* padding:") fileh = tb.open_file("attrs-with-padding.h5", mode="w", pytables_sys_attrs=False) attrs = fileh.root._v_attrs # Set some attrs attrs.pq = np.zeros(2, dt) attrs.qr = np.ones((2, 2), dt) attrs.rs = np.array([(1, 2)], dt) print("repr(attrs)-->", repr(attrs)) fileh.close() # Create a file with no padding print("\nattrs *without* padding:") fileh = tb.open_file("attrs-without-padding.h5", mode="w", pytables_sys_attrs=False, allow_padding=False) attrs = fileh.root._v_attrs # Set some attrs attrs.pq = np.zeros(2, dt) attrs.qr = np.ones((2, 2), dt) attrs.rs = np.array([(1, 2)], dt) print("repr(attrs)-->", repr(attrs)) fileh.close() print("\n ***After closing***\n") print("attrs *with* padding:") fileh = tb.open_file("attrs-with-padding.h5", mode="r") attrs = fileh.root._v_attrs print("repr(attrs)-->", repr(attrs)) fileh.close() print("\nattrs *without* padding:") fileh = tb.open_file("attrs-without-padding.h5", mode="r") attrs = fileh.root._v_attrs print("repr(attrs)-->", repr(attrs)) fileh.close() PyTables-3.7.0/examples/carray1.py000066400000000000000000000007241416254111300167770ustar00rootroot00000000000000import numpy as np import tables as tb fileName = 'carray1.h5' shape = (200, 300) atom = tb.UInt8Atom() filters = tb.Filters(complevel=5, complib='zlib') h5f = tb.open_file(fileName, 'w') ca = h5f.create_carray(h5f.root, 'carray', atom, shape, filters=filters) # Fill a hyperslab in ``ca``. ca[10:60, 20:70] = np.ones((50, 50)) h5f.close() # Re-open and read another hyperslab h5f = tb.open_file(fileName) print(h5f) print(h5f.root.carray[8:12, 18:22]) h5f.close() PyTables-3.7.0/examples/check_examples.sh000077500000000000000000000022121416254111300203670ustar00rootroot00000000000000#!/bin/sh # # Small script to check the example repository quickly # CONFIGURATION - interpreter to use PYTHON=python # exit on non-zero return status set -e for script in \ add-column.py \ array1.py \ array2.py \ array3.py \ array4.py \ attributes1.py \ carray1.py \ earray1.py \ earray2.py \ index.py \ inmemory.py \ links.py \ nested1.py \ nested-tut.py \ particles.py \ read_array_out_arg.py \ split.py \ table1.py \ table2.py \ table3.py \ tutorial1-1.py \ tutorial1-2.py \ tutorial3-1.py \ tutorial3-2.py \ undo-redo.py \ vlarray1.py \ vlarray2.py \ vlarray3.py \ vlarray4.py do $PYTHON "$script" done #TO DEBUG: #--------- python2.7 works #--------- python3.4 DON'T WORK # filenodes1.py # multiprocess_access_queues.py # multiprocess_access_benchmarks.py # objecttree.py # table-tree.py #--------- python2.7 DON'T WORK #--------- python3.4 DON'T WORK # enum.py # This should always fail # nested-iter.py # Run this after "tutorial1-1.py" (file missing) # tutorial2.py # This should always fail at the beginning PyTables-3.7.0/examples/earray1.py000066400000000000000000000010101416254111300167660ustar00rootroot00000000000000import tables as tb import numpy as np fileh = tb.open_file('earray1.h5', mode='w') a = tb.StringAtom(itemsize=8) # Use ``a`` as the object type for the enlargeable array. array_c = fileh.create_earray(fileh.root, 'array_c', a, (0,), "Chars") array_c.append(np.array(['a' * 2, 'b' * 4], dtype='S8')) array_c.append(np.array(['a' * 6, 'b' * 8, 'c' * 10], dtype='S8')) # Read the string ``EArray`` we have created on disk. for s in array_c: print(f'array_c[{array_c.nrow}] => {s!r}') # Close the file. fileh.close() PyTables-3.7.0/examples/earray2.py000066400000000000000000000047561416254111300170130ustar00rootroot00000000000000#!/usr/bin/env python3 """Small example that shows how to work with extendeable arrays of different types, strings included.""" import numpy as np import tables as tb # Open a new empty HDF5 file filename = "earray2.h5" fileh = tb.open_file(filename, mode="w") # Get the root group root = fileh.root # Create an string atom a = tb.StringAtom(itemsize=1) # Use it as a type for the enlargeable array hdfarray = fileh.create_earray(root, 'array_c', a, (0,), "Character array") hdfarray.append(np.array(['a', 'b', 'c'])) # The next is legal: hdfarray.append(np.array(['c', 'b', 'c', 'd'])) # but these are not: # hdfarray.append(array([['c', 'b'], ['c', 'd']])) # hdfarray.append(array([[1,2,3],[3,2,1]], dtype=uint8).reshape(2,1,3)) # Create an atom a = tb.UInt16Atom() hdfarray = fileh.create_earray(root, 'array_e', a, (2, 0, 3), "Unsigned short array") # Create an enlargeable array a = tb.UInt8Atom() hdfarray = fileh.create_earray(root, 'array_b', a, (2, 0, 3), "Unsigned byte array", tb.Filters(complevel=1)) # Append an array to this table hdfarray.append( np.array([[1, 2, 3], [3, 2, 1]], dtype=np.uint8).reshape(2, 1, 3)) hdfarray.append( np.array([[1, 2, 3], [3, 2, 1], [2, 4, 6], [6, 4, 2]], dtype=np.uint8).reshape(2, 2, 3) * 2) # The next should give a type error: # hdfarray.append(array([[1,0,1],[0,0,1]], dtype=Bool).reshape(2,1,3)) # Close the file fileh.close() # Open the file for reading fileh = tb.open_file(filename, mode="r") # Get the root group root = fileh.root a = root.array_c.read() print("Character array -->", repr(a), a.shape) a = root.array_e.read() print("Empty array (yes, this is suported) -->", repr(a), a.shape) a = root.array_b.read(step=2) print("Int8 array, even rows (step = 2) -->", repr(a), a.shape) print("Testing iterator:", end=' ') # for x in root.array_b.iterrows(step=2): for x in root.array_b: print("nrow-->", root.array_b.nrow) print("Element-->", x) print("Testing getitem:") for i in range(root.array_b.shape[0]): print("array_b[" + str(i) + "]", "-->", root.array_b[i]) # The nrows counts the growing dimension, which is different from the # first index for i in range(root.array_b.nrows): print("array_b[:," + str(i) + ",:]", "-->", root.array_b[:, i, :]) print("array_c[1:2]", repr(root.array_c[1:2])) print("array_c[1:3]", repr(root.array_c[1:3])) print("array_b[:]", root.array_b[:]) print(repr(root.array_c)) # Close the file fileh.close() PyTables-3.7.0/examples/filenodes1.py000066400000000000000000000021521416254111300174630ustar00rootroot00000000000000import tables as tb h5file = tb.open_file('fnode.h5', 'w') fnode = tb.nodes.filenode.new_node(h5file, where='/', name='fnode_test') print(h5file.get_node_attr('/fnode_test', 'NODE_TYPE')) print("This is a test text line.", file=fnode) print("And this is another one.", file=fnode) print(file=fnode) fnode.write("Of course, file methods can also be used.") fnode.seek(0) # Go back to the beginning of file. for line in fnode: print(repr(line)) fnode.close() print(fnode.closed) node = h5file.root.fnode_test fnode = filenode.open_node(node, 'a+') print(repr(fnode.readline())) print(fnode.tell()) print("This is a new line.", file=fnode) print(repr(fnode.readline())) fnode.seek(0) for line in fnode: print(repr(line)) fnode.attrs.content_type = 'text/plain; charset=us-ascii' fnode.attrs.author = "Ivan Vilata i Balaguer" fnode.attrs.creation_date = '2004-10-20T13:25:25+0200' fnode.attrs.keywords_en = ["FileNode", "test", "metadata"] fnode.attrs.keywords_ca = ["FileNode", "prova", "metadades"] fnode.attrs.owner = 'ivan' fnode.attrs.acl = {'ivan': 'rw', '@users': 'r'} fnode.close() h5file.close() PyTables-3.7.0/examples/index.py000066400000000000000000000021131416254111300165360ustar00rootroot00000000000000import random import tables as tb print('tables.__version__', tb.__version__) nrows = 10_000 - 1 class Distance(tb.IsDescription): frame = tb.Int32Col(pos=0) distance = tb.Float64Col(pos=1) h5file = tb.open_file('index.h5', mode='w') table = h5file.create_table(h5file.root, 'distance_table', Distance, 'distance table', expectedrows=nrows) row = table.row for i in range(nrows): # r['frame'] = nrows-i row['frame'] = random.randint(0, nrows) row['distance'] = float(i ** 2) row.append() table.flush() table.cols.frame.create_index(optlevel=9, _testmode=True, _verbose=True) # table.cols.frame.optimizeIndex(level=5, verbose=1) results = [r.nrow for r in table.where('frame < 2')] print("frame<2 -->", table.read_coordinates(results)) # print("frame<2 -->", table.get_where_list('frame < 2')) results = [r.nrow for r in table.where('(1 < frame) & (frame <= 5)')] print("rows-->", results) print("1", table.read_coordinates(results)) # print("1", table.get_where_list('(1 < frame) & (frame <= 5)')) h5file.close() PyTables-3.7.0/examples/inmemory.py000066400000000000000000000026471416254111300173020ustar00rootroot00000000000000#!/usr/bin/env python3 """inmemory.py. Example usage of creating in-memory HDF5 file with a specified chunksize using PyTables 3.0.0+ See also Cookbook page http://pytables.github.io/cookbook/inmemory_hdf5_files.html and available drivers http://pytables.github.io/usersguide/parameter_files.html#hdf5-driver-management """ import numpy as np import tables as tb CHUNKY = 30 CHUNKX = 4320 if __name__ == '__main__': # create dataset and add global attrs file_path = 'demofile_chunk%sx%d.h5' % (CHUNKY, CHUNKX) with tb.open_file(file_path, 'w', title='PyTables HDF5 In-memory example', driver='H5FD_CORE') as h5f: # dummy some data lats = np.empty([2160]) lons = np.empty([4320]) # create some simple arrays lat_node = h5f.create_array('/', 'lat', lats, title='latitude') lon_node = h5f.create_array('/', 'lon', lons, title='longitude') # create a 365 x 4320 x 8640 CArray of 32bit float shape = (5, 2160, 4320) atom = tb.Float32Atom(dflt=np.nan) # chunk into daily slices and then further chunk days sst_node = h5f.create_carray( h5f.root, 'sst', atom, shape, chunkshape=(1, CHUNKY, CHUNKX)) # dummy up an ndarray sst = np.empty([2160, 4320], dtype=np.float32) sst.fill(30.0) # write ndarray to a 2D plane in the HDF5 sst_node[0] = sst PyTables-3.7.0/examples/links.py000066400000000000000000000032401416254111300165510ustar00rootroot00000000000000import tables as tb # Create a new file with some structural groups f1 = tb.open_file('links1.h5', 'w') g1 = f1.create_group('/', 'g1') g2 = f1.create_group(g1, 'g2') # Create some datasets a1 = f1.create_carray(g1, 'a1', tb.Int64Atom(), shape=(10_000,)) t1 = f1.create_table(g2, 't1', {'f1': tb.IntCol(), 'f2': tb.FloatCol()}) # Create new group and a first hard link gl = f1.create_group('/', 'gl') ht = f1.create_hard_link(gl, 'ht', '/g1/g2/t1') # ht points to t1 print(f"``{ht}`` is a hard link to: ``{t1}``") # Remove the orginal link to the t1 table t1.remove() print("table continues to be accessible in: ``%s``" % f1.get_node('/gl/ht')) # Let's continue with soft links la1 = f1.create_soft_link(gl, 'la1', '/g1/a1') # la1 points to a1 print(f"``{la1}`` is a soft link to: ``{la1.target}``") lt = f1.create_soft_link(gl, 'lt', '/g1/g2/t1') # lt points to t1 (dangling) print(f"``{lt}`` is a soft link to: ``{lt.target}``") # Recreate the '/g1/g2/t1' path t1 = f1.create_hard_link('/g1/g2', 't1', '/gl/ht') print(f"``{lt}`` is not dangling anymore") # Dereferrencing plt = lt() print("dereferred lt node: ``%s``" % plt) pla1 = la1() print("dereferred la1 node: ``%s``" % pla1) # Copy the array a1 into another file f2 = tb.open_file('links2.h5', 'w') new_a1 = a1.copy(f2.root, 'a1') f2.close() # close the other file # Remove the original soft link and create an external link la1.remove() la1 = f1.create_external_link(gl, 'la1', 'links2.h5:/a1') print(f"``{la1}`` is an external link to: ``{la1.target}``") new_a1 = la1() # dereferrencing la1 returns a1 in links2.h5 print("dereferred la1 node: ``%s``" % new_a1) print("new_a1 file:", new_a1._v_file.filename) f1.close() PyTables-3.7.0/examples/multiprocess_access_benchmarks.py000066400000000000000000000217211416254111300237040ustar00rootroot00000000000000# Benchmark three methods of using PyTables with multiple processes, where data # is read from a PyTables file in one process and then sent to another # # 1. using multiprocessing.Pipe # 2. using a memory mapped file that's shared between two processes, passed as # out argument to tables.Array.read. # 3. using a Unix domain socket (this uses the "abstract namespace" and will # work only on Linux). # 4. using an IPv4 socket # # In all three cases, an array is loaded from a file in one process, sent to # another, and then modified by incrementing each array element. This is meant # to simulate retrieving data and then modifying it. import multiprocessing import os import random import select import socket import time from time import perf_counter as clock import numpy as np import tables as tb # create a PyTables file with a single int64 array with the specified number of # elements def create_file(array_size): array = np.ones(array_size, dtype='i8') with tb.open_file('test.h5', 'w') as fobj: array = fobj.create_array('/', 'test', array) print('file created, size: {} MB'.format(array.size_on_disk / 1e6)) # process to receive an array using a multiprocessing.Pipe connection class PipeReceive(multiprocessing.Process): def __init__(self, receiver_pipe, result_send): super().__init__() self.receiver_pipe = receiver_pipe self.result_send = result_send def run(self): # block until something is received on the pipe array = self.receiver_pipe.recv() recv_timestamp = clock() # perform an operation on the received array array += 1 finish_timestamp = clock() assert(np.all(array == 2)) # send the measured timestamps back to the originating process self.result_send.send((recv_timestamp, finish_timestamp)) def read_and_send_pipe(send_type, array_size): # set up Pipe objects to send the actual array to the other process # and receive the timing results from the other process array_recv, array_send = multiprocessing.Pipe(False) result_recv, result_send = multiprocessing.Pipe(False) # start the other process and pause to allow it to start up recv_process = PipeReceive(array_recv, result_send) recv_process.start() time.sleep(0.15) with tb.open_file('test.h5', 'r') as fobj: array = fobj.get_node('/', 'test') start_timestamp = clock() # read an array from the PyTables file and send it to the other process output = array.read(0, array_size, 1) array_send.send(output) assert(np.all(output + 1 == 2)) # receive the timestamps from the other process recv_timestamp, finish_timestamp = result_recv.recv() print_results(send_type, start_timestamp, recv_timestamp, finish_timestamp) recv_process.join() # process to receive an array using a shared memory mapped file # for real use, this would require creating some protocol to specify the # array's data type and shape class MemmapReceive(multiprocessing.Process): def __init__(self, path_recv, result_send): super().__init__() self.path_recv = path_recv self.result_send = result_send def run(self): # block until the memmap file path is received from the other process path = self.path_recv.recv() # create a memmap array using the received file path array = np.memmap(path, 'i8', 'r+') recv_timestamp = clock() # perform an operation on the array array += 1 finish_timestamp = clock() assert(np.all(array == 2)) # send the timing results back to the other process self.result_send.send((recv_timestamp, finish_timestamp)) def read_and_send_memmap(send_type, array_size): # create a multiprocessing Pipe that will be used to send the memmap # file path to the receiving process path_recv, path_send = multiprocessing.Pipe(False) result_recv, result_send = multiprocessing.Pipe(False) # start the receiving process and pause to allow it to start up recv_process = MemmapReceive(path_recv, result_send) recv_process.start() time.sleep(0.15) with tb.open_file('test.h5', 'r') as fobj: array = fobj.get_node('/', 'test') start_timestamp = clock() # memmap a file as a NumPy array in 'overwrite' mode output = np.memmap('/tmp/array1', 'i8', 'w+', shape=(array_size, )) # read an array from a PyTables file into the memmory mapped array array.read(0, array_size, 1, out=output) # use a multiprocessing.Pipe to send the file's path to the receiving # process path_send.send('/tmp/array1') # receive the timestamps from the other process recv_timestamp, finish_timestamp = result_recv.recv() # because 'output' is shared between processes, all elements should now # be equal to 2 assert(np.all(output == 2)) print_results(send_type, start_timestamp, recv_timestamp, finish_timestamp) recv_process.join() # process to receive an array using a socket # for real use, this would require creating some protocol to specify the # array's data type and shape class SocketReceive(multiprocessing.Process): def __init__(self, socket_family, address, result_send, array_nbytes): super().__init__() self.socket_family = socket_family self.address = address self.result_send = result_send self.array_nbytes = array_nbytes def run(self): # create the socket, listen for a connection and use select to block # until a connection is made sock = socket.socket(self.socket_family, socket.SOCK_STREAM) sock.bind(self.address) sock.listen(1) readable, _, _ = select.select([sock], [], []) # accept the connection and read the sent data into a bytearray connection = sock.accept()[0] recv_buffer = bytearray(self.array_nbytes) view = memoryview(recv_buffer) bytes_recv = 0 while bytes_recv < self.array_nbytes: bytes_recv += connection.recv_into(view[bytes_recv:]) # convert the bytearray into a NumPy array array = np.frombuffer(recv_buffer, dtype='i8') recv_timestamp = clock() # perform an operation on the received array array += 1 finish_timestamp = clock() assert(np.all(array == 2)) # send the timestamps back to the originating process self.result_send.send((recv_timestamp, finish_timestamp)) connection.close() sock.close() def unix_socket_address(): # create a Unix domain address in the abstract namespace # this will only work on Linux return b'\x00' + os.urandom(5) def ipv4_socket_address(): # create an IPv4 socket address return ('127.0.0.1', random.randint(9000, 10_000)) def read_and_send_socket(send_type, array_size, array_bytes, address_func, socket_family): address = address_func() # start the receiving process and pause to allow it to start up result_recv, result_send = multiprocessing.Pipe(False) recv_process = SocketReceive(socket_family, address, result_send, array_bytes) recv_process.start() time.sleep(0.15) with tb.open_file('test.h5', 'r') as fobj: array = fobj.get_node('/', 'test') start_timestamp = clock() # connect to the receiving process' socket sock = socket.socket(socket_family, socket.SOCK_STREAM) sock.connect(address) # read the array from the PyTables file and send its # data buffer to the receiving process output = array.read(0, array_size, 1) sock.send(output.data) assert(np.all(output + 1 == 2)) # receive the timestamps from the other process recv_timestamp, finish_timestamp = result_recv.recv() sock.close() print_results(send_type, start_timestamp, recv_timestamp, finish_timestamp) recv_process.join() def print_results(send_type, start_timestamp, recv_timestamp, finish_timestamp): msg = 'type: {0}\t receive: {1:5.5f}, add:{2:5.5f}, total: {3:5.5f}' print(msg.format(send_type, recv_timestamp - start_timestamp, finish_timestamp - recv_timestamp, finish_timestamp - start_timestamp)) if __name__ == '__main__': random.seed(os.urandom(2)) array_num_bytes = [10**5, 10**6, 10**7, 10**8] for array_bytes in array_num_bytes: array_size = array_bytes // 8 create_file(array_size) read_and_send_pipe('multiproc.Pipe', array_size) read_and_send_memmap('memmap ', array_size) # comment out this line to run on an OS other than Linux read_and_send_socket('Unix socket', array_size, array_bytes, unix_socket_address, socket.AF_UNIX) read_and_send_socket('IPv4 socket', array_size, array_bytes, ipv4_socket_address, socket.AF_INET) print() PyTables-3.7.0/examples/multiprocess_access_queues.py000066400000000000000000000141621416254111300230770ustar00rootroot00000000000000"""Example showing how to access a PyTables file from multiple processes using queues.""" import queue import multiprocessing import random import time from pathlib import Path import numpy as np import tables as tb # this creates an HDF5 file with one array containing n rows def make_file(file_path, n): with tb.open_file(file_path, 'w') as fobj: array = fobj.create_carray('/', 'array', tb.Int64Atom(), (n, n)) for i in range(n): array[i, :] = i # All access to the file goes through a single instance of this class. # It contains several queues that are used to communicate with other # processes. # The read_queue is used for requests to read data from the HDF5 file. # A list of result_queues is used to send data back to client processes. # The write_queue is used for requests to modify the HDF5 file. # One end of a pipe (shutdown) is used to signal the process to terminate. class FileAccess(multiprocessing.Process): def __init__(self, h5_path, read_queue, result_queues, write_queue, shutdown): self.h5_path = h5_path self.read_queue = read_queue self.result_queues = result_queues self.write_queue = write_queue self.shutdown = shutdown self.block_period = .01 super().__init__() def run(self): self.h5_file = tb.open_file(self.h5_path, 'r+') self.array = self.h5_file.get_node('/array') another_loop = True while another_loop: # Check if the process has received the shutdown signal. if self.shutdown.poll(): another_loop = False # Check for any data requests in the read_queue. try: row_num, proc_num = self.read_queue.get( True, self.block_period) # look up the appropriate result_queue for this data processor # instance result_queue = self.result_queues[proc_num] print('processor {} reading from row {}'.format(proc_num, row_num)) result_queue.put(self.read_data(row_num)) another_loop = True except queue.Empty: pass # Check for any write requests in the write_queue. try: row_num, data = self.write_queue.get(True, self.block_period) print('writing row', row_num) self.write_data(row_num, data) another_loop = True except queue.Empty: pass # close the HDF5 file before shutting down self.h5_file.close() def read_data(self, row_num): return self.array[row_num, :] def write_data(self, row_num, data): self.array[row_num, :] = data # This class represents a process that does work by reading and writing to the # HDF5 file. It does this by sending requests to the FileAccess class instance # through its read and write queues. The data results are sent back through # the result_queue. # Its actions are logged to a text file. class DataProcessor(multiprocessing.Process): def __init__(self, read_queue, result_queue, write_queue, proc_num, array_size, output_file): self.read_queue = read_queue self.result_queue = result_queue self.write_queue = write_queue self.proc_num = proc_num self.array_size = array_size self.output_file = output_file super().__init__() def run(self): self.output_file = open(self.output_file, 'w') # read a random row from the file row_num = random.randrange(self.array_size) self.read_queue.put((row_num, self.proc_num)) self.output_file.write(str(row_num) + '\n') self.output_file.write(str(self.result_queue.get()) + '\n') # modify a random row to equal 11 * (self.proc_num + 1) row_num = random.randrange(self.array_size) new_data = (np.zeros((1, self.array_size), 'i8') + 11 * (self.proc_num + 1)) self.write_queue.put((row_num, new_data)) # pause, then read the modified row time.sleep(0.015) self.read_queue.put((row_num, self.proc_num)) self.output_file.write(str(row_num) + '\n') self.output_file.write(str(self.result_queue.get()) + '\n') self.output_file.close() # this function starts the FileAccess class instance and # sets up all the queues used to communicate with it def make_queues(num_processors): read_queue = multiprocessing.Queue() write_queue = multiprocessing.Queue() shutdown_recv, shutdown_send = multiprocessing.Pipe(False) result_queues = [multiprocessing.Queue() for i in range(num_processors)] file_access = FileAccess(file_path, read_queue, result_queues, write_queue, shutdown_recv) file_access.start() return read_queue, result_queues, write_queue, shutdown_send if __name__ == '__main__': # See the discussion in :issue:`790`. multiprocessing.set_start_method('spawn') file_path = 'test.h5' n = 10 make_file(file_path, n) num_processors = 3 (read_queue, result_queues, write_queue, shutdown_send) = make_queues(num_processors) processors = [] output_files = [] for i in range(num_processors): result_queue = result_queues[i] output_file = str(i) processor = DataProcessor(read_queue, result_queue, write_queue, i, n, output_file) processors.append(processor) output_files.append(output_file) # start all DataProcessor instances for processor in processors: processor.start() # wait for all DataProcessor instances to finish for processor in processors: processor.join() # shut down the FileAccess instance shutdown_send.send(0) # print out contents of log files and delete them print() for output_file in output_files: print() print(f'contents of log file {output_file}') print(open(output_file).read()) Path(output_file).unlink() Path('test.h5').unlink() PyTables-3.7.0/examples/nested-tut.py000066400000000000000000000070341416254111300175320ustar00rootroot00000000000000"""Small example showing the use of nested types in PyTables. The program creates an output file, 'nested-tut.h5'. You can view it with ptdump or any HDF5 generic utility. :Author: F. Alted :Date: 2005/06/10 """ import numpy as np import tables as tb #'-**-**-**-**- The sample nested class description -**-**-**-**-**-' class Info(tb.IsDescription): """A sub-structure of Test""" _v_pos = 2 # The position in the whole structure name = tb.StringCol(10) value = tb.Float64Col(pos=0) colors = tb.Enum(['red', 'green', 'blue']) class NestedDescr(tb.IsDescription): """A description that has several nested columns.""" color = tb.EnumCol(colors, 'red', base='uint32') info1 = Info() class info2(tb.IsDescription): _v_pos = 1 name = tb.StringCol(10) value = tb.Float64Col(pos=0) class info3(tb.IsDescription): x = tb.Float64Col(dflt=1) y = tb.UInt8Col(dflt=1) print() print('-**-**-**-**-**-**- file creation -**-**-**-**-**-**-**-') filename = "nested-tut.h5" print("Creating file:", filename) fileh = tb.open_file(filename, "w") print() print('-**-**-**-**-**- nested table creation -**-**-**-**-**-') table = fileh.create_table(fileh.root, 'table', NestedDescr) # Fill the table with some rows row = table.row for i in range(10): row['color'] = colors[['red', 'green', 'blue'][i % 3]] row['info1/name'] = "name1-%s" % i row['info2/name'] = "name2-%s" % i row['info2/info3/y'] = i # All the rest will be filled with defaults row.append() table.flush() # flush the row buffer to disk print(repr(table.nrows)) nra = table[::4] print(repr(nra)) # Append some additional rows table.append(nra) print(repr(table.nrows)) # Create a new table table2 = fileh.create_table(fileh.root, 'table2', nra) print(repr(table2[:])) # Read also the info2/name values with color == colors.red names = [x['info2/name'] for x in table if x['color'] == colors.red] print() print("**** info2/name elements satisfying color == 'red':", repr(names)) print() print('-**-**-**-**-**-**- table data reading & selection -**-**-**-**-**-') # Read the data print() print("**** table data contents:\n", table[:]) print() print("**** table.info2 data contents:\n", repr(table.cols.info2[1:5])) print() print("**** table.info2.info3 data contents:\n", repr(table.cols.info2.info3[1:5])) print("**** _f_col() ****") print(repr(table.cols._f_col('info2'))) print(repr(table.cols._f_col('info2/info3/y'))) print() print('-**-**-**-**-**-**- table metadata -**-**-**-**-**-') # Read description metadata print() print("**** table description (short):\n", repr(table.description)) print() print("**** more from manual, period ***") print(repr(table.description.info1)) print(repr(table.description.info2.info3)) print(repr(table.description._v_nested_names)) print(repr(table.description.info1._v_nested_names)) print() print("**** now some for nested records, take that ****") print(repr(table.description._v_nested_descr)) print(repr(np.rec.array(None, shape=0, dtype=table.description._v_nested_descr))) print(repr(np.rec.array(None, shape=0, dtype=table.description.info2._v_nested_descr))) print() print("**** and some iteration over descriptions, too ****") for coldescr in table.description._f_walk(): print("column-->", coldescr) print() print("**** info2 sub-structure description:\n", table.description.info2) print() print("**** table representation (long form):\n", repr(table)) # Remember to always close the file fileh.close() PyTables-3.7.0/examples/nested1.py000066400000000000000000000043351416254111300170020ustar00rootroot00000000000000# Example to show how nested types can be dealed with PyTables # F. Alted 2005/05/27 import random import tables as tb fileout = "nested1.h5" # An example of enumerated structure colors = tb.Enum(['red', 'green', 'blue']) def read(file): fileh = tb.open_file(file, "r") print("table (short)-->", fileh.root.table) print("table (long)-->", repr(fileh.root.table)) print("table (contents)-->", repr(fileh.root.table[:])) fileh.close() def write(file, desc, indexed): fileh = tb.open_file(file, "w") table = fileh.create_table(fileh.root, 'table', desc) for colname in indexed: table.colinstances[colname].create_index() row = table.row for i in range(10): row['x'] = i row['y'] = 10.2 - i row['z'] = i row['color'] = colors[random.choice(['red', 'green', 'blue'])] row['info/name'] = "name%s" % i row['info/info2/info3/z4'] = i # All the rest will be filled with defaults row.append() fileh.close() # The sample nested class description class Info(tb.IsDescription): _v_pos = 2 Name = tb.UInt32Col() Value = tb.Float64Col() class Test(tb.IsDescription): """A description that has several columns.""" x = tb.Int32Col(shape=2, dflt=0, pos=0) y = tb.Float64Col(dflt=1.2, shape=(2, 3)) z = tb.UInt8Col(dflt=1) color = tb.EnumCol(colors, 'red', base='uint32', shape=(2,)) Info = Info() class info(tb.IsDescription): _v_pos = 1 name = tb.StringCol(10) value = tb.Float64Col(pos=0) y2 = tb.Float64Col(dflt=1, shape=(2, 3), pos=1) z2 = tb.UInt8Col(dflt=1) class info2(tb.IsDescription): y3 = tb.Float64Col(dflt=1, shape=(2, 3)) z3 = tb.UInt8Col(dflt=1) name = tb.StringCol(10) value = tb.EnumCol(colors, 'blue', base='uint32', shape=(1,)) class info3(tb.IsDescription): name = tb.StringCol(10) value = tb.Time64Col() y4 = tb.Float64Col(dflt=1, shape=(2, 3)) z4 = tb.UInt8Col(dflt=1) # Write the file and read it write(fileout, Test, ['info/info2/z3']) read(fileout) print("You can have a look at '%s' output file now." % fileout) PyTables-3.7.0/examples/objecttree.py000066400000000000000000000030541416254111300175620ustar00rootroot00000000000000import tables as tb class Particle(tb.IsDescription): identity = tb.StringCol(itemsize=22, dflt=" ", pos=0) # character String idnumber = tb.Int16Col(dflt=1, pos=1) # short integer speed = tb.Float32Col(dflt=1, pos=1) # single-precision # Open a file in "w"rite mode fileh = tb.open_file("objecttree.h5", mode="w") # Get the HDF5 root group root = fileh.root # Create the groups: group1 = fileh.create_group(root, "group1") group2 = fileh.create_group(root, "group2") # Now, create an array in root group # Currently PyTables arrays don't support Unicode strings, # so we need to make sure we pass plain bytes array1 = fileh.create_array( root, "array1", [b"string", b"array"], "String array") # Create 2 new tables in group1 table1 = fileh.create_table(group1, "table1", Particle) table2 = fileh.create_table("/group2", "table2", Particle) # Create the last table in group2 array2 = fileh.create_array("/group1", "array2", [1, 2, 3, 4]) # Now, fill the tables: for table in (table1, table2): # Get the record object associated with the table: row = table.row # Fill the table with 10 records for i in range(10): # First, assign the values to the Particle record row['identity'] = 'This is particle: %2d' % (i) row['idnumber'] = i row['speed'] = i * 2 # This injects the Record values row.append() # Flush the table buffers table.flush() # Finally, close the file (this also will flush all the remaining buffers!) fileh.close() PyTables-3.7.0/examples/particles.py000066400000000000000000000077471416254111300174370ustar00rootroot00000000000000"""Beware! you need PyTables >= 2.3 to run this script!""" from time import perf_counter as clock import numpy as np import tables as tb # NEVENTS = 10000 NEVENTS = 20_000 MAX_PARTICLES_PER_EVENT = 100 # Particle description class Particle(tb.IsDescription): # event_id = tables.Int32Col(pos=1, indexed=True) # event id (indexed) event_id = tb.Int32Col(pos=1) # event id (not indexed) particle_id = tb.Int32Col(pos=2) # particle id in the event parent_id = tb.Int32Col(pos=3) # the id of the parent # particle (negative # values means no parent) momentum = tb.Float64Col(shape=3, pos=4) # momentum of the particle mass = tb.Float64Col(pos=5) # mass of the particle # Create a new table for events t1 = clock() print( f"Creating a table with {NEVENTS * MAX_PARTICLES_PER_EVENT // 2} " f"entries aprox.. Wait please...") fileh = tb.open_file("particles-pro.h5", mode="w") group = fileh.create_group(fileh.root, "events") table = fileh.create_table(group, 'table', Particle, "A table", tb.Filters(0)) # Choose this line if you want data compression # table = fileh.create_table(group, 'table', Particle, "A table", Filters(1)) # Fill the table with events np.random.seed(1) # In order to have reproducible results particle = table.row for i in range(NEVENTS): for j in range(np.random.randint(0, MAX_PARTICLES_PER_EVENT)): particle['event_id'] = i particle['particle_id'] = j particle['parent_id'] = j - 10 # 10 root particles (max) particle['momentum'] = np.random.normal(5.0, 2.0, size=3) particle['mass'] = np.random.normal(500.0, 10.0) # This injects the row values. particle.append() table.flush() print(f"Added {table.nrows} entries --- Time: {clock() - t1:.3f} sec") t1 = clock() print("Creating index...") table.cols.event_id.create_index(optlevel=0, _verbose=True) print(f"Index created --- Time: {clock() - t1:.3f} sec") # Add the number of events as an attribute table.attrs.nevents = NEVENTS fileh.close() # Open the file en read only mode and start selections print("Selecting events...") fileh = tb.open_file("particles-pro.h5", mode="r") table = fileh.root.events.table print("Particles in event 34:", end=' ') nrows = 0 t1 = clock() for row in table.where("event_id == 34"): nrows += 1 print(nrows) print(f"Done --- Time: {clock() - t1:.3f} sec") print("Root particles in event 34:", end=' ') nrows = 0 t1 = clock() for row in table.where("event_id == 34"): if row['parent_id'] < 0: nrows += 1 print(nrows) print(f"Done --- Time: {clock() - t1:.3f} sec") print("Sum of masses of root particles in event 34:", end=' ') smass = 0.0 t1 = clock() for row in table.where("event_id == 34"): if row['parent_id'] < 0: smass += row['mass'] print(smass) print(f"Done --- Time: {clock() - t1:.3f} sec") print( "Sum of masses of daughter particles for particle 3 in event 34:", end=' ') smass = 0.0 t1 = clock() for row in table.where("event_id == 34"): if row['parent_id'] == 3: smass += row['mass'] print(smass) print(f"Done --- Time: {clock() - t1:.3f} sec") print("Sum of module of momentum for particle 3 in event 34:", end=' ') smomentum = 0.0 t1 = clock() # for row in table.where("(event_id == 34) & ((parent_id) == 3)"): for row in table.where("event_id == 34"): if row['parent_id'] == 3: smomentum += np.sqrt(np.add.reduce(row['momentum'] ** 2)) print(smomentum) print(f"Done --- Time: {clock() - t1:.3f} sec") # This is the same than above, but using generator expressions # Python >= 2.4 needed here! print("Sum of module of momentum for particle 3 in event 34 (2):", end=' ') t1 = clock() print(sum(np.sqrt(np.add.reduce(row['momentum'] ** 2)) for row in table.where("event_id == 34") if row['parent_id'] == 3)) print(f"Done --- Time: {clock() - t1:.3f} sec") fileh.close() PyTables-3.7.0/examples/play-with-enums.py000066400000000000000000000054061416254111300205020ustar00rootroot00000000000000# Example on using enumerated types under PyTables. # This file is intended to be run in an interactive Python session, # since it contains some statements that raise exceptions. # To run it, paste it as the input of ``python``. def COMMENT(string): pass COMMENT("**** Usage of the ``Enum`` class. ****") COMMENT("Create an enumeration of colors with automatic concrete values.") import tables as tb colorList = ['red', 'green', 'blue', 'white', 'black'] colors = tb.Enum(colorList) COMMENT("Take a look at the name-value pairs.") print("Colors:", [v for v in colors]) COMMENT("Access values as attributes.") print("Value of 'red' and 'white':", (colors.red, colors.white)) print("Value of 'yellow':", colors.yellow) COMMENT("Access values as items.") print("Value of 'red' and 'white':", (colors['red'], colors['white'])) print("Value of 'yellow':", colors['yellow']) COMMENT("Access names.") print("Name of value %s:" % colors.red, colors(colors.red)) print("Name of value 1234:", colors(1234)) COMMENT("**** Enumerated columns. ****") COMMENT("Create a new PyTables file.") h5f = tb.open_file('enum.h5', 'w') COMMENT("This describes a ball extraction.") class BallExt(tb.IsDescription): ballTime = tb.Time32Col() ballColor = tb.EnumCol(colors, 'black', base='uint8') COMMENT("Create a table of ball extractions.") tbl = h5f.create_table( '/', 'extractions', BallExt, title="Random ball extractions") COMMENT("Simulate some ball extractions.") import time import random now = time.time() row = tbl.row for i in range(10): row['ballTime'] = now + i row['ballColor'] = colors[random.choice(colorList)] # notice this row.append() COMMENT("Try to append an invalid value.") row['ballTime'] = now + 42 row['ballColor'] = 1234 tbl.flush() COMMENT("Now print them!") for r in tbl: ballTime = r['ballTime'] ballColor = colors(r['ballColor']) # notice this print("Ball extracted on %d is of color %s." % (ballTime, ballColor)) COMMENT("**** Enumerated arrays. ****") COMMENT("This describes a range of working days.") workingDays = {'Mon': 1, 'Tue': 2, 'Wed': 3, 'Thu': 4, 'Fri': 5} dayRange = tb.EnumAtom(workingDays, 'Mon', base='uint16', shape=(0, 2)) COMMENT("Create an EArray of day ranges within a week.") earr = h5f.create_earray('/', 'days', dayRange, title="Working day ranges") earr.flavor = 'python' COMMENT("Throw some day ranges in.") wdays = earr.get_enum() earr.append([(wdays.Mon, wdays.Fri), (wdays.Wed, wdays.Fri)]) COMMENT("The append method does not check values!") earr.append([(wdays.Mon, 1234)]) COMMENT("Print the values.") for (d1, d2) in earr: print("From %s to %s (%d days)." % (wdays(d1), wdays(d2), d2 - d1 + 1)) COMMENT("Close the PyTables file and remove it.") from pathlib import Path h5f.close() Path('enum.h5').unlink() PyTables-3.7.0/examples/read_array_out_arg.py000066400000000000000000000032451416254111300212670ustar00rootroot00000000000000# This script compares reading from an array in a loop using the # tables.Array.read method. In the first case, read is used without supplying # an 'out' argument, which causes a new output buffer to be pre-allocated # with each call. In the second case, the buffer is created once, and then # reused. from time import perf_counter as clock import numpy as np import tables as tb def create_file(array_size): array = np.ones(array_size, dtype='i8') with tb.open_file('test.h5', 'w') as fobj: array = fobj.create_array('/', 'test', array) print('file created, size: {} MB'.format(array.size_on_disk / 1e6)) def standard_read(array_size): N = 10 with tb.open_file('test.h5', 'r') as fobj: array = fobj.get_node('/', 'test') start = clock() for i in range(N): output = array.read(0, array_size, 1) end = clock() assert(np.all(output == 1)) print('standard read \t {:5.5f}'.format((end - start) / N)) def pre_allocated_read(array_size): N = 10 with tb.open_file('test.h5', 'r') as fobj: array = fobj.get_node('/', 'test') start = clock() output = np.empty(array_size, 'i8') for i in range(N): array.read(0, array_size, 1, out=output) end = clock() assert(np.all(output == 1)) print('pre-allocated read\t {:5.5f}'.format((end - start) / N)) if __name__ == '__main__': array_num_bytes = [10**5, 10**6, 10**7, 10**8] for array_bytes in array_num_bytes: array_size = array_bytes // 8 create_file(array_size) standard_read(array_size) pre_allocated_read(array_size) print() PyTables-3.7.0/examples/simple_threading.py000066400000000000000000000042261416254111300207540ustar00rootroot00000000000000#!/usr/bin/env python3 import math import queue import threading from pathlib import Path import numpy as np import tables as tb SIZE = 100 NTHREADS = 5 FILENAME = 'simple_threading.h5' H5PATH = '/array' def create_test_file(filename): data = np.random.rand(SIZE, SIZE) with tb.open_file(filename, 'w') as h5file: h5file.create_array('/', 'array', title="Test Array", obj=data) def chunk_generator(data_size, nchunks): chunk_size = math.ceil(data_size / nchunks) for start in range(0, data_size, chunk_size): yield slice(start, start + chunk_size) lock = threading.Lock() def synchronized_open_file(*args, **kwargs): with lock: return tb.open_file(*args, **kwargs) def synchronized_close_file(self, *args, **kwargs): with lock: return self.close(*args, **kwargs) def run(filename, path, inqueue, outqueue): try: yslice = inqueue.get() h5file = synchronized_open_file(filename, mode='r') h5array = h5file.get_node(path) data = h5array[yslice, ...] psum = np.sum(data) except Exception as e: outqueue.put(e) else: outqueue.put(psum) finally: synchronized_close_file(h5file) def main(): # generate the test data if not Path(FILENAME).exists(): create_test_file(FILENAME) threads = [] inqueue = queue.Queue() outqueue = queue.Queue() # start all threads for _ in range(NTHREADS): thread = threading.Thread(target=run, args=(FILENAME, H5PATH, inqueue, outqueue)) thread.start() threads.append(thread) # push requests in the input queue for yslice in chunk_generator(SIZE, len(threads)): inqueue.put(yslice) # collect results try: mean_ = 0 for _ in range(len(threads)): out = outqueue.get() if isinstance(out, Exception): raise out else: mean_ += out mean_ /= SIZE * SIZE finally: for thread in threads: thread.join() # print results print(f'Mean: {mean_}') if __name__ == '__main__': main() PyTables-3.7.0/examples/split.py000066400000000000000000000017451416254111300165740ustar00rootroot00000000000000"""Use the H5FD_SPLIT driver to store metadata and raw data in separate files. In this example, we store the metadata file in the current directory and the raw data file in a subdirectory. """ import errno from pathlib import Path import numpy as np import tables as tb FNAME = "split" DRIVER = "H5FD_SPLIT" RAW_DIR = Path(__file__).with_name("raw") DRIVER_PROPS = { "driver_split_raw_ext": str(RAW_DIR / "%s-r.h5") } DATA_SHAPE = (2, 10) class FooBar(tb.IsDescription): tag = tb.StringCol(16) data = tb.Float32Col(shape=DATA_SHAPE) try: RAW_DIR.mkdir() except OSError as e: if e.errno == errno.EEXIST: pass with tb.open_file(FNAME, mode="w", driver=DRIVER, **DRIVER_PROPS) as f: group = f.create_group("/", "foo", "foo desc") table = f.create_table(group, "bar", FooBar, "bar desc") for i in range(5): table.row["tag"] = "t%d" % i table.row["data"] = np.random.random_sample(DATA_SHAPE) table.row.append() table.flush() PyTables-3.7.0/examples/table-tree.py000066400000000000000000000226461416254111300174700ustar00rootroot00000000000000import numpy as np import tables as tb class Particle(tb.IsDescription): ADCcount = tb.Int16Col() # signed short integer TDCcount = tb.UInt8Col() # unsigned byte grid_i = tb.Int32Col() # integer grid_j = tb.Int32Col() # integer idnumber = tb.Int64Col() # signed long long name = tb.StringCol(16, dflt="") # 16-character String pressure = tb.Float32Col(shape=2) # float (single-precision) temperature = tb.Float64Col() # double (double-precision) Particle2 = { # You can also use any of the atom factories, i.e. the one which # accepts a PyTables type. "ADCcount": tb.Col.from_type("int16"), # signed short integer "TDCcount": tb.Col.from_type("uint8"), # unsigned byte "grid_i": tb.Col.from_type("int32"), # integer "grid_j": tb.Col.from_type("int32"), # integer "idnumber": tb.Col.from_type("int64"), # signed long long "name": tb.Col.from_kind("string", 16), # 16-character String "pressure": tb.Col.from_type("float32", (2,)), # float # (single-precision) "temperature": tb.Col.from_type("float64"), # double # (double-precision) } # The name of our HDF5 filename filename = "table-tree.h5" # Open a file in "w"rite mode h5file = tb.open_file(filename, mode="w") # Create a new group under "/" (root) group = h5file.create_group("/", 'detector') # Create one table on it # table = h5file.create_table(group, 'table', Particle, "Title example") # You can choose creating a Table from a description dictionary if you wish table = h5file.create_table(group, 'table', Particle2, "Title example") # Create a shortcut to the table record object particle = table.row # Fill the table with 10 particles for i in range(10): # First, assign the values to the Particle record particle['name'] = 'Particle: %6d' % (i) particle['TDCcount'] = i % 256 particle['ADCcount'] = (i * 256) % (1 << 16) particle['grid_i'] = i particle['grid_j'] = 10 - i particle['pressure'] = [float(i * i), float(i * 2)] particle['temperature'] = float(i ** 2) particle['idnumber'] = i * (2 ** 34) # This exceeds integer range # This injects the Record values. particle.append() # Flush the buffers for table table.flush() # Get actual data from table. We are interested in column pressure. pressure = [p['pressure'] for p in table.iterrows()] print("Last record ==>", pressure) print("Column pressure ==>", np.array(pressure)) print("Total records in table ==> ", len(pressure)) print() # Create a new group to hold new arrays gcolumns = h5file.create_group("/", "columns") print("columns ==>", gcolumns, pressure) # Create an array with this info under '/columns' having a 'list' flavor h5file.create_array(gcolumns, 'pressure', pressure, "Pressure column") print("gcolumns.pressure type ==> ", gcolumns.pressure.atom.dtype) # Do the same with TDCcount, but with a numpy object TDC = [p['TDCcount'] for p in table.iterrows()] print("TDC ==>", TDC) print("TDC shape ==>", np.array(TDC).shape) h5file.create_array('/columns', 'TDC', np.array(TDC), "TDCcount column") # Do the same with name column names = [p['name'] for p in table.iterrows()] print("names ==>", names) h5file.create_array('/columns', 'name', names, "Name column") # This works even with homogeneous tuples or lists (!) print("gcolumns.name shape ==>", gcolumns.name.shape) print("gcolumns.name type ==> ", gcolumns.name.atom.dtype) print("Table dump:") for p in table.iterrows(): print(p) # Save a recarray object under detector r = np.rec.array("a" * 300, formats='f4,3i4,a5,i2', shape=3) recarrt = h5file.create_table("/detector", 'recarray', r, "RecArray example") r2 = r[0:3:2] # Change the byteorder property recarrt = h5file.create_table("/detector", 'recarray2', r2, "Non-contiguous recarray") print(recarrt) print() print(h5file.root.detector.table.description) # Close the file h5file.close() # sys.exit() # Reopen it in append mode h5file = tb.open_file(filename, "a") # Ok. let's start browsing the tree from this filename print("Reading info from filename:", h5file.filename) print() # Firstly, list all the groups on tree print("Groups in file:") for group in h5file.walk_groups("/"): print(group) print() # List all the nodes (Group and Leaf objects) on tree print("List of all nodes in file:") print(h5file) # And finally, only the Arrays (Array objects) print("Arrays in file:") for array in h5file.walk_nodes("/", classname="Array"): print(array) print() # Get group /detector and print some info on it detector = h5file.get_node("/detector") print("detector object ==>", detector) # List only leaves on detector print("Leaves in group", detector, ":") for leaf in h5file.list_nodes("/detector", 'Leaf'): print(leaf) print() # List only tables on detector print("Tables in group", detector, ":") for leaf in h5file.list_nodes("/detector", 'Table'): print(leaf) print() # List only arrays on detector (there should be none!) print("Arrays in group", detector, ":") for leaf in h5file.list_nodes("/detector", 'Array'): print(leaf) print() # Get "/detector" Group object group = h5file.root.detector print("/detector ==>", group) # Get the "/detector/table table = h5file.get_node("/detector/table") print("/detector/table ==>", table) # Get metadata from table print("Object:", table) print("Table name:", table.name) print("Table title:", table.title) print("Rows saved on table: %d" % (table.nrows)) print("Variable names on table with their type:") for name in table.colnames: print(" ", name, ':=', table.coldtypes[name]) print() # Read arrays in /columns/names and /columns/pressure # Get the object in "/columns pressure" pressureObject = h5file.get_node("/columns", "pressure") # Get some metadata on this object print("Info on the object:", pressureObject) print(" shape ==>", pressureObject.shape) print(" title ==>", pressureObject.title) print(" type ==> ", pressureObject.atom.dtype) print(" byteorder ==> ", pressureObject.byteorder) # Read the pressure actual data pressureArray = pressureObject.read() print(" data type ==>", type(pressureArray)) print(" data ==>", pressureArray) print() # Get the object in "/columns/names" nameObject = h5file.root.columns.name # Get some metadata on this object print("Info on the object:", nameObject) print(" shape ==>", nameObject.shape) print(" title ==>", nameObject.title) print(" type ==> " % nameObject.atom.dtype) # Read the 'name' actual data nameArray = nameObject.read() print(" data type ==>", type(nameArray)) print(" data ==>", nameArray) # Print the data for both arrays print("Data on arrays name and pressure:") for i in range(pressureObject.shape[0]): print("".join(nameArray[i]), "-->", pressureArray[i]) print() # Finally, append some new records to table table = h5file.root.detector.table # Append 5 new particles to table (yes, tables can be enlarged!) particle = table.row for i in range(10, 15): # First, assign the values to the Particle record particle['name'] = 'Particle: %6d' % (i) particle['TDCcount'] = i % 256 particle['ADCcount'] = (i * 256) % (1 << 16) particle['grid_i'] = i particle['grid_j'] = 10 - i particle['pressure'] = [float(i * i), float(i * 2)] particle['temperature'] = float(i ** 2) particle['idnumber'] = i * (2 ** 34) # This exceeds integer range # This injects the Row values. particle.append() # Flush this table table.flush() print("Columns name and pressure on expanded table:") # Print some table columns, for comparison with array data for p in table: print(p['name'], '-->', p['pressure']) print() # Put several flavors oldflavor = table.flavor print(table.read(field="ADCcount")) table.flavor = "numpy" print(table.read(field="ADCcount")) table.flavor = oldflavor print(table.read(0, 0, 1, "name")) table.flavor = "python" print(table.read(0, 0, 1, "name")) table.flavor = oldflavor print(table.read(0, 0, 2, "pressure")) table.flavor = "python" print(table.read(0, 0, 2, "pressure")) table.flavor = oldflavor # Several range selections print("Extended slice in selection: [0:7:6]") print(table.read(0, 7, 6)) print("Single record in selection: [1]") print(table.read(1)) print("Last record in selection: [-1]") print(table.read(-1)) print("Two records before the last in selection: [-3:-1]") print(table.read(-3, -1)) # Print a recarray in table form table = h5file.root.detector.recarray2 print("recarray2:", table) print(" nrows:", table.nrows) print(" byteorder:", table.byteorder) print(" coldtypes:", table.coldtypes) print(" colnames:", table.colnames) print(table.read()) for p in table.iterrows(): print(p['f1'], '-->', p['f2']) print() result = [rec['f1'] for rec in table if rec.nrow < 2] print(result) # Test the File.rename_node() method # h5file.rename_node(h5file.root.detector.recarray2, "recarray3") h5file.rename_node(table, "recarray3") # Delete a Leaf from the HDF5 tree h5file.remove_node(h5file.root.detector.recarray3) # Delete the detector group and its leaves recursively # h5file.remove_node(h5file.root.detector, recursive=1) # Create a Group and then remove it h5file.create_group(h5file.root, "newgroup") h5file.remove_node(h5file.root, "newgroup") h5file.rename_node(h5file.root.columns, "newcolumns") print(h5file) # Close this file h5file.close() PyTables-3.7.0/examples/table1.py000066400000000000000000000042111416254111300166000ustar00rootroot00000000000000import tables as tb class Particle(tb.IsDescription): name = tb.StringCol(16, pos=1) # 16-character String lati = tb.Int32Col(pos=2) # integer longi = tb.Int32Col(pos=3) # integer pressure = tb.Float32Col(pos=4) # float (single-precision) temperature = tb.Float64Col(pos=5) # double (double-precision) # Open a file in "w"rite mode fileh = tb.open_file("table1.h5", mode="w") # Create a new group group = fileh.create_group(fileh.root, "newgroup") # Create a new table in newgroup group table = fileh.create_table(group, 'table', Particle, "A table", tb.Filters(1)) particle = table.row # Fill the table with 10 particles for i in range(10): # First, assign the values to the Particle record particle['name'] = 'Particle: %6d' % (i) particle['lati'] = i particle['longi'] = 10 - i particle['pressure'] = float(i * i) particle['temperature'] = float(i ** 2) # This injects the row values. particle.append() # We need to flush the buffers in table in order to get an # accurate number of records on it. table.flush() # Add a couple of user attrs table.attrs.user_attr1 = 1.023 table.attrs.user_attr2 = "This is the second user attr" # Append several rows in only one call table.append([("Particle: 10", 10, 0, 10 * 10, 10 ** 2), ("Particle: 11", 11, -1, 11 * 11, 11 ** 2), ("Particle: 12", 12, -2, 12 * 12, 12 ** 2)]) group = fileh.root.newgroup print("Nodes under group", group, ":") for node in fileh.list_nodes(group): print(node) print() print("Leaves everywhere in file", fileh.filename, ":") for leaf in fileh.walk_nodes(classname="Leaf"): print(leaf) print() table = fileh.root.newgroup.table print("Object:", table) print(f"Table name: {table.name}. Table title: {table.title}") print("Rows saved on table: %d" % (table.nrows)) print("Variable names on table with their type:") for name in table.colnames: print(" ", name, ':=', table.coldtypes[name]) print("Table contents:") for row in table: print(row[:]) print("Associated recarray:") print(table.read()) # Finally, close the file fileh.close() PyTables-3.7.0/examples/table2.py000066400000000000000000000032641416254111300166100ustar00rootroot00000000000000# This shows how to use the cols accessors for table columns import tables as tb class Particle(tb.IsDescription): name = tb.StringCol(16, pos=1) # 16-character String lati = tb.Int32Col(pos=2) # integer longi = tb.Int32Col(pos=3) # integer vector = tb.Int32Col(shape=(2,), pos=4) # Integer matrix2D = tb.Float64Col(shape=(2, 2), pos=5) # double (double-precision) # Open a file in "w"rite mode fileh = tb.open_file("table2.h5", mode="w") table = fileh.create_table(fileh.root, 'table', Particle, "A table") # Append several rows in only one call table.append( [("Particle: 10", 10, 0, (10 * 9, 1), [[10 ** 2, 11 * 3]] * 2), ("Particle: 11", 11, -1, (11 * 10, 2), [[11 ** 2, 10 * 3]] * 2), ("Particle: 12", 12, -2, (12 * 11, 3), [[12 ** 2, 9 * 3]] * 2), ("Particle: 13", 13, -3, (13 * 11, 4), [[13 ** 2, 8 * 3]] * 2), ("Particle: 14", 14, -4, (14 * 11, 5), [[14 ** 2, 7 * 3]] * 2)]) print("str(Cols)-->", table.cols) print("repr(Cols)-->", repr(table.cols)) print("Column handlers:") for name in table.colnames: print(table.cols._f_col(name)) print("Select table.cols.name[1]-->", table.cols.name[1]) print("Select table.cols.name[1:2]-->", table.cols.name[1:2]) print("Select table.cols.name[:]-->", table.cols.name[:]) print("Select table.cols._f_col('name')[:]-->", table.cols._f_col('name')[:]) print("Select table.cols.lati[1]-->", table.cols.lati[1]) print("Select table.cols.lati[1:2]-->", table.cols.lati[1:2]) print("Select table.cols.vector[:]-->", table.cols.vector[:]) print("Select table.cols['matrix2D'][:]-->", table.cols.matrix2D[:]) fileh.close() PyTables-3.7.0/examples/table3.py000066400000000000000000000031741416254111300166110ustar00rootroot00000000000000# This is an example on how to use complex columns import tables as tb class Particle(tb.IsDescription): name = tb.StringCol(16, pos=1) # 16-character String lati = tb.ComplexCol(itemsize=16, pos=2) longi = tb.ComplexCol(itemsize=8, pos=3) vector = tb.ComplexCol(itemsize=8, shape=(2,), pos=4) matrix2D = tb.ComplexCol(itemsize=16, shape=(2, 2), pos=5) # Open a file in "w"rite mode fileh = tb.open_file("table3.h5", mode="w") table = fileh.create_table(fileh.root, 'table', Particle, "A table") # Append several rows in only one call table.append([ ("Particle: 10", 10j, 0, (10 * 9 + 1j, 1), [[10 ** 2j, 11 * 3]] * 2), ("Particle: 11", 11j, -1, (11 * 10 + 2j, 2), [[11 ** 2j, 10 * 3]] * 2), ("Particle: 12", 12j, -2, (12 * 11 + 3j, 3), [[12 ** 2j, 9 * 3]] * 2), ("Particle: 13", 13j, -3, (13 * 11 + 4j, 4), [[13 ** 2j, 8 * 3]] * 2), ("Particle: 14", 14j, -4, (14 * 11 + 5j, 5), [[14 ** 2j, 7 * 3]] * 2) ]) print("str(Cols)-->", table.cols) print("repr(Cols)-->", repr(table.cols)) print("Column handlers:") for name in table.colnames: print(table.cols._f_col(name)) print("Select table.cols.name[1]-->", table.cols.name[1]) print("Select table.cols.name[1:2]-->", table.cols.name[1:2]) print("Select table.cols.name[:]-->", table.cols.name[:]) print("Select table.cols._f_col('name')[:]-->", table.cols._f_col('name')[:]) print("Select table.cols.lati[1]-->", table.cols.lati[1]) print("Select table.cols.lati[1:2]-->", table.cols.lati[1:2]) print("Select table.cols.vector[:]-->", table.cols.vector[:]) print("Select table.cols['matrix2D'][:]-->", table.cols.matrix2D[:]) fileh.close() PyTables-3.7.0/examples/tables-with-padding.py000066400000000000000000000040421416254111300212610ustar00rootroot00000000000000# This is an example on how to use complex columns import numpy as np import tables as tb N = 1000 padded_dtype = np.dtype([('string', 'S3'), ('int', 'i4'), ('double', 'f8')], align=True) #assert padded_dtype.itemsize == 16 padded_struct = np.zeros(N, padded_dtype) padded_struct['string'] = np.arange(N).astype('S3') padded_struct['int'] = np.arange(N, dtype='i4') padded_struct['double'] = np.arange(N, dtype='f8') # Create a file with padding (the default) fileh = tb.open_file("tables-with-padding.h5", mode="w", pytables_sys_attrs=False) table = fileh.create_table(fileh.root, 'table', padded_struct, "A table with padding") print("table *with* padding -->", table) print("table.description --> ", table.description) print("table.descrition._v_offsets-->", table.description._v_offsets) print("table.descrition._v_itemsize-->", table.description._v_itemsize) fileh.close() # Create another file without padding fileh = tb.open_file("tables-without-padding.h5", mode="w", pytables_sys_attrs=False, allow_padding=False) table = fileh.create_table(fileh.root, 'table', padded_struct, "A table without padding") print("\ntable *without* padding -->", table) print("table.description --> ", table.description) print("table.descrition._v_offsets-->", table.description._v_offsets) print("table.descrition._v_itemsize-->", table.description._v_itemsize) fileh.close() print("\n ***After closing***\n") fileh = tb.open_file("tables-with-padding.h5", mode="r") table = fileh.root.table print("table *with* padding -->", table) print("table.description --> ", table.description) print("table.descrition._v_offsets-->", table.description._v_offsets) print("table.descrition._v_itemsize-->", table.description._v_itemsize) fileh.close() fileh = tb.open_file("tables-without-padding.h5", mode="r") table = fileh.root.table print("\ntable *without* padding -->", table) print("table.description --> ", table.description) print("table.descrition._v_offsets-->", table.description._v_offsets) print("table.descrition._v_itemsize-->", table.description._v_itemsize) fileh.close() PyTables-3.7.0/examples/threading_monkeypatch.py000066400000000000000000000060461416254111300220070ustar00rootroot00000000000000#!/usr/bin/env python3 import math import queue import functools import threading from pathlib import Path import numpy as np import tables as tb class ThreadsafeFileRegistry(tb.file._FileRegistry): lock = threading.RLock() @property def handlers(self): return self._handlers.copy() def add(self, handler): with self.lock: return super().add(handler) def remove(self, handler): with self.lock: return super().remove(handler) def close_all(self): with self.lock: return super().close_all(handler) class ThreadsafeFile(tb.file.File): def __init__(self, *args, **kargs): with ThreadsafeFileRegistry.lock: super().__init__(*args, **kargs) def close(self): with ThreadsafeFileRegistry.lock: super().close() @functools.wraps(tb.open_file) def synchronized_open_file(*args, **kwargs): with ThreadsafeFileRegistry.lock: return tb.file._original_open_file(*args, **kwargs) # monkey patch the tables package tb.file._original_open_file = tb.file.open_file tb.file.open_file = synchronized_open_file tb.open_file = synchronized_open_file tb.file._original_File = tb.file.File tb.file.File = ThreadsafeFile tb.File = ThreadsafeFile tb.file._open_files = ThreadsafeFileRegistry() SIZE = 100 NTHREADS = 5 FILENAME = 'simple_threading.h5' H5PATH = '/array' def create_test_file(filename): data = np.random.rand(SIZE, SIZE) with tb.open_file(filename, 'w') as h5file: h5file.create_array('/', 'array', title="Test Array", obj=data) def chunk_generator(data_size, nchunks): chunk_size = math.ceil(data_size / nchunks) for start in range(0, data_size, chunk_size): yield slice(start, start + chunk_size) def run(filename, path, inqueue, outqueue): try: yslice = inqueue.get() with tb.open_file(filename, mode='r') as h5file: h5array = h5file.get_node(path) data = h5array[yslice, ...] psum = np.sum(data) except Exception as e: outqueue.put(e) else: outqueue.put(psum) def main(): # generate the test data if not Path(FILENAME).exists(): create_test_file(FILENAME) threads = [] inqueue = queue.Queue() outqueue = queue.Queue() # start all threads for i in range(NTHREADS): thread = threading.Thread(target=run, args=(FILENAME, H5PATH, inqueue, outqueue)) thread.start() threads.append(thread) # push requests in the input queue for yslice in chunk_generator(SIZE, len(threads)): inqueue.put(yslice) # collect results try: mean_ = 0 for _ in range(len(threads)): out = outqueue.get() if isinstance(out, Exception): raise out else: mean_ += out mean_ /= SIZE * SIZE finally: for thread in threads: thread.join() # print results print(f'Mean: {mean_}') if __name__ == '__main__': main() PyTables-3.7.0/examples/tutorial1-1.py000066400000000000000000000066371416254111300175300ustar00rootroot00000000000000"""Small but quite comprehensive example showing the use of PyTables. The program creates an output file, 'tutorial1.h5'. You can view it with any HDF5 generic utility. """ import numpy as np import tables as tb #'-**-**-**-**-**-**- user record definition -**-**-**-**-**-**-**-' # Define a user record to characterize some kind of particles class Particle(tb.IsDescription): name = tb.StringCol(16) # 16-character String idnumber = tb.Int64Col() # Signed 64-bit integer ADCcount = tb.UInt16Col() # Unsigned short integer TDCcount = tb.UInt8Col() # unsigned byte grid_i = tb.Int32Col() # integer grid_j = tb.Int32Col() # integer pressure = tb.Float32Col() # float (single-precision) energy = tb.Float64Col() # double (double-precision) print() print('-**-**-**-**-**-**- file creation -**-**-**-**-**-**-**-') # The name of our HDF5 filename filename = "tutorial1.h5" print("Creating file:", filename) # Open a file in "w"rite mode h5file = tb.open_file(filename, mode="w", title="Test file") print() print('-**-**-**-**-**- group and table creation -**-**-**-**-**-**-**-') # Create a new group under "/" (root) group = h5file.create_group("/", 'detector', 'Detector information') print("Group '/detector' created") # Create one table on it table = h5file.create_table(group, 'readout', Particle, "Readout example") print("Table '/detector/readout' created") # Print the file print(h5file) print() print(repr(h5file)) # Get a shortcut to the record object in table particle = table.row # Fill the table with 10 particles for i in range(10): particle['name'] = 'Particle: %6d' % (i) particle['TDCcount'] = i % 256 particle['ADCcount'] = (i * 256) % (1 << 16) particle['grid_i'] = i particle['grid_j'] = 10 - i particle['pressure'] = float(i * i) particle['energy'] = float(particle['pressure'] ** 4) particle['idnumber'] = i * (2 ** 34) particle.append() # Flush the buffers for table table.flush() print() print('-**-**-**-**-**-**- table data reading & selection -**-**-**-**-**-') # Read actual data from table. We are interested in collecting pressure values # on entries where TDCcount field is greater than 3 and pressure less than 50 xs = [x for x in table.iterrows() if x['TDCcount'] > 3 and 20 <= x['pressure'] < 50] pressure = [x['pressure'] for x in xs ] print("Last record read:") print(repr(xs[-1])) print("Field pressure elements satisfying the cuts:") print(repr(pressure)) # Read also the names with the same cuts names = [ x['name'] for x in table.where( """(TDCcount > 3) & (20 <= pressure) & (pressure < 50)""") ] print("Field names elements satisfying the cuts:") print(repr(names)) print() print('-**-**-**-**-**-**- array object creation -**-**-**-**-**-**-**-') print("Creating a new group called '/columns' to hold new arrays") gcolumns = h5file.create_group(h5file.root, "columns", "Pressure and Name") print("Creating an array called 'pressure' under '/columns' group") h5file.create_array(gcolumns, 'pressure', np.array(pressure), "Pressure column selection") print(repr(h5file.root.columns.pressure)) print("Creating another array called 'name' under '/columns' group") h5file.create_array(gcolumns, 'name', names, "Name column selection") print(repr(h5file.root.columns.name)) print("HDF5 file:") print(h5file) # Close the file h5file.close() print("File '" + filename + "' created") PyTables-3.7.0/examples/tutorial1-2.py000066400000000000000000000214761416254111300175270ustar00rootroot00000000000000"""This example shows how to browse the object tree and enlarge tables. Before to run this program you need to execute first tutorial1-1.py that create the tutorial1.h5 file needed here. """ import tables as tb print() print('-**-**-**-**- open the previous tutorial file -**-**-**-**-**-') # Reopen the file in append mode h5file = tb.open_file("tutorial1.h5", "a") # Print the object tree created from this filename print("Object tree from filename:", h5file.filename) print(h5file) print() print('-**-**-**-**-**-**- traverse tree methods -**-**-**-**-**-**-**-') # List all the nodes (Group and Leaf objects) on tree print(h5file) # List all the nodes (using File iterator) on tree print("Nodes in file:") for node in h5file: print(node) print() # Now, only list all the groups on tree print("Groups in file:") for group in h5file.walk_groups(): print(group) print() # List only the arrays hanging from / print("Arrays in file (I):") for group in h5file.walk_groups("/"): for array in h5file.list_nodes(group, classname='Array'): print(array) # This do the same result print("Arrays in file (II):") for array in h5file.walk_nodes("/", "Array"): print(array) print() # And finally, list only leafs on /detector group (there should be one!) print("Leafs in group '/detector' (I):") for leaf in h5file.list_nodes("/detector", 'Leaf'): print(leaf) # Other way using iterators and natural naming print("Leafs in group '/detector' (II):") for leaf in h5file.root.detector._f_walknodes('Leaf'): print(leaf) print() print('-**-**-**-**-**-**- setting/getting object attributes -**-**--**-**-') # Get a pointer to '/detector/readout' node table = h5file.root.detector.readout # Attach it a string (date) attribute table.attrs.gath_date = "Wed, 06/12/2003 18:33" # Attach a floating point attribute table.attrs.temperature = 18.4 table.attrs.temp_scale = "Celsius" # Get a pointer to '/detector' node detector = h5file.root.detector # Attach a general object to the parent (/detector) group detector._v_attrs.stuff = [5, (2.3, 4.5), "Integer and tuple"] # Now, get the attributes print("gath_date attribute of /detector/readout:", table.attrs.gath_date) print("temperature attribute of /detector/readout:", table.attrs.temperature) print("temp_scale attribute of /detector/readout:", table.attrs.temp_scale) print("stuff attribute in /detector:", detector._v_attrs.stuff) print() # Delete permanently the attribute gath_date of /detector/readout print("Deleting /detector/readout gath_date attribute") del table.attrs.gath_date # Print a representation of all attributes in /detector/table print("AttributeSet instance in /detector/table:", repr(table.attrs)) # Get the (user) attributes of /detector/table print("List of user attributes in /detector/table:", table.attrs._f_list()) # Get the (sys) attributes of /detector/table print("List of user attributes in /detector/table:", table.attrs._f_list("sys")) print() # Rename an attribute print("renaming 'temp_scale' attribute to 'tempScale'") table.attrs._f_rename("temp_scale", "tempScale") print(table.attrs._f_list()) # Try to rename a system attribute: try: table.attrs._f_rename("VERSION", "version") except: print("You can not rename a VERSION attribute: it is read only!.") print() print('-**-**-**-**-**-**- getting object metadata -**-**-**-**-**-**-') # Get a pointer to '/detector/readout' data table = h5file.root.detector.readout # Get metadata from table print("Object:", table) print("Table name:", table.name) print("Table title:", table.title) print("Number of rows in table:", table.nrows) print("Table variable names with their type and shape:") for name in table.colnames: print(name, ':= {}, {}'.format(table.coldtypes[name], table.coldtypes[name].shape)) print() # Get the object in "/columns pressure" pressureObject = h5file.get_node("/columns", "pressure") # Get some metadata on this object print("Info on the object:", repr(pressureObject)) print(" shape: ==>", pressureObject.shape) print(" title: ==>", pressureObject.title) print(" atom: ==>", pressureObject.atom) print() print('-**-**-**-**-**- reading actual data from arrays -**-**-**-**-**-**-') # Read the 'pressure' actual data pressureArray = pressureObject.read() print(repr(pressureArray)) # Check the kind of object we have created (it should be a numpy array) print("pressureArray is an object of type:", type(pressureArray)) # Read the 'name' Array actual data nameArray = h5file.root.columns.name.read() # Check the kind of object we have created (it should be a numpy array) print("nameArray is an object of type:", type(nameArray)) print() # Print the data for both arrays print("Data on arrays nameArray and pressureArray:") for i in range(pressureObject.shape[0]): print(nameArray[i], "-->", pressureArray[i]) print() print('-**-**-**-**-**- reading actual data from tables -**-**-**-**-**-**-') # Create a shortcut to table object table = h5file.root.detector.readout # Read the 'energy' column of '/detector/readout' print("Column 'energy' of '/detector/readout':\n", table.cols.energy) print() # Read the 3rd row of '/detector/readout' print("Third row of '/detector/readout':\n", table[2]) print() # Read the rows from 3 to 9 of row of '/detector/readout' print("Rows from 3 to 9 of '/detector/readout':\n", table[2:9]) print() print('-**-**-**-**- append records to existing table -**-**-**-**-**-') # Get the object row from table table = h5file.root.detector.readout particle = table.row # Append 5 new particles to table for i in range(10, 15): particle['name'] = 'Particle: %6d' % (i) particle['TDCcount'] = i % 256 particle['ADCcount'] = (i * 256) % (1 << 16) particle['grid_i'] = i particle['grid_j'] = 10 - i particle['pressure'] = float(i * i) particle['energy'] = float(particle['pressure'] ** 4) particle['idnumber'] = i * (2 ** 34) # This exceeds long integer range particle.append() # Flush this table table.flush() # Print the data using the table iterator: for r in table: print("%-16s | %11.1f | %11.4g | %6d | %6d | %8d |" % (r['name'], r['pressure'], r['energy'], r['grid_i'], r['grid_j'], r['TDCcount'])) print() print("Total number of entries in resulting table:", table.nrows) print() print('-**-**-**-**- modify records of a table -**-**-**-**-**-') # Single cells print("First row of readout table.") print("Before modif-->", table[0]) table.cols.TDCcount[0] = 1 print("After modifying first row of TDCcount-->", table[0]) table.cols.energy[0] = 2 print("After modifying first row of energy-->", table[0]) # Column slices table.cols.TDCcount[2:5] = [2, 3, 4] print("After modifying slice [2:5] of ADCcount-->", table[0:5]) table.cols.energy[1:9:3] = [2, 3, 4] print("After modifying slice [1:9:3] of energy-->", table[0:9]) # Modifying complete Rows table.modify_rows(start=1, step=3, rows=[(1, 2, 3.0, 4, 5, 6, 'Particle: None', 8.0), (2, 4, 6.0, 8, 10, 12, 'Particle: None*2', 16.0)]) print("After modifying the complete third row-->", table[0:5]) # Modifying columns inside table iterators for row in table.where('TDCcount <= 2'): row['energy'] = row['TDCcount'] * 2 row.update() print("After modifying energy column (where TDCcount <=2)-->", table[0:4]) print() print('-**-**-**-**- modify elements of an array -**-**-**-**-**-') print("pressure array") pressureObject = h5file.root.columns.pressure print("Before modif-->", pressureObject[:]) pressureObject[0] = 2 print("First modif-->", pressureObject[:]) pressureObject[1:3] = [2.1, 3.5] print("Second modif-->", pressureObject[:]) pressureObject[::2] = [1, 2] print("Third modif-->", pressureObject[:]) print("name array") nameObject = h5file.root.columns.name print("Before modif-->", nameObject[:]) nameObject[0] = ['Particle: None'] print("First modif-->", nameObject[:]) nameObject[1:3] = ['Particle: 0', 'Particle: 1'] print("Second modif-->", nameObject[:]) nameObject[::2] = ['Particle: -3', 'Particle: -5'] print("Third modif-->", nameObject[:]) print() print('-**-**-**-**- remove records from a table -**-**-**-**-**-') # Delete some rows on the Table (yes, rows can be removed!) table.remove_rows(5, 10) # Print some table columns, for comparison with array data print("Some columns in final table:") print() # Print the headers print("%-16s | %11s | %11s | %6s | %6s | %8s |" % ('name', 'pressure', 'energy', 'grid_i', 'grid_j', 'TDCcount')) print("%-16s + %11s + %11s + %6s + %6s + %8s +" % ('-' * 16, '-' * 11, '-' * 11, '-' * 6, '-' * 6, '-' * 8)) # Print the data using the table iterator: for r in table.iterrows(): print("%-16s | %11.1f | %11.4g | %6d | %6d | %8d |" % (r['name'], r['pressure'], r['energy'], r['grid_i'], r['grid_j'], r['TDCcount'])) print() print("Total number of entries in final table:", table.nrows) # Close the file h5file.close() PyTables-3.7.0/examples/tutorial2.py000066400000000000000000000074301416254111300173630ustar00rootroot00000000000000"""This program shows the different protections that PyTables offer to the user in order to insure a correct data injection in tables. Example to be used in the second tutorial in the User's Guide. """ import tables as tb import numpy as np # Describe a particle record class Particle(tb.IsDescription): name = tb.StringCol(itemsize=16) # 16-character string lati = tb.Int32Col() # integer longi = tb.Int32Col() # integer pressure = tb.Float32Col(shape=(2, 3)) # array of floats # (single-precision) temperature = tb.Float64Col(shape=(2, 3)) # array of doubles # (double-precision) # Native NumPy dtype instances are also accepted Event = np.dtype([ ("name", "S16"), ("TDCcount", np.uint8), ("ADCcount", np.uint16), ("xcoord", np.float32), ("ycoord", np.float32) ]) # And dictionaries too (this defines the same structure as above) # Event = { # "name" : StringCol(itemsize=16), # "TDCcount" : UInt8Col(), # "ADCcount" : UInt16Col(), # "xcoord" : Float32Col(), # "ycoord" : Float32Col(), # } # Open a file in "w"rite mode fileh = tb.open_file("tutorial2.h5", mode="w") # Get the HDF5 root group root = fileh.root # Create the groups: for groupname in ("Particles", "Events"): group = fileh.create_group(root, groupname) # Now, create and fill the tables in Particles group gparticles = root.Particles # Create 3 new tables for tablename in ("TParticle1", "TParticle2", "TParticle3"): # Create a table table = fileh.create_table("/Particles", tablename, Particle, "Particles: " + tablename) # Get the record object associated with the table: particle = table.row # Fill the table with 257 particles for i in range(257): # First, assign the values to the Particle record particle['name'] = 'Particle: %6d' % (i) particle['lati'] = i particle['longi'] = 10 - i # Detectable errors start here. Play with them! particle['pressure'] = i * np.arange(2 * 4).reshape(2, 4) # Incorrect # particle['pressure'] = i * arange(2 * 3).reshape(2, 3) # Correct # End of errors particle['temperature'] = (i ** 2) # Broadcasting # This injects the Record values particle.append() # Flush the table buffers table.flush() # Now, go for Events: for tablename in ("TEvent1", "TEvent2", "TEvent3"): # Create a table in Events group table = fileh.create_table(root.Events, tablename, Event, "Events: " + tablename) # Get the record object associated with the table: event = table.row # Fill the table with 257 events for i in range(257): # First, assign the values to the Event record event['name'] = 'Event: %6d' % (i) event['TDCcount'] = i % (1 << 8) # Correct range # Detectable errors start here. Play with them! event['xcoor'] = float(i ** 2) # Wrong spelling # event['xcoord'] = float(i**2) # Correct spelling event['ADCcount'] = "sss" # Wrong type # event['ADCcount'] = i * 2 # Correct type # End of errors event['ycoord'] = float(i) ** 4 # This injects the Record values event.append() # Flush the buffers table.flush() # Read the records from table "/Events/TEvent3" and select some table = root.Events.TEvent3 e = [p['TDCcount'] for p in table if p['ADCcount'] < 20 and 4 <= p['TDCcount'] < 15] print("Last record ==>", p) print("Selected values ==>", e) print("Total selected records ==> ", len(e)) # Finally, close the file (this also will flush all the remaining buffers!) fileh.close() PyTables-3.7.0/examples/tutorial3-1.py000066400000000000000000000030641416254111300175210ustar00rootroot00000000000000"""Small example of do/undo capability with PyTables.""" import tables as tb # Create an HDF5 file fileh = tb.open_file("tutorial3-1.h5", "w", title="Undo/Redo demo 1") #'-**-**-**-**-**-**- enable undo/redo log -**-**-**-**-**-**-**-' fileh.enable_undo() # Create a new array one = fileh.create_array('/', 'anarray', [3, 4], "An array") # Mark this point fileh.mark() # Create a new array another = fileh.create_array('/', 'anotherarray', [4, 5], "Another array") # Now undo the past operation fileh.undo() # Check that anotherarray does not exist in the object tree but anarray does assert "/anarray" in fileh assert "/anotherarray" not in fileh # Unwind once more fileh.undo() # Check that anarray does not exist in the object tree assert "/anarray" not in fileh assert "/anotherarray" not in fileh # Go forward up to the next marker fileh.redo() # Check that anarray has come back to life in a sane state assert "/anarray" in fileh assert fileh.root.anarray.read() == [3, 4] assert fileh.root.anarray.title == "An array" assert fileh.root.anarray == one # But anotherarray is not here yet assert "/anotherarray" not in fileh # Now, go rewind up to the end fileh.redo() assert "/anarray" in fileh # Check that anotherarray has come back to life in a sane state assert "/anotherarray" in fileh assert fileh.root.anotherarray.read() == [4, 5] assert fileh.root.anotherarray.title == "Another array" assert fileh.root.anotherarray == another #'-**-**-**-**-**-**- disable undo/redo log -**-**-**-**-**-**-**-' fileh.disable_undo() # Close the file fileh.close() PyTables-3.7.0/examples/tutorial3-2.py000066400000000000000000000050651416254111300175250ustar00rootroot00000000000000"""A more complex example of do/undo capability with PyTables. Here, names has been assigned to the marks, and jumps are done between marks. """ import tables as tb # Create an HDF5 file fileh = tb.open_file('tutorial3-2.h5', 'w', title='Undo/Redo demo 2') #'-**-**-**-**-**-**- enable undo/redo log -**-**-**-**-**-**-**-' fileh.enable_undo() # Start undoable operations fileh.create_array('/', 'otherarray1', [3, 4], 'Another array 1') fileh.create_group('/', 'agroup', 'Group 1') # Create a 'first' mark fileh.mark('first') fileh.create_array('/agroup', 'otherarray2', [4, 5], 'Another array 2') fileh.create_group('/agroup', 'agroup2', 'Group 2') # Create a 'second' mark fileh.mark('second') fileh.create_array('/agroup/agroup2', 'otherarray3', [5, 6], 'Another array 3') # Create a 'third' mark fileh.mark('third') fileh.create_array('/', 'otherarray4', [6, 7], 'Another array 4') fileh.create_array('/agroup', 'otherarray5', [7, 8], 'Another array 5') # Now go to mark 'first' fileh.goto('first') assert '/otherarray1' in fileh assert '/agroup' in fileh assert '/agroup/agroup2' not in fileh assert '/agroup/otherarray2' not in fileh assert '/agroup/agroup2/otherarray3' not in fileh assert '/otherarray4' not in fileh assert '/agroup/otherarray5' not in fileh # Go to mark 'third' fileh.goto('third') assert '/otherarray1' in fileh assert '/agroup' in fileh assert '/agroup/agroup2' in fileh assert '/agroup/otherarray2' in fileh assert '/agroup/agroup2/otherarray3' in fileh assert '/otherarray4' not in fileh assert '/agroup/otherarray5' not in fileh # Now go to mark 'second' fileh.goto('second') assert '/otherarray1' in fileh assert '/agroup' in fileh assert '/agroup/agroup2' in fileh assert '/agroup/otherarray2' in fileh assert '/agroup/agroup2/otherarray3' not in fileh assert '/otherarray4' not in fileh assert '/agroup/otherarray5' not in fileh # Go to the end fileh.goto(-1) assert '/otherarray1' in fileh assert '/agroup' in fileh assert '/agroup/agroup2' in fileh assert '/agroup/otherarray2' in fileh assert '/agroup/agroup2/otherarray3' in fileh assert '/otherarray4' in fileh assert '/agroup/otherarray5' in fileh # Check that objects have come back to life in a sane state assert fileh.root.otherarray1.read() == [3, 4] assert fileh.root.agroup.otherarray2.read() == [4, 5] assert fileh.root.agroup.agroup2.otherarray3.read() == [5, 6] assert fileh.root.otherarray4.read() == [6, 7] assert fileh.root.agroup.otherarray5.read() == [7, 8] #'-**-**-**-**-**-**- disable undo/redo log -**-**-**-**-**-**-**-' fileh.disable_undo() # Close the file fileh.close() PyTables-3.7.0/examples/undo-redo.py000066400000000000000000000104641416254111300173330ustar00rootroot00000000000000"""Yet another couple of examples on do/undo feauture.""" import tables as tb def setUp(filename): # Create an HDF5 file fileh = tb.open_file(filename, mode="w", title="Undo/Redo demo") # Create some nodes in there fileh.create_group("/", "agroup", "Group 1") fileh.create_group("/agroup", "agroup2", "Group 2") fileh.create_array("/", "anarray", [1, 2], "Array 1") # Enable undo/redo. fileh.enable_undo() return fileh def tearDown(fileh): # Disable undo/redo. fileh.disable_undo() # Close the file fileh.close() def demo_6times3marks(): """Checking with six ops and three marks.""" # Initialize the data base with some nodes fileh = setUp("undo-redo-6times3marks.h5") # Create a new array fileh.create_array('/', 'otherarray1', [3, 4], "Another array 1") fileh.create_array('/', 'otherarray2', [4, 5], "Another array 2") # Put a mark fileh.mark() fileh.create_array('/', 'otherarray3', [5, 6], "Another array 3") fileh.create_array('/', 'otherarray4', [6, 7], "Another array 4") # Put a mark fileh.mark() fileh.create_array('/', 'otherarray5', [7, 8], "Another array 5") fileh.create_array('/', 'otherarray6', [8, 9], "Another array 6") # Unwind just one mark fileh.undo() assert "/otherarray1" in fileh assert "/otherarray2" in fileh assert "/otherarray3" in fileh assert "/otherarray4" in fileh assert "/otherarray5" not in fileh assert "/otherarray6" not in fileh # Unwind another mark fileh.undo() assert "/otherarray1" in fileh assert "/otherarray2" in fileh assert "/otherarray3" not in fileh assert "/otherarray4" not in fileh assert "/otherarray5" not in fileh assert "/otherarray6" not in fileh # Unwind all marks fileh.undo() assert "/otherarray1" not in fileh assert "/otherarray2" not in fileh assert "/otherarray3" not in fileh assert "/otherarray4" not in fileh assert "/otherarray5" not in fileh assert "/otherarray6" not in fileh # Redo until the next mark fileh.redo() assert "/otherarray1" in fileh assert "/otherarray2" in fileh assert "/otherarray3" not in fileh assert "/otherarray4" not in fileh assert "/otherarray5" not in fileh assert "/otherarray6" not in fileh # Redo until the next mark fileh.redo() assert "/otherarray1" in fileh assert "/otherarray2" in fileh assert "/otherarray3" in fileh assert "/otherarray4" in fileh assert "/otherarray5" not in fileh assert "/otherarray6" not in fileh # Redo until the end fileh.redo() assert "/otherarray1" in fileh assert "/otherarray2" in fileh assert "/otherarray3" in fileh assert "/otherarray4" in fileh assert "/otherarray5" in fileh assert "/otherarray6" in fileh # Tear down the file tearDown(fileh) def demo_manyops(): """Checking many operations together.""" # Initialize the data base with some nodes fileh = setUp("undo-redo-manyops.h5") # Create an array fileh.create_array(fileh.root, 'anarray3', [3], "Array title 3") # Create a group fileh.create_group(fileh.root, 'agroup3', "Group title 3") # /anarray => /agroup/agroup3/ new_node = fileh.copy_node('/anarray3', '/agroup/agroup2') new_node = fileh.copy_children('/agroup', '/agroup3', recursive=1) # rename anarray fileh.rename_node('/anarray', 'anarray4') # Move anarray new_node = fileh.copy_node('/anarray3', '/agroup') # Remove anarray4 fileh.remove_node('/anarray4') # Undo the actions fileh.undo() assert '/anarray4' not in fileh assert '/anarray3' not in fileh assert '/agroup/agroup2/anarray3' not in fileh assert '/agroup3' not in fileh assert '/anarray4' not in fileh assert '/anarray' in fileh # Redo the actions fileh.redo() # Check that the copied node exists again in the object tree. assert '/agroup/agroup2/anarray3' in fileh assert '/agroup/anarray3' in fileh assert '/agroup3/agroup2/anarray3' in fileh assert '/agroup3/anarray3' not in fileh assert fileh.root.agroup.anarray3 is new_node assert '/anarray' not in fileh assert '/anarray4' not in fileh # Tear down the file tearDown(fileh) if __name__ == '__main__': # run demos demo_6times3marks() demo_manyops() PyTables-3.7.0/examples/vlarray1.py000066400000000000000000000023141416254111300171730ustar00rootroot00000000000000import tables as tb import numpy as np # Create a VLArray: fileh = tb.open_file('vlarray1.h5', mode='w') vlarray = fileh.create_vlarray(fileh.root, 'vlarray1', tb.Int32Atom(shape=()), "ragged array of ints", filters=tb.Filters(1)) # Append some (variable length) rows: vlarray.append(np.array([5, 6])) vlarray.append(np.array([5, 6, 7])) vlarray.append([5, 6, 9, 8]) # Now, read it through an iterator: print('-->', vlarray.title) for x in vlarray: print('%s[%d]--> %s' % (vlarray.name, vlarray.nrow, x)) # Now, do the same with native Python strings. vlarray2 = fileh.create_vlarray(fileh.root, 'vlarray2', tb.StringAtom(itemsize=2), "ragged array of strings", filters=tb.Filters(1)) vlarray2.flavor = 'python' # Append some (variable length) rows: print('-->', vlarray2.title) vlarray2.append(['5', '66']) vlarray2.append(['5', '6', '77']) vlarray2.append(['5', '6', '9', '88']) # Now, read it through an iterator: for x in vlarray2: print('%s[%d]--> %s' % (vlarray2.name, vlarray2.nrow, x)) # Close the file. fileh.close() PyTables-3.7.0/examples/vlarray2.py000066400000000000000000000064351416254111300172040ustar00rootroot00000000000000#!/usr/bin/env python3 """Small example that shows how to work with variable length arrays of different types, UNICODE strings and general Python objects included.""" import pickle import numpy as np import tables as tb # Open a new empty HDF5 file fileh = tb.open_file("vlarray2.h5", mode="w") # Get the root group root = fileh.root # A test with VL length arrays: vlarray = fileh.create_vlarray(root, 'vlarray1', tb.Int32Atom(), "ragged array of ints") vlarray.append(np.array([5, 6])) vlarray.append(np.array([5, 6, 7])) vlarray.append([5, 6, 9, 8]) # Test with lists of bidimensional vectors vlarray = fileh.create_vlarray(root, 'vlarray2', tb.Int64Atom(shape=(2,)), "Ragged array of vectors") a = np.array([[1, 2], [1, 2]], dtype=np.int64) vlarray.append(a) vlarray.append(np.array([[1, 2], [3, 4]], dtype=np.int64)) vlarray.append(np.zeros(dtype=np.int64, shape=(0, 2))) vlarray.append(np.array([[5, 6]], dtype=np.int64)) # This makes an error (shape) # vlarray.append(array([[5], [6]], dtype=int64)) # This makes an error (type) # vlarray.append(array([[5, 6]], dtype=uint64)) # Test with strings vlarray = fileh.create_vlarray(root, 'vlarray3', tb.StringAtom(itemsize=3), "Ragged array of strings") vlarray.append(["123", "456", "3"]) vlarray.append(["456", "3"]) # This makes an error because of different string sizes than declared # vlarray.append(["1234", "456", "3"]) # Python flavor vlarray = fileh.create_vlarray(root, 'vlarray3b', tb.StringAtom(itemsize=3), "Ragged array of strings") vlarray.flavor = "python" vlarray.append(["123", "456", "3"]) vlarray.append(["456", "3"]) # Binary strings vlarray = fileh.create_vlarray(root, 'vlarray4', tb.UInt8Atom(), "pickled bytes") data = pickle.dumps((["123", "456"], "3")) vlarray.append(np.ndarray(buffer=data, dtype=np.uint8, shape=len(data))) # The next is a way of doing the same than before vlarray = fileh.create_vlarray(root, 'vlarray5', tb.ObjectAtom(), "pickled object") vlarray.append([["123", "456"], "3"]) # Boolean arrays are supported as well vlarray = fileh.create_vlarray(root, 'vlarray6', tb.BoolAtom(), "Boolean atoms") # The next lines are equivalent... vlarray.append([1, 0]) vlarray.append([1, 0, 3, 0]) # This will be converted to a boolean # This gives a TypeError # vlarray.append([1,0,1]) # Variable length strings vlarray = fileh.create_vlarray(root, 'vlarray7', tb.VLStringAtom(), "Variable Length String") vlarray.append("asd") vlarray.append("aaana") # Unicode variable length strings vlarray = fileh.create_vlarray(root, 'vlarray8', tb.VLUnicodeAtom(), "Variable Length Unicode String") vlarray.append("aaana") vlarray.append("") # The empty string vlarray.append("asd") vlarray.append("para\u0140lel") # Close the file fileh.close() # Open the file for reading fileh = tb.open_file("vlarray2.h5", mode="r") # Get the root group root = fileh.root for object in fileh.list_nodes(root, "Leaf"): arr = object.read() print(object.name, "-->", arr) print("number of objects in this row:", len(arr)) # Close the file fileh.close() PyTables-3.7.0/examples/vlarray3.py000066400000000000000000000011621416254111300171750ustar00rootroot00000000000000#!/usr/bin/env python3 """Example that shows how to easily save a variable number of atoms with a VLArray.""" import numpy as np import tables as tb N = 100 shape = (3, 3) np.random.seed(10) # For reproductible results f = tb.open_file("vlarray3.h5", mode="w") vlarray = f.create_vlarray(f.root, 'vlarray1', tb.Float64Atom(shape=shape), "ragged array of arrays") k = 0 for i in range(N): l = [] for j in range(np.random.randint(N)): l.append(np.random.randn(*shape)) k += 1 vlarray.append(l) print("Total number of atoms:", k) f.close() PyTables-3.7.0/examples/vlarray4.py000066400000000000000000000011621416254111300171760ustar00rootroot00000000000000#!/usr/bin/env python3 """Example that shows how to easily save a variable number of atoms with a VLArray.""" import numpy as np import tables as tb N = 100 shape = (3, 3) np.random.seed(10) # For reproductible results f = tb.open_file("vlarray4.h5", mode="w") vlarray = f.create_vlarray(f.root, 'vlarray1', tb.Float64Atom(shape=shape), "ragged array of arrays") k = 0 for i in range(N): l = [] for j in range(np.random.randint(N)): l.append(np.random.randn(*shape)) k += 1 vlarray.append(l) print("Total number of atoms:", k) f.close() PyTables-3.7.0/hdf5-blosc/000077500000000000000000000000001416254111300151705ustar00rootroot00000000000000PyTables-3.7.0/hdf5-blosc/.gitignore000066400000000000000000000004411416254111300171570ustar00rootroot00000000000000# Object files *.o *.ko *.obj *.elf # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ # Anything in the 'build' folder. build/ PyTables-3.7.0/hdf5-blosc/.travis.yml000066400000000000000000000004141416254111300173000ustar00rootroot00000000000000language: c os: - linux - osx compiler: - gcc - clang before_install: ./travis-before-install.sh install: sudo apt-get install libhdf5-serial-dev before_script: - mkdir build - cd build - cmake .. script: - cmake --build . --config Release - ctest PyTables-3.7.0/hdf5-blosc/CMakeLists.txt000066400000000000000000000045611416254111300177360ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.10) project(blosc_hdf5) include(ExternalProject) # options option(BUILD_TESTS "Build test programs form the blosc filter" ON) set(BLOSC_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/blosc") set(BLOSC_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/blosc") set(BLOSC_CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${BLOSC_INSTALL_DIR}) message("BLOSC_PREFIX='${BLOSC_PREFIX}'") message("BLOSC_INSTALL_DIR='${BLOSC_INSTALL_DIR}'") message("BLOSC_CMAKE_ARGS='${BLOSC_CMAKE_ARGS}'") message("GIT_EXECUTABLE='${GIT_EXECUTABLE}'") ExternalProject_Add(blosc PREFIX ${BLOSC_PREFIX} GIT_REPOSITORY https://github.com/Blosc/c-blosc.git INSTALL_DIR ${BLOSC_INSTALL_DIR} CMAKE_ARGS ${BLOSC_CMAKE_ARGS} ) # sources set(SOURCES src/blosc_filter.c) # dependencies if(MSVC) # FindHDF5.cmake does not find Windows installations. Try to # use an environment variable instead until the official "find" # file can be updated for Windows. # # Note that you have to set this environment variable by hand. file(TO_CMAKE_PATH "$ENV{HDF5_DIR}" HDF5_HINT) set(HDF5_DIR ${HDF5_HINT} CACHE STRING "Path to HDF5 CMake config directory.") find_package(HDF5 REQUIRED HINTS ${HDF5_DIR}) else(MSVC) find_package(HDF5 REQUIRED) endif(MSVC) include_directories(${HDF5_INCLUDE_DIRS}) # add blosc libraries add_library(blosc_shared SHARED IMPORTED) set_property(TARGET blosc_shared PROPERTY IMPORTED_LOCATION ${BLOSC_INSTALL_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}blosc${CMAKE_SHARED_LIBRARY_SUFFIX}) add_dependencies(blosc_shared project_blosc) include_directories(${BLOSC_INSTALL_DIR}/include) add_library(blosc_filter_shared SHARED ${SOURCES}) set_target_properties( blosc_filter_shared PROPERTIES OUTPUT_NAME blosc_filter) target_link_libraries(blosc_filter_shared blosc_shared ${HDF5_LIBRARIES}) # install install(FILES src/blosc_filter.h DESTINATION include COMPONENT HDF5_FILTER_DEV) install(TARGETS blosc_filter_shared DESTINATION lib COMPONENT HDF5_FILTER_DEV) # test message("LINK LIBRARIES='blosc_filter_shared ${HDF5_LIBRARIES}'") if(BUILD_TESTS) enable_testing() set(CMAKE_THREAD_PREFER_PTHREAD TRUE) find_package(Threads REQUIRED) set(LIBS ${LIBS} ${CMAKE_THREAD_LIBS_INIT}) add_executable(example src/example.c) target_link_libraries(example blosc_filter_shared ${HDF5_LIBRARIES} ${LIBS}) add_test(test_hdf5_filter example) endif(BUILD_TESTS) PyTables-3.7.0/hdf5-blosc/LICENSES/000077500000000000000000000000001416254111300163755ustar00rootroot00000000000000PyTables-3.7.0/hdf5-blosc/LICENSES/BLOSC.txt000066400000000000000000000021741416254111300200040ustar00rootroot00000000000000Blosc - A blocking, shuffling and lossless compression library Copyright (C) 2009-2015 Francesc Alted Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PyTables-3.7.0/hdf5-blosc/LICENSES/BLOSC_HDF5.txt000066400000000000000000000021751416254111300205530ustar00rootroot00000000000000Blosc for HDF5 - An HDF5 filter that uses the Blosc compressor. Copyright (C) 2009-2015 Francesc Alted Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PyTables-3.7.0/hdf5-blosc/LICENSES/H5PY.txt000066400000000000000000000030371416254111300176660ustar00rootroot00000000000000Copyright Notice and Statement for the h5py Project Copyright (c) 2008 Andrew Collette http://h5py.alfven.org All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: a. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. b. 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. c. Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 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. PyTables-3.7.0/hdf5-blosc/README.rst000066400000000000000000000034151416254111300166620ustar00rootroot00000000000000===================== Blosc filter for HDF5 ===================== :Travis CI: |travis| :And...: |powered| .. |travis| image:: https://travis-ci.org/Blosc/hdf5.png?branch=master :target: https://travis-ci.org/Blosc/hdf5 .. |powered| image:: http://b.repl.ca/v1/Powered--By-Blosc-blue.png :target: https://blosc.org This is an example of filter for HDF5 that uses the Blosc compressor. You need to be a bit careful before using this filter because you should not activate the shuffle right in HDF5, but rather from Blosc itself. This is because Blosc uses an SIMD shuffle internally which is much faster. Using the Blosc filter from HDF5 ================================ In order to register Blosc into your HDF5 application, you only need to call a function in blosc_filter.h, with the following signature: int register_blosc(char **version, char **date) Calling this will register the filter with the HDF5 library and will return info about the Blosc release in `**version` and `**date` char pointers. A non-negative return value indicates success. If the registration fails, an error is pushed onto the current error stack and a negative value is returned. An example C program ('src/example.c') is included which demonstrates the proper use of the filter. This filter has been tested against HDF5 versions 1.6.5 through 1.8.10. It is released under the MIT license (see LICENSE.txt for details). Compiling ========= The filter consists of a single 'src/blosc_filter.c' source file and 'src/blosc_filter.h' header, which will need the Blosc library installed to work. As an HDF5 plugin ================= Also, you can use blosc as an HDF5 plugin; see 'src/blosc_plugin.c' for details. Acknowledgments =============== See THANKS.rst. ---- **Enjoy data!** PyTables-3.7.0/hdf5-blosc/src/000077500000000000000000000000001416254111300157575ustar00rootroot00000000000000PyTables-3.7.0/hdf5-blosc/src/blosc_filter.c000066400000000000000000000203231416254111300205720ustar00rootroot00000000000000/* Copyright (C) 2010-2016 Francesc Alted http://blosc.org License: MIT (see LICENSE.txt) Filter program that allows the use of the Blosc filter in HDF5. This is based on the LZF filter interface (http://h5py.alfven.org) by Andrew Collette. */ #include #include #include #include #include "hdf5.h" #include "blosc_filter.h" #if defined(__GNUC__) #define PUSH_ERR(func, minor, str, ...) H5Epush(H5E_DEFAULT, __FILE__, func, __LINE__, H5E_ERR_CLS, H5E_PLINE, minor, str, ##__VA_ARGS__) #elif defined(_MSC_VER) #define PUSH_ERR(func, minor, str, ...) H5Epush(H5E_DEFAULT, __FILE__, func, __LINE__, H5E_ERR_CLS, H5E_PLINE, minor, str, __VA_ARGS__) #else /* This version is portable but it's better to use compiler-supported approaches for handling the trailing comma issue when possible. */ #define PUSH_ERR(func, minor, ...) H5Epush(H5E_DEFAULT, __FILE__, func, __LINE__, H5E_ERR_CLS, H5E_PLINE, minor, __VA_ARGS__) #endif /* defined(__GNUC__) */ #define GET_FILTER(a,b,c,d,e,f,g) H5Pget_filter_by_id(a,b,c,d,e,f,g,NULL) size_t blosc_filter(unsigned flags, size_t cd_nelmts, const unsigned cd_values[], size_t nbytes, size_t *buf_size, void **buf); herr_t blosc_set_local(hid_t dcpl, hid_t type, hid_t space); /* Register the filter, passing on the HDF5 return value */ int register_blosc(char **version, char **date){ int retval; H5Z_class_t filter_class = { H5Z_CLASS_T_VERS, (H5Z_filter_t)(FILTER_BLOSC), 1, 1, "blosc", NULL, (H5Z_set_local_func_t)(blosc_set_local), (H5Z_func_t)(blosc_filter) }; retval = H5Zregister(&filter_class); if(retval<0){ PUSH_ERR("register_blosc", H5E_CANTREGISTER, "Can't register Blosc filter"); } *version = strdup(BLOSC_VERSION_STRING); *date = strdup(BLOSC_VERSION_DATE); return 1; /* lib is available */ } /* Filter setup. Records the following inside the DCPL: 1. If version information is not present, set slots 0 and 1 to the filter revision and Blosc version, respectively. 2. Compute the type size in bytes and store it in slot 2. 3. Compute the chunk size in bytes and store it in slot 3. */ herr_t blosc_set_local(hid_t dcpl, hid_t type, hid_t space){ int ndims; int i; herr_t r; unsigned int typesize, basetypesize; unsigned int bufsize; hsize_t chunkdims[32]; unsigned int flags; size_t nelements = 8; unsigned int values[] = {0,0,0,0,0,0,0,0}; hid_t super_type; H5T_class_t classt; r = GET_FILTER(dcpl, FILTER_BLOSC, &flags, &nelements, values, 0, NULL); if(r<0) return -1; if(nelements < 4) nelements = 4; /* First 4 slots reserved. */ /* Set Blosc info in first two slots */ values[0] = FILTER_BLOSC_VERSION; values[1] = BLOSC_VERSION_FORMAT; ndims = H5Pget_chunk(dcpl, 32, chunkdims); if(ndims<0) return -1; if(ndims>32){ PUSH_ERR("blosc_set_local", H5E_CALLBACK, "Chunk rank exceeds limit"); return -1; } typesize = H5Tget_size(type); if (typesize==0) return -1; /* Get the size of the base type, even for ARRAY types */ classt = H5Tget_class(type); if (classt == H5T_ARRAY) { /* Get the array base component */ super_type = H5Tget_super(type); basetypesize = H5Tget_size(super_type); /* Release resources */ H5Tclose(super_type); } else { basetypesize = typesize; } /* Limit large typesizes (they are pretty inneficient to shuffle and, in addition, Blosc does not handle typesizes larger than 256 bytes). */ if (basetypesize > BLOSC_MAX_TYPESIZE) basetypesize = 1; values[2] = basetypesize; /* Get the size of the chunk */ bufsize = typesize; for (i=0; i= 5) { clevel = cd_values[4]; /* The compression level */ } if (cd_nelmts >= 6) { doshuffle = cd_values[5]; /* BLOSC_SHUFFLE, BLOSC_BITSHUFFLE */ /* bitshuffle is only meant for production in >= 1.8.0 */ #if ( (BLOSC_VERSION_MAJOR <= 1) && (BLOSC_VERSION_MINOR < 8) ) if (doshuffle == BLOSC_BITSHUFFLE) { PUSH_ERR("blosc_filter", H5E_CALLBACK, "this Blosc library version is not supported. Please update to >= 1.8"); goto failed; } #endif } if (cd_nelmts >= 7) { const char *complist; compcode = cd_values[6]; /* The Blosc compressor used */ /* Check that we actually have support for the compressor code */ complist = blosc_list_compressors(); code = blosc_compcode_to_compname(compcode, &compname); if (code == -1) { PUSH_ERR("blosc_filter", H5E_CALLBACK, "this Blosc library does not have support for " "the '%s' compressor, but only for: %s", compname, complist); goto failed; } } /* We're compressing */ if(!(flags & H5Z_FLAG_REVERSE)){ /* Allocate an output buffer exactly as long as the input data; if the result is larger, we simply return 0. The filter is flagged as optional, so HDF5 marks the chunk as uncompressed and proceeds. */ outbuf_size = (*buf_size); #ifdef BLOSC_DEBUG fprintf(stderr, "Blosc: Compress %zd chunk w/buffer %zd\n", nbytes, outbuf_size); #endif outbuf = malloc(outbuf_size); if (outbuf == NULL){ PUSH_ERR("blosc_filter", H5E_CALLBACK, "Can't allocate compression buffer"); goto failed; } blosc_set_compressor(compname); status = blosc_compress(clevel, doshuffle, typesize, nbytes, *buf, outbuf, nbytes); if (status < 0) { PUSH_ERR("blosc_filter", H5E_CALLBACK, "Blosc compression error"); goto failed; } /* We're decompressing */ } else { /* declare dummy variables */ size_t cbytes, blocksize; free(outbuf); /* Extract the exact outbuf_size from the buffer header. * * NOTE: the guess value got from "cd_values" corresponds to the * uncompressed chunk size but it should not be used in a general * cases since other filters in the pipeline can modify the buffere * size. */ blosc_cbuffer_sizes(*buf, &outbuf_size, &cbytes, &blocksize); #ifdef BLOSC_DEBUG fprintf(stderr, "Blosc: Decompress %zd chunk w/buffer %zd\n", nbytes, outbuf_size); #endif outbuf = malloc(outbuf_size); if(outbuf == NULL){ PUSH_ERR("blosc_filter", H5E_CALLBACK, "Can't allocate decompression buffer"); goto failed; } status = blosc_decompress(*buf, outbuf, outbuf_size); if(status <= 0){ /* decompression failed */ PUSH_ERR("blosc_filter", H5E_CALLBACK, "Blosc decompression error"); goto failed; } /* if !status */ } /* compressing vs decompressing */ if(status != 0){ free(*buf); *buf = outbuf; *buf_size = outbuf_size; return status; /* Size of compressed/decompressed data */ } failed: free(outbuf); return 0; } /* End filter function */ PyTables-3.7.0/hdf5-blosc/src/blosc_filter.h000066400000000000000000000010441416254111300205760ustar00rootroot00000000000000#ifndef FILTER_BLOSC_H #define FILTER_BLOSC_H #ifdef __cplusplus extern "C" { #endif #include "blosc.h" /* Filter revision number, starting at 1 */ /* #define FILTER_BLOSC_VERSION 1 */ #define FILTER_BLOSC_VERSION 2 /* multiple compressors since Blosc 1.3 */ /* Filter ID registered with the HDF Group */ #define FILTER_BLOSC 32001 /* Registers the filter with the HDF5 library. */ #if defined(_MSC_VER) __declspec(dllexport) #endif /* defined(_MSC_VER) */ int register_blosc(char **version, char **date); #ifdef __cplusplus } #endif #endif PyTables-3.7.0/hdf5-blosc/src/blosc_plugin.c000066400000000000000000000015401416254111300206030ustar00rootroot00000000000000/* * Dynamically loaded filter plugin for HDF5 blosc filter. * * Author: Kiyoshi Masui * Created: 2014 * */ #include #define H5Z_class_t_vers 2 #include "blosc_plugin.h" #include "blosc_filter.h" // Prototypes for filter function in blosc_filter.c. size_t blosc_filter(unsigned flags, size_t cd_nelmts, const unsigned cd_values[], size_t nbytes, size_t *buf_size, void **buf); herr_t blosc_set_local(hid_t dcpl, hid_t type, hid_t space); H5Z_class_t blosc_H5Filter[1] = {{ H5Z_CLASS_T_VERS, (H5Z_filter_t)(FILTER_BLOSC), 1, 1, "blosc", NULL, (H5Z_set_local_func_t)(blosc_set_local), (H5Z_func_t)(blosc_filter) }}; H5PL_type_t H5PLget_plugin_type(void) {return H5PL_TYPE_FILTER;} const void* H5PLget_plugin_info(void) {return blosc_H5Filter;} PyTables-3.7.0/hdf5-blosc/src/blosc_plugin.h000066400000000000000000000013301416254111300206050ustar00rootroot00000000000000/* * Dynamically loaded filter plugin for HDF5 blosc filter. * * Author: Kiyoshi Masui * Created: 2014 * * * Header file * ----------- * * This provides dynamically loaded HDF5 filter functionality (introduced * in HDF5-1.8.11, May 2013) to the blosc HDF5 filter. * * Usage: compile as a shared library and install either to the default * search location for HDF5 filter plugins (on Linux * /usr/local/hdf5/lib/plugin) or to a location pointed to by the * HDF5_PLUGIN_PATH environment variable. * */ #ifndef PLUGIN_BLOSC_H #define PLUGIN_BLOSC_H #include "H5PLextern.h" H5PL_type_t H5PLget_plugin_type(void); const void* H5PLget_plugin_info(void); #endif // PLUGIN_BLOSC_H PyTables-3.7.0/hdf5-blosc/src/example.c000066400000000000000000000065351416254111300175670ustar00rootroot00000000000000/* Copyright (C) 2010 Francesc Alted http://blosc.org License: MIT (see LICENSE.txt) Example program demonstrating use of the Blosc filter from C code. This is based on the LZF example (http://h5py.alfven.org) by Andrew Collette. To compile this program: h5cc blosc_filter.c example.c -o example -lblosc -lpthread To run: $ ./example Blosc version info: 1.3.0 ($Date:: 2014-01-11 #$) Success! $ h5ls -v example.h5 Opened "example.h5" with sec2 driver. dset Dataset {100/100, 100/100, 100/100} Location: 1:800 Links: 1 Chunks: {1, 100, 100} 40000 bytes Storage: 4000000 logical bytes, 126002 allocated bytes, 3174.55% utilization Filter-0: blosc-32001 OPT {2, 2, 4, 40000, 4, 1, 2} Type: native float */ #include #include "hdf5.h" #include "blosc_filter.h" #define SIZE 100*100*100 #define SHAPE {100,100,100} #define CHUNKSHAPE {1,100,100} int main(){ static float data[SIZE]; static float data_out[SIZE]; const hsize_t shape[] = SHAPE; const hsize_t chunkshape[] = CHUNKSHAPE; char *version, *date; int r, i; unsigned int cd_values[7]; int return_code = 1; hid_t fid, sid, dset, plist = 0; for(i=0; i0) H5Dclose(dset); if(sid>0) H5Sclose(sid); if(plist>0) H5Pclose(plist); if(fid>0) H5Fclose(fid); return return_code; } PyTables-3.7.0/hdf5-blosc/travis-before-install.sh000077500000000000000000000006171416254111300217470ustar00rootroot00000000000000#/bin/sh -f # things to do for travis-ci in the before_install section if ( test "`uname -s`" = "Darwin" ) then #cmake v2.8.12 is installed on the Mac workers now #brew update #brew install cmake echo else #install a newer cmake since at this time Travis only has version 2.8.7 sudo add-apt-repository --yes ppa:kalakris/cmake sudo apt-get update -qq sudo apt-get install cmake fi PyTables-3.7.0/pyproject.toml000066400000000000000000000002631416254111300161570ustar00rootroot00000000000000[build-system] requires = [ "setuptools >=42.0", "wheel", "oldest-supported-numpy", "packaging", "Cython >=0.29.21", ] build-backend = "setuptools.build_meta" PyTables-3.7.0/requirements.txt000066400000000000000000000001771416254111300165330ustar00rootroot00000000000000# Keep in sync with tables/req_versions.py and # doc/source/usersguide/installation.rst numpy>=1.19.0 numexpr>=2.6.2 packaging PyTables-3.7.0/setup.cfg000066400000000000000000000043411416254111300150650ustar00rootroot00000000000000[metadata] name = tables # version = attr: src.VERSION description = Hierarchical datasets for Python long_description = PyTables is a package for managing hierarchical datasets and designed to efficiently cope with extremely large amounts of data. PyTables is built on top of the HDF5 library and the NumPy package and features an object-oriented interface that, combined with C-code generated from Cython sources, makes of it a fast, yet extremely easy to use tool for interactively save and retrieve large amounts of data. long_description_content_type = text/x-rst author = Francesc Alted, Ivan Vilata, Antonio Valentino, Anthony Scopatz et al. author_email = pytables@pytables.org maintainer = PyTables maintainers maintainer_email = pytables@pytables.org url = https://www.pytables.org license = BSD 3-Clause License license_files = LICENSE.txt classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers Intended Audience :: Information Technology Intended Audience :: Science/Research License :: OSI Approved :: BSD License Operating System :: Microsoft :: Windows Operating System :: Unix Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Topic :: Database Topic :: Software Development :: Libraries :: Python Modules platforms = any keywords = hdf5 [options] python_requires = >=3.6 zip_safe = False # install_requires = # numpy>=1.19.0 # numexpr>=2.6.2 # packaging packages = find: include_package_data = True [options.entry_points] console_scripts = ptdump = tables.scripts.ptdump:main ptrepack = tables.scripts.ptrepack:main pt2to3 = tables.scripts.pt2to3:main pttree = tables.scripts.pttree:main [options.extras_require] doc = sphinx>=1.1 sphinx_rtd_theme numpydoc ipython [options.packages.find] exclude = bench [options.package_data] tables.tests = *.h5, *.mat tables.nodes.tests = *.dat, *.xbm, *.h5 [sdist] formats = gztar PyTables-3.7.0/setup.py000077500000000000000000001132171416254111300147640ustar00rootroot00000000000000#!/usr/bin/env python """Setup script for the tables package""" import os import sys import ctypes import shutil import platform import tempfile import textwrap import subprocess from pathlib import Path # Using ``setuptools`` enables lots of goodies from setuptools import setup, Extension from setuptools.command.build_ext import build_ext import pkg_resources from packaging.version import Version # The name for the pkg-config utility PKG_CONFIG = "pkg-config" # Some functions for showing errors and warnings. def _print_admonition(kind, head, body): tw = textwrap.TextWrapper( initial_indent=" ", subsequent_indent=" " ) print(f".. {kind.upper()}:: {head}") for line in tw.wrap(body): print(line) def exit_with_error(head, body=""): _print_admonition("error", head, body) sys.exit(1) def print_warning(head, body=""): _print_admonition("warning", head, body) def get_version(filename): import re with open(filename) as fd: data = fd.read() mobj = re.search( r'''^__version__\s*=\s*(?P['"])(?P.*)(?P=quote)''', data, re.MULTILINE) return mobj.group('version') # Get the HDF5 version provided the 'H5public.h' header def get_hdf5_version(headername): major, minor, release = None, None, None for line in headername.read_text().splitlines(): if "H5_VERS_MAJOR" in line: major = int(line.split()[2]) elif "H5_VERS_MINOR" in line: minor = int(line.split()[2]) elif "H5_VERS_RELEASE" in line: release = int(line.split()[2]) if None not in (major, minor, release): break else: exit_with_error("Unable to detect HDF5 library version!") return Version(f"{major}.{minor}.{release}") # Get the Blosc version provided the 'blosc.h' header def get_blosc_version(headername): major, minor, release = None, None, None for line in headername.read_text().splitlines(): if "BLOSC_VERSION_MAJOR" in line: major = int(line.split()[2]) elif "BLOSC_VERSION_MINOR" in line: minor = int(line.split()[2]) elif "BLOSC_VERSION_RELEASE" in line: release = int(line.split()[2]) if None not in (major, minor, release): break else: exit_with_error("Unable to detect Blosc library version!") return Version(f"{major}.{minor}.{release}") def newer(source, target): """Return true if 'source' exists and is more recently modified than 'target', or if 'source' exists and 'target' doesn't. Return false if both exist and 'target' is the same age or younger than 'source'. Raise FileNotFoundError if 'source' does not exist. """ source = Path(source) if not source.exists(): raise FileNotFoundError(f"file '{source.absolute()}' does not exist") target = Path(target) if not target.exists(): return True return source.stat().st_mtime > target.stat().st_mtime # https://github.com/pypa/setuptools/issues/2806 def new_compiler(): from setuptools import Distribution build_ext = Distribution().get_command_obj("build_ext") build_ext.finalize_options() # register an extension to ensure a compiler is created build_ext.extensions = [Extension("ignored", ["ignored.c"])] # disable building fake extensions build_ext.build_extensions = lambda: None # run to populate self.compiler build_ext.run() return build_ext.compiler def add_from_path(envname, dirs): dirs.extend( Path(x) for x in os.environ.get(envname, "").split(os.pathsep) if x ) def add_from_flags(envname, flag_key, dirs): dirs.extend( Path(flag[len(flag_key):]) for flag in os.environ.get(envname, "").split() if flag.startswith(flag_key) ) def _find_file_path(name, locations, prefixes=("",), suffixes=("",)): for prefix in prefixes: for suffix in suffixes: for location in locations: path = location / f"{prefix}{name}{suffix}" if path.is_file(): return str(path) return None # We need to avoid importing numpy until we can be sure it's installed # This approach is based on this SO answer http://stackoverflow.com/a/21621689 # This is also what pandas does. class BuildExtensions(build_ext): """Subclass setuptools build_ext command BuildExtensions does two things 1) it makes sure numpy is available 2) it injects numpy's core/include directory in the include_dirs parameter of all extensions 3) it runs the original build_ext command """ def run(self): # According to # https://pip.pypa.io/en/stable/reference/pip_install.html#installation-order # at this point we can be sure pip has already installed numpy numpy_incl = pkg_resources.resource_filename( "numpy", "core/include" ) for ext in self.extensions: if ( hasattr(ext, "include_dirs") and numpy_incl not in ext.include_dirs ): ext.include_dirs.append(numpy_incl) build_ext.run(self) if __name__ == "__main__": ROOT = Path(__file__).resolve().parent VERSION = get_version(ROOT.joinpath("tables/__init__.py")) # Fetch the requisites requirements = (ROOT / "requirements.txt").read_text().splitlines() # `cpuinfo.py` uses multiprocessing to check CPUID flags. On Windows, the # entire setup script needs to be protected as a result # For guessing the capabilities of the CPU for C-Blosc try: import cpuinfo cpu_info = cpuinfo.get_cpu_info() cpu_flags = cpu_info["flags"] except Exception as e: print("cpuinfo failed, assuming no CPU features:", e) cpu_flags = [] # The minimum required versions min_python_version = (3, 6) # Check for Python if sys.version_info < min_python_version: exit_with_error("You need Python 3.6 or greater to install PyTables!") print(f"* Using Python {sys.version.splitlines()[0]}") try: import cython print(f"* Found cython {cython.__version__}") del cython except ImportError: pass # Minimum required versions for numpy, numexpr and HDF5 _min_versions = {} exec((ROOT / "tables" / "req_versions.py").read_text(), _min_versions) min_hdf5_version = _min_versions["min_hdf5_version"] min_blosc_version = _min_versions["min_blosc_version"] min_blosc_bitshuffle_version = _min_versions[ "min_blosc_bitshuffle_version" ] # ---------------------------------------------------------------------- debug = "--debug" in sys.argv # Global variables lib_dirs = [] inc_dirs = [Path("hdf5-blosc/src")] optional_libs = [] data_files = [] # list of data files to add to packages (mainly for DLL's) default_header_dirs = None default_library_dirs = None default_runtime_dirs = None if os.name == "posix": prefixes = ("/usr/local", "/sw", "/opt", "/opt/local", "/usr", "/") prefix_paths = [Path(x) for x in prefixes] default_header_dirs = [] add_from_path("CPATH", default_header_dirs) add_from_path("C_INCLUDE_PATH", default_header_dirs) add_from_flags("CPPFLAGS", "-I", default_header_dirs) add_from_flags("CFLAGS", "-I", default_header_dirs) default_header_dirs.extend(_tree / "include" for _tree in prefix_paths) default_library_dirs = [] add_from_flags("LDFLAGS", "-L", default_library_dirs) default_library_dirs.extend( _tree / _arch for _tree in prefix_paths for _arch in ("lib64", "lib") ) default_runtime_dirs = default_library_dirs elif os.name == "nt": default_header_dirs = [] # no default, must be given explicitly default_library_dirs = [] # no default, must be given explicitly default_runtime_dirs = [ # look for DLL files in ``%PATH%`` Path(_path) for _path in os.environ["PATH"].split(";") ] # Add the \Windows\system to the runtime list (necessary for Vista) default_runtime_dirs.append(Path("\\windows\\system")) # Add the \path_to_python\DLLs and tables package to the list default_runtime_dirs.append( Path(sys.prefix) / "Lib" / "site-packages" / "tables" ) # Gcc 4.0.1 on Mac OS X 10.4 does not seem to include the default # header and library paths. See ticket #18. if sys.platform.lower().startswith("darwin"): inc_dirs.extend(default_header_dirs) lib_dirs.extend(default_library_dirs) class BasePackage: _library_prefixes = [] _library_suffixes = [] _runtime_prefixes = [] _runtime_suffixes = [] _component_dirs = [] def __init__( self, name, tag, header_name, library_name, target_function=None ): self.name = name self.tag = tag self.header_name = header_name self.library_name = library_name self.runtime_name = library_name self.target_function = target_function def find_header_path(self, locations=default_header_dirs): return _find_file_path( self.header_name, locations, suffixes=[".h"] ) def find_library_path(self, locations=default_library_dirs): return _find_file_path( self.library_name, locations, self._library_prefixes, self._library_suffixes, ) def find_runtime_path(self, locations=default_runtime_dirs): """ returns True if the runtime can be found returns None otherwise """ # An explicit path can not be provided for runtime libraries. # (The argument is accepted for compatibility with previous # methods.) # dlopen() won't tell us where the file is, just whether # success occurred, so this returns True instead of a filename for prefix in self._runtime_prefixes: for suffix in self._runtime_suffixes: try: ctypes.CDLL(f"{prefix}{self.runtime_name}{suffix}") except OSError: pass else: return True def _pkg_config(self, flags): try: cmd = [PKG_CONFIG] + flags.split() + [self.library_name] config = subprocess.check_output(cmd, stderr=subprocess.STDOUT) except (OSError, subprocess.CalledProcessError): return [] else: return config.decode().strip().split() def find_directories(self, location, use_pkgconfig=False): dirdata = [ (self.header_name, self.find_header_path, default_header_dirs), ( self.library_name, self.find_library_path, default_library_dirs, ), ( self.runtime_name, self.find_runtime_path, default_runtime_dirs, ), ] locations = [] if location: # The path of a custom install of the package has been # provided, so the directories where the components # (headers, libraries, runtime) are going to be searched # are constructed by appending platform-dependent # component directories to the given path. # Remove leading and trailing '"' chars that can mislead # the finding routines on Windows machines locations = [ Path(str(location).strip('"')) / compdir for compdir in self._component_dirs ] if use_pkgconfig: # header pkgconfig_header_dirs = [ Path(d[2:]) for d in self._pkg_config("--cflags") if d.startswith("-I") ] if pkgconfig_header_dirs: print( f"* pkg-config header dirs for {self.name}:", ", ".join(str(x) for x in pkgconfig_header_dirs), ) # library pkgconfig_library_dirs = [ Path(d[2:]) for d in self._pkg_config("--libs-only-L") if d.startswith("-L") ] if pkgconfig_library_dirs: print( f"* pkg-config library dirs for {self.name}:", ", ".join(str(x) for x in pkgconfig_library_dirs), ) # runtime pkgconfig_runtime_dirs = pkgconfig_library_dirs pkgconfig_dirs = [ pkgconfig_header_dirs, pkgconfig_library_dirs, pkgconfig_runtime_dirs, ] else: pkgconfig_dirs = [None, None, None] directories = [None, None, None] # headers, libraries, runtime for idx, (name, find_path, default_dirs) in enumerate(dirdata): path = find_path( pkgconfig_dirs[idx] or locations or default_dirs ) if path: if path is True: directories[idx] = True continue # Take care of not returning a directory component # included in the name. For instance, if name is # 'foo/bar' and path is '/path/foo/bar.h', do *not* # take '/path/foo', but just '/path'. This also works # for name 'libfoo.so' and path '/path/libfoo.so'. # This has been modified to just work over include files. # For libraries, its names can be something like 'bzip2' # and if they are located in places like: # \stuff\bzip2-1.0.3\lib\bzip2.lib # then, the directory will be returned as '\stuff' (!!) # F. Alted 2006-02-16 if idx == 0: directories[idx] = Path(path[: path.rfind(name)]) else: directories[idx] = Path(path).parent return tuple(directories) class PosixPackage(BasePackage): _library_prefixes = ["lib"] _library_suffixes = [".so", ".dylib", ".a"] _runtime_prefixes = _library_prefixes _runtime_suffixes = [".so", ".dylib"] _component_dirs = ["include", "lib", "lib64"] class WindowsPackage(BasePackage): _library_prefixes = [""] _library_suffixes = [".lib"] _runtime_prefixes = [""] _runtime_suffixes = [".dll"] # lookup in '.' seems necessary for LZO2 _component_dirs = ["include", "lib", "dll", "bin", "."] def find_runtime_path(self, locations=default_runtime_dirs): # An explicit path can not be provided for runtime libraries. # (The argument is accepted for compatibility with previous # methods.) return _find_file_path( self.runtime_name, default_runtime_dirs, self._runtime_prefixes, self._runtime_suffixes, ) if os.name == "posix": _Package = PosixPackage _platdep = { # package tag -> platform-dependent components "HDF5": ["hdf5"], "LZO2": ["lzo2"], "LZO": ["lzo"], "BZ2": ["bz2"], "BLOSC": ["blosc"], } elif os.name == "nt": _Package = WindowsPackage _platdep = { # package tag -> platform-dependent components "HDF5": ["hdf5", "hdf5"], "LZO2": ["lzo2", "lzo2"], "LZO": ["liblzo", "lzo1"], "BZ2": ["bzip2", "bzip2"], "BLOSC": ["blosc", "blosc"], } # Copy the next DLL's to binaries by default. dll_files = [ # '\\windows\\system\\zlib1.dll', # '\\windows\\system\\szip.dll', ] if os.environ.get("HDF5_USE_PREFIX", None): # This is used on CI systems to link against HDF5 library # The vendored `hdf5.dll` in a wheel is renamed to: # `pytables_hdf5.dll` This should prevent DLL Hell. print( "* HDF5_USE_PREFIX: Trying to build against pytables_hdf5.dll" ) _platdep["HDF5"] = ["pytables_hdf5", "pytables_hdf5"] if debug: _platdep["HDF5"] = ["hdf5_D", "hdf5_D"] else: _Package = None _platdep = {} exit_with_error(f"Unsupported OS: {os.name}") hdf5_package = _Package("HDF5", "HDF5", "H5public", *_platdep["HDF5"]) hdf5_package.target_function = "H5close" lzo2_package = _Package( "LZO 2", "LZO2", str(Path("lzo/lzo1x")), *_platdep["LZO2"] ) lzo2_package.target_function = "lzo_version_date" lzo1_package = _Package("LZO 1", "LZO", "lzo1x", *_platdep["LZO"]) lzo1_package.target_function = "lzo_version_date" bzip2_package = _Package("bzip2", "BZ2", "bzlib", *_platdep["BZ2"]) bzip2_package.target_function = "BZ2_bzlibVersion" blosc_package = _Package("blosc", "BLOSC", "blosc", *_platdep["BLOSC"]) blosc_package.target_function = "blosc_list_compressors" # Blosc >= 1.3 # ----------------------------------------------------------------- def_macros = [("NDEBUG", 1)] # Define macros for Windows platform if os.name == "nt": def_macros.append(("WIN32", 1)) def_macros.append(("_HDF5USEDLL_", 1)) def_macros.append(("H5_BUILT_AS_DYNAMIC_LIB", 1)) # Allow setting the HDF5 dir and additional link flags either in # the environment or on the command line. # First check the environment... HDF5_DIR = os.environ.get("HDF5_DIR", "") LZO_DIR = os.environ.get("LZO_DIR", "") BZIP2_DIR = os.environ.get("BZIP2_DIR", "") BLOSC_DIR = os.environ.get("BLOSC_DIR", "") LFLAGS = os.environ.get("LFLAGS", "").split() # in GCC-style compilers, -w in extra flags will get rid of copious # 'uninitialized variable' Cython warnings. However, this shouldn't be # the default as it will suppress *all* the warnings, which definitely # is not a good idea. CFLAGS = os.environ.get("CFLAGS", "").split() LIBS = os.environ.get("LIBS", "").split() CONDA_PREFIX = os.environ.get("CONDA_PREFIX", "") # We start using pkg-config since some distributions are putting HDF5 # (and possibly other libraries) in exotic locations. See issue #442. if shutil.which(PKG_CONFIG): USE_PKGCONFIG = os.environ.get("USE_PKGCONFIG", "TRUE") else: USE_PKGCONFIG = "FALSE" # ...then the command line. # Handle --hdf5=[PATH] --lzo=[PATH] --bzip2=[PATH] --blosc=[PATH] # --lflags=[FLAGS] --cflags=[FLAGS] and --debug for arg in list(sys.argv): key, _, val = arg.partition("=") if key == "--hdf5": HDF5_DIR = Path(val).expanduser() elif key == "--lzo": LZO_DIR = Path(val).expanduser() elif key == "--bzip2": BZIP2_DIR = Path(val).expanduser() elif key == "--blosc": BLOSC_DIR = Path(val).expanduser() elif key == "--lflags": LFLAGS = val.split() elif key == "--cflags": CFLAGS = val.split() elif key == "--debug": # For debugging (mainly compression filters) if os.name != "nt": # to prevent including dlfcn.h by utils.c!!! def_macros = [("DEBUG", 1)] # Don't delete this argument. It maybe useful for distutils # when adding more flags later on continue elif key == "--use-pkgconfig": USE_PKGCONFIG = val CONDA_PREFIX = "" elif key == "--no-conda": CONDA_PREFIX = "" else: continue sys.argv.remove(arg) USE_PKGCONFIG = USE_PKGCONFIG.upper() == "TRUE" print("* USE_PKGCONFIG:", USE_PKGCONFIG) # For windows, search for the hdf5 dll in the path and use it if found. # This is much more convenient than having to manually set an environment # variable to rebuild pytables if not HDF5_DIR and os.name == "nt": import ctypes.util if not debug: libdir = ctypes.util.find_library( "hdf5.dll" ) or ctypes.util.find_library("hdf5dll.dll") else: libdir = ctypes.util.find_library( "hdf5_D.dll" ) or ctypes.util.find_library("hdf5ddll.dll") # Like 'C:\\Program Files\\HDF Group\\HDF5\\1.8.8\\bin\\hdf5dll.dll' if libdir: # Strip off the filename and the 'bin' directory HDF5_DIR = Path(libdir).parent.parent print(f"* Found HDF5 using system PATH ('{libdir}')") if CONDA_PREFIX: CONDA_PREFIX = Path(CONDA_PREFIX) print(f"* Found conda env: ``{CONDA_PREFIX}``") if os.name == "nt": CONDA_PREFIX = CONDA_PREFIX / "Library" # The next flag for the C compiler is needed for finding the C headers for # the Cython extensions CFLAGS.append("-Isrc") # Force the 1.8.x HDF5 API even if the library as been compiled to use the # 1.6.x API by default CFLAGS.extend([ "-DH5_USE_18_API", "-DH5Acreate_vers=2", "-DH5Aiterate_vers=2", "-DH5Dcreate_vers=2", "-DH5Dopen_vers=2", "-DH5Eclear_vers=2", "-DH5Eprint_vers=2", "-DH5Epush_vers=2", "-DH5Eset_auto_vers=2", "-DH5Eget_auto_vers=2", "-DH5Ewalk_vers=2", "-DH5E_auto_t_vers=2", "-DH5Gcreate_vers=2", "-DH5Gopen_vers=2", "-DH5Pget_filter_vers=2", "-DH5Pget_filter_by_id_vers=2", # "-DH5Pinsert_vers=2", # "-DH5Pregister_vers=2", # "-DH5Rget_obj_type_vers=2", "-DH5Tarray_create_vers=2", # "-DH5Tcommit_vers=2", "-DH5Tget_array_dims_vers=2", # "-DH5Topen_vers=2", "-DH5Z_class_t_vers=2", ]) # H5Oget_info_by_name seems to have performance issues (see gh-402), so we # need to use teh deprecated H5Gget_objinfo function # CFLAGS.append("-DH5_NO_DEPRECATED_SYMBOLS") # Do not use numpy deprecated API CFLAGS.append("-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION") # Try to locate the compulsory and optional libraries. lzo2_enabled = False compiler = new_compiler() for (package, location) in [ (hdf5_package, HDF5_DIR), (lzo2_package, LZO_DIR), (lzo1_package, LZO_DIR), (bzip2_package, BZIP2_DIR), (blosc_package, BLOSC_DIR), ]: if package.tag == "LZO" and lzo2_enabled: print( f"* Skipping detection of {lzo1_package.name} " f"since {lzo2_package.name} has already been found." ) continue # do not use LZO 1 if LZO 2 is available # if a package location is not specified, try to find it in conda env if not location and CONDA_PREFIX: location = CONDA_PREFIX # looking for lzo/lzo1x.h but pkgconfig already returns # '/usr/include/lzo' use_pkgconfig = USE_PKGCONFIG and package.tag != "LZO2" (hdrdir, libdir, rundir) = package.find_directories( location, use_pkgconfig=use_pkgconfig ) # check if HDF5 library uses old DLL naming scheme if hdrdir and package.tag == "HDF5": hdf5_version = get_hdf5_version(Path(hdrdir) / "H5public.h") if hdf5_version < min_hdf5_version: exit_with_error( f"Unsupported HDF5 version! HDF5 v{min_hdf5_version}+ " f"required. Found version v{hdf5_version}" ) if os.name == "nt" and hdf5_version < Version("1.8.10"): # Change in DLL naming happened in 1.8.10 hdf5_old_dll_name = "hdf5dll" if not debug else "hdf5ddll" package.library_name = hdf5_old_dll_name package.runtime_name = hdf5_old_dll_name _platdep["HDF5"] = [hdf5_old_dll_name, hdf5_old_dll_name] _, libdir, rundir = package.find_directories( location, use_pkgconfig=USE_PKGCONFIG ) # check if the library is in the standard compiler paths if not libdir and package.target_function: libdir = compiler.has_function( package.target_function, libraries=(package.library_name,) ) if not (hdrdir and libdir): if package.tag in ["HDF5"]: # these are compulsory! pname, ptag = package.name, package.tag exit_with_error( f"Could not find a local {pname} installation.", f"You may need to explicitly state where your local " f"{pname} headers and library can be found by setting " f"the ``{ptag}_DIR`` environment variable or by using " f"the ``--{ptag.lower()}`` command-line option.", ) if package.tag == "BLOSC": # this is optional, but comes with sources print( f"* Could not find {package.name} headers and library; " f"using internal sources." ) else: print( f"* Could not find {package.name} headers and library; " f"disabling support for it." ) continue # look for the next library if libdir in ("", True): print( f"* Found {package.name} headers at ``{hdrdir}``, the library " f"is located in the standard system search dirs." ) else: print( f"* Found {package.name} headers at ``{hdrdir}``, " f"library at ``{libdir}``." ) if hdrdir not in default_header_dirs: inc_dirs.append(Path(hdrdir)) # save header directory if needed if libdir not in default_library_dirs and libdir not in ("", True): # save library directory if needed lib_dirs.append(Path(libdir)) if package.tag not in ["HDF5"]: # Keep record of the optional libraries found. optional_libs.append(package.tag) def_macros.append((f"HAVE_{package.tag}_LIB", 1)) if hdrdir and package.tag == "BLOSC": blosc_version = get_blosc_version(Path(hdrdir) / "blosc.h") if blosc_version < min_blosc_version: optional_libs.pop() # Remove Blosc from the discovered libs print_warning( f"Unsupported Blosc version installed! Blosc " f"{min_blosc_version}+ required. Found version " f"{blosc_version}. Using internal Blosc sources." ) if blosc_version < min_blosc_bitshuffle_version: print_warning( f"This Blosc version does not support the BitShuffle " f"filter. Minimum desirable version is " f"{min_blosc_bitshuffle_version}. " f"Found version: {blosc_version}" ) if not rundir: loc = { "posix": "the default library paths", "nt": "any of the directories in %%PATH%%", }[os.name] if "bdist_wheel" in sys.argv and os.name == "nt": exit_with_error( f"Could not find the {package.name} runtime.", f"The {package.name} shared library was *not* found in " f"{loc}. Cannot build wheel without the runtime.", ) else: print_warning( f"Could not find the {package.name} runtime.", f"The {package.name} shared library was *not* found " f"in {loc}. In case of runtime problems, please " f"remember to install it.", ) if os.name == "nt": # LZO DLLs cannot be copied to the binary package for license # reasons if package.tag not in ["LZO", "LZO2"]: dll_file = f"{_platdep[package.tag][1]}.dll" # If DLL is not in rundir, do nothing. This can be useful # for BZIP2, that can be linked either statically (.LIB) # or dynamically (.DLL) if rundir is not None: dll_files.append(Path(rundir) / dll_file) if os.name == "nt" and package.tag in ["HDF5"]: # hdf5.dll usually depends on zlib.dll import ctypes.util z_lib_path = ctypes.util.find_library("zlib.dll") if z_lib_path: print(f"* Adding zlib.dll (hdf5 dependency): ``{z_lib_path}``") dll_files.append(z_lib_path) if package.tag == "LZO2": lzo2_enabled = True lzo_package = lzo2_package if lzo2_enabled else lzo1_package # ------------------------------------------------------------------------------ cython_extnames = [ "utilsextension", "hdf5extension", "tableextension", "linkextension", "_comp_lzo", "_comp_bzip2", "lrucacheextension", "indexesextension", ] def get_cython_extfiles(extnames): extdir = Path("tables") extfiles = {} for extname in extnames: extfile = extdir / extname extpfile = extfile.with_suffix(".pyx") extcfile = extfile.with_suffix(".c") if not extcfile.exists() or newer(extpfile, extcfile): # This is the only place where Cython is needed, but every # developer should have it installed, so it should not be # a hard requisite from Cython.Build import cythonize cythonize(str(extpfile), language_level="2") extfiles[extname] = extcfile return extfiles cython_extfiles = get_cython_extfiles(cython_extnames) # -------------------------------------------------------------------- if os.name == "nt": for dll_file in dll_files: shutil.copy(dll_file, 'tables') dll_dir = Path('tables') dll_files = [dll_dir / Path(dll_file).name for dll_file in dll_files] # Add DLL's to the final package for windows data_files.append((Path("Lib/site-packages/tables"), dll_files)) ADDLIBS = [hdf5_package.library_name] # List of Blosc file dependencies blosc_path = Path("c-blosc/blosc") int_complibs_path = Path("c-blosc/internal-complibs") blosc_sources = [Path("hdf5-blosc/src/blosc_filter.c")] if "BLOSC" not in optional_libs: if not os.environ.get("PYTABLES_NO_EMBEDDED_LIBS", None) is None: exit_with_error( "Unable to find the blosc library. " "The embedded copy of the blosc sources can't be used because " "the PYTABLES_NO_EMBEDDED_LIBS environment variable has been " "specified)." ) # Compiling everything from sources # Blosc + BloscLZ sources blosc_sources += [ f for f in blosc_path.glob("*.c") if "avx2" not in f.stem and "sse2" not in f.stem ] blosc_sources += int_complibs_path.glob("lz4*/*.c") # LZ4 sources blosc_sources += int_complibs_path.glob("zlib*/*.c") # Zlib sources blosc_sources += int_complibs_path.glob("zstd*/*/*.c") # Zstd sources # Finally, add all the include dirs... inc_dirs += [blosc_path] inc_dirs += int_complibs_path.glob("*") inc_dirs += int_complibs_path.glob("zstd*/common") inc_dirs += int_complibs_path.glob("zstd*") # ...and the macros for all the compressors supported def_macros += [("HAVE_LZ4", 1), ("HAVE_ZLIB", 1), ("HAVE_ZSTD", 1)] # Add extra flags for optimizing shuffle in include Blosc def compiler_has_flags(compiler, flags): with tempfile.NamedTemporaryFile( mode="w", suffix=".c", delete=False ) as fd: fd.write("int main() {return 0;}") try: compiler.compile([fd.name], extra_preargs=flags) except Exception: return False else: return True finally: Path(fd.name).unlink() # Set flags for SSE2 and AVX2 preventing false detection in case # of emulation if platform.machine() != 'aarch64': # SSE2 if "sse2" in cpu_flags and "DISABLE_SSE2" not in os.environ: print("SSE2 detected and enabled") CFLAGS.append("-DSHUFFLE_SSE2_ENABLED") if os.name == "nt": # Windows always should have support for SSE2 # (present in all x86/amd64 architectures since 2003) def_macros += [("__SSE2__", 1)] else: # On UNIX, both gcc and clang understand -msse2 CFLAGS.append("-msse2") blosc_sources += blosc_path.glob("*sse2*.c") # AVX2 if "avx2" in cpu_flags and "DISABLE_AVX2" not in os.environ: print("AVX2 detected and enabled") if os.name == "nt": def_macros += [("__AVX2__", 1)] CFLAGS.append("-DSHUFFLE_AVX2_ENABLED") blosc_sources += blosc_path.glob("*avx2*.c") elif compiler_has_flags(compiler, ["-mavx2"]): CFLAGS.append("-DSHUFFLE_AVX2_ENABLED") CFLAGS.append("-mavx2") blosc_sources += blosc_path.glob("*avx2*.c") else: ADDLIBS += ["blosc"] utilsExtension_libs = LIBS + ADDLIBS hdf5Extension_libs = LIBS + ADDLIBS tableExtension_libs = LIBS + ADDLIBS linkExtension_libs = LIBS + ADDLIBS indexesExtension_libs = LIBS + ADDLIBS lrucacheExtension_libs = [] # Doesn't need external libraries # Compressor modules only need other libraries if they are enabled. _comp_lzo_libs = LIBS[:] _comp_bzip2_libs = LIBS[:] for (package, complibs) in [ (lzo_package, _comp_lzo_libs), (bzip2_package, _comp_bzip2_libs), ]: if package.tag in optional_libs: complibs.extend([hdf5_package.library_name, package.library_name]) # Extension expects strings, so we have to convert Path to str blosc_sources = [str(x) for x in blosc_sources] inc_dirs = [str(x) for x in inc_dirs] extension_kwargs = { "extra_compile_args": CFLAGS, "extra_link_args": LFLAGS, "library_dirs": [str(x) for x in lib_dirs], "define_macros": def_macros, "include_dirs": [str(x) for x in inc_dirs], } extensions = [ Extension( "tables.utilsextension", sources=[ str(cython_extfiles["utilsextension"]), "src/utils.c", "src/H5ARRAY.c", "src/H5ATTR.c", ] + blosc_sources, libraries=utilsExtension_libs, **extension_kwargs, ), Extension( "tables.hdf5extension", sources=[ str(cython_extfiles["hdf5extension"]), "src/utils.c", "src/typeconv.c", "src/H5ARRAY.c", "src/H5ARRAY-opt.c", "src/H5VLARRAY.c", "src/H5ATTR.c", ] + blosc_sources, libraries=hdf5Extension_libs, **extension_kwargs, ), Extension( "tables.tableextension", sources=[ str(cython_extfiles["tableextension"]), "src/utils.c", "src/typeconv.c", "src/H5TB-opt.c", "src/H5ATTR.c", ] + blosc_sources, libraries=tableExtension_libs, **extension_kwargs, ), Extension( "tables._comp_lzo", sources=[str(cython_extfiles["_comp_lzo"]), "src/H5Zlzo.c"], libraries=_comp_lzo_libs, **extension_kwargs, ), Extension( "tables._comp_bzip2", sources=[str(cython_extfiles["_comp_bzip2"]), "src/H5Zbzip2.c"], libraries=_comp_bzip2_libs, **extension_kwargs, ), Extension( "tables.linkextension", sources=[str(cython_extfiles["linkextension"])], libraries=tableExtension_libs, **extension_kwargs, ), Extension( "tables.lrucacheextension", sources=[str(cython_extfiles["lrucacheextension"])], libraries=lrucacheExtension_libs, **extension_kwargs, ), Extension( "tables.indexesextension", sources=[ str(cython_extfiles["indexesextension"]), "src/H5ARRAY-opt.c", "src/idx-opt.c", ], libraries=indexesExtension_libs, **extension_kwargs, ), ] setup( version=VERSION, install_requires=requirements, ext_modules=extensions, cmdclass={"build_ext": BuildExtensions}, data_files=[ (str(parent), [str(file) for file in files]) for parent, files in data_files ], ) PyTables-3.7.0/src/000077500000000000000000000000001416254111300140315ustar00rootroot00000000000000PyTables-3.7.0/src/H5ARRAY-opt.c000066400000000000000000000163231416254111300160550ustar00rootroot00000000000000#include "H5ARRAY-opt.h" #include #include /*------------------------------------------------------------------------- * Function: H5ARRAYOread_readSlice * * Purpose: Read records from an opened Array * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted, faltet@pytables.com * * Date: May 27, 2004 * * Comments: * * Modifications: * * *------------------------------------------------------------------------- */ herr_t H5ARRAYOread_readSlice( hid_t dataset_id, hid_t type_id, hsize_t irow, hsize_t start, hsize_t stop, void *data ) { hid_t space_id; hid_t mem_space_id; hsize_t count[2]; int rank = 2; hsize_t offset[2]; hsize_t stride[2] = {1, 1}; count[0] = 1; count[1] = stop - start; offset[0] = irow; offset[1] = start; /* Get the dataspace handle */ if ( (space_id = H5Dget_space( dataset_id )) < 0 ) goto out; /* Create a memory dataspace handle */ if ( (mem_space_id = H5Screate_simple( rank, count, NULL )) < 0 ) goto out; /* Define a hyperslab in the dataset of the size of the records */ if ( H5Sselect_hyperslab(space_id, H5S_SELECT_SET, offset, stride, count, NULL) < 0 ) goto out; /* Read */ if ( H5Dread( dataset_id, type_id, mem_space_id, space_id, H5P_DEFAULT, data ) < 0 ) goto out; /* Terminate access to the memory dataspace */ if ( H5Sclose( mem_space_id ) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; return 0; out: H5Dclose( dataset_id ); return -1; } /*------------------------------------------------------------------------- * Function: H5ARRAYOinit_readSlice * * Purpose: Prepare structures to read specifics arrays faster * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted, faltet@pytables.com * * Date: May 18, 2006 * * Comments: * - The H5ARRAYOinit_readSlice and H5ARRAYOread_readSlice * are intended to read indexes slices only! * F. Alted 2006-05-18 * * Modifications: * * *------------------------------------------------------------------------- */ herr_t H5ARRAYOinit_readSlice( hid_t dataset_id, hid_t *mem_space_id, hsize_t count) { hid_t space_id; int rank = 2; hsize_t count2[2] = {1, count}; /* Get the dataspace handle */ if ( (space_id = H5Dget_space(dataset_id )) < 0 ) goto out; /* Create a memory dataspace handle */ if ( (*mem_space_id = H5Screate_simple(rank, count2, NULL)) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; return 0; out: H5Dclose(dataset_id); return -1; } /*------------------------------------------------------------------------- * Function: H5ARRAYOread_readSortedSlice * * Purpose: Read records from an opened Array * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted, faltet@pytables.com * * Date: Aug 11, 2005 * * Comments: * * Modifications: * - Modified to cache the mem_space_id as well. * F. Alted 2005-08-11 * * *------------------------------------------------------------------------- */ herr_t H5ARRAYOread_readSortedSlice( hid_t dataset_id, hid_t mem_space_id, hid_t type_id, hsize_t irow, hsize_t start, hsize_t stop, void *data ) { hid_t space_id; hsize_t count[2] = {1, stop-start}; hsize_t offset[2] = {irow, start}; hsize_t stride[2] = {1, 1}; /* Get the dataspace handle */ if ( (space_id = H5Dget_space(dataset_id)) < 0 ) goto out; /* Define a hyperslab in the dataset of the size of the records */ if ( H5Sselect_hyperslab(space_id, H5S_SELECT_SET, offset, stride, count, NULL) < 0 ) goto out; /* Read */ if ( H5Dread( dataset_id, type_id, mem_space_id, space_id, H5P_DEFAULT, data ) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; return 0; out: H5Dclose( dataset_id ); return -1; } /*------------------------------------------------------------------------- * Function: H5ARRAYOread_readBoundsSlice * * Purpose: Read records from an opened Array * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted, faltet@pytables.com * * Date: Aug 19, 2005 * * Comments: This is exactly the same as H5ARRAYOread_readSortedSlice, * but I just want to distinguish the calls in profiles. * * Modifications: * * *------------------------------------------------------------------------- */ herr_t H5ARRAYOread_readBoundsSlice( hid_t dataset_id, hid_t mem_space_id, hid_t type_id, hsize_t irow, hsize_t start, hsize_t stop, void *data ) { hid_t space_id; hsize_t count[2] = {1, stop-start}; hsize_t offset[2] = {irow, start}; hsize_t stride[2] = {1, 1}; /* Get the dataspace handle */ if ( (space_id = H5Dget_space(dataset_id)) < 0 ) goto out; /* Define a hyperslab in the dataset of the size of the records */ if ( H5Sselect_hyperslab(space_id, H5S_SELECT_SET, offset, stride, count, NULL) < 0 ) goto out; /* Read */ if ( H5Dread( dataset_id, type_id, mem_space_id, space_id, H5P_DEFAULT, data ) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; return 0; out: H5Dclose( dataset_id ); return -1; } /*------------------------------------------------------------------------- * Function: H5ARRAYreadSliceLR * * Purpose: Reads a slice of LR index cache from disk. * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted, faltet@pytables.com * * Date: August 17, 2005 * *------------------------------------------------------------------------- */ herr_t H5ARRAYOreadSliceLR(hid_t dataset_id, hid_t type_id, hsize_t start, hsize_t stop, void *data) { hid_t space_id; hid_t mem_space_id; hsize_t count[1] = {stop - start}; hsize_t stride[1] = {1}; hsize_t offset[1] = {start}; /* Get the dataspace handle */ if ( (space_id = H5Dget_space(dataset_id)) < 0 ) goto out; /* Define a hyperslab in the dataset of the size of the records */ if ( H5Sselect_hyperslab(space_id, H5S_SELECT_SET, offset, stride, count, NULL) < 0 ) goto out; /* Create a memory dataspace handle */ if ( (mem_space_id = H5Screate_simple(1, count, NULL)) < 0 ) goto out; /* Read */ if ( H5Dread( dataset_id, type_id, mem_space_id, space_id, H5P_DEFAULT, data ) < 0 ) goto out; /* Release resources */ /* Terminate access to the memory dataspace */ if ( H5Sclose( mem_space_id ) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; return 0; out: H5Dclose( dataset_id ); return -1; } PyTables-3.7.0/src/H5ARRAY-opt.h000066400000000000000000000025741416254111300160650ustar00rootroot00000000000000#include herr_t H5ARRAYOinit_readSlice( hid_t dataset_id, hid_t *mem_space_id, hsize_t count ); herr_t H5ARRAYOread_readSlice( hid_t dataset_id, hid_t type_id, hsize_t irow, hsize_t start, hsize_t stop, void *data ); herr_t H5ARRAYOread_readSortedSlice( hid_t dataset_id, hid_t mem_space_id, hid_t type_id, hsize_t irow, hsize_t start, hsize_t stop, void *data ); herr_t H5ARRAYOread_readBoundsSlice( hid_t dataset_id, hid_t mem_space_id, hid_t type_id, hsize_t irow, hsize_t start, hsize_t stop, void *data ); herr_t H5ARRAYOreadSliceLR( hid_t dataset_id, hid_t type_id, hsize_t start, hsize_t stop, void *data ); PyTables-3.7.0/src/H5ARRAY.c000066400000000000000000000541671416254111300152650ustar00rootroot00000000000000#include "H5ATTR.h" #include "tables.h" #include "utils.h" #include "H5Zlzo.h" /* Import FILTER_LZO */ #include "H5Zbzip2.h" /* Import FILTER_BZIP2 */ #include "blosc_filter.h" /* Import FILTER_BLOSC */ #include #include /*------------------------------------------------------------------------- * * Public functions * *------------------------------------------------------------------------- */ /*------------------------------------------------------------------------- * Function: H5ARRAYmake * * Purpose: Creates and writes a dataset of a type type_id * * Return: Success: 0, Failure: -1 * * Programmer: F. Alted. October 21, 2002 * * Date: March 19, 2001 * * Comments: Modified by F. Alted. November 07, 2003 * Modified by A. Cobb. August 21, 2017 (track_times) * *------------------------------------------------------------------------- */ hid_t H5ARRAYmake( hid_t loc_id, const char *dset_name, const char *obversion, const int rank, const hsize_t *dims, int extdim, hid_t type_id, hsize_t *dims_chunk, void *fill_data, int compress, char *complib, int shuffle, int fletcher32, hbool_t track_times, const void *data) { hid_t dataset_id, space_id; hsize_t *maxdims = NULL; hid_t plist_id = 0; unsigned int cd_values[7]; int blosc_compcode; char *blosc_compname = NULL; int chunked = 0; int i; /* Check whether the array has to be chunked or not */ if (dims_chunk) { chunked = 1; } if(chunked) { maxdims = malloc(rank*sizeof(hsize_t)); if(!maxdims) return -1; for(i=0;i dims[_extdim] ) { printf("Asking for a range of rows exceeding the available ones!.\n"); goto out; } /* Define a hyperslab in the dataset of the size of the records */ for (i=0; i dims[i] ) { printf("Asking for a range of rows exceeding the available ones!.\n"); goto out; } } /* Define a hyperslab in the dataset of the size of the records */ if ( H5Sselect_hyperslab( space_id, H5S_SELECT_SET, offset, stride, count, NULL) < 0 ) goto out; /* Create a memory dataspace handle */ if ( (mem_space_id = H5Screate_simple( rank, count, NULL )) < 0 ) goto out; /* Read */ if ( H5Dread( dataset_id, type_id, mem_space_id, space_id, H5P_DEFAULT, data ) < 0 ) goto out; /* Release resources */ free(dims); free(count); /* Terminate access to the memory dataspace */ if ( H5Sclose( mem_space_id ) < 0 ) goto out; } else { /* Scalar case */ /* Read all the dataset */ if (H5Dread(dataset_id, type_id, H5S_ALL, H5S_ALL, H5P_DEFAULT, data) < 0) goto out; } /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; return 0; out: /* H5Dclose( dataset_id ); */ if (dims) free(dims); if (count) free(count); return -1; } /* The next represents a try to implement getCoords for != operator */ /* but it turned out to be too difficult, well, at least to me :( */ /* 2004-06-22 */ /*------------------------------------------------------------------------- * Function: H5ARRAYreadIndex * * Purpose: Reads a slice of array from disk for indexing purposes. * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted, faltet@pytables.com * * Date: June 21, 2004 * *------------------------------------------------------------------------- */ herr_t H5ARRAYreadIndex( hid_t dataset_id, hid_t type_id, int notequal, hsize_t *start, hsize_t *stop, hsize_t *step, void *data ) { hid_t mem_space_id; hid_t space_id; hsize_t *dims = NULL; hsize_t *count = NULL; hsize_t *count2 = NULL; hsize_t *offset2 = NULL; hsize_t *stride = (hsize_t *)step; hsize_t *offset = (hsize_t *)start; int rank; int i; /* Get the dataspace handle */ if ( (space_id = H5Dget_space( dataset_id )) < 0 ) goto out; /* Get the rank */ if ( (rank = H5Sget_simple_extent_ndims(space_id)) < 0 ) goto out; if (rank) { /* Array case */ /* Book some memory for the selections */ dims = (hsize_t *)malloc(rank*sizeof(hsize_t)); count = (hsize_t *)malloc(rank*sizeof(hsize_t)); count2 = (hsize_t *)malloc(rank*sizeof(hsize_t)); offset2 = (hsize_t *)malloc(rank*sizeof(hsize_t)); /* Get dataset dimensionality */ if ( H5Sget_simple_extent_dims( space_id, dims, NULL) < 0 ) goto out; for(i=0;i dims[i] ) { printf("Asking for a range of rows exceeding the available ones!.\n"); goto out; } } /* Define a hyperslab in the dataset of the size of the records */ if ( H5Sselect_hyperslab( space_id, H5S_SELECT_SET, offset, stride, count, NULL) < 0 ) goto out; /* If we want the complementary, do a NOTA against all the row */ if (notequal) { offset2[0] = offset[0]; count2[0] = count[0]; offset2[1] = 0; count2[1] = dims[1]; /* All the row */ count[0] = 1; count[1] = dims[1] - count[1]; /* For memory dataspace */ if ( H5Sselect_hyperslab( space_id, H5S_SELECT_NOTA, offset2, stride, count2, NULL) < 0 ) goto out; } /* Create a memory dataspace handle */ if ( (mem_space_id = H5Screate_simple( rank, count, NULL )) < 0 ) goto out; /* Read */ if ( H5Dread( dataset_id, type_id, mem_space_id, space_id, H5P_DEFAULT, data ) < 0 ) goto out; /* Release resources */ free(dims); free(count); free(offset2); free(count2); /* Terminate access to the memory dataspace */ if ( H5Sclose( mem_space_id ) < 0 ) goto out; } else { /* Scalar case */ /* Read all the dataset */ if (H5Dread(dataset_id, type_id, H5S_ALL, H5S_ALL, H5P_DEFAULT, data) < 0) goto out; } /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; return 0; out: if (dims) free(dims); if (count) free(count); return -1; } /*------------------------------------------------------------------------- * Function: H5ARRAYget_ndims * * Purpose: Gets the dimensionality of an array. * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted * * Date: October 22, 2002 * * Modification: October 13, 2008 * This routine not longer returns the dimensionality of data types * in case they are H5T_ARRAY. * *------------------------------------------------------------------------- */ herr_t H5ARRAYget_ndims( hid_t dataset_id, int *rank ) { hid_t space_id; /* Get the dataspace handle */ if ( (space_id = H5Dget_space( dataset_id )) < 0 ) goto out; /* Get rank */ if ( (*rank = H5Sget_simple_extent_ndims( space_id )) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; return 0; out: return -1; } /* Modified version of H5LTget_dataset_info. */ herr_t H5ARRAYget_info( hid_t dataset_id, hid_t type_id, hsize_t *dims, hsize_t *maxdims, H5T_class_t *class_id, char *byteorder) { hid_t space_id; /* Get the class. */ *class_id = H5Tget_class( type_id ); /* Get the dataspace handle */ if ( (space_id = H5Dget_space( dataset_id )) < 0 ) goto out; /* Get dimensions */ if ( H5Sget_simple_extent_dims( space_id, dims, maxdims) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; /* Get the byteorder */ /* Only integer, float, time, enumerate and array classes can be byteordered */ if ((*class_id == H5T_INTEGER) || (*class_id == H5T_FLOAT) || (*class_id == H5T_BITFIELD) || (*class_id == H5T_COMPOUND) || (*class_id == H5T_TIME) || (*class_id == H5T_ENUM) || (*class_id == H5T_ARRAY)) { get_order(type_id, byteorder); } else { strcpy(byteorder, "irrelevant"); } return 0; out: return -1; } /*------------------------------------------------------------------------- * Function: H5ARRAYget_chunkshape * * Purpose: Gets the chunkshape of a dataset. * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted * * Date: May 20, 2004 * *------------------------------------------------------------------------- */ herr_t H5ARRAYget_chunkshape( hid_t dataset_id, int rank, hsize_t *dims_chunk) { hid_t plist_id; H5D_layout_t layout; /* Get creation properties list */ if ( (plist_id = H5Dget_create_plist( dataset_id )) < 0 ) goto out; /* Get the dataset layout */ layout = H5Pget_layout(plist_id); if (layout != H5D_CHUNKED) { H5Pclose( plist_id ); return -1; } /* Get the chunkshape for all dimensions */ if (H5Pget_chunk(plist_id, rank, dims_chunk ) < 0 ) goto out; /* Terminate access to the datatype */ if ( H5Pclose( plist_id ) < 0 ) goto out; return 0; out: if (dims_chunk) free(dims_chunk); return -1; } /*------------------------------------------------------------------------- * Function: H5ARRAYget_fill_value * * Purpose: Gets the fill value of a dataset. * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted * * Date: Mar 03, 2009 * *------------------------------------------------------------------------- */ herr_t H5ARRAYget_fill_value( hid_t dataset_id, hid_t type_id, int *status, void *value) { hid_t plist_id; /* Get creation properties list */ if ( (plist_id = H5Dget_create_plist(dataset_id)) < 0 ) goto out; /* How the fill value is defined? */ if ( (H5Pfill_value_defined(plist_id, status)) < 0 ) goto out; if ( *status == H5D_FILL_VALUE_USER_DEFINED ) { if ( H5Pget_fill_value(plist_id, type_id, value) < 0 ) goto out; } /* Terminate access to the datatype */ if ( H5Pclose( plist_id ) < 0 ) goto out; return 0; out: return -1; } PyTables-3.7.0/src/H5ARRAY.h000066400000000000000000000057371416254111300152710ustar00rootroot00000000000000#ifndef _H5ARRAY_H #define _H5ARRAY_H #include #define TESTING(WHAT) {printf("%-70s", "Testing " WHAT); fflush(stdout);} #define PASSED() {puts(" PASSED");fflush(stdout);} #define H5_FAILED() {puts("*FAILED*");fflush(stdout);} #define SKIPPED() {puts(" -SKIP-");fflush(stdout);} #ifdef __cplusplus extern "C" { #endif hid_t H5ARRAYmake( hid_t loc_id, const char *dset_name, const char *obversion, const int rank, const hsize_t *dims, int extdim, hid_t type_id, hsize_t *dims_chunk, void *fill_data, int compress, char *complib, int shuffle, int fletcher32, hbool_t track_times, const void *data); herr_t H5ARRAYappend_records( hid_t dataset_id, hid_t type_id, const int rank, hsize_t *dims_orig, hsize_t *dims_new, int extdim, const void *data ); herr_t H5ARRAYwrite_records( hid_t dataset_id, hid_t type_id, const int rank, hsize_t *start, hsize_t *step, hsize_t *count, const void *data ); herr_t H5ARRAYread( hid_t dataset_id, hid_t type_id, hsize_t start, hsize_t nrows, hsize_t step, int extdim, void *data ); herr_t H5ARRAYreadSlice( hid_t dataset_id, hid_t type_id, hsize_t *start, hsize_t *stop, hsize_t *step, void *data ); herr_t H5ARRAYreadIndex( hid_t dataset_id, hid_t type_id, int notequal, hsize_t *start, hsize_t *stop, hsize_t *step, void *data ); herr_t H5ARRAYget_ndims( hid_t dataset_id, int *rank ); herr_t H5ARRAYget_info( hid_t dataset_id, hid_t type_id, hsize_t *dims, hsize_t *maxdims, H5T_class_t *class_id, char *byteorder); herr_t H5ARRAYget_chunkshape( hid_t dataset_id, int rank, hsize_t *dims_chunk); herr_t H5ARRAYget_fill_value( hid_t dataset_id, hid_t type_id, int *status, void *value); #ifdef __cplusplus } #endif #endif PyTables-3.7.0/src/H5ATTR.c000066400000000000000000000353651416254111300151600ustar00rootroot00000000000000/**************************************************************************** * NCSA HDF * * Scientific Data Technologies * * National Center for Supercomputing Applications * * University of Illinois at Urbana-Champaign * * 605 E. Springfield, Champaign IL 61820 * * * * For conditions of distribution and use, see the accompanying * * hdf/COPYING file. * * * * Modified versions of H5LT for getting and setting attributes for open * groups and leaves. * F. Alted 2005/09/29 * * ****************************************************************************/ #include #include #include "H5ATTR.h" /*------------------------------------------------------------------------- * * Set & get attribute functions * *------------------------------------------------------------------------- */ /*------------------------------------------------------------------------- * Function: H5ATTRset_attribute * * Purpose: Create an attribute named attr_name and attach it to the * object specified by the name obj_name. This supports general * n-dimensional types (rank > 0), but if rank == 0, an H5T_SCALAR is * chosen. * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted * * Date: October 18, 2006 * * Comments: * * Modifications: * *------------------------------------------------------------------------- */ herr_t H5ATTRset_attribute( hid_t obj_id, const char *attr_name, hid_t type_id, size_t rank, hsize_t *dims, const char *attr_data ) { hid_t space_id; hid_t attr_id; int has_attr; /* Create the data space for the attribute. */ if (rank == 0) space_id = H5Screate( H5S_SCALAR ); else space_id = H5Screate_simple( rank, dims, NULL ); /* Verify whether the attribute already exists */ has_attr = H5ATTRfind_attribute( obj_id, attr_name ); /* The attribute already exists, delete it */ if ( has_attr == 1 ) { if ( H5Adelete( obj_id, attr_name ) < 0 ) goto out; } /* Create and write the attribute */ attr_id = H5Acreate( obj_id, attr_name, type_id, space_id, H5P_DEFAULT, H5P_DEFAULT ); if ( H5Awrite( attr_id, type_id, attr_data ) < 0 ) goto out; H5Aclose( attr_id ); H5Sclose( space_id ); return 0; out: return -1; } /*------------------------------------------------------------------------- * Function: H5ATTRset_attribute_string * * Purpose: Creates and writes a string attribute named attr_name and attaches * it to the object specified by the name obj_name. * * Return: Success: 0, Failure: -1 * * Programmer: Pedro Vicente, pvn@ncsa.uiuc.edu * * Date: July 23, 2001 * * Comments: If the attribute already exists, it is overwritten * * Modifications: * *------------------------------------------------------------------------- */ herr_t H5ATTRset_attribute_string( hid_t obj_id, const char *attr_name, const char *attr_data, hsize_t attr_size, int cset ) { hid_t attr_type; /*size_t attr_size;*/ hid_t attr_space_id; hid_t attr_id; int has_attr; /* Create the attribute */ if ( (attr_type = H5Tcopy( H5T_C_S1 )) < 0 ) goto out; if ( ( ( cset == H5T_CSET_ASCII ) || ( cset == H5T_CSET_UTF8 ) ) && ( H5Tset_cset( attr_type, cset ) < 0 ) ) goto out; if ( H5Tset_strpad( attr_type, H5T_STR_NULLTERM ) < 0 ) goto out; if ( attr_size > 0 ) { if (H5Tset_size( attr_type, attr_size) < 0 ) goto out; if ( (attr_space_id = H5Screate( H5S_SCALAR )) < 0 ) goto out; } else { if ( (attr_space_id = H5Screate( H5S_NULL )) < 0 ) goto out; } /* Verify if the attribute already exists */ has_attr = H5ATTRfind_attribute( obj_id, attr_name ); /* The attribute already exists, delete it */ if ( has_attr == 1 ) { if ( H5Adelete( obj_id, attr_name ) < 0 ) goto out; } /* Create and write the attribute */ if ( (attr_id = H5Acreate( obj_id, attr_name, attr_type, attr_space_id, H5P_DEFAULT, H5P_DEFAULT )) < 0 ) goto out; if ( H5Awrite( attr_id, attr_type, attr_data ) < 0 ) goto out; if ( H5Aclose( attr_id ) < 0 ) goto out; if ( H5Sclose( attr_space_id ) < 0 ) goto out; if ( H5Tclose(attr_type) < 0 ) goto out; return 0; out: return -1; } /*------------------------------------------------------------------------- * Function: H5ATTRget_attribute * * Purpose: Reads an attribute named attr_name with the memory type type_id * * Return: Success: 0, Failure: -1 * * Programmer: Pedro Vicente, pvn@ncsa.uiuc.edu * * Date: September 19, 2002 * * Comments: * * Modifications: * *------------------------------------------------------------------------- */ herr_t H5ATTRget_attribute( hid_t obj_id, const char *attr_name, hid_t type_id, void *data ) { /* identifiers */ hid_t attr_id; if ( ( attr_id = H5Aopen_by_name(obj_id, ".", attr_name, H5P_DEFAULT, H5P_DEFAULT) ) < 0 ) return -1; if ( H5Aread( attr_id, type_id, data ) < 0 ) goto out; if ( H5Aclose( attr_id ) < 0 ) return -1; return 0; out: H5Aclose( attr_id ); return -1; } /*------------------------------------------------------------------------- * Function: H5ATTRget_attribute_string * * Purpose: Reads an string attribute named attr_name. * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted, faltet@pytables.com * * Date: February 23, 2005 * * Comments: * * Modifications: * *------------------------------------------------------------------------- */ hsize_t H5ATTRget_attribute_string( hid_t obj_id, const char *attr_name, char **data, int *cset ) { /* identifiers */ hid_t attr_id; hid_t attr_type; hid_t space_id; hsize_t type_size = 0; htri_t is_vlstr = 0; *data = NULL; if ( ( attr_id = H5Aopen_by_name(obj_id, ".", attr_name, H5P_DEFAULT, H5P_DEFAULT) ) < 0 ) return -1; if ( (attr_type = H5Aget_type( attr_id )) < 0 ) goto out; if ( ( cset != NULL ) && ( ( *cset = H5Tget_cset( attr_type ) ) < 0 ) ) goto out; is_vlstr = H5Tis_variable_str( attr_type ); if ( is_vlstr == 0 ) { /* Get the size */ if ( (type_size = H5Tget_size( attr_type )) < 0 ) goto out; if ( (space_id = H5Aget_space( attr_id )) < 0 ) goto out; if ( H5Sget_simple_extent_type( space_id ) == H5S_NULL ) type_size = 0; H5Sclose( space_id ); /* Malloc space enough for the string, plus 1 for the trailing '\0' */ *data = (char *)malloc(type_size + 1); if ( type_size > 0) { if ( H5Aread( attr_id, attr_type, *data ) < 0 ) goto out; } /* Set the last character to \0 in case we are dealing with space padded strings */ (*data)[type_size] = '\0'; } else { /* is_vlstr */ if ( H5Aread( attr_id, attr_type, data ) < 0 ) goto out; type_size = strlen( *data ); } if ( H5Tclose( attr_type ) < 0 ) goto out; if ( H5Aclose( attr_id ) < 0 ) return -1; return type_size; out: H5Tclose( attr_type ); H5Aclose( attr_id ); if ( (is_vlstr == 0) && (*data != NULL) ) free(*data); *data = NULL; return -1; } /*------------------------------------------------------------------------- * Function: H5ATTRget_attribute_vlen_string_array * * Purpose: Reads a variable length string attribute named attr_name. * * Return: Success: number of elements of the array, Failure: -1 * * Programmer: Antonio Valentino * * Date: November 27, 2011 * * Comments: only rank 1 attributes of 8bit strings are supported * * Modifications: * *------------------------------------------------------------------------- */ hsize_t H5ATTRget_attribute_vlen_string_array( hid_t obj_id, const char *attr_name, char ***data, int *cset ) { /* identifiers */ hid_t attr_id = -1, attr_type = -1, space_id = -1; hsize_t nelements = 0, *dims = NULL; int ndims = 0, i; *data = NULL; if ( ( attr_id = H5Aopen_by_name( obj_id, ".", attr_name, H5P_DEFAULT, H5P_DEFAULT ) ) < 0 ) return -1; if ( (attr_type = H5Aget_type( attr_id )) < 0 ) goto out; if ( ( cset != NULL ) && ( ( *cset = H5Tget_cset( attr_type ) ) < 0 ) ) goto out; if ( (space_id = H5Aget_space( attr_id )) < 0 ) goto out; if ( (ndims = H5Sget_simple_extent_ndims( space_id )) < 1 ) goto out; if ( (dims = (hsize_t *)malloc(ndims * sizeof(hsize_t))) == NULL ) goto out; if ( H5Sget_simple_extent_dims( space_id, dims, NULL ) < 0 ) goto out; nelements = 1; for ( i = 0; i < ndims; ++i ) nelements *= dims[i]; free( dims ); dims = NULL; if ((*data = (char **)malloc( nelements * sizeof(char*))) == NULL ) goto out; if ( H5Aread( attr_id, attr_type, *data ) < 0 ) goto out; if ( H5Tclose( attr_type ) < 0 ) goto out; if ( H5Sclose( space_id ) < 0 ) goto out; if ( H5Aclose( attr_id ) < 0 ) return -1; return nelements; out: if ( *data != NULL ) free( *data ); *data = NULL; if ( dims != NULL ) free( dims ); H5Tclose( attr_type ); H5Sclose( space_id ); H5Aclose( attr_id ); return -1; } /*------------------------------------------------------------------------- * * Helper functions * *------------------------------------------------------------------------- */ /*------------------------------------------------------------------------- * Function: find_attr * * Purpose: operator function used by H5ATTRfind_attribute * * Programmer: Pedro Vicente, pvn@ncsa.uiuc.edu * * Date: June 21, 2001 * * Comments: * * Modifications: * *------------------------------------------------------------------------- */ static herr_t find_attr( hid_t loc_id, const char *name, const H5A_info_t *ainfo, void *op_data) { /* Define a default zero value for return. This will cause the * iterator to continue if the palette attribute is not found yet. */ int ret = 0; char *attr_name = (char*)op_data; /* Shut the compiler up */ loc_id=loc_id; /* Define a positive value for return value if the attribute was * found. This will cause the iterator to immediately return that * positive value, indicating short-circuit success */ if( strcmp( name, attr_name ) == 0 ) ret = 1; return ret; } /*------------------------------------------------------------------------- * Function: H5ATTRfind_attribute * * Purpose: Inquires if an attribute named attr_name exists attached * to the object loc_id. * * Programmer: Pedro Vicente, pvn@ncsa.uiuc.edu * * Date: June 21, 2001 * * Comments: * The function uses H5Aiterate with the operator function find_attr * * Return: * Success: The return value of the first operator that * returns non-zero, or zero if all members were * processed with no operator returning non-zero. * * Failure: Negative if something goes wrong within the * library, or the negative value returned by one * of the operators. * *------------------------------------------------------------------------- */ herr_t H5ATTRfind_attribute( hid_t loc_id, const char* attr_name ) { hsize_t attr_num; herr_t ret; attr_num = 0; ret = H5Aiterate( loc_id, H5_INDEX_CRT_ORDER, H5_ITER_NATIVE, &attr_num, find_attr, (void *)attr_name ); return ret; } /*------------------------------------------------------------------------- * Function: H5ATTRget_attribute_ndims * * Purpose: Gets the dimensionality of an attribute. * * Return: Success: 0, Failure: -1 * * Programmer: Pedro Vicente, pvn@ncsa.uiuc.edu * * Date: September 4, 2001 * *------------------------------------------------------------------------- */ herr_t H5ATTRget_type_ndims( hid_t obj_id, const char *attr_name, hid_t *type_id, H5T_class_t *class_id, size_t *type_size, int *rank ) { hid_t attr_id; hid_t space_id; /* Open the attribute. */ if ( ( attr_id = H5Aopen_by_name(obj_id, ".", attr_name, H5P_DEFAULT, H5P_DEFAULT) ) < 0 ) { return -1; } /* Get an identifier for the datatype. */ *type_id = H5Aget_type( attr_id ); /* Get the class. */ *class_id = H5Tget_class( *type_id ); /* Get the size. */ *type_size = H5Tget_size( *type_id ); /* Get the dataspace handle */ if ( (space_id = H5Aget_space( attr_id )) < 0 ) goto out; /* Get rank */ if ( (*rank = H5Sget_simple_extent_ndims( space_id )) < 0 ) goto out; /* Terminate access to the attribute */ if ( H5Sclose( space_id ) < 0 ) goto out; /* End access to the attribute */ if ( H5Aclose( attr_id ) ) goto out;; return 0; out: H5Tclose( *type_id ); H5Aclose( attr_id ); return -1; } /*------------------------------------------------------------------------- * Function: H5ATTRget_dims * * Purpose: Gets information about an attribute. * * Return: Success: 0, Failure: -1 * * Programmer: Pedro Vicente, pvn@ncsa.uiuc.edu * * Date: September 4, 2001 * *------------------------------------------------------------------------- */ herr_t H5ATTRget_dims( hid_t obj_id, const char *attr_name, hsize_t *dims) { hid_t attr_id; hid_t space_id; /* Open the attribute. */ if ( ( attr_id = H5Aopen_by_name(obj_id, ".", attr_name, H5P_DEFAULT, H5P_DEFAULT) ) < 0 ) { return -1; } /* Get the dataspace handle */ if ( (space_id = H5Aget_space( attr_id )) < 0 ) goto out; /* Get dimensions */ if ( H5Sget_simple_extent_dims( space_id, dims, NULL) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; /* End access to the attribute */ if ( H5Aclose( attr_id ) ) goto out; return 0; out: H5Aclose( attr_id ); return -1; } PyTables-3.7.0/src/H5ATTR.h000066400000000000000000000062261416254111300151570ustar00rootroot00000000000000 /**************************************************************************** * NCSA HDF * * Scientific Data Technologies * * National Center for Supercomputing Applications * * University of Illinois at Urbana-Champaign * * 605 E. Springfield, Champaign IL 61820 * * * * For conditions of distribution and use, see the accompanying * * hdf/COPYING file. * * * * Modified versions of H5LT for getting and setting attributes for open * groups and leaves. * F. Alted 2005/09/29 * * ****************************************************************************/ #include #ifdef __cplusplus extern "C" { #endif /*------------------------------------------------------------------------- * * Set & get attribute functions * *------------------------------------------------------------------------- */ herr_t H5ATTRset_attribute( hid_t obj_id, const char *attr_name, hid_t type_id, size_t rank, hsize_t *dims, const char *attr_data ); herr_t H5ATTRset_attribute_string( hid_t obj_id, const char *attr_name, const char *attr_data, hsize_t attr_size, int cset ); herr_t H5ATTRget_attribute( hid_t loc_id, const char *attr_name, hid_t type_id, void *data ); hsize_t H5ATTRget_attribute_string( hid_t obj_id, const char *attr_name, char **data, int *cset ); hsize_t H5ATTRget_attribute_vlen_string_array( hid_t obj_id, const char *attr_name, char ***data, int *cset ); /*------------------------------------------------------------------------- * * Query attribute functions * *------------------------------------------------------------------------- */ herr_t H5ATTRfind_attribute( hid_t loc_id, const char* attr_name ); herr_t H5ATTRget_type_ndims( hid_t loc_id, const char *attr_name, hid_t *type_id, H5T_class_t *class_id, size_t *type_size, int *rank ); herr_t H5ATTRget_dims( hid_t loc_id, const char *attr_name, hsize_t *dims ); #ifdef __cplusplus } #endif PyTables-3.7.0/src/H5TB-opt.c000066400000000000000000000442721416254111300155100ustar00rootroot00000000000000/**************************************************************************** * NCSA HDF * * Scientific Data Technologies * * National Center for Supercomputing Applications * * University of Illinois at Urbana-Champaign * * 605 E. Springfield, Champaign IL 61820 * * * * For conditions of distribution and use, see the accompanying * * hdf/COPYING file. * * * ****************************************************************************/ /* WARNING: This is a highly stripped down and modified version of the original H5TB.c that comes with the HDF5 library. These modifications has been done in order to serve the needs of PyTables, and specially for supporting nested datatypes. In particular, the VERSION attribute is out of sync so it is not guaranteed that the resulting PyTables objects will be identical with those generated with HDF5_HL, although they should remain largely compatibles. F. Alted 2005/06/09 Other modifications are that these routines are meant for opened nodes, and do not spend time opening and closing datasets. F. Alted 2005/09/29 */ #include #include #include "H5TB-opt.h" #include "tables.h" #include "H5Zlzo.h" /* Import FILTER_LZO */ #include "H5Zbzip2.h" /* Import FILTER_BZIP2 */ #include "blosc_filter.h" /* Import FILTER_BLOSC */ /* Define this in order to shrink datasets after deleting */ #if 1 #define SHRINK #endif /*------------------------------------------------------------------------- * * Create functions * *------------------------------------------------------------------------- */ /*------------------------------------------------------------------------- * Function: H5TBmake_table * * Purpose: Make a table * * Return: Success: 0, Failure: -1 * * Programmer: Pedro Vicente, pvn@ncsa.uiuc.edu * Quincey Koziol * * Date: January 17, 2001 * * Comments: The data is packed * * Heavily modified and not compliant with attributes * May 20, 2005 * F. Alted * * Modifications: * * Modified by A. Cobb. August 21, 2017 (track_times) * *------------------------------------------------------------------------- */ hid_t H5TBOmake_table( const char *table_title, hid_t loc_id, const char *dset_name, char *version, const char *class_, hid_t type_id, hsize_t nrecords, hsize_t chunk_size, void *fill_data, int compress, char *complib, int shuffle, int fletcher32, hbool_t track_times, const void *data ) { hid_t dataset_id; hid_t space_id; hid_t plist_id; hsize_t dims[1]; hsize_t dims_chunk[1]; hsize_t maxdims[1] = { H5S_UNLIMITED }; unsigned int cd_values[7]; int blosc_compcode; char *blosc_compname = NULL; dims[0] = nrecords; dims_chunk[0] = chunk_size; /* Create a simple data space with unlimited size */ if ( (space_id = H5Screate_simple( 1, dims, maxdims )) < 0 ) return -1; /* Dataset creation properties */ plist_id = H5Pcreate (H5P_DATASET_CREATE); /* Enable or disable recording dataset times */ if ( H5Pset_obj_track_times( plist_id, track_times ) < 0 ) return -1; /* Modify dataset creation properties, i.e. enable chunking */ if ( H5Pset_chunk ( plist_id, 1, dims_chunk ) < 0 ) return -1; /* Set the fill value using a struct as the data type. */ if ( fill_data) { if ( H5Pset_fill_value( plist_id, type_id, fill_data ) < 0 ) return -1; } else { if ( H5Pset_fill_time(plist_id, H5D_FILL_TIME_ALLOC) < 0 ) return -1; } /* Dataset creation property list is modified to use filters */ /* Fletcher must be first */ if (fletcher32) { if ( H5Pset_fletcher32( plist_id) < 0 ) return -1; } /* Then shuffle (blosc shuffles inplace) */ if ((shuffle && compress) && (strncmp(complib, "blosc", 5) != 0)) { if ( H5Pset_shuffle( plist_id) < 0 ) return -1; } /* Finally compression */ if ( compress ) { cd_values[0] = compress; cd_values[1] = (int)(atof(version) * 10); cd_values[2] = Table; /* The default compressor in HDF5 (zlib) */ if (strcmp(complib, "zlib") == 0) { if ( H5Pset_deflate( plist_id, compress) < 0 ) return -1; } /* The Blosc compressor does accept parameters */ else if (strcmp(complib, "blosc") == 0) { cd_values[4] = compress; cd_values[5] = shuffle; if ( H5Pset_filter( plist_id, FILTER_BLOSC, H5Z_FLAG_OPTIONAL, 6, cd_values) < 0 ) return -1; } /* The Blosc compressor can use other compressors */ else if (strncmp(complib, "blosc:", 6) == 0) { cd_values[4] = compress; cd_values[5] = shuffle; blosc_compname = complib + 6; blosc_compcode = blosc_compname_to_compcode(blosc_compname); cd_values[6] = blosc_compcode; if ( H5Pset_filter( plist_id, FILTER_BLOSC, H5Z_FLAG_OPTIONAL, 7, cd_values) < 0 ) return -1; } /* The LZO compressor does accept parameters */ else if (strcmp(complib, "lzo") == 0) { if ( H5Pset_filter( plist_id, FILTER_LZO, H5Z_FLAG_OPTIONAL, 3, cd_values) < 0 ) return -1; } /* The bzip2 compress does accept parameters */ else if (strcmp(complib, "bzip2") == 0) { if ( H5Pset_filter( plist_id, FILTER_BZIP2, H5Z_FLAG_OPTIONAL, 3, cd_values) < 0 ) return -1; } else { /* Compression library not supported */ return -1; } } /* Create the dataset. */ if ( (dataset_id = H5Dcreate( loc_id, dset_name, type_id, space_id, H5P_DEFAULT, plist_id, H5P_DEFAULT )) < 0 ) goto out; /* Only write if there is something to write */ if ( data ) { /* Write data to the dataset. */ if ( H5Dwrite( dataset_id, type_id, H5S_ALL, H5S_ALL, H5P_DEFAULT, data ) < 0 ) goto out; } /* Terminate access to the data space. */ if ( H5Sclose( space_id ) < 0 ) goto out; /* End access to the property list */ if ( H5Pclose( plist_id ) < 0 ) goto out; /* Return the object unique ID for future references */ return dataset_id; /* error zone, gracefully close */ out: H5E_BEGIN_TRY { H5Dclose(dataset_id); H5Sclose(space_id); H5Pclose(plist_id); } H5E_END_TRY; return -1; } /*------------------------------------------------------------------------- * Function: H5TBOread_records * * Purpose: Read records from an opened table * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted, faltet@pytables.com * * Date: April 19, 2003 * * Comments: * * Modifications: * * *------------------------------------------------------------------------- */ herr_t H5TBOread_records( hid_t dataset_id, hid_t mem_type_id, hsize_t start, hsize_t nrecords, void *data ) { hid_t space_id; hid_t mem_space_id; hsize_t count[1]; hsize_t offset[1]; /* Get the dataspace handle */ if ( (space_id = H5Dget_space( dataset_id )) < 0 ) goto out; /* Define a hyperslab in the dataset of the size of the records */ offset[0] = start; count[0] = nrecords; if ( H5Sselect_hyperslab(space_id, H5S_SELECT_SET, offset, NULL, count, NULL) < 0 ) goto out; /* Create a memory dataspace handle */ if ( (mem_space_id = H5Screate_simple( 1, count, NULL )) < 0 ) goto out; if ( H5Dread(dataset_id, mem_type_id, mem_space_id, space_id, H5P_DEFAULT, data ) < 0 ) goto out; /* Terminate access to the memory dataspace */ if ( H5Sclose( mem_space_id ) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; return 0; out: return -1; } /*------------------------------------------------------------------------- * Function: H5TBOread_elements * * Purpose: Read selected records from an opened table * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted, faltet@pytables.com * * Date: April 19, 2003 * * Comments: * * Modifications: * * *------------------------------------------------------------------------- */ herr_t H5TBOread_elements( hid_t dataset_id, hid_t mem_type_id, hsize_t nrecords, void *coords, void *data ) { hid_t space_id; hid_t mem_space_id; hsize_t count[1]; /* Get the dataspace handle */ if ( (space_id = H5Dget_space( dataset_id )) < 0 ) goto out; /* Define a selection of points in the dataset */ if ( H5Sselect_elements(space_id, H5S_SELECT_SET, (size_t)nrecords, (const hsize_t *)coords) < 0 ) goto out; /* Create a memory dataspace handle */ count[0] = nrecords; if ( (mem_space_id = H5Screate_simple( 1, count, NULL )) < 0 ) goto out; if ( H5Dread( dataset_id, mem_type_id, mem_space_id, space_id, H5P_DEFAULT, data ) < 0 ) goto out; /* Terminate access to the memory dataspace */ if ( H5Sclose( mem_space_id ) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; return 0; out: return -1; } /*------------------------------------------------------------------------- * Function: H5TBOappend_records * * Purpose: Appends records to a table * * Return: Success: 0, Failure: -1 * * Programmers: * Francesc Alted, faltet@pytables.com * * Date: April 20, 2003 * * Comments: Uses memory offsets * * Modifications: * * *------------------------------------------------------------------------- */ herr_t H5TBOappend_records( hid_t dataset_id, hid_t mem_type_id, hsize_t nrecords, hsize_t nrecords_orig, const void *data ) { hid_t space_id = -1; /* Shut up the compiler */ hsize_t count[1]; hsize_t offset[1]; hid_t mem_space_id = -1; /* Shut up the compiler */ hsize_t dims[1]; /* Extend the dataset */ dims[0] = nrecords_orig; dims[0] += nrecords; if ( H5Dset_extent(dataset_id, dims) < 0 ) goto out; /* Create a simple memory data space */ count[0]=nrecords; if ( (mem_space_id = H5Screate_simple( 1, count, NULL )) < 0 ) return -1; /* Get the file data space */ if ( (space_id = H5Dget_space(dataset_id)) < 0 ) return -1; /* Define a hyperslab in the dataset */ offset[0] = nrecords_orig; if ( H5Sselect_hyperslab( space_id, H5S_SELECT_SET, offset, NULL, count, NULL) < 0 ) goto out; if ( H5Dwrite( dataset_id, mem_type_id, mem_space_id, space_id, H5P_DEFAULT, data ) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( mem_space_id ) < 0 ) goto out; if ( H5Sclose( space_id ) < 0 ) goto out; return 0; out: return -1; } /*------------------------------------------------------------------------- * Function: H5TBOwrite_records * * Purpose: Writes records * * Return: Success: 0, Failure: -1 * * Programmer: Pedro Vicente, pvn@ncsa.uiuc.edu * * Date: November 19, 2001 * * Comments: Uses memory offsets * * Modifications: * - Added a step parameter in order to support strided writing. * Francesc Alted, faltet@pytables.com. 2004-08-12 * * - Removed the type_size which was unnecessary * Francesc Alted, 2005-10-25 * *------------------------------------------------------------------------- */ herr_t H5TBOwrite_records( hid_t dataset_id, hid_t mem_type_id, hsize_t start, hsize_t nrecords, hsize_t step, const void *data ) { hsize_t count[1]; hsize_t stride[1]; hsize_t offset[1]; hid_t space_id; hid_t mem_space_id; hsize_t dims[1]; /* Get the dataspace handle */ if ( (space_id = H5Dget_space( dataset_id )) < 0 ) goto out; /* Get records */ if ( H5Sget_simple_extent_dims( space_id, dims, NULL) < 0 ) goto out; /* if ( start + nrecords > dims[0] ) */ if ( start + (nrecords-1) * step + 1 > dims[0] ) goto out; /* Define a hyperslab in the dataset of the size of the records */ offset[0] = start; stride[0] = step; count[0] = nrecords; if ( H5Sselect_hyperslab( space_id, H5S_SELECT_SET, offset, stride, count, NULL) < 0 ) goto out; /* Create a memory dataspace handle */ if ( (mem_space_id = H5Screate_simple( 1, count, NULL )) < 0 ) goto out; if ( H5Dwrite( dataset_id, mem_type_id, mem_space_id, space_id, H5P_DEFAULT, data ) < 0 ) goto out; /* Terminate access to the memory dataspace */ if ( H5Sclose( mem_space_id ) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; return 0; out: return -1; } /*------------------------------------------------------------------------- * Function: H5TBOwrite_elements * * Purpose: Writes records on a list of coordinates * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted, * * Date: October 25, 2005 * * Comments: * * *------------------------------------------------------------------------- */ herr_t H5TBOwrite_elements( hid_t dataset_id, hid_t mem_type_id, hsize_t nrecords, const void *coords, const void *data ) { hsize_t count[1]; hid_t space_id; hid_t mem_space_id; /* Get the dataspace handle */ if ( (space_id = H5Dget_space( dataset_id )) < 0 ) goto out; /* Define a selection of points in the dataset */ if ( H5Sselect_elements(space_id, H5S_SELECT_SET, (size_t)nrecords, (const hsize_t *)coords) < 0 ) goto out; /* Create a memory dataspace handle */ count[0] = nrecords; if ( (mem_space_id = H5Screate_simple( 1, count, NULL )) < 0 ) goto out; if ( H5Dwrite( dataset_id, mem_type_id, mem_space_id, space_id, H5P_DEFAULT, data ) < 0 ) goto out; /* Terminate access to the memory dataspace */ if ( H5Sclose( mem_space_id ) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; return 0; out: return -1; } /*------------------------------------------------------------------------- * Function: H5TBOdelete_records * * Purpose: Delete records from middle of table ("pulling up" all the records after it) * * Return: Success: 0, Failure: -1 * * Programmer: Pedro Vicente, pvn@ncsa.uiuc.edu * Modified by: F. Alted * * Date: November 26, 2001 * * Modifications: April 29, 2003 * Modifications: February 19, 2004 (buffered rewriting of trailing rows) * Modifications: September 28, 2005 (adapted to opened tables) * * *------------------------------------------------------------------------- */ herr_t H5TBOdelete_records( hid_t dataset_id, hid_t mem_type_id, hsize_t ntotal_records, size_t src_size, hsize_t start, hsize_t nrecords, hsize_t maxtuples) { hsize_t nrowsread; hsize_t read_start; hsize_t write_start; hsize_t read_nrecords; hsize_t count[1]; hsize_t offset[1]; hid_t space_id; hid_t mem_space_id; hsize_t mem_size[1]; unsigned char *tmp_buf; hsize_t dims[1]; size_t read_nbuf; /* Shut the compiler up */ tmp_buf = NULL; /*------------------------------------------------------------------------- * Read the records after the deleted one(s) *------------------------------------------------------------------------- */ read_start = start + nrecords; write_start = start; read_nrecords = ntotal_records - read_start; /* This check added for the case that there are no records to be read */ /* F. Alted 2003/07/16 */ if (read_nrecords > 0) { nrowsread = 0; while (nrowsread < read_nrecords) { if (nrowsread + maxtuples < read_nrecords) read_nbuf = (size_t)maxtuples; else read_nbuf = (size_t)(read_nrecords - nrowsread); tmp_buf = (unsigned char *)malloc(read_nbuf * src_size ); if ( tmp_buf == NULL ) return -1; /* Read the records after the deleted one(s) */ if ( H5TBOread_records(dataset_id, mem_type_id, read_start, read_nbuf, tmp_buf ) < 0 ) return -1; /*------------------------------------------------------------------------- * Write the records in another position *------------------------------------------------------------------------- */ /* Get the dataspace handle */ if ( (space_id = H5Dget_space( dataset_id )) < 0 ) goto out; /* Define a hyperslab in the dataset of the size of the records */ offset[0] = write_start; count[0] = read_nbuf; if ( H5Sselect_hyperslab( space_id, H5S_SELECT_SET, offset, NULL, count, NULL) < 0 ) goto out; /* Create a memory dataspace handle */ mem_size[0] = count[0]; if ( (mem_space_id = H5Screate_simple( 1, mem_size, NULL )) < 0 ) goto out; if ( H5Dwrite( dataset_id, mem_type_id, mem_space_id, space_id, H5P_DEFAULT, tmp_buf ) < 0 ) goto out; /* Terminate access to the memory dataspace */ if ( H5Sclose( mem_space_id ) < 0 ) goto out; /* Release the reading buffer */ free( tmp_buf ); /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; /* Update the counters */ read_start += read_nbuf; write_start += read_nbuf; nrowsread += read_nbuf; } /* while (nrowsread < read_nrecords) */ } /* if (nread_nrecords > 0) */ /*------------------------------------------------------------------------- * Change the table dimension *------------------------------------------------------------------------- */ #if defined (SHRINK) dims[0] = (int)ntotal_records - (int)nrecords; if ( H5Dset_extent( dataset_id, dims ) < 0 ) goto out; #endif return 0; out: return -1; } PyTables-3.7.0/src/H5TB-opt.h000066400000000000000000000041771416254111300155150ustar00rootroot00000000000000#include #ifdef __cplusplus extern "C" { #endif hid_t H5TBOmake_table( const char *table_title, hid_t loc_id, const char *dset_name, char *version, const char *class_, hid_t type_id, hsize_t nrecords, hsize_t chunk_size, void *fill_data, int compress, char *complib, int shuffle, int fletcher32, hbool_t track_times, const void *data ); herr_t H5TBOread_records( hid_t dataset_id, hid_t mem_type_id, hsize_t start, hsize_t nrecords, void *data ); herr_t H5TBOread_elements( hid_t dataset_id, hid_t mem_type_id, hsize_t nrecords, void *coords, void *data ); herr_t H5TBOappend_records( hid_t dataset_id, hid_t mem_type_id, hsize_t nrecords, hsize_t nrecords_orig, const void *data ); herr_t H5TBOwrite_records( hid_t dataset_id, hid_t mem_type_id, hsize_t start, hsize_t nrecords, hsize_t step, const void *data ); herr_t H5TBOwrite_elements( hid_t dataset_id, hid_t mem_type_id, hsize_t nrecords, const void *coords, const void *data ); herr_t H5TBOdelete_records( hid_t dataset_id, hid_t mem_type_id, hsize_t ntotal_records, size_t src_size, hsize_t start, hsize_t nrecords, hsize_t maxtuples ); PyTables-3.7.0/src/H5VLARRAY.c000066400000000000000000000250601416254111300155150ustar00rootroot00000000000000#include "H5ATTR.h" #include "tables.h" #include "utils.h" /* get_order */ #include "H5Zlzo.h" /* Import FILTER_LZO */ #include "H5Zbzip2.h" /* Import FILTER_BZIP2 */ #include "blosc_filter.h" /* Import FILTER_BLOSC */ #include #include /*------------------------------------------------------------------------- * * Public functions * *------------------------------------------------------------------------- */ /*------------------------------------------------------------------------- * Function: H5VLARRAYmake * * Purpose: Creates and writes a dataset of a variable length type type_id * * Return: Success: 0, Failure: -1 * * Programmer: F. Alted * * Date: November 08, 2003 * * Comments: Modified by A. Cobb. August 21, 2017 (track_times) * *------------------------------------------------------------------------- */ hid_t H5VLARRAYmake( hid_t loc_id, const char *dset_name, const char *obversion, const int rank, const hsize_t *dims, hid_t type_id, hsize_t chunk_size, void *fill_data, int compress, char *complib, int shuffle, int fletcher32, hbool_t track_times, const void *data) { hvl_t vldata; hid_t dataset_id, space_id, datatype, tid1; hsize_t dataset_dims[1]; hsize_t maxdims[1] = { H5S_UNLIMITED }; hsize_t dims_chunk[1]; hid_t plist_id; unsigned int cd_values[7]; int blosc_compcode; char *blosc_compname = NULL; if (data) /* if data, one row will be filled initially */ dataset_dims[0] = 1; else /* no data, so no rows on dataset initally */ dataset_dims[0] = 0; dims_chunk[0] = chunk_size; /* Fill the vldata estructure with the data to write */ /* This is currectly not used */ vldata.p = (void *)data; vldata.len = 1; /* Only one array type to save */ /* Create a VL datatype */ if (rank == 0) { datatype = H5Tvlen_create(type_id); } else { tid1 = H5Tarray_create(type_id, rank, dims); datatype = H5Tvlen_create(tid1); H5Tclose( tid1 ); /* Release resources */ } /* The dataspace */ space_id = H5Screate_simple( 1, dataset_dims, maxdims ); /* Dataset creation properties */ plist_id = H5Pcreate (H5P_DATASET_CREATE); /* Enable or disable recording dataset times */ if ( H5Pset_obj_track_times( plist_id, track_times ) < 0 ) return -1; /* Modify dataset creation properties, i.e. enable chunking */ if ( H5Pset_chunk ( plist_id, 1, dims_chunk ) < 0 ) return -1; /* Dataset creation property list is modified to use */ /* Fletcher must be first */ if (fletcher32) { if ( H5Pset_fletcher32( plist_id) < 0 ) return -1; } /* Then shuffle (blosc shuffles inplace) */ if ((shuffle && compress) && (strncmp(complib, "blosc", 5) != 0)) { if ( H5Pset_shuffle( plist_id) < 0 ) return -1; } /* Finally compression */ if (compress) { cd_values[0] = compress; cd_values[1] = (int)(atof(obversion) * 10); cd_values[2] = VLArray; /* The default compressor in HDF5 (zlib) */ if (strcmp(complib, "zlib") == 0) { if ( H5Pset_deflate( plist_id, compress) < 0 ) return -1; } /* The Blosc compressor does accept parameters */ else if (strcmp(complib, "blosc") == 0) { cd_values[4] = compress; cd_values[5] = shuffle; if ( H5Pset_filter( plist_id, FILTER_BLOSC, H5Z_FLAG_OPTIONAL, 6, cd_values) < 0 ) return -1; } /* The Blosc compressor can use other compressors */ else if (strncmp(complib, "blosc:", 6) == 0) { cd_values[4] = compress; cd_values[5] = shuffle; blosc_compname = complib + 6; blosc_compcode = blosc_compname_to_compcode(blosc_compname); cd_values[6] = blosc_compcode; if ( H5Pset_filter( plist_id, FILTER_BLOSC, H5Z_FLAG_OPTIONAL, 7, cd_values) < 0 ) return -1; } /* The LZO compressor does accept parameters */ else if (strcmp(complib, "lzo") == 0) { if ( H5Pset_filter( plist_id, FILTER_LZO, H5Z_FLAG_OPTIONAL, 3, cd_values) < 0 ) return -1; } /* The bzip2 compress does accept parameters */ else if (strcmp(complib, "bzip2") == 0) { if ( H5Pset_filter( plist_id, FILTER_BZIP2, H5Z_FLAG_OPTIONAL, 3, cd_values) < 0 ) return -1; } else { /* Compression library not supported */ fprintf(stderr, "Compression library not supported\n"); return -1; } } /* Create the dataset. */ if ((dataset_id = H5Dcreate(loc_id, dset_name, datatype, space_id, H5P_DEFAULT, plist_id, H5P_DEFAULT )) < 0 ) goto out; /* Write the dataset only if there is data to write */ if (data) if ( H5Dwrite( dataset_id, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, &vldata ) < 0 ) goto out; /* Terminate access to the data space. */ if ( H5Sclose( space_id ) < 0 ) return -1; /* Release the datatype in the case that it is not an atomic type */ if ( H5Tclose( datatype ) < 0 ) return -1; /* End access to the property list */ if ( H5Pclose( plist_id ) < 0 ) goto out; return dataset_id; out: return -1; } /*------------------------------------------------------------------------- * Function: H5ARRAYappend_records * * Purpose: Appends records to an array * * Return: Success: 0, Failure: -1 * * Programmers: * Francesc Alted * * Date: October 30, 2003 * * Comments: Uses memory offsets * * Modifications: * * *------------------------------------------------------------------------- */ herr_t H5VLARRAYappend_records( hid_t dataset_id, hid_t type_id, int nobjects, hsize_t nrecords, const void *data ) { hid_t space_id; hid_t mem_space_id; hsize_t start[1]; hsize_t dataset_dims[1]; hsize_t dims_new[1] = {1}; /* Only a record on each append */ hvl_t wdata; /* Information to write */ /* Initialize VL data to write */ wdata.p=(void *)data; wdata.len=nobjects; /* Dimension for the new dataset */ dataset_dims[0] = nrecords + 1; /* Extend the dataset */ if ( H5Dset_extent( dataset_id, dataset_dims ) < 0 ) goto out; /* Create a simple memory data space */ if ( (mem_space_id = H5Screate_simple( 1, dims_new, NULL )) < 0 ) return -1; /* Get the file data space */ if ( (space_id = H5Dget_space( dataset_id )) < 0 ) return -1; /* Define a hyperslab in the dataset */ start[0] = nrecords; if ( H5Sselect_hyperslab( space_id, H5S_SELECT_SET, start, NULL, dims_new, NULL) < 0 ) goto out; if ( H5Dwrite( dataset_id, type_id, mem_space_id, space_id, H5P_DEFAULT, &wdata ) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; if ( H5Sclose( mem_space_id ) < 0 ) goto out; return 1; out: return -1; } /*------------------------------------------------------------------------- * Function: H5ARRAYmodify_records * * Purpose: Modify records of an array * * Return: Success: 0, Failure: -1 * * Programmers: * Francesc Alted * * Date: October 28, 2004 * * Comments: Uses memory offsets * * Modifications: * * *------------------------------------------------------------------------- */ herr_t H5VLARRAYmodify_records( hid_t dataset_id, hid_t type_id, hsize_t nrow, int nobjects, const void *data ) { hid_t space_id; hid_t mem_space_id; hsize_t start[1]; hsize_t dims_new[1] = {1}; /* Only a record on each update */ hvl_t wdata; /* Information to write */ /* Initialize VL data to write */ wdata.p=(void *)data; wdata.len=nobjects; /* Create a simple memory data space */ if ( (mem_space_id = H5Screate_simple( 1, dims_new, NULL )) < 0 ) return -1; /* Get the file data space */ if ( (space_id = H5Dget_space( dataset_id )) < 0 ) return -1; /* Define a hyperslab in the dataset */ start[0] = nrow; if ( H5Sselect_hyperslab( space_id, H5S_SELECT_SET, start, NULL, dims_new, NULL) < 0 ) goto out; if ( H5Dwrite( dataset_id, type_id, mem_space_id, space_id, H5P_DEFAULT, &wdata ) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; if ( H5Sclose( mem_space_id ) < 0 ) goto out; return 1; out: return -1; } /*------------------------------------------------------------------------- * Function: H5VLARRAYget_info * * Purpose: Gathers info about the VLEN type and other. * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted * * Date: November 19, 2003 * *------------------------------------------------------------------------- */ herr_t H5VLARRAYget_info( hid_t dataset_id, hid_t type_id, hsize_t *nrecords, char *base_byteorder ) { hid_t space_id; H5T_class_t base_class_id; H5T_class_t atom_class_id; hid_t atom_type_id; hid_t base_type_id; /* Get the dataspace handle */ if ( (space_id = H5Dget_space( dataset_id )) < 0 ) goto out; /* Get number of records (it should be rank-1) */ if ( H5Sget_simple_extent_dims( space_id, nrecords, NULL) < 0 ) goto out; /* Terminate access to the dataspace */ if ( H5Sclose( space_id ) < 0 ) goto out; /* Get the type of the atomic component */ atom_type_id = H5Tget_super( type_id ); /* Get the class of the atomic component. */ atom_class_id = H5Tget_class( atom_type_id ); /* Check whether the atom is an array class object or not */ if ( atom_class_id == H5T_ARRAY) { /* Get the array base component */ base_type_id = H5Tget_super( atom_type_id ); /* Get the class of base component */ base_class_id = H5Tget_class( base_type_id ); /* Release the datatypes */ if ( H5Tclose(atom_type_id ) ) goto out; } else { base_class_id = atom_class_id; base_type_id = atom_type_id; } /* Get the byteorder */ /* Only integer, float and time classes can be byteordered */ if ((base_class_id == H5T_INTEGER) || (base_class_id == H5T_FLOAT) || (base_class_id == H5T_BITFIELD) || (base_class_id == H5T_COMPOUND) || (base_class_id == H5T_TIME)) { get_order(base_type_id, base_byteorder); } else { strcpy(base_byteorder, "irrelevant"); } /* Release the datatypes */ if ( H5Tclose(base_type_id ) ) goto out; return 0; out: return -1; } PyTables-3.7.0/src/H5VLARRAY.h000066400000000000000000000025241416254111300155220ustar00rootroot00000000000000#ifndef _H5VLARRAY_H #define _H5VLARRAY_H #include #ifdef __cplusplus extern "C" { #endif hid_t H5VLARRAYmake( hid_t loc_id, const char *dset_name, const char *obversion, const int rank, const hsize_t *dims, hid_t type_id, hsize_t chunk_size, void *fill_data, int compress, char *complib, int shuffle, int fletcher32, hbool_t track_times, const void *data); herr_t H5VLARRAYappend_records( hid_t dataset_id, hid_t type_id, int nobjects, hsize_t nrecords, const void *data ); herr_t H5VLARRAYmodify_records( hid_t dataset_id, hid_t type_id, hsize_t nrow, int nobjects, const void *data ); herr_t H5VLARRAYget_info( hid_t dataset_id, hid_t type_id, hsize_t *nrecords, char *base_byteorder); #ifdef __cplusplus } #endif #endif PyTables-3.7.0/src/H5Zbzip2.c000066400000000000000000000131711416254111300155550ustar00rootroot00000000000000#include "H5Zbzip2.h" #include #include #include #include #include #include #ifdef HAVE_BZ2_LIB #include "bzlib.h" #endif /* defined HAVE_BZ2_LIB */ size_t bzip2_deflate(unsigned int flags, size_t cd_nelmts, const unsigned int cd_values[], size_t nbytes, size_t *buf_size, void **buf); int register_bzip2(char **version, char **date) { #ifdef HAVE_BZ2_LIB char *libver, *versionp, *datep, *sep; H5Z_class_t filter_class = { H5Z_CLASS_T_VERS, /* H5Z_class_t version */ (H5Z_filter_t)(FILTER_BZIP2), /* filter_id */ 1, 1, /* Encoding and decoding enabled */ "bzip2", /* comment */ NULL, /* can_apply_func */ NULL, /* set_local_func */ (H5Z_func_t)(bzip2_deflate) /* filter_func */ }; /* Register the filter class for the bzip2 compressor. */ H5Zregister(&filter_class); /* Get the library major version from the version string. */ libver = strdup(BZ2_bzlibVersion()); sep = strchr(libver, ','); assert(sep != NULL); assert(*(sep + 1) == ' '); *sep = '\0'; versionp = libver; datep = sep + 2; /* after the comma and a space */ *version = strdup(versionp); *date = strdup(datep); free(libver); return 1; /* library is available */ #else return 0; /* library is not available */ #endif /* defined HAVE_BZ2_LIB */ } size_t bzip2_deflate(unsigned int flags, size_t cd_nelmts, const unsigned int cd_values[], size_t nbytes, size_t *buf_size, void **buf) { #ifdef HAVE_BZ2_LIB char *outbuf = NULL; size_t outbuflen, outdatalen; int ret; if (flags & H5Z_FLAG_REVERSE) { /** Decompress data. ** ** This process is troublesome since the size of uncompressed data ** is unknown, so the low-level interface must be used. ** Data is decompressed to the output buffer (which is sized ** for the average case); if it gets full, its size is doubled ** and decompression continues. This avoids repeatedly trying to ** decompress the whole block, which could be really inefficient. **/ bz_stream stream; char *newbuf = NULL; size_t newbuflen; /* Prepare the output buffer. */ outbuflen = nbytes * 3 + 1; /* average bzip2 compression ratio is 3:1 */ outbuf = malloc(outbuflen); if (outbuf == NULL) { fprintf(stderr, "memory allocation failed for bzip2 decompression\n"); goto cleanupAndFail; } /* Use standard malloc()/free() for internal memory handling. */ stream.bzalloc = NULL; stream.bzfree = NULL; stream.opaque = NULL; /* Start decompression. */ ret = BZ2_bzDecompressInit(&stream, 0, 0); if (ret != BZ_OK) { fprintf(stderr, "bzip2 decompression start failed with error %d\n", ret); goto cleanupAndFail; } /* Feed data to the decompression process and get decompressed data. */ stream.next_out = outbuf; stream.avail_out = outbuflen; stream.next_in = *buf; stream.avail_in = nbytes; do { ret = BZ2_bzDecompress(&stream); if (ret < 0) { fprintf(stderr, "BUG: bzip2 decompression failed with error %d\n", ret); goto cleanupAndFail; } if (ret != BZ_STREAM_END && stream.avail_out == 0) { /* Grow the output buffer. */ newbuflen = outbuflen * 2; newbuf = realloc(outbuf, newbuflen); if (newbuf == NULL) { fprintf(stderr, "memory allocation failed for bzip2 decompression\n"); goto cleanupAndFail; } stream.next_out = newbuf + outbuflen; /* half the new buffer behind */ stream.avail_out = outbuflen; /* half the new buffer ahead */ outbuf = newbuf; outbuflen = newbuflen; } } while (ret != BZ_STREAM_END); /* End compression. */ outdatalen = stream.total_out_lo32; ret = BZ2_bzDecompressEnd(&stream); if (ret != BZ_OK) { fprintf(stderr, "bzip2 compression end failed with error %d\n", ret); goto cleanupAndFail; } } else { /** Compress data. ** ** This is quite simple, since the size of compressed data in the worst ** case is known and it is not much bigger than the size of uncompressed ** data. This allows us to use the simplified one-shot interface to ** compression. **/ unsigned int odatalen; /* maybe not the same size as outdatalen */ int blockSize100k = 9; /* Get compression block size if present. */ if (cd_nelmts > 0) { blockSize100k = cd_values[0]; if (blockSize100k < 1 || blockSize100k > 9) { fprintf(stderr, "invalid compression block size: %d\n", blockSize100k); goto cleanupAndFail; } } /* Prepare the output buffer. */ outbuflen = nbytes + nbytes / 100 + 600; /* worst case (bzip2 docs) */ outbuf = malloc(outbuflen); if (outbuf == NULL) { fprintf(stderr, "memory allocation failed for bzip2 compression\n"); goto cleanupAndFail; } /* Compress data. */ odatalen = outbuflen; ret = BZ2_bzBuffToBuffCompress(outbuf, &odatalen, *buf, nbytes, blockSize100k, 0, 0); outdatalen = odatalen; if (ret != BZ_OK) { fprintf(stderr, "bzip2 compression failed with error %d\n", ret); goto cleanupAndFail; } } /* Always replace the input buffer with the output buffer. */ free(*buf); *buf = outbuf; *buf_size = outbuflen; return outdatalen; cleanupAndFail: if (outbuf) free(outbuf); return 0; #else return 0; #endif /* defined HAVE_BZ2_LIB */ } PyTables-3.7.0/src/H5Zbzip2.h000066400000000000000000000002421416254111300155550ustar00rootroot00000000000000#ifndef __H5ZBZIP2_H__ #define __H5ZBZIP2_H__ 1 #define FILTER_BZIP2 307 int register_bzip2(char **version, char **date); #endif /* ! defined __H5ZBZIP2_H__ */ PyTables-3.7.0/src/H5Zlzo.c000066400000000000000000000221131416254111300153270ustar00rootroot00000000000000#include #include #include #include "H5Zlzo.h" #include "tables.h" #ifdef HAVE_LZO_LIB # include "lzo1x.h" #endif #ifdef HAVE_LZO2_LIB # include "lzo/lzo1x.h" # define HAVE_LZO_LIB /* The API for LZO and LZO2 is mostly identical */ #endif /* #undef DEBUG */ /* Activate the checksum. It is safer and takes only a 1% more of space and a 2% more of CPU (but sometimes is faster than without checksum, which is almost negligible. F. Alted 2003/07/22 Added code for pytables 0.5 backward compatibility. F. Alted 2003/07/28 Added code for saving the uncompressed length buffer as well. F. Alted 2003/07/29 */ /* From pytables 0.8 on I decided to let the user select the fletcher32 checksum provided in HDF5 1.6 or higher. So, even though the CHECKSUM support here seems pretty stable it will be disabled. F. Alted 2004/01/02 */ #undef CHECKSUM size_t lzo_deflate (unsigned flags, size_t cd_nelmts, const unsigned cd_values[], size_t nbytes, size_t *buf_size, void **buf); int register_lzo(char **version, char **date) { #ifdef HAVE_LZO_LIB H5Z_class_t filter_class = { H5Z_CLASS_T_VERS, /* H5Z_class_t version */ (H5Z_filter_t)(FILTER_LZO), /* filter_id */ 1, 1, /* Encoding and decoding enabled */ "lzo", /* comment */ NULL, /* can_apply_func */ NULL, /* set_local_func */ (H5Z_func_t)(lzo_deflate) /* filter_func */ }; /* Init the LZO library */ if (lzo_init()!=LZO_E_OK) { fprintf(stderr, "Problems initializing LZO library\n"); *version = NULL; *date = NULL; return 0; /* lib is not available */ } /* Register the lzo compressor */ H5Zregister(&filter_class); *version = strdup(LZO_VERSION_STRING); *date = strdup(LZO_VERSION_DATE); return 1; /* lib is available */ #else *version = NULL; *date = NULL; return 0; /* lib is not available */ #endif /* HAVE_LZO_LIB */ } size_t lzo_deflate (unsigned flags, size_t cd_nelmts, const unsigned cd_values[], size_t nbytes, size_t *buf_size, void **buf) { size_t ret_value = 0; #ifdef HAVE_LZO_LIB void *outbuf = NULL, *wrkmem = NULL; int status; size_t nalloc = *buf_size; lzo_uint out_len = (lzo_uint) nalloc; /* max_len_buffer will keep the likely output buffer size after processing the first chunk */ static unsigned int max_len_buffer = 0; /* int complevel = 1; */ #if (defined CHECKSUM || defined DEBUG) int object_version = 10; /* Default version 1.0 */ int object_type = Table; /* Default object type */ #endif #ifdef CHECKSUM lzo_uint32 checksum; #endif /* Check arguments */ /* For Table versions < 20, there were no parameters */ if (cd_nelmts==1 ) { /* complevel = cd_values[0]; */ /* This do nothing right now */ } else if (cd_nelmts==2 ) { /* complevel = cd_values[0]; */ /* This do nothing right now */ #if (defined CHECKSUM || defined DEBUG) object_version = cd_values[1]; /* The table VERSION attribute */ #endif } else if (cd_nelmts==3 ) { /* complevel = cd_values[0]; */ /* This do nothing right now */ #if (defined CHECKSUM || defined DEBUG) object_version = cd_values[1]; /* The table VERSION attribute */ object_type = cd_values[2]; /* A tag for identifying the object (see tables.h) */ #endif } #ifdef DEBUG printf("Object type: %d. ", object_type); printf("object_version:%d\n", object_version); #endif if (flags & H5Z_FLAG_REVERSE) { /* Input */ /* printf("Decompressing chunk with LZO\n"); */ #ifdef CHECKSUM if ((object_type == Table && object_version >= 20) || object_type != Table) { nbytes -= 4; /* Point to uncompressed buffer length */ memcpy(&nalloc, ((unsigned char *)(*buf)+nbytes), 4); out_len = nalloc; nbytes -= 4; /* Point to the checksum */ #ifdef DEBUG printf("Compressed bytes: %d. Uncompressed bytes: %d\n", nbytes, nalloc); #endif } #endif /* Only allocate the bytes for the outbuf */ if (max_len_buffer == 0) { if (NULL==(outbuf = (void *)malloc(nalloc))) fprintf(stderr, "Memory allocation failed for lzo uncompression.\n"); } else { if (NULL==(outbuf = (void *)malloc(max_len_buffer))) fprintf(stderr, "Memory allocation failed for lzo uncompression.\n"); out_len = max_len_buffer; nalloc = max_len_buffer; } while(1) { #ifdef DEBUG printf("nbytes -->%d\n", nbytes); printf("nalloc -->%d\n", nalloc); printf("max_len_buffer -->%d\n", max_len_buffer); #endif /* DEBUG */ /* The assembler version is a 10% slower than the C version with gcc 3.2.2 and gcc 3.3.3 */ /* status = lzo1x_decompress_asm_safe(*buf, (lzo_uint)nbytes, outbuf, */ /* &out_len, NULL); */ /* The safe and unsafe versions have the same speed more or less */ status = lzo1x_decompress_safe(*buf, (lzo_uint)nbytes, outbuf, &out_len, NULL); if (status == LZO_E_OK) { #ifdef DEBUG printf("decompressed %lu bytes back into %lu bytes\n", (long) nbytes, (long) out_len); #endif max_len_buffer = out_len; break; /* done */ } else if (status == LZO_E_OUTPUT_OVERRUN) { nalloc *= 2; out_len = (lzo_uint) nalloc; if (NULL==(outbuf = realloc(outbuf, nalloc))) { fprintf(stderr, "Memory allocation failed for lzo uncompression\n"); } } else { /* this should NEVER happen */ fprintf(stderr, "internal error - decompression failed: %d\n", status); ret_value = 0; /* fail */ goto done; } } #ifdef CHECKSUM if ((object_type == Table && object_version >= 20) || object_type != Table) { #ifdef DEBUG printf("Checksum uncompressing..."); #endif /* Compute the checksum */ checksum=lzo_adler32(lzo_adler32(0,NULL,0), outbuf, out_len); /* Compare */ if (memcmp(&checksum, (unsigned char*)(*buf)+nbytes, 4)) { ret_value = 0; /*fail*/ fprintf(stderr,"Checksum failed!.\n"); goto done; } } #endif /* CHECKSUM */ free(*buf); *buf = outbuf; outbuf = NULL; *buf_size = nalloc; ret_value = out_len; } else { /* * Output; compress but fail if the result would be larger than the * input. The library doesn't provide in-place compression, so we * must allocate a separate buffer for the result. */ lzo_byte *z_src = (lzo_byte*)(*buf); lzo_byte *z_dst; /*destination buffer */ lzo_uint z_src_nbytes = (lzo_uint)(nbytes); /* The next was the original computation for worst-case expansion */ /* I don't know why the difference with LZO1*. Perhaps some wrong docs in LZO package? */ /* lzo_uint z_dst_nbytes = (lzo_uint)(nbytes + (nbytes / 64) + 16 + 3); */ /* The next is for LZO1* algorithms */ /* lzo_uint z_dst_nbytes = (lzo_uint)(nbytes + (nbytes / 16) + 64 + 3); */ /* The next is for LZO2* algorithms. This will be the default */ lzo_uint z_dst_nbytes = (lzo_uint)(nbytes + (nbytes / 8) + 128 + 3); #ifdef CHECKSUM if ((object_type == Table && object_version >= 20) || object_type != Table) { z_dst_nbytes += 4+4; /* Checksum + buffer size */ } #endif if (NULL==(z_dst=outbuf=(void *)malloc(z_dst_nbytes))) { fprintf(stderr, "Unable to allocate lzo destination buffer.\n"); ret_value = 0; /* fail */ goto done; } /* Compress this buffer */ wrkmem = malloc(LZO1X_1_MEM_COMPRESS); if (wrkmem == NULL) { fprintf(stderr, "Memory allocation failed for lzo compression\n"); ret_value = 0; goto done; } status = lzo1x_1_compress (z_src, z_src_nbytes, z_dst, &z_dst_nbytes, wrkmem); free(wrkmem); wrkmem = NULL; #ifdef CHECKSUM if ((object_type == Table && object_version >= 20) || object_type != Table) { #ifdef DEBUG printf("Checksum compressing ..."); printf("src_nbytes: %d, dst_nbytes: %d\n", z_src_nbytes, z_dst_nbytes); #endif /* Append checksum of *uncompressed* data at the end */ checksum = lzo_adler32(lzo_adler32(0,NULL,0), *buf, nbytes); memcpy((unsigned char*)(z_dst)+z_dst_nbytes, &checksum, 4); memcpy((unsigned char*)(z_dst)+z_dst_nbytes+4, &nbytes, 4); z_dst_nbytes += (lzo_uint)4+4; nbytes += 4+4; } #endif if (z_dst_nbytes >= nbytes) { #ifdef DEBUG printf("The compressed buffer takes more space than uncompressed!.\n"); #endif ret_value = 0; /* fail */ goto done; } else if (LZO_E_OK != status) { fprintf(stderr,"lzo library error in compression\n"); ret_value = 0; /* fail */ goto done; } else { free(*buf); *buf = outbuf; outbuf = NULL; *buf_size = z_dst_nbytes; ret_value = z_dst_nbytes; } } done: if(outbuf) free(outbuf); #endif /* HAVE_LZO_LIB */ return ret_value; } PyTables-3.7.0/src/H5Zlzo.h000066400000000000000000000002301416254111300153300ustar00rootroot00000000000000#ifndef __H5ZLZO_H__ #define __H5ZLZO_H__ 1 #define FILTER_LZO 305 int register_lzo(char **version, char **date); #endif /* ! defined __H5ZLZO_H__ */ PyTables-3.7.0/src/Makefile000066400000000000000000000004461416254111300154750ustar00rootroot00000000000000VERSION = $(shell cat ../VERSION) # All the generated files GENERATED = version.h .PHONY: all clean distclean all: $(GENERATED) clean: rm -f $(GENERATED) distclean: clean version.h: version.h.in ../VERSION cat "$<" | sed -e 's/@VERSION@/$(VERSION)/g' > "$@" PyTables-3.7.0/src/idx-opt.c000066400000000000000000000207021416254111300155620ustar00rootroot00000000000000#include /* See https://numpy.org/doc/1.17/reference/c-api.array.html#c.NO_IMPORT_ARRAY */ #define NO_IMPORT_ARRAY #include "idx-opt.h" /*------------------------------------------------------------------------- * * Binary search functions * *------------------------------------------------------------------------- */ /*------------------------------------------------------------------------- * Function: bisect_{left,right}_optim_* * * Purpose: Look-up for a value in sorted arrays * * Return: The index of the value in array * * Programmer: Francesc Alted * * Date: August, 2005 * * Comments: * * Modifications: * *------------------------------------------------------------------------- */ /* Optimised version for left/int8 */ int bisect_left_b(npy_int8 *a, long x, int hi, int offset) { int lo = 0; int mid; if (x <= a[offset]) return 0; if (a[hi-1+offset] < x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (a[mid+offset] < x) lo = mid+1; else hi = mid; } return lo; } /* Optimised version for left/uint8 */ int bisect_left_ub(npy_uint8 *a, long x, int hi, int offset) { int lo = 0; int mid; if (x <= a[offset]) return 0; if (a[hi-1+offset] < x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (a[mid+offset] < x) lo = mid+1; else hi = mid; } return lo; } /* Optimised version for right/int8 */ int bisect_right_b(npy_int8 *a, long x, int hi, int offset) { int lo = 0; int mid; if (x < a[offset]) return 0; if (a[hi-1+offset] <= x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (x < a[mid+offset]) hi = mid; else lo = mid+1; } return lo; } /* Optimised version for right/uint8 */ int bisect_right_ub(npy_uint8 *a, long x, int hi, int offset) { int lo = 0; int mid; if (x < a[offset]) return 0; if (a[hi-1+offset] <= x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (x < a[mid+offset]) hi = mid; else lo = mid+1; } return lo; } /* Optimised version for left/int16 */ int bisect_left_s(npy_int16 *a, long x, int hi, int offset) { int lo = 0; int mid; if (x <= a[offset]) return 0; if (a[hi-1+offset] < x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (a[mid+offset] < x) lo = mid+1; else hi = mid; } return lo; } /* Optimised version for left/uint16 */ int bisect_left_us(npy_uint16 *a, long x, int hi, int offset) { int lo = 0; int mid; if (x <= a[offset]) return 0; if (a[hi-1+offset] < x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (a[mid+offset] < x) lo = mid+1; else hi = mid; } return lo; } /* Optimised version for right/int16 */ int bisect_right_s(npy_int16 *a, long x, int hi, int offset) { int lo = 0; int mid; if (x < a[offset]) return 0; if (a[hi-1+offset] <= x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (x < a[mid+offset]) hi = mid; else lo = mid+1; } return lo; } /* Optimised version for right/uint16 */ int bisect_right_us(npy_uint16 *a, long x, int hi, int offset) { int lo = 0; int mid; if (x < a[offset]) return 0; if (a[hi-1+offset] <= x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (x < a[mid+offset]) hi = mid; else lo = mid+1; } return lo; } /* Optimised version for left/int32 */ int bisect_left_i(npy_int32 *a, long x, int hi, int offset) { int lo = 0; int mid; if (x <= a[offset]) return 0; if (a[hi-1+offset] < x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (a[mid+offset] < x) lo = mid+1; else hi = mid; } return lo; } /* Optimised version for left/uint32 */ int bisect_left_ui(npy_uint32 *a, npy_uint32 x, int hi, int offset) { int lo = 0; int mid; if (x <= a[offset]) return 0; if (a[hi-1+offset] < x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (a[mid+offset] < x) lo = mid+1; else hi = mid; } return lo; } /* Optimised version for right/int32 */ int bisect_right_i(npy_int32 *a, long x, int hi, int offset) { int lo = 0; int mid; if (x < a[offset]) return 0; if (a[hi-1+offset] <= x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (x < a[mid+offset]) hi = mid; else lo = mid+1; } return lo; } /* Optimised version for right/uint32 */ int bisect_right_ui(npy_uint32 *a, npy_uint32 x, int hi, int offset) { int lo = 0; int mid; if (x < a[offset]) return 0; if (a[hi-1+offset] <= x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (x < a[mid+offset]) hi = mid; else lo = mid+1; } return lo; } /* Optimised version for left/int64 */ int bisect_left_ll(npy_int64 *a, npy_int64 x, int hi, int offset) { int lo = 0; int mid; if (x <= a[offset]) return 0; if (a[hi-1+offset] < x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (a[mid+offset] < x) lo = mid+1; else hi = mid; } return lo; } /* Optimised version for left/uint64 */ int bisect_left_ull(npy_uint64 *a, npy_uint64 x, int hi, int offset) { int lo = 0; int mid; if (x <= a[offset]) return 0; if (a[hi-1+offset] < x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (a[mid+offset] < x) lo = mid+1; else hi = mid; } return lo; } /* Optimised version for right/int64 */ int bisect_right_ll(npy_int64 *a, npy_int64 x, int hi, int offset) { int lo = 0; int mid; if (x < a[offset]) return 0; if (a[hi-1+offset] <= x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (x < a[mid+offset]) hi = mid; else lo = mid+1; } return lo; } /* Optimised version for right/uint64 */ int bisect_right_ull(npy_uint64 *a, npy_uint64 x, int hi, int offset) { int lo = 0; int mid; if (x < a[offset]) return 0; if (a[hi-1+offset] <= x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (x < a[mid+offset]) hi = mid; else lo = mid+1; } return lo; } /* Optimised version for left/float16 */ int bisect_left_e(npy_float16 *a, npy_float64 x, int hi, int offset) { int lo = 0; int mid; if (x <= a[offset]) return 0; if (a[hi-1+offset] < x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (a[mid+offset] < x) lo = mid+1; else hi = mid; } return lo; } /* Optimised version for right/float16 */ int bisect_right_e(npy_float16 *a, npy_float64 x, int hi, int offset) { int lo = 0; int mid; if (x < a[offset]) return 0; if (a[hi-1+offset] <= x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (x < a[mid+offset]) hi = mid; else lo = mid+1; } return lo; } /* Optimised version for left/float32 */ int bisect_left_f(npy_float32 *a, npy_float64 x, int hi, int offset) { int lo = 0; int mid; if (x <= a[offset]) return 0; if (a[hi-1+offset] < x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (a[mid+offset] < x) lo = mid+1; else hi = mid; } return lo; } /* Optimised version for right/float32 */ int bisect_right_f(npy_float32 *a, npy_float64 x, int hi, int offset) { int lo = 0; int mid; if (x < a[offset]) return 0; if (a[hi-1+offset] <= x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (x < a[mid+offset]) hi = mid; else lo = mid+1; } return lo; } /* Optimised version for left/float64 */ int bisect_left_d(npy_float64 *a, npy_float64 x, int hi, int offset) { int lo = 0; int mid; if (x <= a[offset]) return 0; if (a[hi-1+offset] < x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (a[mid+offset] < x) lo = mid+1; else hi = mid; } return lo; } /* Optimised version for right/float64 */ int bisect_right_d(npy_float64 *a, npy_float64 x, int hi, int offset) { int lo = 0; int mid; if (x < a[offset]) return 0; if (a[hi-1+offset] <= x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (x < a[mid+offset]) hi = mid; else lo = mid+1; } return lo; } /* Optimised version for left/longdouble */ int bisect_left_g(npy_longdouble *a, npy_longdouble x, int hi, int offset) { int lo = 0; int mid; if (x <= a[offset]) return 0; if (a[hi-1+offset] < x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (a[mid+offset] < x) lo = mid+1; else hi = mid; } return lo; } /* Optimised version for right/longdouble */ int bisect_right_g(npy_longdouble *a, npy_longdouble x, int hi, int offset) { int lo = 0; int mid; if (x < a[offset]) return 0; if (a[hi-1+offset] <= x) return hi; while (lo < hi) { mid = lo + (hi-lo)/2; if (x < a[mid+offset]) hi = mid; else lo = mid+1; } return lo; } PyTables-3.7.0/src/idx-opt.h000066400000000000000000000034751416254111300155770ustar00rootroot00000000000000#include #include #ifndef NPY_FLOAT16 typedef npy_uint16 npy_float16; #endif #ifndef NPY_FLOAT96 typedef long double npy_float96; #endif #ifndef NPY_FLOAT128 typedef long double npy_float128; #endif int bisect_left_b(npy_int8 *a, long x, int hi, int offset); int bisect_left_ub(npy_uint8 *a, long x, int hi, int offset); int bisect_right_b(npy_int8 *a, long x, int hi, int offset); int bisect_right_ub(npy_uint8 *a, long x, int hi, int offset); int bisect_left_s(npy_int16 *a, long x, int hi, int offset); int bisect_left_us(npy_uint16 *a, long x, int hi, int offset); int bisect_right_s(npy_int16 *a, long x, int hi, int offset); int bisect_right_us(npy_uint16 *a, long x, int hi, int offset); int bisect_left_i(npy_int32 *a, long x, int hi, int offset); int bisect_left_ui(npy_uint32 *a, npy_uint32 x, int hi, int offset); int bisect_right_i(npy_int32 *a, long x, int hi, int offset); int bisect_right_ui(npy_uint32 *a, npy_uint32 x, int hi, int offset); int bisect_left_ll(npy_int64 *a, npy_int64 x, int hi, int offset); int bisect_left_ull(npy_uint64 *a, npy_uint64 x, int hi, int offset); int bisect_right_ll(npy_int64 *a, npy_int64 x, int hi, int offset); int bisect_right_ull(npy_uint64 *a, npy_uint64 x, int hi, int offset); int bisect_left_e(npy_float16 *a, npy_float64 x, int hi, int offset); int bisect_right_e(npy_float16 *a, npy_float64 x, int hi, int offset); int bisect_left_f(npy_float32 *a, npy_float64 x, int hi, int offset); int bisect_right_f(npy_float32 *a, npy_float64 x, int hi, int offset); int bisect_left_d(npy_float64 *a, npy_float64 x, int hi, int offset); int bisect_right_d(npy_float64 *a, npy_float64 x, int hi, int offset); int bisect_left_g(npy_longdouble *a, npy_longdouble x, int hi, int offset); int bisect_right_g(npy_longdouble *a, npy_longdouble x, int hi, int offset); PyTables-3.7.0/src/tables.h000066400000000000000000000001151416254111300154510ustar00rootroot00000000000000typedef enum { Table, Array, EArray, VLArray, CArray } TablesType; PyTables-3.7.0/src/typeconv.c000066400000000000000000000051541416254111300160510ustar00rootroot00000000000000/*********************************************************************** * * License: BSD * Created: December 21, 2004 * Author: Ivan Vilata i Balaguer - reverse:net.selidor@ivan * Modified: * Function inlining and some castings for 64-bit adressing * Francesc Alted 2004-12-27 * * $Source: /cvsroot/pytables/pytables/src/typeconv.c,v $ * $Id$ * ***********************************************************************/ /* Type conversion functions for PyTables types which are stored * with a different representation between numpy and HDF5. */ #include "typeconv.h" #include #include #if (!defined _ISOC99_SOURCE && !defined __USE_ISOC99) long int lround(double x) { double trunx; if (x > 0.0) { trunx = floor(x); if (x - trunx >= 0.5) trunx += 1; } else { trunx = ceil(x); if (trunx - x >= 0.5) trunx -= 1; } return (long int)(trunx); } #endif /* !_ISOC99_SOURCE && !__USE_ISOC99 */ void conv_float64_timeval32(void *base, unsigned long byteoffset, unsigned long bytestride, PY_LONG_LONG nrecords, unsigned long nelements, int sense) { PY_LONG_LONG record; unsigned long element, gapsize; double *fieldbase; union { PY_LONG_LONG i64; double f64; } tv; assert(bytestride > 0); assert(nelements > 0); /* Byte distance from end of field to beginning of next field. */ gapsize = bytestride - nelements * sizeof(double); fieldbase = (double *)((unsigned char *)(base) + byteoffset); for (record = 0; record < nrecords; record++) { for (element = 0; element < nelements; element++) { /* Perform an explicit copy of data to avoid errors related to unaligned memory access on platforms like AMR, etc. Patch submitted by Julian Taylor */ double fb; memcpy(&fb, fieldbase, sizeof(*fieldbase)); if (sense == 0) { /* Convert from float64 to timeval32. */ tv.i64 = (((PY_LONG_LONG)(fb) << 32) | (lround((fb - (int)(fb)) * 1e+6) & 0x0ffffffff)); fb = tv.f64; } else { /* Convert from timeval32 to float64. */ tv.f64 = fb; /* the next computation is 64 bit-platforms aware */ fb = 1e-6 * (int)tv.i64 + (tv.i64 >> 32); } memcpy(fieldbase, &fb, sizeof(*fieldbase)); fieldbase++; } fieldbase = (double *)((unsigned char *)(fieldbase) + gapsize); } assert(fieldbase == (base + byteoffset + bytestride * nrecords)); } PyTables-3.7.0/src/typeconv.h000066400000000000000000000024041416254111300160510ustar00rootroot00000000000000/*********************************************************************** * * License: BSD * Created: December 21, 2004 * Author: Ivan Vilata i Balaguer - reverse:net.selidor@ivan * * $Source: /home/ivan/_/programari/pytables/svn/cvs/pytables/pytables/src/typeconv.h,v $ * $Id$ * ***********************************************************************/ /* Type conversion functions for PyTables types which are stored * with a different representation between numpy and HDF5. */ #ifndef __TYPECONV_H__ #define __TYPECONV_H__ 1 #include /* Meaning for common arguments: * * base: pointer to data * * byteoffset: offset of first field/element into the data * * bytestride: distance in bytes from a field/record to the next one * * nrecords: number of fields/records to translate * * nelements: number of elements in a field/record * * sense: 0 for numpy -> HDF5, otherwise HDF5 -> numpy */ void conv_float64_timeval32(void *base, unsigned long byteoffset, unsigned long bytestride, PY_LONG_LONG nrecords, unsigned long nelements, int sense); #endif /* def __TYPECONV_H__ */ PyTables-3.7.0/src/utils.c000066400000000000000000000657441416254111300153550ustar00rootroot00000000000000#include #include "utils.h" #include "H5Zlzo.h" /* Import FILTER_LZO */ #include "H5Zbzip2.h" /* Import FILTER_BZIP2 */ #define PyString_FromString PyUnicode_FromString /* See https://numpy.org/doc/1.17/reference/c-api.array.html#c.NO_IMPORT_ARRAY */ #define NO_IMPORT_ARRAY #include #ifndef NPY_COMPLEX192 typedef npy_cdouble npy_complex192; #endif #ifndef NPY_COMPLEX256 typedef npy_cdouble npy_complex256; #endif /* ---------------------------------------------------------------- */ #ifdef WIN32 #include /* This routine is meant to detect whether a dynamic library can be loaded on Windows. This is only way to detect its presence without harming the user. */ int getLibrary(char *libname) { HINSTANCE hinstLib; /* Load the dynamic library */ hinstLib = LoadLibrary(TEXT(libname)); if (hinstLib != NULL) { /* Free the dynamic library */ FreeLibrary(hinstLib); return 0; } else { return -1; } } #else /* Unix platforms */ #include /* Routine to detect the existance of shared libraries in UNIX. This has to be checked in MacOSX. However, this is not used right now in utilsExtension.pyx because UNIX does not complain when trying to load an extension library that depends on a shared library that it is not in the system (python raises just the ImportError). */ int getLibrary(char *libname) { void *hinstLib; /* Load the dynamic library */ hinstLib = dlopen(libname, RTLD_LAZY); if (hinstLib != NULL) { /* Free the dynamic library */ dlclose(hinstLib); return 0; } else { return -1; } } #endif /* Win32 */ herr_t set_cache_size(hid_t file_id, size_t cache_size) { #if H5_VERS_MAJOR == 1 && H5_VERS_MINOR >= 7 /* MSVS2005 chokes on declarations after statements */ H5AC_cache_config_t config; #endif /* if H5_VERSION < "1.7" */ herr_t code; code = 0; #if H5_VERS_MAJOR == 1 && H5_VERS_MINOR >= 7 config.version = H5AC__CURR_CACHE_CONFIG_VERSION; code = H5Fget_mdc_config(file_id, &config); config.set_initial_size = TRUE; config.initial_size = cache_size; /* config.incr_mode = H5C_incr__off; */ /* config.decr_mode = H5C_decr__off; */ /* printf("Setting cache size to: %d\n", cache_size); */ code = H5Fset_mdc_config(file_id, &config); /* printf("Return code for H5Fset_mdc_config: %d\n", code); */ #endif /* if H5_VERSION < "1.7" */ return code; } PyObject *getHDF5VersionInfo(void) { long binver; unsigned majnum, minnum, relnum; char strver[16]; PyObject *t; /* H5get_libversion(&majnum, &minnum, &relnum); */ majnum = H5_VERS_MAJOR; minnum = H5_VERS_MINOR; relnum = H5_VERS_RELEASE; /* Get a binary number */ binver = majnum << 16 | minnum << 8 | relnum; /* A string number */ if (strcmp(H5_VERS_SUBRELEASE, "")) { snprintf(strver, 16, "%d.%d.%d-%s", majnum, minnum, relnum, H5_VERS_SUBRELEASE); } else { snprintf(strver, 16, "%d.%d.%d", majnum, minnum, relnum); } t = PyTuple_New(2); PyTuple_SetItem(t, 0, PyLong_FromLong(binver)); PyTuple_SetItem(t, 1, PyString_FromString(strver)); return t; } /**************************************************************** ** ** createNamesTuple(): Create Python tuple from a string of *char. ** ****************************************************************/ PyObject *createNamesTuple(char *buffer[], int nelements) { int i; PyObject *t; PyObject *str; t = PyTuple_New(nelements); for (i = 0; i < nelements; i++) { str = PyString_FromString(buffer[i]); PyTuple_SetItem(t, i, str); /* PyTuple_SetItem does not need a decref, because it already do this */ /* Py_DECREF(str); */ } return t; } PyObject *createNamesList(char *buffer[], int nelements) { int i; PyObject *t; PyObject *str; t = PyList_New(nelements); for (i = 0; i < nelements; i++) { str = PyString_FromString(buffer[i]); PyList_SetItem(t, i, str); /* PyList_SetItem does not need a decref, because it already do this */ /* Py_DECREF(str); */ } return t; } /*------------------------------------------------------------------------- * Function: get_filter_names * * Purpose: Get the filter names for the chunks in a dataset * * Return: Success: 0, Failure: -1 * * Programmer: Francesc Alted, faltet@pytables.com * * Date: December 19, 2003 * * Comments: * * Modifications: * * *------------------------------------------------------------------------- */ PyObject *get_filter_names( hid_t loc_id, const char *dset_name) { hid_t dset; hid_t dcpl; /* dataset creation property list */ /* hsize_t chsize[64]; /\* chunk size in elements *\/ */ int i, j; int nf; /* number of filters */ unsigned filt_flags; /* filter flags */ size_t cd_nelmts; /* filter client number of values */ unsigned cd_values[20]; /* filter client data values */ char f_name[256]; /* filter name */ PyObject *filters; PyObject *filter_values; /* Open the dataset. */ if ( (dset = H5Dopen( loc_id, dset_name, H5P_DEFAULT )) < 0 ) { goto out; } /* Get the properties container */ dcpl = H5Dget_create_plist(dset); /* Collect information about filters on chunked storage */ if (H5D_CHUNKED==H5Pget_layout(dcpl)) { filters = PyDict_New(); if ((nf = H5Pget_nfilters(dcpl))>0) { for (i=0; itype) { case H5L_TYPE_SOFT: case H5L_TYPE_EXTERNAL: PyList_Append(out_info[2], strname); break; case H5L_TYPE_ERROR: /* XXX: check */ PyList_Append(out_info[3], strname); break; case H5L_TYPE_HARD: /* Get type of the object and check it */ ret = H5Gget_objinfo(loc_id, name, FALSE, &oinfo); if (ret < 0) return -1; switch(oinfo.type) { case H5G_GROUP: PyList_Append(out_info[0], strname); break; case H5G_DATASET: PyList_Append(out_info[1], strname); break; case H5G_TYPE: ++namedtypes; break; case H5G_UNKNOWN: PyList_Append(out_info[3], strname); break; case H5G_LINK: /* should not happen */ PyList_Append(out_info[2], strname); break; default: /* should not happen: assume it is an external link */ PyList_Append(out_info[2], strname); } /* H5Oget_info_by_name seems to have performance issues (see gh-402) ret = H5Oget_info_by_name(loc_id, name, &oinfo, H5P_DEFAULT); if (ret < 0) return -1; switch(oinfo.type) { case H5O_TYPE_GROUP: PyList_Append(out_info[0], strname); break; case H5O_TYPE_DATASET: PyList_Append(out_info[1], strname); break; case H5O_TYPE_NAMED_DATATYPE: ++namedtypes; break; case H5O_TYPE_UNKNOWN: PyList_Append(out_info[3], strname); break; default: / * should not happen * / PyList_Append(out_info[3], strname); } */ break; default: /* should not happen */ PyList_Append(out_info[3], strname); } Py_DECREF(strname); return 0 ; /* Loop until no more objects remain in directory */ } /**************************************************************** ** ** Giterate(): Group iteration routine. ** ****************************************************************/ PyObject *Giterate(hid_t parent_id, hid_t loc_id, const char *name) { hsize_t i=0; PyObject *t, *tgroup, *tleave, *tlink, *tunknown; PyObject *info[4]; info[0] = tgroup = PyList_New(0); info[1] = tleave = PyList_New(0); info[2] = tlink = PyList_New(0); info[3] = tunknown = PyList_New(0); /* Iterate over all the childs behind loc_id (parent_id+loc_id). * NOTE: using H5_INDEX_CRT_ORDER instead of H5_INDEX_NAME causes failures * in the test suite */ H5Literate_by_name(parent_id, name, H5_INDEX_NAME, H5_ITER_NATIVE, &i, litercb, info, H5P_DEFAULT); /* Create the tuple with the list of Groups and Datasets */ t = PyTuple_New(4); PyTuple_SetItem(t, 0, tgroup); PyTuple_SetItem(t, 1, tleave); PyTuple_SetItem(t, 2, tlink); PyTuple_SetItem(t, 3, tunknown); return t; } /**************************************************************** ** ** aitercb(): Custom attribute iteration callback routine. ** ****************************************************************/ static herr_t aitercb( hid_t loc_id, const char *name, const H5A_info_t *ainfo, void *op_data) { PyObject *strname; strname = PyString_FromString(name); /* Return the name of the attribute on op_data */ PyList_Append(op_data, strname); Py_DECREF(strname); return(0); /* Loop until no more attrs remain in object */ } /**************************************************************** ** ** Aiterate(): Attribute set iteration routine. ** ****************************************************************/ PyObject *Aiterate(hid_t loc_id) { hsize_t i = 0; PyObject *attrlist; /* List where the attrnames are put */ attrlist = PyList_New(0); H5Aiterate(loc_id, H5_INDEX_CRT_ORDER, H5_ITER_NATIVE, &i, (H5A_operator_t)aitercb, (void *)attrlist); return attrlist; } /**************************************************************** ** ** getHDF5ClassID(): Returns class ID for loc_id.name. -1 if error. ** ****************************************************************/ H5T_class_t getHDF5ClassID(hid_t loc_id, const char *name, H5D_layout_t *layout, hid_t *type_id, hid_t *dataset_id) { H5T_class_t class_id; hid_t plist; /* Open the dataset. */ if ( (*dataset_id = H5Dopen( loc_id, name, H5P_DEFAULT )) < 0 ) return -1; /* Get an identifier for the datatype. */ *type_id = H5Dget_type( *dataset_id ); /* Get the class. */ class_id = H5Tget_class( *type_id ); /* Get the layout of the datatype */ plist = H5Dget_create_plist(*dataset_id); *layout = H5Pget_layout(plist); H5Pclose(plist); return class_id; } /* Helper routine that returns the rank, dims and byteorder for UnImplemented objects. 2004 */ PyObject *H5UIget_info( hid_t loc_id, const char *dset_name, char *byteorder) { hid_t dataset_id; int rank; hsize_t *dims; hid_t space_id; H5T_class_t class_id; H5T_order_t order; hid_t type_id; PyObject *t; int i; /* Open the dataset. */ if ( (dataset_id = H5Dopen( loc_id, dset_name, H5P_DEFAULT )) < 0 ) { Py_INCREF(Py_None); return Py_None; /* Not chunked, so return None */ } /* Get an identifier for the datatype. */ type_id = H5Dget_type( dataset_id ); /* Get the class. */ class_id = H5Tget_class( type_id ); /* Get the dataspace handle */ if ( (space_id = H5Dget_space( dataset_id )) < 0 ) goto out; /* Get rank */ if ( (rank = H5Sget_simple_extent_ndims( space_id )) < 0 ) goto out; /* Book resources for dims */ dims = (hsize_t *)malloc(rank * sizeof(hsize_t)); /* Get dimensions */ if ( H5Sget_simple_extent_dims( space_id, dims, NULL) < 0 ) goto out; /* Assign the dimensions to a tuple */ t = PyTuple_New(rank); for(i=0;i April 2004. Adapted to support Tables by F. Alted September 2004. */ /* Test whether the datatype is of class complex return 1 if it corresponds to our complex class, otherwise 0 */ /* This may be ultimately confused with nested types with 2 components called 'r' and 'i' and being floats, but in that case, the user most probably wanted to keep a complex type, so getting a complex instead of a nested type should not be a big issue (I hope!) :-/ F. Alted 2005-05-23 */ int is_complex(hid_t type_id) { hid_t class_id, base_type_id; hid_t class1, class2; char *colname1, *colname2; int result = 0; hsize_t nfields; class_id = H5Tget_class(type_id); if (class_id == H5T_COMPOUND) { nfields = H5Tget_nmembers(type_id); if (nfields == 2) { colname1 = H5Tget_member_name(type_id, 0); colname2 = H5Tget_member_name(type_id, 1); if ((strcmp(colname1, "r") == 0) && (strcmp(colname2, "i") == 0)) { class1 = H5Tget_member_class(type_id, 0); class2 = H5Tget_member_class(type_id, 1); if (class1 == H5T_FLOAT && class2 == H5T_FLOAT) result = 1; } pt_H5free_memory(colname1); pt_H5free_memory(colname2); } } /* Is an Array of Complex? */ else if (class_id == H5T_ARRAY) { /* Get the array base component */ base_type_id = H5Tget_super(type_id); /* Call is_complex again */ result = is_complex(base_type_id); H5Tclose(base_type_id); } return result; } /* Return the byteorder of a complex datatype. It is obtained from the real part, which is the first member. */ static H5T_order_t get_complex_order(hid_t type_id) { hid_t class_id, base_type_id; hid_t real_type = 0; H5T_order_t result = 0; class_id = H5Tget_class(type_id); if (class_id == H5T_COMPOUND) { real_type = H5Tget_member_type(type_id, 0); } else if (class_id == H5T_ARRAY) { /* Get the array base component */ base_type_id = H5Tget_super(type_id); /* Get the type of real component. */ real_type = H5Tget_member_type(base_type_id, 0); H5Tclose(base_type_id); } if ((class_id == H5T_COMPOUND) || (class_id == H5T_ARRAY)) { result = H5Tget_order(real_type); H5Tclose(real_type); } return result; } /* Return the byteorder of a HDF5 data type */ /* This is actually an extension of H5Tget_order to handle complex types */ herr_t get_order(hid_t type_id, char *byteorder) { H5T_order_t h5byteorder; /* hid_t class_id; class_id = H5Tget_class(type_id); */ if (is_complex(type_id)) { h5byteorder = get_complex_order(type_id); } else { h5byteorder = H5Tget_order(type_id); } if (h5byteorder == H5T_ORDER_LE) { strcpy(byteorder, "little"); return h5byteorder; } else if (h5byteorder == H5T_ORDER_BE ) { strcpy(byteorder, "big"); return h5byteorder; } else if (h5byteorder == H5T_ORDER_NONE ) { strcpy(byteorder, "irrelevant"); return h5byteorder; } else { /* This should never happen! */ fprintf(stderr, "Error: unsupported byteorder <%d>\n", h5byteorder); strcpy(byteorder, "unsupported"); return -1; } } /* Set the byteorder of type_id. */ /* This only works for datatypes that are not Complex. However, these types should already been created with correct byteorder */ herr_t set_order(hid_t type_id, const char *byteorder) { herr_t status=0; if (! is_complex(type_id)) { if (strcmp(byteorder, "little") == 0) status = H5Tset_order(type_id, H5T_ORDER_LE); else if (strcmp(byteorder, "big") == 0) status = H5Tset_order(type_id, H5T_ORDER_BE); else if (strcmp(byteorder, "irrelevant") == 0) { /* Do nothing because 'irrelevant' doesn't require setting the byteorder explicitely */ /* status = H5Tset_order(type_id, H5T_ORDER_NONE ); */ } else { fprintf(stderr, "Error: unsupported byteorder <%s>\n", byteorder); status = -1; } } return status; } /* Create a HDF5 atomic datatype that represents half precision floatting point numbers defined by numpy as float16. */ hid_t create_ieee_float16(const char *byteorder) { hid_t float_id; if (byteorder == NULL) float_id = H5Tcopy(H5T_NATIVE_FLOAT); else if (strcmp(byteorder, "little") == 0) float_id = H5Tcopy(H5T_IEEE_F32LE); else float_id = H5Tcopy(H5T_IEEE_F32BE); if (float_id < 0) return float_id; if (H5Tset_fields(float_id, 15, 10, 5, 0, 10) < 0) return -1; if (H5Tset_size(float_id, 2) < 0) return -1; if (H5Tset_ebias(float_id, 15) < 0) return -1; return float_id; } /* Create a HDF5 atomic datatype that represents quad precision floatting point numbers. */ hid_t create_ieee_quadprecision_float(const char *byteorder) { hid_t float_id; if (byteorder == NULL) float_id = H5Tcopy(H5T_NATIVE_DOUBLE); else if (strcmp(byteorder, "little") == 0) float_id = H5Tcopy(H5T_IEEE_F64LE); else float_id = H5Tcopy(H5T_IEEE_F64BE); if (float_id < 0) return float_id; if (H5Tset_size(float_id, 16) < 0) return -1; if ((H5Tset_precision(float_id, 128)) < 0) return -1; if (H5Tset_fields(float_id , 127, 112, 15, 0, 112) < 0) return -1; if (H5Tset_ebias(float_id, 16383) < 0) return -1; return float_id; } /* Create a HDF5 compound datatype that represents complex numbers defined by numpy as complex64. */ hid_t create_ieee_complex64(const char *byteorder) { hid_t float_id, complex_id; complex_id = H5Tcreate(H5T_COMPOUND, sizeof(npy_complex64)); if (byteorder == NULL) float_id = H5Tcopy(H5T_NATIVE_FLOAT); else if (strcmp(byteorder, "little") == 0) float_id = H5Tcopy(H5T_IEEE_F32LE); else float_id = H5Tcopy(H5T_IEEE_F32BE); if (float_id < 0) { H5Tclose(complex_id); return float_id; } H5Tinsert(complex_id, "r", HOFFSET(npy_complex64, real), float_id); H5Tinsert(complex_id, "i", HOFFSET(npy_complex64, imag), float_id); H5Tclose(float_id); return complex_id; } /* Counterpart for complex128 */ hid_t create_ieee_complex128(const char *byteorder) { hid_t float_id, complex_id; complex_id = H5Tcreate(H5T_COMPOUND, sizeof(npy_complex128)); if (byteorder == NULL) float_id = H5Tcopy(H5T_NATIVE_DOUBLE); else if (strcmp(byteorder, "little") == 0) float_id = H5Tcopy(H5T_IEEE_F64LE); else float_id = H5Tcopy(H5T_IEEE_F64BE); if (float_id < 0) { H5Tclose(complex_id); return float_id; } H5Tinsert(complex_id, "r", HOFFSET(npy_complex128, real), float_id); H5Tinsert(complex_id, "i", HOFFSET(npy_complex128, imag), float_id); H5Tclose(float_id); return complex_id; } /* Counterpart for complex192 */ hid_t create_ieee_complex192(const char *byteorder) { herr_t err = 0; hid_t float_id, complex_id; H5T_order_t h5order = H5Tget_order(H5T_NATIVE_LDOUBLE); complex_id = H5Tcreate(H5T_COMPOUND, sizeof(npy_complex192)); float_id = H5Tcopy(H5T_NATIVE_LDOUBLE); if (float_id < 0) { H5Tclose(complex_id); return float_id; } if ((strcmp(byteorder, "little") == 0) && (h5order != H5T_ORDER_LE)) err = H5Tset_order(float_id, H5T_ORDER_LE); else if ((strcmp(byteorder, "big") == 0) && (h5order != H5T_ORDER_BE)) err = H5Tset_order(float_id, H5T_ORDER_BE); if (err < 0) { H5Tclose(complex_id); return err; } H5Tinsert(complex_id, "r", HOFFSET(npy_complex192, real), float_id); H5Tinsert(complex_id, "i", HOFFSET(npy_complex192, imag), float_id); H5Tclose(float_id); return complex_id; } /* Counterpart for complex256 */ hid_t create_ieee_complex256(const char *byteorder) { herr_t err = 0; hid_t float_id, complex_id; H5T_order_t h5order = H5Tget_order(H5T_NATIVE_LDOUBLE); complex_id = H5Tcreate(H5T_COMPOUND, sizeof(npy_complex256)); float_id = H5Tcopy(H5T_NATIVE_LDOUBLE); if (float_id < 0) { H5Tclose(complex_id); return float_id; } if ((strcmp(byteorder, "little") == 0) && (h5order != H5T_ORDER_LE)) err = H5Tset_order(float_id, H5T_ORDER_LE); else if ((strcmp(byteorder, "big") == 0) && (h5order != H5T_ORDER_BE)) err = H5Tset_order(float_id, H5T_ORDER_BE); if (err < 0) { H5Tclose(complex_id); return err; } H5Tinsert(complex_id, "r", HOFFSET(npy_complex256, real), float_id); H5Tinsert(complex_id, "i", HOFFSET(npy_complex256, imag), float_id); H5Tclose(float_id); return complex_id; } /* Return the number of significant bits in the real and imaginary parts */ /* This is actually an extension of H5Tget_precision to handle complex types */ size_t get_complex_precision(hid_t type_id) { hid_t real_type; size_t result; real_type = H5Tget_member_type(type_id, 0); result = H5Tget_precision(real_type); H5Tclose(real_type); return result; } /* End of complex additions */ /* The get_len_of_range has been taken from Python interpreter */ /* Return number of items in range/xrange (lo, hi, step). step > 0 * required. Return a value < 0 if & only if the true value is too * large to fit in a signed long. */ hsize_t get_len_of_range(hsize_t lo, hsize_t hi, hsize_t step) { /* ------------------------------------------------------------- If lo >= hi, the range is empty. Else if n values are in the range, the last one is lo + (n-1)*step, which must be <= hi-1. Rearranging, n <= (hi - lo - 1)/step + 1, so taking the floor of the RHS gives the proper value. Since lo < hi in this case, hi-lo-1 >= 0, so the RHS is non-negative and so truncation is the same as the floor. Letting M be the largest positive long, the worst case for the RHS numerator is hi=M, lo=-M-1, and then hi-lo-1 = M-(-M-1)-1 = 2*M. Therefore unsigned long has enough precision to compute the RHS exactly. Note: We are using here 64 bit ints because PyTables can deal with 64-bit addresses even on 32-bit platforms. F. Alted 2006-09-25 ---------------------------------------------------------------*/ hsize_t n = 0; if (lo < hi) { hsize_t diff = hi - lo - 1; n = (hsize_t)(diff / step + 1); } return n; } /* Truncate the dataset to at most size rows */ herr_t truncate_dset( hid_t dataset_id, const int maindim, const hsize_t size) { hid_t space_id; hsize_t *dims = NULL; int rank; /* Get the dataspace handle */ if ( (space_id = H5Dget_space(dataset_id)) < 0 ) goto out; /* Get the rank */ if ( (rank = H5Sget_simple_extent_ndims(space_id)) < 0 ) goto out; if (rank) { /* multidimensional case */ /* Book some memory for the selections */ dims = (hsize_t *)malloc(rank*sizeof(hsize_t)); /* Get dataset dimensionality */ if ( H5Sget_simple_extent_dims(space_id, dims, NULL) < 0 ) goto out; /* Truncate the EArray */ dims[maindim] = size; if ( H5Dset_extent(dataset_id, dims) < 0 ) goto out; /* Release resources */ free(dims); } else { /* scalar case (should never enter here) */ printf("A scalar Array cannot be truncated!.\n"); goto out; } /* Free resources */ if ( H5Sclose(space_id) < 0 ) return -1; return 0; out: if (dims) free(dims); return -1; } /* * Helpers for management of HDF5 drivers */ /* DIRECT driver */ #ifndef H5_HAVE_DIRECT herr_t pt_H5Pset_fapl_direct(hid_t fapl_id, size_t alignment, size_t block_size, size_t cbuf_size) { return -1; } #endif /* H5_HAVE_DIRECT */ /* WINDOWS driver */ #ifndef H5_HAVE_WINDOWS herr_t pt_H5Pset_fapl_windows(hid_t fapl_id) { return -1; } #endif /* H5_HAVE_WINDOWS */ #if (H5_HAVE_IMAGE_FILE != 1) /* HDF5 version < 1.8.9 */ herr_t pt_H5Pset_file_image(hid_t fapl_id, void *buf_ptr, size_t buf_len) { return -1; } ssize_t pt_H5Fget_file_image(hid_t file_id, void *buf_ptr, size_t buf_len) { return -1; } #endif /* (H5_HAVE_IMAGE_FILE != 1) */ #if H5_VERSION_LE(1,8,12) herr_t pt_H5free_memory(void *buf) { free(buf); return 0; } #endif PyTables-3.7.0/src/utils.h000066400000000000000000000113611416254111300153440ustar00rootroot00000000000000#include #include "hdf5.h" /* Define this variable for error printings */ /*#define DEBUG 1 */ /* Define this variable for debugging printings */ /*#define PRINT 1 */ /* Define this for compile the main() function */ /* #define MAIN 1 */ /* * Status return values for the herr_t' type. * Since some unix/c routines use 0 and -1 (or more precisely, non-negative * vs. negative) as their return code, and some assumption had been made in * the code about that, it is important to keep these constants the same * values. When checking the success or failure of an integer-valued * function, remember to compare against zero and not one of these two * values. */ #define SUCCEED 0 #define FAIL (-1) #define UFAIL (unsigned)(-1) /* * HDF Boolean type. */ #ifndef FALSE # define FALSE 0 #endif #ifndef TRUE # define TRUE (!FALSE) #endif #ifdef H5_HAVE_WINDOWS #define H5_HAVE_WINDOWS_DRIVER 1 #else #define H5_HAVE_WINDOWS_DRIVER 0 #endif #ifdef H5_HAVE_DIRECT #define H5_HAVE_DIRECT_DRIVER 1 #else #define H5_HAVE_DIRECT_DRIVER 0 #endif #if (H5_VERS_MAJOR == 1 && H5_VERS_MINOR == 8 && H5_VERS_RELEASE >= 9) || (H5_VERS_MAJOR == 1 && H5_VERS_MINOR > 8) /* HDF5 version >= 1.8.9 */ #define H5_HAVE_IMAGE_FILE 1 #else /* HDF5 version < 1.8.9 */ #define H5_HAVE_IMAGE_FILE 0 #endif /* COMAPTIBILITY: H5_VERSION_LE has been introduced in HDF5 1.8.7 */ #ifndef H5_VERSION_LE #define H5_VERSION_LE(Maj,Min,Rel) \ (((H5_VERS_MAJOR==Maj) && (H5_VERS_MINOR==Min) && (H5_VERS_RELEASE<=Rel)) || \ ((H5_VERS_MAJOR==Maj) && (H5_VERS_MINOR= 1.8.9 */ herr_t pt_H5Pset_file_image(hid_t fapl_id, void *buf_ptr, size_t buf_len); ssize_t pt_H5Fget_file_image(hid_t file_id, void *buf_ptr, size_t buf_len); #else /* (H5_HAVE_IMAGE_FILE != 1) */ /* HDF5 version < 1.8.9 */ #define pt_H5Pset_file_image H5Pset_file_image #define pt_H5Fget_file_image H5Fget_file_image #endif /* (H5_HAVE_IMAGE_FILE != 1) */ #if H5_VERSION_LE(1,8,12) herr_t pt_H5free_memory(void *buf); #else #define pt_H5free_memory H5free_memory #endif PyTables-3.7.0/tables/000077500000000000000000000000001416254111300145145ustar00rootroot00000000000000PyTables-3.7.0/tables/__init__.py000066400000000000000000000113341416254111300166270ustar00rootroot00000000000000"""PyTables, hierarchical datasets in Python. :URL: http://www.pytables.org/ PyTables is a package for managing hierarchical datasets and designed to efficiently cope with extremely large amounts of data. """ # Necessary imports to get versions stored on the cython extension from .utilsextension import get_hdf5_version as _get_hdf5_version __version__ = "3.7.0" """The PyTables version number.""" hdf5_version = _get_hdf5_version() """The underlying HDF5 library version number. .. versionadded:: 3.0 """ from .utilsextension import ( blosc_compcode_to_compname_ as blosc_compcode_to_compname, blosc_get_complib_info_ as blosc_get_complib_info, ) from .utilsextension import ( blosc_compressor_list, is_hdf5_file, is_pytables_file, which_lib_version, set_blosc_max_threads, silence_hdf5_messages, ) from .misc.enum import Enum from .atom import * from .flavor import restrict_flavors from .description import * from .filters import Filters # Import the user classes from the proper modules from .exceptions import * from .file import File, open_file, copy_file from .node import Node from .group import Group from .leaf import Leaf from .table import Table, Cols, Column from .array import Array from .carray import CArray from .earray import EArray from .vlarray import VLArray from .unimplemented import UnImplemented, Unknown from .expression import Expr from .tests import print_versions, test # List here only the objects we want to be publicly available __all__ = [ # Exceptions and warnings: 'HDF5ExtError', 'ClosedNodeError', 'ClosedFileError', 'FileModeError', 'NaturalNameWarning', 'NodeError', 'NoSuchNodeError', 'UndoRedoError', 'UndoRedoWarning', 'PerformanceWarning', 'FlavorError', 'FlavorWarning', 'FiltersWarning', 'DataTypeWarning', # Functions: 'is_hdf5_file', 'is_pytables_file', 'which_lib_version', 'copy_file', 'open_file', 'print_versions', 'test', 'split_type', 'restrict_flavors', 'set_blosc_max_threads', 'silence_hdf5_messages', # Helper classes: 'IsDescription', 'Description', 'Filters', 'Cols', 'Column', # Types: 'Enum', # Atom types: 'Atom', 'StringAtom', 'BoolAtom', 'IntAtom', 'UIntAtom', 'Int8Atom', 'UInt8Atom', 'Int16Atom', 'UInt16Atom', 'Int32Atom', 'UInt32Atom', 'Int64Atom', 'UInt64Atom', 'FloatAtom', 'Float32Atom', 'Float64Atom', 'ComplexAtom', 'Complex32Atom', 'Complex64Atom', 'Complex128Atom', 'TimeAtom', 'Time32Atom', 'Time64Atom', 'EnumAtom', 'PseudoAtom', 'ObjectAtom', 'VLStringAtom', 'VLUnicodeAtom', # Column types: 'Col', 'StringCol', 'BoolCol', 'IntCol', 'UIntCol', 'Int8Col', 'UInt8Col', 'Int16Col', 'UInt16Col', 'Int32Col', 'UInt32Col', 'Int64Col', 'UInt64Col', 'FloatCol', 'Float32Col', 'Float64Col', 'ComplexCol', 'Complex32Col', 'Complex64Col', 'Complex128Col', 'TimeCol', 'Time32Col', 'Time64Col', 'EnumCol', # Node classes: 'Node', 'Group', 'Leaf', 'Table', 'Array', 'CArray', 'EArray', 'VLArray', 'UnImplemented', 'Unknown', # The File class: 'File', # Expr class 'Expr', ] if 'Float16Atom' in locals(): # float16 is new in numpy 1.6.0 __all__.extend(('Float16Atom', 'Float16Col')) from .utilsextension import _broken_hdf5_long_double if not _broken_hdf5_long_double(): if 'Float96Atom' in locals(): __all__.extend(('Float96Atom', 'Float96Col')) __all__.extend(('Complex192Atom', 'Complex192Col')) # XXX check if 'Float128Atom' in locals(): __all__.extend(('Float128Atom', 'Float128Col')) __all__.extend(('Complex256Atom', 'Complex256Col')) # XXX check else: from . import atom as _atom from . import description as _description try: del _atom.Float96Atom, _atom.Complex192Col del _description.Float96Col, _description.Complex192Col _atom.all_types.discard('complex192') _atom.ComplexAtom._isizes.remove(24) except AttributeError: try: del _atom.Float128Atom, _atom.Complex256Atom del _description.Float128Col, _description.Complex256Col _atom.all_types.discard('complex256') _atom.ComplexAtom._isizes.remove(32) except AttributeError: pass del _atom, _description del _broken_hdf5_long_double def get_pytables_version(): warnings.warn( "the 'get_pytables_version()' function is deprecated and could be " "removed in future versions. Please use 'tables.__version__'", DeprecationWarning) return __version__ def get_hdf5_version(): warnings.warn( "the 'get_hdf5_version()' function is deprecated and could be " "removed in future versions. Please use 'tables.hdf5_version'", DeprecationWarning) return hdf5_versionPyTables-3.7.0/tables/_comp_bzip2.pyx000066400000000000000000000005671416254111300174710ustar00rootroot00000000000000import sys from libc.stdlib cimport free cdef extern from "H5Zbzip2.h": int register_bzip2(char **, char **) def register_(): cdef char *version cdef char *date if not register_bzip2(&version, &date): return None compinfo = (version, date) free(version) free(date) return compinfo[0].decode('ascii'), compinfo[1].decode('ascii') PyTables-3.7.0/tables/_comp_lzo.pyx000066400000000000000000000005611416254111300172410ustar00rootroot00000000000000import sys from libc.stdlib cimport free cdef extern from "H5Zlzo.h": int register_lzo(char **, char **) def register_(): cdef char *version cdef char *date if not register_lzo(&version, &date): return None compinfo = (version, date) free(version) free(date) return compinfo[0].decode('ascii'), compinfo[1].decode('ascii') PyTables-3.7.0/tables/array.py000066400000000000000000001044451416254111300162140ustar00rootroot00000000000000"""Here is defined the Array class.""" import operator import sys import numpy as np from . import hdf5extension from .filters import Filters from .flavor import flavor_of, array_as_internal, internal_to_flavor from .leaf import Leaf from .utils import (is_idx, convert_to_np_atom2, SizeType, lazyattr, byteorders, quantize) # default version for ARRAY objects # obversion = "1.0" # initial version # obversion = "2.0" # Added an optional EXTDIM attribute # obversion = "2.1" # Added support for complex datatypes # obversion = "2.2" # This adds support for time datatypes. # obversion = "2.3" # This adds support for enumerated datatypes. obversion = "2.4" # Numeric and numarray flavors are gone. class Array(hdf5extension.Array, Leaf): """This class represents homogeneous datasets in an HDF5 file. This class provides methods to write or read data to or from array objects in the file. This class does not allow you neither to enlarge nor compress the datasets on disk; use the EArray class (see :ref:`EArrayClassDescr`) if you want enlargeable dataset support or compression features, or CArray (see :ref:`CArrayClassDescr`) if you just want compression. An interesting property of the Array class is that it remembers the *flavor* of the object that has been saved so that if you saved, for example, a list, you will get a list during readings afterwards; if you saved a NumPy array, you will get a NumPy object, and so forth. Note that this class inherits all the public attributes and methods that Leaf (see :ref:`LeafClassDescr`) already provides. However, as Array instances have no internal I/O buffers, it is not necessary to use the flush() method they inherit from Leaf in order to save their internal state to disk. When a writing method call returns, all the data is already on disk. Parameters ---------- parentnode The parent :class:`Group` object. .. versionchanged:: 3.0 Renamed from *parentNode* to *parentnode* name : str The name of this node in its parent group. obj The array or scalar to be saved. Accepted types are NumPy arrays and scalars as well as native Python sequences and scalars, provided that values are regular (i.e. they are not like ``[[1,2],2]``) and homogeneous (i.e. all the elements are of the same type). .. versionchanged:: 3.0 Renamed form *object* into *obj*. title A description for this node (it sets the ``TITLE`` HDF5 attribute on disk). byteorder The byteorder of the data *on disk*, specified as 'little' or 'big'. If this is not specified, the byteorder is that of the given `object`. track_times Whether time data associated with the leaf are recorded (object access time, raw data modification time, metadata change time, object birth time); default True. Semantics of these times depend on their implementation in the HDF5 library: refer to documentation of the H5O_info_t data structure. As of HDF5 1.8.15, only ctime (metadata change time) is implemented. .. versionadded:: 3.4.3 """ # Class identifier. _c_classid = 'ARRAY' @lazyattr def dtype(self): """The NumPy ``dtype`` that most closely matches this array.""" return self.atom.dtype @property def nrows(self): """The number of rows in the array.""" if self.shape == (): return SizeType(1) # scalar case else: return self.shape[self.maindim] @property def rowsize(self): """The size of the rows in bytes in dimensions orthogonal to *maindim*.""" maindim = self.maindim rowsize = self.atom.size for i, dim in enumerate(self.shape): if i != maindim: rowsize *= dim return rowsize @property def size_in_memory(self): """The size of this array's data in bytes when it is fully loaded into memory.""" return self.nrows * self.rowsize def __init__(self, parentnode, name, obj=None, title="", byteorder=None, _log=True, _atom=None, track_times=True): self._v_version = None """The object version of this array.""" self._v_new = new = obj is not None """Is this the first time the node has been created?""" self._v_new_title = title """New title for this node.""" self._obj = obj """The object to be stored in the array. It can be any of numpy, list, tuple, string, integer of floating point types, provided that they are regular (i.e. they are not like ``[[1, 2], 2]``). .. versionchanged:: 3.0 Renamed form *_object* into *_obj*. """ self._v_convert = True """Whether the ``Array`` object must be converted or not.""" # Miscellaneous iteration rubbish. self._start = None """Starting row for the current iteration.""" self._stop = None """Stopping row for the current iteration.""" self._step = None """Step size for the current iteration.""" self._nrowsread = None """Number of rows read up to the current state of iteration.""" self._startb = None """Starting row for current buffer.""" self._stopb = None """Stopping row for current buffer. """ self._row = None """Current row in iterators (sentinel).""" self._init = False """Whether we are in the middle of an iteration or not (sentinel).""" self.listarr = None """Current buffer in iterators.""" # Documented (*public*) attributes. self.atom = _atom """An Atom (see :ref:`AtomClassDescr`) instance representing the *type* and *shape* of the atomic objects to be saved. """ self.shape = None """The shape of the stored array.""" self.nrow = None """On iterators, this is the index of the current row.""" self.extdim = -1 # ordinary arrays are not enlargeable """The index of the enlargeable dimension.""" # Ordinary arrays have no filters: leaf is created with default ones. super().__init__(parentnode, name, new, Filters(), byteorder, _log, track_times) def _g_create(self): """Save a new array in file.""" self._v_version = obversion try: # `Leaf._g_post_init_hook()` should be setting the flavor on disk. self._flavor = flavor = flavor_of(self._obj) nparr = array_as_internal(self._obj, flavor) except Exception: # XXX # Problems converting data. Close the node and re-raise exception. self.close(flush=0) raise # Raise an error in case of unsupported object if nparr.dtype.kind in ['V', 'U', 'O']: # in void, unicode, object raise TypeError("Array objects cannot currently deal with void, " "unicode or object arrays") # Decrease the number of references to the object self._obj = None # Fix the byteorder of data nparr = self._g_fix_byteorder_data(nparr, nparr.dtype.byteorder) # Create the array on-disk try: # ``self._v_objectid`` needs to be set because would be # needed for setting attributes in some descendants later # on (self._v_objectid, self.shape, self.atom) = self._create_array( nparr, self._v_new_title, self.atom) except Exception: # XXX # Problems creating the Array on disk. Close node and re-raise. self.close(flush=0) raise # Compute the optimal buffer size self.nrowsinbuf = self._calc_nrowsinbuf() # Arrays don't have chunkshapes (so, set it to None) self._v_chunkshape = None return self._v_objectid def _g_open(self): """Get the metadata info for an array in file.""" (oid, self.atom, self.shape, self._v_chunkshape) = self._open_array() self.nrowsinbuf = self._calc_nrowsinbuf() return oid def get_enum(self): """Get the enumerated type associated with this array. If this array is of an enumerated type, the corresponding Enum instance (see :ref:`EnumClassDescr`) is returned. If it is not of an enumerated type, a TypeError is raised. """ if self.atom.kind != 'enum': raise TypeError("array ``%s`` is not of an enumerated type" % self._v_pathname) return self.atom.enum def iterrows(self, start=None, stop=None, step=None): """Iterate over the rows of the array. This method returns an iterator yielding an object of the current flavor for each selected row in the array. The returned rows are taken from the *main dimension*. If a range is not supplied, *all the rows* in the array are iterated upon - you can also use the :meth:`Array.__iter__` special method for that purpose. If you only want to iterate over a given *range of rows* in the array, you may use the start, stop and step parameters. Examples -------- :: result = [row for row in arrayInstance.iterrows(step=4)] .. versionchanged:: 3.0 If the *start* parameter is provided and *stop* is None then the array is iterated from *start* to the last line. In PyTables < 3.0 only one element was returned. """ try: (self._start, self._stop, self._step) = self._process_range( start, stop, step) except IndexError: # If problems with indexes, silently return the null tuple return () self._init_loop() return self def __iter__(self): """Iterate over the rows of the array. This is equivalent to calling :meth:`Array.iterrows` with default arguments, i.e. it iterates over *all the rows* in the array. Examples -------- :: result = [row[2] for row in array] Which is equivalent to:: result = [row[2] for row in array.iterrows()] """ if not self._init: # If the iterator is called directly, assign default variables self._start = 0 self._stop = self.nrows self._step = 1 # and initialize the loop self._init_loop() return self def _init_loop(self): """Initialization for the __iter__ iterator.""" self._nrowsread = self._start self._startb = self._start self._row = -1 # Sentinel self._init = True # Sentinel self.nrow = SizeType(self._start - self._step) # row number def __next__(self): """Get the next element of the array during an iteration. The element is returned as an object of the current flavor. """ # this could probably be sped up for long iterations by reusing the # listarr buffer if self._nrowsread >= self._stop: self._init = False self.listarr = None # fixes issue #308 raise StopIteration # end of iteration else: # Read a chunk of rows if self._row + 1 >= self.nrowsinbuf or self._row < 0: self._stopb = self._startb + self._step * self.nrowsinbuf # Protection for reading more elements than needed if self._stopb > self._stop: self._stopb = self._stop listarr = self._read(self._startb, self._stopb, self._step) # Swap the axes to easy the return of elements if self.extdim > 0: listarr = listarr.swapaxes(self.extdim, 0) self.listarr = internal_to_flavor(listarr, self.flavor) self._row = -1 self._startb = self._stopb self._row += 1 self.nrow += self._step self._nrowsread += self._step # Fixes bug #968132 # if self.listarr.shape: if self.shape: return self.listarr[self._row] else: return self.listarr # Scalar case def _interpret_indexing(self, keys): """Internal routine used by __getitem__ and __setitem__""" maxlen = len(self.shape) shape = (maxlen,) startl = np.empty(shape=shape, dtype=SizeType) stopl = np.empty(shape=shape, dtype=SizeType) stepl = np.empty(shape=shape, dtype=SizeType) stop_None = np.zeros(shape=shape, dtype=SizeType) if not isinstance(keys, tuple): keys = (keys,) nkeys = len(keys) dim = 0 # Here is some problem when dealing with [...,...] params # but this is a bit weird way to pass parameters anyway for key in keys: ellipsis = 0 # Sentinel if isinstance(key, type(Ellipsis)): ellipsis = 1 for diml in range(dim, len(self.shape) - (nkeys - dim) + 1): startl[dim] = 0 stopl[dim] = self.shape[diml] stepl[dim] = 1 dim += 1 elif dim >= maxlen: raise IndexError("Too many indices for object '%s'" % self._v_pathname) elif is_idx(key): key = operator.index(key) # Protection for index out of range if key >= self.shape[dim]: raise IndexError("Index out of range") if key < 0: # To support negative values (Fixes bug #968149) key += self.shape[dim] start, stop, step = self._process_range( key, key + 1, 1, dim=dim) stop_None[dim] = 1 elif isinstance(key, slice): start, stop, step = self._process_range( key.start, key.stop, key.step, dim=dim) else: raise TypeError("Non-valid index or slice: %s" % key) if not ellipsis: startl[dim] = start stopl[dim] = stop stepl[dim] = step dim += 1 # Complete the other dimensions, if needed if dim < len(self.shape): for diml in range(dim, len(self.shape)): startl[dim] = 0 stopl[dim] = self.shape[diml] stepl[dim] = 1 dim += 1 # Compute the shape for the container properly. Fixes #1288792 shape = [] for dim in range(len(self.shape)): new_dim = len(range(startl[dim], stopl[dim], stepl[dim])) if not (new_dim == 1 and stop_None[dim]): shape.append(new_dim) return startl, stopl, stepl, shape def _fancy_selection(self, args): """Performs a NumPy-style fancy selection in `self`. Implements advanced NumPy-style selection operations in addition to the standard slice-and-int behavior. Indexing arguments may be ints, slices or lists of indices. Note: This is a backport from the h5py project. """ # Internal functions def validate_number(num, length): """Validate a list member for the given axis length.""" try: num = int(num) except TypeError: raise TypeError("Illegal index: %r" % num) if num > length - 1: raise IndexError("Index out of bounds: %d" % num) def expand_ellipsis(args, rank): """Expand ellipsis objects and fill in missing axes.""" n_el = sum(1 for arg in args if arg is Ellipsis) if n_el > 1: raise IndexError("Only one ellipsis may be used.") elif n_el == 0 and len(args) != rank: args = args + (Ellipsis,) final_args = [] n_args = len(args) for idx, arg in enumerate(args): if arg is Ellipsis: final_args.extend((slice(None),) * (rank - n_args + 1)) else: final_args.append(arg) if len(final_args) > rank: raise IndexError("Too many indices.") return final_args def translate_slice(exp, length): """Given a slice object, return a 3-tuple (start, count, step) This is for for use with the hyperslab selection routines. """ start, stop, step = exp.start, exp.stop, exp.step if start is None: start = 0 else: start = int(start) if stop is None: stop = length else: stop = int(stop) if step is None: step = 1 else: step = int(step) if step < 1: raise IndexError("Step must be >= 1 (got %d)" % step) if stop == start: raise IndexError("Zero-length selections are not allowed") if stop < start: raise IndexError("Reverse-order selections are not allowed") if start < 0: start = length + start if stop < 0: stop = length + stop if not 0 <= start <= (length - 1): raise IndexError( "Start index %s out of range (0-%d)" % (start, length - 1)) if not 1 <= stop <= length: raise IndexError( "Stop index %s out of range (1-%d)" % (stop, length)) count = (stop - start) // step if (stop - start) % step != 0: count += 1 if start + count > length: raise IndexError( "Selection out of bounds (%d; axis has %d)" % (start + count, length)) return start, count, step # Main code for _fancy_selection mshape = [] selection = [] if not isinstance(args, tuple): args = (args,) args = expand_ellipsis(args, len(self.shape)) list_seen = False reorder = None for idx, (exp, length) in enumerate(zip(args, self.shape)): if isinstance(exp, slice): start, count, step = translate_slice(exp, length) selection.append((start, count, step, idx, "AND")) mshape.append(count) else: try: exp = list(exp) except TypeError: exp = [exp] # Handle scalar index as a list of length 1 mshape.append(0) # Keep track of scalar index for NumPy else: mshape.append(len(exp)) if len(exp) == 0: raise IndexError( "Empty selections are not allowed (axis %d)" % idx) elif len(exp) > 1: if list_seen: raise IndexError("Only one selection list is allowed") else: list_seen = True else: if (not isinstance(exp[0], (int, np.integer)) or (isinstance(exp[0], np.ndarray) and not np.issubdtype(exp[0].dtype, np.integer))): raise TypeError("Only integer coordinates allowed.") nexp = np.asarray(exp, dtype="i8") # Convert negative values nexp = np.where(nexp < 0, length + nexp, nexp) # Check whether the list is ordered or not # (only one unordered list is allowed) if len(nexp) != len(np.unique(nexp)): raise IndexError( "Selection lists cannot have repeated values") neworder = nexp.argsort() if (neworder.shape != (len(exp),) or np.sum(np.abs(neworder - np.arange(len(exp)))) != 0): if reorder is not None: raise IndexError( "Only one selection list can be unordered") corrected_idx = sum(1 for x in mshape if x != 0) - 1 reorder = (corrected_idx, neworder) nexp = nexp[neworder] for select_idx in range(len(nexp) + 1): # This crazy piece of code performs a list selection # using HDF5 hyperslabs. # For each index, perform a "NOTB" selection on every # portion of *this axis* which falls *outside* the list # selection. For this to work, the input array MUST be # monotonically increasing. if select_idx < len(nexp): validate_number(nexp[select_idx], length) if select_idx == 0: start = 0 count = nexp[0] elif select_idx == len(nexp): start = nexp[-1] + 1 count = length - start else: start = nexp[select_idx - 1] + 1 count = nexp[select_idx] - start if count > 0: selection.append((start, count, 1, idx, "NOTB")) mshape = tuple(x for x in mshape if x != 0) return selection, reorder, mshape def __getitem__(self, key): """Get a row, a range of rows or a slice from the array. The set of tokens allowed for the key is the same as that for extended slicing in Python (including the Ellipsis or ... token). The result is an object of the current flavor; its shape depends on the kind of slice used as key and the shape of the array itself. Furthermore, NumPy-style fancy indexing, where a list of indices in a certain axis is specified, is also supported. Note that only one list per selection is supported right now. Finally, NumPy-style point and boolean selections are supported as well. Examples -------- :: array1 = array[4] # simple selection array2 = array[4:1000:2] # slice selection array3 = array[1, ..., ::2, 1:4, 4:] # general slice selection array4 = array[1, [1,5,10], ..., -1] # fancy selection array5 = array[np.where(array[:] > 4)] # point selection array6 = array[array[:] > 4] # boolean selection """ self._g_check_open() try: # First, try with a regular selection startl, stopl, stepl, shape = self._interpret_indexing(key) arr = self._read_slice(startl, stopl, stepl, shape) except TypeError: # Then, try with a point-wise selection try: coords = self._point_selection(key) arr = self._read_coords(coords) except TypeError: # Finally, try with a fancy selection selection, reorder, shape = self._fancy_selection(key) arr = self._read_selection(selection, reorder, shape) if self.flavor == "numpy" or not self._v_convert: return arr return internal_to_flavor(arr, self.flavor) def __setitem__(self, key, value): """Set a row, a range of rows or a slice in the array. It takes different actions depending on the type of the key parameter: if it is an integer, the corresponding array row is set to value (the value is broadcast when needed). If key is a slice, the row slice determined by it is set to value (as usual, if the slice to be updated exceeds the actual shape of the array, only the values in the existing range are updated). If value is a multidimensional object, then its shape must be compatible with the shape determined by key, otherwise, a ValueError will be raised. Furthermore, NumPy-style fancy indexing, where a list of indices in a certain axis is specified, is also supported. Note that only one list per selection is supported right now. Finally, NumPy-style point and boolean selections are supported as well. Examples -------- :: a1[0] = 333 # assign an integer to a Integer Array row a2[0] = 'b' # assign a string to a string Array row a3[1:4] = 5 # broadcast 5 to slice 1:4 a4[1:4:2] = 'xXx' # broadcast 'xXx' to slice 1:4:2 # General slice update (a5.shape = (4,3,2,8,5,10). a5[1, ..., ::2, 1:4, 4:] = numpy.arange(1728, shape=(4,3,2,4,3,6)) a6[1, [1,5,10], ..., -1] = arr # fancy selection a7[np.where(a6[:] > 4)] = 4 # point selection + broadcast a8[arr > 4] = arr2 # boolean selection """ self._g_check_open() # Create an array compliant with the specified slice nparr = convert_to_np_atom2(value, self.atom) if nparr.size == 0: return # truncate data if least_significant_digit filter is set # TODO: add the least_significant_digit attribute to the array on disk if (self.filters.least_significant_digit is not None and not np.issubdtype(nparr.dtype, np.signedinteger)): nparr = quantize(nparr, self.filters.least_significant_digit) try: startl, stopl, stepl, shape = self._interpret_indexing(key) self._write_slice(startl, stopl, stepl, shape, nparr) except TypeError: # Then, try with a point-wise selection try: coords = self._point_selection(key) self._write_coords(coords, nparr) except TypeError: selection, reorder, shape = self._fancy_selection(key) self._write_selection(selection, reorder, shape, nparr) def _check_shape(self, nparr, slice_shape): """Test that nparr shape is consistent with underlying object. If not, try creating a new nparr object, using broadcasting if necessary. """ if nparr.shape != (slice_shape + self.atom.dtype.shape): # Create an array compliant with the specified shape narr = np.empty(shape=slice_shape, dtype=self.atom.dtype) # Assign the value to it. It will raise a ValueError exception # if the objects cannot be broadcast to a single shape. narr[...] = nparr return narr else: return nparr def _read_slice(self, startl, stopl, stepl, shape): """Read a slice based on `startl`, `stopl` and `stepl`.""" nparr = np.empty(dtype=self.atom.dtype, shape=shape) # Protection against reading empty arrays if 0 not in shape: # Arrays that have non-zero dimensionality self._g_read_slice(startl, stopl, stepl, nparr) # For zero-shaped arrays, return the scalar if nparr.shape == (): nparr = nparr[()] return nparr def _read_coords(self, coords): """Read a set of points defined by `coords`.""" nparr = np.empty(dtype=self.atom.dtype, shape=len(coords)) if len(coords) > 0: self._g_read_coords(coords, nparr) # For zero-shaped arrays, return the scalar if nparr.shape == (): nparr = nparr[()] return nparr def _read_selection(self, selection, reorder, shape): """Read a `selection`. Reorder if necessary. """ # Create the container for the slice nparr = np.empty(dtype=self.atom.dtype, shape=shape) # Arrays that have non-zero dimensionality self._g_read_selection(selection, nparr) # For zero-shaped arrays, return the scalar if nparr.shape == (): nparr = nparr[()] elif reorder is not None: # We need to reorder the array idx, neworder = reorder k = [slice(None)] * len(shape) k[idx] = neworder.argsort() # Apparently, a copy is not needed here, but doing it # for symmetry with the `_write_selection()` method. nparr = nparr[tuple(k)].copy() return nparr def _write_slice(self, startl, stopl, stepl, shape, nparr): """Write `nparr` in a slice based on `startl`, `stopl` and `stepl`.""" nparr = self._check_shape(nparr, tuple(shape)) countl = ((stopl - startl - 1) // stepl) + 1 self._g_write_slice(startl, stepl, countl, nparr) def _write_coords(self, coords, nparr): """Write `nparr` values in points defined by `coords` coordinates.""" if len(coords) > 0: nparr = self._check_shape(nparr, (len(coords),)) self._g_write_coords(coords, nparr) def _write_selection(self, selection, reorder, shape, nparr): """Write `nparr` in `selection`. Reorder if necessary. """ nparr = self._check_shape(nparr, tuple(shape)) # Check whether we should reorder the array if reorder is not None: idx, neworder = reorder k = [slice(None)] * len(shape) k[idx] = neworder # For a reason a don't understand well, we need a copy of # the reordered array nparr = nparr[tuple(k)].copy() self._g_write_selection(selection, nparr) def _read(self, start, stop, step, out=None): """Read the array from disk without slice or flavor processing.""" nrowstoread = len(range(start, stop, step)) shape = list(self.shape) if shape: shape[self.maindim] = nrowstoread if out is None: arr = np.empty(dtype=self.atom.dtype, shape=shape) else: bytes_required = self.rowsize * nrowstoread # if buffer is too small, it will segfault if bytes_required != out.nbytes: raise ValueError(f'output array size invalid, got {out.nbytes}' f' bytes, need {bytes_required} bytes') if not out.flags['C_CONTIGUOUS']: raise ValueError('output array not C contiguous') arr = out # Protection against reading empty arrays if 0 not in shape: # Arrays that have non-zero dimensionality self._read_array(start, stop, step, arr) # data is always read in the system byteorder # if the out array's byteorder is different, do a byteswap if (out is not None and byteorders[arr.dtype.byteorder] != sys.byteorder): arr.byteswap(True) return arr def read(self, start=None, stop=None, step=None, out=None): """Get data in the array as an object of the current flavor. The start, stop and step parameters can be used to select only a *range of rows* in the array. Their meanings are the same as in the built-in range() Python function, except that negative values of step are not allowed yet. Moreover, if only start is specified, then stop will be set to start + 1. If you do not specify neither start nor stop, then *all the rows* in the array are selected. The out parameter may be used to specify a NumPy array to receive the output data. Note that the array must have the same size as the data selected with the other parameters. Note that the array's datatype is not checked and no type casting is performed, so if it does not match the datatype on disk, the output will not be correct. Also, this parameter is only valid when the array's flavor is set to 'numpy'. Otherwise, a TypeError will be raised. When data is read from disk in NumPy format, the output will be in the current system's byteorder, regardless of how it is stored on disk. The exception is when an output buffer is supplied, in which case the output will be in the byteorder of that output buffer. .. versionchanged:: 3.0 Added the *out* parameter. """ self._g_check_open() if out is not None and self.flavor != 'numpy': msg = ("Optional 'out' argument may only be supplied if array " "flavor is 'numpy', currently is {}").format(self.flavor) raise TypeError(msg) (start, stop, step) = self._process_range_read(start, stop, step) arr = self._read(start, stop, step, out) return internal_to_flavor(arr, self.flavor) def _g_copy_with_stats(self, group, name, start, stop, step, title, filters, chunkshape, _log, **kwargs): """Private part of Leaf.copy() for each kind of leaf.""" # Compute the correct indices. (start, stop, step) = self._process_range_read(start, stop, step) # Get the slice of the array # (non-buffered version) if self.shape: arr = self[start:stop:step] else: arr = self[()] # Build the new Array object. Use the _atom reserved keyword # just in case the array is being copied from a native HDF5 # with atomic types different from scalars. # For details, see #275 of trac. object_ = Array(group, name, arr, title=title, _log=_log, _atom=self.atom) nbytes = np.prod(self.shape, dtype=SizeType) * self.atom.size return (object_, nbytes) def __repr__(self): """This provides more metainfo in addition to standard __str__""" return f"""{self} atom := {self.atom!r} maindim := {self.maindim!r} flavor := {self.flavor!r} byteorder := {self.byteorder!r} chunkshape := {self.chunkshape!r}""" class ImageArray(Array): """Array containing an image. This class has no additional behaviour or functionality compared to that of an ordinary array. It simply enables the user to open an ``IMAGE`` HDF5 node as a normal `Array` node in PyTables. """ # Class identifier. _c_classid = 'IMAGE' PyTables-3.7.0/tables/atom.py000066400000000000000000001210671416254111300160350ustar00rootroot00000000000000"""Atom classes for describing dataset contents.""" import re import inspect import warnings import numpy as np from .utils import SizeType from .misc.enum import Enum import pickle from .exceptions import FlavorWarning __docformat__ = 'reStructuredText' """The format of documentation strings in this module.""" all_types = set() # filled as atom classes are created """Set of all PyTables types.""" atom_map = {} # filled as atom classes are created """Maps atom kinds to item sizes and atom classes. If there is a fixed set of possible item sizes for a given kind, the kind maps to another mapping from item size in bytes to atom class. Otherwise, the kind maps directly to the atom class. """ deftype_from_kind = {} # filled as atom classes are created """Maps atom kinds to their default atom type (if any).""" _type_re = re.compile(r'^([a-z]+)([0-9]*)$') def split_type(type): """Split a PyTables type into a PyTables kind and an item size. Returns a tuple of (kind, itemsize). If no item size is present in the type (in the form of a precision), the returned item size is None:: >>> split_type('int32') ('int', 4) >>> split_type('string') ('string', None) >>> split_type('int20') Traceback (most recent call last): ... ValueError: precision must be a multiple of 8: 20 >>> split_type('foo bar') Traceback (most recent call last): ... ValueError: malformed type: 'foo bar' """ match = _type_re.match(type) if not match: raise ValueError("malformed type: %r" % type) kind, precision = match.groups() itemsize = None if precision: precision = int(precision) itemsize, remainder = divmod(precision, 8) if remainder: # 0 could be a valid item size raise ValueError("precision must be a multiple of 8: %d" % precision) return (kind, itemsize) def _invalid_itemsize_error(kind, itemsize, itemsizes): isizes = sorted(itemsizes) return ValueError("invalid item size for kind ``%s``: %r; " "it must be one of ``%r``" % (kind, itemsize, isizes)) def _abstract_atom_init(deftype, defvalue): """Return a constructor for an abstract `Atom` class.""" defitemsize = split_type(deftype)[1] def __init__(self, itemsize=defitemsize, shape=(), dflt=defvalue): assert self.kind in atom_map try: atomclass = atom_map[self.kind][itemsize] except KeyError: raise _invalid_itemsize_error(self.kind, itemsize, atom_map[self.kind]) self.__class__ = atomclass atomclass.__init__(self, shape, dflt) return __init__ def _normalize_shape(shape): """Check that the `shape` is safe to be used and return it as a tuple.""" if isinstance(shape, (np.integer, int)): if shape < 1: raise ValueError("shape value must be greater than 0: %d" % shape) shape = (shape,) # N is a shorthand for (N,) try: shape = tuple(shape) except TypeError: raise TypeError("shape must be an integer or sequence: %r" % (shape,)) # XXX Get from HDF5 library if possible. # HDF5 does not support ranks greater than 32 if len(shape) > 32: raise ValueError( f"shapes with rank > 32 are not supported: {shape!r}") return tuple(SizeType(s) for s in shape) def _normalize_default(value, dtype): """Return `value` as a valid default of NumPy type `dtype`.""" # Create NumPy objects as defaults # This is better in order to serialize them as attributes if value is None: value = 0 basedtype = dtype.base try: default = np.array(value, dtype=basedtype) except ValueError: array = np.array(value) if array.shape != basedtype.shape: raise # Maybe nested dtype with "scalar" value. default = np.array(value, dtype=basedtype.base) # 0-dim arrays will be representented as NumPy scalars # (PyTables attribute convention) if default.shape == (): default = default[()] return default def _cmp_dispatcher(other_method_name): """Dispatch comparisons to a method of the *other* object. Returns a new *rich comparison* method which dispatches calls to the method `other_method_name` of the *other* object. If there is no such method in the object, ``False`` is returned. This is part of the implementation of a double dispatch pattern. """ def dispatched_cmp(self, other): try: other_method = getattr(other, other_method_name) except AttributeError: return False return other_method(self) return dispatched_cmp class MetaAtom(type): """Atom metaclass. This metaclass ensures that data about atom classes gets inserted into the suitable registries. """ def __init__(cls, name, bases, dict_): super().__init__(name, bases, dict_) kind = dict_.get('kind') itemsize = dict_.get('itemsize') type_ = dict_.get('type') deftype = dict_.get('_deftype') if kind and deftype: deftype_from_kind[kind] = deftype if type_: all_types.add(type_) if kind and itemsize and not hasattr(itemsize, '__int__'): # Atom classes with a non-fixed item size do have an # ``itemsize``, but it's not a number (e.g. property). atom_map[kind] = cls return if kind: # first definition of kind, make new entry atom_map[kind] = {} if itemsize and hasattr(itemsize, '__int__'): # fixed kind = cls.kind # maybe from superclasses atom_map[kind][int(itemsize)] = cls class Atom(metaclass=MetaAtom): """Defines the type of atomic cells stored in a dataset. The meaning of *atomic* is that individual elements of a cell can not be extracted directly by indexing (i.e. __getitem__()) the dataset; e.g. if a dataset has shape (2, 2) and its atoms have shape (3,), to get the third element of the cell at (1, 0) one should use dataset[1,0][2] instead of dataset[1,0,2]. The Atom class is meant to declare the different properties of the *base element* (also known as *atom*) of CArray, EArray and VLArray datasets, although they are also used to describe the base elements of Array datasets. Atoms have the property that their length is always the same. However, you can grow datasets along the extensible dimension in the case of EArray or put a variable number of them on a VLArray row. Moreover, they are not restricted to scalar values, and they can be *fully multidimensional objects*. Parameters ---------- itemsize : int For types with a non-fixed size, this sets the size in bytes of individual items in the atom. shape : tuple Sets the shape of the atom. An integer shape of N is equivalent to the tuple (N,). dflt Sets the default value for the atom. The following are the public methods and attributes of the Atom class. Notes ----- A series of descendant classes are offered in order to make the use of these element descriptions easier. You should use a particular Atom descendant class whenever you know the exact type you will need when writing your code. Otherwise, you may use one of the Atom.from_*() factory Methods. .. rubric:: Atom attributes .. attribute:: dflt The default value of the atom. If the user does not supply a value for an element while filling a dataset, this default value will be written to disk. If the user supplies a scalar value for a multidimensional atom, this value is automatically *broadcast* to all the items in the atom cell. If dflt is not supplied, an appropriate zero value (or *null* string) will be chosen by default. Please note that default values are kept internally as NumPy objects. .. attribute:: dtype The NumPy dtype that most closely matches this atom. .. attribute:: itemsize Size in bytes of a single item in the atom. Specially useful for atoms of the string kind. .. attribute:: kind The PyTables kind of the atom (a string). .. attribute:: shape The shape of the atom (a tuple for scalar atoms). .. attribute:: type The PyTables type of the atom (a string). Atoms can be compared with atoms and other objects for strict (in)equality without having to compare individual attributes:: >>> atom1 = StringAtom(itemsize=10) # same as ``atom2`` >>> atom2 = Atom.from_kind('string', 10) # same as ``atom1`` >>> atom3 = IntAtom() >>> atom1 == 'foo' False >>> atom1 == atom2 True >>> atom2 != atom1 False >>> atom1 == atom3 False >>> atom3 != atom2 True """ @classmethod def prefix(cls): """Return the atom class prefix.""" cname = cls.__name__ return cname[:cname.rfind('Atom')] @classmethod def from_sctype(cls, sctype, shape=(), dflt=None): """Create an Atom from a NumPy scalar type sctype. Optional shape and default value may be specified as the shape and dflt arguments, respectively. Information in the sctype not represented in an Atom is ignored:: >>> import numpy as np >>> Atom.from_sctype(np.int16, shape=(2, 2)) Int16Atom(shape=(2, 2), dflt=0) >>> Atom.from_sctype('S5', dflt='hello') Traceback (most recent call last): ... ValueError: unknown NumPy scalar type: 'S5' >>> Atom.from_sctype('float64') Float64Atom(shape=(), dflt=0.0) """ if (not isinstance(sctype, type) or not issubclass(sctype, np.generic)): if sctype not in np.sctypeDict: raise ValueError(f"unknown NumPy scalar type: {sctype!r}") sctype = np.sctypeDict[sctype] return cls.from_dtype(np.dtype((sctype, shape)), dflt) @classmethod def from_dtype(cls, dtype, dflt=None): """Create an Atom from a NumPy dtype. An optional default value may be specified as the dflt argument. Information in the dtype not represented in an Atom is ignored:: >>> import numpy as np >>> Atom.from_dtype(np.dtype((np.int16, (2, 2)))) Int16Atom(shape=(2, 2), dflt=0) >>> Atom.from_dtype(np.dtype('float64')) Float64Atom(shape=(), dflt=0.0) Note: for easier use in Python 3, where all strings lead to the Unicode dtype, this dtype will also generate a StringAtom. Since this is only viable for strings that are castable as ascii, a warning is issued. >>> Atom.from_dtype(np.dtype('U20')) # doctest: +SKIP Atom.py:392: FlavorWarning: support for unicode type is very limited, and only works for strings that can be cast as ascii StringAtom(itemsize=20, shape=(), dflt=b'') """ basedtype = dtype.base if basedtype.names: raise ValueError("compound data types are not supported: %r" % dtype) if basedtype.shape != (): raise ValueError("nested data types are not supported: %r" % dtype) if basedtype.kind == 'S': # can not reuse something like 'string80' itemsize = basedtype.itemsize return cls.from_kind('string', itemsize, dtype.shape, dflt) elif basedtype.kind == 'U': # workaround for unicode type (standard string type in Python 3) warnings.warn("support for unicode type is very limited, and " "only works for strings that can be cast as ascii", FlavorWarning) itemsize = basedtype.itemsize // 4 assert str(itemsize) in basedtype.str, ( "something went wrong in handling unicode.") return cls.from_kind('string', itemsize, dtype.shape, dflt) # Most NumPy types have direct correspondence with PyTables types. return cls.from_type(basedtype.name, dtype.shape, dflt) @classmethod def from_type(cls, type, shape=(), dflt=None): """Create an Atom from a PyTables type. Optional shape and default value may be specified as the shape and dflt arguments, respectively:: >>> Atom.from_type('bool') BoolAtom(shape=(), dflt=False) >>> Atom.from_type('int16', shape=(2, 2)) Int16Atom(shape=(2, 2), dflt=0) >>> Atom.from_type('string40', dflt='hello') Traceback (most recent call last): ... ValueError: unknown type: 'string40' >>> Atom.from_type('Float64') Traceback (most recent call last): ... ValueError: unknown type: 'Float64' """ if type not in all_types: raise ValueError(f"unknown type: {type!r}") kind, itemsize = split_type(type) return cls.from_kind(kind, itemsize, shape, dflt) @classmethod def from_kind(cls, kind, itemsize=None, shape=(), dflt=None): """Create an Atom from a PyTables kind. Optional item size, shape and default value may be specified as the itemsize, shape and dflt arguments, respectively. Bear in mind that not all atoms support a default item size:: >>> Atom.from_kind('int', itemsize=2, shape=(2, 2)) Int16Atom(shape=(2, 2), dflt=0) >>> Atom.from_kind('int', shape=(2, 2)) Int32Atom(shape=(2, 2), dflt=0) >>> Atom.from_kind('int', shape=1) Int32Atom(shape=(1,), dflt=0) >>> Atom.from_kind('string', dflt=b'hello') Traceback (most recent call last): ... ValueError: no default item size for kind ``string`` >>> Atom.from_kind('Float') Traceback (most recent call last): ... ValueError: unknown kind: 'Float' Moreover, some kinds with atypical constructor signatures are not supported; you need to use the proper constructor:: >>> Atom.from_kind('enum') #doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: the ``enum`` kind is not supported... """ kwargs = {'shape': shape} if kind not in atom_map: raise ValueError(f"unknown kind: {kind!r}") # This incompatibility detection may get out-of-date and is # too hard-wired, but I couldn't come up with something # smarter. -- Ivan (2007-02-08) if kind in ['enum']: raise ValueError("the ``%s`` kind is not supported; " "please use the appropriate constructor" % kind) # If no `itemsize` is given, try to get the default type of the # kind (which has a fixed item size). if itemsize is None: if kind not in deftype_from_kind: raise ValueError("no default item size for kind ``%s``" % kind) type_ = deftype_from_kind[kind] kind, itemsize = split_type(type_) kdata = atom_map[kind] # Look up the class and set a possible item size. if hasattr(kdata, 'kind'): # atom class: non-fixed item size atomclass = kdata kwargs['itemsize'] = itemsize else: # dictionary: fixed item size if itemsize not in kdata: raise _invalid_itemsize_error(kind, itemsize, kdata) atomclass = kdata[itemsize] # Only set a `dflt` argument if given (`None` may not be understood). if dflt is not None: kwargs['dflt'] = dflt return atomclass(**kwargs) @property def size(self): """Total size in bytes of the atom.""" return self.dtype.itemsize @property def recarrtype(self): """String type to be used in numpy.rec.array().""" return str(self.dtype.shape) + self.dtype.base.str[1:] @property def ndim(self): """The number of dimensions of the atom. .. versionadded:: 2.4""" return len(self.shape) def __init__(self, nptype, shape, dflt): if not hasattr(self, 'type'): raise NotImplementedError("``%s`` is an abstract class; " "please use one of its subclasses" % self.__class__.__name__) self.shape = shape = _normalize_shape(shape) """The shape of the atom (a tuple for scalar atoms).""" # Curiously enough, NumPy isn't generally able to accept NumPy # integers in a shape. ;( npshape = tuple(int(s) for s in shape) self.dtype = dtype = np.dtype((nptype, npshape)) """The NumPy dtype that most closely matches this atom.""" self.dflt = _normalize_default(dflt, dtype) """The default value of the atom. If the user does not supply a value for an element while filling a dataset, this default value will be written to disk. If the user supplies a scalar value for a multidimensional atom, this value is automatically *broadcast* to all the items in the atom cell. If dflt is not supplied, an appropriate zero value (or *null* string) will be chosen by default. Please note that default values are kept internally as NumPy objects.""" def __repr__(self): args = f'shape={self.shape}, dflt={self.dflt!r}' if not hasattr(self.__class__.itemsize, '__int__'): # non-fixed args = f'itemsize={self.itemsize}, {args}' return f'{self.__class__.__name__}({args})' __eq__ = _cmp_dispatcher('_is_equal_to_atom') def __ne__(self, other): return not self.__eq__(other) # XXX: API incompatible change for PyTables 3 line # Overriding __eq__ blocks inheritance of __hash__ in 3.x # def __hash__(self): # return hash((self.__class__, self.type, self.shape, self.itemsize, # self.dflt)) def copy(self, **override): """Get a copy of the atom, possibly overriding some arguments. Constructor arguments to be overridden must be passed as keyword arguments:: >>> atom1 = Int32Atom(shape=12) >>> atom2 = atom1.copy() >>> print(atom1) Int32Atom(shape=(12,), dflt=0) >>> print(atom2) Int32Atom(shape=(12,), dflt=0) >>> atom1 is atom2 False >>> atom3 = atom1.copy(shape=(2, 2)) >>> print(atom3) Int32Atom(shape=(2, 2), dflt=0) >>> atom1.copy(foobar=42) #doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: ...__init__() got an unexpected keyword argument 'foobar' """ newargs = self._get_init_args() newargs.update(override) return self.__class__(**newargs) def _get_init_args(self): """Get a dictionary of instance constructor arguments. This implementation works on classes which use the same names for both constructor arguments and instance attributes. """ signature = inspect.signature(self.__init__) parameters = signature.parameters args = [arg for arg, p in parameters.items() if p.kind is p.POSITIONAL_OR_KEYWORD] return {arg: getattr(self, arg) for arg in args if arg != 'self'} def _is_equal_to_atom(self, atom): """Is this object equal to the given `atom`?""" return (self.type == atom.type and self.shape == atom.shape and self.itemsize == atom.itemsize and np.all(self.dflt == atom.dflt)) class StringAtom(Atom): """Defines an atom of type string. The item size is the *maximum* length in characters of strings. """ kind = 'string' type = 'string' _defvalue = b'' @property def itemsize(self): """Size in bytes of a sigle item in the atom.""" return self.dtype.base.itemsize def __init__(self, itemsize, shape=(), dflt=_defvalue): if not hasattr(itemsize, '__int__') or int(itemsize) < 0: raise ValueError("invalid item size for kind ``%s``: %r; " "it must be a positive integer" % ('string', itemsize)) Atom.__init__(self, 'S%d' % itemsize, shape, dflt) class BoolAtom(Atom): """Defines an atom of type bool.""" kind = 'bool' itemsize = 1 type = 'bool' _deftype = 'bool8' _defvalue = False def __init__(self, shape=(), dflt=_defvalue): Atom.__init__(self, self.type, shape, dflt) class IntAtom(Atom): """Defines an atom of a signed integral type (int kind).""" kind = 'int' signed = True _deftype = 'int32' _defvalue = 0 __init__ = _abstract_atom_init(_deftype, _defvalue) class UIntAtom(Atom): """Defines an atom of an unsigned integral type (uint kind).""" kind = 'uint' signed = False _deftype = 'uint32' _defvalue = 0 __init__ = _abstract_atom_init(_deftype, _defvalue) class FloatAtom(Atom): """Defines an atom of a floating point type (float kind).""" kind = 'float' _deftype = 'float64' _defvalue = 0.0 __init__ = _abstract_atom_init(_deftype, _defvalue) def _create_numeric_class(baseclass, itemsize): """Create a numeric atom class with the given `baseclass` and an `itemsize`.""" prefix = '%s%d' % (baseclass.prefix(), itemsize * 8) type_ = prefix.lower() classdict = {'itemsize': itemsize, 'type': type_, '__doc__': "Defines an atom of type ``%s``." % type_} def __init__(self, shape=(), dflt=baseclass._defvalue): Atom.__init__(self, self.type, shape, dflt) classdict['__init__'] = __init__ return type('%sAtom' % prefix, (baseclass,), classdict) Int8Atom = _create_numeric_class(IntAtom, 1) Int16Atom = _create_numeric_class(IntAtom, 2) Int32Atom = _create_numeric_class(IntAtom, 4) Int64Atom = _create_numeric_class(IntAtom, 8) UInt8Atom = _create_numeric_class(UIntAtom, 1) UInt16Atom = _create_numeric_class(UIntAtom, 2) UInt32Atom = _create_numeric_class(UIntAtom, 4) UInt64Atom = _create_numeric_class(UIntAtom, 8) if hasattr(np, 'float16'): Float16Atom = _create_numeric_class(FloatAtom, 2) Float32Atom = _create_numeric_class(FloatAtom, 4) Float64Atom = _create_numeric_class(FloatAtom, 8) if hasattr(np, 'float96'): Float96Atom = _create_numeric_class(FloatAtom, 12) if hasattr(np, 'float128'): Float128Atom = _create_numeric_class(FloatAtom, 16) class ComplexAtom(Atom): """Defines an atom of kind complex. Allowed item sizes are 8 (single precision) and 16 (double precision). This class must be used instead of more concrete ones to avoid confusions with numarray-like precision specifications used in PyTables 1.X. """ # This definition is a little more complex (no pun intended) # because, although the complex kind is a normal numerical one, # the usage of bottom-level classes is artificially forbidden. # Everything will be back to normality when people has stopped # using the old bottom-level complex classes. kind = 'complex' _deftype = 'complex128' _defvalue = 0j _isizes = [8, 16] @property def itemsize(self): """Size in bytes of a sigle item in the atom.""" return self.dtype.base.itemsize # Only instances have a `type` attribute, so complex types must be # registered by hand. all_types.add('complex64') all_types.add('complex128') if hasattr(np, 'complex192'): all_types.add('complex192') _isizes.append(24) if hasattr(np, 'complex256'): all_types.add('complex256') _isizes.append(32) def __init__(self, itemsize, shape=(), dflt=_defvalue): if itemsize not in self._isizes: raise _invalid_itemsize_error('complex', itemsize, self._isizes) self.type = '%s%d' % (self.kind, itemsize * 8) Atom.__init__(self, self.type, shape, dflt) class _ComplexErrorAtom(ComplexAtom, metaclass=type): """Reminds the user to stop using the old complex atom names.""" def __init__(self, shape=(), dflt=ComplexAtom._defvalue): raise TypeError( "to avoid confusions with PyTables 1.X complex atom names, " "please use ``ComplexAtom(itemsize=N)``, " "where N=8 for single precision complex atoms, " "and N=16 for double precision complex atoms") Complex32Atom = Complex64Atom = Complex128Atom = _ComplexErrorAtom if hasattr(np, 'complex192'): Complex192Atom = _ComplexErrorAtom if hasattr(np, 'complex256'): Complex256Atom = _ComplexErrorAtom class TimeAtom(Atom): """Defines an atom of time type (time kind). There are two distinct supported types of time: a 32 bit integer value and a 64 bit floating point value. Both of them reflect the number of seconds since the Unix epoch. This atom has the property of being stored using the HDF5 time datatypes. """ kind = 'time' _deftype = 'time32' _defvalue = 0 __init__ = _abstract_atom_init(_deftype, _defvalue) class Time32Atom(TimeAtom): """Defines an atom of type time32.""" itemsize = 4 type = 'time32' _defvalue = 0 def __init__(self, shape=(), dflt=_defvalue): Atom.__init__(self, 'int32', shape, dflt) class Time64Atom(TimeAtom): """Defines an atom of type time64.""" itemsize = 8 type = 'time64' _defvalue = 0.0 def __init__(self, shape=(), dflt=_defvalue): Atom.__init__(self, 'float64', shape, dflt) class EnumAtom(Atom): """Description of an atom of an enumerated type. Instances of this class describe the atom type used to store enumerated values. Those values belong to an enumerated type, defined by the first argument (enum) in the constructor of the atom, which accepts the same kinds of arguments as the Enum class (see :ref:`EnumClassDescr`). The enumerated type is stored in the enum attribute of the atom. A default value must be specified as the second argument (dflt) in the constructor; it must be the *name* (a string) of one of the enumerated values in the enumerated type. When the atom is created, the corresponding concrete value is broadcast and stored in the dflt attribute (setting different default values for items in a multidimensional atom is not supported yet). If the name does not match any value in the enumerated type, a KeyError is raised. Another atom must be specified as the base argument in order to determine the base type used for storing the values of enumerated values in memory and disk. This *storage atom* is kept in the base attribute of the created atom. As a shorthand, you may specify a PyTables type instead of the storage atom, implying that this has a scalar shape. The storage atom should be able to represent each and every concrete value in the enumeration. If it is not, a TypeError is raised. The default value of the storage atom is ignored. The type attribute of enumerated atoms is always enum. Enumerated atoms also support comparisons with other objects:: >>> enum = ['T0', 'T1', 'T2'] >>> atom1 = EnumAtom(enum, 'T0', 'int8') # same as ``atom2`` >>> atom2 = EnumAtom(enum, 'T0', Int8Atom()) # same as ``atom1`` >>> atom3 = EnumAtom(enum, 'T0', 'int16') >>> atom4 = Int8Atom() >>> atom1 == enum False >>> atom1 == atom2 True >>> atom2 != atom1 False >>> atom1 == atom3 False >>> atom1 == atom4 False >>> atom4 != atom1 True Examples -------- The next C enum construction:: enum myEnum { T0, T1, T2 }; would correspond to the following PyTables declaration:: >>> my_enum_atom = EnumAtom(['T0', 'T1', 'T2'], 'T0', 'int32') Please note the dflt argument with a value of 'T0'. Since the concrete value matching T0 is unknown right now (we have not used explicit concrete values), using the name is the only option left for defining a default value for the atom. The chosen representation of values for this enumerated atom uses unsigned 32-bit integers, which surely wastes quite a lot of memory. Another size could be selected by using the base argument (this time with a full-blown storage atom):: >>> my_enum_atom = EnumAtom(['T0', 'T1', 'T2'], 'T0', UInt8Atom()) You can also define multidimensional arrays for data elements:: >>> my_enum_atom = EnumAtom( ... ['T0', 'T1', 'T2'], 'T0', base='uint32', shape=(3,2)) for 3x2 arrays of uint32. """ # Registering this class in the class map may be a little wrong, # since the ``Atom.from_kind()`` method fails miserably with # enumerations, as they don't support an ``itemsize`` argument. # However, resetting ``__metaclass__`` to ``type`` doesn't seem to # work and I don't feel like creating a subclass of ``MetaAtom``. kind = 'enum' type = 'enum' @property def itemsize(self): """Size in bytes of a single item in the atom.""" return self.dtype.base.itemsize def _checkbase(self, base): """Check the `base` storage atom.""" if base.kind == 'enum': raise TypeError("can not use an enumerated atom " "as a storage atom: %r" % base) # Check whether the storage atom can represent concrete values # in the enumeration... basedtype = base.dtype pyvalues = [value for (name, value) in self.enum] try: npgenvalues = np.array(pyvalues) except ValueError: raise TypeError("concrete values are not uniformly-shaped") try: npvalues = np.array(npgenvalues, dtype=basedtype.base) except ValueError: raise TypeError("storage atom type is incompatible with " "concrete values in the enumeration") if npvalues.shape[1:] != basedtype.shape: raise TypeError("storage atom shape does not match that of " "concrete values in the enumeration") if npvalues.tolist() != npgenvalues.tolist(): raise TypeError("storage atom type lacks precision for " "concrete values in the enumeration") # ...with some implementation limitations. if npvalues.dtype.kind not in ['i', 'u']: raise NotImplementedError("only integer concrete values " "are supported for the moment, sorry") if len(npvalues.shape) > 1: raise NotImplementedError("only scalar concrete values " "are supported for the moment, sorry") def _get_init_args(self): """Get a dictionary of instance constructor arguments.""" return dict(enum=self.enum, dflt=self._defname, base=self.base, shape=self.shape) def _is_equal_to_atom(self, atom): """Is this object equal to the given `atom`?""" return False def _is_equal_to_enumatom(self, enumatom): """Is this object equal to the given `enumatom`?""" return (self.enum == enumatom.enum and self.shape == enumatom.shape and np.all(self.dflt == enumatom.dflt) and self.base == enumatom.base) def __init__(self, enum, dflt, base, shape=()): if not isinstance(enum, Enum): enum = Enum(enum) self.enum = enum if isinstance(base, str): base = Atom.from_type(base) self._checkbase(base) self.base = base default = enum[dflt] # check default value self._defname = dflt # kept for representation purposes # These are kept to ease dumping this particular # representation of the enumeration to storage. names, values = [], [] for (name, value) in enum: names.append(name) values.append(value) basedtype = self.base.dtype self._names = names self._values = np.array(values, dtype=basedtype.base) Atom.__init__(self, basedtype, shape, default) def __repr__(self): return ('EnumAtom(enum=%r, dflt=%r, base=%r, shape=%r)' % (self.enum, self._defname, self.base, self.shape)) __eq__ = _cmp_dispatcher('_is_equal_to_enumatom') # XXX: API incompatible change for PyTables 3 line # Overriding __eq__ blocks inheritance of __hash__ in 3.x # def __hash__(self): # return hash((self.__class__, self.enum, self.shape, self.dflt, # self.base)) class ReferenceAtom(Atom): """Defines an atom of type object to read references. This atom is read-only. """ kind = 'reference' type = 'object' _deftype = 'NoneType' _defvalue = None @property def itemsize(self): """Size in bytes of a single item in the atom.""" return self.dtype.base.itemsize def __init__(self, shape=()): Atom.__init__(self, self.type, shape, self._defvalue) def __repr__(self): return f'ReferenceAtom(shape={self.shape})' # Pseudo-atom classes # =================== # # Now, there come three special classes, `ObjectAtom`, `VLStringAtom` # and `VLUnicodeAtom`, that actually do not descend from `Atom`, but # which goal is so similar that they should be described here. # Pseudo-atoms can only be used with `VLArray` datasets, and they do # not support multidimensional values, nor multiple values per row. # # They can be recognised because they also have ``kind``, ``type`` and # ``shape`` attributes, but no ``size``, ``itemsize`` or ``dflt`` # ones. Instead, they have a ``base`` atom which defines the elements # used for storage. # # See ``examples/vlarray1.py`` and ``examples/vlarray2.py`` for # further examples on `VLArray` datasets, including object # serialization and string management. class PseudoAtom: """Pseudo-atoms can only be used in ``VLArray`` nodes. They can be recognised because they also have `kind`, `type` and `shape` attributes, but no `size`, `itemsize` or `dflt` ones. Instead, they have a `base` atom which defines the elements used for storage. """ def __repr__(self): return '%s()' % self.__class__.__name__ def toarray(self, object_): """Convert an `object_` into an array of base atoms.""" raise NotImplementedError def fromarray(self, array): """Convert an `array` of base atoms into an object.""" raise NotImplementedError class _BufferedAtom(PseudoAtom): """Pseudo-atom which stores data as a buffer (flat array of uints).""" shape = () def toarray(self, object_): buffer_ = self._tobuffer(object_) array = np.ndarray(buffer=buffer_, dtype=self.base.dtype, shape=len(buffer_)) return array def _tobuffer(self, object_): """Convert an `object_` into a buffer.""" raise NotImplementedError class VLStringAtom(_BufferedAtom): """Defines an atom of type ``vlstring``. This class describes a *row* of the VLArray class, rather than an atom. It differs from the StringAtom class in that you can only add *one instance of it to one specific row*, i.e. the :meth:`VLArray.append` method only accepts one object when the base atom is of this type. This class stores bytestrings. It does not make assumptions on the encoding of the string, and raw bytes are stored as is. To store a string you will need to *explicitly* convert it to a bytestring before you can save them:: >>> s = 'A unicode string: hbar = \u210f' >>> bytestring = s.encode('utf-8') >>> VLArray.append(bytestring) # doctest: +SKIP For full Unicode support, using VLUnicodeAtom (see :ref:`VLUnicodeAtom`) is recommended. Variable-length string atoms do not accept parameters and they cause the reads of rows to always return Python bytestrings. You can regard vlstring atoms as an easy way to save generic variable length strings. """ kind = 'vlstring' type = 'vlstring' base = UInt8Atom() def _tobuffer(self, object_): if isinstance(object_, str): warnings.warn("Storing non bytestrings in VLStringAtom is " "deprecated.", DeprecationWarning) elif not isinstance(object_, bytes): raise TypeError(f"object is not a string: {object_!r}") return np.string_(object_) def fromarray(self, array): return array.tobytes() class VLUnicodeAtom(_BufferedAtom): """Defines an atom of type vlunicode. This class describes a *row* of the VLArray class, rather than an atom. It is very similar to VLStringAtom (see :ref:`VLStringAtom`), but it stores Unicode strings (using 32-bit characters a la UCS-4, so all strings of the same length also take up the same space). This class does not make assumptions on the encoding of plain input strings. Plain strings are supported as long as no character is out of the ASCII set; otherwise, you will need to *explicitly* convert them to Unicode before you can save them. Variable-length Unicode atoms do not accept parameters and they cause the reads of rows to always return Python Unicode strings. You can regard vlunicode atoms as an easy way to save variable length Unicode strings. """ kind = 'vlunicode' type = 'vlunicode' base = UInt32Atom() # numpy.unicode_ no more implements the buffer interface in Python 3 # # When the Python build is UCS-2, we need to promote the # Unicode string to UCS-4. We *must* use a 0-d array since # NumPy scalars inherit the UCS-2 encoding from Python (see # NumPy ticket #525). Since ``_tobuffer()`` can't return an # array, we must override ``toarray()`` itself. def toarray(self, object_): if isinstance(object_, bytes): warnings.warn("Storing bytestrings in VLUnicodeAtom is " "deprecated.", DeprecationWarning) elif not isinstance(object_, str): raise TypeError(f"object is not a string: {object_!r}") ustr = str(object_) uarr = np.array(ustr, dtype='U') return np.ndarray( buffer=uarr, dtype=self.base.dtype, shape=len(ustr)) def _tobuffer(self, object_): # This works (and is used) only with UCS-4 builds of Python, # where the width of the internal representation of a # character matches that of the base atoms. if isinstance(object_, bytes): warnings.warn("Storing bytestrings in VLUnicodeAtom is " "deprecated.", DeprecationWarning) elif not isinstance(object_, str): raise TypeError(f"object is not a string: {object_!r}") return np.unicode_(object_) def fromarray(self, array): length = len(array) if length == 0: return '' # ``array.view('U0')`` raises a `TypeError` return array.view('U%d' % length).item() class ObjectAtom(_BufferedAtom): """Defines an atom of type object. This class is meant to fit *any* kind of Python object in a row of a VLArray dataset by using pickle behind the scenes. Due to the fact that you can not foresee how long will be the output of the pickle serialization (i.e. the atom already has a *variable* length), you can only fit *one object per row*. However, you can still group several objects in a single tuple or list and pass it to the :meth:`VLArray.append` method. Object atoms do not accept parameters and they cause the reads of rows to always return Python objects. You can regard object atoms as an easy way to save an arbitrary number of generic Python objects in a VLArray dataset. """ kind = 'object' type = 'object' base = UInt8Atom() def _tobuffer(self, object_): return pickle.dumps(object_, pickle.HIGHEST_PROTOCOL) def fromarray(self, array): # We have to check for an empty array because of a possible # bug in HDF5 which makes it claim that a dataset has one # record when in fact it is empty. if array.size == 0: return None return pickle.loads(array.tobytes()) PyTables-3.7.0/tables/attributeset.py000066400000000000000000000626461416254111300176230ustar00rootroot00000000000000"""Here is defined the AttributeSet class.""" import re import sys import warnings import pickle import numpy as np from . import hdf5extension from .utils import SizeType from .registry import class_name_dict from .exceptions import ClosedNodeError, PerformanceWarning from .path import check_attribute_name from .undoredo import attr_to_shadow from .filters import Filters # System attributes SYS_ATTRS = ["CLASS", "VERSION", "TITLE", "NROWS", "EXTDIM", "ENCODING", "PYTABLES_FORMAT_VERSION", "FLAVOR", "FILTERS", "AUTO_INDEX", "DIRTY", "NODE_TYPE", "NODE_TYPE_VERSION", "PSEUDOATOM"] # Prefixes of other system attributes SYS_ATTRS_PREFIXES = ["FIELD_"] # RO_ATTRS will be disabled and let the user modify them if they # want to. The user is still not allowed to remove or rename # system attributes. Francesc Alted 2004-12-19 # Read-only attributes: # RO_ATTRS = ["CLASS", "FLAVOR", "VERSION", "NROWS", "EXTDIM", # "PYTABLES_FORMAT_VERSION", "FILTERS", # "NODE_TYPE", "NODE_TYPE_VERSION"] # RO_ATTRS = [] # The next attributes are not meant to be copied during a Node copy process SYS_ATTRS_NOTTOBECOPIED = ["CLASS", "VERSION", "TITLE", "NROWS", "EXTDIM", "PYTABLES_FORMAT_VERSION", "FILTERS", "ENCODING"] # Attributes forced to be copied during node copies FORCE_COPY_CLASS = ['CLASS', 'VERSION'] # Regular expression for column default values. _field_fill_re = re.compile('^FIELD_[0-9]+_FILL$') # Regular expression for fixing old pickled filters. _old_filters_re = re.compile(br'\(([ic])tables\.Leaf\n') # Fixed version of the previous string. _new_filters_sub = br'(\1tables.filters\n' def issysattrname(name): """Check if a name is a system attribute or not""" return bool(name in SYS_ATTRS or np.prod( [name.startswith(prefix) for prefix in SYS_ATTRS_PREFIXES])) class AttributeSet(hdf5extension.AttributeSet): """Container for the HDF5 attributes of a Node. This class provides methods to create new HDF5 node attributes, and to get, rename or delete existing ones. Like in Group instances (see :ref:`GroupClassDescr`), AttributeSet instances make use of the *natural naming* convention, i.e. you can access the attributes on disk as if they were normal Python attributes of the AttributeSet instance. This offers the user a very convenient way to access HDF5 node attributes. However, for this reason and in order not to pollute the object namespace, one can not assign *normal* attributes to AttributeSet instances, and their members use names which start by special prefixes as happens with Group objects. .. rubric:: Notes on native and pickled attributes The values of most basic types are saved as HDF5 native data in the HDF5 file. This includes Python bool, int, float, complex and str (but not long nor unicode) values, as well as their NumPy scalar versions and homogeneous or *structured* NumPy arrays of them. When read, these values are always loaded as NumPy scalar or array objects, as needed. For that reason, attributes in native HDF5 files will be always mapped into NumPy objects. Specifically, a multidimensional attribute will be mapped into a multidimensional ndarray and a scalar will be mapped into a NumPy scalar object (for example, a scalar H5T_NATIVE_LLONG will be read and returned as a numpy.int64 scalar). However, other kinds of values are serialized using pickle, so you only will be able to correctly retrieve them using a Python-aware HDF5 library. Thus, if you want to save Python scalar values and make sure you are able to read them with generic HDF5 tools, you should make use of *scalar or homogeneous/structured array NumPy objects* (for example, numpy.int64(1) or numpy.array([1, 2, 3], dtype='int16')). One more advice: because of the various potential difficulties in restoring a Python object stored in an attribute, you may end up getting a pickle string where a Python object is expected. If this is the case, you may wish to run pickle.loads() on that string to get an idea of where things went wrong, as shown in this example:: >>> import os, tempfile >>> import tables as tb >>> >>> class MyClass: ... foo = 'bar' ... >>> myObject = MyClass() # save object of custom class in HDF5 attr >>> h5fname = tempfile.mktemp(suffix='.h5') >>> h5f = tb.open_file(h5fname, 'w') >>> h5f.root._v_attrs.obj = myObject # store the object >>> print(h5f.root._v_attrs.obj.foo) # retrieve it bar >>> h5f.close() >>> >>> del MyClass, myObject # delete class of object and reopen file >>> h5f = tb.open_file(h5fname, 'r') >>> print(repr(h5f.root._v_attrs.obj)) b'ccopy_reg\\n_reconstructor... >>> import pickle # let's unpickle that to see what went wrong >>> pickle.loads(h5f.root._v_attrs.obj) Traceback (most recent call last): ... AttributeError: Can't get attribute 'MyClass' ... >>> # So the problem was not in the stored object, ... # but in the *environment* where it was restored. ... h5f.close() >>> os.remove(h5fname) .. rubric:: Notes on AttributeSet methods Note that this class overrides the __getattr__(), __setattr__(), __delattr__() and __dir__() special methods. This allows you to read, assign or delete attributes on disk by just using the next constructs:: leaf.attrs.myattr = 'str attr' # set a string (native support) leaf.attrs.myattr2 = 3 # set an integer (native support) leaf.attrs.myattr3 = [3, (1, 2)] # a generic object (Pickled) attrib = leaf.attrs.myattr # get the attribute ``myattr`` del leaf.attrs.myattr # delete the attribute ``myattr`` In addition, the dictionary-like __getitem__(), __setitem__() and __delitem__() methods are available, so you may write things like this:: for name in node._v_attrs._f_list(): print("name: %s, value: %s" % (name, node._v_attrs[name])) Use whatever idiom you prefer to access the attributes. Finally, on interactive python sessions you may get autocompletions of attributes named as *valid python identifiers* by pressing the `[Tab]` key, or to use the dir() global function. If an attribute is set on a target node that already has a large number of attributes, a PerformanceWarning will be issued. .. rubric:: AttributeSet attributes .. attribute:: _v_attrnames A list with all attribute names. .. attribute:: _v_attrnamessys A list with system attribute names. .. attribute:: _v_attrnamesuser A list with user attribute names. .. attribute:: _v_unimplemented A list of attribute names with unimplemented native HDF5 types. """ def _g_getnode(self): return self._v__nodefile._get_node(self._v__nodepath) @property def _v_node(self): """The :class:`Node` instance this attribute set is associated with.""" return self._g_getnode() def __init__(self, node): """Create the basic structures to keep the attribute information. Reads all the HDF5 attributes (if any) on disk for the node "node". Parameters ---------- node The parent node """ # Refuse to create an instance of an already closed node if not node._v_isopen: raise ClosedNodeError("the node for attribute set is closed") dict_ = self.__dict__ self._g_new(node) dict_["_v__nodefile"] = node._v_file dict_["_v__nodepath"] = node._v_pathname dict_["_v_attrnames"] = self._g_list_attr(node) # The list of unimplemented attribute names dict_["_v_unimplemented"] = [] # Get the file version format. This is an optimization # in order to avoid accessing it too much. try: format_version = node._v_file.format_version except AttributeError: parsed_version = None else: if format_version == 'unknown': parsed_version = None else: parsed_version = tuple(map(int, format_version.split('.'))) dict_["_v__format_version"] = parsed_version # Split the attribute list in system and user lists dict_["_v_attrnamessys"] = [] dict_["_v_attrnamesuser"] = [] for attr in self._v_attrnames: # put the attributes on the local dictionary to allow # tab-completion self.__getattr__(attr) if issysattrname(attr): self._v_attrnamessys.append(attr) else: self._v_attrnamesuser.append(attr) # Sort the attributes self._v_attrnames.sort() self._v_attrnamessys.sort() self._v_attrnamesuser.sort() def _g_update_node_location(self, node): """Updates the location information about the associated `node`.""" dict_ = self.__dict__ dict_['_v__nodefile'] = node._v_file dict_['_v__nodepath'] = node._v_pathname # hdf5extension operations: self._g_new(node) def _f_list(self, attrset='user'): """Get a list of attribute names. The attrset string selects the attribute set to be used. A 'user' value returns only user attributes (this is the default). A 'sys' value returns only system attributes. Finally, 'all' returns both system and user attributes. """ if attrset == "user": return self._v_attrnamesuser[:] elif attrset == "sys": return self._v_attrnamessys[:] elif attrset == "all": return self._v_attrnames[:] def __dir__(self): """Autocomplete only children named as valid python identifiers. Only PY3 supports this special method. """ return list({c for c in super().__dir__() + self._v_attrnames if c.isidentifier()}) def __getattr__(self, name): """Get the attribute named "name".""" # If attribute does not exist, raise AttributeError if name not in self._v_attrnames: raise AttributeError(f"Attribute {name!r} does not exist " f"in node: {self._v__nodepath!r}") # Read the attribute from disk. This is an optimization to read # quickly system attributes that are _string_ values, but it # takes care of other types as well as for example NROWS for # Tables and EXTDIM for EArrays format_version = self._v__format_version value = self._g_getattr(self._v_node, name) # Check whether the value is pickled # Pickled values always seems to end with a "." maybe_pickled = ( isinstance(value, np.generic) and # NumPy scalar? value.dtype.type == np.bytes_ and # string type? value.itemsize > 0 and value.endswith(b'.')) if (maybe_pickled and value in [b"0", b"0."]): # Workaround for a bug in many versions of Python (starting # somewhere after Python 2.6.1). See ticket #253. retval = value elif (maybe_pickled and _field_fill_re.match(name) and format_version == (1, 5)): # This format was used during the first 1.2 releases, just # for string defaults. try: retval = pickle.loads(value) retval = np.array(retval) except ImportError: retval = None # signal error avoiding exception elif (maybe_pickled and name == 'FILTERS' and format_version is not None and format_version < (2, 0)): # This is a big hack, but we don't have other way to recognize # pickled filters of PyTables 1.x files. value = _old_filters_re.sub(_new_filters_sub, value, 1) retval = pickle.loads(value) # pass unpickling errors through elif maybe_pickled: try: retval = pickle.loads(value) # except cPickle.UnpicklingError: # It seems that pickle may raise other errors than UnpicklingError # Perhaps it would be better just an "except:" clause? # except (cPickle.UnpicklingError, ImportError): # Definitely (see SF bug #1254636) except UnicodeDecodeError: # Object maybe pickled on python 2 and unpickled on python 3. # encoding='bytes' was added in python 3.4 to resolve this. # However 'bytes' mangles class attributes as they are # unplicked as bytestrings. Hence try 'latin1' first. # Ref: http://bugs.python.org/issue6784 try: retval = pickle.loads(value, encoding='latin1') except TypeError: try: retval = pickle.loads(value, encoding='bytes') except Exception: retval = value except Exception: retval = value except Exception: # catch other unpickling errors: # ivb (2005-09-07): It is too hard to tell # whether the unpickling failed # because of the string not being a pickle one at all, # because of a malformed pickle string, # or because of some other problem in object reconstruction, # thus making inconvenient even the issuing of a warning here. # The documentation contains a note on this issue, # explaining how the user can tell where the problem was. retval = value # Additional check for allowing a workaround for #307 if isinstance(retval, str) and retval == '': retval = np.array(retval)[()] elif (name == 'FILTERS' and format_version is not None and format_version >= (2, 0)): try: retval = Filters._unpack(value) except ValueError: sys.stderr.write('Failed parsing FILTERS key\n') sys.stderr.flush() retval = None elif name == 'TITLE' and not isinstance(value, str): retval = value.decode('utf-8') elif (issysattrname(name) and isinstance(value, (bytes, str)) and not isinstance(value, str) and not _field_fill_re.match(name)): # system attributes should always be str # python 3, bytes and not "FIELD_[0-9]+_FILL" retval = value.decode('utf-8') else: retval = value # Put this value in local directory self.__dict__[name] = retval return retval def _g__setattr(self, name, value): """Set a PyTables attribute. Sets a (maybe new) PyTables attribute with the specified `name` and `value`. If the attribute already exists, it is simply replaced. It does not log the change. """ # Save this attribute to disk # (overwriting an existing one if needed) stvalue = value if issysattrname(name): if name in ["EXTDIM", "AUTO_INDEX", "DIRTY", "NODE_TYPE_VERSION"]: stvalue = np.array(value, dtype=np.int32) value = stvalue[()] elif name == "NROWS": stvalue = np.array(value, dtype=SizeType) value = stvalue[()] elif (name == "FILTERS" and self._v__format_version is not None and self._v__format_version >= (2, 0)): stvalue = value._pack() # value will remain as a Filters instance here # Convert value from a Python scalar into a NumPy scalar # (only in case it has not been converted yet) # Fixes ticket #59 if (stvalue is value and type(value) in (bool, bytes, int, float, complex, str, np.unicode_)): # Additional check for allowing a workaround for #307 if isinstance(value, str) and len(value) == 0: stvalue = np.array('') else: stvalue = np.array(value) value = stvalue[()] self._g_setattr(self._v_node, name, stvalue) # New attribute or value. Introduce it into the local # directory self.__dict__[name] = value # Finally, add this attribute to the list if not present attrnames = self._v_attrnames if name not in attrnames: attrnames.append(name) attrnames.sort() if issysattrname(name): attrnamessys = self._v_attrnamessys attrnamessys.append(name) attrnamessys.sort() else: attrnamesuser = self._v_attrnamesuser attrnamesuser.append(name) attrnamesuser.sort() def __setattr__(self, name, value): """Set a PyTables attribute. Sets a (maybe new) PyTables attribute with the specified `name` and `value`. If the attribute already exists, it is simply replaced. A ``ValueError`` is raised when the name starts with a reserved prefix or contains a ``/``. A `NaturalNameWarning` is issued if the name is not a valid Python identifier. A `PerformanceWarning` is issued when the recommended maximum number of attributes in a node is going to be exceeded. """ nodefile = self._v__nodefile attrnames = self._v_attrnames # Check for name validity check_attribute_name(name) nodefile._check_writable() # Check if there are too many attributes. max_node_attrs = nodefile.params['MAX_NODE_ATTRS'] if len(attrnames) >= max_node_attrs: warnings.warn("""\ node ``%s`` is exceeding the recommended maximum number of attributes (%d);\ be ready to see PyTables asking for *lots* of memory and possibly slow I/O""" % (self._v__nodepath, max_node_attrs), PerformanceWarning) undo_enabled = nodefile.is_undo_enabled() # Log old attribute removal (if any). if undo_enabled and (name in attrnames): self._g_del_and_log(name) # Set the attribute. self._g__setattr(name, value) # Log new attribute addition. if undo_enabled: self._g_log_add(name) def _g_log_add(self, name): self._v__nodefile._log('ADDATTR', self._v__nodepath, name) def _g_del_and_log(self, name): nodefile = self._v__nodefile node_pathname = self._v__nodepath # Log *before* moving to use the right shadow name. nodefile._log('DELATTR', node_pathname, name) attr_to_shadow(nodefile, node_pathname, name) def _g__delattr(self, name): """Delete a PyTables attribute. Deletes the specified existing PyTables attribute. It does not log the change. """ # Delete the attribute from disk self._g_remove(self._v_node, name) # Delete the attribute from local lists self._v_attrnames.remove(name) if name in self._v_attrnamessys: self._v_attrnamessys.remove(name) else: self._v_attrnamesuser.remove(name) # Delete the attribute from the local directory # closes (#1049285) del self.__dict__[name] def __delattr__(self, name): """Delete a PyTables attribute. Deletes the specified existing PyTables attribute from the attribute set. If a nonexistent or system attribute is specified, an ``AttributeError`` is raised. """ nodefile = self._v__nodefile # Check if attribute exists if name not in self._v_attrnames: raise AttributeError( "Attribute ('%s') does not exist in node '%s'" % (name, self._v__nodepath)) nodefile._check_writable() # Remove the PyTables attribute or move it to shadow. if nodefile.is_undo_enabled(): self._g_del_and_log(name) else: self._g__delattr(name) def __getitem__(self, name): """The dictionary like interface for __getattr__().""" try: return self.__getattr__(name) except AttributeError: # Capture the AttributeError an re-raise a KeyError one raise KeyError( "Attribute ('%s') does not exist in node '%s'" % (name, self._v__nodepath)) def __setitem__(self, name, value): """The dictionary like interface for __setattr__().""" self.__setattr__(name, value) def __delitem__(self, name): """The dictionary like interface for __delattr__().""" try: self.__delattr__(name) except AttributeError: # Capture the AttributeError an re-raise a KeyError one raise KeyError( "Attribute ('%s') does not exist in node '%s'" % (name, self._v__nodepath)) def __contains__(self, name): """Is there an attribute with that name? A true value is returned if the attribute set has an attribute with the given name, false otherwise. """ return name in self._v_attrnames def _f_rename(self, oldattrname, newattrname): """Rename an attribute from oldattrname to newattrname.""" if oldattrname == newattrname: # Do nothing return # First, fetch the value of the oldattrname attrvalue = getattr(self, oldattrname) # Now, create the new attribute setattr(self, newattrname, attrvalue) # Finally, remove the old attribute delattr(self, oldattrname) def _g_copy(self, newset, set_attr=None, copyclass=False): """Copy set attributes. Copies all user and allowed system PyTables attributes to the given attribute set, replacing the existing ones. You can specify a *bound* method of the destination set that will be used to set its attributes. Else, its `_g__setattr` method will be used. Changes are logged depending on the chosen setting method. The default setting method does not log anything. .. versionchanged:: 3.0 The *newSet* parameter has been renamed into *newset*. .. versionchanged:: 3.0 The *copyClass* parameter has been renamed into *copyclass*. """ copysysattrs = newset._v__nodefile.params['PYTABLES_SYS_ATTRS'] if set_attr is None: set_attr = newset._g__setattr for attrname in self._v_attrnamesuser: # Do not copy the unimplemented attributes. if attrname not in self._v_unimplemented: set_attr(attrname, getattr(self, attrname)) # Copy the system attributes that we are allowed to. if copysysattrs: for attrname in self._v_attrnamessys: if ((attrname not in SYS_ATTRS_NOTTOBECOPIED) and # Do not copy the FIELD_ attributes in tables as this can # be really *slow* (don't know exactly the reason). # See #304. not attrname.startswith("FIELD_")): set_attr(attrname, getattr(self, attrname)) # Copy CLASS and VERSION attributes if requested if copyclass: for attrname in FORCE_COPY_CLASS: if attrname in self._v_attrnamessys: set_attr(attrname, getattr(self, attrname)) def _f_copy(self, where): """Copy attributes to the where node. Copies all user and certain system attributes to the given where node (a Node instance - see :ref:`NodeClassDescr`), replacing the existing ones. """ # AttributeSet must be defined in order to define a Node. # However, we need to know Node here. # Using class_name_dict avoids a circular import. if not isinstance(where, class_name_dict['Node']): raise TypeError(f"destination object is not a node: {where!r}") self._g_copy(where._v_attrs, where._v_attrs.__setattr__) def _g_close(self): # Nothing will be done here, as the existing instance is completely # operative now. pass def __str__(self): """The string representation for this object.""" # The pathname pathname = self._v__nodepath # Get this class name classname = self.__class__.__name__ # The attribute names attrnumber = sum(1 for _ in self._v_attrnames) return f"{pathname}._v_attrs ({classname}), {attrnumber} attributes" def __repr__(self): """A detailed string representation for this object.""" # print additional info only if there are attributes to show attrnames = list(self._v_attrnames) if attrnames: rep = [f'{attr} := {getattr(self, attr)!r}' for attr in attrnames] return f"{self!s}:\n [" + ',\n '.join(rep) + "]" else: return str(self) class NotLoggedAttributeSet(AttributeSet): def _g_log_add(self, name): pass def _g_del_and_log(self, name): self._g__delattr(name) PyTables-3.7.0/tables/carray.py000066400000000000000000000241361416254111300163550ustar00rootroot00000000000000"""Here is defined the CArray class.""" import sys import numpy as np from .atom import Atom from .array import Array from .utils import correct_byteorder, SizeType # default version for CARRAY objects # obversion = "1.0" # Support for time & enumerated datatypes. obversion = "1.1" # Numeric and numarray flavors are gone. class CArray(Array): """This class represents homogeneous datasets in an HDF5 file. The difference between a CArray and a normal Array (see :ref:`ArrayClassDescr`), from which it inherits, is that a CArray has a chunked layout and, as a consequence, it supports compression. You can use datasets of this class to easily save or load arrays to or from disk, with compression support included. CArray includes all the instance variables and methods of Array. Only those with different behavior are mentioned here. Parameters ---------- parentnode The parent :class:`Group` object. .. versionchanged:: 3.0 Renamed from *parentNode* to *parentnode*. name : str The name of this node in its parent group. atom An `Atom` instance representing the *type* and *shape* of the atomic objects to be saved. shape The shape of the new array. title A description for this node (it sets the ``TITLE`` HDF5 attribute on disk). filters An instance of the `Filters` class that provides information about the desired I/O filters to be applied during the life of this object. chunkshape The shape of the data chunk to be read or written in a single HDF5 I/O operation. Filters are applied to those chunks of data. The dimensionality of `chunkshape` must be the same as that of `shape`. If ``None``, a sensible value is calculated (which is recommended). byteorder The byteorder of the data *on disk*, specified as 'little' or 'big'. If this is not specified, the byteorder is that of the platform. track_times Whether time data associated with the leaf are recorded (object access time, raw data modification time, metadata change time, object birth time); default True. Semantics of these times depend on their implementation in the HDF5 library: refer to documentation of the H5O_info_t data structure. As of HDF5 1.8.15, only ctime (metadata change time) is implemented. .. versionadded:: 3.4.3 Examples -------- See below a small example of the use of the `CArray` class. The code is available in ``examples/carray1.py``:: import numpy as np import tables as tb fileName = 'carray1.h5' shape = (200, 300) atom = tb.UInt8Atom() filters = tb.Filters(complevel=5, complib='zlib') h5f = tb.open_file(fileName, 'w') ca = h5f.create_carray(h5f.root, 'carray', atom, shape, filters=filters) # Fill a hyperslab in ``ca``. ca[10:60, 20:70] = np.ones((50, 50)) h5f.close() # Re-open a read another hyperslab h5f = tb.open_file(fileName) print(h5f) print(h5f.root.carray[8:12, 18:22]) h5f.close() The output for the previous script is something like:: carray1.h5 (File) '' Last modif.: 'Thu Apr 12 10:15:38 2007' Object Tree: / (RootGroup) '' /carray (CArray(200, 300), shuffle, zlib(5)) '' [[0 0 0 0] [0 0 0 0] [0 0 1 1] [0 0 1 1]] """ # Class identifier. _c_classid = 'CARRAY' def __init__(self, parentnode, name, atom=None, shape=None, title="", filters=None, chunkshape=None, byteorder=None, _log=True, track_times=True): self.atom = atom """An `Atom` instance representing the shape, type of the atomic objects to be saved. """ self.shape = None """The shape of the stored array.""" self.extdim = -1 # `CArray` objects are not enlargeable by default """The index of the enlargeable dimension.""" # Other private attributes self._v_version = None """The object version of this array.""" self._v_new = new = atom is not None """Is this the first time the node has been created?""" self._v_new_title = title """New title for this node.""" self._v_convert = True """Whether the ``Array`` object must be converted or not.""" self._v_chunkshape = chunkshape """Private storage for the `chunkshape` property of the leaf.""" # Miscellaneous iteration rubbish. self._start = None """Starting row for the current iteration.""" self._stop = None """Stopping row for the current iteration.""" self._step = None """Step size for the current iteration.""" self._nrowsread = None """Number of rows read up to the current state of iteration.""" self._startb = None """Starting row for current buffer.""" self._stopb = None """Stopping row for current buffer. """ self._row = None """Current row in iterators (sentinel).""" self._init = False """Whether we are in the middle of an iteration or not (sentinel).""" self.listarr = None """Current buffer in iterators.""" if new: if not isinstance(atom, Atom): raise ValueError("atom parameter should be an instance of " "tables.Atom and you passed a %s." % type(atom)) if shape is None: raise ValueError("you must specify a non-empty shape") try: shape = tuple(shape) except TypeError: raise TypeError("`shape` parameter must be a sequence " "and you passed a %s" % type(shape)) self.shape = tuple(SizeType(s) for s in shape) if chunkshape is not None: try: chunkshape = tuple(chunkshape) except TypeError: raise TypeError( "`chunkshape` parameter must be a sequence " "and you passed a %s" % type(chunkshape)) if len(shape) != len(chunkshape): raise ValueError(f"the shape ({shape}) and chunkshape " f"({chunkshape}) ranks must be equal.") elif min(chunkshape) < 1: raise ValueError("chunkshape parameter cannot have " "zero-dimensions.") self._v_chunkshape = tuple(SizeType(s) for s in chunkshape) # The `Array` class is not abstract enough! :( super(Array, self).__init__(parentnode, name, new, filters, byteorder, _log, track_times) def _g_create(self): """Create a new array in file (specific part).""" if min(self.shape) < 1: raise ValueError( "shape parameter cannot have zero-dimensions.") # Finish the common part of creation process return self._g_create_common(self.nrows) def _g_create_common(self, expectedrows): """Create a new array in file (common part).""" self._v_version = obversion if self._v_chunkshape is None: # Compute the optimal chunk size self._v_chunkshape = self._calc_chunkshape( expectedrows, self.rowsize, self.atom.size) # Compute the optimal nrowsinbuf self.nrowsinbuf = self._calc_nrowsinbuf() # Correct the byteorder if needed if self.byteorder is None: self.byteorder = correct_byteorder(self.atom.type, sys.byteorder) try: # ``self._v_objectid`` needs to be set because would be # needed for setting attributes in some descendants later # on self._v_objectid = self._create_carray(self._v_new_title) except Exception: # XXX # Problems creating the Array on disk. Close node and re-raise. self.close(flush=0) raise return self._v_objectid def _g_copy_with_stats(self, group, name, start, stop, step, title, filters, chunkshape, _log, **kwargs): """Private part of Leaf.copy() for each kind of leaf.""" (start, stop, step) = self._process_range_read(start, stop, step) maindim = self.maindim shape = list(self.shape) shape[maindim] = len(range(start, stop, step)) # Now, fill the new carray with values from source nrowsinbuf = self.nrowsinbuf # The slices parameter for self.__getitem__ slices = [slice(0, dim, 1) for dim in self.shape] # This is a hack to prevent doing unnecessary conversions # when copying buffers self._v_convert = False # Build the new CArray object object = CArray(group, name, atom=self.atom, shape=shape, title=title, filters=filters, chunkshape=chunkshape, _log=_log) # Start the copy itself for start2 in range(start, stop, step * nrowsinbuf): # Save the records on disk stop2 = start2 + step * nrowsinbuf if stop2 > stop: stop2 = stop # Set the proper slice in the main dimension slices[maindim] = slice(start2, stop2, step) start3 = (start2 - start) // step stop3 = start3 + nrowsinbuf if stop3 > shape[maindim]: stop3 = shape[maindim] # The next line should be generalised if, in the future, # maindim is designed to be different from 0 in CArrays. # See ticket #199. object[start3:stop3] = self.__getitem__(tuple(slices)) # Activate the conversion again (default) self._v_convert = True nbytes = np.prod(self.shape, dtype=SizeType) * self.atom.size return (object, nbytes) PyTables-3.7.0/tables/conditions.py000066400000000000000000000367301416254111300172500ustar00rootroot00000000000000"""Utility functions and classes for supporting query conditions. Classes: `CompileCondition` Container for a compiled condition. Functions: `compile_condition` Compile a condition and extract usable index conditions. `call_on_recarr` Evaluate a function over a structured array. """ import re import numexpr as ne from .utilsextension import get_nested_field from .utils import lazyattr _no_matching_opcode = re.compile(r"[^a-z]([a-z]+)_([a-z]+)[^a-z]") # E.g. "gt" and "bfc" from "couldn't find matching opcode for 'gt_bfc'". def _unsupported_operation_error(exception): """Make the \"no matching opcode\" Numexpr `exception` more clear. A new exception of the same kind is returned. """ message = exception.args[0] op, types = _no_matching_opcode.search(message).groups() newmessage = "unsupported operand types for *%s*: " % op newmessage += ', '.join( ne.necompiler.typecode_to_kind[t] for t in types[1:]) return exception.__class__(newmessage) def _check_indexable_cmp(getidxcmp): """Decorate `getidxcmp` to check the returned indexable comparison. This does some extra checking that Numexpr would perform later on the comparison if it was compiled within a complete condition. """ def newfunc(exprnode, indexedcols): result = getidxcmp(exprnode, indexedcols) if result[0] is not None: try: ne.necompiler.typeCompileAst( ne.necompiler.expressionToAST(exprnode)) except NotImplementedError as nie: # Try to make this Numexpr error less cryptic. raise _unsupported_operation_error(nie) return result newfunc.__name__ = getidxcmp.__name__ newfunc.__doc__ = getidxcmp.__doc__ return newfunc @_check_indexable_cmp def _get_indexable_cmp(exprnode, indexedcols): """Get the indexable variable-constant comparison in `exprnode`. A tuple of (variable, operation, constant) is returned if `exprnode` is a variable-constant (or constant-variable) comparison, and the variable is in `indexedcols`. A normal variable can also be used instead of a constant: a tuple with its name will appear instead of its value. Otherwise, the values in the tuple are ``None``. """ not_indexable = (None, None, None) turncmp = {'lt': 'gt', 'le': 'ge', 'eq': 'eq', 'ge': 'le', 'gt': 'lt', } def get_cmp(var, const, op): var_value, const_value = var.value, const.value if (var.astType == 'variable' and var_value in indexedcols and const.astType in ['constant', 'variable']): if const.astType == 'variable': const_value = (const_value, ) return (var_value, op, const_value) return None def is_indexed_boolean(node): return (node.astType == 'variable' and node.astKind == 'bool' and node.value in indexedcols) # Boolean variables are indexable by themselves. if is_indexed_boolean(exprnode): return (exprnode.value, 'eq', True) # And so are negations of boolean variables. if exprnode.astType == 'op' and exprnode.value == 'invert': child = exprnode.children[0] if is_indexed_boolean(child): return (child.value, 'eq', False) # A negation of an expression will be returned as ``~child``. # The indexability of the negated expression will be decided later on. if child.astKind == "bool": return (child, 'invert', None) # Check node type. Only comparisons are indexable from now on. if exprnode.astType != 'op': return not_indexable cmpop = exprnode.value if cmpop not in turncmp: return not_indexable # Look for a variable-constant comparison in both directions. left, right = exprnode.children cmp_ = get_cmp(left, right, cmpop) if cmp_: return cmp_ cmp_ = get_cmp(right, left, turncmp[cmpop]) if cmp_: return cmp_ return not_indexable def _equiv_expr_node(x, y): """Returns whether two ExpressionNodes are equivalent. This is needed because '==' is overridden on ExpressionNode to return a new ExpressionNode. """ if (not isinstance(x, ne.expressions.ExpressionNode) and not isinstance(y, ne.expressions.ExpressionNode)): return x == y elif (type(x) is not type(y) or not isinstance(x, ne.expressions.ExpressionNode) or not isinstance(y, ne.expressions.ExpressionNode) or x.value != y.value or x.astKind != y.astKind or len(x.children) != len(y.children)): return False for xchild, ychild in zip(x.children, y.children): if not _equiv_expr_node(xchild, ychild): return False return True def _get_idx_expr_recurse(exprnode, indexedcols, idxexprs, strexpr): """Here lives the actual implementation of the get_idx_expr() wrapper. 'idxexprs' is a list of expressions in the form ``(var, (ops), (limits))``. 'strexpr' is the indexable expression in string format. These parameters will be received empty (i.e. [], ['']) for the first time and populated during the different recursive calls. Finally, they are returned in the last level to the original wrapper. If 'exprnode' is not indexable, it will return the tuple ([], ['']) so as to signal this. """ not_indexable = ([], ['']) op_conv = { 'and': '&', 'or': '|', 'not': '~', } negcmp = { 'lt': 'ge', 'le': 'gt', 'ge': 'lt', 'gt': 'le', } def fix_invert(idxcmp, exprnode, indexedcols): invert = False # Loop until all leading negations have been dealt with while idxcmp[1] == "invert": invert ^= True # The information about the negated node is in first position exprnode = idxcmp[0] idxcmp = _get_indexable_cmp(exprnode, indexedcols) return idxcmp, exprnode, invert # Indexable variable-constant comparison. idxcmp = _get_indexable_cmp(exprnode, indexedcols) idxcmp, exprnode, invert = fix_invert(idxcmp, exprnode, indexedcols) if idxcmp[0]: if invert: var, op, value = idxcmp if op == 'eq' and value in [True, False]: # ``var`` must be a boolean index. Flip its value. value ^= True else: op = negcmp[op] expr = (var, (op,), (value,)) invert = False else: expr = (idxcmp[0], (idxcmp[1],), (idxcmp[2],)) return [expr] # For now negations of complex expressions will be not supported as # forming part of an indexable condition. This might be supported in # the future. if invert: return not_indexable # Only conjunctions and disjunctions of comparisons are considered # for the moment. if exprnode.astType != 'op' or exprnode.value not in ['and', 'or']: return not_indexable left, right = exprnode.children # Get the expression at left lcolvar, lop, llim = _get_indexable_cmp(left, indexedcols) # Get the expression at right rcolvar, rop, rlim = _get_indexable_cmp(right, indexedcols) # Use conjunction of indexable VC comparisons like # ``(a <[=] x) & (x <[=] b)`` or ``(a >[=] x) & (x >[=] b)`` # as ``a <[=] x <[=] b``, for the moment. op = exprnode.value if (lcolvar is not None and rcolvar is not None and _equiv_expr_node(lcolvar, rcolvar) and op == 'and'): if lop in ['gt', 'ge'] and rop in ['lt', 'le']: # l <= x <= r expr = (lcolvar, (lop, rop), (llim, rlim)) return [expr] if lop in ['lt', 'le'] and rop in ['gt', 'ge']: # l >= x >= r expr = (rcolvar, (rop, lop), (rlim, llim)) return [expr] # Recursively get the expressions at the left and the right lexpr = _get_idx_expr_recurse(left, indexedcols, idxexprs, strexpr) rexpr = _get_idx_expr_recurse(right, indexedcols, idxexprs, strexpr) def add_expr(expr, idxexprs, strexpr): """Add a single expression to the list.""" if isinstance(expr, list): # expr is a single expression idxexprs.append(expr[0]) lenexprs = len(idxexprs) # Mutate the strexpr string if lenexprs == 1: strexpr[:] = ["e0"] else: strexpr[:] = [ "(%s %s e%d)" % (strexpr[0], op_conv[op], lenexprs - 1)] # Add expressions to the indexable list when they are and'ed, or # they are both indexable. if lexpr != not_indexable and (op == "and" or rexpr != not_indexable): add_expr(lexpr, idxexprs, strexpr) if rexpr != not_indexable: add_expr(rexpr, idxexprs, strexpr) return (idxexprs, strexpr) if rexpr != not_indexable and op == "and": add_expr(rexpr, idxexprs, strexpr) return (idxexprs, strexpr) # Can not use indexed column. return not_indexable def _get_idx_expr(expr, indexedcols): """Extract an indexable expression out of `exprnode`. Looks for variable-constant comparisons in the expression node `exprnode` involving variables in `indexedcols`. It returns a tuple of (idxexprs, strexpr) where 'idxexprs' is a list of expressions in the form ``(var, (ops), (limits))`` and 'strexpr' is the indexable expression in string format. Expressions such as ``0 < c1 <= 1`` do not work as expected. Right now only some of the *indexable comparisons* are considered: * ``a <[=] x``, ``a == x`` and ``a >[=] x`` * ``(a <[=] x) & (y <[=] b)`` and ``(a == x) | (b == y)`` * ``~(~c_bool)``, ``~~c_bool`` and ``~(~c_bool) & (c_extra != 2)`` (where ``a``, ``b`` and ``c_bool`` are indexed columns, but ``c_extra`` is not) Particularly, the ``!=`` operator and negations of complex boolean expressions are *not considered* as valid candidates: * ``a != 1`` and ``c_bool != False`` * ``~((a > 0) & (c_bool))`` """ return _get_idx_expr_recurse(expr, indexedcols, [], ['']) class CompiledCondition: """Container for a compiled condition.""" @lazyattr def index_variables(self): """The columns participating in the index expression.""" idxexprs = self.index_expressions idxvars = [] for expr in idxexprs: idxvar = expr[0] if idxvar not in idxvars: idxvars.append(idxvar) return frozenset(idxvars) def __init__(self, func, params, idxexprs, strexpr, **kwargs): self.function = func """The compiled function object corresponding to this condition.""" self.parameters = params """A list of parameter names for this condition.""" self.index_expressions = idxexprs """A list of expressions in the form ``(var, (ops), (limits))``.""" self.string_expression = strexpr """The indexable expression in string format.""" self.kwargs = kwargs """NumExpr kwargs (used to pass ex_uses_vml to numexpr)""" def __repr__(self): return ("idxexprs: %s\nstrexpr: %s\nidxvars: %s" % (self.index_expressions, self.string_expression, self.index_variables)) def with_replaced_vars(self, condvars): """Replace index limit variables with their values in-place. A new compiled condition is returned. Values are taken from the `condvars` mapping and converted to Python scalars. """ exprs = self.index_expressions exprs2 = [] for expr in exprs: idxlims = expr[2] # the limits are in third place limit_values = [] for idxlim in idxlims: if isinstance(idxlim, tuple): # variable idxlim = condvars[idxlim[0]] # look up value idxlim = idxlim.tolist() # convert back to Python limit_values.append(idxlim) # Add this replaced entry to the new exprs2 var, ops, _ = expr exprs2.append((var, ops, tuple(limit_values))) # Create a new container for the converted values newcc = CompiledCondition( self.function, self.parameters, exprs2, self.string_expression, **self.kwargs) return newcc def _get_variable_names(expression): """Return the list of variable names in the Numexpr `expression`.""" names = [] stack = [expression] while stack: node = stack.pop() if node.astType == 'variable': names.append(node.value) elif hasattr(node, 'children'): stack.extend(node.children) return list(set(names)) # remove repeated names def compile_condition(condition, typemap, indexedcols): """Compile a condition and extract usable index conditions. Looks for variable-constant comparisons in the `condition` string involving the indexed columns whose variable names appear in `indexedcols`. The part of `condition` having usable indexes is returned as a compiled condition in a `CompiledCondition` container. Expressions such as '0 < c1 <= 1' do not work as expected. The Numexpr types of *all* variables must be given in the `typemap` mapping. The ``function`` of the resulting `CompiledCondition` instance is a Numexpr function object, and the ``parameters`` list indicates the order of its parameters. """ # Get the expression tree and extract index conditions. expr = ne.necompiler.stringToExpression(condition, typemap, {}) if expr.astKind != 'bool': raise TypeError("condition ``%s`` does not have a boolean type" % condition) idxexprs = _get_idx_expr(expr, indexedcols) # Post-process the answer if isinstance(idxexprs, list): # Simple expression strexpr = ['e0'] else: # Complex expression idxexprs, strexpr = idxexprs # Get rid of the unneccessary list wrapper for strexpr strexpr = strexpr[0] # Get the variable names used in the condition. # At the same time, build its signature. varnames = _get_variable_names(expr) signature = [(var, typemap[var]) for var in varnames] try: # See the comments in `numexpr.evaluate()` for the # reasons of inserting copy operators for unaligned, # *unidimensional* arrays. func = ne.necompiler.NumExpr(expr, signature) except NotImplementedError as nie: # Try to make this Numexpr error less cryptic. raise _unsupported_operation_error(nie) _, ex_uses_vml = ne.necompiler.getExprNames(condition, {}) kwargs = {'ex_uses_vml': ex_uses_vml} params = varnames # This is more comfortable to handle about than a tuple. return CompiledCondition(func, params, idxexprs, strexpr, **kwargs) def call_on_recarr(func, params, recarr, param2arg=None, **kwargs): """Call `func` with `params` over `recarr`. The `param2arg` function, when specified, is used to get an argument given a parameter name; otherwise, the parameter itself is used as an argument. When the argument is a `Column` object, the proper column from `recarr` is used as its value. """ args = [] for param in params: if param2arg: arg = param2arg(param) else: arg = param if hasattr(arg, 'pathname'): # looks like a column arg = get_nested_field(recarr, arg.pathname) args.append(arg) return func(*args, **kwargs) PyTables-3.7.0/tables/definitions.pxd000066400000000000000000000527651416254111300175630ustar00rootroot00000000000000######################################################################## # # License: BSD # Created: June 20, 2005 # Author: Francesc Alted - faltet@pytables.com # # $Id: definitions.pyd 1018 2005-06-20 09:43:34Z faltet $ # ######################################################################## """Here are some definitions for sharing between extensions.""" import sys cdef extern from *: ctypedef long uintptr_t # Standard C functions. cdef extern from "time.h": ctypedef int time_t from libc.stdio cimport FILE #----------------------------------------------------------------------------- # API for NumPy objects from numpy cimport dtype cdef extern from "numpy/arrayobject.h": object PyArray_Scalar(void *data, dtype descr, object itemsize) #----------------------------------------------------------------------------- # Structs and types from HDF5 cdef extern from "hdf5.h" nogil: ctypedef long long hid_t # In H5Ipublic.h ctypedef int hbool_t ctypedef int herr_t ctypedef int htri_t # hsize_t should be unsigned, but Windows platform does not support # such an unsigned long long type. ctypedef unsigned long long hsize_t ctypedef signed long long hssize_t ctypedef long long int64_t ctypedef unsigned long long haddr_t ctypedef haddr_t hobj_ref_t ctypedef struct hvl_t: size_t len # Length of VL data (in base type units) void *p # Pointer to VL data int H5F_ACC_TRUNC, H5F_ACC_RDONLY, H5F_ACC_RDWR, H5F_ACC_EXCL int H5F_ACC_DEBUG, H5F_ACC_CREAT int H5P_DEFAULT, H5P_DATASET_XFER, H5S_ALL int H5P_FILE_CREATE, H5P_FILE_ACCESS int H5FD_LOG_LOC_WRITE, H5FD_LOG_ALL int H5I_INVALID_HID int H5E_DEFAULT int H5T_STD_REF_OBJ int H5R_OBJ_REF_BUF_SIZE # Library types cdef enum H5I_type_t: H5I_UNINIT = -2 # uninitialized type H5I_BADID = -1 # invalid Type H5I_FILE = 1 # File objects H5I_GROUP = 0 # Group objects H5I_DATATYPE = 1 # Datatype objects H5I_DATASPACE = 2 # Dataspace objects H5I_DATASET = 3 # Dataset objects H5I_ATTR = 4 # Attribute objects H5I_REFERENCE = 5 # Reference objects H5I_VFL = 6 # virtual file layer H5I_GENPROP_CLS = 7 # generic property list classes H5I_GENPROP_LST = 8 # generic property lists H5I_ERROR_CLASS = 9 # error classes H5I_ERROR_MSG = 10 # error messages H5I_ERROR_STACK = 11 # error stacks H5I_NTYPES # Sentinel value - must be last # Reference types cdef enum H5R_type_t: H5R_BADTYPE = -1 # Invalid Reference Type H5R_OBJECT = 0 # Object reference H5R_DATASET_REGION = 1 # Dataset Region Reference H5R_MAXTYPE # Sentinel value - must be last # The difference between a single file and a set of mounted files cdef enum H5F_scope_t: H5F_SCOPE_LOCAL = 0 # specified file handle only H5F_SCOPE_GLOBAL = 1 # entire virtual file H5F_SCOPE_DOWN = 2 # for internal use only cdef enum H5FD_mem_t: H5FD_MEM_NOLIST = -1, # Data should not appear in the free list. # Must be negative. H5FD_MEM_DEFAULT = 0, # Value not yet set. Can also be the # datatype set in a larger allocation # that will be suballocated by the library. # Must be zero. H5FD_MEM_SUPER = 1, # Superblock data H5FD_MEM_BTREE = 2, # B-tree data H5FD_MEM_DRAW = 3, # Raw data (content of datasets, etc.) H5FD_MEM_GHEAP = 4, # Global heap data H5FD_MEM_LHEAP = 5, # Local heap data H5FD_MEM_OHDR = 6, # Object header data H5FD_MEM_NTYPES # Sentinel value - must be last cdef enum H5O_type_t: H5O_TYPE_UNKNOWN = -1 # Unknown object type H5O_TYPE_GROUP # Object is a group H5O_TYPE_DATASET # Object is a dataset H5O_TYPE_NAMED_DATATYPE # Object is a named data type cdef enum H5L_type_t: H5L_TYPE_ERROR = -1 # Invalid link type id H5L_TYPE_HARD = 0 # Hard link id H5L_TYPE_SOFT = 1 # Soft link id H5L_TYPE_EXTERNAL = 64, # External link id # Values for fill value status cdef enum H5D_fill_value_t: H5D_FILL_VALUE_ERROR = -1 H5D_FILL_VALUE_UNDEFINED = 0 H5D_FILL_VALUE_DEFAULT = 1 H5D_FILL_VALUE_USER_DEFINED = 2 # HDF5 layouts cdef enum H5D_layout_t: H5D_LAYOUT_ERROR = -1 H5D_COMPACT = 0 # raw data is very small H5D_CONTIGUOUS = 1 # the default H5D_CHUNKED = 2 # slow and fancy H5D_NLAYOUTS = 3 # this one must be last! # Byte orders cdef enum H5T_order_t: H5T_ORDER_ERROR = -1 # error H5T_ORDER_LE = 0 # little endian H5T_ORDER_BE = 1 # bit endian H5T_ORDER_VAX = 2 # VAX mixed endian H5T_ORDER_NONE = 3 # no particular order (strings, bits,..) # HDF5 signed enums cdef enum H5T_sign_t: H5T_SGN_ERROR = -1 # error H5T_SGN_NONE = 0 # this is an unsigned type H5T_SGN_2 = 1 # two's complement H5T_NSGN = 2 # this must be last! # HDF5 type classes cdef enum H5T_class_t: H5T_NO_CLASS = -1 # error H5T_INTEGER = 0 # integer types H5T_FLOAT = 1 # floating-point types H5T_TIME = 2 # date and time types H5T_STRING = 3 # character string types H5T_BITFIELD = 4 # bit field types H5T_OPAQUE = 5 # opaque types H5T_COMPOUND = 6 # compound types H5T_REFERENCE = 7 # reference types H5T_ENUM = 8 # enumeration types H5T_VLEN = 9 # variable-length types H5T_ARRAY = 10 # array types H5T_NCLASSES # this must be last # Native types hid_t H5T_C_S1 hid_t H5T_NATIVE_B8 hid_t H5T_NATIVE_CHAR hid_t H5T_NATIVE_SCHAR hid_t H5T_NATIVE_UCHAR hid_t H5T_NATIVE_SHORT hid_t H5T_NATIVE_USHORT hid_t H5T_NATIVE_INT hid_t H5T_NATIVE_UINT hid_t H5T_NATIVE_LONG hid_t H5T_NATIVE_ULONG hid_t H5T_NATIVE_LLONG hid_t H5T_NATIVE_ULLONG hid_t H5T_NATIVE_FLOAT hid_t H5T_NATIVE_DOUBLE hid_t H5T_NATIVE_LDOUBLE # "Standard" types hid_t H5T_STD_I8LE hid_t H5T_STD_I16LE hid_t H5T_STD_I32LE hid_t H5T_STD_I64LE hid_t H5T_STD_U8LE hid_t H5T_STD_U16LE hid_t H5T_STD_U32LE hid_t H5T_STD_U64LE hid_t H5T_STD_B8LE hid_t H5T_STD_B16LE hid_t H5T_STD_B32LE hid_t H5T_STD_B64LE hid_t H5T_IEEE_F32LE hid_t H5T_IEEE_F64LE hid_t H5T_STD_I8BE hid_t H5T_STD_I16BE hid_t H5T_STD_I32BE hid_t H5T_STD_I64BE hid_t H5T_STD_U8BE hid_t H5T_STD_U16BE hid_t H5T_STD_U32BE hid_t H5T_STD_U64BE hid_t H5T_STD_B8BE hid_t H5T_STD_B16BE hid_t H5T_STD_B32BE hid_t H5T_STD_B64BE hid_t H5T_IEEE_F32BE hid_t H5T_IEEE_F64BE # Types which are particular to UNIX (for Time types) hid_t H5T_UNIX_D32LE hid_t H5T_UNIX_D64LE hid_t H5T_UNIX_D32BE hid_t H5T_UNIX_D64BE # The order to retrieve atomic native datatype cdef enum H5T_direction_t: H5T_DIR_DEFAULT = 0 # default direction is inscendent H5T_DIR_ASCEND = 1 # in inscendent order H5T_DIR_DESCEND = 2 # in descendent order # Codes for defining selections cdef enum H5S_seloper_t: H5S_SELECT_NOOP = -1 H5S_SELECT_SET = 0 H5S_SELECT_OR H5S_SELECT_AND H5S_SELECT_XOR H5S_SELECT_NOTB H5S_SELECT_NOTA H5S_SELECT_APPEND H5S_SELECT_PREPEND H5S_SELECT_INVALID # Must be the last one # Character set to use for text strings cdef enum H5T_cset_t: H5T_CSET_ERROR = -1 # error H5T_CSET_ASCII = 0 # US ASCII H5T_CSET_UTF8 = 1 # UTF-8 Unicode encoding H5T_CSET_RESERVED_2 = 2 H5T_CSET_RESERVED_3 = 3 H5T_CSET_RESERVED_4 = 4 H5T_CSET_RESERVED_5 = 5 H5T_CSET_RESERVED_6 = 6 H5T_CSET_RESERVED_7 = 7 H5T_CSET_RESERVED_8 = 8 H5T_CSET_RESERVED_9 = 9 H5T_CSET_RESERVED_10 = 10 H5T_CSET_RESERVED_11 = 11 H5T_CSET_RESERVED_12 = 12 H5T_CSET_RESERVED_13 = 13 H5T_CSET_RESERVED_14 = 14 H5T_CSET_RESERVED_15 = 15 # Error stack traversal direction cdef enum H5E_direction_t: H5E_WALK_UPWARD = 0 # begin deep, end at API function H5E_WALK_DOWNWARD = 1 # begin at API function, end deep cdef enum H5E_type_t: H5E_MAJOR H5E_MINOR ctypedef struct H5E_error_t: hid_t cls_id # class ID hid_t maj_num # major error ID hid_t min_num # minor error number unsigned line # line in file where error occurs const char *func_name # function in which error occurred const char *file_name # file in which error occurred const char *desc # optional supplied description ctypedef herr_t (*H5E_walk_t)(unsigned n, H5E_error_t *err, void *data) ctypedef herr_t (*H5E_auto_t)(hid_t estack, void *data) # object info ctypedef struct H5O_info_t: unsigned long fileno # Number of file where object is located haddr_t addr # Object address in file H5O_type_t type # Basic object type unsigned rc # Reference count of object time_t atime # Access time time_t mtime # Modification time time_t ctime # Change time time_t btime # Birth time hsize_t num_attrs # number of attributes attached to object #H5O_hdr_info_t hdr # Object header information #struct { # H5_ih_info_t obj # H5_ih_info_t attr #} meta_size #------------------------------------------------------------------ # HDF5 API # Version functions herr_t H5get_libversion(unsigned *majnum, unsigned *minnum, unsigned *relnum ) herr_t H5check_version(unsigned majnum, unsigned minnum, unsigned relnum ) # misc #herr_t H5free_memory(void *buf) # new in HDF5 1.8.13 # Operations with files hid_t H5Fcreate(char *filename, unsigned int flags, hid_t create_plist, hid_t access_plist) hid_t H5Fopen(char *name, unsigned flags, hid_t access_id) herr_t H5Fclose (hid_t file_id) htri_t H5Fis_hdf5(char *name) herr_t H5Fflush(hid_t object_id, H5F_scope_t scope) herr_t H5Fget_vfd_handle(hid_t file_id, hid_t fapl_id, void **file_handle) ssize_t H5Fget_file_image(hid_t file_id, void *buf_ptr, size_t buf_len) herr_t H5Fget_filesize(hid_t file_id, hsize_t *size) hid_t H5Fget_create_plist(hid_t file_id) # Operations with groups hid_t H5Gcreate(hid_t loc_id, char *name, hid_t lcpl_id, hid_t gcpl_id, hid_t gapl_id) hid_t H5Gopen(hid_t loc_id, char *name, hid_t gapl_id) herr_t H5Gclose(hid_t group_id) # Operations with links herr_t H5Ldelete(hid_t file_id, char *name, hid_t lapl_id) herr_t H5Lmove(hid_t src_loc_id, char *src_name, hid_t dst_loc_id, char *dst_name, hid_t lcpl, hid_t lap) # For dealing with datasets hid_t H5Dopen(hid_t file_id, char *name, hid_t dapl_id) herr_t H5Dclose(hid_t dset_id) herr_t H5Dread(hid_t dset_id, hid_t mem_type_id, hid_t mem_space_id, hid_t file_space_id, hid_t plist_id, void *buf) herr_t H5Dwrite(hid_t dset_id, hid_t mem_type_id, hid_t mem_space_id, hid_t file_space_id, hid_t plist_id, void *buf) hid_t H5Dget_type(hid_t dset_id) hid_t H5Dget_space(hid_t dset_id) herr_t H5Dvlen_reclaim(hid_t type_id, hid_t space_id, hid_t plist_id, void *buf) hid_t H5Dget_create_plist(hid_t dataset_id) hsize_t H5Dget_storage_size(hid_t dataset_id) herr_t H5Dvlen_get_buf_size(hid_t dataset_id, hid_t type_id, hid_t space_id, hsize_t *size) # Functions for dealing with dataspaces hid_t H5Screate_simple(int rank, hsize_t dims[], hsize_t maxdims[]) int H5Sget_simple_extent_ndims(hid_t space_id) int H5Sget_simple_extent_dims(hid_t space_id, hsize_t dims[], hsize_t maxdims[]) herr_t H5Sselect_all(hid_t spaceid) herr_t H5Sselect_hyperslab(hid_t space_id, H5S_seloper_t op, hsize_t start[], hsize_t _stride[], hsize_t count[], hsize_t _block[]) herr_t H5Sselect_elements(hid_t space_id, H5S_seloper_t op, size_t num_elements, hsize_t *coord) herr_t H5Sclose(hid_t space_id) # Functions for dealing with datatypes H5T_class_t H5Tget_class(hid_t type_id) hid_t H5Tget_super(hid_t type) H5T_sign_t H5Tget_sign(hid_t type_id) H5T_order_t H5Tget_order(hid_t type_id) size_t H5Tget_size(hid_t type_id) herr_t H5Tset_size(hid_t type_id, size_t size) size_t H5Tget_precision(hid_t dtype_id) herr_t H5Tset_precision(hid_t type_id, size_t prec) hid_t H5Tcreate(H5T_class_t type, size_t size) hid_t H5Tvlen_create(hid_t base_type_id) hid_t H5Tcopy(hid_t type_id) herr_t H5Tclose(hid_t type_id) htri_t H5Tequal(hid_t dtype_id1, hid_t dtype_id2) # Operations defined on string data types htri_t H5Tis_variable_str(hid_t dtype_id) # Operations for compound data types int H5Tget_nmembers(hid_t type_id) char *H5Tget_member_name(hid_t type_id, unsigned membno) hid_t H5Tget_member_type(hid_t type_id, unsigned membno) hid_t H5Tget_native_type(hid_t type_id, H5T_direction_t direction) herr_t H5Tget_member_value(hid_t type_id, int membno, void *value) size_t H5Tget_member_offset(hid_t type_id, unsigned memb_no) int H5Tget_offset(hid_t type_id) herr_t H5Tinsert(hid_t parent_id, char *name, size_t offset, hid_t member_id) herr_t H5Tpack(hid_t type_id) # Operations for enumerated data types hid_t H5Tenum_create(hid_t base_id) herr_t H5Tenum_insert(hid_t type, char *name, void *value) # Operations for array data types hid_t H5Tarray_create(hid_t base_id, int ndims, hsize_t dims[]) int H5Tget_array_ndims(hid_t type_id) int H5Tget_array_dims(hid_t type_id, hsize_t dims[]) # Operations with attributes herr_t H5Adelete(hid_t loc_id, char *name) int H5Aget_num_attrs(hid_t loc_id) size_t H5Aget_name(hid_t attr_id, size_t buf_size, char *buf) hid_t H5Aopen_idx(hid_t loc_id, unsigned int idx) herr_t H5Aread(hid_t attr_id, hid_t mem_type_id, void *buf) herr_t H5Aclose(hid_t attr_id) # Operations with properties hid_t H5Pcreate(hid_t plist_id) herr_t H5Pclose(hid_t plist_id) herr_t H5Pset_cache(hid_t plist_id, int mdc_nelmts, int rdcc_nelmts, size_t rdcc_nbytes, double rdcc_w0) herr_t H5Pset_sieve_buf_size(hid_t fapl_id, hsize_t size) H5D_layout_t H5Pget_layout(hid_t plist) int H5Pget_chunk(hid_t plist, int max_ndims, hsize_t *dims) hid_t H5Pget_driver(hid_t plist_id) herr_t H5Pset_fapl_sec2(hid_t fapl_id) #herr_t H5Pget_fapl_direct(hid_t fapl_id, size_t *alignment, # size_t *block_size, size_t *cbuf_size) #herr_t H5Pset_fapl_direct(hid_t fapl_id, size_t alignment, # size_t block_size, size_t cbuf_size) herr_t H5Pset_fapl_log(hid_t fapl_id, const char *logfile, unsigned long long flags, size_t buf_size) #herr_t H5Pset_fapl_windows(hid_t fapl_id) herr_t H5Pset_fapl_stdio(hid_t fapl_id) #herr_t H5Pget_fapl_core(hid_t fapl_id, size_t *increment, # hbool_t *backing_store) herr_t H5Pset_fapl_core(hid_t fapl_id, size_t increment, hbool_t backing_store) #herr_t H5Pget_fapl_family(hid_t fapl_id, hsize_t *memb_size, # hid_t *memb_fapl_id) herr_t H5Pset_fapl_family(hid_t fapl_id, hsize_t memb_size, hid_t memb_fapl_id) #herr_t H5Pget_fapl_multi(hid_t fapl_id, H5FD_mem_t *memb_map, # hid_t *memb_fapl, const char **memb_name, # haddr_t *memb_addr, hbool_t *relax) herr_t H5Pset_fapl_multi(hid_t fapl_id, H5FD_mem_t *memb_map, hid_t *memb_fapl, char **memb_name, haddr_t *memb_addr, hbool_t relax) herr_t H5Pset_fapl_split(hid_t fapl_id, char *meta_ext, hid_t meta_plist_id, char *raw_ext, hid_t raw_plist_id) #herr_t H5Pget_fapl_mpio(hid_t fapl_id, MPI_Comm *comm, MPI_Info *info) #herr_t H5Pset_fapl_mpio(hid_t fapl_id, MPI_Comm comm, MPI_Info info) #herr_t H5Pget_fapl_mpiposix(hid_t fapl_id, MPI_Comm *comm, # hbool_t *use_gpfs_hints) #herr_t H5Pset_fapl_mpiposix(hid_t fapl_id, MPI_Comm comm, # hbool_t use_gpfs_hints) herr_t H5Pset_file_image(hid_t fapl_id, void *buf_ptr, size_t buf_len) herr_t H5Pget_userblock(hid_t plist, hsize_t *size) herr_t H5Pset_userblock(hid_t plist, hsize_t size) herr_t H5Pget_obj_track_times(hid_t ocpl_id, hbool_t *track_times) # Error Handling Interface #herr_t H5Eget_auto(hid_t estack_id, H5E_auto_t *func, void** data) herr_t H5Eset_auto(hid_t estack_id, H5E_auto_t func, void *data) herr_t H5Eprint(hid_t estack_id, FILE *stream) herr_t H5Ewalk(hid_t estack_id, H5E_direction_t dir, H5E_walk_t func, void *data) #hid_t H5Eget_current_stack(void) #herr_t H5Eclose_stack(hid_t estack_id) #ssize_t H5Eget_num(hid_t estack_id) ssize_t H5Eget_msg(hid_t mesg_id, H5E_type_t* mesg_type, char* mesg, size_t size) #herr_t H5Eclose_msg(hid_t mesg_id) #ssize_t H5Eget_class_name(hid_t class_id, char* name, size_t size) # Onject interface herr_t H5Oget_info(hid_t object_id, H5O_info_t *object_info) # Operations with filters and compression interface ctypedef int H5Z_filter_t #herr_t H5Zregister(const void *cls) herr_t H5Zunregister(H5Z_filter_t id) #htri_t H5Zfilter_avail(H5Z_filter_t id) #herr_t H5Zget_filter_info(H5Z_filter_t, unsigned int*) # Operations on the references H5I_type_t H5Iget_type(hid_t id) herr_t H5Rcreate(void *reference, hid_t loc_id, const char *name, H5R_type_t type, hid_t space_id) hid_t H5Rdereference(hid_t dset, H5R_type_t rtype, void *reference) herr_t H5Oclose( hid_t object_id ) # Specific HDF5 functions for PyTables cdef extern from "H5ATTR.h" nogil: herr_t H5ATTRget_attribute(hid_t loc_id, char *attr_name, hid_t type_id, void *data) hsize_t H5ATTRget_attribute_string(hid_t loc_id, char *attr_name, char **attr_value, int *cset) hsize_t H5ATTRget_attribute_vlen_string_array(hid_t loc_id, char *attr_name, char ***attr_value, int *cset) herr_t H5ATTRset_attribute(hid_t obj_id, char *attr_name, hid_t type_id, size_t rank, hsize_t *dims, char *attr_data) herr_t H5ATTRset_attribute_string(hid_t loc_id, char *attr_name, char *attr_data, hsize_t attr_size, int cset) herr_t H5ATTRfind_attribute(hid_t loc_id, char *attr_name) herr_t H5ATTRget_type_ndims(hid_t loc_id, char *attr_name, hid_t *type_id, H5T_class_t *class_id, size_t *type_size, int *rank) herr_t H5ATTRget_dims(hid_t loc_id, char *attr_name, hsize_t *dims) # Functions for operations with ARRAY cdef extern from "H5ARRAY.h" nogil: herr_t H5ARRAYget_ndims(hid_t dataset_id, int *rank) herr_t H5ARRAYget_info(hid_t dataset_id, hid_t type_id, hsize_t *dims, hsize_t *maxdims, H5T_class_t *super_class_id, char *byteorder) # Some utilities cdef extern from "utils.h" nogil: herr_t set_cache_size(hid_t file_id, size_t cache_size) int get_objinfo(hid_t loc_id, char *name) int get_linkinfo(hid_t loc_id, char *name) hsize_t get_len_of_range(hsize_t lo, hsize_t hi, hsize_t step) hid_t create_ieee_float16(char *byteorder) hid_t create_ieee_complex64(char *byteorder) hid_t create_ieee_complex128(char *byteorder) hid_t create_ieee_complex192(char *byteorder) hid_t create_ieee_complex256(char *byteorder) herr_t set_order(hid_t type_id, char *byteorder) herr_t get_order(hid_t type_id, char *byteorder) int is_complex(hid_t type_id) herr_t truncate_dset(hid_t dataset_id, int maindim, hsize_t size) # compatibility herr_t pt_H5Pset_fapl_direct(hid_t fapl_id, size_t alignment, size_t block_size, size_t cbuf_size) herr_t pt_H5Pset_fapl_windows(hid_t fapl_id) herr_t pt_H5Pset_file_image(hid_t fapl_id, void *buf_ptr, size_t buf_len) ssize_t pt_H5Fget_file_image(hid_t file_id, void *buf_ptr, size_t buf_len) herr_t pt_H5free_memory(void *buf) int H5_HAVE_DIRECT_DRIVER, H5_HAVE_WINDOWS_DRIVER, H5_HAVE_IMAGE_FILE cdef extern from "utils.h": object Giterate(hid_t parent_id, hid_t loc_id, char *name) object Aiterate(hid_t loc_id) object H5UIget_info(hid_t loc_id, char *name, char *byteorder) # Type conversion routines cdef extern from "typeconv.h" nogil: void conv_float64_timeval32(void *base, unsigned long byteoffset, unsigned long bytestride, long long nrecords, unsigned long nelements, int sense) # Blosc registration cdef extern from "blosc_filter.h" nogil: int register_blosc(char **version, char **date) int FILTER_BLOSC PyTables-3.7.0/tables/description.py000066400000000000000000001123611416254111300174150ustar00rootroot00000000000000"""Classes for describing columns for ``Table`` objects.""" import copy import warnings import numpy as np from . import atom from .path import check_name_validity __docformat__ = 'reStructuredText' """The format of documentation strings in this module.""" def same_position(oldmethod): """Decorate `oldmethod` to also compare the `_v_pos` attribute.""" def newmethod(self, other): try: other._v_pos except AttributeError: return False # not a column definition return self._v_pos == other._v_pos and oldmethod(self, other) newmethod.__name__ = oldmethod.__name__ newmethod.__doc__ = oldmethod.__doc__ return newmethod class Col(atom.Atom, metaclass=type): """Defines a non-nested column. Col instances are used as a means to declare the different properties of a non-nested column in a table or nested column. Col classes are descendants of their equivalent Atom classes (see :ref:`AtomClassDescr`), but their instances have an additional _v_pos attribute that is used to decide the position of the column inside its parent table or nested column (see the IsDescription class in :ref:`IsDescriptionClassDescr` for more information on column positions). In the same fashion as Atom, you should use a particular Col descendant class whenever you know the exact type you will need when writing your code. Otherwise, you may use one of the Col.from_*() factory methods. Each factory method inherited from the Atom class is available with the same signature, plus an additional pos parameter (placed in last position) which defaults to None and that may take an integer value. This parameter might be used to specify the position of the column in the table. Besides, there are the next additional factory methods, available only for Col objects. The following parameters are available for most Col-derived constructors. Parameters ---------- itemsize : int For types with a non-fixed size, this sets the size in bytes of individual items in the column. shape : tuple Sets the shape of the column. An integer shape of N is equivalent to the tuple (N,). dflt Sets the default value for the column. pos : int Sets the position of column in table. If unspecified, the position will be randomly selected. """ _class_from_prefix = {} # filled as column classes are created """Maps column prefixes to column classes.""" @classmethod def prefix(cls): """Return the column class prefix.""" cname = cls.__name__ return cname[:cname.rfind('Col')] @classmethod def from_atom(cls, atom, pos=None, _offset=None): """Create a Col definition from a PyTables atom. An optional position may be specified as the pos argument. """ prefix = atom.prefix() kwargs = atom._get_init_args() colclass = cls._class_from_prefix[prefix] return colclass(pos=pos, _offset=_offset, **kwargs) @classmethod def from_sctype(cls, sctype, shape=(), dflt=None, pos=None): """Create a `Col` definition from a NumPy scalar type `sctype`. Optional shape, default value and position may be specified as the `shape`, `dflt` and `pos` arguments, respectively. Information in the `sctype` not represented in a `Col` is ignored. """ newatom = atom.Atom.from_sctype(sctype, shape, dflt) return cls.from_atom(newatom, pos=pos) @classmethod def from_dtype(cls, dtype, dflt=None, pos=None, _offset=None): """Create a `Col` definition from a NumPy `dtype`. Optional default value and position may be specified as the `dflt` and `pos` arguments, respectively. The `dtype` must have a byte order which is irrelevant or compatible with that of the system. Information in the `dtype` not represented in a `Col` is ignored. """ newatom = atom.Atom.from_dtype(dtype, dflt) return cls.from_atom(newatom, pos=pos, _offset=_offset) @classmethod def from_type(cls, type, shape=(), dflt=None, pos=None): """Create a `Col` definition from a PyTables `type`. Optional shape, default value and position may be specified as the `shape`, `dflt` and `pos` arguments, respectively. """ newatom = atom.Atom.from_type(type, shape, dflt) return cls.from_atom(newatom, pos=pos) @classmethod def from_kind(cls, kind, itemsize=None, shape=(), dflt=None, pos=None): """Create a `Col` definition from a PyTables `kind`. Optional item size, shape, default value and position may be specified as the `itemsize`, `shape`, `dflt` and `pos` arguments, respectively. Bear in mind that not all columns support a default item size. """ newatom = atom.Atom.from_kind(kind, itemsize, shape, dflt) return cls.from_atom(newatom, pos=pos) @classmethod def _subclass_from_prefix(cls, prefix): """Get a column subclass for the given `prefix`.""" cname = '%sCol' % prefix class_from_prefix = cls._class_from_prefix if cname in class_from_prefix: return class_from_prefix[cname] atombase = getattr(atom, '%sAtom' % prefix) class NewCol(cls, atombase): """Defines a non-nested column of a particular type. The constructor accepts the same arguments as the equivalent `Atom` class, plus an additional ``pos`` argument for position information, which is assigned to the `_v_pos` attribute. """ def __init__(self, *args, **kwargs): pos = kwargs.pop('pos', None) offset = kwargs.pop('_offset', None) class_from_prefix = self._class_from_prefix atombase.__init__(self, *args, **kwargs) # The constructor of an abstract atom may have changed # the class of `self` to something different of `NewCol` # and `atombase` (that's why the prefix map is saved). if self.__class__ is not NewCol: colclass = class_from_prefix[self.prefix()] self.__class__ = colclass self._v_pos = pos self._v_offset = offset __eq__ = same_position(atombase.__eq__) _is_equal_to_atom = same_position(atombase._is_equal_to_atom) # XXX: API incompatible change for PyTables 3 line # Overriding __eq__ blocks inheritance of __hash__ in 3.x # def __hash__(self): # return hash((self._v_pos, self.atombase)) if prefix == 'Enum': _is_equal_to_enumatom = same_position( atombase._is_equal_to_enumatom) NewCol.__name__ = cname class_from_prefix[prefix] = NewCol return NewCol def __repr__(self): # Reuse the atom representation. atomrepr = super().__repr__() lpar = atomrepr.index('(') rpar = atomrepr.rindex(')') atomargs = atomrepr[lpar + 1:rpar] classname = self.__class__.__name__ return f'{classname}({atomargs}, pos={self._v_pos})' def _get_init_args(self): """Get a dictionary of instance constructor arguments.""" kwargs = {arg: getattr(self, arg) for arg in ('shape', 'dflt')} kwargs['pos'] = getattr(self, '_v_pos', None) return kwargs def _generate_col_classes(): """Generate all column classes.""" # Abstract classes are not in the class map. cprefixes = ['Int', 'UInt', 'Float', 'Time'] for (kind, kdata) in atom.atom_map.items(): if hasattr(kdata, 'kind'): # atom class: non-fixed item size atomclass = kdata cprefixes.append(atomclass.prefix()) else: # dictionary: fixed item size for atomclass in kdata.values(): cprefixes.append(atomclass.prefix()) # Bottom-level complex classes are not in the type map, of course. # We still want the user to get the compatibility warning, though. cprefixes.extend(['Complex32', 'Complex64', 'Complex128']) if hasattr(atom, 'Complex192Atom'): cprefixes.append('Complex192') if hasattr(atom, 'Complex256Atom'): cprefixes.append('Complex256') for cprefix in cprefixes: newclass = Col._subclass_from_prefix(cprefix) yield newclass # Create all column classes. # for _newclass in _generate_col_classes(): # exec('%s = _newclass' % _newclass.__name__) # del _newclass StringCol = Col._subclass_from_prefix('String') BoolCol = Col._subclass_from_prefix('Bool') EnumCol = Col._subclass_from_prefix('Enum') IntCol = Col._subclass_from_prefix('Int') Int8Col = Col._subclass_from_prefix('Int8') Int16Col = Col._subclass_from_prefix('Int16') Int32Col = Col._subclass_from_prefix('Int32') Int64Col = Col._subclass_from_prefix('Int64') UIntCol = Col._subclass_from_prefix('UInt') UInt8Col = Col._subclass_from_prefix('UInt8') UInt16Col = Col._subclass_from_prefix('UInt16') UInt32Col = Col._subclass_from_prefix('UInt32') UInt64Col = Col._subclass_from_prefix('UInt64') FloatCol = Col._subclass_from_prefix('Float') if hasattr(atom, 'Float16Atom'): Float16Col = Col._subclass_from_prefix('Float16') Float32Col = Col._subclass_from_prefix('Float32') Float64Col = Col._subclass_from_prefix('Float64') if hasattr(atom, 'Float96Atom'): Float96Col = Col._subclass_from_prefix('Float96') if hasattr(atom, 'Float128Atom'): Float128Col = Col._subclass_from_prefix('Float128') ComplexCol = Col._subclass_from_prefix('Complex') Complex32Col = Col._subclass_from_prefix('Complex32') Complex64Col = Col._subclass_from_prefix('Complex64') Complex128Col = Col._subclass_from_prefix('Complex128') if hasattr(atom, 'Complex192Atom'): Complex192Col = Col._subclass_from_prefix('Complex192') if hasattr(atom, 'Complex256Atom'): Complex256Col = Col._subclass_from_prefix('Complex256') TimeCol = Col._subclass_from_prefix('Time') Time32Col = Col._subclass_from_prefix('Time32') Time64Col = Col._subclass_from_prefix('Time64') # Table description classes # ========================= class Description: """This class represents descriptions of the structure of tables. An instance of this class is automatically bound to Table (see :ref:`TableClassDescr`) objects when they are created. It provides a browseable representation of the structure of the table, made of non-nested (Col - see :ref:`ColClassDescr`) and nested (Description) columns. Column definitions under a description can be accessed as attributes of it (*natural naming*). For instance, if table.description is a Description instance with a column named col1 under it, the later can be accessed as table.description.col1. If col1 is nested and contains a col2 column, this can be accessed as table.description.col1.col2. Because of natural naming, the names of members start with special prefixes, like in the Group class (see :ref:`GroupClassDescr`). .. rubric:: Description attributes .. attribute:: _v_colobjects A dictionary mapping the names of the columns hanging directly from the associated table or nested column to their respective descriptions (Col - see :ref:`ColClassDescr` or Description - see :ref:`DescriptionClassDescr` instances). .. versionchanged:: 3.0 The *_v_colObjects* attribute has been renamed into *_v_colobjects*. .. attribute:: _v_dflts A dictionary mapping the names of non-nested columns hanging directly from the associated table or nested column to their respective default values. .. attribute:: _v_dtype The NumPy type which reflects the structure of this table or nested column. You can use this as the dtype argument of NumPy array factories. .. attribute:: _v_dtypes A dictionary mapping the names of non-nested columns hanging directly from the associated table or nested column to their respective NumPy types. .. attribute:: _v_is_nested Whether the associated table or nested column contains further nested columns or not. .. attribute:: _v_itemsize The size in bytes of an item in this table or nested column. .. attribute:: _v_name The name of this description group. The name of the root group is '/'. .. attribute:: _v_names A list of the names of the columns hanging directly from the associated table or nested column. The order of the names matches the order of their respective columns in the containing table. .. attribute:: _v_nested_descr A nested list of pairs of (name, format) tuples for all the columns under this table or nested column. You can use this as the dtype and descr arguments of NumPy array factories. .. versionchanged:: 3.0 The *_v_nestedDescr* attribute has been renamed into *_v_nested_descr*. .. attribute:: _v_nested_formats A nested list of the NumPy string formats (and shapes) of all the columns under this table or nested column. You can use this as the formats argument of NumPy array factories. .. versionchanged:: 3.0 The *_v_nestedFormats* attribute has been renamed into *_v_nested_formats*. .. attribute:: _v_nestedlvl The level of the associated table or nested column in the nested datatype. .. attribute:: _v_nested_names A nested list of the names of all the columns under this table or nested column. You can use this as the names argument of NumPy array factories. .. versionchanged:: 3.0 The *_v_nestedNames* attribute has been renamed into *_v_nested_names*. .. attribute:: _v_pathname Pathname of the table or nested column. .. attribute:: _v_pathnames A list of the pathnames of all the columns under this table or nested column (in preorder). If it does not contain nested columns, this is exactly the same as the :attr:`Description._v_names` attribute. .. attribute:: _v_types A dictionary mapping the names of non-nested columns hanging directly from the associated table or nested column to their respective PyTables types. .. attribute:: _v_offsets A list of offsets for all the columns. If the list is empty, means that there are no padding in the data structure. However, the support for offsets is currently limited to flat tables; for nested tables, the potential padding is always removed (exactly the same as in pre-3.5 versions), and this variable is set to empty. .. versionadded:: 3.5 Previous to this version all the compound types were converted internally to 'packed' types, i.e. with no padding between the component types. Starting with 3.5, the holes in native HDF5 types (non-nested) are honored and replicated during dataset and attribute copies. """ def __init__(self, classdict, nestedlvl=-1, validate=True, ptparams=None): if not classdict: raise ValueError("cannot create an empty data type") # Do a shallow copy of classdict just in case this is going to # be shared by other instances newdict = self.__dict__ newdict["_v_name"] = "/" # The name for root descriptor newdict["_v_names"] = [] newdict["_v_dtypes"] = {} newdict["_v_types"] = {} newdict["_v_dflts"] = {} newdict["_v_colobjects"] = {} newdict["_v_is_nested"] = False nestedFormats = [] nestedDType = [] if not hasattr(newdict, "_v_nestedlvl"): newdict["_v_nestedlvl"] = nestedlvl + 1 cols_with_pos = [] # colum (position, name) pairs cols_no_pos = [] # just column names cols_offsets = [] # the offsets of the columns valid_offsets = False # by default there a no valid offsets # Check for special variables and convert column descriptions for (name, descr) in classdict.items(): if name.startswith('_v_'): if name in newdict: # print("Warning!") # special methods &c: copy to newdict, warn about conflicts warnings.warn("Can't set attr %r in description class %r" % (name, self)) else: # print("Special variable!-->", name, classdict[name]) newdict[name] = descr continue # This variable is not needed anymore columns = None if (type(descr) == type(IsDescription) and issubclass(descr, IsDescription)): # print("Nested object (type I)-->", name) columns = descr().columns elif (type(descr.__class__) == type(IsDescription) and issubclass(descr.__class__, IsDescription)): # print("Nested object (type II)-->", name) columns = descr.columns elif isinstance(descr, dict): # print("Nested object (type III)-->", name) columns = descr else: # print("Nested object (type IV)-->", name) descr = copy.copy(descr) # The copies above and below ensure that the structures # provided by the user will remain unchanged even if we # tamper with the values of ``_v_pos`` here. if columns is not None: descr = Description(copy.copy(columns), self._v_nestedlvl, ptparams=ptparams) classdict[name] = descr pos = getattr(descr, '_v_pos', None) if pos is None: cols_no_pos.append(name) else: cols_with_pos.append((pos, name)) offset = getattr(descr, '_v_offset', None) if offset is not None: cols_offsets.append(offset) # Sort field names: # # 1. Fields with explicit positions, according to their # positions (and their names if coincident). # 2. Fields with no position, in alphabetical order. cols_with_pos.sort() cols_no_pos.sort() keys = [name for (pos, name) in cols_with_pos] + cols_no_pos pos = 0 nested = False # Get properties for compound types for k in keys: if validate: # Check for key name validity check_name_validity(k) # Class variables object = classdict[k] newdict[k] = object # To allow natural naming if not isinstance(object, (Col, Description)): raise TypeError('Passing an incorrect value to a table column.' ' Expected a Col (or subclass) instance and ' 'got: "%s". Please make use of the Col(), or ' 'descendant, constructor to properly ' 'initialize columns.' % object) object._v_pos = pos # Set the position of this object object._v_parent = self # The parent description pos += 1 newdict['_v_colobjects'][k] = object newdict['_v_names'].append(k) object.__dict__['_v_name'] = k if not isinstance(k, str): # numpy only accepts "str" for field names # Python 3.x: bytes --> str (unicode) kk = k.decode() else: kk = k if isinstance(object, Col): dtype = object.dtype newdict['_v_dtypes'][k] = dtype newdict['_v_types'][k] = object.type newdict['_v_dflts'][k] = object.dflt nestedFormats.append(object.recarrtype) baserecarrtype = dtype.base.str[1:] nestedDType.append((kk, baserecarrtype, dtype.shape)) else: # A description nestedFormats.append(object._v_nested_formats) nestedDType.append((kk, object._v_dtype)) nested = True # Useful for debugging purposes # import traceback # if ptparams is None: # print("*** print_stack:") # traceback.print_stack() # Check whether we are gonna use padding or not. Two possibilities: # 1) Make padding True by default (except if ALLOW_PADDING is set # to False) # 2) Make padding False by default (except if ALLOW_PADDING is set # to True) # Currently we choose 1) because it favours honoring padding even on # unhandled situations (should be very few). # However, for development, option 2) is recommended as it catches # most of the unhandled situations. allow_padding = ptparams is None or ptparams['ALLOW_PADDING'] # allow_padding = ptparams is not None and ptparams['ALLOW_PADDING'] if (allow_padding and len(cols_offsets) > 1 and len(keys) == len(cols_with_pos) and len(keys) == len(cols_offsets) and not nested): # TODO: support offsets with nested types # We have to sort the offsets too, as they must follow the column # order. As the offsets and the pos should be place in the same # order, a single sort is enough here. cols_offsets.sort() valid_offsets = True else: newdict['_v_offsets'] = [] # Assign the format list to _v_nested_formats newdict['_v_nested_formats'] = nestedFormats if self._v_nestedlvl == 0: # Get recursively nested _v_nested_names and _v_nested_descr attrs self._g_set_nested_names_descr() # Get pathnames for nested groups self._g_set_path_names() # Check the _v_byteorder has been used an issue an Error if hasattr(self, "_v_byteorder"): raise ValueError( "Using a ``_v_byteorder`` in the description is obsolete. " "Use the byteorder parameter in the constructor instead.") # Compute the dtype with offsets or without # print("offsets ->", cols_offsets, nestedDType, nested, valid_offsets) if valid_offsets: # TODO: support offsets within nested types dtype_fields = { 'names': newdict['_v_names'], 'formats': nestedFormats, 'offsets': cols_offsets} itemsize = newdict.get('_v_itemsize', None) if itemsize is not None: dtype_fields['itemsize'] = itemsize dtype = np.dtype(dtype_fields) else: dtype = np.dtype(nestedDType) newdict['_v_dtype'] = dtype newdict['_v_itemsize'] = dtype.itemsize newdict['_v_offsets'] = [dtype.fields[name][1] for name in dtype.names] def _g_set_nested_names_descr(self): """Computes the nested names and descriptions for nested datatypes.""" names = self._v_names fmts = self._v_nested_formats self._v_nested_names = names[:] # Important to do a copy! self._v_nested_descr = list(zip(names, fmts)) for i, name in enumerate(names): new_object = self._v_colobjects[name] if isinstance(new_object, Description): new_object._g_set_nested_names_descr() # replace the column nested name by a correct tuple self._v_nested_names[i] = (name, new_object._v_nested_names) self._v_nested_descr[i] = (name, new_object._v_nested_descr) # set the _v_is_nested flag self._v_is_nested = True def _g_set_path_names(self): """Compute the pathnames for arbitrary nested descriptions. This method sets the ``_v_pathname`` and ``_v_pathnames`` attributes of all the elements (both descriptions and columns) in this nested description. """ def get_cols_in_order(description): return [description._v_colobjects[colname] for colname in description._v_names] def join_paths(path1, path2): if not path1: return path2 return f'{path1}/{path2}' # The top of the stack always has a nested description # and a list of its child columns # (be they nested ``Description`` or non-nested ``Col`` objects). # In the end, the list contains only a list of column paths # under this one. # # For instance, given this top of the stack:: # # (, [, ]) # # After computing the rest of the stack, the top is:: # # (, ['a', 'a/m', 'a/n', ... , 'b', ...]) stack = [] # We start by pushing the top-level description # and its child columns. self._v_pathname = '' stack.append((self, get_cols_in_order(self))) while stack: desc, cols = stack.pop() head = cols[0] # What's the first child in the list? if isinstance(head, Description): # A nested description. We remove it from the list and # push it with its child columns. This will be the next # handled description. head._v_pathname = join_paths(desc._v_pathname, head._v_name) stack.append((desc, cols[1:])) # alter the top stack.append((head, get_cols_in_order(head))) # new top elif isinstance(head, Col): # A non-nested column. We simply remove it from the # list and append its name to it. head._v_pathname = join_paths(desc._v_pathname, head._v_name) cols.append(head._v_name) # alter the top stack.append((desc, cols[1:])) # alter the top else: # Since paths and names are appended *to the end* of # children lists, a string signals that no more children # remain to be processed, so we are done with the # description at the top of the stack. assert isinstance(head, str) # Assign the computed set of descendent column paths. desc._v_pathnames = cols if len(stack) > 0: # Compute the paths with respect to the parent node # (including the path of the current description) # and append them to its list. descName = desc._v_name colPaths = [join_paths(descName, path) for path in cols] colPaths.insert(0, descName) parentCols = stack[-1][1] parentCols.extend(colPaths) # (Nothing is pushed, we are done with this description.) def _f_walk(self, type='All'): """Iterate over nested columns. If type is 'All' (the default), all column description objects (Col and Description instances) are yielded in top-to-bottom order (preorder). If type is 'Col' or 'Description', only column descriptions of that type are yielded. """ if type not in ["All", "Col", "Description"]: raise ValueError("""\ type can only take the parameters 'All', 'Col' or 'Description'.""") stack = [self] while stack: object = stack.pop(0) # pop at the front so as to ensure the order if type in ["All", "Description"]: yield object # yield description for name in object._v_names: new_object = object._v_colobjects[name] if isinstance(new_object, Description): stack.append(new_object) else: if type in ["All", "Col"]: yield new_object # yield column def __repr__(self): """Gives a detailed Description column representation.""" rep = ['%s\"%s\": %r' % (" " * self._v_nestedlvl, k, self._v_colobjects[k]) for k in self._v_names] return '{\n %s}' % (',\n '.join(rep)) def __str__(self): """Gives a brief Description representation.""" return f'Description({self._v_nested_descr})' class MetaIsDescription(type): """Helper metaclass to return the class variables as a dictionary.""" def __new__(mcs, classname, bases, classdict): """Return a new class with a "columns" attribute filled.""" newdict = {"columns": {}, } if '__doc__' in classdict: newdict['__doc__'] = classdict['__doc__'] for b in bases: if "columns" in b.__dict__: newdict["columns"].update(b.__dict__["columns"]) for k in classdict: # if not (k.startswith('__') or k.startswith('_v_')): # We let pass _v_ variables to configure class behaviour if not (k.startswith('__')): newdict["columns"][k] = classdict[k] # Return a new class with the "columns" attribute filled return type.__new__(mcs, classname, bases, newdict) class IsDescription(metaclass=MetaIsDescription): """Description of the structure of a table or nested column. This class is designed to be used as an easy, yet meaningful way to describe the structure of new Table (see :ref:`TableClassDescr`) datasets or nested columns through the definition of *derived classes*. In order to define such a class, you must declare it as descendant of IsDescription, with as many attributes as columns you want in your table. The name of each attribute will become the name of a column, and its value will hold a description of it. Ordinary columns can be described using instances of the Col class (see :ref:`ColClassDescr`). Nested columns can be described by using classes derived from IsDescription, instances of it, or name-description dictionaries. Derived classes can be declared in place (in which case the column takes the name of the class) or referenced by name. Nested columns can have a _v_pos special attribute which sets the *relative* position of the column among sibling columns *also having explicit positions*. The pos constructor argument of Col instances is used for the same purpose. Columns with no explicit position will be placed afterwards in alphanumeric order. Once you have created a description object, you can pass it to the Table constructor, where all the information it contains will be used to define the table structure. .. rubric:: IsDescription attributes .. attribute:: _v_pos Sets the position of a possible nested column description among its sibling columns. This attribute can be specified *when declaring* an IsDescription subclass to complement its *metadata*. .. attribute:: columns Maps the name of each column in the description to its own descriptive object. This attribute is *automatically created* when an IsDescription subclass is declared. Please note that declared columns can no longer be accessed as normal class variables after its creation. """ def descr_from_dtype(dtype_, ptparams=None): """Get a description instance and byteorder from a (nested) NumPy dtype.""" fields = {} fbyteorder = '|' for name in dtype_.names: dtype, offset = dtype_.fields[name][:2] kind = dtype.base.kind byteorder = dtype.base.byteorder if byteorder in '><=': if fbyteorder not in ['|', byteorder]: raise NotImplementedError( "structured arrays with mixed byteorders " "are not supported yet, sorry") fbyteorder = byteorder # Non-nested column if kind in 'biufSUc': col = Col.from_dtype(dtype, pos=offset, _offset=offset) # Nested column elif kind == 'V' and dtype.shape in [(), (1,)]: if dtype.shape != (): warnings.warn( "nested descriptions will be converted to scalar") col, _ = descr_from_dtype(dtype.base, ptparams=ptparams) col._v_pos = offset col._v_offset = offset else: raise NotImplementedError( "structured arrays with columns with type description ``%s`` " "are not supported yet, sorry" % dtype) fields[name] = col return Description(fields, ptparams=ptparams), fbyteorder def dtype_from_descr(descr, byteorder=None, ptparams=None): """Get a (nested) NumPy dtype from a description instance and byteorder. The descr parameter can be a Description or IsDescription instance, sub-class of IsDescription or a dictionary. """ if isinstance(descr, dict): descr = Description(descr, ptparams=ptparams) elif (type(descr) == type(IsDescription) and issubclass(descr, IsDescription)): descr = Description(descr().columns, ptparams=ptparams) elif isinstance(descr, IsDescription): descr = Description(descr.columns, ptparams=ptparams) elif not isinstance(descr, Description): raise ValueError('invalid description: %r' % descr) dtype_ = descr._v_dtype if byteorder and byteorder != '|': dtype_ = dtype_.newbyteorder(byteorder) return dtype_ if __name__ == "__main__": """Test code.""" class Info(IsDescription): _v_pos = 2 Name = UInt32Col() Value = Float64Col() class Test(IsDescription): """A description that has several columns.""" x = Col.from_type("int32", 2, 0, pos=0) y = Col.from_kind('float', dflt=1, shape=(2, 3)) z = UInt8Col(dflt=1) color = StringCol(2, dflt=" ") # color = UInt32Col(2) Info = Info() class info(IsDescription): _v_pos = 1 name = UInt32Col() value = Float64Col(pos=0) y2 = Col.from_kind('float', dflt=1, shape=(2, 3), pos=1) z2 = UInt8Col(dflt=1) class info2(IsDescription): y3 = Col.from_kind('float', dflt=1, shape=(2, 3)) z3 = UInt8Col(dflt=1) name = UInt32Col() value = Float64Col() class info3(IsDescription): name = UInt32Col() value = Float64Col() y4 = Col.from_kind('float', dflt=1, shape=(2, 3)) z4 = UInt8Col(dflt=1) # class Info(IsDescription): # _v_pos = 2 # Name = StringCol(itemsize=2) # Value = ComplexCol(itemsize=16) # class Test(IsDescription): # """A description that has several columns""" # x = Col.from_type("int32", 2, 0, pos=0) # y = Col.from_kind('float', dflt=1, shape=(2,3)) # z = UInt8Col(dflt=1) # color = StringCol(2, dflt=" ") # Info = Info() # class info(IsDescription): # _v_pos = 1 # name = StringCol(itemsize=2) # value = ComplexCol(itemsize=16, pos=0) # y2 = Col.from_kind('float', dflt=1, shape=(2,3), pos=1) # z2 = UInt8Col(dflt=1) # class info2(IsDescription): # y3 = Col.from_kind('float', dflt=1, shape=(2,3)) # z3 = UInt8Col(dflt=1) # name = StringCol(itemsize=2) # value = ComplexCol(itemsize=16) # class info3(IsDescription): # name = StringCol(itemsize=2) # value = ComplexCol(itemsize=16) # y4 = Col.from_kind('float', dflt=1, shape=(2,3)) # z4 = UInt8Col(dflt=1) # example cases of class Test klass = Test() # klass = Info() desc = Description(klass.columns) print("Description representation (short) ==>", desc) print("Description representation (long) ==>", repr(desc)) print("Column names ==>", desc._v_names) print("Column x ==>", desc.x) print("Column Info ==>", desc.Info) print("Column Info.value ==>", desc.Info.Value) print("Nested column names ==>", desc._v_nested_names) print("Defaults ==>", desc._v_dflts) print("Nested Formats ==>", desc._v_nested_formats) print("Nested Descriptions ==>", desc._v_nested_descr) print("Nested Descriptions (info) ==>", desc.info._v_nested_descr) print("Total size ==>", desc._v_dtype.itemsize) # check _f_walk for object in desc._f_walk(): if isinstance(object, Description): print("******begin object*************", end=' ') print("name -->", object._v_name) # print("name -->", object._v_dtype.name) # print("object childs-->", object._v_names) # print("object nested childs-->", object._v_nested_names) print("totalsize-->", object._v_dtype.itemsize) else: # pass print("leaf -->", object._v_name, object.dtype) class testDescParent(IsDescription): c = Int32Col() class testDesc(testDescParent): pass assert 'c' in testDesc.columns PyTables-3.7.0/tables/earray.py000066400000000000000000000223651416254111300163610ustar00rootroot00000000000000"""Here is defined the EArray class.""" import numpy as np from .utils import convert_to_np_atom2, SizeType from .carray import CArray # default version for EARRAY objects # obversion = "1.0" # initial version # obversion = "1.1" # support for complex datatypes # obversion = "1.2" # This adds support for time datatypes. # obversion = "1.3" # This adds support for enumerated datatypes. obversion = "1.4" # Numeric and numarray flavors are gone. class EArray(CArray): """This class represents extendable, homogeneous datasets in an HDF5 file. The main difference between an EArray and a CArray (see :ref:`CArrayClassDescr`), from which it inherits, is that the former can be enlarged along one of its dimensions, the *enlargeable dimension*. That means that the :attr:`Leaf.extdim` attribute (see :class:`Leaf`) of any EArray instance will always be non-negative. Multiple enlargeable dimensions might be supported in the future. New rows can be added to the end of an enlargeable array by using the :meth:`EArray.append` method. Parameters ---------- parentnode The parent :class:`Group` object. .. versionchanged:: 3.0 Renamed from *parentNode* to *parentnode*. name : str The name of this node in its parent group. atom An `Atom` instance representing the *type* and *shape* of the atomic objects to be saved. shape The shape of the new array. One (and only one) of the shape dimensions *must* be 0. The dimension being 0 means that the resulting `EArray` object can be extended along it. Multiple enlargeable dimensions are not supported right now. title A description for this node (it sets the ``TITLE`` HDF5 attribute on disk). filters An instance of the `Filters` class that provides information about the desired I/O filters to be applied during the life of this object. expectedrows A user estimate about the number of row elements that will be added to the growable dimension in the `EArray` node. If not provided, the default value is ``EXPECTED_ROWS_EARRAY`` (see ``tables/parameters.py``). If you plan to create either a much smaller or a much bigger `EArray` try providing a guess; this will optimize the HDF5 B-Tree creation and management process time and the amount of memory used. chunkshape The shape of the data chunk to be read or written in a single HDF5 I/O operation. Filters are applied to those chunks of data. The dimensionality of `chunkshape` must be the same as that of `shape` (beware: no dimension should be 0 this time!). If ``None``, a sensible value is calculated based on the `expectedrows` parameter (which is recommended). byteorder The byteorder of the data *on disk*, specified as 'little' or 'big'. If this is not specified, the byteorder is that of the platform. track_times Whether time data associated with the leaf are recorded (object access time, raw data modification time, metadata change time, object birth time); default True. Semantics of these times depend on their implementation in the HDF5 library: refer to documentation of the H5O_info_t data structure. As of HDF5 1.8.15, only ctime (metadata change time) is implemented. .. versionadded:: 3.4.3 Examples -------- See below a small example of the use of the `EArray` class. The code is available in ``examples/earray1.py``:: import numpy as np import tables as tb fileh = tb.open_file('earray1.h5', mode='w') a = tb.StringAtom(itemsize=8) # Use ``a`` as the object type for the enlargeable array. array_c = fileh.create_earray(fileh.root, 'array_c', a, (0,), \"Chars\") array_c.append(np.array(['a'*2, 'b'*4], dtype='S8')) array_c.append(np.array(['a'*6, 'b'*8, 'c'*10], dtype='S8')) # Read the string ``EArray`` we have created on disk. for s in array_c: print('array_c[%s] => %r' % (array_c.nrow, s)) # Close the file. fileh.close() The output for the previous script is something like:: array_c[0] => 'aa' array_c[1] => 'bbbb' array_c[2] => 'aaaaaa' array_c[3] => 'bbbbbbbb' array_c[4] => 'cccccccc' """ # Class identifier. _c_classid = 'EARRAY' def __init__(self, parentnode, name, atom=None, shape=None, title="", filters=None, expectedrows=None, chunkshape=None, byteorder=None, _log=True, track_times=True): # Specific of EArray if expectedrows is None: expectedrows = parentnode._v_file.params['EXPECTED_ROWS_EARRAY'] self._v_expectedrows = expectedrows """The expected number of rows to be stored in the array.""" # Call the parent (CArray) init code super().__init__(parentnode, name, atom, shape, title, filters, chunkshape, byteorder, _log, track_times) def _g_create(self): """Create a new array in file (specific part).""" # Pre-conditions and extdim computation zerodims = np.sum(np.array(self.shape) == 0) if zerodims > 0: if zerodims == 1: self.extdim = list(self.shape).index(0) else: raise NotImplementedError( "Multiple enlargeable (0-)dimensions are not " "supported.") else: raise ValueError( "When creating EArrays, you need to set one of " "the dimensions of the Atom instance to zero.") # Finish the common part of the creation process return self._g_create_common(self._v_expectedrows) def _check_shape_append(self, nparr): """Test that nparr shape is consistent with underlying EArray.""" # The arrays conforms self expandibility? myrank = len(self.shape) narank = len(nparr.shape) - len(self.atom.shape) if myrank != narank: raise ValueError(("the ranks of the appended object (%d) and the " "``%s`` EArray (%d) differ") % (narank, self._v_pathname, myrank)) for i in range(myrank): if i != self.extdim and self.shape[i] != nparr.shape[i]: raise ValueError(("the shapes of the appended object and the " "``%s`` EArray differ in non-enlargeable " "dimension %d") % (self._v_pathname, i)) def append(self, sequence): """Add a sequence of data to the end of the dataset. The sequence must have the same type as the array; otherwise a TypeError is raised. In the same way, the dimensions of the sequence must conform to the shape of the array, that is, all dimensions must match, with the exception of the enlargeable dimension, which can be of any length (even 0!). If the shape of the sequence is invalid, a ValueError is raised. """ self._g_check_open() self._v_file._check_writable() # Convert the sequence into a NumPy object nparr = convert_to_np_atom2(sequence, self.atom) # Check if it has a consistent shape with underlying EArray self._check_shape_append(nparr) # If the size of the nparr is zero, don't do anything else if nparr.size > 0: self._append(nparr) def _g_copy_with_stats(self, group, name, start, stop, step, title, filters, chunkshape, _log, **kwargs): """Private part of Leaf.copy() for each kind of leaf.""" (start, stop, step) = self._process_range_read(start, stop, step) # Build the new EArray object maindim = self.maindim shape = list(self.shape) shape[maindim] = 0 # The number of final rows nrows = len(range(start, stop, step)) # Build the new EArray object object = EArray( group, name, atom=self.atom, shape=shape, title=title, filters=filters, expectedrows=nrows, chunkshape=chunkshape, _log=_log) # Now, fill the new earray with values from source nrowsinbuf = self.nrowsinbuf # The slices parameter for self.__getitem__ slices = [slice(0, dim, 1) for dim in self.shape] # This is a hack to prevent doing unnecessary conversions # when copying buffers self._v_convert = False # Start the copy itself for start2 in range(start, stop, step * nrowsinbuf): # Save the records on disk stop2 = start2 + step * nrowsinbuf if stop2 > stop: stop2 = stop # Set the proper slice in the extensible dimension slices[maindim] = slice(start2, stop2, step) object._append(self.__getitem__(tuple(slices))) # Active the conversion again (default) self._v_convert = True nbytes = np.prod(self.shape, dtype=SizeType) * self.atom.itemsize return (object, nbytes) PyTables-3.7.0/tables/exceptions.py000066400000000000000000000250561416254111300172570ustar00rootroot00000000000000"""Declare exceptions and warnings that are specific to PyTables.""" import os import warnings import traceback __docformat__ = 'reStructuredText' """The format of documentation strings in this module.""" class HDF5ExtError(RuntimeError): """A low level HDF5 operation failed. This exception is raised the low level PyTables components used for accessing HDF5 files. It usually signals that something is not going well in the HDF5 library or even at the Input/Output level. Errors in the HDF5 C library may be accompanied by an extensive HDF5 back trace on standard error (see also :func:`tables.silence_hdf5_messages`). .. versionchanged:: 2.4 Parameters ---------- message error message h5bt This parameter (keyword only) controls the HDF5 back trace handling. Any keyword arguments other than h5bt is ignored. * if set to False the HDF5 back trace is ignored and the :attr:`HDF5ExtError.h5backtrace` attribute is set to None * if set to True the back trace is retrieved from the HDF5 library and stored in the :attr:`HDF5ExtError.h5backtrace` attribute as a list of tuples * if set to "VERBOSE" (default) the HDF5 back trace is stored in the :attr:`HDF5ExtError.h5backtrace` attribute and also included in the string representation of the exception * if not set (or set to None) the default policy is used (see :attr:`HDF5ExtError.DEFAULT_H5_BACKTRACE_POLICY`) """ # NOTE: in order to avoid circular dependencies between modules the # _dump_h5_backtrace method is set at initialization time in # the utilsExtenion. _dump_h5_backtrace = None DEFAULT_H5_BACKTRACE_POLICY = "VERBOSE" """Default policy for HDF5 backtrace handling * if set to False the HDF5 back trace is ignored and the :attr:`HDF5ExtError.h5backtrace` attribute is set to None * if set to True the back trace is retrieved from the HDF5 library and stored in the :attr:`HDF5ExtError.h5backtrace` attribute as a list of tuples * if set to "VERBOSE" (default) the HDF5 back trace is stored in the :attr:`HDF5ExtError.h5backtrace` attribute and also included in the string representation of the exception This parameter can be set using the :envvar:`PT_DEFAULT_H5_BACKTRACE_POLICY` environment variable. Allowed values are "IGNORE" (or "FALSE"), "SAVE" (or "TRUE") and "VERBOSE" to set the policy to False, True and "VERBOSE" respectively. The special value "DEFAULT" can be used to reset the policy to the default value .. versionadded:: 2.4 """ @classmethod def set_policy_from_env(cls): envmap = { "IGNORE": False, "FALSE": False, "SAVE": True, "TRUE": True, "VERBOSE": "VERBOSE", "DEFAULT": "VERBOSE", } oldvalue = cls.DEFAULT_H5_BACKTRACE_POLICY envvalue = os.environ.get("PT_DEFAULT_H5_BACKTRACE_POLICY", "DEFAULT") try: newvalue = envmap[envvalue.upper()] except KeyError: warnings.warn("Invalid value for the environment variable " "'PT_DEFAULT_H5_BACKTRACE_POLICY'. The default " "policy for HDF5 back trace management in PyTables " "will be: '%s'" % oldvalue) else: cls.DEFAULT_H5_BACKTRACE_POLICY = newvalue return oldvalue def __init__(self, *args, **kargs): super().__init__(*args) self._h5bt_policy = kargs.get('h5bt', self.DEFAULT_H5_BACKTRACE_POLICY) if self._h5bt_policy and self._dump_h5_backtrace is not None: self.h5backtrace = self._dump_h5_backtrace() """HDF5 back trace. Contains the HDF5 back trace as a (possibly empty) list of tuples. Each tuple has the following format:: (filename, line number, function name, text) Depending on the value of the *h5bt* parameter passed to the initializer the h5backtrace attribute can be set to None. This means that the HDF5 back trace has been simply ignored (not retrieved from the HDF5 C library error stack) or that there has been an error (silently ignored) during the HDF5 back trace retrieval. .. versionadded:: 2.4 See Also -------- traceback.format_list : :func:`traceback.format_list` """ # XXX: check _dump_h5_backtrace failures else: self.h5backtrace = None def __str__(self): """Returns a sting representation of the exception. The actual result depends on policy set in the initializer :meth:`HDF5ExtError.__init__`. .. versionadded:: 2.4 """ verbose = bool(self._h5bt_policy in ('VERBOSE', 'verbose')) if verbose and self.h5backtrace: bt = "\n".join([ "HDF5 error back trace\n", self.format_h5_backtrace(), "End of HDF5 error back trace" ]) if len(self.args) == 1 and isinstance(self.args[0], str): msg = super().__str__() msg = f"{bt}\n\n{msg}" elif self.h5backtrace[-1][-1]: msg = f"{bt}\n\n{self.h5backtrace[-1][-1]}" else: msg = bt else: msg = super().__str__() return msg def format_h5_backtrace(self, backtrace=None): """Convert the HDF5 trace back represented as a list of tuples. (see :attr:`HDF5ExtError.h5backtrace`) into a string. .. versionadded:: 2.4 """ if backtrace is None: backtrace = self.h5backtrace if backtrace is None: return 'No HDF5 back trace available' else: return ''.join(traceback.format_list(backtrace)) # Initialize the policy for HDF5 back trace handling HDF5ExtError.set_policy_from_env() # The following exceptions are concretions of the ``ValueError`` exceptions # raised by ``file`` objects on certain operations. class ClosedNodeError(ValueError): """The operation can not be completed because the node is closed. For instance, listing the children of a closed group is not allowed. """ pass class ClosedFileError(ValueError): """The operation can not be completed because the hosting file is closed. For instance, getting an existing node from a closed file is not allowed. """ pass class FileModeError(ValueError): """The operation can not be carried out because the mode in which the hosting file is opened is not adequate. For instance, removing an existing leaf from a read-only file is not allowed. """ pass class NodeError(AttributeError, LookupError): """Invalid hierarchy manipulation operation requested. This exception is raised when the user requests an operation on the hierarchy which can not be run because of the current layout of the tree. This includes accessing nonexistent nodes, moving or copying or creating over an existing node, non-recursively removing groups with children, and other similarly invalid operations. A node in a PyTables database cannot be simply overwritten by replacing it. Instead, the old node must be removed explicitely before another one can take its place. This is done to protect interactive users from inadvertedly deleting whole trees of data by a single erroneous command. """ pass class NoSuchNodeError(NodeError): """An operation was requested on a node that does not exist. This exception is raised when an operation gets a path name or a ``(where, name)`` pair leading to a nonexistent node. """ pass class UndoRedoError(Exception): """Problems with doing/redoing actions with Undo/Redo feature. This exception indicates a problem related to the Undo/Redo mechanism, such as trying to undo or redo actions with this mechanism disabled, or going to a nonexistent mark. """ pass class UndoRedoWarning(Warning): """Issued when an action not supporting Undo/Redo is run. This warning is only shown when the Undo/Redo mechanism is enabled. """ pass class NaturalNameWarning(Warning): """Issued when a non-pythonic name is given for a node. This is not an error and may even be very useful in certain contexts, but one should be aware that such nodes cannot be accessed using natural naming (instead, ``getattr()`` must be used explicitly). """ pass class PerformanceWarning(Warning): """Warning for operations which may cause a performance drop. This warning is issued when an operation is made on the database which may cause it to slow down on future operations (i.e. making the node tree grow too much). """ pass class FlavorError(ValueError): """Unsupported or unavailable flavor or flavor conversion. This exception is raised when an unsupported or unavailable flavor is given to a dataset, or when a conversion of data between two given flavors is not supported nor available. """ pass class FlavorWarning(Warning): """Unsupported or unavailable flavor conversion. This warning is issued when a conversion of data between two given flavors is not supported nor available, and raising an error would render the data inaccessible (e.g. on a dataset of an unavailable flavor in a read-only file). See the `FlavorError` class for more information. """ pass class FiltersWarning(Warning): """Unavailable filters. This warning is issued when a valid filter is specified but it is not available in the system. It may mean that an available default filter is to be used instead. """ pass class OldIndexWarning(Warning): """Unsupported index format. This warning is issued when an index in an unsupported format is found. The index will be marked as invalid and will behave as if doesn't exist. """ pass class DataTypeWarning(Warning): """Unsupported data type. This warning is issued when an unsupported HDF5 data type is found (normally in a file created with other tool than PyTables). """ pass class ExperimentalFeatureWarning(Warning): """Generic warning for experimental features. This warning is issued when using a functionality that is still experimental and that users have to use with care. """ pass PyTables-3.7.0/tables/expression.py000066400000000000000000000664661416254111300173070ustar00rootroot00000000000000"""Here is defined the Expr class.""" import sys import warnings import numexpr as ne import numpy as np import tables as tb from .exceptions import PerformanceWarning from .parameters import IO_BUFFER_SIZE, BUFFER_TIMES class Expr: """A class for evaluating expressions with arbitrary array-like objects. Expr is a class for evaluating expressions containing array-like objects. With it, you can evaluate expressions (like "3 * a + 4 * b") that operate on arbitrary large arrays while optimizing the resources required to perform them (basically main memory and CPU cache memory). It is similar to the Numexpr package (see :ref:`[NUMEXPR] `), but in addition to NumPy objects, it also accepts disk-based homogeneous arrays, like the Array, CArray, EArray and Column PyTables objects. .. warning:: Expr class only offers a subset of the Numexpr features due to the complexity of implement some of them when dealing with huge amount of data. All the internal computations are performed via the Numexpr package, so all the broadcast and upcasting rules of Numexpr applies here too. These rules are very similar to the NumPy ones, but with some exceptions due to the particularities of having to deal with potentially very large disk-based arrays. Be sure to read the documentation of the Expr constructor and methods as well as that of Numexpr, if you want to fully grasp these particularities. Parameters ---------- expr : str This specifies the expression to be evaluated, such as "2 * a + 3 * b". uservars : dict This can be used to define the variable names appearing in *expr*. This mapping should consist of identifier-like strings pointing to any `Array`, `CArray`, `EArray`, `Column` or NumPy ndarray instances (or even others which will tried to be converted to ndarrays). When `uservars` is not provided or `None`, the current local and global namespace is sought instead of `uservars`. It is also possible to pass just some of the variables in expression via the `uservars` mapping, and the rest will be retrieved from the current local and global namespaces. kwargs : dict This is meant to pass additional parameters to the Numexpr kernel. This is basically the same as the kwargs argument in Numexpr.evaluate(), and is mainly meant for advanced use. Examples -------- The following shows an example of using Expr:: >>> f = tb.open_file('/tmp/test_expr.h5', 'w') >>> a = f.create_array('/', 'a', np.array([1,2,3])) >>> b = f.create_array('/', 'b', np.array([3,4,5])) >>> c = np.array([4,5,6]) >>> expr = tb.Expr("2 * a + b * c") # initialize the expression >>> expr.eval() # evaluate it array([14, 24, 36], dtype=int64) >>> sum(expr) # use as an iterator 74 where you can see that you can mix different containers in the expression (whenever shapes are consistent). You can also work with multidimensional arrays:: >>> a2 = f.create_array('/', 'a2', np.array([[1,2],[3,4]])) >>> b2 = f.create_array('/', 'b2', np.array([[3,4],[5,6]])) >>> c2 = np.array([4,5]) # This will be broadcasted >>> expr = tb.Expr("2 * a2 + b2-c2") >>> expr.eval() array([[1, 3], [7, 9]], dtype=int64) >>> sum(expr) array([ 8, 12], dtype=int64) >>> f.close() .. rubric:: Expr attributes .. attribute:: append_mode The append mode for user-provided output containers. .. attribute:: maindim Common main dimension for inputs in expression. .. attribute:: names The names of variables in expression (list). .. attribute:: out The user-provided container (if any) for the expression outcome. .. attribute:: o_start The start range selection for the user-provided output. .. attribute:: o_stop The stop range selection for the user-provided output. .. attribute:: o_step The step range selection for the user-provided output. .. attribute:: shape Common shape for the arrays in expression. .. attribute:: values The values of variables in expression (list). """ _exprvars_cache = {} """Cache of variables participating in expressions. .. versionadded:: 3.0 """ def __init__(self, expr, uservars=None, **kwargs): self.append_mode = False """The append mode for user-provided output containers.""" self.maindim = 0 """Common main dimension for inputs in expression.""" self.names = [] """The names of variables in expression (list).""" self.out = None """The user-provided container (if any) for the expression outcome.""" self.o_start = None """The start range selection for the user-provided output.""" self.o_stop = None """The stop range selection for the user-provided output.""" self.o_step = None """The step range selection for the user-provided output.""" self.shape = None """Common shape for the arrays in expression.""" self.start, self.stop, self.step = (None,) * 3 self.start = None """The start range selection for the input.""" self.stop = None """The stop range selection for the input.""" self.step = None """The step range selection for the input.""" self.values = [] """The values of variables in expression (list).""" self._compiled_expr = None """The compiled expression.""" self._single_row_out = None """A sample of the output with just a single row.""" # First, get the signature for the arrays in expression vars_ = self._required_expr_vars(expr, uservars) context = ne.necompiler.getContext(kwargs) self.names, _ = ne.necompiler.getExprNames(expr, context) # Raise a ValueError in case we have unsupported objects for name, var in vars_.items(): if type(var) in (int, float, str): continue if not isinstance(var, (tb.Leaf, tb.Column)): if hasattr(var, "dtype"): # Quacks like a NumPy object continue raise TypeError("Unsupported variable type: %r" % var) objname = var.__class__.__name__ if objname not in ("Array", "CArray", "EArray", "Column"): raise TypeError("Unsupported variable type: %r" % var) # NumPy arrays to be copied? (we don't need to worry about # PyTables objects, as the reads always return contiguous and # aligned objects, or at least I think so). for name, var in vars_.items(): if isinstance(var, np.ndarray): # See numexpr.necompiler.evaluate for a rational # of the code below if not var.flags.aligned: if var.ndim != 1: # Do a copy of this variable var = var.copy() # Update the vars_ dictionary vars_[name] = var # Get the variables and types values = self.values types_ = [] for name in self.names: value = vars_[name] if hasattr(value, 'atom'): types_.append(value.atom) elif hasattr(value, 'dtype'): types_.append(value) else: # try to convert into a NumPy array value = np.array(value) types_.append(value) values.append(value) # Create a signature for the expression signature = [(name, ne.necompiler.getType(type_)) for (name, type_) in zip(self.names, types_)] # Compile the expression self._compiled_expr = ne.necompiler.NumExpr(expr, signature, **kwargs) # Guess the shape for the outcome and the maindim of inputs self.shape, self.maindim = self._guess_shape() # The next method is similar to their counterpart in `Table`, but # adapted to the `Expr` own requirements. def _required_expr_vars(self, expression, uservars, depth=2): """Get the variables required by the `expression`. A new dictionary defining the variables used in the `expression` is returned. Required variables are first looked up in the `uservars` mapping, then in the set of top-level columns of the table. Unknown variables cause a `NameError` to be raised. When `uservars` is `None`, the local and global namespace where the API callable which uses this method is called is sought instead. To disable this mechanism, just specify a mapping as `uservars`. Nested columns and variables with an ``uint64`` type are not allowed (`TypeError` and `NotImplementedError` are raised, respectively). `depth` specifies the depth of the frame in order to reach local or global variables. """ # Get the names of variables used in the expression. exprvars_cache = self._exprvars_cache if expression not in exprvars_cache: # Protection against growing the cache too much if len(exprvars_cache) > 256: # Remove 10 (arbitrary) elements from the cache for k in list(exprvars_cache)[:10]: del exprvars_cache[k] cexpr = compile(expression, '', 'eval') exprvars = [var for var in cexpr.co_names if var not in ['None', 'False', 'True'] and var not in ne.expressions.functions] exprvars_cache[expression] = exprvars else: exprvars = exprvars_cache[expression] # Get the local and global variable mappings of the user frame # if no mapping has been explicitly given for user variables. user_locals, user_globals = {}, {} if uservars is None: user_frame = sys._getframe(depth) user_locals = user_frame.f_locals user_globals = user_frame.f_globals # Look for the required variables first among the ones # explicitly provided by the user. reqvars = {} for var in exprvars: # Get the value. if uservars is not None and var in uservars: val = uservars[var] elif uservars is None and var in user_locals: val = user_locals[var] elif uservars is None and var in user_globals: val = user_globals[var] else: raise NameError("name ``%s`` is not defined" % var) # Check the value. if hasattr(val, 'dtype') and val.dtype.str[1:] == 'u8': raise NotImplementedError( "variable ``%s`` refers to " "a 64-bit unsigned integer object, that is " "not yet supported in expressions, sorry; " % var) elif hasattr(val, '_v_colpathnames'): # nested column # This branch is never reached because the compile step # above already raise a ``TypeError`` for nested # columns, but that could change in the future. So it # is best to let this here. raise TypeError( "variable ``%s`` refers to a nested column, " "not allowed in expressions" % var) reqvars[var] = val return reqvars def set_inputs_range(self, start=None, stop=None, step=None): """Define a range for all inputs in expression. The computation will only take place for the range defined by the start, stop and step parameters in the main dimension of inputs (or the leading one, if the object lacks the concept of main dimension, like a NumPy container). If not a common main dimension exists for all inputs, the leading dimension will be used instead. """ self.start = start self.stop = stop self.step = step def set_output(self, out, append_mode=False): """Set out as container for output as well as the append_mode. The out must be a container that is meant to keep the outcome of the expression. It should be an homogeneous type container and can typically be an Array, CArray, EArray, Column or a NumPy ndarray. The append_mode specifies the way of which the output is filled. If true, the rows of the outcome are *appended* to the out container. Of course, for doing this it is necessary that out would have an append() method (like an EArray, for example). If append_mode is false, the output is set via the __setitem__() method (see the Expr.set_output_range() for info on how to select the rows to be updated). If out is smaller than what is required by the expression, only the computations that are needed to fill up the container are carried out. If it is larger, the excess elements are unaffected. """ if not (hasattr(out, "shape") and hasattr(out, "__setitem__")): raise ValueError( "You need to pass a settable multidimensional container " "as output") self.out = out if append_mode and not hasattr(out, "append"): raise ValueError( "For activating the ``append`` mode, you need a container " "with an `append()` method (like the `EArray`)") self.append_mode = append_mode def set_output_range(self, start=None, stop=None, step=None): """Define a range for user-provided output object. The output object will only be modified in the range specified by the start, stop and step parameters in the main dimension of output (or the leading one, if the object does not have the concept of main dimension, like a NumPy container). """ if self.out is None: raise IndexError( "You need to pass an output object to `setOut()` first") self.o_start = start self.o_stop = stop self.o_step = step # Although the next code is similar to the method in `Leaf`, it # allows the use of pure NumPy objects. def _calc_nrowsinbuf(self, object_): """Calculate the number of rows that will fit in a buffer.""" # Compute the rowsize for the *leading* dimension shape_ = list(object_.shape) if shape_: shape_[0] = 1 rowsize = np.prod(shape_) * object_.dtype.itemsize # Compute the nrowsinbuf # Multiplying the I/O buffer size by 4 gives optimal results # in my benchmarks with `tables.Expr` (see ``bench/poly.py``) buffersize = IO_BUFFER_SIZE * 4 nrowsinbuf = buffersize // rowsize # Safeguard against row sizes being extremely large if nrowsinbuf == 0: nrowsinbuf = 1 # If rowsize is too large, issue a Performance warning maxrowsize = BUFFER_TIMES * buffersize if rowsize > maxrowsize: warnings.warn("""\ The object ``%s`` is exceeding the maximum recommended rowsize (%d bytes); be ready to see PyTables asking for *lots* of memory and possibly slow I/O. You may want to reduce the rowsize by trimming the value of dimensions that are orthogonal (and preferably close) to the *leading* dimension of this object.""" % (object, maxrowsize), PerformanceWarning) return nrowsinbuf def _guess_shape(self): """Guess the shape of the output of the expression.""" # First, compute the maximum dimension of inputs and maindim # (if it exists) maxndim = 0 maindims = [] for val in self.values: # Get the minimum of the lengths if len(val.shape) > maxndim: maxndim = len(val.shape) if hasattr(val, "maindim"): maindims.append(val.maindim) if maxndim == 0: self._single_row_out = out = self._compiled_expr(*self.values) return (), None if maindims and [maindims[0]] * len(maindims) == maindims: # If all maindims detected are the same, use this as maindim maindim = maindims[0] else: # If not, the main dimension will be the default one maindim = 0 # The slices parameter for inputs slices = (slice(None),) * maindim + (0,) # Now, collect the values in first row of arrays with maximum dims vals = [] lens = [] for val in self.values: shape = val.shape # Warning: don't use len(val) below or it will raise an # `Overflow` error on 32-bit platforms for large enough arrays. if shape != () and shape[maindim] == 0: vals.append(val[:]) lens.append(0) elif len(shape) < maxndim: vals.append(val) else: vals.append(val.__getitem__(slices)) lens.append(shape[maindim]) minlen = min(lens) self._single_row_out = out = self._compiled_expr(*vals) shape = list(out.shape) if minlen > 0: shape.insert(maindim, minlen) return shape, maindim def _get_info(self, shape, maindim, itermode=False): """Return various info needed for evaluating the computation loop.""" # Compute the shape of the resulting container having # in account new possible values of start, stop and step in # the inputs range if maindim is not None: (start, stop, step) = slice( self.start, self.stop, self.step).indices(shape[maindim]) shape[maindim] = min( shape[maindim], len(range(start, stop, step))) i_nrows = shape[maindim] else: start, stop, step = 0, 0, None i_nrows = 0 if not itermode: # Create a container for output if not defined yet o_maindim = 0 # Default maindim if self.out is None: out = np.empty(shape, dtype=self._single_row_out.dtype) # Get the trivial values for start, stop and step if maindim is not None: (o_start, o_stop, o_step) = (0, shape[maindim], 1) else: (o_start, o_stop, o_step) = (0, 0, 1) else: out = self.out # Out container already provided. Do some sanity checks. if hasattr(out, "maindim"): o_maindim = out.maindim # Refine the shape of the resulting container having in # account new possible values of start, stop and step in # the output range o_shape = list(out.shape) s = slice(self.o_start, self.o_stop, self.o_step) o_start, o_stop, o_step = s.indices(o_shape[o_maindim]) o_shape[o_maindim] = min(o_shape[o_maindim], len(range(o_start, o_stop, o_step))) # Check that the shape of output is consistent with inputs tr_oshape = list(o_shape) # this implies a copy olen_ = tr_oshape.pop(o_maindim) tr_shape = list(shape) # do a copy if maindim is not None: len_ = tr_shape.pop(o_maindim) else: len_ = 1 if tr_oshape != tr_shape: raise ValueError( "Shape for out container does not match expression") # Force the input length to fit in `out` if not self.append_mode and olen_ < len_: shape[o_maindim] = olen_ stop = start + olen_ # Get the positions of inputs that should be sliced (the others # will be broadcasted) ndim = len(shape) slice_pos = [i for i, val in enumerate(self.values) if len(val.shape) == ndim] # The size of the I/O buffer nrowsinbuf = 1 for i, val in enumerate(self.values): # Skip scalar values in variables if i in slice_pos: nrows = self._calc_nrowsinbuf(val) if nrows > nrowsinbuf: nrowsinbuf = nrows if not itermode: return (i_nrows, slice_pos, start, stop, step, nrowsinbuf, out, o_maindim, o_start, o_stop, o_step) else: # For itermode, we don't need the out info return (i_nrows, slice_pos, start, stop, step, nrowsinbuf) def eval(self): """Evaluate the expression and return the outcome. Because of performance reasons, the computation order tries to go along the common main dimension of all inputs. If not such a common main dimension is found, the iteration will go along the leading dimension instead. For non-consistent shapes in inputs (i.e. shapes having a different number of dimensions), the regular NumPy broadcast rules applies. There is one exception to this rule though: when the dimensions orthogonal to the main dimension of the expression are consistent, but the main dimension itself differs among the inputs, then the shortest one is chosen for doing the computations. This is so because trying to expand very large on-disk arrays could be too expensive or simply not possible. Also, the regular Numexpr casting rules (which are similar to those of NumPy, although you should check the Numexpr manual for the exceptions) are applied to determine the output type. Finally, if the setOuput() method specifying a user container has already been called, the output is sent to this user-provided container. If not, a fresh NumPy container is returned instead. .. warning:: When dealing with large on-disk inputs, failing to specify an on-disk container may consume all your available memory. """ values, shape, maindim = self.values, self.shape, self.maindim # Get different info we need for the main computation loop (i_nrows, slice_pos, start, stop, step, nrowsinbuf, out, o_maindim, o_start, o_stop, o_step) = \ self._get_info(shape, maindim) if i_nrows == 0: # No elements to compute if start >= stop and self.start is not None: return out else: return self._single_row_out # Create a key that selects every element in inputs and output # (including the main dimension) i_slices = [slice(None)] * (maindim + 1) o_slices = [slice(None)] * (o_maindim + 1) # This is a hack to prevent doing unnecessary flavor conversions # while reading buffers for val in values: if hasattr(val, 'maindim'): val._v_convert = False # Start the computation itself for start2 in range(start, stop, step * nrowsinbuf): stop2 = start2 + step * nrowsinbuf if stop2 > stop: stop2 = stop # Set the proper slice for inputs i_slices[maindim] = slice(start2, stop2, step) # Get the input values vals = [] for i, val in enumerate(values): if i in slice_pos: vals.append(val.__getitem__(tuple(i_slices))) else: # A read of values is not apparently needed, as PyTables # leaves seems to work just fine inside Numexpr vals.append(val) # Do the actual computation for this slice rout = self._compiled_expr(*vals) # Set the values into the out buffer if self.append_mode: out.append(rout) else: # Compute the slice to be filled in output start3 = o_start + (start2 - start) // step stop3 = start3 + nrowsinbuf * o_step if stop3 > o_stop: stop3 = o_stop o_slices[o_maindim] = slice(start3, stop3, o_step) # Set the slice out[tuple(o_slices)] = rout # Activate the conversion again (default) for val in values: if hasattr(val, 'maindim'): val._v_convert = True return out def __iter__(self): """Iterate over the rows of the outcome of the expression. This iterator always returns rows as NumPy objects, so a possible out container specified in :meth:`Expr.set_output` method is ignored here. """ values, shape, maindim = self.values, self.shape, self.maindim # Get different info we need for the main computation loop (i_nrows, slice_pos, start, stop, step, nrowsinbuf) = \ self._get_info(shape, maindim, itermode=True) if i_nrows == 0: # No elements to compute return # Create a key that selects every element in inputs # (including the main dimension) i_slices = [slice(None)] * (maindim + 1) # This is a hack to prevent doing unnecessary flavor conversions # while reading buffers for val in values: if hasattr(val, 'maindim'): val._v_convert = False # Start the computation itself for start2 in range(start, stop, step * nrowsinbuf): stop2 = start2 + step * nrowsinbuf if stop2 > stop: stop2 = stop # Set the proper slice in the main dimension i_slices[maindim] = slice(start2, stop2, step) # Get the values for computing the buffer vals = [] for i, val in enumerate(values): if i in slice_pos: vals.append(val.__getitem__(tuple(i_slices))) else: # A read of values is not apparently needed, as PyTables # leaves seems to work just fine inside Numexpr vals.append(val) # Do the actual computation rout = self._compiled_expr(*vals) # Return one row per call yield from rout # Activate the conversion again (default) for val in values: if hasattr(val, 'maindim'): val._v_convert = True if __name__ == "__main__": # shape = (10000,10000) shape = (10, 10_000) f = tb.open_file("/tmp/expression.h5", "w") # Create some arrays a = f.create_carray(f.root, 'a', atom=tb.Float32Atom(dflt=1), shape=shape) b = f.create_carray(f.root, 'b', atom=tb.Float32Atom(dflt=2), shape=shape) c = f.create_carray(f.root, 'c', atom=tb.Float32Atom(dflt=3), shape=shape) out = f.create_carray(f.root, 'out', atom=tb.Float32Atom(dflt=3), shape=shape) expr = Expr("a * b + c") expr.set_output(out) d = expr.eval() print("returned-->", repr(d)) # print(`d[:]`) f.close() PyTables-3.7.0/tables/file.py000066400000000000000000003164771416254111300160270ustar00rootroot00000000000000"""Create PyTables files and the object tree. This module support importing generic HDF5 files, on top of which PyTables files are created, read or extended. If a file exists, an object tree mirroring their hierarchical structure is created in memory. File class offer methods to traverse the tree, as well as to create new nodes. """ import atexit import datetime import os import sys import weakref import warnings from collections import defaultdict from pathlib import Path import numexpr as ne import numpy as np from . import hdf5extension from . import utilsextension from . import parameters from .exceptions import (ClosedFileError, FileModeError, NodeError, NoSuchNodeError, UndoRedoError, ClosedNodeError, PerformanceWarning) from .registry import get_class_by_name from .path import join_path, split_path from . import undoredo from .description import (IsDescription, UInt8Col, StringCol, descr_from_dtype, dtype_from_descr) from .filters import Filters from .node import Node, NotLoggedMixin from .group import Group, RootGroup from .group import TransactionGroupG, TransactionG, MarkG from .leaf import Leaf from .array import Array from .carray import CArray from .earray import EArray from .vlarray import VLArray from .table import Table from . import linkextension from .utils import detect_number_of_cores from . import lrucacheextension from .flavor import flavor_of, array_as_internal from .atom import Atom from .link import SoftLink, ExternalLink # format_version = "1.0" # Initial format # format_version = "1.1" # Changes in ucl compression # format_version = "1.2" # Support for enlargeable arrays and VLA's # # 1.2 was introduced in PyTables 0.8 # format_version = "1.3" # Support for indexes in Tables # # 1.3 was introduced in PyTables 0.9 # format_version = "1.4" # Support for multidimensional attributes # # 1.4 was introduced in PyTables 1.1 # format_version = "1.5" # Support for persistent defaults in tables # # 1.5 was introduced in PyTables 1.2 # format_version = "1.6" # Support for NumPy objects and new flavors for # # objects. # # 1.6 was introduced in pytables 1.3 # format_version = "2.0" # Pickles are not used anymore in system attrs # # 2.0 was introduced in PyTables 2.0 format_version = "2.1" # Numeric and numarray flavors are gone. compatible_formats = [] # Old format versions we can read # # Empty means that we support all the old formats class _FileRegistry: def __init__(self): self._name_mapping = defaultdict(set) self._handlers = set() @property def filenames(self): return list(self._name_mapping) @property def handlers(self): # return set(self._handlers) # return a copy return self._handlers def __len__(self): return len(self._handlers) def __contains__(self, filename): return filename in self.filenames def add(self, handler): self._name_mapping[handler.filename].add(handler) self._handlers.add(handler) def remove(self, handler): filename = handler.filename self._name_mapping[filename].remove(handler) # remove enpty keys if not self._name_mapping[filename]: del self._name_mapping[filename] self._handlers.remove(handler) def get_handlers_by_name(self, filename): # return set(self._name_mapping[filename]) # return a copy return self._name_mapping[filename] def close_all(self): are_open_files = len(self._handlers) > 0 if are_open_files: sys.stderr.write("Closing remaining open files:") handlers = list(self._handlers) # make a copy for fileh in handlers: sys.stderr.write("%s..." % fileh.filename) fileh.close() sys.stderr.write("done") if are_open_files: sys.stderr.write("\n") # Dict of opened files (keys are filenames and values filehandlers) _open_files = _FileRegistry() # Opcodes for do-undo actions _op_to_code = { "MARK": 0, "CREATE": 1, "REMOVE": 2, "MOVE": 3, "ADDATTR": 4, "DELATTR": 5, } _code_to_op = ["MARK", "CREATE", "REMOVE", "MOVE", "ADDATTR", "DELATTR"] # Paths and names for hidden nodes related with transactions. _trans_version = '1.0' _trans_group_parent = '/' _trans_group_name = '_p_transactions' _trans_group_path = join_path(_trans_group_parent, _trans_group_name) _action_log_parent = _trans_group_path _action_log_name = 'actionlog' _action_log_path = join_path(_action_log_parent, _action_log_name) _trans_parent = _trans_group_path _trans_name = 't%d' # %d -> transaction number _trans_path = join_path(_trans_parent, _trans_name) _markParent = _trans_path _markName = 'm%d' # %d -> mark number _markPath = join_path(_markParent, _markName) _shadow_parent = _markPath _shadow_name = 'a%d' # %d -> action number _shadow_path = join_path(_shadow_parent, _shadow_name) def _checkfilters(filters): if not (filters is None or isinstance(filters, Filters)): raise TypeError("filter parameter has to be None or a Filter " "instance and the passed type is: '%s'" % type(filters)) def copy_file(srcfilename, dstfilename, overwrite=False, **kwargs): """An easy way of copying one PyTables file to another. This function allows you to copy an existing PyTables file named srcfilename to another file called dstfilename. The source file must exist and be readable. The destination file can be overwritten in place if existing by asserting the overwrite argument. This function is a shorthand for the :meth:`File.copy_file` method, which acts on an already opened file. kwargs takes keyword arguments used to customize the copying process. See the documentation of :meth:`File.copy_file` for a description of those arguments. """ # Open the source file. srcfileh = open_file(srcfilename, mode="r") try: # Copy it to the destination file. srcfileh.copy_file(dstfilename, overwrite=overwrite, **kwargs) finally: # Close the source file. srcfileh.close() hdf5_version_str = utilsextension.get_hdf5_version() hdf5_version_tup = tuple(map(int, hdf5_version_str.split('-')[0].split('.'))) _FILE_OPEN_POLICY = 'strict' if hdf5_version_tup < (1, 8, 7) else 'default' def open_file(filename, mode="r", title="", root_uep="/", filters=None, **kwargs): """Open a PyTables (or generic HDF5) file and return a File object. Parameters ---------- filename : str The name of the file (supports environment variable expansion). It is suggested that file names have any of the .h5, .hdf or .hdf5 extensions, although this is not mandatory. mode : str The mode to open the file. It can be one of the following: * *'r'*: Read-only; no data can be modified. * *'w'*: Write; a new file is created (an existing file with the same name would be deleted). * *'a'*: Append; an existing file is opened for reading and writing, and if the file does not exist it is created. * *'r+'*: It is similar to 'a', but the file must already exist. title : str If the file is to be created, a TITLE string attribute will be set on the root group with the given value. Otherwise, the title will be read from disk, and this will not have any effect. root_uep : str The root User Entry Point. This is a group in the HDF5 hierarchy which will be taken as the starting point to create the object tree. It can be whatever existing group in the file, named by its HDF5 path. If it does not exist, an HDF5ExtError is issued. Use this if you do not want to build the *entire* object tree, but rather only a *subtree* of it. .. versionchanged:: 3.0 The *rootUEP* parameter has been renamed into *root_uep*. filters : Filters An instance of the Filters (see :ref:`FiltersClassDescr`) class that provides information about the desired I/O filters applicable to the leaves that hang directly from the *root group*, unless other filter properties are specified for these leaves. Besides, if you do not specify filter properties for child groups, they will inherit these ones, which will in turn propagate to child nodes. Notes ----- In addition, it recognizes the (lowercase) names of parameters present in :file:`tables/parameters.py` as additional keyword arguments. See :ref:`parameter_files` for a detailed info on the supported parameters. .. note:: If you need to deal with a large number of nodes in an efficient way, please see :ref:`LRUOptim` for more info and advices about the integrated node cache engine. """ filename = os.fspath(filename) # XXX filename normalization ?? # Check already opened files if _FILE_OPEN_POLICY == 'strict': # This policy do not allows to open the same file multiple times # even in read-only mode if filename in _open_files: raise ValueError( "The file '%s' is already opened. " "Please close it before reopening. " "HDF5 v.%s, FILE_OPEN_POLICY = '%s'" % ( filename, utilsextension.get_hdf5_version(), _FILE_OPEN_POLICY)) else: for filehandle in _open_files.get_handlers_by_name(filename): omode = filehandle.mode # 'r' is incompatible with everything except 'r' itself if mode == 'r' and omode != 'r': raise ValueError( "The file '%s' is already opened, but " "not in read-only mode (as requested)." % filename) # 'a' and 'r+' are compatible with everything except 'r' elif mode in ('a', 'r+') and omode == 'r': raise ValueError( "The file '%s' is already opened, but " "in read-only mode. Please close it before " "reopening in append mode." % filename) # 'w' means that we want to destroy existing contents elif mode == 'w': raise ValueError( "The file '%s' is already opened. Please " "close it before reopening in write mode." % filename) # Finally, create the File instance, and return it return File(filename, mode, title, root_uep, filters, **kwargs) # A dumb class that doesn't keep nothing at all class _NoCache: def __len__(self): return 0 def __contains__(self, key): return False def __iter__(self): return iter([]) def __setitem__(self, key, value): pass __marker = object() def pop(self, key, d=__marker): if d is not self.__marker: return d raise KeyError(key) class _DictCache(dict): def __init__(self, nslots): if nslots < 1: raise ValueError("Invalid number of slots: %d" % nslots) self.nslots = nslots super().__init__() def __setitem__(self, key, value): # Check if we are running out of space if len(self) > self.nslots: warnings.warn( "the dictionary of node cache is exceeding the recommended " "maximum number (%d); be ready to see PyTables asking for " "*lots* of memory and possibly slow I/O." % ( self.nslots), PerformanceWarning) super().__setitem__(key, value) class NodeManager: def __init__(self, nslots=64, node_factory=None): super().__init__() self.registry = weakref.WeakValueDictionary() if nslots > 0: cache = lrucacheextension.NodeCache(nslots) elif nslots == 0: cache = _NoCache() else: # nslots < 0 cache = _DictCache(-nslots) self.cache = cache # node_factory(node_path) self.node_factory = node_factory def register_node(self, node, key): if key is None: key = node._v_pathname if key in self.registry: if not self.registry[key]._v_isopen: del self.registry[key] self.registry[key] = node elif self.registry[key] is not node: raise RuntimeError('trying to register a node with an ' 'existing key: ``%s``' % key) else: self.registry[key] = node def cache_node(self, node, key=None): if key is None: key = node._v_pathname self.register_node(node, key) if key in self.cache: oldnode = self.cache.pop(key) if oldnode is not node and oldnode._v_isopen: raise RuntimeError('trying to cache a node with an ' 'existing key: ``%s``' % key) self.cache[key] = node def get_node(self, key): node = self.cache.pop(key, None) if node is not None: if node._v_isopen: self.cache_node(node, key) return node else: # this should not happen warnings.warn("a closed node found in the cache: ``%s``" % key) if key in self.registry: node = self.registry[key] if node is None: # this should not happen since WeakValueDictionary drops all # dead weakrefs warnings.warn("None is stored in the registry for key: " "``%s``" % key) elif node._v_isopen: self.cache_node(node, key) return node else: # this should not happen warnings.warn("a closed node found in the registry: " "``%s``" % key) del self.registry[key] node = None if self.node_factory: node = self.node_factory(key) self.cache_node(node, key) return node def rename_node(self, oldkey, newkey): for cache in (self.cache, self.registry): if oldkey in cache: node = cache.pop(oldkey) cache[newkey] = node def drop_from_cache(self, nodepath): """Remove the node from cache""" # Remove the node from the cache. self.cache.pop(nodepath, None) def drop_node(self, node, check_unregistered=True): """Drop the `node`. Remove the node from the cache and, if it has no more references, close it. """ # Remove all references to the node. nodepath = node._v_pathname self.drop_from_cache(nodepath) if nodepath in self.registry: if not node._v_isopen: del self.registry[nodepath] elif check_unregistered: # If the node is not in the registry (this should never happen) # we close it forcibly since it is not ensured that the __del__ # method is called for object that are still alive when the # interpreter is shut down if node._v_isopen: warnings.warn("dropping a node that is not in the registry: " "``%s``" % nodepath) node._g_pre_kill_hook() node._f_close() def flush_nodes(self): # Only iter on the nodes in the registry since nodes in the cahce # should always have an entry in the registry closed_keys = [] for path, node in list(self.registry.items()): if not node._v_isopen: closed_keys.append(path) elif '/_i_' not in path: # Indexes are not necessary to be flushed if isinstance(node, Leaf): node.flush() for path in closed_keys: # self.cache.pop(path, None) if path in self.cache: warnings.warn("closed node the cache: ``%s``" % path) self.cache.pop(path, None) self.registry.pop(path) @staticmethod def _close_nodes(nodepaths, get_node): for nodepath in nodepaths: try: node = get_node(nodepath) except KeyError: pass else: if not node._v_isopen or node._v__deleting: continue try: # Avoid descendent nodes to also iterate over # their descendents, which are already to be # closed by this loop. if hasattr(node, '_f_get_child'): node._g_close() else: node._f_close() del node except ClosedNodeError: # import traceback # type_, value, tb = sys.exc_info() # exception_dump = ''.join( # traceback.format_exception(type_, value, tb)) # warnings.warn( # "A '%s' exception occurred trying to close a node " # "that was supposed to be open.\n" # "%s" % (type_.__name__, exception_dump)) pass def close_subtree(self, prefix='/'): if not prefix.endswith('/'): prefix = prefix + '/' cache = self.cache registry = self.registry # Ensure tables are closed before their indices paths = [ path for path in cache if path.startswith(prefix) and '/_i_' not in path ] self._close_nodes(paths, cache.pop) # Close everything else (i.e. indices) paths = [path for path in cache if path.startswith(prefix)] self._close_nodes(paths, cache.pop) # Ensure tables are closed before their indices paths = [ path for path in registry if path.startswith(prefix) and '/_i_' not in path ] self._close_nodes(paths, registry.pop) # Close everything else (i.e. indices) paths = [path for path in registry if path.startswith(prefix)] self._close_nodes(paths, registry.pop) def shutdown(self): registry = self.registry cache = self.cache # self.close_subtree('/') keys = list(cache) # copy for key in keys: node = cache.pop(key) if node._v_isopen: registry.pop(node._v_pathname, None) node._f_close() while registry: key, node = registry.popitem() if node._v_isopen: node._f_close() class File(hdf5extension.File): """The in-memory representation of a PyTables file. An instance of this class is returned when a PyTables file is opened with the :func:`tables.open_file` function. It offers methods to manipulate (create, rename, delete...) nodes and handle their attributes, as well as methods to traverse the object tree. The *user entry point* to the object tree attached to the HDF5 file is represented in the root_uep attribute. Other attributes are available. File objects support an *Undo/Redo mechanism* which can be enabled with the :meth:`File.enable_undo` method. Once the Undo/Redo mechanism is enabled, explicit *marks* (with an optional unique name) can be set on the state of the database using the :meth:`File.mark` method. There are two implicit marks which are always available: the initial mark (0) and the final mark (-1). Both the identifier of a mark and its name can be used in *undo* and *redo* operations. Hierarchy manipulation operations (node creation, movement and removal) and attribute handling operations (setting and deleting) made after a mark can be undone by using the :meth:`File.undo` method, which returns the database to the state of a past mark. If undo() is not followed by operations that modify the hierarchy or attributes, the :meth:`File.redo` method can be used to return the database to the state of a future mark. Else, future states of the database are forgotten. Note that data handling operations can not be undone nor redone by now. Also, hierarchy manipulation operations on nodes that do not support the Undo/Redo mechanism issue an UndoRedoWarning *before* changing the database. The Undo/Redo mechanism is persistent between sessions and can only be disabled by calling the :meth:`File.disable_undo` method. File objects can also act as context managers when using the with statement introduced in Python 2.5. When exiting a context, the file is automatically closed. Parameters ---------- filename : str The name of the file (supports environment variable expansion). It is suggested that file names have any of the .h5, .hdf or .hdf5 extensions, although this is not mandatory. mode : str The mode to open the file. It can be one of the following: * *'r'*: Read-only; no data can be modified. * *'w'*: Write; a new file is created (an existing file with the same name would be deleted). * *'a'*: Append; an existing file is opened for reading and writing, and if the file does not exist it is created. * *'r+'*: It is similar to 'a', but the file must already exist. title : str If the file is to be created, a TITLE string attribute will be set on the root group with the given value. Otherwise, the title will be read from disk, and this will not have any effect. root_uep : str The root User Entry Point. This is a group in the HDF5 hierarchy which will be taken as the starting point to create the object tree. It can be whatever existing group in the file, named by its HDF5 path. If it does not exist, an HDF5ExtError is issued. Use this if you do not want to build the *entire* object tree, but rather only a *subtree* of it. .. versionchanged:: 3.0 The *rootUEP* parameter has been renamed into *root_uep*. filters : Filters An instance of the Filters (see :ref:`FiltersClassDescr`) class that provides information about the desired I/O filters applicable to the leaves that hang directly from the *root group*, unless other filter properties are specified for these leaves. Besides, if you do not specify filter properties for child groups, they will inherit these ones, which will in turn propagate to child nodes. Notes ----- In addition, it recognizes the (lowercase) names of parameters present in :file:`tables/parameters.py` as additional keyword arguments. See :ref:`parameter_files` for a detailed info on the supported parameters. .. rubric:: File attributes .. attribute:: filename The name of the opened file. .. attribute:: format_version The PyTables version number of this file. .. attribute:: isopen True if the underlying file is open, false otherwise. .. attribute:: mode The mode in which the file was opened. .. attribute:: root The *root* of the object tree hierarchy (a Group instance). .. attribute:: root_uep The UEP (user entry point) group name in the file (see the :func:`open_file` function). .. versionchanged:: 3.0 The *rootUEP* attribute has been renamed into *root_uep*. """ # The top level kinds. Group must go first! _node_kinds = ('Group', 'Leaf', 'Link', 'Unknown') @property def title(self): """The title of the root group in the file.""" return self.root._v_title @title.setter def title(self, title): self.root._v_title = title @title.deleter def title(self): del self.root._v_title @property def filters(self): """Default filter properties for the root group (see :ref:`FiltersClassDescr`).""" return self.root._v_filters @filters.setter def filters(self, filters): self.root._v_filters = filters @filters.deleter def filters(self): del self.root._v_filters def __init__(self, filename, mode="r", title="", root_uep="/", filters=None, **kwargs): self.filename = os.fspath(filename) """The name of the opened file.""" self.mode = mode """The mode in which the file was opened.""" if mode not in ('r', 'r+', 'a', 'w'): raise ValueError("invalid mode string ``%s``. Allowed modes are: " "'r', 'r+', 'a' and 'w'" % mode) # Get all the parameters in parameter file(s) params = {k: v for k, v in parameters.__dict__.items() if k.isupper() and not k.startswith('_')} # Update them with possible keyword arguments if [k for k in kwargs if k.isupper()]: warnings.warn("The use of uppercase keyword parameters is " "deprecated", DeprecationWarning) kwargs = {k.upper(): v for k, v in kwargs.items()} params.update(kwargs) # If MAX_ * _THREADS is not set yet, set it to the number of cores # on this machine. if params['MAX_NUMEXPR_THREADS'] is None: params['MAX_NUMEXPR_THREADS'] = detect_number_of_cores() if params['MAX_BLOSC_THREADS'] is None: params['MAX_BLOSC_THREADS'] = detect_number_of_cores() self.params = params # Now, it is time to initialize the File extension self._g_new(filename, mode, **params) # Check filters and set PyTables format version for new files. new = self._v_new if new: _checkfilters(filters) self.format_version = format_version """The PyTables version number of this file.""" # The node manager must be initialized before the root group # initialization but the node_factory attribute is set onl later # because it is a bount method of the root grop itself. node_cache_slots = params['NODE_CACHE_SLOTS'] self._node_manager = NodeManager(nslots=node_cache_slots) # For the moment Undo/Redo is not enabled. self._undoEnabled = False # Set the flag to indicate that the file has been opened. # It must be set before opening the root group # to allow some basic access to its attributes. self.isopen = 1 """True if the underlying file os open, False otherwise.""" # Append the name of the file to the global dict of files opened. _open_files.add(self) # Set the number of times this file has been opened to 1 self._open_count = 1 # Get the root group from this file self.root = root = self.__get_root_group(root_uep, title, filters) """The *root* of the object tree hierarchy (a Group instance).""" # Complete the creation of the root node # (see the explanation in ``RootGroup.__init__()``. root._g_post_init_hook() self._node_manager.node_factory = self.root._g_load_child # Save the PyTables format version for this file. if new: if params['PYTABLES_SYS_ATTRS']: root._v_attrs._g__setattr( 'PYTABLES_FORMAT_VERSION', format_version) # If the file is old, and not opened in "read-only" mode, # check if it has a transaction log if not new and self.mode != "r" and _trans_group_path in self: # It does. Enable the undo. self.enable_undo() # Set the maximum number of threads for Numexpr ne.set_vml_num_threads(params['MAX_NUMEXPR_THREADS']) def __get_root_group(self, root_uep, title, filters): """Returns a Group instance which will act as the root group in the hierarchical tree. If file is opened in "r", "r+" or "a" mode, and the file already exists, this method dynamically builds a python object tree emulating the structure present on file. """ self._v_objectid = self._get_file_id() if root_uep in [None, ""]: root_uep = "/" # Save the User Entry Point in a variable class self.root_uep = root_uep new = self._v_new # Get format version *before* getting the object tree if not new: # Firstly, get the PyTables format version for this file self.format_version = utilsextension.read_f_attr( self._v_objectid, 'PYTABLES_FORMAT_VERSION') if not self.format_version: # PYTABLES_FORMAT_VERSION attribute is not present self.format_version = "unknown" self._isPTFile = False elif not isinstance(self.format_version, str): # system attributes should always be str self.format_version = self.format_version.decode('utf-8') # Create new attributes for the root Group instance and # create the object tree return RootGroup(self, root_uep, title=title, new=new, filters=filters) def _get_or_create_path(self, path, create): """Get the given `path` or create it if `create` is true. If `create` is true, `path` *must* be a string path and not a node, otherwise a `TypeError`will be raised. """ if create: return self._create_path(path) else: return self.get_node(path) def _create_path(self, path): """Create the groups needed for the `path` to exist. The group associated with the given `path` is returned. """ if not hasattr(path, 'split'): raise TypeError("when creating parents, parent must be a path") if path == '/': return self.root parent, create_group = self.root, self.create_group for pcomp in path.split('/')[1:]: try: child = parent._f_get_child(pcomp) except NoSuchNodeError: child = create_group(parent, pcomp) parent = child return parent def create_group(self, where, name, title="", filters=None, createparents=False): """Create a new group. Parameters ---------- where : str or Group The parent group from which the new group will hang. It can be a path string (for example '/level1/leaf5'), or a Group instance (see :ref:`GroupClassDescr`). name : str The name of the new group. title : str, optional A description for this node (it sets the TITLE HDF5 attribute on disk). filters : Filters An instance of the Filters class (see :ref:`FiltersClassDescr`) that provides information about the desired I/O filters applicable to the leaves that hang directly from this new group (unless other filter properties are specified for these leaves). Besides, if you do not specify filter properties for its child groups, they will inherit these ones. createparents : bool Whether to create the needed groups for the parent path to exist (not done by default). See Also -------- Group : for more information on groups """ parentnode = self._get_or_create_path(where, createparents) _checkfilters(filters) return Group(parentnode, name, title=title, new=True, filters=filters) def create_table(self, where, name, description=None, title="", filters=None, expectedrows=10_000, chunkshape=None, byteorder=None, createparents=False, obj=None, track_times=True): """Create a new table with the given name in where location. Parameters ---------- where : str or Group The parent group from which the new table will hang. It can be a path string (for example '/level1/leaf5'), or a Group instance (see :ref:`GroupClassDescr`). name : str The name of the new table. description : Description This is an object that describes the table, i.e. how many columns it has, their names, types, shapes, etc. It can be any of the following: * *A user-defined class*: This should inherit from the IsDescription class (see :ref:`IsDescriptionClassDescr`) where table fields are specified. * *A dictionary*: For example, when you do not know beforehand which structure your table will have). * *A Description instance*: You can use the description attribute of another table to create a new one with the same structure. * *A NumPy dtype*: A completely general structured NumPy dtype. * *A NumPy (structured) array instance*: The dtype of this structured array will be used as the description. Also, in case the array has actual data, it will be injected into the newly created table. .. versionchanged:: 3.0 The *description* parameter can be None (default) if *obj* is provided. In that case the structure of the table is deduced by *obj*. title : str A description for this node (it sets the TITLE HDF5 attribute on disk). filters : Filters An instance of the Filters class (see :ref:`FiltersClassDescr`) that provides information about the desired I/O filters to be applied during the life of this object. expectedrows : int A user estimate of the number of records that will be in the table. If not provided, the default value is EXPECTED_ROWS_TABLE (see :file:`tables/parameters.py`). If you plan to create a bigger table try providing a guess; this will optimize the HDF5 B-Tree creation and management process time and memory used. chunkshape The shape of the data chunk to be read or written in a single HDF5 I/O operation. Filters are applied to those chunks of data. The rank of the chunkshape for tables must be 1. If None, a sensible value is calculated based on the expectedrows parameter (which is recommended). byteorder : str The byteorder of data *on disk*, specified as 'little' or 'big'. If this is not specified, the byteorder is that of the platform, unless you passed an array as the description, in which case its byteorder will be used. createparents : bool Whether to create the needed groups for the parent path to exist (not done by default). obj : python object The recarray to be saved. Accepted types are NumPy record arrays. The *obj* parameter is optional and it can be provided in alternative to the *description* parameter. If both *obj* and *description* are provided they must be consistent with each other. .. versionadded:: 3.0 track_times Whether time data associated with the leaf are recorded (object access time, raw data modification time, metadata change time, object birth time); default True. Semantics of these times depend on their implementation in the HDF5 library: refer to documentation of the H5O_info_t data structure. As of HDF5 1.8.15, only ctime (metadata change time) is implemented. .. versionadded:: 3.4.3 See Also -------- Table : for more information on tables """ if obj is not None: if not isinstance(obj, np.ndarray): raise TypeError('invalid obj parameter %r' % obj) descr, _ = descr_from_dtype(obj.dtype, ptparams=self.params) if (description is not None and dtype_from_descr(description, ptparams=self.params) != obj.dtype): raise TypeError('the desctiption parameter is not consistent ' 'with the data type of the obj parameter') elif description is None: description = descr parentnode = self._get_or_create_path(where, createparents) if description is None: raise ValueError("invalid table description: None") _checkfilters(filters) ptobj = Table(parentnode, name, description=description, title=title, filters=filters, expectedrows=expectedrows, chunkshape=chunkshape, byteorder=byteorder, track_times=track_times) if obj is not None: ptobj.append(obj) return ptobj def create_array(self, where, name, obj=None, title="", byteorder=None, createparents=False, atom=None, shape=None, track_times=True): """Create a new array. Parameters ---------- where : str or Group The parent group from which the new array will hang. It can be a path string (for example '/level1/leaf5'), or a Group instance (see :ref:`GroupClassDescr`). name : str The name of the new array obj : python object The array or scalar to be saved. Accepted types are NumPy arrays and scalars, as well as native Python sequences and scalars, provided that values are regular (i.e. they are not like ``[[1,2],2]``) and homogeneous (i.e. all the elements are of the same type). Also, objects that have some of their dimensions equal to 0 are not supported (use an EArray node (see :ref:`EArrayClassDescr`) if you want to store an array with one of its dimensions equal to 0). .. versionchanged:: 3.0 The *Object parameter has been renamed into *obj*.* title : str A description for this node (it sets the TITLE HDF5 attribute on disk). byteorder : str The byteorder of the data *on disk*, specified as 'little' or 'big'. If this is not specified, the byteorder is that of the given object. createparents : bool, optional Whether to create the needed groups for the parent path to exist (not done by default). atom : Atom An Atom (see :ref:`AtomClassDescr`) instance representing the *type* and *shape* of the atomic objects to be saved. .. versionadded:: 3.0 shape : tuple of ints The shape of the stored array. .. versionadded:: 3.0 track_times Whether time data associated with the leaf are recorded (object access time, raw data modification time, metadata change time, object birth time); default True. Semantics of these times depend on their implementation in the HDF5 library: refer to documentation of the H5O_info_t data structure. As of HDF5 1.8.15, only ctime (metadata change time) is implemented. .. versionadded:: 3.4.3 See Also -------- Array : for more information on arrays create_table : for more information on the rest of parameters """ if obj is None: if atom is None or shape is None: raise TypeError('if the obj parameter is not specified ' '(or None) then both the atom and shape ' 'parametes should be provided.') else: # Making strides=(0,...) below is a trick to create the # array fast and without memory consumption dflt = np.zeros((), dtype=atom.dtype) obj = np.ndarray(shape, dtype=atom.dtype, buffer=dflt, strides=(0,)*len(shape)) else: flavor = flavor_of(obj) # use a temporary object because converting obj at this stage # breaks some test. This is solution performs a double, # potentially expensive, conversion of the obj parameter. _obj = array_as_internal(obj, flavor) if shape is not None and shape != _obj.shape: raise TypeError('the shape parameter do not match obj.shape') if atom is not None and atom.dtype != _obj.dtype: raise TypeError('the atom parameter is not consistent with ' 'the data type of the obj parameter') parentnode = self._get_or_create_path(where, createparents) return Array(parentnode, name, obj=obj, title=title, byteorder=byteorder, track_times=track_times) def create_carray(self, where, name, atom=None, shape=None, title="", filters=None, chunkshape=None, byteorder=None, createparents=False, obj=None, track_times=True): """Create a new chunked array. Parameters ---------- where : str or Group The parent group from which the new array will hang. It can be a path string (for example '/level1/leaf5'), or a Group instance (see :ref:`GroupClassDescr`). name : str The name of the new array atom : Atom An Atom (see :ref:`AtomClassDescr`) instance representing the *type* and *shape* of the atomic objects to be saved. .. versionchanged:: 3.0 The *atom* parameter can be None (default) if *obj* is provided. shape : tuple The shape of the new array. .. versionchanged:: 3.0 The *shape* parameter can be None (default) if *obj* is provided. title : str, optional A description for this node (it sets the TITLE HDF5 attribute on disk). filters : Filters, optional An instance of the Filters class (see :ref:`FiltersClassDescr`) that provides information about the desired I/O filters to be applied during the life of this object. chunkshape : tuple or number or None, optional The shape of the data chunk to be read or written in a single HDF5 I/O operation. Filters are applied to those chunks of data. The dimensionality of chunkshape must be the same as that of shape. If None, a sensible value is calculated (which is recommended). byteorder : str, optional The byteorder of the data *on disk*, specified as 'little' or 'big'. If this is not specified, the byteorder is that of the given object. createparents : bool, optional Whether to create the needed groups for the parent path to exist (not done by default). obj : python object The array or scalar to be saved. Accepted types are NumPy arrays and scalars, as well as native Python sequences and scalars, provided that values are regular (i.e. they are not like ``[[1,2],2]``) and homogeneous (i.e. all the elements are of the same type). Also, objects that have some of their dimensions equal to 0 are not supported. Please use an EArray node (see :ref:`EArrayClassDescr`) if you want to store an array with one of its dimensions equal to 0. The *obj* parameter is optional and it can be provided in alternative to the *atom* and *shape* parameters. If both *obj* and *atom* and/or *shape* are provided they must be consistent with each other. .. versionadded:: 3.0 track_times Whether time data associated with the leaf are recorded (object access time, raw data modification time, metadata change time, object birth time); default True. Semantics of these times depend on their implementation in the HDF5 library: refer to documentation of the H5O_info_t data structure. As of HDF5 1.8.15, only ctime (metadata change time) is implemented. .. versionadded:: 3.4.3 See Also -------- CArray : for more information on chunked arrays """ if obj is not None: flavor = flavor_of(obj) obj = array_as_internal(obj, flavor) if shape is not None and shape != obj.shape: raise TypeError('the shape parameter do not match obj.shape') else: shape = obj.shape if atom is not None and atom.dtype != obj.dtype: raise TypeError("the 'atom' parameter is not consistent with " "the data type of the 'obj' parameter") elif atom is None: atom = Atom.from_dtype(obj.dtype) else: if atom is None and shape is None: raise TypeError( "the 'atom' and 'shape' parameters or the 'obj' parameter " "must be provided") parentnode = self._get_or_create_path(where, createparents) _checkfilters(filters) ptobj = CArray(parentnode, name, atom=atom, shape=shape, title=title, filters=filters, chunkshape=chunkshape, byteorder=byteorder, track_times=track_times) if obj is not None: ptobj[...] = obj return ptobj def create_earray(self, where, name, atom=None, shape=None, title="", filters=None, expectedrows=1000, chunkshape=None, byteorder=None, createparents=False, obj=None, track_times=True): """Create a new enlargeable array. Parameters ---------- where : str or Group The parent group from which the new array will hang. It can be a path string (for example '/level1/leaf5'), or a Group instance (see :ref:`GroupClassDescr`). name : str The name of the new array atom : Atom An Atom (see :ref:`AtomClassDescr`) instance representing the *type* and *shape* of the atomic objects to be saved. .. versionchanged:: 3.0 The *atom* parameter can be None (default) if *obj* is provided. shape : tuple The shape of the new array. One (and only one) of the shape dimensions *must* be 0. The dimension being 0 means that the resulting EArray object can be extended along it. Multiple enlargeable dimensions are not supported right now. .. versionchanged:: 3.0 The *shape* parameter can be None (default) if *obj* is provided. title : str, optional A description for this node (it sets the TITLE HDF5 attribute on disk). expectedrows : int, optional A user estimate about the number of row elements that will be added to the growable dimension in the EArray node. If not provided, the default value is EXPECTED_ROWS_EARRAY (see tables/parameters.py). If you plan to create either a much smaller or a much bigger array try providing a guess; this will optimize the HDF5 B-Tree creation and management process time and the amount of memory used. chunkshape : tuple, numeric, or None, optional The shape of the data chunk to be read or written in a single HDF5 I/O operation. Filters are applied to those chunks of data. The dimensionality of chunkshape must be the same as that of shape (beware: no dimension should be 0 this time!). If None, a sensible value is calculated based on the expectedrows parameter (which is recommended). byteorder : str, optional The byteorder of the data *on disk*, specified as 'little' or 'big'. If this is not specified, the byteorder is that of the platform. createparents : bool, optional Whether to create the needed groups for the parent path to exist (not done by default). obj : python object The array or scalar to be saved. Accepted types are NumPy arrays and scalars, as well as native Python sequences and scalars, provided that values are regular (i.e. they are not like ``[[1,2],2]``) and homogeneous (i.e. all the elements are of the same type). The *obj* parameter is optional and it can be provided in alternative to the *atom* and *shape* parameters. If both *obj* and *atom* and/or *shape* are provided they must be consistent with each other. .. versionadded:: 3.0 track_times Whether time data associated with the leaf are recorded (object access time, raw data modification time, metadata change time, object birth time); default True. Semantics of these times depend on their implementation in the HDF5 library: refer to documentation of the H5O_info_t data structure. As of HDF5 1.8.15, only ctime (metadata change time) is implemented. .. versionadded:: 3.4.3 See Also -------- EArray : for more information on enlargeable arrays """ if obj is not None: flavor = flavor_of(obj) obj = array_as_internal(obj, flavor) earray_shape = (0,) + obj.shape[1:] if shape is not None and shape != earray_shape: raise TypeError('the shape parameter is not compatible ' 'with obj.shape.') else: shape = earray_shape if atom is not None and atom.dtype != obj.dtype: raise TypeError('the atom parameter is not consistent with ' 'the data type of the obj parameter') elif atom is None: atom = Atom.from_dtype(obj.dtype) parentnode = self._get_or_create_path(where, createparents) _checkfilters(filters) ptobj = EArray(parentnode, name, atom=atom, shape=shape, title=title, filters=filters, expectedrows=expectedrows, chunkshape=chunkshape, byteorder=byteorder, track_times=track_times) if obj is not None: ptobj.append(obj) return ptobj def create_vlarray(self, where, name, atom=None, title="", filters=None, expectedrows=None, chunkshape=None, byteorder=None, createparents=False, obj=None, track_times=True): """Create a new variable-length array. Parameters ---------- where : str or Group The parent group from which the new array will hang. It can be a path string (for example '/level1/leaf5'), or a Group instance (see :ref:`GroupClassDescr`). name : str The name of the new array atom : Atom An Atom (see :ref:`AtomClassDescr`) instance representing the *type* and *shape* of the atomic objects to be saved. .. versionchanged:: 3.0 The *atom* parameter can be None (default) if *obj* is provided. title : str, optional A description for this node (it sets the TITLE HDF5 attribute on disk). filters : Filters An instance of the Filters class (see :ref:`FiltersClassDescr`) that provides information about the desired I/O filters to be applied during the life of this object. expectedrows : int, optional A user estimate about the number of row elements that will be added to the growable dimension in the `VLArray` node. If not provided, the default value is ``EXPECTED_ROWS_VLARRAY`` (see ``tables/parameters.py``). If you plan to create either a much smaller or a much bigger `VLArray` try providing a guess; this will optimize the HDF5 B-Tree creation and management process time and the amount of memory used. .. versionadded:: 3.0 chunkshape : int or tuple of int, optional The shape of the data chunk to be read or written in a single HDF5 I/O operation. Filters are applied to those chunks of data. The dimensionality of chunkshape must be 1. If None, a sensible value is calculated (which is recommended). byteorder : str, optional The byteorder of the data *on disk*, specified as 'little' or 'big'. If this is not specified, the byteorder is that of the platform. createparents : bool, optional Whether to create the needed groups for the parent path to exist (not done by default). obj : python object The array or scalar to be saved. Accepted types are NumPy arrays and scalars, as well as native Python sequences and scalars, provided that values are regular (i.e. they are not like ``[[1,2],2]``) and homogeneous (i.e. all the elements are of the same type). The *obj* parameter is optional and it can be provided in alternative to the *atom* parameter. If both *obj* and *atom* and are provided they must be consistent with each other. .. versionadded:: 3.0 track_times Whether time data associated with the leaf are recorded (object access time, raw data modification time, metadata change time, object birth time); default True. Semantics of these times depend on their implementation in the HDF5 library: refer to documentation of the H5O_info_t data structure. As of HDF5 1.8.15, only ctime (metadata change time) is implemented. .. versionadded:: 3.4.3 See Also -------- VLArray : for more informationon variable-length arrays .. versionchanged:: 3.0 The *expectedsizeinMB* parameter has been replaced by *expectedrows*. """ if obj is not None: flavor = flavor_of(obj) obj = array_as_internal(obj, flavor) if atom is not None and atom.dtype != obj.dtype: raise TypeError('the atom parameter is not consistent with ' 'the data type of the obj parameter') if atom is None: atom = Atom.from_dtype(obj.dtype) elif atom is None: raise ValueError('atom parameter cannot be None') parentnode = self._get_or_create_path(where, createparents) _checkfilters(filters) ptobj = VLArray(parentnode, name, atom=atom, title=title, filters=filters, expectedrows=expectedrows, chunkshape=chunkshape, byteorder=byteorder, track_times=track_times) if obj is not None: ptobj.append(obj) return ptobj def create_hard_link(self, where, name, target, createparents=False): """Create a hard link. Create a hard link to a `target` node with the given `name` in `where` location. `target` can be a node object or a path string. If `createparents` is true, the intermediate groups required for reaching `where` are created (the default is not doing so). The returned node is a regular `Group` or `Leaf` instance. """ targetnode = self.get_node(target) parentnode = self._get_or_create_path(where, createparents) linkextension._g_create_hard_link(parentnode, name, targetnode) # Refresh children names in link's parent node parentnode._g_add_children_names() # Return the target node return self.get_node(parentnode, name) def create_soft_link(self, where, name, target, createparents=False): """Create a soft link (aka symbolic link) to a `target` node. Create a soft link (aka symbolic link) to a `target` nodewith the given `name` in `where` location. `target` can be a node object or a path string. If `createparents` is true, the intermediate groups required for reaching `where` are created. (the default is not doing so). The returned node is a SoftLink instance. See the SoftLink class (in :ref:`SoftLinkClassDescr`) for more information on soft links. """ if not isinstance(target, str): if hasattr(target, '_v_pathname'): # quacks like a Node target = target._v_pathname else: raise ValueError( "`target` has to be a string or a node object") parentnode = self._get_or_create_path(where, createparents) slink = SoftLink(parentnode, name, target) # Refresh children names in link's parent node parentnode._g_add_children_names() return slink def create_external_link(self, where, name, target, createparents=False): """Create an external link. Create an external link to a *target* node with the given *name* in *where* location. *target* can be a node object in another file or a path string in the form 'file:/path/to/node'. If *createparents* is true, the intermediate groups required for reaching *where* are created (the default is not doing so). The returned node is an :class:`ExternalLink` instance. """ if not isinstance(target, str): if hasattr(target, '_v_pathname'): # quacks like a Node target = target._v_file.filename + ':' + target._v_pathname else: raise ValueError( "`target` has to be a string or a node object") elif target.find(':/') == -1: raise ValueError( "`target` must expressed as 'file:/path/to/node'") parentnode = self._get_or_create_path(where, createparents) elink = ExternalLink(parentnode, name, target) # Refresh children names in link's parent node parentnode._g_add_children_names() return elink def _get_node(self, nodepath): # The root node is always at hand. if nodepath == '/': return self.root node = self._node_manager.get_node(nodepath) assert node is not None, "unable to instantiate node ``%s``" % nodepath return node def get_node(self, where, name=None, classname=None): """Get the node under where with the given name. Parameters ---------- where : str or Node This can be a path string leading to a node or a Node instance (see :ref:`NodeClassDescr`). If no name is specified, that node is returned. .. note:: If where is a Node instance from a different file than the one on which this function is called, the returned node will also be from that other file. name : str, optional If a name is specified, this must be a string with the name of a node under where. In this case the where argument can only lead to a Group (see :ref:`GroupClassDescr`) instance (else a TypeError is raised). The node called name under the group where is returned. classname : str, optional If the classname argument is specified, it must be the name of a class derived from Node (e.g. Table). If the node is found but it is not an instance of that class, a NoSuchNodeError is also raised. If the node to be returned does not exist, a NoSuchNodeError is raised. Please note that hidden nodes are also considered. """ self._check_open() if isinstance(where, Node): where._g_check_open() basepath = where._v_pathname nodepath = join_path(basepath, name or '') or '/' node = where._v_file._get_node(nodepath) elif isinstance(where, (str, np.str_)): if not where.startswith('/'): raise NameError("``where`` must start with a slash ('/')") basepath = where nodepath = join_path(basepath, name or '') or '/' node = self._get_node(nodepath) else: raise TypeError( f"``where`` must be a string or a node: {where!r}") # Finally, check whether the desired node is an instance # of the expected class. if classname: class_ = get_class_by_name(classname) if not isinstance(node, class_): npathname = node._v_pathname nclassname = node.__class__.__name__ # This error message is right since it can never be shown # for ``classname in [None, 'Node']``. raise NoSuchNodeError( "could not find a ``%s`` node at ``%s``; " "instead, a ``%s`` node has been found there" % (classname, npathname, nclassname)) return node def is_visible_node(self, path): """Is the node under `path` visible? If the node does not exist, a NoSuchNodeError is raised. """ # ``util.isvisiblepath()`` is still recommended for internal use. return self.get_node(path)._f_isvisible() def rename_node(self, where, newname, name=None, overwrite=False): """Change the name of the node specified by where and name to newname. Parameters ---------- where, name These arguments work as in :meth:`File.get_node`, referencing the node to be acted upon. newname : str The new name to be assigned to the node (a string). overwrite : bool Whether to recursively remove a node with the same newname if it already exists (not done by default). """ obj = self.get_node(where, name=name) obj._f_rename(newname, overwrite) def move_node(self, where, newparent=None, newname=None, name=None, overwrite=False, createparents=False): """Move the node specified by where and name to newparent/newname. Parameters ---------- where, name : path These arguments work as in :meth:`File.get_node`, referencing the node to be acted upon. newparent The destination group the node will be moved into (a path name or a Group instance). If it is not specified or None, the current parent group is chosen as the new parent. newname The new name to be assigned to the node in its destination (a string). If it is not specified or None, the current name is chosen as the new name. Notes ----- The other arguments work as in :meth:`Node._f_move`. """ obj = self.get_node(where, name=name) obj._f_move(newparent, newname, overwrite, createparents) def copy_node(self, where, newparent=None, newname=None, name=None, overwrite=False, recursive=False, createparents=False, **kwargs): """Copy the node specified by where and name to newparent/newname. Parameters ---------- where : str These arguments work as in :meth:`File.get_node`, referencing the node to be acted upon. newparent : str or Group The destination group that the node will be copied into (a path name or a Group instance). If not specified or None, the current parent group is chosen as the new parent. newname : str The name to be assigned to the new copy in its destination (a string). If it is not specified or None, the current name is chosen as the new name. name : str These arguments work as in :meth:`File.get_node`, referencing the node to be acted upon. overwrite : bool, optional If True, the destination group will be overwritten if it already exists. Defaults to False. recursive : bool, optional If True, all descendant nodes of srcgroup are recursively copied. Defaults to False. createparents : bool, optional If True, any necessary parents of dstgroup will be created. Defaults to False. kwargs Additional keyword arguments can be used to customize the copying process. See the documentation of :meth:`Group._f_copy` for a description of those arguments. Returns ------- node : Node The newly created copy of the source node (i.e. the destination node). See :meth:`.Node._f_copy` for further details on the semantics of copying nodes. """ obj = self.get_node(where, name=name) if obj._v_depth == 0 and newparent and not newname: npobj = self.get_node(newparent) if obj._v_file is not npobj._v_file: # Special case for copying file1:/ --> file2:/path self.root._f_copy_children(npobj, overwrite=overwrite, recursive=recursive, **kwargs) return npobj else: raise OSError( "You cannot copy a root group over the same file") return obj._f_copy(newparent, newname, overwrite, recursive, createparents, **kwargs) def remove_node(self, where, name=None, recursive=False): """Remove the object node *name* under *where* location. Parameters ---------- where, name These arguments work as in :meth:`File.get_node`, referencing the node to be acted upon. recursive : bool If not supplied or false, the node will be removed only if it has no children; if it does, a NodeError will be raised. If supplied with a true value, the node and all its descendants will be completely removed. """ obj = self.get_node(where, name=name) obj._f_remove(recursive) def get_node_attr(self, where, attrname, name=None): """Get a PyTables attribute from the given node. Parameters ---------- where, name These arguments work as in :meth:`File.get_node`, referencing the node to be acted upon. attrname The name of the attribute to retrieve. If the named attribute does not exist, an AttributeError is raised. """ obj = self.get_node(where, name=name) return obj._f_getattr(attrname) def set_node_attr(self, where, attrname, attrvalue, name=None): """Set a PyTables attribute for the given node. Parameters ---------- where, name These arguments work as in :meth:`File.get_node`, referencing the node to be acted upon. attrname The name of the attribute to set. attrvalue The value of the attribute to set. Any kind of Python object (like strings, ints, floats, lists, tuples, dicts, small NumPy objects ...) can be stored as an attribute. However, if necessary, pickle is automatically used so as to serialize objects that you might want to save. See the :class:`AttributeSet` class for details. Notes ----- If the node already has a large number of attributes, a PerformanceWarning is issued. """ obj = self.get_node(where, name=name) obj._f_setattr(attrname, attrvalue) def del_node_attr(self, where, attrname, name=None): """Delete a PyTables attribute from the given node. Parameters ---------- where, name These arguments work as in :meth:`File.get_node`, referencing the node to be acted upon. attrname The name of the attribute to delete. If the named attribute does not exist, an AttributeError is raised. """ obj = self.get_node(where, name=name) obj._f_delattr(attrname) def copy_node_attrs(self, where, dstnode, name=None): """Copy PyTables attributes from one node to another. Parameters ---------- where, name These arguments work as in :meth:`File.get_node`, referencing the node to be acted upon. dstnode The destination node where the attributes will be copied to. It can be a path string or a Node instance (see :ref:`NodeClassDescr`). """ srcobject = self.get_node(where, name=name) dstobject = self.get_node(dstnode) srcobject._v_attrs._f_copy(dstobject) def copy_children(self, srcgroup, dstgroup, overwrite=False, recursive=False, createparents=False, **kwargs): """Copy the children of a group into another group. Parameters ---------- srcgroup : str The group to copy from. dstgroup : str The destination group. overwrite : bool, optional If True, the destination group will be overwritten if it already exists. Defaults to False. recursive : bool, optional If True, all descendant nodes of srcgroup are recursively copied. Defaults to False. createparents : bool, optional If True, any necessary parents of dstgroup will be created. Defaults to False. kwargs : dict Additional keyword arguments can be used to customize the copying process. See the documentation of :meth:`Group._f_copy_children` for a description of those arguments. """ srcgroup = self.get_node(srcgroup) # Does the source node exist? self._check_group(srcgroup) # Is it a group? srcgroup._f_copy_children( dstgroup, overwrite, recursive, createparents, **kwargs) def copy_file(self, dstfilename, overwrite=False, **kwargs): """Copy the contents of this file to dstfilename. Parameters ---------- dstfilename : str A path string indicating the name of the destination file. If it already exists, the copy will fail with an IOError, unless the overwrite argument is true. overwrite : bool, optional If true, the destination file will be overwritten if it already exists. In this case, the destination file must be closed, or errors will occur. Defaults to False. kwargs Additional keyword arguments discussed below. Notes ----- Additional keyword arguments may be passed to customize the copying process. For instance, title and filters may be changed, user attributes may be or may not be copied, data may be sub-sampled, stats may be collected, etc. Arguments unknown to nodes are simply ignored. Check the documentation for copying operations of nodes to see which options they support. In addition, it recognizes the names of parameters present in :file:`tables/parameters.py` as additional keyword arguments. See :ref:`parameter_files` for a detailed info on the supported parameters. Copying a file usually has the beneficial side effect of creating a more compact and cleaner version of the original file. """ self._check_open() # Check that we are not treading our own shoes if Path(self.filename).resolve() == Path(dstfilename).resolve(): raise OSError("You cannot copy a file over itself") # Compute default arguments. # These are *not* passed on. filters = kwargs.pop('filters', None) if filters is None: # By checking the HDF5 attribute, we avoid setting filters # in the destination file if not explicitly set in the # source file. Just by assigning ``self.filters`` we would # not be able to tell. filters = getattr(self.root._v_attrs, 'FILTERS', None) copyuserattrs = kwargs.get('copyuserattrs', True) title = kwargs.pop('title', self.title) if Path(dstfilename).is_file() and not overwrite: raise OSError( f"file ``{dstfilename}`` already exists; you may want to " f"use the ``overwrite`` argument" ) # Create destination file, overwriting it. dstfileh = open_file( dstfilename, mode="w", title=title, filters=filters, **kwargs) try: # Maybe copy the user attributes of the root group. if copyuserattrs: self.root._v_attrs._f_copy(dstfileh.root) # Copy the rest of the hierarchy. self.root._f_copy_children(dstfileh.root, recursive=True, **kwargs) finally: dstfileh.close() def list_nodes(self, where, classname=None): """Return a *list* with children nodes hanging from where. This is a list-returning version of :meth:`File.iter_nodes`. """ group = self.get_node(where) # Does the parent exist? self._check_group(group) # Is it a group? return group._f_list_nodes(classname) def iter_nodes(self, where, classname=None): """Iterate over children nodes hanging from where. Parameters ---------- where This argument works as in :meth:`File.get_node`, referencing the node to be acted upon. classname If the name of a class derived from Node (see :ref:`NodeClassDescr`) is supplied, only instances of that class (or subclasses of it) will be returned. Notes ----- The returned nodes are alphanumerically sorted by their name. This is an iterator version of :meth:`File.list_nodes`. """ group = self.get_node(where) # Does the parent exist? self._check_group(group) # Is it a group? return group._f_iter_nodes(classname) def __contains__(self, path): """Is there a node with that path? Returns True if the file has a node with the given path (a string), False otherwise. """ try: self.get_node(path) except NoSuchNodeError: return False else: return True def __iter__(self): """Recursively iterate over the nodes in the tree. This is equivalent to calling :meth:`File.walk_nodes` with no arguments. Examples -------- :: # Recursively list all the nodes in the object tree. h5file = tables.open_file('vlarray1.h5') print("All nodes in the object tree:") for node in h5file: print(node) """ return self.walk_nodes('/') def walk_nodes(self, where="/", classname=None): """Recursively iterate over nodes hanging from where. Parameters ---------- where : str or Group, optional If supplied, the iteration starts from (and includes) this group. It can be a path string or a Group instance (see :ref:`GroupClassDescr`). classname If the name of a class derived from Node (see :ref:`GroupClassDescr`) is supplied, only instances of that class (or subclasses of it) will be returned. Notes ----- This version iterates over the leaves in the same group in order to avoid having a list referencing to them and thus, preventing the LRU cache to remove them after their use. Examples -------- :: # Recursively print all the nodes hanging from '/detector'. print("Nodes hanging from group '/detector':") for node in h5file.walk_nodes('/detector', classname='EArray'): print(node) """ class_ = get_class_by_name(classname) if class_ is Group: # only groups yield from self.walk_groups(where) elif class_ is Node: # all nodes yield self.get_node(where) for group in self.walk_groups(where): yield from self.iter_nodes(group) else: # only nodes of the named type for group in self.walk_groups(where): yield from self.iter_nodes(group, classname) def walk_groups(self, where="/"): """Recursively iterate over groups (not leaves) hanging from where. The where group itself is listed first (preorder), then each of its child groups (following an alphanumerical order) is also traversed, following the same procedure. If where is not supplied, the root group is used. The where argument can be a path string or a Group instance (see :ref:`GroupClassDescr`). """ group = self.get_node(where) # Does the parent exist? self._check_group(group) # Is it a group? return group._f_walk_groups() def _check_open(self): """Check the state of the file. If the file is closed, a `ClosedFileError` is raised. """ if not self.isopen: raise ClosedFileError("the file object is closed") def _iswritable(self): """Is this file writable?""" return self.mode in ('w', 'a', 'r+') def _check_writable(self): """Check whether the file is writable. If the file is not writable, a `FileModeError` is raised. """ if not self._iswritable(): raise FileModeError("the file is not writable") def _check_group(self, node): # `node` must already be a node. if not isinstance(node, Group): raise TypeError(f"node ``{node._v_pathname}`` is not a group") def is_undo_enabled(self): """Is the Undo/Redo mechanism enabled? Returns True if the Undo/Redo mechanism has been enabled for this file, False otherwise. Please note that this mechanism is persistent, so a newly opened PyTables file may already have Undo/Redo support enabled. """ self._check_open() return self._undoEnabled def _check_undo_enabled(self): if not self._undoEnabled: raise UndoRedoError("Undo/Redo feature is currently disabled!") def _create_transaction_group(self): tgroup = TransactionGroupG( self.root, _trans_group_name, "Transaction information container", new=True) # The format of the transaction container. tgroup._v_attrs._g__setattr('FORMATVERSION', _trans_version) return tgroup def _create_transaction(self, troot, tid): return TransactionG( troot, _trans_name % tid, "Transaction number %d" % tid, new=True) def _create_mark(self, trans, mid): return MarkG( trans, _markName % mid, "Mark number %d" % mid, new=True) def enable_undo(self, filters=Filters(complevel=1)): """Enable the Undo/Redo mechanism. This operation prepares the database for undoing and redoing modifications in the node hierarchy. This allows :meth:`File.mark`, :meth:`File.undo`, :meth:`File.redo` and other methods to be called. The filters argument, when specified, must be an instance of class Filters (see :ref:`FiltersClassDescr`) and is meant for setting the compression values for the action log. The default is having compression enabled, as the gains in terms of space can be considerable. You may want to disable compression if you want maximum speed for Undo/Redo operations. Calling this method when the Undo/Redo mechanism is already enabled raises an UndoRedoError. """ maxundo = self.params['MAX_UNDO_PATH_LENGTH'] class ActionLog(NotLoggedMixin, Table): pass class ActionLogDesc(IsDescription): opcode = UInt8Col(pos=0) arg1 = StringCol(maxundo, pos=1, dflt=b"") arg2 = StringCol(maxundo, pos=2, dflt=b"") self._check_open() # Enabling several times is not allowed to avoid the user having # the illusion that a new implicit mark has been created # when calling enable_undo for the second time. if self.is_undo_enabled(): raise UndoRedoError("Undo/Redo feature is already enabled!") self._markers = {} self._seqmarkers = [] self._nmarks = 0 self._curtransaction = 0 self._curmark = -1 # No marks yet # Get the Group for keeping user actions try: tgroup = self.get_node(_trans_group_path) except NodeError: # The file is going to be changed. self._check_writable() # A transaction log group does not exist. Create it tgroup = self._create_transaction_group() # Create a transaction. self._trans = self._create_transaction( tgroup, self._curtransaction) # Create an action log self._actionlog = ActionLog( tgroup, _action_log_name, ActionLogDesc, "Action log", filters=filters) # Create an implicit mark self._actionlog.append([(_op_to_code["MARK"], str(0), '')]) self._nmarks += 1 self._seqmarkers.append(0) # current action is 0 # Create a group for mark 0 self._create_mark(self._trans, 0) # Initialize the marker pointer self._curmark = int(self._nmarks - 1) # Initialize the action pointer self._curaction = self._actionlog.nrows - 1 else: # The group seems to exist already # Get the default transaction self._trans = tgroup._f_get_child( _trans_name % self._curtransaction) # Open the action log and go to the end of it self._actionlog = tgroup.actionlog for row in self._actionlog: if row["opcode"] == _op_to_code["MARK"]: name = row["arg2"].decode('utf-8') self._markers[name] = self._nmarks self._seqmarkers.append(row.nrow) self._nmarks += 1 # Get the current mark and current action self._curmark = int(self._actionlog.attrs.CURMARK) self._curaction = self._actionlog.attrs.CURACTION # The Undo/Redo mechanism has been enabled. self._undoEnabled = True def disable_undo(self): """Disable the Undo/Redo mechanism. Disabling the Undo/Redo mechanism leaves the database in the current state and forgets past and future database states. This makes :meth:`File.mark`, :meth:`File.undo`, :meth:`File.redo` and other methods fail with an UndoRedoError. Calling this method when the Undo/Redo mechanism is already disabled raises an UndoRedoError. """ self._check_open() if not self.is_undo_enabled(): raise UndoRedoError("Undo/Redo feature is already disabled!") # The file is going to be changed. self._check_writable() del self._markers del self._seqmarkers del self._curmark del self._curaction del self._curtransaction del self._nmarks del self._actionlog # Recursively delete the transaction group tnode = self.get_node(_trans_group_path) tnode._g_remove(recursive=1) # The Undo/Redo mechanism has been disabled. self._undoEnabled = False def mark(self, name=None): """Mark the state of the database. Creates a mark for the current state of the database. A unique (and immutable) identifier for the mark is returned. An optional name (a string) can be assigned to the mark. Both the identifier of a mark and its name can be used in :meth:`File.undo` and :meth:`File.redo` operations. When the name has already been used for another mark, an UndoRedoError is raised. This method can only be called when the Undo/Redo mechanism has been enabled. Otherwise, an UndoRedoError is raised. """ self._check_open() self._check_undo_enabled() if name is None: name = '' else: if not isinstance(name, str): raise TypeError("Only strings are allowed as mark names. " "You passed object: '%s'" % name) if name in self._markers: raise UndoRedoError("Name '%s' is already used as a marker " "name. Try another one." % name) # The file is going to be changed. self._check_writable() self._markers[name] = self._curmark + 1 # Create an explicit mark # Insert the mark in the action log self._log("MARK", str(self._curmark + 1), name) self._curmark += 1 self._nmarks = self._curmark + 1 self._seqmarkers.append(self._curaction) # Create a group for the current mark self._create_mark(self._trans, self._curmark) return self._curmark def _log(self, action, *args): """Log an action. The `action` must be an all-uppercase string identifying it. Arguments must also be strings. This method should be called once the action has been completed. This method can only be called when the Undo/Redo mechanism has been enabled. Otherwise, an `UndoRedoError` is raised. """ assert self.is_undo_enabled() maxundo = self.params['MAX_UNDO_PATH_LENGTH'] # Check whether we are at the end of the action log or not if self._curaction != self._actionlog.nrows - 1: # We are not, so delete the trailing actions self._actionlog.remove_rows(self._curaction + 1, self._actionlog.nrows) # Reset the current marker group mnode = self.get_node(_markPath % (self._curtransaction, self._curmark)) mnode._g_reset() # Delete the marker groups with backup objects for mark in range(self._curmark + 1, self._nmarks): mnode = self.get_node(_markPath % (self._curtransaction, mark)) mnode._g_remove(recursive=1) # Update the new number of marks self._nmarks = self._curmark + 1 self._seqmarkers = self._seqmarkers[:self._nmarks] if action not in _op_to_code: # INTERNAL raise UndoRedoError("Action ``%s`` not in ``_op_to_code`` " "dictionary: %r" % (action, _op_to_code)) arg1 = "" arg2 = "" if len(args) <= 1: arg1 = args[0] elif len(args) <= 2: arg1 = args[0] arg2 = args[1] else: # INTERNAL raise UndoRedoError("Too many parameters for action log: " "%r").with_traceback(args) if (len(arg1) > maxundo or len(arg2) > maxundo): # INTERNAL raise UndoRedoError("Parameter arg1 or arg2 is too long: " "(%r, %r)" % (arg1, arg2)) # print("Logging-->", (action, arg1, arg2)) self._actionlog.append([(_op_to_code[action], arg1.encode('utf-8'), arg2.encode('utf-8'))]) self._curaction += 1 def _get_mark_id(self, mark): """Get an integer markid from a mark sequence number or name.""" if isinstance(mark, int): markid = mark elif isinstance(mark, str): if mark not in self._markers: lmarkers = sorted(self._markers) raise UndoRedoError("The mark that you have specified has not " "been found in the internal marker list: " "%r" % lmarkers) markid = self._markers[mark] else: raise TypeError("Parameter mark can only be an integer or a " "string, and you passed a type <%s>" % type(mark)) # print("markid, self._nmarks:", markid, self._nmarks) return markid def _get_final_action(self, markid): """Get the action to go. It does not touch the self private attributes """ if markid > self._nmarks - 1: # The required mark is beyond the end of the action log # The final action is the last row return self._actionlog.nrows elif markid <= 0: # The required mark is the first one # return the first row return 0 return self._seqmarkers[markid] def _doundo(self, finalaction, direction): """Undo/Redo actions up to final action in the specificed direction.""" if direction < 0: actionlog = \ self._actionlog[finalaction + 1:self._curaction + 1][::-1] else: actionlog = self._actionlog[self._curaction:finalaction] # Uncomment this for debugging # print("curaction, finalaction, direction", \ # self._curaction, finalaction, direction) for i in range(len(actionlog)): if actionlog['opcode'][i] != _op_to_code["MARK"]: # undo/redo the action if direction > 0: # Uncomment this for debugging # print("redo-->", \ # _code_to_op[actionlog['opcode'][i]],\ # actionlog['arg1'][i],\ # actionlog['arg2'][i]) undoredo.redo(self, # _code_to_op[actionlog['opcode'][i]], # The next is a workaround for python < 2.5 _code_to_op[int(actionlog['opcode'][i])], actionlog['arg1'][i].decode('utf8'), actionlog['arg2'][i].decode('utf8')) else: # Uncomment this for debugging # print("undo-->", \ # _code_to_op[actionlog['opcode'][i]],\ # actionlog['arg1'][i].decode('utf8'),\ # actionlog['arg2'][i].decode('utf8')) undoredo.undo(self, # _code_to_op[actionlog['opcode'][i]], # The next is a workaround for python < 2.5 _code_to_op[int(actionlog['opcode'][i])], actionlog['arg1'][i].decode('utf8'), actionlog['arg2'][i].decode('utf8')) else: if direction > 0: self._curmark = int(actionlog['arg1'][i]) else: self._curmark = int(actionlog['arg1'][i]) - 1 # Protection against negative marks if self._curmark < 0: self._curmark = 0 self._curaction += direction def undo(self, mark=None): """Go to a past state of the database. Returns the database to the state associated with the specified mark. Both the identifier of a mark and its name can be used. If the mark is omitted, the last created mark is used. If there are no past marks, or the specified mark is not older than the current one, an UndoRedoError is raised. This method can only be called when the Undo/Redo mechanism has been enabled. Otherwise, an UndoRedoError is raised. """ self._check_open() self._check_undo_enabled() # print("(pre)UNDO: (curaction, curmark) = (%s,%s)" % \ # (self._curaction, self._curmark)) if mark is None: markid = self._curmark # Correction if we are settled on top of a mark opcode = self._actionlog.cols.opcode if opcode[self._curaction] == _op_to_code["MARK"]: markid -= 1 else: # Get the mark ID number markid = self._get_mark_id(mark) # Get the final action ID to go finalaction = self._get_final_action(markid) if finalaction > self._curaction: raise UndoRedoError("Mark ``%s`` is newer than the current mark. " "Use `redo()` or `goto()` instead." % (mark,)) # The file is going to be changed. self._check_writable() # Try to reach this mark by unwinding actions in the log self._doundo(finalaction - 1, -1) if self._curaction < self._actionlog.nrows - 1: self._curaction += 1 self._curmark = int(self._actionlog.cols.arg1[self._curaction]) # print("(post)UNDO: (curaction, curmark) = (%s,%s)" % \ # (self._curaction, self._curmark)) def redo(self, mark=None): """Go to a future state of the database. Returns the database to the state associated with the specified mark. Both the identifier of a mark and its name can be used. If the `mark` is omitted, the next created mark is used. If there are no future marks, or the specified mark is not newer than the current one, an UndoRedoError is raised. This method can only be called when the Undo/Redo mechanism has been enabled. Otherwise, an UndoRedoError is raised. """ self._check_open() self._check_undo_enabled() # print("(pre)REDO: (curaction, curmark) = (%s, %s)" % \ # (self._curaction, self._curmark)) if self._curaction >= self._actionlog.nrows - 1: # We are at the end of log, so no action return if mark is None: mark = self._curmark + 1 elif mark == -1: mark = int(self._nmarks) # Go beyond the mark bounds up to the end # Get the mark ID number markid = self._get_mark_id(mark) finalaction = self._get_final_action(markid) if finalaction < self._curaction + 1: raise UndoRedoError("Mark ``%s`` is older than the current mark. " "Use `redo()` or `goto()` instead." % (mark,)) # The file is going to be changed. self._check_writable() # Get the final action ID to go self._curaction += 1 # Try to reach this mark by redoing the actions in the log self._doundo(finalaction, 1) # Increment the current mark only if we are not at the end of marks if self._curmark < self._nmarks - 1: self._curmark += 1 if self._curaction > self._actionlog.nrows - 1: self._curaction = self._actionlog.nrows - 1 # print("(post)REDO: (curaction, curmark) = (%s,%s)" % \ # (self._curaction, self._curmark)) def goto(self, mark): """Go to a specific mark of the database. Returns the database to the state associated with the specified mark. Both the identifier of a mark and its name can be used. This method can only be called when the Undo/Redo mechanism has been enabled. Otherwise, an UndoRedoError is raised. """ self._check_open() self._check_undo_enabled() if mark == -1: # Special case mark = self._nmarks # Go beyond the mark bounds up to the end # Get the mark ID number markid = self._get_mark_id(mark) finalaction = self._get_final_action(markid) if finalaction < self._curaction: self.undo(mark) else: self.redo(mark) def get_current_mark(self): """Get the identifier of the current mark. Returns the identifier of the current mark. This can be used to know the state of a database after an application crash, or to get the identifier of the initial implicit mark after a call to :meth:`File.enable_undo`. This method can only be called when the Undo/Redo mechanism has been enabled. Otherwise, an UndoRedoError is raised. """ self._check_open() self._check_undo_enabled() return self._curmark def _shadow_name(self): """Compute and return a shadow name. Computes the current shadow name according to the current transaction, mark and action. It returns a tuple with the shadow parent node and the name of the shadow in it. """ parent = self.get_node( _shadow_parent % (self._curtransaction, self._curmark)) name = _shadow_name % (self._curaction,) return (parent, name) def flush(self): """Flush all the alive leaves in the object tree.""" self._check_open() # Flush the cache to disk self._node_manager.flush_nodes() self._flush_file(0) # 0 means local scope, 1 global (virtual) scope def close(self): """Flush all the alive leaves in object tree and close the file.""" # If the file is already closed, return immediately if not self.isopen: return # If this file has been opened more than once, decrease the # counter and return if self._open_count > 1: self._open_count -= 1 return filename = self.filename if self._undoEnabled and self._iswritable(): # Save the current mark and current action self._actionlog.attrs._g__setattr("CURMARK", self._curmark) self._actionlog.attrs._g__setattr("CURACTION", self._curaction) # Close all loaded nodes. self.root._f_close() self._node_manager.shutdown() # Post-conditions assert len(self._node_manager.cache) == 0, \ ("cached nodes remain after closing: %s" % list(self._node_manager.cache)) # No other nodes should have been revived. assert len(self._node_manager.registry) == 0, \ ("alive nodes remain after closing: %s" % list(self._node_manager.registry)) # Close the file self._close_file() # After the objects are disconnected, destroy the # object dictionary using the brute force ;-) # This should help to the garbage collector self.__dict__.clear() # Set the flag to indicate that the file is closed self.isopen = 0 # Restore the filename attribute that is used by _FileRegistry self.filename = filename # Delete the entry from he registry of opened files _open_files.remove(self) def __enter__(self): """Enter a context and return the same file.""" return self def __exit__(self, *exc_info): """Exit a context and close the file.""" self.close() return False # do not hide exceptions def __str__(self): """Return a short string representation of the object tree. Examples -------- :: >>> import tables >>> f = tables.open_file('tables/tests/Tables_lzo2.h5') >>> print(f) tables/tests/Tables_lzo2.h5 (File) 'Table Benchmark' Last modif.: '...' Object Tree: / (RootGroup) 'Table Benchmark' /tuple0 (Table(100,)lzo(1)) 'This is the table title' /group0 (Group) '' /group0/tuple1 (Table(100,)lzo(1)) 'This is the table title' /group0/group1 (Group) '' /group0/group1/tuple2 (Table(100,)lzo(1)) 'This is the table title' /group0/group1/group2 (Group) '' >>> f.close() """ if not self.isopen: return "" # Print all the nodes (Group and Leaf objects) on object tree try: date = datetime.datetime.fromtimestamp( Path(self.filename).stat().st_mtime, datetime.timezone.utc ).isoformat(timespec='seconds') except OSError: # in-memory file date = "" lines = [f'{self.filename} (File) {self.title!r}', f'Last modif.: {date!r}', 'Object Tree: '] for group in self.walk_groups("/"): lines.append(f'{group}') for kind in self._node_kinds[1:]: for node in self.list_nodes(group, kind): lines.append(f'{node}') return '\n'.join(lines) + '\n' def __repr__(self): """Return a detailed string representation of the object tree.""" if not self.isopen: return "" # Print all the nodes (Group and Leaf objects) on object tree lines = [ f'File(filename={self.filename!s}, title={self.title!r}, ' f'mode={self.mode!r}, root_uep={self.root_uep!r}, ' f'filters={self.filters!r})'] for group in self.walk_groups("/"): lines.append(f'{group}') for kind in self._node_kinds[1:]: for node in self.list_nodes(group, kind): lines.append(f'{node!r}') return '\n'.join(lines) + '\n' def _update_node_locations(self, oldpath, newpath): """Update location information of nodes under `oldpath`. This only affects *already loaded* nodes. """ oldprefix = oldpath + '/' # root node can not be renamed, anyway oldprefix_len = len(oldprefix) # Update alive and dead descendents. for cache in [self._node_manager.cache, self._node_manager.registry]: for nodepath in list(cache): if nodepath.startswith(oldprefix) and nodepath != oldprefix: nodesuffix = nodepath[oldprefix_len:] newnodepath = join_path(newpath, nodesuffix) newnodeppath = split_path(newnodepath)[0] descendent_node = self._get_node(nodepath) descendent_node._g_update_location(newnodeppath) # If a user hits ^C during a run, it is wise to gracefully close the # opened files. atexit.register(_open_files.close_all) PyTables-3.7.0/tables/filters.py000066400000000000000000000374251416254111300165510ustar00rootroot00000000000000"""Functionality related with filters in a PyTables file.""" import warnings import numpy as np from . import utilsextension, blosc_compressor_list, blosc_compcode_to_compname from .exceptions import FiltersWarning from packaging.version import Version import tables as tb blosc_version = Version(tb.which_lib_version("blosc")[1]) __docformat__ = 'reStructuredText' """The format of documentation strings in this module.""" all_complibs = ['zlib', 'lzo', 'bzip2', 'blosc'] all_complibs += ['blosc:%s' % cname for cname in blosc_compressor_list()] """List of all compression libraries.""" foreign_complibs = ['szip'] """List of known but unsupported compression libraries.""" default_complib = 'zlib' """The default compression library.""" _shuffle_flag = 0x1 _fletcher32_flag = 0x2 _rounding_flag = 0x4 _bitshuffle_flag = 0x8 class Filters: """Container for filter properties. This class is meant to serve as a container that keeps information about the filter properties associated with the chunked leaves, that is Table, CArray, EArray and VLArray. Instances of this class can be directly compared for equality. Parameters ---------- complevel : int Specifies a compression level for data. The allowed range is 0-9. A value of 0 (the default) disables compression. complib : str Specifies the compression library to be used. Right now, 'zlib' (the default), 'lzo', 'bzip2' and 'blosc' are supported. Additional compressors for Blosc like 'blosc:blosclz' ('blosclz' is the default in case the additional compressor is not specified), 'blosc:lz4', 'blosc:lz4hc', 'blosc:snappy', 'blosc:zlib' and 'blosc:zstd' are supported too. Specifying a compression library which is not available in the system issues a FiltersWarning and sets the library to the default one. shuffle : bool Whether or not to use the *Shuffle* filter in the HDF5 library. This is normally used to improve the compression ratio. A false value disables shuffling and a true one enables it. The default value depends on whether compression is enabled or not; if compression is enabled, shuffling defaults to be enabled, else shuffling is disabled. Shuffling can only be used when compression is enabled. bitshuffle : bool Whether or not to use the *BitShuffle* filter in the Blosc library. This is normally used to improve the compression ratio. A false value disables bitshuffling and a true one enables it. The default value is disabled. fletcher32 : bool Whether or not to use the *Fletcher32* filter in the HDF5 library. This is used to add a checksum on each data chunk. A false value (the default) disables the checksum. least_significant_digit : int If specified, data will be truncated (quantized). In conjunction with enabling compression, this produces 'lossy', but significantly more efficient compression. For example, if *least_significant_digit=1*, data will be quantized using ``around(scale*data)/scale``, where ``scale = 2**bits``, and bits is determined so that a precision of 0.1 is retained (in this case bits=4). Default is *None*, or no quantization. .. note:: quantization is only applied if some form of compression is enabled Examples -------- This is a small example on using the Filters class:: import numpy as np import tables as tb fileh = tb.open_file('test5.h5', mode='w') atom = Float32Atom() filters = Filters(complevel=1, complib='blosc', fletcher32=True) arr = fileh.create_earray(fileh.root, 'earray', atom, (0,2), "A growable array", filters=filters) # Append several rows in only one call arr.append(np.array([[1., 2.], [2., 3.], [3., 4.]], dtype=np.float32)) # Print information on that enlargeable array print("Result Array:") print(repr(arr)) fileh.close() This enforces the use of the Blosc library, a compression level of 1 and a Fletcher32 checksum filter as well. See the output of this example:: Result Array: /earray (EArray(3, 2), fletcher32, shuffle, blosc(1)) 'A growable array' type = float32 shape = (3, 2) itemsize = 4 nrows = 3 extdim = 0 flavor = 'numpy' byteorder = 'little' .. rubric:: Filters attributes .. attribute:: fletcher32 Whether the *Fletcher32* filter is active or not. .. attribute:: complevel The compression level (0 disables compression). .. attribute:: complib The compression filter used (irrelevant when compression is not enabled). .. attribute:: shuffle Whether the *Shuffle* filter is active or not. .. attribute:: bitshuffle Whether the *BitShuffle* filter is active or not (Blosc only). """ @property def shuffle_bitshuffle(self): """Encode NoShuffle (0), Shuffle (1) and BitShuffle (2) filters.""" if (self.shuffle and self.bitshuffle): raise ValueError( "Shuffle and BitShuffle cannot be active at the same time") if not (self.shuffle or self.bitshuffle): return 0 if self.shuffle: return 1 if self.bitshuffle: return 2 @classmethod def _from_leaf(cls, leaf): # Get a dictionary with all the filters parent = leaf._v_parent filters_dict = utilsextension.get_filters(parent._v_objectid, leaf._v_name) if filters_dict is None: filters_dict = {} # not chunked # Keyword arguments are all off kwargs = dict(complevel=0, shuffle=False, bitshuffle=False, fletcher32=False, least_significant_digit=None, _new=False) for (name, values) in filters_dict.items(): if name == 'deflate': name = 'zlib' if name in all_complibs: kwargs['complib'] = name if name == "blosc": kwargs['complevel'] = values[4] if values[5] == 1: # Shuffle filter is internal to blosc kwargs['shuffle'] = True elif values[5] == 2: # Shuffle filter is internal to blosc kwargs['bitshuffle'] = True # From Blosc 1.3 on, parameter 6 is used for the compressor if len(values) > 6: cname = blosc_compcode_to_compname(values[6]) kwargs['complib'] = "blosc:%s" % cname else: kwargs['complevel'] = values[0] elif name in foreign_complibs: kwargs['complib'] = name kwargs['complevel'] = 1 # any nonzero value will do elif name in ['shuffle', 'fletcher32']: kwargs[name] = True return cls(**kwargs) @classmethod def _unpack(cls, packed): """Create a new `Filters` object from a packed version. >>> Filters._unpack(0) Filters(complevel=0, shuffle=False, bitshuffle=False, fletcher32=False, least_significant_digit=None) >>> Filters._unpack(0x101) Filters(complevel=1, complib='zlib', shuffle=False, bitshuffle=False, fletcher32=False, least_significant_digit=None) >>> Filters._unpack(0x30109) Filters(complevel=9, complib='zlib', shuffle=True, bitshuffle=False, fletcher32=True, least_significant_digit=None) >>> Filters._unpack(0x3010A) Traceback (most recent call last): ... ValueError: compression level must be between 0 and 9 >>> Filters._unpack(0x1) Traceback (most recent call last): ... ValueError: invalid compression library id: 0 """ kwargs = {'_new': False} # Byte 0: compression level. kwargs['complevel'] = complevel = packed & 0xff packed >>= 8 # Byte 1: compression library id (0 for none). if complevel > 0: complib_id = int(packed & 0xff) if not (0 < complib_id <= len(all_complibs)): raise ValueError("invalid compression library id: %d" % complib_id) kwargs['complib'] = all_complibs[complib_id - 1] packed >>= 8 # Byte 2: parameterless filters. kwargs['shuffle'] = packed & _shuffle_flag kwargs['bitshuffle'] = packed & _bitshuffle_flag kwargs['fletcher32'] = packed & _fletcher32_flag has_rounding = packed & _rounding_flag packed >>= 8 # Byte 3: least significant digit. if has_rounding: kwargs['least_significant_digit'] = np.int8(packed & 0xff) else: kwargs['least_significant_digit'] = None return cls(**kwargs) def _pack(self): """Pack the `Filters` object into a 64-bit NumPy integer.""" packed = np.int64(0) # Byte 3: least significant digit. if self.least_significant_digit is not None: # assert isinstance(self.least_significant_digit, numpy.int8) packed |= self.least_significant_digit packed <<= 8 # Byte 2: parameterless filters. if self.shuffle: packed |= _shuffle_flag if self.bitshuffle: packed |= _bitshuffle_flag if self.fletcher32: packed |= _fletcher32_flag if self.least_significant_digit: packed |= _rounding_flag packed <<= 8 # Byte 1: compression library id (0 for none). if self.complevel > 0: packed |= all_complibs.index(self.complib) + 1 packed <<= 8 # Byte 0: compression level. packed |= self.complevel return packed def __init__(self, complevel=0, complib=default_complib, shuffle=True, bitshuffle=False, fletcher32=False, least_significant_digit=None, _new=True): if not (0 <= complevel <= 9): raise ValueError("compression level must be between 0 and 9") if _new and complevel > 0: # These checks are not performed when loading filters from disk. if complib not in all_complibs: raise ValueError( "compression library ``%s`` is not supported; " "it must be one of: %s" % (complib, ", ".join(all_complibs))) if utilsextension.which_lib_version(complib) is None: warnings.warn("compression library ``%s`` is not available; " "using ``%s`` instead" % (complib, default_complib), FiltersWarning) complib = default_complib # always available complevel = int(complevel) complib = str(complib) shuffle = bool(shuffle) bitshuffle = bool(bitshuffle) fletcher32 = bool(fletcher32) if least_significant_digit is not None: least_significant_digit = np.int8(least_significant_digit) if complevel == 0: # Override some inputs when compression is not enabled. complib = None # make it clear there is no compression shuffle = False # shuffling and not compressing makes no sense least_significant_digit = None elif complib not in all_complibs: # Do not try to use a meaningful level for unsupported libs. complevel = -1 self.complevel = complevel """The compression level (0 disables compression).""" self.complib = complib """The compression filter used (irrelevant when compression is not enabled). """ self.shuffle = shuffle """Whether the *Shuffle* filter is active or not.""" self.bitshuffle = bitshuffle """Whether the *BitShuffle* filter is active or not.""" if (self.complib and self.bitshuffle and not self.complib.startswith('blosc')): raise ValueError("BitShuffle can only be used inside Blosc") if self.shuffle and self.bitshuffle: # BitShuffle has priority in case both are specified self.shuffle = False if (self.bitshuffle and blosc_version < tb.req_versions.min_blosc_bitshuffle_version): raise ValueError(f"This Blosc library does not have support for " f"the bitshuffle filter. Please update to " f"Blosc >= " f"{tb.req_versions.min_blosc_bitshuffle_version}") self.fletcher32 = fletcher32 """Whether the *Fletcher32* filter is active or not.""" self.least_significant_digit = least_significant_digit """The least significant digit to which data shall be truncated.""" def __repr__(self): args = [] if self.complevel >= 0: # meaningful compression level args.append(f'complevel={self.complevel}') if self.complevel != 0: # compression enabled (-1 or > 0) args.append(f'complib={self.complib!r}') args.append(f'shuffle={self.shuffle}') args.append(f'bitshuffle={self.bitshuffle}') args.append(f'fletcher32={self.fletcher32}') args.append(f'least_significant_digit={self.least_significant_digit}') return f'{self.__class__.__name__}({", ".join(args)})' def __str__(self): return repr(self) def __eq__(self, other): if not isinstance(other, self.__class__): return False for attr in self.__dict__: if getattr(self, attr) != getattr(other, attr): return False return True # XXX: API incompatible change for PyTables 3 line # Overriding __eq__ blocks inheritance of __hash__ in 3.x # def __hash__(self): # return hash((self.__class__, self.complevel, self.complib, # self.shuffle, self.bitshuffle, self.fletcher32)) def copy(self, **override): """Get a copy of the filters, possibly overriding some arguments. Constructor arguments to be overridden must be passed as keyword arguments. Using this method is recommended over replacing the attributes of an instance, since instances of this class may become immutable in the future:: >>> filters1 = Filters() >>> filters2 = filters1.copy() >>> filters1 == filters2 True >>> filters1 is filters2 False >>> filters3 = filters1.copy(complevel=1) #doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: compression library ``None`` is not supported... >>> filters3 = filters1.copy(complevel=1, complib='zlib') >>> print(filters1) Filters(complevel=0, shuffle=False, bitshuffle=False, fletcher32=False, least_significant_digit=None) >>> print(filters3) Filters(complevel=1, complib='zlib', shuffle=False, bitshuffle=False, fletcher32=False, least_significant_digit=None) >>> filters1.copy(foobar=42) #doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: ...__init__() got an unexpected keyword argument 'foobar' """ newargs = self.__dict__.copy() newargs.update(override) return self.__class__(**newargs) def _test(): """Run ``doctest`` on this module.""" import doctest doctest.testmod() if __name__ == '__main__': _test() PyTables-3.7.0/tables/flavor.py000066400000000000000000000311351416254111300163620ustar00rootroot00000000000000"""Utilities for handling different array flavors in PyTables. Variables ========= `__docformat`__ The format of documentation strings in this module. `internal_flavor` The flavor used internally by PyTables. `all_flavors` List of all flavors available to PyTables. `alias_map` Maps old flavor names to the most similar current flavor. `description_map` Maps flavors to short descriptions of their supported objects. `identifier_map` Maps flavors to functions that can identify their objects. The function associated with a given flavor will return a true value if the object passed to it can be identified as being of that flavor. See the `flavor_of()` function for a friendlier interface to flavor identification. `converter_map` Maps (source, destination) flavor pairs to converter functions. Converter functions get an array of the source flavor and return an array of the destination flavor. See the `array_of_flavor()` and `flavor_to_flavor()` functions for friendlier interfaces to flavor conversion. """ import warnings import numpy as np from .exceptions import FlavorError, FlavorWarning __docformat__ = 'reStructuredText' """The format of documentation strings in this module.""" internal_flavor = 'numpy' """The flavor used internally by PyTables.""" # This is very slightly slower than a set for a small number of values # in terms of (infrequent) lookup time, but allows `flavor_of()` # (which may be called much more frequently) to check for flavors in # order, beginning with the most common one. all_flavors = [] # filled as flavors are registered """List of all flavors available to PyTables.""" alias_map = {} # filled as flavors are registered """Maps old flavor names to the most similar current flavor.""" description_map = {} # filled as flavors are registered """Maps flavors to short descriptions of their supported objects.""" identifier_map = {} # filled as flavors are registered """Maps flavors to functions that can identify their objects. The function associated with a given flavor will return a true value if the object passed to it can be identified as being of that flavor. See the `flavor_of()` function for a friendlier interface to flavor identification. """ converter_map = {} # filled as flavors are registered """Maps (source, destination) flavor pairs to converter functions. Converter functions get an array of the source flavor and return an array of the destination flavor. See the `array_of_flavor()` and `flavor_to_flavor()` functions for friendlier interfaces to flavor conversion. """ def check_flavor(flavor): """Raise a ``FlavorError`` if the `flavor` is not valid.""" if flavor not in all_flavors: available_flavs = ", ".join(flav for flav in all_flavors) raise FlavorError( "flavor ``%s`` is unsupported or unavailable; " "available flavors in this system are: %s" % (flavor, available_flavs)) def array_of_flavor2(array, src_flavor, dst_flavor): """Get a version of the given `array` in a different flavor. The input `array` must be of the given `src_flavor`, and the returned array will be of the indicated `dst_flavor`. Both flavors may be the same, but it is not guaranteed that the returned array will be the same object as the input one in this case. If the conversion is not supported, a ``FlavorError`` is raised. """ convkey = (src_flavor, dst_flavor) if convkey not in converter_map: raise FlavorError("conversion from flavor ``%s`` to flavor ``%s`` " "is unsupported or unavailable in this system" % (src_flavor, dst_flavor)) convfunc = converter_map[convkey] return convfunc(array) def flavor_to_flavor(array, src_flavor, dst_flavor): """Get a version of the given `array` in a different flavor. The input `array` must be of the given `src_flavor`, and the returned array will be of the indicated `dst_flavor` (see below for an exception to this). Both flavors may be the same, but it is not guaranteed that the returned array will be the same object as the input one in this case. If the conversion is not supported, a `FlavorWarning` is issued and the input `array` is returned as is. """ try: return array_of_flavor2(array, src_flavor, dst_flavor) except FlavorError as fe: warnings.warn("%s; returning an object of the ``%s`` flavor instead" % (fe.args[0], src_flavor), FlavorWarning) return array def internal_to_flavor(array, dst_flavor): """Get a version of the given `array` in a different `dst_flavor`. The input `array` must be of the internal flavor, and the returned array will be of the given `dst_flavor`. See `flavor_to_flavor()` for more information. """ return flavor_to_flavor(array, internal_flavor, dst_flavor) def array_as_internal(array, src_flavor): """Get a version of the given `array` in the internal flavor. The input `array` must be of the given `src_flavor`, and the returned array will be of the internal flavor. If the conversion is not supported, a ``FlavorError`` is raised. """ return array_of_flavor2(array, src_flavor, internal_flavor) def flavor_of(array): """Identify the flavor of a given `array`. If the `array` can not be matched with any flavor, a ``TypeError`` is raised. """ for flavor in all_flavors: if identifier_map[flavor](array): return flavor type_name = type(array).__name__ supported_descs = "; ".join(description_map[fl] for fl in all_flavors) raise TypeError( "objects of type ``%s`` are not supported in this context, sorry; " "supported objects are: %s" % (type_name, supported_descs)) def array_of_flavor(array, dst_flavor): """Get a version of the given `array` in a different `dst_flavor`. The flavor of the input `array` is guessed, and the returned array will be of the given `dst_flavor`. If the conversion is not supported, a ``FlavorError`` is raised. """ return array_of_flavor2(array, flavor_of(array), dst_flavor) def restrict_flavors(keep=('python',)): """Disable all flavors except those in keep. Providing an empty keep sequence implies disabling all flavors (but the internal one). If the sequence is not specified, only optional flavors are disabled. .. important:: Once you disable a flavor, it can not be enabled again. """ remove = set(all_flavors) - set(keep) - {internal_flavor} for flavor in remove: _disable_flavor(flavor) # Flavor registration # # The order in which flavors appear in `all_flavors` determines the # order in which they will be tested for by `flavor_of()`, so place # most frequent flavors first. all_flavors.append('numpy') # this is the internal flavor all_flavors.append('python') # this is always supported def _register_aliases(): """Register aliases of *available* flavors.""" for flavor in all_flavors: aliases = eval('_%s_aliases' % flavor) for alias in aliases: alias_map[alias] = flavor def _register_descriptions(): """Register descriptions of *available* flavors.""" for flavor in all_flavors: description_map[flavor] = eval('_%s_desc' % flavor) def _register_identifiers(): """Register identifier functions of *available* flavors.""" for flavor in all_flavors: identifier_map[flavor] = eval('_is_%s' % flavor) def _register_converters(): """Register converter functions between *available* flavors.""" def identity(array): return array for src_flavor in all_flavors: for dst_flavor in all_flavors: # Converters with the same source and destination flavor # are used when available, since they may perform some # optimizations on the resulting array (e.g. making it # contiguous). Otherwise, an identity function is used. convfunc = None try: convfunc = eval(f'_conv_{src_flavor}_to_{dst_flavor}') except NameError: if src_flavor == dst_flavor: convfunc = identity if convfunc: converter_map[(src_flavor, dst_flavor)] = convfunc def _register_all(): """Register all *available* flavors.""" _register_aliases() _register_descriptions() _register_identifiers() _register_converters() def _deregister_aliases(flavor): """Deregister aliases of a given `flavor` (no checks).""" rm_aliases = [] for (an_alias, a_flavor) in alias_map.items(): if a_flavor == flavor: rm_aliases.append(an_alias) for an_alias in rm_aliases: del alias_map[an_alias] def _deregister_description(flavor): """Deregister description of a given `flavor` (no checks).""" del description_map[flavor] def _deregister_identifier(flavor): """Deregister identifier function of a given `flavor` (no checks).""" del identifier_map[flavor] def _deregister_converters(flavor): """Deregister converter functions of a given `flavor` (no checks).""" rm_flavor_pairs = [] for flavor_pair in converter_map: if flavor in flavor_pair: rm_flavor_pairs.append(flavor_pair) for flavor_pair in rm_flavor_pairs: del converter_map[flavor_pair] def _disable_flavor(flavor): """Completely disable the given `flavor` (no checks).""" _deregister_aliases(flavor) _deregister_description(flavor) _deregister_identifier(flavor) _deregister_converters(flavor) all_flavors.remove(flavor) # Implementation of flavors _python_aliases = [ 'List', 'Tuple', 'Int', 'Float', 'String', 'VLString', 'Object', ] _python_desc = ("homogeneous list or tuple, " "integer, float, complex or bytes") def _is_python(array): return isinstance(array, (tuple, list, int, float, complex, bytes)) _numpy_aliases = [] _numpy_desc = "NumPy array, record or scalar" if np.lib.NumpyVersion(np.__version__) >= np.lib.NumpyVersion('1.19.0'): def toarray(array, *args, **kwargs): with warnings.catch_warnings(): warnings.simplefilter('error') try: array = np.array(array, *args, **kwargs) except np.VisibleDeprecationWarning: raise ValueError( 'cannot guess the desired dtype from the input') return array else: toarray = np.array def _is_numpy(array): return isinstance(array, (np.ndarray, np.generic)) def _numpy_contiguous(convfunc): """Decorate `convfunc` to return a *contiguous* NumPy array. Note: When arrays are 0-strided, the copy is avoided. This allows to use `array` to still carry info about the dtype and shape. """ def conv_to_numpy(array): nparr = convfunc(array) if (hasattr(nparr, 'flags') and not nparr.flags.contiguous and sum(nparr.strides) != 0): nparr = nparr.copy() # copying the array makes it contiguous return nparr conv_to_numpy.__name__ = convfunc.__name__ conv_to_numpy.__doc__ = convfunc.__doc__ return conv_to_numpy @_numpy_contiguous def _conv_numpy_to_numpy(array): # Passes contiguous arrays through and converts scalars into # scalar arrays. nparr = np.asarray(array) if nparr.dtype.kind == 'U': # from Python 3 loads of common strings are disguised as Unicode try: # try to convert to basic 'S' type return nparr.astype('S') except UnicodeEncodeError: pass # pass on true Unicode arrays downstream in case it can be # handled in the future return nparr @_numpy_contiguous def _conv_python_to_numpy(array): nparr = toarray(array) if nparr.dtype.kind == 'U': # from Python 3 loads of common strings are disguised as Unicode try: # try to convert to basic 'S' type return nparr.astype('S') except UnicodeEncodeError: pass # pass on true Unicode arrays downstream in case it can be # handled in the future return nparr def _conv_numpy_to_python(array): if array.shape != (): # Lists are the default for returning multidimensional objects array = array.tolist() else: # 0-dim or scalar case array = array.item() return array # Now register everything related with *available* flavors. _register_all() def _test(): """Run ``doctest`` on this module.""" import doctest doctest.testmod() if __name__ == '__main__': _test() PyTables-3.7.0/tables/group.py000066400000000000000000001347231416254111300162340ustar00rootroot00000000000000"""Here is defined the Group class.""" import os import weakref import warnings from .misc.proxydict import ProxyDict from . import hdf5extension from . import utilsextension from .registry import class_id_dict from .exceptions import (NodeError, NoSuchNodeError, NaturalNameWarning, PerformanceWarning) from .filters import Filters from .registry import get_class_by_name from .path import check_name_validity, join_path, isvisiblename from .node import Node, NotLoggedMixin from .leaf import Leaf from .unimplemented import UnImplemented, Unknown from .link import Link, SoftLink, ExternalLink obversion = "1.0" class _ChildrenDict(ProxyDict): def _get_value_from_container(self, container, key): return container._f_get_child(key) class Group(hdf5extension.Group, Node): """Basic PyTables grouping structure. Instances of this class are grouping structures containing *child* instances of zero or more groups or leaves, together with supporting metadata. Each group has exactly one *parent* group. Working with groups and leaves is similar in many ways to working with directories and files, respectively, in a Unix filesystem. As with Unix directories and files, objects in the object tree are often described by giving their full (or absolute) path names. This full path can be specified either as a string (like in '/group1/group2') or as a complete object path written in *natural naming* schema (like in file.root.group1.group2). A collateral effect of the *natural naming* schema is that the names of members in the Group class and its instances must be carefully chosen to avoid colliding with existing children node names. For this reason and to avoid polluting the children namespace all members in a Group start with some reserved prefix, like _f_ (for public methods), _g_ (for private ones), _v_ (for instance variables) or _c_ (for class variables). Any attempt to create a new child node whose name starts with one of these prefixes will raise a ValueError exception. Another effect of natural naming is that children named after Python keywords or having names not valid as Python identifiers (e.g. class, $a or 44) can not be accessed using the node.child syntax. You will be forced to use node._f_get_child(child) to access them (which is recommended for programmatic accesses). You will also need to use _f_get_child() to access an existing child node if you set a Python attribute in the Group with the same name as that node (you will get a NaturalNameWarning when doing this). Parameters ---------- parentnode The parent :class:`Group` object. name : str The name of this node in its parent group. title The title for this group new If this group is new or has to be read from disk filters : Filters A Filters instance .. versionchanged:: 3.0 *parentNode* renamed into *parentnode* Notes ----- The following documentation includes methods that are automatically called when a Group instance is accessed in a special way. For instance, this class defines the __setattr__, __getattr__, __delattr__ and __dir__ methods, and they set, get and delete *ordinary Python attributes* as normally intended. In addition to that, __getattr__ allows getting *child nodes* by their name for the sake of easy interaction on the command line, as long as there is no Python attribute with the same name. Groups also allow the interactive completion (when using readline) of the names of child nodes. For instance:: # get a Python attribute nchild = group._v_nchildren # Add a Table child called 'table' under 'group'. h5file.create_table(group, 'table', myDescription) table = group.table # get the table child instance group.table = 'foo' # set a Python attribute # (PyTables warns you here about using the name of a child node.) foo = group.table # get a Python attribute del group.table # delete a Python attribute table = group.table # get the table child instance again Additionally, on interactive python sessions you may get autocompletions of children named as *valid python identifiers* by pressing the `[Tab]` key, or to use the dir() global function. .. rubric:: Group attributes The following instance variables are provided in addition to those in Node (see :ref:`NodeClassDescr`): .. attribute:: _v_children Dictionary with all nodes hanging from this group. .. attribute:: _v_groups Dictionary with all groups hanging from this group. .. attribute:: _v_hidden Dictionary with all hidden nodes hanging from this group. .. attribute:: _v_leaves Dictionary with all leaves hanging from this group. .. attribute:: _v_links Dictionary with all links hanging from this group. .. attribute:: _v_unknown Dictionary with all unknown nodes hanging from this group. """ # Class identifier. _c_classid = 'GROUP' # Children containers that should be loaded only in a lazy way. # These are documented in the ``Group._g_add_children_names`` method. _c_lazy_children_attrs = ( '__members__', '_v_children', '_v_groups', '_v_leaves', '_v_links', '_v_unknown', '_v_hidden') # `_v_nchildren` is a direct read-only shorthand # for the number of *visible* children in a group. def _g_getnchildren(self): """The number of children hanging from this group.""" return len(self._v_children) _v_nchildren = property(_g_getnchildren) # `_v_filters` is a direct read-write shorthand for the ``FILTERS`` # attribute with the default `Filters` instance as a default value. def _g_getfilters(self): filters = getattr(self._v_attrs, 'FILTERS', None) if filters is None: filters = Filters() return filters def _g_setfilters(self, value): if not isinstance(value, Filters): raise TypeError( f"value is not an instance of `Filters`: {value!r}") self._v_attrs.FILTERS = value def _g_delfilters(self): del self._v_attrs.FILTERS _v_filters = property( _g_getfilters, _g_setfilters, _g_delfilters, """Default filter properties for child nodes. You can (and are encouraged to) use this property to get, set and delete the FILTERS HDF5 attribute of the group, which stores a Filters instance (see :ref:`FiltersClassDescr`). When the group has no such attribute, a default Filters instance is used. """) def __init__(self, parentnode, name, title="", new=False, filters=None, _log=True): # Remember to assign these values in the root group constructor # if it does not use this one! # First, set attributes belonging to group objects. self._v_version = obversion """The object version of this group.""" self._v_new = new """Is this the first time the node has been created?""" self._v_new_title = title """New title for this node.""" self._v_new_filters = filters """New default filter properties for child nodes.""" self._v_max_group_width = parentnode._v_file.params['MAX_GROUP_WIDTH'] """Maximum number of children on each group before warning the user. .. versionchanged:: 3.0 The *_v_maxGroupWidth* attribute has been renamed into *_v_max_group_width*. """ # Finally, set up this object as a node. super().__init__(parentnode, name, _log) def _g_post_init_hook(self): if self._v_new: if self._v_file.params['PYTABLES_SYS_ATTRS']: # Save some attributes for the new group on disk. set_attr = self._v_attrs._g__setattr # Set the title, class and version attributes. set_attr('TITLE', self._v_new_title) set_attr('CLASS', self._c_classid) set_attr('VERSION', self._v_version) # Set the default filter properties. newfilters = self._v_new_filters if newfilters is None: # If no filters have been passed in the constructor, # inherit them from the parent group, but only if they # have been inherited or explicitly set. newfilters = getattr( self._v_parent._v_attrs, 'FILTERS', None) if newfilters is not None: set_attr('FILTERS', newfilters) else: # If the file has PyTables format, get the VERSION attr if 'VERSION' in self._v_attrs._v_attrnamessys: self._v_version = self._v_attrs.VERSION else: self._v_version = "0.0 (unknown)" # We don't need to get more attributes from disk, # since the most important ones are defined as properties. def __del__(self): if (self._v_isopen and self._v_pathname in self._v_file._node_manager.registry and '_v_children' in self.__dict__): # The group is going to be killed. Rebuild weak references # (that Python cancelled just before calling this method) so # that they are still usable if the object is revived later. selfref = weakref.ref(self) self._v_children.containerref = selfref self._v_groups.containerref = selfref self._v_leaves.containerref = selfref self._v_links.containerref = selfref self._v_unknown.containerref = selfref self._v_hidden.containerref = selfref super().__del__() def _g_get_child_group_class(self, childname): """Get the class of a not-yet-loaded group child. `childname` must be the name of a *group* child. """ childCID = self._g_get_gchild_attr(childname, 'CLASS') if childCID is not None and not isinstance(childCID, str): childCID = childCID.decode('utf-8') if childCID in class_id_dict: return class_id_dict[childCID] # look up group class else: return Group # default group class def _g_get_child_leaf_class(self, childname, warn=True): """Get the class of a not-yet-loaded leaf child. `childname` must be the name of a *leaf* child. If the child belongs to an unknown kind of leaf, or if its kind can not be guessed, `UnImplemented` will be returned and a warning will be issued if `warn` is true. """ if self._v_file.params['PYTABLES_SYS_ATTRS']: childCID = self._g_get_lchild_attr(childname, 'CLASS') if childCID is not None and not isinstance(childCID, str): childCID = childCID.decode('utf-8') else: childCID = None if childCID in class_id_dict: return class_id_dict[childCID] # look up leaf class else: # Unknown or no ``CLASS`` attribute, try a guess. childCID2 = utilsextension.which_class(self._v_objectid, childname) if childCID2 == 'UNSUPPORTED': if warn: if childCID is None: warnings.warn( "leaf ``%s`` is of an unsupported type; " "it will become an ``UnImplemented`` node" % self._g_join(childname)) else: warnings.warn( ("leaf ``%s`` has an unknown class ID ``%s``; " "it will become an ``UnImplemented`` node") % (self._g_join(childname), childCID)) return UnImplemented assert childCID2 in class_id_dict return class_id_dict[childCID2] # look up leaf class def _g_add_children_names(self): """Add children names to this group taking into account their visibility and kind.""" mydict = self.__dict__ # The names of the lazy attributes mydict['__members__'] = members = [] """The names of visible children nodes for readline-style completion. """ mydict['_v_children'] = children = _ChildrenDict(self) """The number of children hanging from this group.""" mydict['_v_groups'] = groups = _ChildrenDict(self) """Dictionary with all groups hanging from this group.""" mydict['_v_leaves'] = leaves = _ChildrenDict(self) """Dictionary with all leaves hanging from this group.""" mydict['_v_links'] = links = _ChildrenDict(self) """Dictionary with all links hanging from this group.""" mydict['_v_unknown'] = unknown = _ChildrenDict(self) """Dictionary with all unknown nodes hanging from this group.""" mydict['_v_hidden'] = hidden = _ChildrenDict(self) """Dictionary with all hidden nodes hanging from this group.""" # Get the names of *all* child groups and leaves. (group_names, leaf_names, link_names, unknown_names) = \ self._g_list_group(self._v_parent) # Separate groups into visible groups and hidden nodes, # and leaves into visible leaves and hidden nodes. for (childnames, childdict) in ((group_names, groups), (leaf_names, leaves), (link_names, links), (unknown_names, unknown)): for childname in childnames: # See whether the name implies that the node is hidden. # (Assigned values are entirely irrelevant.) if isvisiblename(childname): # Visible node. members.insert(0, childname) children[childname] = None childdict[childname] = None else: # Hidden node. hidden[childname] = None def _g_check_has_child(self, name): """Check whether 'name' is a children of 'self' and return its type.""" # Get the HDF5 name matching the PyTables name. node_type = self._g_get_objinfo(name) if node_type == "NoSuchNode": raise NoSuchNodeError( "group ``%s`` does not have a child named ``%s``" % (self._v_pathname, name)) return node_type def __iter__(self): """Iterate over the child nodes hanging directly from the group. This iterator is *not* recursive. Examples -------- :: # Non-recursively list all the nodes hanging from '/detector' print("Nodes in '/detector' group:") for node in h5file.root.detector: print(node) """ return self._f_iter_nodes() def __contains__(self, name): """Is there a child with that `name`? Returns a true value if the group has a child node (visible or hidden) with the given `name` (a string), false otherwise. """ self._g_check_open() try: self._g_check_has_child(name) except NoSuchNodeError: return False return True def __getitem__(self, childname): """Return the (visible or hidden) child with that `name` ( a string). Raise IndexError if child not exist. """ try: return self._f_get_child(childname) except NoSuchNodeError: raise IndexError(childname) def _f_walknodes(self, classname=None): """Iterate over descendant nodes. This method recursively walks *self* top to bottom (preorder), iterating over child groups in alphanumerical order, and yielding nodes. If classname is supplied, only instances of the named class are yielded. If *classname* is Group, it behaves like :meth:`Group._f_walk_groups`, yielding only groups. If you don't want a recursive behavior, use :meth:`Group._f_iter_nodes` instead. Examples -------- :: # Recursively print all the arrays hanging from '/' print("Arrays in the object tree '/':") for array in h5file.root._f_walknodes('Array', recursive=True): print(array) """ self._g_check_open() # For compatibility with old default arguments. if classname == '': classname = None if classname == "Group": # Recursive algorithm yield from self._f_walk_groups() else: for group in self._f_walk_groups(): yield from group._f_iter_nodes(classname) def _g_join(self, name): """Helper method to correctly concatenate a name child object with the pathname of this group.""" if name == "/": # This case can happen when doing copies return self._v_pathname return join_path(self._v_pathname, name) def _g_width_warning(self): """Issue a :exc:`PerformanceWarning` on too many children.""" warnings.warn("""\ group ``%s`` is exceeding the recommended maximum number of children (%d); \ be ready to see PyTables asking for *lots* of memory and possibly slow I/O.""" % (self._v_pathname, self._v_max_group_width), PerformanceWarning) def _g_refnode(self, childnode, childname, validate=True): """Insert references to a `childnode` via a `childname`. Checks that the `childname` is valid and does not exist, then creates references to the given `childnode` by that `childname`. The validation of the name can be omitted by setting `validate` to a false value (this may be useful for adding already existing nodes to the tree). """ # Check for name validity. if validate: check_name_validity(childname) childnode._g_check_name(childname) # Check if there is already a child with the same name. # This can be triggered because of the user # (via node construction or renaming/movement). # Links are not checked here because they are copied and referenced # using ``File.get_node`` so they already exist in `self`. if (not isinstance(childnode, Link)) and childname in self: raise NodeError( "group ``%s`` already has a child node named ``%s``" % (self._v_pathname, childname)) # Show a warning if there is an object attribute with that name. if childname in self.__dict__: warnings.warn( "group ``%s`` already has an attribute named ``%s``; " "you will not be able to use natural naming " "to access the child node" % (self._v_pathname, childname), NaturalNameWarning) # Check group width limits. if (len(self._v_children) + len(self._v_hidden) >= self._v_max_group_width): self._g_width_warning() # Update members information. # Insert references to the new child. # (Assigned values are entirely irrelevant.) if isvisiblename(childname): # Visible node. self.__members__.insert(0, childname) # enable completion self._v_children[childname] = None # insert node if isinstance(childnode, Unknown): self._v_unknown[childname] = None elif isinstance(childnode, Link): self._v_links[childname] = None elif isinstance(childnode, Leaf): self._v_leaves[childname] = None elif isinstance(childnode, Group): self._v_groups[childname] = None else: # Hidden node. self._v_hidden[childname] = None # insert node def _g_unrefnode(self, childname): """Remove references to a node. Removes all references to the named node. """ # This can *not* be triggered because of the user. assert childname in self, \ ("group ``%s`` does not have a child node named ``%s``" % (self._v_pathname, childname)) # Update members information, if needed if '_v_children' in self.__dict__: if childname in self._v_children: # Visible node. members = self.__members__ member_index = members.index(childname) del members[member_index] # disables completion del self._v_children[childname] # remove node self._v_unknown.pop(childname, None) self._v_links.pop(childname, None) self._v_leaves.pop(childname, None) self._v_groups.pop(childname, None) else: # Hidden node. del self._v_hidden[childname] # remove node def _g_move(self, newparent, newname): # Move the node to the new location. oldpath = self._v_pathname super()._g_move(newparent, newname) newpath = self._v_pathname # Update location information in children. This node shouldn't # be affected since it has already been relocated. self._v_file._update_node_locations(oldpath, newpath) def _g_copy(self, newparent, newname, recursive, _log=True, **kwargs): # Compute default arguments. title = kwargs.get('title', self._v_title) filters = kwargs.get('filters', None) stats = kwargs.get('stats', None) # Fix arguments with explicit None values for backwards compatibility. if title is None: title = self._v_title # If no filters have been passed to the call, copy them from the # source group, but only if inherited or explicitly set. if filters is None: filters = getattr(self._v_attrs, 'FILTERS', None) # Create a copy of the object. new_node = Group(newparent, newname, title, new=True, filters=filters, _log=_log) # Copy user attributes if needed. if kwargs.get('copyuserattrs', True): self._v_attrs._g_copy(new_node._v_attrs, copyclass=True) # Update statistics if needed. if stats is not None: stats['groups'] += 1 if recursive: # Copy child nodes if a recursive copy was requested. # Some arguments should *not* be passed to children copy ops. kwargs = kwargs.copy() kwargs.pop('title', None) self._g_copy_children(new_node, **kwargs) return new_node def _g_copy_children(self, newparent, **kwargs): """Copy child nodes. Copies all nodes descending from this one into the specified `newparent`. If the new parent has a child node with the same name as one of the nodes in this group, the copy fails with a `NodeError`, maybe resulting in a partial copy. Nothing is logged. """ # Recursive version of children copy. # for srcchild in self._v_children.itervalues(): # srcchild._g_copy_as_child(newparent, **kwargs) # Non-recursive version of children copy. use_hardlinks = kwargs.get('use_hardlinks', False) if use_hardlinks: address_map = kwargs.setdefault('address_map', {}) parentstack = [(self, newparent)] # [(source, destination), ...] while parentstack: (srcparent, dstparent) = parentstack.pop() if use_hardlinks: for srcchild in srcparent._v_children.values(): addr, rc = srcchild._get_obj_info() if rc > 1 and addr in address_map: where, name = address_map[addr][0] localsrc = os.path.join(where, name) dstparent._v_file.create_hard_link(dstparent, srcchild.name, localsrc) address_map[addr].append( (dstparent._v_pathname, srcchild.name) ) # Update statistics if needed. stats = kwargs.pop('stats', None) if stats is not None: stats['hardlinks'] += 1 else: dstchild = srcchild._g_copy_as_child(dstparent, **kwargs) if isinstance(srcchild, Group): parentstack.append((srcchild, dstchild)) if rc > 1: address_map[addr] = [ (dstparent._v_pathname, srcchild.name) ] else: for srcchild in srcparent._v_children.values(): dstchild = srcchild._g_copy_as_child(dstparent, **kwargs) if isinstance(srcchild, Group): parentstack.append((srcchild, dstchild)) def _f_get_child(self, childname): """Get the child called childname of this group. If the child exists (be it visible or not), it is returned. Else, a NoSuchNodeError is raised. Using this method is recommended over getattr() when doing programmatic accesses to children if childname is unknown beforehand or when its name is not a valid Python identifier. """ self._g_check_open() self._g_check_has_child(childname) childpath = join_path(self._v_pathname, childname) return self._v_file._get_node(childpath) def _f_list_nodes(self, classname=None): """Return a *list* with children nodes. This is a list-returning version of :meth:`Group._f_iter_nodes()`. """ return list(self._f_iter_nodes(classname)) def _f_iter_nodes(self, classname=None): """Iterate over children nodes. Child nodes are yielded alphanumerically sorted by node name. If the name of a class derived from Node (see :ref:`NodeClassDescr`) is supplied in the classname parameter, only instances of that class (or subclasses of it) will be returned. This is an iterator version of :meth:`Group._f_list_nodes`. """ self._g_check_open() if not classname: # Returns all the children alphanumerically sorted for name in sorted(self._v_children): yield self._v_children[name] elif classname == 'Group': # Returns all the groups alphanumerically sorted for name in sorted(self._v_groups): yield self._v_groups[name] elif classname == 'Leaf': # Returns all the leaves alphanumerically sorted for name in sorted(self._v_leaves): yield self._v_leaves[name] elif classname == 'Link': # Returns all the links alphanumerically sorted for name in sorted(self._v_links): yield self._v_links[name] elif classname == 'IndexArray': raise TypeError( "listing ``IndexArray`` nodes is not allowed") else: class_ = get_class_by_name(classname) for childname, childnode in sorted(self._v_children.items()): if isinstance(childnode, class_): yield childnode def _f_walk_groups(self): """Recursively iterate over descendent groups (not leaves). This method starts by yielding *self*, and then it goes on to recursively iterate over all child groups in alphanumerical order, top to bottom (preorder), following the same procedure. """ self._g_check_open() stack = [self] yield self # Iterate over the descendants while stack: objgroup = stack.pop() groupnames = sorted(objgroup._v_groups) # Sort the groups before delivering. This uses the groups names # for groups in tree (in order to sort() can classify them). for groupname in groupnames: # TODO: check recursion stack.append(objgroup._v_groups[groupname]) yield objgroup._v_groups[groupname] def __delattr__(self, name): """Delete a Python attribute called name. This method only provides a extra warning in case the user tries to delete a children node using __delattr__. To remove a children node from this group use :meth:`File.remove_node` or :meth:`Node._f_remove`. To delete a PyTables node attribute use :meth:`File.del_node_attr`, :meth:`Node._f_delattr` or :attr:`Node._v_attrs``. If there is an attribute and a child node with the same name, the child node will be made accessible again via natural naming. """ try: super().__delattr__(name) # nothing particular except AttributeError as ae: hint = " (use ``node._f_remove()`` if you want to remove a node)" raise ae.__class__(str(ae) + hint) def __dir__(self): """Autocomplete only children named as valid python identifiers. Only PY3 supports this special method. """ subnods = [c for c in self._v_children if c.isidentifier()] return super().__dir__() + subnods def __getattr__(self, name): """Get a Python attribute or child node called name. If the node has a child node called name it is returned, else an AttributeError is raised. """ if name in self._c_lazy_children_attrs: self._g_add_children_names() return self.__dict__[name] return self._f_get_child(name) def __setattr__(self, name, value): """Set a Python attribute called name with the given value. This method stores an *ordinary Python attribute* in the object. It does *not* store new children nodes under this group; for that, use the File.create*() methods (see the File class in :ref:`FileClassDescr`). It does *neither* store a PyTables node attribute; for that, use :meth:`File.set_node_attr`, :meth`:Node._f_setattr` or :attr:`Node._v_attrs`. If there is already a child node with the same name, a NaturalNameWarning will be issued and the child node will not be accessible via natural naming nor getattr(). It will still be available via :meth:`File.get_node`, :meth:`Group._f_get_child` and children dictionaries in the group (if visible). """ # Show a warning if there is an child node with that name. # # ..note:: # # Using ``if name in self:`` is not right since that would # require ``_v_children`` and ``_v_hidden`` to be already set # when the very first attribute assignments are made. # Moreover, this warning is only concerned about clashes with # names used in natural naming, i.e. those in ``__members__``. # # ..note:: # # The check ``'__members__' in myDict`` allows attribute # assignment to happen before calling `Group.__init__()`, by # avoiding to look into the still not assigned ``__members__`` # attribute. This allows subclasses to set up some attributes # and then call the constructor of the superclass. If the # check above is disabled, that results in Python entering an # endless loop on exit! mydict = self.__dict__ if '__members__' in mydict and name in self.__members__: warnings.warn( "group ``%s`` already has a child node named ``%s``; " "you will not be able to use natural naming " "to access the child node" % (self._v_pathname, name), NaturalNameWarning) super().__setattr__(name, value) def _f_flush(self): """Flush this Group.""" self._g_check_open() self._g_flush_group() def _g_close_descendents(self): """Close all the *loaded* descendent nodes of this group.""" node_manager = self._v_file._node_manager node_manager.close_subtree(self._v_pathname) def _g_close(self): """Close this (open) group.""" if self._v_isopen: # hdf5extension operations: # Close HDF5 group. self._g_close_group() # Close myself as a node. super()._f_close() def _f_close(self): """Close this group and all its descendents. This method has the behavior described in :meth:`Node._f_close`. It should be noted that this operation closes all the nodes descending from this group. You should not need to close nodes manually because they are automatically opened/closed when they are loaded/evicted from the integrated LRU cache. """ # If the group is already closed, return immediately if not self._v_isopen: return # First, close all the descendents of this group, unless a) the # group is being deleted (evicted from LRU cache) or b) the node # is being closed during an aborted creation, in which cases # this is not an explicit close issued by the user. if not (self._v__deleting or self._v_objectid is None): self._g_close_descendents() # When all the descendents have been closed, close this group. # This is done at the end because some nodes may still need to # be loaded during the closing process; thus this node must be # open until the very end. self._g_close() def _g_remove(self, recursive=False, force=False): """Remove (recursively if needed) the Group. This version correctly handles both visible and hidden nodes. """ if self._v_nchildren > 0: if not (recursive or force): raise NodeError("group ``%s`` has child nodes; " "please set `recursive` or `force` to true " "to remove it" % (self._v_pathname,)) # First close all the descendents hanging from this group, # so that it is not possible to use a node that no longer exists. self._g_close_descendents() # Remove the node itself from the hierarchy. super()._g_remove(recursive, force) def _f_copy(self, newparent=None, newname=None, overwrite=False, recursive=False, createparents=False, **kwargs): """Copy this node and return the new one. This method has the behavior described in :meth:`Node._f_copy`. In addition, it recognizes the following keyword arguments: Parameters ---------- title The new title for the destination. If omitted or None, the original title is used. This only applies to the topmost node in recursive copies. filters : Filters Specifying this parameter overrides the original filter properties in the source node. If specified, it must be an instance of the Filters class (see :ref:`FiltersClassDescr`). The default is to copy the filter properties from the source node. copyuserattrs You can prevent the user attributes from being copied by setting thisparameter to False. The default is to copy them. stats This argument may be used to collect statistics on the copy process. When used, it should be a dictionary with keys 'groups', 'leaves', 'links' and 'bytes' having a numeric value. Their values willbe incremented to reflect the number of groups, leaves and bytes, respectively, that have been copied during the operation. """ return super()._f_copy( newparent, newname, overwrite, recursive, createparents, **kwargs) def _f_copy_children(self, dstgroup, overwrite=False, recursive=False, createparents=False, **kwargs): """Copy the children of this group into another group. Children hanging directly from this group are copied into dstgroup, which can be a Group (see :ref:`GroupClassDescr`) object or its pathname in string form. If createparents is true, the needed groups for the given destination group path to exist will be created. The operation will fail with a NodeError if there is a child node in the destination group with the same name as one of the copied children from this one, unless overwrite is true; in this case, the former child node is recursively removed before copying the later. By default, nodes descending from children groups of this node are not copied. If the recursive argument is true, all descendant nodes of this node are recursively copied. Additional keyword arguments may be passed to customize the copying process. For instance, title and filters may be changed, user attributes may be or may not be copied, data may be sub-sampled, stats may be collected, etc. Arguments unknown to nodes are simply ignored. Check the documentation for copying operations of nodes to see which options they support. """ self._g_check_open() # `dstgroup` is used instead of its path to avoid accepting # `Node` objects when `createparents` is true. Also, note that # there is no risk of creating parent nodes and failing later # because of destination nodes already existing. dstparent = self._v_file._get_or_create_path(dstgroup, createparents) self._g_check_group(dstparent) # Is it a group? if not overwrite: # Abort as early as possible when destination nodes exist # and overwriting is not enabled. for childname in self._v_children: if childname in dstparent: raise NodeError( "destination group ``%s`` already has " "a node named ``%s``; " "you may want to use the ``overwrite`` argument" % (dstparent._v_pathname, childname)) use_hardlinks = kwargs.get('use_hardlinks', False) if use_hardlinks: address_map = kwargs.setdefault('address_map', {}) for child in self._v_children.values(): addr, rc = child._get_obj_info() if rc > 1 and addr in address_map: where, name = address_map[addr][0] localsrc = os.path.join(where, name) dstparent._v_file.create_hard_link(dstparent, child.name, localsrc) address_map[addr].append( (dstparent._v_pathname, child.name) ) # Update statistics if needed. stats = kwargs.pop('stats', None) if stats is not None: stats['hardlinks'] += 1 else: child._f_copy(dstparent, None, overwrite, recursive, **kwargs) if rc > 1: address_map[addr] = [ (dstparent._v_pathname, child.name) ] else: for child in self._v_children.values(): child._f_copy(dstparent, None, overwrite, recursive, **kwargs) def __str__(self): """Return a short string representation of the group. Examples -------- :: >>> import tables >>> f = tables.open_file('tables/tests/Tables_lzo2.h5') >>> print(f.root.group0) /group0 (Group) '' >>> f.close() """ return (f"{self._v_pathname} ({self.__class__.__name__}) " f"{self._v_title!r}") def __repr__(self): """Return a detailed string representation of the group. Examples -------- :: >>> import tables >>> f = tables.open_file('tables/tests/Tables_lzo2.h5') >>> f.root.group0 /group0 (Group) '' children := ['group1' (Group), 'tuple1' (Table)] >>> f.close() """ rep = [ f'{childname!r} ({child.__class__.__name__})' for (childname, child) in self._v_children.items() ] return f'{self!s}\n children := [{", ".join(rep)}]' # Special definition for group root class RootGroup(Group): def __init__(self, ptfile, name, title, new, filters): mydict = self.__dict__ # Set group attributes. self._v_version = obversion self._v_new = new if new: self._v_new_title = title self._v_new_filters = filters else: self._v_new_title = None self._v_new_filters = None # Set node attributes. self._v_file = ptfile self._v_isopen = True # root is always open self._v_pathname = '/' self._v_name = '/' self._v_depth = 0 self._v_max_group_width = ptfile.params['MAX_GROUP_WIDTH'] self._v__deleting = False self._v_objectid = None # later # Only the root node has the file as a parent. # Bypass __setattr__ to avoid the ``Node._v_parent`` property. mydict['_v_parent'] = ptfile ptfile._node_manager.register_node(self, '/') # hdf5extension operations (do before setting an AttributeSet): # Update node attributes. self._g_new(ptfile, name, init=True) # Open the node and get its object ID. self._v_objectid = self._g_open() # Set disk attributes and read children names. # # This *must* be postponed because this method needs the root node # to be created and bound to ``File.root``. # This is an exception to the rule, handled by ``File.__init()__``. # # self._g_post_init_hook() def _g_load_child(self, childname): """Load a child node from disk. The child node `childname` is loaded from disk and an adequate `Node` object is created and returned. If there is no such child, a `NoSuchNodeError` is raised. """ if self._v_file.root_uep != "/": childname = join_path(self._v_file.root_uep, childname) # Is the node a group or a leaf? node_type = self._g_check_has_child(childname) # Nodes that HDF5 report as H5G_UNKNOWN if node_type == 'Unknown': return Unknown(self, childname) # Guess the PyTables class suited to the node, # build a PyTables node and return it. if node_type == "Group": if self._v_file.params['PYTABLES_SYS_ATTRS']: ChildClass = self._g_get_child_group_class(childname) else: # Default is a Group class ChildClass = Group return ChildClass(self, childname, new=False) elif node_type == "Leaf": ChildClass = self._g_get_child_leaf_class(childname, warn=True) # Building a leaf may still fail because of unsupported types # and other causes. # return ChildClass(self, childname) # uncomment for debugging try: return ChildClass(self, childname) except Exception as exc: # XXX warnings.warn( "problems loading leaf ``%s``::\n\n" " %s\n\n" "The leaf will become an ``UnImplemented`` node." % (self._g_join(childname), exc)) # If not, associate an UnImplemented object to it return UnImplemented(self, childname) elif node_type == "SoftLink": return SoftLink(self, childname) elif node_type == "ExternalLink": return ExternalLink(self, childname) else: return UnImplemented(self, childname) def _f_rename(self, newname): raise NodeError("the root node can not be renamed") def _f_move(self, newparent=None, newname=None, createparents=False): raise NodeError("the root node can not be moved") def _f_remove(self, recursive=False): raise NodeError("the root node can not be removed") class TransactionGroupG(NotLoggedMixin, Group): _c_classid = 'TRANSGROUP' def _g_width_warning(self): warnings.warn("""\ the number of transactions is exceeding the recommended maximum (%d);\ be ready to see PyTables asking for *lots* of memory and possibly slow I/O""" % (self._v_max_group_width,), PerformanceWarning) class TransactionG(NotLoggedMixin, Group): _c_classid = 'TRANSG' def _g_width_warning(self): warnings.warn("""\ transaction ``%s`` is exceeding the recommended maximum number of marks (%d);\ be ready to see PyTables asking for *lots* of memory and possibly slow I/O""" % (self._v_pathname, self._v_max_group_width), PerformanceWarning) class MarkG(NotLoggedMixin, Group): # Class identifier. _c_classid = 'MARKG' import re _c_shadow_name_re = re.compile(r'^a[0-9]+$') def _g_width_warning(self): warnings.warn("""\ mark ``%s`` is exceeding the recommended maximum action storage (%d nodes);\ be ready to see PyTables asking for *lots* of memory and possibly slow I/O""" % (self._v_pathname, self._v_max_group_width), PerformanceWarning) def _g_reset(self): """Empty action storage (nodes and attributes). This method empties all action storage kept in this node: nodes and attributes. """ # Remove action storage nodes. for child in list(self._v_children.values()): child._g_remove(True, True) # Remove action storage attributes. attrs = self._v_attrs shname = self._c_shadow_name_re for attrname in attrs._v_attrnamesuser[:]: if shname.match(attrname): attrs._g__delattr(attrname) PyTables-3.7.0/tables/hdf5extension.pxd000066400000000000000000000016261416254111300200210ustar00rootroot00000000000000######################################################################## # # License: BSD # Created: # Author: Francesc Alted - faltet@pytables.com # # $Id$ # ######################################################################## from numpy cimport ndarray from .definitions cimport hid_t, hsize_t, hbool_t # Declaration of instance variables for shared classes cdef class Node: cdef object name cdef hid_t parent_id cdef class Leaf(Node): cdef hid_t dataset_id cdef hid_t type_id cdef hid_t base_type_id cdef hid_t disk_type_id cdef hsize_t *dims # Necessary to be here because of Leaf._g_truncate() cdef _get_type_ids(self) cdef _convert_time64(self, ndarray nparr, int sense) cdef class Array(Leaf): cdef int rank cdef hsize_t *maxdims cdef hsize_t *dims_chunk ## Local Variables: ## mode: python ## py-indent-offset: 2 ## tab-width: 2 ## fill-column: 78 ## End: PyTables-3.7.0/tables/hdf5extension.pyx000066400000000000000000002203101416254111300200370ustar00rootroot00000000000000######################################################################## # # License: BSD # Created: September 21, 2002 # Author: Francesc Alted - faltet@pytables.com # # $Id$ # ######################################################################## """Cython interface between several PyTables classes and HDF5 library. Classes (type extensions): File AttributeSet Node Leaf Group Array VLArray UnImplemented Functions: Misc variables: """ import os import warnings from collections import namedtuple ObjInfo = namedtuple('ObjInfo', ['addr', 'rc']) ObjTimestamps = namedtuple('ObjTimestamps', ['atime', 'mtime', 'ctime', 'btime']) import pickle import numpy from .exceptions import HDF5ExtError, DataTypeWarning from .utils import (check_file_access, byteorders, correct_byteorder, SizeType) from .atom import Atom from .description import descr_from_dtype from .utilsextension import (encode_filename, set_blosc_max_threads, atom_to_hdf5_type, atom_from_hdf5_type, hdf5_to_np_ext_type, create_nested_type, pttype_to_hdf5, pt_special_kinds, npext_prefixes_to_ptkinds, hdf5_class_to_string, platform_byteorder) # Types, constants, functions, classes & other objects from everywhere from libc.stdlib cimport malloc, free from libc.string cimport strdup, strlen from numpy cimport (import_array, ndarray, npy_intp, PyArray_BYTES, PyArray_DATA, PyArray_DIMS, PyArray_NDIM, PyArray_STRIDE) from cpython.bytes cimport (PyBytes_AsString, PyBytes_FromStringAndSize, PyBytes_Check) from cpython.unicode cimport PyUnicode_DecodeUTF8 from .definitions cimport (uintptr_t, hid_t, herr_t, hsize_t, hvl_t, H5S_seloper_t, H5D_FILL_VALUE_UNDEFINED, H5O_TYPE_UNKNOWN, H5O_TYPE_GROUP, H5O_TYPE_DATASET, H5O_TYPE_NAMED_DATATYPE, H5L_TYPE_ERROR, H5L_TYPE_HARD, H5L_TYPE_SOFT, H5L_TYPE_EXTERNAL, H5T_class_t, H5T_sign_t, H5T_NATIVE_INT, H5T_cset_t, H5T_CSET_ASCII, H5T_CSET_UTF8, H5F_SCOPE_GLOBAL, H5F_ACC_TRUNC, H5F_ACC_RDONLY, H5F_ACC_RDWR, H5P_DEFAULT, H5P_FILE_ACCESS, H5P_FILE_CREATE, H5T_DIR_DEFAULT, H5S_SELECT_SET, H5S_SELECT_AND, H5S_SELECT_NOTB, H5Fcreate, H5Fopen, H5Fclose, H5Fflush, H5Fget_vfd_handle, H5Fget_filesize, H5Fget_create_plist, H5Gcreate, H5Gopen, H5Gclose, H5Ldelete, H5Lmove, H5Dopen, H5Dclose, H5Dread, H5Dwrite, H5Dget_type, H5Dget_create_plist, H5Dget_space, H5Dvlen_reclaim, H5Dget_storage_size, H5Dvlen_get_buf_size, H5Tget_native_type, H5Tclose, H5Tis_variable_str, H5Tget_sign, H5Adelete, H5T_BITFIELD, H5T_INTEGER, H5T_FLOAT, H5T_STRING, H5Tget_order, H5Pcreate, H5Pset_cache, H5Pclose, H5Pget_userblock, H5Pset_userblock, H5Pset_fapl_sec2, H5Pset_fapl_log, H5Pset_fapl_stdio, H5Pset_fapl_core, H5Pset_fapl_split, H5Pget_obj_track_times, H5Sselect_all, H5Sselect_elements, H5Sselect_hyperslab, H5Screate_simple, H5Sclose, H5Oget_info, H5O_info_t, H5ATTRset_attribute, H5ATTRset_attribute_string, H5ATTRget_attribute, H5ATTRget_attribute_string, H5ATTRget_attribute_vlen_string_array, H5ATTRfind_attribute, H5ATTRget_type_ndims, H5ATTRget_dims, H5ARRAYget_ndims, H5ARRAYget_info, set_cache_size, get_objinfo, get_linkinfo, Giterate, Aiterate, H5UIget_info, get_len_of_range, conv_float64_timeval32, truncate_dset, H5_HAVE_DIRECT_DRIVER, pt_H5Pset_fapl_direct, H5_HAVE_WINDOWS_DRIVER, pt_H5Pset_fapl_windows, H5_HAVE_IMAGE_FILE, pt_H5Pset_file_image, pt_H5Fget_file_image, H5Tget_size, hobj_ref_t) cdef int H5T_CSET_DEFAULT = 16 from .utilsextension cimport malloc_dims, get_native_type, cstr_to_pystr, load_reference #------------------------------------------------------------------- cdef extern from "Python.h": object PyByteArray_FromStringAndSize(char *s, Py_ssize_t len) # Functions from HDF5 ARRAY (this is not part of HDF5 HL; it's private) cdef extern from "H5ARRAY.h" nogil: herr_t H5ARRAYmake(hid_t loc_id, char *dset_name, char *obversion, int rank, hsize_t *dims, int extdim, hid_t type_id, hsize_t *dims_chunk, void *fill_data, int complevel, char *complib, int shuffle, int fletcher32, hbool_t track_times, void *data) herr_t H5ARRAYappend_records(hid_t dataset_id, hid_t type_id, int rank, hsize_t *dims_orig, hsize_t *dims_new, int extdim, void *data ) herr_t H5ARRAYwrite_records(hid_t dataset_id, hid_t type_id, int rank, hsize_t *start, hsize_t *step, hsize_t *count, void *data) herr_t H5ARRAYread(hid_t dataset_id, hid_t type_id, hsize_t start, hsize_t nrows, hsize_t step, int extdim, void *data) herr_t H5ARRAYreadSlice(hid_t dataset_id, hid_t type_id, hsize_t *start, hsize_t *stop, hsize_t *step, void *data) herr_t H5ARRAYreadIndex(hid_t dataset_id, hid_t type_id, int notequal, hsize_t *start, hsize_t *stop, hsize_t *step, void *data) herr_t H5ARRAYget_chunkshape(hid_t dataset_id, int rank, hsize_t *dims_chunk) herr_t H5ARRAYget_fill_value( hid_t dataset_id, hid_t type_id, int *status, void *value) # Functions for dealing with VLArray objects cdef extern from "H5VLARRAY.h" nogil: herr_t H5VLARRAYmake( hid_t loc_id, char *dset_name, char *obversion, int rank, hsize_t *dims, hid_t type_id, hsize_t chunk_size, void *fill_data, int complevel, char *complib, int shuffle, int fletcher32, hbool_t track_times, void *data) herr_t H5VLARRAYappend_records( hid_t dataset_id, hid_t type_id, int nobjects, hsize_t nrecords, void *data ) herr_t H5VLARRAYmodify_records( hid_t dataset_id, hid_t type_id, hsize_t nrow, int nobjects, void *data ) herr_t H5VLARRAYget_info( hid_t dataset_id, hid_t type_id, hsize_t *nrecords, char *base_byteorder) #---------------------------------------------------------------------------- # Initialization code # The numpy API requires this function to be called before # using any numpy facilities in an extension module. import_array() #--------------------------------------------------------------------------- # Helper functions cdef hsize_t *npy_malloc_dims(int rank, npy_intp *pdims): """Returns a malloced hsize_t dims from a npy_intp *pdims.""" cdef int i cdef hsize_t *dims dims = NULL if rank > 0: dims = malloc(rank * sizeof(hsize_t)) for i from 0 <= i < rank: dims[i] = pdims[i] return dims cdef object getshape(int rank, hsize_t *dims): """Return a shape (tuple) from a dims C array of rank dimensions.""" cdef int i cdef object shape shape = [] for i from 0 <= i < rank: shape.append(SizeType(dims[i])) return tuple(shape) # Helper function for quickly fetch an attribute string cdef object get_attribute_string_or_none(hid_t node_id, char* attr_name): """Returns a string/unicode attribute if it exists in node_id. It returns ``None`` in case it don't exists (or there have been problems reading it). """ cdef char *attr_value cdef int cset = H5T_CSET_DEFAULT cdef object retvalue cdef hsize_t size attr_value = NULL retvalue = None # Default value if H5ATTRfind_attribute(node_id, attr_name): size = H5ATTRget_attribute_string(node_id, attr_name, &attr_value, &cset) if size == 0: if cset == H5T_CSET_UTF8: retvalue = numpy.unicode_('') else: retvalue = numpy.bytes_(b'') elif cset == H5T_CSET_UTF8: if size == 1 and attr_value[0] == 0: # compatibility with PyTables <= 3.1.1 retvalue = numpy.unicode_('') retvalue = PyUnicode_DecodeUTF8(attr_value, size, NULL) retvalue = numpy.unicode_(retvalue) else: retvalue = PyBytes_FromStringAndSize(attr_value, size) # AV: oct 2012 # since now we use the string size got form HDF5 we have to strip # trailing zeros used for padding. # The entire process is quite odd but due to a bug (??) in the way # numpy arrays are pickled in python 3 we can't assume that # strlen(attr_value) is the actual length of the attribute # and numpy.bytes_(attr_value) can give a truncated pickle string retvalue = retvalue.rstrip(b'\x00') retvalue = numpy.bytes_(retvalue) # Important to release attr_value, because it has been malloc'ed! if attr_value: free(attr_value) return retvalue # Get the numpy dtype scalar attribute from an HDF5 type as fast as possible cdef object get_dtype_scalar(hid_t type_id, H5T_class_t class_id, size_t itemsize): cdef H5T_sign_t sign cdef object stype if class_id == H5T_BITFIELD: stype = "b1" elif class_id == H5T_INTEGER: # Get the sign sign = H5Tget_sign(type_id) if (sign > 0): stype = "i%s" % (itemsize) else: stype = "u%s" % (itemsize) elif class_id == H5T_FLOAT: stype = "f%s" % (itemsize) elif class_id == H5T_STRING: if H5Tis_variable_str(type_id): raise TypeError("variable length strings are not supported yet") stype = "S%s" % (itemsize) # Try to get a NumPy type. If this can't be done, return None. try: ntype = numpy.dtype(stype) except TypeError: ntype = None return ntype _supported_drivers = ( "H5FD_SEC2", "H5FD_DIRECT", #"H5FD_LOG", "H5FD_WINDOWS", "H5FD_STDIO", "H5FD_CORE", #"H5FD_FAMILY", #"H5FD_MULTI", "H5FD_SPLIT", #"H5FD_MPIO", #"H5FD_MPIPOSIX", #"H5FD_STREAM", ) HAVE_DIRECT_DRIVER = bool(H5_HAVE_DIRECT_DRIVER) HAVE_WINDOWS_DRIVER = bool(H5_HAVE_WINDOWS_DRIVER) # Type extensions declarations (these are subclassed by PyTables # Python classes) cdef class File: cdef hid_t file_id cdef hid_t access_plist cdef object name def _g_new(self, name, pymode, **params): cdef herr_t err = 0 cdef hid_t access_plist, create_plist = H5P_DEFAULT cdef hid_t meta_plist_id = H5P_DEFAULT, raw_plist_id = H5P_DEFAULT cdef size_t img_buf_len = 0, user_block_size = 0 cdef void *img_buf_p = NULL cdef bytes encname #cdef bytes logfile_name # Check if we can handle the driver driver = params["DRIVER"] if driver is not None and driver not in _supported_drivers: raise ValueError("Invalid or not supported driver: '%s'" % driver) if driver == "H5FD_SPLIT": meta_ext = params.get("DRIVER_SPLIT_META_EXT", "-m.h5") raw_ext = params.get("DRIVER_SPLIT_RAW_EXT", "-r.h5") meta_name = meta_ext % name if "%s" in meta_ext else name + meta_ext raw_name = raw_ext % name if "%s" in raw_ext else name + raw_ext enc_meta_ext = encode_filename(meta_ext) enc_raw_ext = encode_filename(raw_ext) # Create a new file using default properties self.name = name # Encode the filename in case it is unicode encname = encode_filename(name) # These fields can be seen from Python. self._v_new = None # this will be computed later # """Is this file going to be created from scratch?""" self._isPTFile = True # assume a PyTables file by default # """Does this HDF5 file have a PyTables format?""" assert pymode in ('r', 'r+', 'a', 'w'), ("an invalid mode string ``%s`` " "passed the ``check_file_access()`` test; " "please report this to the authors" % pymode) image = params.get('DRIVER_CORE_IMAGE') if image: if driver != "H5FD_CORE": warnings.warn("The DRIVER_CORE_IMAGE parameter will be ignored by " "the '%s' driver" % driver) elif not PyBytes_Check(image): raise TypeError("The DRIVER_CORE_IMAGE must be a string of bytes") elif not H5_HAVE_IMAGE_FILE: raise RuntimeError("Support for image files is only available in " "HDF5 >= 1.8.9") # After the following check we can be quite sure # that the file or directory exists and permissions are right. if driver == "H5FD_SPLIT": for n in meta_name, raw_name: check_file_access(n, pymode) else: backing_store = params.get("DRIVER_CORE_BACKING_STORE", 1) if driver != "H5FD_CORE" or backing_store: check_file_access(name, pymode) # Should a new file be created? if image: exists = True elif driver == "H5FD_SPLIT": exists = os.path.exists(meta_name) and os.path.exists(raw_name) else: exists = os.path.exists(name) self._v_new = not (pymode in ('r', 'r+') or (pymode == 'a' and exists)) user_block_size = params.get("USER_BLOCK_SIZE", 0) if user_block_size and not self._v_new: warnings.warn("The HDF5 file already esists: the USER_BLOCK_SIZE " "will be ignored") elif user_block_size: user_block_size = int(user_block_size) is_pow_of_2 = ((user_block_size & (user_block_size - 1)) == 0) if user_block_size < 512 or not is_pow_of_2: raise ValueError("The USER_BLOCK_SIZE must be a power od 2 greather " "than 512 or zero") # File creation property list create_plist = H5Pcreate(H5P_FILE_CREATE) err = H5Pset_userblock(create_plist, user_block_size) if err < 0: H5Pclose(create_plist) raise HDF5ExtError("Unable to set the user block size") # File access property list access_plist = H5Pcreate(H5P_FILE_ACCESS) # Set parameters for chunk cache H5Pset_cache(access_plist, 0, params["CHUNK_CACHE_NELMTS"], params["CHUNK_CACHE_SIZE"], params["CHUNK_CACHE_PREEMPT"]) # Set the I/O driver if driver == "H5FD_SEC2": err = H5Pset_fapl_sec2(access_plist) elif driver == "H5FD_DIRECT": if not H5_HAVE_DIRECT_DRIVER: H5Pclose(create_plist) H5Pclose(access_plist) raise RuntimeError("The H5FD_DIRECT driver is not available") err = pt_H5Pset_fapl_direct(access_plist, params["DRIVER_DIRECT_ALIGNMENT"], params["DRIVER_DIRECT_BLOCK_SIZE"], params["DRIVER_DIRECT_CBUF_SIZE"]) #elif driver == "H5FD_LOG": # if "DRIVER_LOG_FILE" not in params: # H5Pclose(access_plist) # raise ValueError("The DRIVER_LOG_FILE parameter is required for " # "the H5FD_LOG driver") # logfile_name = encode_filename(params["DRIVER_LOG_FILE"]) # err = H5Pset_fapl_log(access_plist, # logfile_name, # params["DRIVER_LOG_FLAGS"], # params["DRIVER_LOG_BUF_SIZE"]) elif driver == "H5FD_WINDOWS": if not H5_HAVE_WINDOWS_DRIVER: H5Pclose(access_plist) H5Pclose(create_plist) raise RuntimeError("The H5FD_WINDOWS driver is not available") err = pt_H5Pset_fapl_windows(access_plist) elif driver == "H5FD_STDIO": err = H5Pset_fapl_stdio(access_plist) elif driver == "H5FD_CORE": err = H5Pset_fapl_core(access_plist, params["DRIVER_CORE_INCREMENT"], backing_store) if image: img_buf_len = len(image) img_buf_p = PyBytes_AsString(image) err = pt_H5Pset_file_image(access_plist, img_buf_p, img_buf_len) if err < 0: H5Pclose(create_plist) H5Pclose(access_plist) raise HDF5ExtError("Unable to set the file image") #elif driver == "H5FD_FAMILY": # H5Pset_fapl_family(access_plist, # params["DRIVER_FAMILY_MEMB_SIZE"], # fapl_id) #elif driver == "H5FD_MULTI": # err = H5Pset_fapl_multi(access_plist, memb_map, memb_fapl, memb_name, # memb_addr, relax) elif driver == "H5FD_SPLIT": err = H5Pset_fapl_split(access_plist, enc_meta_ext, meta_plist_id, enc_raw_ext, raw_plist_id) if err < 0: e = HDF5ExtError("Unable to set the file access property list") H5Pclose(create_plist) H5Pclose(access_plist) raise e if pymode == 'r': self.file_id = H5Fopen(encname, H5F_ACC_RDONLY, access_plist) elif pymode == 'r+': self.file_id = H5Fopen(encname, H5F_ACC_RDWR, access_plist) elif pymode == 'a': if exists: # A test for logging. ## H5Pset_sieve_buf_size(access_plist, 0) ## H5Pset_fapl_log (access_plist, "test.log", H5FD_LOG_LOC_WRITE, 0) self.file_id = H5Fopen(encname, H5F_ACC_RDWR, access_plist) else: self.file_id = H5Fcreate(encname, H5F_ACC_TRUNC, create_plist, access_plist) elif pymode == 'w': self.file_id = H5Fcreate(encname, H5F_ACC_TRUNC, create_plist, access_plist) if self.file_id < 0: e = HDF5ExtError("Unable to open/create file '%s'" % name) H5Pclose(create_plist) H5Pclose(access_plist) raise e H5Pclose(create_plist) H5Pclose(access_plist) # Set the cache size set_cache_size(self.file_id, params["METADATA_CACHE_SIZE"]) # Set the maximum number of threads for Blosc set_blosc_max_threads(params["MAX_BLOSC_THREADS"]) # XXX: add the possibility to pass a pre-allocated buffer def get_file_image(self): """Retrieves an in-memory image of an existing, open HDF5 file. .. note:: this method requires HDF5 >= 1.8.9. .. versionadded:: 3.0 """ cdef ssize_t size = 0 cdef size_t buf_len = 0 cdef bytes image cdef char* cimage self.flush() # retrieve the size of the buffer for the file image size = pt_H5Fget_file_image(self.file_id, NULL, buf_len) if size < 0: raise HDF5ExtError("Unable to retrieve the size of the buffer for the " "file image. Plese note that not all drivers " "provide support for image files.") # allocate the memory buffer image = PyBytes_FromStringAndSize(NULL, size) if not image: raise RuntimeError("Unable to allecote meomory fir the file image") cimage = image buf_len = size size = pt_H5Fget_file_image(self.file_id, cimage, buf_len) if size < 0: raise HDF5ExtError("Unable to retrieve the file image. " "Plese note that not all drivers provide support " "for image files.") return image def get_filesize(self): """Returns the size of an HDF5 file. The returned size is that of the entire file, as opposed to only the HDF5 portion of the file. I.e., size includes the user block, if any, the HDF5 portion of the file, and any data that may have been appended beyond the data written through the HDF5 Library. .. versionadded:: 3.0 """ cdef herr_t err = 0 cdef hsize_t size = 0 err = H5Fget_filesize(self.file_id, &size) if err < 0: raise HDF5ExtError("Unable to retrieve the HDF5 file size") return size def get_userblock_size(self): """Retrieves the size of a user block. .. versionadded:: 3.0 """ cdef herr_t err = 0 cdef hsize_t size = 0 cdef hid_t create_plist create_plist = H5Fget_create_plist(self.file_id) if create_plist < 0: raise HDF5ExtError("Unable to get the creation property list") err = H5Pget_userblock(create_plist, &size) if err < 0: H5Pclose(create_plist) raise HDF5ExtError("unable to retrieve the user block size") H5Pclose(create_plist) return size # Accessor definitions def _get_file_id(self): return self.file_id def fileno(self): """Return the underlying OS integer file descriptor. This is needed for lower-level file interfaces, such as the ``fcntl`` module. """ cdef void *file_handle cdef uintptr_t *descriptor cdef herr_t err err = H5Fget_vfd_handle(self.file_id, H5P_DEFAULT, &file_handle) if err < 0: raise HDF5ExtError( "Problems getting file descriptor for file ``%s``" % self.name) # Convert the 'void *file_handle' into an 'int *descriptor' descriptor = file_handle return descriptor[0] def _flush_file(self, scope): # Close the file H5Fflush(self.file_id, scope) def _close_file(self): # Close the file H5Fclose( self.file_id ) self.file_id = 0 # Means file closed # This method is moved out of scope, until we provide code to delete # the memory booked by this extension types def __dealloc__(self): cdef int ret if self.file_id > 0: # Close the HDF5 file because user didn't do that! ret = H5Fclose(self.file_id) if ret < 0: raise HDF5ExtError("Problems closing the file '%s'" % self.name) cdef class AttributeSet: cdef object name def _g_new(self, node): self.name = node._v_name def _g_list_attr(self, node): """Return a tuple with the attribute list""" a = Aiterate(node._v_objectid) return a def _g_setattr(self, node, name, object value): """Save Python or NumPy objects as HDF5 attributes. Scalar Python objects, scalar NumPy & 0-dim NumPy objects will all be saved as H5T_SCALAR type. N-dim NumPy objects will be saved as H5T_ARRAY type. """ cdef int ret cdef hid_t dset_id, type_id cdef hsize_t *dims cdef ndarray ndv cdef object byteorder, rabyteorder, baseatom cdef char* cname = NULL cdef bytes encoded_name cdef int cset = H5T_CSET_DEFAULT encoded_name = name.encode('utf-8') # get the C pointer cname = encoded_name # The dataset id of the node dset_id = node._v_objectid # Convert a NumPy scalar into a NumPy 0-dim ndarray if isinstance(value, numpy.generic): value = numpy.array(value) # Check if value is a NumPy ndarray and of a supported type if (isinstance(value, numpy.ndarray) and value.dtype.kind in ('V', 'S', 'b', 'i', 'u', 'f', 'c')): # get a contiguous array: fixes #270 and gh-176 #value = numpy.ascontiguousarray(value) value = value.copy() if value.dtype.kind == 'V': description, rabyteorder = descr_from_dtype(value.dtype, ptparams=node._v_file.params) byteorder = byteorders[rabyteorder] type_id = create_nested_type(description, byteorder) # Make sure the value is consistent with offsets of the description value = value.astype(description._v_dtype) else: # Get the associated native HDF5 type of the scalar type baseatom = Atom.from_dtype(value.dtype.base) byteorder = byteorders[value.dtype.byteorder] type_id = atom_to_hdf5_type(baseatom, byteorder) # Get dimensionality info ndv = value dims = npy_malloc_dims(PyArray_NDIM(ndv), PyArray_DIMS(ndv)) # Actually write the attribute ret = H5ATTRset_attribute(dset_id, cname, type_id, PyArray_NDIM(ndv), dims, PyArray_BYTES(ndv)) if ret < 0: raise HDF5ExtError("Can't set attribute '%s' in node:\n %s." % (name, self._v_node)) # Release resources free(dims) H5Tclose(type_id) else: # Object cannot be natively represented in HDF5. if (isinstance(value, numpy.ndarray) and value.dtype.kind == 'U' and value.shape == ()): value = value[()].encode('utf-8') cset = H5T_CSET_UTF8 else: # Convert this object to a null-terminated string # (binary pickles are not supported at this moment) value = pickle.dumps(value, 0) ret = H5ATTRset_attribute_string(dset_id, cname, value, len(value), cset) if ret < 0: raise HDF5ExtError("Can't set attribute '%s' in node:\n %s." % (name, self._v_node)) # Get attributes def _g_getattr(self, node, attrname): """Get HDF5 attributes and retrieve them as NumPy objects. H5T_SCALAR types will be retrieved as scalar NumPy. H5T_ARRAY types will be retrieved as ndarray NumPy objects. """ cdef hsize_t *dims cdef H5T_class_t class_id cdef size_t type_size cdef hid_t mem_type, dset_id, type_id, native_type cdef int rank, ret, enumtype cdef void *rbuf cdef char *str_value cdef char **str_values = NULL cdef ndarray ndvalue cdef object shape, stype_atom, shape_atom, retvalue cdef int i, nelements cdef char* cattrname = NULL cdef bytes encoded_attrname cdef int cset = H5T_CSET_DEFAULT encoded_attrname = attrname.encode('utf-8') # Get the C pointer cattrname = encoded_attrname # The dataset id of the node dset_id = node._v_objectid dims = NULL ret = H5ATTRget_type_ndims(dset_id, cattrname, &type_id, &class_id, &type_size, &rank ) if ret < 0: raise HDF5ExtError("Can't get type info on attribute %s in node %s." % (attrname, self.name)) # Call a fast function for scalar values and typical class types if (rank == 0 and class_id == H5T_STRING): type_size = H5ATTRget_attribute_string(dset_id, cattrname, &str_value, &cset) if type_size == 0: if cset == H5T_CSET_UTF8: retvalue = numpy.unicode_('') else: retvalue = numpy.bytes_(b'') elif cset == H5T_CSET_UTF8: if type_size == 1 and str_value[0] == 0: # compatibility with PyTables <= 3.1.1 retvalue = numpy.unicode_('') retvalue = PyUnicode_DecodeUTF8(str_value, type_size, NULL) retvalue = numpy.unicode_(retvalue) else: retvalue = PyBytes_FromStringAndSize(str_value, type_size) # AV: oct 2012 # since now we use the string size got form HDF5 we have to strip # trailing zeros used for padding. # The entire process is quite odd but due to a bug (??) in the way # numpy arrays are pickled in python 3 we can't assume that # strlen(attr_value) is the actual length of the attibute # and numpy.bytes_(attr_value) can give a truncated pickle sting retvalue = retvalue.rstrip(b'\x00') retvalue = numpy.bytes_(retvalue) # bytes # Important to release attr_value, because it has been malloc'ed! if str_value: free(str_value) H5Tclose(type_id) return retvalue elif (rank == 0 and class_id in (H5T_BITFIELD, H5T_INTEGER, H5T_FLOAT)): dtype_ = get_dtype_scalar(type_id, class_id, type_size) if dtype_ is None: warnings.warn("Unsupported type for attribute '%s' in node '%s'. " "Offending HDF5 class: %d" % (attrname, self.name, class_id), DataTypeWarning) self._v_unimplemented.append(attrname) return None shape = () else: # General case # Get the dimensional info dims = malloc(rank * sizeof(hsize_t)) ret = H5ATTRget_dims(dset_id, cattrname, dims) if ret < 0: raise HDF5ExtError("Can't get dims info on attribute %s in node %s." % (attrname, self.name)) shape = getshape(rank, dims) # dims is not needed anymore free( dims) # Get the NumPy dtype from the type_id try: stype_, shape_ = hdf5_to_np_ext_type(type_id, pure_numpy_types=True, ptparams=node._v_file.params) dtype_ = numpy.dtype(stype_, shape_) except TypeError: if class_id == H5T_STRING and H5Tis_variable_str(type_id): nelements = H5ATTRget_attribute_vlen_string_array(dset_id, cattrname, &str_values, &cset) if nelements < 0: raise HDF5ExtError("Can't read attribute %s in node %s." % (attrname, self.name)) # The following generator expressions do not work with Cython 0.15.1 if cset == H5T_CSET_UTF8: #retvalue = numpy.fromiter( # PyUnicode_DecodeUTF8(str_values[i], # strlen(str_values[i]), # NULL) # for i in range(nelements), "O8") retvalue = numpy.array([ PyUnicode_DecodeUTF8(str_values[i], strlen(str_values[i]), NULL) for i in range(nelements)], "O8") else: #retvalue = numpy.fromiter( # str_values[i] for i in range(nelements), "O8") retvalue = numpy.array( [str_values[i] for i in range(nelements)], "O8") retvalue.shape = shape # Important to release attr_value, because it has been malloc'ed! for i in range(nelements): free(str_values[i]) free(str_values) return retvalue # This class is not supported. Instead of raising a TypeError, issue a # warning explaining the problem. This will allow to continue browsing # native HDF5 files, while informing the user about the problem. warnings.warn("Unsupported type for attribute '%s' in node '%s'. " "Offending HDF5 class: %d" % (attrname, self.name, class_id), DataTypeWarning) self._v_unimplemented.append(attrname) return None # Get the container for data ndvalue = numpy.empty(dtype=dtype_, shape=shape) # Get the pointer to the buffer data area rbuf = PyArray_DATA(ndvalue) # Actually read the attribute from disk ret = H5ATTRget_attribute(dset_id, cattrname, type_id, rbuf) if ret < 0: raise HDF5ExtError("Attribute %s exists in node %s, but can't get it." % (attrname, self.name)) H5Tclose(type_id) if rank > 0: # multidimensional case retvalue = ndvalue else: retvalue = ndvalue[()] # 0-dim ndarray becomes a NumPy scalar return retvalue def _g_remove(self, node, attrname): cdef int ret cdef hid_t dset_id cdef char *cattrname = NULL cdef bytes encoded_attrname encoded_attrname = attrname.encode('utf-8') # Get the C pointer cattrname = encoded_attrname # The dataset id of the node dset_id = node._v_objectid ret = H5Adelete(dset_id, cattrname) if ret < 0: raise HDF5ExtError("Attribute '%s' exists in node '%s', but cannot be " "deleted." % (attrname, self.name)) cdef class Node: # Instance variables declared in .pxd def _g_new(self, where, name, init): self.name = name # """The name of this node in its parent group.""" self.parent_id = where._v_objectid # """The identifier of the parent group.""" def _g_delete(self, parent): cdef int ret cdef bytes encoded_name encoded_name = self.name.encode('utf-8') # Delete this node ret = H5Ldelete(parent._v_objectid, encoded_name, H5P_DEFAULT) if ret < 0: raise HDF5ExtError("problems deleting the node ``%s``" % self.name) return ret def __dealloc__(self): self.parent_id = 0 def _get_obj_info(self): cdef herr_t ret = 0 cdef H5O_info_t oinfo ret = H5Oget_info(self._v_objectid, &oinfo) if ret < 0: raise HDF5ExtError("Unable to get object info for '%s'" % self. _v_pathname) return ObjInfo(oinfo.addr, oinfo.rc) def _get_obj_timestamps(self): cdef herr_t ret = 0 cdef H5O_info_t oinfo ret = H5Oget_info(self._v_objectid, &oinfo) if ret < 0: raise HDF5ExtError("Unable to get object info for '%s'" % self. _v_pathname) return ObjTimestamps(oinfo.atime, oinfo.mtime, oinfo.ctime, oinfo.btime) cdef class Group(Node): cdef hid_t group_id def _g_create(self): cdef hid_t ret cdef bytes encoded_name encoded_name = self.name.encode('utf-8') # @TODO: set property list --> utf-8 # Create a new group ret = H5Gcreate(self.parent_id, encoded_name, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT) if ret < 0: raise HDF5ExtError("Can't create the group %s." % self.name) self.group_id = ret return self.group_id def _g_open(self): cdef hid_t ret cdef bytes encoded_name encoded_name = self.name.encode('utf-8') ret = H5Gopen(self.parent_id, encoded_name, H5P_DEFAULT) if ret < 0: raise HDF5ExtError("Can't open the group: '%s'." % self.name) self.group_id = ret return self.group_id def _g_get_objinfo(self, object h5name): """Check whether 'name' is a children of 'self' and return its type.""" cdef int ret cdef object node_type cdef bytes encoded_name cdef char *cname encoded_name = h5name.encode('utf-8') # Get the C pointer cname = encoded_name ret = get_linkinfo(self.group_id, cname) if ret == -2 or ret == H5L_TYPE_ERROR: node_type = "NoSuchNode" elif ret == H5L_TYPE_SOFT: node_type = "SoftLink" elif ret == H5L_TYPE_EXTERNAL: node_type = "ExternalLink" elif ret == H5L_TYPE_HARD: ret = get_objinfo(self.group_id, cname) if ret == -2: node_type = "NoSuchNode" elif ret == H5O_TYPE_UNKNOWN: node_type = "Unknown" elif ret == H5O_TYPE_GROUP: node_type = "Group" elif ret == H5O_TYPE_DATASET: node_type = "Leaf" elif ret == H5O_TYPE_NAMED_DATATYPE: node_type = "NamedType" # Not supported yet #else H5O_TYPE_LINK: # # symbolic link # raise RuntimeError('unexpected object type') else: node_type = "Unknown" return node_type def _g_list_group(self, parent): """Return a tuple with the groups and the leaves hanging from self.""" cdef bytes encoded_name encoded_name = self.name.encode('utf-8') return Giterate(parent._v_objectid, self._v_objectid, encoded_name) def _g_get_gchild_attr(self, group_name, attr_name): """Return an attribute of a child `Group`. If the attribute does not exist, ``None`` is returned. """ cdef hid_t gchild_id cdef object retvalue cdef bytes encoded_group_name cdef bytes encoded_attr_name encoded_group_name = group_name.encode('utf-8') encoded_attr_name = attr_name.encode('utf-8') # Open the group retvalue = None # Default value gchild_id = H5Gopen(self.group_id, encoded_group_name, H5P_DEFAULT) if gchild_id < 0: raise HDF5ExtError("Non-existing node ``%s`` under ``%s``" % (group_name, self._v_pathname)) retvalue = get_attribute_string_or_none(gchild_id, encoded_attr_name) # Close child group H5Gclose(gchild_id) return retvalue def _g_get_lchild_attr(self, leaf_name, attr_name): """Return an attribute of a child `Leaf`. If the attribute does not exist, ``None`` is returned. """ cdef hid_t leaf_id cdef object retvalue cdef bytes encoded_leaf_name cdef bytes encoded_attr_name encoded_leaf_name = leaf_name.encode('utf-8') encoded_attr_name = attr_name.encode('utf-8') # Open the dataset leaf_id = H5Dopen(self.group_id, encoded_leaf_name, H5P_DEFAULT) if leaf_id < 0: raise HDF5ExtError("Non-existing node ``%s`` under ``%s``" % (leaf_name, self._v_pathname)) retvalue = get_attribute_string_or_none(leaf_id, encoded_attr_name) # Close the dataset H5Dclose(leaf_id) return retvalue def _g_flush_group(self): # Close the group H5Fflush(self.group_id, H5F_SCOPE_GLOBAL) def _g_close_group(self): cdef int ret ret = H5Gclose(self.group_id) if ret < 0: raise HDF5ExtError("Problems closing the Group %s" % self.name) self.group_id = 0 # indicate that this group is closed def _g_move_node(self, hid_t oldparent, oldname, hid_t newparent, newname, oldpathname, newpathname): cdef int ret cdef bytes encoded_oldname, encoded_newname encoded_oldname = oldname.encode('utf-8') encoded_newname = newname.encode('utf-8') ret = H5Lmove(oldparent, encoded_oldname, newparent, encoded_newname, H5P_DEFAULT, H5P_DEFAULT) if ret < 0: raise HDF5ExtError("Problems moving the node %s to %s" % (oldpathname, newpathname) ) return ret cdef class Leaf(Node): # Instance variables declared in .pxd def _get_storage_size(self): return H5Dget_storage_size(self.dataset_id) def _get_obj_track_times(self): """Get track_times boolean for dataset Uses H5Pget_obj_track_times to determine if the dataset was created with the track_times property. If the leaf is not a dataset, this will fail with HDF5ExtError. The track times dataset creation property does not seem to survive closing and reopening as of HDF5 1.8.17. Currently, it may be more accurate to test whether the ctime for the dataset is 0: track_times = (leaf._get_obj_timestamps().ctime == 0) """ cdef: hbool_t track_times = True if self.dataset_id < 0: raise ValueError('Invalid dataset id %s' % self.dataset_id) plist_id = H5Dget_create_plist(self.dataset_id) if plist_id < 0: raise HDF5ExtError("Could not get dataset creation property list " "from dataset id %s" % self.dataset_id) try: # Get track_times boolean for dataset if H5Pget_obj_track_times(plist_id, &track_times) < 0: raise HDF5ExtError("Could not get dataset track_times property " "from dataset id %s" % self.dataset_id) finally: H5Pclose(plist_id) return bool(track_times) def _g_new(self, where, name, init): if init: # Put this info to 0 just when the class is initialized self.dataset_id = -1 self.type_id = -1 self.base_type_id = -1 self.disk_type_id = -1 super()._g_new(where, name, init) cdef _get_type_ids(self): """Get the disk and native HDF5 types associated with this leaf. It is guaranteed that both disk and native types are not the same descriptor (so that it is safe to close them separately). """ cdef hid_t disk_type_id, native_type_id disk_type_id = H5Dget_type(self.dataset_id) native_type_id = get_native_type(disk_type_id) return disk_type_id, native_type_id cdef _convert_time64(self, ndarray nparr, int sense): """Converts a NumPy of Time64 elements between NumPy and HDF5 formats. NumPy to HDF5 conversion is performed when 'sense' is 0. Otherwise, HDF5 to NumPy conversion is performed. The conversion is done in place, i.e. 'nparr' is modified. """ cdef void *t64buf cdef long byteoffset, bytestride, nelements cdef hsize_t nrecords byteoffset = 0 # NumPy objects doesn't have an offset if (nparr).shape == (): # 0-dim array does contain *one* element nrecords = 1 bytestride = 8 else: nrecords = len(nparr) bytestride = PyArray_STRIDE(nparr, 0) # supports multi-dimensional recarray nelements = nparr.size // nrecords t64buf = PyArray_DATA(nparr) conv_float64_timeval32( t64buf, byteoffset, bytestride, nrecords, nelements, sense) # can't do since cdef'd def _g_truncate(self, hsize_t size): """Truncate a Leaf to `size` nrows.""" cdef hsize_t ret ret = truncate_dset(self.dataset_id, self.maindim, size) if ret < 0: raise HDF5ExtError("Problems truncating the leaf: %s" % self) classname = self.__class__.__name__ if classname in ('EArray', 'CArray'): # Update the new dimensionality self.dims[self.maindim] = size # Update the shape shape = list(self.shape) shape[self.maindim] = SizeType(size) self.shape = tuple(shape) elif classname in ('Table', 'VLArray'): self.nrows = size else: raise ValueError("Unexpected classname: %s" % classname) def _g_flush(self): # Flush the dataset (in fact, the entire buffers in file!) if self.dataset_id >= 0: H5Fflush(self.dataset_id, H5F_SCOPE_GLOBAL) def _g_close(self): # Close dataset in HDF5 space # Release resources if self.type_id >= 0: H5Tclose(self.type_id) if self.disk_type_id >= 0: H5Tclose(self.disk_type_id) if self.base_type_id >= 0: H5Tclose(self.base_type_id) if self.dataset_id >= 0: H5Dclose(self.dataset_id) cdef void* _array_data(ndarray arr): # When the object is not a 0-d ndarray and its strides == 0, that # means that the array does not contain actual data cdef npy_intp i, ndim ndim = PyArray_NDIM(arr) if ndim == 0: return PyArray_DATA(arr) for i in range(ndim): if PyArray_STRIDE(arr, i) > 0: return PyArray_DATA(arr) return NULL cdef class Array(Leaf): # Instance variables declared in .pxd def _create_array(self, ndarray nparr, object title, object atom): cdef int i cdef herr_t ret cdef void *rbuf cdef bytes complib, version, class_ cdef object dtype_, atom_, shape cdef ndarray dims cdef bytes encoded_title, encoded_name cdef H5T_cset_t cset = H5T_CSET_ASCII encoded_title = title.encode('utf-8') encoded_name = self.name.encode('utf-8') # Get the HDF5 type associated with this numpy type shape = (nparr).shape if atom is None or atom.shape == (): dtype_ = nparr.dtype.base atom_ = Atom.from_dtype(dtype_) else: atom_ = atom shape = shape[:-len(atom_.shape)] self.disk_type_id = atom_to_hdf5_type(atom_, self.byteorder) if self.disk_type_id < 0: raise HDF5ExtError( "Problems creating the %s: invalid disk type ID for atom %s" % ( self.__class__.__name__, atom_)) # Allocate space for the dimension axis info and fill it dims = numpy.array(shape, dtype=numpy.intp) self.rank = len(shape) self.dims = npy_malloc_dims(self.rank, PyArray_DATA(dims)) rbuf = _array_data(nparr) # Save the array complib = (self.filters.complib or '').encode('utf-8') version = self._v_version.encode('utf-8') class_ = self._c_classid.encode('utf-8') self.dataset_id = H5ARRAYmake(self.parent_id, encoded_name, version, self.rank, self.dims, self.extdim, self.disk_type_id, NULL, NULL, self.filters.complevel, complib, self.filters.shuffle_bitshuffle, self.filters.fletcher32, self._want_track_times, rbuf) if self.dataset_id < 0: raise HDF5ExtError("Problems creating the %s." % self.__class__.__name__) if self._v_file.params['PYTABLES_SYS_ATTRS']: cset = H5T_CSET_UTF8 # Set the conforming array attributes H5ATTRset_attribute_string(self.dataset_id, "CLASS", class_, len(class_), cset) H5ATTRset_attribute_string(self.dataset_id, "VERSION", version, len(version), cset) H5ATTRset_attribute_string(self.dataset_id, "TITLE", encoded_title, len(encoded_title), cset) # Get the native type (so that it is HDF5 who is the responsible to deal # with non-native byteorders on-disk) self.type_id = get_native_type(self.disk_type_id) return self.dataset_id, shape, atom_ def _create_carray(self, object title): cdef int i cdef herr_t ret cdef void *rbuf cdef bytes complib, version, class_ cdef ndarray dflts cdef void *fill_data cdef ndarray extdim cdef object atom cdef bytes encoded_title, encoded_name encoded_title = title.encode('utf-8') encoded_name = self.name.encode('utf-8') atom = self.atom self.disk_type_id = atom_to_hdf5_type(atom, self.byteorder) self.rank = len(self.shape) self.dims = malloc_dims(self.shape) if self.chunkshape: self.dims_chunk = malloc_dims(self.chunkshape) rbuf = NULL # The data pointer. We don't have data to save initially # Encode strings complib = (self.filters.complib or '').encode('utf-8') version = self._v_version.encode('utf-8') class_ = self._c_classid.encode('utf-8') # Get the fill values if isinstance(atom.dflt, numpy.ndarray) or atom.dflt: dflts = numpy.array(atom.dflt, dtype=atom.dtype) fill_data = PyArray_DATA(dflts) else: dflts = numpy.zeros((), dtype=atom.dtype) fill_data = NULL if atom.shape == (): # The default is preferred as a scalar value instead of 0-dim array atom.dflt = dflts[()] else: atom.dflt = dflts # Create the CArray/EArray self.dataset_id = H5ARRAYmake(self.parent_id, encoded_name, version, self.rank, self.dims, self.extdim, self.disk_type_id, self.dims_chunk, fill_data, self.filters.complevel, complib, self.filters.shuffle_bitshuffle, self.filters.fletcher32, self._want_track_times, rbuf) if self.dataset_id < 0: raise HDF5ExtError("Problems creating the %s." % self.__class__.__name__) if self._v_file.params['PYTABLES_SYS_ATTRS']: # Set the conforming array attributes H5ATTRset_attribute_string(self.dataset_id, "CLASS", class_, len(class_), H5T_CSET_ASCII) H5ATTRset_attribute_string(self.dataset_id, "VERSION", version, len(version), H5T_CSET_ASCII) H5ATTRset_attribute_string(self.dataset_id, "TITLE", encoded_title, len(encoded_title), H5T_CSET_ASCII) if self.extdim >= 0: extdim = numpy.array([self.extdim], dtype="int32") # Attach the EXTDIM attribute in case of enlargeable arrays H5ATTRset_attribute(self.dataset_id, "EXTDIM", H5T_NATIVE_INT, 0, NULL, PyArray_BYTES(extdim)) # Get the native type (so that it is HDF5 who is the responsible to deal # with non-native byteorders on-disk) self.type_id = get_native_type(self.disk_type_id) return self.dataset_id def _open_array(self): cdef size_t type_size, type_precision cdef H5T_class_t class_id cdef char cbyteorder[11] # "irrelevant" fits easily here cdef int i cdef int extdim cdef herr_t ret cdef object shape, chunkshapes, atom cdef int fill_status cdef ndarray dflts cdef void *fill_data cdef bytes encoded_name cdef str byteorder encoded_name = self.name.encode('utf-8') # Open the dataset self.dataset_id = H5Dopen(self.parent_id, encoded_name, H5P_DEFAULT) if self.dataset_id < 0: raise HDF5ExtError("Non-existing node ``%s`` under ``%s``" % (self.name, self._v_parent._v_pathname)) # Get the datatype handles self.disk_type_id, self.type_id = self._get_type_ids() # Get the atom for this type atom = atom_from_hdf5_type(self.type_id) # Get the rank for this array object if H5ARRAYget_ndims(self.dataset_id, &self.rank) < 0: raise HDF5ExtError("Problems getting ndims!") # Allocate space for the dimension axis info self.dims = malloc(self.rank * sizeof(hsize_t)) self.maxdims = malloc(self.rank * sizeof(hsize_t)) # Get info on dimensions, class and type (of base class) ret = H5ARRAYget_info(self.dataset_id, self.disk_type_id, self.dims, self.maxdims, &class_id, cbyteorder) if ret < 0: raise HDF5ExtError("Unable to get array info.") byteorder = cstr_to_pystr(cbyteorder) # Get the extendable dimension (if any) self.extdim = -1 # default is non-extensible Array for i from 0 <= i < self.rank: if self.maxdims[i] == -1: self.extdim = i break # Get the shape as a python tuple shape = getshape(self.rank, self.dims) # Allocate space for the dimension chunking info self.dims_chunk = malloc(self.rank * sizeof(hsize_t)) if H5ARRAYget_chunkshape(self.dataset_id, self.rank, self.dims_chunk) < 0: # The Array class is not chunked! chunkshapes = None else: # Get the chunkshape as a python tuple chunkshapes = getshape(self.rank, self.dims_chunk) # object arrays should not be read directly into memory if atom.dtype != object: # Get the fill value dflts = numpy.zeros((), dtype=atom.dtype) fill_data = PyArray_DATA(dflts) H5ARRAYget_fill_value(self.dataset_id, self.type_id, &fill_status, fill_data); if fill_status == H5D_FILL_VALUE_UNDEFINED: # This can only happen with datasets created with other libraries # than PyTables. dflts = None if dflts is not None and atom.shape == (): # The default is preferred as a scalar value instead of 0-dim array atom.dflt = dflts[()] else: atom.dflt = dflts # Get the byteorder self.byteorder = correct_byteorder(atom.type, byteorder) return self.dataset_id, atom, shape, chunkshapes def _append(self, ndarray nparr): cdef int ret, extdim cdef hsize_t *dims_arr cdef void *rbuf cdef object shape if self.atom.kind == "reference": raise ValueError("Cannot append to the reference types") # Allocate space for the dimension axis info dims_arr = npy_malloc_dims(self.rank, PyArray_DIMS(nparr)) # Get the pointer to the buffer data area rbuf = PyArray_DATA(nparr) # Convert some NumPy types to HDF5 before storing. if self.atom.type == 'time64': self._convert_time64(nparr, 0) # Append the records extdim = self.extdim with nogil: ret = H5ARRAYappend_records(self.dataset_id, self.type_id, self.rank, self.dims, dims_arr, extdim, rbuf) if ret < 0: raise HDF5ExtError("Problems appending the elements") free(dims_arr) # Update the new dimensionality shape = list(self.shape) shape[self.extdim] = SizeType(self.dims[self.extdim]) self.shape = tuple(shape) def _read_array(self, hsize_t start, hsize_t stop, hsize_t step, ndarray nparr): cdef herr_t ret cdef void *rbuf cdef hsize_t nrows cdef int extdim cdef size_t item_size = H5Tget_size(self.type_id) cdef void * refbuf = NULL # Number of rows to read nrows = get_len_of_range(start, stop, step) # Get the pointer to the buffer data area if self.atom.kind == "reference": refbuf = malloc(nrows * item_size) rbuf = refbuf else: rbuf = PyArray_DATA(nparr) if hasattr(self, "extdim"): extdim = self.extdim else: extdim = -1 # Do the physical read with nogil: ret = H5ARRAYread(self.dataset_id, self.type_id, start, nrows, step, extdim, rbuf) try: if ret < 0: raise HDF5ExtError("Problems reading the array data.") # Get the pointer to the buffer data area if self.atom.kind == "reference": load_reference(self.dataset_id, rbuf, item_size, nparr) finally: if refbuf: free(refbuf) refbuf = NULL if self.atom.kind == 'time': # Swap the byteorder by hand (this is not currently supported by HDF5) if H5Tget_order(self.type_id) != platform_byteorder: nparr.byteswap(True) # Convert some HDF5 types to NumPy after reading. if self.atom.type == 'time64': self._convert_time64(nparr, 1) return def _g_read_slice(self, ndarray startl, ndarray stopl, ndarray stepl, ndarray nparr): cdef herr_t ret cdef hsize_t *start cdef hsize_t *stop cdef hsize_t *step cdef void *rbuf cdef size_t item_size = H5Tget_size(self.type_id) cdef void * refbuf = NULL # Get the pointer to the buffer data area of startl, stopl and stepl arrays start = PyArray_DATA(startl) stop = PyArray_DATA(stopl) step = PyArray_DATA(stepl) # Get the pointer to the buffer data area if self.atom.kind == "reference": refbuf = malloc(nparr.size * item_size) rbuf = refbuf else: rbuf = PyArray_DATA(nparr) # Do the physical read with nogil: ret = H5ARRAYreadSlice(self.dataset_id, self.type_id, start, stop, step, rbuf) try: if ret < 0: raise HDF5ExtError("Problems reading the array data.") # Get the pointer to the buffer data area if self.atom.kind == "reference": load_reference(self.dataset_id, rbuf, item_size, nparr) finally: if refbuf: free(refbuf) refbuf = NULL if self.atom.kind == 'time': # Swap the byteorder by hand (this is not currently supported by HDF5) if H5Tget_order(self.type_id) != platform_byteorder: nparr.byteswap(True) # Convert some HDF5 types to NumPy after reading if self.atom.type == 'time64': self._convert_time64(nparr, 1) return def _g_read_coords(self, ndarray coords, ndarray nparr): """Read coordinates in an already created NumPy array.""" cdef herr_t ret cdef hid_t space_id cdef hid_t mem_space_id cdef hsize_t size cdef void *rbuf cdef object mode cdef size_t item_size = H5Tget_size(self.type_id) cdef void * refbuf = NULL # Get the dataspace handle space_id = H5Dget_space(self.dataset_id) # Create a memory dataspace handle size = nparr.size mem_space_id = H5Screate_simple(1, &size, NULL) # Select the dataspace to be read H5Sselect_elements(space_id, H5S_SELECT_SET, size, PyArray_DATA(coords)) # Get the pointer to the buffer data area if self.atom.kind == "reference": refbuf = malloc(nparr.size * item_size) rbuf = refbuf else: rbuf = PyArray_DATA(nparr) # Do the actual read with nogil: ret = H5Dread(self.dataset_id, self.type_id, mem_space_id, space_id, H5P_DEFAULT, rbuf) try: if ret < 0: raise HDF5ExtError("Problems reading the array data.") # Get the pointer to the buffer data area if self.atom.kind == "reference": load_reference(self.dataset_id, rbuf, item_size, nparr) finally: if refbuf: free(refbuf) refbuf = NULL # Terminate access to the memory dataspace H5Sclose(mem_space_id) # Terminate access to the dataspace H5Sclose(space_id) if self.atom.kind == 'time': # Swap the byteorder by hand (this is not currently supported by HDF5) if H5Tget_order(self.type_id) != platform_byteorder: nparr.byteswap(True) # Convert some HDF5 types to NumPy after reading if self.atom.type == 'time64': self._convert_time64(nparr, 1) return def perform_selection(self, space_id, start, count, step, idx, mode): """Performs a selection using start/count/step in the given axis. All other axes have their full range selected. The selection is added to the current `space_id` selection using the given mode. Note: This is a backport from the h5py project. """ cdef int select_mode cdef ndarray start_, count_, step_ cdef hsize_t *startp cdef hsize_t *countp cdef hsize_t *stepp # Build arrays for the selection parameters startl, countl, stepl = [], [], [] for i, x in enumerate(self.shape): if i != idx: startl.append(0) countl.append(x) stepl.append(1) else: startl.append(start) countl.append(count) stepl.append(step) start_ = numpy.array(startl, dtype="i8") count_ = numpy.array(countl, dtype="i8") step_ = numpy.array(stepl, dtype="i8") # Get the pointers to array data startp = PyArray_DATA(start_) countp = PyArray_DATA(count_) stepp = PyArray_DATA(step_) # Do the actual selection select_modes = {"AND": H5S_SELECT_AND, "NOTB": H5S_SELECT_NOTB} assert mode in select_modes select_mode = select_modes[mode] H5Sselect_hyperslab(space_id, select_mode, startp, stepp, countp, NULL) def _g_read_selection(self, object selection, ndarray nparr): """Read a selection in an already created NumPy array.""" cdef herr_t ret cdef hid_t space_id cdef hid_t mem_space_id cdef hsize_t size cdef void *rbuf cdef object mode cdef size_t item_size = H5Tget_size(self.type_id) cdef void * refbuf = NULL # Get the dataspace handle space_id = H5Dget_space(self.dataset_id) # Create a memory dataspace handle size = nparr.size mem_space_id = H5Screate_simple(1, &size, NULL) # Select the dataspace to be read # Start by selecting everything H5Sselect_all(space_id) # Now refine with outstanding selections for args in selection: self.perform_selection(space_id, *args) # Get the pointer to the buffer data area if self.atom.kind == "reference": refbuf = malloc(nparr.size * item_size) rbuf = refbuf else: rbuf = PyArray_DATA(nparr) # Do the actual read with nogil: ret = H5Dread(self.dataset_id, self.type_id, mem_space_id, space_id, H5P_DEFAULT, rbuf) try: if ret < 0: raise HDF5ExtError("Problems reading the array data.") # Get the pointer to the buffer data area if self.atom.kind == "reference": load_reference(self.dataset_id, rbuf, item_size, nparr) finally: if refbuf: free(refbuf) refbuf = NULL # Terminate access to the memory dataspace H5Sclose(mem_space_id) # Terminate access to the dataspace H5Sclose(space_id) if self.atom.kind == 'time': # Swap the byteorder by hand (this is not currently supported by HDF5) if H5Tget_order(self.type_id) != platform_byteorder: nparr.byteswap(True) # Convert some HDF5 types to NumPy after reading if self.atom.type == 'time64': self._convert_time64(nparr, 1) return def _g_write_slice(self, ndarray startl, ndarray stepl, ndarray countl, ndarray nparr): """Write a slice in an already created NumPy array.""" cdef int ret cdef void *rbuf cdef void *temp cdef hsize_t *start cdef hsize_t *step cdef hsize_t *count if self.atom.kind == "reference": raise ValueError("Cannot write reference types yet") # Get the pointer to the buffer data area rbuf = PyArray_DATA(nparr) # Get the start, step and count values start = PyArray_DATA(startl) step = PyArray_DATA(stepl) count = PyArray_DATA(countl) # Convert some NumPy types to HDF5 before storing. if self.atom.type == 'time64': self._convert_time64(nparr, 0) # Modify the elements: with nogil: ret = H5ARRAYwrite_records(self.dataset_id, self.type_id, self.rank, start, step, count, rbuf) if ret < 0: raise HDF5ExtError("Internal error modifying the elements " "(H5ARRAYwrite_records returned errorcode -%i)" % (-ret)) return def _g_write_coords(self, ndarray coords, ndarray nparr): """Write a selection in an already created NumPy array.""" cdef herr_t ret cdef hid_t space_id cdef hid_t mem_space_id cdef hsize_t size cdef void *rbuf cdef object mode if self.atom.kind == "reference": raise ValueError("Cannot write reference types yet") # Get the dataspace handle space_id = H5Dget_space(self.dataset_id) # Create a memory dataspace handle size = nparr.size mem_space_id = H5Screate_simple(1, &size, NULL) # Select the dataspace to be written H5Sselect_elements(space_id, H5S_SELECT_SET, size, PyArray_DATA(coords)) # Get the pointer to the buffer data area rbuf = PyArray_DATA(nparr) # Convert some NumPy types to HDF5 before storing. if self.atom.type == 'time64': self._convert_time64(nparr, 0) # Do the actual write with nogil: ret = H5Dwrite(self.dataset_id, self.type_id, mem_space_id, space_id, H5P_DEFAULT, rbuf) if ret < 0: raise HDF5ExtError("Problems writing the array data.") # Terminate access to the memory dataspace H5Sclose(mem_space_id) # Terminate access to the dataspace H5Sclose(space_id) return def _g_write_selection(self, object selection, ndarray nparr): """Write a selection in an already created NumPy array.""" cdef herr_t ret cdef hid_t space_id cdef hid_t mem_space_id cdef hsize_t size cdef void *rbuf cdef object mode if self.atom.kind == "reference": raise ValueError("Cannot write reference types yet") # Get the dataspace handle space_id = H5Dget_space(self.dataset_id) # Create a memory dataspace handle size = nparr.size mem_space_id = H5Screate_simple(1, &size, NULL) # Select the dataspace to be written # Start by selecting everything H5Sselect_all(space_id) # Now refine with outstanding selections for args in selection: self.perform_selection(space_id, *args) # Get the pointer to the buffer data area rbuf = PyArray_DATA(nparr) # Convert some NumPy types to HDF5 before storing. if self.atom.type == 'time64': self._convert_time64(nparr, 0) # Do the actual write with nogil: ret = H5Dwrite(self.dataset_id, self.type_id, mem_space_id, space_id, H5P_DEFAULT, rbuf) if ret < 0: raise HDF5ExtError("Problems writing the array data.") # Terminate access to the memory dataspace H5Sclose(mem_space_id) # Terminate access to the dataspace H5Sclose(space_id) return def __dealloc__(self): if self.dims: free(self.dims) if self.maxdims: free(self.maxdims) if self.dims_chunk: free(self.dims_chunk) cdef class VLArray(Leaf): # Instance variables cdef hsize_t nrecords def _create_array(self, object title): cdef int rank cdef hsize_t *dims cdef herr_t ret cdef void *rbuf cdef bytes complib, version, class_ cdef object type_, itemsize, atom, scatom cdef bytes encoded_title, encoded_name cdef H5T_cset_t cset = H5T_CSET_ASCII encoded_title = title.encode('utf-8') encoded_name = self.name.encode('utf-8') atom = self.atom if not hasattr(atom, 'size'): # it is a pseudo-atom atom = atom.base # Get the HDF5 type of the *scalar* atom scatom = atom.copy(shape=()) self.base_type_id = atom_to_hdf5_type(scatom, self.byteorder) if self.base_type_id < 0: raise HDF5ExtError( "Problems creating the %s: invalid base type ID for atom %s" % ( self.__class__.__name__, scatom)) # Allocate space for the dimension axis info rank = len(atom.shape) dims = malloc_dims(atom.shape) rbuf = NULL # We don't have data to save initially # Encode strings complib = (self.filters.complib or '').encode('utf-8') version = self._v_version.encode('utf-8') class_ = self._c_classid.encode('utf-8') # Create the vlarray self.dataset_id = H5VLARRAYmake(self.parent_id, encoded_name, version, rank, dims, self.base_type_id, self.chunkshape[0], rbuf, self.filters.complevel, complib, self.filters.shuffle_bitshuffle, self.filters.fletcher32, self._want_track_times, rbuf) if dims: free(dims) if self.dataset_id < 0: raise HDF5ExtError("Problems creating the VLArray.") self.nrecords = 0 # Initialize the number of records saved if self._v_file.params['PYTABLES_SYS_ATTRS']: cset = H5T_CSET_UTF8 # Set the conforming array attributes H5ATTRset_attribute_string(self.dataset_id, "CLASS", class_, len(class_), cset) H5ATTRset_attribute_string(self.dataset_id, "VERSION", version, len(version), cset) H5ATTRset_attribute_string(self.dataset_id, "TITLE", encoded_title, len(encoded_title), cset) # Get the datatype handles self.disk_type_id, self.type_id = self._get_type_ids() return self.dataset_id def _open_array(self): cdef char cbyteorder[11] # "irrelevant" fits easily here cdef int i, enumtype cdef int rank cdef herr_t ret cdef hsize_t nrecords, chunksize cdef object shape, type_ cdef bytes encoded_name cdef str byteorder encoded_name = self.name.encode('utf-8') # Open the dataset self.dataset_id = H5Dopen(self.parent_id, encoded_name, H5P_DEFAULT) if self.dataset_id < 0: raise HDF5ExtError("Non-existing node ``%s`` under ``%s``" % (self.name, self._v_parent._v_pathname)) # Get the datatype handles self.disk_type_id, self.type_id = self._get_type_ids() # Get the atom for this type atom = atom_from_hdf5_type(self.type_id) # Get info on dimensions & types (of base class) H5VLARRAYget_info(self.dataset_id, self.disk_type_id, &nrecords, cbyteorder) byteorder = cstr_to_pystr(cbyteorder) # Get some properties of the atomic type self._atomicdtype = atom.dtype self._atomictype = atom.type self._atomicshape = atom.shape self._atomicsize = atom.size # Get the byteorder self.byteorder = correct_byteorder(atom.type, byteorder) # Get the chunkshape (VLArrays are unidimensional entities) H5ARRAYget_chunkshape(self.dataset_id, 1, &chunksize) self.nrecords = nrecords # Initialize the number of records saved return self.dataset_id, SizeType(nrecords), (SizeType(chunksize),), atom def _append(self, ndarray nparr, int nobjects): cdef int ret cdef void *rbuf # Get the pointer to the buffer data area if nobjects: rbuf = PyArray_DATA(nparr) # Convert some NumPy types to HDF5 before storing. if self.atom.type == 'time64': self._convert_time64(nparr, 0) else: rbuf = NULL # Append the records: with nogil: ret = H5VLARRAYappend_records(self.dataset_id, self.type_id, nobjects, self.nrecords, rbuf) if ret < 0: raise HDF5ExtError("Problems appending the records.") self.nrecords = self.nrecords + 1 def _modify(self, hsize_t nrow, ndarray nparr, int nobjects): cdef int ret cdef void *rbuf # Get the pointer to the buffer data area rbuf = PyArray_DATA(nparr) if nobjects: # Convert some NumPy types to HDF5 before storing. if self.atom.type == 'time64': self._convert_time64(nparr, 0) # Append the records: with nogil: ret = H5VLARRAYmodify_records(self.dataset_id, self.type_id, nrow, nobjects, rbuf) if ret < 0: raise HDF5ExtError("Problems modifying the record.") return nobjects # Because the size of each "row" is unknown, there is no easy way to # calculate this value def _get_memory_size(self): cdef hid_t space_id cdef hsize_t size cdef herr_t ret if self.nrows == 0: size = 0 else: # Get the dataspace handle space_id = H5Dget_space(self.dataset_id) # Return the size of the entire dataset ret = H5Dvlen_get_buf_size(self.dataset_id, self.type_id, space_id, &size) if ret < 0: size = -1 # Terminate access to the dataspace H5Sclose(space_id) return size def _read_array(self, hsize_t start, hsize_t stop, hsize_t step): cdef int i cdef size_t vllen cdef herr_t ret cdef hvl_t *rdata cdef hsize_t nrows cdef hid_t space_id cdef hid_t mem_space_id cdef object buf, nparr, shape, datalist # Compute the number of rows to read nrows = get_len_of_range(start, stop, step) if start + nrows > self.nrows: raise HDF5ExtError( "Asking for a range of rows exceeding the available ones!.", h5bt=False) # Now, read the chunk of rows with nogil: # Allocate the necessary memory for keeping the row handlers rdata = malloc(nrows*sizeof(hvl_t)) # Get the dataspace handle space_id = H5Dget_space(self.dataset_id) # Create a memory dataspace handle mem_space_id = H5Screate_simple(1, &nrows, NULL) # Select the data to be read H5Sselect_hyperslab(space_id, H5S_SELECT_SET, &start, &step, &nrows, NULL) # Do the actual read ret = H5Dread(self.dataset_id, self.type_id, mem_space_id, space_id, H5P_DEFAULT, rdata) if ret < 0: raise HDF5ExtError( "VLArray._read_array: Problems reading the array data.") datalist = [] for i from 0 <= i < nrows: # Number of atoms in row vllen = rdata[i].len # Get the pointer to the buffer data area if vllen > 0: # Create a buffer to keep this info. It is important to do a # copy, because we will dispose the buffer memory later on by # calling the H5Dvlen_reclaim. PyByteArray_FromStringAndSize does this. buf = PyByteArray_FromStringAndSize(rdata[i].p, vllen*self._atomicsize) else: # Case where there is info with zero lentgh buf = None # Compute the shape for the read array shape = list(self._atomicshape) shape.insert(0, vllen) # put the length at the beginning of the shape nparr = numpy.ndarray( buffer=buf, dtype=self._atomicdtype.base, shape=shape) # Set the writeable flag for this ndarray object nparr.flags.writeable = True if self.atom.kind == 'time': # Swap the byteorder by hand (this is not currently supported by HDF5) if H5Tget_order(self.type_id) != platform_byteorder: nparr.byteswap(True) # Convert some HDF5 types to NumPy after reading. if self.atom.type == 'time64': self._convert_time64(nparr, 1) # Append this array to the output list datalist.append(nparr) # Release resources # Reclaim all the (nested) VL data ret = H5Dvlen_reclaim(self.type_id, mem_space_id, H5P_DEFAULT, rdata) if ret < 0: raise HDF5ExtError("VLArray._read_array: error freeing the data buffer.") # Terminate access to the memory dataspace H5Sclose(mem_space_id) # Terminate access to the dataspace H5Sclose(space_id) # Free the amount of row pointers to VL row data free(rdata) return datalist def get_row_size(self, row): """Return the total size in bytes of all the elements contained in a given row.""" cdef hid_t space_id cdef hsize_t size cdef herr_t ret cdef hsize_t offset[1] cdef hsize_t count[1] if row >= self.nrows: raise HDF5ExtError( "Asking for a range of rows exceeding the available ones!.", h5bt=False) # Get the dataspace handle space_id = H5Dget_space(self.dataset_id) offset[0] = row count[0] = 1 ret = H5Sselect_hyperslab(space_id, H5S_SELECT_SET, offset, NULL, count, NULL); if ret < 0: size = -1 ret = H5Dvlen_get_buf_size(self.dataset_id, self.type_id, space_id, &size) if ret < 0: size = -1 # Terminate access to the dataspace H5Sclose(space_id) return size cdef class UnImplemented(Leaf): def _open_unimplemented(self): cdef object shape cdef char cbyteorder[11] # "irrelevant" fits easily here cdef bytes encoded_name cdef str byteorder encoded_name = self.name.encode('utf-8') # Get info on dimensions shape = H5UIget_info(self.parent_id, encoded_name, cbyteorder) shape = tuple(map(SizeType, shape)) self.dataset_id = H5Dopen(self.parent_id, encoded_name, H5P_DEFAULT) byteorder = cstr_to_pystr(cbyteorder) return (shape, byteorder, self.dataset_id) def _g_close(self): H5Dclose(self.dataset_id) ## Local Variables: ## mode: python ## py-indent-offset: 2 ## tab-width: 2 ## fill-column: 78 ## End: PyTables-3.7.0/tables/idxutils.py000066400000000000000000000377051416254111300167470ustar00rootroot00000000000000"""Utilities to be used mainly by the Index class.""" import math import numpy as np # Hints for chunk/slice/block/superblock computations: # - The slicesize should not exceed 2**32 elements (because of # implementation reasons). Such an extreme case would make the # sorting algorithms to consume up to 64 GB of memory. # - In general, one should favor a small chunksize ( < 128 KB) if one # wants to reduce the latency for indexed queries. However, keep in # mind that a very low value of chunksize for big datasets may hurt # the performance by requering the HDF5 to use a lot of memory and CPU # for its internal B-Tree. def csformula(nrows): """Return the fitted chunksize (a float value) for nrows.""" # This formula has been computed using two points: # 2**12 = m * 2**(n + log10(10**6)) # 2**15 = m * 2**(n + log10(10**9)) # where 2**12 and 2**15 are reasonable values for chunksizes for indexes # with 10**6 and 10**9 elements respectively. # Yes, return a floating point number! return 64 * 2**math.log10(nrows) def limit_er(expectedrows): """Protection against creating too small or too large chunks or slices.""" if expectedrows < 10**5: expectedrows = 10**5 elif expectedrows > 10**12: expectedrows = 10**12 return expectedrows def computechunksize(expectedrows): """Get the optimum chunksize based on expectedrows.""" expectedrows = limit_er(expectedrows) zone = int(math.log10(expectedrows)) nrows = 10**zone return int(csformula(nrows)) def computeslicesize(expectedrows, memlevel): """Get the optimum slicesize based on expectedrows and memorylevel.""" expectedrows = limit_er(expectedrows) # First, the optimum chunksize cs = csformula(expectedrows) # Now, the actual chunksize chunksize = computechunksize(expectedrows) # The optimal slicesize ss = int(cs * memlevel**2) # We *need* slicesize to be an exact multiple of the actual chunksize ss = (ss // chunksize) * chunksize ss *= 4 # slicesize should be at least divisible by 4 # ss cannot be bigger than 2**31 - 1 elements because of fundamental # reasons (this limitation comes mainly from the way of compute # indices for indexes, but also because C keysort is not implemented # yet for the string type). Besides, it cannot be larger than # 2**30, because limitiations of the optimized binary search code # (in idx-opt.c, the line ``mid = lo + (hi-lo)/2;`` will overflow # for values of ``lo`` and ``hi`` >= 2**30). Finally, ss must be a # multiple of 4, so 2**30 must definitely be an upper limit. if ss > 2**30: ss = 2**30 return ss def computeblocksize(expectedrows, compoundsize, lowercompoundsize): """Calculate the optimum number of superblocks made from compounds blocks. This is useful for computing the sizes of both blocks and superblocks (using the PyTables terminology for blocks in indexes). """ nlowerblocks = (expectedrows // lowercompoundsize) + 1 if nlowerblocks > 2**20: # Protection against too large number of compound blocks nlowerblocks = 2**20 size = int(lowercompoundsize * nlowerblocks) # We *need* superblocksize to be an exact multiple of the actual # compoundblock size (a ceil must be performed here!) size = ((size // compoundsize) + 1) * compoundsize return size def calc_chunksize(expectedrows, optlevel=6, indsize=4, memlevel=4, node=None): """Calculate the HDF5 chunk size for index and sorted arrays. The logic to do that is based purely in experiments playing with different chunksizes and compression flag. It is obvious that using big chunks optimizes the I/O speed, but if they are too large, the uncompressor takes too much time. This might (should) be further optimized by doing more experiments. """ chunksize = computechunksize(expectedrows) slicesize = computeslicesize(expectedrows, memlevel) # Avoid excessive slicesize in Indexes, see https://github.com/PyTables/PyTables/issues/879 if node is not None: maxsize = node._v_file.params['BUFFER_TIMES'] * node._v_file.params['IO_BUFFER_SIZE'] while (slicesize * node.dtype.itemsize) > maxsize: slicesize = slicesize // 2 # Correct the slicesize and the chunksize based on optlevel if indsize == 1: # ultralight chunksize, slicesize = ccs_ultralight(optlevel, chunksize, slicesize) elif indsize == 2: # light chunksize, slicesize = ccs_light(optlevel, chunksize, slicesize) elif indsize == 4: # medium chunksize, slicesize = ccs_medium(optlevel, chunksize, slicesize) elif indsize == 8: # full chunksize, slicesize = ccs_full(optlevel, chunksize, slicesize) # Finally, compute blocksize and superblocksize blocksize = computeblocksize(expectedrows, slicesize, chunksize) superblocksize = computeblocksize(expectedrows, blocksize, slicesize) # The size for different blocks information sizes = (superblocksize, blocksize, slicesize, chunksize) return sizes def ccs_ultralight(optlevel, chunksize, slicesize): """Correct the slicesize and the chunksize based on optlevel.""" if optlevel in (0, 1, 2): slicesize //= 2 slicesize += optlevel * slicesize elif optlevel in (3, 4, 5): slicesize *= optlevel - 1 elif optlevel in (6, 7, 8): slicesize *= optlevel - 1 elif optlevel == 9: slicesize *= optlevel - 1 return chunksize, slicesize def ccs_light(optlevel, chunksize, slicesize): """Correct the slicesize and the chunksize based on optlevel.""" if optlevel in (0, 1, 2): slicesize //= 2 elif optlevel in (3, 4, 5): pass elif optlevel in (6, 7, 8): chunksize //= 2 elif optlevel == 9: # Reducing the chunksize and enlarging the slicesize is the # best way to reduce the entropy with the current algorithm. chunksize //= 2 slicesize *= 2 return chunksize, slicesize def ccs_medium(optlevel, chunksize, slicesize): """Correct the slicesize and the chunksize based on optlevel.""" if optlevel in (0, 1, 2): slicesize //= 2 elif optlevel in (3, 4, 5): pass elif optlevel in (6, 7, 8): chunksize //= 2 elif optlevel == 9: # Reducing the chunksize and enlarging the slicesize is the # best way to reduce the entropy with the current algorithm. chunksize //= 2 slicesize *= 2 return chunksize, slicesize def ccs_full(optlevel, chunksize, slicesize): """Correct the slicesize and the chunksize based on optlevel.""" if optlevel in (0, 1, 2): slicesize //= 2 elif optlevel in (3, 4, 5): pass elif optlevel in (6, 7, 8): chunksize //= 2 elif optlevel == 9: # Reducing the chunksize and enlarging the slicesize is the # best way to reduce the entropy with the current algorithm. chunksize //= 2 slicesize *= 2 return chunksize, slicesize def calcoptlevels(nblocks, optlevel, indsize): """Compute the optimizations to be done. The calculation is based on the number of blocks, optlevel and indexing mode. """ if indsize == 2: # light return col_light(nblocks, optlevel) elif indsize == 4: # medium return col_medium(nblocks, optlevel) elif indsize == 8: # full return col_full(nblocks, optlevel) def col_light(nblocks, optlevel): """Compute the optimizations to be done for light indexes.""" optmedian, optstarts, optstops, optfull = (False,) * 4 if 0 < optlevel <= 3: optmedian = True elif 3 < optlevel <= 6: optmedian, optstarts = (True, True) elif 6 < optlevel <= 9: optmedian, optstarts, optstops = (True, True, True) return optmedian, optstarts, optstops, optfull def col_medium(nblocks, optlevel): """Compute the optimizations to be done for medium indexes.""" optmedian, optstarts, optstops, optfull = (False,) * 4 # Medium case if nblocks <= 1: if 0 < optlevel <= 3: optmedian = True elif 3 < optlevel <= 6: optmedian, optstarts = (True, True) elif 6 < optlevel <= 9: optfull = 1 else: # More than a block if 0 < optlevel <= 3: optfull = 1 elif 3 < optlevel <= 6: optfull = 2 elif 6 < optlevel <= 9: optfull = 3 return optmedian, optstarts, optstops, optfull def col_full(nblocks, optlevel): """Compute the optimizations to be done for full indexes.""" optmedian, optstarts, optstops, optfull = (False,) * 4 # Full case if nblocks <= 1: if 0 < optlevel <= 3: optmedian = True elif 3 < optlevel <= 6: optmedian, optstarts = (True, True) elif 6 < optlevel <= 9: optfull = 1 else: # More than a block if 0 < optlevel <= 3: optfull = 1 elif 3 < optlevel <= 6: optfull = 2 elif 6 < optlevel <= 9: optfull = 3 return optmedian, optstarts, optstops, optfull def get_reduction_level(indsize, optlevel, slicesize, chunksize): """Compute the reduction level based on indsize and optlevel.""" rlevels = [ [8, 8, 8, 8, 4, 4, 4, 2, 2, 1], # 8-bit indices (ultralight) [4, 4, 4, 4, 2, 2, 2, 1, 1, 1], # 16-bit indices (light) [2, 2, 2, 2, 1, 1, 1, 1, 1, 1], # 32-bit indices (medium) [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], # 64-bit indices (full) ] isizes = {1: 0, 2: 1, 4: 2, 8: 3} rlevel = rlevels[isizes[indsize]][optlevel] # The next cases should only happen in tests if rlevel >= slicesize: rlevel = 1 if slicesize <= chunksize * rlevel: rlevel = 1 if indsize == 8: # Ensure that, for full indexes we will never perform a reduction. # This is required because of implementation assumptions. assert rlevel == 1 return rlevel # Python implementations of NextAfter and NextAfterF # # These implementations exist because the standard function # nextafterf is not available on Microsoft platforms. # # These implementations are based on the IEEE representation of # floats and doubles. # Author: Shack Toms - shack@livedata.com # # Thanks to Shack Toms shack@livedata.com for NextAfter and NextAfterF # implementations in Python. 2004-10-01 # epsilon = math.ldexp(1.0, -53) # smallest double such that # # 0.5 + epsilon != 0.5 # epsilonF = math.ldexp(1.0, -24) # smallest float such that 0.5 + epsilonF # != 0.5 # maxFloat = float(2**1024 - 2**971) # From the IEEE 754 standard # maxFloatF = float(2**128 - 2**104) # From the IEEE 754 standard # minFloat = math.ldexp(1.0, -1022) # min positive normalized double # minFloatF = math.ldexp(1.0, -126) # min positive normalized float # smallEpsilon = math.ldexp(1.0, -1074) # smallest increment for # # doubles < minFloat # smallEpsilonF = math.ldexp(1.0, -149) # smallest increment for # # floats < minFloatF infinity = math.ldexp(1.0, 1023) * 2 infinityf = math.ldexp(1.0, 128) # Finf = float("inf") # Infinite in the IEEE 754 standard (not avail in Win) # A portable representation of NaN # if sys.byteorder == "little": # testNaN = struct.unpack("d", '\x01\x00\x00\x00\x00\x00\xf0\x7f')[0] # elif sys.byteorder == "big": # testNaN = struct.unpack("d", '\x7f\xf0\x00\x00\x00\x00\x00\x01')[0] # else: # raise ValueError("Byteorder '%s' not supported!" % sys.byteorder) # This one seems better # testNaN = infinity - infinity # "infinity" for several types infinitymap = { 'bool': [0, 1], 'int8': [-2**7, 2**7 - 1], 'uint8': [0, 2**8 - 1], 'int16': [-2**15, 2**15 - 1], 'uint16': [0, 2**16 - 1], 'int32': [-2**31, 2**31 - 1], 'uint32': [0, 2**32 - 1], 'int64': [-2**63, 2**63 - 1], 'uint64': [0, 2**64 - 1], 'float32': [-infinityf, infinityf], 'float64': [-infinity, infinity], } if hasattr(np, 'float16'): infinitymap['float16'] = [-np.float16(np.inf), np.float16(np.inf)] if hasattr(np, 'float96'): infinitymap['float96'] = [-np.float96(np.inf), np.float96(np.inf)] if hasattr(np, 'float128'): infinitymap['float128'] = [-np.float128(np.inf), np.float128(np.inf)] # deprecated API infinityMap = infinitymap infinityF = infinityf # Utility functions def inftype(dtype, itemsize, sign=+1): """Return a superior limit for maximum representable data type.""" assert sign in [-1, +1] if dtype.kind == "S": if sign < 0: return b"\x00" * itemsize else: return b"\xff" * itemsize try: return infinitymap[dtype.name][sign >= 0] except KeyError: raise TypeError("Type %s is not supported" % dtype.name) def string_next_after(x, direction, itemsize): """Return the next representable neighbor of x in the appropriate direction.""" assert direction in [-1, +1] # Pad the string with \x00 chars until itemsize completion padsize = itemsize - len(x) if padsize > 0: x += b"\x00" * padsize # int.to_bytes is not available in Python < 3.2 # xlist = [i.to_bytes(1, sys.byteorder) for i in x] xlist = [bytes([i]) for i in x] xlist.reverse() i = 0 if direction > 0: if xlist == b"\xff" * itemsize: # Maximum value, return this return b"".join(xlist) for xchar in xlist: if ord(xchar) < 0xff: xlist[i] = chr(ord(xchar) + 1).encode('ascii') break else: xlist[i] = b"\x00" i += 1 else: if xlist == b"\x00" * itemsize: # Minimum value, return this return b"".join(xlist) for xchar in xlist: if ord(xchar) > 0x00: xlist[i] = chr(ord(xchar) - 1).encode('ascii') break else: xlist[i] = b"\xff" i += 1 xlist.reverse() return b"".join(xlist) def int_type_next_after(x, direction, itemsize): """Return the next representable neighbor of x in the appropriate direction.""" assert direction in [-1, +1] # x is guaranteed to be either an int or a float if direction < 0: if isinstance(x, int): return x - 1 else: # return int(PyNextAfter(x, x - 1)) return int(np.nextafter(x, x - 1)) else: if isinstance(x, int): return x + 1 else: # return int(PyNextAfter(x,x + 1)) + 1 return int(np.nextafter(x, x + 1)) + 1 def bool_type_next_after(x, direction, itemsize): """Return the next representable neighbor of x in the appropriate direction.""" assert direction in [-1, +1] # x is guaranteed to be either a boolean if direction < 0: return False else: return True def nextafter(x, direction, dtype, itemsize): """Return the next representable neighbor of x in the appropriate direction.""" assert direction in [-1, 0, +1] assert dtype.kind == "S" or type(x) in (bool, float, int) if direction == 0: return x if dtype.kind == "S": return string_next_after(x, direction, itemsize) if dtype.kind in ['b']: return bool_type_next_after(x, direction, itemsize) elif dtype.kind in ['i', 'u']: return int_type_next_after(x, direction, itemsize) elif dtype.kind == "f": if direction < 0: return np.nextafter(x, x - 1) else: return np.nextafter(x, x + 1) # elif dtype.name == "float32": # if direction < 0: # return PyNextAfterF(x,x-1) # else: # return PyNextAfterF(x,x + 1) # elif dtype.name == "float64": # if direction < 0: # return PyNextAfter(x,x-1) # else: # return PyNextAfter(x,x + 1) raise TypeError("data type ``%s`` is not supported" % dtype) PyTables-3.7.0/tables/index.py000066400000000000000000002602501416254111300162020ustar00rootroot00000000000000"""Here is defined the Index class.""" import math import operator import os import sys import tempfile import warnings from pathlib import Path from time import perf_counter as clock from time import process_time as cpuclock import numpy as np from .idxutils import (calc_chunksize, calcoptlevels, get_reduction_level, nextafter, inftype) from . import indexesextension from .node import NotLoggedMixin from .atom import UIntAtom, Atom from .earray import EArray from .carray import CArray from .leaf import Filters from .indexes import CacheArray, LastRowArray, IndexArray from .group import Group from .path import join_path from .exceptions import PerformanceWarning from .utils import is_idx, idx2long, lazyattr from .utilsextension import (nan_aware_gt, nan_aware_ge, nan_aware_lt, nan_aware_le, bisect_left, bisect_right) from .lrucacheextension import ObjectCache # default version for INDEX objects # obversion = "1.0" # Version of indexes in PyTables 1.x series # obversion = "2.0" # Version of indexes in PyTables Pro 2.0 series obversion = "2.1" # Version of indexes in PyTables Pro 2.1 and up series, # # including the join 2.3 Std + Pro version debug = False # debug = True # Uncomment this for printing sizes purposes profile = False # profile = True # Uncomment for profiling if profile: from .utils import show_stats # The default method for sorting # defsort = "quicksort" # Changing to mergesort to fix #441 defsort = "mergesort" # Default policy for automatically updating indexes after a table # append operation, or automatically reindexing after an # index-invalidating operation like removing or modifying table rows. default_auto_index = True # Keep in sync with ``Table.autoindex`` docstring. # Default filters used to compress indexes. This is quite fast and # compression is pretty good. # Remember to keep these defaults in sync with the docstrings and UG. default_index_filters = Filters(complevel=1, complib='zlib', shuffle=True, fletcher32=False) # Deprecated API defaultAutoIndex = default_auto_index defaultIndexFilters = default_index_filters # The list of types for which an optimised search in cython and C has # been implemented. Always add here the name of a new optimised type. opt_search_types = ("int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float32", "float64") # The upper limit for uint32 ints max32 = 2**32 def _table_column_pathname_of_index(indexpathname): names = indexpathname.split("/") for i, name in enumerate(names): if name.startswith('_i_'): break tablepathname = "/".join(names[:i]) + "/" + name[3:] colpathname = "/".join(names[i + 1:]) return (tablepathname, colpathname) class Index(NotLoggedMixin, Group, indexesextension.Index): """Represents the index of a column in a table. This class is used to keep the indexing information for columns in a Table dataset (see :ref:`TableClassDescr`). It is actually a descendant of the Group class (see :ref:`GroupClassDescr`), with some added functionality. An Index is always associated with one and only one column in the table. .. note:: This class is mainly intended for internal use, but some of its documented attributes and methods may be interesting for the programmer. Parameters ---------- parentnode The parent :class:`Group` object. .. versionchanged:: 3.0 Renamed from *parentNode* to *parentnode*. name : str The name of this node in its parent group. atom : Atom An Atom object representing the shape and type of the atomic objects to be saved. Only scalar atoms are supported. title Sets a TITLE attribute of the Index entity. kind The desired kind for this index. The 'full' kind specifies a complete track of the row position (64-bit), while the 'medium', 'light' or 'ultralight' kinds only specify in which chunk the row is (using 32-bit, 16-bit and 8-bit respectively). optlevel The desired optimization level for this index. filters : Filters An instance of the Filters class that provides information about the desired I/O filters to be applied during the life of this object. tmp_dir The directory for the temporary files. expectedrows Represents an user estimate about the number of row slices that will be added to the growable dimension in the IndexArray object. byteorder The byteorder of the index datasets *on-disk*. blocksizes The four main sizes of the compound blocks in index datasets (a low level parameter). """ _c_classid = 'INDEX' @property def kind(self): """The kind of this index.""" return {1: 'ultralight', 2: 'light', 4: 'medium', 8: 'full'}[self.indsize] @property def filters(self): """Filter properties for this index - see Filters in :ref:`FiltersClassDescr`.""" return self._v_filters @property def dirty(self): """Whether the index is dirty or not. Dirty indexes are out of sync with column data, so they exist but they are not usable. """ # If there is no ``DIRTY`` attribute, index should be clean. return getattr(self._v_attrs, 'DIRTY', False) @dirty.setter def dirty(self, dirty): wasdirty, isdirty = self.dirty, bool(dirty) self._v_attrs.DIRTY = dirty # If an *actual* change in dirtiness happens, # notify the condition cache by setting or removing a nail. conditioncache = self.table._condition_cache if not wasdirty and isdirty: conditioncache.nail() if wasdirty and not isdirty: conditioncache.unnail() @property def column(self): """The Column (see :ref:`ColumnClassDescr`) instance for the indexed column.""" tablepath, columnpath = _table_column_pathname_of_index( self._v_pathname) table = self._v_file._get_node(tablepath) column = table.cols._g_col(columnpath) return column @property def table(self): """Accessor for the `Table` object of this index.""" tablepath, columnpath = _table_column_pathname_of_index( self._v_pathname) table = self._v_file._get_node(tablepath) return table @property def nblockssuperblock(self): """The number of blocks in a superblock.""" return self.superblocksize // self.blocksize @property def nslicesblock(self): """The number of slices in a block.""" return self.blocksize // self.slicesize @property def nchunkslice(self): """The number of chunks in a slice.""" return self.slicesize // self.chunksize @property def nsuperblocks(self): """The total number of superblocks in index.""" # Last row should not be considered as a superblock nelements = self.nelements - self.nelementsILR nblocks = nelements // self.superblocksize if nelements % self.blocksize > 0: nblocks += 1 return nblocks @property def nblocks(self): """The total number of blocks in index.""" # Last row should not be considered as a block nelements = self.nelements - self.nelementsILR nblocks = nelements // self.blocksize if nelements % self.blocksize > 0: nblocks += 1 return nblocks @property def nslices(self): """The number of complete slices in index.""" return self.nelements // self.slicesize @property def nchunks(self): """The number of complete chunks in index.""" return self.nelements // self.chunksize @property def shape(self): """The shape of this index (in slices and elements).""" return (self.nrows, self.slicesize) @property def temp_required(self): """Whether a temporary file for indexes is required or not.""" return (self.indsize > 1 and self.optlevel > 0 and self.table.nrows > self.slicesize) @property def want_complete_sort(self): """Whether we should try to build a completely sorted index or not.""" return self.indsize == 8 and self.optlevel == 9 @property def is_csi(self): """Whether the index is completely sorted or not. .. versionchanged:: 3.0 The *is_CSI* property has been renamed into *is_csi*. """ if self.nelements == 0: # An index with 0 indexed elements is not a CSI one (by definition) return False if self.indsize < 8: # An index that is not full cannot be completely sorted return False # Try with the 'is_csi' attribute if 'is_csi' in self._v_attrs: return self._v_attrs.is_csi # If not, then compute the overlaps manually # (the attribute 'is_csi' will be set there) self.compute_overlaps(self, None, False) return self.noverlaps == 0 @lazyattr def nrowsinchunk(self): """The number of rows that fits in a *table* chunk.""" return self.table.chunkshape[0] @lazyattr def lbucket(self): """Return the length of a bucket based index type.""" # Avoid to set a too large lbucket size (mainly useful for tests) lbucket = min(self.nrowsinchunk, self.chunksize) if self.indsize == 1: # For ultra-light, we will never have to keep track of a # bucket outside of a slice. maxnb = 2**8 if self.slicesize > maxnb * lbucket: lbucket = math.ceil(self.slicesize / maxnb) elif self.indsize == 2: # For light, we will never have to keep track of a # bucket outside of a block. maxnb = 2**16 if self.blocksize > maxnb * lbucket: lbucket = math.ceil(self.blocksize / maxnb) else: # For medium and full indexes there should not be a need to # increase lbucket pass return lbucket def __init__(self, parentnode, name, atom=None, title="", kind=None, optlevel=None, filters=None, tmp_dir=None, expectedrows=0, byteorder=None, blocksizes=None, new=True): self._v_version = None """The object version of this index.""" self.optlevel = optlevel """The optimization level for this index.""" self.tmp_dir = tmp_dir """The directory for the temporary files.""" self.expectedrows = expectedrows """The expected number of items of index arrays.""" if byteorder in ["little", "big"]: self.byteorder = byteorder else: self.byteorder = sys.byteorder """The byteorder of the index datasets.""" if atom is not None: self.dtype = atom.dtype.base self.type = atom.type """The datatypes to be stored by the sorted index array.""" # ############## Important note ########################### # The datatypes saved as index values are NumPy native # types, so we get rid of type metainfo like Time* or Enum* # that belongs to HDF5 types (actually, this metainfo is # not needed for sorting and looking-up purposes). # ######################################################### indsize = { 'ultralight': 1, 'light': 2, 'medium': 4, 'full': 8}[kind] assert indsize in (1, 2, 4, 8), "indsize should be 1, 2, 4 or 8!" self.indsize = indsize """The itemsize for the indices part of the index.""" self.nrows = None """The total number of slices in the index.""" self.nelements = None """The number of currently indexed rows for this column.""" self.blocksizes = blocksizes """The four main sizes of the compound blocks (if specified).""" self.dirtycache = True """Dirty cache (for ranges, bounds & sorted) flag.""" self.superblocksize = None """Size of the superblock for this index.""" self.blocksize = None """Size of the block for this index.""" self.slicesize = None """Size of the slice for this index.""" self.chunksize = None """Size of the chunk for this index.""" self.tmpfilename = None """Filename for temporary bounds.""" self.opt_search_types = opt_search_types """The types for which and optimized search has been implemented.""" self.noverlaps = -1 """The number of overlaps in an index. 0 means a completely sorted index. -1 means that this number is not computed yet.""" self.tprof = 0 """Time counter for benchmarking purposes.""" from .file import open_file self._openFile = open_file """The `open_file()` function, to avoid a circular import.""" super().__init__(parentnode, name, title, new, filters) def _g_post_init_hook(self): if self._v_new: # The version for newly created indexes self._v_version = obversion super()._g_post_init_hook() # Index arrays must only be created for new indexes if not self._v_new: idxversion = self._v_version # Set-up some variables from info on disk and return attrs = self._v_attrs # Coerce NumPy scalars to Python scalars in order # to avoid undesired upcasting operations. self.superblocksize = int(attrs.superblocksize) self.blocksize = int(attrs.blocksize) self.slicesize = int(attrs.slicesize) self.chunksize = int(attrs.chunksize) self.blocksizes = (self.superblocksize, self.blocksize, self.slicesize, self.chunksize) self.optlevel = int(attrs.optlevel) sorted = self.sorted indices = self.indices self.dtype = sorted.atom.dtype self.type = sorted.atom.type self.indsize = indices.atom.itemsize # Some sanity checks for slicesize, chunksize and indsize assert self.slicesize == indices.shape[1], "Wrong slicesize" assert self.chunksize == indices._v_chunkshape[ 1], "Wrong chunksize" assert self.indsize in (1, 2, 4, 8), "Wrong indices itemsize" if idxversion > "2.0": self.reduction = int(attrs.reduction) nelementsSLR = int(self.sortedLR.attrs.nelements) nelementsILR = int(self.indicesLR.attrs.nelements) else: self.reduction = 1 nelementsILR = self.indicesLR[-1] nelementsSLR = nelementsILR self.nrows = sorted.nrows self.nelements = self.nrows * self.slicesize + nelementsILR self.nelementsSLR = nelementsSLR self.nelementsILR = nelementsILR if nelementsILR > 0: self.nrows += 1 # Get the bounds as a cache (this has to remain here!) rchunksize = self.chunksize // self.reduction nboundsLR = (nelementsSLR - 1) // rchunksize if nboundsLR < 0: nboundsLR = 0 # correction for -1 bounds nboundsLR += 2 # bounds + begin + end # All bounds values (+begin + end) are at the end of sortedLR self.bebounds = self.sortedLR[ nelementsSLR:nelementsSLR + nboundsLR] return # The index is new. Initialize the values self.nrows = 0 self.nelements = 0 self.nelementsSLR = 0 self.nelementsILR = 0 # The atom atom = Atom.from_dtype(self.dtype) # The filters filters = self.filters # Compute the superblocksize, blocksize, slicesize and chunksize values # (in case these parameters haven't been passed to the constructor) if self.blocksizes is None: self.blocksizes = calc_chunksize( self.expectedrows, self.optlevel, self.indsize, node=self) (self.superblocksize, self.blocksize, self.slicesize, self.chunksize) = self.blocksizes if debug: print("blocksizes:", self.blocksizes) # Compute the reduction level self.reduction = get_reduction_level( self.indsize, self.optlevel, self.slicesize, self.chunksize) rchunksize = self.chunksize // self.reduction rslicesize = self.slicesize // self.reduction # Save them on disk as attributes self._v_attrs.superblocksize = np.uint64(self.superblocksize) self._v_attrs.blocksize = np.uint64(self.blocksize) self._v_attrs.slicesize = np.uint32(self.slicesize) self._v_attrs.chunksize = np.uint32(self.chunksize) # Save the optlevel as well self._v_attrs.optlevel = self.optlevel # Save the reduction level self._v_attrs.reduction = self.reduction # Create the IndexArray for sorted values sorted = IndexArray(self, 'sorted', atom, "Sorted Values", filters, self.byteorder) # Create the IndexArray for index values IndexArray(self, 'indices', UIntAtom(itemsize=self.indsize), "Number of chunk in table", filters, self.byteorder) # Create the cache for range values (1st order cache) CacheArray(self, 'ranges', atom, (0, 2), "Range Values", filters, self.expectedrows // self.slicesize, byteorder=self.byteorder) # median ranges EArray(self, 'mranges', atom, (0,), "Median ranges", filters, byteorder=self.byteorder, _log=False) # Create the cache for boundary values (2nd order cache) nbounds_inslice = (rslicesize - 1) // rchunksize CacheArray(self, 'bounds', atom, (0, nbounds_inslice), "Boundary Values", filters, self.nchunks, (1, nbounds_inslice), byteorder=self.byteorder) # begin, end & median bounds (only for numerical types) EArray(self, 'abounds', atom, (0,), "Start bounds", filters, byteorder=self.byteorder, _log=False) EArray(self, 'zbounds', atom, (0,), "End bounds", filters, byteorder=self.byteorder, _log=False) EArray(self, 'mbounds', atom, (0,), "Median bounds", filters, byteorder=self.byteorder, _log=False) # Create the Array for last (sorted) row values + bounds shape = (rslicesize + 2 + nbounds_inslice,) sortedLR = LastRowArray(self, 'sortedLR', atom, shape, "Last Row sorted values + bounds", filters, (rchunksize,), byteorder=self.byteorder) # Create the Array for the number of chunk in last row shape = (self.slicesize,) # enough for indexes and length indicesLR = LastRowArray(self, 'indicesLR', UIntAtom(itemsize=self.indsize), shape, "Last Row indices", filters, (self.chunksize,), byteorder=self.byteorder) # The number of elements in LR will be initialized here sortedLR.attrs.nelements = 0 indicesLR.attrs.nelements = 0 # All bounds values (+begin + end) are uninitialized in creation time self.bebounds = None # The starts and lengths initialization self.starts = np.empty(shape=self.nrows, dtype=np.int32) """Where the values fulfiling conditions starts for every slice.""" self.lengths = np.empty(shape=self.nrows, dtype=np.int32) """Lengths of the values fulfilling conditions for every slice.""" # Finally, create a temporary file for indexes if needed if self.temp_required: self.create_temp() def initial_append(self, xarr, nrow, reduction): """Compute an initial indices arrays for data to be indexed.""" if profile: tref = clock() if profile: show_stats("Entering initial_append", tref) arr = xarr.pop() indsize = self.indsize slicesize = self.slicesize nelementsILR = self.nelementsILR if profile: show_stats("Before creating idx", tref) if indsize == 8: idx = np.arange(0, len(arr), dtype="uint64") + nrow * slicesize elif indsize == 4: # For medium (32-bit) all the rows in tables should be # directly reachable. But as len(arr) < 2**31, we can # choose uint32 for representing indices. In this way, we # consume far less memory during the keysort process. The # offset will be added in self.final_idx32() later on. # # This optimization also prevents the values in LR to # participate in the ``swap_chunks`` process, and this is # the main reason to not allow the medium indexes to create # completely sorted indexes. However, I don't find this to # be a big limitation, as probably fully indexes are much # more suitable for producing completely sorted indexes # because in this case the indices part is usable for # getting the reverse indices of the index, and I forsee # this to be a common requirement in many operations (for # example, in table sorts). # # F. Alted 2008-09-15 idx = np.arange(0, len(arr), dtype="uint32") else: idx = np.empty(len(arr), "uint%d" % (indsize * 8)) lbucket = self.lbucket # Fill the idx with the bucket indices offset = int(lbucket - ((nrow * (slicesize % lbucket)) % lbucket)) idx[0:offset] = 0 for i in range(offset, slicesize, lbucket): idx[i:i + lbucket] = (i + lbucket - 1) // lbucket if indsize == 2: # Add a second offset in this case # First normalize the number of rows offset2 = (nrow % self.nslicesblock) * slicesize // lbucket idx += offset2 # Add the last row at the beginning of arr & idx (if needed) if (indsize == 8 and nelementsILR > 0): # It is possible that the values in LR are already sorted. # Fetch them and override existing values in arr and idx. assert len(arr) > nelementsILR self.read_slice_lr(self.sortedLR, arr[:nelementsILR]) self.read_slice_lr(self.indicesLR, idx[:nelementsILR]) # In-place sorting if profile: show_stats("Before keysort", tref) indexesextension.keysort(arr, idx) larr = arr[-1] if reduction > 1: # It's important to do a copy() here in order to ensure that # sorted._append() will receive a contiguous array. if profile: show_stats("Before reduction", tref) reduc = arr[::reduction].copy() if profile: show_stats("After reduction", tref) arr = reduc if profile: show_stats("After arr <-- reduc", tref) # A completely sorted index is not longer possible after an # append of an index with already one slice. if nrow > 0: self._v_attrs.is_csi = False if profile: show_stats("Exiting initial_append", tref) return larr, arr, idx def final_idx32(self, idx, offset): """Perform final operations in 32-bit indices.""" if profile: tref = clock() if profile: show_stats("Entering final_idx32", tref) # Do an upcast first in order to add the offset. idx = idx.astype('uint64') idx += offset # The next partition is valid up to table sizes of # 2**30 * 2**18 = 2**48 bytes, that is, 256 Tera-elements, # which should be a safe figure, at least for a while. idx //= self.lbucket # After the division, we can downsize the indexes to 'uint32' idx = idx.astype('uint32') if profile: show_stats("Exiting final_idx32", tref) return idx def append(self, xarr, update=False): """Append the array to the index objects.""" if profile: tref = clock() if profile: show_stats("Entering append", tref) if not update and self.temp_required: where = self.tmp # The reduction will take place *after* the optimization process reduction = 1 else: where = self reduction = self.reduction sorted = where.sorted indices = where.indices ranges = where.ranges mranges = where.mranges bounds = where.bounds mbounds = where.mbounds abounds = where.abounds zbounds = where.zbounds sortedLR = where.sortedLR indicesLR = where.indicesLR nrows = sorted.nrows # before sorted.append() larr, arr, idx = self.initial_append(xarr, nrows, reduction) # Save the sorted array sorted.append(arr.reshape(1, arr.size)) cs = self.chunksize // reduction ncs = self.nchunkslice # Save ranges & bounds ranges.append([[arr[0], larr]]) bounds.append([arr[cs::cs]]) abounds.append(arr[0::cs]) zbounds.append(arr[cs - 1::cs]) # Compute the medians smedian = arr[cs // 2::cs] mbounds.append(smedian) mranges.append([smedian[ncs // 2]]) if profile: show_stats("Before deleting arr & smedian", tref) del arr, smedian # delete references if profile: show_stats("After deleting arr & smedian", tref) # Now that arr is gone, we can upcast the indices and add the offset if self.indsize == 4: idx = self.final_idx32(idx, nrows * self.slicesize) indices.append(idx.reshape(1, idx.size)) if profile: show_stats("Before deleting idx", tref) del idx # Update counters after a successful append self.nrows = nrows + 1 self.nelements = self.nrows * self.slicesize self.nelementsSLR = 0 # reset the counter of the last row index to 0 self.nelementsILR = 0 # reset the counter of the last row index to 0 # The number of elements will be saved as an attribute. # This is necessary in case the LR arrays can remember its values # after a possible node preemtion/reload. sortedLR.attrs.nelements = self.nelementsSLR indicesLR.attrs.nelements = self.nelementsILR self.dirtycache = True # the cache is dirty now if profile: show_stats("Exiting append", tref) def append_last_row(self, xarr, update=False): """Append the array to the last row index objects.""" if profile: tref = clock() if profile: show_stats("Entering appendLR", tref) # compute the elements in the last row sorted & bounds array nrows = self.nslices if not update and self.temp_required: where = self.tmp # The reduction will take place *after* the optimization process reduction = 1 else: where = self reduction = self.reduction indicesLR = where.indicesLR sortedLR = where.sortedLR larr, arr, idx = self.initial_append(xarr, nrows, reduction) nelementsSLR = len(arr) nelementsILR = len(idx) # Build the cache of bounds rchunksize = self.chunksize // reduction self.bebounds = np.concatenate((arr[::rchunksize], [larr])) # The number of elements will be saved as an attribute sortedLR.attrs.nelements = nelementsSLR indicesLR.attrs.nelements = nelementsILR # Save the number of elements, bounds and sorted values # at the end of the sorted array offset2 = len(self.bebounds) sortedLR[nelementsSLR:nelementsSLR + offset2] = self.bebounds sortedLR[:nelementsSLR] = arr del arr # Now that arr is gone, we can upcast the indices and add the offset if self.indsize == 4: idx = self.final_idx32(idx, nrows * self.slicesize) # Save the reverse index array indicesLR[:len(idx)] = idx del idx # Update counters after a successful append self.nrows = nrows + 1 self.nelements = nrows * self.slicesize + nelementsILR self.nelementsILR = nelementsILR self.nelementsSLR = nelementsSLR self.dirtycache = True # the cache is dirty now if profile: show_stats("Exiting appendLR", tref) def optimize(self, verbose=False): """Optimize an index so as to allow faster searches. verbose If True, messages about the progress of the optimization process are printed out. """ if not self.temp_required: return if verbose: self.verbose = True else: self.verbose = debug # Initialize last_tover and last_nover self.last_tover = 0 self.last_nover = 0 # Compute the correct optimizations for current optim level opts = calcoptlevels(self.nblocks, self.optlevel, self.indsize) optmedian, optstarts, optstops, optfull = opts if debug: print("optvalues:", opts) self.create_temp2() # Start the optimization process while True: if optfull: for niter in range(optfull): if self.swap('chunks', 'median'): break if self.nblocks > 1: # Swap slices only in the case that we have # several blocks if self.swap('slices', 'median'): break if self.swap('chunks', 'median'): break if self.swap('chunks', 'start'): break if self.swap('chunks', 'stop'): break else: if optmedian: if self.swap('chunks', 'median'): break if optstarts: if self.swap('chunks', 'start'): break if optstops: if self.swap('chunks', 'stop'): break break # If we reach this, exit the loop # Check if we require a complete sort. Important: this step # should be carried out *after* the optimization process has # been completed (this is to guarantee that the complete sort # does not take too much memory). if self.want_complete_sort: if self.noverlaps > 0: self.do_complete_sort() # Check that we have effectively achieved the complete sort if self.noverlaps > 0: warnings.warn( "OPSI was not able to achieve a completely sorted index." " Please report this to the authors.", UserWarning) # Close and delete the temporal optimization index file self.cleanup_temp() return def do_complete_sort(self): """Bring an already optimized index into a complete sorted state.""" if self.verbose: t1 = clock() c1 = cpuclock() ss = self.slicesize tmp = self.tmp ranges = tmp.ranges[:] nslices = self.nslices nelementsLR = self.nelementsILR if nelementsLR > 0: # Add the ranges corresponding to the last row rangeslr = np.array([self.bebounds[0], self.bebounds[-1]]) ranges = np.concatenate((ranges, [rangeslr])) nslices += 1 sorted = tmp.sorted indices = tmp.indices sortedLR = tmp.sortedLR indicesLR = tmp.indicesLR sremain = np.array([], dtype=self.dtype) iremain = np.array([], dtype='u%d' % self.indsize) starts = np.zeros(shape=nslices, dtype=np.int_) for i in range(nslices): # Find the overlapping elements for slice i sover = np.array([], dtype=self.dtype) iover = np.array([], dtype='u%d' % self.indsize) prev_end = ranges[i, 1] for j in range(i + 1, nslices): stj = starts[j] if ((j < self.nslices and stj == ss) or (j == self.nslices and stj == nelementsLR)): # This slice has been already dealt with continue if j < self.nslices: assert stj < ss, \ "Two slices cannot overlap completely at this stage!" next_beg = sorted[j, stj] else: assert stj < nelementsLR, \ "Two slices cannot overlap completely at this stage!" next_beg = sortedLR[stj] next_end = ranges[j, 1] if prev_end > next_end: # Complete overlapping case if j < self.nslices: sover = np.concatenate((sover, sorted[j, stj:])) iover = np.concatenate((iover, indices[j, stj:])) starts[j] = ss else: n = nelementsLR sover = np.concatenate((sover, sortedLR[stj:n])) iover = np.concatenate((iover, indicesLR[stj:n])) starts[j] = nelementsLR elif prev_end > next_beg: idx = self.search_item_lt(tmp, prev_end, j, ranges[j], stj) if j < self.nslices: sover = np.concatenate((sover, sorted[j, stj:idx])) iover = np.concatenate((iover, indices[j, stj:idx])) else: sover = np.concatenate((sover, sortedLR[stj:idx])) iover = np.concatenate((iover, indicesLR[stj:idx])) starts[j] = idx # Build the extended slices to sort out if i < self.nslices: ssorted = np.concatenate( (sremain, sorted[i, starts[i]:], sover)) sindices = np.concatenate( (iremain, indices[i, starts[i]:], iover)) else: ssorted = np.concatenate( (sremain, sortedLR[starts[i]:nelementsLR], sover)) sindices = np.concatenate( (iremain, indicesLR[starts[i]:nelementsLR], iover)) # Sort the extended slices indexesextension.keysort(ssorted, sindices) # Save the first elements of extended slices in the slice i if i < self.nslices: sorted[i] = ssorted[:ss] indices[i] = sindices[:ss] # Update caches for this slice self.update_caches(i, ssorted[:ss]) # Save the remaining values in a separate array send = len(sover) + len(sremain) sremain = ssorted[ss:ss + send] iremain = sindices[ss:ss + send] else: # Still some elements remain for the last row n = len(ssorted) assert n == nelementsLR send = 0 sortedLR[:n] = ssorted indicesLR[:n] = sindices # Update the caches for last row sortedlr = sortedLR[:nelementsLR] bebounds = np.concatenate( (sortedlr[::self.chunksize], [sortedlr[-1]])) sortedLR[nelementsLR:nelementsLR + len(bebounds)] = bebounds self.bebounds = bebounds # Verify that we have dealt with all the remaining values assert send == 0 # Compute the overlaps in order to verify that we have achieved # a complete sort. This has to be executed always (and not only # in verbose mode!). self.compute_overlaps(self.tmp, "do_complete_sort()", self.verbose) if self.verbose: print(f"time: {clock() - t1:.4f}. clock: {cpuclock() - c1:.4f}") def swap(self, what, mode=None): """Swap chunks or slices using a certain bounds reference.""" # Thresholds for avoiding continuing the optimization # thnover = 4 * self.slicesize # minimum number of overlapping # # elements thnover = 40 thmult = 0.1 # minimum ratio of multiplicity (a 10%) thtover = 0.01 # minimum overlaping index for slices (a 1%) if self.verbose: t1 = clock() c1 = cpuclock() if what == "chunks": self.swap_chunks(mode) elif what == "slices": self.swap_slices(mode) if mode: message = f"swap_{what}({mode})" else: message = f"swap_{what}" (nover, mult, tover) = self.compute_overlaps( self.tmp, message, self.verbose) rmult = len(mult.nonzero()[0]) / len(mult) if self.verbose: print(f"time: {clock() - t1:.4f}. clock: {cpuclock() - c1:.4f}") # Check that entropy is actually decreasing if what == "chunks" and self.last_tover > 0 and self.last_nover > 0: tover_var = (self.last_tover - tover) / self.last_tover nover_var = (self.last_nover - nover) / self.last_nover if tover_var < 0.05 and nover_var < 0.05: # Less than a 5% of improvement is too few return True self.last_tover = tover self.last_nover = nover # Check if some threshold has met if nover < thnover: return True if rmult < thmult: return True # Additional check for the overlap ratio if 0 <= tover < thtover: return True return False def create_temp(self): """Create some temporary objects for slice sorting purposes.""" # The index will be dirty during the index optimization process self.dirty = True # Build the name of the temporary file fd, self.tmpfilename = tempfile.mkstemp( ".tmp", "pytables-", self.tmp_dir) # Close the file descriptor so as to avoid leaks os.close(fd) # Create the proper PyTables file self.tmpfile = self._openFile(self.tmpfilename, "w") self.tmp = tmp = self.tmpfile.root cs = self.chunksize ss = self.slicesize filters = self.filters # temporary sorted & indices arrays shape = (0, ss) atom = Atom.from_dtype(self.dtype) EArray(tmp, 'sorted', atom, shape, "Temporary sorted", filters, chunkshape=(1, cs)) EArray(tmp, 'indices', UIntAtom(itemsize=self.indsize), shape, "Temporary indices", filters, chunkshape=(1, cs)) # temporary bounds nbounds_inslice = (ss - 1) // cs shape = (0, nbounds_inslice) EArray(tmp, 'bounds', atom, shape, "Temp chunk bounds", filters, chunkshape=(cs, nbounds_inslice)) shape = (0,) EArray(tmp, 'abounds', atom, shape, "Temp start bounds", filters, chunkshape=(cs,)) EArray(tmp, 'zbounds', atom, shape, "Temp end bounds", filters, chunkshape=(cs,)) EArray(tmp, 'mbounds', atom, shape, "Median bounds", filters, chunkshape=(cs,)) # temporary ranges EArray(tmp, 'ranges', atom, (0, 2), "Temporary range values", filters, chunkshape=(cs, 2)) EArray(tmp, 'mranges', atom, (0,), "Median ranges", filters, chunkshape=(cs,)) # temporary last row (sorted) shape = (ss + 2 + nbounds_inslice,) CArray(tmp, 'sortedLR', atom, shape, "Temp Last Row sorted values + bounds", filters, chunkshape=(cs,)) # temporary last row (indices) shape = (ss,) CArray(tmp, 'indicesLR', UIntAtom(itemsize=self.indsize), shape, "Temp Last Row indices", filters, chunkshape=(cs,)) def create_temp2(self): """Create some temporary objects for slice sorting purposes.""" # The algorithms for doing the swap can be optimized so that # one should be necessary to create temporaries for keeping just # the contents of a single superblock. # F. Alted 2007-01-03 cs = self.chunksize ss = self.slicesize filters = self.filters # temporary sorted & indices arrays shape = (self.nslices, ss) atom = Atom.from_dtype(self.dtype) tmp = self.tmp CArray(tmp, 'sorted2', atom, shape, "Temporary sorted 2", filters, chunkshape=(1, cs)) CArray(tmp, 'indices2', UIntAtom(itemsize=self.indsize), shape, "Temporary indices 2", filters, chunkshape=(1, cs)) # temporary bounds nbounds_inslice = (ss - 1) // cs shape = (self.nslices, nbounds_inslice) CArray(tmp, 'bounds2', atom, shape, "Temp chunk bounds 2", filters, chunkshape=(cs, nbounds_inslice)) shape = (self.nchunks,) CArray(tmp, 'abounds2', atom, shape, "Temp start bounds 2", filters, chunkshape=(cs,)) CArray(tmp, 'zbounds2', atom, shape, "Temp end bounds 2", filters, chunkshape=(cs,)) CArray(tmp, 'mbounds2', atom, shape, "Median bounds 2", filters, chunkshape=(cs,)) # temporary ranges CArray(tmp, 'ranges2', atom, (self.nslices, 2), "Temporary range values 2", filters, chunkshape=(cs, 2)) CArray(tmp, 'mranges2', atom, (self.nslices,), "Median ranges 2", filters, chunkshape=(cs,)) def cleanup_temp(self): """Copy the data and delete the temporaries for sorting purposes.""" if self.verbose: print("Copying temporary data...") # tmp -> index reduction = self.reduction cs = self.chunksize // reduction ncs = self.nchunkslice tmp = self.tmp for i in range(self.nslices): # Copy sorted & indices slices sorted = tmp.sorted[i][::reduction].copy() self.sorted.append(sorted.reshape(1, sorted.size)) # Compute ranges self.ranges.append([[sorted[0], sorted[-1]]]) # Compute chunk bounds self.bounds.append([sorted[cs::cs]]) # Compute start, stop & median bounds and ranges self.abounds.append(sorted[0::cs]) self.zbounds.append(sorted[cs - 1::cs]) smedian = sorted[cs // 2::cs] self.mbounds.append(smedian) self.mranges.append([smedian[ncs // 2]]) del sorted, smedian # delete references # Now that sorted is gone, we can copy the indices indices = tmp.indices[i] self.indices.append(indices.reshape(1, indices.size)) # Now it is the last row turn (if needed) if self.nelementsSLR > 0: # First, the sorted values sortedLR = self.sortedLR indicesLR = self.indicesLR nelementsLR = self.nelementsILR sortedlr = tmp.sortedLR[:nelementsLR][::reduction].copy() nelementsSLR = len(sortedlr) sortedLR[:nelementsSLR] = sortedlr # Now, the bounds self.bebounds = np.concatenate((sortedlr[::cs], [sortedlr[-1]])) offset2 = len(self.bebounds) sortedLR[nelementsSLR:nelementsSLR + offset2] = self.bebounds # Finally, the indices indicesLR[:] = tmp.indicesLR[:] # Update the number of (reduced) sorted elements self.nelementsSLR = nelementsSLR # The number of elements will be saved as an attribute self.sortedLR.attrs.nelements = self.nelementsSLR self.indicesLR.attrs.nelements = self.nelementsILR if self.verbose: print("Deleting temporaries...") self.tmp = None self.tmpfile.close() Path(self.tmpfilename).unlink() self.tmpfilename = None # The optimization process has finished, and the index is ok now self.dirty = False # ...but the memory data cache is dirty now self.dirtycache = True def get_neworder(self, neworder, src_disk, tmp_disk, lastrow, nslices, offset, dtype): """Get sorted & indices values in new order.""" cs = self.chunksize ncs = ncs2 = self.nchunkslice self_nslices = self.nslices tmp = np.empty(shape=self.slicesize, dtype=dtype) for i in range(nslices): ns = offset + i if ns == self_nslices: # The number of complete chunks in the last row ncs2 = self.nelementsILR // cs # Get slices in new order for j in range(ncs2): idx = neworder[i * ncs + j] ins = idx // ncs inc = (idx - ins * ncs) * cs ins += offset nc = j * cs if ins == self_nslices: tmp[nc:nc + cs] = lastrow[inc:inc + cs] else: tmp[nc:nc + cs] = src_disk[ins, inc:inc + cs] if ns == self_nslices: # The number of complete chunks in the last row lastrow[:ncs2 * cs] = tmp[:ncs2 * cs] # The elements in the last chunk of the last row will # participate in the global reordering later on, during # the phase of sorting of *two* slices at a time # (including the last row slice, see # self.reorder_slices()). The caches for last row will # be updated in self.reorder_slices() too. # F. Altet 2008-08-25 else: tmp_disk[ns] = tmp def swap_chunks(self, mode="median"): """Swap & reorder the different chunks in a block.""" boundsnames = { 'start': 'abounds', 'stop': 'zbounds', 'median': 'mbounds'} tmp = self.tmp sorted = tmp.sorted indices = tmp.indices tmp_sorted = tmp.sorted2 tmp_indices = tmp.indices2 sortedLR = tmp.sortedLR indicesLR = tmp.indicesLR cs = self.chunksize ncs = self.nchunkslice nsb = self.nslicesblock ncb = ncs * nsb ncb2 = ncb boundsobj = tmp._f_get_child(boundsnames[mode]) can_cross_bbounds = (self.indsize == 8 and self.nelementsILR > 0) for nblock in range(self.nblocks): # Protection for last block having less chunks than ncb remainingchunks = self.nchunks - nblock * ncb if remainingchunks < ncb: ncb2 = remainingchunks if ncb2 <= 1: # if only zero or one chunks remains we are done break nslices = ncb2 // ncs bounds = boundsobj[nblock * ncb:nblock * ncb + ncb2] # Do this only if lastrow elements can cross block boundaries if (nblock == self.nblocks - 1 and # last block can_cross_bbounds): nslices += 1 ul = self.nelementsILR // cs bounds = np.concatenate((bounds, self.bebounds[:ul])) sbounds_idx = bounds.argsort(kind=defsort) offset = int(nblock * nsb) # Swap sorted and indices following the new order self.get_neworder(sbounds_idx, sorted, tmp_sorted, sortedLR, nslices, offset, self.dtype) self.get_neworder(sbounds_idx, indices, tmp_indices, indicesLR, nslices, offset, 'u%d' % self.indsize) # Reorder completely the index at slice level self.reorder_slices(tmp=True) def read_slice(self, where, nslice, buffer, start=0): """Read a slice from the `where` dataset and put it in `buffer`.""" # Create the buffers for specifying the coordinates self.startl = np.array([nslice, start], np.uint64) self.stopl = np.array([nslice + 1, start + buffer.size], np.uint64) self.stepl = np.ones(shape=2, dtype=np.uint64) where._g_read_slice(self.startl, self.stopl, self.stepl, buffer) def write_slice(self, where, nslice, buffer, start=0): """Write a `slice` to the `where` dataset with the `buffer` data.""" self.startl = np.array([nslice, start], np.uint64) self.stopl = np.array([nslice + 1, start + buffer.size], np.uint64) self.stepl = np.ones(shape=2, dtype=np.uint64) countl = self.stopl - self.startl # (1, self.slicesize) where._g_write_slice(self.startl, self.stepl, countl, buffer) # Read version for LastRow def read_slice_lr(self, where, buffer, start=0): """Read a slice from the `where` dataset and put it in `buffer`.""" startl = np.array([start], dtype=np.uint64) stopl = np.array([start + buffer.size], dtype=np.uint64) stepl = np.array([1], dtype=np.uint64) where._g_read_slice(startl, stopl, stepl, buffer) # Write version for LastRow def write_sliceLR(self, where, buffer, start=0): """Write a slice from the `where` dataset with the `buffer` data.""" startl = np.array([start], dtype=np.uint64) countl = np.array([start + buffer.size], dtype=np.uint64) stepl = np.array([1], dtype=np.uint64) where._g_write_slice(startl, stepl, countl, buffer) def reorder_slice(self, nslice, sorted, indices, ssorted, sindices, tmp_sorted, tmp_indices): """Copy & reorder the slice in source to final destination.""" ss = self.slicesize # Load the second part in buffers self.read_slice(tmp_sorted, nslice, ssorted[ss:]) self.read_slice(tmp_indices, nslice, sindices[ss:]) indexesextension.keysort(ssorted, sindices) # Write the first part of the buffers to the regular leaves self.write_slice(sorted, nslice - 1, ssorted[:ss]) self.write_slice(indices, nslice - 1, sindices[:ss]) # Update caches self.update_caches(nslice - 1, ssorted[:ss]) # Shift the slice in the end to the beginning ssorted[:ss] = ssorted[ss:] sindices[:ss] = sindices[ss:] def update_caches(self, nslice, ssorted): """Update the caches for faster lookups.""" cs = self.chunksize ncs = self.nchunkslice tmp = self.tmp # update first & second cache bounds (ranges & bounds) tmp.ranges[nslice] = ssorted[[0, -1]] tmp.bounds[nslice] = ssorted[cs::cs] # update start & stop bounds tmp.abounds[nslice * ncs:(nslice + 1) * ncs] = ssorted[0::cs] tmp.zbounds[nslice * ncs:(nslice + 1) * ncs] = ssorted[cs - 1::cs] # update median bounds smedian = ssorted[cs // 2::cs] tmp.mbounds[nslice * ncs:(nslice + 1) * ncs] = smedian tmp.mranges[nslice] = smedian[ncs // 2] def reorder_slices(self, tmp): """Reorder completely the index at slice level. This method has to maintain the locality of elements in the ambit of ``blocks``, i.e. an element of a ``block`` cannot be sent to another ``block`` during this reordering. This is *critical* for ``light`` indexes to be able to use this. This version of reorder_slices is optimized in that *two* complete slices are taken at a time (including the last row slice) so as to sort them. Then, each new slice that is read is put at the end of this two-slice buffer, while the previous one is moved to the beginning of the buffer. This is in order to better reduce the entropy of the regular part (i.e. all except the last row) of the index. A secondary effect of this is that it takes at least *twice* of memory than a previous version of reorder_slices() that only reorders on a slice-by-slice basis. However, as this is more efficient than the old version, one can configure the slicesize to be smaller, so the memory consumption is barely similar. """ tmp = self.tmp sorted = tmp.sorted indices = tmp.indices if tmp: tmp_sorted = tmp.sorted2 tmp_indices = tmp.indices2 else: tmp_sorted = tmp.sorted tmp_indices = tmp.indices cs = self.chunksize ss = self.slicesize nsb = self.blocksize // self.slicesize nslices = self.nslices nblocks = self.nblocks nelementsLR = self.nelementsILR # Create the buffer for reordering 2 slices at a time ssorted = np.empty(shape=ss * 2, dtype=self.dtype) sindices = np.empty(shape=ss * 2, dtype=np.dtype('u%d' % self.indsize)) if self.indsize == 8: # Bootstrap the process for reordering # Read the first slice in buffers self.read_slice(tmp_sorted, 0, ssorted[:ss]) self.read_slice(tmp_indices, 0, sindices[:ss]) nslice = 0 # Just in case the loop behind executes nothing # Loop over the remainding slices in block for nslice in range(1, sorted.nrows): self.reorder_slice(nslice, sorted, indices, ssorted, sindices, tmp_sorted, tmp_indices) # End the process (enrolling the lastrow if necessary) if nelementsLR > 0: sortedLR = self.tmp.sortedLR indicesLR = self.tmp.indicesLR # Shrink the ssorted and sindices arrays to the minimum ssorted2 = ssorted[:ss + nelementsLR] sortedlr = ssorted2[ss:] sindices2 = sindices[:ss + nelementsLR] indiceslr = sindices2[ss:] # Read the last row info in the second part of the buffer self.read_slice_lr(sortedLR, sortedlr) self.read_slice_lr(indicesLR, indiceslr) indexesextension.keysort(ssorted2, sindices2) # Write the second part of the buffers to the lastrow indices self.write_sliceLR(sortedLR, sortedlr) self.write_sliceLR(indicesLR, indiceslr) # Update the caches for last row bebounds = np.concatenate((sortedlr[::cs], [sortedlr[-1]])) sortedLR[nelementsLR:nelementsLR + len(bebounds)] = bebounds self.bebounds = bebounds # Write the first part of the buffers to the regular leaves self.write_slice(sorted, nslice, ssorted[:ss]) self.write_slice(indices, nslice, sindices[:ss]) # Update caches for this slice self.update_caches(nslice, ssorted[:ss]) else: # Iterate over each block. No data should cross block # boundaries to avoid adressing problems with short indices. for nb in range(nblocks): # Bootstrap the process for reordering # Read the first slice in buffers nrow = nb * nsb self.read_slice(tmp_sorted, nrow, ssorted[:ss]) self.read_slice(tmp_indices, nrow, sindices[:ss]) # Loop over the remainding slices in block lrb = nrow + nsb if lrb > nslices: lrb = nslices nslice = nrow # Just in case the loop behind executes nothing for nslice in range(nrow + 1, lrb): self.reorder_slice(nslice, sorted, indices, ssorted, sindices, tmp_sorted, tmp_indices) # Write the first part of the buffers to the regular leaves self.write_slice(sorted, nslice, ssorted[:ss]) self.write_slice(indices, nslice, sindices[:ss]) # Update caches for this slice self.update_caches(nslice, ssorted[:ss]) def swap_slices(self, mode="median"): """Swap slices in a superblock.""" tmp = self.tmp sorted = tmp.sorted indices = tmp.indices tmp_sorted = tmp.sorted2 tmp_indices = tmp.indices2 ncs = self.nchunkslice nss = self.superblocksize // self.slicesize nss2 = nss for sblock in range(self.nsuperblocks): # Protection for last superblock having less slices than nss remainingslices = self.nslices - sblock * nss if remainingslices < nss: nss2 = remainingslices if nss2 <= 1: break if mode == "start": ranges = tmp.ranges[sblock * nss:sblock * nss + nss2, 0] elif mode == "stop": ranges = tmp.ranges[sblock * nss:sblock * nss + nss2, 1] elif mode == "median": ranges = tmp.mranges[sblock * nss:sblock * nss + nss2] sranges_idx = ranges.argsort(kind=defsort) # Don't swap the superblock at all if one doesn't need to ndiff = (sranges_idx != np.arange(nss2)).sum() / 2 if ndiff * 50 < nss2: # The number of slices to rearrange is less than 2.5%, # so skip the reordering of this superblock # (too expensive for such a little improvement) if self.verbose: print("skipping reordering of superblock ->", sblock) continue ns = sblock * nss2 # Swap sorted and indices slices following the new order for i in range(nss2): idx = sranges_idx[i] # Swap sorted & indices slices oi = ns + i oidx = ns + idx tmp_sorted[oi] = sorted[oidx] tmp_indices[oi] = indices[oidx] # Swap start, stop & median ranges tmp.ranges2[oi] = tmp.ranges[oidx] tmp.mranges2[oi] = tmp.mranges[oidx] # Swap chunk bounds tmp.bounds2[oi] = tmp.bounds[oidx] # Swap start, stop & median bounds j = oi * ncs jn = (oi + 1) * ncs xj = oidx * ncs xjn = (oidx + 1) * ncs tmp.abounds2[j:jn] = tmp.abounds[xj:xjn] tmp.zbounds2[j:jn] = tmp.zbounds[xj:xjn] tmp.mbounds2[j:jn] = tmp.mbounds[xj:xjn] # tmp -> originals for i in range(nss2): # Copy sorted & indices slices oi = ns + i sorted[oi] = tmp_sorted[oi] indices[oi] = tmp_indices[oi] # Copy start, stop & median ranges tmp.ranges[oi] = tmp.ranges2[oi] tmp.mranges[oi] = tmp.mranges2[oi] # Copy chunk bounds tmp.bounds[oi] = tmp.bounds2[oi] # Copy start, stop & median bounds j = oi * ncs jn = (oi + 1) * ncs tmp.abounds[j:jn] = tmp.abounds2[j:jn] tmp.zbounds[j:jn] = tmp.zbounds2[j:jn] tmp.mbounds[j:jn] = tmp.mbounds2[j:jn] def search_item_lt(self, where, item, nslice, limits, start=0): """Search a single item in a specific sorted slice.""" # This method will only works under the assumtion that item # *is to be found* in the nslice. assert nan_aware_lt(limits[0], item) and nan_aware_le(item, limits[1]) cs = self.chunksize ss = self.slicesize nelementsLR = self.nelementsILR bstart = start // cs # Find the chunk if nslice < self.nslices: nchunk = bisect_left(where.bounds[nslice], item, bstart) else: # We need to subtract 1 chunk here because bebounds # has a leading value nchunk = bisect_left(self.bebounds, item, bstart) - 1 assert nchunk >= 0 # Find the element in chunk pos = nchunk * cs if nslice < self.nslices: pos += bisect_left(where.sorted[nslice, pos:pos + cs], item) assert pos <= ss else: end = pos + cs if end > nelementsLR: end = nelementsLR pos += bisect_left(self.sortedLR[pos:end], item) assert pos <= nelementsLR assert pos > 0 return pos def compute_overlaps_finegrain(self, where, message, verbose): """Compute some statistics about overlaping of slices in index. It returns the following info: noverlaps : int The total number of elements that overlaps in index. multiplicity : array of int The number of times that a concrete slice overlaps with any other. toverlap : float An ovelap index: the sum of the values in segment slices that overlaps divided by the entire range of values. This index is only computed for numerical types. """ ss = self.slicesize ranges = where.ranges[:] sorted = where.sorted sortedLR = where.sortedLR nslices = self.nslices nelementsLR = self.nelementsILR if nelementsLR > 0: # Add the ranges corresponding to the last row rangeslr = np.array([self.bebounds[0], self.bebounds[-1]]) ranges = np.concatenate((ranges, [rangeslr])) nslices += 1 soverlap = 0 toverlap = -1 multiplicity = np.zeros(shape=nslices, dtype="int_") overlaps = multiplicity.copy() starts = multiplicity.copy() for i in range(nslices): prev_end = ranges[i, 1] for j in range(i + 1, nslices): stj = starts[j] assert stj <= ss if stj == ss: # This slice has already been counted continue if j < self.nslices: next_beg = sorted[j, stj] else: next_beg = sortedLR[stj] next_end = ranges[j, 1] if prev_end > next_end: # Complete overlapping case multiplicity[j - i] += 1 if j < self.nslices: overlaps[i] += ss - stj starts[j] = ss # a sentinel else: overlaps[i] += nelementsLR - stj starts[j] = nelementsLR # a sentinel elif prev_end > next_beg: multiplicity[j - i] += 1 idx = self.search_item_lt( where, prev_end, j, ranges[j], stj) nelem = idx - stj overlaps[i] += nelem starts[j] = idx if self.type != "string": # Convert ranges into floats in order to allow # doing operations with them without overflows soverlap += float(ranges[i, 1]) - float(ranges[j, 0]) # Return the overlap as the ratio between overlaps and entire range if self.type != "string": erange = float(ranges[-1, 1]) - float(ranges[0, 0]) # Check that there is an effective range of values # Beware, erange can be negative in situations where # the values are suffering overflow. This can happen # specially on big signed integer values (on overflows, # the end value will become negative!). # Also, there is no way to compute overlap ratios for # non-numerical types. So, be careful and always check # that toverlap has a positive value (it must have been # initialized to -1. before) before using it. # F. Alted 2007-01-19 if erange > 0: toverlap = soverlap / erange if verbose and message != "init": print("toverlap (%s):" % message, toverlap) print("multiplicity:\n", multiplicity, multiplicity.sum()) print("overlaps:\n", overlaps, overlaps.sum()) noverlaps = overlaps.sum() # For full indexes, set the 'is_csi' flag if self.indsize == 8 and self._v_file._iswritable(): self._v_attrs.is_csi = (noverlaps == 0) # Save the number of overlaps for future references self.noverlaps = noverlaps return (noverlaps, multiplicity, toverlap) def compute_overlaps(self, where, message, verbose): """Compute some statistics about overlaping of slices in index. It returns the following info: noverlaps : int The total number of slices that overlaps in index. multiplicity : array of int The number of times that a concrete slice overlaps with any other. toverlap : float An ovelap index: the sum of the values in segment slices that overlaps divided by the entire range of values. This index is only computed for numerical types. """ ranges = where.ranges[:] nslices = self.nslices if self.nelementsILR > 0: # Add the ranges corresponding to the last row rangeslr = np.array([self.bebounds[0], self.bebounds[-1]]) ranges = np.concatenate((ranges, [rangeslr])) nslices += 1 noverlaps = 0 soverlap = 0 toverlap = -1 multiplicity = np.zeros(shape=nslices, dtype="int_") for i in range(nslices): for j in range(i + 1, nslices): if ranges[i, 1] > ranges[j, 0]: noverlaps += 1 multiplicity[j - i] += 1 if self.type != "string": # Convert ranges into floats in order to allow # doing operations with them without overflows soverlap += float(ranges[i, 1]) - float(ranges[j, 0]) # Return the overlap as the ratio between overlaps and entire range if self.type != "string": erange = float(ranges[-1, 1]) - float(ranges[0, 0]) # Check that there is an effective range of values # Beware, erange can be negative in situations where # the values are suffering overflow. This can happen # specially on big signed integer values (on overflows, # the end value will become negative!). # Also, there is no way to compute overlap ratios for # non-numerical types. So, be careful and always check # that toverlap has a positive value (it must have been # initialized to -1. before) before using it. # F. Altet 2007-01-19 if erange > 0: toverlap = soverlap / erange if verbose: print("overlaps (%s):" % message, noverlaps, toverlap) print(multiplicity) # For full indexes, set the 'is_csi' flag if self.indsize == 8 and self._v_file._iswritable(): self._v_attrs.is_csi = (noverlaps == 0) # Save the number of overlaps for future references self.noverlaps = noverlaps return (noverlaps, multiplicity, toverlap) def read_sorted_indices(self, what, start, stop, step): """Return the sorted or indices values in the specified range.""" (start, stop, step) = self._process_range(start, stop, step) if start >= stop: return np.empty(0, self.dtype) # Correction for negative values of step (reverse indices) if step < 0: tmp = start start = self.nelements - stop stop = self.nelements - tmp if what == "sorted": values = self.sorted valuesLR = self.sortedLR buffer_ = np.empty(stop - start, dtype=self.dtype) else: values = self.indices valuesLR = self.indicesLR buffer_ = np.empty(stop - start, dtype="u%d" % self.indsize) ss = self.slicesize nrow_start = start // ss istart = start % ss nrow_stop = stop // ss tlen = stop - start bstart = 0 ilen = 0 for nrow in range(nrow_start, nrow_stop + 1): blen = ss - istart if ilen + blen > tlen: blen = tlen - ilen if blen <= 0: break if nrow < self.nslices: self.read_slice( values, nrow, buffer_[bstart:bstart + blen], istart) else: self.read_slice_lr( valuesLR, buffer_[bstart:bstart + blen], istart) istart = 0 bstart += blen ilen += blen return buffer_[::step] def read_sorted(self, start=None, stop=None, step=None): """Return the sorted values of index in the specified range. The meaning of the start, stop and step arguments is the same as in :meth:`Table.read_sorted`. """ return self.read_sorted_indices('sorted', start, stop, step) def read_indices(self, start=None, stop=None, step=None): """Return the indices values of index in the specified range. The meaning of the start, stop and step arguments is the same as in :meth:`Table.read_sorted`. """ return self.read_sorted_indices('indices', start, stop, step) def _process_range(self, start, stop, step): """Get a range specifc for the index usage.""" if start is not None and stop is None: # Special case for the behaviour of PyTables iterators stop = idx2long(start + 1) if start is None: start = 0 else: start = idx2long(start) if stop is None: stop = idx2long(self.nelements) else: stop = idx2long(stop) if step is None: step = 1 else: step = idx2long(step) return (start, stop, step) def __getitem__(self, key): """Return the indices values of index in the specified range. If key argument is an integer, the corresponding index is returned. If key is a slice, the range of indices determined by it is returned. A negative value of step in slice is supported, meaning that the results will be returned in reverse order. This method is equivalent to :meth:`Index.read_indices`. """ if is_idx(key): key = operator.index(key) if key < 0: # To support negative values key += self.nelements return self.read_indices(key, key + 1, 1)[0] elif isinstance(key, slice): return self.read_indices(key.start, key.stop, key.step) def __len__(self): return self.nelements def restorecache(self): """Clean the limits cache and resize starts and lengths arrays""" params = self._v_file.params # The sorted IndexArray is absolutely required to be in memory # at the same time than the Index instance, so create a strong # reference to it. We are not introducing leaks because the # strong reference will disappear when this Index instance is # to be closed. self._sorted = self.sorted self._sorted.boundscache = ObjectCache(params['BOUNDS_MAX_SLOTS'], params['BOUNDS_MAX_SIZE'], 'non-opt types bounds') self.sorted.boundscache = ObjectCache(params['BOUNDS_MAX_SLOTS'], params['BOUNDS_MAX_SIZE'], 'non-opt types bounds') """A cache for the bounds (2nd hash) data. Only used for non-optimized types searches.""" self.limboundscache = ObjectCache(params['LIMBOUNDS_MAX_SLOTS'], params['LIMBOUNDS_MAX_SIZE'], 'bounding limits') """A cache for bounding limits.""" self.sortedLRcache = ObjectCache(params['SORTEDLR_MAX_SLOTS'], params['SORTEDLR_MAX_SIZE'], 'last row chunks') """A cache for the last row chunks. Only used for searches in the last row, and mainly useful for small indexes.""" self.starts = np.empty(shape=self.nrows, dtype=np.int32) self.lengths = np.empty(shape=self.nrows, dtype=np.int32) self.sorted._init_sorted_slice(self) self.dirtycache = False def search(self, item): """Do a binary search in this index for an item.""" if profile: tref = clock() if profile: show_stats("Entering search", tref) if self.dirtycache: self.restorecache() # An empty item or if left limit is larger than the right one # means that the number of records is always going to be empty, # so we avoid further computation (including looking up the # limits cache). if not item or item[0] > item[1]: self.starts[:] = 0 self.lengths[:] = 0 return 0 tlen = 0 # Check whether the item tuple is in the limits cache or not nslot = self.limboundscache.getslot(item) if nslot >= 0: startlengths = self.limboundscache.getitem(nslot) # Reset the lengths array (not necessary for starts) self.lengths[:] = 0 # Now, set the interesting rows for nrow2, start, length in startlengths: self.starts[nrow2] = start self.lengths[nrow2] = length tlen = tlen + length return tlen # The item is not in cache. Do the real lookup. sorted = self.sorted if self.nslices > 0: if self.type in self.opt_search_types: # The next are optimizations. However, they hide the # CPU functions consumptions from python profiles. # You may want to de-activate them during profiling. if self.type == "int32": tlen = sorted._search_bin_na_i(*item) elif self.type == "int64": tlen = sorted._search_bin_na_ll(*item) elif self.type == "float16": tlen = sorted._search_bin_na_e(*item) elif self.type == "float32": tlen = sorted._search_bin_na_f(*item) elif self.type == "float64": tlen = sorted._search_bin_na_d(*item) elif self.type == "float96": tlen = sorted._search_bin_na_g(*item) elif self.type == "float128": tlen = sorted._search_bin_na_g(*item) elif self.type == "uint32": tlen = sorted._search_bin_na_ui(*item) elif self.type == "uint64": tlen = sorted._search_bin_na_ull(*item) elif self.type == "int8": tlen = sorted._search_bin_na_b(*item) elif self.type == "int16": tlen = sorted._search_bin_na_s(*item) elif self.type == "uint8": tlen = sorted._search_bin_na_ub(*item) elif self.type == "uint16": tlen = sorted._search_bin_na_us(*item) else: assert False, "This can't happen!" else: tlen = self.search_scalar(item, sorted) # Get possible remaining values in last row if self.nelementsSLR > 0: # Look for more indexes in the last row (start, stop) = self.search_last_row(item) self.starts[-1] = start self.lengths[-1] = stop - start tlen += stop - start if self.limboundscache.couldenablecache(): # Get a startlengths tuple and save it in cache. # This is quite slow, but it is a good way to compress # the bounds info. Moreover, the .couldenablecache() # is doing a good work so as to avoid computing this # when it is not necessary to do it. startlengths = [] for nrow, length in enumerate(self.lengths): if length > 0: startlengths.append((nrow, self.starts[nrow], length)) # Compute the size of the recarray (aproximately) # The +1 at the end is important to avoid 0 lengths # (remember, the object headers take some space) size = len(startlengths) * 8 * 2 + 1 # Put this startlengths list in cache self.limboundscache.setitem(item, startlengths, size) if profile: show_stats("Exiting search", tref) return tlen # This is an scalar version of search. It works with strings as well. def search_scalar(self, item, sorted): """Do a binary search in this index for an item.""" tlen = 0 # Do the lookup for values fullfilling the conditions for i in range(self.nslices): (start, stop) = sorted._search_bin(i, item) self.starts[i] = start self.lengths[i] = stop - start tlen += stop - start return tlen def search_last_row(self, item): # Variable initialization item1, item2 = item bebounds = self.bebounds b0, b1 = bebounds[0], bebounds[-1] bounds = bebounds[1:-1] itemsize = self.dtype.itemsize sortedLRcache = self.sortedLRcache hi = self.nelementsSLR # maximum number of elements rchunksize = self.chunksize // self.reduction nchunk = -1 # Lookup for item1 if nan_aware_gt(item1, b0): if nan_aware_le(item1, b1): # Search the appropriate chunk in bounds cache nchunk = bisect_left(bounds, item1) # Lookup for this chunk in cache nslot = sortedLRcache.getslot(nchunk) if nslot >= 0: chunk = sortedLRcache.getitem(nslot) else: begin = rchunksize * nchunk end = rchunksize * (nchunk + 1) if end > hi: end = hi # Read the chunk from disk chunk = self.sortedLR._read_sorted_slice( self.sorted, begin, end) # Put it in cache. It's important to *copy* # the buffer, as it is reused in future reads! sortedLRcache.setitem(nchunk, chunk.copy(), (end - begin) * itemsize) start = bisect_left(chunk, item1) start += rchunksize * nchunk else: start = hi else: start = 0 # Lookup for item2 if nan_aware_ge(item2, b0): if nan_aware_lt(item2, b1): # Search the appropriate chunk in bounds cache nchunk2 = bisect_right(bounds, item2) if nchunk2 != nchunk: # Lookup for this chunk in cache nslot = sortedLRcache.getslot(nchunk2) if nslot >= 0: chunk = sortedLRcache.getitem(nslot) else: begin = rchunksize * nchunk2 end = rchunksize * (nchunk2 + 1) if end > hi: end = hi # Read the chunk from disk chunk = self.sortedLR._read_sorted_slice( self.sorted, begin, end) # Put it in cache. It's important to *copy* # the buffer, as it is reused in future reads! # See bug #60 in xot.carabos.com sortedLRcache.setitem(nchunk2, chunk.copy(), (end - begin) * itemsize) stop = bisect_right(chunk, item2) stop += rchunksize * nchunk2 else: stop = hi else: stop = 0 return (start, stop) def get_chunkmap(self): """Compute a map with the interesting chunks in index.""" if profile: tref = clock() if profile: show_stats("Entering get_chunkmap", tref) ss = self.slicesize nsb = self.nslicesblock nslices = self.nslices lbucket = self.lbucket indsize = self.indsize bucketsinblock = self.blocksize / lbucket nchunks = math.ceil(self.nelements / lbucket) chunkmap = np.zeros(shape=nchunks, dtype="bool") reduction = self.reduction starts = (self.starts - 1) * reduction + 1 stops = (self.starts + self.lengths) * reduction starts[starts < 0] = 0 # All negative values set to zero indices = self.indices for nslice in range(self.nrows): start = starts[nslice] stop = stops[nslice] if stop > start: idx = np.empty(shape=stop - start, dtype='u%d' % indsize) if nslice < nslices: indices._read_index_slice(nslice, start, stop, idx) else: self.indicesLR._read_index_slice(start, stop, idx) if indsize == 8: idx //= lbucket elif indsize == 2: # The chunkmap size cannot be never larger than 'int_' idx = idx.astype("int_") offset = int((nslice // nsb) * bucketsinblock) idx += offset elif indsize == 1: # The chunkmap size cannot be never larger than 'int_' idx = idx.astype("int_") offset = (nslice * ss) // lbucket idx += offset chunkmap[idx] = True # The case lbucket < nrowsinchunk should only happen in tests nrowsinchunk = self.nrowsinchunk if lbucket != nrowsinchunk: # Map the 'coarse grain' chunkmap into the 'true' chunkmap nelements = self.nelements tnchunks = math.ceil(nelements / nrowsinchunk) tchunkmap = np.zeros(shape=tnchunks, dtype="bool") ratio = lbucket / nrowsinchunk idx = chunkmap.nonzero()[0] starts = (idx * ratio).astype('int_') stops = np.ceil((idx + 1) * ratio).astype('int_') for start, stop in zip(starts, stops): tchunkmap[start:stop] = True chunkmap = tchunkmap if profile: show_stats("Exiting get_chunkmap", tref) return chunkmap def get_lookup_range(self, ops, limits): assert len(ops) in [1, 2] assert len(limits) in [1, 2] assert len(ops) == len(limits) column = self.column coldtype = column.dtype.base itemsize = coldtype.itemsize if len(limits) == 1: assert ops[0] in ['lt', 'le', 'eq', 'ge', 'gt'] limit = limits[0] op = ops[0] if op == 'lt': range_ = (inftype(coldtype, itemsize, sign=-1), nextafter(limit, -1, coldtype, itemsize)) elif op == 'le': range_ = (inftype(coldtype, itemsize, sign=-1), limit) elif op == 'gt': range_ = (nextafter(limit, +1, coldtype, itemsize), inftype(coldtype, itemsize, sign=+1)) elif op == 'ge': range_ = (limit, inftype(coldtype, itemsize, sign=+1)) elif op == 'eq': range_ = (limit, limit) elif len(limits) == 2: assert ops[0] in ('gt', 'ge') and ops[1] in ('lt', 'le') lower, upper = limits if lower > upper: # ``a <[=] x <[=] b`` is always false if ``a > b``. return () if ops == ('gt', 'lt'): # lower < col < upper range_ = (nextafter(lower, +1, coldtype, itemsize), nextafter(upper, -1, coldtype, itemsize)) elif ops == ('ge', 'lt'): # lower <= col < upper range_ = (lower, nextafter(upper, -1, coldtype, itemsize)) elif ops == ('gt', 'le'): # lower < col <= upper range_ = (nextafter(lower, +1, coldtype, itemsize), upper) elif ops == ('ge', 'le'): # lower <= col <= upper range_ = (lower, upper) return range_ def _f_remove(self, recursive=False): """Remove this Index object.""" # Index removal is always recursive, # no matter what `recursive` says. super()._f_remove(True) def __str__(self): """This provides a more compact representation than __repr__""" # The filters filters = [] if self.filters.complevel: if self.filters.shuffle: filters.append('shuffle') if self.filters.bitshuffle: filters.append('bitshuffle') filters.append(f'{self.filters.complib}({self.filters.complevel})') return (f"Index({self.optlevel}, " f"{self.kind}{', '.join(filters)}).is_csi={self.is_csi}") def __repr__(self): """This provides more metainfo than standard __repr__""" cpathname = f"{self.table._v_pathname}.cols.{self.column.pathname}" retstr = f"""{self._v_pathname} (Index for column {cpathname}) optlevel := {self.optlevel} kind := {self.kind} filters := {self.filters} is_csi := {self.is_csi} nelements := {self.nelements} chunksize := {self.chunksize} slicesize := {self.slicesize} blocksize := {self.blocksize} superblocksize := {self.superblocksize} dirty := {self.dirty} byteorder := {self.byteorder!r} sorted := {self.sorted} indices := {self.indices} ranges := {self.ranges} bounds := {self.bounds} sortedLR := {self.sortedLR} indicesLR := {self.indicesLR}""" return retstr class IndexesDescG(NotLoggedMixin, Group): _c_classid = 'DINDEX' def _g_width_warning(self): warnings.warn( "the number of indexed columns on a single description group " "is exceeding the recommended maximum (%d); " "be ready to see PyTables asking for *lots* of memory " "and possibly slow I/O" % self._v_max_group_width, PerformanceWarning) class IndexesTableG(NotLoggedMixin, Group): _c_classid = 'TINDEX' @property def auto(self): if 'AUTO_INDEX' not in self._v_attrs: return default_auto_index return self._v_attrs.AUTO_INDEX @auto.setter def auto(self, auto): self._v_attrs.AUTO_INDEX = bool(auto) @auto.deleter def auto(self): del self._v_attrs.AUTO_INDEX def _g_width_warning(self): warnings.warn( "the number of indexed columns on a single table " "is exceeding the recommended maximum (%d); " "be ready to see PyTables asking for *lots* of memory " "and possibly slow I/O" % self._v_max_group_width, PerformanceWarning) def _g_check_name(self, name): if not name.startswith('_i_'): raise ValueError( "names of index groups must start with ``_i_``: %s" % name) @property def table(self): """Accessor for the `Table` object of this `IndexesTableG` container.""" names = self._v_pathname.split("/") tablename = names.pop()[3:] # "_i_" is at the beginning parentpathname = "/".join(names) tablepathname = join_path(parentpathname, tablename) table = self._v_file._get_node(tablepathname) return table class OldIndex(NotLoggedMixin, Group): """This is meant to hide indexes of PyTables 1.x files.""" _c_classid = 'CINDEX' PyTables-3.7.0/tables/indexes.py000066400000000000000000000133741416254111300165350ustar00rootroot00000000000000"""Here is defined the IndexArray class.""" from bisect import bisect_left, bisect_right from .node import NotLoggedMixin from .carray import CArray from .earray import EArray from . import indexesextension # Declarations for inheriting class CacheArray(indexesextension.CacheArray, NotLoggedMixin, EArray): """Container for keeping index caches of 1st and 2nd level.""" # Class identifier. _c_classid = 'CACHEARRAY' class LastRowArray(indexesextension.LastRowArray, NotLoggedMixin, CArray): """Container for keeping sorted and indices values of last row of an index.""" # Class identifier. _c_classid = 'LASTROWARRAY' class IndexArray(indexesextension.IndexArray, NotLoggedMixin, EArray): """Represent the index (sorted or reverse index) dataset in HDF5 file. All NumPy typecodes are supported except for complex datatypes. Parameters ---------- parentnode The Index class from which this object will hang off. .. versionchanged:: 3.0 Renamed from *parentNode* to *parentnode*. name : str The name of this node in its parent group. atom An Atom object representing the shape and type of the atomic objects to be saved. Only scalar atoms are supported. title Sets a TITLE attribute on the array entity. filters : Filters An instance of the Filters class that provides information about the desired I/O filters to be applied during the life of this object. byteorder The byteroder of the data on-disk. """ # Class identifier. _c_classid = 'INDEXARRAY' @property def chunksize(self): """The chunksize for this object.""" return self.chunkshape[1] @property def slicesize(self): """The slicesize for this object.""" return self.shape[1] def __init__(self, parentnode, name, atom=None, title="", filters=None, byteorder=None): """Create an IndexArray instance.""" self._v_pathname = parentnode._g_join(name) if atom is not None: # The shape and chunkshape needs to be fixed here if name == "sorted": reduction = parentnode.reduction shape = (0, parentnode.slicesize // reduction) chunkshape = (1, parentnode.chunksize // reduction) else: shape = (0, parentnode.slicesize) chunkshape = (1, parentnode.chunksize) else: # The shape and chunkshape will be read from disk later on shape = None chunkshape = None super().__init__( parentnode, name, atom, shape, title, filters, chunkshape=chunkshape, byteorder=byteorder) # This version of searchBin uses both ranges (1st level) and # bounds (2nd level) caches. It uses a cache for boundary rows, # but not for 'sorted' rows (this is only supported for the # 'optimized' types). def _search_bin(self, nrow, item): item1, item2 = item result1 = -1 result2 = -1 hi = self.shape[1] ranges = self._v_parent.rvcache boundscache = self.boundscache # First, look at the beginning of the slice begin = ranges[nrow, 0] # Look for items at the beginning of sorted slices if item1 <= begin: result1 = 0 if item2 < begin: result2 = 0 if result1 >= 0 and result2 >= 0: return (result1, result2) # Then, look for items at the end of the sorted slice end = ranges[nrow, 1] if result1 < 0: if item1 > end: result1 = hi if result2 < 0: if item2 >= end: result2 = hi if result1 >= 0 and result2 >= 0: return (result1, result2) # Finally, do a lookup for item1 and item2 if they were not found # Lookup in the middle of slice for item1 chunksize = self.chunksize # Number of elements/chunksize nchunk = -1 # Try to get the bounds row from the LRU cache nslot = boundscache.getslot(nrow) if nslot >= 0: # Cache hit. Use the row kept there. bounds = boundscache.getitem(nslot) else: # No luck with cached data. Read the row and put it in the cache. bounds = self._v_parent.bounds[nrow] size = bounds.size * bounds.itemsize boundscache.setitem(nrow, bounds, size) if result1 < 0: # Search the appropriate chunk in bounds cache nchunk = bisect_left(bounds, item1) chunk = self._read_sorted_slice(nrow, chunksize * nchunk, chunksize * (nchunk + 1)) result1 = indexesextension._bisect_left(chunk, item1, chunksize) result1 += chunksize * nchunk # Lookup in the middle of slice for item2 if result2 < 0: # Search the appropriate chunk in bounds cache nchunk2 = bisect_right(bounds, item2) if nchunk2 != nchunk: chunk = self._read_sorted_slice(nrow, chunksize * nchunk2, chunksize * (nchunk2 + 1)) result2 = indexesextension._bisect_right(chunk, item2, chunksize) result2 += chunksize * nchunk2 return (result1, result2) def __str__(self): """A compact representation of this class""" return f"IndexArray(path={self._v_pathname})" def __repr__(self): """A verbose representation of this class.""" return f"""{self} atom = {self.atom!r} shape = {self.shape} nrows = {self.nrows} chunksize = {self.chunksize} slicesize = {self.slicesize} byteorder = {self.byteorder!r}""" PyTables-3.7.0/tables/indexesextension.pyx000066400000000000000000001375771416254111300206760ustar00rootroot00000000000000######################################################################## # # License: BSD # Created: May 18, 2006 # Author: Francesc Alted - faltet@pytables.com # # $Id$ # ######################################################################## """cython interface for keeping indexes classes. Classes (type extensions): IndexArray CacheArray LastRowArray Functions: keysort Misc variables: """ import cython import numpy cimport numpy as cnp from .exceptions import HDF5ExtError from .hdf5extension cimport Array # Types, constants, functions, classes & other objects from everywhere from numpy cimport (import_array, ndarray, npy_int8, npy_int16, npy_int32, npy_int64, npy_uint8, npy_uint16, npy_uint32, npy_uint64, npy_float32, npy_float64, npy_float, npy_double, npy_longdouble, PyArray_BYTES, PyArray_DATA) # These two types are defined in npy_common.h but not in cython's numpy.pxd ctypedef unsigned char npy_bool ctypedef npy_uint16 npy_float16 from libc.stdlib cimport malloc, free from libc.string cimport memcpy, strncmp from .definitions cimport hid_t, herr_t, hsize_t, H5Screate_simple, H5Sclose from .lrucacheextension cimport NumCache #------------------------------------------------------------------- # External C functions # Functions for optimized operations with ARRAY for indexing purposes cdef extern from "H5ARRAY-opt.h" nogil: herr_t H5ARRAYOinit_readSlice( hid_t dataset_id, hid_t *mem_space_id, hsize_t count) herr_t H5ARRAYOread_readSlice( hid_t dataset_id, hid_t type_id, hsize_t irow, hsize_t start, hsize_t stop, void *data) herr_t H5ARRAYOread_readSortedSlice( hid_t dataset_id, hid_t mem_space_id, hid_t type_id, hsize_t irow, hsize_t start, hsize_t stop, void *data) herr_t H5ARRAYOread_readBoundsSlice( hid_t dataset_id, hid_t mem_space_id, hid_t type_id, hsize_t irow, hsize_t start, hsize_t stop, void *data) herr_t H5ARRAYOreadSliceLR( hid_t dataset_id, hid_t type_id, hsize_t start, hsize_t stop, void *data) # Functions for optimized operations for dealing with indexes cdef extern from "idx-opt.h" nogil: int bisect_left_b(npy_int8 *a, long x, int hi, int offset) int bisect_left_ub(npy_uint8 *a, long x, int hi, int offset) int bisect_right_b(npy_int8 *a, long x, int hi, int offset) int bisect_right_ub(npy_uint8 *a, long x, int hi, int offset) int bisect_left_s(npy_int16 *a, long x, int hi, int offset) int bisect_left_us(npy_uint16 *a, long x, int hi, int offset) int bisect_right_s(npy_int16 *a, long x, int hi, int offset) int bisect_right_us(npy_uint16 *a, long x, int hi, int offset) int bisect_left_i(npy_int32 *a, long x, int hi, int offset) int bisect_left_ui(npy_uint32 *a, npy_uint32 x, int hi, int offset) int bisect_right_i(npy_int32 *a, long x, int hi, int offset) int bisect_right_ui(npy_uint32 *a, npy_uint32 x, int hi, int offset) int bisect_left_ll(npy_int64 *a, npy_int64 x, int hi, int offset) int bisect_left_ull(npy_uint64 *a, npy_uint64 x, int hi, int offset) int bisect_right_ll(npy_int64 *a, npy_int64 x, int hi, int offset) int bisect_right_ull(npy_uint64 *a, npy_uint64 x, int hi, int offset) int bisect_left_e(npy_float16 *a, npy_float64 x, int hi, int offset) int bisect_right_e(npy_float16 *a, npy_float64 x, int hi, int offset) int bisect_left_f(npy_float32 *a, npy_float64 x, int hi, int offset) int bisect_right_f(npy_float32 *a, npy_float64 x, int hi, int offset) int bisect_left_d(npy_float64 *a, npy_float64 x, int hi, int offset) int bisect_right_d(npy_float64 *a, npy_float64 x, int hi, int offset) int bisect_left_g(npy_longdouble *a, npy_longdouble x, int hi, int offset) int bisect_right_g(npy_longdouble *a, npy_longdouble x, int hi, int offset) #---------------------------------------------------------------------------- # Initialization code # The numpy API requires this function to be called before # using any numpy facilities in an extension module. import_array() #--------------------------------------------------------------------------- ctypedef fused floating_type: npy_float32 npy_float64 npy_longdouble ctypedef fused number_type: npy_int8 npy_int16 npy_int32 npy_int64 npy_uint8 npy_uint16 npy_uint32 npy_uint64 npy_float32 npy_float64 npy_longdouble #=========================================================================== # Functions #=========================================================================== #--------------------------------------------------------------------------- # keysort #--------------------------------------------------------------------------- DEF PYA_QS_STACK = 100 DEF SMALL_QUICKSORT = 15 def keysort(ndarray array1, ndarray array2): """Sort array1 in-place. array2 is also sorted following the array1 order. array1 can be of any type, except complex or string. array2 may be made of elements on any size. """ cdef size_t size = cnp.PyArray_SIZE(array1) cdef size_t elsize1 = cnp.PyArray_ITEMSIZE(array1) cdef size_t elsize2 = cnp.PyArray_ITEMSIZE(array2) cdef int type_num = cnp.PyArray_TYPE(array1) # floating types if type_num == cnp.NPY_FLOAT16: _keysort[npy_float16](PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size) elif type_num == cnp.NPY_FLOAT32: _keysort[npy_float32](PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size) elif type_num == cnp.NPY_FLOAT64: _keysort[npy_float64](PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size) elif type_num == cnp.NPY_LONGDOUBLE: _keysort[npy_longdouble](PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size) # signed integer types elif type_num == cnp.NPY_INT8: _keysort[npy_int8](PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size) elif type_num == cnp.NPY_INT16: _keysort[npy_int16](PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size) elif type_num == cnp.NPY_INT32: _keysort[npy_int32](PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size) elif type_num == cnp.NPY_INT64: _keysort[npy_int64](PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size) # unsigned integer types elif type_num == cnp.NPY_UINT8: _keysort[npy_uint8](PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size) elif type_num == cnp.NPY_UINT16: _keysort[npy_uint16](PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size) elif type_num == cnp.NPY_UINT32: _keysort[npy_uint32](PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size) elif type_num == cnp.NPY_UINT64: _keysort[npy_uint64](PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size) # other elif type_num == cnp.NPY_BOOL: _keysort[npy_bool](PyArray_DATA(array1), PyArray_BYTES(array2), elsize2, size) elif type_num == cnp.NPY_STRING: _keysort_string(PyArray_BYTES(array1), elsize1, PyArray_BYTES(array2), elsize2, size) else: raise ValueError("Unknown array datatype") cdef inline void swap_bytes(char *x, char *y, size_t n) nogil: if n == 8: (x)[0], (y)[0] = (y)[0], (x)[0] elif n == 4: (x)[0], (y)[0] = (y)[0], (x)[0] elif n == 2: (x)[0], (y)[0] = (y)[0], (x)[0] else: for i in range(n): x[i], y[i] = y[i], x[i] cdef inline int less_than(number_type* a, number_type* b) nogil: if number_type in floating_type: return a[0] < b[0] or (b[0] != b[0] and a[0] == a[0]) else: return a[0] < b[0] @cython.cdivision(True) cdef void _keysort(number_type* start1, char* start2, size_t elsize2, size_t n) nogil: cdef number_type *pl = start1 cdef number_type *pr = start1 + (n - 1) cdef char *ipl = start2 cdef char *ipr = start2 + (n - 1) * elsize2 cdef number_type vp cdef char *ivp = malloc(elsize2) cdef number_type *stack[PYA_QS_STACK] cdef number_type **sptr = stack cdef char *istack[PYA_QS_STACK] cdef char **isptr = istack cdef size_t stack_index = 0 cdef number_type *pm cdef number_type *pi cdef number_type *pj cdef number_type *pt cdef char *ipm cdef char *ipi cdef char *ipj cdef char *ipt while True: while pr - pl > SMALL_QUICKSORT: pm = pl + ((pr - pl) >> 1) ipm = ipl + ((ipr - ipl)//elsize2 >> 1)*elsize2 if less_than(pm, pl): pm[0], pl[0] = pl[0], pm[0] swap_bytes(ipm, ipl, elsize2) if less_than(pr, pm): pr[0], pm[0] = pm[0], pr[0] swap_bytes(ipr, ipm, elsize2) if less_than(pm, pl): pm[0], pl[0] = pl[0], pm[0] swap_bytes(ipm, ipl, elsize2) vp = pm[0] pi = pl ipi = ipl pj = pr - 1 ipj = ipr - elsize2 pm[0], pj[0] = pj[0], pm[0] swap_bytes(ipm, ipj, elsize2) while True: pi += 1 ipi += elsize2 while less_than(pi, &vp): pi += 1 ipi += elsize2 pj -= 1 ipj -= elsize2 while less_than(&vp, pj): pj -= 1 ipj -= elsize2 if pi >= pj: break pi[0], pj[0] = pj[0], pi[0] swap_bytes(ipi, ipj, elsize2) pi[0], (pr-1)[0] = (pr-1)[0], pi[0] swap_bytes(ipi, ipr-elsize2, elsize2) # push largest partition on stack and proceed with the other if (pi - pl) < (pr - pi): sptr[0] = pi + 1 sptr[1] = pr sptr += 2 isptr[0] = ipi + elsize2 isptr[1] = ipr isptr += 2 pr = pi - 1 ipr = ipi - elsize2 else: sptr[0] = pl sptr[1] = pi - 1 sptr += 2 isptr[0] = ipl isptr[1] = ipi - elsize2 isptr += 2 pl = pi + 1 ipl = ipi + elsize2 pi = pl + 1 ipi = ipl + elsize2 while pi <= pr: vp = pi[0] memcpy(ivp, ipi, elsize2) pj = pi pt = pi - 1 ipj = ipi ipt = ipi - elsize2 while pj > pl and less_than(&vp, pt): pj[0] = pt[0] pj -= 1 pt -= 1 memcpy(ipj, ipt, elsize2) ipj -= elsize2 ipt -= elsize2 pj[0] = vp memcpy(ipj, ivp, elsize2) pi += 1 ipi += elsize2 if sptr == stack: break sptr -= 2 pl = sptr[0] pr = sptr[1] isptr -= 2 ipl = isptr[0] ipr = isptr[1] free(ivp) @cython.cdivision(True) cdef void _keysort_string(char* start1, size_t ss, char* start2, size_t ts, size_t n) nogil: cdef char *pl = start1 cdef char *pr = start1 + (n - 1) * ss cdef char *ipl = start2 cdef char *ipr = start2 + (n - 1) * ts cdef char *vp = malloc(ss) cdef char *ivp = malloc(ts) cdef char *stack[PYA_QS_STACK] cdef char **sptr = stack cdef char *istack[PYA_QS_STACK] cdef char **isptr = istack cdef size_t stack_index = 0 cdef char *pm cdef char *pi cdef char *pj cdef char *pt cdef char *ipm cdef char *ipi cdef char *ipj cdef char *ipt while True: while pr - pl > SMALL_QUICKSORT * ss: pm = pl + ((pr - pl)//ss >> 1)*ss ipm = ipl + ((ipr - ipl)//ts >> 1)*ts if strncmp(pm, pl, ss) < 0: swap_bytes(pm, pl, ss) swap_bytes(ipm, ipl, ts) if strncmp(pr, pm, ss) < 0: swap_bytes(pr, pm, ss) swap_bytes(ipr, ipm, ts) if strncmp(pm, pl, ss) < 0: swap_bytes(pm, pl, ss) swap_bytes(ipm, ipl, ts) memcpy(vp, pm, ss) pi = pl ipi = ipl pj = pr - ss ipj = ipr - ts swap_bytes(pm, pj, ss) swap_bytes(ipm, ipj, ts) while True: pi += ss ipi += ts while strncmp(pi, vp, ss) < 0: pi += ss ipi += ts pj -= ss ipj -= ts while strncmp(vp, pj, ss) < 0: pj -= ss ipj -= ts if pi >= pj: break swap_bytes(pi, pj, ss) swap_bytes(ipi, ipj, ts) swap_bytes(pi, pr-ss, ss) swap_bytes(ipi, ipr-ts, ts) # push largest partition on stack and proceed with the other if (pi - pl) < (pr - pi): sptr[0] = pi + ss sptr[1] = pr sptr += 2 isptr[0] = ipi + ts isptr[1] = ipr isptr += 2 pr = pi - ss ipr = ipi - ts else: sptr[0] = pl sptr[1] = pi - ss sptr += 2 isptr[0] = ipl isptr[1] = ipi - ts isptr += 2 pl = pi + ss ipl = ipi + ts pi = pl + ss ipi = ipl + ts while pi <= pr: memcpy(vp, pi, ss) memcpy(ivp, ipi, ts) pj = pi pt = pi - ss ipj = ipi ipt = ipi - ts while pj > pl and strncmp(vp, pt, ss) < 0: memcpy(pj, pt, ss) pj -= ss pt -= ss memcpy(ipj, ipt, ts) ipj -= ts ipt -= ts memcpy(pj, vp, ss) memcpy(ipj, ivp, ts) pi += ss ipi += ts if sptr == stack: break sptr -= 2 pl = sptr[0] pr = sptr[1] isptr -= 2 ipl = isptr[0] ipr = isptr[1] free(vp) free(ivp) #--------------------------------------------------------------------------- # bisect #--------------------------------------------------------------------------- # This has been copied from the standard module bisect. # Checks for the values out of limits has been added at the beginning # because I forsee that this should be a very common case. # 2004-05-20 def _bisect_left(a, x, int hi): """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e < x, and all e in a[i:] have e >= x. So if x already appears in the list, i points just before the leftmost x already there. """ cdef int lo, mid lo = 0 if x <= a[0]: return 0 if a[-1] < x: return hi while lo < hi: mid = (lo+hi)//2 if a[mid] < x: lo = mid+1 else: hi = mid return lo def _bisect_right(a, x, int hi): """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e <= x, and all e in a[i:] have e > x. So if x already appears in the list, i points just beyond the rightmost x already there. """ cdef int lo, mid lo = 0 if x < a[0]: return 0 if a[-1] <= x: return hi while lo < hi: mid = (lo+hi)//2 if x < a[mid]: hi = mid else: lo = mid+1 return lo #=========================================================================== # Classes #=========================================================================== cdef class Index: pass cdef class CacheArray(Array): """Container for keeping index caches of 1st and 2nd level.""" cdef hid_t mem_space_id cdef initread(self, int nbounds): # "Actions to accelerate the reads afterwards." # Precompute the mem_space_id if (H5ARRAYOinit_readSlice(self.dataset_id, &self.mem_space_id, nbounds) < 0): raise HDF5ExtError("Problems initializing the bounds array data.") return cdef read_slice(self, hsize_t nrow, hsize_t start, hsize_t stop, void *rbuf): # "Read an slice of bounds." if (H5ARRAYOread_readBoundsSlice( self.dataset_id, self.mem_space_id, self.type_id, nrow, start, stop, rbuf) < 0): raise HDF5ExtError("Problems reading the bounds array data.") return def _g_close(self): super()._g_close() # Release specific resources of this class if self.mem_space_id > 0: H5Sclose(self.mem_space_id) cdef class IndexArray(Array): """Container for keeping sorted and indices values.""" cdef void *rbufst cdef void *rbufln cdef void *rbufrv cdef void *rbufbc cdef void *rbuflb cdef hid_t mem_space_id cdef int l_chunksize, l_slicesize, nbounds, indsize cdef CacheArray bounds_ext cdef NumCache boundscache, sortedcache cdef ndarray bufferbc, bufferlb def _read_index_slice(self, hsize_t irow, hsize_t start, hsize_t stop, ndarray idx): cdef herr_t ret cdef void *buf = PyArray_DATA(idx) # Do the physical read with nogil: ret = H5ARRAYOread_readSlice(self.dataset_id, self.type_id, irow, start, stop, buf) if ret < 0: raise HDF5ExtError("Problems reading the index indices.") def _init_sorted_slice(self, index): """Initialize the structures for doing a binary search.""" cdef long ndims cdef int rank, buflen, cachesize cdef char *bname cdef hsize_t count[2] cdef ndarray starts, lengths, rvcache cdef object maxslots, rowsize dtype = self.atom.dtype # Create the buffer for reading sorted data chunks if not created yet if self.bufferlb is None: # Internal buffers self.bufferlb = numpy.empty(dtype=dtype, shape=self.chunksize) # Get the pointers to the different buffer data areas self.rbuflb = PyArray_DATA(self.bufferlb) # Init structures for accelerating sorted array reads rank = 2 count[0] = 1 count[1] = self.chunksize self.mem_space_id = H5Screate_simple(rank, count, NULL) # Cache some counters in local extension variables self.l_chunksize = self.chunksize self.l_slicesize = self.slicesize # Get the addresses of buffer data starts = index.starts lengths = index.lengths self.rbufst = PyArray_DATA(starts) self.rbufln = PyArray_DATA(lengths) # The 1st cache is loaded completely in memory and needs to be reloaded rvcache = index.ranges[:] self.rbufrv = PyArray_DATA(rvcache) index.rvcache = rvcache # Init the bounds array for reading self.nbounds = index.bounds.shape[1] self.bounds_ext = index.bounds self.bounds_ext.initread(self.nbounds) if str(dtype) in self._v_parent.opt_search_types: # The next caches should be defined only for optimized search types. # The 2nd level cache will replace the already existing ObjectCache and # already bound to the boundscache attribute. This way, the cache will # not be duplicated (I know, this smells badly, but anyway). params = self._v_file.params rowsize = (self.bounds_ext._v_chunkshape[1] * dtype.itemsize) maxslots = params['BOUNDS_MAX_SIZE'] // rowsize self.boundscache = NumCache( (maxslots, self.nbounds), dtype, 'non-opt types bounds') self.bufferbc = numpy.empty(dtype=dtype, shape=self.nbounds) # Get the pointer for the internal buffer for 2nd level cache self.rbufbc = PyArray_DATA(self.bufferbc) # Another NumCache for the sorted values rowsize = (self.chunksize*dtype.itemsize) maxslots = params['SORTED_MAX_SIZE'] // (self.chunksize*dtype.itemsize) self.sortedcache = NumCache( (maxslots, self.chunksize), dtype, 'sorted') cdef void *_g_read_sorted_slice(self, hsize_t irow, hsize_t start, hsize_t stop): """Read the sorted part of an index.""" with nogil: ret = H5ARRAYOread_readSortedSlice( self.dataset_id, self.mem_space_id, self.type_id, irow, start, stop, self.rbuflb) if ret < 0: raise HDF5ExtError("Problems reading the array data.") return self.rbuflb # can't time machine since this function is cdef'd #_g_read_sorted_slice = prveious_api(_g_read_sorted_slice) # This is callable from python def _read_sorted_slice(self, hsize_t irow, hsize_t start, hsize_t stop): """Read the sorted part of an index.""" self._g_read_sorted_slice(irow, start, stop) return self.bufferlb cdef void *get_lru_bounds(self, int nrow, int nbounds): """Get the bounds from the cache, or read them.""" cdef void *vpointer cdef long nslot nslot = self.boundscache.getslot_(nrow) if nslot >= 0: vpointer = self.boundscache.getitem1_(nslot) else: # Bounds row is not in cache. Read it and put it in the LRU cache. self.bounds_ext.read_slice(nrow, 0, nbounds, self.rbufbc) self.boundscache.setitem_(nrow, self.rbufbc, 0) vpointer = self.rbufbc return vpointer # can't time machine since get_lru_bounds() function is cdef'd cdef void *get_lru_sorted(self, int nrow, int ncs, int nchunk, int cs): """Get the sorted row from the cache or read it.""" cdef void *vpointer cdef npy_int64 nckey cdef long nslot cdef hsize_t start, stop # Compute the number of chunk read and use it as the key for the cache. nckey = nrow*ncs+nchunk nslot = self.sortedcache.getslot_(nckey) if nslot >= 0: vpointer = self.sortedcache.getitem1_(nslot) else: # The sorted chunk is not in cache. Read it and put it in the LRU cache. start = cs*nchunk stop = cs*(nchunk+1) vpointer = self._g_read_sorted_slice(nrow, start, stop) self.sortedcache.setitem_(nckey, vpointer, 0) return vpointer # can't time machine since get_lru_sorted() function is cdef'd # Optimized version for int8 def _search_bin_na_b(self, long item1, long item2): cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow cdef int start, stop, tlength, length, bread, nchunk, nchunk2 cdef int *rbufst cdef int *rbufln # Variables with specific type cdef npy_int8 *rbufrv cdef npy_int8 *rbufbc = NULL cdef npy_int8 *rbuflb = NULL cs = self.l_chunksize ss = self.l_slicesize ncs = ss // cs nbounds = self.nbounds nrows = self.nrows rbufst = self.rbufst rbufln = self.rbufln rbufrv = self.rbufrv tlength = 0 for nrow from 0 <= nrow < nrows: rvrow = nrow*2 bread = 0 nchunk = -1 # Look if item1 is in this row if item1 > rbufrv[rvrow]: if item1 <= rbufrv[rvrow+1]: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) bread = 1 nchunk = bisect_left_b(rbufbc, item1, nbounds, 0) # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk, cs) start = bisect_left_b(rbuflb, item1, cs, 0) + cs*nchunk else: start = ss else: start = 0 # Now, for item2 if item2 >= rbufrv[rvrow]: if item2 < rbufrv[rvrow+1]: if not bread: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) nchunk2 = bisect_right_b(rbufbc, item2, nbounds, 0) if nchunk2 <> nchunk: # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk2, cs) stop = bisect_right_b(rbuflb, item2, cs, 0) + cs*nchunk2 else: stop = ss else: stop = 0 length = stop - start tlength = tlength + length rbufst[nrow] = start rbufln[nrow] = length return tlength # Optimized version for uint8 def _search_bin_na_ub(self, long item1, long item2): cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow cdef int start, stop, tlength, length, bread, nchunk, nchunk2 cdef int *rbufst cdef int *rbufln # Variables with specific type cdef npy_uint8 *rbufrv cdef npy_uint8 *rbufbc = NULL cdef npy_uint8 *rbuflb = NULL cs = self.l_chunksize ss = self.l_slicesize ncs = ss // cs nbounds = self.nbounds nrows = self.nrows rbufst = self.rbufst rbufln = self.rbufln rbufrv = self.rbufrv tlength = 0 for nrow from 0 <= nrow < nrows: rvrow = nrow*2 bread = 0 nchunk = -1 # Look if item1 is in this row if item1 > rbufrv[rvrow]: if item1 <= rbufrv[rvrow+1]: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) bread = 1 nchunk = bisect_left_ub(rbufbc, item1, nbounds, 0) # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk, cs) start = bisect_left_ub(rbuflb, item1, cs, 0) + cs*nchunk else: start = ss else: start = 0 # Now, for item2 if item2 >= rbufrv[rvrow]: if item2 < rbufrv[rvrow+1]: if not bread: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) nchunk2 = bisect_right_ub(rbufbc, item2, nbounds, 0) if nchunk2 <> nchunk: # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk2, cs) stop = bisect_right_ub(rbuflb, item2, cs, 0) + cs*nchunk2 else: stop = ss else: stop = 0 length = stop - start tlength = tlength + length rbufst[nrow] = start rbufln[nrow] = length return tlength # Optimized version for int16 def _search_bin_na_s(self, long item1, long item2): cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow cdef int start, stop, tlength, length, bread, nchunk, nchunk2 cdef int *rbufst cdef int *rbufln # Variables with specific type cdef npy_int16 *rbufrv cdef npy_int16 *rbufbc = NULL cdef npy_int16 *rbuflb = NULL cs = self.l_chunksize ss = self.l_slicesize ncs = ss // cs nbounds = self.nbounds nrows = self.nrows rbufst = self.rbufst rbufln = self.rbufln rbufrv = self.rbufrv tlength = 0 for nrow from 0 <= nrow < nrows: rvrow = nrow*2 bread = 0 nchunk = -1 # Look if item1 is in this row if item1 > rbufrv[rvrow]: if item1 <= rbufrv[rvrow+1]: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) bread = 1 nchunk = bisect_left_s(rbufbc, item1, nbounds, 0) # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk, cs) start = bisect_left_s(rbuflb, item1, cs, 0) + cs*nchunk else: start = ss else: start = 0 # Now, for item2 if item2 >= rbufrv[rvrow]: if item2 < rbufrv[rvrow+1]: if not bread: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) nchunk2 = bisect_right_s(rbufbc, item2, nbounds, 0) if nchunk2 <> nchunk: # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk2, cs) stop = bisect_right_s(rbuflb, item2, cs, 0) + cs*nchunk2 else: stop = ss else: stop = 0 length = stop - start tlength = tlength + length rbufst[nrow] = start rbufln[nrow] = length return tlength # Optimized version for uint16 def _search_bin_na_us(self, long item1, long item2): cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow cdef int start, stop, tlength, length, bread, nchunk, nchunk2 cdef int *rbufst cdef int *rbufln # Variables with specific type cdef npy_uint16 *rbufrv cdef npy_uint16 *rbufbc = NULL cdef npy_uint16 *rbuflb = NULL cs = self.l_chunksize ss = self.l_slicesize ncs = ss // cs nbounds = self.nbounds nrows = self.nrows rbufst = self.rbufst rbufln = self.rbufln rbufrv = self.rbufrv tlength = 0 for nrow from 0 <= nrow < nrows: rvrow = nrow*2 bread = 0 nchunk = -1 # Look if item1 is in this row if item1 > rbufrv[rvrow]: if item1 <= rbufrv[rvrow+1]: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) bread = 1 nchunk = bisect_left_us(rbufbc, item1, nbounds, 0) # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk, cs) start = bisect_left_us(rbuflb, item1, cs, 0) + cs*nchunk else: start = ss else: start = 0 # Now, for item2 if item2 >= rbufrv[rvrow]: if item2 < rbufrv[rvrow+1]: if not bread: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) nchunk2 = bisect_right_us(rbufbc, item2, nbounds, 0) if nchunk2 <> nchunk: # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk2, cs) stop = bisect_right_us(rbuflb, item2, cs, 0) + cs*nchunk2 else: stop = ss else: stop = 0 length = stop - start tlength = tlength + length rbufst[nrow] = start rbufln[nrow] = length return tlength # Optimized version for int32 def _search_bin_na_i(self, long item1, long item2): cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow cdef int start, stop, tlength, length, bread, nchunk, nchunk2 cdef int *rbufst cdef int *rbufln # Variables with specific type cdef npy_int32 *rbufrv cdef npy_int32 *rbufbc = NULL cdef npy_int32 *rbuflb = NULL cs = self.l_chunksize ss = self.l_slicesize ncs = ss // cs nbounds = self.nbounds nrows = self.nrows rbufst = self.rbufst rbufln = self.rbufln rbufrv = self.rbufrv tlength = 0 for nrow from 0 <= nrow < nrows: rvrow = nrow*2 bread = 0 nchunk = -1 # Look if item1 is in this row if item1 > rbufrv[rvrow]: if item1 <= rbufrv[rvrow+1]: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) bread = 1 nchunk = bisect_left_i(rbufbc, item1, nbounds, 0) # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk, cs) start = bisect_left_i(rbuflb, item1, cs, 0) + cs*nchunk else: start = ss else: start = 0 # Now, for item2 if item2 >= rbufrv[rvrow]: if item2 < rbufrv[rvrow+1]: if not bread: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) nchunk2 = bisect_right_i(rbufbc, item2, nbounds, 0) if nchunk2 <> nchunk: # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk2, cs) stop = bisect_right_i(rbuflb, item2, cs, 0) + cs*nchunk2 else: stop = ss else: stop = 0 length = stop - start tlength = tlength + length rbufst[nrow] = start rbufln[nrow] = length return tlength # Optimized version for uint32 def _search_bin_na_ui(self, npy_uint32 item1, npy_uint32 item2): cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow cdef int start, stop, tlength, length, bread, nchunk, nchunk2 cdef int *rbufst cdef int *rbufln # Variables with specific type cdef npy_uint32 *rbufrv cdef npy_uint32 *rbufbc = NULL cdef npy_uint32 *rbuflb = NULL cs = self.l_chunksize ss = self.l_slicesize ncs = ss // cs nbounds = self.nbounds nrows = self.nrows rbufst = self.rbufst rbufln = self.rbufln rbufrv = self.rbufrv tlength = 0 for nrow from 0 <= nrow < nrows: rvrow = nrow*2 bread = 0 nchunk = -1 # Look if item1 is in this row if item1 > rbufrv[rvrow]: if item1 <= rbufrv[rvrow+1]: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) bread = 1 nchunk = bisect_left_ui(rbufbc, item1, nbounds, 0) # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk, cs) start = bisect_left_ui(rbuflb, item1, cs, 0) + cs*nchunk else: start = ss else: start = 0 # Now, for item2 if item2 >= rbufrv[rvrow]: if item2 < rbufrv[rvrow+1]: if not bread: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) nchunk2 = bisect_right_ui(rbufbc, item2, nbounds, 0) if nchunk2 <> nchunk: # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk2, cs) stop = bisect_right_ui(rbuflb, item2, cs, 0) + cs*nchunk2 else: stop = ss else: stop = 0 length = stop - start tlength = tlength + length rbufst[nrow] = start rbufln[nrow] = length return tlength # Optimized version for int64 def _search_bin_na_ll(self, npy_int64 item1, npy_int64 item2): cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow cdef int start, stop, tlength, length, bread, nchunk, nchunk2 cdef int *rbufst cdef int *rbufln # Variables with specific type cdef npy_int64 *rbufrv cdef npy_int64 *rbufbc = NULL cdef npy_int64 *rbuflb = NULL cs = self.l_chunksize ss = self.l_slicesize ncs = ss // cs nbounds = self.nbounds nrows = self.nrows rbufst = self.rbufst rbufln = self.rbufln rbufrv = self.rbufrv tlength = 0 for nrow from 0 <= nrow < nrows: rvrow = nrow*2 bread = 0 nchunk = -1 # Look if item1 is in this row if item1 > rbufrv[rvrow]: if item1 <= rbufrv[rvrow+1]: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) bread = 1 nchunk = bisect_left_ll(rbufbc, item1, nbounds, 0) # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk, cs) start = bisect_left_ll(rbuflb, item1, cs, 0) + cs*nchunk else: start = ss else: start = 0 # Now, for item2 if item2 >= rbufrv[rvrow]: if item2 < rbufrv[rvrow+1]: if not bread: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) nchunk2 = bisect_right_ll(rbufbc, item2, nbounds, 0) if nchunk2 <> nchunk: # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk2, cs) stop = bisect_right_ll(rbuflb, item2, cs, 0) + cs*nchunk2 else: stop = ss else: stop = 0 length = stop - start tlength = tlength + length rbufst[nrow] = start rbufln[nrow] = length return tlength # Optimized version for uint64 def _search_bin_na_ull(self, npy_uint64 item1, npy_uint64 item2): cdef int cs, ss, ncs, nrow, nrows, nbounds, rvrow cdef int start, stop, tlength, length, bread, nchunk, nchunk2 cdef int *rbufst cdef int *rbufln # Variables with specific type cdef npy_uint64 *rbufrv cdef npy_uint64 *rbufbc = NULL cdef npy_uint64 *rbuflb = NULL cs = self.l_chunksize ss = self.l_slicesize ncs = ss // cs nbounds = self.nbounds nrows = self.nrows rbufst = self.rbufst rbufln = self.rbufln rbufrv = self.rbufrv tlength = 0 for nrow from 0 <= nrow < nrows: rvrow = nrow*2 bread = 0 nchunk = -1 # Look if item1 is in this row if item1 > rbufrv[rvrow]: if item1 <= rbufrv[rvrow+1]: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) bread = 1 nchunk = bisect_left_ull(rbufbc, item1, nbounds, 0) # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk, cs) start = bisect_left_ull(rbuflb, item1, cs, 0) + cs*nchunk else: start = ss else: start = 0 # Now, for item2 if item2 >= rbufrv[rvrow]: if item2 < rbufrv[rvrow+1]: if not bread: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) nchunk2 = bisect_right_ull(rbufbc, item2, nbounds, 0) if nchunk2 <> nchunk: # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk2, cs) stop = bisect_right_ull(rbuflb, item2, cs, 0) + cs*nchunk2 else: stop = ss else: stop = 0 length = stop - start tlength = tlength + length rbufst[nrow] = start rbufln[nrow] = length return tlength # Optimized version for float16 def _search_bin_na_e(self, npy_float64 item1, npy_float64 item2): cdef int cs, ss, ncs, nrow, nrows, nrow2, nbounds, rvrow cdef int start, stop, tlength, length, bread, nchunk, nchunk2 cdef int *rbufst cdef int *rbufln # Variables with specific type cdef npy_float16 *rbufrv cdef npy_float16 *rbufbc = NULL cdef npy_float16 *rbuflb = NULL cs = self.l_chunksize ss = self.l_slicesize ncs = ss // cs nbounds = self.nbounds nrows = self.nrows tlength = 0 rbufst = self.rbufst rbufln = self.rbufln # Limits not in cache, do a lookup rbufrv = self.rbufrv for nrow from 0 <= nrow < nrows: rvrow = nrow*2 bread = 0 nchunk = -1 # Look if item1 is in this row if item1 > rbufrv[rvrow]: if item1 <= rbufrv[rvrow+1]: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) bread = 1 nchunk = bisect_left_e(rbufbc, item1, nbounds, 0) # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk, cs) start = bisect_left_e(rbuflb, item1, cs, 0) + cs*nchunk else: start = ss else: start = 0 # Now, for item2 if item2 >= rbufrv[rvrow]: if item2 < rbufrv[rvrow+1]: if not bread: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) nchunk2 = bisect_right_e(rbufbc, item2, nbounds, 0) if nchunk2 <> nchunk: # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk2, cs) stop = bisect_right_e(rbuflb, item2, cs, 0) + cs*nchunk2 else: stop = ss else: stop = 0 length = stop - start tlength = tlength + length rbufst[nrow] = start rbufln[nrow] = length return tlength # Optimized version for float32 def _search_bin_na_f(self, npy_float64 item1, npy_float64 item2): cdef int cs, ss, ncs, nrow, nrows, nrow2, nbounds, rvrow cdef int start, stop, tlength, length, bread, nchunk, nchunk2 cdef int *rbufst cdef int *rbufln # Variables with specific type cdef npy_float32 *rbufrv cdef npy_float32 *rbufbc = NULL cdef npy_float32 *rbuflb = NULL cs = self.l_chunksize ss = self.l_slicesize ncs = ss // cs nbounds = self.nbounds nrows = self.nrows tlength = 0 rbufst = self.rbufst rbufln = self.rbufln # Limits not in cache, do a lookup rbufrv = self.rbufrv for nrow from 0 <= nrow < nrows: rvrow = nrow*2 bread = 0 nchunk = -1 # Look if item1 is in this row if item1 > rbufrv[rvrow]: if item1 <= rbufrv[rvrow+1]: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) bread = 1 nchunk = bisect_left_f(rbufbc, item1, nbounds, 0) # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk, cs) start = bisect_left_f(rbuflb, item1, cs, 0) + cs*nchunk else: start = ss else: start = 0 # Now, for item2 if item2 >= rbufrv[rvrow]: if item2 < rbufrv[rvrow+1]: if not bread: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) nchunk2 = bisect_right_f(rbufbc, item2, nbounds, 0) if nchunk2 <> nchunk: # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk2, cs) stop = bisect_right_f(rbuflb, item2, cs, 0) + cs*nchunk2 else: stop = ss else: stop = 0 length = stop - start tlength = tlength + length rbufst[nrow] = start rbufln[nrow] = length return tlength # Optimized version for float64 def _search_bin_na_d(self, npy_float64 item1, npy_float64 item2): cdef int cs, ss, ncs, nrow, nrows, nrow2, nbounds, rvrow cdef int start, stop, tlength, length, bread, nchunk, nchunk2 cdef int *rbufst cdef int *rbufln # Variables with specific type cdef npy_float64 *rbufrv cdef npy_float64 *rbufbc = NULL cdef npy_float64 *rbuflb = NULL cs = self.l_chunksize ss = self.l_slicesize ncs = ss // cs nbounds = self.nbounds nrows = self.nrows tlength = 0 rbufst = self.rbufst rbufln = self.rbufln # Limits not in cache, do a lookup rbufrv = self.rbufrv for nrow from 0 <= nrow < nrows: rvrow = nrow*2 bread = 0 nchunk = -1 # Look if item1 is in this row if item1 > rbufrv[rvrow]: if item1 <= rbufrv[rvrow+1]: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) bread = 1 nchunk = bisect_left_d(rbufbc, item1, nbounds, 0) # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk, cs) start = bisect_left_d(rbuflb, item1, cs, 0) + cs*nchunk else: start = ss else: start = 0 # Now, for item2 if item2 >= rbufrv[rvrow]: if item2 < rbufrv[rvrow+1]: if not bread: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) nchunk2 = bisect_right_d(rbufbc, item2, nbounds, 0) if nchunk2 <> nchunk: # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk2, cs) stop = bisect_right_d(rbuflb, item2, cs, 0) + cs*nchunk2 else: stop = ss else: stop = 0 length = stop - start tlength = tlength + length rbufst[nrow] = start rbufln[nrow] = length return tlength # Optimized version for npy_longdouble/float96/float128 def _search_bin_na_g(self, npy_longdouble item1, npy_longdouble item2): cdef int cs, ss, ncs, nrow, nrows, nrow2, nbounds, rvrow cdef int start, stop, tlength, length, bread, nchunk, nchunk2 cdef int *rbufst cdef int *rbufln # Variables with specific type cdef npy_longdouble *rbufrv cdef npy_longdouble *rbufbc = NULL cdef npy_longdouble *rbuflb = NULL cs = self.l_chunksize ss = self.l_slicesize ncs = ss // cs nbounds = self.nbounds nrows = self.nrows tlength = 0 rbufst = self.rbufst rbufln = self.rbufln # Limits not in cache, do a lookup rbufrv = self.rbufrv for nrow from 0 <= nrow < nrows: rvrow = nrow*2 bread = 0 nchunk = -1 # Look if item1 is in this row if item1 > rbufrv[rvrow]: if item1 <= rbufrv[rvrow+1]: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) bread = 1 nchunk = bisect_left_g(rbufbc, item1, nbounds, 0) # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk, cs) start = bisect_left_g(rbuflb, item1, cs, 0) + cs*nchunk else: start = ss else: start = 0 # Now, for item2 if item2 >= rbufrv[rvrow]: if item2 < rbufrv[rvrow+1]: if not bread: # Get the bounds row from the LRU cache or read them. rbufbc = self.get_lru_bounds(nrow, nbounds) nchunk2 = bisect_right_g(rbufbc, item2, nbounds, 0) if nchunk2 <> nchunk: # Get the sorted row from the LRU cache or read it. rbuflb = self.get_lru_sorted(nrow, ncs, nchunk2, cs) stop = bisect_right_g(rbuflb, item2, cs, 0) + cs*nchunk2 else: stop = ss else: stop = 0 length = stop - start tlength = tlength + length rbufst[nrow] = start rbufln[nrow] = length return tlength def _g_close(self): super()._g_close() # Release specific resources of this class if self.mem_space_id > 0: H5Sclose(self.mem_space_id) cdef class LastRowArray(Array): """ Container for keeping sorted and indices values of last rows of an index. """ def _read_index_slice(self, hsize_t start, hsize_t stop, ndarray idx): """Read the reverse index part of an LR index.""" cdef void *buf = PyArray_DATA(idx) with nogil: ret = H5ARRAYOreadSliceLR(self.dataset_id, self.type_id, start, stop, buf) if ret < 0: raise HDF5ExtError("Problems reading the index data in Last Row.") def _read_sorted_slice(self, IndexArray sorted, hsize_t start, hsize_t stop): """Read the sorted part of an LR index.""" cdef void *rbuflb rbuflb = sorted.rbuflb # direct access to rbuflb: very fast. with nogil: ret = H5ARRAYOreadSliceLR(self.dataset_id, self.type_id, start, stop, rbuflb) if ret < 0: raise HDF5ExtError("Problems reading the index data.") return sorted.bufferlb[:stop-start] ## Local Variables: ## mode: python ## py-indent-offset: 2 ## tab-width: 2 ## fill-column: 78 ## End: PyTables-3.7.0/tables/leaf.py000066400000000000000000000644441416254111300160110ustar00rootroot00000000000000"""Here is defined the Leaf class.""" import warnings import math import numpy as np from .flavor import (check_flavor, internal_flavor, toarray, alias_map as flavor_alias_map) from .node import Node from .filters import Filters from .utils import byteorders, lazyattr, SizeType from .exceptions import PerformanceWarning def csformula(expected_mb): """Return the fitted chunksize for expected_mb.""" # For a basesize of 8 KB, this will return: # 8 KB for datasets <= 1 MB # 1 MB for datasets >= 10 TB basesize = 8 * 1024 # 8 KB is a good minimum return basesize * int(2**math.log10(expected_mb)) def limit_es(expected_mb): """Protection against creating too small or too large chunks.""" if expected_mb < 1: # < 1 MB expected_mb = 1 elif expected_mb > 10**7: # > 10 TB expected_mb = 10**7 return expected_mb def calc_chunksize(expected_mb): """Compute the optimum HDF5 chunksize for I/O purposes. Rational: HDF5 takes the data in bunches of chunksize length to write the on disk. A BTree in memory is used to map structures on disk. The more chunks that are allocated for a dataset the larger the B-tree. Large B-trees take memory and causes file storage overhead as well as more disk I/O and higher contention for the meta data cache. You have to balance between memory and I/O overhead (small B-trees) and time to access to data (big B-trees). The tuning of the chunksize parameter affects the performance and the memory consumed. This is based on my own experiments and, as always, your mileage may vary. """ expected_mb = limit_es(expected_mb) zone = int(math.log10(expected_mb)) expected_mb = 10**zone chunksize = csformula(expected_mb) # XXX: Multiply by 8 seems optimal for sequential access return chunksize * 8 class Leaf(Node): """Abstract base class for all PyTables leaves. A leaf is a node (see the Node class in :class:`Node`) which hangs from a group (see the Group class in :class:`Group`) but, unlike a group, it can not have any further children below it (i.e. it is an end node). This definition includes all nodes which contain actual data (datasets handled by the Table - see :ref:`TableClassDescr`, Array - see :ref:`ArrayClassDescr`, CArray - see :ref:`CArrayClassDescr`, EArray - see :ref:`EArrayClassDescr`, and VLArray - see :ref:`VLArrayClassDescr` classes) and unsupported nodes (the UnImplemented class - :ref:`UnImplementedClassDescr`) these classes do in fact inherit from Leaf. .. rubric:: Leaf attributes These instance variables are provided in addition to those in Node (see :ref:`NodeClassDescr`): .. attribute:: byteorder The byte ordering of the leaf data *on disk*. It will be either ``little`` or ``big``. .. attribute:: dtype The NumPy dtype that most closely matches this leaf type. .. attribute:: extdim The index of the enlargeable dimension (-1 if none). .. attribute:: nrows The length of the main dimension of the leaf data. .. attribute:: nrowsinbuf The number of rows that fit in internal input buffers. You can change this to fine-tune the speed or memory requirements of your application. .. attribute:: shape The shape of data in the leaf. """ # These are a little hard to override, but so are properties. attrs = Node._v_attrs """The associated AttributeSet instance - see :ref:`AttributeSetClassDescr` (This is an easier-to-write alias of :attr:`Node._v_attrs`.""" title = Node._v_title """A description for this node (This is an easier-to-write alias of :attr:`Node._v_title`).""" @property def name(self): """The name of this node in its parent group (This is an easier-to-write alias of :attr:`Node._v_name`).""" return self._v_name @property def chunkshape(self): """The HDF5 chunk size for chunked leaves (a tuple). This is read-only because you cannot change the chunk size of a leaf once it has been created. """ return getattr(self, '_v_chunkshape', None) @property def object_id(self): """A node identifier, which may change from run to run. (This is an easier-to-write alias of :attr:`Node._v_objectid`). .. versionchanged:: 3.0 The *objectID* property has been renamed into *object_id*. """ return self._v_objectid @property def ndim(self): """The number of dimensions of the leaf data. .. versionadded: 2.4""" return len(self.shape) @lazyattr def filters(self): """Filter properties for this leaf. See Also -------- Filters """ return Filters._from_leaf(self) @property def track_times(self): """Whether timestamps for the leaf are recorded If the leaf is not a dataset, this will fail with HDF5ExtError. The track times dataset creation property does not seem to survive closing and reopening as of HDF5 1.8.17. Currently, it may be more accurate to test whether the ctime for the dataset is 0: track_times = (leaf._get_obj_timestamps().ctime == 0) """ return self._get_obj_track_times() @property def maindim(self): """The dimension along which iterators work. Its value is 0 (i.e. the first dimension) when the dataset is not extendable, and self.extdim (where available) for extendable ones. """ if self.extdim < 0: return 0 # choose the first dimension return self.extdim @property def flavor(self): """The type of data object read from this leaf. It can be any of 'numpy' or 'python'. You can (and are encouraged to) use this property to get, set and delete the FLAVOR HDF5 attribute of the leaf. When the leaf has no such attribute, the default flavor is used.. """ return self._flavor @flavor.setter def flavor(self, flavor): self._v_file._check_writable() check_flavor(flavor) self._v_attrs.FLAVOR = self._flavor = flavor # logs the change @flavor.deleter def flavor(self): del self._v_attrs.FLAVOR self._flavor = internal_flavor @property def size_on_disk(self): """ The size of this leaf's data in bytes as it is stored on disk. If the data is compressed, this shows the compressed size. In the case of uncompressed, chunked data, this may be slightly larger than the amount of data, due to partially filled chunks. """ return self._get_storage_size() def __init__(self, parentnode, name, new=False, filters=None, byteorder=None, _log=True, track_times=True): self._v_new = new """Is this the first time the node has been created?""" self.nrowsinbuf = None """ The number of rows that fits in internal input buffers. You can change this to fine-tune the speed or memory requirements of your application. """ self._flavor = None """Private storage for the `flavor` property.""" if new: # Get filter properties from parent group if not given. if filters is None: filters = parentnode._v_filters self.__dict__['filters'] = filters # bypass the property if byteorder not in (None, 'little', 'big'): raise ValueError( "the byteorder can only take 'little' or 'big' values " "and you passed: %s" % byteorder) self.byteorder = byteorder """The byte ordering of the leaf data *on disk*.""" self._want_track_times = track_times # Existing filters need not be read since `filters` # is a lazy property that automatically handles their loading. super().__init__(parentnode, name, _log) def __len__(self): """Return the length of the main dimension of the leaf data. Please note that this may raise an OverflowError on 32-bit platforms for datasets having more than 2**31-1 rows. This is a limitation of Python that you can work around by using the nrows or shape attributes. """ return self.nrows def __str__(self): """The string representation for this object is its pathname in the HDF5 object tree plus some additional metainfo.""" filters = [] if self.filters.fletcher32: filters.append("fletcher32") if self.filters.complevel: if self.filters.shuffle: filters.append("shuffle") if self.filters.bitshuffle: filters.append("bitshuffle") filters.append(f"{self.filters.complib}({self.filters.complevel})") return (f"{self._v_pathname} ({self.__class__.__name__}" f"{self.shape}{', '.join(filters)}) {self._v_title!r}") def _g_post_init_hook(self): """Code to be run after node creation and before creation logging. This method gets or sets the flavor of the leaf. """ super()._g_post_init_hook() if self._v_new: # set flavor of new node if self._flavor is None: self._flavor = internal_flavor else: # flavor set at creation time, do not log if self._v_file.params['PYTABLES_SYS_ATTRS']: self._v_attrs._g__setattr('FLAVOR', self._flavor) else: # get flavor of existing node (if any) if self._v_file.params['PYTABLES_SYS_ATTRS']: flavor = getattr(self._v_attrs, 'FLAVOR', internal_flavor) self._flavor = flavor_alias_map.get(flavor, flavor) else: self._flavor = internal_flavor def _calc_chunkshape(self, expectedrows, rowsize, itemsize): """Calculate the shape for the HDF5 chunk.""" # In case of a scalar shape, return the unit chunksize if self.shape == (): return (SizeType(1),) # Compute the chunksize MB = 1024 * 1024 expected_mb = (expectedrows * rowsize) // MB chunksize = calc_chunksize(expected_mb) maindim = self.maindim # Compute the chunknitems chunknitems = chunksize // itemsize # Safeguard against itemsizes being extremely large if chunknitems == 0: chunknitems = 1 chunkshape = list(self.shape) # Check whether trimming the main dimension is enough chunkshape[maindim] = 1 newchunknitems = np.prod(chunkshape, dtype=SizeType) if newchunknitems <= chunknitems: chunkshape[maindim] = chunknitems // newchunknitems else: # No, so start trimming other dimensions as well for j in range(len(chunkshape)): # Check whether trimming this dimension is enough chunkshape[j] = 1 newchunknitems = np.prod(chunkshape, dtype=SizeType) if newchunknitems <= chunknitems: chunkshape[j] = chunknitems // newchunknitems break else: # Ops, we ran out of the loop without a break # Set the last dimension to chunknitems chunkshape[-1] = chunknitems return tuple(SizeType(s) for s in chunkshape) def _calc_nrowsinbuf(self): """Calculate the number of rows that fits on a PyTables buffer.""" params = self._v_file.params # Compute the nrowsinbuf rowsize = self.rowsize buffersize = params['IO_BUFFER_SIZE'] if rowsize != 0: nrowsinbuf = buffersize // rowsize else: nrowsinbuf = 1 # Safeguard against row sizes being extremely large if nrowsinbuf == 0: nrowsinbuf = 1 # If rowsize is too large, issue a Performance warning maxrowsize = params['BUFFER_TIMES'] * buffersize if rowsize > maxrowsize: warnings.warn("""\ The Leaf ``%s`` is exceeding the maximum recommended rowsize (%d bytes); be ready to see PyTables asking for *lots* of memory and possibly slow I/O. You may want to reduce the rowsize by trimming the value of dimensions that are orthogonal (and preferably close) to the *main* dimension of this leave. Alternatively, in case you have specified a very small/large chunksize, you may want to increase/decrease it.""" % (self._v_pathname, maxrowsize), PerformanceWarning) return nrowsinbuf # This method is appropriate for calls to __getitem__ methods def _process_range(self, start, stop, step, dim=None, warn_negstep=True): if dim is None: nrows = self.nrows # self.shape[self.maindim] else: nrows = self.shape[dim] if warn_negstep and step and step < 0: raise ValueError("slice step cannot be negative") # if start is not None: start = long(start) # if stop is not None: stop = long(stop) # if step is not None: step = long(step) return slice(start, stop, step).indices(int(nrows)) # This method is appropriate for calls to read() methods def _process_range_read(self, start, stop, step, warn_negstep=True): nrows = self.nrows if start is not None and stop is None and step is None: # Protection against start greater than available records # nrows == 0 is a special case for empty objects if 0 < nrows <= start: raise IndexError("start of range (%s) is greater than " "number of rows (%s)" % (start, nrows)) step = 1 if start == -1: # corner case stop = nrows else: stop = start + 1 # Finally, get the correct values (over the main dimension) start, stop, step = self._process_range(start, stop, step, warn_negstep=warn_negstep) return (start, stop, step) def _g_copy(self, newparent, newname, recursive, _log=True, **kwargs): # Compute default arguments. start = kwargs.pop('start', None) stop = kwargs.pop('stop', None) step = kwargs.pop('step', None) title = kwargs.pop('title', self._v_title) filters = kwargs.pop('filters', self.filters) chunkshape = kwargs.pop('chunkshape', self.chunkshape) copyuserattrs = kwargs.pop('copyuserattrs', True) stats = kwargs.pop('stats', None) if chunkshape == 'keep': chunkshape = self.chunkshape # Keep the original chunkshape elif chunkshape == 'auto': chunkshape = None # Will recompute chunkshape # Fix arguments with explicit None values for backwards compatibility. if title is None: title = self._v_title if filters is None: filters = self.filters # Create a copy of the object. (new_node, bytes) = self._g_copy_with_stats( newparent, newname, start, stop, step, title, filters, chunkshape, _log, **kwargs) # Copy user attributes if requested (or the flavor at least). if copyuserattrs: self._v_attrs._g_copy(new_node._v_attrs, copyclass=True) elif 'FLAVOR' in self._v_attrs: if self._v_file.params['PYTABLES_SYS_ATTRS']: new_node._v_attrs._g__setattr('FLAVOR', self._flavor) new_node._flavor = self._flavor # update cached value # Update statistics if needed. if stats is not None: stats['leaves'] += 1 stats['bytes'] += bytes return new_node def _g_fix_byteorder_data(self, data, dbyteorder): """Fix the byteorder of data passed in constructors.""" dbyteorder = byteorders[dbyteorder] # If self.byteorder has not been passed as an argument of # the constructor, then set it to the same value of data. if self.byteorder is None: self.byteorder = dbyteorder # Do an additional in-place byteswap of data if the in-memory # byteorder doesn't match that of the on-disk. This is the only # place that we have to do the conversion manually. In all the # other cases, it will be HDF5 the responsible of doing the # byteswap properly. if dbyteorder in ['little', 'big']: if dbyteorder != self.byteorder: # if data is not writeable, do a copy first if not data.flags.writeable: data = data.copy() data.byteswap(True) else: # Fix the byteorder again, no matter which byteorder have # specified the user in the constructor. self.byteorder = "irrelevant" return data def _point_selection(self, key): """Perform a point-wise selection. `key` can be any of the following items: * A boolean array with the same shape than self. Those positions with True values will signal the coordinates to be returned. * A numpy array (or list or tuple) with the point coordinates. This has to be a two-dimensional array of size len(self.shape) by num_elements containing a list of of zero-based values specifying the coordinates in the dataset of the selected elements. The order of the element coordinates in the array specifies the order in which the array elements are iterated through when I/O is performed. Duplicate coordinate locations are not checked for. Return the coordinates array. If this is not possible, raise a `TypeError` so that the next selection method can be tried out. This is useful for whatever `Leaf` instance implementing a point-wise selection. """ input_key = key if type(key) in (list, tuple): if isinstance(key, tuple) and len(key) > len(self.shape): raise IndexError(f"Invalid index or slice: {key!r}") # Try to convert key to a numpy array. If not possible, # a TypeError will be issued (to be catched later on). try: key = toarray(key) except ValueError: raise TypeError(f"Invalid index or slice: {key!r}") elif not isinstance(key, np.ndarray): raise TypeError(f"Invalid index or slice: {key!r}") # Protection against empty keys if len(key) == 0: return np.array([], dtype="i8") if key.dtype.kind == 'b': if not key.shape == self.shape: raise IndexError( "Boolean indexing array has incompatible shape") # Get the True coordinates (64-bit indices!) coords = np.asarray(key.nonzero(), dtype='i8') coords = np.transpose(coords) elif key.dtype.kind == 'i' or key.dtype.kind == 'u': if len(key.shape) > 2: raise IndexError( "Coordinate indexing array has incompatible shape") elif len(key.shape) == 2: if key.shape[0] != len(self.shape): raise IndexError( "Coordinate indexing array has incompatible shape") coords = np.asarray(key, dtype="i8") coords = np.transpose(coords) else: # For 1-dimensional datasets coords = np.asarray(key, dtype="i8") # handle negative indices base = coords if coords.base is None else coords.base if base is input_key: # never modify the original "key" data coords = coords.copy() idx = coords < 0 coords[idx] = (coords + self.shape)[idx] # bounds check if np.any(coords < 0) or np.any(coords >= self.shape): raise IndexError("Index out of bounds") else: raise TypeError("Only integer coordinates allowed.") # We absolutely need a contiguous array if not coords.flags.contiguous: coords = coords.copy() return coords # Tree manipulation def remove(self): """Remove this node from the hierarchy. This method has the behavior described in :meth:`Node._f_remove`. Please note that there is no recursive flag since leaves do not have child nodes. """ self._f_remove(False) def rename(self, newname): """Rename this node in place. This method has the behavior described in :meth:`Node._f_rename()`. """ self._f_rename(newname) def move(self, newparent=None, newname=None, overwrite=False, createparents=False): """Move or rename this node. This method has the behavior described in :meth:`Node._f_move` """ self._f_move(newparent, newname, overwrite, createparents) def copy(self, newparent=None, newname=None, overwrite=False, createparents=False, **kwargs): """Copy this node and return the new one. This method has the behavior described in :meth:`Node._f_copy`. Please note that there is no recursive flag since leaves do not have child nodes. .. warning:: Note that unknown parameters passed to this method will be ignored, so may want to double check the spelling of these (i.e. if you write them incorrectly, they will most probably be ignored). Parameters ---------- title The new title for the destination. If omitted or None, the original title is used. filters : Filters Specifying this parameter overrides the original filter properties in the source node. If specified, it must be an instance of the Filters class (see :ref:`FiltersClassDescr`). The default is to copy the filter properties from the source node. copyuserattrs You can prevent the user attributes from being copied by setting this parameter to False. The default is to copy them. start, stop, step : int Specify the range of rows to be copied; the default is to copy all the rows. stats This argument may be used to collect statistics on the copy process. When used, it should be a dictionary with keys 'groups', 'leaves' and 'bytes' having a numeric value. Their values will be incremented to reflect the number of groups, leaves and bytes, respectively, that have been copied during the operation. chunkshape The chunkshape of the new leaf. It supports a couple of special values. A value of keep means that the chunkshape will be the same than original leaf (this is the default). A value of auto means that a new shape will be computed automatically in order to ensure best performance when accessing the dataset through the main dimension. Any other value should be an integer or a tuple matching the dimensions of the leaf. """ return self._f_copy( newparent, newname, overwrite, createparents, **kwargs) def truncate(self, size): """Truncate the main dimension to be size rows. If the main dimension previously was larger than this size, the extra data is lost. If the main dimension previously was shorter, it is extended, and the extended part is filled with the default values. The truncation operation can only be applied to *enlargeable* datasets, else a TypeError will be raised. """ # A non-enlargeable arrays (Array, CArray) cannot be truncated if self.extdim < 0: raise TypeError("non-enlargeable datasets cannot be truncated") self._g_truncate(size) def isvisible(self): """Is this node visible? This method has the behavior described in :meth:`Node._f_isvisible()`. """ return self._f_isvisible() # Attribute handling def get_attr(self, name): """Get a PyTables attribute from this node. This method has the behavior described in :meth:`Node._f_getattr`. """ return self._f_getattr(name) def set_attr(self, name, value): """Set a PyTables attribute for this node. This method has the behavior described in :meth:`Node._f_setattr()`. """ self._f_setattr(name, value) def del_attr(self, name): """Delete a PyTables attribute from this node. This method has the behavior described in :meth:`Node_f_delAttr`. """ self._f_delattr(name) # Data handling def flush(self): """Flush pending data to disk. Saves whatever remaining buffered data to disk. It also releases I/O buffers, so if you are filling many datasets in the same PyTables session, please call flush() extensively so as to help PyTables to keep memory requirements low. """ self._g_flush() def _f_close(self, flush=True): """Close this node in the tree. This method has the behavior described in :meth:`Node._f_close`. Besides that, the optional argument flush tells whether to flush pending data to disk or not before closing. """ if not self._v_isopen: return # the node is already closed or not initialized # Only do a flush in case the leaf has an IO buffer. The # internal buffers of HDF5 will be flushed afterwards during the # self._g_close() call. Avoiding an unnecessary flush() # operation accelerates the closing for the unbuffered leaves. if flush and hasattr(self, "_v_iobuf"): self.flush() # Close the dataset and release resources self._g_close() # Close myself as a node. super()._f_close() def close(self, flush=True): """Close this node in the tree. This method is completely equivalent to :meth:`Leaf._f_close`. """ self._f_close(flush) PyTables-3.7.0/tables/link.py000066400000000000000000000312571416254111300160330ustar00rootroot00000000000000"""Create links in the HDF5 file. This module implements containers for soft and external links. Hard links doesn't need a container as such as they are the same as regular nodes (groups or leaves). Classes: SoftLink ExternalLink Functions: Misc variables: """ from pathlib import Path import tables as tb from . import linkextension from .node import Node from .utils import lazyattr from .attributeset import AttributeSet def _g_get_link_class(parent_id, name): """Guess the link class.""" return linkextension._get_link_class(parent_id, name) class Link(Node): """Abstract base class for all PyTables links. A link is a node that refers to another node. The Link class inherits from Node class and the links that inherits from Link are SoftLink and ExternalLink. There is not a HardLink subclass because hard links behave like a regular Group or Leaf. Contrarily to other nodes, links cannot have HDF5 attributes. This is an HDF5 library limitation that might be solved in future releases. See :ref:`LinksTutorial` for a small tutorial on how to work with links. .. rubric:: Link attributes .. attribute:: target The path string to the pointed node. """ # Properties @lazyattr def _v_attrs(self): """ A *NoAttrs* instance replacing the typical *AttributeSet* instance of other node objects. The purpose of *NoAttrs* is to make clear that HDF5 attributes are not supported in link nodes. """ class NoAttrs(AttributeSet): def __getattr__(self, name): raise KeyError("you cannot get attributes from this " "`%s` instance" % self.__class__.__name__) def __setattr__(self, name, value): raise KeyError("you cannot set attributes to this " "`%s` instance" % self.__class__.__name__) def _g_close(self): pass return NoAttrs(self) def __init__(self, parentnode, name, target=None, _log=False): self._v_new = target is not None self.target = target """The path string to the pointed node.""" super().__init__(parentnode, name, _log) # Public and tailored versions for copy, move, rename and remove methods def copy(self, newparent=None, newname=None, overwrite=False, createparents=False): """Copy this link and return the new one. See :meth:`Node._f_copy` for a complete explanation of the arguments. Please note that there is no recursive flag since links do not have child nodes. """ newnode = self._f_copy(newparent=newparent, newname=newname, overwrite=overwrite, createparents=createparents) # Insert references to a `newnode` via `newname` newnode._v_parent._g_refnode(newnode, newname, True) return newnode def move(self, newparent=None, newname=None, overwrite=False): """Move or rename this link. See :meth:`Node._f_move` for a complete explanation of the arguments. """ return self._f_move(newparent=newparent, newname=newname, overwrite=overwrite) def remove(self): """Remove this link from the hierarchy.""" return self._f_remove() def rename(self, newname=None, overwrite=False): """Rename this link in place. See :meth:`Node._f_rename` for a complete explanation of the arguments. """ return self._f_rename(newname=newname, overwrite=overwrite) def __repr__(self): return str(self) class SoftLink(linkextension.SoftLink, Link): """Represents a soft link (aka symbolic link). A soft link is a reference to another node in the *same* file hierarchy. Provided that the target node exists, its attributes and methods can be accessed directly from the softlink using the normal `.` syntax. Softlinks also have the following public methods/attributes: * `target` * `dereference()` * `copy()` * `move()` * `remove()` * `rename()` * `is_dangling()` Note that these will override any correspondingly named methods/attributes of the target node. For backwards compatibility, it is also possible to obtain the target node via the `__call__()` special method (this action is called *dereferencing*; see below) Examples -------- :: >>> import numpy as np >>> f = tb.open_file('/tmp/test_softlink.h5', 'w') >>> a = f.create_array('/', 'A', np.arange(10)) >>> link_a = f.create_soft_link('/', 'link_A', target='/A') # transparent read/write access to a softlinked node >>> link_a[0] = -1 >>> link_a[:], link_a.dtype (array([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9]), dtype('int64')) # dereferencing a softlink using the __call__() method >>> link_a() is a True # SoftLink.remove() overrides Array.remove() >>> link_a.remove() >>> print(link_a) >>> a[:], a.dtype (array([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9]), dtype('int64')) >>> f.close() """ # Class identifier. _c_classid = 'SOFTLINK' # attributes with these names/prefixes are treated as attributes of the # SoftLink rather than the target node _link_attrnames = ('target', 'dereference', 'is_dangling', 'copy', 'move', 'remove', 'rename', '__init__', '__str__', '__repr__', '__unicode__', '__class__', '__dict__') _link_attrprefixes = ('_f_', '_c_', '_g_', '_v_') def __call__(self): """Dereference `self.target` and return the object. Examples -------- :: >>> f = tb.open_file('tables/tests/slink.h5') >>> f.root.arr2 /arr2 (SoftLink) -> /arr >>> print(f.root.arr2()) /arr (Array(2,)) '' >>> f.close() """ return self.dereference() def dereference(self): if self._v_isopen: target = self.target # Check for relative pathnames if not self.target.startswith('/'): target = self._v_parent._g_join(self.target) return self._v_file._get_node(target) else: return None def __getattribute__(self, attrname): # get attribute of the SoftLink itself if (attrname in SoftLink._link_attrnames or attrname[:3] in SoftLink._link_attrprefixes): return object.__getattribute__(self, attrname) # get attribute of the target node elif not self._v_isopen: raise tb.ClosedNodeError('the node object is closed') elif self.is_dangling(): return None else: target_node = self.dereference() try: # __getattribute__() fails to get children of Groups return target_node.__getattribute__(attrname) except AttributeError: # some node classes (e.g. Array) don't implement __getattr__() return target_node.__getattr__(attrname) def __setattr__(self, attrname, value): # set attribute of the SoftLink itself if (attrname in SoftLink._link_attrnames or attrname[:3] in SoftLink._link_attrprefixes): object.__setattr__(self, attrname, value) # set attribute of the target node elif not self._v_isopen: raise tb.ClosedNodeError('the node object is closed') elif self.is_dangling(): raise ValueError("softlink target does not exist") else: self.dereference().__setattr__(attrname, value) def __getitem__(self, key): """__getitem__ must be defined in the SoftLink class in order for array indexing syntax to work""" if not self._v_isopen: raise tb.ClosedNodeError('the node object is closed') elif self.is_dangling(): raise ValueError("softlink target does not exist") else: return self.dereference().__getitem__(key) def __setitem__(self, key, value): """__setitem__ must be defined in the SoftLink class in order for array indexing syntax to work""" if not self._v_isopen: raise tb.ClosedNodeError('the node object is closed') elif self.is_dangling(): raise ValueError("softlink target does not exist") else: self.dereference().__setitem__(key, value) def is_dangling(self): return not (self.dereference() in self._v_file) def __str__(self): """Return a short string representation of the link. Examples -------- :: >>> f = tb.open_file('tables/tests/slink.h5') >>> f.root.arr2 /arr2 (SoftLink) -> /arr >>> f.close() """ target = str(self.target) # Check for relative pathnames if not self.target.startswith('/'): target = self._v_parent._g_join(self.target) closed = "" if self._v_isopen else "closed " dangling = "" if target in self._v_file else " (dangling)" return (f"{closed}{self._v_pathname} ({self.__class__.__name__}) -> " f"{self.target}{dangling}") class ExternalLink(linkextension.ExternalLink, Link): """Represents an external link. An external link is a reference to a node in *another* file. Getting access to the pointed node (this action is called *dereferencing*) is done via the :meth:`__call__` special method (see below). .. rubric:: ExternalLink attributes .. attribute:: extfile The external file handler, if the link has been dereferenced. In case the link has not been dereferenced yet, its value is None. """ # Class identifier. _c_classid = 'EXTERNALLINK' def __init__(self, parentnode, name, target=None, _log=False): self.extfile = None """The external file handler, if the link has been dereferenced. In case the link has not been dereferenced yet, its value is None.""" super().__init__(parentnode, name, target, _log) def _get_filename_node(self): """Return the external filename and nodepath from `self.target`.""" # This is needed for avoiding the 'C:\\file.h5' filepath notation filename, target = self.target.split(':/') return filename, '/' + target def __call__(self, **kwargs): """Dereference self.target and return the object. You can pass all the arguments supported by the :func:`open_file` function (except filename, of course) so as to open the referenced external file. Examples -------- :: >>> f = tb.open_file('tables/tests/elink.h5') >>> f.root.pep.pep2 /pep/pep2 (ExternalLink) -> elink2.h5:/pep >>> pep2 = f.root.pep.pep2(mode='r') # open in 'r'ead mode >>> print(pep2) /pep (Group) '' >>> pep2._v_file.filename # belongs to referenced file 'tables/tests/elink2.h5' >>> f.close() """ filename, target = self._get_filename_node() if not Path(filename).is_absolute(): # Resolve the external link with respect to the this # file's directory. See #306. filename = str(Path(self._v_file.filename).with_name(filename)) if self.extfile is None or not self.extfile.isopen: self.extfile = tb.open_file(filename, **kwargs) else: # XXX: implement better consistency checks assert self.extfile.filename == filename assert self.extfile.mode == kwargs.get('mode', 'r') return self.extfile._get_node(target) def umount(self): """Safely unmount self.extfile, if opened.""" extfile = self.extfile # Close external file, if open if extfile is not None and extfile.isopen: extfile.close() self.extfile = None def _f_close(self): """Especific close for external links.""" self.umount() super()._f_close() def __str__(self): """Return a short string representation of the link. Examples -------- :: >>> f = tb.open_file('tables/tests/elink.h5') >>> f.root.pep.pep2 /pep/pep2 (ExternalLink) -> elink2.h5:/pep >>> f.close() """ return (f"{self._v_pathname} ({self.__class__.__name__}) -> " f"{self.target}") PyTables-3.7.0/tables/linkextension.pyx000066400000000000000000000176421416254111300201620ustar00rootroot00000000000000######################################################################## # # License: BSD # Created: November 25, 2009 # Author: Francesc Alted - faltet@pytables.com # # $Id$ # ######################################################################## """Cython functions and classes for supporting links in HDF5.""" from .exceptions import HDF5ExtError from .hdf5extension cimport Node from .utilsextension cimport cstr_to_pystr from libc.stdlib cimport malloc, free from libc.string cimport strlen from cpython.unicode cimport PyUnicode_DecodeUTF8 from .definitions cimport (H5P_DEFAULT, hid_t, herr_t, hbool_t, int64_t, H5T_cset_t, haddr_t) #---------------------------------------------------------------------- # External declarations cdef extern from "H5Lpublic.h" nogil: ctypedef enum H5L_type_t: H5L_TYPE_ERROR = (-1), # Invalid link type id H5L_TYPE_HARD = 0, # Hard link id H5L_TYPE_SOFT = 1, # Soft link id H5L_TYPE_EXTERNAL = 64, # External link id H5L_TYPE_MAX = 255 # Maximum link type id # Information struct for link (for H5Lget_info) cdef union _add_u: haddr_t address # Address hard link points to size_t val_size # Size of a soft link or UD link value ctypedef struct H5L_info_t: H5L_type_t type # Type of link hbool_t corder_valid # Indicate if creation order is valid int64_t corder # Creation order H5T_cset_t cset # Character set of link name _add_u u # Size of a soft link or UD link value # Operations with links herr_t H5Lcreate_hard( hid_t obj_loc_id, char *obj_name, hid_t link_loc_id, char *link_name, hid_t lcpl_id, hid_t lapl_id) herr_t H5Lcreate_soft( char *target_path, hid_t link_loc_id, char *link_name, hid_t lcpl_id, hid_t lapl_id) herr_t H5Lcreate_external( char *file_name, char *object_name, hid_t link_loc_id, char *link_name, hid_t lcpl_id, hid_t lapl_id) herr_t H5Lget_info( hid_t link_loc_id, char *link_name, H5L_info_t *link_buff, hid_t lapl_id) herr_t H5Lget_val( hid_t link_loc_id, char *link_name, void *linkval_buff, size_t size, hid_t lapl_id) herr_t H5Lunpack_elink_val( char *ext_linkval, size_t link_size, unsigned *flags, const char **filename, const char **obj_path) herr_t H5Lcopy( hid_t src_loc_id, char *src_name, hid_t dest_loc_id, char *dest_name, hid_t lcpl_id, hid_t lapl_id) #---------------------------------------------------------------------- # Helper functions def _get_link_class(parent_id, name): """Guess the link class.""" cdef herr_t ret cdef H5L_info_t link_buff cdef H5L_type_t link_type ret = H5Lget_info(parent_id, name, &link_buff, H5P_DEFAULT) if ret < 0: raise HDF5ExtError("failed to get info about link") link_type = link_buff.type if link_type == H5L_TYPE_SOFT: return "SoftLink" elif link_type == H5L_TYPE_EXTERNAL: return "ExternalLink" # elif link_type == H5L_TYPE_HARD: # return "HardLink" else: return "UnImplemented" def _g_create_hard_link(parentnode, str name, targetnode): """Create a hard link in the file.""" cdef herr_t ret cdef bytes encoded_name = name.encode('utf-8') cdef bytes encoded_v_name = targetnode._v_name.encode('utf-8') ret = H5Lcreate_hard(targetnode._v_parent._v_objectid, encoded_v_name, parentnode._v_objectid, encoded_name, H5P_DEFAULT, H5P_DEFAULT) if ret < 0: raise HDF5ExtError("failed to create HDF5 hard link") #---------------------------------------------------------------------- # Public classes cdef class Link(Node): """Extension class from which all link extensions inherits.""" def _g_copy(self, newparent, newname, recursive, _log=True, **kwargs): """Private part for the _f_copy() method.""" cdef herr_t ret cdef object stats cdef bytes encoded_name, encoded_newname encoded_name = self.name.encode('utf-8') encoded_newname = newname.encode('utf-8') # @TODO: set property list --> utf-8 ret = H5Lcopy(self.parent_id, encoded_name, newparent._v_objectid, encoded_newname, H5P_DEFAULT, H5P_DEFAULT) if ret < 0: raise HDF5ExtError("failed to copy HDF5 link") # Update statistics if needed. stats = kwargs.get('stats', None) if stats is not None: stats['links'] += 1 return newparent._v_file.get_node(newparent, newname) cdef class SoftLink(Link): """Extension class representing a soft link.""" def _g_create(self): """Create the link in file.""" cdef herr_t ret cdef bytes encoded_name = self.name.encode('utf-8') cdef bytes encoded_target = self.target.encode('utf-8') ret = H5Lcreate_soft(encoded_target, self.parent_id, encoded_name, H5P_DEFAULT, H5P_DEFAULT) if ret < 0: raise HDF5ExtError("failed to create HDF5 soft link") return 0 # Object ID is zero'ed, as HDF5 does not assign one for links def _g_open(self): """Open the link in file.""" cdef herr_t ret cdef H5L_info_t link_buff cdef size_t val_size cdef char *clinkval cdef bytes encoded_name encoded_name = self.name.encode('utf-8') ret = H5Lget_info(self.parent_id, encoded_name, &link_buff, H5P_DEFAULT) if ret < 0: raise HDF5ExtError("failed to get info about soft link") val_size = link_buff.u.val_size clinkval = malloc(val_size) ret = H5Lget_val(self.parent_id, encoded_name, clinkval, val_size, H5P_DEFAULT) if ret < 0: raise HDF5ExtError("failed to get target value") self.target = PyUnicode_DecodeUTF8(clinkval, strlen(clinkval), NULL) # Release resources free(clinkval) return 0 # Object ID is zero'ed, as HDF5 does not assign one for links cdef class ExternalLink(Link): """Extension class representing an external link.""" def _g_create(self): """Create the link in file.""" cdef herr_t ret cdef bytes encoded_name, encoded_filename, encoded_target encoded_name = self.name.encode('utf-8') filename, target = self._get_filename_node() encoded_filename = filename.encode('utf-8') encoded_target = target.encode('utf-8') ret = H5Lcreate_external(encoded_filename, encoded_target, self.parent_id, encoded_name, H5P_DEFAULT, H5P_DEFAULT) if ret < 0: raise HDF5ExtError("failed to create HDF5 external link") return 0 # Object ID is zero'ed, as HDF5 does not assign one for links def _g_open(self): """Open the link in file.""" cdef herr_t ret cdef H5L_info_t link_buff cdef size_t val_size cdef char *clinkval cdef char *cfilename cdef char *c_obj_path cdef unsigned flags cdef bytes encoded_name cdef str filename, obj_path encoded_name = self.name.encode('utf-8') ret = H5Lget_info(self.parent_id, encoded_name, &link_buff, H5P_DEFAULT) if ret < 0: raise HDF5ExtError("failed to get info about external link") val_size = link_buff.u.val_size clinkval = malloc(val_size) ret = H5Lget_val(self.parent_id, encoded_name, clinkval, val_size, H5P_DEFAULT) if ret < 0: raise HDF5ExtError("failed to get target value") ret = H5Lunpack_elink_val(clinkval, val_size, &flags, &cfilename, &c_obj_path) if ret < 0: raise HDF5ExtError("failed to unpack external link value") filename = cstr_to_pystr(cfilename) obj_path = cstr_to_pystr(c_obj_path) self.target = filename+':'+obj_path # Release resources free(clinkval) return 0 # Object ID is zero'ed, as HDF5 does not assign one for links ## Local Variables: ## mode: python ## py-indent-offset: 2 ## tab-width: 2 ## fill-column: 78 ## End: PyTables-3.7.0/tables/lrucacheextension.pxd000066400000000000000000000044671416254111300207670ustar00rootroot00000000000000######################################################################## # # License: BSD # Created: # Author: Francesc Alted - faltet@pytables.com # # $Id$ # ######################################################################## from numpy cimport ndarray # Declaration of instance variables for shared classes # The NodeCache class is useful for caching general objects (like Nodes). cdef class NodeCache: cdef readonly long nslots cdef long nextslot cdef object nodes, paths cdef object setitem(self, object path, object node) cdef long getslot(self, object path) cdef object cpop(self, object path) # Base class for other caches cdef class BaseCache: cdef int iscachedisabled, incsetcount cdef long setcount, getcount, containscount cdef long disablecyclecount, disableeverycycles cdef long enablecyclecount, enableeverycycles cdef double nprobes, hitratio cdef long seqn_, nextslot, nslots cdef long *ratimes cdef double lowesthr cdef ndarray atimes cdef object name cdef int checkhitratio(self) cdef int couldenablecache_(self) cdef long incseqn(self) # Helper class for ObjectCache cdef class ObjectNode: cdef object key, obj cdef long nslot # The ObjectCache class is useful for general python objects cdef class ObjectCache(BaseCache): cdef long maxcachesize, cachesize, maxobjsize cdef long *rsizes cdef ndarray sizes cdef object __list, __dict cdef ObjectNode mrunode cdef removeslot_(self, long nslot) cdef clearcache_(self) cdef updateslot_(self, long nslot, long size, object key, object value) cdef long setitem_(self, object key, object value, long size) cdef long getslot_(self, object key) cdef object getitem_(self, long nslot) # The NumCache class is useful for caching numerical data in an efficient way cdef class NumCache(BaseCache): cdef long itemsize, slotsize cdef ndarray cacheobj, keys cdef void *rcache cdef long long *rkeys cdef object __dict cdef void *getaddrslot_(self, long nslot) cdef long setitem_(self, long long key, void *data, long start) cdef long setitem1_(self, long long key) cdef long getslot_(self, long long key) cdef getitem_(self, long nslot, void *data, long start) cdef void *getitem1_(self, long nslot) ## Local Variables: ## mode: python ## py-indent-offset: 2 ## tab-width: 2 ## fill-column: 78 ## End: PyTables-3.7.0/tables/lrucacheextension.pyx000066400000000000000000000522001416254111300210000ustar00rootroot00000000000000######################################################################## # # License: BSD # Created: Aug 13, 2006 # Author: Francesc Alted - faltet@pytables.com # # $Id: $ # ######################################################################## """Cython interface for several LRU cache systems. Classes (type extensions): NodeCache ObjectCache NumCache Functions: Misc variables: """ cdef extern from "Python.h": int PyUnicode_Compare(object, object) import sys import numpy from libc.string cimport memcpy, strcmp from cpython.unicode cimport PyUnicode_Check from numpy cimport import_array, ndarray, PyArray_DATA from .parameters import (DISABLE_EVERY_CYCLES, ENABLE_EVERY_CYCLES, LOWEST_HIT_RATIO) #---------------------------------------------------------------------------- # Initialization code. # The numpy API requires this function to be called before # using any numpy facilities in an extension module. import_array() #---------------------------------------------------------------------------- # ------- Minimalist NodeCache for nodes in PyTables --------- # The next NodeCache code relies on the fact that a node that is # fetched from the cache will be removed from it. Said in other words: # "A node cannot be alive and dead at the same time." # Thanks to the above behaviour, the next code has been stripped down # to a bare minimum (the info in cache is kept in just 2 lists). #*********************** Important note! ***************************** # The code behind has been carefully tuned to serve the needs of # PyTables cache for nodes. As a consequence, it is no longer # appropriate as a general LRU cache implementation. You have been # warned!. F. Alted 2006-08-08 #********************************************************************* cdef class NodeCache: """Least-Recently-Used (LRU) cache for PyTables nodes.""" def __init__(self, nslots): """Maximum nslots of the cache. If more than 'nslots' elements are added to the cache, the least-recently-used ones will be discarded. """ if nslots < 0: raise ValueError("Negative number (%s) of slots!" % nslots) self.nslots = nslots self.nextslot = 0 self.nodes = [] self.paths = [] def __len__(self): return len(self.nodes) def __setitem__(self, path, node): self.setitem(path, node) cdef setitem(self, object path, object node): """Puts a new node in the node list.""" if self.nslots == 0: # Oops, the cache is set to empty return # Check if we are growing out of space if self.nextslot == self.nslots: # It is critical to reduce nextslot *before* the preemption of # the LRU node. If not, this can lead with problems in situations # with very small caches (length 1 or so). # F. Alted 2008-10-22 self.nextslot = self.nextslot - 1 # Remove the LRU node and path (the start of the lists) del self.nodes[0] del self.paths[0] # The equality protection has been put for situations in which a # node is being preempted and added simultaneously (with very small # caches). if len(self.nodes) == len(self.paths): # Add the node and path to the end of its lists self.nodes.append(node) self.paths.append(path) self.nextslot = self.nextslot + 1 def __contains__(self, path): if self.getslot(path) == -1: return 0 else: return 1 cdef long getslot(self, object path): """Checks whether path is in this cache or not.""" cdef long i, nslot, compare nslot = -1 # -1 means not found if PyUnicode_Check(path): # Start looking from the trailing values (most recently used) for i from self.nextslot > i >= 0: #if strcmp(encoded_path, self.paths[i]) == 0: if PyUnicode_Compare(path, self.paths[i]) == 0: nslot = i break else: # Start looking from the trailing values (most recently used) for i from self.nextslot > i >= 0: #if strcmp(path, self.paths[i]) == 0: if PyUnicode_Check(self.paths[i]): compare = PyUnicode_Compare(path, self.paths[i]) else: compare = strcmp(path, self.paths[i]) if compare == 0: nslot = i break return nslot __marker = object() def pop(self, path, d=__marker): try: node = self.cpop(path) except KeyError: if d is not self.__marker: return d else: raise else: return node cdef object cpop(self, object path): cdef long nslot nslot = self.getslot(path) if nslot == -1: raise KeyError(path) else: node = self.nodes[nslot] del self.nodes[nslot] del self.paths[nslot] self.nextslot = self.nextslot - 1 return node def __iter__(self): # Do a copy of the paths list because it can be modified in the middle of # the iterator! copy = self.paths[:] return iter(copy) def __repr__(self): return "<%s (%d elements)>" % (str(self.__class__), len(self.paths)) ######################################################################## # Common code for other LRU cache classes ######################################################################## cdef class BaseCache: """Base class that implements automatic probing/disabling of the cache.""" def __init__(self, long nslots, object name): if nslots < 0: raise ValueError("Negative number (%s) of slots!" % nslots) self.setcount = 0; self.getcount = 0; self.containscount = 0 self.enablecyclecount = 0; self.disablecyclecount = 0 self.iscachedisabled = False # Cache is enabled by default self.disableeverycycles = DISABLE_EVERY_CYCLES self.enableeverycycles = ENABLE_EVERY_CYCLES self.lowesthr = LOWEST_HIT_RATIO self.nprobes = 0.0; self.hitratio = 0.0 self.nslots = nslots self.seqn_ = 0; self.nextslot = 0 self.name = name self.incsetcount = False # The array for keeping the access times (using long ints here) self.atimes = numpy.zeros(shape=nslots, dtype=numpy.int_) self.ratimes = PyArray_DATA(self.atimes) def __len__(self): return self.nslots # Machinery for determining whether the hit ratio is being effective # or not. If not, the cache will be disabled. The efficency will be # checked every cycle (the time that the cache would be refilled # completely). In situations where the cache is not being re-filled # (i.e. it is not enabled) for a long time, it is forced to be # re-enabled when a certain number of cycles has passed so as to # check whether a new scenario where the cache can be useful again # has come. # F. Alted 2006-08-09 cdef int checkhitratio(self): cdef double hitratio cdef long nslot if self.setcount > self.nslots: self.disablecyclecount = self.disablecyclecount + 1 self.enablecyclecount = self.enablecyclecount + 1 self.nprobes = self.nprobes + 1 hitratio = self.getcount / self.containscount self.hitratio = self.hitratio + hitratio # Reset the hit counters self.setcount = 0; self.getcount = 0; self.containscount = 0 if (not self.iscachedisabled and self.disablecyclecount >= self.disableeverycycles): # Check whether the cache is being effective or not if hitratio < self.lowesthr: # Hit ratio is low. Disable the cache. self.iscachedisabled = True else: # Hit ratio is acceptable. (Re-)Enable the cache. self.iscachedisabled = False self.disablecyclecount = 0 if self.enablecyclecount >= self.enableeverycycles: # We have reached the time for forcing the cache to act again self.iscachedisabled = False self.enablecyclecount = 0 return not self.iscachedisabled def couldenablecache(self): return self.couldenablecache_() # Check whether the cache is enabled or *could* be enabled in the next # setitem operation. This method can be used in order to probe whether # an (expensive) operation to be done before a .setitem() is worth the # effort or not. cdef int couldenablecache_(self): if self.nslots == 0: return False # Increment setitem because it can be that .setitem() doesn't # get called after calling this. self.setcount = self.setcount + 1; self.incsetcount = True if self.iscachedisabled: if self.setcount == self.nslots: # The cache *could* be enabled in the next setitem operation return True else: return False else: return True # Increase the access time (implemented as a C long sequence) cdef long incseqn(self): self.seqn_ = self.seqn_ + 1 if self.seqn_ < 0: # Ooops, the counter has run out of range! Reset all the access times. self.atimes[:] = sys.maxint # Set the counter to 1 (to indicate that it is newer than existing ones) self.seqn_ = 1 return self.seqn_ def __repr__(self): return "<%s(%s) (%d elements)>" % (self.name, str(self.__class__), self.nslots) ######################################################################## # Helper class for ObjectCache ######################################################################## cdef class ObjectNode: """Record of a cached value. Not for public consumption.""" def __init__(self, object key, object obj, long nslot): object.__init__(self) self.key = key self.obj = obj self.nslot = nslot def __repr__(self): return "<%s %s (slot #%s) => %s>" % (self.__class__, self.key, self.nslot, self.object) ######################################################################## # Minimalistic LRU cache implementation for general python objects # This is a *true* general lru cache for python objects ######################################################################## cdef class ObjectCache(BaseCache): """Least-Recently-Used (LRU) cache specific for python objects.""" def __init__(self, long nslots, long maxcachesize, object name): """Maximum size of the cache. If more than 'nslots' elements are added to the cache, the least-recently-used ones will be discarded. Parameters: nslots - The number of slots in cache name - A descriptive name for this cache """ super().__init__(nslots, name) self.cachesize = 0 self.maxcachesize = maxcachesize # maxobjsize will be the same as the maximum cache size self.maxobjsize = maxcachesize self.__list = [None]*nslots self.__dict = {} self.mrunode = None # Most Recent Used node # The array for keeping the object size (using long ints here) self.sizes = numpy.zeros(shape=nslots, dtype=numpy.int_) self.rsizes = PyArray_DATA(self.sizes) # Clear cache cdef clearcache_(self): self.__list = [None]*self.nslots self.__dict = {} self.mrunode = None self.cachesize = 0 self.nextslot = 0 self.seqn_ = 0 # Remove a slot (if it exists in cache) cdef removeslot_(self, long nslot): cdef ObjectNode node assert nslot < self.nslots, "Attempting to remove beyond cache capacity." node = self.__list[nslot] if node is not None: self.__list[nslot] = None del self.__dict[node.key] self.cachesize = self.cachesize - self.rsizes[nslot] self.rsizes[nslot] = 0 if self.mrunode and self.mrunode.nslot == nslot: self.mrunode = None # The next slot to be updated will be this one self.nextslot = nslot # Update a slot cdef updateslot_(self, long nslot, long size, object key, object value): cdef ObjectNode node, oldnode cdef long nslot1, nslot2 cdef object lruidx assert nslot < self.nslots, "Number of nodes exceeding cache capacity." # Remove the previous nslot self.removeslot_(nslot) # Protection against too large data cache size while size + self.cachesize > self.maxcachesize: # Remove the LRU node among the 10 largest ones largidx = self.sizes.argsort()[-10:] nslot1 = self.atimes[largidx].argmin() nslot2 = largidx[nslot1] self.removeslot_(nslot2) # Insert the new one node = ObjectNode(key, value, nslot) self.ratimes[nslot] = self.incseqn() self.rsizes[nslot] = size self.__list[nslot] = node self.__dict[key] = node self.mrunode = node self.cachesize = self.cachesize + size # The next slot to update will be the LRU self.nextslot = self.atimes.argmin() # Put the object to the data in cache (for Python calls) def setitem(self, object key, object value, object size): return self.setitem_(key, value, size) # Put the object in cache (for cython calls) # size can be the exact size of the value object or an estimation. cdef long setitem_(self, object key, object value, long size): cdef long nslot if self.nslots == 0: # The cache has been set to empty return -1 nslot = -1 # Perhaps setcount has been already incremented in couldenablecache() if not self.incsetcount: self.setcount = self.setcount + 1 else: self.incsetcount = False if size > self.maxobjsize: # Check if the object is too large return -1 if self.checkhitratio(): nslot = self.nextslot self.updateslot_(nslot, size, key, value) else: # Empty the cache because it is not effective and it is taking space self.clearcache_() return nslot # Tells whether the key is in cache or not def __contains__(self, object key): return self.__dict.has_key(key) # Tells in which slot the key is. If not found, -1 is returned. def getslot(self, object key): return self.getslot_(key) # Tells in which slot the key is. If not found, -1 is returned. cdef long getslot_(self, object key): cdef ObjectNode node if self.nslots == 0: # The cache has been set to empty return -1 self.containscount = self.containscount + 1 # Give a chance to the MRU node node = self.mrunode if node and node.key == key: return node.nslot # No luck. Look in the dictionary. node = self.__dict.get(key) if node is None: return -1 return node.nslot # Return the object to the data in cache (for Python calls) def getitem(self, object nslot): return self.getitem_(nslot) # Return the object to the data in cache (for cython calls) cdef object getitem_(self, long nslot): cdef ObjectNode node self.getcount = self.getcount + 1 node = self.__list[nslot] self.ratimes[nslot] = self.incseqn() self.mrunode = node return node.obj def __repr__(self): if self.nprobes > 0: hitratio = self.hitratio / self.nprobes else: hitratio = self.getcount / self.containscount return """<%s(%s) (%d maxslots, %d slots used, %.3f KB cachesize, hit ratio: %.3f, disabled? %s)> """ % (self.name, str(self.__class__), self.nslots, self.nextslot, self.cachesize / 1024., hitratio, self.iscachedisabled) ################################################################### # Minimalistic LRU cache implementation for numerical data ################################################################### # The next code is more efficient in situations where efficiency is low. ################################################################### #*********************** Important note! **************************** # The code behind has been carefully tuned to serve the needs of # caching numerical data. As a consequence, it is no longer appropriate # as a general LRU cache implementation. You have been warned!. # F. Alted 2006-08-09 #******************************************************************** cdef class NumCache(BaseCache): """Least-Recently-Used (LRU) cache specific for Numerical data.""" def __init__(self, object shape, object dtype, object name): """Maximum size of the cache. If more than 'nslots' elements are added to the cache, the least-recently-used ones will be discarded. Parameters: shape - The rectangular shape of the cache (nslots, nelemsperslot) itemsize - The size of the element base in cache name - A descriptive name for this cache """ cdef long nslots nslots = shape[0]; self.slotsize = shape[1] if nslots >= 1<<16: # nslots can't be higher than 2**16. Will silently trunk the number. nslots = ((1<<16)-1) # Cast makes cython happy here super().__init__(nslots, name) self.itemsize = dtype.itemsize self.__dict = {} # The cache object where all data will go # The last slot is to allow the setitem1_ method to still return # a valid scratch area for writing purposes self.cacheobj = numpy.empty(shape=(nslots+1, self.slotsize), dtype=dtype) self.rcache = PyArray_DATA(self.cacheobj) # The array for keeping the keys of slots self.keys = (-numpy.ones(shape=nslots, dtype=numpy.int64)) self.rkeys = PyArray_DATA(self.keys) # Returns the address of nslot cdef void *getaddrslot_(self, long nslot): if nslot >= 0: return self.rcache + nslot * self.slotsize * self.itemsize else: return self.rcache + self.nslots * self.slotsize * self.itemsize def setitem(self, long long key, ndarray nparr, long start): return self.setitem_(key, PyArray_DATA(nparr), start) # Copy the new data into a cache slot cdef long setitem_(self, long long key, void *data, long start): cdef long nslot nslot = self.setitem1_(key) if nslot >= 0: # Copy the data to cache memcpy(self.rcache + nslot * self.slotsize * self.itemsize, data + start * self.itemsize, self.slotsize * self.itemsize) return nslot # Return a cache data pointer appropriate to save data. # Even if the cache is disabled, this will return a -1, which is # the last element in the cache. # This version avoids a memcpy of data, but the user should be # aware that data in nslot cannot be overwritten! cdef long setitem1_(self, long long key): cdef long nslot cdef object key2 if self.nslots == 0: # Oops, the cache is set to empty return -1 # Perhaps setcount has been already incremented in couldenablecache() if not self.incsetcount: self.setcount = self.setcount + 1 else: self.incsetcount = False nslot = -1 if self.checkhitratio(): # Check if we are growing out of space if self.nextslot == self.nslots: # Get the least recently used slot nslot = self.atimes.argmin() # Remove the slot from the dict key2 = self.keys[nslot] del self.__dict[key2] self.nextslot = self.nextslot - 1 else: # Get the next slot available nslot = self.nextslot # Insert the slot in the dictionary self.__dict[key] = nslot self.keys[nslot] = key self.ratimes[nslot] = self.incseqn() self.nextslot = self.nextslot + 1 # The next reduces the performance of the cache in scenarios where # the efficicency is near to zero. I don't understand exactly why. # F. Alted 24-03-2008 elif self.nextslot > 0: # Empty the cache if needed self.__dict.clear() self.nextslot = 0 return nslot def getslot(self, long long key): return self.getslot_(key) # Tells in which slot key is. If not found, -1 is returned. cdef long getslot_(self, long long key): cdef object nslot self.containscount = self.containscount + 1 if self.nextslot == 0: # No chances for finding a slot return -1 try: nslot = self.__dict[key] except KeyError: return -1 return nslot def getitem(self, long nslot, ndarray nparr, long start): self.getitem_(nslot, PyArray_DATA(nparr), start) # This version copies data in cache to data+start. # The user should be responsible to provide a large enough data buffer # to keep all the data. cdef getitem_(self, long nslot, void *data, long start): cdef void *cachedata cachedata = self.getitem1_(nslot) # Copy the data in cache to destination memcpy(data + start * self.itemsize, cachedata, self.slotsize * self.itemsize) # Return the pointer to the data in cache # This version avoids a memcpy of data, but the user should be # aware that data in nslot cannot be overwritten! cdef void *getitem1_(self, long nslot): self.getcount = self.getcount + 1 self.ratimes[nslot] = self.incseqn() return self.rcache + nslot * self.slotsize * self.itemsize def __repr__(self): cachesize = (self.nslots * self.slotsize * self.itemsize) / 1024. if self.nprobes > 0: hitratio = self.hitratio / self.nprobes elif self.containscount > 0: hitratio = self.getcount / self.containscount else: hitratio = numpy.nan return """<%s(%s) (%d maxslots, %d slots used, %.3f KB cachesize, hit ratio: %.3f, disabled? %s)> """ % (self.name, str(self.__class__), self.nslots, self.nextslot, cachesize, hitratio, self.iscachedisabled) ## Local Variables: ## mode: python ## py-indent-offset: 2 ## tab-width: 2 ## fill-column: 78 ## End: PyTables-3.7.0/tables/misc/000077500000000000000000000000001416254111300154475ustar00rootroot00000000000000PyTables-3.7.0/tables/misc/__init__.py000066400000000000000000000003351416254111300175610ustar00rootroot00000000000000"""Miscellaneous general-purpose modules The purpose, authorship and license of modules in this package is diverse, and they may be useful outside of PyTables. Please read their source code for further information. """ PyTables-3.7.0/tables/misc/enum.py000066400000000000000000000315171416254111300167740ustar00rootroot00000000000000"""Implementation of enumerated types. This module provides the `Enum` class, which can be used to construct enumerated types. Those types are defined by providing an *exhaustive set or list* of possible, named values for a variable of that type. Enumerated variables of the same type are usually compared between them for equality and sometimes for order, but are not usually operated upon. Enumerated values have an associated *name* and *concrete value*. Every name is unique and so are concrete values. An enumerated variable always takes the concrete value, not its name. Usually, the concrete value is not used directly, and frequently it is entirely irrelevant. For the same reason, an enumerated variable is not usually compared with concrete values out of its enumerated type. For that kind of use, standard variables and constants are more adequate. """ __docformat__ = 'reStructuredText' """The format of documentation strings in this module.""" class Enum: """Enumerated type. Each instance of this class represents an enumerated type. The values of the type must be declared *exhaustively* and named with *strings*, and they might be given explicit concrete values, though this is not compulsory. Once the type is defined, it can not be modified. There are three ways of defining an enumerated type. Each one of them corresponds to the type of the only argument in the constructor of Enum: - *Sequence of names*: each enumerated value is named using a string, and its order is determined by its position in the sequence; the concrete value is assigned automatically:: >>> boolEnum = Enum(['True', 'False']) - *Mapping of names*: each enumerated value is named by a string and given an explicit concrete value. All of the concrete values must be different, or a ValueError will be raised:: >>> priority = Enum({'red': 20, 'orange': 10, 'green': 0}) >>> colors = Enum({'red': 1, 'blue': 1}) Traceback (most recent call last): ... ValueError: enumerated values contain duplicate concrete values: 1 - *Enumerated type*: in that case, a copy of the original enumerated type is created. Both enumerated types are considered equal:: >>> prio2 = Enum(priority) >>> priority == prio2 True Please note that names starting with _ are not allowed, since they are reserved for internal usage:: >>> prio2 = Enum(['_xx']) Traceback (most recent call last): ... ValueError: name of enumerated value can not start with ``_``: '_xx' The concrete value of an enumerated value is obtained by getting its name as an attribute of the Enum instance (see __getattr__()) or as an item (see __getitem__()). This allows comparisons between enumerated values and assigning them to ordinary Python variables:: >>> redv = priority.red >>> redv == priority['red'] True >>> redv > priority.green True >>> priority.red == priority.orange False The name of the enumerated value corresponding to a concrete value can also be obtained by using the __call__() method of the enumerated type. In this way you get the symbolic name to use it later with __getitem__():: >>> priority(redv) 'red' >>> priority.red == priority[priority(priority.red)] True (If you ask, the __getitem__() method is not used for this purpose to avoid ambiguity in the case of using strings as concrete values.) """ def __init__(self, enum): mydict = self.__dict__ mydict['_names'] = {} mydict['_values'] = {} if isinstance(enum, list) or isinstance(enum, tuple): for (value, name) in enumerate(enum): # values become 0, 1, 2... self._check_and_set_pair(name, value) elif isinstance(enum, dict): for (name, value) in enum.items(): self._check_and_set_pair(name, value) elif isinstance(enum, Enum): for (name, value) in enum._names.items(): self._check_and_set_pair(name, value) else: raise TypeError("""\ enumerations can only be created from \ sequences, mappings and other enumerations""") def _check_and_set_pair(self, name, value): """Check validity of enumerated value and insert it into type.""" names = self._names values = self._values if not isinstance(name, str): raise TypeError( f"name of enumerated value is not a string: {name!r}") if name.startswith('_'): raise ValueError( "name of enumerated value can not start with ``_``: %r" % name) # This check is only necessary with a sequence base object. if name in names: raise ValueError( "enumerated values contain duplicate names: %r" % name) # This check is only necessary with a mapping base object. if value in values: raise ValueError( "enumerated values contain duplicate concrete values: %r" % value) names[name] = value values[value] = name self.__dict__[name] = value def __getitem__(self, name): """Get the concrete value of the enumerated value with that name. The name of the enumerated value must be a string. If there is no value with that name in the enumeration, a KeyError is raised. Examples -------- Let ``enum`` be an enumerated type defined as: >>> enum = Enum({'T0': 0, 'T1': 2, 'T2': 5}) then: >>> enum['T1'] 2 >>> enum['foo'] Traceback (most recent call last): ... KeyError: "no enumerated value with that name: 'foo'" """ try: return self._names[name] except KeyError: raise KeyError(f"no enumerated value with that name: {name!r}") def __setitem__(self, name, value): """This operation is forbidden.""" raise IndexError("operation not allowed") def __delitem__(self, name): """This operation is forbidden.""" raise IndexError("operation not allowed") def __getattr__(self, name): """Get the concrete value of the enumerated value with that name. The name of the enumerated value must be a string. If there is no value with that name in the enumeration, an AttributeError is raised. Examples -------- Let ``enum`` be an enumerated type defined as: >>> enum = Enum({'T0': 0, 'T1': 2, 'T2': 5}) then: >>> enum.T1 2 >>> enum.foo Traceback (most recent call last): ... AttributeError: no enumerated value with that name: 'foo' """ try: return self[name] except KeyError as ke: raise AttributeError(*ke.args) def __setattr__(self, name, value): """This operation is forbidden.""" raise AttributeError("operation not allowed") def __delattr__(self, name): """This operation is forbidden.""" raise AttributeError("operation not allowed") def __contains__(self, name): """Is there an enumerated value with that name in the type? If the enumerated type has an enumerated value with that name, True is returned. Otherwise, False is returned. The name must be a string. This method does *not* check for concrete values matching a value in an enumerated type. For that, please use the :meth:`Enum.__call__` method. Examples -------- Let ``enum`` be an enumerated type defined as: >>> enum = Enum({'T0': 0, 'T1': 2, 'T2': 5}) then: >>> 'T1' in enum True >>> 'foo' in enum False >>> 0 in enum Traceback (most recent call last): ... TypeError: name of enumerated value is not a string: 0 >>> enum.T1 in enum # Be careful with this! Traceback (most recent call last): ... TypeError: name of enumerated value is not a string: 2 """ if not isinstance(name, str): raise TypeError( f"name of enumerated value is not a string: {name!r}") return name in self._names def __call__(self, value, *default): """Get the name of the enumerated value with that concrete value. If there is no value with that concrete value in the enumeration and a second argument is given as a default, this is returned. Else, a ValueError is raised. This method can be used for checking that a concrete value belongs to the set of concrete values in an enumerated type. Examples -------- Let ``enum`` be an enumerated type defined as: >>> enum = Enum({'T0': 0, 'T1': 2, 'T2': 5}) then: >>> enum(5) 'T2' >>> enum(42, None) is None True >>> enum(42) Traceback (most recent call last): ... ValueError: no enumerated value with that concrete value: 42 """ try: return self._values[value] except KeyError: if len(default) > 0: return default[0] raise ValueError( f"no enumerated value with that concrete value: {value!r}") def __len__(self): """Return the number of enumerated values in the enumerated type. Examples -------- >>> len(Enum(['e%d' % i for i in range(10)])) 10 """ return len(self._names) def __iter__(self): """Iterate over the enumerated values. Enumerated values are returned as (name, value) pairs *in no particular order*. Examples -------- >>> enumvals = {'red': 4, 'green': 2, 'blue': 1} >>> enum = Enum(enumvals) >>> enumdict = dict([(name, value) for (name, value) in enum]) >>> enumvals == enumdict True """ yield from self._names.items() def __eq__(self, other): """Is the other enumerated type equivalent to this one? Two enumerated types are equivalent if they have exactly the same enumerated values (i.e. with the same names and concrete values). Examples -------- Let ``enum*`` be enumerated types defined as: >>> enum1 = Enum({'T0': 0, 'T1': 2}) >>> enum2 = Enum(enum1) >>> enum3 = Enum({'T1': 2, 'T0': 0}) >>> enum4 = Enum({'T0': 0, 'T1': 2, 'T2': 5}) >>> enum5 = Enum({'T0': 0}) >>> enum6 = Enum({'T0': 10, 'T1': 20}) then: >>> enum1 == enum1 True >>> enum1 == enum2 == enum3 True >>> enum1 == enum4 False >>> enum5 == enum1 False >>> enum1 == enum6 False Comparing enumerated types with other kinds of objects produces a false result: >>> enum1 == {'T0': 0, 'T1': 2} False >>> enum1 == ['T0', 'T1'] False >>> enum1 == 2 False """ if not isinstance(other, Enum): return False return self._names == other._names def __ne__(self, other): """Is the `other` enumerated type different from this one? Two enumerated types are different if they don't have exactly the same enumerated values (i.e. with the same names and concrete values). Examples -------- Let ``enum*`` be enumerated types defined as: >>> enum1 = Enum({'T0': 0, 'T1': 2}) >>> enum2 = Enum(enum1) >>> enum3 = Enum({'T1': 2, 'T0': 0}) >>> enum4 = Enum({'T0': 0, 'T1': 2, 'T2': 5}) >>> enum5 = Enum({'T0': 0}) >>> enum6 = Enum({'T0': 10, 'T1': 20}) then: >>> enum1 != enum1 False >>> enum1 != enum2 != enum3 False >>> enum1 != enum4 True >>> enum5 != enum1 True >>> enum1 != enum6 True """ return not self.__eq__(other) # XXX: API incompatible change for PyTables 3 line # Overriding __eq__ blocks inheritance of __hash__ in 3.x # def __hash__(self): # return hash((self.__class__, tuple(self._names.items()))) def __repr__(self): """Return the canonical string representation of the enumeration. The output of this method can be evaluated to give a new enumeration object that will compare equal to this one. Examples -------- >>> repr(Enum({'name': 10})) "Enum({'name': 10})" """ return 'Enum(%s)' % self._names def _test(): import doctest return doctest.testmod() if __name__ == '__main__': _test() PyTables-3.7.0/tables/misc/proxydict.py000066400000000000000000000034731416254111300200550ustar00rootroot00000000000000"""Proxy dictionary for objects stored in a container.""" import weakref class ProxyDict(dict): """A dictionary which uses a container object to store its values.""" def __init__(self, container): self.containerref = weakref.ref(container) """A weak reference to the container object. .. versionchanged:: 3.0 The *containerRef* attribute has been renamed into *containerref*. """ def __getitem__(self, key): if key not in self: raise KeyError(key) # Values are not actually stored to avoid extra references. return self._get_value_from_container(self._get_container(), key) def __setitem__(self, key, value): # Values are not actually stored to avoid extra references. super().__setitem__(key, None) def __repr__(self): return object.__repr__(self) def __str__(self): # C implementation does not use `self.__getitem__()`. :( return '{' + ", ".join("{k!r}: {v!r}" for k, v in self.items()) + '}' def values(self): # C implementation does not use `self.__getitem__()`. :( return [self[key] for key in self.keys()] def itervalues(self): # C implementation does not use `self.__getitem__()`. :( for key in self.keys(): yield self[key] def items(self): # C implementation does not use `self.__getitem__()`. :( return [(key, self[key]) for key in self.keys()] def iteritems(self): # C implementation does not use `self.__getitem__()`. :( for key in self.keys(): yield (key, self[key]) def _get_container(self): container = self.containerref() if container is None: raise ValueError("the container object does no longer exist") return container PyTables-3.7.0/tables/node.py000066400000000000000000000766631416254111300160350ustar00rootroot00000000000000"""PyTables nodes.""" import warnings import functools from .registry import class_name_dict, class_id_dict from .exceptions import (ClosedNodeError, NodeError, UndoRedoWarning, PerformanceWarning) from .path import join_path, split_path, isvisiblepath from .utils import lazyattr from .undoredo import move_to_shadow from .attributeset import AttributeSet, NotLoggedAttributeSet __docformat__ = 'reStructuredText' """The format of documentation strings in this module.""" def _closedrepr(oldmethod): """Decorate string representation method to handle closed nodes. If the node is closed, a string like this is returned:: instead of calling `oldmethod` and returning its result. """ @functools.wraps(oldmethod) def newmethod(self): if not self._v_isopen: return (f'') return oldmethod(self) return newmethod class MetaNode(type): """Node metaclass. This metaclass ensures that their instance classes get registered into several dictionaries (namely the `tables.utils.class_name_dict` class name dictionary and the `tables.utils.class_id_dict` class identifier dictionary). It also adds sanity checks to some methods: * Check that the node is open when calling string representation and provide a default string if so. """ def __new__(mcs, name, bases, dict_): # Add default behaviour for representing closed nodes. for mname in ['__str__', '__repr__']: if mname in dict_: dict_[mname] = _closedrepr(dict_[mname]) return type.__new__(mcs, name, bases, dict_) def __init__(cls, name, bases, dict_): super().__init__(name, bases, dict_) # Always register into class name dictionary. class_name_dict[cls.__name__] = cls # Register into class identifier dictionary only if the class # has an identifier and it is different from its parents'. cid = getattr(cls, '_c_classid', None) if cid is not None: for base in bases: pcid = getattr(base, '_c_classid', None) if pcid == cid: break else: class_id_dict[cid] = cls class Node(metaclass=MetaNode): """Abstract base class for all PyTables nodes. This is the base class for *all* nodes in a PyTables hierarchy. It is an abstract class, i.e. it may not be directly instantiated; however, every node in the hierarchy is an instance of this class. A PyTables node is always hosted in a PyTables *file*, under a *parent group*, at a certain *depth* in the node hierarchy. A node knows its own *name* in the parent group and its own *path name* in the file. All the previous information is location-dependent, i.e. it may change when moving or renaming a node in the hierarchy. A node also has location-independent information, such as its *HDF5 object identifier* and its *attribute set*. This class gathers the operations and attributes (both location-dependent and independent) which are common to all PyTables nodes, whatever their type is. Nonetheless, due to natural naming restrictions, the names of all of these members start with a reserved prefix (see the Group class in :ref:`GroupClassDescr`). Sub-classes with no children (e.g. *leaf nodes*) may define new methods, attributes and properties to avoid natural naming restrictions. For instance, _v_attrs may be shortened to attrs and _f_rename to rename. However, the original methods and attributes should still be available. .. rubric:: Node attributes .. attribute:: _v_depth The depth of this node in the tree (an non-negative integer value). .. attribute:: _v_file The hosting File instance (see :ref:`FileClassDescr`). .. attribute:: _v_name The name of this node in its parent group (a string). .. attribute:: _v_pathname The path of this node in the tree (a string). .. attribute:: _v_objectid A node identifier (may change from run to run). .. versionchanged:: 3.0 The *_v_objectID* attribute has been renamed into *_v_object_id*. """ # By default, attributes accept Undo/Redo. _AttributeSet = AttributeSet # `_v_parent` is accessed via its file to avoid upwards references. def _g_getparent(self): """The parent :class:`Group` instance""" (parentpath, nodename) = split_path(self._v_pathname) return self._v_file._get_node(parentpath) _v_parent = property(_g_getparent) # '_v_attrs' is defined as a lazy read-only attribute. # This saves 0.7s/3.8s. @lazyattr def _v_attrs(self): """The associated `AttributeSet` instance. See Also -------- tables.attributeset.AttributeSet : container for the HDF5 attributes """ return self._AttributeSet(self) # '_v_title' is a direct read-write shorthand for the 'TITLE' attribute # with the empty string as a default value. def _g_gettitle(self): """A description of this node. A shorthand for TITLE attribute.""" if hasattr(self._v_attrs, 'TITLE'): return self._v_attrs.TITLE else: return '' def _g_settitle(self, title): self._v_attrs.TITLE = title _v_title = property(_g_gettitle, _g_settitle) # This may be looked up by ``__del__`` when ``__init__`` doesn't get # to be called. See ticket #144 for more info. _v_isopen = False """Whehter this node is open or not.""" # The ``_log`` argument is only meant to be used by ``_g_copy_as_child()`` # to avoid logging the creation of children nodes of a copied sub-tree. def __init__(self, parentnode, name, _log=True): # Remember to assign these values in the root group constructor # as it does not use this method implementation! # if the parent node is a softlink, dereference it if isinstance(parentnode, class_name_dict['SoftLink']): parentnode = parentnode.dereference() self._v_file = None """The hosting File instance (see :ref:`FileClassDescr`).""" self._v_isopen = False """Whether this node is open or not.""" self._v_pathname = None """The path of this node in the tree (a string).""" self._v_name = None """The name of this node in its parent group (a string).""" self._v_depth = None """The depth of this node in the tree (an non-negative integer value). """ self._v_maxtreedepth = parentnode._v_file.params['MAX_TREE_DEPTH'] """Maximum tree depth before warning the user. .. versionchanged:: 3.0 Renamed into *_v_maxtreedepth* from *_v_maxTreeDepth*. """ self._v__deleting = False """Is the node being deleted?""" self._v_objectid = None """A node identifier (may change from run to run). .. versionchanged:: 3.0 The *_v_objectID* attribute has been renamed into *_v_objectid*. """ validate = new = self._v_new # set by subclass constructor # Is the parent node a group? Is it open? self._g_check_group(parentnode) parentnode._g_check_open() file_ = parentnode._v_file # Will the file be able to host a new node? if new: file_._check_writable() # Bind to the parent node and set location-dependent information. if new: # Only new nodes need to be referenced. # Opened nodes are already known by their parent group. parentnode._g_refnode(self, name, validate) self._g_set_location(parentnode, name) try: # hdf5extension operations: # Update node attributes. self._g_new(parentnode, name, init=True) # Create or open the node and get its object ID. if new: self._v_objectid = self._g_create() else: self._v_objectid = self._g_open() # The node *has* been created, log that. if new and _log and file_.is_undo_enabled(): self._g_log_create() # This allows extra operations after creating the node. self._g_post_init_hook() except Exception: # If anything happens, the node must be closed # to undo every possible registration made so far. # We do *not* rely on ``__del__()`` doing it later, # since it might never be called anyway. self._f_close() raise def _g_log_create(self): self._v_file._log('CREATE', self._v_pathname) def __del__(self): # Closed `Node` instances can not be killed and revived. # Instead, accessing a closed and deleted (from memory, not # disk) one yields a *new*, open `Node` instance. This is # because of two reasons: # # 1. Predictability. After closing a `Node` and deleting it, # only one thing can happen when accessing it again: a new, # open `Node` instance is returned. If closed nodes could be # revived, one could get either a closed or an open `Node`. # # 2. Ease of use. If the user wants to access a closed node # again, the only condition would be that no references to # the `Node` instance were left. If closed nodes could be # revived, the user would also need to force the closed # `Node` out of memory, which is not a trivial task. # if not self._v_isopen: return # the node is already closed or not initialized self._v__deleting = True # If we get here, the `Node` is still open. try: node_manager = self._v_file._node_manager node_manager.drop_node(self, check_unregistered=False) finally: # At this point the node can still be open if there is still some # alive reference around (e.g. if the __del__ method is called # explicitly by the user). if self._v_isopen: self._v__deleting = True self._f_close() def _g_pre_kill_hook(self): """Code to be called before killing the node.""" pass def _g_create(self): """Create a new HDF5 node and return its object identifier.""" raise NotImplementedError def _g_open(self): """Open an existing HDF5 node and return its object identifier.""" raise NotImplementedError def _g_check_open(self): """Check that the node is open. If the node is closed, a `ClosedNodeError` is raised. """ if not self._v_isopen: raise ClosedNodeError("the node object is closed") assert self._v_file.isopen, "found an open node in a closed file" def _g_set_location(self, parentnode, name): """Set location-dependent attributes. Sets the location-dependent attributes of this node to reflect that it is placed under the specified `parentnode`, with the specified `name`. This also triggers the insertion of file references to this node. If the maximum recommended tree depth is exceeded, a `PerformanceWarning` is issued. """ file_ = parentnode._v_file parentdepth = parentnode._v_depth self._v_file = file_ self._v_isopen = True root_uep = file_.root_uep if name.startswith(root_uep): # This has been called from File._get_node() assert parentdepth == 0 if root_uep == "/": self._v_pathname = name else: self._v_pathname = name[len(root_uep):] _, self._v_name = split_path(name) self._v_depth = name.count("/") - root_uep.count("/") + 1 else: # If we enter here is because this has been called elsewhere self._v_name = name self._v_pathname = join_path(parentnode._v_pathname, name) self._v_depth = parentdepth + 1 # Check if the node is too deep in the tree. if parentdepth >= self._v_maxtreedepth: warnings.warn("""\ node ``%s`` is exceeding the recommended maximum depth (%d);\ be ready to see PyTables asking for *lots* of memory and possibly slow I/O""" % (self._v_pathname, self._v_maxtreedepth), PerformanceWarning) if self._v_pathname != '/': file_._node_manager.cache_node(self, self._v_pathname) def _g_update_location(self, newparentpath): """Update location-dependent attributes. Updates location data when an ancestor node has changed its location in the hierarchy to `newparentpath`. In fact, this method is expected to be called by an ancestor of this node. This also triggers the update of file references to this node. If the maximum recommended node depth is exceeded, a `PerformanceWarning` is issued. This warning is assured to be unique. """ oldpath = self._v_pathname newpath = join_path(newparentpath, self._v_name) newdepth = newpath.count('/') self._v_pathname = newpath self._v_depth = newdepth # Check if the node is too deep in the tree. if newdepth > self._v_maxtreedepth: warnings.warn("""\ moved descendent node is exceeding the recommended maximum depth (%d);\ be ready to see PyTables asking for *lots* of memory and possibly slow I/O""" % (self._v_maxtreedepth,), PerformanceWarning) node_manager = self._v_file._node_manager node_manager.rename_node(oldpath, newpath) # Tell dependent objects about the new location of this node. self._g_update_dependent() def _g_del_location(self): """Clear location-dependent attributes. This also triggers the removal of file references to this node. """ node_manager = self._v_file._node_manager pathname = self._v_pathname if not self._v__deleting: node_manager.drop_from_cache(pathname) # Note: node_manager.drop_node do not removes the node form the # registry if it is still open node_manager.registry.pop(pathname, None) self._v_file = None self._v_isopen = False self._v_pathname = None self._v_name = None self._v_depth = None def _g_post_init_hook(self): """Code to be run after node creation and before creation logging.""" pass def _g_update_dependent(self): """Update dependent objects after a location change. All dependent objects (but not nodes!) referencing this node must be updated here. """ if '_v_attrs' in self.__dict__: self._v_attrs._g_update_node_location(self) def _f_close(self): """Close this node in the tree. This releases all resources held by the node, so it should not be used again. On nodes with data, it may be flushed to disk. You should not need to close nodes manually because they are automatically opened/closed when they are loaded/evicted from the integrated LRU cache. """ # After calling ``_f_close()``, two conditions are met: # # 1. The node object is detached from the tree. # 2. *Every* attribute of the node is removed. # # Thus, cleanup operations used in ``_f_close()`` in sub-classes # must be run *before* calling the method in the superclass. if not self._v_isopen: return # the node is already closed myDict = self.__dict__ # Close the associated `AttributeSet` # only if it has already been placed in the object's dictionary. if '_v_attrs' in myDict: self._v_attrs._g_close() # Detach the node from the tree if necessary. self._g_del_location() # Finally, clear all remaining attributes from the object. myDict.clear() # Just add a final flag to signal that the node is closed: self._v_isopen = False def _g_remove(self, recursive, force): """Remove this node from the hierarchy. If the node has children, recursive removal must be stated by giving `recursive` a true value; otherwise, a `NodeError` will be raised. If `force` is set to true, the node will be removed no matter it has children or not (useful for deleting hard links). It does not log the change. """ # Remove the node from the PyTables hierarchy. parent = self._v_parent parent._g_unrefnode(self._v_name) # Close the node itself. self._f_close() # hdf5extension operations: # Remove the node from the HDF5 hierarchy. self._g_delete(parent) def _f_remove(self, recursive=False, force=False): """Remove this node from the hierarchy. If the node has children, recursive removal must be stated by giving recursive a true value; otherwise, a NodeError will be raised. If the node is a link to a Group object, and you are sure that you want to delete it, you can do this by setting the force flag to true. """ self._g_check_open() file_ = self._v_file file_._check_writable() if file_.is_undo_enabled(): self._g_remove_and_log(recursive, force) else: self._g_remove(recursive, force) def _g_remove_and_log(self, recursive, force): file_ = self._v_file oldpathname = self._v_pathname # Log *before* moving to use the right shadow name. file_._log('REMOVE', oldpathname) move_to_shadow(file_, oldpathname) def _g_move(self, newparent, newname): """Move this node in the hierarchy. Moves the node into the given `newparent`, with the given `newname`. It does not log the change. """ oldparent = self._v_parent oldname = self._v_name oldpathname = self._v_pathname # to move the HDF5 node # Try to insert the node into the new parent. newparent._g_refnode(self, newname) # Remove the node from the new parent. oldparent._g_unrefnode(oldname) # Remove location information for this node. self._g_del_location() # Set new location information for this node. self._g_set_location(newparent, newname) # hdf5extension operations: # Update node attributes. self._g_new(newparent, self._v_name, init=False) # Move the node. # self._v_parent._g_move_node(oldpathname, self._v_pathname) self._v_parent._g_move_node(oldparent._v_objectid, oldname, newparent._v_objectid, newname, oldpathname, self._v_pathname) # Tell dependent objects about the new location of this node. self._g_update_dependent() def _f_rename(self, newname, overwrite=False): """Rename this node in place. Changes the name of a node to *newname* (a string). If a node with the same newname already exists and overwrite is true, recursively remove it before renaming. """ self._f_move(newname=newname, overwrite=overwrite) def _f_move(self, newparent=None, newname=None, overwrite=False, createparents=False): """Move or rename this node. Moves a node into a new parent group, or changes the name of the node. newparent can be a Group object (see :ref:`GroupClassDescr`) or a pathname in string form. If it is not specified or None, the current parent group is chosen as the new parent. newname must be a string with a new name. If it is not specified or None, the current name is chosen as the new name. If createparents is true, the needed groups for the given new parent group path to exist will be created. Moving a node across databases is not allowed, nor it is moving a node *into* itself. These result in a NodeError. However, moving a node *over* itself is allowed and simply does nothing. Moving over another existing node is similarly not allowed, unless the optional overwrite argument is true, in which case that node is recursively removed before moving. Usually, only the first argument will be used, effectively moving the node to a new location without changing its name. Using only the second argument is equivalent to renaming the node in place. """ self._g_check_open() file_ = self._v_file oldparent = self._v_parent oldname = self._v_name # Set default arguments. if newparent is None and newname is None: raise NodeError("you should specify at least " "a ``newparent`` or a ``newname`` parameter") if newparent is None: newparent = oldparent if newname is None: newname = oldname # Get destination location. if hasattr(newparent, '_v_file'): # from node newfile = newparent._v_file newpath = newparent._v_pathname elif hasattr(newparent, 'startswith'): # from path newfile = file_ newpath = newparent else: raise TypeError("new parent is not a node nor a path: %r" % (newparent,)) # Validity checks on arguments. # Is it in the same file? if newfile is not file_: raise NodeError("nodes can not be moved across databases; " "please make a copy of the node") # The movement always fails if the hosting file can not be modified. file_._check_writable() # Moving over itself? oldpath = oldparent._v_pathname if newpath == oldpath and newname == oldname: # This is equivalent to renaming the node to its current name, # and it does not change the referenced object, # so it is an allowed no-op. return # Moving into itself? self._g_check_not_contains(newpath) # Note that the previous checks allow us to go ahead and create # the parent groups if `createparents` is true. `newparent` is # used instead of `newpath` to avoid accepting `Node` objects # when `createparents` is true. newparent = file_._get_or_create_path(newparent, createparents) self._g_check_group(newparent) # Is it a group? # Moving over an existing node? self._g_maybe_remove(newparent, newname, overwrite) # Move the node. oldpathname = self._v_pathname self._g_move(newparent, newname) # Log the change. if file_.is_undo_enabled(): self._g_log_move(oldpathname) def _g_log_move(self, oldpathname): self._v_file._log('MOVE', oldpathname, self._v_pathname) def _g_copy(self, newparent, newname, recursive, _log=True, **kwargs): """Copy this node and return the new one. Creates and returns a copy of the node in the given `newparent`, with the given `newname`. If `recursive` copy is stated, all descendents are copied as well. Additional keyword argumens may affect the way that the copy is made. Unknown arguments must be ignored. On recursive copies, all keyword arguments must be passed on to the children invocation of this method. If `_log` is false, the change is not logged. This is *only* intended to be used by ``_g_copy_as_child()`` as a means of optimising sub-tree copies. """ raise NotImplementedError def _g_copy_as_child(self, newparent, **kwargs): """Copy this node as a child of another group. Copies just this node into `newparent`, not recursing children nor overwriting nodes nor logging the copy. This is intended to be used when copying whole sub-trees. """ return self._g_copy(newparent, self._v_name, recursive=False, _log=False, **kwargs) def _f_copy(self, newparent=None, newname=None, overwrite=False, recursive=False, createparents=False, **kwargs): """Copy this node and return the new node. Creates and returns a copy of the node, maybe in a different place in the hierarchy. newparent can be a Group object (see :ref:`GroupClassDescr`) or a pathname in string form. If it is not specified or None, the current parent group is chosen as the new parent. newname must be a string with a new name. If it is not specified or None, the current name is chosen as the new name. If recursive copy is stated, all descendants are copied as well. If createparents is true, the needed groups for the given new parent group path to exist will be created. Copying a node across databases is supported but can not be undone. Copying a node over itself is not allowed, nor it is recursively copying a node into itself. These result in a NodeError. Copying over another existing node is similarly not allowed, unless the optional overwrite argument is true, in which case that node is recursively removed before copying. Additional keyword arguments may be passed to customize the copying process. For instance, title and filters may be changed, user attributes may be or may not be copied, data may be sub-sampled, stats may be collected, etc. See the documentation for the particular node type. Using only the first argument is equivalent to copying the node to a new location without changing its name. Using only the second argument is equivalent to making a copy of the node in the same group. """ self._g_check_open() srcfile = self._v_file srcparent = self._v_parent srcname = self._v_name dstparent = newparent dstname = newname # Set default arguments. if dstparent is None and dstname is None: raise NodeError("you should specify at least " "a ``newparent`` or a ``newname`` parameter") if dstparent is None: dstparent = srcparent if dstname is None: dstname = srcname # Get destination location. if hasattr(dstparent, '_v_file'): # from node dstfile = dstparent._v_file dstpath = dstparent._v_pathname elif hasattr(dstparent, 'startswith'): # from path dstfile = srcfile dstpath = dstparent else: raise TypeError("new parent is not a node nor a path: %r" % (dstparent,)) # Validity checks on arguments. if dstfile is srcfile: # Copying over itself? srcpath = srcparent._v_pathname if dstpath == srcpath and dstname == srcname: raise NodeError( "source and destination nodes are the same node: ``%s``" % self._v_pathname) # Recursively copying into itself? if recursive: self._g_check_not_contains(dstpath) # Note that the previous checks allow us to go ahead and create # the parent groups if `createparents` is true. `dstParent` is # used instead of `dstPath` because it may be in other file, and # to avoid accepting `Node` objects when `createparents` is # true. dstparent = srcfile._get_or_create_path(dstparent, createparents) self._g_check_group(dstparent) # Is it a group? # Copying to another file with undo enabled? if dstfile is not srcfile and srcfile.is_undo_enabled(): warnings.warn("copying across databases can not be undone " "nor redone from this database", UndoRedoWarning) # Copying over an existing node? self._g_maybe_remove(dstparent, dstname, overwrite) # Copy the node. # The constructor of the new node takes care of logging. return self._g_copy(dstparent, dstname, recursive, **kwargs) def _f_isvisible(self): """Is this node visible?""" self._g_check_open() return isvisiblepath(self._v_pathname) def _g_check_group(self, node): # Node must be defined in order to define a Group. # However, we need to know Group here. # Using class_name_dict avoids a circular import. if not isinstance(node, class_name_dict['Node']): raise TypeError("new parent is not a registered node: %s" % node._v_pathname) if not isinstance(node, class_name_dict['Group']): raise TypeError("new parent node ``%s`` is not a group" % node._v_pathname) def _g_check_not_contains(self, pathname): # The not-a-TARDIS test. ;) mypathname = self._v_pathname if (mypathname == '/' # all nodes fall below the root group or pathname == mypathname or pathname.startswith(mypathname + '/')): raise NodeError("can not move or recursively copy node ``%s`` " "into itself" % mypathname) def _g_maybe_remove(self, parent, name, overwrite): if name in parent: if not overwrite: raise NodeError( f"destination group ``{parent._v_pathname}`` already " f"has a node named ``{name}``; you may want to use the " f"``overwrite`` argument") parent._f_get_child(name)._f_remove(True) def _g_check_name(self, name): """Check validity of name for this particular kind of node. This is invoked once the standard HDF5 and natural naming checks have successfully passed. """ if name.startswith('_i_'): # This is reserved for table index groups. raise ValueError( "node name starts with reserved prefix ``_i_``: %s" % name) def _f_getattr(self, name): """Get a PyTables attribute from this node. If the named attribute does not exist, an AttributeError is raised. """ return getattr(self._v_attrs, name) def _f_setattr(self, name, value): """Set a PyTables attribute for this node. If the node already has a large number of attributes, a PerformanceWarning is issued. """ setattr(self._v_attrs, name, value) def _f_delattr(self, name): """Delete a PyTables attribute from this node. If the named attribute does not exist, an AttributeError is raised. """ delattr(self._v_attrs, name) # class NotLoggedMixin: # Include this class in your inheritance tree # to avoid changes to instances of your class from being logged. _AttributeSet = NotLoggedAttributeSet def _g_log_create(self): pass def _g_log_move(self, oldpathname): pass def _g_remove_and_log(self, recursive, force): self._g_remove(recursive, force) PyTables-3.7.0/tables/nodes/000077500000000000000000000000001416254111300156245ustar00rootroot00000000000000PyTables-3.7.0/tables/nodes/__init__.py000066400000000000000000000005761416254111300177450ustar00rootroot00000000000000"""Special node behaviours for PyTables. This package contains several modules that give specific behaviours to PyTables nodes. For instance, the filenode module provides a file interface to a PyTables node. Package modules: filenode -- A file interface to nodes for PyTables databases. """ # The list of names to be exported to the importing module. __all__ = ['filenode'] PyTables-3.7.0/tables/nodes/filenode.py000066400000000000000000000647671416254111300200070ustar00rootroot00000000000000"""A file interface to nodes for PyTables databases. The FileNode module provides a file interface for using inside of PyTables database files. Use the new_node() function to create a brand new file node which can be read and written as any ordinary Python file. Use the open_node() function to open an existing (i.e. created with new_node()) node for read-only or read-write access. Read acces is always available. Write access (enabled on new files and files opened with mode 'a+') only allows appending data to a file node. Currently only binary I/O is supported. See :ref:`filenode_usersguide` for instructions on use. .. versionchanged:: 3.0 In version 3.0 the module as been completely rewritten to be fully compliant with the interfaces defined in the :mod:`io` module. """ import io import os import re import warnings from pathlib import Path import numpy as np import tables as tb NodeType = 'file' """Value for NODE_TYPE node system attribute.""" NodeTypeVersions = [1, 2] """Supported values for NODE_TYPE_VERSION node system attribute.""" class RawPyTablesIO(io.RawIOBase): """Base class for raw binary I/O on HDF5 files using PyTables.""" # A lambda to turn a size into a shape, for each version. _size_to_shape = [ None, lambda l: (l, 1), lambda l: (l, ), ] def __init__(self, node, mode=None): super().__init__() self._check_node(node) self._check_attributes(node) if mode is None: mode = node._v_file.mode else: self._check_mode(mode) self._cross_check_mode(mode, node._v_file.mode) self._node = node self._mode = mode self._pos = 0 self._version = int(node.attrs.NODE_TYPE_VERSION) self._vshape = self._size_to_shape[self._version] self._vtype = node.atom.dtype.base.type # read only attribute @property def mode(self): """File mode.""" return self._mode # def tell(self) -> int: def tell(self): """Return current stream position.""" self._checkClosed() return self._pos # def seek(self, pos: int, whence: int = 0) -> int: def seek(self, pos, whence=0): """Change stream position. Change the stream position to byte offset offset. offset is interpreted relative to the position indicated by whence. Values for whence are: * 0 -- start of stream (the default); offset should be zero or positive * 1 -- current stream position; offset may be negative * 2 -- end of stream; offset is usually negative Return the new absolute position. """ self._checkClosed() try: pos = pos.__index__() # except AttributeError as err: # raise TypeError("an integer is required") from err except AttributeError: raise TypeError("an integer is required") if whence == 0: if pos < 0: raise ValueError(f"negative seek position {pos!r}") self._pos = pos elif whence == 1: self._pos = max(0, self._pos + pos) elif whence == 2: self._pos = max(0, self._node.nrows + pos) else: raise ValueError("invalid whence value") return self._pos # def seekable(self) -> bool: def seekable(self): """Return whether object supports random access. If False, seek(), tell() and truncate() will raise IOError. This method may need to do a test seek(). """ return True # def fileno(self) -> int: def fileno(self): """Returns underlying file descriptor if one exists. An IOError is raised if the IO object does not use a file descriptor. """ self._checkClosed() return self._node._v_file.fileno() # def close(self) -> None: def close(self): """Flush and close the IO object. This method has no effect if the file is already closed. """ if not self.closed: if getattr(self._node, '_v_file', None) is None: warnings.warn("host PyTables file is already closed!") try: super().close() finally: # Release node object to allow closing the file. self._node = None def flush(self): """Flush write buffers, if applicable. This is not implemented for read-only and non-blocking streams. """ self._checkClosed() self._node.flush() # def truncate(self, pos: int = None) -> int: def truncate(self, pos=None): """Truncate file to size bytes. Size defaults to the current IO position as reported by tell(). Return the new size. Currently, this method only makes sense to grow the file node, since data can not be rewritten nor deleted. """ self._checkClosed() self._checkWritable() if pos is None: pos = self._pos elif pos < 0: raise ValueError(f"negative truncate position {pos!r}") if pos < self._node.nrows: raise OSError("truncating is only allowed for growing a file") self._append_zeros(pos - self._node.nrows) return self.seek(pos) # def readable(self) -> bool: def readable(self): """Return whether object was opened for reading. If False, read() will raise IOError. """ mode = self._mode return 'r' in mode or '+' in mode # def writable(self) -> bool: def writable(self): """Return whether object was opened for writing. If False, write() and truncate() will raise IOError. """ mode = self._mode return 'w' in mode or 'a' in mode or '+' in mode # def readinto(self, b: bytearray) -> int: def readinto(self, b): """Read up to len(b) bytes into b. Returns number of bytes read (0 for EOF), or None if the object is set not to block as has no data to read. """ self._checkClosed() self._checkReadable() if self._pos >= self._node.nrows: return 0 n = len(b) start = self._pos stop = self._pos + n # XXX optimized path # if stop <= self._node.nrows and isinstance(b, np.ndarray): # self._node.read(start, stop, out=b) # self._pos += n # return n if stop > self._node.nrows: stop = self._node.nrows n = stop - start # XXX This ought to work with anything that supports the buffer API b[:n] = self._node.read(start, stop).tobytes() self._pos += n return n # def readline(self, limit: int = -1) -> bytes: def readline(self, limit=-1): """Read and return a line from the stream. If limit is specified, at most limit bytes will be read. The line terminator is always ``\\n`` for binary files; for text files, the newlines argument to open can be used to select the line terminator(s) recognized. """ self._checkClosed() self._checkReadable() chunksize = self._node.chunkshape[0] if self._node.chunkshape else -1 # XXX: check lsep = b'\n' lseplen = len(lsep) # Set the remaining bytes to read to the specified size. remsize = limit partial = [] finished = False while not finished: # Read a string limited by the remaining number of bytes. if limit <= 0: ibuff = self.read(chunksize) else: ibuff = self.read(min(remsize, chunksize)) ibufflen = len(ibuff) remsize -= ibufflen if ibufflen >= lseplen: # Separator fits, look for EOL string. eolindex = ibuff.find(lsep) elif ibufflen == 0: # EOF was immediately reached. finished = True continue else: # ibufflen < lseplen # EOF was hit and separator does not fit. ;) partial.append(ibuff) finished = True continue if eolindex >= 0: # Found an EOL. If there are trailing characters, # cut the input buffer and seek back; # else add the whole input buffer. trailing = ibufflen - lseplen - eolindex # Bytes beyond EOL. if trailing > 0: obuff = ibuff[:-trailing] self.seek(-trailing, 1) remsize += trailing else: obuff = ibuff finished = True elif lseplen > 1 and (limit <= 0 or remsize > 0): # Seek back a little since the end of the read string # may have fallen in the middle of the line separator. obuff = ibuff[:-lseplen + 1] self.seek(-lseplen + 1, 1) remsize += lseplen - 1 else: # eolindex<0 and (lseplen<=1 or (limit>0 and remsize<=0)) # Did not find an EOL, add the whole input buffer. obuff = ibuff # Append (maybe cut) buffer. partial.append(obuff) # If a limit has been specified and the remaining count # reaches zero, the reading is finished. if limit > 0 and remsize <= 0: finished = True return b''.join(partial) # def write(self, b: bytes) -> int: def write(self, b): """Write the given buffer to the IO stream. Returns the number of bytes written, which may be less than len(b). """ self._checkClosed() self._checkWritable() if isinstance(b, str): raise TypeError("can't write str to binary stream") n = len(b) if n == 0: return 0 pos = self._pos # Is the pointer beyond the real end of data? end2off = pos - self._node.nrows if end2off > 0: # Zero-fill the gap between the end of data and the pointer. self._append_zeros(end2off) # Append data. self._node.append( np.ndarray(buffer=b, dtype=self._vtype, shape=self._vshape(n))) self._pos += n return n def _checkClosed(self): """Checks if file node is open. Checks whether the file node is open or has been closed. In the second case, a ValueError is raised. If the host PyTables has been closed, ValueError is also raised. """ super()._checkClosed() if getattr(self._node, '_v_file', None) is None: raise ValueError("host PyTables file is already closed!") def _check_node(self, node): if not isinstance(node, tb.EArray): raise TypeError('the "node" parameter should be a tables.EArray') if not isinstance(node.atom, tb.UInt8Atom): raise TypeError('only nodes with atom "UInt8Atom" are allowed') def _check_mode(self, mode): if not isinstance(mode, str): raise TypeError("invalid mode: %r" % mode) modes = set(mode) if modes - set("arwb+tU") or len(mode) > len(modes): raise ValueError("invalid mode: %r" % mode) reading = "r" in modes writing = "w" in modes appending = "a" in modes # updating = "+" in modes text = "t" in modes binary = "b" in modes if "U" in modes: if writing or appending: raise ValueError("can't use U and writing mode at once") reading = True if text and binary: raise ValueError("can't have text and binary mode at once") if reading + writing + appending > 1: raise ValueError("can't have read/write/append mode at once") if not (reading or writing or appending): raise ValueError("must have exactly one of read/write/append mode") def _cross_check_mode(self, mode, h5filemode): # XXX: check # readable = bool('r' in mode or '+' in mode) # h5readable = bool('r' in h5filemode or '+' in h5filemode) # # if readable and not h5readable: # raise ValueError("RawPyTablesIO can't be open in read mode if " # "the underlying hdf5 file is not readable") writable = bool('w' in mode or 'a' in mode or '+' in mode) h5writable = bool('w' in h5filemode or 'a' in h5filemode or '+' in h5filemode) if writable and not h5writable: raise ValueError("RawPyTablesIO can't be open in write mode if " "the underlying hdf5 file is not writable") def _check_attributes(self, node): """Checks file node-specific attributes. Checks for the presence and validity of the system attributes 'NODE_TYPE' and 'NODE_TYPE_VERSION' in the specified PyTables node (leaf). ValueError is raised if an attribute is missing or incorrect. """ attrs = node.attrs ltype = getattr(attrs, 'NODE_TYPE', None) ltypever = getattr(attrs, 'NODE_TYPE_VERSION', None) if ltype != NodeType: raise ValueError(f"invalid type of node object: {ltype}") if ltypever not in NodeTypeVersions: raise ValueError( f"unsupported type version of node object: {ltypever}") def _append_zeros(self, size): """_append_zeros(size) -> None. Appends a string of zeros. Appends a string of 'size' zeros to the array, without moving the file pointer. """ # Appending an empty array would raise an error. if size == 0: return # XXX This may be redone to avoid a potentially large in-memory array. self._node.append( np.zeros(dtype=self._vtype, shape=self._vshape(size))) class FileNodeMixin: """Mixin class for FileNode objects. It provides access to the attribute set of the node that becomes available via the attrs property. You can add attributes there, but try to avoid attribute names in all caps or starting with '_', since they may clash with internal attributes. """ # The attribute set property methods. def _get_attrs(self): """Returns the attribute set of the file node.""" # sefl._checkClosed() return self._node.attrs def _set_attrs(self, value): """set_attrs(string) -> None. Raises ValueError.""" raise ValueError("changing the whole attribute set is not allowed") def _del_attrs(self): """del_attrs() -> None. Raises ValueError.""" raise ValueError("deleting the whole attribute set is not allowed") # The attribute set property. attrs = property( _get_attrs, _set_attrs, _del_attrs, "A property pointing to the attribute set of the file node.") class ROFileNode(FileNodeMixin, RawPyTablesIO): """Creates a new read-only file node. Creates a new read-only file node associated with the specified PyTables node, providing a standard Python file interface to it. The node has to have been created on a previous occasion using the new_node() function. The node used as storage is also made available via the read-only attribute node. Please do not tamper with this object if it's avoidable, since you may break the operation of the file node object. The constructor is not intended to be used directly. Use the open_node() function in read-only mode ('r') instead. :Version 1: implements the file storage as a UInt8 uni-dimensional EArray. :Version 2: uses an UInt8 N vector EArray. .. versionchanged:: 3.0 The offset attribute is no more available, please use seek/tell methods instead. .. versionchanged:: 3.0 The line_separator property is no more available. The only line separator used for binary I/O is ``\\n``. """ def __init__(self, node): RawPyTablesIO.__init__(self, node, 'r') self._checkReadable() @property def node(self): return self._node class RAFileNode(FileNodeMixin, RawPyTablesIO): """Creates a new read-write file node. The first syntax opens the specified PyTables node, while the second one creates a new node in the specified PyTables file. In the second case, additional named arguments 'where' and 'name' must be passed to specify where the file node is to be created. Other named arguments such as 'title' and 'filters' may also be passed. The special named argument 'expectedsize', indicating an estimate of the file size in bytes, may also be passed. Write access means reading as well as appending data is allowed. The node used as storage is also made available via the read-only attribute node. Please do not tamper with this object if it's avoidable, since you may break the operation of the file node object. The constructor is not intended to be used directly. Use the new_node() or open_node() functions instead. :Version 1: implements the file storage as a UInt8 uni-dimensional EArray. :Version 2: uses an UInt8 N vector EArray. .. versionchanged:: 3.0 The offset attribute is no more available, please use seek/tell methods instead. .. versionchanged:: 3.0 The line_separator property is no more available. The only line separator used for binary I/O is ``\\n``. """ # The atom representing a byte in the array, for each version. _byte_shape = [ None, (0, 1), (0,), ] __allowed_init_kwargs = [ 'where', 'name', 'title', 'filters', 'expectedsize'] def __init__(self, node, h5file, **kwargs): if node is not None: # Open an existing node and get its version. self._check_attributes(node) self._version = node.attrs.NODE_TYPE_VERSION elif h5file is not None: # Check for allowed keyword arguments, # to avoid unwanted arguments falling through to array constructor. for kwarg in kwargs: if kwarg not in self.__allowed_init_kwargs: raise TypeError( "%s keyword argument is not allowed" % repr(kwarg)) # Turn 'expectedsize' into 'expectedrows'. if 'expectedsize' in kwargs: # These match since one byte is stored per row. expectedrows = kwargs['expectedsize'] kwargs = kwargs.copy() del kwargs['expectedsize'] kwargs['expectedrows'] = expectedrows # Create a new array in the specified PyTables file. self._version = NodeTypeVersions[-1] shape = self._byte_shape[self._version] node = h5file.create_earray( atom=tb.UInt8Atom(), shape=shape, **kwargs) # Set the node attributes, else remove the array itself. try: self._set_attributes(node) except RuntimeError: h5file.remove_node(kwargs['where'], kwargs['name']) raise RawPyTablesIO.__init__(self, node, 'a+') self._checkReadable() self._checkWritable() @property def node(self): return self._node def _set_attributes(self, node): """_set_attributes(node) -> None. Adds file node-specific attributes. Sets the system attributes 'NODE_TYPE' and 'NODE_TYPE_VERSION' in the specified PyTables node (leaf). """ attrs = node.attrs attrs.NODE_TYPE = NodeType attrs.NODE_TYPE_VERSION = NodeTypeVersions[-1] def new_node(h5file, **kwargs): """Creates a new file node object in the specified PyTables file object. Additional named arguments where and name must be passed to specify where the file node is to be created. Other named arguments such as title and filters may also be passed. The special named argument expectedsize, indicating an estimate of the file size in bytes, may also be passed. It returns the file node object. """ return RAFileNode(None, h5file, **kwargs) def open_node(node, mode='r'): """Opens an existing file node. Returns a file node object from the existing specified PyTables node. If mode is not specified or it is 'r', the file can only be read, and the pointer is positioned at the beginning of the file. If mode is 'a+', the file can be read and appended, and the pointer is positioned at the end of the file. """ if mode == 'r': return ROFileNode(node) elif mode == 'a+': return RAFileNode(node, None) else: raise OSError(f"invalid mode: {mode}") def save_to_filenode(h5file, filename, where, name=None, overwrite=False, title="", filters=None): """Save a file's contents to a filenode inside a PyTables file. .. versionadded:: 3.2 Parameters ---------- h5file The PyTables file to be written to; can be either a string giving the file's location or a :class:`File` object. If a file with name *h5file* already exists, it will be opened in mode ``a``. filename Path of the file which shall be stored within the PyTables file. where, name Location of the filenode where the data shall be stored. If *name* is not given, and *where* is either a :class:`Group` object or a string ending on ``/``, the leaf name will be set to the file name of *filename*. The *name* will be modified to adhere to Python's natural naming convention; the original filename will be preserved in the filenode's *_filename* attribute. overwrite Whether or not a possibly existing filenode of the specified name shall be overwritten. title A description for this node (it sets the ``TITLE`` HDF5 attribute on disk). filters An instance of the :class:`Filters` class that provides information about the desired I/O filters to be applied during the life of this object. """ path = Path(filename).resolve() # sanity checks if not os.access(path, os.R_OK): raise OSError(f"The file '{path}' could not be read") if isinstance(h5file, tb.file.File) and h5file.mode == "r": raise OSError(f"The file '{h5file.filename}' is opened read-only") # guess filenode's name if necessary if name is None: if isinstance(where, tb.group.Group): name = os.path.split(filename)[1] if isinstance(where, str): if where.endswith("/"): name = os.path.split(filename)[1] else: nodepath = where.split("/") where = "/" + "/".join(nodepath[:-1]) name = nodepath[-1] # sanitize name if necessary if not tb.path._python_id_re.match(name): name = re.sub('(?![a-zA-Z0-9_]).', "_", re.sub('^(?![a-zA-Z_]).', "_", name)) new_h5file = not isinstance(h5file, tb.file.File) f = tb.File(h5file, "a") if new_h5file else h5file # check for already existing filenode try: f.get_node(where=where, name=name) if not overwrite: if new_h5file: f.close() raise OSError( f"Specified node already exists in file '{f.filename}'" ) except tb.NoSuchNodeError: pass # read data from disk data = path.read_bytes() # remove existing filenode if present try: f.remove_node(where=where, name=name) except tb.NoSuchNodeError: pass # write file's contents to filenode fnode = new_node(f, where=where, name=name, title=title, filters=filters) fnode.write(data) fnode.attrs._filename = path.name fnode.close() # cleanup if new_h5file: f.close() def read_from_filenode(h5file, filename, where, name=None, overwrite=False, create_target=False): r"""Read a filenode from a PyTables file and write its contents to a file. .. versionadded:: 3.2 Parameters ---------- h5file The PyTables file to be read from; can be either a string giving the file's location or a :class:`File` object. filename Path of the file where the contents of the filenode shall be written to. If *filename* points to a directory or ends with ``/`` (``\`` on Windows), the filename will be set to the *_filename* (if present; otherwise the *name*) attribute of the read filenode. where, name Location of the filenode where the data shall be read from. If no node *name* can be found at *where*, the first node at *where* whose *_filename* attribute matches *name* will be read. overwrite Whether or not a possibly existing file of the specified *filename* shall be overwritten. create_target Whether or not the folder hierarchy needed to accomodate the given target ``filename`` will be created. """ path = Path(filename).resolve() new_h5file = not isinstance(h5file, tb.file.File) f = tb.File(h5file, "r") if new_h5file else h5file try: fnode = open_node(f.get_node(where=where, name=name)) except tb.NoSuchNodeError: fnode = None for n in f.walk_nodes(where=where, classname="EArray"): if n.attrs._filename == name: fnode = open_node(n) break if fnode is None: f.close() raise tb.NoSuchNodeError("A filenode '%s' cannot be found at " "'%s'" % (name, where)) # guess output filename if necessary # TODO: pathlib.Path strips trailing slash automatically :-( if path.is_dir() or filename.endswith(os.path.sep): try: path = path / fnode.node.attrs._filename except Exception: path = path / fnode.node.name if os.access(path, os.R_OK) and not overwrite: if new_h5file: f.close() raise OSError(f"The file '{path}' already exists") # create folder hierarchy if necessary if create_target: path.parent.mkdir(parents=True, exist_ok=True) if not os.access(path.parent, os.W_OK): if new_h5file: f.close() raise OSError("The file '%s' cannot be written to" % filename) # read data from filenode data = fnode.read() fnode.close() # store data to file path.write_bytes(data) # cleanup del data if new_h5file: f.close() PyTables-3.7.0/tables/nodes/tests/000077500000000000000000000000001416254111300167665ustar00rootroot00000000000000PyTables-3.7.0/tables/nodes/tests/__init__.py000066400000000000000000000000561416254111300211000ustar00rootroot00000000000000"""Unit tests for special node behaviours.""" PyTables-3.7.0/tables/nodes/tests/test_filenode.dat000066400000000000000000000063221416254111300223070ustar00rootroot00000000000000#define test_width 64 #define test_height 64 static char test_bits[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x1E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xB8, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xF8, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0xF1, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0x1F, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x38, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3D, 0xEE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0x01, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x70, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x84, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x18, 0x7C, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x19, 0x7C, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x31, 0x3C, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0x0E, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0x87, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xC7, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x61, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x31, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x38, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x07, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x03, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0xF1, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x06, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x42, 0x84, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x79, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x71, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x1E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x03, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0x61, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xC1, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC1, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x19, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x19, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x71, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; PyTables-3.7.0/tables/nodes/tests/test_filenode.py000066400000000000000000001033101416254111300221620ustar00rootroot00000000000000"""Unit test for the filenode module.""" import os import shutil import tempfile import warnings from pathlib import Path from ... import open_file, file, NoSuchNodeError from ...nodes import filenode from ...tests.common import ( unittest, TempFileMixin, parse_argv, print_versions, PyTablesTestCase as TestCase) def test_file(name): from pkg_resources import resource_filename return resource_filename('tables.nodes.tests', name) class NewFileTestCase(TempFileMixin, TestCase): """Tests creating a new file node with the new_node() function.""" def test00_NewFile(self): """Creation of a brand new file node.""" try: fnode = filenode.new_node(self.h5file, where='/', name='test') node = self.h5file.get_node('/test') except LookupError: self.fail("filenode.new_node() failed to create a new node.") else: self.assertEqual( fnode.node, node, "filenode.new_node() created a node in the wrong place.") def test01_NewFileTooFewArgs(self): """Creation of a new file node without arguments for node creation.""" self.assertRaises(TypeError, filenode.new_node, self.h5file) def test02_NewFileWithExpectedSize(self): """Creation of a new file node with 'expectedsize' argument.""" try: filenode.new_node( self.h5file, where='/', name='test', expectedsize=100_000) except TypeError: self.fail("filenode.new_node() failed to accept 'expectedsize'" " argument.") def test03_NewFileWithExpectedRows(self): """Creation of a new file node with illegal 'expectedrows' argument.""" self.assertRaises( TypeError, filenode.new_node, self.h5file, where='/', name='test', expectedrows=100_000) class ClosedFileTestCase(TempFileMixin, TestCase): """Tests calling several methods on a closed file.""" def setUp(self): """setUp() -> None This method sets the following instance attributes: * 'h5fname', the name of the temporary HDF5 file * 'h5file', the writable, temporary HDF5 file with a '/test' node * 'fnode', the closed file node in '/test' """ super().setUp() self.fnode = filenode.new_node(self.h5file, where='/', name='test') self.fnode.close() def tearDown(self): """tearDown() -> None Closes 'h5file'; removes 'h5fname'. """ self.fnode = None super().tearDown() # All these tests mey seem odd, but Python (2.3) files # do test whether the file is not closed regardless of their mode. def test00_Close(self): """Closing a closed file.""" try: self.fnode.close() except ValueError: self.fail("Could not close an already closed file.") def test01_Flush(self): """Flushing a closed file.""" self.assertRaises(ValueError, self.fnode.flush) def test02_Next(self): """Getting the next line of a closed file.""" self.assertRaises(ValueError, next, self.fnode) def test03_Read(self): """Reading a closed file.""" self.assertRaises(ValueError, self.fnode.read) def test04_Readline(self): """Reading a line from a closed file.""" self.assertRaises(ValueError, self.fnode.readline) def test05_Readlines(self): """Reading lines from a closed file.""" self.assertRaises(ValueError, self.fnode.readlines) def test06_Seek(self): """Seeking a closed file.""" self.assertRaises(ValueError, self.fnode.seek, 0) def test07_Tell(self): """Getting the pointer position in a closed file.""" self.assertRaises(ValueError, self.fnode.tell) def test08_Truncate(self): """Truncating a closed file.""" self.assertRaises(ValueError, self.fnode.truncate) def test09_Write(self): """Writing a closed file.""" self.assertRaises(ValueError, self.fnode.write, b'foo') def test10_Writelines(self): """Writing lines to a closed file.""" self.assertRaises(ValueError, self.fnode.writelines, [b'foo\n']) def copyFileToFile(srcfile, dstfile, blocksize=4096): """copyFileToFile(srcfile, dstfile[, blocksize]) -> None Copies a readable opened file 'srcfile' to a writable opened file 'destfile' in blocks of 'blocksize' bytes (4 KiB by default). """ data = srcfile.read(blocksize) while len(data) > 0: dstfile.write(data) data = srcfile.read(blocksize) class WriteFileTestCase(TempFileMixin, TestCase): """Tests writing, seeking and truncating a new file node.""" datafname = 'test_filenode.dat' def setUp(self): """setUp() -> None This method sets the following instance attributes: * 'h5fname', the name of the temporary HDF5 file * 'h5file', the writable, temporary HDF5 file with a '/test' node * 'fnode', the writable file node in '/test' """ super().setUp() self.fnode = filenode.new_node(self.h5file, where='/', name='test') self.datafname = test_file(self.datafname) def tearDown(self): """tearDown() -> None Closes 'fnode' and 'h5file'; removes 'h5fname'. """ self.fnode.close() self.fnode = None super().tearDown() def test00_WriteFile(self): """Writing a whole file node.""" datafile = open(self.datafname, 'rb') try: copyFileToFile(datafile, self.fnode) finally: datafile.close() def test01_SeekFile(self): """Seeking and writing file node.""" self.fnode.write(b'0123') self.fnode.seek(8) self.fnode.write(b'4567') self.fnode.seek(3) data = self.fnode.read(6) self.assertEqual( data, b'3\0\0\0\0'b'4', "Gap caused by forward seek was not properly filled.") self.fnode.seek(0) self.fnode.write(b'test') self.fnode.seek(0) data = self.fnode.read(4) self.assertNotEqual( data, b'test', "Data was overwritten instead of appended.") self.fnode.seek(-4, 2) data = self.fnode.read(4) self.assertEqual(data, b'test', "Written data was not appended.") self.fnode.seek(0, 2) oldendoff = self.fnode.tell() self.fnode.seek(-2, 2) self.fnode.write(b'test') newendoff = self.fnode.tell() self.assertEqual( newendoff, oldendoff - 2 + 4, "Pointer was not correctly moved on append.") def test02_TruncateFile(self): """Truncating a file node.""" self.fnode.write(b'test') self.fnode.seek(2) self.assertRaises(IOError, self.fnode.truncate) self.fnode.seek(6) self.fnode.truncate() self.fnode.seek(0) data = self.fnode.read() self.assertEqual( data, b'test\0\0', "File was not grown to the current offset.") self.fnode.truncate(8) self.fnode.seek(0) data = self.fnode.read() self.assertEqual( data, b'test\0\0\0\0', "File was not grown to an absolute size.") class OpenFileTestCase(TempFileMixin, TestCase): """Tests opening an existing file node for reading and writing.""" def setUp(self): """setUp() -> None This method sets the following instance attributes: * 'h5fname', the name of the temporary HDF5 file * 'h5file', the writable, temporary HDF5 file with a '/test' node """ super().setUp() fnode = filenode.new_node(self.h5file, where='/', name='test') fnode.close() def test00_OpenFileRead(self): """Opening an existing file node for reading.""" node = self.h5file.get_node('/test') fnode = filenode.open_node(node) self.assertEqual( fnode.node, node, "filenode.open_node() opened the wrong node.") self.assertEqual( fnode.mode, 'r', "File was opened with an invalid mode %s." % repr(fnode.mode)) self.assertEqual( fnode.tell(), 0, "Pointer is not positioned at the beginning of the file.") fnode.close() def test01_OpenFileReadAppend(self): """Opening an existing file node for reading and appending.""" node = self.h5file.get_node('/test') fnode = filenode.open_node(node, 'a+') self.assertEqual( fnode.node, node, "filenode.open_node() opened the wrong node.") self.assertEqual( fnode.mode, 'a+', "File was opened with an invalid mode %s." % repr(fnode.mode)) self.assertEqual( fnode.tell(), 0, "Pointer is not positioned at the beginning of the file.") fnode.close() def test02_OpenFileInvalidMode(self): """Opening an existing file node with an invalid mode.""" self.assertRaises( IOError, filenode.open_node, self.h5file.get_node('/test'), 'w') # This no longer works since type and type version attributes # are now system attributes. ivb(2004-12-29) # def test03_OpenFileNoAttrs(self): # "Opening a node with no type attributes." # # node = self.h5file.get_node('/test') # self.h5file.del_node_attr('/test', '_type') # # Another way to get the same result is changing the value. # ##self.h5file.set_node_attr('/test', '_type', 'foobar') # self.assertRaises(ValueError, filenode.open_node, node) class ReadFileTestCase(TempFileMixin, TestCase): """Tests reading from an existing file node.""" datafname = 'test_filenode.xbm' def setUp(self): """setUp() -> None This method sets the following instance attributes: * 'datafile', the opened data file * 'h5fname', the name of the temporary HDF5 file * 'h5file', the writable, temporary HDF5 file with a '/test' node * 'fnode', the readable file node in '/test', with data in it """ self.datafname = test_file(self.datafname) self.datafile = open(self.datafname, 'rb') super().setUp() fnode = filenode.new_node(self.h5file, where='/', name='test') copyFileToFile(self.datafile, fnode) fnode.close() self.datafile.seek(0) self.fnode = filenode.open_node(self.h5file.get_node('/test')) def tearDown(self): """tearDown() -> None Closes 'fnode', 'h5file' and 'datafile'; removes 'h5fname'. """ self.fnode.close() self.fnode = None self.datafile.close() self.datafile = None super().tearDown() def test00_CompareFile(self): """Reading and comparing a whole file node.""" import hashlib dfiledigest = hashlib.md5(self.datafile.read()).digest() fnodedigest = hashlib.md5(self.fnode.read()).digest() self.assertEqual( dfiledigest, fnodedigest, "Data read from file node differs from that in the file on disk.") def test01_Write(self): """Writing on a read-only file.""" self.assertRaises(IOError, self.fnode.write, 'no way') def test02_UseAsImageFile(self): """Using a file node with Python Imaging Library.""" try: from PIL import Image Image.open(self.fnode) except ImportError: # PIL not available, nothing to do. pass except OSError: self.fail( "PIL was not able to create an image from the file node.") def test_fileno(self): self.assertIsNot(self.fnode.fileno(), None) class ReadlineTestCase(TempFileMixin, TestCase): """Base class for text line-reading test cases. It provides a set of tests independent of the line separator string. Sub-classes must provide the 'line_separator' attribute. """ def setUp(self): """This method sets the following instance attributes: * ``h5fname``: the name of the temporary HDF5 file. * ``h5file``: the writable, temporary HDF5 file with a ``/test`` node. * ``fnode``: the readable file node in ``/test``, with text in it. """ super().setUp() linesep = self.line_separator # Fill the node file with some text. fnode = filenode.new_node(self.h5file, where='/', name='test') # fnode.line_separator = linesep fnode.write(linesep) data = 'short line%sshort line%s%s' % ((linesep.decode('ascii'),) * 3) data = data.encode('ascii') fnode.write(data) fnode.write(b'long line ' * 20 + linesep) fnode.write(b'unterminated') fnode.close() # Re-open it for reading. self.fnode = filenode.open_node(self.h5file.get_node('/test')) # self.fnode.line_separator = linesep def tearDown(self): """tearDown() -> None Closes 'fnode' and 'h5file'; removes 'h5fname'. """ self.fnode.close() self.fnode = None super().tearDown() def test00_Readline(self): """Reading individual lines.""" linesep = self.line_separator line = self.fnode.readline() self.assertEqual(line, linesep) line = self.fnode.readline() # 'short line' + linesep line = self.fnode.readline() self.assertEqual(line, b'short line' + linesep) line = self.fnode.readline() self.assertEqual(line, linesep) line = self.fnode.readline() self.assertEqual(line, b'long line ' * 20 + linesep) line = self.fnode.readline() self.assertEqual(line, b'unterminated') line = self.fnode.readline() self.assertEqual(line, b'') line = self.fnode.readline() self.assertEqual(line, b'') def test01_ReadlineSeek(self): """Reading individual lines and seeking back and forth.""" linesep = self.line_separator lseplen = len(linesep) self.fnode.readline() # linesep self.fnode.readline() # 'short line' + linesep self.fnode.seek(-(lseplen + 4), 1) line = self.fnode.readline() self.assertEqual(line, b'line' + linesep, "Seeking back yielded different data.") self.fnode.seek(lseplen + 20, 1) # Into the long line. line = self.fnode.readline() self.assertEqual( line[-(lseplen + 10):], b'long line ' + linesep, "Seeking forth yielded unexpected data.") def test02_Iterate(self): """Iterating over the lines.""" linesep = self.line_separator # Iterate to the end. for line in self.fnode: pass self.assertRaises(StopIteration, next, self.fnode) self.fnode.seek(0) line = next(self.fnode) self.assertEqual(line, linesep) line = next(self.fnode) self.assertEqual(line, b'short line' + linesep) def test03_Readlines(self): """Reading a list of lines.""" linesep = self.line_separator lines = self.fnode.readlines() self.assertEqual(lines, [ linesep, b'short line' + linesep, b'short line' + linesep, linesep, b'long line ' * 20 + linesep, b'unterminated']) def test04_ReadlineSize(self): """Reading individual lines of limited size.""" linesep = self.line_separator lseplen = len(linesep) line = self.fnode.readline() # linesep line = self.fnode.readline(lseplen + 20) self.assertEqual(line, b'short line' + linesep) line = self.fnode.readline(5) self.assertEqual(line, b'short') line = self.fnode.readline(lseplen + 20) self.assertEqual(line, b' line' + linesep) line = self.fnode.readline(lseplen) self.assertEqual(line, linesep) self.fnode.seek(-4, 2) line = self.fnode.readline(4) self.assertEqual(line, b'ated') self.fnode.seek(-4, 2) line = self.fnode.readline(20) self.assertEqual(line, b'ated') def test05_ReadlinesSize(self): """Reading a list of lines with a limited size.""" linesep = self.line_separator data = '%sshort line%sshort' % ((linesep.decode('ascii'),) * 2) data = data.encode('ascii') lines = self.fnode.readlines(len(data)) # self.assertEqual(lines, [linesep, b'short line' + linesep, b'short']) # # line = self.fnode.readline() # self.assertEqual(line, b' line' + linesep) # NOTE: the test is relaxed because the *hint* parameter of # io.BaseIO.readlines controls the amout of read data in a coarse way self.assertEqual(len(lines), len(data.split(b'\n'))) self.assertEqual(lines[:-1], [linesep, b'short line' + linesep]) self.assertTrue(lines[-1].startswith(b'short')) class MonoReadlineTestCase(ReadlineTestCase): """Tests reading one-byte-separated text lines from an existing file node.""" line_separator = b'\n' # class MultiReadlineTestCase(ReadlineTestCase): # "Tests reading multibyte-separated text lines from an existing file node." # # line_separator = b'
' # class LineSeparatorTestCase(TempFileMixin, TestCase): # "Tests text line separator manipulation in a file node." # # def setUp(self): # """setUp() -> None # # This method sets the following instance attributes: # * 'h5fname', the name of the temporary HDF5 file # * 'h5file', the writable, temporary HDF5 file with a '/test' node # * 'fnode', the writable file node in '/test' # """ # super().setUp() # self.fnode = filenode.new_node(self.h5file, where='/', name='test') # # def tearDown(self): # """tearDown() -> None # # Closes 'fnode' and 'h5file'; removes 'h5fname'. # """ # self.fnode.close() # self.fnode = None # super().tearDown() # # def test00_DefaultLineSeparator(self): # "Default line separator." # # self.assertEqual( # self.fnode.line_separator, os.linesep.encode('ascii'), # "Default line separator does not match that in os.linesep.") # # def test01_SetLineSeparator(self): # "Setting a valid line separator." # # try: # self.fnode.line_separator = b'SEPARATOR' # except ValueError: # self.fail("Valid line separator was not accepted.") # else: # self.assertEqual( # self.fnode.line_separator, b'SEPARATOR', # "Line separator was not correctly set.") # # def test02_SetInvalidLineSeparator(self): # "Setting an invalid line separator." # # self.assertRaises( # ValueError, setattr, self.fnode, 'line_separator', b'') # self.assertRaises( # ValueError, setattr, self.fnode, 'line_separator', b'x' * 1024) # self.assertRaises( # TypeError, setattr, self.fnode, 'line_separator', 'x') class AttrsTestCase(TempFileMixin, TestCase): """Tests setting and getting file node attributes.""" def setUp(self): """setUp() -> None This method sets the following instance attributes: * 'h5fname', the name of the temporary HDF5 file * 'h5file', the writable, temporary HDF5 file with a '/test' node * 'fnode', the writable file node in '/test' """ super().setUp() self.fnode = filenode.new_node(self.h5file, where='/', name='test') def tearDown(self): """tearDown() -> None Closes 'fnode' and 'h5file'; removes 'h5fname'. """ self.fnode.close() self.fnode = None super().tearDown() # This no longer works since type and type version attributes # are now system attributes. ivb(2004-12-29) # def test00_GetTypeAttr(self): # "Getting the type attribute of a file node." # # self.assertEqual( # getattr(self.fnode.attrs, '_type', None), filenode.NodeType, # "File node has no '_type' attribute.") def test00_MangleTypeAttrs(self): """Mangling the type attributes on a file node.""" nodeType = getattr(self.fnode.attrs, 'NODE_TYPE', None) self.assertEqual( nodeType, filenode.NodeType, "File node does not have a valid 'NODE_TYPE' attribute.") nodeTypeVersion = getattr(self.fnode.attrs, 'NODE_TYPE_VERSION', None) self.assertTrue( nodeTypeVersion in filenode.NodeTypeVersions, "File node does not have a valid 'NODE_TYPE_VERSION' attribute.") # System attributes are now writable. ivb(2004-12-30) # self.assertRaises( # AttributeError, # setattr, self.fnode.attrs, 'NODE_TYPE', 'foobar') # self.assertRaises( # AttributeError, # setattr, self.fnode.attrs, 'NODE_TYPE_VERSION', 'foobar') # System attributes are now removables. F. Alted (2007-03-06) # self.assertRaises( # AttributeError, # delattr, self.fnode.attrs, 'NODE_TYPE') # self.assertRaises( # AttributeError, # delattr, self.fnode.attrs, 'NODE_TYPE_VERSION') # System attributes are now writable. ivb(2004-12-30) # def test01_SetSystemAttr(self): # "Setting a system attribute on a file node." # # self.assertRaises( # AttributeError, setattr, self.fnode.attrs, 'CLASS', 'foobar') def test02_SetGetDelUserAttr(self): """Setting a user attribute on a file node.""" self.assertEqual( getattr(self.fnode.attrs, 'userAttr', None), None, "Inexistent attribute has a value that is not 'None'.") self.fnode.attrs.userAttr = 'foobar' self.assertEqual( getattr(self.fnode.attrs, 'userAttr', None), 'foobar', "User attribute was not correctly set.") self.fnode.attrs.userAttr = 'bazquux' self.assertEqual( getattr(self.fnode.attrs, 'userAttr', None), 'bazquux', "User attribute was not correctly changed.") del self.fnode.attrs.userAttr self.assertEqual( getattr(self.fnode.attrs, 'userAttr', None), None, "User attribute was not deleted.") # Another way is looking up the attribute in the attribute list. # if 'userAttr' in self.fnode.attrs._f_list(): # self.fail("User attribute was not deleted.") def test03_AttrsOnClosedFile(self): """Accessing attributes on a closed file node.""" self.fnode.close() self.assertRaises(AttributeError, getattr, self.fnode, 'attrs') class ClosedH5FileTestCase(TempFileMixin, TestCase): """Tests accessing a file node in a closed PyTables file.""" def setUp(self): """setUp() -> None This method sets the following instance attributes: * 'h5fname', the name of the temporary HDF5 file * 'h5file', the closed HDF5 file with a '/test' node * 'fnode', the writable file node in '/test' """ super().setUp() self.fnode = filenode.new_node(self.h5file, where='/', name='test') self.h5file.close() def tearDown(self): """tearDown() -> None Closes 'fnode'; removes 'h5fname'. """ # ivilata: We know that a UserWarning will be raised # because the PyTables file has already been closed. # However, we don't want it to pollute the test output. warnings.filterwarnings('ignore', category=UserWarning) try: self.fnode.close() except ValueError: pass finally: warnings.filterwarnings('default', category=UserWarning) self.fnode = None super().tearDown() def test00_Write(self): """Writing to a file node in a closed PyTables file.""" self.assertRaises(ValueError, self.fnode.write, 'data') def test01_Attrs(self): """Accessing the attributes of a file node in a closed PyTables file.""" self.assertRaises(ValueError, getattr, self.fnode, 'attrs') class OldVersionTestCase(TestCase): """Base class for old version compatibility test cases. It provides some basic tests for file operations and attribute handling. Sub-classes must provide the 'oldversion' attribute and the 'oldh5fname' attribute. """ def setUp(self): """This method sets the following instance attributes: * ``h5fname``: the name of the temporary HDF5 file. * ``h5file``: the writable, temporary HDF5 file with a ``/test`` node. * ``fnode``: the readable file node in ``/test``. """ super().setUp() self.h5fname = tempfile.mktemp(suffix='.h5') self.oldh5fname = test_file(self.oldh5fname) oldh5f = open_file(self.oldh5fname) oldh5f.copy_file(self.h5fname) oldh5f.close() self.h5file = open_file( self.h5fname, 'r+', title="Test for file node old version compatibility") self.fnode = filenode.open_node(self.h5file.root.test, 'a+') def tearDown(self): """Closes ``fnode`` and ``h5file``; removes ``h5fname``.""" self.fnode.close() self.fnode = None self.h5file.close() self.h5file = None Path(self.h5fname).unlink() super().tearDown() def test00_Read(self): """Reading an old version file node.""" # self.fnode.line_separator = '\n' line = self.fnode.readline() self.assertEqual(line, 'This is only\n') line = self.fnode.readline() self.assertEqual(line, 'a test file\n') line = self.fnode.readline() self.assertEqual(line, 'for FileNode version %d\n' % self.oldversion) line = self.fnode.readline() self.assertEqual(line, '') self.fnode.seek(0) line = self.fnode.readline() self.assertEqual(line, 'This is only\n') def test01_Write(self): """Writing an old version file node.""" # self.fnode.line_separator = '\n' self.fnode.write('foobar\n') self.fnode.seek(-7, 2) line = self.fnode.readline() self.assertEqual(line, 'foobar\n') def test02_Attributes(self): """Accessing attributes in an old version file node.""" self.fnode.attrs.userAttr = 'foobar' self.assertEqual( getattr(self.fnode.attrs, 'userAttr', None), 'foobar', "User attribute was not correctly set.") self.fnode.attrs.userAttr = 'bazquux' self.assertEqual( getattr(self.fnode.attrs, 'userAttr', None), 'bazquux', "User attribute was not correctly changed.") del self.fnode.attrs.userAttr self.assertEqual( getattr(self.fnode.attrs, 'userAttr', None), None, "User attribute was not deleted.") class Version1TestCase(OldVersionTestCase): """Basic test for version 1 format compatibility.""" oldversion = 1 oldh5fname = 'test_filenode_v1.h5' class DirectReadWriteTestCase(TempFileMixin, TestCase): datafname = 'test_filenode.dat' def setUp(self): """This method sets the following instance attributes: * ``h5fname``: the name of the temporary HDF5 file. * ``h5file``, the writable, temporary HDF5 file with a '/test' node * ``datafname``: the name of the data file to be stored in the temporary HDF5 file. * ``data``: the contents of the file ``datafname`` * ``testfname``: the name of a temporary file to be written to. """ super().setUp() self.datafname = test_file(self.datafname) self.testfname = tempfile.mktemp() self.testh5fname = tempfile.mktemp(suffix=".h5") self.data = Path(self.datafname).read_bytes() self.testdir = tempfile.mkdtemp() def tearDown(self): """tearDown() -> None Closes 'fnode' and 'h5file'; removes 'h5fname'. """ if os.access(self.testfname, os.R_OK): Path(self.testfname).unlink() if os.access(self.testh5fname, os.R_OK): Path(self.testh5fname).unlink() shutil.rmtree(self.testdir) super().tearDown() def test01_WriteToPathlibPath(self): testh5fname = Path(self.testh5fname) datafname = Path(self.datafname) filenode.save_to_filenode(testh5fname, datafname, "/test1") def test01_WriteToFilename(self): # write contents of datafname to h5 testfile filenode.save_to_filenode(self.testh5fname, self.datafname, "/test1") # make sure writing to an existing node doesn't work ... self.assertRaises(IOError, filenode.save_to_filenode, self.testh5fname, self.datafname, "/test1") # ... except if overwrite is True filenode.save_to_filenode(self.testh5fname, self.datafname, "/test1", overwrite=True) # write again, this time specifying a name filenode.save_to_filenode(self.testh5fname, self.datafname, "/", name="test2") # read from test h5file filenode.read_from_filenode(self.testh5fname, self.testfname, "/test1") # and compare result to what it should be self.assertEqual(Path(self.testfname).read_bytes(), self.data) # make sure extracting to an existing file doesn't work ... with warnings.catch_warnings(): warnings.simplefilter("ignore") self.assertRaises(IOError, filenode.read_from_filenode, self.testh5fname, self.testfname, "/test1") # except overwrite is True. And try reading with a name filenode.read_from_filenode(self.testh5fname, self.testfname, "/", name="test2", overwrite=True) # and compare to what it should be self.assertEqual(Path(self.testfname).read_bytes(), self.data) # cleanup Path(self.testfname).unlink() Path(self.testh5fname).unlink() def test02_WriteToHDF5File(self): # write contents of datafname to h5 testfile filenode.save_to_filenode(self.h5file, self.datafname, "/test1") # make sure writing to an existing node doesn't work ... self.assertRaises(IOError, filenode.save_to_filenode, self.h5file, self.datafname, "/test1") # ... except if overwrite is True filenode.save_to_filenode(self.h5file, self.datafname, "/test1", overwrite=True) # read from test h5file filenode.read_from_filenode(self.h5file, self.testfname, "/test1") # and compare result to what it should be self.assertEqual(Path(self.testfname).read_bytes(), self.data) # make sure extracting to an existing file doesn't work ... self.assertRaises(IOError, filenode.read_from_filenode, self.h5file, self.testfname, "/test1") # make sure the original h5file is still alive and kicking self.assertEqual(isinstance(self.h5file, file.File), True) self.assertEqual(self.h5file.mode, "w") def test03_AutomaticNameGuessing(self): # write using the filename as node name filenode.save_to_filenode(self.testh5fname, self.datafname, "/") # and read again datafname = Path(self.datafname).name filenode.read_from_filenode(self.testh5fname, self.testdir, "/", name=datafname.replace(".", "_")) # test if the output file really has the expected name self.assertEqual(os.access(Path(self.testdir) / datafname, os.R_OK), True) # and compare result to what it should be self.assertEqual((Path(self.testdir) / datafname).read_bytes(), self.data) def test04_AutomaticNameGuessingWithFilenameAttribute(self): # write using the filename as node name filenode.save_to_filenode(self.testh5fname, self.datafname, "/") # and read again datafname = Path(self.datafname).name filenode.read_from_filenode(self.testh5fname, self.testdir, "/", name=datafname) # test if the output file really has the expected name self.assertEqual(os.access(Path(self.testdir) / datafname, os.R_OK), True) # and compare result to what it should be self.assertEqual((Path(self.testdir) / datafname).read_bytes(), self.data) def test05_ReadFromNonexistingNodeRaises(self): # write using the filename as node name filenode.save_to_filenode(self.testh5fname, self.datafname, "/") # and read again self.assertRaises(NoSuchNodeError, filenode.read_from_filenode, self.testh5fname, self.testdir, "/", name="THISNODEDOESNOTEXIST") def suite(): """suite() -> test suite Returns a test suite consisting of all the test cases in the module. """ theSuite = unittest.TestSuite() theSuite.addTest(unittest.makeSuite(NewFileTestCase)) theSuite.addTest(unittest.makeSuite(ClosedFileTestCase)) theSuite.addTest(unittest.makeSuite(WriteFileTestCase)) theSuite.addTest(unittest.makeSuite(OpenFileTestCase)) theSuite.addTest(unittest.makeSuite(ReadFileTestCase)) theSuite.addTest(unittest.makeSuite(MonoReadlineTestCase)) # theSuite.addTest(unittest.makeSuite(MultiReadlineTestCase)) # theSuite.addTest(unittest.makeSuite(LineSeparatorTestCase)) theSuite.addTest(unittest.makeSuite(AttrsTestCase)) theSuite.addTest(unittest.makeSuite(ClosedH5FileTestCase)) theSuite.addTest(unittest.makeSuite(DirectReadWriteTestCase)) return theSuite if __name__ == '__main__': import sys parse_argv(sys.argv) print_versions() unittest.main(defaultTest='suite') PyTables-3.7.0/tables/nodes/tests/test_filenode.xbm000066400000000000000000000063221416254111300223250ustar00rootroot00000000000000#define test_width 64 #define test_height 64 static char test_bits[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x1E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xB8, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xF8, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0xF1, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0x1F, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x38, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3D, 0xEE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0x01, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x70, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x84, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x18, 0x7C, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x19, 0x7C, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x31, 0x3C, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0x0E, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0x87, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xC7, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x61, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x31, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x38, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x07, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x03, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0xF1, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x06, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x42, 0x84, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x79, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x71, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x1E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x03, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0x61, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xC1, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC1, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x19, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x19, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x71, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; PyTables-3.7.0/tables/nodes/tests/test_filenode_v1.h5000066400000000000000000000215461416254111300224660ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ`#ÿÿÿÿÿÿÿÿ HEAP€testðTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà `Ø8((0ÿÿÿÿÿÿÿÿ (ôtYE (CLASSEARRAY 8 EXTDIM SNODÐTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿô8ô€` @TITLEFileNode version 1 test file (CLASSGROUP (VERSION1.0 ÐFILTERS°ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I0 sS'complevel' p7 I0 sS'fletcher32' p8 I0 sS'complib' p9 S'zlib' p10 sb. 8PYTABLES_FORMAT_VERSION1.3This is only a test file for FileNode version 1  0FLAVOR numarray (VERSION1.1 (TITLE`P 0 NODE_TYPEfile  H NODE_TYPE_VERSION  PyTables-3.7.0/tables/parameters.py000066400000000000000000000345661416254111300172470ustar00rootroot00000000000000"""Parameters for PyTables.""" import os as _os __docformat__ = 'reStructuredText' """The format of documentation strings in this module.""" _KB = 1024 """The size of a Kilobyte in bytes""" _MB = 1024 * _KB """The size of a Megabyte in bytes""" # Tunable parameters # ================== # Be careful when touching these! # Parameters for different internal caches # ---------------------------------------- BOUNDS_MAX_SIZE = 1 * _MB """The maximum size for bounds values cached during index lookups.""" BOUNDS_MAX_SLOTS = 4 * _KB """The maximum number of slots for the BOUNDS cache.""" ITERSEQ_MAX_ELEMENTS = 1 * _KB """The maximum number of iterator elements cached in data lookups.""" ITERSEQ_MAX_SIZE = 1 * _MB """The maximum space that will take ITERSEQ cache (in bytes).""" ITERSEQ_MAX_SLOTS = 128 """The maximum number of slots in ITERSEQ cache.""" LIMBOUNDS_MAX_SIZE = 256 * _KB """The maximum size for the query limits (for example, ``(lim1, lim2)`` in conditions like ``lim1 <= col < lim2``) cached during index lookups (in bytes).""" LIMBOUNDS_MAX_SLOTS = 128 """The maximum number of slots for LIMBOUNDS cache.""" TABLE_MAX_SIZE = 1 * _MB """The maximum size for table chunks cached during index queries.""" SORTED_MAX_SIZE = 1 * _MB """The maximum size for sorted values cached during index lookups.""" SORTEDLR_MAX_SIZE = 8 * _MB """The maximum size for chunks in last row cached in index lookups (in bytes).""" SORTEDLR_MAX_SLOTS = 1 * _KB """The maximum number of chunks for SORTEDLR cache.""" # Parameters for general cache behaviour # -------------------------------------- # # The next parameters will not be effective if passed to the # `open_file()` function (so, they can only be changed in a *global* # way). You can change them in the file, but this is strongly # discouraged unless you know well what you are doing. DISABLE_EVERY_CYCLES = 10 """The number of cycles in which a cache will be forced to be disabled if the hit ratio is lower than the LOWEST_HIT_RATIO (see below). This value should provide time enough to check whether the cache is being efficient or not.""" ENABLE_EVERY_CYCLES = 50 """The number of cycles in which a cache will be forced to be (re-)enabled, irregardingly of the hit ratio. This will provide a chance for checking if we are in a better scenario for doing caching again.""" LOWEST_HIT_RATIO = 0.6 """The minimum acceptable hit ratio for a cache to avoid disabling (and freeing) it.""" # Tunable parameters # ================== # Be careful when touching these! # Recommended maximum values # -------------------------- # Following are the recommended values for several limits. However, # these limits are somewhat arbitrary and can be increased if you have # enough resources. MAX_COLUMNS = 512 """Maximum number of columns in :class:`tables.Table` objects before a :exc:`tables.PerformanceWarning` is issued. This limit is somewhat arbitrary and can be increased. """ MAX_NODE_ATTRS = 4 * _KB """Maximum allowed number of attributes in a node.""" MAX_GROUP_WIDTH = 16 * _KB """Maximum allowed number of children hanging from a group.""" MAX_TREE_DEPTH = 2 * _KB """Maximum depth in object tree allowed.""" MAX_UNDO_PATH_LENGTH = 10 * _KB """Maximum length of paths allowed in undo/redo operations.""" # Cache limits # ------------ COND_CACHE_SLOTS = 128 """Maximum number of conditions for table queries to be kept in memory.""" CHUNK_CACHE_NELMTS = 521 """Number of elements for HDF5 chunk cache.""" CHUNK_CACHE_PREEMPT = 0.0 """Chunk preemption policy. This value should be between 0 and 1 inclusive and indicates how much chunks that have been fully read are favored for preemption. A value of zero means fully read chunks are treated no differently than other chunks (the preemption is strictly LRU) while a value of one means fully read chunks are always preempted before other chunks.""" CHUNK_CACHE_SIZE = 2 * _MB """Size (in bytes) for HDF5 chunk cache.""" # Size for new metadata cache system METADATA_CACHE_SIZE = 1 * _MB # 1 MB is the default for HDF5 """Size (in bytes) of the HDF5 metadata cache.""" # NODE_CACHE_SLOTS tells the number of nodes that fits in the cache. # # There are several forces driving the election of this number: # 1.- As more nodes, better chances to re-use nodes # --> better performance # 2.- As more nodes, the re-ordering of the LRU cache takes more time # --> less performance # 3.- As more nodes, the memory needs for PyTables grows, specially for table # writings (that could take double of memory than table reads!). # # The default value here is quite conservative. If you have a system # with tons of memory, and if you are touching regularly a very large # number of leaves, try increasing this value and see if it fits better # for you. Please report back your feedback. NODE_CACHE_SLOTS = 64 """Maximum number of nodes to be kept in the metadata cache. It is the number of nodes to be kept in the metadata cache. Least recently used nodes are unloaded from memory when this number of loaded nodes is reached. To load a node again, simply access it as usual. Nodes referenced by user variables and, in general, all nodes that are still open are registered in the node manager and can be quickly accessed even if they are not in the cache. Negative value means that all the touched nodes will be kept in an internal dictionary. This is the faster way to load/retrieve nodes. However, and in order to avoid a large memory comsumption, the user will be warned when the number of loaded nodes will reach the ``-NODE_CACHE_SLOTS`` value. Finally, a value of zero means that any cache mechanism is disabled. """ # Parameters for the I/O buffer in `Leaf` objects # ----------------------------------------------- IO_BUFFER_SIZE = 1 * _MB """The PyTables internal buffer size for I/O purposes. Should not exceed the amount of highest level cache size in your CPU.""" BUFFER_TIMES = 100 """The maximum buffersize/rowsize ratio before issuing a :exc:`tables.PerformanceWarning`.""" # Miscellaneous # ------------- EXPECTED_ROWS_EARRAY = 1000 """Default expected number of rows for :class:`EArray` objects.""" EXPECTED_ROWS_VLARRAY = 1000 """Default expected number of rows for :class:`VLArray` objects. .. versionadded:: 3.0 """ EXPECTED_ROWS_TABLE = 10_000 """Default expected number of rows for :class:`Table` objects.""" PYTABLES_SYS_ATTRS = True """Set this to ``False`` if you don't want to create PyTables system attributes in datasets. Also, if set to ``False`` the possible existing system attributes are not considered for guessing the class of the node during its loading from disk (this work is delegated to the PyTables' class discoverer function for general HDF5 files).""" MAX_NUMEXPR_THREADS = _os.environ.get("NUMEXPR_MAX_THREADS", 4) """The maximum number of threads that PyTables should use internally in Numexpr. If `None`, it is automatically set to the number of cores in your machine. In general, it is a good idea to set this to the number of cores in your machine or, when your machine has many of them (e.g. > 8), perhaps stay at 8 at maximum. In general, 4 threads is a good tradeoff.""" MAX_BLOSC_THREADS = 1 # 1 is safe for concurrency """The maximum number of threads that PyTables should use internally in Blosc. If `None`, it is automatically set to the number of cores in your machine. For applications that use several PyTables instances concurrently and so as to avoid locking problems, the recommended value is 1. In other cases a value of 2 or 4 could make sense. """ USER_BLOCK_SIZE = 0 """Sets the user block size of a file. The default user block size is 0; it may be set to any power of 2 equal to 512 or greater (512, 1024, 2048, etc.). .. versionadded:: 3.0 """ ALLOW_PADDING = True """Allow padding in compound data types. Starting on version 3.5 padding is honored during copies, or when tables are created from NumPy structured arrays with padding (e.g. `align=True`). If you actually want to get rid of any possible padding in new datasets/attributes (i.e. the previous behaviour), set this to `False`. .. versionadded:: 3.5 """ # HDF5 driver management # ---------------------- DRIVER = None """The HDF5 driver that should be used for reading/writing to the file. Following drivers are supported: * H5FD_SEC2: this driver uses POSIX file-system functions like read and write to perform I/O to a single, permanent file on local disk with no system buffering. This driver is POSIX-compliant and is the default file driver for all systems. * H5FD_DIRECT: this is the H5FD_SEC2 driver except data is written to or read from the file synchronously without being cached by the system. * H5FD_WINDOWS: this driver was modified in HDF5-1.8.8 to be a wrapper of the POSIX driver, H5FD_SEC2. This change should not affect user applications. * H5FD_STDIO: this driver uses functions from the standard C stdio.h to perform I/O to a single, permanent file on local disk with additional system buffering. * H5FD_CORE: with this driver, an application can work with a file in memory for faster reads and writes. File contents are kept in memory until the file is closed. At closing, the memory version of the file can be written back to disk or abandoned. * H5FD_SPLIT: this file driver splits a file into two parts. One part stores metadata, and the other part stores raw data. This splitting a file into two parts is a limited case of the Multi driver. The following drivers are not currently supported: * H5FD_LOG: this is the H5FD_SEC2 driver with logging capabilities. * H5FD_FAMILY: with this driver, the HDF5 file’s address space is partitioned into pieces and sent to separate storage files using an underlying driver of the user’s choice. This driver is for systems that do not support files larger than 2 gigabytes. * H5FD_MULTI: with this driver, data can be stored in multiple files according to the type of the data. I/O might work better if data is stored in separate files based on the type of data. The Split driver is a special case of this driver. * H5FD_MPIO: this is the standard HDF5 file driver for parallel file systems. This driver uses the MPI standard for both communication and file I/O. * H5FD_MPIPOSIX: this parallel file system driver uses MPI for communication and POSIX file-system calls for file I/O. * H5FD_STREAM: this driver is no longer available. .. seealso:: the `Drivers section`_ of the `HDF5 User's Guide`_ for more information. .. note:: not all supported drivers are always available. For example the H5FD_WINDOWS driver is not available on non Windows platforms. If the user try to use a driver that is not available on the target platform a :exc:`RuntimeError` is raised. .. versionadded:: 3.0 .. _`Drivers section`: http://www.hdfgroup.org/HDF5/doc/UG/08_TheFile.html#Drivers .. _`HDF5 User's Guide`: http://www.hdfgroup.org/HDF5/doc/UG/index.html """ DRIVER_DIRECT_ALIGNMENT = 0 """Specifies the required alignment boundary in memory. A value of 0 (zero) means to use HDF5 Library’s default value. .. versionadded:: 3.0 """ DRIVER_DIRECT_BLOCK_SIZE = 0 """Specifies the file system block size. A value of 0 (zero) means to use HDF5 Library’s default value of 4KB. .. versionadded:: 3.0 """ DRIVER_DIRECT_CBUF_SIZE = 0 """Specifies the copy buffer size. A value of 0 (zero) means to use HDF5 Library’s default value. .. versionadded:: 3.0 """ # DRIVER_LOG_FLAGS = 0x0001ffff # """Flags specifying the types of logging activity. # # .. versionadded:: 3.0 # # .. seeealso:: # http://www.hdfgroup.org/HDF5/doc/RM/RM_H5P.html#Property-SetFaplLog # # """ # # DRIVER_LOG_BUF_SIZE = 4 * _KB # """The size of the logging buffers, in bytes. # # One buffer of size DRIVER_LOG_BUF_SIZE will be created for each of # H5FD_LOG_FILE_READ, H5FD_LOG_FILE_WRITE and H5FD_LOG_FLAVOR when those # flags are set; these buffers will not grow as the file increases in # size. # # .. versionadded:: 3.0 # # """ DRIVER_CORE_INCREMENT = 64 * _KB """Core driver memory increment. Specifies the increment by which allocated memory is to be increased each time more memory is required. .. versionadded:: 3.0 """ DRIVER_CORE_BACKING_STORE = 1 """Enables backing store for the core driver. With the H5FD_CORE driver, if the DRIVER_CORE_BACKING_STORE is set to 1 (True), the file contents are flushed to a file with the same name as this core file when the file is closed or access to the file is terminated in memory. The application is allowed to open an existing file with H5FD_CORE driver. In that case, if the DRIVER_CORE_BACKING_STORE is set to 1 and the flags for :func:`tables.open_file` is set to H5F_ACC_RDWR, any change to the file contents are saved to the file when the file is closed. If backing_store is set to 0 and the flags for :func:`tables.open_file` is set to H5F_ACC_RDWR, any change to the file contents will be lost when the file is closed. If the flags for :func:`tables.open_file` is set to H5F_ACC_RDONLY, no change to the file is allowed either in memory or on file. .. versionadded:: 3.0 """ DRIVER_CORE_IMAGE = None """String containing an HDF5 file image. If this option is passed to the :func:`tables.open_file` function then the returned file object is set up using the specified image. A file image can be retrieved from an existing (and opened) file object using the :meth:`tables.File.get_file_image` method. .. note:: requires HDF5 >= 1.8.9. .. versionadded:: 3.0 """ DRIVER_SPLIT_META_EXT = '-m.h5' """The extension for the metadata file used by the H5FD_SPLIT driver. If this option is passed to the :func:`tables.openFile` function along with driver='H5FD_SPLIT', the extension is appended to the name passed as the first parameter to form the name of the metadata file. If the string '%s' is used in the extension, the metadata file name is formed by replacing '%s' with the name passed as the first parameter instead. .. versionadded:: 3.1 """ DRIVER_SPLIT_RAW_EXT = '-r.h5' """The extension for the raw data file used by the H5FD_SPLIT driver. If this option is passed to the :func:`tables.openFile` function along with driver='H5FD_SPLIT', the extension is appended to the name passed as the first parameter to form the name of the raw data file. If the string '%s' is used in the extension, the raw data file name is formed by replacing '%s' with the name passed as the first parameter instead. .. versionadded:: 3.1 """ PyTables-3.7.0/tables/path.py000066400000000000000000000147561416254111300160370ustar00rootroot00000000000000"""Functionality related with node paths in a PyTables file. Variables ========= `__docformat`__ The format of documentation strings in this module. """ import re import warnings import keyword from .exceptions import NaturalNameWarning __docformat__ = 'reStructuredText' """The format of documentation strings in this module.""" _python_id_re = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') """Python identifier regular expression.""" _reserved_id_re = re.compile('^_[cfgv]_') """PyTables reserved identifier regular expression. - c: class variables - f: class public methods - g: class private methods - v: instance variables """ _hidden_name_re = re.compile('^_[pi]_') """Nodes with a name *matching* this expression are considered hidden. For instance, ``name`` whould be visible while ``_i_name`` would not. """ _hidden_path_re = re.compile('/_[pi]_') """Nodes with a path *containing* this expression are considered hidden. For instance, a node with a pathname like ``/a/b/c`` would be visible while nodes with pathnames like ``/a/c/_i_x`` or ``/a/_p_x/y`` would not. """ _warnInfo = ( "you will not be able to use natural naming to access this object; " "using ``getattr()`` will still work, though") """Warning printed when a name will not be reachable through natural naming""" def check_attribute_name(name): """Check the validity of the `name` of an attribute in AttributeSet. If the name is not valid, a ``ValueError`` is raised. If it is valid but it can not be used with natural naming, a `NaturalNameWarning` is issued. >>> warnings.simplefilter("ignore") >>> check_attribute_name('a') >>> check_attribute_name('a_b') >>> check_attribute_name('a:b') # NaturalNameWarning >>> check_attribute_name('/a/b') # NaturalNameWarning >>> check_attribute_name('/') # NaturalNameWarning >>> check_attribute_name('.') # NaturalNameWarning >>> check_attribute_name('__members__') Traceback (most recent call last): ... ValueError: ``__members__`` is not allowed as an object name >>> check_attribute_name(1) Traceback (most recent call last): ... TypeError: object name is not a string: 1 >>> check_attribute_name('') Traceback (most recent call last): ... ValueError: the empty string is not allowed as an object name """ if not isinstance(name, str): # Python >= 2.3 raise TypeError(f"object name is not a string: {name!r}") if name == '': raise ValueError("the empty string is not allowed as an object name") # Check whether `name` is a valid Python identifier. if not _python_id_re.match(name): warnings.warn("object name is not a valid Python identifier: %r; " "it does not match the pattern ``%s``; %s" % (name, _python_id_re.pattern, _warnInfo), NaturalNameWarning, stacklevel=2) return # However, Python identifiers and keywords have the same form. if keyword.iskeyword(name): warnings.warn("object name is a Python keyword: %r; %s" % (name, _warnInfo), NaturalNameWarning, stacklevel=2) return # Still, names starting with reserved prefixes are not allowed. if _reserved_id_re.match(name): raise ValueError("object name starts with a reserved prefix: %r; " "it matches the pattern ``%s``" % (name, _reserved_id_re.pattern)) # ``__members__`` is the only exception to that rule. if name == '__members__': raise ValueError("``__members__`` is not allowed as an object name") def check_name_validity(name): """Check the validity of the `name` of a Node object, which more limited than attribute names. If the name is not valid, a ``ValueError`` is raised. If it is valid but it can not be used with natural naming, a `NaturalNameWarning` is issued. >>> warnings.simplefilter("ignore") >>> check_name_validity('a') >>> check_name_validity('a_b') >>> check_name_validity('a:b') # NaturalNameWarning >>> check_name_validity('/a/b') Traceback (most recent call last): ... ValueError: the ``/`` character is not allowed in object names: '/a/b' >>> check_name_validity('.') Traceback (most recent call last): ... ValueError: ``.`` is not allowed as an object name >>> check_name_validity('') Traceback (most recent call last): ... ValueError: the empty string is not allowed as an object name """ check_attribute_name(name) # Check whether `name` is a valid HDF5 name. # http://hdfgroup.org/HDF5/doc/UG/03_Model.html#Structure if name == '.': raise ValueError("``.`` is not allowed as an object name") elif '/' in name: raise ValueError("the ``/`` character is not allowed " "in object names: %r" % name) def join_path(parentpath, name): """Join a *canonical* `parentpath` with a *non-empty* `name`. .. versionchanged:: 3.0 The *parentPath* parameter has been renamed into *parentpath*. >>> join_path('/', 'foo') '/foo' >>> join_path('/foo', 'bar') '/foo/bar' >>> join_path('/foo', '/foo2/bar') '/foo/foo2/bar' >>> join_path('/foo', '/') '/foo' """ if name.startswith('./'): # Support relative paths (mainly for links) name = name[2:] if parentpath == '/' and name.startswith('/'): pstr = '%s' % name elif parentpath == '/' or name.startswith('/'): pstr = f'{parentpath}{name}' else: pstr = f'{parentpath}/{name}' if pstr.endswith('/'): pstr = pstr[:-1] return pstr def split_path(path): """Split a *canonical* `path` into a parent path and a node name. The result is returned as a tuple. The parent path does not include a trailing slash. >>> split_path('/') ('/', '') >>> split_path('/foo/bar') ('/foo', 'bar') """ lastslash = path.rfind('/') ppath = path[:lastslash] name = path[lastslash + 1:] if ppath == '': ppath = '/' return (ppath, name) def isvisiblename(name): """Does this `name` make the named node a visible one?""" return _hidden_name_re.match(name) is None def isvisiblepath(path): """Does this `path` make the named node a visible one?""" return _hidden_path_re.search(path) is None def _test(): """Run ``doctest`` on this module.""" import doctest doctest.testmod() if __name__ == '__main__': _test() PyTables-3.7.0/tables/registry.py000066400000000000000000000042761416254111300167470ustar00rootroot00000000000000"""Miscellaneous mappings used to avoid circular imports. Variables: `class_name_dict` Node class name to class object mapping. `class_id_dict` Class identifier to class object mapping. Misc variables: `__docformat__` The format of documentation strings in this module. """ # Important: no modules from PyTables should be imported here # (but standard modules are OK), since the main reason for this module # is avoiding circular imports! __docformat__ = 'reStructuredText' """The format of documentation strings in this module.""" class_name_dict = {} """Node class name to class object mapping. This dictionary maps class names (e.g. ``'Group'``) to actual class objects (e.g. `Group`). Classes are registered here when they are defined, and they are not expected to be unregistered (by now), but they can be replaced when the module that defines them is reloaded. .. versionchanged:: 3.0 The *classNameDict* dictionary has been renamed into *class_name_dict*. """ class_id_dict = {} """Class identifier to class object mapping. This dictionary maps class identifiers (e.g. ``'GROUP'``) to actual class objects (e.g. `Group`). Classes defining a new ``_c_classid`` attribute are registered here when they are defined, and they are not expected to be unregistered (by now), but they can be replaced when the module that defines them is reloaded. .. versionchanged:: 3.0 The *classIdDict* dictionary has been renamed into *class_id_dict*. """ # Deprecated API classNameDict = class_name_dict classIdDict = class_id_dict def get_class_by_name(classname): """Get the node class matching the `classname`. If the name is not registered, a ``TypeError`` is raised. The empty string and ``None`` are also accepted, and mean the ``Node`` class. .. versionadded:: 3.0 """ # The empty string is accepted for compatibility # with old default arguments. if classname is None or classname == '': classname = 'Node' # Get the class object corresponding to `classname`. if classname not in class_name_dict: raise TypeError("there is no registered node class named ``%s``" % (classname,)) return class_name_dict[classname] PyTables-3.7.0/tables/req_versions.py000066400000000000000000000011451416254111300176060ustar00rootroot00000000000000"""Required versions for PyTables dependencies.""" from packaging.version import Version # ********************************************************************** # Keep these in sync with setup.cfg and user's guide # ********************************************************************** # Minimum recommended versions for mandatory packages min_numpy_version = Version('1.9.3') min_numexpr_version = Version('2.6.2') min_hdf5_version = Version('1.8.4') min_blosc_version = Version("1.4.1") min_blosc_bitshuffle_version = Version("1.8.0") """The minumum Blosc version where BitShuffle can be used safely.""" PyTables-3.7.0/tables/scripts/000077500000000000000000000000001416254111300162035ustar00rootroot00000000000000PyTables-3.7.0/tables/scripts/__init__.py000066400000000000000000000002501416254111300203110ustar00rootroot00000000000000"""Utility scripts for PyTables. This package contains some modules which provide a ``main()`` function (with no arguments), so that they can be used as scripts. """ PyTables-3.7.0/tables/scripts/pt2to3.py000066400000000000000000000551101416254111300177120ustar00rootroot00000000000000"""This utility helps you migrate from PyTables 2.x APIs to 3.x APIs, which are PEP 8 compliant. """ import re import sys import argparse from pathlib import Path old2newnames = dict([ # from __init__.py ('hdf5Version', 'hdf5_version'), # data # from array.py ('parentNode', 'parentnode'), # kwarg ('getEnum', 'get_enum'), ('_initLoop', '_init_loop'), ('_fancySelection', '_fancy_selection'), ('_checkShape', '_check_shape'), ('_readSlice', '_read_slice'), ('_readCoords', '_read_coords'), ('_readSelection', '_read_selection'), ('_writeSlice', '_write_slice'), ('_writeCoords', '_write_coords'), ('_writeSelection', '_write_selection'), ('_g_copyWithStats', '_g_copy_with_stats'), ('_c_classId', '_c_classid'), # attr # from atom.py ('_checkBase', '_checkbase'), # from attributeset.py ('newSet', 'newset'), # kwarg ('copyClass', 'copyclass'), # kwarg ('_g_updateNodeLocation', '_g_update_node_location'), ('_g_logAdd', '_g_log_add'), ('_g_delAndLog', '_g_del_and_log'), ('_v__nodeFile', '_v__nodefile'), # attr (private) ('_v__nodePath', '_v__nodepath'), # attr (private) # from carray.py # ('parentNode', 'parentnode'), # kwarg # from description.py ('_g_setNestedNamesDescr', '_g_set_nested_names_descr'), ('_g_setPathNames', '_g_set_path_names'), ('_v_colObjects', '_v_colobjects'), # attr ('_v_nestedFormats', '_v_nested_formats'), # attr ('_v_nestedNames', '_v_nested_names'), # attr ('_v_nestedDescr', '_v_nested_descr'), # attr ('getColsInOrder', 'get_cols_in_order'), ('joinPaths', 'join_paths'), ('metaIsDescription', 'MetaIsDescription'), # from earray.py # ('parentNode', 'parentnode'), # kwarg ('_checkShapeAppend', '_check_shape_append'), # from expression.py ('_exprvarsCache', '_exprvars_cache'), # attr (private) ('_requiredExprVars', '_required_expr_vars'), ('setInputsRange', 'set_inputs_range'), ('setOutput', 'set_output'), ('setOutputRange', 'set_output_range'), # from file.py ('_opToCode', '_op_to_code'), # data (private) ('_codeToOp', '_code_to_op'), # data (private) ('_transVersion', '_trans_version'), # data (private) ('_transGroupParent', '_trans_group_parent'), # data (private) ('_transGroupName', '_trans_group_name'), # data (private) ('_transGroupPath', '_trans_group_path'), # data (private) ('_actionLogParent', '_action_log_parent'), # data (private) ('_actionLogName', '_action_log_name'), # data (private) ('_actionLogPath', '_action_log_path'), # data (private) ('_transParent', '_trans_parent'), # data (private) ('_transName', '_trans_name'), # data (private) ('_transPath', '_trans_path'), # data (private) ('_shadowParent', '_shadow_parent'), # data (private) ('_shadowName', '_shadow_name'), # data (private) ('_shadowPath', '_shadow_path'), # data (private) ('copyFile', 'copy_file'), ('openFile', 'open_file'), ('_getValueFromContainer', '_get_value_from_container'), ('__getRootGroup', '__get_root_group'), ('rootUEP', 'root_uep'), # attr ('_getOrCreatePath', '_get_or_create_path'), ('_createPath', '_create_path'), ('createGroup', 'create_group'), ('createTable', 'create_table'), ('createArray', 'create_array'), ('createCArray', 'create_carray'), ('createEArray', 'create_earray'), ('createVLArray', 'create_vlarray'), ('createHardLink', 'create_hard_link'), ('createSoftLink', 'create_soft_link'), ('createExternalLink', 'create_external_link'), ('_getNode', '_get_node'), ('getNode', 'get_node'), ('isVisibleNode', 'is_visible_node'), ('renameNode', 'rename_node'), ('moveNode', 'move_node'), ('copyNode', 'copy_node'), ('removeNode', 'remove_node'), ('getNodeAttr', 'get_node_attr'), ('setNodeAttr', 'set_node_attr'), ('delNodeAttr', 'del_node_attr'), ('copyNodeAttrs', 'copy_node_attrs'), ('copyChildren', 'copy_children'), ('listNodes', 'list_nodes'), ('iterNodes', 'iter_nodes'), ('walkNodes', 'walk_nodes'), ('walkGroups', 'walk_groups'), ('_checkOpen', '_check_open'), ('_isWritable', '_iswritable'), ('_checkWritable', '_check_writable'), ('_checkGroup', '_check_group'), ('isUndoEnabled', 'is_undo_enabled'), ('_checkUndoEnabled', '_check_undo_enabled'), ('_createTransactionGroup', '_create_transaction_group'), ('_createTransaction', '_create_transaction'), ('_createMark', '_create_mark'), ('enableUndo', 'enable_undo'), ('disableUndo', 'disable_undo'), ('_getMarkID', '_get_mark_id'), ('_getFinalAction', '_get_final_action'), ('getCurrentMark', 'get_current_mark'), ('_updateNodeLocations', '_update_node_locations'), # from group.py # ('parentNode', 'parentnode'), # kwarg # ('ptFile', 'ptfile'), # kwarg ('_getValueFromContainer', '_get_value_from_container'), ('_g_postInitHook', '_g_post_init_hook'), ('_g_getChildGroupClass', '_g_get_child_group_class'), ('_g_getChildLeafClass', '_g_get_child_leaf_class'), ('_g_addChildrenNames', '_g_add_children_names'), ('_g_checkHasChild', '_g_check_has_child'), ('_f_walkNodes', '_f_walknodes'), ('_g_widthWarning', '_g_width_warning'), ('_g_refNode', '_g_refnode'), ('_g_unrefNode', '_g_unrefnode'), ('_g_copyChildren', '_g_copy_children'), ('_f_getChild', '_f_get_child'), ('_f_listNodes', '_f_list_nodes'), ('_f_iterNodes', '_f_iter_nodes'), ('_f_walkGroups', '_f_walk_groups'), ('_g_closeDescendents', '_g_close_descendents'), ('_f_copyChildren', '_f_copy_children'), ('_v_maxGroupWidth', '_v_max_group_width'), # attr ('_v_objectID', '_v_objectid'), # attr ('_g_loadChild', '_g_load_child'), ('childName', 'childname'), # ??? ('_c_shadowNameRE', '_c_shadow_name_re'), # attr (private) # from hdf5extension.p{yx,xd} ('hdf5Extension', 'hdf5extension'), ('_getFileId', '_get_file_id'), ('_flushFile', '_flush_file'), ('_closeFile', '_close_file'), ('_g_listAttr', '_g_list_attr'), ('_g_setAttr', '_g_setattr'), ('_g_getAttr', '_g_getattr'), ('_g_listGroup', '_g_list_group'), ('_g_getGChildAttr', '_g_get_gchild_attr'), ('_g_getLChildAttr', '_g_get_lchild_attr'), ('_g_flushGroup', '_g_flush_group'), ('_g_closeGroup', '_g_close_group'), ('_g_moveNode', '_g_move_node'), ('_convertTime64', '_convert_time64'), ('_createArray', '_create_array'), ('_createCArray', '_create_carray'), ('_openArray', '_open_array'), ('_readArray', '_read_array'), ('_g_readSlice', '_g_read_slice'), ('_g_readCoords', '_g_read_coords'), ('_g_readSelection', '_g_read_selection'), ('_g_writeSlice', '_g_write_slice'), ('_g_writeCoords', '_g_write_coords'), ('_g_writeSelection', '_g_write_selection'), # from idxutils.py ('calcChunksize', 'calc_chunksize'), ('infinityF', 'infinityf'), # data ('infinityMap', 'infinitymap'), # data ('infType', 'inftype'), ('StringNextAfter', 'string_next_after'), ('IntTypeNextAfter', 'int_type_next_after'), ('BoolTypeNextAfter', 'bool_type_next_after'), # from index.py # ('parentNode', 'parentnode'), # kwarg ('defaultAutoIndex', 'default_auto_index'), # data ('defaultIndexFilters', 'default_index_filters'), # data ('_tableColumnPathnameOfIndex', '_table_column_pathname_of_index'), ('_is_CSI', '_is_csi'), ('is_CSI', 'is_csi'), # property ('appendLastRow', 'append_last_row'), ('read_sliceLR', 'read_slice_lr'), ('readSorted', 'read_sorted'), ('readIndices', 'read_indices'), ('_processRange', '_process_range'), ('searchLastRow', 'search_last_row'), ('getLookupRange', 'get_lookup_range'), ('_g_checkName', '_g_check_name'), # from indexes.py # ('parentNode', 'parentnode'), # kwarg ('_searchBin', '_search_bin'), # from indexesextension ('indexesExtension', 'indexesextension'), ('initRead', 'initread'), ('readSlice', 'read_slice'), ('_readIndexSlice', '_read_index_slice'), ('_initSortedSlice', '_init_sorted_slice'), ('_g_readSortedSlice', '_g_read_sorted_slice'), ('_readSortedSlice', '_read_sorted_slice'), ('getLRUbounds', 'get_lru_bounds'), ('getLRUsorted', 'get_lru_sorted'), ('_searchBinNA_b', '_search_bin_na_b'), ('_searchBinNA_ub', '_search_bin_na_ub'), ('_searchBinNA_s', '_search_bin_na_s'), ('_searchBinNA_us', '_search_bin_na_us'), ('_searchBinNA_i', '_search_bin_na_i'), ('_searchBinNA_ui', '_search_bin_na_ui'), ('_searchBinNA_ll', '_search_bin_na_ll'), ('_searchBinNA_ull', '_search_bin_na_ull'), ('_searchBinNA_e', '_search_bin_na_e'), ('_searchBinNA_f', '_search_bin_na_f'), ('_searchBinNA_d', '_search_bin_na_d'), ('_searchBinNA_g', '_search_bin_na_g'), # from leaf.py # ('parentNode', 'parentnode'), # kwarg ('objectID', 'object_id'), # property ('_processRangeRead', '_process_range_read'), ('_pointSelection', '_point_selection'), ('isVisible', 'isvisible'), ('getAttr', 'get_attr'), ('setAttr', 'set_attr'), ('delAttr', 'del_attr'), # from link.py # ('parentNode', 'parentnode'), # kwarg ('_g_getLinkClass', '_g_get_link_class'), # from linkextension ('linkExtension', 'linkextension'), ('_getLinkClass', '_get_link_class'), ('_g_createHardLink', '_g_create_hard_link'), # from lrucacheextension ('lrucacheExtension', 'lrucacheextension'), # from misc/enum.py ('_checkAndSetPair', '_check_and_set_pair'), ('_getContainer', '_get_container'), # from misc/proxydict.py ('containerRef', 'containerref'), # attr # from node.py # ('parentNode', 'parentnode'), # kwarg ('_g_logCreate', '_g_log_create'), ('_g_preKillHook', '_g_pre_kill_hook'), ('_g_checkOpen', '_g_check_open'), ('_g_setLocation', '_g_set_location'), ('_g_updateLocation', '_g_update_location'), ('_g_delLocation', '_g_del_location'), ('_g_updateDependent', '_g_update_dependent'), ('_g_removeAndLog', '_g_remove_and_log'), ('_g_logMove', '_g_log_move'), ('oldPathname', 'oldpathname'), # ?? ('_g_copyAsChild', '_g_copy_as_child'), ('_f_isVisible', '_f_isvisible'), ('_g_checkGroup', '_g_check_group'), ('_g_checkNotContains', '_g_check_not_contains'), ('_g_maybeRemove', '_g_maybe_remove'), ('_f_getAttr', '_f_getattr'), ('_f_setAttr', '_f_setattr'), ('_f_delAttr', '_f_delattr'), ('_v_maxTreeDepth', '_v_maxtreedepth'), # attr # from nodes/filenode.py ('newNode', 'new_node'), ('openNode', 'open_node'), ('_lineChunkSize', '_line_chunksize'), # attr (private) ('_lineSeparator', '_line_separator'), # attr (private) # ('getLineSeparator', 'get_line_separator'), # dropped # ('setLineSeparator', 'set_line_separator'), # dropped # ('delLineSeparator', 'del_line_separator'), # dropped # ('lineSeparator', 'line_separator'), # property -- dropped ('_notReadableError', '_not_readable_error'), ('_appendZeros', '_append_zeros'), ('getAttrs', '_get_attrs'), ('setAttrs', '_set_attrs'), ('delAttrs', '_del_attrs'), ('_setAttributes', '_set_attributes'), ('_checkAttributes', '_check_attributes'), ('_checkNotClosed', '_check_not_closed'), ('__allowedInitKwArgs', '__allowed_init_kwargs'), # attr (private) ('_byteShape', '_byte_shape'), # attr (private) ('_sizeToShape', '_size_to_shape'), # attr (private) ('_vType', '_vtype'), # attr (private) ('_vShape', '_vshape'), # attr (private) # from path.py ('parentPath', 'parentpath'), # kwarg ('_pythonIdRE', '_python_id_re'), # attr (private) ('_reservedIdRE', '_reserved_id_re'), # attr (private) ('_hiddenNameRE', '_hidden_name_re'), # attr (private) ('_hiddenPathRE', '_hidden_path_re'), # attr (private) ('checkNameValidity', 'check_name_validity'), ('joinPath', 'join_path'), ('splitPath', 'split_path'), ('isVisibleName', 'isvisiblename'), ('isVisiblePath', 'isvisiblepath'), # from registry.py ('className', 'classname'), # kwarg ('classNameDict', 'class_name_dict'), # data ('classIdDict', 'class_id_dict'), # data ('getClassByName', 'get_class_by_name'), # from scripts/ptdump.py ('dumpLeaf', 'dump_leaf'), ('dumpGroup', 'dump_group'), # from scripts/ptrepack.py ('newdstGroup', 'newdst_group'), ('recreateIndexes', 'recreate_indexes'), ('copyLeaf', 'copy_leaf'), # from table.py # ('parentNode', 'parentnode'), # kwarg ('_nxTypeFromNPType', '_nxtype_from_nptype'), # data (private) ('_npSizeType', '_npsizetype'), # data (private) ('_indexNameOf', '_index_name_of'), ('_indexPathnameOf', '_index_pathname_of'), ('_indexPathnameOfColumn', '_index_pathname_of_column'), ('_indexNameOf_', '_index_name_of_'), ('_indexPathnameOf_', '_index_pathname_of_'), ('_indexPathnameOfColumn_', '_index_pathname_of_column_'), ('_table__setautoIndex', '_table__setautoindex'), ('_table__getautoIndex', '_table__getautoindex'), ('_table__autoIndex', '_table__autoindex'), # data (private) ('_table__whereIndexed', '_table__where_indexed'), ('createIndexesTable', 'create_indexes_table'), ('createIndexesDescr', 'create_indexes_descr'), ('_column__createIndex', '_column__create_index'), ('_autoIndex', '_autoindex'), # attr ('autoIndex', 'autoindex'), # attr ('_useIndex', '_use_index'), ('_whereCondition', '_where_condition'), # attr (private) ('_conditionCache', '_condition_cache'), # attr (private) # ('_exprvarsCache', '_exprvars_cache'), ('_enabledIndexingInQueries', '_enabled_indexing_in_queries'), # attr (private) ('_emptyArrayCache', '_empty_array_cache'), # attr (private) ('_getTypeColNames', '_get_type_col_names'), ('_getEnumMap', '_get_enum_map'), ('_cacheDescriptionData', '_cache_description_data'), ('_getColumnInstance', '_get_column_instance'), ('_checkColumn', '_check_column'), ('_disableIndexingInQueries', '_disable_indexing_in_queries'), ('_enableIndexingInQueries', '_enable_indexing_in_queries'), # ('_requiredExprVars', '_required_expr_vars'), ('_getConditionKey', '_get_condition_key'), ('_compileCondition', '_compile_condition'), ('willQueryUseIndexing', 'will_query_use_indexing'), ('readWhere', 'read_where'), ('whereAppend', 'append_where'), ('getWhereList', 'get_where_list'), ('_check_sortby_CSI', '_check_sortby_csi'), ('_readCoordinates', '_read_coordinates'), ('readCoordinates', 'read_coordinates'), ('_saveBufferedRows', '_save_buffered_rows'), ('modifyCoordinates', 'modify_coordinates'), ('modifyRows', 'modify_rows'), ('modifyColumn', 'modify_column'), ('modifyColumns', 'modify_columns'), ('flushRowsToIndex', 'flush_rows_to_index'), ('_addRowsToIndex', '_add_rows_to_index'), ('removeRows', 'remove_rows'), ('_setColumnIndexing', '_set_column_indexing'), ('_markColumnsAsDirty', '_mark_columns_as_dirty'), ('_reIndex', '_reindex'), ('_doReIndex', '_do_reindex'), ('reIndex', 'reindex'), ('reIndexDirty', 'reindex_dirty'), ('_g_copyRows', '_g_copy_rows'), ('_g_copyRows_optim', '_g_copy_rows_optim'), ('_g_propIndexes', '_g_prop_indexes'), ('_g_updateTableLocation', '_g_update_table_location'), ('_tableFile', '_table_file'), # attr (private) ('_tablePath', '_table_path'), # attr (private) ('createIndex', 'create_index'), ('createCSIndex', 'create_csindex'), ('removeIndex', 'remove_index'), # from tableextension ('tableExtension', 'tableextension'), ('getNestedFieldCache', 'get_nested_field_cache'), ('getNestedType', 'get_nested_type'), ('_createTable', '_create_table'), ('_getInfo', '_get_info'), ('indexChunk', 'indexchunk'), # attr ('indexValid', 'indexvalid'), # attr ('indexValues', 'indexvalues'), # attr ('bufcoordsData', 'bufcoords_data'), # attr ('indexValuesData', 'index_values_data'), # attr ('chunkmapData', 'chunkmap_data'), # attr ('indexValidData', 'index_valid_data'), # attr ('whereCond', 'wherecond'), # attr ('iterseqMaxElements', 'iterseq_max_elements'), # attr ('IObuf', 'iobuf'), # attr ('IObufcpy', 'iobufcpy'), # attr ('_convertTime64_', '_convert_time64_'), ('_convertTypes', '_convert_types'), ('_newBuffer', '_new_buffer'), ('__next__inKernel', '__next__inkernel'), ('_fillCol', '_fill_col'), ('_flushBufferedRows', '_flush_buffered_rows'), ('_getUnsavedNrows', '_get_unsaved_nrows'), ('_flushModRows', '_flush_mod_rows'), # from undoredo.py ('moveToShadow', 'move_to_shadow'), ('moveFromShadow', 'move_from_shadow'), ('undoCreate', 'undo_create'), ('redoCreate', 'redo_create'), ('undoRemove', 'undo_remove'), ('redoRemove', 'redo_remove'), ('undoMove', 'undo_move'), ('redoMove', 'redo_move'), ('attrToShadow', 'attr_to_shadow'), ('attrFromShadow', 'attr_from_shadow'), ('undoAddAttr', 'undo_add_attr'), ('redoAddAttr', 'redo_add_attr'), ('undoDelAttr', 'undo_del_attr'), ('redoDelAttr', 'redo_del_attr'), # from utils.py ('convertToNPAtom', 'convert_to_np_atom'), ('convertToNPAtom2', 'convert_to_np_atom2'), ('checkFileAccess', 'check_file_access'), ('logInstanceCreation', 'log_instance_creation'), ('fetchLoggedInstances', 'fetch_logged_instances'), ('countLoggedInstances', 'count_logged_instances'), ('listLoggedInstances', 'list_logged_instances'), ('dumpLoggedInstances', 'dump_logged_instances'), ('detectNumberOfCores', 'detect_number_of_cores'), # from utilsextension ('utilsExtension', 'utilsextension'), ('PTTypeToHDF5', 'pttype_to_hdf5'), # data ('PTSpecialKinds', 'pt_special_kinds'), # data ('NPExtPrefixesToPTKinds', 'npext_prefixes_to_ptkinds'), # data ('HDF5ClassToString', 'hdf5_class_to_string'), # data ('setBloscMaxThreads', 'set_blosc_max_threads'), ('silenceHDF5Messages', 'silence_hdf5_messages'), ('isHDF5File', 'is_hdf5_file'), ('isPyTablesFile', 'is_pytables_file'), ('getHDF5Version', 'get_hdf5_version'), ('getPyTablesVersion', 'get_pytables_version'), ('whichLibVersion', 'which_lib_version'), ('whichClass', 'which_class'), ('getNestedField', 'get_nested_field'), ('getFilters', 'get_filters'), ('getTypeEnum', 'get_type_enum'), ('enumFromHDF5', 'enum_from_hdf5'), ('enumToHDF5', 'enum_to_hdf5'), ('AtomToHDF5Type', 'atom_to_hdf5_type'), ('loadEnum', 'load_enum'), ('HDF5ToNPNestedType', 'hdf5_to_np_nested_type'), ('HDF5ToNPExtType', 'hdf5_to_np_ext_type'), ('AtomFromHDF5Type', 'atom_from_hdf5_type'), ('createNestedType', 'create_nested_type'), # from unimlemented.py ('_openUnImplemented', '_open_unimplemented'), # from vlarray.py # ('parentNode', 'parentnode'), # kwarg # ('expectedsizeinMB', 'expected_mb'), # --> expectedrows # ('_v_expectedsizeinMB', '_v_expected_mb'), # --> expectedrows ]) new2oldnames = {v: k for k, v in old2newnames.items()} # Note that it is tempting to use the ast module here, but then this # breaks transforming cython files. So instead we are going to do the # dumb thing with replace. def make_subs(ns): names = new2oldnames if ns.reverse else old2newnames s = r'(?<=\W)({})(?=\W)'.format('|'.join(list(names))) if ns.ignore_previous: s += r'(?!\s*?=\s*?previous_api(_property)?\()' s += r'(?!\* to \*\w+\*)' s += r'(?!\* parameter has been renamed into \*\w+\*\.)' s += r'(?! is pending deprecation, import \w+ instead\.)' subs = re.compile(s, flags=re.MULTILINE) def repl(m): return names.get(m.group(1), m.group(0)) return subs, repl def main(): desc = ('PyTables 2.x -> 3.x API transition tool\n\n' 'This tool displays to standard out, so it is \n' 'common to pipe this to another file:\n\n' '$ pt2to3 oldfile.py > newfile.py') parser = argparse.ArgumentParser(description=desc) parser.add_argument('-r', '--reverse', action='store_true', default=False, dest='reverse', help="reverts changes, going from 3.x -> 2.x.") parser.add_argument('-p', '--no-ignore-previous', action='store_false', default=True, dest='ignore_previous', help="ignores previous_api() calls.") parser.add_argument('-o', default=None, dest='output', help="output file to write to.") parser.add_argument('-i', '--inplace', action='store_true', default=False, dest='inplace', help="overwrites the file in-place.") parser.add_argument('filename', help='path to input file.') ns = parser.parse_args() if not Path(ns.filename).is_file(): sys.exit(f'file {ns.filename!r} not found') src = Path(ns.filename).read_text() subs, repl = make_subs(ns) targ = subs.sub(repl, src) ns.output = ns.filename if ns.inplace else ns.output if ns.output is None: sys.stdout.write(targ) else: Path(ns.output).write_text(targ) if __name__ == '__main__': main() PyTables-3.7.0/tables/scripts/ptdump.py000066400000000000000000000123531416254111300200720ustar00rootroot00000000000000"""This utility lets you look into the data and metadata of your data files. Pass the flag -h to this for help on usage. """ import argparse import operator import tables as tb # default options options = argparse.Namespace( rng=slice(None), showattrs=0, verbose=0, dump=0, colinfo=0, idxinfo=0, ) def dump_leaf(leaf): if options.verbose: print(repr(leaf)) else: print(str(leaf)) if options.showattrs: print(" "+repr(leaf.attrs)) if options.dump and not isinstance(leaf, tb.unimplemented.UnImplemented): print(" Data dump:") # print((leaf.read(options.rng.start, options.rng.stop, # options.rng.step)) # This is better for large objects if options.rng.start is None: start = 0 else: start = options.rng.start if options.rng.stop is None: if leaf.shape != (): stop = leaf.shape[0] else: stop = options.rng.stop if options.rng.step is None: step = 1 else: step = options.rng.step if leaf.shape == (): print("[SCALAR] %s" % (leaf[()])) else: for i in range(start, stop, step): print("[{}] {}".format(i, leaf[i])) if isinstance(leaf, tb.table.Table) and options.colinfo: # Show info of columns for colname in leaf.colnames: print(repr(leaf.cols._f_col(colname))) if isinstance(leaf, tb.table.Table) and options.idxinfo: # Show info of indexes for colname in leaf.colnames: col = leaf.cols._f_col(colname) if isinstance(col, tb.table.Column) and col.index is not None: idx = col.index print(repr(idx)) def dump_group(pgroup, sort=False): node_kinds = pgroup._v_file._node_kinds[1:] what = pgroup._f_walk_groups() if sort: what = sorted(what, key=operator.attrgetter('_v_pathname')) for group in what: print(str(group)) if options.showattrs: print(" "+repr(group._v_attrs)) for kind in node_kinds: for node in group._f_list_nodes(kind): if options.verbose or options.dump: dump_leaf(node) else: print(str(node)) def _get_parser(): parser = argparse.ArgumentParser( description='''The ptdump utility allows you look into the contents of your PyTables files. It lets you see not only the data but also the metadata (that is, the *structure* and additional information in the form of *attributes*).''') parser.add_argument( '-v', '--verbose', action='store_true', help='dump more metainformation on nodes', ) parser.add_argument( '-d', '--dump', action='store_true', help='dump data information on leaves', ) parser.add_argument( '-a', '--showattrs', action='store_true', help='show attributes in nodes (only useful when -v or -d are active)', ) parser.add_argument( '-s', '--sort', action='store_true', help='sort output by node name', ) parser.add_argument( '-c', '--colinfo', action='store_true', help='''show info of columns in tables (only useful when -v or -d are active)''', ) parser.add_argument( '-i', '--idxinfo', action='store_true', help='''show info of indexed columns (only useful when -v or -d are active)''', ) parser.add_argument( '-R', '--range', dest='rng', metavar='RANGE', help='''select a RANGE of rows (in the form "start,stop,step") during the copy of *all* the leaves. Default values are "None,None,1", which means a copy of all the rows.''', ) parser.add_argument('src', metavar='filename[:nodepath]', help='name of the HDF5 file to dump') return parser def main(): parser = _get_parser() args = parser.parse_args(namespace=options) # Get the options if isinstance(args.rng, str): try: options.rng = eval("slice(" + args.rng + ")") except Exception: parser.error("Error when getting the range parameter.") else: args.dump = 1 # Catch the files passed as the last arguments src = args.src.rsplit(':', 1) if len(src) == 1: filename, nodename = src[0], "/" else: filename, nodename = src if nodename == "": # case where filename == "filename:" instead of "filename:/" nodename = "/" try: h5file = tb.open_file(filename, 'r') except Exception as e: return 'Cannot open input file: ' + str(e) with h5file: # Check whether the specified node is a group or a leaf nodeobject = h5file.get_node(nodename) if isinstance(nodeobject, tb.group.Group): # Close the file again and reopen using the root_uep dump_group(nodeobject, args.sort) elif isinstance(nodeobject, tb.leaf.Leaf): # If it is not a Group, it must be a Leaf dump_leaf(nodeobject) else: # This should never happen print("Unrecognized object:", nodeobject) PyTables-3.7.0/tables/scripts/ptrepack.py000066400000000000000000000543771416254111300204060ustar00rootroot00000000000000"""This utility lets you repack your data files in a flexible way. Pass the flag -h to this for help on usage. """ import argparse import sys import warnings from pathlib import Path from time import perf_counter as clock from time import process_time as cpuclock import tables as tb # Global variables verbose = False regoldindexes = True createsysattrs = True numpy_aliases = [ 'numeric', 'Numeric', 'numarray', 'NumArray', 'CharArray', ] def newdst_group(dstfileh, dstgroup, title, filters): group = dstfileh.root # Now, create the new group. This works even if dstgroup == '/' for nodename in dstgroup.split('/'): if nodename == '': continue # First try if possible intermediate groups does already exist. try: group2 = dstfileh.get_node(group, nodename) except tb.exceptions.NoSuchNodeError: # The group does not exist. Create it. group2 = dstfileh.create_group(group, nodename, title=title, filters=filters) group = group2 return group def recreate_indexes(table, dstfileh, dsttable): listoldindexes = table._listoldindexes if listoldindexes != []: if not regoldindexes: if verbose: print("[I]Not regenerating indexes for table: '%s:%s'" % (dstfileh.filename, dsttable._v_pathname)) return # Now, recreate the indexed columns if verbose: print("[I]Regenerating indexes for table: '%s:%s'" % (dstfileh.filename, dsttable._v_pathname)) for colname in listoldindexes: if verbose: print("[I]Indexing column: '%s'. Please wait..." % colname) colobj = dsttable.cols._f_col(colname) # We don't specify the filters for the indexes colobj.create_index(filters=None) def copy_leaf(srcfile, dstfile, srcnode, dstnode, title, filters, copyuserattrs, overwritefile, overwrtnodes, stats, start, stop, step, chunkshape, sortby, check_CSI, propindexes, upgradeflavors, allow_padding): # Open the source file srcfileh = tb.open_file(srcfile, 'r', allow_padding=allow_padding) # Get the source node (that should exist) srcnode = srcfileh.get_node(srcnode) # Get the destination node and its parent last_slash = dstnode.rindex('/') if last_slash == len(dstnode)-1: # print("Detected a trailing slash in destination node. " # "Interpreting it as a destination group.") dstgroup = dstnode[:-1] elif last_slash > 0: dstgroup = dstnode[:last_slash] else: dstgroup = "/" dstleaf = dstnode[last_slash + 1:] if dstleaf == "": dstleaf = srcnode.name # Check whether the destination group exists or not if Path(dstfile).is_file() and not overwritefile: dstfileh = tb.open_file(dstfile, 'a', pytables_sys_attrs=createsysattrs, allow_padding=allow_padding) try: dstgroup = dstfileh.get_node(dstgroup) except Exception: # The dstgroup does not seem to exist. Try creating it. dstgroup = newdst_group(dstfileh, dstgroup, title, filters) else: # The node exists, but it is really a group? if not isinstance(dstgroup, tb.group.Group): # No. Should we overwrite it? if overwrtnodes: parent = dstgroup._v_parent last_slash = dstgroup._v_pathname.rindex('/') dstgroupname = dstgroup._v_pathname[last_slash + 1:] dstgroup.remove() dstgroup = dstfileh.create_group(parent, dstgroupname, title=title, filters=filters) else: raise RuntimeError("Please check that the node names are " "not duplicated in destination, and " "if so, add the --overwrite-nodes " "flag if desired.") else: # The destination file does not exist or will be overwritten. dstfileh = tb.open_file(dstfile, 'w', title=title, filters=filters, pytables_sys_attrs=createsysattrs, allow_padding=allow_padding) dstgroup = newdst_group(dstfileh, dstgroup, title="", filters=filters) # Finally, copy srcnode to dstnode try: dstnode = srcnode.copy( dstgroup, dstleaf, filters=filters, copyuserattrs=copyuserattrs, overwrite=overwrtnodes, stats=stats, start=start, stop=stop, step=step, chunkshape=chunkshape, sortby=sortby, check_CSI=check_CSI, propindexes=propindexes) except Exception: (type_, value, traceback) = sys.exc_info() print("Problems doing the copy from '%s:%s' to '%s:%s'" % (srcfile, srcnode, dstfile, dstnode)) print(f"The error was --> {type_}: {value}") print("The destination file looks like:\n", dstfileh) # Close all the open files: srcfileh.close() dstfileh.close() raise RuntimeError("Please check that the node names are not " "duplicated in destination, and if so, add " "the --overwrite-nodes flag if desired.") # Upgrade flavors in dstnode, if required if upgradeflavors: if srcfileh.format_version.startswith("1"): # Remove original flavor in case the source file has 1.x format dstnode.del_attr('FLAVOR') elif srcfileh.format_version < "2.1": if dstnode.get_attr('FLAVOR') in numpy_aliases: dstnode.set_attr('FLAVOR', tb.flavor.internal_flavor) # Recreate possible old indexes in destination node if srcnode._c_classid == "TABLE": recreate_indexes(srcnode, dstfileh, dstnode) # Close all the open files: srcfileh.close() dstfileh.close() def copy_children(srcfile, dstfile, srcgroup, dstgroup, title, recursive, filters, copyuserattrs, overwritefile, overwrtnodes, stats, start, stop, step, chunkshape, sortby, check_CSI, propindexes, upgradeflavors, allow_padding, use_hardlinks=True): """Copy the children from source group to destination group""" # Open the source file with srcgroup as root_uep srcfileh = tb.open_file(srcfile, 'r', root_uep=srcgroup, allow_padding=allow_padding) # Assign the root to srcgroup srcgroup = srcfileh.root created_dstgroup = False # Check whether the destination group exists or not if Path(dstfile).is_file() and not overwritefile: dstfileh = tb.open_file(dstfile, 'a', pytables_sys_attrs=createsysattrs, allow_padding=allow_padding) try: dstgroup = dstfileh.get_node(dstgroup) except tb.exceptions.NoSuchNodeError: # The dstgroup does not seem to exist. Try creating it. dstgroup = newdst_group(dstfileh, dstgroup, title, filters) created_dstgroup = True else: # The node exists, but it is really a group? if not isinstance(dstgroup, tb.group.Group): # No. Should we overwrite it? if overwrtnodes: parent = dstgroup._v_parent last_slash = dstgroup._v_pathname.rindex('/') dstgroupname = dstgroup._v_pathname[last_slash + 1:] dstgroup.remove() dstgroup = dstfileh.create_group(parent, dstgroupname, title=title, filters=filters) else: raise RuntimeError("Please check that the node names are " "not duplicated in destination, and " "if so, add the --overwrite-nodes " "flag if desired.") else: # The destination file does not exist or will be overwritten. dstfileh = tb.open_file(dstfile, 'w', title=title, filters=filters, pytables_sys_attrs=createsysattrs, allow_padding=allow_padding) dstgroup = newdst_group(dstfileh, dstgroup, title="", filters=filters) created_dstgroup = True # Copy the attributes to dstgroup, if needed if created_dstgroup and copyuserattrs: srcgroup._v_attrs._f_copy(dstgroup) # Finally, copy srcgroup children to dstgroup try: srcgroup._f_copy_children( dstgroup, recursive=recursive, filters=filters, copyuserattrs=copyuserattrs, overwrite=overwrtnodes, stats=stats, start=start, stop=stop, step=step, chunkshape=chunkshape, sortby=sortby, check_CSI=check_CSI, propindexes=propindexes, use_hardlinks=use_hardlinks) except Exception: (type_, value, traceback) = sys.exc_info() print("Problems doing the copy from '%s:%s' to '%s:%s'" % (srcfile, srcgroup, dstfile, dstgroup)) print(f"The error was --> {type_}: {value}") print("The destination file looks like:\n", dstfileh) # Close all the open files: srcfileh.close() dstfileh.close() raise RuntimeError("Please check that the node names are not " "duplicated in destination, and if so, add the " "--overwrite-nodes flag if desired. In " "particular, pay attention that root_uep is not " "fooling you.") # Upgrade flavors in dstnode, if required if upgradeflavors: for dstnode in dstgroup._f_walknodes("Leaf"): if srcfileh.format_version.startswith("1"): # Remove original flavor in case the source file has 1.x format dstnode.del_attr('FLAVOR') elif srcfileh.format_version < "2.1": if dstnode.get_attr('FLAVOR') in numpy_aliases: dstnode.set_attr('FLAVOR', tb.flavor.internal_flavor) # Convert the remaining tables with old indexes (if any) for table in srcgroup._f_walknodes("Table"): dsttable = dstfileh.get_node(dstgroup, table._v_pathname) recreate_indexes(table, dstfileh, dsttable) # Close all the open files: srcfileh.close() dstfileh.close() def _get_parser(): parser = argparse.ArgumentParser( description='''This utility is very powerful and lets you copy any leaf, group or complete subtree into another file. During the copy process you are allowed to change the filter properties if you want so. Also, in the case of duplicated pathnames, you can decide if you want to overwrite already existing nodes on the destination file. Generally speaking, ptrepack can be useful in may situations, like replicating a subtree in another file, change the filters in objects and see how affect this to the compression degree or I/O performance, consolidating specific data in repositories or even *importing* generic HDF5 files and create true PyTables counterparts.''') parser.add_argument( '-v', '--verbose', action='store_true', help='show verbose information', ) parser.add_argument( '-o', '--overwrite', action='store_true', dest='overwritefile', help='overwrite destination file', ) parser.add_argument( '-R', '--range', dest='rng', metavar='RANGE', help='''select a RANGE of rows (in the form "start,stop,step") during the copy of *all* the leaves. Default values are "None,None,1", which means a copy of all the rows.''', ) parser.add_argument( '--non-recursive', action='store_false', default=True, dest='recursive', help='do not do a recursive copy. Default is to do it', ) parser.add_argument( '--dest-title', dest='title', default='', help='title for the new file (if not specified, the source is copied)', ) parser.add_argument( '--dont-create-sysattrs', action='store_false', default=True, dest='createsysattrs', help='do not create sys attrs (default is to do it)', ) parser.add_argument( '--dont-copy-userattrs', action='store_false', default=True, dest='copyuserattrs', help='do not copy the user attrs (default is to do it)', ) parser.add_argument( '--overwrite-nodes', action='store_true', dest='overwrtnodes', help='''overwrite destination nodes if they exist. Default is to not overwrite them''', ) parser.add_argument( '--complevel', type=int, default=0, help='''set a compression level (0 for no compression, which is the default)''', ) parser.add_argument( '--complib', choices=( "zlib", "lzo", "bzip2", "blosc", "blosc:blosclz", "blosc:lz4", "blosc:lz4hc", "blosc:snappy", "blosc:zlib", "blosc:zstd"), default='zlib', help='''set the compression library to be used during the copy. Defaults to %(default)s''', ) parser.add_argument( '--shuffle', type=int, choices=(0, 1), help='''activate or not the shuffle filter (default is active if complevel > 0)''', ) parser.add_argument( '--bitshuffle', type=int, choices=(0, 1), help='activate or not the bitshuffle filter (not active by default)', ) parser.add_argument( '--fletcher32', type=int, choices=(0, 1), help='''whether to activate or not the fletcher32 filter (not active by default)''', ) parser.add_argument( '--keep-source-filters', action='store_true', dest='keepfilters', help='''use the original filters in source files. The default is not doing that if any of --complevel, --complib, --shuffle --bitshuffle or --fletcher32 option is specified''', ) parser.add_argument( '--chunkshape', default='keep', help='''set a chunkshape. Possible options are: "keep" | "auto" | int | tuple. A value of "auto" computes a sensible value for the chunkshape of the leaves copied. The default is to "keep" the original value''', ) parser.add_argument( '--upgrade-flavors', action='store_true', dest='upgradeflavors', help='''when repacking PyTables 1.x or PyTables 2.x files, the flavor of leaves will be unset. With this, such a leaves will be serialized as objects with the internal flavor ('numpy' for 3.x series)''', ) parser.add_argument( '--dont-regenerate-old-indexes', action='store_false', default=True, dest='regoldindexes', help='''disable regenerating old indexes. The default is to regenerate old indexes as they are found''', ) parser.add_argument( '--sortby', metavar='COLUMN', help='''do a table copy sorted by the index in "column". For reversing the order, use a negative value in the "step" part of "RANGE" (see "-r" flag). Only applies to table objects''', ) parser.add_argument( '--checkCSI', action='store_true', help='force the check for a CSI index for the --sortby column', ) parser.add_argument( '--propindexes', action='store_true', help='''propagate the indexes existing in original tables. The default is to not propagate them. Only applies to table objects''', ) parser.add_argument( '--dont-allow-padding', action='store_true', dest="dont_allow_padding", help='''remove the possible padding in compound types in source files. The default is to propagate it. Only applies to table objects''', ) parser.add_argument( 'src', metavar='sourcefile:sourcegroup', help='source file/group', ) parser.add_argument( 'dst', metavar='destfile:destgroup', help='destination file/group', ) return parser def main(): global verbose global regoldindexes global createsysattrs parser = _get_parser() args = parser.parse_args() # check arguments if args.rng: try: args.rng = eval("slice(" + args.rng + ")") except Exception: parser.error("Error when getting the range parameter.") if args.chunkshape.isdigit() or args.chunkshape.startswith('('): args.chunkshape = eval(args.chunkshape) if args.complevel < 0 or args.complevel > 9: parser.error( 'invalid "complevel" value, it sould be in te range [0, 9]' ) # Catch the files passed as the last arguments src = args.src.rsplit(':', 1) dst = args.dst.rsplit(':', 1) if len(src) == 1: srcfile, srcnode = src[0], "/" else: srcfile, srcnode = src if len(dst) == 1: dstfile, dstnode = dst[0], "/" else: dstfile, dstnode = dst if srcnode == "": # case where filename == "filename:" instead of "filename:/" srcnode = "/" if dstnode == "": # case where filename == "filename:" instead of "filename:/" dstnode = "/" # Ignore the warnings for tables that contains oldindexes # (these will be handled by the copying routines) warnings.filterwarnings("ignore", category=tb.exceptions.OldIndexWarning) # Ignore the flavors warnings during upgrading flavor operations if args.upgradeflavors: warnings.filterwarnings("ignore", category=tb.exceptions.FlavorWarning) # Build the Filters instance filter_params = ( args.complevel, args.complib, args.shuffle, args.bitshuffle, args.fletcher32, ) if (filter_params == (None,) * 4 or args.keepfilters): filters = None else: if args.complevel is None: args.complevel = 0 if args.shuffle is None: if args.complevel > 0: args.shuffle = True else: args.shuffle = False if args.bitshuffle is None: args.bitshuffle = False if args.bitshuffle: # Shuffle and bitshuffle are mutually exclusive args.shuffle = False if args.complib is None: args.complib = "zlib" if args.fletcher32 is None: args.fletcher32 = False filters = tb.leaf.Filters(complevel=args.complevel, complib=args.complib, shuffle=args.shuffle, bitshuffle=args.bitshuffle, fletcher32=args.fletcher32) # The start, stop and step params: start, stop, step = None, None, 1 # Defaults if args.rng: start, stop, step = args.rng.start, args.rng.stop, args.rng.step # Set globals verbose = args.verbose regoldindexes = args.regoldindexes createsysattrs = args.createsysattrs # Some timing t1 = clock() cpu1 = cpuclock() # Copy the file if verbose: print("+=+" * 20) print("Recursive copy:", args.recursive) print("Applying filters:", filters) if args.sortby is not None: print("Sorting table(s) by column:", args.sortby) print("Forcing a CSI creation:", args.checkCSI) if args.propindexes: print("Recreating indexes in copied table(s)") print(f"Start copying {srcfile}:{srcnode} to {dstfile}:{dstnode}") print("+=+" * 20) allow_padding = not args.dont_allow_padding # Check whether the specified source node is a group or a leaf h5srcfile = tb.open_file(srcfile, 'r', allow_padding=allow_padding) srcnodeobject = h5srcfile.get_node(srcnode) # Close the file again h5srcfile.close() stats = {'groups': 0, 'leaves': 0, 'links': 0, 'bytes': 0, 'hardlinks': 0} if isinstance(srcnodeobject, tb.group.Group): copy_children( srcfile, dstfile, srcnode, dstnode, title=args.title, recursive=args.recursive, filters=filters, copyuserattrs=args.copyuserattrs, overwritefile=args.overwritefile, overwrtnodes=args.overwrtnodes, stats=stats, start=start, stop=stop, step=step, chunkshape=args.chunkshape, sortby=args.sortby, check_CSI=args.checkCSI, propindexes=args.propindexes, upgradeflavors=args.upgradeflavors, allow_padding=allow_padding, use_hardlinks=True) else: # If not a Group, it should be a Leaf copy_leaf( srcfile, dstfile, srcnode, dstnode, title=args.title, filters=filters, copyuserattrs=args.copyuserattrs, overwritefile=args.overwritefile, overwrtnodes=args.overwrtnodes, stats=stats, start=start, stop=stop, step=step, chunkshape=args.chunkshape, sortby=args.sortby, check_CSI=args.checkCSI, propindexes=args.propindexes, upgradeflavors=args.upgradeflavors, allow_padding=allow_padding, ) # Gather some statistics t2 = clock() cpu2 = cpuclock() tcopy = t2 - t1 cpucopy = cpu2 - cpu1 if verbose: ngroups = stats['groups'] nleaves = stats['leaves'] nlinks = stats['links'] nhardlinks = stats['hardlinks'] nbytescopied = stats['bytes'] nnodes = ngroups + nleaves + nlinks + nhardlinks print( "Groups copied:", ngroups, ", Leaves copied:", nleaves, ", Links copied:", nlinks, ", Hard links copied:", nhardlinks, ) if args.copyuserattrs: print("User attrs copied") else: print("User attrs not copied") print(f"KBytes copied: {nbytescopied / 1024:.3f}") print( f"Time copying: {tcopy:.3f} s (real) {cpucopy:.3f} s " f"(cpu) {cpucopy / tcopy:.0%}") print(f"Copied nodes/sec: {nnodes / tcopy:.1f}") print(f"Copied KB/s : {nbytescopied / tcopy / 1024:.0f}") PyTables-3.7.0/tables/scripts/pttree.py000066400000000000000000000356761416254111300201010ustar00rootroot00000000000000"""This utility prints the contents of an HDF5 file as a tree. Pass the flag -h to this for help on usage. """ import os import argparse from collections import defaultdict, deque import warnings from pathlib import Path import numpy as np import tables as tb def _get_parser(): parser = argparse.ArgumentParser( description=''' `pttree` is designed to give a quick overview of the contents of a PyTables HDF5 file by printing a depth-indented list of nodes, similar to the output of the Unix `tree` function. It can also display the size, shape and compression states of individual nodes, as well as summary information for the whole file. For a more verbose output (including metadata), see `ptdump`. ''') parser.add_argument( '-L', '--max-level', type=int, dest='max_depth', help='maximum branch depth of tree to display (-1 == no limit)', ) parser.add_argument( '-S', '--sort-by', type=str, dest='sort_by', help='artificially order nodes, can be either "size", "name" or "none"' ) parser.add_argument( '--print-size', action='store_true', dest='print_size', help='print size of each node/branch', ) parser.add_argument( '--no-print-size', action='store_false', dest='print_size', ) parser.add_argument( '--print-shape', action='store_true', dest='print_shape', help='print shape of each node', ) parser.add_argument( '--no-print-shape', action='store_false', dest='print_shape', ) parser.add_argument( '--print-compression', action='store_true', dest='print_compression', help='print compression library(level) for each compressed node', ) parser.add_argument( '--no-print-compression', action='store_false', dest='print_compression', ) parser.add_argument( '--print-percent', action='store_true', dest='print_percent', help='print size of each node as a %% of the total tree size on disk', ) parser.add_argument( '--no-print-percent', action='store_false', dest='print_percent', ) parser.add_argument( '--use-si-units', action='store_true', dest='use_si_units', help='report sizes in SI units (1 MB == 10^6 B)', ) parser.add_argument( '--use-binary-units', action='store_false', dest='use_si_units', help='report sizes in binary units (1 MiB == 2^20 B)', ) parser.add_argument('src', metavar='filename[:nodepath]', help='path to the root of the tree structure') parser.set_defaults(max_depth=1, sort_by="size", print_size=True, print_percent=True, print_shape=False, print_compression=False, use_si_units=False) return parser def main(): parser = _get_parser() args = parser.parse_args() # Catch the files passed as the last arguments src = args.__dict__.pop('src').rsplit(':', 1) if len(src) == 1: filename, nodename = src[0], "/" else: filename, nodename = src if nodename == "": # case where filename == "filename:" instead of "filename:/" nodename = "/" with tb.open_file(filename, 'r') as f: tree_str = get_tree_str(f, nodename, **args.__dict__) print(tree_str) pass def get_tree_str(f, where='/', max_depth=-1, print_class=True, print_size=True, print_percent=True, print_shape=False, print_compression=False, print_total=True, sort_by=None, use_si_units=False): """ Generate the ASCII string representing the tree structure, and the summary info (if requested) """ root = f.get_node(where) root._g_check_open() start_depth = root._v_depth if max_depth < 0: max_depth = os.sys.maxint b2h = bytes2human(use_si_units) # we will pass over each node in the tree twice # on the first pass we'll start at the root node and recurse down the # branches, finding all of the leaf nodes and calculating the total size # over all tables and arrays total_in_mem = 0 total_on_disk = 0 total_items = 0 # defaultdicts for holding the cumulative branch sizes at each node in_mem = defaultdict(lambda: 0) on_disk = defaultdict(lambda: 0) leaf_count = defaultdict(lambda: 0) # keep track of node addresses within the HDF5 file so that we don't count # nodes with multiple references (i.e. hardlinks) multiple times ref_count = defaultdict(lambda: 0) ref_idx = defaultdict(lambda: 0) hl_addresses = defaultdict(lambda: None) hl_targets = defaultdict(lambda: '') stack = deque(root) leaves = deque() while stack: node = stack.pop() if isinstance(node, tb.link.Link): # we treat links like leaves, except we don't dereference them to # get their sizes or addresses leaves.append(node) continue path = node._v_pathname addr, rc = node._get_obj_info() ref_count[addr] += 1 ref_idx[path] = ref_count[addr] hl_addresses[path] = addr if isinstance(node, tb.UnImplemented): leaves.append(node) elif isinstance(node, tb.Leaf): # only count the size of a hardlinked leaf the first time it is # visited if ref_count[addr] == 1: try: m = node.size_in_memory d = node.size_on_disk # size of this node in_mem[path] += m on_disk[path] += d leaf_count[path] += 1 # total over all nodes total_in_mem += m total_on_disk += d total_items += 1 # arbitrarily treat this node as the 'target' for all other # hardlinks that point to the same address hl_targets[addr] = path except NotImplementedError as e: # size_on_disk is not implemented for VLArrays warnings.warn(str(e)) # push leaf nodes onto the stack for the next pass leaves.append(node) elif isinstance(node, tb.Group): # don't recurse down the same hardlinked branch multiple times! if ref_count[addr] == 1: stack.extend(list(node._v_children.values())) hl_targets[addr] = path # if we've already visited this group's address, treat it as a leaf # instead else: leaves.append(node) # on the second pass we start at each leaf and work upwards towards the # root node, computing the cumulative size of each branch at each node, and # instantiating a PrettyTree object for each node to create an ASCII # representation of the tree structure # this will store the PrettyTree objects for every node we're printing pretty = {} stack = leaves while stack: node = stack.pop() path = node._v_pathname parent = node._v_parent parent_path = parent._v_pathname # cumulative size at parent node in_mem[parent_path] += in_mem[path] on_disk[parent_path] += on_disk[path] leaf_count[parent_path] += leaf_count[path] depth = node._v_depth - start_depth # if we're deeper than the max recursion depth, we print nothing if not depth > max_depth: # create a PrettyTree representation of this node name = node._v_name if print_class: name += " (%s)" % node.__class__.__name__ labels = [] ratio = on_disk[path] / total_on_disk # if the address of this object has a ref_count > 1, it has # multiple hardlinks if ref_count[hl_addresses[path]] > 1: name += ', addr=%i, ref=%i/%i' % ( hl_addresses[path], ref_idx[path], ref_count[hl_addresses[path]] ) if isinstance(node, tb.link.Link): labels.append('softlink --> %s' % node.target) elif ref_idx[path] > 1: labels.append('hardlink --> %s' % hl_targets[hl_addresses[path]]) elif isinstance(node, (tb.Array, tb.Table)): if print_size: sizestr = 'mem={}, disk={}'.format( b2h(in_mem[path]), b2h(on_disk[path])) if print_percent: sizestr += f' [{ratio:5.1%}]' labels.append(sizestr) if print_shape: labels.append('shape=%s' % repr(node.shape)) if print_compression: lib = node.filters.complib level = node.filters.complevel if level: compstr = '%s(%i)' % (lib, level) else: compstr = 'None' labels.append('compression=%s' % compstr) # if we're at our max recursion depth, we'll print summary # information for this branch elif depth == max_depth: itemstr = '... %i leaves' % leaf_count[path] if print_size: itemstr += ', mem={}, disk={}'.format( b2h(in_mem[path]), b2h(on_disk[path])) if print_percent: itemstr += f' [{ratio:5.1%}]' labels.append(itemstr) # create a PrettyTree for this node, if one doesn't exist already if path not in pretty: pretty.update({path: PrettyTree()}) pretty[path].name = name pretty[path].labels = labels if sort_by == 'size': # descending size order pretty[path].sort_by = -ratio elif sort_by == 'name': pretty[path].sort_by = node._v_name else: # natural order if path == '/': # root is not in root._v_children pretty[path].sort_by = 0 else: pretty[path].sort_by = list(parent._v_children.values( )).index(node) # exclude root node or we'll get infinite recursions (since '/' is # the parent of '/') if path != '/': # create a PrettyTree for the parent of this node, if one # doesn't exist already if parent_path not in pretty: pretty.update({parent_path: PrettyTree()}) # make this PrettyTree a child of the parent PrettyTree pretty[parent_path].add_child(pretty[path]) if node is not root and parent not in stack: # we append to the 'bottom' of the stack, so that we exhaust all of # the nodes at this level before going up a level in the tree stack.appendleft(parent) out_str = '\n' + '-' * 60 + '\n' * 2 out_str += str(pretty[root._v_pathname]) + '\n' * 2 if print_total: avg_ratio = total_on_disk / total_in_mem fsize = Path(f.filename).stat().st_size out_str += '-' * 60 + '\n' out_str += 'Total branch leaves: %i\n' % total_items out_str += 'Total branch size: {} in memory, {} on disk\n'.format( b2h(total_in_mem), b2h(total_on_disk)) out_str += 'Mean compression ratio: %.2f\n' % avg_ratio out_str += 'HDF5 file size: %s\n' % b2h(fsize) out_str += '-' * 60 + '\n' return out_str class PrettyTree: """ A pretty ASCII representation of a recursive tree structure. Each node can have multiple labels, given as a list of strings. Example: -------- A = PrettyTree('A', labels=['wow']) B = PrettyTree('B', labels=['such tree']) C = PrettyTree('C', children=[A, B]) D = PrettyTree('D', labels=['so recursive']) root = PrettyTree('root', labels=['many nodes'], children=[C, D]) print root Credit to Andrew Cooke's blog: """ def __init__(self, name=None, children=None, labels=None, sort_by=None): # NB: do NOT assign default list/dict arguments in the function # declaration itself - these objects are shared between ALL instances # of PrettyTree, and by assigning to them it's easy to get into # infinite recursions, e.g. when 'self in self.children == True' if children is None: children = [] if labels is None: labels = [] self.name = name self.children = children self.labels = labels self.sort_by = sort_by def add_child(self, child): # some basic checks to help to avoid infinite recursion assert child is not self assert self not in child.children if child not in self.children: self.children.append(child) def tree_lines(self): yield self.name for label in self.labels: yield ' ' + label children = sorted(self.children, key=(lambda c: c.sort_by)) last = children[-1] if children else None for child in children: prefix = '`--' if child is last else '+--' for line in child.tree_lines(): yield prefix + line prefix = ' ' if child is last else '| ' def __str__(self): return "\n".join(self.tree_lines()) def __repr__(self): return f'<{self.__class__.__name__} at 0x{id(self):x}>' def bytes2human(use_si_units=False): if use_si_units: prefixes = 'TB', 'GB', 'MB', 'kB', 'B' values = 1E12, 1E9, 1E6, 1E3, 1 else: prefixes = 'TiB', 'GiB', 'MiB', 'KiB', 'B' values = 2 ** 40, 2 ** 30, 2 ** 20, 2 ** 10, 1 def b2h(nbytes): for (prefix, value) in zip(prefixes, values): scaled = nbytes / value if scaled >= 1: break return f"{scaled:.1f}{prefix}" return b2h def make_test_file(prefix='/tmp'): f = tb.open_file(str(Path(prefix) / 'test_pttree.hdf5'), 'w') g1 = f.create_group('/', 'group1') g1a = f.create_group(g1, 'group1a') g1b = f.create_group(g1, 'group1b') filters = tb.Filters(complevel=5, complib='bzip2') for gg in g1a, g1b: f.create_carray(gg, 'zeros128b', obj=np.zeros(32, dtype=np.float64), filters=filters) f.create_carray(gg, 'random128b', obj=np.random.rand(32), filters=filters) g2 = f.create_group('/', 'group2') f.create_soft_link(g2, 'softlink_g1_z128', '/group1/group1a/zeros128b') f.create_hard_link(g2, 'hardlink_g1a_z128', '/group1/group1a/zeros128b') f.create_hard_link(g2, 'hardlink_g1a', '/group1/group1a') return f PyTables-3.7.0/tables/table.py000066400000000000000000004302451416254111300161650ustar00rootroot00000000000000"""Here is defined the Table class.""" import functools import math import operator import sys import warnings from pathlib import Path from time import perf_counter as clock import numexpr as ne import numpy as np from . import tableextension from .lrucacheextension import ObjectCache, NumCache from .atom import Atom from .conditions import compile_condition from .flavor import flavor_of, array_as_internal, internal_to_flavor from .utils import is_idx, lazyattr, SizeType, NailedDict as CacheDict from .leaf import Leaf from .description import (IsDescription, Description, Col, descr_from_dtype) from .exceptions import ( NodeError, HDF5ExtError, PerformanceWarning, OldIndexWarning, NoSuchNodeError) from .utilsextension import get_nested_field from .path import join_path, split_path from .index import ( OldIndex, default_index_filters, default_auto_index, Index, IndexesDescG, IndexesTableG) profile = False # profile = True # Uncomment for profiling if profile: from .utils import show_stats # 2.2: Added support for complex types. Introduced in version 0.9. # 2.2.1: Added suport for time types. # 2.3: Changed the indexes naming schema. # 2.4: Changed indexes naming schema (again). # 2.5: Added the FIELD_%d_FILL attributes. # 2.6: Added the FLAVOR attribute (optional). # 2.7: Numeric and numarray flavors are gone. obversion = "2.7" # The Table VERSION number # Maps NumPy types to the types used by Numexpr. _nxtype_from_nptype = { np.bool_: bool, np.int8: ne.necompiler.int_, np.int16: ne.necompiler.int_, np.int32: ne.necompiler.int_, np.int64: ne.necompiler.long_, np.uint8: ne.necompiler.int_, np.uint16: ne.necompiler.int_, np.uint32: ne.necompiler.long_, np.uint64: ne.necompiler.long_, np.float32: float, np.float64: ne.necompiler.double, np.complex64: complex, np.complex128: complex, np.bytes_: bytes, } _nxtype_from_nptype[np.str_] = str if hasattr(np, 'float16'): _nxtype_from_nptype[np.float16] = float # XXX: check if hasattr(np, 'float96'): _nxtype_from_nptype[np.float96] = ne.necompiler.double # XXX: check if hasattr(np, 'float128'): _nxtype_from_nptype[np.float128] = ne.necompiler.double # XXX: check if hasattr(np, 'complex192'): _nxtype_from_nptype[np.complex192] = complex # XXX: check if hasattr(np, 'complex256'): _nxtype_from_nptype[np.complex256] = complex # XXX: check # The NumPy scalar type corresponding to `SizeType`. _npsizetype = np.array(SizeType(0)).dtype.type def _index_name_of(node): return '_i_%s' % node._v_name def _index_pathname_of(node): nodeParentPath = split_path(node._v_pathname)[0] return join_path(nodeParentPath, _index_name_of(node)) def _index_pathname_of_column(table, colpathname): return join_path(_index_pathname_of(table), colpathname) # The next are versions that work with just paths (i.e. we don't need # a node instance for using them, which can be critical in certain # situations) def _index_name_of_(nodeName): return '_i_%s' % nodeName def _index_pathname_of_(nodePath): nodeParentPath, nodeName = split_path(nodePath) return join_path(nodeParentPath, _index_name_of_(nodeName)) def _index_pathname_of_column_(tablePath, colpathname): return join_path(_index_pathname_of_(tablePath), colpathname) def restorecache(self): # Define a cache for sparse table reads params = self._v_file.params chunksize = self._v_chunkshape[0] nslots = params['TABLE_MAX_SIZE'] / (chunksize * self._v_dtype.itemsize) self._chunkcache = NumCache((nslots, chunksize), self._v_dtype, 'table chunk cache') self._seqcache = ObjectCache(params['ITERSEQ_MAX_SLOTS'], params['ITERSEQ_MAX_SIZE'], 'Iter sequence cache') self._dirtycache = False def _table__where_indexed(self, compiled, condition, condvars, start, stop, step): if profile: tref = clock() if profile: show_stats("Entering table_whereIndexed", tref) self._use_index = True # Clean the table caches for indexed queries if needed if self._dirtycache: restorecache(self) # Get the values in expression that are not columns values = [] for key, value in condvars.items(): if isinstance(value, np.ndarray): values.append((key, value.item())) # Build a key for the sequence cache seqkey = (condition, tuple(values), (start, stop, step)) # Do a lookup in sequential cache for this query nslot = self._seqcache.getslot(seqkey) if nslot >= 0: # Get the row sequence from the cache seq = self._seqcache.getitem(nslot) if len(seq) == 0: return iter([]) # seq is a list. seq = np.array(seq, dtype='int64') # Correct the ranges in cached sequence if (start, stop, step) != (0, self.nrows, 1): seq = seq[(seq >= start) & ( seq < stop) & ((seq - start) % step == 0)] return self.itersequence(seq) else: # No luck. self._seqcache will be populated # in the iterator if possible. (Row._finish_riterator) self._seqcache_key = seqkey # Compute the chunkmap for every index in indexed expression idxexprs = compiled.index_expressions strexpr = compiled.string_expression cmvars = {} tcoords = 0 for i, idxexpr in enumerate(idxexprs): var, ops, lims = idxexpr col = condvars[var] index = col.index assert index is not None, "the chosen column is not indexed" assert not index.dirty, "the chosen column has a dirty index" # Get the number of rows that the indexed condition yields. range_ = index.get_lookup_range(ops, lims) ncoords = index.search(range_) tcoords += ncoords if index.reduction == 1 and ncoords == 0: # No values from index condition, thus the chunkmap should be empty nrowsinchunk = self.chunkshape[0] nchunks = math.ceil(self.nrows / nrowsinchunk) chunkmap = np.zeros(shape=nchunks, dtype="bool") else: # Get the chunkmap from the index chunkmap = index.get_chunkmap() # Assign the chunkmap to the cmvars dictionary cmvars["e%d" % i] = chunkmap if index.reduction == 1 and tcoords == 0: # No candidates found in any indexed expression component, so leave now self._seqcache.setitem(seqkey, [], 1) return iter([]) # Compute the final chunkmap chunkmap = ne.evaluate(strexpr, cmvars) if not chunkmap.any(): # The chunkmap is all False, so the result is empty self._seqcache.setitem(seqkey, [], 1) return iter([]) if profile: show_stats("Exiting table_whereIndexed", tref) return chunkmap def create_indexes_table(table): itgroup = IndexesTableG( table._v_parent, _index_name_of(table), "Indexes container for table " + table._v_pathname, new=True) return itgroup def create_indexes_descr(igroup, dname, iname, filters): idgroup = IndexesDescG( igroup, iname, "Indexes container for sub-description " + dname, filters=filters, new=True) return idgroup def _column__create_index(self, optlevel, kind, filters, tmp_dir, blocksizes, verbose): name = self.name table = self.table dtype = self.dtype descr = self.descr index = self.index get_node = table._v_file._get_node # Warn if the index already exists if index: raise ValueError("%s for column '%s' already exists. If you want to " "re-create it, please, try with reindex() method " "better" % (str(index), str(self.pathname))) # Check that the datatype is indexable. if dtype.str[1:] == 'u8': raise NotImplementedError( "indexing 64-bit unsigned integer columns " "is not supported yet, sorry") if dtype.kind == 'c': raise TypeError("complex columns can not be indexed") if dtype.shape != (): raise TypeError("multidimensional columns can not be indexed") # Get the indexes group for table, and if not exists, create it try: itgroup = get_node(_index_pathname_of(table)) except NoSuchNodeError: itgroup = create_indexes_table(table) # Create the necessary intermediate groups for descriptors idgroup = itgroup dname = "" pathname = descr._v_pathname if pathname != '': inames = pathname.split('/') for iname in inames: if dname == '': dname = iname else: dname += '/' + iname try: idgroup = get_node(f'{itgroup._v_pathname}/{dname}') except NoSuchNodeError: idgroup = create_indexes_descr(idgroup, dname, iname, filters) # Create the atom assert dtype.shape == () atom = Atom.from_dtype(np.dtype((dtype, (0,)))) # Protection on tables larger than the expected rows (perhaps the # user forgot to pass this parameter to the Table constructor?) expectedrows = table._v_expectedrows if table.nrows > expectedrows: expectedrows = table.nrows # Create the index itself index = Index( idgroup, name, atom=atom, title="Index for %s column" % name, kind=kind, optlevel=optlevel, filters=filters, tmp_dir=tmp_dir, expectedrows=expectedrows, byteorder=table.byteorder, blocksizes=blocksizes) table._set_column_indexing(self.pathname, True) # Feed the index with values # Add rows to the index if necessary if table.nrows > 0: indexedrows = table._add_rows_to_index( self.pathname, 0, table.nrows, lastrow=True, update=False) else: indexedrows = 0 index.dirty = False table._indexedrows = indexedrows table._unsaved_indexedrows = table.nrows - indexedrows # Optimize the index that has been already filled-up index.optimize(verbose=verbose) # We cannot do a flush here because when reindexing during a # flush, the indexes are created anew, and that creates a nested # call to flush(). # table.flush() return indexedrows class _ColIndexes(dict): """Provides a nice representation of column indexes.""" def __repr__(self): """Gives a detailed Description column representation.""" rep = [f' \"{k}\": {v}' for k, v in self.items()] return '{\n %s}' % (',\n '.join(rep)) class Table(tableextension.Table, Leaf): """This class represents heterogeneous datasets in an HDF5 file. Tables are leaves (see the Leaf class in :ref:`LeafClassDescr`) whose data consists of a unidimensional sequence of *rows*, where each row contains one or more *fields*. Fields have an associated unique *name* and *position*, with the first field having position 0. All rows have the same fields, which are arranged in *columns*. Fields can have any type supported by the Col class (see :ref:`ColClassDescr`) and its descendants, which support multidimensional data. Moreover, a field can be *nested* (to an arbitrary depth), meaning that it includes further fields inside. A field named x inside a nested field a in a table can be accessed as the field a/x (its *path name*) from the table. The structure of a table is declared by its description, which is made available in the Table.description attribute (see :class:`Table`). This class provides new methods to read, write and search table data efficiently. It also provides special Python methods to allow accessing the table as a normal sequence or array (with extended slicing supported). PyTables supports *in-kernel* searches working simultaneously on several columns using complex conditions. These are faster than selections using Python expressions. See the :meth:`Table.where` method for more information on in-kernel searches. Non-nested columns can be *indexed*. Searching an indexed column can be several times faster than searching a non-nested one. Search methods automatically take advantage of indexing where available. When iterating a table, an object from the Row (see :ref:`RowClassDescr`) class is used. This object allows to read and write data one row at a time, as well as to perform queries which are not supported by in-kernel syntax (at a much lower speed, of course). Objects of this class support access to individual columns via *natural naming* through the :attr:`Table.cols` accessor. Nested columns are mapped to Cols instances, and non-nested ones to Column instances. See the Column class in :ref:`ColumnClassDescr` for examples of this feature. Parameters ---------- parentnode The parent :class:`Group` object. .. versionchanged:: 3.0 Renamed from *parentNode* to *parentnode*. name : str The name of this node in its parent group. description An IsDescription subclass or a dictionary where the keys are the field names, and the values the type definitions. In addition, a pure NumPy dtype is accepted. If None, the table metadata is read from disk, else, it's taken from previous parameters. title Sets a TITLE attribute on the HDF5 table entity. filters : Filters An instance of the Filters class that provides information about the desired I/O filters to be applied during the life of this object. expectedrows A user estimate about the number of rows that will be on table. If not provided, the default value is ``EXPECTED_ROWS_TABLE`` (see ``tables/parameters.py``). If you plan to save bigger tables, try providing a guess; this will optimize the HDF5 B-Tree creation and management process time and memory used. chunkshape The shape of the data chunk to be read or written as a single HDF5 I/O operation. The filters are applied to those chunks of data. Its rank for tables has to be 1. If ``None``, a sensible value is calculated based on the `expectedrows` parameter (which is recommended). byteorder The byteorder of the data *on-disk*, specified as 'little' or 'big'. If this is not specified, the byteorder is that of the platform, unless you passed a recarray as the `description`, in which case the recarray byteorder will be chosen. track_times Whether time data associated with the leaf are recorded (object access time, raw data modification time, metadata change time, object birth time); default True. Semantics of these times depend on their implementation in the HDF5 library: refer to documentation of the H5O_info_t data structure. As of HDF5 1.8.15, only ctime (metadata change time) is implemented. .. versionadded:: 3.4.3 Notes ----- The instance variables below are provided in addition to those in Leaf (see :ref:`LeafClassDescr`). Please note that there are several col* dictionaries to ease retrieving information about a column directly by its path name, avoiding the need to walk through Table.description or Table.cols. .. rubric:: Table attributes .. attribute:: coldescrs Maps the name of a column to its Col description (see :ref:`ColClassDescr`). .. attribute:: coldflts Maps the name of a column to its default value. .. attribute:: coldtypes Maps the name of a column to its NumPy data type. .. attribute:: colindexed Is the column which name is used as a key indexed? .. attribute:: colinstances Maps the name of a column to its Column (see :ref:`ColumnClassDescr`) or Cols (see :ref:`ColsClassDescr`) instance. .. attribute:: colnames A list containing the names of *top-level* columns in the table. .. attribute:: colpathnames A list containing the pathnames of *bottom-level* columns in the table. These are the leaf columns obtained when walking the table description left-to-right, bottom-first. Columns inside a nested column have slashes (/) separating name components in their pathname. .. attribute:: cols A Cols instance that provides *natural naming* access to non-nested (Column, see :ref:`ColumnClassDescr`) and nested (Cols, see :ref:`ColsClassDescr`) columns. .. attribute:: coltypes Maps the name of a column to its PyTables data type. .. attribute:: description A Description instance (see :ref:`DescriptionClassDescr`) reflecting the structure of the table. .. attribute:: extdim The index of the enlargeable dimension (always 0 for tables). .. attribute:: indexed Does this table have any indexed columns? .. attribute:: nrows The current number of rows in the table. """ # Class identifier. _c_classid = 'TABLE' @lazyattr def row(self): """The associated Row instance (see :ref:`RowClassDescr`).""" return tableextension.Row(self) @lazyattr def dtype(self): """The NumPy ``dtype`` that most closely matches this table.""" return self.description._v_dtype @property def shape(self): """The shape of this table.""" return (self.nrows,) @property def rowsize(self): """The size in bytes of each row in the table.""" return self.description._v_dtype.itemsize @property def size_in_memory(self): """The size of this table's data in bytes when it is fully loaded into memory. This may be used in combination with size_on_disk to calculate the compression ratio of the data.""" return self.nrows * self.rowsize @lazyattr def _v_iobuf(self): """A buffer for doing I/O.""" return self._get_container(self.nrowsinbuf) @lazyattr def _v_wdflts(self): """The defaults for writing in recarray format.""" # First, do a check to see whether we need to set default values # different from 0 or not. for coldflt in self.coldflts.values(): if isinstance(coldflt, np.ndarray) or coldflt: break else: # No default different from 0 found. Returning None. return None wdflts = self._get_container(1) for colname, coldflt in self.coldflts.items(): ra = get_nested_field(wdflts, colname) ra[:] = coldflt return wdflts @lazyattr def _colunaligned(self): """The pathnames of unaligned, *unidimensional* columns.""" colunaligned, rarr = [], self._get_container(0) for colpathname in self.colpathnames: carr = get_nested_field(rarr, colpathname) if not carr.flags.aligned and carr.ndim == 1: colunaligned.append(colpathname) return frozenset(colunaligned) # **************** WARNING! *********************** # This function can be called during the destruction time of a table # so measures have been taken so that it doesn't have to revive # another node (which can fool the LRU cache). The solution devised # has been to add a cache for autoindex (Table._autoindex), populate # it in creation time of the cache (which is a safe period) and then # update the cache whenever it changes. # This solves the error when running test_indexes.py ManyNodesTestCase. # F. Alted 2007-04-20 # ************************************************** @property def autoindex(self): """Automatically keep column indexes up to date? Setting this value states whether existing indexes should be automatically updated after an append operation or recomputed after an index-invalidating operation (i.e. removal and modification of rows). The default is true. This value gets into effect whenever a column is altered. If you don't have automatic indexing activated and you want to do an an immediate update use `Table.flush_rows_to_index()`; for an immediate reindexing of invalidated indexes, use `Table.reindex_dirty()`. This value is persistent. .. versionchanged:: 3.0 The *autoIndex* property has been renamed into *autoindex*. """ if self._autoindex is None: try: indexgroup = self._v_file._get_node(_index_pathname_of(self)) except NoSuchNodeError: self._autoindex = default_auto_index # update cache return self._autoindex else: self._autoindex = indexgroup.auto # update cache return self._autoindex else: # The value is in cache, return it return self._autoindex @autoindex.setter def autoindex(self, auto): auto = bool(auto) try: indexgroup = self._v_file._get_node(_index_pathname_of(self)) except NoSuchNodeError: indexgroup = create_indexes_table(self) indexgroup.auto = auto # Update the cache in table instance as well self._autoindex = auto @property def indexedcolpathnames(self): """List of pathnames of indexed columns in the table.""" return [_colpname for _colpname in self.colpathnames if self.colindexed[_colpname]] @property def colindexes(self): """A dictionary with the indexes of the indexed columns.""" return _ColIndexes((_colpname, self.cols._f_col(_colpname).index) for _colpname in self.colpathnames if self.colindexed[_colpname]) @property def _dirtyindexes(self): """Whether some index in table is dirty.""" return self._condition_cache._nailcount > 0 def __init__(self, parentnode, name, description=None, title="", filters=None, expectedrows=None, chunkshape=None, byteorder=None, _log=True, track_times=True): self._v_new = new = description is not None """Is this the first time the node has been created?""" self._v_new_title = title """New title for this node.""" self._v_new_filters = filters """New filter properties for this node.""" self.extdim = 0 # Tables only have one dimension currently """The index of the enlargeable dimension (always 0 for tables).""" self._v_recarray = None """A structured array to be stored in the table.""" self._rabyteorder = None """The computed byteorder of the self._v_recarray.""" if expectedrows is None: expectedrows = parentnode._v_file.params['EXPECTED_ROWS_TABLE'] self._v_expectedrows = expectedrows """The expected number of rows to be stored in the table.""" self.nrows = SizeType(0) """The current number of rows in the table.""" self.description = None """A Description instance (see :ref:`DescriptionClassDescr`) reflecting the structure of the table.""" self._time64colnames = [] """The names of ``Time64`` columns.""" self._strcolnames = [] """The names of ``String`` columns.""" self._colenums = {} """Maps the name of an enumerated column to its ``Enum`` instance.""" self._v_chunkshape = None """Private storage for the `chunkshape` property of the leaf.""" self.indexed = False """Does this table have any indexed columns?""" self._indexedrows = 0 """Number of rows indexed in disk.""" self._unsaved_indexedrows = 0 """Number of rows indexed in memory but still not in disk.""" self._listoldindexes = [] """The list of columns with old indexes.""" self._autoindex = None """Private variable that caches the value for autoindex.""" self.colnames = [] """A list containing the names of *top-level* columns in the table.""" self.colpathnames = [] """A list containing the pathnames of *bottom-level* columns in the table. These are the leaf columns obtained when walking the table description left-to-right, bottom-first. Columns inside a nested column have slashes (/) separating name components in their pathname. """ self.colinstances = {} """Maps the name of a column to its Column (see :ref:`ColumnClassDescr`) or Cols (see :ref:`ColsClassDescr`) instance.""" self.coldescrs = {} """Maps the name of a column to its Col description (see :ref:`ColClassDescr`).""" self.coltypes = {} """Maps the name of a column to its PyTables data type.""" self.coldtypes = {} """Maps the name of a column to its NumPy data type.""" self.coldflts = {} """Maps the name of a column to its default value.""" self.colindexed = {} """Is the column which name is used as a key indexed?""" self._use_index = False """Whether an index can be used or not in a search. Boolean.""" self._where_condition = None """Condition function and argument list for selection of values.""" self._seqcache_key = None """The key under which to save a query's results (list of row indexes) or None to not save.""" max_slots = parentnode._v_file.params['COND_CACHE_SLOTS'] self._condition_cache = CacheDict(max_slots) """Cache of already compiled conditions.""" self._exprvars_cache = {} """Cache of variables participating in numexpr expressions.""" self._enabled_indexing_in_queries = True """Is indexing enabled in queries? *Use only for testing.*""" self._empty_array_cache = {} """Cache of empty arrays.""" self._v_dtype = None """The NumPy datatype fopr this table.""" self.cols = None """ A Cols instance that provides *natural naming* access to non-nested (Column, see :ref:`ColumnClassDescr`) and nested (Cols, see :ref:`ColsClassDescr`) columns. """ self._dirtycache = True """Whether the data caches are dirty or not. Initially set to yes.""" self._descflavor = None """Temporarily keeps the flavor of a description with data.""" # Initialize this object in case is a new Table # Try purely descriptive description objects. if new and isinstance(description, dict): # Dictionary case self.description = Description(description, ptparams=parentnode._v_file.params) elif new and (type(description) == type(IsDescription) and issubclass(description, IsDescription)): # IsDescription subclass case descr = description() self.description = Description(descr.columns, ptparams=parentnode._v_file.params) elif new and isinstance(description, Description): # It is a Description instance already self.description = description # No description yet? if new and self.description is None: # Try NumPy dtype instances if isinstance(description, np.dtype): tup = descr_from_dtype(description, ptparams=parentnode._v_file.params) self.description, self._rabyteorder = tup # No description yet? if new and self.description is None: # Try structured array description objects. try: self._descflavor = flavor = flavor_of(description) except TypeError: # probably not an array pass else: if flavor == 'python': nparray = np.rec.array(description) else: nparray = array_as_internal(description, flavor) self.nrows = nrows = SizeType(nparray.size) # If `self._v_recarray` is set, it will be used as the # initial buffer. if nrows > 0: self._v_recarray = nparray tup = descr_from_dtype(nparray.dtype, ptparams=parentnode._v_file.params) self.description, self._rabyteorder = tup # No description yet? if new and self.description is None: raise TypeError( "the ``description`` argument is not of a supported type: " "``IsDescription`` subclass, ``Description`` instance, " "dictionary, or structured array") # Check the chunkshape parameter if new and chunkshape is not None: if isinstance(chunkshape, (int, np.integer)): chunkshape = (chunkshape,) try: chunkshape = tuple(chunkshape) except TypeError: raise TypeError( "`chunkshape` parameter must be an integer or sequence " "and you passed a %s" % type(chunkshape)) if len(chunkshape) != 1: raise ValueError("`chunkshape` rank (length) must be 1: %r" % (chunkshape,)) self._v_chunkshape = tuple(SizeType(s) for s in chunkshape) super().__init__(parentnode, name, new, filters, byteorder, _log, track_times) def _g_post_init_hook(self): # We are putting here the index-related issues # as well as filling general info for table # This is needed because we need first the index objects created # First, get back the flavor of input data (if any) for # `Leaf._g_post_init_hook()`. self._flavor, self._descflavor = self._descflavor, None super()._g_post_init_hook() # Create a cols accessor. self.cols = Cols(self, self.description) # Place the `Cols` and `Column` objects into `self.colinstances`. colinstances, cols = self.colinstances, self.cols for colpathname in self.description._v_pathnames: colinstances[colpathname] = cols._g_col(colpathname) if self._v_new: # Columns are never indexed on creation. self.colindexed = {cpn: False for cpn in self.colpathnames} return # The following code is only for opened tables. # Do the indexes group exist? indexesgrouppath = _index_pathname_of(self) igroup = indexesgrouppath in self._v_file oldindexes = False for colobj in self.description._f_walk(type="Col"): colname = colobj._v_pathname # Is this column indexed? if igroup: indexname = _index_pathname_of_column(self, colname) indexed = indexname in self._v_file self.colindexed[colname] = indexed if indexed: column = self.cols._g_col(colname) indexobj = column.index if isinstance(indexobj, OldIndex): indexed = False # Not a vaild index oldindexes = True self._listoldindexes.append(colname) else: # Tell the condition cache about columns with dirty # indexes. if indexobj.dirty: self._condition_cache.nail() else: indexed = False self.colindexed[colname] = False if indexed: self.indexed = True if oldindexes: # this should only appear under 2.x Pro warnings.warn( "table ``%s`` has column indexes with PyTables 1.x format. " "Unfortunately, this format is not supported in " "PyTables 2.x series. Note that you can use the " "``ptrepack`` utility in order to recreate the indexes. " "The 1.x indexed columns found are: %s" % (self._v_pathname, self._listoldindexes), OldIndexWarning) # It does not matter to which column 'indexobj' belongs, # since their respective index objects share # the same number of elements. if self.indexed: self._indexedrows = indexobj.nelements self._unsaved_indexedrows = self.nrows - self._indexedrows # Put the autoindex value in a cache variable self._autoindex = self.autoindex def _calc_nrowsinbuf(self): """Calculate the number of rows that fits on a PyTables buffer.""" params = self._v_file.params # Compute the nrowsinbuf rowsize = self.rowsize buffersize = params['IO_BUFFER_SIZE'] if rowsize != 0: nrowsinbuf = buffersize // rowsize # The number of rows in buffer needs to be an exact multiple of # chunkshape[0] for queries using indexed columns. # Fixes #319 and probably #409 too. nrowsinbuf -= nrowsinbuf % self.chunkshape[0] else: nrowsinbuf = 1 # tableextension.pyx performs an assertion # to make sure nrowsinbuf is greater than or # equal to the chunksize. # See gh-206 and gh-238 if self.chunkshape is not None: if nrowsinbuf < self.chunkshape[0]: nrowsinbuf = self.chunkshape[0] # Safeguard against row sizes being extremely large if nrowsinbuf == 0: nrowsinbuf = 1 # If rowsize is too large, issue a Performance warning maxrowsize = params['BUFFER_TIMES'] * buffersize if rowsize > maxrowsize: warnings.warn("""\ The Table ``%s`` is exceeding the maximum recommended rowsize (%d bytes); be ready to see PyTables asking for *lots* of memory and possibly slow I/O. You may want to reduce the rowsize by trimming the value of dimensions that are orthogonal (and preferably close) to the *main* dimension of this leave. Alternatively, in case you have specified a very small/large chunksize, you may want to increase/decrease it.""" % (self._v_pathname, maxrowsize), PerformanceWarning) return nrowsinbuf def _getemptyarray(self, dtype): # Acts as a cache for empty arrays key = dtype if key in self._empty_array_cache: return self._empty_array_cache[key] else: self._empty_array_cache[ key] = arr = np.empty(shape=0, dtype=key) return arr def _get_container(self, shape): """Get the appropriate buffer for data depending on table nestedness.""" # This is *much* faster than the numpy.rec.array counterpart return np.empty(shape=shape, dtype=self._v_dtype) def _get_type_col_names(self, type_): """Returns a list containing 'type_' column names.""" return [colobj._v_pathname for colobj in self.description._f_walk('Col') if colobj.type == type_] def _get_enum_map(self): """Return mapping from enumerated column names to `Enum` instances.""" enumMap = {} for colobj in self.description._f_walk('Col'): if colobj.kind == 'enum': enumMap[colobj._v_pathname] = colobj.enum return enumMap def _g_create(self): """Create a new table on disk.""" # Warning against assigning too much columns... # F. Alted 2005-06-05 maxColumns = self._v_file.params['MAX_COLUMNS'] if (len(self.description._v_names) > maxColumns): warnings.warn( "table ``%s`` is exceeding the recommended " "maximum number of columns (%d); " "be ready to see PyTables asking for *lots* of memory " "and possibly slow I/O" % (self._v_pathname, maxColumns), PerformanceWarning) # 1. Create the HDF5 table (some parameters need to be computed). # Fix the byteorder of the recarray and update the number of # expected rows if necessary if self._v_recarray is not None: self._v_recarray = self._g_fix_byteorder_data(self._v_recarray, self._rabyteorder) if len(self._v_recarray) > self._v_expectedrows: self._v_expectedrows = len(self._v_recarray) # Compute a sensible chunkshape if self._v_chunkshape is None: self._v_chunkshape = self._calc_chunkshape( self._v_expectedrows, self.rowsize, self.rowsize) # Correct the byteorder, if still needed if self.byteorder is None: self.byteorder = sys.byteorder # Cache some data which is already in the description. # This is necessary to happen before creation time in order # to be able to populate the self._v_wdflts self._cache_description_data() # After creating the table, ``self._v_objectid`` needs to be # set because it is needed for setting attributes afterwards. self._v_objectid = self._create_table( self._v_new_title, self.filters.complib or '', obversion) self._v_recarray = None # not useful anymore self._rabyteorder = None # not useful anymore # 2. Compute or get chunk shape and buffer size parameters. self.nrowsinbuf = self._calc_nrowsinbuf() # 3. Get field fill attributes from the table description and # set them on disk. if self._v_file.params['PYTABLES_SYS_ATTRS']: set_attr = self._v_attrs._g__setattr for i, colobj in enumerate(self.description._f_walk(type="Col")): fieldname = "FIELD_%d_FILL" % i set_attr(fieldname, colobj.dflt) return self._v_objectid def _g_open(self): """Opens a table from disk and read the metadata on it. Creates an user description on the flight to easy the access to the actual data. """ # 1. Open the HDF5 table and get some data from it. self._v_objectid, description, chunksize = self._get_info() self._v_expectedrows = self.nrows # the actual number of rows # 2. Create an instance description to host the record fields. validate = not self._v_file._isPTFile # only for non-PyTables files self.description = Description(description, validate=validate, ptparams=self._v_file.params) # 3. Compute or get chunk shape and buffer size parameters. if chunksize == 0: self._v_chunkshape = self._calc_chunkshape( self._v_expectedrows, self.rowsize, self.rowsize) else: self._v_chunkshape = (chunksize,) self.nrowsinbuf = self._calc_nrowsinbuf() # 4. If there are field fill attributes, get them from disk and # set them in the table description. if self._v_file.params['PYTABLES_SYS_ATTRS']: if "FIELD_0_FILL" in self._v_attrs._f_list("sys"): i = 0 get_attr = self._v_attrs.__getattr__ for objcol in self.description._f_walk(type="Col"): colname = objcol._v_pathname # Get the default values for each column fieldname = "FIELD_%s_FILL" % i defval = get_attr(fieldname) if defval is not None: objcol.dflt = defval else: warnings.warn("could not load default value " "for the ``%s`` column of table ``%s``; " "using ``%r`` instead" % (colname, self._v_pathname, objcol.dflt)) defval = objcol.dflt i += 1 # Set also the correct value in the desc._v_dflts dictionary for descr in self.description._f_walk(type="Description"): for name in descr._v_names: objcol = descr._v_colobjects[name] if isinstance(objcol, Col): descr._v_dflts[objcol._v_name] = objcol.dflt # 5. Cache some data which is already in the description. self._cache_description_data() return self._v_objectid def _cache_description_data(self): """Cache some data which is already in the description. Some information is extracted from `self.description` to build some useful (but redundant) structures: * `self.colnames` * `self.colpathnames` * `self.coldescrs` * `self.coltypes` * `self.coldtypes` * `self.coldflts` * `self._v_dtype` * `self._time64colnames` * `self._strcolnames` * `self._colenums` """ self.colnames = list(self.description._v_names) self.colpathnames = [ col._v_pathname for col in self.description._f_walk() if not hasattr(col, '_v_names')] # bottom-level # Find ``time64`` column names. self._time64colnames = self._get_type_col_names('time64') # Find ``string`` column names. self._strcolnames = self._get_type_col_names('string') # Get a mapping of enumerated columns to their `Enum` instances. self._colenums = self._get_enum_map() # Get info about columns for colobj in self.description._f_walk(type="Col"): colname = colobj._v_pathname # Get the column types, types and defaults self.coldescrs[colname] = colobj self.coltypes[colname] = colobj.type self.coldtypes[colname] = colobj.dtype self.coldflts[colname] = colobj.dflt # Assign _v_dtype for this table self._v_dtype = self.description._v_dtype def _get_column_instance(self, colpathname): """Get the instance of the column with the given `colpathname`. If the column does not exist in the table, a `KeyError` is raised. """ try: return functools.reduce( getattr, colpathname.split('/'), self.description) except AttributeError: raise KeyError("table ``%s`` does not have a column named ``%s``" % (self._v_pathname, colpathname)) _check_column = _get_column_instance def _disable_indexing_in_queries(self): """Force queries not to use indexing. *Use only for testing.* """ if not self._enabled_indexing_in_queries: return # already disabled # The nail avoids setting/getting compiled conditions in/from # the cache where indexing is used. self._condition_cache.nail() self._enabled_indexing_in_queries = False def _enable_indexing_in_queries(self): """Allow queries to use indexing. *Use only for testing.* """ if self._enabled_indexing_in_queries: return # already enabled self._condition_cache.unnail() self._enabled_indexing_in_queries = True def _required_expr_vars(self, expression, uservars, depth=1): """Get the variables required by the `expression`. A new dictionary defining the variables used in the `expression` is returned. Required variables are first looked up in the `uservars` mapping, then in the set of top-level columns of the table. Unknown variables cause a `NameError` to be raised. When `uservars` is `None`, the local and global namespace where the API callable which uses this method is called is sought instead. This mechanism will not work as expected if this method is not used *directly* from an API callable. To disable this mechanism, just specify a mapping as `uservars`. Nested columns and columns from other tables are not allowed (`TypeError` and `ValueError` are raised, respectively). Also, non-column variable values are converted to NumPy arrays. `depth` specifies the depth of the frame in order to reach local or global variables. """ # Get the names of variables used in the expression. exprvarscache = self._exprvars_cache if expression not in exprvarscache: # Protection against growing the cache too much if len(exprvarscache) > 256: # Remove 10 (arbitrary) elements from the cache for k in list(exprvarscache)[:10]: del exprvarscache[k] cexpr = compile(expression, '', 'eval') exprvars = [var for var in cexpr.co_names if var not in ['None', 'False', 'True'] and var not in ne.expressions.functions] exprvarscache[expression] = exprvars else: exprvars = exprvarscache[expression] # Get the local and global variable mappings of the user frame # if no mapping has been explicitly given for user variables. user_locals, user_globals = {}, {} if uservars is None: # We use specified depth to get the frame where the API # callable using this method is called. For instance: # # * ``table._required_expr_vars()`` (depth 0) is called by # * ``table._where()`` (depth 1) is called by # * ``table.where()`` (depth 2) is called by # * user-space functions (depth 3) user_frame = sys._getframe(depth) user_locals = user_frame.f_locals user_globals = user_frame.f_globals colinstances = self.colinstances tblfile, tblpath = self._v_file, self._v_pathname # Look for the required variables first among the ones # explicitly provided by the user, then among implicit columns, # then among external variables (only if no explicit variables). reqvars = {} for var in exprvars: # Get the value. if uservars is not None and var in uservars: val = uservars[var] elif var in colinstances: val = colinstances[var] elif uservars is None and var in user_locals: val = user_locals[var] elif uservars is None and var in user_globals: val = user_globals[var] else: raise NameError("name ``%s`` is not defined" % var) # Check the value. if hasattr(val, 'pathname'): # non-nested column if val.shape[1:] != (): raise NotImplementedError( "variable ``%s`` refers to " "a multidimensional column, " "not yet supported in conditions, sorry" % var) if (val._table_file is not tblfile or val._table_path != tblpath): raise ValueError("variable ``%s`` refers to a column " "which is not part of table ``%s``" % (var, tblpath)) if val.dtype.str[1:] == 'u8': raise NotImplementedError( "variable ``%s`` refers to " "a 64-bit unsigned integer column, " "not yet supported in conditions, sorry; " "please use regular Python selections" % var) elif hasattr(val, '_v_colpathnames'): # nested column raise TypeError( "variable ``%s`` refers to a nested column, " "not allowed in conditions" % var) else: # only non-column values are converted to arrays # XXX: not 100% sure about this if isinstance(val, str): val = np.asarray(val.encode('ascii')) else: val = np.asarray(val) reqvars[var] = val return reqvars def _get_condition_key(self, condition, condvars): """Get the condition cache key for `condition` with `condvars`. Currently, the key is a tuple of `condition`, column variables names, normal variables names, column paths and variable paths (all are tuples). """ # Variable names for column and normal variables. colnames, varnames = [], [] # Column paths and types for each of the previous variable. colpaths, vartypes = [], [] for (var, val) in condvars.items(): if hasattr(val, 'pathname'): # column colnames.append(var) colpaths.append(val.pathname) else: # array try: varnames.append(var) vartypes.append(ne.necompiler.getType(val)) # expensive except ValueError: # This is more clear than the error given by Numexpr. raise TypeError("variable ``%s`` has data type ``%s``, " "not allowed in conditions" % (var, val.dtype.name)) colnames, varnames = tuple(colnames), tuple(varnames) colpaths, vartypes = tuple(colpaths), tuple(vartypes) condkey = (condition, colnames, varnames, colpaths, vartypes) return condkey def _compile_condition(self, condition, condvars): """Compile the `condition` and extract usable index conditions. This method returns an instance of ``CompiledCondition``. See the ``compile_condition()`` function in the ``conditions`` module for more information about the compilation process. This method makes use of the condition cache when possible. """ # Look up the condition in the condition cache. condcache = self._condition_cache condkey = self._get_condition_key(condition, condvars) compiled = condcache.get(condkey) if compiled: return compiled.with_replaced_vars(condvars) # bingo! # Bad luck, the condition must be parsed and compiled. # Fortunately, the key provides some valuable information. ;) (condition, colnames, varnames, colpaths, vartypes) = condkey # Extract more information from referenced columns. # start with normal variables typemap = dict(list(zip(varnames, vartypes))) indexedcols = [] for colname in colnames: col = condvars[colname] # Extract types from *all* the given variables. coltype = col.dtype.type typemap[colname] = _nxtype_from_nptype[coltype] # Get the set of columns with usable indexes. if (self._enabled_indexing_in_queries # no in-kernel searches and self.colindexed[col.pathname] and not col.index.dirty): indexedcols.append(colname) indexedcols = frozenset(indexedcols) # Now let ``compile_condition()`` do the Numexpr-related job. compiled = compile_condition(condition, typemap, indexedcols) # Check that there actually are columns in the condition. if not set(compiled.parameters).intersection(set(colnames)): raise ValueError("there are no columns taking part " "in condition ``%s``" % (condition,)) # Store the compiled condition in the cache and return it. condcache[condkey] = compiled return compiled.with_replaced_vars(condvars) def will_query_use_indexing(self, condition, condvars=None): """Will a query for the condition use indexing? The meaning of the condition and *condvars* arguments is the same as in the :meth:`Table.where` method. If condition can use indexing, this method returns a frozenset with the path names of the columns whose index is usable. Otherwise, it returns an empty list. This method is mainly intended for testing. Keep in mind that changing the set of indexed columns or their dirtiness may make this method return different values for the same arguments at different times. """ # Compile the condition and extract usable index conditions. condvars = self._required_expr_vars(condition, condvars, depth=2) compiled = self._compile_condition(condition, condvars) # Return the columns in indexed expressions idxcols = [condvars[var].pathname for var in compiled.index_variables] return frozenset(idxcols) def where(self, condition, condvars=None, start=None, stop=None, step=None): r"""Iterate over values fulfilling a condition. This method returns a Row iterator (see :ref:`RowClassDescr`) which only selects rows in the table that satisfy the given condition (an expression-like string). The condvars mapping may be used to define the variable names appearing in the condition. condvars should consist of identifier-like strings pointing to Column (see :ref:`ColumnClassDescr`) instances *of this table*, or to other values (which will be converted to arrays). A default set of condition variables is provided where each top-level, non-nested column with an identifier-like name appears. Variables in condvars override the default ones. When condvars is not provided or None, the current local and global namespace is sought instead of condvars. The previous mechanism is mostly intended for interactive usage. To disable it, just specify a (maybe empty) mapping as condvars. If a range is supplied (by setting some of the start, stop or step parameters), only the rows in that range and fulfilling the condition are used. The meaning of the start, stop and step parameters is the same as for Python slices. When possible, indexed columns participating in the condition will be used to speed up the search. It is recommended that you place the indexed columns as left and out in the condition as possible. Anyway, this method has always better performance than regular Python selections on the table. You can mix this method with regular Python selections in order to support even more complex queries. It is strongly recommended that you pass the most restrictive condition as the parameter to this method if you want to achieve maximum performance. .. warning:: When in the middle of a table row iterator, you should not use methods that can change the number of rows in the table (like :meth:`Table.append` or :meth:`Table.remove_rows`) or unexpected errors will happen. Examples -------- :: passvalues = [ row['col3'] for row in table.where('(col1 > 0) & (col2 <= 20)', step=5) if your_function(row['col2']) ] print("Values that pass the cuts:", passvalues) .. note:: A special care should be taken when the query condition includes string literals. Let's assume that the table ``table`` has the following structure:: class Record(IsDescription): col1 = StringCol(4) # 4-character String of bytes col2 = IntCol() col3 = FloatCol() The type of "col1" corresponds to strings of bytes. Any condition involving "col1" should be written using the appropriate type for string literals in order to avoid :exc:`TypeError`\ s. The code below will fail with a :exc:`TypeError`:: condition = 'col1 == "AAAA"' for record in table.where(condition): # TypeError in Python3 # do something with "record" The reason is that in Python 3 "condition" implies a comparison between a string of bytes ("col1" contents) and a unicode literal ("AAAA"). The correct way to write the condition is:: condition = 'col1 == b"AAAA"' .. versionchanged:: 3.0 The start, stop and step parameters now behave like in slice. """ return self._where(condition, condvars, start, stop, step) def _where(self, condition, condvars, start=None, stop=None, step=None): """Low-level counterpart of `self.where()`.""" if profile: tref = clock() if profile: show_stats("Entering table._where", tref) # Adjust the slice to be used. (start, stop, step) = self._process_range_read(start, stop, step) if start >= stop: # empty range, reset conditions self._use_index = False self._where_condition = None return iter([]) # Compile the condition and extract usable index conditions. condvars = self._required_expr_vars(condition, condvars, depth=3) compiled = self._compile_condition(condition, condvars) # Can we use indexes? if compiled.index_expressions: chunkmap = _table__where_indexed( self, compiled, condition, condvars, start, stop, step) if not isinstance(chunkmap, np.ndarray): # If it is not a NumPy array it should be an iterator # Reset conditions self._use_index = False self._where_condition = None # ...and return the iterator return chunkmap else: chunkmap = None # default to an in-kernel query args = [condvars[param] for param in compiled.parameters] self._where_condition = (compiled.function, args, compiled.kwargs) row = tableextension.Row(self) if profile: show_stats("Exiting table._where", tref) return row._iter(start, stop, step, chunkmap=chunkmap) def read_where(self, condition, condvars=None, field=None, start=None, stop=None, step=None): """Read table data fulfilling the given *condition*. This method is similar to :meth:`Table.read`, having their common arguments and return values the same meanings. However, only the rows fulfilling the *condition* are included in the result. The meaning of the other arguments is the same as in the :meth:`Table.where` method. """ self._g_check_open() coords = [p.nrow for p in self._where(condition, condvars, start, stop, step)] self._where_condition = None # reset the conditions if len(coords) > 1: cstart, cstop = coords[0], coords[-1] + 1 if cstop - cstart == len(coords): # Chances for monotonically increasing row values. Refine. inc_seq = np.alltrue( np.arange(cstart, cstop) == np.array(coords)) if inc_seq: return self.read(cstart, cstop, field=field) return self.read_coordinates(coords, field) def append_where(self, dstTable, condition=None, condvars=None, start=None, stop=None, step=None): """Append rows fulfilling the condition to the dstTable table. dstTable must be capable of taking the rows resulting from the query, i.e. it must have columns with the expected names and compatible types. The meaning of the other arguments is the same as in the :meth:`Table.where` method. The number of rows appended to dstTable is returned as a result. .. versionchanged:: 3.0 The *whereAppend* method has been renamed into *append_where*. """ self._g_check_open() # Check that the destination file is not in read-only mode. dstTable._v_file._check_writable() # Row objects do not support nested columns, so we must iterate # over the flat column paths. When rows support nesting, # ``self.colnames`` can be directly iterated upon. colNames = [colName for colName in self.colpathnames] dstRow = dstTable.row nrows = 0 if condition is not None: srcRows = self._where(condition, condvars, start, stop, step) else: srcRows = self.iterrows(start, stop, step) for srcRow in srcRows: for colName in colNames: dstRow[colName] = srcRow[colName] dstRow.append() nrows += 1 dstTable.flush() return nrows def get_where_list(self, condition, condvars=None, sort=False, start=None, stop=None, step=None): """Get the row coordinates fulfilling the given condition. The coordinates are returned as a list of the current flavor. sort means that you want to retrieve the coordinates ordered. The default is to not sort them. The meaning of the other arguments is the same as in the :meth:`Table.where` method. """ self._g_check_open() coords = [p.nrow for p in self._where(condition, condvars, start, stop, step)] coords = np.array(coords, dtype=SizeType) # Reset the conditions self._where_condition = None if sort: coords = np.sort(coords) return internal_to_flavor(coords, self.flavor) def itersequence(self, sequence): """Iterate over a sequence of row coordinates.""" if not hasattr(sequence, '__getitem__'): raise TypeError("Wrong 'sequence' parameter type. Only sequences " "are suported.") # start, stop and step are necessary for the new iterator for # coordinates, and perhaps it would be useful to add them as # parameters in the future (not now, because I've just removed # the `sort` argument for 2.1). # # *Important note*: Negative values for step are not supported # for the general case, but only for the itersorted() and # read_sorted() purposes! The self._process_range_read will raise # an appropiate error. # F. Alted 2008-09-18 # A.V. 20130513: _process_range_read --> _process_range (start, stop, step) = self._process_range(None, None, None) if (start > stop) or (len(sequence) == 0): return iter([]) row = tableextension.Row(self) return row._iter(start, stop, step, coords=sequence) def _check_sortby_csi(self, sortby, checkCSI): if isinstance(sortby, Column): icol = sortby elif isinstance(sortby, str): icol = self.cols._f_col(sortby) else: raise TypeError( "`sortby` can only be a `Column` or string object, " "but you passed an object of type: %s" % type(sortby)) if icol.is_indexed and icol.index.kind == "full": if checkCSI and not icol.index.is_csi: # The index exists, but it is not a CSI one. raise ValueError( "Field `%s` must have associated a CSI index " "in table `%s`, but the existing one is not. " % (sortby, self)) return icol.index else: raise ValueError( "Field `%s` must have associated a 'full' index " "in table `%s`." % (sortby, self)) def itersorted(self, sortby, checkCSI=False, start=None, stop=None, step=None): """Iterate table data following the order of the index of sortby column. The sortby column must have associated a full index. If you want to ensure a fully sorted order, the index must be a CSI one. You may want to use the checkCSI argument in order to explicitly check for the existence of a CSI index. The meaning of the start, stop and step arguments is the same as in :meth:`Table.read`. .. versionchanged:: 3.0 If the *start* parameter is provided and *stop* is None then the table is iterated from *start* to the last line. In PyTables < 3.0 only one element was returned. """ index = self._check_sortby_csi(sortby, checkCSI) # Adjust the slice to be used. (start, stop, step) = self._process_range(start, stop, step, warn_negstep=False) if (start > stop and 0 < step) or (start < stop and 0 > step): # Fall-back action is to return an empty iterator return iter([]) row = tableextension.Row(self) return row._iter(start, stop, step, coords=index) def read_sorted(self, sortby, checkCSI=False, field=None, start=None, stop=None, step=None): """Read table data following the order of the index of sortby column. The sortby column must have associated a full index. If you want to ensure a fully sorted order, the index must be a CSI one. You may want to use the checkCSI argument in order to explicitly check for the existence of a CSI index. If field is supplied only the named column will be selected. If the column is not nested, an *array* of the current flavor will be returned; if it is, a *structured array* will be used instead. If no field is specified, all the columns will be returned in a structured array of the current flavor. The meaning of the start, stop and step arguments is the same as in :meth:`Table.read`. .. versionchanged:: 3.0 The start, stop and step parameters now behave like in slice. """ self._g_check_open() index = self._check_sortby_csi(sortby, checkCSI) coords = index[start:stop:step] return self.read_coordinates(coords, field) def iterrows(self, start=None, stop=None, step=None): """Iterate over the table using a Row instance. If a range is not supplied, *all the rows* in the table are iterated upon - you can also use the :meth:`Table.__iter__` special method for that purpose. If you want to iterate over a given *range of rows* in the table, you may use the start, stop and step parameters. .. warning:: When in the middle of a table row iterator, you should not use methods that can change the number of rows in the table (like :meth:`Table.append` or :meth:`Table.remove_rows`) or unexpected errors will happen. See Also -------- tableextension.Row : the table row iterator and field accessor Examples -------- :: result = [ row['var2'] for row in table.iterrows(step=5) if row['var1'] <= 20 ] .. versionchanged:: 3.0 If the *start* parameter is provided and *stop* is None then the table is iterated from *start* to the last line. In PyTables < 3.0 only one element was returned. """ (start, stop, step) = self._process_range(start, stop, step, warn_negstep=False) if (start > stop and 0 < step) or (start < stop and 0 > step): # Fall-back action is to return an empty iterator return iter([]) row = tableextension.Row(self) return row._iter(start, stop, step) def __iter__(self): """Iterate over the table using a Row instance. This is equivalent to calling :meth:`Table.iterrows` with default arguments, i.e. it iterates over *all the rows* in the table. See Also -------- tableextension.Row : the table row iterator and field accessor Examples -------- :: result = [ row['var2'] for row in table if row['var1'] <= 20 ] Which is equivalent to:: result = [ row['var2'] for row in table.iterrows() if row['var1'] <= 20 ] """ return self.iterrows() def _read(self, start, stop, step, field=None, out=None): """Read a range of rows and return an in-memory object.""" select_field = None if field: if field not in self.coldtypes: if field in self.description._v_names: # Remember to select this field select_field = field field = None else: raise KeyError(("Field {} not found in table " "{}").format(field, self)) else: # The column hangs directly from the top dtype_field = self.coldtypes[field] # Return a rank-0 array if start > stop if (start >= stop and 0 < step) or (start <= stop and 0 > step): if field is None: nra = self._get_container(0) return nra return np.empty(shape=0, dtype=dtype_field) nrows = len(range(start, stop, step)) if out is None: # Compute the shape of the resulting column object if field: # Create a container for the results result = np.empty(shape=nrows, dtype=dtype_field) else: # Recarray case result = self._get_container(nrows) else: # there is no fast way to byteswap, since different columns may # have different byteorders if not out.dtype.isnative: raise ValueError("output array must be in system's byteorder " "or results will be incorrect") if field: bytes_required = dtype_field.itemsize * nrows else: bytes_required = self.rowsize * nrows if bytes_required != out.nbytes: raise ValueError(f'output array size invalid, got {out.nbytes}' f' bytes, need {bytes_required} bytes') if not out.flags['C_CONTIGUOUS']: raise ValueError('output array not C contiguous') result = out # Call the routine to fill-up the resulting array if step == 1 and not field: # This optimization works three times faster than # the row._fill_col method (up to 170 MB/s on a pentium IV @ 2GHz) self._read_records(start, stop - start, result) # Warning!: _read_field_name should not be used until # H5TBread_fields_name in tableextension will be finished # F. Alted 2005/05/26 # XYX Ho implementem per a PyTables 2.0?? elif field and step > 15 and 0: # For step>15, this seems to work always faster than row._fill_col. self._read_field_name(result, start, stop, step, field) else: self.row._fill_col(result, start, stop, step, field) if select_field: return result[select_field] else: return result def read(self, start=None, stop=None, step=None, field=None, out=None): """Get data in the table as a (record) array. The start, stop and step parameters can be used to select only a *range of rows* in the table. Their meanings are the same as in the built-in Python slices. If field is supplied only the named column will be selected. If the column is not nested, an *array* of the current flavor will be returned; if it is, a *structured array* will be used instead. If no field is specified, all the columns will be returned in a structured array of the current flavor. Columns under a nested column can be specified in the field parameter by using a slash character (/) as a separator (e.g. 'position/x'). The out parameter may be used to specify a NumPy array to receive the output data. Note that the array must have the same size as the data selected with the other parameters. Note that the array's datatype is not checked and no type casting is performed, so if it does not match the datatype on disk, the output will not be correct. When specifying a single nested column with the field parameter, and supplying an output buffer with the out parameter, the output buffer must contain all columns in the table. The data in all columns will be read into the output buffer. However, only the specified nested column will be returned from the method call. When data is read from disk in NumPy format, the output will be in the current system's byteorder, regardless of how it is stored on disk. If the out parameter is specified, the output array also must be in the current system's byteorder. .. versionchanged:: 3.0 Added the *out* parameter. Also the start, stop and step parameters now behave like in slice. Examples -------- Reading the entire table:: t.read() Reading record n. 6:: t.read(6, 7) Reading from record n. 6 to the end of the table:: t.read(6) """ self._g_check_open() if field: self._check_column(field) if out is not None and self.flavor != 'numpy': msg = ("Optional 'out' argument may only be supplied if array " "flavor is 'numpy', currently is {}").format(self.flavor) raise TypeError(msg) start, stop, step = self._process_range(start, stop, step, warn_negstep=False) arr = self._read(start, stop, step, field, out) return internal_to_flavor(arr, self.flavor) def _read_coordinates(self, coords, field=None): """Private part of `read_coordinates()` with no flavor conversion.""" coords = self._point_selection(coords) ncoords = len(coords) # Create a read buffer only if needed if field is None or ncoords > 0: # Doing a copy is faster when ncoords is small (<1000) if ncoords < min(1000, self.nrowsinbuf): result = self._v_iobuf[:ncoords].copy() else: result = self._get_container(ncoords) # Do the real read if ncoords > 0: # Turn coords into an array of coordinate indexes, if necessary if not (isinstance(coords, np.ndarray) and coords.dtype.type is _npsizetype and coords.flags.contiguous and coords.flags.aligned): # Get a contiguous and aligned coordinate array coords = np.array(coords, dtype=SizeType) self._read_elements(coords, result) # Do the final conversions, if needed if field: if ncoords > 0: result = get_nested_field(result, field) else: # Get an empty array from the cache result = self._getemptyarray(self.coldtypes[field]) return result def read_coordinates(self, coords, field=None): """Get a set of rows given their indexes as a (record) array. This method works much like the :meth:`Table.read` method, but it uses a sequence (coords) of row indexes to select the wanted columns, instead of a column range. The selected rows are returned in an array or structured array of the current flavor. """ self._g_check_open() result = self._read_coordinates(coords, field) return internal_to_flavor(result, self.flavor) def get_enum(self, colname): """Get the enumerated type associated with the named column. If the column named colname (a string) exists and is of an enumerated type, the corresponding Enum instance (see :ref:`EnumClassDescr`) is returned. If it is not of an enumerated type, a TypeError is raised. If the column does not exist, a KeyError is raised. """ self._check_column(colname) try: return self._colenums[colname] except KeyError: raise TypeError( "column ``%s`` of table ``%s`` is not of an enumerated type" % (colname, self._v_pathname)) def col(self, name): """Get a column from the table. If a column called name exists in the table, it is read and returned as a NumPy object. If it does not exist, a KeyError is raised. Examples -------- :: narray = table.col('var2') That statement is equivalent to:: narray = table.read(field='var2') Here you can see how this method can be used as a shorthand for the :meth:`Table.read` method. """ return self.read(field=name) def __getitem__(self, key): """Get a row or a range of rows from the table. If key argument is an integer, the corresponding table row is returned as a record of the current flavor. If key is a slice, the range of rows determined by it is returned as a structured array of the current flavor. In addition, NumPy-style point selections are supported. In particular, if key is a list of row coordinates, the set of rows determined by it is returned. Furthermore, if key is an array of boolean values, only the coordinates where key is True are returned. Note that for the latter to work it is necessary that key list would contain exactly as many rows as the table has. Examples -------- :: record = table[4] recarray = table[4:1000:2] recarray = table[[4,1000]] # only retrieves rows 4 and 1000 recarray = table[[True, False, ..., True]] Those statements are equivalent to:: record = table.read(start=4)[0] recarray = table.read(start=4, stop=1000, step=2) recarray = table.read_coordinates([4,1000]) recarray = table.read_coordinates([True, False, ..., True]) Here, you can see how indexing can be used as a shorthand for the :meth:`Table.read` and :meth:`Table.read_coordinates` methods. """ self._g_check_open() if is_idx(key): key = operator.index(key) # Index out of range protection if key >= self.nrows: raise IndexError("Index out of range") if key < 0: # To support negative values key += self.nrows (start, stop, step) = self._process_range(key, key + 1, 1) return self.read(start, stop, step)[0] elif isinstance(key, slice): (start, stop, step) = self._process_range( key.start, key.stop, key.step) return self.read(start, stop, step) # Try with a boolean or point selection elif type(key) in (list, tuple) or isinstance(key, np.ndarray): return self._read_coordinates(key, None) else: raise IndexError(f"Invalid index or slice: {key!r}") def __setitem__(self, key, value): """Set a row or a range of rows in the table. It takes different actions depending on the type of the *key* parameter: if it is an integer, the corresponding table row is set to *value* (a record or sequence capable of being converted to the table structure). If *key* is a slice, the row slice determined by it is set to *value* (a record array or sequence capable of being converted to the table structure). In addition, NumPy-style point selections are supported. In particular, if key is a list of row coordinates, the set of rows determined by it is set to value. Furthermore, if key is an array of boolean values, only the coordinates where key is True are set to values from value. Note that for the latter to work it is necessary that key list would contain exactly as many rows as the table has. Examples -------- :: # Modify just one existing row table[2] = [456,'db2',1.2] # Modify two existing rows rows = numpy.rec.array([[457,'db1',1.2],[6,'de2',1.3]], formats='i4,a3,f8') table[1:30:2] = rows # modify a table slice table[[1,3]] = rows # only modifies rows 1 and 3 table[[True,False,True]] = rows # only modifies rows 0 and 2 Which is equivalent to:: table.modify_rows(start=2, rows=[456,'db2',1.2]) rows = numpy.rec.array([[457,'db1',1.2],[6,'de2',1.3]], formats='i4,a3,f8') table.modify_rows(start=1, stop=3, step=2, rows=rows) table.modify_coordinates([1,3,2], rows) table.modify_coordinates([True, False, True], rows) Here, you can see how indexing can be used as a shorthand for the :meth:`Table.modify_rows` and :meth:`Table.modify_coordinates` methods. """ self._g_check_open() self._v_file._check_writable() if is_idx(key): key = operator.index(key) # Index out of range protection if key >= self.nrows: raise IndexError("Index out of range") if key < 0: # To support negative values key += self.nrows return self.modify_rows(key, key + 1, 1, [value]) elif isinstance(key, slice): (start, stop, step) = self._process_range( key.start, key.stop, key.step) return self.modify_rows(start, stop, step, value) # Try with a boolean or point selection elif type(key) in (list, tuple) or isinstance(key, np.ndarray): return self.modify_coordinates(key, value) else: raise IndexError(f"Invalid index or slice: {key!r}") def _save_buffered_rows(self, wbufRA, lenrows): """Update the indexes after a flushing of rows.""" self._open_append(wbufRA) self._append_records(lenrows) self._close_append() if self.indexed: self._unsaved_indexedrows += lenrows # The table caches for indexed queries are dirty now self._dirtycache = True if self.autoindex: # Flush the unindexed rows self.flush_rows_to_index(_lastrow=False) else: # All the columns are dirty now self._mark_columns_as_dirty(self.colpathnames) def append(self, rows): """Append a sequence of rows to the end of the table. The rows argument may be any object which can be converted to a structured array compliant with the table structure (otherwise, a ValueError is raised). This includes NumPy structured arrays, lists of tuples or array records, and a string or Python buffer. Examples -------- :: import tables as tb class Particle(tb.IsDescription): name = tb.StringCol(16, pos=1) # 16-character String lati = tb.IntCol(pos=2) # integer longi = tb.IntCol(pos=3) # integer pressure = tb.Float32Col(pos=4) # float (single-precision) temperature = tb.FloatCol(pos=5) # double (double-precision) fileh = tb.open_file('test4.h5', mode='w') table = fileh.create_table(fileh.root, 'table', Particle, "A table") # Append several rows in only one call table.append([("Particle: 10", 10, 0, 10 * 10, 10**2), ("Particle: 11", 11, -1, 11 * 11, 11**2), ("Particle: 12", 12, -2, 12 * 12, 12**2)]) fileh.close() """ self._g_check_open() self._v_file._check_writable() if not self._chunked: raise HDF5ExtError( "You cannot append rows to a non-chunked table.", h5bt=False) # Try to convert the object into a recarray compliant with table try: iflavor = flavor_of(rows) if iflavor != 'python': rows = array_as_internal(rows, iflavor) # Works for Python structures and always copies the original, # so the resulting object is safe for in-place conversion. wbufRA = np.rec.array(rows, dtype=self._v_dtype) except Exception as exc: # XXX raise ValueError("rows parameter cannot be converted into a " "recarray object compliant with table '%s'. " "The error was: <%s>" % (str(self), exc)) lenrows = wbufRA.shape[0] # If the number of rows to append is zero, don't do anything else if lenrows > 0: # Save write buffer to disk self._save_buffered_rows(wbufRA, lenrows) def _conv_to_recarr(self, obj): """Try to convert the object into a recarray.""" try: iflavor = flavor_of(obj) if iflavor != 'python': obj = array_as_internal(obj, iflavor) if hasattr(obj, "shape") and obj.shape == (): # To allow conversion of scalars (void type) into arrays. # See http://projects.scipy.org/scipy/numpy/ticket/315 # for discussion on how to pass buffers to constructors # See also http://projects.scipy.org/scipy/numpy/ticket/348 recarr = np.array([obj], dtype=self._v_dtype) else: # Works for Python structures and always copies the original, # so the resulting object is safe for in-place conversion. recarr = np.rec.array(obj, dtype=self._v_dtype) except Exception as exc: # XXX raise ValueError("Object cannot be converted into a recarray " "object compliant with table format '%s'. " "The error was: <%s>" % (self.description._v_nested_descr, exc)) return recarr def modify_coordinates(self, coords, rows): """Modify a series of rows in positions specified in coords. The values in the selected rows will be modified with the data given in rows. This method returns the number of rows modified. The possible values for the rows argument are the same as in :meth:`Table.append`. """ if rows is None: # Nothing to be done return SizeType(0) # Convert the coordinates to something expected by HDF5 coords = self._point_selection(coords) lcoords = len(coords) if len(rows) < lcoords: raise ValueError("The value has not enough elements to fill-in " "the specified range") # Convert rows into a recarray recarr = self._conv_to_recarr(rows) if len(coords) > 0: # Do the actual update of rows self._update_elements(lcoords, coords, recarr) # Redo the index if needed self._reindex(self.colpathnames) return SizeType(lcoords) def modify_rows(self, start=None, stop=None, step=None, rows=None): """Modify a series of rows in the slice [start:stop:step]. The values in the selected rows will be modified with the data given in rows. This method returns the number of rows modified. Should the modification exceed the length of the table, an IndexError is raised before changing data. The possible values for the rows argument are the same as in :meth:`Table.append`. """ if step is None: step = 1 if rows is None: # Nothing to be done return SizeType(0) if start is None: start = 0 if start < 0: raise ValueError("'start' must have a positive value.") if step < 1: raise ValueError( "'step' must have a value greater or equal than 1.") if stop is None: # compute the stop value. start + len(rows)*step does not work stop = start + (len(rows) - 1) * step + 1 (start, stop, step) = self._process_range(start, stop, step) if stop > self.nrows: raise IndexError("This modification will exceed the length of " "the table. Giving up.") # Compute the number of rows to read. nrows = len(range(start, stop, step)) if len(rows) != nrows: raise ValueError("The value has different elements than the " "specified range") # Convert rows into a recarray recarr = self._conv_to_recarr(rows) lenrows = len(recarr) if start + lenrows > self.nrows: raise IndexError("This modification will exceed the length of the " "table. Giving up.") # Do the actual update self._update_records(start, stop, step, recarr) # Redo the index if needed self._reindex(self.colpathnames) return SizeType(lenrows) def modify_column(self, start=None, stop=None, step=None, column=None, colname=None): """Modify one single column in the row slice [start:stop:step]. The colname argument specifies the name of the column in the table to be modified with the data given in column. This method returns the number of rows modified. Should the modification exceed the length of the table, an IndexError is raised before changing data. The *column* argument may be any object which can be converted to a (record) array compliant with the structure of the column to be modified (otherwise, a ValueError is raised). This includes NumPy (record) arrays, lists of scalars, tuples or array records, and a string or Python buffer. """ if step is None: step = 1 if not isinstance(colname, str): raise TypeError("The 'colname' parameter must be a string.") self._v_file._check_writable() if column is None: # Nothing to be done return SizeType(0) if start is None: start = 0 if start < 0: raise ValueError("'start' must have a positive value.") if step < 1: raise ValueError( "'step' must have a value greater or equal than 1.") # Get the column format to be modified: objcol = self._get_column_instance(colname) descr = [objcol._v_parent._v_nested_descr[objcol._v_pos]] # Try to convert the column object into a NumPy ndarray try: # If the column is a recarray (or kind of), convert into ndarray if hasattr(column, 'dtype') and column.dtype.kind == 'V': column = np.rec.array(column, dtype=descr).field(0) else: # Make sure the result is always a *copy* of the original, # so the resulting object is safe for in-place conversion. iflavor = flavor_of(column) column = array_as_internal(column, iflavor) except Exception as exc: # XXX raise ValueError("column parameter cannot be converted into a " "ndarray object compliant with specified column " "'%s'. The error was: <%s>" % (str(column), exc)) # Get rid of single-dimensional dimensions column = column.squeeze() if column.shape == (): # Oops, stripped off to much dimensions column.shape = (1,) if stop is None: # compute the stop value. start + len(rows)*step does not work stop = start + (len(column) - 1) * step + 1 (start, stop, step) = self._process_range(start, stop, step) if stop > self.nrows: raise IndexError("This modification will exceed the length of " "the table. Giving up.") # Compute the number of rows to read. nrows = len(range(start, stop, step)) if len(column) < nrows: raise ValueError("The value has not enough elements to fill-in " "the specified range") # Now, read the original values: mod_recarr = self._read(start, stop, step) # Modify the appropriate column in the original recarray mod_col = get_nested_field(mod_recarr, colname) mod_col[:] = column # save this modified rows in table self._update_records(start, stop, step, mod_recarr) # Redo the index if needed self._reindex([colname]) return SizeType(nrows) def modify_columns(self, start=None, stop=None, step=None, columns=None, names=None): """Modify a series of columns in the row slice [start:stop:step]. The names argument specifies the names of the columns in the table to be modified with the data given in columns. This method returns the number of rows modified. Should the modification exceed the length of the table, an IndexError is raised before changing data. The columns argument may be any object which can be converted to a structured array compliant with the structure of the columns to be modified (otherwise, a ValueError is raised). This includes NumPy structured arrays, lists of tuples or array records, and a string or Python buffer. """ if step is None: step = 1 if type(names) not in (list, tuple): raise TypeError("The 'names' parameter must be a list of strings.") if columns is None: # Nothing to be done return SizeType(0) if start is None: start = 0 if start < 0: raise ValueError("'start' must have a positive value.") if step < 1: raise ValueError("'step' must have a value greater or " "equal than 1.") descr = [] for colname in names: objcol = self._get_column_instance(colname) descr.append(objcol._v_parent._v_nested_descr[objcol._v_pos]) # descr.append(objcol._v_parent._v_dtype[objcol._v_pos]) # Try to convert the columns object into a recarray try: # Make sure the result is always a *copy* of the original, # so the resulting object is safe for in-place conversion. iflavor = flavor_of(columns) if iflavor != 'python': columns = array_as_internal(columns, iflavor) recarray = np.rec.array(columns, dtype=descr) else: recarray = np.rec.fromarrays(columns, dtype=descr) except Exception as exc: # XXX raise ValueError("columns parameter cannot be converted into a " "recarray object compliant with table '%s'. " "The error was: <%s>" % (str(self), exc)) if stop is None: # compute the stop value. start + len(rows)*step does not work stop = start + (len(recarray) - 1) * step + 1 (start, stop, step) = self._process_range(start, stop, step) if stop > self.nrows: raise IndexError("This modification will exceed the length of " "the table. Giving up.") # Compute the number of rows to read. nrows = len(range(start, stop, step)) if len(recarray) < nrows: raise ValueError("The value has not enough elements to fill-in " "the specified range") # Now, read the original values: mod_recarr = self._read(start, stop, step) # Modify the appropriate columns in the original recarray for i, name in enumerate(recarray.dtype.names): mod_col = get_nested_field(mod_recarr, names[i]) mod_col[:] = recarray[name].squeeze() # save this modified rows in table self._update_records(start, stop, step, mod_recarr) # Redo the index if needed self._reindex(names) return SizeType(nrows) def flush_rows_to_index(self, _lastrow=True): """Add remaining rows in buffers to non-dirty indexes. This can be useful when you have chosen non-automatic indexing for the table (see the :attr:`Table.autoindex` property in :class:`Table`) and you want to update the indexes on it. """ rowsadded = 0 if self.indexed: # Update the number of unsaved indexed rows start = self._indexedrows nrows = self._unsaved_indexedrows for (colname, colindexed) in self.colindexed.items(): if colindexed: col = self.cols._g_col(colname) if nrows > 0 and not col.index.dirty: rowsadded = self._add_rows_to_index( colname, start, nrows, _lastrow, update=True) self._unsaved_indexedrows -= rowsadded self._indexedrows += rowsadded return rowsadded def _add_rows_to_index(self, colname, start, nrows, lastrow, update): """Add more elements to the existing index.""" # This method really belongs to Column, but since it makes extensive # use of the table, it gets dangerous when closing the file, since the # column may be accessing a table which is being destroyed. index = self.cols._g_col(colname).index slicesize = index.slicesize # The next loop does not rely on xrange so that it can # deal with long ints (i.e. more than 32-bit integers) # This allows to index columns with more than 2**31 rows # F. Alted 2005-05-09 startLR = index.sorted.nrows * slicesize indexedrows = startLR - start stop = start + nrows - slicesize + 1 while startLR < stop: index.append( [self._read(startLR, startLR + slicesize, 1, colname)], update=update) indexedrows += slicesize startLR += slicesize # index the remaining rows in last row if lastrow and startLR < self.nrows: index.append_last_row( [self._read(startLR, self.nrows, 1, colname)], update=update) indexedrows += self.nrows - startLR return indexedrows def remove_rows(self, start=None, stop=None, step=None): """Remove a range of rows in the table. If only start is supplied, that row and all following will be deleted. If a range is supplied, i.e. both the start and stop parameters are passed, all the rows in the range are removed. .. versionchanged:: 3.0 The start, stop and step parameters now behave like in slice. .. seealso:: remove_row() Parameters ---------- start : int Sets the starting row to be removed. It accepts negative values meaning that the count starts from the end. A value of 0 means the first row. stop : int Sets the last row to be removed to stop-1, i.e. the end point is omitted (in the Python range() tradition). Negative values are also accepted. If None all rows after start will be removed. step : int The step size between rows to remove. .. versionadded:: 3.0 Examples -------- Removing rows from 5 to 10 (excluded):: t.remove_rows(5, 10) Removing all rows starting from the 10th:: t.remove_rows(10) Removing the 6th row:: t.remove_rows(6, 7) .. note:: removing a single row can be done using the specific :meth:`remove_row` method. """ (start, stop, step) = self._process_range(start, stop, step) nrows = self._remove_rows(start, stop, step) # remove_rows is a invalidating index operation self._reindex(self.colpathnames) return SizeType(nrows) def remove_row(self, n): """Removes a row from the table. Parameters ---------- n : int The index of the row to remove. .. versionadded:: 3.0 Examples -------- Remove row 15:: table.remove_row(15) Which is equivalent to:: table.remove_rows(15, 16) .. warning:: This is not equivalent to:: table.remove_rows(15) """ self.remove_rows(start=n, stop=n + 1) def _g_update_dependent(self): super()._g_update_dependent() # Update the new path in columns self.cols._g_update_table_location(self) # Update the new path in the Row instance, if cached. Fixes #224. if 'row' in self.__dict__: self.__dict__['row'] = tableextension.Row(self) def _g_move(self, newparent, newname): """Move this node in the hierarchy. This overloads the Node._g_move() method. """ itgpathname = _index_pathname_of(self) # First, move the table to the new location. super()._g_move(newparent, newname) # Then move the associated index group (if any). try: itgroup = self._v_file._get_node(itgpathname) except NoSuchNodeError: pass else: newigroup = self._v_parent newiname = _index_name_of(self) itgroup._g_move(newigroup, newiname) def _g_remove(self, recursive=False, force=False): # Remove the associated index group (if any). itgpathname = _index_pathname_of(self) try: itgroup = self._v_file._get_node(itgpathname) except NoSuchNodeError: pass else: itgroup._f_remove(recursive=True) self.indexed = False # there are indexes no more # Remove the leaf itself from the hierarchy. super()._g_remove(recursive, force) def _set_column_indexing(self, colpathname, indexed): """Mark the referred column as indexed or non-indexed.""" colindexed = self.colindexed isindexed, wasindexed = bool(indexed), colindexed[colpathname] if isindexed == wasindexed: return # indexing state is unchanged # Changing the set of indexed columns invalidates the condition cache self._condition_cache.clear() colindexed[colpathname] = isindexed self.indexed = max(colindexed.values()) # this is an OR :) def _mark_columns_as_dirty(self, colnames): """Mark column indexes in `colnames` as dirty.""" assert len(colnames) > 0 if self.indexed: colindexed, cols = self.colindexed, self.cols # Mark the proper indexes as dirty for colname in colnames: if colindexed[colname]: col = cols._g_col(colname) col.index.dirty = True def _reindex(self, colnames): """Re-index columns in `colnames` if automatic indexing is true.""" if self.indexed: colindexed, cols = self.colindexed, self.cols colstoindex = [] # Mark the proper indexes as dirty for colname in colnames: if colindexed[colname]: col = cols._g_col(colname) col.index.dirty = True colstoindex.append(colname) # Now, re-index the dirty ones if self.autoindex and colstoindex: self._do_reindex(dirty=True) # The table caches for indexed queries are dirty now self._dirtycache = True def _do_reindex(self, dirty): """Common code for `reindex()` and `reindex_dirty()`.""" indexedrows = 0 for (colname, colindexed) in self.colindexed.items(): if colindexed: indexcol = self.cols._g_col(colname) indexedrows = indexcol._do_reindex(dirty) # Update counters in case some column has been updated if indexedrows > 0: self._indexedrows = indexedrows self._unsaved_indexedrows = self.nrows - indexedrows return SizeType(indexedrows) def reindex(self): """Recompute all the existing indexes in the table. This can be useful when you suspect that, for any reason, the index information for columns is no longer valid and want to rebuild the indexes on it. """ self._do_reindex(dirty=False) def reindex_dirty(self): """Recompute the existing indexes in table, *if* they are dirty. This can be useful when you have set :attr:`Table.autoindex` (see :class:`Table`) to false for the table and you want to update the indexes after a invalidating index operation (:meth:`Table.remove_rows`, for example). """ self._do_reindex(dirty=True) def _g_copy_rows(self, object, start, stop, step, sortby, checkCSI): """Copy rows from self to object""" if sortby is None: self._g_copy_rows_optim(object, start, stop, step) return lenbuf = self.nrowsinbuf absstep = step if step < 0: absstep = -step start, stop = stop + 1, start + 1 if sortby is not None: index = self._check_sortby_csi(sortby, checkCSI) for start2 in range(start, stop, absstep * lenbuf): stop2 = start2 + absstep * lenbuf if stop2 > stop: stop2 = stop # The next 'if' is not needed, but it doesn't bother either if sortby is None: rows = self[start2:stop2:step] else: coords = index[start2:stop2:step] rows = self.read_coordinates(coords) # Save the records on disk object.append(rows) object.flush() def _g_copy_rows_optim(self, object, start, stop, step): """Copy rows from self to object (optimized version)""" nrowsinbuf = self.nrowsinbuf object._open_append(self._v_iobuf) nrowsdest = object.nrows for start2 in range(start, stop, step * nrowsinbuf): # Save the records on disk stop2 = start2 + step * nrowsinbuf if stop2 > stop: stop2 = stop # Optimized version (it saves some conversions) nrows = ((stop2 - start2 - 1) // step) + 1 self.row._fill_col(self._v_iobuf, start2, stop2, step, None) # The output buffer is created anew, # so the operation is safe to in-place conversion. object._append_records(nrows) nrowsdest += nrows object._close_append() def _g_prop_indexes(self, other): """Generate index in `other` table for every indexed column here.""" oldcols, newcols = self.colinstances, other.colinstances for colname in newcols: if (isinstance(oldcols[colname], Column)): oldcolindexed = oldcols[colname].is_indexed if oldcolindexed: oldcolindex = oldcols[colname].index newcol = newcols[colname] newcol.create_index( kind=oldcolindex.kind, optlevel=oldcolindex.optlevel, filters=oldcolindex.filters, tmp_dir=None) def _g_copy_with_stats(self, group, name, start, stop, step, title, filters, chunkshape, _log, **kwargs): """Private part of Leaf.copy() for each kind of leaf.""" # Get the private args for the Table flavor of copy() sortby = kwargs.pop('sortby', None) propindexes = kwargs.pop('propindexes', False) checkCSI = kwargs.pop('checkCSI', False) # Compute the correct indices. (start, stop, step) = self._process_range_read( start, stop, step, warn_negstep=sortby is None) # And the number of final rows nrows = len(range(start, stop, step)) # Create the new table and copy the selected data. newtable = Table(group, name, self.description, title=title, filters=filters, expectedrows=nrows, chunkshape=chunkshape, _log=_log) self._g_copy_rows(newtable, start, stop, step, sortby, checkCSI) nbytes = newtable.nrows * newtable.rowsize # Generate equivalent indexes in the new table, if required. if propindexes and self.indexed: self._g_prop_indexes(newtable) return (newtable, nbytes) # This overloading of copy is needed here in order to document # the additional keywords for the Table case. def copy(self, newparent=None, newname=None, overwrite=False, createparents=False, **kwargs): """Copy this table and return the new one. This method has the behavior and keywords described in :meth:`Leaf.copy`. Moreover, it recognises the following additional keyword arguments. Parameters ---------- sortby If specified, and sortby corresponds to a column with an index, then the copy will be sorted by this index. If you want to ensure a fully sorted order, the index must be a CSI one. A reverse sorted copy can be achieved by specifying a negative value for the step keyword. If sortby is omitted or None, the original table order is used. checkCSI If true and a CSI index does not exist for the sortby column, an error will be raised. If false (the default), it does nothing. You can use this flag in order to explicitly check for the existence of a CSI index. propindexes If true, the existing indexes in the source table are propagated (created) to the new one. If false (the default), the indexes are not propagated. """ return super().copy( newparent, newname, overwrite, createparents, **kwargs) def flush(self): """Flush the table buffers.""" if self._v_file._iswritable(): # Flush rows that remains to be appended if 'row' in self.__dict__: self.row._flush_buffered_rows() if self.indexed and self.autoindex: # Flush any unindexed row rowsadded = self.flush_rows_to_index(_lastrow=True) assert rowsadded <= 0 or self._indexedrows == self.nrows, \ ("internal error: the number of indexed rows (%d) " "and rows in the table (%d) is not equal; " "please report this to the authors." % (self._indexedrows, self.nrows)) if self._dirtyindexes: # Finally, re-index any dirty column self.reindex_dirty() super().flush() def _g_pre_kill_hook(self): """Code to be called before killing the node.""" # Flush the buffers before to clean-up them # self.flush() # It seems that flushing during the __del__ phase is a sure receipt for # bringing all kind of problems: # 1. Illegal Instruction # 2. Malloc(): trying to call free() twice # 3. Bus Error # 4. Segmentation fault # So, the best would be doing *nothing* at all in this __del__ phase. # As a consequence, the I/O will not be cleaned until a call to # Table.flush() would be done. This could lead to a potentially large # memory consumption. # NOTE: The user should make a call to Table.flush() whenever he has # finished working with his table. # I've added a Performance warning in order to compel the user to # call self.flush() before the table is being preempted. # F. Alted 2006-08-03 if (('row' in self.__dict__ and self.row._get_unsaved_nrows() > 0) or (self.indexed and self.autoindex and (self._unsaved_indexedrows > 0 or self._dirtyindexes))): warnings.warn(("table ``%s`` is being preempted from alive nodes " "without its buffers being flushed or with some " "index being dirty. This may lead to very " "ineficient use of resources and even to fatal " "errors in certain situations. Please do a call " "to the .flush() or .reindex_dirty() methods on " "this table before start using other nodes.") % (self._v_pathname), PerformanceWarning) # Get rid of the IO buffers (if they have been created at all) mydict = self.__dict__ if '_v_iobuf' in mydict: del mydict['_v_iobuf'] if '_v_wdflts' in mydict: del mydict['_v_wdflts'] def _f_close(self, flush=True): if not self._v_isopen: return # the node is already closed # .. note:: # # As long as ``Table`` objects access their indices on closing, # ``File.close()`` will need to make *two separate passes* # to first close ``Table`` objects and then ``Index`` hierarchies. # # Flush right now so the row object does not get in the middle. if flush: self.flush() # Some warnings can be issued after calling `self._g_set_location()` # in `self.__init__()`. If warnings are turned into exceptions, # `self._g_post_init_hook` may not be called and `self.cols` not set. # One example of this is # ``test_create.createTestCase.test05_maxFieldsExceeded()``. cols = self.cols if cols is not None: cols._g_close() # Close myself as a leaf. super()._f_close(False) def __repr__(self): """This provides column metainfo in addition to standard __str__""" if self.indexed: format = """\ %s description := %r byteorder := %r chunkshape := %r autoindex := %r colindexes := %r""" return format % (str(self), self.description, self.byteorder, self.chunkshape, self.autoindex, _ColIndexes(self.colindexes)) else: return """\ %s description := %r byteorder := %r chunkshape := %r""" % \ (str(self), self.description, self.byteorder, self.chunkshape) class Cols: """Container for columns in a table or nested column. This class is used as an *accessor* to the columns in a table or nested column. It supports the *natural naming* convention, so that you can access the different columns as attributes which lead to Column instances (for non-nested columns) or other Cols instances (for nested columns). For instance, if table.cols is a Cols instance with a column named col1 under it, the later can be accessed as table.cols.col1. If col1 is nested and contains a col2 column, this can be accessed as table.cols.col1.col2 and so on. Because of natural naming, the names of members start with special prefixes, like in the Group class (see :ref:`GroupClassDescr`). Like the Column class (see :ref:`ColumnClassDescr`), Cols supports item access to read and write ranges of values in the table or nested column. .. rubric:: Cols attributes .. attribute:: _v_colnames A list of the names of the columns hanging directly from the associated table or nested column. The order of the names matches the order of their respective columns in the containing table. .. attribute:: _v_colpathnames A list of the pathnames of all the columns under the associated table or nested column (in preorder). If it does not contain nested columns, this is exactly the same as the :attr:`Cols._v_colnames` attribute. .. attribute:: _v_desc The associated Description instance (see :ref:`DescriptionClassDescr`). """ @property def _v_table(self): """The parent Table instance (see :ref:`TableClassDescr`).""" return self._v__tableFile._get_node(self._v__tablePath) def __init__(self, table, desc): myDict = self.__dict__ myDict['_v__tableFile'] = table._v_file myDict['_v__tablePath'] = table._v_pathname myDict['_v_desc'] = desc myDict['_v_colnames'] = desc._v_names myDict['_v_colpathnames'] = table.description._v_pathnames # Put the column in the local dictionary for name in desc._v_names: if name in desc._v_types: myDict[name] = Column(table, name, desc) else: myDict[name] = Cols(table, desc._v_colobjects[name]) def _g_update_table_location(self, table): """Updates the location information about the associated `table`.""" myDict = self.__dict__ myDict['_v__tableFile'] = table._v_file myDict['_v__tablePath'] = table._v_pathname # Update the locations in individual columns. for colname in self._v_colnames: myDict[colname]._g_update_table_location(table) def __len__(self): """Get the number of top level columns in table.""" return len(self._v_colnames) def _f_col(self, colname): """Get an accessor to the column colname. This method returns a Column instance (see :ref:`ColumnClassDescr`) if the requested column is not nested, and a Cols instance (see :ref:`ColsClassDescr`) if it is. You may use full column pathnames in colname. Calling cols._f_col('col1/col2') is equivalent to using cols.col1.col2. However, the first syntax is more intended for programmatic use. It is also better if you want to access columns with names that are not valid Python identifiers. """ if not isinstance(colname, str): raise TypeError("Parameter can only be an string. You passed " "object: %s" % colname) if ((colname.find('/') > -1 and colname not in self._v_colpathnames) and colname not in self._v_colnames): raise KeyError(("Cols accessor ``%s.cols%s`` does not have a " "column named ``%s``") % (self._v__tablePath, self._v_desc._v_pathname, colname)) return self._g_col(colname) def _g_col(self, colname): """Like `self._f_col()` but it does not check arguments.""" # Get the Column or Description object inames = colname.split('/') cols = self for iname in inames: cols = cols.__dict__[iname] return cols def __getitem__(self, key): """Get a row or a range of rows from a table or nested column. If key argument is an integer, the corresponding nested type row is returned as a record of the current flavor. If key is a slice, the range of rows determined by it is returned as a structured array of the current flavor. Examples -------- :: record = table.cols[4] # equivalent to table[4] recarray = table.cols.Info[4:1000:2] Those statements are equivalent to:: nrecord = table.read(start=4)[0] nrecarray = table.read(start=4, stop=1000, step=2).field('Info') Here you can see how a mix of natural naming, indexing and slicing can be used as shorthands for the :meth:`Table.read` method. """ table = self._v_table nrows = table.nrows if is_idx(key): key = operator.index(key) # Index out of range protection if key >= nrows: raise IndexError("Index out of range") if key < 0: # To support negative values key += nrows (start, stop, step) = table._process_range(key, key + 1, 1) colgroup = self._v_desc._v_pathname if colgroup == "": # The root group return table.read(start, stop, step)[0] else: crecord = table.read(start, stop, step)[0] return crecord[colgroup] elif isinstance(key, slice): (start, stop, step) = table._process_range( key.start, key.stop, key.step) colgroup = self._v_desc._v_pathname if colgroup == "": # The root group return table.read(start, stop, step) else: crecarray = table.read(start, stop, step) if hasattr(crecarray, "field"): return crecarray.field(colgroup) # RecArray case else: return get_nested_field(crecarray, colgroup) # numpy case else: raise TypeError(f"invalid index or slice: {key!r}") def __setitem__(self, key, value): """Set a row or a range of rows in a table or nested column. If key argument is an integer, the corresponding row is set to value. If key is a slice, the range of rows determined by it is set to value. Examples -------- :: table.cols[4] = record table.cols.Info[4:1000:2] = recarray Those statements are equivalent to:: table.modify_rows(4, rows=record) table.modify_column(4, 1000, 2, colname='Info', column=recarray) Here you can see how a mix of natural naming, indexing and slicing can be used as shorthands for the :meth:`Table.modify_rows` and :meth:`Table.modify_column` methods. """ table = self._v_table nrows = table.nrows if is_idx(key): key = operator.index(key) # Index out of range protection if key >= nrows: raise IndexError("Index out of range") if key < 0: # To support negative values key += nrows (start, stop, step) = table._process_range(key, key + 1, 1) elif isinstance(key, slice): (start, stop, step) = table._process_range( key.start, key.stop, key.step) else: raise TypeError(f"invalid index or slice: {key!r}") # Actually modify the correct columns colgroup = self._v_desc._v_pathname if colgroup == "": # The root group table.modify_rows(start, stop, step, rows=value) else: table.modify_column( start, stop, step, colname=colgroup, column=value) def _g_close(self): # First, close the columns (ie possible indices open) for col in self._v_colnames: colobj = self._g_col(col) if isinstance(colobj, Column): colobj.close() # Delete the reference to column del self.__dict__[col] else: colobj._g_close() self.__dict__.clear() def __str__(self): """The string representation for this object.""" # The pathname descpathname = self._v_desc._v_pathname if descpathname: descpathname = "." + descpathname return (f"{self._v__tablePath}.cols{descpathname} " f"({self.__class__.__name__}), " f"{len(self._v_colnames)} columns") def __repr__(self): """A detailed string representation for this object.""" lines = [f'{self!s}'] for name in self._v_colnames: # Get this class name classname = getattr(self, name).__class__.__name__ # The type if name in self._v_desc._v_dtypes: tcol = self._v_desc._v_dtypes[name] # The shape for this column shape = (self._v_table.nrows,) + \ self._v_desc._v_dtypes[name].shape else: tcol = "Description" # Description doesn't have a shape currently shape = () lines.append(f" {name} ({classname}{shape}, {tcol})") return '\n'.join(lines) + '\n' class Column: """Accessor for a non-nested column in a table. Each instance of this class is associated with one *non-nested* column of a table. These instances are mainly used to read and write data from the table columns using item access (like the Cols class - see :ref:`ColsClassDescr`), but there are a few other associated methods to deal with indexes. .. rubric:: Column attributes .. attribute:: descr The Description (see :ref:`DescriptionClassDescr`) instance of the parent table or nested column. .. attribute:: name The name of the associated column. .. attribute:: pathname The complete pathname of the associated column (the same as Column.name if the column is not inside a nested column). Parameters ---------- table The parent table instance name The name of the column that is associated with this object descr The parent description object """ @lazyattr def dtype(self): """The NumPy dtype that most closely matches this column.""" return self.descr._v_dtypes[self.name].base # Get rid of shape info @lazyattr def type(self): """The PyTables type of the column (a string).""" return self.descr._v_types[self.name] @property def table(self): """The parent Table instance (see :ref:`TableClassDescr`).""" return self._table_file._get_node(self._table_path) @property def index(self): """The Index instance (see :ref:`IndexClassDescr`) associated with this column (None if the column is not indexed).""" indexPath = _index_pathname_of_column_(self._table_path, self.pathname) try: index = self._table_file._get_node(indexPath) except NodeError: index = None # The column is not indexed return index @lazyattr def _itemtype(self): return self.descr._v_dtypes[self.name] @property def shape(self): """The shape of this column.""" return (self.table.nrows,) + self.descr._v_dtypes[self.name].shape @property def is_indexed(self): """True if the column is indexed, false otherwise.""" if self.index is None: return False else: return True @property def maindim(self): """"The dimension along which iterators work. Its value is 0 (i.e. the first dimension).""" return 0 def __init__(self, table, name, descr): self._table_file = table._v_file self._table_path = table._v_pathname self.name = name """The name of the associated column.""" self.pathname = descr._v_colobjects[name]._v_pathname """The complete pathname of the associated column (the same as Column.name if the column is not inside a nested column).""" self.descr = descr """The Description (see :ref:`DescriptionClassDescr`) instance of the parent table or nested column.""" def _g_update_table_location(self, table): """Updates the location information about the associated `table`.""" self._table_file = table._v_file self._table_path = table._v_pathname def __len__(self): """Get the number of elements in the column. This matches the length in rows of the parent table. """ return self.table.nrows def __getitem__(self, key): """Get a row or a range of rows from a column. If key argument is an integer, the corresponding element in the column is returned as an object of the current flavor. If key is a slice, the range of elements determined by it is returned as an array of the current flavor. Examples -------- :: print("Column handlers:") for name in table.colnames: print(table.cols._f_col(name)) print("Select table.cols.name[1]-->", table.cols.name[1]) print("Select table.cols.name[1:2]-->", table.cols.name[1:2]) print("Select table.cols.name[:]-->", table.cols.name[:]) print("Select table.cols._f_col('name')[:]-->", table.cols._f_col('name')[:]) The output of this for a certain arbitrary table is:: Column handlers: /table.cols.name (Column(), string, idx=None) /table.cols.lati (Column(), int32, idx=None) /table.cols.longi (Column(), int32, idx=None) /table.cols.vector (Column(2,), int32, idx=None) /table.cols.matrix2D (Column(2, 2), float64, idx=None) Select table.cols.name[1]--> Particle: 11 Select table.cols.name[1:2]--> ['Particle: 11'] Select table.cols.name[:]--> ['Particle: 10' 'Particle: 11' 'Particle: 12' 'Particle: 13' 'Particle: 14'] Select table.cols._f_col('name')[:]--> ['Particle: 10' 'Particle: 11' 'Particle: 12' 'Particle: 13' 'Particle: 14'] See the :file:`examples/table2.py` file for a more complete example. """ table = self.table # Generalized key support not there yet, but at least allow # for a tuple with one single element (the main dimension). # (key,) --> key if isinstance(key, tuple) and len(key) == 1: key = key[0] if is_idx(key): key = operator.index(key) # Index out of range protection if key >= table.nrows: raise IndexError("Index out of range") if key < 0: # To support negative values key += table.nrows (start, stop, step) = table._process_range(key, key + 1, 1) return table.read(start, stop, step, self.pathname)[0] elif isinstance(key, slice): (start, stop, step) = table._process_range( key.start, key.stop, key.step) return table.read(start, stop, step, self.pathname) else: raise TypeError( "'%s' key type is not valid in this context" % key) def __iter__(self): """Iterate through all items in the column.""" table = self.table itemsize = self.dtype.itemsize nrowsinbuf = table._v_file.params['IO_BUFFER_SIZE'] // itemsize buf = np.empty((nrowsinbuf, ), self._itemtype) max_row = len(self) for start_row in range(0, len(self), nrowsinbuf): end_row = min(start_row + nrowsinbuf, max_row) buf_slice = buf[0:end_row - start_row] table.read(start_row, end_row, 1, field=self.pathname, out=buf_slice) yield from buf_slice def __setitem__(self, key, value): """Set a row or a range of rows in a column. If key argument is an integer, the corresponding element is set to value. If key is a slice, the range of elements determined by it is set to value. Examples -------- :: # Modify row 1 table.cols.col1[1] = -1 # Modify rows 1 and 3 table.cols.col1[1::2] = [2,3] Which is equivalent to:: # Modify row 1 table.modify_columns(start=1, columns=[[-1]], names=['col1']) # Modify rows 1 and 3 columns = numpy.rec.fromarrays([[2,3]], formats='i4') table.modify_columns(start=1, step=2, columns=columns, names=['col1']) """ table = self.table table._v_file._check_writable() # Generalized key support not there yet, but at least allow # for a tuple with one single element (the main dimension). # (key,) --> key if isinstance(key, tuple) and len(key) == 1: key = key[0] if is_idx(key): key = operator.index(key) # Index out of range protection if key >= table.nrows: raise IndexError("Index out of range") if key < 0: # To support negative values key += table.nrows return table.modify_column(key, key + 1, 1, [[value]], self.pathname) elif isinstance(key, slice): (start, stop, step) = table._process_range( key.start, key.stop, key.step) return table.modify_column(start, stop, step, value, self.pathname) else: raise ValueError("Non-valid index or slice: %s" % key) def create_index(self, optlevel=6, kind="medium", filters=None, tmp_dir=None, _blocksizes=None, _testmode=False, _verbose=False): """Create an index for this column. .. warning:: In some situations it is useful to get a completely sorted index (CSI). For those cases, it is best to use the :meth:`Column.create_csindex` method instead. Parameters ---------- optlevel : int The optimization level for building the index. The levels ranges from 0 (no optimization) up to 9 (maximum optimization). Higher levels of optimization mean better chances for reducing the entropy of the index at the price of using more CPU, memory and I/O resources for creating the index. kind : str The kind of the index to be built. It can take the 'ultralight', 'light', 'medium' or 'full' values. Lighter kinds ('ultralight' and 'light') mean that the index takes less space on disk, but will perform queries slower. Heavier kinds ('medium' and 'full') mean better chances for reducing the entropy of the index (increasing the query speed) at the price of using more disk space as well as more CPU, memory and I/O resources for creating the index. Note that selecting a full kind with an optlevel of 9 (the maximum) guarantees the creation of an index with zero entropy, that is, a completely sorted index (CSI) - provided that the number of rows in the table does not exceed the 2**48 figure (that is more than 100 trillions of rows). See :meth:`Column.create_csindex` method for a more direct way to create a CSI index. filters : Filters Specify the Filters instance used to compress the index. If None, default index filters will be used (currently, zlib level 1 with shuffling). tmp_dir When kind is other than 'ultralight', a temporary file is created during the index build process. You can use the tmp_dir argument to specify the directory for this temporary file. The default is to create it in the same directory as the file containing the original table. """ kinds = ['ultralight', 'light', 'medium', 'full'] if kind not in kinds: raise ValueError("Kind must have any of these values: %s" % kinds) if (not isinstance(optlevel, int) or (optlevel < 0 or optlevel > 9)): raise ValueError("Optimization level must be an integer in the " "range 0-9") if filters is None: filters = default_index_filters if tmp_dir is None: tmp_dir = str(Path(self._table_file.filename).parent) else: if not Path(tmp_dir).is_dir(): raise ValueError( f"Temporary directory '{tmp_dir}' does not exist" ) if (_blocksizes is not None and (not isinstance(_blocksizes, tuple) or len(_blocksizes) != 4)): raise ValueError("_blocksizes must be a tuple with exactly 4 " "elements") idxrows = _column__create_index(self, optlevel, kind, filters, tmp_dir, _blocksizes, _verbose) return SizeType(idxrows) def create_csindex(self, filters=None, tmp_dir=None, _blocksizes=None, _testmode=False, _verbose=False): """Create a completely sorted index (CSI) for this column. This method guarantees the creation of an index with zero entropy, that is, a completely sorted index (CSI) -- provided that the number of rows in the table does not exceed the 2**48 figure (that is more than 100 trillions of rows). A CSI index is needed for some table methods (like :meth:`Table.itersorted` or :meth:`Table.read_sorted`) in order to ensure completely sorted results. For the meaning of filters and tmp_dir arguments see :meth:`Column.create_index`. Notes ----- This method is equivalent to Column.create_index(optlevel=9, kind='full', ...). """ return self.create_index( kind='full', optlevel=9, filters=filters, tmp_dir=tmp_dir, _blocksizes=_blocksizes, _testmode=_testmode, _verbose=_verbose) def _do_reindex(self, dirty): """Common code for reindex() and reindex_dirty() codes.""" index = self.index dodirty = True if dirty and not index.dirty: dodirty = False if index is not None and dodirty: self._table_file._check_writable() # Get the old index parameters kind = index.kind optlevel = index.optlevel filters = index.filters # We *need* to tell the index that it is going to be undirty. # This is needed here so as to unnail() the condition cache. index.dirty = False # Delete the existing Index index._f_remove() # Create a new Index with the previous parameters return SizeType(self.create_index( kind=kind, optlevel=optlevel, filters=filters)) else: return SizeType(0) # The column is not intended for indexing def reindex(self): """Recompute the index associated with this column. This can be useful when you suspect that, for any reason, the index information is no longer valid and you want to rebuild it. This method does nothing if the column is not indexed. """ self._do_reindex(dirty=False) def reindex_dirty(self): """Recompute the associated index only if it is dirty. This can be useful when you have set :attr:`Table.autoindex` to false for the table and you want to update the column's index after an invalidating index operation (like :meth:`Table.remove_rows`). This method does nothing if the column is not indexed. """ self._do_reindex(dirty=True) def remove_index(self): """Remove the index associated with this column. This method does nothing if the column is not indexed. The removed index can be created again by calling the :meth:`Column.create_index` method. """ self._table_file._check_writable() # Remove the index if existing. if self.is_indexed: index = self.index index._f_remove() self.table._set_column_indexing(self.pathname, False) def close(self): """Close this column.""" self.__dict__.clear() def __str__(self): """The string representation for this object.""" return (f"{self._table_path}.cols.{self.pathname.replace('/', '.')} " f"({self.__class__.__name__}{self.shape}, " f"{self.descr._v_types[self.name]}, idx={self.index})") def __repr__(self): """A detailed string representation for this object.""" return str(self) PyTables-3.7.0/tables/tableextension.pyx000066400000000000000000001643011416254111300203070ustar00rootroot00000000000000######################################################################## # # License: BSD # Created: June 17, 2005 # Author: Francesc Alted - faltet@pytables.com # # $Id$ # ######################################################################## """Here is where Table and Row extension types live. Classes (type extensions): Table Row Functions: Misc variables: """ import sys import numpy from time import time from .description import Col from .exceptions import HDF5ExtError from .conditions import call_on_recarr from .utilsextension import (get_nested_field, atom_from_hdf5_type, create_nested_type, hdf5_to_np_ext_type, create_nested_type, platform_byteorder, pttype_to_hdf5, pt_special_kinds, npext_prefixes_to_ptkinds, hdf5_class_to_string, H5T_STD_I64) from .utils import SizeType from .utilsextension cimport get_native_type, cstr_to_pystr # numpy functions & objects from hdf5extension cimport Leaf from cpython cimport PyErr_Clear from libc.stdio cimport snprintf from libc.stdlib cimport malloc, free from libc.string cimport memcpy, strdup, strcmp, strlen from numpy cimport (import_array, ndarray, npy_intp, PyArray_GETITEM, PyArray_SETITEM, PyArray_BYTES, PyArray_DATA, PyArray_NDIM, PyArray_STRIDE) from .definitions cimport (hid_t, herr_t, hsize_t, htri_t, hbool_t, H5F_ACC_RDONLY, H5P_DEFAULT, H5D_CHUNKED, H5T_DIR_DEFAULT, H5F_SCOPE_LOCAL, H5F_SCOPE_GLOBAL, H5T_COMPOUND, H5Tget_order, H5Fflush, H5Dget_create_plist, H5T_ORDER_LE, H5D_layout_t, H5Dopen, H5Dclose, H5Dread, H5Dget_type, H5Dget_space, H5Pget_layout, H5Pget_chunk, H5Pclose, H5Sget_simple_extent_ndims, H5Sget_simple_extent_dims, H5Sclose, H5T_class_t, H5Tget_size, H5Tset_size, H5Tcreate, H5Tcopy, H5Tclose, H5Tget_nmembers, H5Tget_member_name, H5Tget_member_type, H5Tget_native_type, H5Tget_member_offset, H5Tinsert, H5Tget_class, H5Tget_super, H5Tget_offset, H5T_cset_t, H5T_CSET_ASCII, H5T_CSET_UTF8, H5ATTRset_attribute_string, H5ATTRset_attribute, get_len_of_range, get_order, set_order, is_complex, conv_float64_timeval32, truncate_dset, pt_H5free_memory) from .lrucacheextension cimport ObjectCache, NumCache #----------------------------------------------------------------- # Optimized HDF5 API for PyTables cdef extern from "H5TB-opt.h" nogil: herr_t H5TBOmake_table( char *table_title, hid_t loc_id, char *dset_name, char *version, char *class_, hid_t mem_type_id, hsize_t nrecords, hsize_t chunk_size, void *fill_data, int compress, char *complib, int shuffle, int fletcher32, hbool_t track_times, void *data ) herr_t H5TBOread_records( hid_t dataset_id, hid_t mem_type_id, hsize_t start, hsize_t nrecords, void *data ) herr_t H5TBOread_elements( hid_t dataset_id, hid_t mem_type_id, hsize_t nrecords, void *coords, void *data ) herr_t H5TBOappend_records( hid_t dataset_id, hid_t mem_type_id, hsize_t nrecords, hsize_t nrecords_orig, void *data ) herr_t H5TBOwrite_records ( hid_t dataset_id, hid_t mem_type_id, hsize_t start, hsize_t nrecords, hsize_t step, void *data ) herr_t H5TBOwrite_elements( hid_t dataset_id, hid_t mem_type_id, hsize_t nrecords, void *coords, void *data ) herr_t H5TBOdelete_records( hid_t dataset_id, hid_t mem_type_id, hsize_t ntotal_records, size_t src_size, hsize_t start, hsize_t nrecords, hsize_t maxtuples ) #---------------------------------------------------------------------------- # Initialization code # The numpy API requires this function to be called before # using any numpy facilities in an extension module. import_array() #------------------------------------------------------------- # Private functions cdef get_nested_field_cache(recarray, fieldname, fieldcache): """Get the maybe nested field named `fieldname` from the `recarray`. The `fieldname` may be a simple field name or a nested field name with slah-separated components. It can also be an integer specifying the position of the field. """ try: field = fieldcache[fieldname] except KeyError: # Check whether fieldname is an integer and if so, get the field # straight from the recarray dictionary (it can't be anywhere else) if isinstance(fieldname, int): field = recarray[fieldname] else: field = get_nested_field(recarray, fieldname) fieldcache[fieldname] = field return field cdef join_path(object parent, object name): if parent == "": return name else: return parent + '/' + name # Public classes cdef class Table(Leaf): # instance variables cdef void *wbuf def _create_table(self, title, complib, obversion): cdef int offset cdef int ret cdef long buflen cdef hid_t oid cdef void *data cdef hsize_t nrows cdef bytes class_ cdef ndarray wdflts cdef void *fill_data cdef ndarray recarr cdef object name cdef bytes encoded_title, encoded_complib, encoded_obversion cdef char *ctitle = NULL cdef char *cobversion = NULL cdef bytes encoded_name cdef char fieldname[128] cdef int i cdef H5T_cset_t cset = H5T_CSET_ASCII encoded_title = title.encode('utf-8') encoded_complib = complib.encode('utf-8') encoded_obversion = obversion.encode('utf-8') encoded_name = self.name.encode('utf-8') # Get the C pointer ctitle = encoded_title cobversion = encoded_obversion # Compute the complete compound datatype based on the table description self.disk_type_id = create_nested_type(self.description, self.byteorder) #self.type_id = H5Tcopy(self.disk_type_id) # A H5Tcopy only is not enough, as we want the in-memory type to be # in the byteorder of the machine (sys.byteorder). self.type_id = create_nested_type(self.description, sys.byteorder) # The fill values area wdflts = self._v_wdflts if wdflts is None: fill_data = NULL else: fill_data = PyArray_DATA(wdflts) # test if there is data to be saved initially if self._v_recarray is not None: recarr = self._v_recarray data = PyArray_DATA(recarr) else: data = NULL class_ = self._c_classid.encode('utf-8') self.dataset_id = H5TBOmake_table(ctitle, self.parent_id, encoded_name, cobversion, class_, self.disk_type_id, self.nrows, self.chunkshape[0], fill_data, self.filters.complevel, encoded_complib, self.filters.shuffle_bitshuffle, self.filters.fletcher32, self._want_track_times, data) if self.dataset_id < 0: raise HDF5ExtError("Problems creating the table") if self._v_file.params['PYTABLES_SYS_ATTRS']: cset = H5T_CSET_UTF8 # Set the conforming table attributes # Attach the CLASS attribute ret = H5ATTRset_attribute_string(self.dataset_id, "CLASS", class_, len(class_), cset) if ret < 0: raise HDF5ExtError("Can't set attribute '%s' in table:\n %s." % ("CLASS", self.name)) # Attach the VERSION attribute ret = H5ATTRset_attribute_string(self.dataset_id, "VERSION", cobversion, len(encoded_obversion), cset) if ret < 0: raise HDF5ExtError("Can't set attribute '%s' in table:\n %s." % ("VERSION", self.name)) # Attach the TITLE attribute ret = H5ATTRset_attribute_string(self.dataset_id, "TITLE", ctitle, len(encoded_title), cset) if ret < 0: raise HDF5ExtError("Can't set attribute '%s' in table:\n %s." % ("TITLE", self.name)) # Attach the NROWS attribute nrows = self.nrows ret = H5ATTRset_attribute(self.dataset_id, "NROWS", H5T_STD_I64, 0, NULL, &nrows) if ret < 0: raise HDF5ExtError("Can't set attribute '%s' in table:\n %s." % ("NROWS", self.name)) # Attach the FIELD_N_NAME attributes # We write only the first level names for i, name in enumerate(self.description._v_names): snprintf(fieldname, 128, "FIELD_%d_NAME", i) encoded_name = name.encode('utf-8') ret = H5ATTRset_attribute_string(self.dataset_id, fieldname, encoded_name, len(encoded_name), cset) if ret < 0: raise HDF5ExtError("Can't set attribute '%s' in table:\n %s." % (fieldname, self.name)) # If created in PyTables, the table is always chunked self._chunked = True # Accessible from python # Finally, return the object identifier. return self.dataset_id cdef get_nested_type(self, hid_t type_id, hid_t native_type_id, object colpath, object field_byteorders): """Open a nested type and return a nested dictionary as description.""" cdef hid_t member_type_id, native_member_type_id, member_offset cdef hsize_t nfields, i cdef hsize_t dims[1] cdef size_t itemsize cdef char *c_colname cdef H5T_class_t class_id cdef char c_byteorder2[11] # "irrelevant" fits easily here cdef char *sys_byteorder cdef object desc, colobj, colpath2, typeclassname, typeclass cdef object byteorder cdef str colname, byteorder2 offset = 0 desc = {} # Get the number of members nfields = H5Tget_nmembers(type_id) # Iterate through fields to get the correct order that elements may appear in # The object type can be stored not in order, so order based on the offset in the data position_order = [] for i in range(nfields): member_offset = H5Tget_member_offset(type_id, i) position_order.append((member_offset, i)) position_order.sort() # Iterate thru the members for pos, i in enumerate([x[1] for x in position_order]): # Get the member name c_colname = H5Tget_member_name(type_id, i) colname = cstr_to_pystr(c_colname) # Get the member type member_type_id = H5Tget_member_type(type_id, i) # Get the member offset member_offset = H5Tget_member_offset(type_id, i) # Get the HDF5 class class_id = H5Tget_class(member_type_id) if class_id == H5T_COMPOUND and not is_complex(member_type_id): colpath2 = join_path(colpath, colname) # Create the native data in-memory itemsize = H5Tget_size(member_type_id) native_member_type_id = H5Tcreate(H5T_COMPOUND, itemsize) desc[colname], itemsize = self.get_nested_type( member_type_id, native_member_type_id, colpath2, field_byteorders) desc[colname]["_v_pos"] = pos desc[colname]["_v_offset"] = member_offset else: # Get the member format and the corresponding Col object try: native_member_type_id = get_native_type(member_type_id) atom = atom_from_hdf5_type(native_member_type_id) colobj = Col.from_atom(atom, pos=pos, _offset=member_offset) itemsize = H5Tget_size(native_member_type_id) except TypeError, te: # Re-raise TypeError again with more info raise TypeError( ("table ``%s``, column ``%s``: %%s" % (self.name, colname)) % te.args[0]) desc[colname] = colobj # For time kinds, save the byteorder of the column # (useful for conversion of time datatypes later on) if colobj.kind == "time": colobj._byteorder = H5Tget_order(member_type_id) if colobj._byteorder == H5T_ORDER_LE: field_byteorders.append("little") else: field_byteorders.append("big") elif colobj.kind in ['int', 'uint', 'float', 'complex', 'enum']: # Keep track of the byteorder for this column get_order(member_type_id, c_byteorder2) byteorder2 = cstr_to_pystr(c_byteorder2) if byteorder2 in ["little", "big"]: field_byteorders.append(byteorder2) # Insert the native member H5Tinsert(native_type_id, c_colname, member_offset, native_member_type_id) # Update the offset offset = offset + itemsize # Release resources H5Tclose(native_member_type_id) H5Tclose(member_type_id) pt_H5free_memory(c_colname) # set the byteorder and other things (just in top level) if colpath == "": # Compute a byteorder for the entire table if len(field_byteorders) > 0: field_byteorders = numpy.array(field_byteorders) # Cython doesn't interpret well the extended comparison # operators so this: field_byteorders == "little" doesn't work # as expected if numpy.alltrue(field_byteorders.__eq__("little")): byteorder = "little" elif numpy.alltrue(field_byteorders.__eq__("big")): byteorder = "big" else: # Yes! someone has done it! byteorder = "mixed" else: byteorder = "irrelevant" self.byteorder = byteorder return desc, offset def _get_info(self): """Get info from a table on disk.""" cdef hid_t space_id, plist cdef size_t type_size, size2 cdef hsize_t dims[1] # enough for unidimensional tables cdef hsize_t chunksize[1] cdef H5D_layout_t layout cdef bytes encoded_name encoded_name = self.name.encode('utf-8') # Open the dataset self.dataset_id = H5Dopen(self.parent_id, encoded_name, H5P_DEFAULT) if self.dataset_id < 0: raise HDF5ExtError("Non-existing node ``%s`` under ``%s``" % (self.name, self._v_parent._v_pathname)) # Get the datatype on disk self.disk_type_id = H5Dget_type(self.dataset_id) if H5Tget_class(self.disk_type_id) != H5T_COMPOUND: raise ValueError("Node ``%s`` is not a Table object" % (self._v_parent._v_leaves[self.name]._v_pathname)) # Get the number of rows space_id = H5Dget_space(self.dataset_id) H5Sget_simple_extent_dims(space_id, dims, NULL) self.nrows = SizeType(dims[0]) # Free resources H5Sclose(space_id) # Get the layout of the datatype plist = H5Dget_create_plist(self.dataset_id) layout = H5Pget_layout(plist) if layout == H5D_CHUNKED: self._chunked = 1 # Get the chunksize H5Pget_chunk(plist, 1, chunksize) else: self._chunked = 0 chunksize[0] = 0 H5Pclose(plist) # Get the type size type_size = H5Tget_size(self.disk_type_id) # Create the native data in-memory self.type_id = H5Tcreate(H5T_COMPOUND, type_size) # Fill-up the (nested) native type and description desc, offset = self.get_nested_type(self.disk_type_id, self.type_id, "", []) if desc == {}: raise HDF5ExtError("Problems getting desciption for table %s", self.name) if offset < type_size: # Trailing padding, set the itemsize to the correct type_size (see #765) desc['_v_itemsize'] = type_size # Return the object ID and the description return (self.dataset_id, desc, SizeType(chunksize[0])) cdef _convert_time64_(self, ndarray nparr, hsize_t nrecords, int sense): """Converts a NumPy of Time64 elements between NumPy and HDF5 formats. NumPy to HDF5 conversion is performed when 'sense' is 0. Otherwise, HDF5 to NumPy conversion is performed. The conversion is done in place, i.e. 'nparr' is modified. """ cdef void *t64buf cdef long byteoffset cdef npy_intp bytestride, nelements byteoffset = 0 # NumPy objects doesn't have an offset bytestride = PyArray_STRIDE(nparr, 0) # supports multi-dimensional recarray # Compute the number of elements in the multidimensional cell nelements = nparr.size // len(nparr) t64buf = PyArray_DATA(nparr) conv_float64_timeval32( t64buf, byteoffset, bytestride, nrecords, nelements, sense) cpdef _convert_types(self, ndarray recarr, hsize_t nrecords, int sense): """Converts columns in 'recarr' between NumPy and HDF5 formats. NumPy to HDF5 conversion is performed when 'sense' is 0. Otherwise, HDF5 to NumPy conversion is performed. The conversion is done in place, i.e. 'recarr' is modified. """ # For reading, first swap the byteorder by hand # (this is not currently supported by HDF5) if sense == 1: for colpathname in self.colpathnames: if self.coltypes[colpathname] in ["time32", "time64"]: colobj = self.coldescrs[colpathname] if hasattr(colobj, "_byteorder"): if colobj._byteorder != platform_byteorder: column = get_nested_field(recarr, colpathname) # Do an *inplace* byteswapping column.byteswap(True) # This should be generalised to support other type conversions. for t64cname in self._time64colnames: column = get_nested_field(recarr, t64cname) self._convert_time64_(column, nrecords, sense) def _open_append(self, ndarray recarr): self._v_recarray = recarr # Get the pointer to the buffer data area self.wbuf = PyArray_DATA(recarr) def _append_records(self, hsize_t nrecords): cdef int ret cdef hsize_t nrows # Convert some NumPy types to HDF5 before storing. self._convert_types(self._v_recarray, nrecords, 0) nrows = self.nrows # release GIL (allow other threads to use the Python interpreter) with nogil: # Append the records: ret = H5TBOappend_records(self.dataset_id, self.type_id, nrecords, nrows, self.wbuf) if ret < 0: raise HDF5ExtError("Problems appending the records.") self.nrows = self.nrows + nrecords def _close_append(self): cdef hsize_t nrows if self._v_file.params['PYTABLES_SYS_ATTRS']: # Update the NROWS attribute nrows = self.nrows if (H5ATTRset_attribute(self.dataset_id, "NROWS", H5T_STD_I64, 0, NULL, &nrows) < 0): raise HDF5ExtError("Problems setting the NROWS attribute.") # Set the caches to dirty (in fact, and for the append case, # it should be only the caches based on limits, but anyway) self._dirtycache = True # Delete the reference to recarray as we doesn't need it anymore self._v_recarray = None def _update_records(self, hsize_t start, hsize_t stop, hsize_t step, ndarray recarr): cdef herr_t ret cdef void *rbuf cdef hsize_t nrecords, nrows # Get the pointer to the buffer data area rbuf = PyArray_DATA(recarr) # Compute the number of records to update nrecords = len(recarr) nrows = get_len_of_range(start, stop, step) if nrecords > nrows: nrecords = nrows # Convert some NumPy types to HDF5 before storing. self._convert_types(recarr, nrecords, 0) # Update the records: with nogil: ret = H5TBOwrite_records(self.dataset_id, self.type_id, start, nrecords, step, rbuf ) if ret < 0: raise HDF5ExtError("Problems updating the records.") # Set the caches to dirty self._dirtycache = True def _update_elements(self, hsize_t nrecords, ndarray coords, ndarray recarr): cdef herr_t ret cdef void *rbuf cdef void *rcoords # Get the chunk of the coords that correspond to a buffer rcoords = PyArray_DATA(coords) # Get the pointer to the buffer data area rbuf = PyArray_DATA(recarr) # Convert some NumPy types to HDF5 before storing. self._convert_types(recarr, nrecords, 0) # Update the records: with nogil: ret = H5TBOwrite_elements(self.dataset_id, self.type_id, nrecords, rcoords, rbuf) if ret < 0: raise HDF5ExtError("Problems updating the records.") # Set the caches to dirty self._dirtycache = True def _read_records(self, hsize_t start, hsize_t nrecords, ndarray recarr): cdef void *rbuf cdef int ret # Correct the number of records to read, if needed if (start + nrecords) > self.nrows: nrecords = self.nrows - start # Get the pointer to the buffer data area rbuf = PyArray_DATA(recarr) # Read the records from disk with nogil: ret = H5TBOread_records(self.dataset_id, self.type_id, start, nrecords, rbuf) if ret < 0: raise HDF5ExtError("Problems reading records.") # Convert some HDF5 types to NumPy after reading. self._convert_types(recarr, nrecords, 1) return nrecords cdef hsize_t _read_chunk(self, hsize_t nchunk, ndarray iobuf, long cstart): cdef long nslot cdef hsize_t start, nrecords, chunkshape cdef int ret cdef void *rbuf cdef NumCache chunkcache chunkcache = self._chunkcache chunkshape = chunkcache.slotsize # Correct the number of records to read, if needed start = nchunk*chunkshape nrecords = chunkshape if (start + nrecords) > self.nrows: nrecords = self.nrows - start rbuf = PyArray_BYTES(iobuf) + cstart * chunkcache.itemsize # Try to see if the chunk is in cache nslot = chunkcache.getslot_(nchunk) if nslot >= 0: chunkcache.getitem_(nslot, rbuf, 0) else: # Chunk is not in cache. Read it and put it in the LRU cache. with nogil: ret = H5TBOread_records(self.dataset_id, self.type_id, start, nrecords, rbuf) if ret < 0: raise HDF5ExtError("Problems reading chunk records.") nslot = chunkcache.setitem_(nchunk, rbuf, 0) return nrecords def _read_elements(self, ndarray coords, ndarray recarr): cdef long nrecords cdef void *rbuf cdef void *rbuf2 cdef int ret # Get the chunk of the coords that correspond to a buffer nrecords = coords.size # Get the pointer to the buffer data area rbuf = PyArray_DATA(recarr) # Get the pointer to the buffer coords area rbuf2 = PyArray_DATA(coords) with nogil: ret = H5TBOread_elements(self.dataset_id, self.type_id, nrecords, rbuf2, rbuf) if ret < 0: raise HDF5ExtError("Problems reading records.") # Convert some HDF5 types to NumPy after reading. self._convert_types(recarr, nrecords, 1) return nrecords def _remove_rows(self, hsize_t start, hsize_t stop, long step): cdef size_t rowsize cdef hsize_t nrecords=0, nrecords2 cdef hsize_t i if step == 1: nrecords = stop - start rowsize = self.rowsize # Using self.disk_type_id should be faster (i.e. less conversions) if (H5TBOdelete_records(self.dataset_id, self.disk_type_id, self.nrows, rowsize, start, nrecords, self.nrowsinbuf) < 0): raise HDF5ExtError("Problems deleting records.") self.nrows = self.nrows - nrecords if self._v_file.params['PYTABLES_SYS_ATTRS']: # Attach the NROWS attribute nrecords2 = self.nrows H5ATTRset_attribute(self.dataset_id, "NROWS", H5T_STD_I64, 0, NULL, &nrecords2) # Set the caches to dirty self._dirtycache = True elif step == -1: nrecords = self._remove_rows(stop+1, start+1, 1) elif step >= 1: # always want to go through the space backwards for i in range(stop - step, start - step, -step): nrecords += self._remove_rows(i, i+1, 1) elif step <= -1: # always want to go through the space backwards for i in range(start, stop, step): nrecords += self._remove_rows(i, i+1, 1) else: raise ValueError("step size may not be 0.") # Return the number of records removed return nrecords cdef class Row: """Table row iterator and field accessor. Instances of this class are used to fetch and set the values of individual table fields. It works very much like a dictionary, where keys are the pathnames or positions (extended slicing is supported) of the fields in the associated table in a specific row. This class provides an *iterator interface* so that you can use the same Row instance to access successive table rows one after the other. There are also some important methods that are useful for accessing, adding and modifying values in tables. .. rubric:: Row attributes .. attribute:: nrow The current row number. This property is useful for knowing which row is being dealt with in the middle of a loop or iterator. """ cdef npy_intp _stride cdef long _row, _unsaved_nrows, _mod_nrows cdef hsize_t start, absstep cdef long long stop, step, nextelement, _nrow, stopb # has to be long long, not hsize_t, for negative step sizes cdef hsize_t nrowsinbuf, nrows, nrowsread cdef hsize_t chunksize, nchunksinbuf, totalchunks cdef hsize_t startb, lenbuf cdef long long indexchunk cdef int bufcounter, counter cdef int exist_enum_cols cdef int _riterator, _rowsize, _write_to_seqcache cdef int wherecond, indexed cdef int ro_filemode, chunked cdef int _bufferinfo_done, sss_on cdef int iterseq_max_elements cdef ndarray bufcoords, indexvalid, indexvalues, chunkmap cdef hsize_t *bufcoords_data cdef hsize_t *index_values_data cdef char *chunkmap_data cdef char *index_valid_data cdef object dtype cdef object iobuf, iobufcpy cdef object wrec, wreccpy cdef object wfields, rfields cdef object coords cdef object condfunc, condargs, condkwargs cdef object mod_elements, colenums cdef object rfieldscache, wfieldscache cdef object iterseq cdef object _table_file, _table_path cdef object modified_fields cdef object seqcache_key # The nrow() method has been converted into a property, which is handier property nrow: """The current row number. This property is useful for knowing which row is being dealt with in the middle of a loop or iterator. """ def __get__(self): return SizeType(self._nrow) property table: def __get__(self): self._table_file._check_open() return self._table_file._get_node(self._table_path) def __cinit__(self, table): cdef int nfields, i # Location-dependent information. self._table_file = table._v_file self._table_path = table._v_pathname self._unsaved_nrows = 0 self._mod_nrows = 0 self._row = 0 self._nrow = 0 # Useful in mod_append read iterators self._riterator = 0 self._bufferinfo_done = 0 # Some variables from table will be cached here if table._v_file.mode == 'r': self.ro_filemode = 1 else: self.ro_filemode = 0 self.chunked = table._chunked self.colenums = table._colenums self.exist_enum_cols = len(self.colenums) self.nrowsinbuf = table.nrowsinbuf self.chunksize = table.chunkshape[0] self.nchunksinbuf = self.nrowsinbuf // self.chunksize self.dtype = table._v_dtype self._new_buffer(table) self.mod_elements = None self.rfieldscache = {} self.wfieldscache = {} self.modified_fields = set() def _iter(self, start=0, stop=0, step=1, coords=None, chunkmap=None): """Return an iterator for traversiong the data in table.""" self._init_loop(start, stop, step, coords, chunkmap) return iter(self) def __iter__(self): """Iterator that traverses all the data in the Table""" return self cdef _new_buffer(self, table): """Create the recarrays for I/O buffering""" wdflts = table._v_wdflts if wdflts is None: self.wrec = numpy.zeros(1, dtype=self.dtype) # Defaults are zero else: self.wrec = table._v_wdflts.copy() self.wreccpy = self.wrec.copy() # A copy of the defaults # Build the wfields dictionary for faster access to columns self.wfields = {} for name in self.dtype.names: self.wfields[name] = self.wrec[name] # Get the read buffer for this instance (it is private, remember!) buff = self.iobuf = table._get_container(self.nrowsinbuf) # Build the rfields dictionary for faster access to columns # This is quite fast, as it only takes around 5 us per column # in my laptop (Pentium 4 @ 2 GHz). # F. Alted 2006-08-18 self.rfields = {} for i, name in enumerate(self.dtype.names): self.rfields[i] = buff[name] self.rfields[name] = buff[name] # Get the stride of these buffers self._stride = PyArray_STRIDE(buff, 0) # The rowsize self._rowsize = self.dtype.itemsize self.nrows = table.nrows # This value may change cdef _init_loop(self, hsize_t start, long long stop, long long step, object coords, object chunkmap): """Initialization for the __iter__ iterator""" table = self.table self._riterator = 1 # We are inside a read iterator self.start = start self.stop = stop self.step = step self.coords = coords self.startb = 0 if step > 0: self._row = -1 # a sentinel self.nrowsread = start elif step < 0: self._row = 0 self.nrowsread = 0 self.nextelement = start self._nrow = start - self.step self.wherecond = 0 self.indexed = 0 self.nrows = table.nrows # Update the row counter if coords is not None and 0 < step: self.nrowsread = start self.nextelement = start self.stop = min(stop, len(coords)) self.absstep = abs(step) return elif coords is not None and 0 > step: #self.nrowsread = 0 #self.nextelement = start #self.stop = min(stop, len(coords)) #self.stop = max(stop, start - len(coords)) self.absstep = abs(step) return if table._where_condition: self.wherecond = 1 #self.condkwargs = {'ex_uses_vml': True} self.condfunc, self.condargs, self.condkwargs = table._where_condition table._where_condition = None if table._use_index: # Indexing code depends on this condition (see #319) assert self.nrowsinbuf % self.chunksize == 0 self.indexed = 1 # Compute totalchunks here because self.nrows can change during the # life of a Row instance. self.totalchunks = self.nrows // self.chunksize if self.nrows % self.chunksize: self.totalchunks = self.totalchunks + 1 self.nrowsread = 0 self.nextelement = 0 self.chunkmap = chunkmap self.chunkmap_data = PyArray_BYTES(self.chunkmap) table._use_index = False self.lenbuf = self.nrowsinbuf # Check if we have limitations on start, stop, step self.sss_on = (self.start > 0 or self.stop < self.nrows or self.step > 1) self.seqcache_key = table._seqcache_key table._seqcache_key = None if self.seqcache_key is not None: self._write_to_seqcache = 1 self.iterseq_max_elements = table._v_file.params['ITERSEQ_MAX_ELEMENTS'] self.iterseq = [] # all the row indexes, unless it would be longer than ITERSEQ_MAX_ELEMENTS else: self._write_to_seqcache = 0 self.iterseq = None def __next__(self): """next() method for __iter__() that is called on each iteration""" if not self._riterator: # The iterator is already exhausted! raise StopIteration if self.indexed: return self.__next__indexed() elif self.coords is not None: return self.__next__coords() elif self.wherecond: return self.__next__inkernel() else: return self.__next__general() cdef __next__indexed(self): """The version of next() for indexed columns and a chunkmap.""" cdef long recout, j, cs, vlen, rowsize cdef hsize_t nchunksread cdef object tmp_range cdef Table table cdef ndarray iobuf cdef void *IObufData cdef long nslot cdef object seq cdef object seqcache assert self.nrowsinbuf >= self.chunksize while self.nextelement < self.stop: if self.nextelement >= self.nrowsread: # Skip until there is interesting information while self.start > self.nrowsread + self.nrowsinbuf: self.nrowsread = self.nrowsread + self.nrowsinbuf self.nextelement = self.nextelement + self.nrowsinbuf table = self.table iobuf = self.iobuf j = 0; recout = 0; cs = self.chunksize nchunksread = self.nrowsread // cs tmp_range = numpy.arange(0, cs, dtype='int64') self.bufcoords = numpy.empty(self.nrowsinbuf, dtype='int64') # Fetch valid chunks until the I/O buffer is full while nchunksread < self.totalchunks: if self.chunkmap_data[nchunksread]: self.bufcoords[j*cs:(j+1)*cs] = tmp_range + self.nrowsread # Not optimized read # recout = recout + table._read_records( # nchunksread*cs, cs, iobuf[j*cs:]) # # Optimized read through the use of a chunk cache. This cache has # more or less the same speed than the integrated HDF5 chunk # cache, but using the PyTables one has the advantage that the # user can easily change this parameter. recout = recout + table._read_chunk(nchunksread, iobuf, j*cs) j = j + 1 self.nrowsread = (nchunksread+1)*cs if self.nrowsread > self.stop: self.nrowsread = self.stop break elif j == self.nchunksinbuf: break nchunksread = nchunksread + 1 # Evaluate the condition on this table fragment. iobuf = iobuf[:recout] if len(iobuf) > 0: self.table._convert_types(iobuf, len(iobuf), 1) self.indexvalid = call_on_recarr( self.condfunc, self.condargs, iobuf, **self.condkwargs) self.index_valid_data = PyArray_BYTES(self.indexvalid) # Get the valid coordinates self.indexvalues = self.bufcoords[:recout][self.indexvalid] self.index_values_data = PyArray_DATA(self.indexvalues) self.lenbuf = self.indexvalues.size # Place the valid results at the beginning of the buffer iobuf[:self.lenbuf] = iobuf[self.indexvalid] # Initialize the internal buffer row counter self._row = -1 if self._write_to_seqcache: # Feed the indexvalues into the seqcache seqcache = self.iterseq if self.lenbuf + len(seqcache) < self.iterseq_max_elements: seqcache.extend(self.indexvalues) else: self.iterseq = None self._write_to_seqcache = 0 self._row = self._row + 1 # Check whether we have read all the rows in buf if self._row == self.lenbuf: self.nextelement = self.nrowsread # Make _row to point to the last valid entry in buffer # (this is useful for accessing the last row after an iterator loop) self._row = self._row - 1 continue self._nrow = self.index_values_data[self._row] # Check additional conditions on start, stop, step params if self.sss_on: if (self._nrow < self.start or self._nrow >= self.stop): self.nextelement = self.nextelement + 1 continue if (self.step > 1 and ((self._nrow - self.start) % self.step > 0)): self.nextelement = self.nextelement + 1 continue # Return this row self.nextelement = self._nrow + 1 return self else: # All the elements have been read for this mode self._finish_riterator() cdef __next__coords(self): """The version of next() for user-required coordinates""" cdef int recout cdef long long lenbuf, nextelement cdef object tmp if 0 < self.step: while self.nextelement < self.stop: if self.nextelement >= self.nrowsread: # Correction for avoiding reading past self.stop if self.nrowsread+self.nrowsinbuf > self.stop: lenbuf = self.stop-self.nrowsread else: lenbuf = self.nrowsinbuf tmp = self.coords[self.nrowsread:self.nrowsread+lenbuf:self.step] # We have to get a contiguous buffer, so numpy.array is the way to go self.bufcoords = numpy.array(tmp, dtype="uint64") self._row = -1 if self.bufcoords.size > 0: recout = self.table._read_elements(self.bufcoords, self.iobuf) else: recout = 0 self.bufcoords_data = PyArray_DATA(self.bufcoords) self.nrowsread = self.nrowsread + lenbuf if recout == 0: # no items were read, skip out continue self._row = self._row + 1 self._nrow = self.bufcoords_data[self._row] self.nextelement = self.nextelement + self.absstep return self else: # All the elements have been read for this mode self._finish_riterator() elif 0 > self.step: #print("self.nextelement = ", self.nextelement, self.start, self.nrowsread, self.nextelement < self.start - self.nrowsread + 1) while self.nextelement > self.stop: if self.nextelement < self.start - ( self.nrowsread) + 1: if 0 > self.nextelement - ( self.nrowsinbuf) + 1: tmp = self.coords[0:self.nextelement + 1] else: tmp = self.coords[self.nextelement - ( self.nrowsinbuf) + 1:self.nextelement + 1] self.bufcoords = numpy.array(tmp, dtype="uint64") recout = self.table._read_elements(self.bufcoords, self.iobuf) self.bufcoords_data = PyArray_DATA(self.bufcoords) self.nrowsread = self.nrowsread + self.nrowsinbuf self._row = len(self.bufcoords) - 1 else: self._row = (self._row + self.step) % len(self.bufcoords) self._nrow = self.nextelement - self.step self.nextelement = self.nextelement + self.step # Return this value return self else: # All the elements have been read for this mode self._finish_riterator() else: self._finish_riterator() cdef __next__inkernel(self): """The version of next() in case of in-kernel conditions""" cdef hsize_t recout, correct cdef object numexpr_locals, colvar, col self.nextelement = self._nrow + self.step while self.nextelement < self.stop: if self.nextelement >= self.nrowsread: # Skip until there is interesting information while self.nextelement >= self.nrowsread + self.nrowsinbuf: self.nrowsread = self.nrowsread + self.nrowsinbuf # Compute the end for this iteration self.stopb = self.stop - self.nrowsread if self.stopb > self.nrowsinbuf: self.stopb = self.nrowsinbuf self._row = self.startb - self.step # Read a chunk recout = self.table._read_records(self.nextelement, self.nrowsinbuf, self.iobuf) self.nrowsread = self.nrowsread + recout self.indexchunk = -self.step # Evaluate the condition on this table fragment. self.indexvalid = call_on_recarr( self.condfunc, self.condargs, self.iobuf[:recout], **self.condkwargs) self.index_valid_data = PyArray_BYTES(self.indexvalid) # Is there any interesting information in this buffer? if not numpy.sometrue(self.indexvalid): # No, so take the next one if self.step >= self.nrowsinbuf: self.nextelement = self.nextelement + self.step else: self.nextelement = self.nextelement + self.nrowsinbuf # Correction for step size > 1 if self.step > 1: correct = (self.nextelement - self.start) % self.step self.nextelement = self.nextelement - correct continue self._row = self._row + self.step self._nrow = self.nextelement if self._row + self.step >= self.stopb: # Compute the start row for the next buffer self.startb = 0 self.nextelement = self._nrow + self.step # Return only if this value is interesting self.indexchunk = self.indexchunk + self.step if self.index_valid_data[self.indexchunk]: return self else: self._finish_riterator() cdef __next__general(self): """The version of next() for the general cases""" cdef int recout if 0 < self.step: self.nextelement = self._nrow + self.step while self.nextelement < self.stop: if self.nextelement >= self.nrowsread: # Skip until there is interesting information while self.nextelement >= self.nrowsread + self.nrowsinbuf: self.nrowsread = self.nrowsread + self.nrowsinbuf # Compute the end for this iteration self.stopb = self.stop - self.nrowsread if self.stopb > self.nrowsinbuf: self.stopb = self.nrowsinbuf self._row = self.startb - self.step # Read a chunk recout = self.table._read_records(self.nrowsread, self.nrowsinbuf, self.iobuf) self.nrowsread = self.nrowsread + recout self._row = self._row + self.step self._nrow = self.nextelement if self._row + self.step >= self.stopb: # Compute the start row for the next buffer self.startb = (self._row + self.step) % self.nrowsinbuf self.nextelement = self._nrow + self.step # Return this value return self else: self._finish_riterator() elif 0 > self.step: self.stopb = -1 while self.nextelement - 1 > self.stop: if self.nextelement < self.start - self.nrowsread + 1: # Read a chunk recout = self.table._read_records(self.nextelement - self.nrowsinbuf + 1, self.nrowsinbuf, self.iobuf) self.nrowsread = self.nrowsread + self.nrowsinbuf self._row = self.nrowsinbuf - 1 else: self._row = (self._row + self.step) % self.nrowsinbuf self._nrow = self.nextelement - self.step self.nextelement = self.nextelement + self.step # Return this value return self else: self._finish_riterator() cdef _finish_riterator(self): """Clean-up things after iterator has been done""" cdef ObjectCache seqcache self.rfieldscache = {} # empty rfields cache self.wfieldscache = {} # empty wfields cache # Make a copy of the last read row in the private record # (this is useful for accessing the last row after an iterator loop) if self._row >= 0: self.wrec[:] = self.iobuf[self._row] if self._write_to_seqcache: seqcache = self.table._seqcache # Guessing iterseq size: Each element in self.iterseq should take at least 8 bytes seqcache.setitem_(self.seqcache_key, self.iterseq, len(self.iterseq) * 8) self._riterator = 0 # out of iterator self.iterseq = None # empty seqcache-related things self.seqcache_key = None if self._mod_nrows > 0: # Check if there is some modified row self._flush_mod_rows() # Flush any possible modified row self.modified_fields = set() # Empty the set of modified fields raise StopIteration # end of iteration def _fill_col(self, result, start, stop, step, field): """Read a field from a table on disk and put the result in result""" cdef hsize_t startr, istartb cdef hsize_t istart, inrowsinbuf, inextelement cdef long long stopr, istopb, i, j, inrowsread cdef long long istop, istep cdef object fields # We can't reuse existing buffers in this context self._init_loop(start, stop, step, None, None) istart, istop, istep = self.start, self.stop, self.step inrowsinbuf, inextelement, inrowsread = self.nrowsinbuf, istart, istart istartb, startr = self.startb, 0 i = istart if 0 < istep: while i < istop: if (inextelement >= inrowsread + inrowsinbuf): inrowsread = inrowsread + inrowsinbuf i = i + inrowsinbuf continue # Compute the end for this iteration istopb = istop - inrowsread if istopb > inrowsinbuf: istopb = inrowsinbuf stopr = startr + ((istopb - istartb - 1) // istep) + 1 # Read a chunk inrowsread = inrowsread + self.table._read_records(i, inrowsinbuf, self.iobuf) # Assign the correct part to result fields = self.iobuf if field: fields = get_nested_field(fields, field) result[startr:stopr] = fields[istartb:istopb:istep] # Compute some indexes for the next iteration startr = stopr j = istartb + ((istopb - istartb - 1) // istep) * istep istartb = (j+istep) % inrowsinbuf inextelement = inextelement + istep i = i + inrowsinbuf elif istep < 0: inrowsinbuf = self.nrowsinbuf #istartb = self.startb istartb = self.nrowsinbuf - 1 #istopb = self.stopb - 1 istopb = -1 startr = 0 i = istart inextelement = istart inrowsread = 0 while i-1 > istop: #if (inextelement <= inrowsread + inrowsinbuf): if (inextelement < i - inrowsinbuf): inrowsread = inrowsread + inrowsinbuf i = i - inrowsinbuf continue # Compute the end for this iteration # (we know we are going backward so try to keep indices positive) stopr = startr + (1 - istopb + istartb) // (-istep) # Read a chunk inrowsread = inrowsread + self.table._read_records(i - inrowsinbuf + 1, inrowsinbuf, self.iobuf) # Assign the correct part to result fields = self.iobuf if field: fields = get_nested_field(fields, field) if istopb >= 0: result[startr:stopr] = fields[istartb:istopb:istep] else: result[startr:stopr] = fields[istartb::istep] # Compute some indexes for the next iteration startr = stopr istartb = (i - istartb)%inrowsinbuf inextelement = inextelement + istep i = i - inrowsinbuf self._riterator = 0 # out of iterator return def append(self): """Add a new row of data to the end of the dataset. Once you have filled the proper fields for the current row, calling this method actually appends the new data to the *output buffer* (which will eventually be dumped to disk). If you have not set the value of a field, the default value of the column will be used. .. warning:: After completion of the loop in which :meth:`Row.append` has been called, it is always convenient to make a call to :meth:`Table.flush` in order to avoid losing the last rows that may still remain in internal buffers. Examples -------- :: row = table.row for i in xrange(nrows): row['col1'] = i-1 row['col2'] = 'a' row['col3'] = -1.0 row.append() table.flush() """ cdef ndarray iobuf, wrec, wreccpy if self.ro_filemode: raise IOError("Attempt to write over a file opened in read-only mode") if not self.chunked: raise HDF5ExtError("You cannot append rows to a non-chunked table.", h5tb=False) if self._riterator: raise NotImplementedError("You cannot append rows when in middle of a table iterator. If what you want is to update records, use Row.update() instead.") # Commit the private record into the write buffer # self.iobuf[self._unsaved_nrows] = self.wrec # The next is faster iobuf = self.iobuf; wrec = self.wrec memcpy(PyArray_BYTES(iobuf) + self._unsaved_nrows * self._stride, PyArray_BYTES(wrec), self._rowsize) # Restore the defaults for the private record # self.wrec[:] = self.wreccpy # The next is faster wreccpy = self.wreccpy memcpy(PyArray_BYTES(wrec), PyArray_BYTES(wreccpy), self._rowsize) self._unsaved_nrows = self._unsaved_nrows + 1 # When the buffer is full, flush it if self._unsaved_nrows == self.nrowsinbuf: self._flush_buffered_rows() def _flush_buffered_rows(self): if self._unsaved_nrows > 0: self.table._save_buffered_rows(self.iobuf, self._unsaved_nrows) # Reset the buffer unsaved counter self._unsaved_nrows = 0 def _get_unsaved_nrows(self): return self._unsaved_nrows def update(self): """Change the data of the current row in the dataset. This method allows you to modify values in a table when you are in the middle of a table iterator like :meth:`Table.iterrows` or :meth:`Table.where`. Once you have filled the proper fields for the current row, calling this method actually changes data in the *output buffer* (which will eventually be dumped to disk). If you have not set the value of a field, its original value will be used. .. warning:: After completion of the loop in which :meth:`Row.update` has been called, it is always convenient to make a call to :meth:`Table.flush` in order to avoid losing changed rows that may still remain in internal buffers. Examples -------- :: for row in table.iterrows(step=10): row['col1'] = row.nrow row['col2'] = 'b' row['col3'] = 0.0 row.update() table.flush() which modifies every tenth row in table. Or:: for row in table.where('col1 > 3'): row['col1'] = row.nrow row['col2'] = 'b' row['col3'] = 0.0 row.update() table.flush() which just updates the rows with values bigger than 3 in the first column. """ cdef ndarray iobufcpy, iobuf if self.ro_filemode: raise IOError("Attempt to write over a file opened in read-only mode") if not self._riterator: raise NotImplementedError("You are only allowed to update rows through the Row.update() method if you are in the middle of a table iterator.") if self.mod_elements is None: # Initialize an array for keeping the modified elements # (just in case Row.update() would be used) self.mod_elements = numpy.empty(shape=self.nrowsinbuf, dtype=SizeType) # We need a different copy for self.iobuf here self.iobufcpy = self.iobuf.copy() # Add this row to the list of elements to be modified self.mod_elements[self._mod_nrows] = self._nrow # Copy the current buffer row in input to the output buffer # self.iobufcpy[self._mod_nrows] = self.iobuf[self._row] # The next is faster iobufcpy = self.iobufcpy; iobuf = self.iobuf memcpy(PyArray_BYTES(iobufcpy) + self._mod_nrows * self._stride, PyArray_BYTES(iobuf) + self._row * self._stride, self._rowsize) # Increase the modified buffer count by one self._mod_nrows = self._mod_nrows + 1 # No point writing seqcache -- Table.flush will invalidate it # since we no longer know whether this row will meet _where_condition self._write_to_seqcache = 0 # When the buffer is full, flush it if self._mod_nrows == self.nrowsinbuf: self._flush_mod_rows() def _flush_mod_rows(self): """Flush any possible modified row using Row.update()""" table = self.table # Save the records on disk table._update_elements(self._mod_nrows, self.mod_elements, self.iobufcpy) # Reset the counter of modified rows to 0 self._mod_nrows = 0 # Mark the modified fields' indexes as dirty. table._mark_columns_as_dirty(self.modified_fields) def __contains__(self, item): """__contains__(item) A true value is returned if item is found in current row, false otherwise. """ return item in self.fetch_all_fields() # This method is twice as faster than __getattr__ because there is # not a lookup in the local dictionary def __getitem__(self, key): """__getitem__(key) Get the row field specified by the `key`. The key can be a string (the name of the field), an integer (the position of the field) or a slice (the range of field positions). When key is a slice, the returned value is a *tuple* containing the values of the specified fields. Examples -------- :: res = [row['var3'] for row in table.where('var2 < 20')] which selects the var3 field for all the rows that fulfil the condition. Or:: res = [row[4] for row in table if row[1] < 20] which selects the field in the *4th* position for all the rows that fulfil the condition. Or:: res = [row[:] for row in table if row['var2'] < 20] which selects the all the fields (in the form of a *tuple*) for all the rows that fulfil the condition. Or:: res = [row[1::2] for row in table.iterrows(2, 3000, 3)] which selects all the fields in even positions (in the form of a *tuple*) for all the rows in the slice [2:3000:3]. """ cdef long offset cdef ndarray field cdef object row, fields, fieldscache if self._riterator: # If in the middle of an iterator loop, the user probably wants to # access the read buffer fieldscache = self.rfieldscache; fields = self.rfields offset = self._row else: # We are not in an iterator loop, so the user probably wants to access # the write buffer fieldscache = self.wfieldscache; fields = self.wfields offset = 0 try: # Check whether this object is in the cache dictionary field = fieldscache[key] except (KeyError, TypeError): try: # Try to get it from fields (str or int keys) field = get_nested_field_cache(fields, key, fieldscache) except TypeError: # No luck yet. Still, the key can be a slice. # Fetch the complete row and convert it into a tuple if self._riterator: row = self.iobuf[self._row].copy().item() else: row = self.wrec[0].copy().item() # Try with __getitem__() return row[key] if PyArray_NDIM(field) == 1: # For an scalar it is not needed a copy (immutable object) return PyArray_GETITEM(field, PyArray_BYTES(field) + offset * self._stride) else: # Do a copy of the array, so that it can be overwritten by the user # without damaging the internal self.rfields buffer return field[offset].copy() # This is slightly faster (around 3%) than __setattr__ def __setitem__(self, object key, object value): """__setitem__(key, value) Set the key row field to the specified value. Differently from its __getitem__() counterpart, in this case key can only be a string (the name of the field). The changes done via __setitem__() will not take effect on the data on disk until any of the :meth:`Row.append` or :meth:`Row.update` methods are called. Examples -------- :: for row in table.iterrows(step=10): row['col1'] = row.nrow row['col2'] = 'b' row['col3'] = 0.0 row.update() table.flush() which modifies every tenth row in the table. """ cdef int ret cdef long offset cdef ndarray field cdef object fields, fieldscache if self.ro_filemode: raise IOError("attempt to write over a file opened in read-only mode") if self._riterator: # If in the middle of an iterator loop, or *after*, the user # probably wants to access the read buffer fieldscache = self.rfieldscache; fields = self.rfields offset = self._row else: # We are not in an iterator loop, so the user probably wants to access # the write buffer fieldscache = self.wfieldscache; fields = self.wfields offset = 0 # Check validity of enumerated value. if self.exist_enum_cols: if key in self.colenums: enum = self.colenums[key] for cenval in numpy.asarray(value).flat: enum(cenval) # raises ``ValueError`` on invalid values # Get the field to be modified field = get_nested_field_cache(fields, key, fieldscache) if key not in self.modified_fields: self.modified_fields.add(key) # Finally, try to set it to the value try: # Optimization for scalar values. This can optimize the writes # between a 10% and 100%, depending on the number of columns modified if PyArray_NDIM(field) == 1: ret = PyArray_SETITEM(field, PyArray_BYTES(field) + offset * self._stride, value) if ret < 0: PyErr_Clear() raise TypeError ##### End of optimization for scalar values else: field[offset] = value except TypeError: raise TypeError("invalid type (%s) for column ``%s``" % (type(value), key)) def fetch_all_fields(self): """Retrieve all the fields in the current row. Contrarily to row[:] (see :ref:`RowSpecialMethods`), this returns row data as a NumPy void scalar. For instance:: [row.fetch_all_fields() for row in table.where('col1 < 3')] will select all the rows that fulfill the given condition as a list of NumPy records. """ # We need to do a cast for recognizing negative row numbers! if self._nrow < 0: return ("Warning: Row iterator has not been initialized for table:\n" " %s\n" " You will normally want to use this method in iterator " "contexts." % self.table) # Always return a copy of the row so that new data that is written # in self.iobuf doesn't overwrite the original returned data. return self.iobuf[self._row].copy() def __str__(self): """Represent the record as an string""" # We need to do a cast for recognizing negative row numbers! if self._nrow < 0: return ("Warning: Row iterator has not been initialized for table:\n" " %s\n" " You will normally want to use this object in iterator " "contexts." % self.table) tablepathname = self.table._v_pathname classname = self.__class__.__name__ return "%s.row (%s), pointing to row #%d" % (tablepathname, classname, self._nrow) def __repr__(self): """Represent the record as an string""" return str(self) ## Local Variables: ## mode: python ## py-indent-offset: 2 ## tab-width: 2 ## fill-column: 78 ## End: PyTables-3.7.0/tables/tests/000077500000000000000000000000001416254111300156565ustar00rootroot00000000000000PyTables-3.7.0/tables/tests/Table2_1_lzo_nrv2e_shuffle.h5000066400000000000000000000454061416254111300231720ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿKÿÿÿÿÿÿÿÿ €`HEAP€tuple0group0èTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà  ;Ȩvar1var2 var3 ?@4 4ÿdÿÿÿÿÿÿÿÿÈ<(SNOD€`(ÐHEAP0ð>TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ   ?€¨var1var2 var3 ?@4 4ÿdÿÿÿÿÿÿÿÿ @(SNOD` @  °HEAP0ÈBTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ  øB€¨var1var2 var3 ?@4 4ÿdÿÿÿÿÿÿÿÿxD(SNOD@ è HEAPTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ  FHTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿË+TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÁË-TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÅŒ0 àY@ 1úÀX;ý€½Ný@½ ý½þÀW=ý€½\ý@½(ý½.þÀVRý€½Oý@½ý½IþÀU<ý€½ ý@½Qý½þÀTœ'}€½ý@¼(ü½2þÀS'ý€½%ý@¼'½¼(ýRý€½Yý@¼'=½_þÀQ)ý€¼(ü½ý½SþÀPHý€¼'½@¼'}½/'½Oý½3þ€N6ý½Pþ€Mœ'ý½'}Lý¼'þ€Kœ'½½bþ€Jœ'= ½"'}I8ý½4þ€Hý¼'>€Gœ'ý¼(ýFD'ü½þ€E7ý¼(= D['|¼'~ €C '|¼'>€B'|¼'þ €Aœ'½ ½`þ€@@ý½'=?M'=>œ(½=œ'¾<œ'þ;œ(}:œ(½9+'}8 '=7œ(}6œ'þ5œ'¾ 4E'ý3'=2œ'> 1-=05'½.#'=,œ'>*œ(ý(œ'þ&'ý$U'="œ(} T'}œ'¾ œ(½^'½œ'~ œ(ýœ(¾ð?'è Ï  7àY@ ]úÀXý€½Oý@½\ý½>þÀW.ý€½`ý@½#ý½6þÀV^ý€½ý@½Qý½(þÀUý€½ ý@½ý½ þÀT/ý€½Vý@½ý¼'¾ÀS:ý€½ ý@-=->ÀRœ'½€½1'ü¼'½½þÀQ ý€½ý@½;ý½þÀPœ(ü½&ý@½ý½þ€ODý½+þ€N!ý½Wþ€Mý½Xþ€Lœ(ü½)'}Kœ'=¼'¾ €J"ý¼'¾€I_'|½'þ€HSý½Bþ€G0ý½Tþ€Fý½þ€Eý½,þ€Dœ'½¼(½Cœ(|½'ýBYý½Mþ€Aœ'=¼(ý@ý¼(=?'}>'==œ(ý<'};œ(}:œ'~9œ(ý85'ý7'=6œ'þ5-=4œ(= 3I'ý2@'=1œ'>0œ(}.œ'>,'ý*F'=(P'=&œ(ý$œ'þ"œ'> œ(ýœ(}œ(=œ(}œ(½<'=->ð?(h Î$ %ãY@ AúÀXaý€½Ký@½ý½,þÀW\ý€½Rý@-=¼(ýVYý€½Qý@½ý½IþÀUý€½Zý@½ ý½þÀT0ý€½bý@½ý½þÀSœ'½€½Wý@½?ý½DþÀRý€½_ý@½ý½]þÀQ1ý€½#ý@½2ý¼(ýPœ'}€½/ý@¼'}½=þ€Oý½'þ€Nœ(ü½V'}Mœ'½½`'}L ý½þ€K@ý¼(= Jœ'} ¼(}Iœ'ý¼'>€Hœ(ü½(þ €G-|¼(}FEý¼'>€Eœ'ý ½&þ€D3ý½-þ€C.ý½*þ€B7ý½cþ€A!ý½4þ€@œ(ü ¼û?'A>M'==œ'> <['};C'=:A'=9œ'>8L'}7œ'>6œ(=5œ'¾ 4'ý3œ(ý2œ(½1œ'>0œ(=.œ(},)'}*^'=('=&J'=$<'="œ': H'Aœ(}œ(= œ(½œ(½ œ'¾ œ(}ð(è'Ì Æ €` 0TITLETable Benchmark (CLASSGROUP (VERSION1.0 ˜FILTERSu(itables.Leaf Filters p1 (dp2 S'shuffle' p3 I0 sS'complevel' p4 I1 sS'fletcher32' p5 I0 sS'complib' p6 S'lzo' p7 sb. 8PYTABLES_FORMAT_VERSION1.2 0test2 just a test (1lzopª=#@ (CLASSTABLE (VERSION2.1 8TITLEThis is the table title 8 NROWS d 0 FIELD_0_NAMEvar1 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar3 8 test tuple1group1`( (TITLE (CLASSGROUP (VERSION1.0 ˜FILTERSu(itables.Leaf Filters p1 (dp2 S'shuffle' p3 I0 sS'complevel' p4 I1 sS'fletcher32' p5 I0 sS'complib' p6 S'lzo' p7 sb. 0test2 just a test (1lzo ª=#@ (CLASSTABLE (VERSION2.1 8TITLEThis is the table title 8 NROWS d 0 FIELD_0_NAMEvar1 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar3 8 test tuple2group2@  (TITLE (CLASSGROUP (VERSION1.0 ˜FILTERSu(itables.Leaf Filters p1 (dp2 S'shuffle' p3 I0 sS'complevel' p4 I1 sS'fletcher32' p5 I0 sS'complib' p6 S'lzo' p7 sb. 0test2 just a test (1lzoÐ"ª=#@ (CLASSTABLE (VERSION2.1 8TITLEThis is the table title 8 NROWS d 0 FIELD_0_NAMEvar1 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar3 8 test  è (TITLE (CLASSGROUP (VERSION1.0 ˜FILTERSu(itables.Leaf Filters p1 (dp2 S'shuffle' p3 I0 sS'complevel' p4 I1 sS'fletcher32' p5 I0 sS'complib' p6 S'lzo' p7 sb. PyTables-3.7.0/tables/tests/Tables_lzo1.h5000066400000000000000000000555031416254111300203030ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ=[ÿÿÿÿÿÿÿÿ €`HEAP€tuple0group0èTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàkTdZdZdZdZdZ e e e e d„Z eeee e e e e d „Z eeee e e e e d „Z e e e e d „Zd „Zd ddd„Ze d„Zd„Ze d„Zd„Zd„Zd„ZdS‰ˆxù·ˆxù·ÌPA§fä·DHͶ";E» oͶ¼sͶcE@¯à³Í¶d±ˆ· ÷cò{ͶkàڲͶ „ͶȊ°¸˜šâ·Œfâ·J´C’À`ä·O̶w³ ä·ŒpͶwqE„  ä·ˆª*MŸã ;ͶL±ˆ·o€ŠÍ¶äJͶjYsƒ`ŠÍ¶ŒsͶ°ž/áxlͶ|HͶ“÷D¿€=Ͷ\sͶÐU¥Þ`éâ·   ¨var3 ?@4 4ÿvar2 var1 dÿÿÿÿÿÿÿÿ SNOD€`(ÐHEAP0°TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ kTdZdZdZdZdZ e e e e d„Z eeee e e e e d „Z eeee e e e e d „Z e e e e d „Zd „Zd ddd„Ze d„Zd„Ze d„Zd„Zd„Zd„ZdS‰ˆxù·ˆxù·ÌPA§fä·DHͶ";E» oͶ¼sͶcE@¯à³Í¶d±ˆ· ÷cò{ͶkàڲͶ „ͶȊ°¸˜šâ·Œfâ·J´C’À`ä·O̶w³ ä·ŒpͶwqE„  ä·ˆª*MŸã ;ͶL±ˆ·o€ŠÍ¶äJͶjYsƒ`ŠÍ¶ŒsͶ°ž/áxlͶ|HͶ“÷D¿€=Ͷ\sͶÐU¥Þ`éâ· 0(¸¨var3 ?@4 4ÿvar2 var1 dÿÿÿÿÿÿÿÿè)SNOD` @  °HEAP0àTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ kTdZdZdZdZdZ e e e e d„Z eeee e e e e d „Z eeee e e e e d „Z e e e e d „Zd „Zd ddd„Ze d„Zd„Ze d„Zd„Zd„Zd„ZdS‰ˆxù·ˆxù·ÌPA§fä·DHͶ";E» oͶ¼sͶcE@¯à³Í¶d±ˆ· ÷cò{ͶkàڲͶ „ͶȊ°¸˜šâ·Œfâ·J´C’À`ä·O̶w³ ä·ŒpͶwqE„  ä·ˆª*MŸã ;ͶL±ˆ·o€ŠÍ¶äJͶjYsƒ`ŠÍ¶ŒsͶ°ž/áxlͶ|HͶ“÷D¿€=Ͷ\sͶÐU¥Þ`éâ· ø+¸¨var3 ?@4 4ÿvar2 var1 dÿÿÿÿÿÿÿÿ°-SNOD@ è HEAPTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ kTdZdZdZdZdZ e e e e d„Z eeee e e e e d „Z eeee e e e e d „Z e e e e d „Zd „Zd ddd„Ze d„Zd„Ze d„Zd„Zd„Zd„ZdS‰ˆxù·ˆxù·ÌPA§fä·DHͶ";E» oͶ¼sͶcE@¯à³Í¶d±ˆ· ÷cò{ͶkàڲͶ „ͶȊ°¸˜šâ·Œfâ·J´C’À`ä·O̶w³ ä·ŒpͶwqE„  ä·ˆª*MŸã ;ͶL±ˆ·o€ŠÍ¶äJͶjYsƒ`ŠÍ¶ŒsͶ°ž/áxlͶ|HͶ“÷D¿€=Ͷ\sͶÐU¥Þ`éâ· À/€TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿX0 €` 0TITLETable Benchmark (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I0 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb. 8PYTABLES_FORMAT_VERSION1.4 0test2 just a test (1lzopC¶B (CLASSTABLE (VERSION2.4 8TITLEThis is the table title 0 NROWS@d 0 FIELD_0_NAMEvar3 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar1 (testI2 .tuple1group1tuple2group2Y@A @XÀX@@Y X€X@'<@X@*<X@*<ÀW@*<€W@*<@W@*<W@*<ÀV@ *<€V@ *<@V@ *<V@ *<ÀU@ *<€U@*<@U@*<U@*<ÀT@*<€T@*<@T@*<T@*<ÀS@*<€S@*<@S@*<S@*<ÀR@*<€R@*<@R@*<R@*<ÀQ@*<€Q@*<@Q@*<Q@ *<ÀP@!*<€P@"*<@P@#*<P@$*<€O@%*<O@&*<€N@'*<N@(*<€M@)*<M@**<€L@+*<L@,*<€K@-*<K@.*<€J@/*<J@0*<€I@1*<I@2*<€H@3*<H@4*<€G@5*<G@6*<€F@7*<F@8*<€E@9*<E@:*<€D@;*<D@<*<€C@=*<C@>*<€B@?*?B@`€'|€A@A+A@B*<€@@C*<@@D+??@E+?>@F+?=@G+?<@H+?;@I+?:@J+?9@K+?8@L+?7@M+?6@N+?5@O+?4@P+?3@Q+?2@R+?1@S+?0@T+?.@U+?,@V+?*@W+?(@X+?&@Y+?$@Z+?"@[+? @\+?@]+?@^+?@_+?@`+?@a+?@b+?ð?c+<P ‘`( (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I0 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb. 0test2 just a test (1lzo@1C¶B (CLASSTABLE (VERSION2.4 8TITLEThis is the table title 0 NROWS@d 0 FIELD_0_NAMEvar3 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar1 (testI2 .@  (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I0 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb. 0test2 just a test (1lzo`HC¶B (CLASSTABLE (VERSION2.4 8TITLEThis is the table title 0 NROWS@d 0 FIELD_0_NAMEvar3 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar1 (testI2 . è (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I0 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb.TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿq`@ X@ ÀW@ €W@ @W@ W@ ÀV@ €V@ @V@ V@ ÀU@ €U@ @U@ U@ ÀT@ €T@ @T@ T@ ÀS@ €S@ @S@ S@ ÀR@ €R@ @R@ R@ ÀQ@ €Q@ @Q@ Q@ ÀP@! €P@" @P@# P@$ €O@% O@& €N@' N@( €M@) M@* €L@+ L@, €K@- K@. €J@/ J@0 €I@1 I@2 €H@3 H@4 €G@5 G@6 €F@7 F@8 €E@9 E@: €D@; D@< €C@= C@> €B@? B@@ €A@A A@B €@@C @@D ?@E >@F =@G <@H ;@I :@J 9@K 8@L 7@M 6@N 5@O 4@P 3@Q 2@R 1@S 0@T .@U ,@V *@W (@X &@Y $@Z "@[ @\ @] @^ @_ @` @a @b ð?c Y@A @XÀX@@Y X€X@'<@X@*<X@*<ÀW@*<€W@*<@W@*<W@*<ÀV@ *<€V@ *<@V@ *<V@ *<ÀU@ *<€U@*<@U@*<U@*<ÀT@*<€T@*<@T@*<T@*<ÀS@*<€S@*<@S@*<S@*<ÀR@*<€R@*<@R@*<R@*<ÀQ@*<€Q@*<@Q@*<Q@ *<ÀP@!*<€P@"*<@P@#*<P@$*<€O@%*<O@&*<€N@'*<N@(*<€M@)*<M@**<€L@+*<L@,*<€K@-*<K@.*<€J@/*<J@0*<€I@1*<I@2*<€H@3*<H@4*<€G@5*<G@6*<€F@7*<F@8*<€E@9*<E@:*<€D@;*<D@<*<€C@=*<C@>*<€B@?*?B@`€'|€A@A+A@B*<€@@C*<@@D+??@E+?>@F+?=@G+?<@H+?;@I+?:@J+?9@K+?8@L+?7@M+?6@N+?5@O+?4@P+?3@Q+?2@R+?1@S+?0@T+?.@U+?,@V+?*@W+?(@X+?&@Y+?$@Z+?"@[+? @\+?@]+?@^+?@_+?@`+?@a+?@b+?ð?c+<P ô ¡aÑ80Ë8@MPM`LpË8ÞÎ8M M°MÀMÐMàMðLÌ8M M0M@MPM`MpM€MM M°MÀMÐMàMðLÍ8L"Ù ]0M@MPM`MpM€MM M°MÀMÐMàMðMMM M0M@MPM`MpM€MM M°MÀMÐMàMðLÏ8LŒ]0M@MPM`MpM€MM M°MÀMÐMàMðLÐ8M M0M@MPM`MpM€MM M°MÀMÐMàMðM]/LŒ]0M@MPLl O l; OÐÝ ýl2 Z Ê_ b o b ¿ Z }ÝAÏ Z ýAŸ b ¯ b ÿ Z ýl¾]ƒ|Çà3 Vß Zéï  Þ|Ôð .? R-îÝ@}ÝO R .- >-<.¼ +=ýÝ@/ JÌ6ý Z }Ý Z ý"_ Z ýÍoÜì S¿ Z }ÍÏÜ SüíŸ b ¯ b ÿ b ÜÆm3,̆l K ß)Þ"8 KðÌ ïÌ8B VüÊ?),íî%Ü/ü ?O1  Í>   ü>½N / > >Œ} b  b _ b o b ¿ b Ï b Ÿ b ¯ b ÿ b  N||¢. ß bï  î :ìAm"? J->5­O5\| G - N)|) G /-  GÜ* b  b _ b o b ¿ b Ï b Ÿ b ¯ b ÿ b  >L*<.-î%­Aß bïÎ8C \:.  +<?Ï8$Ý1>Ü +| -O "ÝN ,&:- " :|m=­/ B |:  b  b _ b o b ¿ b Ï b Ÿ b ¯ b ÿ b Ìø: ?mß bï- > R]? FÍN9½&O F :M 1  N B/1 :Ì 3m b  b _ b o b ¿ b Ï b Ÿ b ¯ b ÿ b )m¹>), 3<6Mß bï b ?Ý/N-,#6Ü {]  BÜ 7}.9íAOÍ/Ü 7  ×\TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ­ Pp(ñ*l5§·Œ³·øý'ð®'¨(X+àöÜ· öÜ· öÜ·€öÜ·€öÜ·˜+øý'‡A¦=釷$ï­¹`뇷Œζ`7臷,PͶDí40˜ê©·€îâ·›{ÙP燷,Ͷõ˜šx燷ζ!éU ˆ·äζ¯à ` ˆ·ôζ9ü“`ˆ·˜@â·C¯Å½èê©·dîD‚✀öÜ·,&ª·F‚âœàǫܷ̈ӷG‚✠öÜ·ŒXÓ·MQ X釷,ÕͶÓ?çë©·ζY4_Àê©·|ζ\7ܠ燷ÌÔͶlòI!&ª·€ ˆ·í?/MÀ@Ó·˜@â·ñOàØ8뇷ÌͶyGÝëØæ‡·lζzû"ª·,ŠÍ¶_n…€çá¶$ζ 8ÄPÜᶬͶ ªLÆ€%Ó·`„”»pßá¶œζ‰Û}(Üá¶,‰Í¶Và1ß·ìˆÍ¶”Ò› 0à¶`R}î Pâ·ÚbY@A @XÀX@@Y X€X@'<@X@*<X@*<ÀW@*<€W@*<@W@*<W@*<ÀV@ *<€V@ *<@V@ *<V@ *<ÀU@ *<€U@*<@U@*<U@*<ÀT@*<€T@*<@T@*<T@*<ÀS@*<€S@*<@S@*<S@*<ÀR@*<€R@*<@R@*<R@*<ÀQ@*<€Q@*<@Q@*<Q@ *<ÀP@!*<€P@"*<@P@#*<P@$*<€O@%*<O@&*<€N@'*<N@(*<€M@)*<M@**<€L@+*<L@,*<€K@-*<K@.*<€J@/*<J@0*<€I@1*<I@2*<€H@3*<H@4*<€G@5*<G@6*<€F@7*<F@8*<€E@9*<E@:*<€D@;*<D@<*<€C@=*<C@>*<€B@?*?B@`€'|€A@A+A@B*<€@@C*<@@D+??@E+?>@F+?=@G+?<@H+?;@I+?:@J+?9@K+?8@L+?7@M+?6@N+?5@O+?4@P+?3@Q+?2@R+?1@S+?0@T+?.@U+?,@V+?*@W+?(@X+?&@Y+?$@Z+?"@[+? @\+?@]+?@^+?@_+?@`+?@a+?@b+?ð?c+<P " Ë8xÀ7&/Ï<|7à *a b  b _ b o b ¿ b Ï b Ÿ b ¯ b ÿ bÐ aß bï- > R]? FÎNÎ8ÍO F :™1Ì8Ì:, m/1  , Ý b  b _ b o b ¿ b Ï b Ÿ b ¯ b ÿ bÑ(]>), Ì Ýß bï b ?Ý/N-,# L _ÎÍ(\ -Ü Ü ½.9íAOÍ/Ü L œ¨a`n°¥Y@A @X$r@Y Xx%r'<"ýq*\"Üq|"¼q|"œq|"|q|"\q|ÀV@ |"q|"üp|"Üp|"¼p|"œp|"|p|"\p|ÀT@|"p|@T@|"Üo|"¼o|"œo|"|o|"\o|ÀR@|"o|"ün|"Ün|"¼n|"œn|"|n|"\n|ÀP@!|"n|"üm|"Üm|"¼m|"œm|"|m|"\m|€M@)|"m|"ül|"Ül|"¼l|"œl|"|l|"\l|€I@1|"l|"ük|"Ük|"¼k|"œk|"|k|"\k|€E@9|"k|"üj|"Üj|"¼j|"œj|"|j*?B@`€'|€A@A+A@B—@@C|"Ôi+??@E+?>@F+?=@G+?<@H+?;@I+?:@J+?9@K+?8@L_7@M_6@N_5@O_4@P_3@Q_2@R_1@S_0@TG.@UO,@VW*@W_(@X_&@Y_$@Z_"@[_ @\G@]O@^O@_O@`W@aW@bTð?c+<P"Õu<3ô ¡aÑ80Ë8@MPM`LpË8ÞÎ8M M°MÀMÐMàMðLÌ8M M0MÐMpM€MM2ÄÍ8L"Ù ] ÐMM ‘Ï’ Œ ˆeÐ $P]/L)ˆLl O l; OÐÝ ýl2"€ƒ Z Ê_ b op¿ Z }ÝAÏgýAŸm¯qÿ`?ýl¾]ƒ|Çà3 Vß Zéï  Þ|Ôð .? R-îÝ@}ÝO R .- >-<.¼ +=ýÝ@/ JÌ6ýs }Ý Ÿý"_x ýÍoÜì S´ÍÏÜ Süí*<HLÜÆm3,̆l K ß)Þ"8 KðÌ ïÌ8B VüÊ?),íî%Ü/ü ?O1  Í>   ü>½N / > >Œ}yp*xUÏl.$ N||¢. T"bï  î :ìAm"? J->5­O5\| G d"N)|) G /-HGÜ* Ä>L*<.-î%­Aß bïÎ8C \:.  +<?Ï8$Ý1>@(+| -O "ÝN ,&:- " :|m=­/ B |: $Ìø: ?mÌx= R]? FÍN9½&O F :M 1  N B/1TÌ 3m ¤ )m¹>), 3<6Mß ˆ-b ?Ý/N-,#6Ü {]  BÜ 7}.9íAOÍ/Ü 7 Ÿ×\"t‹<Øõ;9\4ì Ä%ýAÜ Í. ìA ?­~ ’ 9¼i ?¼ t} ï= z¼ Ïý-- b\c 3 .=  N¼6~ ^ ^ü6\ O] ’Ü% 3 æ 3l àM +Íx. –íA~-ü% oMŽ =ü o¬ a=. ²íA~=ÜA oÍ Ž æü? o\ =N +|1 ü ¿Í+ Þm0Í ~m. ;ý~1½AM1L { Ž ~ìAí'^ –Í †|ü, @M ~¼ @ü.}Í æ­5M J~ zAÍ .Ž –½^ æ\ .¼ ×=M .\ × ªÝŽ ŽœA>Ý^ Jü'>,.}n òì.| =Ž zÜA ? ^ ŠBn ŠìA PyTables-3.7.0/tables/tests/Tables_lzo1_shuffle.h5000066400000000000000000000511511416254111300220120ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿcRÿÿÿÿÿÿÿÿ €`HEAP€tuple0group0èTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàXdSHðÀ¦ÏÿÿÿÿdZy dkZWn ej oZdZdkZdkZy(eideƒ\Z Z \Z Z Z Wn…ej oygiZeiƒD]-\Z Z Z e eijoee ƒq‹q‹[Zeo!deddieƒfZq nXde eiiƒdfZeeeƒe‚nXdklZlZlZlZlZd klZlZl Z l!Z!l"Z"l#Z#d kl$Z$l%Z%l&Z&l'Z'l(Z(l)Z)yd kl*Z*l+Z+Wnej onXd   ¨var3 ?@4 4ÿvar2 var1 dÿÿÿÿÿÿÿÿ (SNOD€`(ÐHEAP0ÈTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ XdSHðÀ¦ÏÿÿÿÿdZy dkZWn ej oZdZdkZdkZy(eideƒ\Z Z \Z Z Z Wn…ej oygiZeiƒD]-\Z Z Z e eijoee ƒq‹q‹[Zeo!deddieƒfZq nXde eiiƒdfZeeeƒe‚nXdklZlZlZlZlZd klZlZl Z l!Z!l"Z"l#Z#d kl$Z$l%Z%l&Z&l'Z'l(Z(l)Z)yd kl*Z*l+Z+Wnej onXd 0(¸¨var3 ?@4 4ÿvar2 var1 dÿÿÿÿÿÿÿÿè)(SNOD` @  °HEAP0øTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ XdSHðÀ¦ÏÿÿÿÿdZy dkZWn ej oZdZdkZdkZy(eideƒ\Z Z \Z Z Z Wn…ej oygiZeiƒD]-\Z Z Z e eijoee ƒq‹q‹[Zeo!deddieƒfZq nXde eiiƒdfZeeeƒe‚nXdklZlZlZlZlZd klZlZl Z l!Z!l"Z"l#Z#d kl$Z$l%Z%l&Z&l'Z'l(Z(l)Z)yd kl*Z*l+Z+Wnej onXd ,¸¨var3 ?@4 4ÿvar2 var1 dÿÿÿÿÿÿÿÿÈ-(SNOD@ è HEAPTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ XdSHðÀ¦ÏÿÿÿÿdZy dkZWn ej oZdZdkZdkZy(eideƒ\Z Z \Z Z Z Wn…ej oygiZeiƒD]-\Z Z Z e eijoee ƒq‹q‹[Zeo!deddieƒfZq nXde eiiƒdfZeeeƒe‚nXdklZlZlZlZlZd klZlZl Z l!Z!l"Z"l#Z#d kl$Z$l%Z%l&Z&l'Z'l(Z(l)Z)yd kl*Z*l+Z+Wnej onXd ð/€TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿl0 €` 0TITLETable Benchmark (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I1 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb. 8PYTABLES_FORMAT_VERSION1.4 0test2 just a test @shuffle1lzop:¶B (CLASSTABLE (VERSION2.4 8TITLEThis is the table title 0 NROWS@d 0 FIELD_0_NAMEvar3 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar1 (testI2 .tuple1group1tuple2group2 {À€@>€< ¹{RYXXXXWWWWVVVVUUUUTTTTSSSSRRRRQQQQPPPPOONNMMLLKKJJIIHHGGFFEEDDCCBBAA@@?>=<;:9876543210.,*(&$" ð šá@ A? šdR  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abc ›d >Ù B šÀJ Ba Úd ~ € : : @„¨WSNF<( *:JZ›AL ¾ @X @€ AdaÀVRME;&š +;K[d ¾YÔª ;H/ ¢dEÀ€„Ã:$šd ,N^š ôÀ€@>€< = dn>{UQKC7@R/?O_"äa =d ¿tRYXXXXWWWWVVVVUUUUTTTTSSSSRRRRQQQQPPPPOONNMMLLKKJJIIHHGGFFEEDDCCBBAA@@?>=<;:9876543210.,*(&$" ð ¤Ã ;ø @„  >zGdà 6  0@P`>J;Ô q@ A? ¸ª ;ø @„  >dRUQJB5A!1AQaÙ ?À;P £pQ  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abc Œ¸ª ;ø @„  ; €Ydà 4"2BRbB;Ô ¥p¸ª ; @„  ;dx#XTPIA3#3CSc at ¾ ¸ª ;€ @„ @€u#<„à 2?$4DT ÃDà AX @„ @€dÃXTPH@1ð %5EUB ?2t » ¬– = ð° :ø  ;¤ :  #l„Ã0 &6FVD ;HX À¬– = ° :ø  ;¤ :  !l { WSOG?.'7GW !D ଖ = „° :ø  dtdà >,(8HX Ú:Ø lÔ¢ < ¬ :ø  l«:” l¤  ìn=* )9IYd¼U|  `( (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I1 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb. 0test2 just a test @shuffle1lzop1:¶B (CLASSTABLE (VERSION2.4 8TITLEThis is the table title 0 NROWS@d 0 FIELD_0_NAMEvar3 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar1 (testI2 .@  (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I1 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb. 0test2 just a test @shuffle1lzo 9:¶B (CLASSTABLE (VERSION2.4 8TITLEThis is the table title 0 NROWS@d 0 FIELD_0_NAMEvar3 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar1 (testI2 . è (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I1 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb.TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ œ! X@ ÀW@ €W@ @W@ W@ ÀV@ €V@ @V@ V@ ÀU@ €U@ @U@ U@ ÀT@ €T@ @T@ T@ ÀS@ €S@ @S@ S@ ÀR@ €R@ @R@ R@ ÀQ@ €Q@ @Q@ Q@ ÀP@! €P@" @P@# P@$ €O@% O@& €N@' N@( €M@) M@* €L@+ L@, €K@- K@. €J@/ J@0 €I@1 I@2 €H@3 H@4 €G@5 G@6 €F@7 F@8 €E@9 E@: €D@; D@< €C@= C@> €B@? B@@ €A@A A@B €@@C @@D ?@E >@F =@G <@H ;@I :@J 9@K 8@L 7@M 6@N 5@O 4@P 3@Q 2@R 1@S 0@T .@U ,@V *@W (@X &@Y $@Z "@[ @\ @] @^ @_ @` @a @b ð?c TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÓ Hp(ñ*,u³·¬s³·XÔÈi+¨(p +÷Ü·ÀöÜ·ÀöÜ· öÜ· öÜ·˜{ù·˜{ù·‡A¦=釷$ï­¹`뇷Œζ`7臷,@ͶDí40˜ê©·€îâ·›{ÙP燷LÖͶõ˜šx燷ζ!éUÀˆ·äζ¯à € ˆ·ôζ9ü“€ˆ·˜@â·C¯Å½èê©·dîD‚✠öÜ·L&ª·F‚âœ÷Ü·ì¨Ó·G‚âœÀöÜ·¬XÓ·MQ X釷lÕͶÓ?çë©·ζY4_Àê©·|ζ\7ܠ燷 ÕͶlòI! &ª·  ˆ·í?/MÀ@Ó·˜@â·ñOàØ8뇷LͶyGÝëØæ‡·lζzû "ª·ìͶ_n… çá¶$ζ 8ÄPÜá¶lͶ ªLÆ %Ó·`„”»pßá¶œζ‰Û}(Üá¶ÌͶV€0ß· ‰Í¶”Ò›@0à¶`R}î Pâ·Úb £¨(  ˆ*¨:ˆ :R -l°+Md+4 &0¼ ¶] &P7D7d b€€ð¸´ÍÜ VRLD8+Xx Á>l  @ 44X T(8HXl¸*B| ; P -p°+M+4 &0¼ ´ &H da(¬;*J(t 5&M ,\+𥠸 3µ @¸ 3€Í 8Ü @@ìü*/ hÐUQKC7Ð-U #$ ¡Ã?H °@( A(( $ )9IY(ü¼ µ .#(”åIà %¸´Ä $øÌ -¼ 4è-¤ X8¤<¶Z ?E -€¸´Ä 5¨Ô ½ ¹   ð¼ ¤ Œ¸ *nt­ ì:fÃ64…Q4X T¤Ãl°d' '$   *:J)Ð ¼ ¶b ` è $p(¤¸*$(t 5%X @`5 5Ü T¾I› ?Å; -€¸*, -„05V0H D°+5´+Œ?2ÀÀ9Í0O?ø €UQJB55U*K ü?ˆ.d+¨ $ð9 +;K[. ¼ '4N.x (=3 ($€ ¸*4€ :T¸ :„ Ih)œa*Ì G&*Ð !,¸*< !T< .A°<È tüÂì¼ ¶_ô YfÃ44C4X T4dÉ! ,4´ ,€<:Œ Gl)œa0ÅW0H }D* ®L ?Ä .€¸´ÉÜ (œa*¼ #xxXTPIA35u6*Ð ,;e1 T*Ü -=M5EU - D¤/*ð 5,¼ ¶T 5Œ(TUHYXXXXWWWWVVVVUUUUTTTTSSSSRRRRQQQQPPPPOONNMMLLKKJJIIHHGGFFEEDDCCBBAA@@?>=<;:9876543210.,*(&$" ð(À W%S -ÜØS*Qš*0 ',¼ µ/( '”ü .&¸´É Ü 2½ { #d…Ã25$ $AL@€d ¸* $^.>N^d 0 $¨d(X°(<+4 &0¼ µØ &h1@4 = $Xl1ô IN $@'¼¼R´Ñ Ü 2½ € -191L H¸*. 8'è(œa'H'"UÀ'(  XTPH@1"8'°+$1H+| 0¸(¨° µ $à/?O5hM #t•(ìдн¸ 3¼ @eU"ä6Q  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abc 3ˆ MF -´¸S´Å Ü 2+œ 2|é "à %¸*` $°G.œa*´ $T…Ã0x °d - ;d;XT  0@P`4´ -0I+dÁà )UXÀ )D.$"` .L ´< .Œ!¼R*+ ?Ø Aåá -*x®N #„4œa¤ $äWSOG?#x3¸´¡ 5`zÔ .d¸+œ¨ !1AQ5Œ .\¸* .l’0T -=°+˜1H+| ¨1( - ¸F*a .Ôª0¼ Aä -´ Ä*‘5à[È " $äeÃ>#Œ°*ð¬%„, ¨:fRÙ px"2BR5Ü $pt ¬ #* $´ 4"ä4d T°+é+4 0¤I* $´*: M:p l+ð¹ ¸ 3´ $eÀ:a7 #ˆWSNF=*d ¸*#”Il  4d¥ U¸ ° #3CS"[14 $(Ý.d¼ µ .À‡.® Aõ B0h *<€B@?*?B@`€'|€A@A+A@B*<€@@C*<@@D+??@E+?>@F+?=@G+?<@H+?;@I+?:@J+?9@K+?8@L+?7@M+?6@N+?5@O+?4@P+?3@Q+?2@R+?1@S+?0@T+?.@U+?,@V+?*@W+?(@X+?&@Y+?$@Z+?"@[+? @\+?@]+?@^+?@_+?@`+?@a+?@b+?ð?c+<P ‘`( (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I0 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb. 0test2 just a test (1lzo@1§¶B (CLASSTABLE (VERSION2.4 8TITLEThis is the table title 0 NROWS@d 0 FIELD_0_NAMEvar3 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar1 (testI2 .@  (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I0 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb. 0test2 just a test (1lzo`H§¶B (CLASSTABLE (VERSION2.4 8TITLEThis is the table title 0 NROWS@d 0 FIELD_0_NAMEvar3 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar1 (testI2 . è (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I0 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb.TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŒ`@ X@ ÀW@ €W@ @W@ W@ ÀV@ €V@ @V@ V@ ÀU@ €U@ @U@ U@ ÀT@ €T@ @T@ T@ ÀS@ €S@ @S@ S@ ÀR@ €R@ @R@ R@ ÀQ@ €Q@ @Q@ Q@ ÀP@! €P@" @P@# P@$ €O@% O@& €N@' N@( €M@) M@* €L@+ L@, €K@- K@. €J@/ J@0 €I@1 I@2 €H@3 H@4 €G@5 G@6 €F@7 F@8 €E@9 E@: €D@; D@< €C@= C@> €B@? B@@ €A@A A@B €@@C @@D ?@E >@F =@G <@H ;@I :@J 9@K 8@L 7@M 6@N 5@O 4@P 3@Q 2@R 1@S 0@T .@U ,@V *@W (@X &@Y $@Z "@[ @\ @] @^ @_ @` @a @b ð?c Y@A @XÀX@@Y X€X@'<@X@*<X@*<ÀW@*<€W@*<@W@*<W@*<ÀV@ *<€V@ *<@V@ *<V@ *<ÀU@ *<€U@*<@U@*<U@*<ÀT@*<€T@*<@T@*<T@*<ÀS@*<€S@*<@S@*<S@*<ÀR@*<€R@*<@R@*<R@*<ÀQ@*<€Q@*<@Q@*<Q@ *<ÀP@!*<€P@"*<@P@#*<P@$*<€O@%*<O@&*<€N@'*<N@(*<€M@)*<M@**<€L@+*<L@,*<€K@-*<K@.*<€J@/*<J@0*<€I@1*<I@2*<€H@3*<H@4*<€G@5*<G@6*<€F@7*<F@8*<€E@9*<E@:*<€D@;*<D@<*<€C@=*<C@>*<€B@?*?B@`€'|€A@A+A@B*<€@@C*<@@D+??@E+?>@F+?=@G+?<@H+?;@I+?:@J+?9@K+?8@L+?7@M+?6@N+?5@O+?4@P+?3@Q+?2@R+?1@S+?0@T+?.@U+?,@V+?*@W+?(@X+?&@Y+?$@Z+?"@[+? @\+?@]+?@^+?@_+?@`+?@a+?@b+?ð?c+<P ô q‘Ó8`Í8pM€ML Í8Ñ8ÀMÐMàMðLÎ8M M0M@MPM`MpM€MM M°MÀMÐMàMðLÏ8M M0M@L"ÙP]`MpM€MM M°MÀMÐMàMðLÐ8M M0M@MPM`MpM€MM M°MÀMÐMàMðM]M M0M@LŒ]`MpM€MM M°MÀMÐMàMðLÒ8M M0M@MPM`MpM€MM M°MÀMÐMàMðMM.M M0M@LŒ]`MpM€Ll O ¯|< OÐÝ -l+¿ Z Ê b Ÿ b ï Z ­|u]Aÿ Z ýAÏ b ß b /|¸ð" N-ÝA? Z é b   lÎà .o   +ÙÝ@­"< ]ƒ R .=O- n-<.¼ +=-Í _Ü +l -¯ Z ­Ý£¿ Z ý% Z -ÝbŸ Z ý ï Z ­ÍÿÜì SÏ b ß b /ÌS S í?) \̆ì G)  V¬7Ï8r Vüý,o)í%8ü ?1  Ín  O ü>½~ _ > >Œ}¯ b ¿ b  b Ÿ b ï b ÿ b Ï b ß b /%ì7l… W ? b  b    :ìA}?o  | # n5­5\ #Ì  O- ~ R­/_-  ì +¯ b ¿ b  b Ÿ b ï b ÿ b Ï b ß b / Nl92m? Z %­A Z ì"Ñ8s \:ü 3oÑ8TÍ!nÜ 3œ:M "Ý~ m.O " :ŒmN=­_ B |: ¯ b ¿ b  b Ÿ b ï b ÿ b Ï b ß b / ><;:lí? b  b - n-<ü% ;o FÍ~9í, F :O1 N N B_1 :Ì 3m¯ b ¿ b  b Ÿ b ï b ÿ b Ï b ß b /%ü= 3ì M?) n), ü # VÜ*í b oÍ~ bl+*l -N  BOÜ Ü Í^9íAÍ_Ü < ŒTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÐ PŒ³·x(à+àöÜ· öÜ· öÜ·€öÜ·€öÜ·Ð+¨­‡A¦=釷$ï­¹`뇷ŒÆÊ¶`7臷,ʶDí40˜ê©·€îâ·›{ÙP燷,Mʶõ˜šx燷ÆÊ¶!éU ˆ·äÃʶ¯à ` ˆ·ôÄʶ9ü“`ˆ·˜@â·C¯Å½èê©·dîD‚✀öÜ·,&ª·F‚âœàǫܷ̈ӷG‚✠öÜ·ŒXÓ·MQ X釷,•ʶÓ?çë©·ÇʶY4_Àê©·|Åʶ\7ܠ燷̔ʶlòI!&ª·€ ˆ·í?/MÀ@Ó·˜@â·ñOàØ8뇷ÌMʶyGÝëØæ‡·lÄʶzû"ª·,Jʶ_n…€çá¶$Èʶ 8ÄPÜᶬMʶ ªLÆ€%Ó·`„”»pßá¶œÇʶ‰Û}(Üá¶,IʶVà1ß·ìHʶ”Ò› 0à¶`R}î Pâ·Úb äá¶¼vʶ\ÓlÜá¶(®*…AdE˜@Ó·¼­*Y@A @XÀX@@Y X€X@'<@X@*<X@*<ÀW@*<€W@*<@W@*<W@*<ÀV@ *<€V@ *<@V@ *<V@ *<ÀU@ *<€U@*<@U@*<U@*<ÀT@*<€T@*<@T@*<T@*<ÀS@*<€S@*<@S@*<S@*<ÀR@*<€R@*<@R@*<R@*<ÀQ@*<€Q@*<@Q@*<Q@ *<ÀP@!*<€P@"*<@P@#*<P@$*<€O@%*<O@&*<€N@'*<N@(*<€M@)*<M@**<€L@+*<L@,*<€K@-*<K@.*<€J@/*<J@0*<€I@1*<I@2*<€H@3*<H@4*<€G@5*<G@6*<€F@7*<F@8*<€E@9*<E@:*<€D@;*<D@<*<€C@=*<C@>*<€B@?*?B@`€'|€A@A+A@B*<€@@C*<@@D+??@E+?>@F+?=@G+?<@H+?;@I+?:@J+?9@K+?8@L+?7@M+?6@N+?5@O+?4@P+?3@Q+?2@R+?1@S+?0@T+?.@U+?,@V+?*@W+?(@X+?&@Y+?$@Z+?"@[+? @\+?@]+?@^+?@_+?@`+?@a+?@b+?ð?c+<P " NÍ8xÀ7&_Ñ<|7à *a¯ b ¿ b  b Ÿ b ï b ÿ b Ï b ß b/Ò a? b  b - n-< *X7)o FÎ~Ð8Ý  F 7O1NÎ8Ì:, m_1  , ݯ b ¿ b  b Ÿ b ï b ÿ b Ï b ß b/Ó ML2?) n),2¬ 7M VÜ*= b oÍ~ bl+*l .NÏ m OÜ Ü Í^9íAÍ_Ü < Œ¨a`n ¦Y@A @X$r@Y Xx%r'<"ýq*\"Üq|"¼q|"œq|"|q|"\q|ÀV@ |"q|"üp|"Üp|"¼p|"œp|"|p|"\p|ÀT@|"p|@T@|"Üo|"¼o|"œo|"|o|"\o|ÀR@|"o|"ün|"Ün|"¼n|"œn|"|n|"\n|ÀP@!|"n|"üm|"Üm|"¼m|"œm|"|m|"\m|€M@)|"m|"ül|"Ül|"¼l|"œl|"|l|"\l|€I@1|"l|"ük|"Ük|"¼k|"œk|"|k|"\k|€E@9|"k|"üj|"Üj|"¼j|"œj|"|j*?B@`€'|€A@A+A@B—@@C|"Ôi+??@E+?>@F+?=@G+?<@H+?;@I+?:@J+?9@K+?8@L_7@M_6@N_5@O_4@P_3@Q_2@R_1@S_0@TG.@UO,@VW*@W_(@X_&@Y_$@Z_"@[_ @\G@]O@^O@_O@`W@aW@bTð?c+<P"Õu<3ô q‘Ó8`Í8pM€ML Í8Ñ8ÀMÐMàMðLÎ8M M0M@MPM`MÐM M°MÀM)ÅÏ*ÄL"ÙP]?ÑÐ*Ð?˜M](XLŒ ‰Ò ŠM.5ˆLl O ¯|< OÐÝ -l+"€ƒ ¿ Z Ê b Ÿpï Z ­|u]AÿoýAÏußp /|¸ð" N-ÝA?sé|4  lÎà .o   +ÙÝ@­"< ]ƒ R .=O- n-<.¼ +=-Í _Ü +l -¯žÝ£‡ý%x-ÝbŸ~ ý ´ÍÿÜì S)TMÌS S í?) \̆ì G)  V¬7Ï8r Vüý,o)í%8ü ?1  Ín  O ü>½~ _ > >Œ}¯e¿p*ŒU%ÿl)%ì7l… W ?`À#  :ìA}?o  | # n5­5\ #Ì  |"~ R­/_-\ì + Ü Nl92m?`&%­Ad& ì"Ñ8s \:ü 3oÑ8TÍ!nH.3œ:M "Ý~ m.O " :ŒmN=­_ B |: ><;:lí)ìÐ=ü% ;pBFÍ~9í, F :O1 N N B_1HÌ 3m ¸%ü= 3ì Mp>-n), ü # VÜ*í b oÍ~ bl+*l -N  BOÜ Ü Í^9íAÍsP<§‘ Œ"à‹)Ek9\4)˜ Á)M%ýANÜ Á^ ìA ?­® ’ Í9¼i ?¼ t}M ï=Í z¼ ÏýM--N bœk 3 ^= Í N¼6® ^M ^ü6\ O]Í ’Ü% 3M æ 3l àMN +Íx^ –íA®-ü% oM¾ =ü o¬ a=^ ²íA®=ÜA oÍ ¾ æü? o\ =~ +|1 ü ¿Í[ Þm0ý ~m^ ;ý®1½A}1L { ¾ ~ìAí'Ž –ý †|ü, @} ~¼ @ü.}ý æ­5} J® zAý .¾ –½Ž æ\ .¼ ×=} .\ × ªÝ¾ ŽœA>ÝŽ Jü'>,.}ž òì.| =¾ zÜA ? Ž ŠBž ŠìA PyTables-3.7.0/tables/tests/Tables_lzo2_shuffle.h5000066400000000000000000000511511416254111300220130ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿcRÿÿÿÿÿÿÿÿ €`HEAP€tuple0group0èTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàa   ¨var3 ?@4 4ÿvar2 var1 dÿÿÿÿÿÿÿÿ (SNOD€`(ÐHEAP0ÈTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ a 0(¸¨var3 ?@4 4ÿvar2 var1 dÿÿÿÿÿÿÿÿè)(SNOD` @  °HEAP0øTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ a ,¸¨var3 ?@4 4ÿvar2 var1 dÿÿÿÿÿÿÿÿÈ-(SNOD@ è HEAPTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ a ð/€TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿl0 €` 0TITLETable Benchmark (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I1 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb. 8PYTABLES_FORMAT_VERSION1.4 0test2 just a test @shuffle1lzop´¶B (CLASSTABLE (VERSION2.4 8TITLEThis is the table title 0 NROWS@d 0 FIELD_0_NAMEvar3 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar1 (testI2 .tuple1group1tuple2group2 {À€@>€< ¹{RYXXXXWWWWVVVVUUUUTTTTSSSSRRRRQQQQPPPPOONNMMLLKKJJIIHHGGFFEEDDCCBBAA@@?>=<;:9876543210.,*(&$" ð šá@ A? šdR  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abc ›d >Ù B šÀJ Ba Úd ~ € : : @„¨WSNF<( *:JZ›AL ¾ @X @€ AdaÀVRME;&š +;K[d ¾YÔª ;H/ ¢dEÀ€„Ã:$šd ,N^š ôÀ€@>€< = dn>{UQKC7@R/?O_"äa =d ¿tRYXXXXWWWWVVVVUUUUTTTTSSSSRRRRQQQQPPPPOONNMMLLKKJJIIHHGGFFEEDDCCBBAA@@?>=<;:9876543210.,*(&$" ð ¤Ã ;ø @„  >zGdà 6  0@P`>J;Ô q@ A? ¸ª ;ø @„  >dRUQJB5A!1AQaÙ ?À;P £pQ  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abc Œ¸ª ;ø @„  ; €Ydà 4"2BRbB;Ô ¥p¸ª ; @„  ;dx#XTPIA3#3CSc at ¾ ¸ª ;€ @„ @€u#<„à 2?$4DT ÃDà AX @„ @€dÃXTPH@1ð %5EUB ?2t » ¬– = ð° :ø  ;¤ :  #l„Ã0 &6FVD ;HX À¬– = ° :ø  ;¤ :  !l { WSOG?.'7GW !D ଖ = „° :ø  dtdà >,(8HX Ú:Ø lÔ¢ < ¬ :ø  l«:” l¤  ìn=* )9IYd¼U|  `( (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I1 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb. 0test2 just a test @shuffle1lzop1´¶B (CLASSTABLE (VERSION2.4 8TITLEThis is the table title 0 NROWS@d 0 FIELD_0_NAMEvar3 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar1 (testI2 .@  (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I1 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb. 0test2 just a test @shuffle1lzo 9´¶B (CLASSTABLE (VERSION2.4 8TITLEThis is the table title 0 NROWS@d 0 FIELD_0_NAMEvar3 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar1 (testI2 . è (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS¯ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I1 sS'complevel' p7 I1 sS'fletcher32' p8 I0 sS'complib' p9 S'lzo' p10 sb.TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ œ! X@ ÀW@ €W@ @W@ W@ ÀV@ €V@ @V@ V@ ÀU@ €U@ @U@ U@ ÀT@ €T@ @T@ T@ ÀS@ €S@ @S@ S@ ÀR@ €R@ @R@ R@ ÀQ@ €Q@ @Q@ Q@ ÀP@! €P@" @P@# P@$ €O@% O@& €N@' N@( €M@) M@* €L@+ L@, €K@- K@. €J@/ J@0 €I@1 I@2 €H@3 H@4 €G@5 G@6 €F@7 F@8 €E@9 E@: €D@; D@< €C@= C@> €B@? B@@ €A@A A@B €@@C @@D ?@E >@F =@G <@H ;@I :@J 9@K 8@L 7@M 6@N 5@O 4@P 3@Q 2@R 1@S 0@T .@U ,@V *@W (@X &@Y $@Z "@[ @\ @] @^ @_ @` @a @b ð?c TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÓ H‡A¦=釷$ï­¹`뇷ŒÆÊ¶`7臷,ʶDí40˜ê©·€îâ·›{ÙP燷L–ʶõ˜šx燷ÆÊ¶!éUÀˆ·äÃʶ¯à € ˆ·ôÄʶ9ü“€ˆ·˜@â·C¯Å½èê©·dîD‚✠öÜ·L&ª·F‚âœ÷Ü·ì¨Ó·G‚âœÀöÜ·¬XÓ·MQ X釷l•ʶÓ?çë©·ÇʶY4_Àê©·|Åʶ\7ܠ燷 •ʶlòI! &ª·  ˆ·í?/MÀ@Ó·˜@â·ñOàØ8뇷LMʶyGÝëØæ‡·lÄʶzû "ª·ìMʶ_n… çá¶$Èʶ 8ÄPÜá¶lMʶ ªLÆ %Ó·`„”»pßá¶œÇʶ‰Û}(Üá¶ÌMʶV€0ß· Iʶ”Ò›@0à¶`R}î Pâ·ÚbÀäá¶¼vʶ\ÓlÜá¶H°*…AdE˜@ӷܯ* £¨(  ˆ*¨:ˆ :R -l°+Md+4 &0¼ ¶] &P7D7d b€€ð¸´ÍÜ VRLD8+Xx Á>l  @ 44X T(8HXl¸*B| ; P -p°+M+4 &0¼ ´ &H da(¬;*J(t 5&M ,\+𥠸 3µ @¸ 3€Í 8Ü @@ìü*/ hÐUQKC7Ð-U #$ ¡Ã?H °@( A(( $ )9IY(ü¼ µ .#(”åIà %¸´Ä $øÌ -¼ 4è-¤ X8¤<¶Z ?E -€¸´Ä 5¨Ô ½ ¹   ð¼ ¤ Œ¸ *nt­ ì:fÃ64…Q4X T¤Ãl°d' '$   *:J)Ð ¼ ¶b ` è $p(¤¸*$(t 5%X @`5 5Ü T¾I› ?Å; -€¸*, -„05V0H D°+5´+Œ?2ÀÀ9Í0O?ø €UQJB55U*K ü?ˆ.d+¨ $ð9 +;K[. ¼ '4N.x (=3 ($€ ¸*4€ :T¸ :„ Ih)œa*Ì G&*Ð !,¸*< !T< .A°<È tüÂì¼ ¶_ô YfÃ44C4X T4dÉ! ,4´ ,€<:Œ Gl)œa0ÅW0H }D* ®L ?Ä .€¸´ÉÜ (œa*¼ #xxXTPIA35u6*Ð ,;e1 T*Ü -=M5EU - D¤/*ð 5,¼ ¶T 5Œ(TUHYXXXXWWWWVVVVUUUUTTTTSSSSRRRRQQQQPPPPOONNMMLLKKJJIIHHGGFFEEDDCCBBAA@@?>=<;:9876543210.,*(&$" ð(À W%S -ÜØS*Qš*0 ',¼ µ/( '”ü .&¸´É Ü 2½ { #d…Ã25$ $AL@€d ¸* $^.>N^d 0 $¨d(X°(<+4 &0¼ µØ &h1@4 = $Xl1ô IN $@'¼¼R´Ñ Ü 2½ € -191L H¸*. 8'è(œa'H'"UÀ'(  XTPH@1"8'°+$1H+| 0¸(¨° µ $à/?O5hM #t•(ìдн¸ 3¼ @eU"ä6Q  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abc 3ˆ MF -´¸S´Å Ü 2+œ 2|é "à %¸*` $°G.œa*´ $T…Ã0x °d - ;d;XT  0@P`4´ -0I+dÁà )UXÀ )D.$"` .L ´< .Œ!¼R*+ ?Ø Aåá -*x®N #„4œa¤ $äWSOG?#x3¸´¡ 5`zÔ .d¸+œ¨ !1AQ5Œ .\¸* .l’0T -=°+˜1H+| ¨1( - ¸F*a .Ôª0¼ Aä -´ Ä*‘5à[È " $äeÃ>#Œ°*ð¬%„, ¨:fRÙ px"2BR5Ü $pt ¬ #* $´ 4"ä4d T°+é+4 0¤I* $´*: M:p l+ð¹ ¸ 3´ $eÀ:a7 #ˆWSNF=*d ¸*#”Il  4d¥ U¸ ° #3CS"[14 $(Ý.d¼ µ .À‡.® Aõ B0h [—ï V7Nÿy.¾þ´µ.›k¿Tâum>G©ƒìßQ~BŠvÿgÿ¬Ë§¼ù65¸øòíO÷]ÒY›Öе$ùy‰ÎÎï„íúLÙ2ïsñõ'hæSµûsaú³®dÓ:îZ–_pG-ÝãúÛðŒx ZŸÂ~“È>®ÏskÎú¹õâöuz¾Á7¤Ç”·mý¶Iç¤eÍß埃µ?퉳&¿ [¯?'g³(ë»6¿TK_kS¿¦¬ùäúÞÒi_¾ýy¬÷y.ßtnû{ñÌéßÖT¤¼õÝîqñ Xÿiuòëê€öߨX~í)lüÝÐÔNÿ™t-_™ül @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€þÏÿÜbHEAPX*TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¨Ø Lئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ pcHHEAP0¸cTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÐ1Ø Lئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ø,À,HEAP`(ècTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ 5 Ø Lئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ Hd0SNOD 1€/H/HEAP0xeTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿp9Ø Lئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ P33SNODp5P33È ¨ p ˜x@HEAP`P¨eTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@= PQHLئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ f°SNOD@9 7è6HEAPØ:TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@= PQHLئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ¸f0SNOD=ð:¸:à@À>ˆ>hCHAA ðEÐC˜CHEAP¨>TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@= PQHLئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ èg0HEAP0ATREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@= PQHLئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ i0HEAP¸CTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@= PQHLئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ Hj0HEAP@FTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@= PQHLئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ xk0HEAPÈHTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@= PQHLئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ¨l0HEAPPKTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@= PQHLئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ Øm0HEAPØMTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@= PQHLئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ o0ÿÿÿÿ ÿÿÿÿÿÿÿÿ˜Rèú?I€SNOD(xHXF F0KàH¨H8ˆMhK0K@PðM¸MH@PTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ †%HEAP08pTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿP]PQHLئÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ [ÈZSNOD Èaxesidtracesvectors8Ð 8 major_version  8 minor_version  8 release_version  (writerNI-HWS 0type NI-Waveformaxis0axis1¨ p 8 implicit? 8start ?@4 4ÿ @ increment ?@4 4ÿ:Œ0âŽyU> @ ref_time€ 8 numDigits9x@ 8 implicit? 8 explicit_vector  8 num_signals  8 data_type data È 8namewfm_group0:vector0p*8* (nametrace0 render_infox-axisy-axis8€/H/ (name 0 x_axis  0 y_axis  0 version  8 data_typedigital bit0bit1bit2bit3bit4bit5bit6bit7order 7è6 H user_specified_DWDT_order  @ max_channels_per_device ð:¸: (ID0 0name Signal 0 8 line_color ÿ 0 radix 0 showÀ>ˆ> (ID1 0name Signal 1 8 line_color ÿ 0 radix 0 showHAA (ID2 0name Signal 2 8 line_color ÿ 0 radix 0 showÐC˜C (ID3 0name Signal 3 8 line_color ÿ 0 radix 0 showXF F (ID4 0name Signal 4 8 line_color ÿ 0 radix 0 showàH¨H (ID5 0name Signal 5 8 line_color ÿ 0 radix 0 showhK0K (ID6 0name Signal 6 8 line_color ÿ 0 radix 0 showðM¸M (ID7 0name Signal 7 8 line_color ÿ 0 radix 0 showvector0 PyTables-3.7.0/tables/tests/blosc_bigendian.h5000066400000000000000000000273061416254111300212260ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿÀ.ÿÿÿÿÿÿÿÿ` èTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ HEAPX(Èi1i2i4i80ˆ¨ (TITLE (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.0   € 0}blosc €À€%U'L (CLASSCARRAY``SNODðp ð! (VERSION1.0 (TITLETREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ«ƒ,€ @  @ 0}blosc €@@%U'L (CLASSCARRAY (VERSION1.0 (TITLETREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿº.-@ @   0}blosc €À %U'L (CLASSCARRAY (VERSION1.0 (TITLETREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿØè-  @ @  0}blosc €@#%U'L (CLASSCARRAY (VERSION1.0 (TITLETREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿp+€€!àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"* àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø€€«“* àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿi€€ºK!àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ1S* àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ)€€Ø+!àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ+!àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ+!àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3* àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ  PyTables-3.7.0/tables/tests/bug-idx.h5000066400000000000000000000344711416254111300174640ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ99ÿÿÿÿÿÿÿÿ`ˆ¨ èTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿHEAPXÈtableHˆ¨ (TITLE (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.1 ðˆÿÿÿÿÿÿÿÿ@path@ 8shuffledeflate   °@U`@SNOD (CLASSTABLE (VERSION2.7 (TITLE 0 FIELD_0_NAMEpath 8 FIELD_0_FILL@ 0 NROWS@ðˆTREE%ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÐ î @ `-€L k ÀŠàª É é@`'€F e Àƒà£ Â! á"@$`% €>& ^'À}(àœ)»* Ø+@ö, `.€5/ T0Às1 à’2²3  Ñ4@ñ5`7 €/8 xœíÑUVP@Á§؀؊Š(‚€ÝbwïÞ5püœÙ Ã0¬ÉÚŒd]ÖgC6f4›²9[²5Û²=;2–ñLdg&³+»³'{³/ûs s(S9œ#9šéËLŽçDfs2s™Ï©œÎ™œÍ¹œÏ…,äb.år®äj®åznäfnåvîd1KYÎÝÜËý<ÈÃ<Êã<ÉÓ<Ëó¼È˼ÊJ^çMÞæ]ÞçC>æS>çK¾æ[¾çG~æW~çOþfðáÇ>|øðáÇ>|øðáÇ>|øðáÇ>Vñ1Àð’'ÛxœíÑUVP@A lEE±[ |&˜`ƒÝ˜û߃w ?g¶0Ã0 —s%Ws-×s#³™ËÍÜÊíÜÉÝÜË|r?ò0ò8Oò4‹Yʳ<Ï‹¼Ì«¼Î›¼Í»¼Ï‡|̧|Η|Í·,g%«ùžù™_ù?Éš¬Íº¬Ïh6dc6es¶dk¶e{vd,;³+»3ž=Ù›‰ìËþÈÁÊáÉÑLf*Çr<'r2§r:gr6Ó9—󹋙ɥ >|øðáÇ>|øðáÇ>|øðáÇ>|øðáÃÇ?|ŒÀðF@ÛxœíÑUVP@AìB± ,L0;± AŸºÿ=x×àñsf 3 Ãð+¿ó'#Y“µY—õÙÙ”ÍÙ’­Ù–ÑlÏŽŒegƳ+Ù=Ù›}ÙŸ9˜C9œ#9šÉLåXŽçDNf:§r:gr639—󹋹”Ù\Ε\͵\ÏÌe>7s+·s'ws/ ¹ŸÅ<ÈÃ<Êã<ÉÓ<Ëó¼È˼Êë,åMÞæ]ÞçC>æS>çK¾æ[–ó=+YÍüÌàÇ>|øðáÇ>|øðáÇ>|øðáÇ>|üÃÇüâ xœíÑçZˆ€Ñle¯lÙóCBe¬BdFÙ÷ÞkðøyÎ-œa†Ë¹’«™ÌµLåznäf¦3“ÙÜÊíÜÉÝÜËý<ÈÃ<ÊãÌåIžæYžçEæ³—y•×YÌ›¼ÍRÞå}>äc>ås–ó%_³’Õ|Ë÷üÈÏüÊïüÉHÖdmÖe}6dc6esF3–-ÙšmÙžÙ™]Ù=Ù›}ÏþÈÁÊáÉÑËDŽçDNæTNçLÎæ\ÎçB.æR>|øðáÇ>|øðáÇ>|øðáÇ>|øðáã>Fà?ø gß=ÛxœíÑçZˆ€ÑŒPöÊ–½GBe¬BdFŸ}ÿ÷ཟç܆aø–ïù‘Ÿù•ßù“‘¬ÉÚ¬ËúŒfC6fSÆ2žÍÙ’­Ù–íّٕÝÙ“½™È¾ìÏÌ¡ΑÍdŽåxNädNåtÎälÎå|.äb.år®äj¦r-Ó¹ž¹™™Ìf.·r;wr7÷r?ò0ò8óy’§y–çy‘…,æe^åu–ò&o³œwyŸù˜Oùœ•|É׬fðáÇ>|øðáÇ>|øðáÇ>|øðáÇ>þácþƒ¿ý#[xœíÑçZˆ€ÑŒPöÊ–í³ •=²Ê̊ʾÿ{ð^ƒÇÏsná Ã0œËù\ÈÅ\Êå\ÉÕ\ËT®çFnf:3™Í­ÜÎÜͽÜσ<Ì£<Î\žäižåy^d> y™Wy7y›wYÌû|ÈÇ|Êç,åK¾f9+YÍ·|Ïü̯üΟŒdMÖf]Ög4²1›2–ñlΖlͶlÏŽìÌ®ìΞìÍDöeä`åpŽäh&s,Çs"'s*§s&g3øðáÇ>|øðáÇ>|øðáÇ>|øðáÇÿð1ÿÁ_~:ÛxœíÑUVP@AlQQì ±[ÀóaÇþ÷à]ƒÇÏ™-Ì0 Ãj>çK¾æ[¾çG~æW~çOF²&k³.ë³!³)›3š-Ùš±lËöŒgG&²3»2™ÝÙ“½Ù—ý9ƒ9”Ã9’©LçhŽåxNd&'s*§s&³9›s9Ÿ ¹˜¹\Ê|.çJ®æZ®çFnæVnçNîæ^îçA²˜¥<Ì£<Γ<ͳ<Ï‹¼Ì«,çuÞd%oó.ïó!ó)ƒ>|øðáÇ>|øðáÇ>|øðáÇ>|øðñ#ðüä¤&›xœíÑçZˆ€Ñl‰ìQø2²’½U6‘-{Üÿ=x¯Áãç9·p†afr"'s*§3›39›s9Ÿ ™ËÅÌçR.çJ®æZ®çFnæVnçNîæ^îg!‹Yʃ<Ì£<Γ<ͳ<Ï‹,çe^e%¯ó&oó.ïó!ó)«ùœ/ùšoùžù™_ù?Éš¬Íº¬Ï†l̦lÎh¶dkƲ-Û3ž™ÈÎìÊdvgOöf_öç@æPçH¦2£9–ã|øðáÇ>|øðáÇ>|øðáÇ>|øðáÇø€ÿà/é7ÛxœíÑçZˆ€Ñl‰ìQFFV²·ÊŸ‘=îÿ¼×àñóœ[8Ã0 ïó!ó)«ùœ/ùšoùžù™_ù?Éš¬Íº¬Ï†l̦lÎh¶dkƲ-Û3ž™ÈÎìÊdvgOöf_öç@æPçH¦2£9–ã9‘™œÌ©œÎ™ÌælÎå|.äbær)ó¹œ+¹šk¹ž¹™[¹;¹›{¹ŸYÈb–ò0ò8Oò4Ïò|øðáÇ>|øðáÇ>|øðáÇ>|øðáÃÇ?|ŒÀð—3)ÛxœíÑçZˆ€ÑŒìMÙú콊ʞ‘!*ûþïÁ{ ?Ϲ…3 Ãp<'r2§r:gr6çr>r1—r9Wr5×r=“™ÊÜÌtf2›[¹;¹›{¹Ÿy˜Gyœ'yš¹<ËóÌçE^æU^çMÞæ]ò>²˜ù”ÏYÊ—|ÍrV²šoùžù™_ù?Éš¬Íº¬Ïh6dc6es¶dk¶e{vdgvewödoöe,ãÙŸ9˜C9œ#9šc™ÈàÇ>|øðáÇ>|øðáÇ>|øðáÇ>|üÃÇü8é4ÛxœíÑUVP@A TÀl±»° ¬' Øû߃w ?g¶0Ã0 ﳘù˜Oùœ¥|É×,g%«ù–ïù‘Ÿù•ßù“‘¬ÉÚ¬ËúŒfC6fSÆ2ž‰lΖlͶlÏŽìÌ®ìΞLfoöeä`åpŽd*Gs,Çs"'s*§s&gs.çs!s)—s%Ws-×s#7s+Ó™ÉlnçNîæ^îçAæQçIæò4Ïò|øðáÇ>|øðáÇ>|øðáÇ>|øðáÃÇ?|ŒÀðJ-xœíÑçZˆ€ÑŒlBd}”-#[™e—½WeÞÿ=x¯Áãç9·p†açH&3•£9–ã9‘“9•Ó9“éœÍ¹œÏ…Ìäb.år®äj®åznd6s¹™[¹;¹›{™ÏBîçAæQçIžf1Ky–çy‘—y•×y“·y—÷ùù”Ïù’¯ù–å¬d5ßó#?ó+¿ó'#Y“µY—õ͆l̦lΖlͶlÏŽŒegvewƳ'{3‘}ÙŸ9˜C|øðáÇ>|øðáÇ>|øðáÇ>|øðáÇø€ÿà/Ý¢1ÛxœíÑçZˆ€Ñl‰ìQFFV²·ÊöE¶ìqÿ÷ཟç܆aXÎ˼ÊJ^çMÞæ]ÞçC>æSVó9_ò5ßò=?ò3¿ò;2’5Y›uYŸ Ù˜MÙœÑlÉÖŒe[¶g<;2‘Ù•ÉìΞì;ìÏÌ¡ΑLe:Gs,Çs"39™S93™ÍÙœËù\ÈÅÌåRæs9Wr5×r=7r3·r;wr7÷r?²Å,åaåqžäižåy^dðáÇ>|øðáÇ>|øðáÇ>|øðáÇ>þácþƒ¿^Ú0[xœíÑUVP@A lAEñ™`‹Ý`‚-v`îÞ5xøœÙ Ã0ìËþÈÁÊáLe:Gr4Çr<'r2§r:39“³9—󹋹”˹’«¹–빑ÙÌåfnåvîänîe> ¹Ÿy˜Gyœ'yšgYÌó¼È˼Êë¼ÉÛ¼Ëû|ÈÇ|Êç|É×|Ë÷,e9+ù‘Ÿù•ßù“¿Éš¬Íº¬Ïh6dc6es¶dk¶e{Æ2žÙ™]™ÈîìÉdöfðáÇ>|øðáÇ>|øðáÇ>|øðáÇ>þãcVÁ?±ò.ÛxœíÑçZˆ€Ñl‰ìQFFV²·ÊøŒÈ–=îÿ¼×àñóœ[8Ã0 Oó,Ïó"Ëy™WYÉë¼ÉÛ¼Ëû|ÈÇ|Êj>çK¾æ[¾çG~æW~çOF²&k³.ë³!³)›3š-Ùš±lËöŒgG&²3»2™ÝÙ“½Ù—ý9ƒ9”Ã9’©LçhŽåxNd&'s*§s&³9›s9Ÿ ¹˜¹\Ê|.çJ®æZ®çFnæVnçNîæ^îçA²˜¥<Ì£<Γ >|øðáÇ>|øðáÇ>|øðáÇ>|øðáÃÇ?|ŒÀðsò3›xœíÑçZˆ€ÑleóÉ–½GBe¬Bd…²ïÿ¼×àñóœ[8Ã0 {3ž}ÙŸ9˜C9œ#9š‰ËñœÈÉœÊéœÉÙœËù\ÈÅ\Êå\ÉÕLæZ¦r=7r3Ó™Élnåvîänîå~äaåqæò$Oó,Ïó"óYÈ˼Êë,æMÞf)ïò>ò1Ÿ²œÏù’¯YÉj¾å{~äg~åwþd$k²6ë²>²1›²9£Ë–lͶlÏŽìÌ®ìΞ >|øðáÇ>|øðáÇ>|øðáÇ>|øðáÃÇ?|ŒÀðµÊ+ÛxœíÑçZˆ€Ñl‰ìQFFV²·ê³E¶ìqÿ÷ཟç܆ax˜Gyœ'yšgyžYÎ˼ÊJ^çMÞæ]ÞçC>æSVó9_ò5ßò=?ò3¿ò;2’5Y›uYŸ Ù˜MÙœÑlÉÖŒe[¶g<;2‘Ù•ÉìΞì;ìÏÌ¡ΑLe:Gs,Çs"39™S93™ÍÙœËù\ÈÅÌåRæs9Wr5×r=7r3·r;wr7÷r?²Å,eðáÇ>|øðáÇ>|øðáÇ>|øðáÇ>þácþƒ¿T’6ÛxœíÑçZˆ€Ñl}‘=ÊÈÈJöVÙD¶ìqÿ÷ཟç܆a˜ÈŽìÌdvewödoöeä`åp¦2#9šc9ž™œÈÉœÊéÌæLÎæ\ÎçBær1ó¹”˹’«¹–빑›¹•Û¹“»¹—ûYÈb–ò ó(ó$Oó,Ïó"Ëy™WYÉë¼ÉÛ¼Ëû|ÈÇ|Êj>çK¾æ[¾çG~æW~çOF²&k³.ë³!³)›3š-Ùš±lËöŒgðáÇ>|øðáÇ>|øðáÇ>|øðáÇ>þácþƒ¿é*(ÛxœíÑçZˆ€ÑŒìMÙ²÷,*û3";BTöý߃÷<~žs g†áQçIžf.Ïò<óy‘—y•×y“·y—…¼Ï‡,æc>ås–ò%_³œ•¬æ[¾çG~æW~çOF²&k³.ë3š Ù˜MÙœ-ÙšmÙžÙ™]Ù=Ù›}Ëxöç@æPçHŽæX&r<'r2§r:gr6çr>r1—r9Wr5×r=72™©ÜÌ­Lg&³¹;¹›{¹Ÿy˜Á‡>|øðáÇ>|øðáÇ>|øðáÇ>|øø‡øþº:xœíÑçZˆ€Ñlå³É–½GBe¬BdFÙ÷ÞkðøyÎ-œa†­Ù–íّٕÝÙ“½ϾìÏÌ¡ΑÍDŽåxNädNåtÎälÎå|.äb.år®äj&s-S¹ž¹™éÌd6·r;wr7÷r?ò0ò8sy’§y–çy‘ù,äe^åuó&o³”wyŸù˜Oùœå|É׬d5ßò=?ò3¿ò;2’5Y›uYŸ Ù˜MٜьeK>|øðáÇ>|øðáÇ>|øðáÇ>|øðáã>Fà?ø L!%ÛxœíÑUVP@AìB± ,LPŸ"vb‚¹ÿ=x×àñsf 3 Ãp'w³{YÌý<ÈÃ<Êã<ÉÓ<Ëó¼È˼ÊR^çMÞæ]ÞçC>æS>çK¾f9ß²’Õ|Ïü̯üΟŒdMÖf]ÖgC6fS6gK¶f[F³=;2–Ï®Ldwödoöeä`åpŽäh&3•c9ž9™éœÊéœÉÙÌä\ÎçB.æRfs9Wr5×r=72—ùÜÌ­ÜÎàÇ>|øðáÇ>|øðáÇ>|øðáÇ>|üÃÇüx[=[xœíÑUVP@Aìvݨ€X`a¢‚½ÿ=x×àñsf 3 Ã0š±lΖlͶlÏŽìÌ®ìΞìÍx&²/ûs s(‡s$“9šc9ž9™S939›s9Ÿ ¹˜K¹œ+™ÊÕLçZ®çFf2›¹ÜÌ­ÜÎÜͽÜσ<Ì£Ìçqžäižåy²˜y™Wy7YÊÛ¼Ëû|ÈÇ|Êr>çK¾f%«ù–ïù‘Ÿù•ßù“‘¬ÉÚ¬ËúlÈÆlÊàÇ>|øðáÇ>|øðáÇ>|øðáÇ>|üÃÇüÞ‘"ÛxœíÑçZˆ€ÑŒlBd„²edë3Ë.{¯Ê¼ÿ{ð^ƒÇÏsná Ã0ÜÌ­ÜÎÜͽÌg!÷ó ó(ó$O³˜¥<Ëó¼È˼Êë¼ÉÛ¼Ëû|ÈÇ|Êç|É×|ËrV²šïù‘Ÿù•ßù“‘¬ÉÚ¬ËúŒfC6fS6gK¶f[¶gGƲ3»²;ãÙ“½™È¾ìÏÌdåpŽd*Ó9šc9ž9™S93™ÉÙœËù\Èl.æR.çJ®æZ®çFæ2øðáÇ>|øðáÇ>|øðáÇ>|øðáÇÿð1ÿÁ_»„@›xœíÑUVP@AÔ‡­¨Ø¶Ø‚ 6Ø]„µÿ=x×àñsf 3 Ã0šõÙÙ”ÍÙ’­Ù–íËŽìÌ®Œgwödoöeä`åpŽäh&2™c9ž9™S939›©œËù\ÈÅLçR.çJ®æZ®çFnf&³¹•Û¹“»¹—û™Ë|äaåqžäiže!‹yžy™Wy7y›wyŸù˜Oùœ/ùšoùž¥,g%«ù‘Ÿù•ßù“‘¬ÉÚ¬ËàÇ>|øðáÇ>|øðáÇ>|øðáÇ>|üÃÇü ˜ÛxœíÑwBÈ€Q+*+ÙdefËÏŽÈÌÌ–¹ÿ|gàß÷®ð†a®åznäfnåvîd.ws/ó¹ŸYÈÃ<Êã<ÉÓ<ËbžçE^æU^çM–ò6ïò>ò1Ÿò9_²œ¯ù–ïù‘ŸYɯüÎjþdMÖf]ÖgCF²1›2š±Œgs¶dk¶e{&²#“Ù™]Ù=Ù›}ÙŸ9˜©ÊáÉÑËtŽçDNæTNçLfr6çr>r1—r9W2›«|øðáÇ>|øðáÇ>|øðáÇ>|øðáÇü€ÿõÊ5CÛxœíÑUVP@Á§؀؊Š(‚€ÝbwïÞ5püœÙ Ã0¬ÉÚŒd]ÖgC6f4›²9[²5Û²=;2–ñLdg&³+»³'{³/ûs s(S9œ#9šéËLŽçDfs2s™Ï©œÎ™œÍ¹œÏ…,äb.år®äj®åznäfnåvîd1KYÎÝÜËý<ÈÃ<Êã<ÉÓ<Ëó¼È˼ÊJ^çMÞæ]ÞçC>æS>çK¾æ[¾çG~æW~çOþfðáÇ>|øðáÇ>|øðáÇ>|øðáÇ>Vñ1Àð’'ÛxœíÑUVP@A lEE±[ |&˜`ƒÝ˜û߃w ?g¶0Ã0 —s%Ws-×s#³™ËÍÜÊíÜÉÝÜË|r?ò0ò8Oò4‹Yʳ<Ï‹¼Ì«¼Î›¼Í»¼Ï‡|̧|Η|Í·,g%«ùžù™_ù?Éš¬Íº¬Ïh6dc6es¶dk¶e{vd,;³+»3ž=Ù›‰ìËþÈÁÊáÉÑLf*Çr<'r2§r:gr6Ó9—󹋙ɥ >|øðáÇ>|øðáÇ>|øðáÇ>|øðáÃÇ?|ŒÀðF@ÛxœíÑUVP@AìB± ,L0;± AŸºÿ=x×àñsf 3 Ãð+¿ó'#Y“µY—õÙÙ”ÍÙ’­Ù–ÑlÏŽŒegƳ+Ù=Ù›}ÙŸ9˜C9œ#9šÉLåXŽçDNf:§r:gr639—󹋹”Ù\Ε\͵\ÏÌe>7s+·s'ws/ ¹ŸÅ<ÈÃ<Êã<ÉÓ<Ëó¼È˼Êë,åMÞæ]ÞçC>æS>çK¾æ[–ó=+YÍüÌàÇ>|øðáÇ>|øðáÇ>|øðáÇ>|üÃÇüâ xœíÑçZˆ€Ñle¯lÙóCBe¬BdFÙ÷ÞkðøyÎ-œa†Ë¹’«™ÌµLåznäf¦3“ÙÜÊíÜÉÝÜËý<ÈÃ<ÊãÌåIžæYžçEæ³—y•×YÌ›¼ÍRÞå}>äc>ås–ó%_³’Õ|Ë÷üÈÏüÊïüÉHÖdmÖe}6dc6esF3–-ÙšmÙžÙ™]Ù=Ù›}ÏþÈÁÊáÉÑËDŽçDNæTNçLÎæ\ÎçB.æR>|øðáÇ>|øðáÇ>|øðáÇ>|øðáã>Fà?ø gß=ÛxœíÑçZˆ€ÑŒPöÊ–½GBe¬BdFŸ}ÿ÷ཟç܆aø–ïù‘Ÿù•ßù“‘¬ÉÚ¬ËúŒfC6fSÆ2žÍÙ’­Ù–íّٕÝÙ“½™È¾ìÏÌ¡ΑÍdŽåxNädNåtÎälÎå|.äb.år®äj¦r-Ó¹ž¹™™Ìf.·r;wr7÷r?ò0ò8óy’§y–çy‘…,æe^åu–ò&o³œwyŸù˜Oùœ•|É׬fðáÇ>|øðáÇ>|øðáÇ>|øðáÇ>þácþƒ¿ý#[xœíÑçZˆ€ÑŒPöÊ–í³ •=²Ê̊ʾÿ{ð^ƒÇÏsná Ã0œËù\ÈÅ\Êå\ÉÕ\ËT®çFnf:3™Í­ÜÎÜͽÜσ<Ì£<Î\žäižåy^d> y™Wy7y›wYÌû|ÈÇ|Êç,åK¾f9+YÍ·|Ïü̯üΟŒdMÖf]Ög4²1›2–ñlΖlͶlÏŽìÌ®ìΞìÍDöeä`åpŽäh&s,Çs"'s*§s&g3øðáÇ>|øðáÇ>|øðáÇ>|øðáÇÿð1ÿÁ_~:ÛxœíÑUVP@AlQQì ±[ÀóaÇþ÷à]ƒÇÏ™-Ì0 Ãj>çK¾æ[¾çG~æW~çOF²&k³.ë³!³)›3š-Ùš±lËöŒgG&²3»2™ÝÙ“½Ù—ý9ƒ9”Ã9’©LçhŽåxNd&'s*§s&³9›s9Ÿ ¹˜¹\Ê|.çJ®æZ®çFnæVnçNîæ^îçA²˜¥<Ì£<Γ<ͳ<Ï‹¼Ì«,çuÞd%oó.ïó!ó)ƒ>|øðáÇ>|øðáÇ>|øðáÇ>|øðñ#ðüä¤&›xœíÑçZˆ€Ñl‰ìQø2²’½U6‘-{Üÿ=x¯Áãç9·p†afr"'s*§3›39›s9Ÿ ™ËÅÌçR.çJ®æZ®çFnæVnçNîæ^îg!‹Yʃ<Ì£<Γ<ͳ<Ï‹,çe^e%¯ó&oó.ïó!ó)«ùœ/ùšoùžù™_ù?Éš¬Íº¬Ï†l̦lÎh¶dkƲ-Û3ž™ÈÎìÊdvgOöf_öç@æPçH¦2£9–ã|øðáÇ>|øðáÇ>|øðáÇ>|øðáÇø€ÿà/é7ÛxœíÑçZˆ€Ñl‰ìQFFV²·ÊŸ‘=îÿ¼×àñóœ[8Ã0 ïó!ó)«ùœ/ùšoùžù™_ù?Éš¬Íº¬Ï†l̦lÎh¶dkƲ-Û3ž™ÈÎìÊdvgOöf_öç@æPçH¦2£9–ã9‘™œÌ©œÎ™ÌælÎå|.äbær)ó¹œ+¹šk¹ž¹™[¹;¹›{¹ŸYÈb–ò0ò8Oò4Ïò|øðáÇ>|øðáÇ>|øðáÇ>|øðáÃÇ?|ŒÀð—3)ÛxœíÑçZˆ€ÑŒìMÙú콊ʞ‘!*ûþïÁ{ ?Ϲ…3 Ãp<'r2§r:gr6çr>r1—r9Wr5×r=“™ÊÜÌtf2›[¹;¹›{¹Ÿy˜Gyœ'yš¹<ËóÌçE^æU^çMÞæ]ò>²˜ù”ÏYÊ—|ÍrV²šoùžù™_ù?Éš¬Íº¬Ïh6dc6es¶dk¶e{vdgvewödoöe,ãÙŸ9˜C9œ#9šc™ÈàÇ>|øðáÇ>|øðáÇ>|øðáÇ>|üÃÇü8é4ÛxœíÑUVP@A TÀl±»° ¬' Øû߃w ?g¶0Ã0 ﳘù˜Oùœ¥|É×,g%«ù–ïù‘Ÿù•ßù“‘¬ÉÚ¬ËúŒfC6fSÆ2ž‰lΖlͶlÏŽìÌ®ìΞLfoöeä`åpŽd*Gs,Çs"'s*§s&gs.çs!s)—s%Ws-×s#7s+Ó™ÉlnçNîæ^îçAæQçIæò4Ïò|øðáÇ>|øðáÇ>|øðáÇ>|øðáÃÇ?|ŒÀðJ-xœíÑEBP@A;û[ˆ¢(vƒ­ØÝ÷¿ƒï¬ÜÍ\a†aæPçHŽf:Ç2“ã9‘ٜ̩ÌåtÎd>gs.çs! ¹˜K¹œ+¹šk¹ž¹™[¹;¹›Å,å^îçAf9ò8Oò4ÏòþË«ûßÊ8PyTables-3.7.0/tables/tests/check_leaks.py000066400000000000000000000274001416254111300204670ustar00rootroot00000000000000from pathlib import Path from time import perf_counter as clock import tables as tb tref = clock() trel = tref def show_mem(explain): global tref, trel for line in Path("/proc/self/status").read_text().splitlines(): if line.startswith("VmSize:"): vmsize = int(line.split()[1]) elif line.startswith("VmRSS:"): vmrss = int(line.split()[1]) elif line.startswith("VmData:"): vmdata = int(line.split()[1]) elif line.startswith("VmStk:"): vmstk = int(line.split()[1]) elif line.startswith("VmExe:"): vmexe = int(line.split()[1]) elif line.startswith("VmLib:"): vmlib = int(line.split()[1]) print("\nMemory usage: ******* %s *******" % explain) print(f"VmSize: {vmsize:>7} kB\tVmRSS: {vmrss:>7} kB") print(f"VmData: {vmdata:>7} kB\tVmStk: {vmstk:>7} kB") print(f"VmExe: {vmexe:>7} kB\tVmLib: {vmlib:>7} kB") print("WallClock time:", clock() - tref, end=' ') print(" Delta time:", clock() - trel) trel = clock() def write_group(filename, nchildren, niter): for i in range(niter): fileh = tb.open_file(filename, mode="w") for child in range(nchildren): fileh.create_group(fileh.root, 'group' + str(child), "child: %d" % child) show_mem("After creating. Iter %s" % i) fileh.close() show_mem("After close") def read_group(filename, nchildren, niter): for i in range(niter): fileh = tb.open_file(filename, mode="r") for child in range(nchildren): node = fileh.get_node(fileh.root, 'group' + str(child)) assert node is not None # flavor = node._v_attrs.CLASS # for child in fileh.walk_nodes(): # pass show_mem("After reading metadata. Iter %s" % i) fileh.close() show_mem("After close") def write_array(filename, nchildren, niter): for i in range(niter): fileh = tb.open_file(filename, mode="w") for child in range(nchildren): fileh.create_array(fileh.root, 'array' + str(child), [1, 1], "child: %d" % child) show_mem("After creating. Iter %s" % i) fileh.close() show_mem("After close") def read_array(filename, nchildren, niter): for i in range(niter): fileh = tb.open_file(filename, mode="r") for child in range(nchildren): node = fileh.get_node(fileh.root, 'array' + str(child)) # flavor = node._v_attrs.FLAVOR data = node[:] # Read data assert data is not None show_mem("After reading data. Iter %s" % i) # for child in range(nchildren): # node = fileh.get_node(fileh.root, 'array' + str(child)) # flavor = node._v_attrs.FLAVOR # # flavor = node._v_attrs # for child in fileh.walk_nodes(): # pass # show_mem("After reading metadata. Iter %s" % i) fileh.close() show_mem("After close") def write_carray(filename, nchildren, niter): for i in range(niter): fileh = tb.open_file(filename, mode="w") for child in range(nchildren): fileh.create_carray(fileh.root, 'array' + str(child), tb.IntAtom(), (2,), "child: %d" % child) show_mem("After creating. Iter %s" % i) fileh.close() show_mem("After close") def read_carray(filename, nchildren, niter): for i in range(niter): fileh = tb.open_file(filename, mode="r") for child in range(nchildren): node = fileh.get_node(fileh.root, 'array' + str(child)) # flavor = node._v_attrs.FLAVOR data = node[:] # Read data assert data is not None # print("data-->", data) show_mem("After reading data. Iter %s" % i) fileh.close() show_mem("After close") def write_earray(filename, nchildren, niter): for i in range(niter): fileh = tb.open_file(filename, mode="w") for child in range(nchildren): ea = fileh.create_earray(fileh.root, 'array' + str(child), tb.IntAtom(), shape=(0,), title="child: %d" % child) ea.append([1, 2, 3]) show_mem("After creating. Iter %s" % i) fileh.close() show_mem("After close") def read_earray(filename, nchildren, niter): for i in range(niter): fileh = tb.open_file(filename, mode="r") for child in range(nchildren): node = fileh.get_node(fileh.root, 'array' + str(child)) # flavor = node._v_attrs.FLAVOR data = node[:] # Read data assert data is not None # print("data-->", data) show_mem("After reading data. Iter %s" % i) fileh.close() show_mem("After close") def write_vlarray(filename, nchildren, niter): for i in range(niter): fileh = tb.open_file(filename, mode="w") for child in range(nchildren): vl = fileh.create_vlarray(fileh.root, 'array' + str(child), tb.IntAtom(), "child: %d" % child) vl.append([1, 2, 3]) show_mem("After creating. Iter %s" % i) fileh.close() show_mem("After close") def read_vlarray(filename, nchildren, niter): for i in range(niter): fileh = tb.open_file(filename, mode="r") for child in range(nchildren): node = fileh.get_node(fileh.root, 'array' + str(child)) # flavor = node._v_attrs.FLAVOR data = node[:] # Read data assert data is not None # print("data-->", data) show_mem("After reading data. Iter %s" % i) fileh.close() show_mem("After close") def write_table(filename, nchildren, niter): class Record(tb.IsDescription): var1 = tb.IntCol(pos=1) var2 = tb.StringCol(length=1, pos=2) var3 = tb.FloatCol(pos=3) for i in range(niter): fileh = tb.open_file(filename, mode="w") for child in range(nchildren): t = fileh.create_table(fileh.root, 'table' + str(child), Record, "child: %d" % child) t.append([[1, "2", 3.]]) show_mem("After creating. Iter %s" % i) fileh.close() show_mem("After close") def read_table(filename, nchildren, niter): for i in range(niter): fileh = tb.open_file(filename, mode="r") for child in range(nchildren): node = fileh.get_node(fileh.root, 'table' + str(child)) # klass = node._v_attrs.CLASS data = node[:] # Read data assert data is not None # print("data-->", data) show_mem("After reading data. Iter %s" % i) fileh.close() show_mem("After close") def write_xtable(filename, nchildren, niter): class Record(tb.IsDescription): var1 = tb.IntCol(pos=1) var2 = tb.StringCol(length=1, pos=2) var3 = tb.FloatCol(pos=3) for i in range(niter): fileh = tb.open_file(filename, mode="w") for child in range(nchildren): t = fileh.create_table(fileh.root, 'table' + str(child), Record, "child: %d" % child) t.append([[1, "2", 3.]]) t.cols.var1.create_index() show_mem("After creating. Iter %s" % i) fileh.close() show_mem("After close") def read_xtable(filename, nchildren, niter): for i in range(niter): fileh = tb.open_file(filename, mode="r") for child in range(nchildren): node = fileh.get_node(fileh.root, 'table' + str(child)) # klass = node._v_attrs.CLASS # data = node[:] # Read data # print("data-->", data) show_mem("After reading data. Iter %s" % i) fileh.close() show_mem("After close") del node if __name__ == '__main__': import pstats import argparse import profile as prof def _get_parser(): parser = argparse.ArgumentParser( description='Check for PyTables memory leaks.') parser.add_argument('-v', '--verbose', action='store_true', help='enable verbose mode') parser.add_argument('-p', '--profile', action='store_true', help='profile') parser.add_argument('-a', '--array', action='store_true', help='create/read arrays (default)') parser.add_argument('-c', '--carray', action='store_true', help='create/read carrays') parser.add_argument('-e', '--earray', action='store_true', help='create/read earrays') parser.add_argument('-l', '--vlarray', action='store_true', help='create/read vlarrays') parser.add_argument('-t', '--table', action='store_true', help='create/read tables') parser.add_argument('-x', '--indexed-table', action='store_true', dest='xtable', help='create/read indexed-tables') parser.add_argument('-g', '--group', action='store_true', help='create/read groups') parser.add_argument('-r', '--read', action='store_true', help='only read test') parser.add_argument('-w', '--write', action='store_true', help='only write test') parser.add_argument('-n', '--nchildren', type=int, default=1000, help='number of children (%(default)d is the ' 'default)') parser.add_argument('-i', '--niter', type=int, default=3, help='number of iterations (default: %(default)d)') parser.add_argument('filename', help='HDF5 file name') return parser parser = _get_parser() args = parser.parse_args() # set 'array' as default value if no ather option has been specified for name in ('carray', 'earray', 'vlarray', 'table', 'xtable', 'group'): if getattr(args, name): break else: args.array = True filename = args.filename nchildren = args.nchildren niter = args.niter if args.array: fwrite = 'write_array' fread = 'read_array' elif args.carray: fwrite = 'write_carray' fread = 'read_carray' elif args.earray: fwrite = 'write_earray' fread = 'read_earray' elif args.vlarray: fwrite = 'write_vlarray' fread = 'read_vlarray' elif args.table: fwrite = 'write_table' fread = 'read_table' elif args.xtable: fwrite = 'write_xtable' fread = 'read_xtable' elif args.group: fwrite = 'write_group' fread = 'read_group' show_mem("Before open") if args.write: if args.profile: prof.run(str(fwrite)+'(filename, nchildren, niter)', 'write_file.prof') stats = pstats.Stats('write_file.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') if args.verbose: stats.print_stats() else: stats.print_stats(20) else: eval(fwrite+'(filename, nchildren, niter)') if args.read: if args.profile: prof.run(fread+'(filename, nchildren, niter)', 'read_file.prof') stats = pstats.Stats('read_file.prof') stats.strip_dirs() stats.sort_stats('time', 'calls') if args.verbose: print('profile -verbose') stats.print_stats() else: stats.print_stats(20) else: eval(fread+'(filename, nchildren, niter)') PyTables-3.7.0/tables/tests/common.py000066400000000000000000000267621416254111300175350ustar00rootroot00000000000000"""Utilities for PyTables' test suites.""" import re import sys import locale import platform import tempfile from pathlib import Path from time import perf_counter as clock from packaging.version import Version import unittest import numexpr as ne import numpy as np import tables as tb from tables.req_versions import min_blosc_bitshuffle_version hdf5_version = Version(tb.hdf5_version) blosc_version = Version(tb.which_lib_version("blosc")[1]) verbose = False """Show detailed output of the testing process.""" heavy = False """Run all tests even when they take long to complete.""" show_memory = False """Show the progress of memory consumption.""" def parse_argv(argv): global verbose, heavy if 'verbose' in argv: verbose = True argv.remove('verbose') if 'silent' in argv: # take care of old flag, just in case verbose = False argv.remove('silent') if '--heavy' in argv: heavy = True argv.remove('--heavy') return argv zlib_avail = tb.which_lib_version("zlib") is not None lzo_avail = tb.which_lib_version("lzo") is not None bzip2_avail = tb.which_lib_version("bzip2") is not None blosc_avail = tb.which_lib_version("blosc") is not None def print_heavy(heavy): if heavy: print("""Performing the complete test suite!""") else: print("""\ Performing only a light (yet comprehensive) subset of the test suite. If you want a more complete test, try passing the --heavy flag to this script (or set the 'heavy' parameter in case you are using tables.test() call). The whole suite will take more than 4 hours to complete on a relatively modern CPU and around 512 MB of main memory.""") print('-=' * 38) def print_versions(): """Print all the versions of software that PyTables relies on.""" print('-=' * 38) print("PyTables version: %s" % tb.__version__) print("HDF5 version: %s" % tb.which_lib_version("hdf5")[1]) print("NumPy version: %s" % np.__version__) tinfo = tb.which_lib_version("zlib") if ne.use_vml: # Get only the main version number and strip out all the rest vml_version = ne.get_vml_version() vml_version = re.findall("[0-9.]+", vml_version)[0] vml_avail = "using VML/MKL %s" % vml_version else: vml_avail = "not using Intel's VML/MKL" print(f"Numexpr version: {ne.__version__} ({vml_avail})") if tinfo is not None: print(f"Zlib version: {tinfo[1]} (in Python interpreter)") tinfo = tb.which_lib_version("lzo") if tinfo is not None: print("LZO version: {} ({})".format(tinfo[1], tinfo[2])) tinfo = tb.which_lib_version("bzip2") if tinfo is not None: print("BZIP2 version: {} ({})".format(tinfo[1], tinfo[2])) tinfo = tb.which_lib_version("blosc") if tinfo is not None: blosc_date = tinfo[2].split()[1] print("Blosc version: {} ({})".format(tinfo[1], blosc_date)) blosc_cinfo = tb.blosc_get_complib_info() blosc_cinfo = [ "{} ({})".format(k, v[1]) for k, v in sorted(blosc_cinfo.items()) ] print("Blosc compressors: %s" % ', '.join(blosc_cinfo)) blosc_finfo = ['shuffle'] if Version(tinfo[1]) >= tb.req_versions.min_blosc_bitshuffle_version: blosc_finfo.append('bitshuffle') print("Blosc filters: %s" % ', '.join(blosc_finfo)) try: from Cython import __version__ as cython_version print('Cython version: %s' % cython_version) except Exception: pass print('Python version: %s' % sys.version) print('Platform: %s' % platform.platform()) # if os.name == 'posix': # (sysname, nodename, release, version, machine) = os.uname() # print('Platform: %s-%s' % (sys.platform, machine)) print('Byte-ordering: %s' % sys.byteorder) print('Detected cores: %s' % tb.utils.detect_number_of_cores()) print('Default encoding: %s' % sys.getdefaultencoding()) print('Default FS encoding: %s' % sys.getfilesystemencoding()) print('Default locale: (%s, %s)' % locale.getdefaultlocale()) print('-=' * 38) # This should improve readability whan tests are run by CI tools sys.stdout.flush() def test_filename(filename): from pkg_resources import resource_filename return resource_filename('tables.tests', filename) def verbosePrint(string, nonl=False): """Print out the `string` if verbose output is enabled.""" if not verbose: return if nonl: print(string, end=' ') else: print(string) def allequal(a, b, flavor="numpy"): """Checks if two numerical objects are equal.""" # print("a-->", repr(a)) # print("b-->", repr(b)) if not hasattr(b, "shape"): # Scalar case return a == b if ((not hasattr(a, "shape") or a.shape == ()) and (not hasattr(b, "shape") or b.shape == ())): return a == b if a.shape != b.shape: if verbose: print("Shape is not equal:", a.shape, "!=", b.shape) return 0 # Way to check the type equality without byteorder considerations if hasattr(b, "dtype") and a.dtype.str[1:] != b.dtype.str[1:]: if verbose: print("dtype is not equal:", a.dtype, "!=", b.dtype) return 0 # Rank-0 case if len(a.shape) == 0: if a[()] == b[()]: return 1 else: if verbose: print("Shape is not equal:", a.shape, "!=", b.shape) return 0 # null arrays if a.size == 0: # len(a) is not correct for generic shapes if b.size == 0: return 1 else: if verbose: print("length is not equal") print("len(a.data) ==>", len(a.data)) print("len(b.data) ==>", len(b.data)) return 0 # Multidimensional case result = (a == b) result = np.all(result) if not result and verbose: print("Some of the elements in arrays are not equal") return result def areArraysEqual(arr1, arr2): """Are both `arr1` and `arr2` equal arrays? Arguments can be regular NumPy arrays, chararray arrays or structured arrays (including structured record arrays). They are checked for type and value equality. """ t1 = type(arr1) t2 = type(arr2) if not ((hasattr(arr1, 'dtype') and arr1.dtype == arr2.dtype) or issubclass(t1, t2) or issubclass(t2, t1)): return False return np.all(arr1 == arr2) class PyTablesTestCase(unittest.TestCase): def tearDown(self): super().tearDown() for key in self.__dict__: if self.__dict__[key].__class__.__name__ != 'instancemethod': self.__dict__[key] = None def _getName(self): """Get the name of this test case.""" return self.id().split('.')[-2] def _getMethodName(self): """Get the name of the method currently running in the test case.""" return self.id().split('.')[-1] def _verboseHeader(self): """Print a nice header for the current test method if verbose.""" if verbose: name = self._getName() methodName = self._getMethodName() title = f"Running {name}.{methodName}" print('{}\n{}'.format(title, '-' * len(title))) # COMPATIBILITY: assertWarns is new in Python 3.2 if not hasattr(unittest.TestCase, 'assertWarns'): def assertWarns(self, expected_warning, callable_obj=None, *args, **kwargs): context = _AssertWarnsContext(expected_warning, self, callable_obj) return context.handle('assertWarns', callable_obj, args, kwargs) def _checkEqualityGroup(self, node1, node2, hardlink=False): if verbose: print("Group 1:", node1) print("Group 2:", node2) if hardlink: self.assertTrue( node1._v_pathname != node2._v_pathname, "node1 and node2 have the same pathnames.") else: self.assertTrue( node1._v_pathname == node2._v_pathname, "node1 and node2 does not have the same pathnames.") self.assertTrue( node1._v_children == node2._v_children, "node1 and node2 does not have the same children.") def _checkEqualityLeaf(self, node1, node2, hardlink=False): if verbose: print("Leaf 1:", node1) print("Leaf 2:", node2) if hardlink: self.assertTrue( node1._v_pathname != node2._v_pathname, "node1 and node2 have the same pathnames.") else: self.assertTrue( node1._v_pathname == node2._v_pathname, "node1 and node2 does not have the same pathnames.") self.assertTrue( areArraysEqual(node1[:], node2[:]), "node1 and node2 does not have the same values.") class TestFileMixin: h5fname = None open_kwargs = {} def setUp(self): super().setUp() self.h5file = tb.open_file( self.h5fname, title=self._getName(), **self.open_kwargs) def tearDown(self): """Close ``h5file``.""" self.h5file.close() super().tearDown() class TempFileMixin: open_mode = 'w' open_kwargs = {} def _getTempFileName(self): return tempfile.mktemp(prefix=self._getName(), suffix='.h5') def setUp(self): """Set ``h5file`` and ``h5fname`` instance attributes. * ``h5fname``: the name of the temporary HDF5 file. * ``h5file``: the writable, empty, temporary HDF5 file. """ super().setUp() self.h5fname = self._getTempFileName() self.h5file = tb.open_file( self.h5fname, self.open_mode, title=self._getName(), **self.open_kwargs) def tearDown(self): """Close ``h5file`` and remove ``h5fname``.""" self.h5file.close() self.h5file = None Path(self.h5fname).unlink() # comment this for debug only super().tearDown() def _reopen(self, mode='r', **kwargs): """Reopen ``h5file`` in the specified ``mode``. Returns a true or false value depending on whether the file was reopenend or not. If not, nothing is changed. """ self.h5file.close() self.h5file = tb.open_file(self.h5fname, mode, **kwargs) return True class ShowMemTime(PyTablesTestCase): tref = clock() """Test for showing memory and time consumption.""" def test00(self): """Showing memory and time consumption.""" # Obtain memory info (only for Linux 2.6.x) for line in Path("/proc/self/status").read_text().splitlines(): if line.startswith("VmSize:"): vmsize = int(line.split()[1]) elif line.startswith("VmRSS:"): vmrss = int(line.split()[1]) elif line.startswith("VmData:"): vmdata = int(line.split()[1]) elif line.startswith("VmStk:"): vmstk = int(line.split()[1]) elif line.startswith("VmExe:"): vmexe = int(line.split()[1]) elif line.startswith("VmLib:"): vmlib = int(line.split()[1]) print("\nWallClock time:", clock() - self.tref) print("Memory usage: ******* %s *******" % self._getName()) print(f"VmSize: {vmsize:>7} kB\tVmRSS: {vmrss:>7} kB") print(f"VmData: {vmdata:>7} kB\tVmStk: {vmstk:>7} kB") print(f"VmExe: {vmexe:>7} kB\tVmLib: {vmlib:>7} kB") PyTables-3.7.0/tables/tests/create-nested-type.c000066400000000000000000000067021416254111300215310ustar00rootroot00000000000000// This program creates nested types with gaps for testing purposes. // F. Alted 2008-06-27 #include "hdf5.h" #include hid_t create_nested_type(void) { hid_t tid, tid2, tid3; size_t offset, offset2; offset = 1; offset2 = 2; // Create a coumpound type large enough (>= 20) tid = H5Tcreate(H5T_COMPOUND, 21); // Insert an atomic type tid2 = H5Tcopy(H5T_NATIVE_FLOAT); H5Tinsert(tid, "float", offset, tid2); H5Tclose(tid2); offset += 4 + 2; // add two to the offset so as to create gaps // Insert a nested compound tid2 = H5Tcreate(H5T_COMPOUND, 12); tid3 = H5Tcopy(H5T_NATIVE_CHAR); H5Tinsert(tid2, "char", offset2, tid3); H5Tclose(tid3); offset2 += 2; // add one space (for introducing gaps) tid3 = H5Tcopy(H5T_NATIVE_DOUBLE); H5Tinsert(tid2, "double", offset2, tid3); H5Tclose(tid3); offset2 += 5; // add one space (for introducing gaps) H5Tinsert(tid, "compound", offset, tid2); H5Tclose(tid2); offset += 12 + 1; return(tid); } size_t getNestedSizeType(hid_t type_id) { hid_t member_type_id; H5T_class_t class_id; hsize_t i, nfields; size_t itemsize, offset; nfields = H5Tget_nmembers(type_id); offset = 0; // Iterate thru the members for (i=0; i < nfields; i++) { // Get the member type member_type_id = H5Tget_member_type(type_id, i); // Get the HDF5 class class_id = H5Tget_class(member_type_id); if (class_id == H5T_COMPOUND) { // Get the member size for compound type itemsize = getNestedSizeType(member_type_id); } else { // Get the atomic member size itemsize = H5Tget_size(member_type_id); } // Update the offset offset = offset + itemsize; } return(offset); } int main(int argc, char **argv) { char file_name[256], dset_name[256]; hid_t file_id, dataset_id, space_id, plist_id, type_id; hsize_t dims[1], dims_chunk[1]; hsize_t maxdims[1] = { H5S_UNLIMITED }; size_t disk_type_size, computed_type_size, packed_type_size; if (argc < 3) { printf("Pass the name of the file and dataset to check as arguments\n"); return(0); } strcpy(file_name, argv[1]); strcpy(dset_name, argv[2]); dims[0] = 20; // Create 20 records dims_chunk[0] = 10; // Create a new file file_id = H5Fcreate(file_name, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); // Create a simple data space with unlimited size space_id = H5Screate_simple(1, dims, maxdims); // Modify dataset creation properties, i.e. enable chunking plist_id = H5Pcreate (H5P_DATASET_CREATE); H5Pset_chunk(plist_id, 1, dims_chunk); // Get the nested type type_id = create_nested_type(); // Create the dataset dataset_id = H5Dcreate(file_id, dset_name, type_id, space_id, H5P_DEFAULT, plist_id, H5P_DEFAULT); // Free resources H5Sclose(space_id); H5Pclose(plist_id); H5Dclose(dataset_id); H5Fclose(file_id); // Compute type sizes for native and packed disk_type_size = H5Tget_size(type_id); computed_type_size = getNestedSizeType(type_id); H5Tpack(type_id); // pack type packed_type_size = H5Tget_size(type_id); printf("Disk type size: %d\n", disk_type_size); printf("Packed type size: %d (should be %d)\n", packed_type_size, computed_type_size); H5Tclose(type_id); return(1); } PyTables-3.7.0/tables/tests/create_backcompat_indexes.py000066400000000000000000000022171416254111300234000ustar00rootroot00000000000000# Script for creating different kind of indexes in a small space as possible. # This is intended for testing purposes. import tables as tb class Descr(tb.IsDescription): var1 = tb.StringCol(itemsize=4, shape=(), dflt='', pos=0) var2 = tb.BoolCol(shape=(), dflt=False, pos=1) var3 = tb.Int32Col(shape=(), dflt=0, pos=2) var4 = tb.Float64Col(shape=(), dflt=0.0, pos=3) # Parameters for the table and index creation small_chunkshape = (2,) small_blocksizes = (64, 32, 16, 8) nrows = 43 # Create the new file h5fname = 'indexes_2_1.h5' h5file = tb.open_file(h5fname, 'w') t1 = h5file.create_table(h5file.root, 'table1', Descr) row = t1.row for i in range(nrows): row['var1'] = i row['var2'] = i row['var3'] = i row['var4'] = i row.append() t1.flush() # Do a copy of table1 t1.copy(h5file.root, 'table2') # Create indexes of all kinds t1.cols.var1.create_index(0, 'ultralight', _blocksizes=small_blocksizes) t1.cols.var2.create_index(3, 'light', _blocksizes=small_blocksizes) t1.cols.var3.create_index(6, 'medium', _blocksizes=small_blocksizes) t1.cols.var4.create_index(9, 'full', _blocksizes=small_blocksizes) h5file.close() PyTables-3.7.0/tables/tests/elink.h5000066400000000000000000000067361416254111300172320ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿØ ÿÿÿÿÿÿÿÿ`ˆ¨ èTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈHEAPXÈpepHˆ¨ (TITLE (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.0 ¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿx HEAPXppep3HSNODh p (TITLE (CLASSGROUP (VERSION1.0À ¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿx HEAPX PSNOD¸à (TITLE (CLASSGROUP (VERSION1.0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ pep3¸ @pep2elink2.h5/pep PyTables-3.7.0/tables/tests/elink2.h5000066400000000000000000000042761416254111300173110ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ¸ÿÿÿÿÿÿÿÿ`ˆ¨ èTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈHEAPXÈpepHˆ¨ (TITLE (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.0¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈHEAPXpPSNOD0P (TITLE (CLASSGROUP (VERSION1.0 PyTables-3.7.0/tables/tests/ex-noattr.h5000066400000000000000000000300661416254111300200420ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ00ÿÿÿÿÿÿÿÿ €`HEAP €detectorcolumnsàTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿX €`HEAP0@!TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ° ÐSNOD€`((Ðp!èÿÿÿÿÿÿÿÿ deflate20030130201707ø /`SNOD TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ”0(/HEAP`(X#TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ `((P  ?@4 4ÿ200301302017070P 0TITLEPressure column@SNOD 0 °  20030130201707€  0TITLETDCcount columnXð?@"@0@9@B@€H@P@@T@ Particle: 0Particle: 1Particle: 2Particle: 3Particle: 4Particle: 5Particle: 6Particle: 7Particle: 8Particle: 9x^íÐ;N1@Ñ—‚(RPPPDˆð‡ÐÄD”l! $*IJVÃrXvÆÂ5ÝyÒÈ“ñ•cˆíìtËvžÞ?^Ÿß^nÛ9þµÑëõòïYý6ló“ˆÍªÛþ^E¿ßÏ/Óš—õÏé§9OÝö<Å`0È/“šï¶ùYÎn»íÃÃa¹À¸æó6?ϧ×ü8Åh4ÊIyÊìµùEÄWÍ—)Æãrrù‡2ûm~™/³îv×)&“rïrÿ2m~q×å›ûÓiÑ(:em~»zúCŠÙ¬˜û2Gm¾Œøìòô˜jf!@€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€üƒÀùN9 20030130201707¨  0TITLE Name column`table à/ADCcountTDCcountgrid_i grid_j idnumber @namepressure#  temperature' ?@4 4ÿpressureTDCname8x^íÒ¹nSA€áãx_å‚‚‚ÂB QÄì¡ÉRP¦ ¡A $*ÄøYxš<¢f›ñL|{ªïHѽñü¾qÄ~fõ±Ÿ‹w_¾~úðùòùf?Ç×–"z½^þÚ>tómÄî´.?£££ü2iyyìþ ç©.¯Sôûýü2nù¢›?ÌÙ‹º|;Å`P0jùº›?Ê»·ü8Åp8ÌIù+s£›?ޏjùIŠÑ¨ì\¾¡ÌÍnþ$欮ž¥˹ËùËÜêæO#Îk¾{•b2)E§Ì¦›?Ë]Ûý"ÅtZÌ‹}™;Ýü$â[ÍÓë³Ùõ½ÛÉ·ùV¯ÚîoRÌçóœüÉS6¸ßÍó­þh»¿M±X”«ùÝòòù÷V·ùV×/ëQß§X.—ùåWËóæùVïÕ|s™bµZå·Ÿ-Ï ‡yÞá¼æ»©‰x @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ @€ÿOà/¼™hi PyTables-3.7.0/tables/tests/flavored_vlarrays-format1.6.h5000066400000000000000000000305151416254111300233600ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿG1ÿÿÿÿÿÿÿÿ €`HEAP(€vlarray1vlarray2ØTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàƒZdklZdklZlZl Z dk Tdk Tdk Tdk TdklZlZlZdklZdklZdklZlZd klZd klZlZlZd kl Z d k!l"Z"d k#l$Z$dk%l&Z&dk'l(Z(ddddddddddddddddd d!d"d#d$d%d&d'd(d)d*d+d,d-d.d/d0d1d2d3d4d5d6d7d8d9d:d;d<d=d>d?d@dAdBdCdDdEdFdGdHdIdJdKdLdMdNdOdPdQdRdSdTdUdVdWdXdYdZd[d\d]d^gOZ)d_S G)À ˜'Àÿÿÿÿÿÿÿÿ 8shuffledeflate(LÞE (CLASSVLARRAYSNODÐXTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿs`(GCOL  566567756988P ÿÿÿÿÿÿÿÿ 8shuffledeflateh¢ÞE (CLASSVLARRAY+ TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿtÓ( (FLAVORnumeric (VERSION1.2 8TITLEragged array of intsxíÐA °Áx£oþ•pHà9.­632V @€ @€ @€ ÐY FÍÊÎl'@€^vÜ  @€ @€ ð“À/ÈBxíÐA ÀOôÍ¿‚žk ´×¢G† @€ @€ @€ @€/ TÖ¨ùòÛ  @€[§B€ @€ @€ü$°O¼K€` (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS°ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I0 sS'complevel' p7 I0 sS'fletcher32' p8 I0 sS'complib' p9 S'zlib' p10 sb. 8PYTABLES_FORMAT_VERSION1.5 (FLAVORpython (VERSION1.2 8TITLEragged array of strings PyTables-3.7.0/tables/tests/float.h5000066400000000000000000000112061416254111300172210ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿ`ˆ¨ˆ¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ00HEAPX@Èfloat16float32float64longdoublequadprecision(   `<’VDQhSNOD xˆ `0p(  œx’VDQh( ?@4 4ÿ ð’VDQh<@BDE<@BDEF@BDEFGBDEFGHDEFGH€H€?@@@€@ @€?@@@€@ @À@@@@€@ @À@à@@@€@ @À@à@A€@ @À@à@AAð?@@@@ð?@@@@@@@@@@@@@@@@ @@@@@ @"@€ÿ?€@À@€@ @€ÿ?€@@À@@€@@ @@À@ @€@@À@@€@"@ @À@à@À@€@ @À@à@€@€@ @À@à@€@@ÿ?@€@@@@ÿ?@€@@@@€@@€@@@@€@À@€@@@@€@À@@@@@€@À@@ @(OP@@ÿ? à’VDQh( €ppÿ?ä à’VDQh PyTables-3.7.0/tables/tests/idx-std-1.x.h5000066400000000000000000000640461416254111300201060ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ hÿÿÿÿÿÿÿÿ €`HEAP €table_i_tableàTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà 7])) vlarray.append([5, 6, 9, 8]) # Now, read it through an iterator: for x in vlarray: print vlarray.name+"["+str(vlarray.nrow)+"]-->", x # Close the file fileh.close() The output of the previous program looks like this:: vlarray1[0]--> [5 6] vlarray1[1]--> [5 6 7] vlarray1[2]--> [5 6 9 8] The `objects` argument is only retained for backwards compatib XÈ0Ð(SþظE (CLASSTABLE (VERSION2.6 (TITLESNODX8ÐTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈ0S€` (TITLE (CLASSGROUP (VERSION1.0 ØFILTERS²ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I00 sS'complevel' p7 I0 sS'fletcher32' p8 I00 sS'complib' p9 S'zlib' p10 sb. 8PYTABLES_FORMAT_VERSION1.6öÿÿÿöÿÿÿ ‡%À ‡%ÀþÿÿÿþÿÿÿpèfÀpèfÀÄ­T@Ä­T@ !]%@!]%@ dpÔ#@dpÔ#@Hk @Hk @ Hné&@Hné&@ ȶD(@ȶD(@ ¸öA%@¸öA%@@#ù @@#ù @ :$)@:$)@ ÀM$%@ÀM$%@|ý0@|ý0@æ9J.@æ9J.@ ˆíW'@ˆíW'@®0E3@®0E3@ †|)@†|)@ ôGš)@ôGš)@P­Z¼1@P­Z¼1@Vã83@Vã83@ äð$@äð$@Þ÷0@Þ÷0@"/43@"/43@=ž¶;@=ž¶;@x1/7@x1/7@Ô#é>@Ô#é>@5Â<8@5Â<8@ü5î3@ü5î3@´JÐ5@´JÐ5@""ü A@ü A@oÛ:@oÛ:@ò}@9@ò}@9@##Ð,¢A@Ð,¢A@àˆQ;@àˆQ;@&&Ò@C@Ò@C@'',~ÄC@,~ÄC@##`¥Û¦A@`¥Û¦A@## ›ŠûA@ ›ŠûA@%%ÀßÛB@ÀßÛB@((àøçD@àøçD@%%€äUØB@€äUØB@&&N[C@N[C@%%Ã'ÂB@Ã'ÂB@!!Ndÿ@@Ndÿ@@--€RÓF@€RÓF@))€ö}¡D@€ö}¡D@))€œN«D@€œN«D@33€yÒãI@€yÒãI@22S;I@S;I@++—v÷E@—v÷E@  ècol1 col2 col3 ?@4 4ÿcol4 ?@4 4ÿ2ÿÿÿÿÿÿÿÿ 0 NROWS@2 0 FIELD_0_NAMEcol1 0 FIELD_1_NAMEcol2 0 FIELD_2_NAMEcol3 0 FIELD_3_NAMEcol4 0FLAVOR numarray 8 AUTOMATIC_INDEX  0 REINDEX à 8 FIELD_0_FILL  8 FIELD_1_FILL  @ FIELD_2_FILL ?@4 4ÿ @ FIELD_3_FILL ?@4 4ÿHEAP0˜^TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ" 7])) vlarray.append([5, 6, 9, 8]) # Now, read it through an iterator: for x in vlarray: print vlarray.name+"["+str(vlarray.nrow)+"]-->", x # Close the file fileh.close() The output of the previous program looks like this:: vlarray1[0]--> [5 6] vlarray1[1]--> [5 6 7] vlarray1[2]--> [5 6 9 8] The `objects` argument is only retained for backwards compatib È^àHEAP0¨`TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿh$ 7])) vlarray.append([5, 6, 9, 8]) # Now, read it through an iterator: for x in vlarray: print vlarray.name+"["+str(vlarray.nrow)+"]-->", x # Close the file fileh.close() The output of the previous program looks like this:: vlarray1[0]--> [5 6] vlarray1[1]--> [5 6 7] vlarray1[2]--> [5 6 9 8] The `objects` argument is only retained for backwards compatib Ø`ÐSNODà!Àˆ01 Dø0  (2ÿÿÿÿÿÿÿÿ2 h: þظE 0CLASS INDEXARRAY¨bØSNOD°%X#  (2ÿÿÿÿÿÿÿÿ2 À& þظE 0CLASS INDEXARRAY€cØTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(ø( h2(2(¸2((˜4 HEAP0Xd ˆdÐ     !+ $%*(& ‡%ÀpèfÀÄ­T@Hk @@#ù @dpÔ#@äð$@ÀM$%@¸öA%@!]%@  !+$ %&(*öÿÿÿþÿÿÿ !"###%%%Hné&@ˆíW'@ȶD(@†|)@:$)@ôGš)@æ9J.@Þ÷0@|ý0@P­Z¼1@"/43@Vã83@®0E3@ü5î3@´JÐ5@x1/7@5Â<8@ò}@9@oÛ:@àˆQ;@=ž¶;@Ô#é>@Ndÿ@@ü A@Ð,¢A@`¥Û¦A@ ›ŠûA@Ã'ÂB@€äUØB@ÀßÛB@&&'())+-23")#'-.1,0/Ò@C@N[C@,~ÄC@àøçD@€ö}¡D@€œN«D@—v÷E@€RÓF@S;I@€yÒãI@")#'-.1,0/TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(à2( 3(03(X3((p4 TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÐG 7])) vlarray.append([5, 6, 9, 8]) # Now, read it through an iterator: for x in vlarray: print vlarray.name+"["+str(vlarray.nrow)+"]-->", x # Close the file fileh.close() The output of the previous program looks like this:: vlarray1[0]--> [5 6] vlarray1[1]--> [5 6 7] vlarray1[2]--> [5 6 9 8] The `objects` argument is only retained for backwards compatib Xfð ?@4 4ÿ(2ÿÿÿÿÿÿÿÿ2 `T þظE 0CLASS INDEXARRAYSNODIÀF  (2ÿÿÿÿÿÿÿÿ2 (J þظE 0CLASS INDEXARRAYHgØTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(`1( ˆ1(°1(Ø1((5 TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿP2P €3PÐ3P 4P(À4 col2col48 HTITLE#Indexes container for table /table (CLASSTINDEX (VERSION1.0 ØFILTERS²ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I00 sS'complevel' p7 I0 sS'fletcher32' p8 I00 sS'complib' p9 S'zlib' p10 sb. 0 PATHNAME/tablesortedindicesÀˆ 8TITLEIndex for col2 column (CLASSCINDEX (VERSION1.0 ØFILTERS²ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I00 sS'complevel' p7 I0 sS'fletcher32' p8 I00 sS'complib' p9 S'zlib' p10 sb. 0 DIRTY  0 EXTDIM  0FLAVOR numarray (VERSION1.3 0TITLESorted Values 0 EXTDIM  0FLAVOR numarray (VERSION1.3 0TITLEReverse Indicessortedindices Dø0 8TITLEIndex for col4 column (CLASSCINDEX (VERSION1.0 ØFILTERS²ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I00 sS'complevel' p7 I0 sS'fletcher32' p8 I00 sS'complib' p9 S'zlib' p10 sb. 0 DIRTY  0 EXTDIM  0FLAVOR numarray (VERSION1.3 0TITLESorted Values 0 EXTDIM  0FLAVOR numarray (VERSION1.3 0TITLEReverse Indices PyTables-3.7.0/tables/tests/indexes_2_0.h5000066400000000000000000001666011416254111300202250ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ{íÿÿÿÿÿÿÿÿ €`HEAP(€table1table2_i_table1ØTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàˆ8 ôèÜh 8shuffledeflate8áªýÃG (CLASSTABLE (VERSION2.6SNODÜ)K4hÐ(Dh 8shuffledeflate¬!áªýÃG (CLASSTABLE (VERSION2.6TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÄ 0á¼DHEAP`(ÀHEAPÀhsÀ  3Áhxí˜û_Ò÷ÇAE¼£‚ˆ* rPA‘/¢¦ÜUE„DA\­UËÖeG[iÍšµ¬­Ym«VëžîQéYZ[›ÖÙjêlUÇÕÊ.;ýùï?àùx<Ÿ¯ï÷—F£-¸®g^ ¸›-M,Ggr{¬ öÙš©1ZÇ×åî6×Ê[Är©så7™?òWhé>3Ö†*cÑØë¸JÈ]b®k¾IQá‰Ü¹¼Càb5>Ø0g‚îÆ¸'5Ÿ3Å®6νEJ Û8 ôÏÆÞà7ZËõïÓ„Ä_PÃÆÏdÂÀ,€çÞ~Bp;¦ÚªœOª>³“PñêLHäñ˜ñŒi=Õ’.oò]Wº?‘'Äx?øeà×ÐW˜_Tœ/Žãð×FÝu°Àº ©r¯Pß%02€ ÆÇÝ‹ìn%MKï²K:úÿrl3€;fJ?n!©58ÚTeBÊÄHÓT/)à<ØÏÙN‰a‘—zûv"à±iý±p?úwÚçQçy¿2ô&^¤/Ão eu;V9û­=©ºÏïïö5œ›+>%Ó!kržWÅëJ;©N–ìÄ,?¤ ]ÿ>)H¢ntMŽ×wE“¯•o‹“C¨•‰µçpŸÇòbØ.Ce†»Xv6Œu&ÆPÊ¥è!÷eÛ´e¢l}­gŽe(ÀçÚòI‰Œ®¦ÈˆWu]ÖâÚŽìDË‘²î¤É¬)Ü=ãù§å4P§,$JŒËƒïdŽ–>-–Æû¢…Éݦ›úô—\ŸææQGB ~ÛŸªrL9…@SAH¼½7ñaí“60Îý´FÐî\•A`;R×AÝø,í?ú)¸`’ QÓÖ² zäßa·âÛÖ Ö«¶ËzS_ê•®Ø#{å 8n°ü`°°©W²ôPÑ £%Ú¥«Òýï˜MQ&ƒÞI·¼TßÁWsO³s5Otþ„QÌq@véí¼‘¸c>sØØ¢*õkãMÀ1™º¡Þµ}„Ø]’' ÅöG• ç·ö”kE@ý†šÖ·:ÏËP Ž£]ƒ›$"øc°ñvL´C;³b+8£ü”¢5yWyL3²‘¡Ÿ·ü•~´Pà×iïzÉÔÁ”Û5’Âߢ'’û¨}–PǹôLÌ-z®Ä9Y,@k¶ ªå ¡Ò® •l¤¿+ÚahÍÚÇΖ¼WΊz¸Ÿw¢æ‘—{%ÌIj$†™eÿ!ÏÂüª.ƒžy§pƒdBá±ãÀ«ÊÉÜ,Â(ÃRüx¨á~˜4OfXt~ˆ¾<ìVYö§…$Šé)T5o ¡~jÞRÑY\ߊÑ^ÕÓ b顊½Íëq¹Á3|=¡Þ­»+ì¶(© ~êØ~3¿ó—h+t€ðRñ3}´Ãß³r)§±ùû 3ȤIz·@ +èò¬± (]£NÈ…_„úzÅg|„Ñ‹ ß¼"Ƶ£#Zm_ ÖZ&’θ¶7ËÛƒö@1Ú¡è¿Ú¼"d\µù5õóÔykÂÞ¸wv(pÖ}…CŒuM}ðÛš%÷d ªzcêZÛÎÚl4œ_1Ü܆7VD-Æž±Ù(“[´¼Ôú±H—츊º z¤:Ä v˜öròÁ5ouÁ ÁlbוSÌ4Áx|xÞÅ´[•Wþ¶ïy»Â®%’b‰bkwºm^;€•ý/i}ÀZà“8uàƒì½´Œw,½qeܱ`(sÊ[nz”ׂ9½Eû•¹øÄʃæÍáTro ‚½¦MÄþ*$=ì$ŠX ²3ŠXLâv’ge(Ÿ‰êõåuÕ*jðô*=ïJŽ–ã÷ú!sdz awØ÷UÍþ%Ô­‘äô¬6üH¯@h¯Án¢ Òoê€Qü.YOuqdÃî ×5Qs²]™NÚ5ÚçʈJQé·-+ \‘-»0Mø»õ¨„”1à¶»Cü|ªzîä£a¯<§É¾ z¤ÿÕdµZŠÒ‚ë?JÏ|…»!º˜Ë¤¥6¾ÈEîbÇë̔òË"²ç\˜Võj˜YOh6Ýñ=Ð$A…CÕ>1º$ùeãEÐû ýÁj ÝUJ1kt—RÀzKéÄÁ’™œh¨Ôö‚úƒþ%B`µû¦óèÉãA2Aÿi‹Ì¯dzÖX†>@`Šø¡?¹€q Îð4vù<ùEÁI·À“îäÖjo€žMføî ßÙÆõ–9Ì_K†| ‚nlßÊ­`ŸJ,*}z,|_Õ³–)aài ò³àwD_zÛš4´þÁeŽ!-ážôÌ¡¢Ë˜"½Ž5ˆÐ€!¡KæÞœIš˜± v$`SÀýfº(‚‡`I¾#pÊZçWÊ‚ÚÒàœ6&VQR½3qªk·@¨Á0WáU[™”3nL¸úOýBH5Á7]«ê ä´üSÌYÞ¼CÃOç`ÈI¯„Vh™î¢í©ÚåÞƒ,`À“wù¶DvAÑ6QA䡬çBª°ãU@&…ëž‚ÿäYc9 »!™àHŸ}œ3ÄÑ ù_E_sGÀjÌä„î+Åz>9i‘ÚÚüIÝ×Fxê òµ¸‰}ô +C¾ûýQÕµúm”o½—ŒKpk­Wê( ¿L”žH2ÑŽuÎVmŒ4Šiz°éjr“KX'ßõ”ŒóãǤÊ‹4ý,vâW v@ÝÃ#—CÏÃ\Xå•ÒüÙ›}¼ó !ˆ.+f$? †äL€óÓ^чýþ(žånì0¤…ü›Ú—_ê<¬;™rƒ¼)ª.ü¿¢#Q©ðÝGÌŽÀÍźìI8K#¥ÂŒæ>PŒ•$;»jw$ Ç®™ƒþυت‡V¥×Bd5‘+Ê\›‘á‚[ lm™e{p?}Ò3ÆÛ.à÷æ*²,Þø–ôL_*ÑKv©j0\ô\wÁD1ÝÆšÐC¬¡¢ãzv˜ý\=F¸\£UvÔm¬4Er¿Ó+PQÞ/œ{ªmØ5u[,>IKH e›­OG„Èõç‹ÂT•Å‚ò£æàK.’ø¼ð…“ù®XUÐx%5Ë„‘ª€sí$êá„wpG4o[ÏÃã½¹ ­ß¥š(%Ývqš3QSÁ¬RßÃÉ'<^ž¢¬Hæ-ù’éjs£²`Sé>™:¬ñ2ÿ6WHUmÇ%‹µX²¡>!> ä’z” qŠäCüÁ̱<{‹œ{­üµ¥ßl srŸµ?m‹i9…{ˆžÕ΄Ý=¨Ûx¤LEXªðüÁoý‹ü?Žyè€` (TITLE (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.0àvar1var2var3 var4  ?@4 4ÿÿÿÿÿÿÿÿÿ @TITLEThis is the IndexArray title 0 NROWS@ 0 FIELD_0_NAMEvar1 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar3 0 FIELD_3_NAMEvar4 0 FIELD_0_FILL 8 FIELD_1_FILL 8 FIELD_2_FILL  @ FIELD_3_FILL ?@4 4ÿàvar1var2var3 var4  ?@4 4ÿÿÿÿÿÿÿÿÿ @TITLEThis is the IndexArray title 0 NROWS@ 0 FIELD_0_NAMEvar1 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar3 0 FIELD_3_NAMEvar4 0 FIELD_0_FILL 8 FIELD_1_FILL 8 FIELD_2_FILL  @ FIELD_3_FILL ?@4 4ÿTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ' $*áçEBüâ= ›Ãxc`£!0#5xí–÷?þG×H—DFŠ2.Ñu•p­dǵ®™‘[öÌÅ•=>ÙóÚ{7{gĵWfFW¸²"Y!ã^¾ß?âþ–×psžïÞbbbbÓr¯¿QßT»I;é¿×/å—›¤¼ÈÜbcBþU‹]M¡#þ^AWõ‘*{>v~òí¹ŠÞÜþ‘·¢¦üpÕÍ”oíÚÚÚŒ„j¹6!Ô‡+la}öâêY+Tå‚iÕŸa¦LJ8Éðöµ©ÍèC8:\¬õ ßìÞ^ÉÀWû&¿ Ãý I(»“÷O²Ë#moøÝB0–9¦ Íæ,w-g}”—§0»^±%Ñ»&_'·XQµ³¸!Nõ©¹“â ìÅ»Tbhß¶µ fb0iïÜu¤¢‡x[û¹)¿×´¿Jƒ•NµüJk·]†JL§XZÕÝ¡¿«›þ¨kÜ-¼_/ ‘|mb¥H°GüW¬ ‰jÄH°rÚïFE‘Vî’FFfBWêþri›2TQSP©Aùà ' íŽ]ž‹~ªsÆ®÷ìÔŠ§” û;=ª Fé4à F«—Œ¾4èö›sÜÃ-Ì1~C¹,Dt δ~ìZIÂ⯇ZÙðó9”(È#¤xn·!mqÚ ã=õ§t‘ÑH‚f¦¹å?Ó5³ü¯N*¢@—ù+ª+Yvš—“ºÀ¦wS|éxDïãnWՠί rÝ4ãÊZW1ô¡¦-Ø8 Oƒ·6ÓsÔÕ ôÑZ­uÆûéþØã* Hlmʈãê±6ùŽîFxÉ•4ÉA'o¾XžØ³Ráãc¹]ÁÉæë!++d¨&§v»?òß ¢[ö†tBÙN³¡‰S¤L¡Abœ”2©õ.yŽ2™â³ß›h-¤Wî?X¬)eæ†IMÊyåz"Þ'€ÂTTÇÈ~ÄÕhy$ä¯)%|ý„±M ,§}w+=§¹ß%PäYÀW:Oš­i¿èÒF¤=Œ”qÎeÀBøÝÀ-AùÆtP`r6jy>óO&Aõ¦c ãr¤ÿxhÔ»Æ åft9© d犘¬þútÿ@çý^@,¿g=†·H¹­ ¿†/H±u»‰ç. L»i![G:{°ú…Ó!fü‰ÖOf±‰áÊ_‹¤'XS‡$Ëc¡×²UÍEð2dÍ,¶ËºÁqÇ'êvˆw–&ÎÍ~˲—'R.šÀ>¿I\;W±ˆ %áyØÚ•ñ²/¹ÜCj½KžA¥Õ£¦VÈh.­{YìÉc¼Ï™]3ã»mõ+3Ë9žƒ{µ/ëójs“5¯áR¯Ôæ3Ãúסú[l¼lÄM»óÐ^ºùbv *µÓ>Ô§ÕH*wœ*0+çh€òbÇôÖœi¹:Ö¥,ìüÚ’«ŠºZ_¤<æ›I_8 T6]ßMXçPòß&¼Û½„Ôúv8n¥«+årôbö¼wžâf«ÛËÝÍ+÷ïùÁ‹¢çóÚЉӾ:!PÀQ¬uãÜ0 ÏWtðUÜ"x“¹1ìÐ IË^G‡…sxz Ê"Ô¿È{>n0šC…\äcµ„öy;àš+jIŠtÊ ©9” Æ-îj\SD›‘¹ƒ¢Â0m°=r·ú©õ.ySA}rxpïhV´ÜL …þhÜCÙœ ´uo9ãÀÐ{–G ´«ã ¿äBaD„z„!dÀ(2òíl—¼û£zÌ÷VfGÆfü`È–@ì¹­ŽV‰!²èÎޙƹù«p¿Z^â |S]¥æ¶îj´ª=ç]ºÿ²awûl|S¼\ÐNÆÀOzeQ‘ÅSØËAH]`0•ef¿ßºgYÛîøÙí D<ÏA)ê2{ëõͲLÿ=KºiVÚlíÙPóÇèDT~ Î÷7ã²þ‹y¡½C'®¨ï_¯XŸ;…˜®ñŧðWÚ@ÌŸ'Š)di7ø£>©¿åÂ/å¿ .a#„ÒTµxà„5º¦ò7K‹+ ®Ø´v5ý$kXV(ÍÊ∠©õ.y¢ *«psHÞ#‹˜ïuWüÆs¯¹G0J”'ÖSpçtÎJö‘¾0àÍì¬rH¡gÜDë05È_ðE>¸Vø´~Žbúeø)r¦áÅ–óŠ\ª¯Ø\}°dÍ1Fq8s;啣ьË>e2ù]^¦š^¯äJ¼„Ô¤c^oÛZ%uÆOáÙúŸÛ½ý¥Ê¥¤ƒ0ÿnÃÿ[w²á1ÿósùLBÝbE\ͧóþÔB»ûOTaÄoóQuzGº µ±\îܵ.òõëó:¶Û²Ò­‘žô4hM`«­gÛ`Þ¡ÆK)1** 3Mð³7Ví©à[Ô]{bDÈ›"µÞ%øÌ]rƒšmÒMœñœsG‹vÔ ’4Ó²±¹I3ÓCÎ8µP¡D"_²NA7ÛˆýÏI–?‚û=wÃL Uô|€uÉúF› ¶[â¸åúÍì±õ6ÀŽÅà· Áx1âpúSy¼ý¼’ƒ§±K8§ìÆZºð„м¬}°Ñfñ–²¬Óå'‹ä/’©ÐwJ6;2ª«Š d´ª~°#í¬>Øqe9%#©Pƒª*n„Þ¿S€¿/EÉçÚÅèr»<õCÙkù?JsOS)†[)©ÜéOWòâyÄ0EêÊ·ÂèÏP´ßœ-PÃYÿeÁÉ·‘¬ªÃ3ŸËGŒ@f¯V¥U !ƒàñÞg0ľÁRÆ¢È^þ°»¬Éýþyà‰HÙ ¸B¬í00ÉÞ±%ÍàÚuTú¦¤¥xýÔÌlž^ð‘}Ø.ãr3‘ êÊÐZ³ÛVW¿uÿM!²å.Ñ‰Žžq©Yå=˜`x§…âåHSš{? HêuœšËW³”\R5.P¸— xvÆix`Äçœ>¬snÖ«‘Ñ´Y èbÈ][ûÏïAÒ4ÿ¿[MW©§^+ï¬ëì§5³¼êN•s< ~âp'"¹šbÇ( ˉV·åª—>3gÆŽÙé!„-àð }¨ÎÇs¤·i[Aô ¦{zÄ»\£>&¿ Þ6œbdùc4’ \¬%/È`½gYøæ·âM„;-"WåXÝc£G3çcf¸×ƼØXO. 2õs×Iê÷ÏÃäÿ;G¦áŸƒÕ)˜Í-®:««2^ÍJÑŠˆáÝgþÀµ&5âÞ䡹!¯u4]”€Šš6ä¬ùù-oΨÆoç;h1çDb½ù€¤‚#§úÊgÔ[¶©ÞZB Ò†ïn¦d ¥©Rb[KÇe…nÂ(˜ný ËÛ é@‡%ˆ–2f•ã½]ù¹y}å¸|þùMHà@Ì€TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‹8ˆ8TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿã:@«D8SNOD»Y›WcW S¢3 ûŸ;‚€ãØk6  ›Äð ?@4 4ÿ( ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃGSNOD0k@([?+<XóE@‹B ‹Åð@( ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃG {Æð ?@4 4ÿ(ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃG  ?@4 4ÿÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿªýÃGkÇÐ ;Èð ?@4 4ÿ(ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃG  ?@4 4ÿÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿªýÃG+ÉÐ  ?@4 4ÿÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿªýÃGûÉÐ  ?@4 4ÿÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿªýÃGËÊÐ  ?@4 4ÿ 8shuffledeflate3OªýÃG›Ë°SNOD K>;=Ó9H›C8{A @ 8shuffledeflateGªýÃGK̰TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ>²]?«\ TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJh]HEAPÀhûÌTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûZ@+m8  »Íh ( ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃG#ÐØSNOD0ëh(Ûg«dXsn@ kxíÐ1 Â0Žù×<:h 0*p£ß¶  @€ @€ @€T <xíÐ  ÷Om( 0`À€ 0`À€÷xíÐ1 Â0Žù×<:h 0*p£ß¶  @€ @€ @€T <xíÐ1 Â0Žù×<:h 0*p£ß¶  @€ @€ @€T <xíÐ1 Â0Žù×<:h 0*p£ß¶  @€ @€ @€T <xíÐ1 AFdá_ ˜›û¹K¯J @€ @€ @€@š@_i¯ü!@à#0×giC€@šÀœ1±xíÐ  ëûw6‡D ³  0`À€ 0`À€ 0`À€ 0`ÀÀëßòxíÐ  G²ºÏ"ÔaÀ€ 0`À€ <>!xíÐ  ëûw6‡D ³  0`À€ 0`À€ 0`À€ 0`ÀÀëßòxíÐ  ëûw6‡D ³  0`À€ 0`À€ 0`À€ 0`ÀÀëßòxíÐ  ëûw6‡D ³  0`À€ 0`À€ 0`À€ 0`ÀÀëßò ûÐð@( ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃG (ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃGëÑØ ÃÒ¸ÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿªýÃG (CLASSEARRAY (ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃG{ÓØ SÔ¸ÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿªýÃG (CLASSEARRAY  Õ¸ÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿªýÃG (CLASSEARRAY ÃÕ¸ÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿªýÃG (CLASSEARRAY {Ö 8shuffledeflate³wªýÃG 0CLASS LASTROWARRAYSNOD Ëf»eëYHl8ûi @ 8shuffledeflateƒoªýÃG ×°TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ>^?C\ TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,ð]HEAPÀh»×TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ƒ@C8  {Øh  ( ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃGãÚØSNOD0‰(ó‡ÄX‹Ž@#‹ »Ûð@( ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃG  (ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃG«ÜØ ƒÝ¸ ÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿªýÃG (CLASSEARRAY  (ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃG;ÞØ ߸ ÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿªýÃG (CLASSEARRAY Ë߸ ÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿªýÃG (CLASSEARRAY ƒà¸ ÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿªýÃG (CLASSEARRAY   8shuffledeflateË—ªýÃG;á°SNOD ã†Ó…k‚H3Œ8Š @ 8shuffledeflate›ªýÃGëá°TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ>Z^?ê\ TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ)‚\HEAPÀh›âTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ“£@[­8  [ãh ( ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃGÃåØSNOD0©( ¨Û¤X£®@;« ›æð@( ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃG (ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃG‹çØ cè¸ÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿ ªýÃG (CLASSEARRAY (ÿÿÿÿÿÿÿÿ 8shuffledeflate ÿÿÿÿÿÿÿÿªýÃGéØ óé¸ÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿ ªýÃG (CLASSEARRAY «ê¸ÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿ ªýÃG (CLASSEARRAY cë¸ÿÿÿÿÿÿÿÿ 8shuffledeflateÿÿÿÿÿÿÿÿ ªýÃG (CLASSEARRAY  8shuffledeflateã·ªýÃGì°SNOD û¦ë¥ƒ¢HK¬8+ª @ 8shuffledeflate³¯ªýÃGËì°TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ>˜^?)] TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ *var4var1var3var28sortedindicesrangesmrangesboundsaboundszboundsmboundssortedLRindicesLRXk6  8TITLEIndex for var4 column (CLASSINDEX (VERSION2.0 0 FILTERS@ 8 superblocksize@@ 8 blocksize@ 8 slicesize  8 chunksize  8 optlevel  0 DIRTY K4h HTITLE#Indexes container for table /table1 (CLASSTINDEX (VERSION1.0 0 FILTERS@ 0CLASS INDEXARRAY 0 EXTDIM  (VERSION1.0 0TITLESorted Values 0CLASS INDEXARRAY 0 EXTDIM  (VERSION1.0 0TITLEReverse Indices 0CLASS CACHEARRAY 0 EXTDIM  (VERSION1.0 0TITLE Range Values (CLASSEARRAY 0 EXTDIM  (VERSION1.0 0TITLEMedian ranges 0CLASS CACHEARRAY 0 EXTDIM  (VERSION1.0 0TITLEBoundary Values (CLASSEARRAY 0 EXTDIM  (VERSION1.0 0TITLE Start bounds (CLASSEARRAY 0 EXTDIM  (VERSION1.0 0TITLE End bounds (CLASSEARRAY 0 EXTDIM  (VERSION1.0 0TITLEMedian bounds 0CLASS LASTROWARRAY (VERSION1.0 @TITLE Last Row sorted values + bounds 0CLASS LASTROWARRAY (VERSION1.0 @TITLELast Row reverse indicessortedindicesrangesmrangesboundsaboundszboundsmboundssortedLRindicesLRX›WcW 8TITLEIndex for var1 column (CLASSINDEX (VERSION2.0 0 FILTERS@ 8 superblocksize@@ 8 blocksize@ 8 slicesize  8 chunksize  8 optlevel  0 DIRTY  0CLASS INDEXARRAY 0 EXTDIM  (VERSION1.0 0TITLESorted Values 0CLASS INDEXARRAY 0 EXTDIM  (VERSION1.0 0TITLEReverse Indices 0CLASS CACHEARRAY 0 EXTDIM  (VERSION1.0 0TITLE Range Values 0 EXTDIM  (VERSION1.0 0TITLEMedian ranges 0CLASS CACHEARRAY 0 EXTDIM  (VERSION1.0 0TITLEBoundary Values 0 EXTDIM  (VERSION1.0 0TITLE Start bounds 0 EXTDIM  (VERSION1.0 0TITLE End bounds 0 EXTDIM  (VERSION1.0 0TITLEMedian bounds (VERSION1.0 @TITLE Last Row sorted values + bounds 0CLASS LASTROWARRAY (VERSION1.0 @TITLELast Row reverse indicessortedindicesrangesmrangesboundsaboundszboundsmboundssortedLRindicesLRX€ã 8TITLEIndex for var3 column (CLASSINDEX (VERSION2.0 0 FILTERS@ 8 superblocksize@@ 8 blocksize@ 8 slicesize  8 chunksize  8 optlevel  0 DIRTY  0CLASS INDEXARRAY 0 EXTDIM  (VERSION1.0 0TITLESorted Values 0CLASS INDEXARRAY 0 EXTDIM  (VERSION1.0 0TITLEReverse Indices 0CLASS CACHEARRAY 0 EXTDIM  (VERSION1.0 0TITLE Range Values 0 EXTDIM  (VERSION1.0 0TITLEMedian ranges 0CLASS CACHEARRAY 0 EXTDIM  (VERSION1.0 0TITLEBoundary Values 0 EXTDIM  (VERSION1.0 0TITLE Start bounds 0 EXTDIM  (VERSION1.0 0TITLE End bounds 0 EXTDIM  (VERSION1.0 0TITLEMedian bounds 0CLASS LASTROWARRAY (VERSION1.0 @TITLE Last Row sorted values + bounds 0CLASS LASTROWARRAY (VERSION1.0 @TITLELast Row reverse indicessortedindicesrangesmrangesboundsaboundszboundsmboundssortedLRindicesLRX3 ûŸ 8TITLEIndex for var2 column (CLASSINDEX (VERSION2.0 0 FILTERS@ 8 superblocksize@@ 8 blocksize@ 8 slicesize  8 chunksize  8 optlevel  0 DIRTY  0CLASS INDEXARRAY 0 EXTDIM  (VERSION1.0 0TITLESorted Values 0CLASS INDEXARRAY 0 EXTDIM  (VERSION1.0 0TITLEReverse Indices 0CLASS CACHEARRAY 0 EXTDIM  (VERSION1.0 0TITLE Range Values 0 EXTDIM  (VERSION1.0 0TITLEMedian ranges 0CLASS CACHEARRAY 0 EXTDIM  (VERSION1.0 0TITLEBoundary Values 0 EXTDIM  (VERSION1.0 0TITLE Start bounds 0 EXTDIM  (VERSION1.0 0TITLE End bounds 0 EXTDIM  (VERSION1.0 0TITLEMedian bounds 0CLASS LASTROWARRAY (VERSION1.0 @TITLE Last Row sorted values + bounds 0CLASS LASTROWARRAY (VERSION1.0 @TITLELast Row reverse indices PyTables-3.7.0/tables/tests/indexes_2_1.h5000066400000000000000000004374701416254111300202330ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ2?ÿÿÿÿÿÿÿÿ` èTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ˜HEAPX(Ètable1table2_i_table10ˆ¨ (TITLE (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.0€ àÈàvar1var2var3 var4  ?@4 4ÿÿÿÿÿÿÿÿÿ¨ á£U!ISNOD1>É1 (CLASSTABLE (VERSION2.6 (TITLE 0 FIELD_0_NAMEvar1 0 FIELD_1_NAMEvar2 0 FIELD_2_NAMEvar3 0 FIELD_3_NAMEvar4 0 FIELD_0_FILL 8 FIELD_1_FILL 8 FIELD_2_FILL  @ FIELD_3_FILL ?@4 4ÿ 0 NROWS@TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñØá01ð?2@3@4@5@6@7@8 @9 "@10 $@11 &@12 (@13 *@14,@15.@160@171@182@193@204@( àvar1var2var3 var4  ?@4 4ÿÿÿÿÿÿÿÿÿ6á£U!I (CLASSTABLE (VERSION2.6 (TITLE 0 NROWS@ 0 FIELD_0_FILL 0 FIELD_0_NAMEvar1 8 FIELD_1_FILL 0 FIELD_1_NAMEvar2 8 FIELD_2_FILL  0 FIELD_2_NAMEvar3 @ FIELD_3_FILL ?@4 4ÿ 0 FIELD_3_NAMEvar4TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñAáñ@ÈTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyD HEAPX(™@var1var2var3var40Y>y@ HTITLE#Indexes container for table /table1 (CLASSTINDEX (VERSION1.0ÁEpTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿAI@iX8HEAP°hQ[ 0CLASS LASTROWARRAYx30„C#cS4B§·SNOD¹AÁ¿¹4 ±©Á®P 8TITLEIndex for var1 column (CLASSINDEX (VERSION2.1 0 FILTERS@ 8 superblocksize@@ 8 blocksize@ 8 slicesize  8 chunksize  8 optlevel  8 reduction  (ÿÿÿÿÿÿÿÿ 8shuffledeflate!]£U!I‰JØSNOD0aR(¡PaKXAZ@±U 0CLASS INDEXARRAY (VERSION1.0 0TITLESorted Values 0 EXTDIM ¬ È(ÿÿÿÿÿÿÿÿ 8shuffledeflate‰œ£U!I 0CLASS INDEXARRAY (VERSION1.0 @TITLENumber of chunk in table 0 EXTDIM ­ °(ÿÿÿÿÿÿÿÿ 8shuffledeflateYg£U!I 0CLASS CACHEARRAY (VERSION1.0 0TITLE Range Values 0 EXTDIM ­ ˜ÿÿÿÿÿÿÿÿ 8shuffledeflateY”£U!I (CLASSEARRAY (VERSION1.0 0TITLEMedian ranges 0 EXTDIM ® °(ÿÿÿÿÿÿÿÿ 8shuffledeflate‘q£U!I 0CLASS CACHEARRAY (VERSION1.0 0TITLEBoundary Values 0 EXTDIM ¶ ˜ÿÿÿÿÿÿÿÿ 8shuffledeflateÉ{£U!I (CLASSEARRAY (VERSION1.0 0TITLE Start bounds 0 EXTDIM ± ˜ÿÿÿÿÿÿÿÿ 8shuffledeflateùƒ£U!I (CLASSEARRAY (VERSION1.0 0TITLE End bounds 0 EXTDIM ² ˜ÿÿÿÿÿÿÿÿ 8shuffledeflate)Œ£U!I (CLASSEARRAY (VERSION1.0 0TITLEMedian bounds 0 EXTDIM ³ ±Y 8shuffledeflate¯£U!I 0CLASS LASTROWARRAYSNOD ùN9M1HHYW8 Tq\X (VERSION1.0 @TITLE Last Row sorted values + boundsÉ\X 8shuffledeflateA·£U!I!D8\psortedindicesrangesmrangesboundsaboundszboundsmboundssortedLRindicesLRH (VERSION1.0 8TITLELast Row indices 8 nelements  8 nelements TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿYDð¦TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?§TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ЧTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ>–§TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿBÔ§TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿA¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ<N§TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ Á¦ §xc``xc`dbfaecg ´xa [x326153·°dÀ/œ­xc`FF xíÐ1  Iê;ÿƪ£ H 0`À€ 0`À€ 0`À€ 0`À€ã@ŠjxíÐ  VõZ;`ƒ 0`À€ 0`À€ 0`À€ 0ð>0àZ7x3b``Ì3xíÐ  Gïn9ÁüaÀ€ 0`À€ 0`À€ 0`À€ Œ`‚cxíÐ1Ã0,qpÌ¿1„,uÐlF @€@™À•ýÚ%@€ @€ @€ XÕ xíÐ1Ã0,Á1ÿÒ²ÔA³ @€eWök— @€ @€4<°Éšx3444424b03·°403`@EÑÎxc`€xcd„, xcd„, xíÐ  úºN0  0`À€ 0`À€ 0`À€ 0`ÀÀùÀ?ÿxíÐ  úºN°Á€ 0`À€ 0`À€ 0`À€ x@xíÐ  úºN0  0`À€ 0`À€ 0`À€ 0`ÀÀùÀ?ÿxíÐ  úºN°  0`À€ 0`À€ 0`À€ 0`ÀÀùÀ_ÿxíÐ  úºN°  0`À€ 0`À€ 0`À€ 0`ÀÀùÀ_ÿxcd+xc`€xãàäâæáåãgÀ 4]xcd„, xíÐ  ôÿm;` 0`À€ 0`À€ 0`À€ 0`À€óxíÐ  ôo;`ƒ 0`À€ 0`À€ 0`À€ 0ð>0  xã```$ xíÐ1  Oû'6‡D  0`À€ 0`À€ 0`À€ 0`ÀÀùÀ  xíÐ1  }=çßmu4 ä 0`À€ 0`À€ 0`À€ 0``| àxíÐ1  =ç_ou4 ä 0`À€ 0`À€ 0`À€ 0``|  xaÀZxc`€xc` ((©¨ihéè9@(9xãàäâæáåãg Ô]xíÐ1 ±*ª†úWU Ì\˜!3F€ @€ @€Ô lÝ#‡H. u´ <°oxíÐ1 ±Ê©ŒúWU Ì\˜!3F€ @€ @€´ lÛ!ˆ..…´ <`ixc`ˆaxíÐ1 ±J©”úWU Ì\˜!3F€ @€ @€Ô lÝ#‡H. u´ <¾axíÐ11„  øW…æ^:ÿÒT @€ @€ &ГöȾ»ßRG€@šÀoÎËxíÐ11$ øW…æ^:ÿÒT @€ @€ &ГöȾ»ßRG€@šÀߏxc` ›˜08@ºxc`  @q¿P 0 is_csiTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿW¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ p¨áAD 0 DIRTY ÂpTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÆ@YÕ8HEAP°haØ 0CLASS LASTROWARRAYxc`„$xc`€¹#P 8TITLEIndex for var2 column (CLASSINDEX (VERSION2.1 0 FILTERS@ 8 superblocksize@@ 8 blocksize@ 8 slicesize  8 chunksize  8 optlevel  8 reduction  (ÿÿÿÿÿÿÿÿ 8shuffledeflateÚ£U!IIÇØSNOD09Ï(qÍ!ÈXQ×@™Ò 0CLASS INDEXARRAY (VERSION1.0 0TITLESorted Values 0 EXTDIM ± È(ÿÿÿÿÿÿÿÿ 8shuffledeflate£U!I 0CLASS INDEXARRAY (VERSION1.0 @TITLENumber of chunk in table 0 EXTDIM ² ¸(ÿÿÿÿÿÿÿÿ 8shuffledeflateQä£U!I 0CLASS CACHEARRAY (VERSION1.0 0TITLE Range Values 0 EXTDIM ²  ÿÿÿÿÿÿÿÿ 8shuffledeflateQ £U!I (CLASSEARRAY (VERSION1.0 0TITLEMedian ranges 0 EXTDIM ³ ¸(ÿÿÿÿÿÿÿÿ 8shuffledeflate‰î£U!I 0CLASS CACHEARRAY (VERSION1.0 0TITLEBoundary Values 0 EXTDIM »  ÿÿÿÿÿÿÿÿ 8shuffledeflateÁø £U!I (CLASSEARRAY (VERSION1.0 0TITLE Start bounds 0 EXTDIM ¶  ÿÿÿÿÿÿÿÿ 8shuffledeflateñ £U!I (CLASSEARRAY (VERSION1.0 0TITLE End bounds 0 EXTDIM ·  ÿÿÿÿÿÿÿÿ 8shuffledeflate!  £U!I (CLASSEARRAY (VERSION1.0 0TITLEMedian bounds 0 EXTDIM ¸  8shuffledeflate $£U!I¡Ö°Ù@SNOD ÁËùÉñÄHIÔ8éÐ 0CLASS LASTROWARRAY (VERSION1.0 @TITLE Last Row sorted values + boundsÁÙX 8shuffledeflate9,£U!I)Â8ÙpsortedindicesrangesmrangesboundsaboundszboundsmboundssortedLRindicesLRH (VERSION1.0 8TITLELast Row indices 8 nelements  8 nelements TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ a {¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ<‘¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ h¬TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ<© TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ<D© TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ<€© TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ;ͨ TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ l †¨i4P 0 is_csiTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ¼©TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ È©é¿ Â 0 DIRTY y7pTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿù:@QJ8HEAP°hYM 0CLASS LASTROWARRAYxc`dbfaecgÀxc`€±˜P 8TITLEIndex for var3 column (CLASSINDEX (VERSION2.1 0 FILTERS@ 8 superblocksize@@ 8 blocksize@ 8 slicesize  8 chunksize  8 optlevel  8 reduction   (ÿÿÿÿÿÿÿÿ 8shuffledeflateO£U!IA<ØSNOD01D(iB=XIL@‘G 0CLASS INDEXARRAY (VERSION1.0 0TITLESorted Values 0 EXTDIM ¾ È(ÿÿÿÿÿÿÿÿ 8shuffledeflateyŽ£U!I 0CLASS INDEXARRAY (VERSION1.0 @TITLENumber of chunk in table 0 EXTDIM ¿ ¸ (ÿÿÿÿÿÿÿÿ 8shuffledeflateIY£U!I 0CLASS CACHEARRAY (VERSION1.0 0TITLE Range Values 0 EXTDIM ¿   ÿÿÿÿÿÿÿÿ 8shuffledeflateI†£U!I (CLASSEARRAY (VERSION1.0 0TITLEMedian ranges 0 EXTDIM À ¸ (ÿÿÿÿÿÿÿÿ 8shuffledeflatec£U!I 0CLASS CACHEARRAY (VERSION1.0 0TITLEBoundary Values 0 EXTDIM È   ÿÿÿÿÿÿÿÿ 8shuffledeflate¹m£U!I (CLASSEARRAY (VERSION1.0 0TITLE Start bounds 0 EXTDIM à   ÿÿÿÿÿÿÿÿ 8shuffledeflateéu£U!I (CLASSEARRAY (VERSION1.0 0TITLE End bounds 0 EXTDIM Ä   ÿÿÿÿÿÿÿÿ 8shuffledeflate~£U!I (CLASSEARRAY (VERSION1.0 0TITLEMedian bounds 0 EXTDIM Å   8shuffledeflate™£U!I™K°yN@SNOD ¹@ñ>é9HAI8áE 0CLASS LASTROWARRAY (VERSION1.0 @TITLE Last Row sorted values + bounds¹NX 8shuffledeflate1¡£U!I!78 NpsortedindicesrangesmrangesboundsaboundszboundsmboundssortedLRindicesLRH (VERSION1.0 8TITLELast Row indices 8 nelements  8 nelements TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿY7Ó©TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ=ð©TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ iªTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ=uªTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ>²ªTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ>ðªTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ<-ªTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ k7 å©a©P 0 is_csiTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ.«TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ?«á47 0 DIRTY q¬pTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñ¯@É¿8HEAP°hÑ 0CLASS LASTROWARRAYxc` |`à‘a°w9xc¹Äˆ 8TITLEIndex for var4 column (CLASSINDEX (VERSION2.1 0 FILTERS@ 8 superblocksize@@ 8 blocksize@ 8 slicesize  8 chunksize  8 optlevel  8 reduction  9±ð ?@4 4ÿ(ÿÿÿÿÿÿÿÿ 8shuffledeflateAÅ£U!ISNOD0y¹(¡·)²XÁÁ@ù¼ 0CLASS INDEXARRAY (VERSION1.0 0TITLESorted Values 0 EXTDIM Ë Ð@(ÿÿÿÿÿÿÿÿ 8shuffledeflate©£U!I 0CLASS INDEXARRAY (VERSION1.0 @TITLENumber of chunk in table 0 EXTDIM Ì È ?@4 4ÿ(ÿÿÿÿÿÿÿÿ 8shuffledeflateyÏ£U!I 0CLASS CACHEARRAY (VERSION1.0 0TITLE Range Values 0 EXTDIM Ì ° ?@4 4ÿÿÿÿÿÿÿÿÿ 8shuffledeflateyü£U!I (CLASSEARRAY (VERSION1.0 0TITLEMedian ranges 0 EXTDIM Í È ?@4 4ÿ(ÿÿÿÿÿÿÿÿ 8shuffledeflate±Ù£U!I 0CLASS CACHEARRAY (VERSION1.0 0TITLEBoundary Values 0 EXTDIM Õ ° ?@4 4ÿÿÿÿÿÿÿÿÿ 8shuffledeflateéã£U!I (CLASSEARRAY (VERSION1.0 0TITLE Start bounds 0 EXTDIM Ð ° ?@4 4ÿÿÿÿÿÿÿÿÿ 8shuffledeflateì£U!I (CLASSEARRAY (VERSION1.0 0TITLE End bounds 0 EXTDIM Ñ ° ?@4 4ÿÿÿÿÿÿÿÿÿ 8shuffledeflateIô£U!I (CLASSEARRAY (VERSION1.0 0TITLEMedian bounds 0 EXTDIM Ò ÄX ?@4 4ÿ 8shuffledeflateá£U!IÁ°SNOD áµ ´á®H¹¾89» 0CLASS LASTROWARRAY (VERSION1.0 @TITLE Last Row sorted values + boundsÈ@ 8shuffledeflate£U!I¬8sortedindicesrangesmrangesboundsaboundszboundsmboundssortedLRindicesLRHaÄX (VERSION1.0 8TITLELast Row indices 8 nelements  8 nelements Ù©ù« 0 is_csi 0 DIRTY TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿQ¬J«TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿGs«TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ¬TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿG¬TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJU¬TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJŸ¬TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿGº«TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿͦ`«TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿé¬TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿঠ­01ð?2@3@4@5@6@7@8 @9 "@10 $@11 &@12 (@13 *@14,@15.@160@171@182@193@204@ PyTables-3.7.0/tables/tests/issue_368.h5000066400000000000000000000023201416254111300176410ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿÐÿÿÿÿÿÿÿÿ`ˆ¨ °TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿHEAPXÈPˆ¨ (TITLE (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.1 Àpy2_pickled_unicodecnumpy.core.multiarray scalar p1 (cnumpy dtype p2 (S'U3' I0 I1 tRp3 (I3 S'<' NNNI12 I4 I0 tbS'a\x00\x00\x00b\x00\x00\x00c\x00\x00\x00' tRp4 .PyTables-3.7.0/tables/tests/issue_560.h5000066400000000000000000000044501416254111300176410ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ( ÿÿÿÿÿÿÿÿ`ˆ¨ TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿHEAPXÈPˆ¨ (TITLE (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.1 xpy2_pickled_datetimeFcdatetime datetime p1 (S'\x07\xe0\x06\x1e\x16\x1c \x03\x7f\xcc' tRp2 . ˜py2_pickled_dictf(dp1 S'x' cnumpy.core.multiarray _reconstruct p2 (cnumpy ndarray p3 (I0 tS'b' tRp4 (I1 (I10 I3 tcnumpy dtype p5 (S'f8' I0 I1 tRp6 (I3 S'<' NNNI-1 I-1 I0 tbI00 S'\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' tbsS's' S'just a string' p7 s.PyTables-3.7.0/tables/tests/itemsize.h5000066400000000000000000000040601416254111300177450ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ0ÿÿÿÿÿÿÿÿ`ˆ¨ˆ¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ0HEAPXÈTestHpA B 0‡Z] SNOD  ejs; rogr ta\cPyTables-3.7.0/tables/tests/matlab_file.mat000066400000000000000000000036261416254111300206270ustar00rootroot00000000000000MATLAB 7.3 MAT-file, Platform: PCWIN64, Created on: Wed Dec 19 10:25:30 2012 HDF5 schema 1.00 . IM‰HDF  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ`ˆ¨ˆ¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿHHEAPXÈaH ?@4 4ÿ ð?@@šÒP 0 MATLAB_classdoublePSNOD  PyTables-3.7.0/tables/tests/nested-type-with-gaps.h5000066400000000000000000000034461416254111300222650ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ €`HEAP€nestedtypeèTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà €`(øÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÏiH€SNODÐðfloat  compound chardouble ?@4 4ÿ PyTables-3.7.0/tables/tests/non-chunked-table.h5000066400000000000000000000140501416254111300214120ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ"ÿÿÿÿÿÿÿÿ HEAP€test_varèTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿX €`HEAP0 "TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ° ÐSNOD(ÐR"(¢DˆSNOD @@@@dstructure variable&"a!?@4 4ÿb!?@4 4ÿc!?@4 4ÿd  PyTables-3.7.0/tables/tests/oldflavor_numeric.h5000066400000000000000000003332501416254111300216340ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ¨¶ÿÿÿÿÿÿÿÿ`ˆ¨ èTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ8HEAPXHÈarray1array2carray1carray2vlarray1vlarray2ˆ¨ (TITLE (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.0  ?@4 4ÿ P&vN (CLASSARRAY (VERSION2.3``SNODÀ X(°80- (TITLE (FLAVORnumeric @ ?@4 4ÿ P&vN (CLASSARRAY (VERSION2.3 (TITLE (FLAVORpython H(ÿÿÿÿÿÿÿÿP&vN (CLASSCARRAY (VERSION1.0 (TITLE (FLAVORnumeric H(ÿÿÿÿÿÿÿÿP&vN (CLASSCARRAY (VERSION1.0 (TITLE (FLAVORpython @ ÿÿÿÿÿÿÿÿP&vN (CLASSVLARRAY (VERSION1.3 (TITLE (FLAVORnumericTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€¨6GCOL  566567756988P 8ÿÿÿÿÿÿÿÿx.P&vN (CLASSVLARRAY (VERSION1.3 (TITLE (FLAVORpythonTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¨¶000000PyTables-3.7.0/tables/tests/out_of_order_types.h5000066400000000000000000002125311416254111300220320ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿXÿÿÿÿÿÿÿÿ`ˆ¨ àTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀHEAPXÈgroupHˆ¨ TITLE (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.1¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ HEAPXhtableHSNOD(H(H (TITLEgroup (CLASSGROUP (VERSION1.0 0˜test_5test_10 test_158 ˆDê×_SNOD°ÿÿÿÿÿÿÿÿ (CLASSTABLE (VERSION2.7 TITLE 0 FIELD_0_NAMEtest_5 0 FIELD_1_NAMEtest_10 0 FIELD_2_NAMEtest_15 0 FIELD_0_FILL 0 FIELD_1_FILL 0 FIELD_2_FILL 0 NROWS@TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿhˆ**************---------.... PyTables-3.7.0/tables/tests/python2.h5000066400000000000000000002334521416254111300175300ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ$7ÿÿÿÿÿÿÿÿ`ˆ¨ ðTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ HEAPXHÈarraytableanarrayatableagroupagroup2anarray1X 0TITLE File title (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.0 @ ^5sP (CLASSARRAY (VERSION2.3hhSNOD(Ø0H*X8à5  Ð 0TITLEArray example (FLAVORpython ð@var1 ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@^5sP (CLASSTABLE (VERSION2.6 0TITLETable example 0 NROWS@ 0 FIELD_0_NAMEvar1 8 FIELD_0_FILL 8ðˆ¨ 8 testattr@) 8@Ð^5sP (CLASSARRAY (VERSION2.3 0TITLE Array title (FLAVORpython ð@var1 ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@^5sP (CLASSTABLE (VERSION2.6 0TITLE Table title 0 NROWS@ 0 FIELD_0_NAMEvar1 8 FIELD_0_FILL TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ80HEAPX@àanarray1anarray2atable1atable2agroup3 À 0TITLE Group title (CLASSGROUP (VERSION1.0 8 testattr@* @ 8^5sP (CLASSARRAY (VERSION2.3€¨SNOD8¸-(((p0p 0TITLEArray title 1 (FLAVORpython 8 testattr@* 8@H ^5sP (CLASSARRAY (VERSION2.3 0TITLEArray title 2 (FLAVORpython ð@var1 ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@^5sP (CLASSTABLE (VERSION2.6 0TITLETable title 1 0 NROWS@ 0 FIELD_0_NAMEvar1 8 FIELD_0_FILL ¨f0f1  f2À'ˆª*^5sPTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿ(7ª*ÿÿÿÿÿÿÿÿ (CLASSTABLE (VERSION2.6 0TITLETable title 2 0 NROWS@ 0 FIELD_0_NAMEf0 0 FIELD_1_NAMEf1 0 FIELD_2_NAMEf2 8 FIELD_0_FILL @ FIELD_1_FILL   0 FIELD_2_FILL (FLAVORnumpy-°TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ80HEAPX°,Pp*, 0TITLE Group title 2 (CLASSGROUP (VERSION1.0x0°TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿè3HEAPX 0agroup4Hà-0 0TITLE Group title 3 (CLASSGROUP (VERSION1.005°TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿè3HEAPX3PSNOD(1P1p3 0TITLE Group title 4 (CLASSGROUP (VERSION1.0 8@P ^5sP (CLASSARRAY (VERSION2.3 0TITLEArray example (FLAVORpython0Aa PyTables-3.7.0/tables/tests/python3.h5000066400000000000000000002334521416254111300175310ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ$7ÿÿÿÿÿÿÿÿ`ˆ¨ ðTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ HEAPXHÈarraytableanarrayatableagroupagroup2anarray1X 0TITLE File title (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.0 @ à4sP (CLASSARRAY (VERSION2.3hhSNOD(Ø0H*X8à5  Ð 0TITLEArray example (FLAVORpython ð@var1 ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@à4sP (CLASSTABLE (VERSION2.6 0TITLETable example 0 NROWS@ 0 FIELD_0_NAMEvar1 8 FIELD_0_FILL 8ðˆ¨ 8 testattr@) 8@Ðà4sP (CLASSARRAY (VERSION2.3 0TITLE Array title (FLAVORpython ð@var1 ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@à4sP (CLASSTABLE (VERSION2.6 0TITLE Table title 0 NROWS@ 0 FIELD_0_NAMEvar1 8 FIELD_0_FILL TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ80HEAPX@àanarray1anarray2atable1atable2agroup3 À 0TITLE Group title (CLASSGROUP (VERSION1.0 8 testattr@* @ 8à4sP (CLASSARRAY (VERSION2.3€¨SNOD8¸-(((p0p 0TITLEArray title 1 (FLAVORpython 8 testattr@* 8@H à4sP (CLASSARRAY (VERSION2.3 0TITLEArray title 2 (FLAVORpython ð@var1 ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@à4sP (CLASSTABLE (VERSION2.6 0TITLETable title 1 0 NROWS@ 0 FIELD_0_NAMEvar1 8 FIELD_0_FILL ¨f0f1  f2À'ˆª*à4sPTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿ(7ª*ÿÿÿÿÿÿÿÿ (CLASSTABLE (VERSION2.6 0TITLETable title 2 0 NROWS@ 0 FIELD_0_NAMEf0 0 FIELD_1_NAMEf1 0 FIELD_2_NAMEf2 8 FIELD_0_FILL @ FIELD_1_FILL   0 FIELD_2_FILL (FLAVORnumpy-°TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ80HEAPX°,Pp*, 0TITLEGroup title 2 (CLASSGROUP (VERSION1.0x0°TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿè3HEAPX 0agroup4Hà-0 0TITLEGroup title 3 (CLASSGROUP (VERSION1.005°TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿè3HEAPX3PSNOD(1P1p3 0TITLEGroup title 4 (CLASSGROUP (VERSION1.0 8@P à4sP (CLASSARRAY (VERSION2.3 0TITLEArray example (FLAVORpython0Aa PyTables-3.7.0/tables/tests/scalar.h5000066400000000000000000000201461416254111300173640ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ` ÿÿÿÿÿÿÿÿ`ˆ¨ˆ¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ0HEAPX Èvariable length string8`Œ…nNˆSNOD  `GCOL Some stringÐ PyTables-3.7.0/tables/tests/slink.h5000066400000000000000000000125761416254111300172470ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿxÿÿÿÿÿÿÿÿ`ˆ¨ èTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈHEAPX8Èpeppep2/peparrarr2/arr ˆ¨ (TITLE (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.0¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿx HEAPXppep3HSNOD h (ÿÿÿÿÿÿÿÿ0ÿÿÿÿÿÿÿÿ0P (TITLE (CLASSGROUP (VERSION1.0À ¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿx HEAPX PSNOD¸à (TITLE (CLASSGROUP (VERSION1.0 0@h¶ K (CLASSARRAY (VERSION2.3 (TITLE (FLAVORpython PyTables-3.7.0/tables/tests/smpl_SDSextendible.h5000066400000000000000000000141461416254111300216520ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ`ÿÿÿÿÿÿÿÿ €`HEAP€ExtendibleArrayèTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà €`  ( ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ (™ACPSNODÐTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(ˆ(`(°(Ø( PyTables-3.7.0/tables/tests/smpl_compound_chunked.h5000066400000000000000000000132161416254111300224770ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿˆÿÿÿÿÿÿÿÿ`ˆ¨ˆ¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@HEAPXÈCompoundChunked@TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ P  ð à0žÜ0`0j0aœHello!     BDmÿôH0mô0cˆÿóÿô Hello!     ?uÂúÔ¬@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<ŸmëúÔ¨ÿõPúÓLÿõÐÿô°Hello!     ?õÂ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<ŸmHello!     @8Qì@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZîmcˆ00#ÿö Hello!     v$@uÂ׈¾@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿm 0tˆìÿ÷°Hello!     lX@™™š0µÜ@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇmëëG°à à =C€&àa_name  c_named_name*d  e_name€! f_nameˆ*P !?@4 4ÿg_nameØ v$@uÂ׈¾@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿm 0tˆìÿ÷°Hello!  SNODP PyTables-3.7.0/tables/tests/smpl_enum.h5000066400000000000000000000040561416254111300201200ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ(ÿÿÿÿÿÿÿÿ €`HEAP€EnumTestèTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà €`P  REDGREENBLUEWHITEBLACK  “±;CHSNODÐ PyTables-3.7.0/tables/tests/smpl_f64be.h5000066400000000000000000000043661416254111300200660ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿ €`HEAP€TestArrayèTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà €`!?@4 4ÿ š±;CpSNODÐ?ð@@@?ð@@@@@@@@@@@@@@@@@@@ @@@@ @" PyTables-3.7.0/tables/tests/smpl_f64le.h5000066400000000000000000000043661416254111300201000ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿ €`HEAP€TestArrayèTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà €` ?@4 4ÿ œ±;CpSNODÐð?@@@ð?@@@@@@@@@@@@@@@@@@ @@@@ @"@ PyTables-3.7.0/tables/tests/smpl_i32be.h5000066400000000000000000000041761416254111300200630ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿxÿÿÿÿÿÿÿÿ €`HEAP€TestArrayèTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà €`   ¥±;CxSNODÐ PyTables-3.7.0/tables/tests/smpl_i32le.h5000066400000000000000000000041761416254111300200750ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿxÿÿÿÿÿÿÿÿ €`HEAP€TestArrayèTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà €`  §±;CxSNODÐ PyTables-3.7.0/tables/tests/smpl_i64be.h5000066400000000000000000000043661416254111300200710ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿ €`HEAP€TestArrayèTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà €` @ ¬±;CxSNODÐ PyTables-3.7.0/tables/tests/smpl_i64le.h5000066400000000000000000000043661416254111300201030ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿ €`HEAP€TestArrayèTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà €`@ ­±;CxSNODÐ PyTables-3.7.0/tables/tests/smpl_unsupptype.h5000066400000000000000000000271361416254111300214140ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿX.ÿÿÿÿÿÿÿÿ €`HEAP€CompoundChunkedèTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà €`X&Ø(û=CˆSNODÐTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ0X0ˆ!GCOL -- Professor Cheng Man-ch'ing(In which case, you deserve what you get.;A combative stance means that you've accepted the contract.5A fight is a contract that takes two people to honor. -- Professor Cheng Man-ch'ing(In which case, you deserve what you get.;A combative stance means that you've accepted the contract.5A fight is a contract that takes two people to honor. -- Professor Cheng Man-ch'ing (In which case, you deserve what you get. ;A combative stance means that you've accepted the contract. 5A fight is a contract that takes two people to honor. -- Professor Cheng Man-ch'ing(In which case, you deserve what you get.;A combative stance means that you've accepted the contract.5A fight is a contract that takes two people to honor. -- Professor Cheng Man-ch'ing(In which case, you deserve what you get.;A combative stance means that you've accepted the contract.5A fight is a contract that takes two people to honor. -- Professor Cheng Man-ch'ing(In which case, you deserve what you get.;A combative stance means that you've accepted the contract.5A fight is a contract that takes two people to honor.ð 5X;X(X XHello!     m5X;X(X XHello!     ?uÂ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿ@ÚÔ,<Ÿm5X ;X (X X Hello!     ?õÂ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿ@ ÚÔ,<Ÿm5X;X(X X Hello!     @8Qì@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZî@¨È>BZîm5X;X(X XHello!     @uÂ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿ@°ÚÔ,<Ÿm5X;X(X XHello!     @™™š@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇ@´щ7KÇmÐ&a_name  b_name@c_nameDd_nameJd  e_name°! f_name¸P !?@4 4ÿg_name PyTables-3.7.0/tables/tests/test_all.py000066400000000000000000000030161416254111300200370ustar00rootroot00000000000000"""Run all test cases.""" import sys import numpy as np from packaging.version import Version import tables as tb from tables.tests import common from tables.tests.test_suite import suite, test def get_tuple_version(hexversion): """Get a tuple from a compact version in hex.""" h = hexversion return(h & 0xff0000) >> 16, (h & 0xff00) >> 8, h & 0xff if __name__ == '__main__': common.parse_argv(sys.argv) hdf5_version = get_tuple_version(tb.which_lib_version("hdf5")[0]) hdf5_version_str = "%s.%s.%s" % hdf5_version if Version(hdf5_version_str) < tb.req_versions.min_hdf5_version: print(f"*Warning*: HDF5 version is lower than recommended: " f"{hdf5_version} < {tb.req_versions.min_hdf5_version}") if Version(np.__version__) < tb.req_versions.min_numpy_version: print(f"*Warning*: NumPy version is lower than recommended: " f"{np.__version__} < {tb.req_versions.min_numpy_version}") # Handle some global flags (i.e. only useful for test_all.py) only_versions = 0 args = sys.argv[:] for arg in args: # Remove 'show-versions' for PyTables 2.3 or higher if arg in ['--print-versions', '--show-versions']: only_versions = True sys.argv.remove(arg) elif arg == '--show-memory': common.show_memory = True sys.argv.remove(arg) common.print_versions() if not only_versions: common.print_heavy(common.heavy) common.unittest.main(defaultTest='tb.tests.suite') PyTables-3.7.0/tables/tests/test_array.py000066400000000000000000002723401416254111300204150ustar00rootroot00000000000000import sys import tempfile from pathlib import Path import numpy as np import tables as tb from tables.tests import common # warnings.resetwarnings() class BasicTestCase(common.PyTablesTestCase): """Basic test for all the supported typecodes present in numpy. All of them are included on pytables. """ endiancheck = False def write_read(self, testarray): a = testarray if common.verbose: print('\n', '-=' * 30) print("Running test for array with type '%s'" % a.dtype.type, end=' ') print("for class check:", self.title) # Create an instance of HDF5 file filename = tempfile.mktemp(".h5") try: with tb.open_file(filename, mode="w") as fileh: root = fileh.root # Create the array under root and name 'somearray' if self.endiancheck and a.dtype.kind != "S": b = a.byteswap() b.dtype = a.dtype.newbyteorder() a = b fileh.create_array(root, 'somearray', a, "Some array") # Re-open the file in read-only mode with tb.open_file(filename, mode="r") as fileh: root = fileh.root # Read the saved array b = root.somearray.read() # Compare them. They should be equal. if common.verbose and not common.allequal(a, b): print("Write and read arrays differ!") # print("Array written:", a) print("Array written shape:", a.shape) print("Array written itemsize:", a.itemsize) print("Array written type:", a.dtype.type) # print("Array read:", b) print("Array read shape:", b.shape) print("Array read itemsize:", b.itemsize) print("Array read type:", b.dtype.type) if a.dtype.kind != "S": print("Array written byteorder:", a.dtype.byteorder) print("Array read byteorder:", b.dtype.byteorder) # Check strictly the array equality self.assertEqual(a.shape, b.shape) self.assertEqual(a.shape, root.somearray.shape) if a.dtype.kind == "S": self.assertEqual(root.somearray.atom.type, "string") else: self.assertEqual(a.dtype.type, b.dtype.type) self.assertEqual(a.dtype.type, root.somearray.atom.dtype.type) abo = tb.utils.byteorders[a.dtype.byteorder] bbo = tb.utils.byteorders[b.dtype.byteorder] if abo != "irrelevant": self.assertEqual(abo, root.somearray.byteorder) self.assertEqual(bbo, sys.byteorder) if self.endiancheck: self.assertNotEqual(bbo, abo) obj = root.somearray self.assertEqual(obj.flavor, 'numpy') self.assertEqual(obj.shape, a.shape) self.assertEqual(obj.ndim, a.ndim) self.assertEqual(obj.chunkshape, None) if a.shape: nrows = a.shape[0] else: # scalar nrows = 1 self.assertEqual(obj.nrows, nrows) self.assertTrue(common.allequal(a, b)) finally: # Then, delete the file Path(filename).unlink() def write_read_out_arg(self, testarray): a = testarray if common.verbose: print('\n', '-=' * 30) print("Running test for array with type '%s'" % a.dtype.type, end=' ') print("for class check:", self.title) # Create an instance of HDF5 file filename = tempfile.mktemp(".h5") try: with tb.open_file(filename, mode="w") as fileh: root = fileh.root # Create the array under root and name 'somearray' if self.endiancheck and a.dtype.kind != "S": b = a.byteswap() b.dtype = a.dtype.newbyteorder() a = b fileh.create_array(root, 'somearray', a, "Some array") # Re-open the file in read-only mode with tb.open_file(filename, mode="r") as fileh: root = fileh.root # Read the saved array b = np.empty_like(a, dtype=a.dtype) root.somearray.read(out=b) # Check strictly the array equality self.assertEqual(a.shape, b.shape) self.assertEqual(a.shape, root.somearray.shape) if a.dtype.kind == "S": self.assertEqual(root.somearray.atom.type, "string") else: self.assertEqual(a.dtype.type, b.dtype.type) self.assertEqual(a.dtype.type, root.somearray.atom.dtype.type) abo = tb.utils.byteorders[a.dtype.byteorder] bbo = tb.utils.byteorders[b.dtype.byteorder] if abo != "irrelevant": self.assertEqual(abo, root.somearray.byteorder) self.assertEqual(abo, bbo) if self.endiancheck: self.assertNotEqual(bbo, sys.byteorder) self.assertTrue(common.allequal(a, b)) finally: # Then, delete the file Path(filename).unlink() def write_read_atom_shape_args(self, testarray): a = testarray atom = tb.Atom.from_dtype(a.dtype) shape = a.shape byteorder = None if common.verbose: print('\n', '-=' * 30) print("Running test for array with type '%s'" % a.dtype.type, end=' ') print("for class check:", self.title) # Create an instance of HDF5 file filename = tempfile.mktemp(".h5") try: with tb.open_file(filename, mode="w") as fileh: root = fileh.root # Create the array under root and name 'somearray' if self.endiancheck and a.dtype.kind != "S": b = a.byteswap() b.dtype = a.dtype.newbyteorder() if b.dtype.byteorder in ('>', '<'): byteorder = tb.utils.byteorders[b.dtype.byteorder] a = b ptarr = fileh.create_array(root, 'somearray', atom=atom, shape=shape, title="Some array", # specify the byteorder explicitly # since there is no way to deduce # it in this case byteorder=byteorder) self.assertEqual(shape, ptarr.shape) self.assertEqual(atom, ptarr.atom) ptarr[...] = a # Re-open the file in read-only mode with tb.open_file(filename, mode="r") as fileh: root = fileh.root # Read the saved array b = root.somearray.read() # Compare them. They should be equal. if common.verbose and not common.allequal(a, b): print("Write and read arrays differ!") # print("Array written:", a) print("Array written shape:", a.shape) print("Array written itemsize:", a.itemsize) print("Array written type:", a.dtype.type) # print("Array read:", b) print("Array read shape:", b.shape) print("Array read itemsize:", b.itemsize) print("Array read type:", b.dtype.type) if a.dtype.kind != "S": print("Array written byteorder:", a.dtype.byteorder) print("Array read byteorder:", b.dtype.byteorder) # Check strictly the array equality self.assertEqual(a.shape, b.shape) self.assertEqual(a.shape, root.somearray.shape) if a.dtype.kind == "S": self.assertEqual(root.somearray.atom.type, "string") else: self.assertEqual(a.dtype.type, b.dtype.type) self.assertEqual(a.dtype.type, root.somearray.atom.dtype.type) abo = tb.utils.byteorders[a.dtype.byteorder] bbo = tb.utils.byteorders[b.dtype.byteorder] if abo != "irrelevant": self.assertEqual(abo, root.somearray.byteorder) self.assertEqual(bbo, sys.byteorder) if self.endiancheck: self.assertNotEqual(bbo, abo) obj = root.somearray self.assertEqual(obj.flavor, 'numpy') self.assertEqual(obj.shape, a.shape) self.assertEqual(obj.ndim, a.ndim) self.assertEqual(obj.chunkshape, None) if a.shape: nrows = a.shape[0] else: # scalar nrows = 1 self.assertEqual(obj.nrows, nrows) self.assertTrue(common.allequal(a, b)) finally: # Then, delete the file Path(filename).unlink() def setup00_char(self): """Data integrity during recovery (character objects)""" if not isinstance(self.tupleChar, np.ndarray): a = np.array(self.tupleChar, dtype="S") else: a = self.tupleChar return a def test00_char(self): a = self.setup00_char() self.write_read(a) def test00_char_out_arg(self): a = self.setup00_char() self.write_read_out_arg(a) def test00_char_atom_shape_args(self): a = self.setup00_char() self.write_read_atom_shape_args(a) def test00b_char(self): """Data integrity during recovery (string objects)""" a = self.tupleChar filename = tempfile.mktemp(".h5") try: # Create an instance of HDF5 file with tb.open_file(filename, mode="w") as fileh: fileh.create_array(fileh.root, 'somearray', a, "Some array") # Re-open the file in read-only mode with tb.open_file(filename, mode="r") as fileh: # Read the saved array b = fileh.root.somearray.read() if isinstance(a, bytes): self.assertEqual(type(b), bytes) self.assertEqual(a, b) else: # If a is not a python string, then it should be a list # or ndarray self.assertIn(type(b), [list, np.ndarray]) finally: # Then, delete the file Path(filename).unlink() def test00b_char_out_arg(self): """Data integrity during recovery (string objects)""" a = self.tupleChar filename = tempfile.mktemp(".h5") try: # Create an instance of HDF5 file with tb.open_file(filename, mode="w") as fileh: fileh.create_array(fileh.root, 'somearray', a, "Some array") # Re-open the file in read-only mode with tb.open_file(filename, mode="r") as fileh: # Read the saved array b = np.empty_like(a) if fileh.root.somearray.flavor != 'numpy': self.assertRaises(TypeError, lambda: fileh.root.somearray.read(out=b)) else: fileh.root.somearray.read(out=b) self.assertIsInstance(b, np.ndarray) finally: # Then, delete the file Path(filename).unlink() def test00b_char_atom_shape_args(self): """Data integrity during recovery (string objects)""" a = self.tupleChar filename = tempfile.mktemp(".h5") try: # Create an instance of HDF5 file with tb.open_file(filename, mode="w") as fileh: nparr = np.asarray(a) atom = tb.Atom.from_dtype(nparr.dtype) shape = nparr.shape if nparr.dtype.byteorder in ('>', '<'): byteorder = tb.utils.byteorders[nparr.dtype.byteorder] else: byteorder = None ptarr = fileh.create_array(fileh.root, 'somearray', atom=atom, shape=shape, byteorder=byteorder, title="Some array") self.assertEqual(shape, ptarr.shape) self.assertEqual(atom, ptarr.atom) ptarr[...] = a # Re-open the file in read-only mode with tb.open_file(filename, mode="r") as fileh: # Read the saved array b = np.empty_like(a) if fileh.root.somearray.flavor != 'numpy': self.assertRaises(TypeError, lambda: fileh.root.somearray.read(out=b)) else: fileh.root.somearray.read(out=b) self.assertIsInstance(b, np.ndarray) finally: # Then, delete the file Path(filename).unlink() def setup01_char_nc(self): """Data integrity during recovery (non-contiguous character objects)""" if not isinstance(self.tupleChar, np.ndarray): a = np.array(self.tupleChar, dtype="S") else: a = self.tupleChar if a.ndim == 0: b = a.copy() else: b = a[::2] # Ensure that this numpy string is non-contiguous if len(b) > 1: self.assertEqual(b.flags.contiguous, False) return b def test01_char_nc(self): b = self.setup01_char_nc() self.write_read(b) def test01_char_nc_out_arg(self): b = self.setup01_char_nc() self.write_read_out_arg(b) def test01_char_nc_atom_shape_args(self): b = self.setup01_char_nc() self.write_read_atom_shape_args(b) def test02_types(self): """Data integrity during recovery (numerical types)""" typecodes = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float32', 'float64', 'complex64', 'complex128'] for name in ('float16', 'float96', 'float128', 'complex192', 'complex256'): atomname = name.capitalize() + 'Atom' if hasattr(tb, atomname): typecodes.append(name) for typecode in typecodes: a = np.array(self.tupleInt, typecode) self.write_read(a) b = np.array(self.tupleInt, typecode) self.write_read_out_arg(b) c = np.array(self.tupleInt, typecode) self.write_read_atom_shape_args(c) def test03_types_nc(self): """Data integrity during recovery (non-contiguous numerical types)""" typecodes = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float32', 'float64', 'complex64', 'complex128', ] for name in ('float16', 'float96', 'float128', 'complex192', 'complex256'): atomname = name.capitalize() + 'Atom' if hasattr(tb, atomname): typecodes.append(name) for typecode in typecodes: a = np.array(self.tupleInt, typecode) if a.ndim == 0: b1 = a.copy() b2 = a.copy() b3 = a.copy() else: b1 = a[::2] b2 = a[::2] b3 = a[::2] # Ensure that this array is non-contiguous if len(b1) > 1: self.assertEqual(b1.flags.contiguous, False) if len(b2) > 1: self.assertEqual(b2.flags.contiguous, False) if len(b3) > 1: self.assertEqual(b3.flags.contiguous, False) self.write_read(b1) self.write_read_out_arg(b2) self.write_read_atom_shape_args(b3) class Basic0DOneTestCase(BasicTestCase): # Scalar case title = "Rank-0 case 1" tupleInt = 3 tupleChar = b"3" endiancheck = True class Basic0DTwoTestCase(BasicTestCase): # Scalar case title = "Rank-0 case 2" tupleInt = 33 tupleChar = b"33" endiancheck = True class Basic1DZeroTestCase(BasicTestCase): # This test case is not supported by PyTables (HDF5 limitations) # 1D case title = "Rank-1 case 0" tupleInt = () tupleChar = () endiancheck = False class Basic1DOneTestCase(BasicTestCase): # 1D case title = "Rank-1 case 1" tupleInt = (3,) tupleChar = (b"a",) endiancheck = True class Basic1DTwoTestCase(BasicTestCase): # 1D case title = "Rank-1 case 2" tupleInt = (3, 4) tupleChar = (b"aaa",) endiancheck = True class Basic1DThreeTestCase(BasicTestCase): # 1D case title = "Rank-1 case 3" tupleInt = (3, 4, 5) tupleChar = (b"aaa", b"bbb",) endiancheck = True class Basic2DOneTestCase(BasicTestCase): # 2D case title = "Rank-2 case 1" tupleInt = np.array(np.arange((4)**2)) tupleInt.shape = (4,)*2 tupleChar = np.array(["abc"]*3**2, dtype="S3") tupleChar.shape = (3,)*2 endiancheck = True class Basic2DTwoTestCase(BasicTestCase): # 2D case, with a multidimensional dtype title = "Rank-2 case 2" tupleInt = np.tile(np.arange(4, dtype=np.int64), [4, 1]) tupleChar = np.array(["abc"]*3, dtype=("S3", (3,))) endiancheck = True class Basic10DTestCase(BasicTestCase): # 10D case title = "Rank-10 test" tupleInt = np.array(np.arange((2)**10)) tupleInt.shape = (2,)*10 tupleChar = np.array(["abc"]*2**10, dtype="S3") tupleChar.shape = (2,)*10 endiancheck = True class Basic32DTestCase(BasicTestCase): # 32D case (maximum) title = "Rank-32 test" tupleInt = np.array((32,)) tupleInt.shape = (1,)*32 tupleChar = np.array(["121"], dtype="S3") tupleChar.shape = (1,)*32 class ReadOutArgumentTests(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.size = 1000 def create_array(self): array = np.arange(self.size, dtype='f8') disk_array = self.h5file.create_array('/', 'array', array) return array, disk_array def test_read_entire_array(self): array, disk_array = self.create_array() out_buffer = np.empty((self.size, ), 'f8') disk_array.read(out=out_buffer) np.testing.assert_equal(out_buffer, array) def test_read_contiguous_slice1(self): array, disk_array = self.create_array() out_buffer = np.arange(self.size, dtype='f8') out_buffer = np.random.permutation(out_buffer) out_buffer_orig = out_buffer.copy() start = self.size // 2 disk_array.read(start=start, stop=self.size, out=out_buffer[start:]) np.testing.assert_equal(out_buffer[start:], array[start:]) np.testing.assert_equal(out_buffer[:start], out_buffer_orig[:start]) def test_read_contiguous_slice2(self): array, disk_array = self.create_array() out_buffer = np.arange(self.size, dtype='f8') out_buffer = np.random.permutation(out_buffer) out_buffer_orig = out_buffer.copy() start = self.size // 4 stop = self.size - start disk_array.read(start=start, stop=stop, out=out_buffer[start:stop]) np.testing.assert_equal(out_buffer[start:stop], array[start:stop]) np.testing.assert_equal(out_buffer[:start], out_buffer_orig[:start]) np.testing.assert_equal(out_buffer[stop:], out_buffer_orig[stop:]) def test_read_non_contiguous_slice_contiguous_buffer(self): array, disk_array = self.create_array() out_buffer = np.empty((self.size // 2, ), dtype='f8') disk_array.read(start=0, stop=self.size, step=2, out=out_buffer) np.testing.assert_equal(out_buffer, array[0:self.size:2]) def test_read_non_contiguous_buffer(self): array, disk_array = self.create_array() out_buffer = np.empty((self.size, ), 'f8') out_buffer_slice = out_buffer[0:self.size:2] with self.assertRaisesRegex(ValueError, 'output array not C contiguous'): disk_array.read(0, self.size, 2, out_buffer_slice) def test_buffer_too_small(self): array, disk_array = self.create_array() out_buffer = np.empty((self.size // 2, ), 'f8') self.assertRaises(ValueError, disk_array.read, 0, self.size, 1, out_buffer) try: disk_array.read(0, self.size, 1, out_buffer) except ValueError as exc: self.assertIn('output array size invalid, got', str(exc)) def test_buffer_too_large(self): array, disk_array = self.create_array() out_buffer = np.empty((self.size + 1, ), 'f8') self.assertRaises(ValueError, disk_array.read, 0, self.size, 1, out_buffer) try: disk_array.read(0, self.size, 1, out_buffer) except ValueError as exc: self.assertIn('output array size invalid, got', str(exc)) class SizeOnDiskInMemoryPropertyTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.array_size = (10, 10) self.array = self.h5file.create_array( '/', 'somearray', np.zeros(self.array_size, 'i4')) def test_all_zeros(self): self.assertEqual(self.array.size_on_disk, 10 * 10 * 4) self.assertEqual(self.array.size_in_memory, 10 * 10 * 4) class UnalignedAndComplexTestCase(common.TempFileMixin, common.PyTablesTestCase): """Basic test for all the supported typecodes present in numpy. Most of them are included on PyTables. """ def setUp(self): super().setUp() self.root = self.h5file.root def write_read(self, testArray): if common.verbose: print('\n', '-=' * 30) print("\nRunning test for array with type '%s'" % testArray.dtype.type) # Create the array under root and name 'somearray' a = testArray if self.endiancheck: byteorder = {"little": "big", "big": "little"}[sys.byteorder] else: byteorder = sys.byteorder self.h5file.create_array(self.root, 'somearray', a, "Some array", byteorder=byteorder) if self.reopen: self._reopen() self.root = self.h5file.root # Read the saved array b = self.root.somearray.read() # Get an array to be compared in the correct byteorder c = a.newbyteorder(byteorder) # Compare them. They should be equal. if not common.allequal(c, b) and common.verbose: print("Write and read arrays differ!") print("Array written:", a) print("Array written shape:", a.shape) print("Array written itemsize:", a.itemsize) print("Array written type:", a.dtype.type) print("Array read:", b) print("Array read shape:", b.shape) print("Array read itemsize:", b.itemsize) print("Array read type:", b.dtype.type) # Check strictly the array equality self.assertEqual(a.shape, b.shape) self.assertEqual(a.shape, self.root.somearray.shape) if a.dtype.byteorder != "|": self.assertEqual(a.dtype, b.dtype) self.assertEqual(a.dtype, self.root.somearray.atom.dtype) self.assertEqual(tb.utils.byteorders[b.dtype.byteorder], sys.byteorder) self.assertEqual(self.root.somearray.byteorder, byteorder) self.assertTrue(common.allequal(c, b)) def test01_signedShort_unaligned(self): """Checking an unaligned signed short integer array""" r = np.rec.array(b'a'*200, formats='i1,f4,i2', shape=10) a = r["f2"] # Ensure that this array is non-aligned self.assertEqual(a.flags.aligned, False) self.assertEqual(a.dtype.type, np.int16) self.write_read(a) def test02_float_unaligned(self): """Checking an unaligned single precision array""" r = np.rec.array(b'a'*200, formats='i1,f4,i2', shape=10) a = r["f1"] # Ensure that this array is non-aligned self.assertEqual(a.flags.aligned, 0) self.assertEqual(a.dtype.type, np.float32) self.write_read(a) def test03_byte_offset(self): """Checking an offsetted byte array""" r = np.arange(100, dtype=np.int8) r.shape = (10, 10) a = r[2] self.write_read(a) def test04_short_offset(self): """Checking an offsetted unsigned short int precision array""" r = np.arange(100, dtype=np.uint32) r.shape = (10, 10) a = r[2] self.write_read(a) def test05_int_offset(self): """Checking an offsetted integer array""" r = np.arange(100, dtype=np.int32) r.shape = (10, 10) a = r[2] self.write_read(a) def test06_longlongint_offset(self): """Checking an offsetted long long integer array""" r = np.arange(100, dtype=np.int64) r.shape = (10, 10) a = r[2] self.write_read(a) def test07_float_offset(self): """Checking an offsetted single precision array""" r = np.arange(100, dtype=np.float32) r.shape = (10, 10) a = r[2] self.write_read(a) def test08_double_offset(self): """Checking an offsetted double precision array""" r = np.arange(100, dtype=np.float64) r.shape = (10, 10) a = r[2] self.write_read(a) def test09_float_offset_unaligned(self): """Checking an unaligned and offsetted single precision array""" r = np.rec.array(b'a'*200, formats='i1,3f4,i2', shape=10) a = r["f1"][3] # Ensure that this array is non-aligned self.assertEqual(a.flags.aligned, False) self.assertEqual(a.dtype.type, np.float32) self.write_read(a) def test10_double_offset_unaligned(self): """Checking an unaligned and offsetted double precision array""" r = np.rec.array(b'a'*400, formats='i1,3f8,i2', shape=10) a = r["f1"][3] # Ensure that this array is non-aligned self.assertEqual(a.flags.aligned, False) self.assertEqual(a.dtype.type, np.float64) self.write_read(a) def test11_int_byteorder(self): """Checking setting data with different byteorder in a range (integer)""" # Save an array with the reversed byteorder on it a = np.arange(25, dtype=np.int32).reshape(5, 5) a = a.byteswap() a = a.newbyteorder() array = self.h5file.create_array( self.h5file.root, 'array', a, "byteorder (int)") # Read a subarray (got an array with the machine byteorder) b = array[2:4, 3:5] b = b.byteswap() b = b.newbyteorder() # Set this subarray back to the array array[2:4, 3:5] = b b = b.byteswap() b = b.newbyteorder() # Set this subarray back to the array array[2:4, 3:5] = b # Check that the array is back in the correct byteorder c = array[...] if common.verbose: print("byteorder of array on disk-->", array.byteorder) print("byteorder of subarray-->", b.dtype.byteorder) print("subarray-->", b) print("retrieved array-->", c) self.assertTrue(common.allequal(a, c)) def test12_float_byteorder(self): """Checking setting data with different byteorder in a range (float)""" # Save an array with the reversed byteorder on it a = np.arange(25, dtype=np.float64).reshape(5, 5) a = a.byteswap() a = a.newbyteorder() array = self.h5file.create_array( self.h5file.root, 'array', a, "byteorder (float)") # Read a subarray (got an array with the machine byteorder) b = array[2:4, 3:5] b = b.byteswap() b = b.newbyteorder() # Set this subarray back to the array array[2:4, 3:5] = b b = b.byteswap() b = b.newbyteorder() # Set this subarray back to the array array[2:4, 3:5] = b # Check that the array is back in the correct byteorder c = array[...] if common.verbose: print("byteorder of array on disk-->", array.byteorder) print("byteorder of subarray-->", b.dtype.byteorder) print("subarray-->", b) print("retrieved array-->", c) self.assertTrue(common.allequal(a, c)) class ComplexNotReopenNotEndianTestCase(UnalignedAndComplexTestCase): endiancheck = False reopen = False class ComplexReopenNotEndianTestCase(UnalignedAndComplexTestCase): endiancheck = False reopen = True class ComplexNotReopenEndianTestCase(UnalignedAndComplexTestCase): endiancheck = True reopen = False class ComplexReopenEndianTestCase(UnalignedAndComplexTestCase): endiancheck = True reopen = True class GroupsArrayTestCase(common.TempFileMixin, common.PyTablesTestCase): """This test class checks combinations of arrays with groups.""" def test00_iterativeGroups(self): """Checking combinations of arrays with groups.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_iterativeGroups..." % self.__class__.__name__) # Get the root group group = self.h5file.root # Set the type codes to test # The typecodes below does expose an ambiguity that is reported in: # http://projects.scipy.org/scipy/numpy/ticket/283 and # http://projects.scipy.org/scipy/numpy/ticket/290 typecodes = ['b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'f', 'd', 'F', 'D'] if hasattr(tb, 'Float16Atom'): typecodes.append('e') if hasattr(tb, 'Float96Atom') or hasattr(tb, 'Float128Atom'): typecodes.append('g') if (hasattr(tb, 'Complex192Atom') or hasattr(tb, 'Complex256Atom')): typecodes.append('G') for i, typecode in enumerate(typecodes): a = np.ones((3,), typecode) dsetname = 'array_' + typecode if common.verbose: print("Creating dataset:", group._g_join(dsetname)) self.h5file.create_array(group, dsetname, a, "Large array") group = self.h5file.create_group(group, 'group' + str(i)) # Reopen the file self._reopen() # Get the root group group = self.h5file.root # Get the metadata on the previosly saved arrays for i, typecode in enumerate(typecodes): # Create an array for later comparison a = np.ones((3,), typecode) # Get the dset object hanging from group dset = getattr(group, 'array_' + typecode) # Get the actual array b = dset.read() if common.verbose: print("Info from dataset:", dset._v_pathname) print(" shape ==>", dset.shape, end=' ') print(" type ==> %s" % dset.atom.dtype) print("Array b read from file. Shape: ==>", b.shape, end=' ') print(". Type ==> %s" % b.dtype) self.assertEqual(a.shape, b.shape) self.assertEqual(a.dtype, b.dtype) self.assertTrue(common.allequal(a, b)) # Iterate over the next group group = getattr(group, 'group' + str(i)) def test01_largeRankArrays(self): """Checking creation of large rank arrays (0 < rank <= 32) It also uses arrays ranks which ranges until maxrank. """ # maximum level of recursivity (deepest group level) achieved: # maxrank = 32 (for a effective maximum rank of 32) # This limit is due to HDF5 library limitations. minrank = 1 maxrank = 32 if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_largeRankArrays..." % self.__class__.__name__) print("Maximum rank for tested arrays:", maxrank) group = self.h5file.root if common.verbose: print("Rank array writing progress: ", end=' ') for rank in range(minrank, maxrank + 1): # Create an array of integers, with incrementally bigger ranges a = np.ones((1,) * rank, np.int32) if common.verbose: print("%3d," % (rank), end=' ') self.h5file.create_array(group, "array", a, "Rank: %s" % rank) group = self.h5file.create_group(group, 'group' + str(rank)) # Reopen the file self._reopen() group = self.h5file.root if common.verbose: print() print("Rank array reading progress: ") # Get the metadata on the previosly saved arrays for rank in range(minrank, maxrank + 1): # Create an array for later comparison a = np.ones((1,) * rank, np.int32) # Get the actual array b = group.array.read() if common.verbose: print("%3d," % (rank), end=' ') if common.verbose and not common.allequal(a, b): print("Info from dataset:", group.array._v_pathname) print(" Shape: ==>", group.array.shape, end=' ') print(" typecode ==> %c" % group.array.typecode) print("Array b read from file. Shape: ==>", b.shape, end=' ') print(". Type ==> %c" % b.dtype) self.assertEqual(a.shape, b.shape) self.assertEqual(a.dtype, b.dtype) self.assertTrue(common.allequal(a, b)) # print(self.h5file) # Iterate over the next group group = self.h5file.get_node(group, 'group' + str(rank)) if common.verbose: print() # This flush the stdout buffer class CopyTestCase(common.TempFileMixin, common.PyTablesTestCase): def test01_copy(self): """Checking Array.copy() method.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_copy..." % self.__class__.__name__) # Create an Array arr = np.array([[456, 2], [3, 457]], dtype='int16') array1 = self.h5file.create_array( self.h5file.root, 'array1', arr, "title array1") # Copy to another Array array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) # print("dirs-->", dir(array1), dir(array2)) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal self.assertTrue(common.allequal(array1.read(), array2.read())) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.flavor, array2.flavor) self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.title, array2.title) def test02_copy(self): """Checking Array.copy() method (where specified)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_copy..." % self.__class__.__name__) # Create an Array arr = np.array([[456, 2], [3, 457]], dtype='int16') array1 = self.h5file.create_array( self.h5file.root, 'array1', arr, "title array1") # Copy to another Array group1 = self.h5file.create_group("/", "group1") array2 = array1.copy(group1, 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.group1.array2 if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) # print("dirs-->", dir(array1), dir(array2)) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal self.assertTrue(common.allequal(array1.read(), array2.read())) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.flavor, array2.flavor) self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.title, array2.title) def test03_copy(self): """Checking Array.copy() method (checking title copying)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_copy..." % self.__class__.__name__) # Create an Array arr = np.array([[456, 2], [3, 457]], dtype='int16') array1 = self.h5file.create_array( self.h5file.root, 'array1', arr, "title array1") # Append some user attrs array1.attrs.attr1 = "attr1" array1.attrs.attr2 = 2 # Copy it to another Array array2 = array1.copy('/', 'array2', title="title array2") if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 # Assert user attributes if common.verbose: print("title of destination array-->", array2.title) self.assertEqual(array2.title, "title array2") def test04_copy(self): """Checking Array.copy() method (user attributes copied)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_copy..." % self.__class__.__name__) # Create an Array arr = np.array([[456, 2], [3, 457]], dtype='int16') array1 = self.h5file.create_array( self.h5file.root, 'array1', arr, "title array1") # Append some user attrs array1.attrs.attr1 = "attr1" array1.attrs.attr2 = 2 # Copy it to another Array array2 = array1.copy('/', 'array2', copyuserattrs=1) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Assert user attributes self.assertEqual(array2.attrs.attr1, "attr1") self.assertEqual(array2.attrs.attr2, 2) def test04b_copy(self): """Checking Array.copy() method (user attributes not copied)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05b_copy..." % self.__class__.__name__) # Create an Array arr = np.array([[456, 2], [3, 457]], dtype='int16') array1 = self.h5file.create_array( self.h5file.root, 'array1', arr, "title array1") # Append some user attrs array1.attrs.attr1 = "attr1" array1.attrs.attr2 = 2 # Copy it to another Array array2 = array1.copy('/', 'array2', copyuserattrs=0) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Assert user attributes self.assertEqual(hasattr(array2.attrs, "attr1"), 0) self.assertEqual(hasattr(array2.attrs, "attr2"), 0) class CloseCopyTestCase(CopyTestCase): close = 1 class OpenCopyTestCase(CopyTestCase): close = 0 class CopyIndexTestCase(common.TempFileMixin, common.PyTablesTestCase): def test01_index(self): """Checking Array.copy() method with indexes.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_index..." % self.__class__.__name__) # Create a numpy r = np.arange(200, dtype='int32') r.shape = (100, 2) # Save it in a array: array1 = self.h5file.create_array( self.h5file.root, 'array1', r, "title array1") # Copy to another array array2 = array1.copy("/", 'array2', start=self.start, stop=self.stop, step=self.step) if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal r2 = r[self.start:self.stop:self.step] self.assertTrue(common.allequal(r2, array2.read())) # Assert the number of rows in array if common.verbose: print("nrows in array2-->", array2.nrows) print("and it should be-->", r2.shape[0]) self.assertEqual(r2.shape[0], array2.nrows) def test02_indexclosef(self): """Checking Array.copy() method with indexes (close file version)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_indexclosef..." % self.__class__.__name__) # Create a numpy r = np.arange(200, dtype='int32') r.shape = (100, 2) # Save it in a array: array1 = self.h5file.create_array( self.h5file.root, 'array1', r, "title array1") # Copy to another array array2 = array1.copy("/", 'array2', start=self.start, stop=self.stop, step=self.step) # Close and reopen the file self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal r2 = r[self.start:self.stop:self.step] self.assertTrue(common.allequal(r2, array2.read())) # Assert the number of rows in array if common.verbose: print("nrows in array2-->", array2.nrows) print("and it should be-->", r2.shape[0]) self.assertEqual(r2.shape[0], array2.nrows) class CopyIndex1TestCase(CopyIndexTestCase): start = 0 stop = 7 step = 1 class CopyIndex2TestCase(CopyIndexTestCase): start = 0 stop = -1 step = 1 class CopyIndex3TestCase(CopyIndexTestCase): start = 1 stop = 7 step = 1 class CopyIndex4TestCase(CopyIndexTestCase): start = 0 stop = 6 step = 1 class CopyIndex5TestCase(CopyIndexTestCase): start = 3 stop = 7 step = 1 class CopyIndex6TestCase(CopyIndexTestCase): start = 3 stop = 6 step = 2 class CopyIndex7TestCase(CopyIndexTestCase): start = 0 stop = 7 step = 10 class CopyIndex8TestCase(CopyIndexTestCase): start = 6 stop = -1 # Negative values means starting from the end step = 1 class CopyIndex9TestCase(CopyIndexTestCase): start = 3 stop = 4 step = 1 class CopyIndex10TestCase(CopyIndexTestCase): start = 3 stop = 4 step = 2 class CopyIndex11TestCase(CopyIndexTestCase): start = -3 stop = -1 step = 2 class CopyIndex12TestCase(CopyIndexTestCase): start = -1 # Should point to the last element stop = None # None should mean the last element (including it) step = 1 class GetItemTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_single(self): """Single element access (character types)""" # Create the array under root and name 'somearray' a = self.charList arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element if common.verbose: print("Original first element:", a[0], type(a[0])) print("Read first element:", arr[0], type(arr[0])) self.assertTrue(common.allequal(a[0], arr[0])) self.assertEqual(type(a[0]), type(arr[0])) def test01_single(self): """Single element access (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalList arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element if common.verbose: print("Original first element:", a[0], type(a[0])) print("Read first element:", arr[0], type(arr[0])) self.assertEqual(a[0], arr[0]) self.assertEqual(type(a[0]), type(arr[0])) def test02_range(self): """Range element access (character types)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element if common.verbose: print("Original elements:", a[1:4]) print("Read elements:", arr[1:4]) self.assertTrue(common.allequal(a[1:4], arr[1:4])) def test03_range(self): """Range element access (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element if common.verbose: print("Original elements:", a[1:4]) print("Read elements:", arr[1:4]) self.assertTrue(common.allequal(a[1:4], arr[1:4])) def test04_range(self): """Range element access, strided (character types)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element if common.verbose: print("Original elements:", a[1:4:2]) print("Read elements:", arr[1:4:2]) self.assertTrue(common.allequal(a[1:4:2], arr[1:4:2])) def test05_range(self): """Range element access, strided (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element if common.verbose: print("Original elements:", a[1:4:2]) print("Read elements:", arr[1:4:2]) self.assertTrue(common.allequal(a[1:4:2], arr[1:4:2])) def test06_negativeIndex(self): """Negative Index element access (character types)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element if common.verbose: print("Original last element:", a[-1]) print("Read last element:", arr[-1]) self.assertTrue(common.allequal(a[-1], arr[-1])) def test07_negativeIndex(self): """Negative Index element access (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element if common.verbose: print("Original before last element:", a[-2]) print("Read before last element:", arr[-2]) if isinstance(a[-2], np.ndarray): self.assertTrue(common.allequal(a[-2], arr[-2])) else: self.assertEqual(a[-2], arr[-2]) def test08_negativeRange(self): """Negative range element access (character types)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element if common.verbose: print("Original last elements:", a[-4:-1]) print("Read last elements:", arr[-4:-1]) self.assertTrue(common.allequal(a[-4:-1], arr[-4:-1])) def test09_negativeRange(self): """Negative range element access (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element if common.verbose: print("Original last elements:", a[-4:-1]) print("Read last elements:", arr[-4:-1]) self.assertTrue(common.allequal(a[-4:-1], arr[-4:-1])) class GI1NATestCase(GetItemTestCase, common.PyTablesTestCase): title = "Rank-1 case 1" numericalList = np.array([3]) numericalListME = np.array([3, 2, 1, 0, 4, 5, 6]) charList = np.array(["3"], 'S') charListME = np.array( ["321", "221", "121", "021", "421", "521", "621"], 'S') class GI1NAOpenTestCase(GI1NATestCase): close = 0 class GI1NACloseTestCase(GI1NATestCase): close = 1 class GI2NATestCase(GetItemTestCase): # A more complex example title = "Rank-1,2 case 2" numericalList = np.array([3, 4]) numericalListME = np.array([[3, 2, 1, 0, 4, 5, 6], [2, 1, 0, 4, 5, 6, 7], [4, 3, 2, 1, 0, 4, 5], [3, 2, 1, 0, 4, 5, 6], [3, 2, 1, 0, 4, 5, 6]]) charList = np.array(["a", "b"], 'S') charListME = np.array( [["321", "221", "121", "021", "421", "521", "621"], ["21", "21", "11", "02", "42", "21", "61"], ["31", "21", "12", "21", "41", "51", "621"], ["321", "221", "121", "021", "421", "521", "621"], ["3241", "2321", "13216", "0621", "4421", "5421", "a621"], ["a321", "s221", "d121", "g021", "b421", "5vvv21", "6zxzxs21"]], 'S') class GI2NAOpenTestCase(GI2NATestCase): close = 0 class GI2NACloseTestCase(GI2NATestCase): close = 1 class SetItemTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_single(self): """Single element update (character types)""" # Create the array under root and name 'somearray' a = self.charList arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen('a') arr = self.h5file.root.somearray # Modify a single element of a and arr: a[0] = b"b" arr[0] = b"b" # Get and compare an element if common.verbose: print("Original first element:", a[0]) print("Read first element:", arr[0]) self.assertTrue(common.allequal(a[0], arr[0])) def test01_single(self): """Single element update (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalList arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen('a') arr = self.h5file.root.somearray # Modify elements of a and arr: a[0] = 333 arr[0] = 333 # Get and compare an element if common.verbose: print("Original first element:", a[0]) print("Read first element:", arr[0]) self.assertEqual(a[0], arr[0]) def test02_range(self): """Range element update (character types)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen('a') arr = self.h5file.root.somearray # Modify elements of a and arr: a[1:3] = b"xXx" arr[1:3] = b"xXx" # Get and compare an element if common.verbose: print("Original elements:", a[1:4]) print("Read elements:", arr[1:4]) self.assertTrue(common.allequal(a[1:4], arr[1:4])) def test03_range(self): """Range element update (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen('a') arr = self.h5file.root.somearray # Modify elements of a and arr: s = slice(1, 3, None) rng = np.arange(a[s].size)*2 + 3 rng.shape = a[s].shape a[s] = rng arr[s] = rng # Get and compare an element if common.verbose: print("Original elements:", a[1:4]) print("Read elements:", arr[1:4]) self.assertTrue(common.allequal(a[1:4], arr[1:4])) def test04_range(self): """Range element update, strided (character types)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen('a') arr = self.h5file.root.somearray # Modify elements of a and arr: s = slice(1, 4, 2) a[s] = b"xXx" arr[s] = b"xXx" # Get and compare an element if common.verbose: print("Original elements:", a[1:4:2]) print("Read elements:", arr[1:4:2]) self.assertTrue(common.allequal(a[1:4:2], arr[1:4:2])) def test05_range(self): """Range element update, strided (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen('a') arr = self.h5file.root.somearray # Modify elements of a and arr: s = slice(1, 4, 2) rng = np.arange(a[s].size)*2 + 3 rng.shape = a[s].shape a[s] = rng arr[s] = rng # Get and compare an element if common.verbose: print("Original elements:", a[1:4:2]) print("Read elements:", arr[1:4:2]) self.assertTrue(common.allequal(a[1:4:2], arr[1:4:2])) def test06_negativeIndex(self): """Negative Index element update (character types)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen('a') arr = self.h5file.root.somearray # Modify elements of a and arr: s = -1 a[s] = b"xXx" arr[s] = b"xXx" # Get and compare an element if common.verbose: print("Original last element:", a[-1]) print("Read last element:", arr[-1]) self.assertTrue(common.allequal(a[-1], arr[-1])) def test07_negativeIndex(self): """Negative Index element update (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen('a') arr = self.h5file.root.somearray # Modify elements of a and arr: s = -2 a[s] = a[s]*2 + 3 arr[s] = arr[s]*2 + 3 # Get and compare an element if common.verbose: print("Original before last element:", a[-2]) print("Read before last element:", arr[-2]) if isinstance(a[-2], np.ndarray): self.assertTrue(common.allequal(a[-2], arr[-2])) else: self.assertEqual(a[-2], arr[-2]) def test08_negativeRange(self): """Negative range element update (character types)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen('a') arr = self.h5file.root.somearray # Modify elements of a and arr: s = slice(-4, -1, None) a[s] = b"xXx" arr[s] = b"xXx" # Get and compare an element if common.verbose: print("Original last elements:", a[-4:-1]) print("Read last elements:", arr[-4:-1]) self.assertTrue(common.allequal(a[-4:-1], arr[-4:-1])) def test09_negativeRange(self): """Negative range element update (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen('a') arr = self.h5file.root.somearray # Modify elements of a and arr: s = slice(-3, -1, None) rng = np.arange(a[s].size)*2 + 3 rng.shape = a[s].shape a[s] = rng arr[s] = rng # Get and compare an element if common.verbose: print("Original last elements:", a[-4:-1]) print("Read last elements:", arr[-4:-1]) self.assertTrue(common.allequal(a[-4:-1], arr[-4:-1])) def test10_outOfRange(self): """Out of range update (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen('a') arr = self.h5file.root.somearray # Modify elements of arr that are out of range: s = slice(1, a.shape[0]+1, None) s2 = slice(1, 1000, None) rng = np.arange(a[s].size)*2 + 3 rng.shape = a[s].shape a[s] = rng rng2 = np.arange(a[s2].size)*2 + 3 rng2.shape = a[s2].shape arr[s2] = rng2 # Get and compare an element if common.verbose: print("Original last elements:", a[-4:-1]) print("Read last elements:", arr[-4:-1]) self.assertTrue(common.allequal(a[-4:-1], arr[-4:-1])) class SI1NATestCase(SetItemTestCase, common.PyTablesTestCase): title = "Rank-1 case 1" numericalList = np.array([3]) numericalListME = np.array([3, 2, 1, 0, 4, 5, 6]) charList = np.array(["3"], 'S') charListME = np.array( ["321", "221", "121", "021", "421", "521", "621"], 'S') class SI1NAOpenTestCase(SI1NATestCase): close = 0 class SI1NACloseTestCase(SI1NATestCase): close = 1 class SI2NATestCase(SetItemTestCase): # A more complex example title = "Rank-1,2 case 2" numericalList = np.array([3, 4]) numericalListME = np.array([[3, 2, 1, 0, 4, 5, 6], [2, 1, 0, 4, 5, 6, 7], [4, 3, 2, 1, 0, 4, 5], [3, 2, 1, 0, 4, 5, 6], [3, 2, 1, 0, 4, 5, 6]]) charList = np.array(["a", "b"], 'S') charListME = np.array( [["321", "221", "121", "021", "421", "521", "621"], ["21", "21", "11", "02", "42", "21", "61"], ["31", "21", "12", "21", "41", "51", "621"], ["321", "221", "121", "021", "421", "521", "621"], ["3241", "2321", "13216", "0621", "4421", "5421", "a621"], ["a321", "s221", "d121", "g021", "b421", "5vvv21", "6zxzxs21"]], 'S') class SI2NAOpenTestCase(SI2NATestCase): close = 0 class SI2NACloseTestCase(SI2NATestCase): close = 1 class GeneratorTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00a_single(self): """Testing generator access to Arrays, single elements (char)""" # Create the array under root and name 'somearray' a = self.charList arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element ga = [i for i in a] garr = [i for i in arr] if common.verbose: print("Result of original iterator:", ga) print("Result of read generator:", garr) self.assertEqual(ga, garr) def test00b_me(self): """Testing generator access to Arrays, multiple elements (char)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element ga = list(a) garr = list(arr) if common.verbose: print("Result of original iterator:", ga) print("Result of read generator:", garr) for x_ga, x_garr in zip(ga, garr): self.assertTrue(common.allequal(x_ga, x_garr)) def test01a_single(self): """Testing generator access to Arrays, single elements (numeric)""" # Create the array under root and name 'somearray' a = self.numericalList arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element ga = [i for i in a] garr = [i for i in arr] if common.verbose: print("Result of original iterator:", ga) print("Result of read generator:", garr) self.assertEqual(ga, garr) def test01b_me(self): """Testing generator access to Arrays, multiple elements (numeric)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array( self.h5file.root, 'somearray', a, "Some array") if self.close: self._reopen() arr = self.h5file.root.somearray # Get and compare an element ga = list(a) garr = list(arr) if common.verbose: print("Result of original iterator:", ga) print("Result of read generator:", garr) for x_ga, x_garr in zip(ga, garr): self.assertTrue(common.allequal(x_ga, x_garr)) class GE1NATestCase(GeneratorTestCase): title = "Rank-1 case 1" numericalList = np.array([3]) numericalListME = np.array([3, 2, 1, 0, 4, 5, 6]) charList = np.array(["3"], 'S') charListME = np.array( ["321", "221", "121", "021", "421", "521", "621"], 'S') class GE1NAOpenTestCase(GE1NATestCase): close = 0 class GE1NACloseTestCase(GE1NATestCase): close = 1 class GE2NATestCase(GeneratorTestCase): # A more complex example title = "Rank-1,2 case 2" numericalList = np.array([3, 4]) numericalListME = np.array([[3, 2, 1, 0, 4, 5, 6], [2, 1, 0, 4, 5, 6, 7], [4, 3, 2, 1, 0, 4, 5], [3, 2, 1, 0, 4, 5, 6], [3, 2, 1, 0, 4, 5, 6]]) charList = np.array(["a", "b"], 'S') charListME = np.array( [["321", "221", "121", "021", "421", "521", "621"], ["21", "21", "11", "02", "42", "21", "61"], ["31", "21", "12", "21", "41", "51", "621"], ["321", "221", "121", "021", "421", "521", "621"], ["3241", "2321", "13216", "0621", "4421", "5421", "a621"], ["a321", "s221", "d121", "g021", "b421", "5vvv21", "6zxzxs21"]], 'S') class GE2NAOpenTestCase(GE2NATestCase): close = 0 class GE2NACloseTestCase(GE2NATestCase): close = 1 class NonHomogeneousTestCase(common.TempFileMixin, common.PyTablesTestCase): def test(self): """Test for creation of non-homogeneous arrays.""" # This checks ticket #12. self.assertRaises((ValueError, TypeError), self.h5file.create_array, '/', 'test', [1, [2, 3]]) self.assertRaises(tb.NoSuchNodeError, self.h5file.remove_node, '/test') class TruncateTestCase(common.TempFileMixin, common.PyTablesTestCase): def test(self): """Test for unability to truncate Array objects.""" array1 = self.h5file.create_array('/', 'array1', [0, 2]) self.assertRaises(TypeError, array1.truncate, 0) class PointSelectionTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() # Limits for selections self.limits = [ (0, 1), # just one element (20, -10), # no elements (-10, 4), # several elements (0, 10), # several elements (again) ] # Create a sample array size = np.prod(self.shape) nparr = np.arange(size, dtype=np.int32).reshape(self.shape) self.nparr = nparr self.tbarr = self.h5file.create_array(self.h5file.root, 'array', nparr) def test01a_read(self): """Test for point-selections (read, boolean keys).""" nparr = self.nparr tbarr = self.tbarr for value1, value2 in self.limits: key = (nparr >= value1) & (nparr < value2) if common.verbose: print("Selection to test:", key) a = nparr[key] b = tbarr[key] self.assertTrue( np.alltrue(a == b), "NumPy array and PyTables selections does not match.") def test01b_read(self): """Test for point-selections (read, integer keys).""" nparr = self.nparr tbarr = self.tbarr for value1, value2 in self.limits: key = np.where((nparr >= value1) & (nparr < value2)) if common.verbose: print("Selection to test:", key) a = nparr[key] b = tbarr[key] self.assertTrue( np.alltrue(a == b), "NumPy array and PyTables selections does not match.") def test01c_read(self): """Test for point-selections (read, float keys).""" nparr = self.nparr tbarr = self.tbarr for value1, value2 in self.limits: key = np.where((nparr >= value1) & (nparr < value2)) if common.verbose: print("Selection to test:", key) # a = nparr[key] fkey = np.array(key, "f4") self.assertRaises((IndexError, TypeError), tbarr.__getitem__, fkey) def test01d_read(self): nparr = self.nparr tbarr = self.tbarr for key in self.working_keyset: if nparr.ndim > 1: key = tuple(key) if common.verbose: print("Selection to test:", key) a = nparr[key] b = tbarr[key] np.testing.assert_array_equal( a, b, "NumPy array and PyTables selections does not match.") def test01e_read(self): tbarr = self.tbarr for key in self.not_working_keyset: if common.verbose: print("Selection to test:", key) self.assertRaises(IndexError, tbarr.__getitem__, key) def test02a_write(self): """Test for point-selections (write, boolean keys).""" nparr = self.nparr tbarr = self.tbarr for value1, value2 in self.limits: key = (nparr >= value1) & (nparr < value2) if common.verbose: print("Selection to test:", key) s = nparr[key] nparr[key] = s * 2 tbarr[key] = s * 2 a = nparr[:] b = tbarr[:] self.assertTrue( np.alltrue(a == b), "NumPy array and PyTables modifications does not match.") def test02b_write(self): """Test for point-selections (write, integer keys).""" nparr = self.nparr tbarr = self.tbarr for value1, value2 in self.limits: key = np.where((nparr >= value1) & (nparr < value2)) if common.verbose: print("Selection to test:", key) s = nparr[key] nparr[key] = s * 2 tbarr[key] = s * 2 a = nparr[:] b = tbarr[:] self.assertTrue( np.alltrue(a == b), "NumPy array and PyTables modifications does not match.") def test02c_write(self): """Test for point-selections (write, integer values, broadcast).""" nparr = self.nparr tbarr = self.tbarr for value1, value2 in self.limits: key = np.where((nparr >= value1) & (nparr < value2)) if common.verbose: print("Selection to test:", key) # s = nparr[key] nparr[key] = 2 # force a broadcast tbarr[key] = 2 # force a broadcast a = nparr[:] b = tbarr[:] self.assertTrue( np.alltrue(a == b), "NumPy array and PyTables modifications does not match.") class PointSelection0(PointSelectionTestCase): shape = (3,) working_keyset = [ [0, 1], [0, -1], ] not_working_keyset = [ [0, 3], [0, 4], [0, -4], ] class PointSelection1(PointSelectionTestCase): shape = (5, 3, 3) working_keyset = [ [(0, 0), (0, 1), (0, 0)], [(0, 0), (0, -1), (0, 0)], ] not_working_keyset = [ [(0, 0), (0, 3), (0, 0)], [(0, 0), (0, 4), (0, 0)], [(0, 0), (0, -4), (0, 0)], [(0, 0), (0, -5), (0, 0)] ] class PointSelection2(PointSelectionTestCase): shape = (7, 3) working_keyset = [ [(0, 0), (0, 1)], [(0, 0), (0, -1)], [(0, 0), (0, -2)], ] not_working_keyset = [ [(0, 0), (0, 3)], [(0, 0), (0, 4)], [(0, 0), (0, -4)], [(0, 0), (0, -5)], ] class PointSelection3(PointSelectionTestCase): shape = (4, 3, 2, 1) working_keyset = [ [(0, 0), (0, 1), (0, 0), (0, 0)], [(0, 0), (0, -1), (0, 0), (0, 0)], ] not_working_keyset = [ [(0, 0), (0, 3), (0, 0), (0, 0)], [(0, 0), (0, 4), (0, 0), (0, 0)], [(0, 0), (0, -4), (0, 0), (0, 0)], ] class PointSelection4(PointSelectionTestCase): shape = (1, 3, 2, 5, 6) working_keyset = [ [(0, 0), (0, 1), (0, 0), (0, 0), (0, 0)], [(0, 0), (0, -1), (0, 0), (0, 0), (0, 0)], ] not_working_keyset = [ [(0, 0), (0, 3), (0, 0), (0, 0), (0, 0)], [(0, 0), (0, 4), (0, 0), (0, 0), (0, 0)], [(0, 0), (0, -4), (0, 0), (0, 0), (0, 0)], ] class FancySelectionTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() m, n, o = self.shape # The next are valid selections for both NumPy and PyTables self.working_keyset = [ ([1, 3], slice(1, n-1), 2), ([m-1, 1, 3, 2], slice(None), 2), # unordered lists supported (slice(m), [n-1, 1, 0], slice(None)), (slice(1, m, 3), slice(1, n), [o-1, 1, 0]), (m-1, [2, 1], 1), (1, 2, 1), # regular selection ([1, 2], -2, -1), # negative indices ([1, -2], 2, -1), # more negative indices ([1, -2], 2, Ellipsis), # one ellipsis (Ellipsis, [1, 2]), # one ellipsis (np.array( [1, -2], 'i4'), 2, -1), # array 32-bit instead of list (np.array( [-1, 2], 'i8'), 2, -1), # array 64-bit instead of list ] # Using booleans instead of ints is deprecated since numpy 1.8 # Tests for keys that have to support the __index__ attribute # self.working_keyset.append( # (False, True), # equivalent to (0,1) ;-) # ) # Valid selections for NumPy, but not for PyTables (yet) # The next should raise an IndexError self.not_working_keyset = [ np.array([False, True], dtype="b1"), # boolean arrays ([1, 2, 1], 2, 1), # repeated values ([1, 2], 2, [1, 2]), # several lists ([], 2, 1), # empty selections (Ellipsis, [1, 2], Ellipsis), # several ellipsis # Using booleans instead of ints is deprecated since numpy 1.8 ([False, True]), # boolean values with incompatible shape ] # The next should raise an IndexError in both NumPy and PyTables self.not_working_oob = [ ([1, 2], 2, 1000), # out-of-bounds selections ([1, 2], 2000, 1), # out-of-bounds selections ] # The next should raise a IndexError in both NumPy and PyTables self.not_working_too_many = [ ([1, 2], 2, 1, 1), ] # Create a sample array nparr = np.empty(self.shape, dtype=np.int32) data = np.arange(n * o, dtype=np.int32).reshape(n, o) for i in range(m): nparr[i] = data * i self.nparr = nparr self.tbarr = self.h5file.create_array(self.h5file.root, 'array', nparr) def test01a_read(self): """Test for fancy-selections (working selections, read).""" nparr = self.nparr tbarr = self.tbarr for key in self.working_keyset: if common.verbose: print("Selection to test:", key) a = nparr[key] b = tbarr[key] self.assertTrue( np.alltrue(a == b), "NumPy array and PyTables selections does not match.") def test01b_read(self): """Test for fancy-selections (not working selections, read).""" # nparr = self.nparr tbarr = self.tbarr for key in self.not_working_keyset: if common.verbose: print("Selection to test:", key) # a = nparr[key] self.assertRaises(IndexError, tbarr.__getitem__, key) def test01c_read(self): """Test for fancy-selections (out-of-bound indexes, read).""" nparr = self.nparr tbarr = self.tbarr for key in self.not_working_oob: if common.verbose: print("Selection to test:", key) self.assertRaises(IndexError, nparr.__getitem__, key) self.assertRaises(IndexError, tbarr.__getitem__, key) def test01d_read(self): """Test for fancy-selections (too many indexes, read).""" nparr = self.nparr tbarr = self.tbarr for key in self.not_working_too_many: if common.verbose: print("Selection to test:", key) # ValueError for numpy 1.6.x and earlier # IndexError in numpy > 1.8.0 self.assertRaises((ValueError, IndexError), nparr.__getitem__, key) self.assertRaises(IndexError, tbarr.__getitem__, key) def test02a_write(self): """Test for fancy-selections (working selections, write).""" nparr = self.nparr tbarr = self.tbarr for key in self.working_keyset: if common.verbose: print("Selection to test:", key) s = nparr[key] nparr[key] = s * 2 tbarr[key] = s * 2 a = nparr[:] b = tbarr[:] self.assertTrue( np.alltrue(a == b), "NumPy array and PyTables modifications does not match.") def test02b_write(self): """Test for fancy-selections (working selections, write, broadcast).""" nparr = self.nparr tbarr = self.tbarr for key in self.working_keyset: if common.verbose: print("Selection to test:", key) # s = nparr[key] nparr[key] = 2 # broadcast value tbarr[key] = 2 # broadcast value a = nparr[:] b = tbarr[:] # if common.verbose: # print("NumPy modified array:", a) # print("PyTables modifyied array:", b) self.assertTrue( np.alltrue(a == b), "NumPy array and PyTables modifications does not match.") class FancySelection1(FancySelectionTestCase): shape = (5, 3, 3) # Minimum values class FancySelection2(FancySelectionTestCase): # shape = (5, 3, 3) # Minimum values shape = (7, 3, 3) class FancySelection3(FancySelectionTestCase): # shape = (5, 3, 3) # Minimum values shape = (7, 4, 5) class FancySelection4(FancySelectionTestCase): # shape = (5, 3, 3) # Minimum values shape = (5, 3, 10) class CopyNativeHDF5MDAtom(common.PyTablesTestCase): def setUp(self): super().setUp() filename = common.test_filename("array_mdatom.h5") self.h5file = tb.open_file(filename, "r") self.arr = self.h5file.root.arr self.copy = tempfile.mktemp(".h5") self.copyh = tb.open_file(self.copy, mode="w") self.arr2 = self.arr.copy(self.copyh.root, newname="arr2") def tearDown(self): self.h5file.close() self.copyh.close() Path(self.copy).unlink() super().tearDown() def test01_copy(self): """Checking that native MD atoms are copied as-is""" self.assertEqual(self.arr.atom, self.arr2.atom) self.assertEqual(self.arr.shape, self.arr2.shape) def test02_reopen(self): """Checking that native MD atoms are copied as-is (re-open)""" self.copyh.close() self.copyh = tb.open_file(self.copy, mode="r") self.arr2 = self.copyh.root.arr2 self.assertEqual(self.arr.atom, self.arr2.atom) self.assertEqual(self.arr.shape, self.arr2.shape) class AccessClosedTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() a = np.zeros((10, 10)) self.array = self.h5file.create_array(self.h5file.root, 'array', a) def test_read(self): self.h5file.close() self.assertRaises(tb.ClosedNodeError, self.array.read) def test_getitem(self): self.h5file.close() self.assertRaises(tb.ClosedNodeError, self.array.__getitem__, 0) def test_setitem(self): self.h5file.close() self.assertRaises(tb.ClosedNodeError, self.array.__setitem__, 0, 0) class BroadcastTest(common.TempFileMixin, common.PyTablesTestCase): def test(self): """Test correct broadcasting when the array atom is not scalar.""" array_shape = (2, 3) element_shape = (3,) dtype = np.dtype((np.int64, element_shape)) atom = tb.Atom.from_dtype(dtype) h5arr = self.h5file.create_array(self.h5file.root, 'array', atom=atom, shape=array_shape) size = np.prod(element_shape) nparr = np.arange(size).reshape(element_shape) h5arr[0] = nparr self.assertTrue(np.all(h5arr[0] == nparr)) class TestCreateArrayArgs(common.TempFileMixin, common.PyTablesTestCase): where = '/' name = 'array' obj = np.array([[1, 2], [3, 4]]) title = 'title' byteorder = None createparents = False atom = tb.Atom.from_dtype(obj.dtype) shape = obj.shape def test_positional_args(self): self.h5file.create_array(self.where, self.name, self.obj, self.title) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertTrue(common.allequal(self.obj, nparr)) def test_positional_args_atom_shape(self): self.h5file.create_array(self.where, self.name, None, self.title, self.byteorder, self.createparents, self.atom, self.shape) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertTrue(common.allequal(np.zeros_like(self.obj), nparr)) def test_kwargs_obj(self): self.h5file.create_array(self.where, self.name, title=self.title, obj=self.obj) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_atom_shape_01(self): ptarr = self.h5file.create_array(self.where, self.name, title=self.title, atom=self.atom, shape=self.shape) ptarr[...] = self.obj self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_atom_shape_02(self): ptarr = self.h5file.create_array(self.where, self.name, title=self.title, atom=self.atom, shape=self.shape) # ptarr[...] = self.obj self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertTrue(common.allequal(np.zeros_like(self.obj), nparr)) def test_kwargs_obj_atom(self): ptarr = self.h5file.create_array(self.where, self.name, title=self.title, obj=self.obj, atom=self.atom) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj_shape(self): ptarr = self.h5file.create_array(self.where, self.name, title=self.title, obj=self.obj, shape=self.shape) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj_atom_shape(self): ptarr = self.h5file.create_array(self.where, self.name, title=self.title, obj=self.obj, atom=self.atom, shape=self.shape) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj_atom_error(self): atom = tb.Atom.from_dtype(np.dtype('complex')) # shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_array, self.where, self.name, title=self.title, obj=self.obj, atom=atom) def test_kwargs_obj_shape_error(self): # atom = Atom.from_dtype(numpy.dtype('complex')) shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_array, self.where, self.name, title=self.title, obj=self.obj, shape=shape) def test_kwargs_obj_atom_shape_error_01(self): atom = tb.Atom.from_dtype(np.dtype('complex')) # shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_array, self.where, self.name, title=self.title, obj=self.obj, atom=atom, shape=self.shape) def test_kwargs_obj_atom_shape_error_02(self): # atom = Atom.from_dtype(numpy.dtype('complex')) shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_array, self.where, self.name, title=self.title, obj=self.obj, atom=self.atom, shape=shape) def test_kwargs_obj_atom_shape_error_03(self): atom = tb.Atom.from_dtype(np.dtype('complex')) shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_array, self.where, self.name, title=self.title, obj=self.obj, atom=atom, shape=shape) def suite(): theSuite = common.unittest.TestSuite() niter = 1 for i in range(niter): # The scalar case test should be refined in order to work theSuite.addTest(common.unittest.makeSuite(Basic0DOneTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic0DTwoTestCase)) # theSuite.addTest(unittest.makeSuite(Basic1DZeroTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic1DOneTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic1DTwoTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic1DThreeTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic2DOneTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic2DTwoTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic10DTestCase)) # The 32 dimensions case is tested on GroupsArray # theSuite.addTest(unittest.makeSuite(Basic32DTestCase)) theSuite.addTest(common.unittest.makeSuite(ReadOutArgumentTests)) theSuite.addTest(common.unittest.makeSuite( SizeOnDiskInMemoryPropertyTestCase)) theSuite.addTest(common.unittest.makeSuite(GroupsArrayTestCase)) theSuite.addTest(common.unittest.makeSuite( ComplexNotReopenNotEndianTestCase)) theSuite.addTest(common.unittest.makeSuite( ComplexReopenNotEndianTestCase)) theSuite.addTest(common.unittest.makeSuite( ComplexNotReopenEndianTestCase)) theSuite.addTest(common.unittest.makeSuite( ComplexReopenEndianTestCase)) theSuite.addTest(common.unittest.makeSuite(CloseCopyTestCase)) theSuite.addTest(common.unittest.makeSuite(OpenCopyTestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex1TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex2TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex3TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex4TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex5TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex6TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex7TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex8TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex9TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex10TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex11TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex12TestCase)) theSuite.addTest(common.unittest.makeSuite(GI1NAOpenTestCase)) theSuite.addTest(common.unittest.makeSuite(GI1NACloseTestCase)) theSuite.addTest(common.unittest.makeSuite(GI2NAOpenTestCase)) theSuite.addTest(common.unittest.makeSuite(GI2NACloseTestCase)) theSuite.addTest(common.unittest.makeSuite(SI1NAOpenTestCase)) theSuite.addTest(common.unittest.makeSuite(SI1NACloseTestCase)) theSuite.addTest(common.unittest.makeSuite(SI2NAOpenTestCase)) theSuite.addTest(common.unittest.makeSuite(SI2NACloseTestCase)) theSuite.addTest(common.unittest.makeSuite(GE1NAOpenTestCase)) theSuite.addTest(common.unittest.makeSuite(GE1NACloseTestCase)) theSuite.addTest(common.unittest.makeSuite(GE2NAOpenTestCase)) theSuite.addTest(common.unittest.makeSuite(GE2NACloseTestCase)) theSuite.addTest(common.unittest.makeSuite(NonHomogeneousTestCase)) theSuite.addTest(common.unittest.makeSuite(TruncateTestCase)) theSuite.addTest(common.unittest.makeSuite(FancySelection1)) theSuite.addTest(common.unittest.makeSuite(FancySelection2)) theSuite.addTest(common.unittest.makeSuite(FancySelection3)) theSuite.addTest(common.unittest.makeSuite(FancySelection4)) theSuite.addTest(common.unittest.makeSuite(PointSelection0)) theSuite.addTest(common.unittest.makeSuite(PointSelection1)) theSuite.addTest(common.unittest.makeSuite(PointSelection2)) theSuite.addTest(common.unittest.makeSuite(PointSelection3)) theSuite.addTest(common.unittest.makeSuite(PointSelection4)) theSuite.addTest(common.unittest.makeSuite(CopyNativeHDF5MDAtom)) theSuite.addTest(common.unittest.makeSuite(AccessClosedTestCase)) theSuite.addTest(common.unittest.makeSuite(TestCreateArrayArgs)) theSuite.addTest(common.unittest.makeSuite(BroadcastTest)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_attributes.py000066400000000000000000002126421416254111300214640ustar00rootroot00000000000000"""This test unit checks node attributes that are persistent (AttributeSet).""" import datetime import sys import warnings from packaging.version import Version import numpy as np import tables as tb from tables.tests import common class Record(tb.IsDescription): var1 = tb.StringCol(itemsize=4) # 4-character String var2 = tb.IntCol() # integer var3 = tb.Int16Col() # short integer var4 = tb.FloatCol() # double (double-precision) var5 = tb.Float32Col() # float (single-precision) class CreateTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.root = self.h5file.root # Create a table object self.table = self.h5file.create_table(self.root, 'atable', Record, "Table title") # Create an array object self.array = self.h5file.create_array(self.root, 'anarray', [1], "Array title") # Create a group object self.group = self.h5file.create_group(self.root, 'agroup', "Group title") def test01_setAttributes(self): """Checking setting large string attributes (File methods)""" attrlength = 2048 # Try to put a long string attribute on a group object self.h5file.set_node_attr(self.root.agroup, "attr1", "p" * attrlength) # Now, try with a Table object self.h5file.set_node_attr(self.root.atable, "attr1", "a" * attrlength) # Finally, try with an Array object self.h5file.set_node_attr(self.root.anarray, "attr1", "n" * attrlength) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', node_cache_slots=self.node_cache_slots) self.root = self.h5file.root self.assertEqual(self.h5file.get_node_attr(self.root.agroup, 'attr1'), "p" * attrlength) self.assertEqual(self.h5file.get_node_attr(self.root.atable, 'attr1'), "a" * attrlength) self.assertEqual(self.h5file.get_node_attr(self.root.anarray, 'attr1'), "n" * attrlength) def reopen(self): # Reopen if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', node_cache_slots=self.node_cache_slots) self.root = self.h5file.root def check_missing(self, name): self.reopen() self.assertNotIn(name, self.root.agroup._v_attrs) self.assertNotIn(name, self.root.atable.attrs) self.assertNotIn(name, self.root.anarray.attrs) def check_name(self, name, val=''): """Check validity of attribute name filtering""" self.check_missing(name) # Using File methods self.h5file.set_node_attr(self.root.agroup, name, val) self.h5file.set_node_attr(self.root.atable, name, val) self.h5file.set_node_attr(self.root.anarray, name, val) # Check File methods self.reopen() self.assertEqual(self.h5file.get_node_attr(self.root.agroup, name), val) self.assertEqual(self.h5file.get_node_attr(self.root.atable, name), val) self.assertEqual(self.h5file.get_node_attr(self.root.anarray, name), val) # Remove, file methods self.h5file.del_node_attr(self.root.agroup, name) self.h5file.del_node_attr(self.root.atable, name) self.h5file.del_node_attr(self.root.anarray, name) self.check_missing(name) # Using Node methods self.root.agroup._f_setattr(name, val) self.root.atable.set_attr(name, val) self.root.anarray.set_attr(name, val) # Check Node methods self.reopen() self.assertEqual(self.root.agroup._f_getattr(name), val) self.assertEqual(self.root.atable.get_attr(name), val) self.assertEqual(self.root.anarray.get_attr(name), val) self.root.agroup._f_delattr(name) self.root.atable.del_attr(name) self.root.anarray.del_attr(name) self.check_missing(name) # Using AttributeSet methods setattr(self.root.agroup._v_attrs, name, val) setattr(self.root.atable.attrs, name, val) setattr(self.root.anarray.attrs, name, val) # Check AttributeSet methods self.reopen() self.assertEqual(getattr(self.root.agroup._v_attrs, name), val) self.assertEqual(getattr(self.root.atable.attrs, name), val) self.assertEqual(getattr(self.root.anarray.attrs, name), val) delattr(self.root.agroup._v_attrs, name) delattr(self.root.atable.attrs, name) delattr(self.root.anarray.attrs, name) self.check_missing(name) # Using dict [] self.root.agroup._v_attrs[name] = val self.root.atable.attrs[name] = val self.root.anarray.attrs[name] = val # Check dict [] self.reopen() self.assertEqual(self.root.agroup._v_attrs[name], val) self.assertEqual(self.root.atable.attrs[name], val) self.assertEqual(self.root.anarray.attrs[name], val) del self.root.agroup._v_attrs[name] del self.root.atable.attrs[name] del self.root.anarray.attrs[name] self.check_missing(name) def test01a_setAttributes(self): """Checking attribute names validity""" with warnings.catch_warnings(): warnings.simplefilter('ignore', tb.NaturalNameWarning) self.check_name('a') self.check_name('a:b') self.check_name('/a/b') self.check_name('.') self.assertRaises(ValueError, self.check_name, '') self.assertRaises(ValueError, self.check_name, '__members__') self.assertRaises(TypeError, self.check_name, 0) def test02_setAttributes(self): """Checking setting large string attributes (Node methods)""" attrlength = 2048 # Try to put a long string attribute on a group object self.root.agroup._f_setattr('attr1', "p" * attrlength) # Now, try with a Table object self.root.atable.set_attr('attr1', "a" * attrlength) # Finally, try with an Array object self.root.anarray.set_attr('attr1', "n" * attrlength) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', node_cache_slots=self.node_cache_slots) self.root = self.h5file.root self.assertEqual(self.root.agroup._f_getattr( 'attr1'), "p" * attrlength) self.assertEqual(self.root.atable.get_attr("attr1"), "a" * attrlength) self.assertEqual(self.root.anarray.get_attr("attr1"), "n" * attrlength) def test03_setAttributes(self): """Checking setting large string attributes (AttributeSet methods)""" attrlength = 2048 # Try to put a long string attribute on a group object self.group._v_attrs.attr1 = "p" * attrlength # Now, try with a Table object self.table.attrs.attr1 = "a" * attrlength # Finally, try with an Array object self.array.attrs.attr1 = "n" * attrlength if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', node_cache_slots=self.node_cache_slots) self.root = self.h5file.root # This should work even when the node cache is disabled self.assertEqual(self.root.agroup._v_attrs.attr1, "p" * attrlength) self.assertEqual(self.root.atable.attrs.attr1, "a" * attrlength) self.assertEqual(self.root.anarray.attrs.attr1, "n" * attrlength) def test04_listAttributes(self): """Checking listing attributes.""" # With a Group object self.group._v_attrs.pq = "1" self.group._v_attrs.qr = "2" self.group._v_attrs.rs = "3" if common.verbose: print("Attribute list:", self.group._v_attrs._f_list()) # Now, try with a Table object self.table.attrs.a = "1" self.table.attrs.c = "2" self.table.attrs.b = "3" if common.verbose: print("Attribute list:", self.table.attrs._f_list()) # Finally, try with an Array object self.array.attrs.k = "1" self.array.attrs.j = "2" self.array.attrs.i = "3" if common.verbose: print("Attribute list:", self.array.attrs._f_list()) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', node_cache_slots=self.node_cache_slots) self.root = self.h5file.root agroup = self.root.agroup self.assertEqual(agroup._v_attrs._f_list("user"), ["pq", "qr", "rs"]) self.assertEqual(agroup._v_attrs._f_list("sys"), ['CLASS', 'TITLE', 'VERSION']) self.assertEqual(agroup._v_attrs._f_list("all"), ['CLASS', 'TITLE', 'VERSION', "pq", "qr", "rs"]) atable = self.root.atable self.assertEqual(atable.attrs._f_list(), ["a", "b", "c"]) self.assertEqual(atable.attrs._f_list("sys"), ['CLASS', 'FIELD_0_FILL', 'FIELD_0_NAME', 'FIELD_1_FILL', 'FIELD_1_NAME', 'FIELD_2_FILL', 'FIELD_2_NAME', 'FIELD_3_FILL', 'FIELD_3_NAME', 'FIELD_4_FILL', 'FIELD_4_NAME', 'NROWS', 'TITLE', 'VERSION']) self.assertEqual(atable.attrs._f_list("all"), ['CLASS', 'FIELD_0_FILL', 'FIELD_0_NAME', 'FIELD_1_FILL', 'FIELD_1_NAME', 'FIELD_2_FILL', 'FIELD_2_NAME', 'FIELD_3_FILL', 'FIELD_3_NAME', 'FIELD_4_FILL', 'FIELD_4_NAME', 'NROWS', 'TITLE', 'VERSION', "a", "b", "c"]) anarray = self.root.anarray self.assertEqual(anarray.attrs._f_list(), ["i", "j", "k"]) self.assertEqual( anarray.attrs._f_list("sys"), ['CLASS', 'FLAVOR', 'TITLE', 'VERSION']) self.assertEqual( anarray.attrs._f_list("all"), ['CLASS', 'FLAVOR', 'TITLE', 'VERSION', "i", "j", "k"]) def test05_removeAttributes(self): """Checking removing attributes.""" # With a Group object self.group._v_attrs.pq = "1" self.group._v_attrs.qr = "2" self.group._v_attrs.rs = "3" # delete an attribute del self.group._v_attrs.pq if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', node_cache_slots=self.node_cache_slots) self.root = self.h5file.root agroup = self.root.agroup if common.verbose: print("Attribute list:", agroup._v_attrs._f_list()) # Check the local attributes names self.assertEqual(agroup._v_attrs._f_list(), ["qr", "rs"]) if common.verbose: print("Attribute list in disk:", agroup._v_attrs._f_list("all")) # Check the disk attribute names self.assertEqual(agroup._v_attrs._f_list("all"), ['CLASS', 'TITLE', 'VERSION', "qr", "rs"]) # delete an attribute (__delattr__ method) del agroup._v_attrs.qr if common.verbose: print("Attribute list:", agroup._v_attrs._f_list()) # Check the local attributes names self.assertEqual(agroup._v_attrs._f_list(), ["rs"]) if common.verbose: print("Attribute list in disk:", agroup._v_attrs._f_list()) # Check the disk attribute names self.assertEqual(agroup._v_attrs._f_list("all"), ['CLASS', 'TITLE', 'VERSION', "rs"]) def test05b_removeAttributes(self): """Checking removing attributes (using File.del_node_attr())""" # With a Group object self.group._v_attrs.pq = "1" self.group._v_attrs.qr = "2" self.group._v_attrs.rs = "3" # delete an attribute self.h5file.del_node_attr(self.group, "pq") if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', node_cache_slots=self.node_cache_slots) self.root = self.h5file.root agroup = self.root.agroup if common.verbose: print("Attribute list:", agroup._v_attrs._f_list()) # Check the local attributes names self.assertEqual(agroup._v_attrs._f_list(), ["qr", "rs"]) if common.verbose: print("Attribute list in disk:", agroup._v_attrs._f_list("all")) # Check the disk attribute names self.assertEqual(agroup._v_attrs._f_list("all"), ['CLASS', 'TITLE', 'VERSION', "qr", "rs"]) # delete an attribute (File.del_node_attr method) self.h5file.del_node_attr(self.root, "qr", "agroup") if common.verbose: print("Attribute list:", agroup._v_attrs._f_list()) # Check the local attributes names self.assertEqual(agroup._v_attrs._f_list(), ["rs"]) if common.verbose: print("Attribute list in disk:", agroup._v_attrs._f_list()) # Check the disk attribute names self.assertEqual(agroup._v_attrs._f_list("all"), ['CLASS', 'TITLE', 'VERSION', "rs"]) def test06_removeAttributes(self): """Checking removing system attributes.""" # remove a system attribute if common.verbose: print("Before removing CLASS attribute") print("System attrs:", self.group._v_attrs._v_attrnamessys) del self.group._v_attrs.CLASS self.assertEqual(self.group._v_attrs._f_list("sys"), ['TITLE', 'VERSION']) if common.verbose: print("After removing CLASS attribute") print("System attrs:", self.group._v_attrs._v_attrnamessys) def test07_renameAttributes(self): """Checking renaming attributes.""" # With a Group object self.group._v_attrs.pq = "1" self.group._v_attrs.qr = "2" self.group._v_attrs.rs = "3" # rename an attribute self.group._v_attrs._f_rename("pq", "op") if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', node_cache_slots=self.node_cache_slots) self.root = self.h5file.root agroup = self.root.agroup if common.verbose: print("Attribute list:", agroup._v_attrs._f_list()) # Check the local attributes names (alphabetically sorted) self.assertEqual(agroup._v_attrs._f_list(), ["op", "qr", "rs"]) if common.verbose: print("Attribute list in disk:", agroup._v_attrs._f_list("all")) # Check the disk attribute names (not sorted) self.assertEqual(agroup._v_attrs._f_list("all"), ['CLASS', 'TITLE', 'VERSION', "op", "qr", "rs"]) def test08_renameAttributes(self): """Checking renaming system attributes.""" if common.verbose: print("Before renaming CLASS attribute") print("All attrs:", self.group._v_attrs._v_attrnames) # rename a system attribute self.group._v_attrs._f_rename("CLASS", "op") if common.verbose: print("After renaming CLASS attribute") print("All attrs:", self.group._v_attrs._v_attrnames) # Check the disk attribute names (not sorted) agroup = self.root.agroup self.assertEqual(agroup._v_attrs._f_list("all"), ['TITLE', 'VERSION', "op"]) def test09_overwriteAttributes(self): """Checking overwriting attributes.""" # With a Group object self.group._v_attrs.pq = "1" self.group._v_attrs.qr = "2" self.group._v_attrs.rs = "3" # overwrite attributes self.group._v_attrs.pq = "4" self.group._v_attrs.qr = 2 self.group._v_attrs.rs = [1, 2, 3] if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', node_cache_slots=self.node_cache_slots) self.root = self.h5file.root agroup = self.root.agroup if common.verbose: print("Value of Attribute pq:", agroup._v_attrs.pq) # Check the local attributes names (alphabetically sorted) self.assertEqual(agroup._v_attrs.pq, "4") self.assertEqual(agroup._v_attrs.qr, 2) self.assertEqual(agroup._v_attrs.rs, [1, 2, 3]) if common.verbose: print("Attribute list in disk:", agroup._v_attrs._f_list("all")) # Check the disk attribute names (not sorted) self.assertEqual(agroup._v_attrs._f_list("all"), ['CLASS', 'TITLE', 'VERSION', "pq", "qr", "rs"]) def test10a_copyAttributes(self): """Checking copying attributes.""" # With a Group object self.group._v_attrs.pq = "1" self.group._v_attrs.qr = "2" self.group._v_attrs.rs = "3" # copy all attributes from "/agroup" to "/atable" self.group._v_attrs._f_copy(self.root.atable) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', node_cache_slots=self.node_cache_slots) self.root = self.h5file.root atable = self.root.atable if common.verbose: print("Attribute list:", atable._v_attrs._f_list()) # Check the local attributes names (alphabetically sorted) self.assertEqual(atable._v_attrs._f_list(), ["pq", "qr", "rs"]) if common.verbose: print("Complete attribute list:", atable._v_attrs._f_list("all")) # Check the disk attribute names (not sorted) self.assertEqual(atable._v_attrs._f_list("all"), ['CLASS', 'FIELD_0_FILL', 'FIELD_0_NAME', 'FIELD_1_FILL', 'FIELD_1_NAME', 'FIELD_2_FILL', 'FIELD_2_NAME', 'FIELD_3_FILL', 'FIELD_3_NAME', 'FIELD_4_FILL', 'FIELD_4_NAME', 'NROWS', 'TITLE', 'VERSION', "pq", "qr", "rs"]) def test10b_copyAttributes(self): """Checking copying attributes (copy_node_attrs)""" # With a Group object self.group._v_attrs.pq = "1" self.group._v_attrs.qr = "2" self.group._v_attrs.rs = "3" # copy all attributes from "/agroup" to "/atable" self.h5file.copy_node_attrs(self.group, self.root.atable) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', node_cache_slots=self.node_cache_slots) self.root = self.h5file.root atable = self.root.atable if common.verbose: print("Attribute list:", atable._v_attrs._f_list()) # Check the local attributes names (alphabetically sorted) self.assertEqual(atable._v_attrs._f_list(), ["pq", "qr", "rs"]) if common.verbose: print("Complete attribute list:", atable._v_attrs._f_list("all")) # Check the disk attribute names (not sorted) self.assertEqual(atable._v_attrs._f_list("all"), ['CLASS', 'FIELD_0_FILL', 'FIELD_0_NAME', 'FIELD_1_FILL', 'FIELD_1_NAME', 'FIELD_2_FILL', 'FIELD_2_NAME', 'FIELD_3_FILL', 'FIELD_3_NAME', 'FIELD_4_FILL', 'FIELD_4_NAME', 'NROWS', 'TITLE', 'VERSION', "pq", "qr", "rs"]) def test10c_copyAttributes(self): """Checking copying attributes during group copies.""" # With a Group object self.group._v_attrs['CLASS'] = "GROUP2" self.group._v_attrs['VERSION'] = "1.3" # copy "/agroup" to "/agroup2" self.h5file.copy_node(self.group, self.root, "agroup2") if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', node_cache_slots=self.node_cache_slots) self.root = self.h5file.root agroup2 = self.root.agroup2 if common.verbose: print("Complete attribute list:", agroup2._v_attrs._f_list("all")) self.assertEqual(agroup2._v_attrs['CLASS'], "GROUP2") self.assertEqual(agroup2._v_attrs['VERSION'], "1.3") def test10d_copyAttributes(self): """Checking copying attributes during leaf copies.""" # With a Group object atable = self.root.atable atable._v_attrs['CLASS'] = "TABLE2" atable._v_attrs['VERSION'] = "1.3" # copy "/agroup" to "/agroup2" self.h5file.copy_node(atable, self.root, "atable2") if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', node_cache_slots=self.node_cache_slots) self.root = self.h5file.root atable2 = self.root.atable2 if common.verbose: print("Complete attribute list:", atable2._v_attrs._f_list("all")) self.assertEqual(atable2._v_attrs['CLASS'], "TABLE2") self.assertEqual(atable2._v_attrs['VERSION'], "1.3") def test11a_getitem(self): """Checking the __getitem__ interface.""" attrs = self.group._v_attrs attrs.pq = "1" self.assertEqual(attrs['pq'], "1") def test11b_setitem(self): """Checking the __setitem__ interface.""" attrs = self.group._v_attrs attrs['pq'] = "2" self.assertEqual(attrs['pq'], "2") def test11c_delitem(self): """Checking the __delitem__ interface.""" attrs = self.group._v_attrs attrs.pq = "1" del attrs['pq'] self.assertNotIn('pq', attrs._f_list()) def test11d_KeyError(self): """Checking that KeyError is raised in __getitem__/__delitem__.""" attrs = self.group._v_attrs self.assertRaises(KeyError, attrs.__getitem__, 'pq') self.assertRaises(KeyError, attrs.__delitem__, 'pq') def test_2d_non_contiguous(self): """Checking setting 2D and non-contiguous NumPy attributes""" # Regression for gh-176 numpy. # In the views old implementation PyTAbles performa a copy of the # array: # # value = numpy.array(value) # # in order to get a contiguous array. # Unfortunately array with swapped axis are copyed as they are so # thay are stored in to HDF5 attributes without being actually # contiguous and ths causes an error whn they are restored. data = np.array([[0, 1], [2, 3]]) self.array.attrs['a'] = data self.array.attrs['b'] = data.T.copy() self.array.attrs['c'] = data.T np.testing.assert_array_equal(self.array.attrs['a'], data) np.testing.assert_array_equal(self.array.attrs['b'], data.T) # AssertionError: np.testing.assert_array_equal(self.array.attrs['c'], data.T) def test12_dir(self): """Checking AttributeSet.__dir__""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test12_dir..." % self.__class__.__name__) attrset = self.group._v_attrs user_attr = 'good_attr' sys_attr = 'BETTER_ATTR' for a in [user_attr, sys_attr]: attrset[a] = 1 bad_user = '5bad' bad_sys = 'SYS%' for a in [bad_user, bad_sys]: with warnings.catch_warnings(): warnings.simplefilter('ignore', tb.NaturalNameWarning) attrset[a] = 1 completions = dir(attrset) # Check some regular attributes. self.assertIn('__class__', completions) self.assertIn('_f_copy', completions) self.assertEqual(completions.count('_f_copy'), 1) # Check SYS attrs. self.assertNotIn(bad_sys, completions) self.assertIn(sys_attr, completions) self.assertEqual(completions.count(sys_attr), 1) # Check USER attrs. self.assertIn(user_attr, completions) self.assertNotIn(bad_user, completions) self.assertEqual(completions.count(user_attr), 1) # Now check all for no duplicates. self.assertSequenceEqual(sorted(set(completions)), sorted(completions)) class NotCloseCreate(CreateTestCase): close = False node_cache_slots = tb.parameters.NODE_CACHE_SLOTS open_kwargs = dict(node_cache_slots=node_cache_slots) class CloseCreate(CreateTestCase): close = True node_cache_slots = tb.parameters.NODE_CACHE_SLOTS open_kwargs = dict(node_cache_slots=node_cache_slots) class NoCacheNotCloseCreate(CreateTestCase): close = False node_cache_slots = 0 open_kwargs = dict(node_cache_slots=node_cache_slots) class NoCacheCloseCreate(CreateTestCase): close = True node_cache_slots = 0 open_kwargs = dict(node_cache_slots=node_cache_slots) class DictCacheNotCloseCreate(CreateTestCase): close = False node_cache_slots = -tb.parameters.NODE_CACHE_SLOTS open_kwargs = dict(node_cache_slots=node_cache_slots) class DictCacheCloseCreate(CreateTestCase): close = True node_cache_slots = -tb.parameters.NODE_CACHE_SLOTS open_kwargs = dict(node_cache_slots=node_cache_slots) class TypesTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): self.open_kwargs = {'allow_padding': self.allow_padding} super().setUp() self.root = self.h5file.root # Create an array object self.array = self.h5file.create_array(self.root, 'anarray', [1], "Array title") # Create a group object self.group = self.h5file.create_group(self.root, 'agroup', "Group title") def test00a_setBoolAttributes(self): """Checking setting Bool attributes (scalar, Python case)""" self.array.attrs.pq = True self.array.attrs.qr = False self.array.attrs.rs = True # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray self.assertEqual(self.root.anarray.attrs.pq, True) self.assertEqual(self.root.anarray.attrs.qr, False) self.assertEqual(self.root.anarray.attrs.rs, True) def test00b_setBoolAttributes(self): """Checking setting Bool attributes (scalar, NumPy case)""" self.array.attrs.pq = np.bool_(True) self.array.attrs.qr = np.bool_(False) self.array.attrs.rs = np.bool_(True) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray self.assertIsInstance(self.root.anarray.attrs.pq, np.bool_) self.assertIsInstance(self.root.anarray.attrs.qr, np.bool_) self.assertIsInstance(self.root.anarray.attrs.rs, np.bool_) self.assertEqual(self.root.anarray.attrs.pq, True) self.assertEqual(self.root.anarray.attrs.qr, False) self.assertEqual(self.root.anarray.attrs.rs, True) def test00c_setBoolAttributes(self): """Checking setting Bool attributes (NumPy, 0-dim case)""" self.array.attrs.pq = np.array(True) self.array.attrs.qr = np.array(False) self.array.attrs.rs = np.array(True) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray self.assertEqual(self.root.anarray.attrs.pq, True) self.assertEqual(self.root.anarray.attrs.qr, False) self.assertEqual(self.root.anarray.attrs.rs, True) def test00d_setBoolAttributes(self): """Checking setting Bool attributes (NumPy, multidim case)""" self.array.attrs.pq = np.array([True]) self.array.attrs.qr = np.array([[False]]) self.array.attrs.rs = np.array([[True, False], [True, False]]) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray np.testing.assert_array_equal(self.root.anarray.attrs.pq, np.array([True])) np.testing.assert_array_equal(self.root.anarray.attrs.qr, np.array([[False]])) np.testing.assert_array_equal(self.root.anarray.attrs.rs, np.array([[True, False], [True, False]])) def test01a_setIntAttributes(self): """Checking setting Int attributes (scalar, Python case)""" self.array.attrs.pq = 1 self.array.attrs.qr = 2 self.array.attrs.rs = 3 # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray self.assertIsInstance(self.root.anarray.attrs.pq, np.int_) self.assertIsInstance(self.root.anarray.attrs.qr, np.int_) self.assertIsInstance(self.root.anarray.attrs.rs, np.int_) self.assertEqual(self.root.anarray.attrs.pq, 1) self.assertEqual(self.root.anarray.attrs.qr, 2) self.assertEqual(self.root.anarray.attrs.rs, 3) def test01b_setIntAttributes(self): """Checking setting Int attributes (scalar, NumPy case)""" # 'UInt64' not supported on Win checktypes = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32'] for dtype in checktypes: setattr(self.array.attrs, dtype, np.array(1, dtype=dtype)) # Check the results if common.verbose: for dtype in checktypes: print("type, value-->", dtype, getattr(self.array.attrs, dtype)) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray for dtype in checktypes: np.testing.assert_array_equal(getattr(self.array.attrs, dtype), np.array(1, dtype=dtype)) def test01c_setIntAttributes(self): """Checking setting Int attributes (unidimensional NumPy case)""" # 'UInt64' not supported on Win checktypes = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32'] for dtype in checktypes: setattr(self.array.attrs, dtype, np.array([1, 2], dtype=dtype)) # Check the results if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray for dtype in checktypes: if common.verbose: print("type, value-->", dtype, getattr(self.array.attrs, dtype)) np.testing.assert_array_equal(getattr(self.array.attrs, dtype), np.array([1, 2], dtype=dtype)) def test01d_setIntAttributes(self): """Checking setting Int attributes (unidimensional, non-contiguous)""" # 'UInt64' not supported on Win checktypes = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32'] for dtype in checktypes: arr = np.array([1, 2, 3, 4], dtype=dtype)[::2] setattr(self.array.attrs, dtype, arr) # Check the results if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray for dtype in checktypes: arr = np.array([1, 2, 3, 4], dtype=dtype)[::2] if common.verbose: print("type, value-->", dtype, getattr(self.array.attrs, dtype)) np.testing.assert_array_equal(getattr(self.array.attrs, dtype), arr) def test01e_setIntAttributes(self): """Checking setting Int attributes (bidimensional NumPy case)""" # 'UInt64' not supported on Win checktypes = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32'] for dtype in checktypes: setattr(self.array.attrs, dtype, np.array([[1, 2], [2, 3]], dtype=dtype)) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray # Check the results for dtype in checktypes: if common.verbose: print("type, value-->", dtype, getattr(self.array.attrs, dtype)) np.testing.assert_array_equal( getattr(self.array.attrs, dtype), np.array([[1, 2], [2, 3]], dtype=dtype)) def test02a_setFloatAttributes(self): """Checking setting Float (double) attributes.""" # Set some attrs self.array.attrs.pq = 1.0 self.array.attrs.qr = 2.0 self.array.attrs.rs = 3.0 # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray self.assertIsInstance(self.root.anarray.attrs.pq, np.float_) self.assertIsInstance(self.root.anarray.attrs.qr, np.float_) self.assertIsInstance(self.root.anarray.attrs.rs, np.float_) self.assertEqual(self.root.anarray.attrs.pq, 1.0) self.assertEqual(self.root.anarray.attrs.qr, 2.0) self.assertEqual(self.root.anarray.attrs.rs, 3.0) def test02b_setFloatAttributes(self): """Checking setting Float attributes (scalar, NumPy case)""" checktypes = ['float32', 'float64'] for dtype in checktypes: setattr(self.array.attrs, dtype, np.array(1.1, dtype=dtype)) # Check the results if common.verbose: for dtype in checktypes: print("type, value-->", dtype, getattr(self.array.attrs, dtype)) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray for dtype in checktypes: # assert getattr(self.array.attrs, dtype) == 1.1 # In order to make Float32 tests pass. This is legal, not a trick. np.testing.assert_almost_equal(getattr(self.array.attrs, dtype), 1.1) def test02c_setFloatAttributes(self): """Checking setting Float attributes (unidimensional NumPy case)""" checktypes = ['float32', 'float64'] for dtype in checktypes: setattr(self.array.attrs, dtype, np.array([1.1, 2.1], dtype=dtype)) # Check the results if common.verbose: for dtype in checktypes: print("type, value-->", dtype, getattr(self.array.attrs, dtype)) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray for dtype in checktypes: np.testing.assert_array_equal(getattr(self.array.attrs, dtype), np.array([1.1, 2.1], dtype=dtype)) def test02d_setFloatAttributes(self): """Checking setting Float attributes (unidimensional, non-contiguous)""" checktypes = ['float32', 'float64'] for dtype in checktypes: arr = np.array([1.1, 2.1, 3.1, 4.1], dtype=dtype)[1::2] setattr(self.array.attrs, dtype, arr) # Check the results if common.verbose: for dtype in checktypes: print("type, value-->", dtype, getattr(self.array.attrs, dtype)) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray for dtype in checktypes: arr = np.array([1.1, 2.1, 3.1, 4.1], dtype=dtype)[1::2] np.testing.assert_array_equal(getattr(self.array.attrs, dtype), arr) def test02e_setFloatAttributes(self): """Checking setting Int attributes (bidimensional NumPy case)""" checktypes = ['float32', 'float64'] for dtype in checktypes: setattr(self.array.attrs, dtype, np.array([[1.1, 2.1], [2.1, 3.1]], dtype=dtype)) # Check the results if common.verbose: for dtype in checktypes: print("type, value-->", dtype, getattr(self.array.attrs, dtype)) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray for dtype in checktypes: np.testing.assert_array_equal( getattr(self.array.attrs, dtype), np.array([[1.1, 2.1], [2.1, 3.1]], dtype=dtype)) def test03_setObjectAttributes(self): """Checking setting Object attributes.""" # Set some attrs self.array.attrs.pq = [1.0, 2] self.array.attrs.qr = (1, 2) self.array.attrs.rs = {"ddf": 32.1, "dsd": 1} # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray self.assertEqual(self.root.anarray.attrs.pq, [1.0, 2]) self.assertEqual(self.root.anarray.attrs.qr, (1, 2)) self.assertEqual(self.root.anarray.attrs.rs, {"ddf": 32.1, "dsd": 1}) def test04a_setStringAttributes(self): """Checking setting string attributes (scalar case)""" self.array.attrs.pq = 'foo' self.array.attrs.qr = 'bar' self.array.attrs.rs = 'baz' # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray self.assertIsInstance(self.root.anarray.attrs.pq, np.str_) self.assertIsInstance(self.root.anarray.attrs.qr, np.str_) self.assertIsInstance(self.root.anarray.attrs.rs, np.str_) self.assertEqual(self.root.anarray.attrs.pq, 'foo') self.assertEqual(self.root.anarray.attrs.qr, 'bar') self.assertEqual(self.root.anarray.attrs.rs, 'baz') def test04b_setStringAttributes(self): """Checking setting string attributes (unidimensional 1-elem case)""" self.array.attrs.pq = np.array(['foo']) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray np.testing.assert_array_equal(self.root.anarray.attrs.pq, np.array(['foo'])) def test04c_setStringAttributes(self): """Checking setting string attributes (empty unidimensional 1-elem case)""" self.array.attrs.pq = np.array(['']) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray if common.verbose: print("pq -->", self.array.attrs.pq) np.testing.assert_array_equal(self.root.anarray.attrs.pq, np.array([''])) def test04d_setStringAttributes(self): """Checking setting string attributes (unidimensional 2-elem case)""" self.array.attrs.pq = np.array(['foo', 'bar3']) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray np.testing.assert_array_equal(self.root.anarray.attrs.pq, np.array(['foo', 'bar3'])) def test04e_setStringAttributes(self): """Checking setting string attributes (empty unidimensional 2-elem case)""" self.array.attrs.pq = np.array(['', '']) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray np.testing.assert_array_equal(self.root.anarray.attrs.pq, np.array(['', ''])) def test04f_setStringAttributes(self): """Checking setting string attributes (bidimensional 4-elem case)""" self.array.attrs.pq = np.array([['foo', 'foo2'], ['foo3', 'foo4']]) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray np.testing.assert_array_equal(self.root.anarray.attrs.pq, np.array([['foo', 'foo2'], ['foo3', 'foo4']])) def test05a_setComplexAttributes(self): """Checking setting Complex (python) attributes.""" # Set some attrs self.array.attrs.pq = 1.0 + 2j self.array.attrs.qr = 2.0 + 3j self.array.attrs.rs = 3.0 + 4j # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray self.assertIsInstance(self.root.anarray.attrs.pq, np.complex_) self.assertIsInstance(self.root.anarray.attrs.qr, np.complex_) self.assertIsInstance(self.root.anarray.attrs.rs, np.complex_) self.assertEqual(self.root.anarray.attrs.pq, 1.0 + 2j) self.assertEqual(self.root.anarray.attrs.qr, 2.0 + 3j) self.assertEqual(self.root.anarray.attrs.rs, 3.0 + 4j) def test05b_setComplexAttributes(self): """Checking setting Complex attributes (scalar, NumPy case)""" checktypes = ['complex64', 'complex128'] for dtype in checktypes: setattr(self.array.attrs, dtype, np.array(1.1 + 2j, dtype=dtype)) # Check the results if common.verbose: for dtype in checktypes: print("type, value-->", dtype, getattr(self.array.attrs, dtype)) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray for dtype in checktypes: # assert getattr(self.array.attrs, dtype) == 1.1 + 2j # In order to make Complex32 tests pass. np.testing.assert_almost_equal(getattr(self.array.attrs, dtype), 1.1 + 2j) def test05c_setComplexAttributes(self): """Checking setting Complex attributes (unidimensional NumPy case)""" checktypes = ['complex64', 'complex128'] for dtype in checktypes: setattr(self.array.attrs, dtype, np.array([1.1, 2.1], dtype=dtype)) # Check the results if common.verbose: for dtype in checktypes: print("type, value-->", dtype, getattr(self.array.attrs, dtype)) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray for dtype in checktypes: np.testing.assert_array_equal(getattr(self.array.attrs, dtype), np.array([1.1, 2.1], dtype=dtype)) def test05d_setComplexAttributes(self): """Checking setting Int attributes (bidimensional NumPy case)""" checktypes = ['complex64', 'complex128'] for dtype in checktypes: setattr(self.array.attrs, dtype, np.array([[1.1, 2.1], [2.1, 3.1]], dtype=dtype)) # Check the results if common.verbose: for dtype in checktypes: print("type, value-->", dtype, getattr(self.array.attrs, dtype)) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray for dtype in checktypes: np.testing.assert_array_equal( getattr(self.array.attrs, dtype), np.array([[1.1, 2.1], [2.1, 3.1]], dtype=dtype)) def test06a_setUnicodeAttributes(self): """Checking setting unicode attributes (scalar case)""" self.array.attrs.pq = 'para\u0140lel' self.array.attrs.qr = '' # check #213 or gh-64 self.array.attrs.rs = 'baz' # Check the results if common.verbose: if sys.platform != 'win32': # It seems that Windows cannot print this print("pq -->", repr(self.array.attrs.pq)) # XXX: try to use repr instead # print("pq -->", repr(self.array.attrs.pq)) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray self.assertIsInstance(self.array.attrs.pq, np.unicode_) self.assertIsInstance(self.array.attrs.qr, np.unicode_) self.assertIsInstance(self.array.attrs.rs, np.unicode_) self.assertEqual(self.array.attrs.pq, 'para\u0140lel') self.assertEqual(self.array.attrs.qr, '') self.assertEqual(self.array.attrs.rs, 'baz') def test06b_setUnicodeAttributes(self): """Checking setting unicode attributes (unidimensional 1-elem case)""" self.array.attrs.pq = np.array(['para\u0140lel']) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray np.testing.assert_array_equal(self.array.attrs.pq, np.array(['para\u0140lel'])) def test06c_setUnicodeAttributes(self): """Checking setting unicode attributes (empty unidimensional 1-elem case)""" # The next raises a `TypeError` when unpickled. See: # http://projects.scipy.org/numpy/ticket/1037 # self.array.attrs.pq = numpy.array(['']) self.array.attrs.pq = np.array([''], dtype="U1") # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray if common.verbose: print("pq -->", repr(self.array.attrs.pq)) np.testing.assert_array_equal(self.array.attrs.pq, np.array([''], dtype="U1")) def test06d_setUnicodeAttributes(self): """Checking setting unicode attributes (unidimensional 2-elem case)""" self.array.attrs.pq = np.array(['para\u0140lel', 'bar3']) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray np.testing.assert_array_equal(self.array.attrs.pq, np.array(['para\u0140lel', 'bar3'])) def test06e_setUnicodeAttributes(self): """Checking setting unicode attributes (empty unidimensional 2-elem case)""" self.array.attrs.pq = np.array(['', ''], dtype="U1") # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray np.testing.assert_array_equal(self.array.attrs.pq, np.array(['', ''], dtype="U1")) def test06f_setUnicodeAttributes(self): """Checking setting unicode attributes (bidimensional 4-elem case)""" self.array.attrs.pq = np.array([['para\u0140lel', 'foo2'], ['foo3', 'para\u0140lel4']]) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray np.testing.assert_array_equal(self.array.attrs.pq, np.array([['para\u0140lel', 'foo2'], ['foo3', 'para\u0140lel4']])) def test07a_setRecArrayAttributes(self): """Checking setting RecArray (NumPy) attributes.""" dt = np.dtype('i4,f8', align=self.aligned) # Set some attrs self.array.attrs.pq = np.zeros(2, dt) self.array.attrs.qr = np.ones((2, 2), dt) self.array.attrs.rs = np.array([(1, 2.)], dt) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray self.assertIsInstance(self.array.attrs.pq, np.ndarray) self.assertIsInstance(self.array.attrs.qr, np.ndarray) self.assertIsInstance(self.array.attrs.rs, np.ndarray) np.testing.assert_array_equal(self.array.attrs.pq, np.zeros(2, dt)) np.testing.assert_array_equal(self.array.attrs.qr, np.ones((2, 2), dt)) np.testing.assert_array_equal(self.array.attrs.rs, np.array([(1, 2.)], dt)) def test07b_setRecArrayAttributes(self): """Checking setting nested RecArray (NumPy) attributes.""" # Build a nested dtype dt = np.dtype([('f1', [('f1', 'i2'), ('f2', 'f8')])]) # Set some attrs self.array.attrs.pq = np.zeros(2, dt) self.array.attrs.qr = np.ones((2, 2), dt) self.array.attrs.rs = np.array([((1, 2.),)], dt) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray self.assertIsInstance(self.array.attrs.pq, np.ndarray) self.assertIsInstance(self.array.attrs.qr, np.ndarray) self.assertIsInstance(self.array.attrs.rs, np.ndarray) np.testing.assert_array_equal(self.array.attrs.pq, np.zeros(2, dt)) np.testing.assert_array_equal(self.array.attrs.qr, np.ones((2, 2), dt)) np.testing.assert_array_equal(self.array.attrs.rs, np.array([((1, 2),)], dt)) def test07c_setRecArrayAttributes(self): """Checking setting multidim nested RecArray (NumPy) attributes.""" # Build a nested dtype dt = np.dtype([('f1', [('f1', 'i2', (2,)), ('f2', 'f8')])], align=True) # Set some attrs self.array.attrs.pq = np.zeros(2, dt) self.array.attrs.qr = np.ones((2, 2), dt) self.array.attrs.rs = np.array([(([1, 3], 2.),)], dt) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root self.array = self.h5file.root.anarray self.assertIsInstance(self.array.attrs.pq, np.ndarray) self.assertIsInstance(self.array.attrs.qr, np.ndarray) self.assertIsInstance(self.array.attrs.rs, np.ndarray) np.testing.assert_array_equal(self.array.attrs.pq, np.zeros(2, dt)) np.testing.assert_array_equal(self.array.attrs.qr, np.ones((2, 2), dt)) np.testing.assert_array_equal(self.array.attrs.rs, np.array([(([1, 3], 2),)], dt)) def test08_setRecArrayNotAllowPadding(self): """Checking setting aligned RecArray (NumPy) attributes with `allow_aligned` param set to False when reopen.""" dt = np.dtype('i4,f8', align=self.aligned) # Set some attrs self.array.attrs.pq = np.zeros(2, dt) self.array.attrs.qr = np.ones((2, 2), dt) self.array.attrs.rs = np.array([(1, 2.)], dt) # Check the results if common.verbose: print("pq -->", self.array.attrs.pq) print("qr -->", self.array.attrs.qr) print("rs -->", self.array.attrs.rs) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+', allow_align=False) self.root = self.h5file.root self.array = self.h5file.root.anarray self.assertIsInstance(self.array.attrs.pq, np.ndarray) self.assertIsInstance(self.array.attrs.qr, np.ndarray) self.assertIsInstance(self.array.attrs.rs, np.ndarray) np.testing.assert_array_equal(self.array.attrs.pq, np.zeros(2, dt)) np.testing.assert_array_equal(self.array.attrs.qr, np.ones((2, 2), dt)) np.testing.assert_array_equal(self.array.attrs.rs, np.array([(1, 2.)], dt)) class NotCloseTypesTestCase(TypesTestCase): allow_padding = False aligned = False close = False class NoCloseAlignedTypesTestCase(TypesTestCase): allow_padding = True aligned = True close = False class CloseNotAlignedPaddedTypesTestCase(TypesTestCase): allow_padding = False aligned = False close = True class CloseTypesTestCase(TypesTestCase): allow_padding = True aligned = False close = True class CloseAlignedTypesTestCase(TypesTestCase): allow_padding = False aligned = True close = True class CloseAlignedPaddedTypesTestCase(TypesTestCase): allow_padding = True aligned = True close = True class NoSysAttrsTestCase(common.TempFileMixin, common.PyTablesTestCase): open_kwargs = dict(pytables_sys_attrs=False) def setUp(self): super().setUp() self.root = self.h5file.root # Create a table object self.table = self.h5file.create_table(self.root, 'atable', Record, "Table title") # Create an array object self.array = self.h5file.create_array(self.root, 'anarray', [1], "Array title") # Create a group object self.group = self.h5file.create_group(self.root, 'agroup', "Group title") def test00_listAttributes(self): """Checking listing attributes (no system attrs version).""" # With a Group object self.group._v_attrs.pq = "1" self.group._v_attrs.qr = "2" self.group._v_attrs.rs = "3" if common.verbose: print("Attribute list:", self.group._v_attrs._f_list()) # Now, try with a Table object self.table.attrs.a = "1" self.table.attrs.c = "2" self.table.attrs.b = "3" if common.verbose: print("Attribute list:", self.table.attrs._f_list()) # Finally, try with an Array object self.array.attrs.k = "1" self.array.attrs.j = "2" self.array.attrs.i = "3" if common.verbose: print("Attribute list:", self.array.attrs._f_list()) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='r+') self.root = self.h5file.root agroup = self.root.agroup self.assertEqual(agroup._v_attrs._f_list("user"), ["pq", "qr", "rs"]) self.assertEqual(agroup._v_attrs._f_list("sys"), []) self.assertEqual(agroup._v_attrs._f_list("all"), ["pq", "qr", "rs"]) atable = self.root.atable self.assertEqual(atable.attrs._f_list(), ["a", "b", "c"]) self.assertEqual(atable.attrs._f_list("sys"), []) self.assertEqual(atable.attrs._f_list("all"), ["a", "b", "c"]) anarray = self.root.anarray self.assertEqual(anarray.attrs._f_list(), ["i", "j", "k"]) self.assertEqual(anarray.attrs._f_list("sys"), []) self.assertEqual(anarray.attrs._f_list("all"), ["i", "j", "k"]) class NoSysAttrsNotClose(NoSysAttrsTestCase): close = False class NoSysAttrsClose(NoSysAttrsTestCase): close = True class CompatibilityTestCase(common.TestFileMixin, common.PyTablesTestCase): h5fname = common.test_filename('issue_368.h5') @common.unittest.skipIf(Version(np.__version__) < Version('1.9.0'), 'requires numpy >= 1.9') def test_pickled_unicode_attrs(self): # See also gh-368 and https://github.com/numpy/numpy/issues/4879. # # This is a compatibility test. In PyTables < 3.0 unicode # attributes were stored as pickld unicode stings. # In PyTables >= 3.0 unicode strings are stored as encoded utf-8 # strings (the utf-8 marker is set at HDF5 level). # # In any case PyTables (>= 3.0) should be able to handle correctly # also data files genetated with older versions of PyTables. # Unfortunately a bug in numpy < 1.9 # (https://github.com/numpy/numpy/issues/4879) makes it impossible # unpickle numpy arrays with dtype "U" resulting in an incorrect # behaviour of PyTables. self.assertEqual( self.h5file.get_node_attr('/', 'py2_pickled_unicode'), 'abc') class PicklePy2UnpicklePy3TestCase(common.TestFileMixin, common.PyTablesTestCase): h5fname = common.test_filename('issue_560.h5') def test_pickled_datetime_object(self): # See also gh-560 # # Objects (classes) that are pickled using python 2 may contain # non-ascii characters in the pickled string. This will cause # a UnicodeDecodeError when unpickling on python 3. # Python 3.4 adds encoding='bytes' to fix this # http://bugs.python.org/issue6784 # Objects pickled in the testfile have non-ascii chars in the # picklestring and will throw UnicodeDecodeError when unpickled # on python 3. # datetime will be unpickled with encoding='bytes' self.assertIsInstance( self.h5file.get_node_attr('/', 'py2_pickled_datetime'), datetime.datetime) # dict will be unpickled with encoding='latin1' d = self.h5file.get_node_attr('/', 'py2_pickled_dict') self.assertIsInstance(d, dict) self.assertEqual(d['s'], 'just a string') class SegFaultPythonTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_segfault(self): """Checking workaround for Python unpickle problem (see #253).""" self.h5file.root._v_attrs.trouble1 = "0" self.assertEqual(self.h5file.root._v_attrs.trouble1, "0") self.h5file.root._v_attrs.trouble2 = "0." self.assertEqual(self.h5file.root._v_attrs.trouble2, "0.") # Problem happens after reopening self._reopen() self.assertEqual(self.h5file.root._v_attrs.trouble1, "0") self.assertEqual(self.h5file.root._v_attrs.trouble2, "0.") if common.verbose: print("Great! '0' and '0.' values can be safely retrieved.") class EmbeddedNullsTestCase(common.TempFileMixin, common.PyTablesTestCase): # See laso gh-371 (https://github.com/PyTables/PyTables/issues/371) def test_unicode(self): value = "string with a null byte \x00 in it" self.h5file.root._v_attrs.name = value self.assertEqual(self.h5file.root._v_attrs.name, value) self._reopen() self.assertEqual(self.h5file.root._v_attrs.name, value) def test_bytes(self): value = b"string with a null byte \x00 in it" self.h5file.root._v_attrs.name = value self.assertEqual(self.h5file.root._v_attrs.name, value) self._reopen() self.assertEqual(self.h5file.root._v_attrs.name, value) class VlenStrAttrTestCase(common.PyTablesTestCase): def setUp(self): super().setUp() self.h5fname = common.test_filename('vlstr_attr.h5') self.h5file = tb.open_file(self.h5fname) def tearDown(self): self.h5file.close() super().tearDown() def test01_vlen_str_scalar(self): """Checking file with variable length string attributes.""" attr = "vlen_str_scalar" self.assertEqual( self.h5file.get_node_attr("/", attr), attr.encode('ascii')) def test02_vlen_str_array(self): """Checking file with variable length string attributes (1d).""" attr = "vlen_str_array" v = self.h5file.get_node_attr('/', attr) self.assertEqual(v.ndim, 1) for idx, item in enumerate(v): value = "%s_%d" % (attr, idx) self.assertEqual(item, value.encode('ascii')) def test03_vlen_str_matrix(self): """Checking file with variable length string attributes (2d).""" attr = "vlen_str_matrix" m = self.h5file.get_node_attr('/', attr) self.assertEqual(m.ndim, 2) for row, rowdata in enumerate(m): for col, item in enumerate(rowdata): value = "%s_%d%d" % (attr, row, col) self.assertEqual(item, value.encode('ascii')) class UnsupportedAttrTypeTestCase(common.TestFileMixin, common.PyTablesTestCase): h5fname = common.test_filename('attr-u16.h5') def test00_unsupportedType(self): """Checking file with unsupported type.""" self.assertWarns(tb.exceptions.DataTypeWarning, repr, self.h5file) # Test for specific system attributes class SpecificAttrsTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_earray(self): """Testing EArray specific attrs (create).""" ea = self.h5file.create_earray('/', 'ea', tb.Int32Atom(), (2, 0, 4)) if common.verbose: print("EXTDIM-->", ea.attrs.EXTDIM) self.assertEqual(ea.attrs.EXTDIM, 1) def test01_earray(self): """Testing EArray specific attrs (open).""" ea = self.h5file.create_earray('/', 'ea', tb.Int32Atom(), (0, 1, 4)) self._reopen('r') ea = self.h5file.root.ea if common.verbose: print("EXTDIM-->", ea.attrs.EXTDIM) self.assertEqual(ea.attrs.EXTDIM, 0) def suite(): theSuite = common.unittest.TestSuite() niter = 1 for i in range(niter): theSuite.addTest(common.unittest.makeSuite(NotCloseCreate)) theSuite.addTest(common.unittest.makeSuite(CloseCreate)) theSuite.addTest(common.unittest.makeSuite(NoCacheNotCloseCreate)) theSuite.addTest(common.unittest.makeSuite(NoCacheCloseCreate)) theSuite.addTest(common.unittest.makeSuite(DictCacheNotCloseCreate)) theSuite.addTest(common.unittest.makeSuite(DictCacheCloseCreate)) theSuite.addTest(common.unittest.makeSuite(NotCloseTypesTestCase)) theSuite.addTest(common.unittest.makeSuite(CloseTypesTestCase)) theSuite.addTest(common.unittest.makeSuite( CloseNotAlignedPaddedTypesTestCase)) theSuite.addTest(common.unittest.makeSuite( NoCloseAlignedTypesTestCase)) theSuite.addTest(common.unittest.makeSuite(CloseAlignedTypesTestCase)) theSuite.addTest(common.unittest.makeSuite( CloseAlignedPaddedTypesTestCase)) theSuite.addTest(common.unittest.makeSuite(NoSysAttrsNotClose)) theSuite.addTest(common.unittest.makeSuite(NoSysAttrsClose)) theSuite.addTest(common.unittest.makeSuite(CompatibilityTestCase)) theSuite.addTest(common.unittest.makeSuite( PicklePy2UnpicklePy3TestCase)) theSuite.addTest(common.unittest.makeSuite(SegFaultPythonTestCase)) theSuite.addTest(common.unittest.makeSuite(EmbeddedNullsTestCase)) theSuite.addTest(common.unittest.makeSuite(VlenStrAttrTestCase)) theSuite.addTest(common.unittest.makeSuite( UnsupportedAttrTypeTestCase)) theSuite.addTest(common.unittest.makeSuite(SpecificAttrsTestCase)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_aux.py000066400000000000000000000015261416254111300200700ustar00rootroot00000000000000import unittest import numpy as np import tables as tb class TestAuxiliaryFunctions(unittest.TestCase): def test_keysort(self): N = 1000 rnd = np.random.randint(N, size=N) for dtype1 in ('S6', 'b1', 'i1', 'i8', 'u4', 'u8', 'f4', 'f8'): for dtype2 in ('u4', 'i8'): a = np.array(rnd, dtype1) b = np.array(rnd, dtype2) c = a.copy() d = c.argsort() e = c[d] f = b[d] tb.indexesextension.keysort(a, b) self.assertTrue((a == e).all()) self.assertTrue((b == f).all()) def suite(): theSuite = unittest.TestSuite() theSuite.addTest(unittest.makeSuite(TestAuxiliaryFunctions)) return theSuite if __name__ == '__main__': unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_backcompat.py000066400000000000000000000203551416254111300214000ustar00rootroot00000000000000import shutil import tempfile import warnings from pathlib import Path import numpy as np import tables as tb from tables.tests import common # Check read Tables from pytables version 0.8 class BackCompatTablesTestCase(common.PyTablesTestCase): def test01_readTable(self): """Checking backward compatibility of old formats of tables.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_readTable..." % self.__class__.__name__) # Create an instance of an HDF5 Table with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning) h5file = tb.open_file(common.test_filename(self.h5fname), "r") try: table = h5file.get_node("/tuple0") # Read the 100 records result = [rec['var2'] for rec in table] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last record in table ==>", rec) print("Total selected records in table ==> ", len(result)) self.assertEqual(len(result), 100) finally: h5file.close() @common.unittest.skipIf(not common.lzo_avail, 'lzo not available') class Table2_1LZO(BackCompatTablesTestCase): # pytables 0.8.x versions and after h5fname = "Table2_1_lzo_nrv2e_shuffle.h5" @common.unittest.skipIf(not common.lzo_avail, 'lzo not available') class Tables_LZO1(BackCompatTablesTestCase): h5fname = "Tables_lzo1.h5" # files compressed with LZO1 @common.unittest.skipIf(not common.lzo_avail, 'lzo not available') class Tables_LZO1_shuffle(BackCompatTablesTestCase): # files compressed with LZO1 and shuffle h5fname = "Tables_lzo1_shuffle.h5" @common.unittest.skipIf(not common.lzo_avail, 'lzo not available') class Tables_LZO2(BackCompatTablesTestCase): h5fname = "Tables_lzo2.h5" # files compressed with LZO2 @common.unittest.skipIf(not common.lzo_avail, 'lzo not available') class Tables_LZO2_shuffle(BackCompatTablesTestCase): # files compressed with LZO2 and shuffle h5fname = "Tables_lzo2_shuffle.h5" # Check read attributes from PyTables >= 1.0 properly class BackCompatAttrsTestCase(common.TestFileMixin, common.PyTablesTestCase): FILENAME = "zerodim-attrs-%s.h5" def setUp(self): self.h5fname = common.test_filename(self.FILENAME % self.format) super().setUp() def test01_readAttr(self): """Checking backward compatibility of old formats for attributes.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_readAttr..." % self.__class__.__name__) # Read old formats a = self.h5file.get_node("/a") scalar = np.array(1, dtype="int32") vector = np.array([1], dtype="int32") if self.format == "1.3": self.assertTrue(common.allequal(a.attrs.arrdim1, vector)) self.assertTrue(common.allequal(a.attrs.arrscalar, scalar)) self.assertEqual(a.attrs.pythonscalar, 1) elif self.format == "1.4": self.assertTrue(common.allequal(a.attrs.arrdim1, vector)) self.assertTrue(common.allequal(a.attrs.arrscalar, scalar)) self.assertTrue(common.allequal(a.attrs.pythonscalar, scalar)) class Attrs_1_3(BackCompatAttrsTestCase): format = "1.3" # pytables 1.0.x versions and earlier class Attrs_1_4(BackCompatAttrsTestCase): format = "1.4" # pytables 1.1.x versions and later class VLArrayTestCase(common.TestFileMixin, common.PyTablesTestCase): h5fname = common.test_filename("flavored_vlarrays-format1.6.h5") def test01_backCompat(self): """Checking backward compatibility with old flavors of VLArray.""" # Check that we can read the contents without problems (nor warnings!) vlarray1 = self.h5file.root.vlarray1 self.assertEqual(vlarray1.flavor, "numeric") vlarray2 = self.h5file.root.vlarray2 self.assertEqual(vlarray2.flavor, "python") self.assertEqual(vlarray2[1], [b'5', b'6', b'77']) # Make sure that 1.x files with TimeXX types continue to be readable # and that its byteorder is correctly retrieved. class TimeTestCase(common.TestFileMixin, common.PyTablesTestCase): # Open a PYTABLES_FORMAT_VERSION=1.x file h5fname = common.test_filename("time-table-vlarray-1_x.h5") def test00_table(self): """Checking backward compatibility with old TimeXX types (tables).""" # Check that we can read the contents without problems (nor warnings!) table = self.h5file.root.table self.assertEqual(table.byteorder, "little") def test01_vlarray(self): """Checking backward compatibility with old TimeXX types (vlarrays).""" # Check that we can read the contents without problems (nor warnings!) vlarray4 = self.h5file.root.vlarray4 self.assertEqual(vlarray4.byteorder, "little") vlarray8 = self.h5file.root.vlarray4 self.assertEqual(vlarray8.byteorder, "little") class OldFlavorsTestCase01(common.PyTablesTestCase): close = False # numeric def test01_open(self): """Checking opening of (X)Array (old 'numeric' flavor)""" # Open the HDF5 with old numeric flavor h5fname = common.test_filename("oldflavor_numeric.h5") with tb.open_file(h5fname) as h5file: # Assert other properties in array self.assertEqual(h5file.root.array1.flavor, 'numeric') self.assertEqual(h5file.root.array2.flavor, 'python') self.assertEqual(h5file.root.carray1.flavor, 'numeric') self.assertEqual(h5file.root.carray2.flavor, 'python') self.assertEqual(h5file.root.vlarray1.flavor, 'numeric') self.assertEqual(h5file.root.vlarray2.flavor, 'python') def test02_copy(self): """Checking (X)Array.copy() method ('numetic' flavor)""" srcfile = common.test_filename("oldflavor_numeric.h5") tmpfile = tempfile.mktemp(".h5") shutil.copy(srcfile, tmpfile) try: # Open the HDF5 with old numeric flavor with tb.open_file(tmpfile, "r+") as h5file: # Copy to another location self.assertWarns(tb.exceptions.FlavorWarning, h5file.root.array1.copy, '/', 'array1copy') h5file.root.array2.copy('/', 'array2copy') h5file.root.carray1.copy('/', 'carray1copy') h5file.root.carray2.copy('/', 'carray2copy') h5file.root.vlarray1.copy('/', 'vlarray1copy') h5file.root.vlarray2.copy('/', 'vlarray2copy') if self.close: h5file.close() h5file = tb.open_file(tmpfile) else: h5file.flush() # Assert other properties in array self.assertEqual(h5file.root.array1copy.flavor, 'numeric') self.assertEqual(h5file.root.array2copy.flavor, 'python') self.assertEqual(h5file.root.carray1copy.flavor, 'numeric') self.assertEqual(h5file.root.carray2copy.flavor, 'python') self.assertEqual(h5file.root.vlarray1copy.flavor, 'numeric') self.assertEqual(h5file.root.vlarray2copy.flavor, 'python') finally: Path(tmpfile).unlink() class OldFlavorsTestCase02(common.PyTablesTestCase): close = True def suite(): theSuite = common.unittest.TestSuite() niter = 1 for n in range(niter): theSuite.addTest(common.unittest.makeSuite(VLArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(TimeTestCase)) theSuite.addTest(common.unittest.makeSuite(OldFlavorsTestCase01)) theSuite.addTest(common.unittest.makeSuite(OldFlavorsTestCase02)) theSuite.addTest(common.unittest.makeSuite(Table2_1LZO)) theSuite.addTest(common.unittest.makeSuite(Tables_LZO1)) theSuite.addTest(common.unittest.makeSuite(Tables_LZO1_shuffle)) theSuite.addTest(common.unittest.makeSuite(Tables_LZO2)) theSuite.addTest(common.unittest.makeSuite(Tables_LZO2_shuffle)) return theSuite if __name__ == '__main__': import sys common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_basics.py000066400000000000000000002654411416254111300205470ustar00rootroot00000000000000import os import sys import shutil import platform import tempfile import warnings import threading import subprocess import queue from pathlib import Path import tables try: import multiprocessing as mp multiprocessing_imported = True except ImportError: multiprocessing_imported = False import numpy as np import tables as tb from tables.tests import common class OpenFileFailureTestCase(common.PyTablesTestCase): def setUp(self): super().setUp() self.N = len(tb.file._open_files) self.open_files = tb.file._open_files def test01_open_file(self): """Checking opening of a non existing file.""" h5fname = tempfile.mktemp(".h5") with self.assertRaises(IOError): h5file = tb.open_file(h5fname) h5file.close() self.assertEqual(self.N, len(self.open_files)) def test02_open_file(self): """Checking opening of an existing non HDF5 file.""" # create a dummy file h5fname = tempfile.mktemp(".h5") Path(h5fname).write_text('') # Try to open the dummy file try: with self.assertRaises(tb.HDF5ExtError): h5file = tb.open_file(h5fname) h5file.close() self.assertEqual(self.N, len(self.open_files)) finally: Path(h5fname).unlink() def test03_open_file(self): """Checking opening of an existing file with invalid mode.""" # See gh-318 # create a dummy file h5fname = tempfile.mktemp(".h5") h5file = tb.open_file(h5fname, "w") h5file.close() try: # Try to open the dummy file self.assertRaises(ValueError, tb.open_file, h5fname, "ab") finally: Path(h5fname).unlink() class OpenFileTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.populateFile() def populateFile(self): root = self.h5file.root # Create an array self.h5file.create_array(root, 'array', [1, 2], title="Array example") self.h5file.create_table(root, 'table', {'var1': tb.IntCol()}, "Table example") root._v_attrs.testattr = 41 # Create another array object self.h5file.create_array(root, 'anarray', [1], "Array title") self.h5file.create_table(root, 'atable', {'var1': tb.IntCol()}, "Table title") # Create a group object group = self.h5file.create_group(root, 'agroup', "Group title") group._v_attrs.testattr = 42 # Create a some objects there array1 = self.h5file.create_array(group, 'anarray1', [1, 2, 3, 4, 5, 6, 7], "Array title 1") array1.attrs.testattr = 42 self.h5file.create_array(group, 'anarray2', [2], "Array title 2") self.h5file.create_table(group, 'atable1', { 'var1': tb.IntCol()}, "Table title 1") ra = np.rec.array([(1, 11, 'a')], formats='u1,f4,a1') self.h5file.create_table(group, 'atable2', ra, "Table title 2") # Create a lonely group in first level self.h5file.create_group(root, 'agroup2', "Group title 2") # Create a new group in the second level group3 = self.h5file.create_group(group, 'agroup3', "Group title 3") # Create a new group in the third level self.h5file.create_group(group3, 'agroup4', "Group title 4") # Create an array in the root with the same name as one in 'agroup' self.h5file.create_array(root, 'anarray1', [1, 2], title="Array example") def test00_newFile(self): """Checking creation of a new file.""" self.h5file.create_array(self.h5file.root, 'array_new', [1, 2], title="Array example") # Get the CLASS attribute of the arr object class_ = self.h5file.root.array.attrs.CLASS self.assertEqual(class_.capitalize(), "Array") def test00_newFile_unicode_filename(self): temp_dir = tempfile.mkdtemp() try: h5fname = str(Path(temp_dir) / 'test.h5') with tb.open_file(h5fname, 'w') as h5file: self.assertTrue(h5file, tb.File) finally: shutil.rmtree(temp_dir) def test00_newFile_numpy_str_filename(self): temp_dir = tempfile.mkdtemp() try: h5fname = np.str_(Path(temp_dir) / 'test.h5') with tb.open_file(h5fname, 'w') as h5file: self.assertTrue(h5file, tb.File) finally: shutil.rmtree(temp_dir) def test00_newFile_numpy_unicode_filename(self): temp_dir = tempfile.mkdtemp() try: h5fname = np.unicode_(Path(temp_dir) / 'test.h5') with tb.open_file(h5fname, 'w') as h5file: self.assertTrue(h5file, tb.File) finally: shutil.rmtree(temp_dir) def test01_openFile(self): """Checking opening of an existing file.""" # Open the old HDF5 file self._reopen(node_cache_slots=self.node_cache_slots) # Get the CLASS attribute of the arr object title = self.h5file.root.array.get_attr("TITLE") self.assertEqual(title, "Array example") def test01_open_file_pathlib(self): """Checking opening of an existing file.""" self.h5file.close() h5fname = Path(self.h5fname) with tables.open_file(h5fname) as h5file: title = h5file.root.array.get_attr("TITLE") self.assertEqual(title, "Array example") def test02_appendFile(self): """Checking appending objects to an existing file.""" # Append a new array to the existing file self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) self.h5file.create_array(self.h5file.root, 'array2', [3, 4], title="Title example 2") # Open this file in read-only mode self._reopen(node_cache_slots=self.node_cache_slots) # Get the CLASS attribute of the arr object title = self.h5file.root.array2.get_attr("TITLE") self.assertEqual(title, "Title example 2") def test02b_appendFile2(self): """Checking appending objects to an existing file ("a" version)""" # Append a new array to the existing file self._reopen(mode="a", node_cache_slots=self.node_cache_slots) self.h5file.create_array(self.h5file.root, 'array2', [3, 4], title="Title example 2") # Open this file in read-only mode self._reopen(node_cache_slots=self.node_cache_slots) # Get the CLASS attribute of the arr object title = self.h5file.root.array2.get_attr("TITLE") self.assertEqual(title, "Title example 2") # Begin to raise errors... def test03_appendErrorFile(self): """Checking appending objects to an existing file in "w" mode.""" # Append a new array to the existing file but in write mode # so, the existing file should be deleted! self._reopen(mode="w", node_cache_slots=self.node_cache_slots) self.h5file.create_array(self.h5file.root, 'array2', [3, 4], title="Title example 2") # Open this file in read-only mode self._reopen(node_cache_slots=self.node_cache_slots) with self.assertRaises(LookupError): # Try to get the 'array' object in the old existing file self.h5file.root.array def test04a_openErrorFile(self): """Checking opening a non-existing file for reading""" with self.assertRaises(IOError): tb.open_file("nonexistent.h5", mode="r", node_cache_slots=self.node_cache_slots) def test04b_alternateRootFile(self): """Checking alternate root access to the object tree.""" # Open the existent HDF5 file self._reopen(root_uep="/agroup", node_cache_slots=self.node_cache_slots) # Get the CLASS attribute of the arr object if common.verbose: print("\nFile tree dump:", self.h5file) title = self.h5file.root.anarray1.get_attr("TITLE") # Get the node again, as this can trigger errors in some situations anarray1 = self.h5file.root.anarray1 self.assertIsNotNone(anarray1) self.assertEqual(title, "Array title 1") # This test works well, but HDF5 emits a series of messages that # may loose the user. It is better to deactivate it. def notest04c_alternateRootFile(self): """Checking non-existent alternate root access to the object tree""" with self.assertRaises(RuntimeError): self._reopen(root_uep="/nonexistent", node_cache_slots=self.node_cache_slots) def test05a_removeGroupRecursively(self): """Checking removing a group recursively.""" # Delete a group with leafs self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) with self.assertRaises(tb.NodeError): self.h5file.remove_node(self.h5file.root.agroup) # This should work now self.h5file.remove_node(self.h5file.root, 'agroup', recursive=1) # Open this file in read-only mode self._reopen(node_cache_slots=self.node_cache_slots) # Try to get the removed object with self.assertRaises(LookupError): self.h5file.root.agroup # Try to get a child of the removed object with self.assertRaises(LookupError): self.h5file.get_node("/agroup/agroup3") def test05b_removeGroupRecursively(self): """Checking removing a group recursively and access to it immediately.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05b_removeGroupRecursively..." % self.__class__.__name__) # Delete a group with leafs self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) with self.assertRaises(tb.NodeError): self.h5file.remove_node(self.h5file.root, 'agroup') # This should work now self.h5file.remove_node(self.h5file.root, 'agroup', recursive=1) # Try to get the removed object with self.assertRaises(LookupError): self.h5file.root.agroup # Try to get a child of the removed object with self.assertRaises(LookupError): self.h5file.get_node("/agroup/agroup3") def test06_removeNodeWithDel(self): """Checking removing a node using ``__delattr__()``""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) with self.assertRaises(AttributeError): # This should fail because there is no *Python attribute* # called ``agroup``. del self.h5file.root.agroup def test06a_removeGroup(self): """Checking removing a lonely group from an existing file.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) self.h5file.remove_node(self.h5file.root, 'agroup2') # Open this file in read-only mode self._reopen(node_cache_slots=self.node_cache_slots) # Try to get the removed object with self.assertRaises(LookupError): self.h5file.root.agroup2 def test06b_removeLeaf(self): """Checking removing Leaves from an existing file.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) self.h5file.remove_node(self.h5file.root, 'anarray') # Open this file in read-only mode self._reopen(node_cache_slots=self.node_cache_slots) # Try to get the removed object with self.assertRaises(LookupError): self.h5file.root.anarray def test06c_removeLeaf(self): """Checking removing Leaves and access it immediately.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) self.h5file.remove_node(self.h5file.root, 'anarray') # Try to get the removed object with self.assertRaises(LookupError): self.h5file.root.anarray def test06d_removeLeaf(self): """Checking removing a non-existent node""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # Try to get the removed object with self.assertRaises(LookupError): self.h5file.remove_node(self.h5file.root, 'nonexistent') def test06e_removeTable(self): """Checking removing Tables from an existing file.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) self.h5file.remove_node(self.h5file.root, 'atable') # Open this file in read-only mode self._reopen(node_cache_slots=self.node_cache_slots) # Try to get the removed object with self.assertRaises(LookupError): self.h5file.root.atable def test07_renameLeaf(self): """Checking renaming a leave and access it after a close/open.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) self.h5file.rename_node(self.h5file.root.anarray, 'anarray2') # Open this file in read-only mode self._reopen(node_cache_slots=self.node_cache_slots) # Ensure that the new name exists array_ = self.h5file.root.anarray2 self.assertEqual(array_.name, "anarray2") self.assertEqual(array_._v_pathname, "/anarray2") self.assertEqual(array_._v_depth, 1) # Try to get the previous object with the old name with self.assertRaises(LookupError): self.h5file.root.anarray def test07b_renameLeaf(self): """Checking renaming Leaves and accesing them immediately.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) self.h5file.rename_node(self.h5file.root.anarray, 'anarray2') # Ensure that the new name exists array_ = self.h5file.root.anarray2 self.assertEqual(array_.name, "anarray2") self.assertEqual(array_._v_pathname, "/anarray2") self.assertEqual(array_._v_depth, 1) # Try to get the previous object with the old name with self.assertRaises(LookupError): self.h5file.root.anarray def test07c_renameLeaf(self): """Checking renaming Leaves and modify attributes after that.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) self.h5file.rename_node(self.h5file.root.anarray, 'anarray2') array_ = self.h5file.root.anarray2 array_.attrs.TITLE = "hello" # Ensure that the new attribute has been written correctly self.assertEqual(array_.title, "hello") self.assertEqual(array_.attrs.TITLE, "hello") def test07d_renameLeaf(self): """Checking renaming a Group under a nested group.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) self.h5file.rename_node(self.h5file.root.agroup.anarray2, 'anarray3') # Ensure that we can access n attributes in the new group node = self.h5file.root.agroup.anarray3 self.assertEqual(node._v_title, "Array title 2") def test08_renameToExistingLeaf(self): """Checking renaming a node to an existing name.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # Try to get the previous object with the old name with self.assertRaises(tb.NodeError): self.h5file.rename_node(self.h5file.root.anarray, 'array') # Now overwrite the destination node. anarray = self.h5file.root.anarray self.h5file.rename_node(anarray, 'array', overwrite=True) self.assertNotIn('/anarray', self.h5file) self.assertIs(self.h5file.root.array, anarray) def test08b_renameToNotValidNaturalName(self): """Checking renaming a node to a non-valid natural name""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) with warnings.catch_warnings(): warnings.filterwarnings("error", category=tb.NaturalNameWarning) # Try to get the previous object with the old name with self.assertRaises(tb.NaturalNameWarning): self.h5file.rename_node(self.h5file.root.anarray, 'array 2') def test09_renameGroup(self): """Checking renaming a Group and access it after a close/open.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) self.h5file.rename_node(self.h5file.root.agroup, 'agroup3') # Open this file in read-only mode self._reopen(node_cache_slots=self.node_cache_slots) # Ensure that the new name exists group = self.h5file.root.agroup3 self.assertEqual(group._v_name, "agroup3") self.assertEqual(group._v_pathname, "/agroup3") # The children of this group also must be accessible through the # new name path group2 = self.h5file.get_node("/agroup3/agroup3") self.assertEqual(group2._v_name, "agroup3") self.assertEqual(group2._v_pathname, "/agroup3/agroup3") # Try to get the previous object with the old name with self.assertRaises(LookupError): self.h5file.root.agroup # Try to get a child with the old pathname with self.assertRaises(LookupError): self.h5file.get_node("/agroup/agroup3") def test09b_renameGroup(self): """Checking renaming a Group and access it immediately.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) self.h5file.rename_node(self.h5file.root.agroup, 'agroup3') # Ensure that the new name exists group = self.h5file.root.agroup3 self.assertEqual(group._v_name, "agroup3") self.assertEqual(group._v_pathname, "/agroup3") # The children of this group also must be accessible through the # new name path group2 = self.h5file.get_node("/agroup3/agroup3") self.assertEqual(group2._v_name, "agroup3") self.assertEqual(group2._v_pathname, "/agroup3/agroup3") # Try to get the previous object with the old name with self.assertRaises(LookupError): self.h5file.root.agroup # Try to get a child with the old pathname with self.assertRaises(LookupError): self.h5file.get_node("/agroup/agroup3") def test09c_renameGroup(self): """Checking renaming a Group and modify attributes afterwards.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) self.h5file.rename_node(self.h5file.root.agroup, 'agroup3') # Ensure that we can modify attributes in the new group group = self.h5file.root.agroup3 group._v_attrs.TITLE = "Hello" self.assertEqual(group._v_title, "Hello") self.assertEqual(group._v_attrs.TITLE, "Hello") def test09d_renameGroup(self): """Checking renaming a Group under a nested group.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) self.h5file.rename_node(self.h5file.root.agroup.agroup3, 'agroup4') # Ensure that we can access n attributes in the new group group = self.h5file.root.agroup.agroup4 self.assertEqual(group._v_title, "Group title 3") def test09e_renameGroup(self): """Checking renaming a Group with nested groups in the LRU cache.""" # This checks for ticket #126. self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # Load intermediate groups and keep a nested one alive. g = self.h5file.root.agroup.agroup3.agroup4 self.assertIsNotNone(g) self.h5file.rename_node('/', name='agroup', newname='agroup_') # see ticket #126 self.assertNotIn('/agroup_/agroup4', self.h5file) self.assertNotIn('/agroup', self.h5file) for newpath in ['/agroup_', '/agroup_/agroup3', '/agroup_/agroup3/agroup4']: self.assertIn(newpath, self.h5file) self.assertEqual( newpath, self.h5file.get_node(newpath)._v_pathname) def test10_moveLeaf(self): """Checking moving a leave and access it after a close/open.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) newgroup = self.h5file.create_group("/", "newgroup") self.h5file.move_node(self.h5file.root.anarray, newgroup, 'anarray2') # Open this file in read-only mode self._reopen(node_cache_slots=self.node_cache_slots) # Ensure that the new name exists array_ = self.h5file.root.newgroup.anarray2 self.assertEqual(array_.name, "anarray2") self.assertEqual(array_._v_pathname, "/newgroup/anarray2") self.assertEqual(array_._v_depth, 2) # Try to get the previous object with the old name with self.assertRaises(LookupError): self.h5file.root.anarray def test10b_moveLeaf(self): """Checking moving a leave and access it without a close/open.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) newgroup = self.h5file.create_group("/", "newgroup") self.h5file.move_node(self.h5file.root.anarray, newgroup, 'anarray2') # Ensure that the new name exists array_ = self.h5file.root.newgroup.anarray2 self.assertEqual(array_.name, "anarray2") self.assertEqual(array_._v_pathname, "/newgroup/anarray2") self.assertEqual(array_._v_depth, 2) # Try to get the previous object with the old name with self.assertRaises(LookupError): self.h5file.root.anarray def test10c_moveLeaf(self): """Checking moving Leaves and modify attributes after that.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) newgroup = self.h5file.create_group("/", "newgroup") self.h5file.move_node(self.h5file.root.anarray, newgroup, 'anarray2') array_ = self.h5file.root.newgroup.anarray2 array_.attrs.TITLE = "hello" # Ensure that the new attribute has been written correctly self.assertEqual(array_.title, "hello") self.assertEqual(array_.attrs.TITLE, "hello") def test10d_moveToExistingLeaf(self): """Checking moving a leaf to an existing name.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # Try to get the previous object with the old name with self.assertRaises(tb.NodeError): self.h5file.move_node( self.h5file.root.anarray, self.h5file.root, 'array') def test10_2_moveTable(self): """Checking moving a table and access it after a close/open.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) newgroup = self.h5file.create_group("/", "newgroup") self.h5file.move_node(self.h5file.root.atable, newgroup, 'atable2') # Open this file in read-only mode self._reopen(node_cache_slots=self.node_cache_slots) # Ensure that the new name exists table_ = self.h5file.root.newgroup.atable2 self.assertEqual(table_.name, "atable2") self.assertEqual(table_._v_pathname, "/newgroup/atable2") self.assertEqual(table_._v_depth, 2) # Try to get the previous object with the old name with self.assertRaises(LookupError): self.h5file.root.atable def test10_2b_moveTable(self): """Checking moving a table and access it without a close/open.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) newgroup = self.h5file.create_group("/", "newgroup") self.h5file.move_node(self.h5file.root.atable, newgroup, 'atable2') # Ensure that the new name exists table_ = self.h5file.root.newgroup.atable2 self.assertEqual(table_.name, "atable2") self.assertEqual(table_._v_pathname, "/newgroup/atable2") self.assertEqual(table_._v_depth, 2) # Try to get the previous object with the old name with self.assertRaises(LookupError): self.h5file.root.atable def test10_2b_bis_moveTable(self): """Checking moving a table and use cached row without a close/open.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) newgroup = self.h5file.create_group("/", "newgroup") # Cache the Row attribute prior to the move row = self.h5file.root.atable.row self.h5file.move_node(self.h5file.root.atable, newgroup, 'atable2') # Ensure that the new name exists table_ = self.h5file.root.newgroup.atable2 self.assertEqual(table_.name, "atable2") self.assertEqual(table_._v_pathname, "/newgroup/atable2") self.assertEqual(table_._v_depth, 2) # Ensure that cache Row attribute has been updated row = table_.row self.assertEqual(table_._v_pathname, row.table._v_pathname) nrows = table_.nrows # Add a new row just to make sure that this works row.append() table_.flush() self.assertEqual(table_.nrows, nrows + 1) def test10_2c_moveTable(self): """Checking moving tables and modify attributes after that.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) newgroup = self.h5file.create_group("/", "newgroup") self.h5file.move_node(self.h5file.root.atable, newgroup, 'atable2') table_ = self.h5file.root.newgroup.atable2 table_.attrs.TITLE = "hello" # Ensure that the new attribute has been written correctly self.assertEqual(table_.title, "hello") self.assertEqual(table_.attrs.TITLE, "hello") def test10_2d_moveToExistingTable(self): """Checking moving a table to an existing name.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # Try to get the previous object with the old name with self.assertRaises(tb.NodeError): self.h5file.move_node(self.h5file.root.atable, self.h5file.root, 'table') def test10_2e_moveToExistingTableOverwrite(self): """Checking moving a table to an existing name, overwriting it.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) srcNode = self.h5file.root.atable self.h5file.move_node(srcNode, self.h5file.root, 'table', overwrite=True) dstNode = self.h5file.root.table self.assertIs(srcNode, dstNode) def test11_moveGroup(self): """Checking moving a Group and access it after a close/open.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) newgroup = self.h5file.create_group(self.h5file.root, 'newgroup') self.h5file.move_node(self.h5file.root.agroup, newgroup, 'agroup3') # Open this file in read-only mode self._reopen(node_cache_slots=self.node_cache_slots) # Ensure that the new name exists group = self.h5file.root.newgroup.agroup3 self.assertEqual(group._v_name, "agroup3") self.assertEqual(group._v_pathname, "/newgroup/agroup3") self.assertEqual(group._v_depth, 2) # The children of this group must also be accessible through the # new name path group2 = self.h5file.get_node("/newgroup/agroup3/agroup3") self.assertEqual(group2._v_name, "agroup3") self.assertEqual(group2._v_pathname, "/newgroup/agroup3/agroup3") self.assertEqual(group2._v_depth, 3) # Try to get the previous object with the old name with self.assertRaises(LookupError): self.h5file.root.agroup # Try to get a child with the old pathname with self.assertRaises(LookupError): self.h5file.get_node("/agroup/agroup3") def test11b_moveGroup(self): """Checking moving a Group and access it immediately.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) newgroup = self.h5file.create_group(self.h5file.root, 'newgroup') self.h5file.move_node(self.h5file.root.agroup, newgroup, 'agroup3') # Ensure that the new name exists group = self.h5file.root.newgroup.agroup3 self.assertEqual(group._v_name, "agroup3") self.assertEqual(group._v_pathname, "/newgroup/agroup3") self.assertEqual(group._v_depth, 2) # The children of this group must also be accessible through the # new name path group2 = self.h5file.get_node("/newgroup/agroup3/agroup3") self.assertEqual(group2._v_name, "agroup3") self.assertEqual(group2._v_pathname, "/newgroup/agroup3/agroup3") self.assertEqual(group2._v_depth, 3) # Try to get the previous object with the old name with self.assertRaises(LookupError): self.h5file.root.agroup # Try to get a child with the old pathname with self.assertRaises(LookupError): self.h5file.get_node("/agroup/agroup3") def test11c_moveGroup(self): """Checking moving a Group and modify attributes afterwards.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) newgroup = self.h5file.create_group(self.h5file.root, 'newgroup') self.h5file.move_node(self.h5file.root.agroup, newgroup, 'agroup3') # Ensure that we can modify attributes in the new group group = self.h5file.root.newgroup.agroup3 group._v_attrs.TITLE = "Hello" group._v_attrs.hola = "Hello" self.assertEqual(group._v_title, "Hello") self.assertEqual(group._v_attrs.TITLE, "Hello") self.assertEqual(group._v_attrs.hola, "Hello") def test11d_moveToExistingGroup(self): """Checking moving a group to an existing name.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # Try to get the previous object with the old name with self.assertRaises(tb.NodeError): self.h5file.move_node(self.h5file.root.agroup, self.h5file.root, 'agroup2') def test11e_moveToExistingGroupOverwrite(self): """Checking moving a group to an existing name, overwriting it.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # agroup2 -> agroup srcNode = self.h5file.root.agroup2 self.h5file.move_node(srcNode, self.h5file.root, 'agroup', overwrite=True) dstNode = self.h5file.root.agroup self.assertIs(srcNode, dstNode) def test12a_moveNodeOverItself(self): """Checking moving a node over itself.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # array -> array srcNode = self.h5file.root.array self.h5file.move_node(srcNode, self.h5file.root, 'array') dstNode = self.h5file.root.array self.assertIs(srcNode, dstNode) def test12b_moveGroupIntoItself(self): """Checking moving a group into itself.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) with self.assertRaises(tb.NodeError): # agroup2 -> agroup2/ self.h5file.move_node(self.h5file.root.agroup2, self.h5file.root.agroup2) def test13a_copyLeaf(self): """Copying a leaf.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # array => agroup2/ new_node = self.h5file.copy_node(self.h5file.root.array, self.h5file.root.agroup2) dstNode = self.h5file.root.agroup2.array self.assertIs(new_node, dstNode) def test13b_copyGroup(self): """Copying a group.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # agroup2 => agroup/ new_node = self.h5file.copy_node(self.h5file.root.agroup2, self.h5file.root.agroup) dstNode = self.h5file.root.agroup.agroup2 self.assertIs(new_node, dstNode) def test13c_copyGroupSelf(self): """Copying a group into itself.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # agroup2 => agroup2/ new_node = self.h5file.copy_node(self.h5file.root.agroup2, self.h5file.root.agroup2) dstNode = self.h5file.root.agroup2.agroup2 self.assertIs(new_node, dstNode) def test13d_copyGroupRecursive(self): """Recursively copying a group.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # agroup => agroup2/ new_node = self.h5file.copy_node( self.h5file.root.agroup, self.h5file.root.agroup2, recursive=True) dstNode = self.h5file.root.agroup2.agroup self.assertIs(new_node, dstNode) dstChild1 = dstNode.anarray1 self.assertIsNotNone(dstChild1) dstChild2 = dstNode.anarray2 self.assertIsNotNone(dstChild2) dstChild3 = dstNode.agroup3 self.assertIsNotNone(dstChild3) def test13e_copyRootRecursive(self): """Recursively copying the root group into the root of another file.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) h5fname2 = tempfile.mktemp(".h5") h5file2 = tb.open_file( h5fname2, mode="w", node_cache_slots=self.node_cache_slots) try: # h5file.root => h5file2.root new_node = self.h5file.copy_node( self.h5file.root, h5file2.root, recursive=True) dstNode = h5file2.root self.assertIs(new_node, dstNode) self.assertIn("/agroup", h5file2) self.assertIn("/agroup/anarray1", h5file2) self.assertIn("/agroup/agroup3", h5file2) finally: h5file2.close() Path(h5fname2).unlink() def test13f_copyRootRecursive(self): """Recursively copying the root group into a group in another file.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) h5fname2 = tempfile.mktemp(".h5") h5file2 = tb.open_file( h5fname2, mode="w", node_cache_slots=self.node_cache_slots) try: h5file2.create_group('/', 'agroup2') # fileh.root => h5file2.root.agroup2 new_node = self.h5file.copy_node( self.h5file.root, h5file2.root.agroup2, recursive=True) dstNode = h5file2.root.agroup2 self.assertIs(new_node, dstNode) self.assertIn("/agroup2/agroup", h5file2) self.assertIn("/agroup2/agroup/anarray1", h5file2) self.assertIn("/agroup2/agroup/agroup3", h5file2) finally: h5file2.close() Path(h5fname2).unlink() def test13g_copyRootItself(self): """Recursively copying the root group into itself.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) agroup2 = self.h5file.root self.assertIsNotNone(agroup2) # h5file.root => h5file.root self.assertRaises(IOError, self.h5file.copy_node, self.h5file.root, self.h5file.root, recursive=True) def test14a_copyNodeExisting(self): """Copying over an existing node.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) with self.assertRaises(tb.NodeError): # agroup2 => agroup self.h5file.copy_node(self.h5file.root.agroup2, newname='agroup') def test14b_copyNodeExistingOverwrite(self): """Copying over an existing node, overwriting it.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # agroup2 => agroup new_node = self.h5file.copy_node(self.h5file.root.agroup2, newname='agroup', overwrite=True) dstNode = self.h5file.root.agroup self.assertIs(new_node, dstNode) def test14b2_copyNodeExistingOverwrite(self): """Copying over an existing node in other file, overwriting it.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) h5fname2 = tempfile.mktemp(".h5") h5file2 = tb.open_file( h5fname2, mode="w", node_cache_slots=self.node_cache_slots) try: # file1:/anarray1 => h5fname2:/anarray1 new_node = self.h5file.copy_node(self.h5file.root.agroup.anarray1, newparent=h5file2.root) # file1:/ => h5fname2:/ new_node = self.h5file.copy_node(self.h5file.root, h5file2.root, overwrite=True, recursive=True) dstNode = h5file2.root self.assertIs(new_node, dstNode) finally: h5file2.close() Path(h5fname2).unlink() def test14c_copyNodeExistingSelf(self): """Copying over self.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) with self.assertRaises(tb.NodeError): # agroup => agroup self.h5file.copy_node(self.h5file.root.agroup, newname='agroup') def test14d_copyNodeExistingOverwriteSelf(self): """Copying over self, trying to overwrite.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) with self.assertRaises(tb.NodeError): # agroup => agroup self.h5file.copy_node( self.h5file.root.agroup, newname='agroup', overwrite=True) def test14e_copyGroupSelfRecursive(self): """Recursively copying a group into itself.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) with self.assertRaises(tb.NodeError): # agroup => agroup/ self.h5file.copy_node(self.h5file.root.agroup, self.h5file.root.agroup, recursive=True) def test15a_oneStepMove(self): """Moving and renaming a node in a single action.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # anarray1 -> agroup/array srcNode = self.h5file.root.anarray1 self.h5file.move_node(srcNode, self.h5file.root.agroup, 'array') dstNode = self.h5file.root.agroup.array self.assertIs(srcNode, dstNode) def test15b_oneStepCopy(self): """Copying and renaming a node in a single action.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # anarray1 => agroup/array new_node = self.h5file.copy_node( self.h5file.root.anarray1, self.h5file.root.agroup, 'array') dstNode = self.h5file.root.agroup.array self.assertIs(new_node, dstNode) def test16a_fullCopy(self): """Copying full data and user attributes.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # agroup => groupcopy srcNode = self.h5file.root.agroup new_node = self.h5file.copy_node( srcNode, newname='groupcopy', recursive=True) dstNode = self.h5file.root.groupcopy self.assertIs(new_node, dstNode) self.assertEqual(srcNode._v_attrs.testattr, dstNode._v_attrs.testattr) self.assertEqual( srcNode.anarray1.attrs.testattr, dstNode.anarray1.attrs.testattr) self.assertEqual(srcNode.anarray1.read(), dstNode.anarray1.read()) def test16b_partialCopy(self): """Copying partial data and no user attributes.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) # agroup => groupcopy srcNode = self.h5file.root.agroup new_node = self.h5file.copy_node( srcNode, newname='groupcopy', recursive=True, copyuserattrs=False, start=0, stop=5, step=2) dstNode = self.h5file.root.groupcopy self.assertIs(new_node, dstNode) self.assertFalse(hasattr(dstNode._v_attrs, 'testattr')) self.assertFalse(hasattr(dstNode.anarray1.attrs, 'testattr')) self.assertEqual(srcNode.anarray1.read()[ 0:5:2], dstNode.anarray1.read()) def test16c_fullCopy(self): """Copying full data and user attributes (from file to file).""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) h5fname2 = tempfile.mktemp(".h5") h5file2 = tb.open_file( h5fname2, mode="w", node_cache_slots=self.node_cache_slots) try: # file1:/ => h5fname2:groupcopy srcNode = self.h5file.root new_node = self.h5file.copy_node( srcNode, h5file2.root, newname='groupcopy', recursive=True) dstNode = h5file2.root.groupcopy self.assertIs(new_node, dstNode) self.assertEqual(srcNode._v_attrs.testattr, dstNode._v_attrs.testattr) self.assertEqual( srcNode.agroup.anarray1.attrs.testattr, dstNode.agroup.anarray1.attrs.testattr) self.assertEqual(srcNode.agroup.anarray1.read(), dstNode.agroup.anarray1.read()) finally: h5file2.close() Path(h5fname2).unlink() def test17a_CopyChunkshape(self): """Copying dataset with a chunkshape.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) srcTable = self.h5file.root.table newTable = self.h5file.copy_node( srcTable, newname='tablecopy', chunkshape=11) self.assertEqual(newTable.chunkshape, (11,)) self.assertNotEqual(srcTable.chunkshape, newTable.chunkshape) def test17b_CopyChunkshape(self): """Copying dataset with a chunkshape with 'keep' value.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) srcTable = self.h5file.root.table newTable = self.h5file.copy_node( srcTable, newname='tablecopy', chunkshape='keep') self.assertEqual(srcTable.chunkshape, newTable.chunkshape) def test17c_CopyChunkshape(self): """Copying dataset with a chunkshape with 'auto' value.""" self._reopen(mode="r+", node_cache_slots=self.node_cache_slots) srcTable = self.h5file.root.table newTable = self.h5file.copy_node( srcTable, newname='tablecopy', chunkshape=11) newTable2 = self.h5file.copy_node( newTable, newname='tablecopy2', chunkshape='auto') self.assertEqual(srcTable.chunkshape, newTable2.chunkshape) def test18_closedRepr(self): """Representing a closed node as a string.""" self._reopen(node_cache_slots=self.node_cache_slots) for node in [self.h5file.root.agroup, self.h5file.root.anarray]: node._f_close() self.assertIn('closed', str(node)) self.assertIn('closed', repr(node)) def test19_fileno(self): """Checking that the 'fileno()' method works.""" # Open the old HDF5 file self._reopen(mode="r", node_cache_slots=self.node_cache_slots) # Get the file descriptor for this file fd = self.h5file.fileno() if common.verbose: print("Value of fileno():", fd) self.assertGreaterEqual(fd, 0) class NodeCacheOpenFile(OpenFileTestCase): node_cache_slots = tb.parameters.NODE_CACHE_SLOTS open_kwargs = dict(node_cache_slots=node_cache_slots) class NoNodeCacheOpenFile(OpenFileTestCase): node_cache_slots = 0 open_kwargs = dict(node_cache_slots=node_cache_slots) class DictNodeCacheOpenFile(OpenFileTestCase): node_cache_slots = -tb.parameters.NODE_CACHE_SLOTS open_kwargs = dict(node_cache_slots=node_cache_slots) class CheckFileTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() # Create a regular (text) file self.txtfile = tempfile.mktemp(".h5") self.fileh = open(self.txtfile, "w") self.fileh.write("Hello!") self.fileh.close() def tearDown(self): self.fileh.close() Path(self.txtfile).unlink() super().tearDown() def test00_isHDF5File(self): """Checking tables.is_hdf5_file function (TRUE case)""" # Create a PyTables file (and by so, an HDF5 file) self.h5file.create_array(self.h5file.root, 'array', [1, 2], title="Title example") # For this method to run, it needs a closed file self.h5file.close() # When file has an HDF5 format, always returns 1 if common.verbose: print("\nisHDF5File(%s) ==> %d" % ( self.h5fname, tb.is_hdf5_file(self.h5fname))) self.assertEqual(tb.is_hdf5_file(self.h5fname), 1) def test01_isHDF5File(self): """Checking tables.is_hdf5_file function (FALSE case)""" version = tb.is_hdf5_file(self.txtfile) # When file is not an HDF5 format, always returns 0 or # negative value self.assertLessEqual(version, 0) def test01x_isHDF5File_nonexistent(self): """Identifying a nonexistent HDF5 file.""" self.assertRaises(IOError, tb.is_hdf5_file, 'nonexistent') @common.unittest.skipUnless(hasattr(os, 'getuid') and os.getuid() != 0, "no UID") def test01x_isHDF5File_unreadable(self): """Identifying an unreadable HDF5 file.""" self.h5file.close() Path(self.h5fname).chmod(0) # no permissions at all self.assertRaises(IOError, tb.is_hdf5_file, self.h5fname) def test02_isPyTablesFile(self): """Checking is_pytables_file function (TRUE case)""" # Create a PyTables h5fname self.h5file.create_array(self.h5file.root, 'array', [1, 2], title="Title example") # For this method to run, it needs a closed h5fname self.h5file.close() version = tb.is_pytables_file(self.h5fname) # When h5fname has a PyTables format, always returns "1.0" string or # greater if common.verbose: print() print("\nPyTables format version number ==> %s" % version) self.assertGreaterEqual(version, "1.0") def test03_isPyTablesFile(self): """Checking is_pytables_file function (FALSE case)""" version = tb.is_pytables_file(self.txtfile) # When file is not a PyTables format, always returns 0 or # negative value if common.verbose: print() print("\nPyTables format version number ==> %s" % version) self.assertIsNone(version) def test04_openGenericHDF5File(self): """Checking opening of a generic HDF5 file.""" # Open an existing generic HDF5 file h5fname = common.test_filename("ex-noattr.h5") with tb.open_file(h5fname, mode="r") as h5file: # Check for some objects inside # A group columns = h5file.get_node("/columns", classname="Group") self.assertEqual(columns._v_name, "columns") # An Array array_ = h5file.get_node(columns, "TDC", classname="Array") self.assertEqual(array_._v_name, "TDC") # The new LRU code defers the appearance of a warning to this point # Here comes an Array of H5T_ARRAY type ui = h5file.get_node(columns, "pressure", classname="Array") self.assertEqual(ui._v_name, "pressure") if common.verbose: print("Array object with type H5T_ARRAY -->", repr(ui)) print("Array contents -->", ui[:]) # A Table table = h5file.get_node("/detector", "table", classname="Table") self.assertEqual(table._v_name, "table") def test04b_UnImplementedOnLoading(self): """Checking failure loading resulting in an ``UnImplemented`` node.""" # ############## Note for developers ############################## # This test fails if you have the line: # # ##return ChildClass(self, childname) # uncomment for debugging # # uncommented in Group.py! # # ################################################################# h5fname = common.test_filename('smpl_unsupptype.h5') with tb.open_file(h5fname) as h5file: with self.assertWarns(UserWarning): node = h5file.get_node('/CompoundChunked') self.assertIsInstance(node, tb.UnImplemented) def test04c_UnImplementedScalar(self): """Checking opening of HDF5 files containing scalar dataset of UnImlemented type.""" with tb.open_file(common.test_filename("scalar.h5")) as h5file: with self.assertWarns(UserWarning): node = h5file.get_node('/variable length string') self.assertIsInstance(node, tb.UnImplemented) def test05_copyUnimplemented(self): """Checking that an UnImplemented object cannot be copied.""" # Open an existing generic HDF5 file h5fname = common.test_filename("smpl_unsupptype.h5") with tb.open_file(h5fname, mode="r") as h5file: self.assertWarns(UserWarning, h5file.get_node, '/CompoundChunked') with warnings.catch_warnings(): warnings.simplefilter("ignore") ui = h5file.get_node('/CompoundChunked') self.assertEqual(ui._v_name, 'CompoundChunked') if common.verbose: print("UnImplement object -->", repr(ui)) # Check that it cannot be copied to another file: self.assertWarns(UserWarning, ui.copy, self.h5file.root, "newui") # The next can be used to check the copy of Array objects with H5T_ARRAY # in the future def _test05_copyUnimplemented(self): """Checking that an UnImplemented object cannot be copied.""" # Open an existing generic HDF5 file # We don't need to wrap this in a try clause because # it has already been tried and the warning will not happen again h5fname2 = common.test_filename("ex-noattr.h5") with tb.open_file(h5fname2, mode="r") as h5file2: # An unsupported object (the deprecated H5T_ARRAY type in # Array, from pytables 0.8 on) ui = h5file2.get_node(h5file2.root.columns, "pressure") self.assertEqual(ui._v_name, "pressure") if common.verbose: print("UnImplement object -->", repr(ui)) # Check that it cannot be copied to another file with warnings.catch_warnings(): # Force the userwarning to issue an error warnings.filterwarnings("error", category=UserWarning) with self.assertRaises(UserWarning): ui.copy(self.h5file.root, "newui") @common.unittest.skipIf(tb.file._FILE_OPEN_POLICY == 'strict', 'FILE_OPEN_POLICY = "strict"') class ThreadingTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.h5file.create_carray('/', 'test_array', tb.Int64Atom(), (200, 300)) self.h5file.close() def test(self): lock = threading.Lock() def syncronized_open_file(*args, **kwargs): with lock: return tb.open_file(*args, **kwargs) def syncronized_close_file(self, *args, **kwargs): with lock: return self.close(*args, **kwargs) filename = self.h5fname def run(filename, q): try: f = syncronized_open_file(filename, mode='r') arr = f.root.test_array[8:12, 18:22] assert arr.max() == arr.min() == 0 syncronized_close_file(f) except Exception: q.put(sys.exc_info()) else: q.put('OK') threads = [] q = queue.Queue() for i in range(10): t = threading.Thread(target=run, args=(filename, q)) t.start() threads.append(t) for i in range(10): self.assertEqual(q.get(), 'OK') for t in threads: t.join() class PythonAttrsTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test interactions of Python attributes and child nodes.""" def test00_attrOverChild(self): """Setting a Python attribute over a child node.""" root = self.h5file.root # Create ``/test`` and overshadow it with ``root.test``. child = self.h5file.create_array(root, 'test', [1]) attr = 'foobar' self.assertWarns(tb.NaturalNameWarning, setattr, root, 'test', attr) self.assertIs(root.test, attr) self.assertIs(root._f_get_child('test'), child) # Now bring ``/test`` again to light. del root.test self.assertIs(root.test, child) # Now there is no *attribute* named ``test``. self.assertRaises(AttributeError, delattr, root, 'test') def test01_childUnderAttr(self): """Creating a child node under a Python attribute.""" h5file = self.h5file root = h5file.root # Create ``root.test`` and an overshadowed ``/test``. attr = 'foobar' root.test = attr self.assertWarns(tb.NaturalNameWarning, h5file.create_array, root, 'test', [1]) child = h5file.get_node('/test') self.assertIs(root.test, attr) self.assertIs(root._f_get_child('test'), child) # Now bring ``/test`` again to light. del root.test self.assertIs(root.test, child) # Now there is no *attribute* named ``test``. self.assertRaises(AttributeError, delattr, root, 'test') def test02_nodeAttrInLeaf(self): """Assigning a ``Node`` value as an attribute to a ``Leaf``.""" h5file = self.h5file array1 = h5file.create_array('/', 'array1', [1]) array2 = h5file.create_array('/', 'array2', [1]) # This may make the garbage collector work a little. array1.array2 = array2 array2.array1 = array1 # Check the assignments. self.assertIs(array1.array2, array2) self.assertIs(array2.array1, array1) self.assertRaises(tb.NoSuchNodeError, # ``/array1`` is not a group h5file.get_node, '/array1/array2') self.assertRaises(tb.NoSuchNodeError, # ``/array2`` is not a group h5file.get_node, '/array2/array3') def test03_nodeAttrInGroup(self): """Assigning a ``Node`` value as an attribute to a ``Group``.""" h5file = self.h5file root = h5file.root array = h5file.create_array('/', 'array', [1]) # Assign the array to a pair of attributes, # one of them overshadowing the original. root.arrayAlias = array self.assertWarns(tb.NaturalNameWarning, setattr, root, 'array', array) # Check the assignments. self.assertIs(root.arrayAlias, array) self.assertIs(root.array, array) self.assertRaises(tb.NoSuchNodeError, h5file.get_node, '/arrayAlias') self.assertIs(h5file.get_node('/array'), array) # Remove the attribute overshadowing the child. del root.array # Now there is no *attribute* named ``array``. self.assertRaises(AttributeError, delattr, root, 'array') class StateTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test that ``File`` and ``Node`` operations check their state (open or closed, readable or writable) before proceeding.""" def test00_fileCopyFileClosed(self): """Test copying a closed file.""" self.h5file.close() h5cfname = tempfile.mktemp(suffix='.h5') try: self.assertRaises(tb.ClosedFileError, self.h5file.copy_file, h5cfname) finally: if Path(h5cfname).is_file(): Path(h5cfname).unlink() def test01_fileCloseClosed(self): """Test closing an already closed file.""" self.h5file.close() try: self.h5file.close() except tb.ClosedFileError: self.fail("could not close an already closed file") def test02_fileFlushClosed(self): """Test flushing a closed file.""" self.h5file.close() self.assertRaises(tb.ClosedFileError, self.h5file.flush) def test03_fileFlushRO(self): """Flushing a read-only file.""" self._reopen('r') try: self.h5file.flush() except tb.FileModeError: self.fail("could not flush a read-only file") def test04_fileCreateNodeClosed(self): """Test creating a node in a closed file.""" self.h5file.close() self.assertRaises(tb.ClosedFileError, self.h5file.create_group, '/', 'test') def test05_fileCreateNodeRO(self): """Test creating a node in a read-only file.""" self._reopen('r') self.assertRaises(tb.FileModeError, self.h5file.create_group, '/', 'test') def test06_fileRemoveNodeClosed(self): """Test removing a node from a closed file.""" self.h5file.create_group('/', 'test') self.h5file.close() self.assertRaises(tb.ClosedFileError, self.h5file.remove_node, '/', 'test') def test07_fileRemoveNodeRO(self): """Test removing a node from a read-only file.""" self.h5file.create_group('/', 'test') self._reopen('r') self.assertRaises(tb.FileModeError, self.h5file.remove_node, '/', 'test') def test08_fileMoveNodeClosed(self): """Test moving a node in a closed file.""" self.h5file.create_group('/', 'test1') self.h5file.create_group('/', 'test2') self.h5file.close() self.assertRaises(tb.ClosedFileError, self.h5file.move_node, '/test1', '/', 'test2') def test09_fileMoveNodeRO(self): """Test moving a node in a read-only file.""" self.h5file.create_group('/', 'test1') self.h5file.create_group('/', 'test2') self._reopen('r') self.assertRaises(tb.FileModeError, self.h5file.move_node, '/test1', '/', 'test2') def test10_fileCopyNodeClosed(self): """Test copying a node in a closed file.""" self.h5file.create_group('/', 'test1') self.h5file.create_group('/', 'test2') self.h5file.close() self.assertRaises(tb.ClosedFileError, self.h5file.copy_node, '/test1', '/', 'test2') def test11_fileCopyNodeRO(self): """Test copying a node in a read-only file.""" self.h5file.create_group('/', 'test1') self._reopen('r') self.assertRaises(tb.FileModeError, self.h5file.copy_node, '/test1', '/', 'test2') def test13_fileGetNodeClosed(self): """Test getting a node from a closed file.""" self.h5file.create_group('/', 'test') self.h5file.close() self.assertRaises(tb.ClosedFileError, self.h5file.get_node, '/test') def test14_fileWalkNodesClosed(self): """Test walking a closed file.""" self.h5file.create_group('/', 'test1') self.h5file.create_group('/', 'test2') self.h5file.close() self.assertRaises(tb.ClosedFileError, next, self.h5file.walk_nodes()) def test15_fileAttrClosed(self): """Test setting and deleting a node attribute in a closed file.""" self.h5file.create_group('/', 'test') self.h5file.close() self.assertRaises(tb.ClosedFileError, self.h5file.set_node_attr, '/test', 'foo', 'bar') self.assertRaises(tb.ClosedFileError, self.h5file.del_node_attr, '/test', 'foo') def test16_fileAttrRO(self): """Test setting and deleting a node attribute in a read-only file.""" self.h5file.create_group('/', 'test') self.h5file.set_node_attr('/test', 'foo', 'foo') self._reopen('r') self.assertRaises(tb.FileModeError, self.h5file.set_node_attr, '/test', 'foo', 'bar') self.assertRaises(tb.FileModeError, self.h5file.del_node_attr, '/test', 'foo') def test17_fileUndoClosed(self): """Test undo operations in a closed file.""" self.h5file.enable_undo() self.h5file.create_group('/', 'test2') self.h5file.close() self.assertRaises(tb.ClosedFileError, self.h5file.is_undo_enabled) self.assertRaises(tb.ClosedFileError, self.h5file.get_current_mark) self.assertRaises(tb.ClosedFileError, self.h5file.undo) self.assertRaises(tb.ClosedFileError, self.h5file.disable_undo) def test18_fileUndoRO(self): """Test undo operations in a read-only file.""" self.h5file.enable_undo() self.h5file.create_group('/', 'test') self._reopen('r') self.assertEqual(self.h5file._undoEnabled, False) # self.assertRaises(FileModeError, self.h5file.undo) # self.assertRaises(FileModeError, self.h5file.disable_undo) def test19a_getNode(self): """Test getting a child of a closed node.""" g1 = self.h5file.create_group('/', 'g1') g2 = self.h5file.create_group('/g1', 'g2') # Close this *object* so that it should not be used. g1._f_close() self.assertRaises(tb.ClosedNodeError, g1._f_get_child, 'g2') # Getting a node by its closed object is not allowed. self.assertRaises(tb.ClosedNodeError, self.h5file.get_node, g1) # Going through that *node* should reopen it automatically. try: g2_ = self.h5file.get_node('/g1/g2') except tb.ClosedNodeError: self.fail("closed parent group has not been reopened") # Already open nodes should be closed now, but not the new ones. self.assertIs(g2._v_isopen, False, "open child of closed group has not been closed") self.assertIs(g2_._v_isopen, True, "open child of closed group has not been closed") # And existing closed ones should remain closed, but not the new ones. g1_ = self.h5file.get_node('/g1') self.assertIs(g1._v_isopen, False, "already closed group is not closed anymore") self.assertIs(g1_._v_isopen, True, "newly opened group is still closed") def test19b_getNode(self): """Test getting a node that does not start with a slash ('/').""" # Create an array in the root self.h5file.create_array('/', 'array', [1, 2], title="Title example") # Get the array without specifying a leading slash self.assertRaises(NameError, self.h5file.get_node, "array") def test20_removeNode(self): """Test removing a closed node.""" # This test is a little redundant once we know that ``File.get_node()`` # will reload a closed node, but anyway... group = self.h5file.create_group('/', 'group') array = self.h5file.create_array('/group', 'array', [1]) # The closed *object* can not be used. group._f_close() self.assertRaises(tb.ClosedNodeError, group._f_remove) self.assertRaises(tb.ClosedNodeError, self.h5file.remove_node, group) # Still, the *node* is reloaded when necessary. try: self.h5file.remove_node('/group', recursive=True) except tb.ClosedNodeError: self.fail("closed node has not been reloaded") # Objects of descendent removed nodes # should have been automatically closed when removed. self.assertRaises(tb.ClosedNodeError, array._f_remove) self.assertNotIn('/group/array', self.h5file) # just in case self.assertNotIn('/group', self.h5file) # just in case def test21_attrsOfNode(self): """Test manipulating the attributes of a closed node.""" node = self.h5file.create_group('/', 'test') nodeAttrs = node._v_attrs nodeAttrs.test = attr = 'foo' node._f_close() self.assertRaises(tb.ClosedNodeError, getattr, node, '_v_attrs') # The design of ``AttributeSet`` does not yet allow this test. # self.assertRaises(ClosedNodeError, getattr, nodeAttrs, 'test') self.assertEqual(self.h5file.get_node_attr('/test', 'test'), attr) def test21b_attrsOfNode(self): """Test manipulating the attributes of a node in a read-only file.""" self.h5file.create_group('/', 'test') self.h5file.set_node_attr('/test', 'test', 'foo') self._reopen('r') self.assertRaises(tb.FileModeError, self.h5file.set_node_attr, '/test', 'test', 'bar') def test22_fileClosesNode(self): """Test node closing because of file closing.""" node = self.h5file.create_group('/', 'test') self.h5file.close() self.assertRaises(tb.ClosedNodeError, getattr, node, '_v_attrs') def test23_reopenFile(self): """Testing reopening a file and closing it several times.""" self.h5file.create_array('/', 'test', [1, 2, 3]) self.h5file.close() with tb.open_file(self.h5fname, "r") as h5file1: if tb.file._FILE_OPEN_POLICY == 'strict': self.assertRaises(ValueError, tb.open_file, self.h5fname, "r") else: with tb.open_file(self.h5fname, "r") as h5file2: if common.verbose: print("(h5file1) test[1]:", h5file1.root.test[1]) self.assertEqual(h5file1.root.test[1], 2) h5file1.close() if common.verbose: print("(h5file2) test[1]:", h5file2.root.test[1]) self.assertEqual(h5file2.root.test[1], 2) class FlavorTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test that setting, getting and changing the ``flavor`` attribute of a leaf works as expected.""" array_data = np.arange(10) scalar_data = np.int32(10) def _reopen(self, mode='r'): super()._reopen(mode) self.array = self.h5file.get_node('/array') self.scalar = self.h5file.get_node('/scalar') return True def setUp(self): super().setUp() self.array = self.h5file.create_array('/', 'array', self.array_data) self.scalar = self.h5file.create_array('/', 'scalar', self.scalar_data) def test00_invalid(self): """Setting an invalid flavor.""" self.assertRaises(tb.FlavorError, setattr, self.array, 'flavor', 'foo') def test01_readonly(self): """Setting a flavor in a read-only file.""" self._reopen(mode='r') self.assertRaises(tb.FileModeError, setattr, self.array, 'flavor', tb.flavor.internal_flavor) def test02_change(self): """Changing the flavor and reading data.""" for flavor in tb.flavor.all_flavors: self.array.flavor = flavor self.assertEqual(self.array.flavor, flavor) idata = tb.flavor.array_of_flavor(self.array_data, flavor) odata = self.array[:] self.assertTrue(common.allequal(odata, idata, flavor)) def test03_store(self): """Storing a changed flavor.""" for flavor in tb.flavor.all_flavors: self.array.flavor = flavor self.assertEqual(self.array.flavor, flavor) self._reopen(mode='r+') self.assertEqual(self.array.flavor, flavor) def test04_missing(self): """Reading a dataset of a missing flavor.""" flavor = self.array.flavor # default is internal self.array._v_attrs.FLAVOR = 'foobar' # breaks flavor self._reopen(mode='r') idata = tb.flavor.array_of_flavor(self.array_data, flavor) with self.assertWarns(tb.FlavorWarning): odata = self.array.read() self.assertTrue(common.allequal(odata, idata, flavor)) def test05_delete(self): """Deleting the flavor of a dataset.""" self.array.flavor = 'python' # non-default self.assertEqual(self.array.flavor, 'python') self.assertEqual(self.array.attrs.FLAVOR, 'python') del self.array.flavor self.assertEqual(self.array.flavor, tb.flavor.internal_flavor) self.assertRaises(AttributeError, getattr, self.array.attrs, 'FLAVOR') def test06_copyDeleted(self): """Copying a node with a deleted flavor (see #100).""" snames = [node._v_name for node in [self.array, self.scalar]] dnames = ['%s_copy' % name for name in snames] for name in snames: node = self.h5file.get_node('/', name) del node.flavor # Check the copied flavors right after copying and after reopening. for fmode in ['r+', 'r']: self._reopen(fmode) for sname, dname in zip(snames, dnames): if fmode == 'r+': snode = self.h5file.get_node('/', sname) node = snode.copy('/', dname) elif fmode == 'r': node = self.h5file.get_node('/', dname) self.assertEqual(node.flavor, tb.flavor.internal_flavor, "flavor of node ``%s`` is not internal: %r" % (node._v_pathname, node.flavor)) def test07_restrict_flavors(self): # regression test for gh-163 all_flavors = list(tb.flavor.all_flavors) alias_map = tb.flavor.alias_map.copy() converter_map = tb.flavor.converter_map.copy() identifier_map = tb.flavor.identifier_map.copy() description_map = tb.flavor.description_map.copy() try: tb.flavor.restrict_flavors(keep=[]) self.assertLess(len(tb.flavor.alias_map), len(alias_map)) self.assertLess( len(tb.flavor.converter_map), len(converter_map)) finally: tb.flavor.all_flavors[:] = all_flavors[:] tb.flavor.alias_map.update(alias_map) tb.flavor.converter_map.update(converter_map) tb.flavor.identifier_map.update(identifier_map) tb.flavor.description_map.update(description_map) # @common.unittest.skipIf(sys.getfilesystemencoding() != 'utf-8', # 'need utf-8 file-system encoding') class UnicodeFilename(common.TempFileMixin, common.PyTablesTestCase): unicode_prefix = 'para\u0140lel' def _getTempFileName(self): return tempfile.mktemp(prefix=self.unicode_prefix, suffix='.h5') def setUp(self): super().setUp() self.test = self.h5file.create_array('/', 'test', [1, 2]) # So as to check the reading self._reopen() def test01(self): """Checking creating a filename with Unicode chars.""" test = self.h5file.root.test if common.verbose: print("Filename:", self.h5fname) print("Array:", test[:]) print("Should look like:", [1, 2]) self.assertEqual(test[:], [1, 2], "Values does not match.") def test02(self): """Checking tables.is_hdf5_file with a Unicode filename.""" self.h5file.close() if common.verbose: print("Filename:", self.h5fname) print(" tables.is_hdf5_file?:", tb.is_hdf5_file(self.h5fname)) self.assertTrue(tb.is_hdf5_file(self.h5fname)) def test03(self): """Checking is_pytables_file with a Unicode filename.""" self.h5file.close() if common.verbose: print("Filename:", self.h5fname) print("is_pytables_file?:", tb.is_pytables_file(self.h5fname)) self.assertNotEqual(tb.is_pytables_file(self.h5fname), False) @staticmethod def _store_carray(name, data, group): atom = tb.Atom.from_dtype(data.dtype) node = tb.CArray(group, name, shape=data.shape, atom=atom) node[:] = data def test_store_and_load_with_non_ascii_attributes(self): self.h5file.close() self.h5file = tb.open_file(self.h5fname, "a") root = self.h5file.root group = self.h5file.create_group(root, 'face_data') array_name = 'data at 40\N{DEGREE SIGN}C' data = np.sinh(np.linspace(-1.4, 1.4, 500)) with warnings.catch_warnings(): warnings.simplefilter('ignore', tb.NaturalNameWarning) self._store_carray(array_name, data, group) group = self.h5file.create_group(root, 'vertex_data') @common.unittest.skipIf(sys.version_info < (3, 6), 'PEP 519 was implemented in Python 3.6') class PathLikeFilename(common.TempFileMixin, common.PyTablesTestCase): def _getTempFileName(self): from pathlib import Path return Path(tempfile.mktemp(suffix='.h5')) def setUp(self): super().setUp() self.test = self.h5file.create_array('/', 'test', [1, 2]) # So as to check the reading self._reopen() def test01(self): """Checking creating a file with a PathLike object as the filename.""" test = self.h5file.root.test if common.verbose: print("Filename:", self.h5fname) print("Array:", test[:]) print("Should look like:", [1, 2]) self.assertEqual(test[:], [1, 2], "Values does not match.") def test02(self): """Checking tables.is_hdf5_file with a PathLike object as the filename.""" self.h5file.close() if common.verbose: print("Filename:", self.h5fname) print(" tables.is_hdf5_file?:", tb.is_hdf5_file(self.h5fname)) self.assertTrue(tb.is_hdf5_file(self.h5fname)) def test03(self): """Checking is_pytables_file with a PathLike object as the filename.""" self.h5file.close() if common.verbose: print("Filename:", self.h5fname) print("is_pytables_file?:", tb.is_pytables_file(self.h5fname)) self.assertNotEqual(tb.is_pytables_file(self.h5fname), False) def test04_str(self): str(self.h5file) class FilePropertyTestCase(common.PyTablesTestCase): def setUp(self): super().setUp() self.h5fname = tempfile.mktemp(".h5") self.h5file = None def tearDown(self): if self.h5file: self.h5file.close() if Path(self.h5fname).is_file(): Path(self.h5fname).unlink() super().tearDown() def test_get_filesize(self): data = np.zeros((2000, 2000)) datasize = np.prod(data.shape) * data.dtype.itemsize self.h5file = tb.open_file(self.h5fname, mode="w") self.h5file.create_array(self.h5file.root, 'array', data) h5_filesize = self.h5file.get_filesize() self.h5file.close() fs_filesize = Path(self.h5fname).stat().st_size self.assertGreaterEqual(h5_filesize, datasize) self.assertEqual(h5_filesize, fs_filesize) def test01_null_userblock_size(self): self.h5file = tb.open_file(self.h5fname, mode="w") self.h5file.create_array(self.h5file.root, 'array', [1, 2]) self.assertEqual(self.h5file.get_userblock_size(), 0) def test02_null_userblock_size(self): self.h5file = tb.open_file(self.h5fname, mode="w") self.h5file.create_array(self.h5file.root, 'array', [1, 2]) self.h5file.close() self.h5file = tb.open_file(self.h5fname, mode="r") self.assertEqual(self.h5file.get_userblock_size(), 0) def test03_null_userblock_size(self): USER_BLOCK_SIZE = 0 self.h5file = tb.open_file( self.h5fname, mode="w", user_block_size=USER_BLOCK_SIZE) self.h5file.create_array(self.h5file.root, 'array', [1, 2]) self.assertEqual(self.h5file.get_userblock_size(), 0) def test01_userblock_size(self): USER_BLOCK_SIZE = 512 self.h5file = tb.open_file( self.h5fname, mode="w", user_block_size=USER_BLOCK_SIZE) self.h5file.create_array(self.h5file.root, 'array', [1, 2]) self.assertEqual(self.h5file.get_userblock_size(), USER_BLOCK_SIZE) def test02_userblock_size(self): USER_BLOCK_SIZE = 512 self.h5file = tb.open_file( self.h5fname, mode="w", user_block_size=USER_BLOCK_SIZE) self.h5file.create_array(self.h5file.root, 'array', [1, 2]) self.h5file.close() self.h5file = tb.open_file(self.h5fname, mode="r") self.assertEqual(self.h5file.get_userblock_size(), USER_BLOCK_SIZE) def test_small_userblock_size(self): USER_BLOCK_SIZE = 12 self.assertRaises(ValueError, tb.open_file, self.h5fname, mode="w", user_block_size=USER_BLOCK_SIZE) def test_invalid_userblock_size(self): USER_BLOCK_SIZE = 1025 self.assertRaises(ValueError, tb.open_file, self.h5fname, mode="w", user_block_size=USER_BLOCK_SIZE) # Test for reading a file that uses Blosc and created on a big-endian platform @common.unittest.skipIf(not common.blosc_avail, 'Blosc not available') class BloscBigEndian(common.TestFileMixin, common.PyTablesTestCase): h5fname = common.test_filename("blosc_bigendian.h5") def test00_bigendian(self): """Checking compatibility with Blosc on big-endian machines.""" # Check that we can read the contents without problems (nor warnings!) for dset_name in ('i1', 'i2', 'i4', 'i8'): a = np.arange(10, dtype=dset_name) dset = self.h5file.get_node('/'+dset_name) self.assertTrue(common.allequal(a, dset[:]), "Error in big-endian data!") # Case test for Blosc and subprocesses (via multiprocessing module) # The worker function for the subprocess (needs to be here because Windows # has problems pickling nested functions with the multiprocess module :-/) def _worker(fn, qout=None): fp = tb.open_file(fn) if common.verbose: print("About to load: ", fn) rows = fp.root.table.where('(f0 < 10)') if common.verbose: print("Got the iterator, about to iterate") next(rows) if common.verbose: print("Succeeded in one iteration\n") fp.close() if qout is not None: qout.put("Done") # From: Yaroslav Halchenko # Subject: Skip the unittest on kFreeBSD and Hurd -- locking seems to # be N/A # # on kfreebsd /dev/shm is N/A # on Hurd -- inter-process semaphore locking is N/A @common.unittest.skipIf(not multiprocessing_imported, 'multiprocessing module not available') @common.unittest.skipIf(platform.system().lower() in ('gnu', 'gnu/kfreebsd'), "multiprocessing module is not " "supported on Hurd/kFreeBSD") @common.unittest.skipIf(not common.blosc_avail, 'Blosc not available') class BloscSubprocess(common.PyTablesTestCase): def test_multiprocess(self): # Create a relatively large table with Blosc level 9 (large blocks) h5fname = tempfile.mktemp(prefix="multiproc-blosc9-", suffix=".h5") try: size = 300_000 sa = np.fromiter(((i, i**2, i//3) for i in range(size)), 'i4,i8,f8') with tb.open_file(h5fname, 'w') as h5file: h5file.create_table( h5file.root, 'table', sa, filters=tb.Filters(complevel=9, complib="blosc"), chunkshape=(size // 3,)) if common.verbose: print("**** Running from main process:") _worker(h5fname) if common.verbose: print("**** Running from subprocess:") try: qout = mp.Queue() except OSError: print("Permission denied due to /dev/shm settings") else: ps = mp.Process(target=_worker, args=(h5fname, qout,)) ps.daemon = True ps.start() result = qout.get() if common.verbose: print(result) finally: Path(h5fname).unlink() class HDF5ErrorHandling(common.PyTablesTestCase): def setUp(self): super().setUp() self._old_policy = tb.HDF5ExtError.DEFAULT_H5_BACKTRACE_POLICY def tearDown(self): tb.HDF5ExtError.DEFAULT_H5_BACKTRACE_POLICY = self._old_policy super().tearDown() def test_silence_messages(self): code = """ import tables as tb tb.silence_hdf5_messages(False) tb.silence_hdf5_messages() try: tb.open_file(r'%s') except tb.HDF5ExtError, e: pass """ filename = tempfile.mktemp(prefix="hdf5-error-handling-", suffix=".py") try: with open(filename, 'w') as fp: fp.write(code % filename) p = subprocess.Popen([sys.executable, filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = p.communicate() self.assertNotIn("HDF5-DIAG", stderr.decode('ascii')) finally: Path(filename).unlink() def test_enable_messages(self): code = """ import tables as tb tb.silence_hdf5_messages() tb.silence_hdf5_messages(False) try: tb.open_file(r'%s') except tb.HDF5ExtError as e: pass """ filename = tempfile.mktemp(prefix="hdf5-error-handling-", suffix=".py") try: Path(filename).write_text(code % filename) p = subprocess.Popen([sys.executable, filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = p.communicate() self.assertIn("HDF5-DIAG", stderr.decode('ascii')) finally: Path(filename).unlink() def _raise_exterror(self): h5fname = tempfile.mktemp(".h5") Path(h5fname).write_text('') try: h5file = tb.open_file(h5fname) h5file.close() finally: Path(h5fname).unlink() def test_h5_backtrace_quiet(self): tb.HDF5ExtError.DEFAULT_H5_BACKTRACE_POLICY = True with self.assertRaises(tb.HDF5ExtError) as cm: self._raise_exterror() self.assertIsNotNone(cm.exception.h5backtrace) def test_h5_backtrace_verbose(self): tb.HDF5ExtError.DEFAULT_H5_BACKTRACE_POLICY = "VERBOSE" with self.assertRaises(tb.HDF5ExtError) as cm: self._raise_exterror() self.assertIsNotNone(cm.exception.h5backtrace) msg = str(cm.exception) self.assertIn(cm.exception.h5backtrace[-1][-1], msg) def test_h5_backtrace_ignore(self): tb.HDF5ExtError.DEFAULT_H5_BACKTRACE_POLICY = False with self.assertRaises(tb.HDF5ExtError) as cm: self._raise_exterror() self.assertIsNone(cm.exception.h5backtrace) class TestDescription(common.PyTablesTestCase): def test_isdescription_inheritance(self): # Regression test for gh-65 class TestDescParent(tb.IsDescription): c = tb.Int32Col() class TestDesc(TestDescParent): pass self.assertIn('c', TestDesc.columns) def test_descr_from_dtype(self): t = np.dtype([('col1', 'int16'), ('col2', float)]) descr, byteorder = tb.description.descr_from_dtype(t) self.assertIn('col1', descr._v_colobjects) self.assertIn('col2', descr._v_colobjects) self.assertEqual(len(descr._v_colobjects), 2) self.assertIsInstance(descr._v_colobjects['col1'], tb.Col) self.assertIsInstance(descr._v_colobjects['col2'], tb.Col) self.assertEqual(descr._v_colobjects['col1'].dtype, np.int16) self.assertEqual(descr._v_colobjects['col2'].dtype, float) def test_descr_from_dtype_rich_dtype(self): header = [(('timestamp', 't'), 'u4'), (('unit (cluster) id', 'unit'), 'u2')] t = np.dtype(header) descr, byteorder = tb.description.descr_from_dtype(t) self.assertEqual(len(descr._v_names), 2) self.assertEqual(sorted(descr._v_names), ['t', 'unit']) def test_descr_from_dtype_comp_01(self): d1 = np.dtype([('x', 'int16'), ('y', 'int16')]) d_comp = np.dtype([('time', 'float64'), ('value', d1)]) descr, byteorder = tb.description.descr_from_dtype(d_comp) self.assertTrue(descr._v_is_nested) self.assertIn('time', descr._v_colobjects) self.assertIn('value', descr._v_colobjects) self.assertEqual(len(descr._v_colobjects), 2) self.assertIsInstance(descr._v_colobjects['time'], tb.Col) self.assertTrue(isinstance(descr._v_colobjects['value'], tb.Description)) self.assertEqual(descr._v_colobjects['time'].dtype, np.float64) def test_descr_from_dtype_comp_02(self): d1 = np.dtype([('x', 'int16'), ('y', 'int16')]) d_comp = np.dtype([('time', 'float64'), ('value', (d1, (1,)))]) with self.assertWarns(UserWarning): descr, byteorder = tb.description.descr_from_dtype(d_comp) self.assertTrue(descr._v_is_nested) self.assertIn('time', descr._v_colobjects) self.assertIn('value', descr._v_colobjects) self.assertEqual(len(descr._v_colobjects), 2) self.assertIsInstance(descr._v_colobjects['time'], tb.Col) self.assertTrue(isinstance(descr._v_colobjects['value'], tb.Description)) self.assertEqual(descr._v_colobjects['time'].dtype, np.float64) def test_dtype_from_descr_is_description(self): # See gh-152 class TestDescParent(tb.IsDescription): col1 = tb.Int16Col() col2 = tb.FloatCol() dtype = np.dtype([('col1', 'int16'), ('col2', float)]) t = tb.description.dtype_from_descr(TestDescParent) self.assertEqual(t, dtype) def test_dtype_from_descr_is_description_instance(self): # See gh-152 class TestDescParent(tb.IsDescription): col1 = tb.Int16Col() col2 = tb.FloatCol() dtype = np.dtype([('col1', 'int16'), ('col2', float)]) t = tb.description.dtype_from_descr(TestDescParent()) self.assertEqual(t, dtype) def test_dtype_from_descr_description_instance(self): # See gh-152 class TestDescParent(tb.IsDescription): col1 = tb.Int16Col() col2 = tb.FloatCol() dtype = np.dtype([('col1', 'int16'), ('col2', float)]) desctiption = tb.Description(TestDescParent().columns) t = tb.description.dtype_from_descr(desctiption) self.assertEqual(t, dtype) def test_dtype_from_descr_dict(self): # See gh-152 dtype = np.dtype([('col1', 'int16'), ('col2', float)]) t = tb.description.dtype_from_descr( {'col1': tb.Int16Col(), 'col2': tb.FloatCol()}) self.assertEqual(t, dtype) def test_dtype_from_descr_invalid_type(self): # See gh-152 self.assertRaises(ValueError, tb.description.dtype_from_descr, []) def test_dtype_from_descr_byteorder(self): # See gh-152 class TestDescParent(tb.IsDescription): col1 = tb.Int16Col() col2 = tb.FloatCol() t = tb.description.dtype_from_descr(TestDescParent, byteorder='>') self.assertEqual(t['col1'].byteorder, '>') self.assertEqual(t['col2'].byteorder, '>') def test_str_names(self): # see gh-42 d = {'name': tb.Int16Col()} descr = tb.Description(d) self.assertEqual(sorted(descr._v_names), sorted(d.keys())) self.assertIsInstance(descr._v_dtype, np.dtype) self.assertTrue(sorted(descr._v_dtype.fields), sorted(d.keys())) class TestAtom(common.PyTablesTestCase): def test_atom_attributes01(self): shape = (10, 10) a = tb.Float64Atom(shape=shape) self.assertEqual(a.dflt, 0.) self.assertEqual(a.dtype, np.dtype((np.float64, shape))) self.assertEqual(a.itemsize, a.dtype.base.itemsize) self.assertEqual(a.kind, 'float') self.assertEqual(a.ndim, len(shape)) # self.assertEqual(a.recarrtype, ) self.assertEqual(a.shape, shape) self.assertEqual(a.size, a.itemsize * np.prod(shape)) self.assertEqual(a.type, 'float64') def test_atom_copy01(self): shape = (10, 10) a = tb.Float64Atom(shape=shape) aa = a.copy() self.assertEqual(aa.shape, shape) def test_atom_copy02(self): dflt = 2.0 a = tb.Float64Atom(dflt=dflt) aa = a.copy() self.assertEqual(aa.dflt, dflt) def test_atom_copy_override(self): shape = (10, 10) dflt = 2.0 a = tb.Float64Atom(shape=shape, dflt=dflt) aa = a.copy(dflt=-dflt) self.assertEqual(aa.shape, shape) self.assertNotEqual(aa.dflt, dflt) self.assertEqual(aa.dflt, -dflt) class TestCol(common.PyTablesTestCase): def test_col_copy01(self): shape = (10, 10) c = tb.Float64Col(shape=shape) cc = c.copy() self.assertEqual(cc.shape, shape) def test_col_copy02(self): dflt = 2.0 c = tb.Float64Col(dflt=dflt) cc = c.copy() self.assertEqual(cc.dflt, dflt) def test_col_copy_override(self): shape = (10, 10) dflt = 2.0 pos = 3 c = tb.Float64Col(shape=shape, dflt=dflt, pos=pos) cc = c.copy(pos=2) self.assertEqual(cc.shape, shape) self.assertEqual(cc.dflt, dflt) self.assertNotEqual(cc._v_pos, pos) self.assertEqual(cc._v_pos, 2) class TestSysattrCompatibility(common.PyTablesTestCase): def test_open_python2(self): h5fname = common.test_filename("python2.h5") with tb.open_file(h5fname, "r") as h5file: self.assertTrue(h5file.isopen) def test_open_python3(self): h5fname = common.test_filename("python3.h5") with tb.open_file(h5fname, "r") as h5file: self.assertTrue(h5file.isopen) def suite(): theSuite = common.unittest.TestSuite() niter = 1 for i in range(niter): theSuite.addTest(common.unittest.makeSuite(OpenFileFailureTestCase)) theSuite.addTest(common.unittest.makeSuite(NodeCacheOpenFile)) theSuite.addTest(common.unittest.makeSuite(NoNodeCacheOpenFile)) theSuite.addTest(common.unittest.makeSuite(DictNodeCacheOpenFile)) theSuite.addTest(common.unittest.makeSuite(CheckFileTestCase)) theSuite.addTest(common.unittest.makeSuite(ThreadingTestCase)) theSuite.addTest(common.unittest.makeSuite(PythonAttrsTestCase)) theSuite.addTest(common.unittest.makeSuite(StateTestCase)) theSuite.addTest(common.unittest.makeSuite(FlavorTestCase)) theSuite.addTest(common.unittest.makeSuite(UnicodeFilename)) theSuite.addTest(common.unittest.makeSuite(PathLikeFilename)) theSuite.addTest(common.unittest.makeSuite(FilePropertyTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscBigEndian)) theSuite.addTest(common.unittest.makeSuite(BloscSubprocess)) theSuite.addTest(common.unittest.makeSuite(HDF5ErrorHandling)) theSuite.addTest(common.unittest.makeSuite(TestDescription)) theSuite.addTest(common.unittest.makeSuite(TestAtom)) theSuite.addTest(common.unittest.makeSuite(TestCol)) theSuite.addTest(common.unittest.makeSuite(TestSysattrCompatibility)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_carray.py000066400000000000000000002764131416254111300205650ustar00rootroot00000000000000import sys from pathlib import Path import numpy as np import tables as tb from tables.tests import common class BasicTestCase(common.TempFileMixin, common.PyTablesTestCase): # Default values obj = None flavor = "numpy" type = 'int32' shape = (2, 2) start = 0 stop = 10 step = 1 length = 1 chunkshape = (5, 5) compress = 0 complib = "zlib" # Default compression library shuffle = 0 bitshuffle = 0 fletcher32 = 0 reopen = 1 # Tells whether the file has to be reopened on each test or not def setUp(self): super().setUp() # Create an instance of an HDF5 Table self.rootgroup = self.h5file.root self.populateFile() if self.reopen: # Close the file self.h5file.close() def populateFile(self): group = self.rootgroup obj = self.obj if obj is None: if self.type == "string": atom = tb.StringAtom(itemsize=self.length) else: atom = tb.Atom.from_type(self.type) else: atom = None title = self.__class__.__name__ filters = tb.Filters(complevel=self.compress, complib=self.complib, shuffle=self.shuffle, bitshuffle=self.bitshuffle, fletcher32=self.fletcher32) carray = self.h5file.create_carray(group, 'carray1', atom=atom, shape=self.shape, title=title, filters=filters, chunkshape=self.chunkshape, obj=obj) carray.flavor = self.flavor # Fill it with data self.rowshape = list(carray.shape) self.objsize = self.length * np.prod(carray.shape) if self.flavor == "numpy": if self.type == "string": object = np.ndarray(buffer=b"a"*self.objsize, shape=self.shape, dtype="S%s" % carray.atom.itemsize) else: object = np.arange(self.objsize, dtype=carray.atom.dtype) object.shape = carray.shape if common.verbose: print("Object to append -->", repr(object)) carray[...] = object def _get_shape(self): if self.shape is not None: shape = self.shape else: shape = np.asarray(self.obj).shape return shape def test00_attributes(self): if self.reopen: self.h5file = tb.open_file(self.h5fname, "r") obj = self.h5file.get_node("/carray1") shape = self._get_shape() self.assertEqual(obj.flavor, self.flavor) self.assertEqual(obj.shape, shape) self.assertEqual(obj.ndim, len(shape)) self.assertEqual(obj.chunkshape, self.chunkshape) self.assertEqual(obj.nrows, shape[0]) self.assertEqual(obj.atom.type, self.type) def test01_readCArray(self): """Checking read() of chunked layout arrays.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_readCArray..." % self.__class__.__name__) # Create an instance of an HDF5 Table if self.reopen: self.h5file = tb.open_file(self.h5fname, "r") carray = self.h5file.get_node("/carray1") # Choose a small value for buffer size carray.nrowsinbuf = 3 if common.verbose: print("CArray descr:", repr(carray)) print("shape of read array ==>", carray.shape) print("reopening?:", self.reopen) shape = self._get_shape() # Build the array to do comparisons if self.flavor == "numpy": if self.type == "string": object_ = np.ndarray(buffer=b"a" * self.objsize, shape=self.shape, dtype=f"S{carray.atom.itemsize}") else: object_ = np.arange(self.objsize, dtype=carray.atom.dtype) object_.shape = shape stop = self.stop # stop == None means read only the element designed by start # (in read() contexts) if self.stop is None: if self.start == -1: # corner case stop = carray.nrows else: stop = self.start + 1 # Protection against number of elements less than existing # if rowshape[self.extdim] < self.stop or self.stop == 0: if carray.nrows < stop: # self.stop == 0 means last row only in read() # and not in [::] slicing notation stop = int(carray.nrows) # do a copy() in order to ensure that len(object._data) # actually do a measure of its length obj = object_[self.start:stop:self.step].copy() # Read all the array try: data = carray.read(self.start, stop, self.step) except IndexError: if self.flavor == "numpy": data = np.empty(shape=self.shape, dtype=self.type) else: data = np.empty(shape=self.shape, dtype=self.type) if common.verbose: if hasattr(obj, "shape"): print("shape should look as:", obj.shape) print("Object read ==>", repr(data)) print("Should look like ==>", repr(obj)) if hasattr(data, "shape"): self.assertEqual(len(data.shape), len(shape)) else: # Scalar case self.assertEqual(len(self.shape), 1) self.assertEqual(carray.chunkshape, self.chunkshape) self.assertTrue(common.allequal(data, obj, self.flavor)) def test01_readCArray_out_argument(self): """Checking read() of chunked layout arrays.""" # Create an instance of an HDF5 Table if self.reopen: self.h5file = tb.open_file(self.h5fname, "r") carray = self.h5file.get_node("/carray1") shape = self._get_shape() # Choose a small value for buffer size carray.nrowsinbuf = 3 # Build the array to do comparisons if self.flavor == "numpy": if self.type == "string": object_ = np.ndarray(buffer=b"a" * self.objsize, shape=self.shape, dtype=f"S{carray.atom.itemsize}") else: object_ = np.arange(self.objsize, dtype=carray.atom.dtype) object_.shape = shape stop = self.stop # stop == None means read only the element designed by start # (in read() contexts) if self.stop is None: if self.start == -1: # corner case stop = carray.nrows else: stop = self.start + 1 # Protection against number of elements less than existing # if rowshape[self.extdim] < self.stop or self.stop == 0: if carray.nrows < stop: # self.stop == 0 means last row only in read() # and not in [::] slicing notation stop = int(carray.nrows) # do a copy() in order to ensure that len(object._data) # actually do a measure of its length obj = object_[self.start:stop:self.step].copy() # Read all the array try: data = np.empty(shape, dtype=carray.atom.dtype) data = data[self.start:stop:self.step].copy() carray.read(self.start, stop, self.step, out=data) except IndexError: if self.flavor == "numpy": data = np.empty(shape=shape, dtype=self.type) else: data = np.empty(shape=shape, dtype=self.type) if hasattr(data, "shape"): self.assertEqual(len(data.shape), len(shape)) else: # Scalar case self.assertEqual(len(shape), 1) self.assertEqual(carray.chunkshape, self.chunkshape) self.assertTrue(common.allequal(data, obj, self.flavor)) def test02_getitemCArray(self): """Checking chunked layout array __getitem__ special method.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_getitemCArray..." % self.__class__.__name__) if not hasattr(self, "slices"): # If there is not a slices attribute, create it self.slices = (slice(self.start, self.stop, self.step),) # Create an instance of an HDF5 Table if self.reopen: self.h5file = tb.open_file(self.h5fname, "r") carray = self.h5file.get_node("/carray1") if common.verbose: print("CArray descr:", repr(carray)) print("shape of read array ==>", carray.shape) print("reopening?:", self.reopen) shape = self._get_shape() # Build the array to do comparisons if self.type == "string": object_ = np.ndarray(buffer=b"a" * self.objsize, shape=self.shape, dtype=f"S{carray.atom.itemsize}") else: object_ = np.arange(self.objsize, dtype=carray.atom.dtype) object_.shape = shape # do a copy() in order to ensure that len(object._data) # actually do a measure of its length obj = object_.__getitem__(self.slices).copy() # Read data from the array try: data = carray.__getitem__(self.slices) except IndexError: print("IndexError!") if self.flavor == "numpy": data = np.empty(shape=self.shape, dtype=self.type) else: data = np.empty(shape=self.shape, dtype=self.type) if common.verbose: print("Object read:\n", repr(data)) # , data.info() print("Should look like:\n", repr(obj)) # , object.info() if hasattr(obj, "shape"): print("Original object shape:", self.shape) print("Shape read:", data.shape) print("shape should look as:", obj.shape) if not hasattr(data, "shape"): # Scalar case self.assertEqual(len(self.shape), 1) self.assertEqual(carray.chunkshape, self.chunkshape) self.assertTrue(common.allequal(data, obj, self.flavor)) def test03_setitemCArray(self): """Checking chunked layout array __setitem__ special method.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_setitemCArray..." % self.__class__.__name__) if not hasattr(self, "slices"): # If there is not a slices attribute, create it self.slices = (slice(self.start, self.stop, self.step),) # Create an instance of an HDF5 Table if self.reopen: self.h5file = tb.open_file(self.h5fname, "a") carray = self.h5file.get_node("/carray1") if common.verbose: print("CArray descr:", repr(carray)) print("shape of read array ==>", carray.shape) print("reopening?:", self.reopen) shape = self._get_shape() # Build the array to do comparisons if self.type == "string": object_ = np.ndarray(buffer=b"a" * self.objsize, shape=self.shape, dtype=f"S{carray.atom.itemsize}") else: object_ = np.arange(self.objsize, dtype=carray.atom.dtype) object_.shape = shape # do a copy() in order to ensure that len(object._data) # actually do a measure of its length obj = object_.__getitem__(self.slices).copy() if self.type == "string": if hasattr(self, "wslice"): obj[self.wslize] = "xXx" carray[self.wslice] = "xXx" elif sum(obj[self.slices].shape) != 0: obj[:] = "xXx" if obj.size > 0: carray[self.slices] = obj else: if hasattr(self, "wslice"): obj[self.wslice] = obj[self.wslice] * 2 + 3 carray[self.wslice] = carray[self.wslice] * 2 + 3 elif sum(obj[self.slices].shape) != 0: obj = obj * 2 + 3 if np.prod(obj.shape) > 0: carray[self.slices] = carray[self.slices] * 2 + 3 # Cast again object to its original type obj = np.array(obj, dtype=carray.atom.dtype) # Read datafrom the array try: data = carray.__getitem__(self.slices) except IndexError: print("IndexError!") if self.flavor == "numpy": data = np.empty(shape=self.shape, dtype=self.type) else: data = np.empty(shape=self.shape, dtype=self.type) if common.verbose: print("Object read:\n", repr(data)) # , data.info() print("Should look like:\n", repr(obj)) # , object.info() if hasattr(obj, "shape"): print("Original object shape:", self.shape) print("Shape read:", data.shape) print("shape should look as:", obj.shape) if not hasattr(data, "shape"): # Scalar case self.assertEqual(len(self.shape), 1) self.assertEqual(carray.chunkshape, self.chunkshape) self.assertTrue(common.allequal(data, obj, self.flavor)) class BasicWriteTestCase(BasicTestCase): type = 'int32' shape = (2,) chunkshape = (5,) step = 1 wslice = 1 # single element case class BasicWrite2TestCase(BasicTestCase): type = 'int32' shape = (2,) chunkshape = (5,) step = 1 wslice = slice(shape[0]-2, shape[0], 2) # range of elements reopen = 0 # This case does not reopen files class BasicWrite3TestCase(BasicTestCase): obj = [1, 2] type = np.asarray(obj).dtype.name shape = None chunkshape = (5,) step = 1 reopen = 0 # This case does not reopen files class BasicWrite4TestCase(BasicTestCase): obj = np.array([1, 2]) type = obj.dtype.name shape = None chunkshape = (5,) step = 1 reopen = 0 # This case does not reopen files class BasicWrite5TestCase(BasicTestCase): obj = [[1, 2], [3, 4]] type = np.asarray(obj).dtype.name shape = None chunkshape = (5, 1) step = 1 reopen = 0 # This case does not reopen files class BasicWrite6TestCase(BasicTestCase): obj = [1, 2] type = np.asarray(obj).dtype.name shape = None chunkshape = (5,) step = 1 reopen = 1 # This case does reopen files class BasicWrite7TestCase(BasicTestCase): obj = np.array([1, 2]) type = obj.dtype.name shape = None chunkshape = (5,) step = 1 reopen = 1 # This case does reopen files class BasicWrite8TestCase(BasicTestCase): obj = [[1, 2], [3, 4]] type = np.asarray(obj).dtype.name shape = None chunkshape = (5, 1) step = 1 reopen = 1 # This case does reopen files class EmptyCArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 2) chunkshape = (5, 5) start = 0 stop = 10 step = 1 class EmptyCArray2TestCase(BasicTestCase): type = 'int32' shape = (2, 2) chunkshape = (5, 5) start = 0 stop = 10 step = 1 reopen = 0 # This case does not reopen files @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class SlicesCArrayTestCase(BasicTestCase): compress = 1 complib = "lzo" type = 'int32' shape = (2, 2) chunkshape = (5, 5) slices = (slice(1, 2, 1), slice(1, 3, 1)) class EllipsisCArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 2) chunkshape = (5, 5) # slices = (slice(1,2,1), Ellipsis) slices = (Ellipsis, slice(1, 2, 1)) @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class Slices2CArrayTestCase(BasicTestCase): compress = 1 complib = "lzo" type = 'int32' shape = (2, 2, 4) chunkshape = (5, 5, 5) slices = (slice(1, 2, 1), slice(None, None, None), slice(1, 4, 2)) class Ellipsis2CArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 2, 4) chunkshape = (5, 5, 5) slices = (slice(1, 2, 1), Ellipsis, slice(1, 4, 2)) @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class Slices3CArrayTestCase(BasicTestCase): compress = 1 # To show the chunks id DEBUG is on complib = "lzo" type = 'int32' shape = (2, 3, 4, 2) chunkshape = (5, 5, 5, 5) slices = (slice(1, 2, 1), slice( 0, None, None), slice(1, 4, 2)) # Don't work # slices = (slice(None, None, None), slice(0, None, None), # slice(1,4,1)) # W # slices = (slice(None, None, None), slice(None, None, None), # slice(1,4,2)) # N # slices = (slice(1,2,1), slice(None, None, None), slice(1,4,2)) # N # Disable the failing test temporarily with a working test case slices = (slice(1, 2, 1), slice(1, 4, None), slice(1, 4, 2)) # Y # slices = (slice(1,2,1), slice(0, 4, None), slice(1,4,1)) # Y slices = (slice(1, 2, 1), slice(0, 4, None), slice(1, 4, 2)) # N # slices = (slice(1,2,1), slice(0, 4, None), slice(1,4,2), # slice(0,100,1)) # N class Slices4CArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 4, 2, 5, 6) chunkshape = (5, 5, 5, 5, 5, 5) slices = (slice(1, 2, 1), slice(0, None, None), slice(1, 4, 2), slice(0, 4, 2), slice(3, 5, 2), slice(2, 7, 1)) class Ellipsis3CArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 4, 2) chunkshape = (5, 5, 5, 5) slices = (Ellipsis, slice(0, 4, None), slice(1, 4, 2)) slices = (slice(1, 2, 1), slice(0, 4, None), slice(1, 4, 2), Ellipsis) class Ellipsis4CArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 4, 5) chunkshape = (5, 5, 5, 5) slices = (Ellipsis, slice(0, 4, None), slice(1, 4, 2)) slices = (slice(1, 2, 1), Ellipsis, slice(1, 4, 2)) class Ellipsis5CArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 4, 5) chunkshape = (5, 5, 5, 5) slices = (slice(1, 2, 1), slice(0, 4, None), Ellipsis) class Ellipsis6CArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 4, 5) chunkshape = (5, 5, 5, 5) # The next slices gives problems with setting values (test03) # This is a problem on the test design, not the Array.__setitem__ # code, though. See # see test_earray.py Ellipsis6EArrayTestCase slices = (slice(1, 2, 1), slice(0, 4, None), 2, Ellipsis) class Ellipsis7CArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 4, 5) chunkshape = (5, 5, 5, 5) slices = (slice(1, 2, 1), slice(0, 4, None), slice(2, 3), Ellipsis) class MD3WriteTestCase(BasicTestCase): type = 'int32' shape = (2, 2, 3) chunkshape = (4, 4, 4) step = 2 class MD5WriteTestCase(BasicTestCase): type = 'int32' shape = (2, 2, 3, 4, 5) # ok # shape = (1, 1, 2, 1) # Minimum shape that shows problems with HDF5 1.6.1 # shape = (2, 3, 2, 4, 5) # Floating point exception (HDF5 1.6.1) # shape = (2, 3, 3, 2, 5, 6) # Segmentation fault (HDF5 1.6.1) chunkshape = (1, 1, 1, 1, 1) start = 1 stop = 10 step = 10 class MD6WriteTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 3, 2, 5, 6) chunkshape = (1, 1, 1, 1, 5, 6) start = 1 stop = 10 step = 3 class MD6WriteTestCase__(BasicTestCase): type = 'int32' shape = (2, 2) chunkshape = (1, 1) start = 1 stop = 3 step = 1 class MD7WriteTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 3, 4, 5, 2, 3) chunkshape = (10, 10, 10, 10, 10, 10, 10) start = 1 stop = 10 step = 2 class MD10WriteTestCase(BasicTestCase): type = 'int32' shape = (1, 2, 3, 4, 5, 5, 4, 3, 2, 2) chunkshape = (5, 5, 5, 5, 5, 5, 5, 5, 5, 5) start = -1 stop = -1 step = 10 class ZlibComprTestCase(BasicTestCase): compress = 1 complib = "zlib" start = 3 # stop = 0 # means last row stop = None # means last row from 0.8 on step = 10 class ZlibShuffleTestCase(BasicTestCase): shuffle = 1 compress = 1 complib = "zlib" # case start < stop , i.e. no rows read start = 3 stop = 1 step = 10 @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class BloscComprTestCase(BasicTestCase): compress = 1 # sss complib = "blosc" chunkshape = (10, 10) start = 3 stop = 10 step = 3 @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class BloscShuffleTestCase(BasicTestCase): shape = (20, 30) compress = 1 shuffle = 1 complib = "blosc" chunkshape = (100, 100) start = 3 stop = 10 step = 7 @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( common.blosc_version < common.min_blosc_bitshuffle_version, f'BLOSC >= {common.min_blosc_bitshuffle_version} required') class BloscBitShuffleTestCase(BasicTestCase): shape = (20, 30) compress = 1 bitshuffle = 1 complib = "blosc" chunkshape = (200, 100) start = 2 stop = 11 step = 7 @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class BloscFletcherTestCase(BasicTestCase): # see gh-21 shape = (200, 300) compress = 1 shuffle = 1 fletcher32 = 1 complib = "blosc" chunkshape = (100, 100) start = 3 stop = 10 step = 7 @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class BloscBloscLZTestCase(BasicTestCase): shape = (20, 30) compress = 1 shuffle = 1 complib = "blosc:blosclz" chunkshape = (200, 100) start = 2 stop = 11 step = 7 @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'lz4' not in tb.blosc_compressor_list(), 'lz4 required') class BloscLZ4TestCase(BasicTestCase): shape = (20, 30) compress = 1 shuffle = 1 complib = "blosc:lz4" chunkshape = (100, 100) start = 3 stop = 10 step = 7 @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'lz4' not in tb.blosc_compressor_list(), 'lz4 required') class BloscLZ4HCTestCase(BasicTestCase): shape = (20, 30) compress = 1 shuffle = 1 complib = "blosc:lz4hc" chunkshape = (100, 100) start = 3 stop = 10 step = 7 @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf('snappy' not in tb.blosc_compressor_list(), 'snappy required') class BloscSnappyTestCase(BasicTestCase): shape = (20, 30) compress = 1 shuffle = 1 complib = "blosc:snappy" chunkshape = (100, 100) start = 3 stop = 10 step = 7 @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'zlib' not in tb.blosc_compressor_list(), 'zlib required') class BloscZlibTestCase(BasicTestCase): shape = (20, 30) compress = 1 shuffle = 1 complib = "blosc:zlib" chunkshape = (100, 100) start = 3 stop = 10 step = 7 @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'zstd' not in tb.blosc_compressor_list(), 'zstd required') class BloscZstdTestCase(BasicTestCase): shape = (20, 30) compress = 1 shuffle = 1 complib = "blosc:zstd" chunkshape = (100, 100) start = 3 stop = 10 step = 7 @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class LZOComprTestCase(BasicTestCase): compress = 1 # sss complib = "lzo" chunkshape = (10, 10) start = 3 stop = 10 step = 3 @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class LZOShuffleTestCase(BasicTestCase): shape = (20, 30) compress = 1 shuffle = 1 complib = "lzo" chunkshape = (100, 100) start = 3 stop = 10 step = 7 @common.unittest.skipIf(not common.bzip2_avail, 'BZIP2 compression library not available') class Bzip2ComprTestCase(BasicTestCase): shape = (20, 30) compress = 1 complib = "bzip2" chunkshape = (100, 100) start = 3 stop = 10 step = 8 @common.unittest.skipIf(not common.bzip2_avail, 'BZIP2 compression library not available') class Bzip2ShuffleTestCase(BasicTestCase): shape = (20, 30) compress = 1 shuffle = 1 complib = "bzip2" chunkshape = (100, 100) start = 3 stop = 10 step = 6 class Fletcher32TestCase(BasicTestCase): shape = (60, 50) compress = 0 fletcher32 = 1 chunkshape = (50, 50) start = 4 stop = 20 step = 7 class AllFiltersTestCase(BasicTestCase): compress = 1 shuffle = 1 fletcher32 = 1 complib = "zlib" chunkshape = (20, 20) # sss start = 2 stop = 99 step = 6 class FloatTypeTestCase(BasicTestCase): type = 'float64' shape = (2, 2) chunkshape = (5, 5) start = 3 stop = 10 step = 20 class ComplexTypeTestCase(BasicTestCase): type = 'complex128' shape = (2, 2) chunkshape = (5, 5) start = 3 stop = 10 step = 20 class StringTestCase(BasicTestCase): type = "string" length = 20 shape = (2, 2) # shape = (2,2,20) chunkshape = (5, 5) start = 3 stop = 10 step = 20 slices = (slice(0, 1), slice(1, 2)) class String2TestCase(BasicTestCase): type = "string" length = 20 shape = (2, 20) chunkshape = (5, 5) start = 1 stop = 10 step = 2 class StringComprTestCase(BasicTestCase): type = "string" length = 20 shape = (20, 2, 10) # shape = (20,0,10,20) compr = 1 # shuffle = 1 # this shouldn't do nothing on chars chunkshape = (50, 50, 2) start = -1 stop = 100 step = 20 class Int8TestCase(BasicTestCase): type = "int8" shape = (2, 2) compress = 1 shuffle = 1 chunkshape = (50, 50) start = -1 stop = 100 step = 20 class Int16TestCase(BasicTestCase): type = "int16" shape = (2, 2) compress = 1 shuffle = 1 chunkshape = (50, 50) start = 1 stop = 100 step = 1 class Int32TestCase(BasicTestCase): type = "int32" shape = (2, 2) compress = 1 shuffle = 1 chunkshape = (50, 50) start = -1 stop = 100 step = 20 @common.unittest.skipUnless(hasattr(tb, 'Float16Atom'), 'Float16Atom not available') class Float16TestCase(BasicTestCase): type = "float16" shape = (200,) compress = 1 shuffle = 1 chunkshape = (20,) start = -1 stop = 100 step = 20 class Float32TestCase(BasicTestCase): type = "float32" shape = (200,) compress = 1 shuffle = 1 chunkshape = (20,) start = -1 stop = 100 step = 20 class Float64TestCase(BasicTestCase): type = "float64" shape = (200,) compress = 1 shuffle = 1 chunkshape = (20,) start = -1 stop = 100 step = 20 @common.unittest.skipUnless(hasattr(tb, 'Float96Atom'), 'Float96Atom not available') class Float96TestCase(BasicTestCase): type = "float96" shape = (200,) compress = 1 shuffle = 1 chunkshape = (20,) start = -1 stop = 100 step = 20 @common.unittest.skipUnless(hasattr(tb, 'Float128Atom'), 'Float128Atom not available') class Float128TestCase(BasicTestCase): type = "float128" shape = (200,) compress = 1 shuffle = 1 chunkshape = (20,) start = -1 stop = 100 step = 20 class Complex64TestCase(BasicTestCase): type = "complex64" shape = (4,) compress = 1 shuffle = 1 chunkshape = (2,) start = -1 stop = 100 step = 20 class Complex128TestCase(BasicTestCase): type = "complex128" shape = (20,) compress = 1 shuffle = 1 chunkshape = (2,) start = -1 stop = 100 step = 20 @common.unittest.skipUnless(hasattr(tb, 'Complex192Atom'), 'Complex192Atom not available') class Complex192TestCase(BasicTestCase): type = "complex192" shape = (20,) compress = 1 shuffle = 1 chunkshape = (2,) start = -1 stop = 100 step = 20 @common.unittest.skipUnless(hasattr(tb, 'Complex256Atom'), 'Complex256Atom not available') class Complex256TestCase(BasicTestCase): type = "complex256" shape = (20,) compress = 1 shuffle = 1 chunkshape = (2,) start = -1 stop = 100 step = 20 class ComprTestCase(BasicTestCase): type = "float64" compress = 1 shuffle = 1 shape = (200,) compr = 1 chunkshape = (21,) start = 51 stop = 100 step = 7 # this is a subset of the tests in test_array.py, mostly to verify that errors # are handled in the same way class ReadOutArgumentTests(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.size = 1000 self.filters = tb.Filters(complevel=1, complib='blosc') def create_array(self): array = np.arange(self.size, dtype='i8') disk_array = self.h5file.create_carray('/', 'array', atom=tb.Int64Atom(), shape=(self.size,), filters=self.filters) disk_array[:] = array return array, disk_array def test_read_entire_array(self): array, disk_array = self.create_array() out_buffer = np.empty((self.size, ), 'i8') disk_array.read(out=out_buffer) np.testing.assert_equal(out_buffer, array) def test_read_non_contiguous_buffer(self): array, disk_array = self.create_array() out_buffer = np.empty((self.size, ), 'i8') out_buffer_slice = out_buffer[0:self.size:2] with self.assertRaisesRegex(ValueError, 'output array not C contiguous'): disk_array.read(0, self.size, 2, out_buffer_slice) def test_buffer_too_small(self): array, disk_array = self.create_array() out_buffer = np.empty((self.size // 2, ), 'i8') self.assertRaises(ValueError, disk_array.read, 0, self.size, 1, out_buffer) try: disk_array.read(0, self.size, 1, out_buffer) except ValueError as exc: self.assertIn('output array size invalid, got', str(exc)) class SizeOnDiskInMemoryPropertyTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.array_size = (10_000, 10) # set chunkshape so it divides evenly into array_size, to avoid # partially filled chunks self.chunkshape = (1000, 10) # approximate size (in bytes) of non-data portion of hdf5 file self.hdf_overhead = 6000 def create_array(self, complevel): filters = tb.Filters(complevel=complevel, complib='blosc') self.array = self.h5file.create_carray('/', 'somearray', atom=tb.Int16Atom(), shape=self.array_size, filters=filters, chunkshape=self.chunkshape) def test_no_data(self): complevel = 0 self.create_array(complevel) self.assertEqual(self.array.size_on_disk, 0) self.assertEqual(self.array.size_in_memory, 10_000 * 10 * 2) def test_data_no_compression(self): complevel = 0 self.create_array(complevel) self.array[:] = 1 self.assertEqual(self.array.size_on_disk, 10_000 * 10 * 2) self.assertEqual(self.array.size_in_memory, 10_000 * 10 * 2) def test_highly_compressible_data(self): complevel = 1 self.create_array(complevel) self.array[:] = 1 self.h5file.flush() file_size = Path(self.h5fname).stat().st_size self.assertTrue( abs(self.array.size_on_disk - file_size) <= self.hdf_overhead) self.assertTrue(self.array.size_on_disk < self.array.size_in_memory) self.assertEqual(self.array.size_in_memory, 10_000 * 10 * 2) # XXX def test_random_data(self): complevel = 1 self.create_array(complevel) self.array[:] = np.random.randint(0, 1e6, self.array_size) self.h5file.flush() file_size = Path(self.h5fname).stat().st_size self.assertTrue( abs(self.array.size_on_disk - file_size) <= self.hdf_overhead) # XXX: check. The test fails if blosc is not available if tb.which_lib_version('blosc') is not None: self.assertAlmostEqual(self.array.size_on_disk, 10_000 * 10 * 2) else: self.assertTrue( abs(self.array.size_on_disk - 10_000 * 10 * 2) < 200) class OffsetStrideTestCase(common.TempFileMixin, common.PyTablesTestCase): compress = 0 complib = "zlib" # Default compression library def setUp(self): super().setUp() # Create an instance of an HDF5 Table self.rootgroup = self.h5file.root def test01a_String(self): """Checking carray with offseted NumPy strings appends.""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a_String..." % self.__class__.__name__) shape = (3, 2, 2) # Create an string atom carray = self.h5file.create_carray(root, 'strings', atom=tb.StringAtom(itemsize=3), shape=shape, title="Array of strings", chunkshape=(1, 2, 2)) a = np.array([[["a", "b"], ["123", "45"], ["45", "123"]]], dtype="S3") carray[0] = a[0, 1:] a = np.array([[["s", "a"], ["ab", "f"], ["s", "abc"], ["abc", "f"]]]) carray[1] = a[0, 2:] # Read all the data: data = carray.read() if common.verbose: print("Object read:", data) print("Nrows in", carray._v_pathname, ":", carray.nrows) print("Second row in carray ==>", data[1].tolist()) self.assertEqual(carray.nrows, 3) self.assertEqual(data[0].tolist(), [[b"123", b"45"], [b"45", b"123"]]) self.assertEqual(data[1].tolist(), [[b"s", b"abc"], [b"abc", b"f"]]) self.assertEqual(len(data[0]), 2) self.assertEqual(len(data[1]), 2) def test01b_String(self): """Checking carray with strided NumPy strings appends.""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b_String..." % self.__class__.__name__) shape = (3, 2, 2) # Create an string atom carray = self.h5file.create_carray(root, 'strings', atom=tb.StringAtom(itemsize=3), shape=shape, title="Array of strings", chunkshape=(1, 2, 2)) a = np.array([[["a", "b"], ["123", "45"], ["45", "123"]]], dtype="S3") carray[0] = a[0, ::2] a = np.array([[["s", "a"], ["ab", "f"], ["s", "abc"], ["abc", "f"]]]) carray[1] = a[0, ::2] # Read all the rows: data = carray.read() if common.verbose: print("Object read:", data) print("Nrows in", carray._v_pathname, ":", carray.nrows) print("Second row in carray ==>", data[1].tolist()) self.assertEqual(carray.nrows, 3) self.assertEqual(data[0].tolist(), [[b"a", b"b"], [b"45", b"123"]]) self.assertEqual(data[1].tolist(), [[b"s", b"a"], [b"s", b"abc"]]) self.assertEqual(len(data[0]), 2) self.assertEqual(len(data[1]), 2) def test02a_int(self): """Checking carray with offseted NumPy ints appends.""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test02a_int..." % self.__class__.__name__) shape = (3, 3) # Create an string atom carray = self.h5file.create_carray(root, 'CAtom', atom=tb.Int32Atom(), shape=shape, title="array of ints", chunkshape=(1, 3)) a = np.array([(0, 0, 0), (1, 0, 3), (1, 1, 1), (0, 0, 0)], dtype='int32') carray[0:2] = a[2:] # Introduce an offset a = np.array([(1, 1, 1), (-1, 0, 0)], dtype='int32') carray[2:3] = a[1:] # Introduce an offset # Read all the rows: data = carray.read() if common.verbose: print("Object read:", data) print("Nrows in", carray._v_pathname, ":", carray.nrows) print("Third row in carray ==>", data[2]) self.assertEqual(carray.nrows, 3) self.assertTrue(common.allequal( data[0], np.array([1, 1, 1], dtype='int32'))) self.assertTrue(common.allequal( data[1], np.array([0, 0, 0], dtype='int32'))) self.assertTrue(common.allequal( data[2], np.array([-1, 0, 0], dtype='int32'))) def test02b_int(self): """Checking carray with strided NumPy ints appends.""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test02b_int..." % self.__class__.__name__) shape = (3, 3) # Create an string atom carray = self.h5file.create_carray(root, 'CAtom', atom=tb.Int32Atom(), shape=shape, title="array of ints", chunkshape=(1, 3)) a = np.array([(0, 0, 0), (1, 0, 3), (1, 1, 1), (3, 3, 3)], dtype='int32') carray[0:2] = a[::3] # Create an offset a = np.array([(1, 1, 1), (-1, 0, 0)], dtype='int32') carray[2:3] = a[::2] # Create an offset # Read all the rows: data = carray.read() if common.verbose: print("Object read:", data) print("Nrows in", carray._v_pathname, ":", carray.nrows) print("Third row in carray ==>", data[2]) self.assertEqual(carray.nrows, 3) self.assertTrue(common.allequal( data[0], np.array([0, 0, 0], dtype='int32'))) self.assertTrue(common.allequal( data[1], np.array([3, 3, 3], dtype='int32'))) self.assertTrue(common.allequal( data[2], np.array([1, 1, 1], dtype='int32'))) class CopyTestCase(common.TempFileMixin, common.PyTablesTestCase): def test01a_copy(self): """Checking CArray.copy() method.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a_copy..." % self.__class__.__name__) # Create an CArray shape = (2, 2) atom = tb.Int16Atom() array1 = self.h5file.create_carray( self.h5file.root, 'array1', atom=atom, shape=shape, title="title array1", chunkshape=(2, 2)) array1[...] = np.array([[456, 2], [3, 457]], dtype='int16') if self.close: if common.verbose: print("(closing file version)") self._reopen(mode="a") array1 = self.h5file.root.array1 # Copy it to another location array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) # print("dirs-->", dir(array1), dir(array2)) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal self.assertTrue(common.allequal(array1.read(), array2.read())) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.extdim, array2.extdim) self.assertEqual(array1.flavor, array2.flavor) self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.atom.type, array2.atom.type) self.assertEqual(array1.title, array2.title) self.assertEqual(str(array1.atom), str(array2.atom)) # The next line is commented out because a copy should not # keep the same chunkshape anymore. # F. Alted 2006-11-27 # self.assertEqual(array1.chunkshape, array2.chunkshape) def test01b_copy(self): """Checking CArray.copy() method.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b_copy..." % self.__class__.__name__) # Create an CArray shape = (2, 2) atom = tb.Int16Atom() array1 = self.h5file.create_carray( self.h5file.root, 'array1', atom=atom, shape=shape, title="title array1", chunkshape=(5, 5)) array1[...] = np.array([[456, 2], [3, 457]], dtype='int16') if self.close: if common.verbose: print("(closing h5fname version)") self._reopen(mode="a") array1 = self.h5file.root.array1 # Copy it to another location array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing h5fname version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) # print("dirs-->", dir(array1), dir(array2)) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal self.assertTrue(common.allequal(array1.read(), array2.read())) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.extdim, array2.extdim) self.assertEqual(array1.flavor, array2.flavor) self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.atom.type, array2.atom.type) self.assertEqual(array1.title, array2.title) self.assertEqual(str(array1.atom), str(array2.atom)) # By default, the chunkshape should be the same self.assertEqual(array1.chunkshape, array2.chunkshape) def test01c_copy(self): """Checking CArray.copy() method.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01c_copy..." % self.__class__.__name__) # Create an CArray shape = (5, 5) atom = tb.Int16Atom() array1 = self.h5file.create_carray( self.h5file.root, 'array1', atom=atom, shape=shape, title="title array1", chunkshape=(2, 2)) array1[:2, :2] = np.array([[456, 2], [3, 457]], dtype='int16') if self.close: if common.verbose: print("(closing file version)") self._reopen(mode="a") array1 = self.h5file.root.array1 # Copy it to another location array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) # print("dirs-->", dir(array1), dir(array2)) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal self.assertTrue(common.allequal(array1.read(), array2.read())) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.extdim, array2.extdim) self.assertEqual(array1.flavor, array2.flavor) self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.atom.type, array2.atom.type) self.assertEqual(array1.title, array2.title) self.assertEqual(str(array1.atom), str(array2.atom)) # The next line is commented out because a copy should not # keep the same chunkshape anymore. # F. Alted 2006-11-27 # self.assertEqual(array1.chunkshape, array2.chunkshape) def test02_copy(self): """Checking CArray.copy() method (where specified)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_copy..." % self.__class__.__name__) # Create an CArray shape = (5, 5) atom = tb.Int16Atom() array1 = self.h5file.create_carray( self.h5file.root, 'array1', atom=atom, shape=shape, title="title array1", chunkshape=(2, 2)) array1[:2, :2] = np.array([[456, 2], [3, 457]], dtype='int16') if self.close: if common.verbose: print("(closing file version)") self._reopen(mode="a") array1 = self.h5file.root.array1 # Copy to another location group1 = self.h5file.create_group("/", "group1") array2 = array1.copy(group1, 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.group1.array2 if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) # print("dirs-->", dir(array1), dir(array2)) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal self.assertTrue(common.allequal(array1.read(), array2.read())) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.extdim, array2.extdim) self.assertEqual(array1.flavor, array2.flavor) self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.atom.type, array2.atom.type) self.assertEqual(array1.title, array2.title) self.assertEqual(str(array1.atom), str(array2.atom)) # The next line is commented out because a copy should not # keep the same chunkshape anymore. # F. Alted 2006-11-27 # self.assertEqual(array1.chunkshape, array2.chunkshape) def test03a_copy(self): """Checking CArray.copy() method (python flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03c_copy..." % self.__class__.__name__) shape = (2, 2) atom = tb.Int16Atom() array1 = self.h5file.create_carray( self.h5file.root, 'array1', atom=atom, shape=shape, title="title array1", chunkshape=(2, 2)) array1.flavor = "python" array1[...] = [[456, 2], [3, 457]] if self.close: if common.verbose: print("(closing file version)") self._reopen(mode="a") array1 = self.h5file.root.array1 # Copy to another location array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all elements are equal self.assertEqual(array1.read(), array2.read()) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.extdim, array2.extdim) self.assertEqual(array1.flavor, array2.flavor) # Very important here! self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.atom.type, array2.atom.type) self.assertEqual(array1.title, array2.title) self.assertEqual(str(array1.atom), str(array2.atom)) # The next line is commented out because a copy should not # keep the same chunkshape anymore. # F. Alted 2006-11-27 # self.assertEqual(array1.chunkshape, array2.chunkshape) def test03b_copy(self): """Checking CArray.copy() method (string python flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03d_copy..." % self.__class__.__name__) shape = (2, 2) atom = tb.StringAtom(itemsize=4) array1 = self.h5file.create_carray( self.h5file.root, 'array1', atom=atom, shape=shape, title="title array1", chunkshape=(2, 2)) array1.flavor = "python" array1[...] = [["456", "2"], ["3", "457"]] if self.close: if common.verbose: print("(closing file version)") self._reopen(mode="a") array1 = self.h5file.root.array1 # Copy to another location array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("type value-->", type(array2[:][0][0])) print("value-->", array2[:]) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all elements are equal self.assertEqual(array1.read(), array2.read()) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.extdim, array2.extdim) self.assertEqual(array1.flavor, array2.flavor) # Very important here! self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.atom.type, array2.atom.type) self.assertEqual(array1.title, array2.title) self.assertEqual(str(array1.atom), str(array2.atom)) # The next line is commented out because a copy should not # keep the same chunkshape anymore. # F. Alted 2006-11-27 # self.assertEqual(array1.chunkshape, array2.chunkshape) def test03c_copy(self): """Checking CArray.copy() method (chararray flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03e_copy..." % self.__class__.__name__) shape = (2, 2) atom = tb.StringAtom(itemsize=4) array1 = self.h5file.create_carray( self.h5file.root, 'array1', atom=atom, shape=shape, title="title array1", chunkshape=(2, 2)) array1[...] = np.array([["456", "2"], ["3", "457"]], dtype="S4") if self.close: if common.verbose: print("(closing file version)") self._reopen(mode="a") array1 = self.h5file.root.array1 # Copy to another location array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all elements are equal self.assertTrue(common.allequal(array1.read(), array2.read())) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.extdim, array2.extdim) self.assertEqual(array1.flavor, array2.flavor) # Very important here! self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.atom.type, array2.atom.type) self.assertEqual(array1.title, array2.title) self.assertEqual(str(array1.atom), str(array2.atom)) # The next line is commented out because a copy should not # keep the same chunkshape anymore. # F. Alted 2006-11-27 # self.assertEqual(array1.chunkshape, array2.chunkshape) def test04_copy(self): """Checking CArray.copy() method (checking title copying)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_copy..." % self.__class__.__name__) # Create an CArray shape = (2, 2) atom = tb.Int16Atom() array1 = self.h5file.create_carray( self.h5file.root, 'array1', atom=atom, shape=shape, title="title array1", chunkshape=(2, 2)) array1[...] = np.array([[456, 2], [3, 457]], dtype='int16') # Append some user attrs array1.attrs.attr1 = "attr1" array1.attrs.attr2 = 2 if self.close: if common.verbose: print("(closing file version)") self._reopen(mode="a") array1 = self.h5file.root.array1 # Copy it to another Array array2 = array1.copy('/', 'array2', title="title array2") if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 # Assert user attributes if common.verbose: print("title of destination array-->", array2.title) self.assertEqual(array2.title, "title array2") def test05_copy(self): """Checking CArray.copy() method (user attributes copied)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_copy..." % self.__class__.__name__) # Create an CArray shape = (2, 2) atom = tb.Int16Atom() array1 = self.h5file.create_carray( self.h5file.root, 'array1', atom=atom, shape=shape, title="title array1", chunkshape=(2, 2)) array1[...] = np.array([[456, 2], [3, 457]], dtype='int16') # Append some user attrs array1.attrs.attr1 = "attr1" array1.attrs.attr2 = 2 if self.close: if common.verbose: print("(closing file version)") self._reopen(mode="a") array1 = self.h5file.root.array1 # Copy it to another Array array2 = array1.copy('/', 'array2', copyuserattrs=1) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Assert user attributes self.assertEqual(array2.attrs.attr1, "attr1") self.assertEqual(array2.attrs.attr2, 2) def test05b_copy(self): """Checking CArray.copy() method (user attributes not copied)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05b_copy..." % self.__class__.__name__) # Create an Array shape = (2, 2) atom = tb.Int16Atom() array1 = self.h5file.create_carray( self.h5file.root, 'array1', atom=atom, shape=shape, title="title array1", chunkshape=(2, 2)) array1[...] = np.array([[456, 2], [3, 457]], dtype='int16') # Append some user attrs array1.attrs.attr1 = "attr1" array1.attrs.attr2 = 2 if self.close: if common.verbose: print("(closing file version)") self._reopen(mode="a") array1 = self.h5file.root.array1 # Copy it to another Array array2 = array1.copy('/', 'array2', copyuserattrs=0) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Assert user attributes self.assertEqual(hasattr(array2.attrs, "attr1"), 0) self.assertEqual(hasattr(array2.attrs, "attr2"), 0) class CloseCopyTestCase(CopyTestCase): close = 1 class OpenCopyTestCase(CopyTestCase): close = 0 class CopyIndexTestCase(common.TempFileMixin, common.PyTablesTestCase): nrowsinbuf = 2 def test01_index(self): """Checking CArray.copy() method with indexes.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_index..." % self.__class__.__name__) # Create an CArray shape = (100, 2) atom = tb.Int32Atom() array1 = self.h5file.create_carray( self.h5file.root, 'array1', atom=atom, shape=shape, title="title array1", chunkshape=(2, 2)) r = np.arange(200, dtype='int32') r.shape = shape array1[...] = r # Select a different buffer size: array1.nrowsinbuf = self.nrowsinbuf # Copy to another array array2 = array1.copy("/", 'array2', start=self.start, stop=self.stop, step=self.step) if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal r2 = r[self.start:self.stop:self.step] self.assertTrue(common.allequal(r2, array2.read())) # Assert the number of rows in array if common.verbose: print("nrows in array2-->", array2.nrows) print("and it should be-->", r2.shape[0]) # The next line is commented out because a copy should not # keep the same chunkshape anymore. # F. Alted 2006-11-27 # assert array1.chunkshape == array2.chunkshape self.assertEqual(r2.shape[0], array2.nrows) def _test02_indexclosef(self): """Checking CArray.copy() method with indexes (close file version)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_indexclosef..." % self.__class__.__name__) # Create an CArray shape = (100, 2) atom = tb.Int32Atom() array1 = self.h5file.create_carray( self.h5file.root, 'array1', atom=atom, shape=shape, title="title array1", chunkshape=(2, 2)) r = np.arange(200, dtype='int32') r.shape = shape array1[...] = r # Select a different buffer size: array1.nrowsinbuf = self.nrowsinbuf # Copy to another array array2 = array1.copy("/", 'array2', start=self.start, stop=self.stop, step=self.step) # Close and reopen the file self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal r2 = r[self.start:self.stop:self.step] self.assertEqual(array1.chunkshape, array2.chunkshape) self.assertTrue(common.allequal(r2, array2.read())) # Assert the number of rows in array if common.verbose: print("nrows in array2-->", array2.nrows) print("and it should be-->", r2.shape[0]) self.assertEqual(r2.shape[0], array2.nrows) class CopyIndex1TestCase(CopyIndexTestCase): nrowsinbuf = 1 start = 0 stop = 7 step = 1 class CopyIndex2TestCase(CopyIndexTestCase): nrowsinbuf = 2 start = 0 stop = -1 step = 1 class CopyIndex3TestCase(CopyIndexTestCase): nrowsinbuf = 3 start = 1 stop = 7 step = 1 class CopyIndex4TestCase(CopyIndexTestCase): nrowsinbuf = 4 start = 0 stop = 6 step = 1 class CopyIndex5TestCase(CopyIndexTestCase): nrowsinbuf = 2 start = 3 stop = 7 step = 1 class CopyIndex6TestCase(CopyIndexTestCase): nrowsinbuf = 2 start = 3 stop = 6 step = 2 class CopyIndex7TestCase(CopyIndexTestCase): start = 0 stop = 7 step = 10 class CopyIndex8TestCase(CopyIndexTestCase): start = 6 stop = -1 # Negative values means starting from the end step = 1 class CopyIndex9TestCase(CopyIndexTestCase): start = 3 stop = 4 step = 1 class CopyIndex10TestCase(CopyIndexTestCase): nrowsinbuf = 1 start = 3 stop = 4 step = 2 class CopyIndex11TestCase(CopyIndexTestCase): start = -3 stop = -1 step = 2 class CopyIndex12TestCase(CopyIndexTestCase): start = -1 # Should point to the last element stop = None # None should mean the last element (including it) step = 1 # The next test should be run only in **heavy** mode class Rows64bitsTestCase(common.TempFileMixin, common.PyTablesTestCase): narows = 1000 * 1000 # each array will have 1 million entries # narows = 1000 # for testing only nanumber = 1000 * 3 # That should account for more than 2**31-1 def setUp(self): super().setUp() # Create an CArray shape = (self.narows * self.nanumber,) array = self.h5file.create_carray( self.h5file.root, 'array', atom=tb.Int8Atom(), shape=shape, filters=tb.Filters(complib='lzo', complevel=1)) # Fill the array na = np.arange(self.narows, dtype='int8') # for i in xrange(self.nanumber): # s = slice(i * self.narows, (i + 1)*self.narows) # array[s] = na s = slice(0, self.narows) array[s] = na s = slice((self.nanumber-1)*self.narows, self.nanumber * self.narows) array[s] = na def test01_basiccheck(self): """Some basic checks for carrays exceeding 2**31 rows""" array = self.h5file.root.array if self.close: if common.verbose: # Check how many entries there are in the array print("Before closing") print("Entries:", array.nrows, type(array.nrows)) print("Entries:", array.nrows / (1000 * 1000), "Millions") print("Shape:", array.shape) # Re-open the file self._reopen() array = self.h5file.root.array if common.verbose: print("After re-open") # Check how many entries there are in the array if common.verbose: print("Entries:", array.nrows, type(array.nrows)) print("Entries:", array.nrows / (1000 * 1000), "Millions") print("Shape:", array.shape) print("Last 10 elements-->", array[-10:]) stop = self.narows % 256 if stop > 127: stop -= 256 start = stop - 10 # print("start, stop-->", start, stop) print("Should look like:", np.arange(start, stop, dtype='int8')) nrows = self.narows * self.nanumber # check nrows self.assertEqual(array.nrows, nrows) # Check shape self.assertEqual(array.shape, (nrows,)) # check the 10 first elements self.assertTrue(common.allequal( array[:10], np.arange(10, dtype='int8'))) # check the 10 last elements stop = self.narows % 256 if stop > 127: stop -= 256 start = stop - 10 self.assertTrue(common.allequal( array[-10:], np.arange(start, stop, dtype='int8'))) class Rows64bitsTestCase1(Rows64bitsTestCase): close = 0 class Rows64bitsTestCase2(Rows64bitsTestCase): close = 1 class BigArrayTestCase(common.TempFileMixin, common.PyTablesTestCase): shape = (3_000_000_000,) # more than 2**31-1 def setUp(self): super().setUp() # This should be fast since disk space isn't actually allocated, # so this case is OK for non-heavy test runs. self.h5file.create_carray('/', 'array', atom=tb.Int8Atom(), shape=self.shape) def test00_shape(self): """Check that the shape doesn't overflow.""" # See ticket #147. self.assertEqual(self.h5file.root.array.shape, self.shape) try: self.assertEqual(len(self.h5file.root.array), self.shape[0]) except OverflowError: # This can't be avoided in 32-bit platforms. self.assertTrue(self.shape[0] > np.iinfo(int).max, "Array length overflowed but ``int`` " "is wide enough.") def test01_shape_reopen(self): """Check that the shape doesn't overflow after reopening.""" self._reopen('r') self.test00_shape() # Test for default values when creating arrays. class DfltAtomTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_dflt(self): """Check that Atom.dflt is honored (string version).""" # Create a CArray with default values self.h5file.create_carray( '/', 'bar', atom=tb.StringAtom(itemsize=5, dflt=b"abdef"), shape=(10, 10)) if self.reopen: self._reopen() # Check the values values = self.h5file.root.bar[:] if common.verbose: print("Read values:", values) self.assertTrue(common.allequal( values, np.array(["abdef"] * 100, "S5").reshape(10, 10))) def test01_dflt(self): """Check that Atom.dflt is honored (int version).""" # Create a CArray with default values self.h5file.create_carray('/', 'bar', atom=tb.IntAtom(dflt=1), shape=(10, 10)) if self.reopen: self._reopen() # Check the values values = self.h5file.root.bar[:] if common.verbose: print("Read values:", values) self.assertTrue(common.allequal(values, np.ones((10, 10), "i4"))) def test02_dflt(self): """Check that Atom.dflt is honored (float version).""" # Create a CArray with default values self.h5file.create_carray( '/', 'bar', atom=tb.FloatAtom(dflt=1.134), shape=(10, 10)) if self.reopen: self._reopen() # Check the values values = self.h5file.root.bar[:] if common.verbose: print("Read values:", values) self.assertTrue(common.allequal(values, np.ones((10, 10), "f8")*1.134)) class DfltAtomNoReopen(DfltAtomTestCase): reopen = False class DfltAtomReopen(DfltAtomTestCase): reopen = True # Test for representation of defaults in atoms. Ticket #212. class AtomDefaultReprTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00a_zeros(self): """Testing default values. Zeros (scalar).""" N = () atom = tb.StringAtom(itemsize=3, shape=N, dflt=b"") ca = self.h5file.create_carray('/', 'test', atom=atom, shape=(1,)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Check the value if common.verbose: print("First row-->", repr(ca[0])) print("Defaults-->", repr(ca.atom.dflt)) self.assertTrue(common.allequal(ca[0], np.zeros(N, 'S3'))) self.assertTrue(common.allequal(ca.atom.dflt, np.zeros(N, 'S3'))) def test00b_zeros(self): """Testing default values. Zeros (array).""" N = 2 atom = tb.StringAtom(itemsize=3, shape=N, dflt=b"") ca = self.h5file.create_carray('/', 'test', atom=atom, shape=(1,)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Check the value if common.verbose: print("First row-->", ca[0]) print("Defaults-->", ca.atom.dflt) self.assertTrue(common.allequal(ca[0], np.zeros(N, 'S3'))) self.assertTrue(common.allequal(ca.atom.dflt, np.zeros(N, 'S3'))) def test01a_values(self): """Testing default values. Ones.""" N = 2 atom = tb.Int32Atom(shape=N, dflt=1) ca = self.h5file.create_carray('/', 'test', atom=atom, shape=(1,)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Check the value if common.verbose: print("First row-->", ca[0]) print("Defaults-->", ca.atom.dflt) self.assertTrue(common.allequal(ca[0], np.ones(N, 'i4'))) self.assertTrue(common.allequal(ca.atom.dflt, np.ones(N, 'i4'))) def test01b_values(self): """Testing default values. Generic value.""" N = 2 generic = 112.32 atom = tb.Float32Atom(shape=N, dflt=generic) ca = self.h5file.create_carray('/', 'test', atom=atom, shape=(1,)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Check the value if common.verbose: print("First row-->", ca[0]) print("Defaults-->", ca.atom.dflt) self.assertTrue(common.allequal(ca[0], np.ones(N, 'f4')*generic)) self.assertTrue(common.allequal( ca.atom.dflt, np.ones(N, 'f4')*generic)) def test02a_None(self): """Testing default values. None (scalar).""" N = () atom = tb.Int32Atom(shape=N, dflt=None) ca = self.h5file.create_carray('/', 'test', atom=atom, shape=(1,)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Check the value if common.verbose: print("First row-->", repr(ca[0])) print("Defaults-->", repr(ca.atom.dflt)) self.assertTrue(common.allequal(ca.atom.dflt, np.zeros(N, 'i4'))) def test02b_None(self): """Testing default values. None (array).""" N = 2 atom = tb.Int32Atom(shape=N, dflt=None) ca = self.h5file.create_carray('/', 'test', atom=atom, shape=(1,)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Check the value if common.verbose: print("First row-->", ca[0]) print("Defaults-->", ca.atom.dflt) self.assertTrue(common.allequal(ca.atom.dflt, np.zeros(N, 'i4'))) class AtomDefaultReprNoReopen(AtomDefaultReprTestCase): reopen = False class AtomDefaultReprReopen(AtomDefaultReprTestCase): reopen = True class TruncateTestCase(common.TempFileMixin, common.PyTablesTestCase): def test(self): """Test for unability to truncate Array objects.""" array1 = self.h5file.create_carray('/', 'array1', tb.IntAtom(), [2, 2]) self.assertRaises(TypeError, array1.truncate, 0) # Test for dealing with multidimensional atoms class MDAtomTestCase(common.TempFileMixin, common.PyTablesTestCase): def test01a_assign(self): """Assign a row to a (unidimensional) CArray with a MD atom.""" # Create an CArray ca = self.h5file.create_carray('/', 'test', atom=tb.Int32Atom((2, 2)), shape=(1,)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Assign one row ca[0] = [[1, 3], [4, 5]] self.assertEqual(ca.nrows, 1) if common.verbose: print("First row-->", ca[0]) self.assertTrue(common.allequal( ca[0], np.array([[1, 3], [4, 5]], 'i4'))) def test01b_assign(self): """Assign several rows to a (unidimensional) CArray with a MD atom.""" # Create an CArray ca = self.h5file.create_carray('/', 'test', atom=tb.Int32Atom((2, 2)), shape=(3,)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Assign three rows ca[:] = [[[1]], [[2]], [[3]]] # Simple broadcast self.assertEqual(ca.nrows, 3) if common.verbose: print("Third row-->", ca[2]) self.assertTrue(common.allequal( ca[2], np.array([[3, 3], [3, 3]], 'i4'))) def test02a_assign(self): """Assign a row to a (multidimensional) CArray with a MD atom.""" # Create an CArray ca = self.h5file.create_carray('/', 'test', atom=tb.Int32Atom((2,)), shape=(1, 3)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Assign one row ca[:] = [[[1, 3], [4, 5], [7, 9]]] self.assertEqual(ca.nrows, 1) if common.verbose: print("First row-->", ca[0]) self.assertTrue(common.allequal(ca[0], np.array( [[1, 3], [4, 5], [7, 9]], 'i4'))) def test02b_assign(self): """Assign several rows to a (multidimensional) CArray with a MD atom.""" # Create an CArray ca = self.h5file.create_carray('/', 'test', atom=tb.Int32Atom((2,)), shape=(3, 3)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Assign three rows ca[:] = [[[1, -3], [4, -5], [-7, 9]], [[-1, 3], [-4, 5], [7, -8]], [[-2, 3], [-5, 5], [7, -9]]] self.assertEqual(ca.nrows, 3) if common.verbose: print("Third row-->", ca[2]) self.assertTrue(common.allequal( ca[2], np.array([[-2, 3], [-5, 5], [7, -9]], 'i4'))) def test03a_MDMDMD(self): """Complex assign of a MD array in a MD CArray with a MD atom.""" # Create an CArray ca = self.h5file.create_carray( '/', 'test', atom=tb.Int32Atom((2, 4)), shape=(3, 2, 3)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Assign values # The shape of the atom should be added at the end of the arrays a = np.arange(2 * 3*2*4, dtype='i4').reshape((2, 3, 2, 4)) ca[:] = [a * 1, a*2, a*3] self.assertEqual(ca.nrows, 3) if common.verbose: print("Third row-->", ca[2]) self.assertTrue(common.allequal(ca[2], a * 3)) def test03b_MDMDMD(self): """Complex assign of a MD array in a MD CArray with a MD atom (II).""" # Create an CArray ca = self.h5file.create_carray( '/', 'test', atom=tb.Int32Atom((2, 4)), shape=(2, 3, 3)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Assign values # The shape of the atom should be added at the end of the arrays a = np.arange(2 * 3*3*2*4, dtype='i4').reshape((2, 3, 3, 2, 4)) ca[:] = a self.assertEqual(ca.nrows, 2) if common.verbose: print("Third row-->", ca[:, 2, ...]) self.assertTrue(common.allequal(ca[:, 2, ...], a[:, 2, ...])) def test03c_MDMDMD(self): """Complex assign of a MD array in a MD CArray with a MD atom (III).""" # Create an CArray ca = self.h5file.create_carray( '/', 'test', atom=tb.Int32Atom((2, 4)), shape=(3, 1, 2)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Assign values # The shape of the atom should be added at the end of the arrays a = np.arange(3 * 1*2*2*4, dtype='i4').reshape((3, 1, 2, 2, 4)) ca[:] = a self.assertEqual(ca.nrows, 3) if common.verbose: print("Second row-->", ca[:, :, 1, ...]) self.assertTrue(common.allequal(ca[:, :, 1, ...], a[:, :, 1, ...])) class MDAtomNoReopen(MDAtomTestCase): reopen = False class MDAtomReopen(MDAtomTestCase): reopen = True # Test for building very large MD atoms without defaults. Ticket #211. class MDLargeAtomTestCase(common.TempFileMixin, common.PyTablesTestCase): def test01_create(self): """Create a CArray with a very large MD atom.""" N = 2**16 # 4x larger than maximum object header size (64 KB) ca = self.h5file.create_carray('/', 'test', atom=tb.Int32Atom(shape=N), shape=(1,)) if self.reopen: self._reopen('a') ca = self.h5file.root.test # Check the value if common.verbose: print("First row-->", ca[0]) self.assertTrue(common.allequal(ca[0], np.zeros(N, 'i4'))) class MDLargeAtomNoReopen(MDLargeAtomTestCase): reopen = False class MDLargeAtomReopen(MDLargeAtomTestCase): reopen = True class AccessClosedTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.array = self.h5file.create_carray(self.h5file.root, 'array', atom=tb.Int32Atom(), shape=(10, 10)) self.array[...] = np.zeros((10, 10)) def test_read(self): self.h5file.close() self.assertRaises(tb.ClosedNodeError, self.array.read) def test_getitem(self): self.h5file.close() self.assertRaises(tb.ClosedNodeError, self.array.__getitem__, 0) def test_setitem(self): self.h5file.close() self.assertRaises(tb.ClosedNodeError, self.array.__setitem__, 0, 0) class TestCreateCArrayArgs(common.TempFileMixin, common.PyTablesTestCase): obj = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) where = '/' name = 'carray' atom = tb.Atom.from_dtype(obj.dtype) shape = obj.shape title = 'title' filters = None chunkshape = (1, 2) byteorder = None createparents = False def test_positional_args_01(self): self.h5file.create_carray(self.where, self.name, self.atom, self.shape, self.title, self.filters, self.chunkshape) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(np.zeros_like(self.obj), nparr)) def test_positional_args_02(self): ptarr = self.h5file.create_carray(self.where, self.name, self.atom, self.shape, self.title, self.filters, self.chunkshape) ptarr[...] = self.obj self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_positional_args_obj(self): self.h5file.create_carray(self.where, self.name, None, None, self.title, self.filters, self.chunkshape, self.byteorder, self.createparents, self.obj) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj(self): self.h5file.create_carray(self.where, self.name, title=self.title, chunkshape=self.chunkshape, obj=self.obj) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_atom_shape_01(self): ptarr = self.h5file.create_carray(self.where, self.name, title=self.title, chunkshape=self.chunkshape, atom=self.atom, shape=self.shape) ptarr[...] = self.obj self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_atom_shape_02(self): ptarr = self.h5file.create_carray(self.where, self.name, title=self.title, chunkshape=self.chunkshape, atom=self.atom, shape=self.shape) # ptarr[...] = self.obj self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(np.zeros_like(self.obj), nparr)) def test_kwargs_obj_atom(self): ptarr = self.h5file.create_carray(self.where, self.name, title=self.title, chunkshape=self.chunkshape, obj=self.obj, atom=self.atom) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj_shape(self): ptarr = self.h5file.create_carray(self.where, self.name, title=self.title, chunkshape=self.chunkshape, obj=self.obj, shape=self.shape) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj_atom_shape(self): ptarr = self.h5file.create_carray(self.where, self.name, title=self.title, chunkshape=self.chunkshape, obj=self.obj, atom=self.atom, shape=self.shape) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj_atom_error(self): atom = tb.Atom.from_dtype(np.dtype('complex')) # shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_carray, self.where, self.name, title=self.title, obj=self.obj, atom=atom) def test_kwargs_obj_shape_error(self): # atom = Atom.from_dtype(numpy.dtype('complex')) shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_carray, self.where, self.name, title=self.title, obj=self.obj, shape=shape) def test_kwargs_obj_atom_shape_error_01(self): atom = tb.Atom.from_dtype(np.dtype('complex')) # shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_carray, self.where, self.name, title=self.title, obj=self.obj, atom=atom, shape=self.shape) def test_kwargs_obj_atom_shape_error_02(self): # atom = Atom.from_dtype(numpy.dtype('complex')) shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_carray, self.where, self.name, title=self.title, obj=self.obj, atom=self.atom, shape=shape) def test_kwargs_obj_atom_shape_error_03(self): atom = tb.Atom.from_dtype(np.dtype('complex')) shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_carray, self.where, self.name, title=self.title, obj=self.obj, atom=atom, shape=shape) def suite(): theSuite = common.unittest.TestSuite() niter = 1 # common.heavy = 1 # uncomment this only for testing purposes # theSuite.addTest(unittest.makeSuite(BasicTestCase)) for n in range(niter): theSuite.addTest(common.unittest.makeSuite(BasicWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(BasicWrite2TestCase)) theSuite.addTest(common.unittest.makeSuite(BasicWrite3TestCase)) theSuite.addTest(common.unittest.makeSuite(BasicWrite4TestCase)) theSuite.addTest(common.unittest.makeSuite(BasicWrite5TestCase)) theSuite.addTest(common.unittest.makeSuite(BasicWrite6TestCase)) theSuite.addTest(common.unittest.makeSuite(BasicWrite7TestCase)) theSuite.addTest(common.unittest.makeSuite(BasicWrite8TestCase)) theSuite.addTest(common.unittest.makeSuite(EmptyCArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(EmptyCArray2TestCase)) theSuite.addTest(common.unittest.makeSuite(SlicesCArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Slices2CArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(EllipsisCArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Ellipsis2CArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Ellipsis3CArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(ZlibComprTestCase)) theSuite.addTest(common.unittest.makeSuite(ZlibShuffleTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscComprTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscShuffleTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscBitShuffleTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscFletcherTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscBloscLZTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscLZ4TestCase)) theSuite.addTest(common.unittest.makeSuite(BloscLZ4HCTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscSnappyTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscZlibTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscZstdTestCase)) theSuite.addTest(common.unittest.makeSuite(LZOComprTestCase)) theSuite.addTest(common.unittest.makeSuite(LZOShuffleTestCase)) theSuite.addTest(common.unittest.makeSuite(Bzip2ComprTestCase)) theSuite.addTest(common.unittest.makeSuite(Bzip2ShuffleTestCase)) theSuite.addTest(common.unittest.makeSuite(FloatTypeTestCase)) theSuite.addTest(common.unittest.makeSuite(ComplexTypeTestCase)) theSuite.addTest(common.unittest.makeSuite(StringTestCase)) theSuite.addTest(common.unittest.makeSuite(String2TestCase)) theSuite.addTest(common.unittest.makeSuite(StringComprTestCase)) theSuite.addTest(common.unittest.makeSuite(Int8TestCase)) theSuite.addTest(common.unittest.makeSuite(Int16TestCase)) theSuite.addTest(common.unittest.makeSuite(Int32TestCase)) theSuite.addTest(common.unittest.makeSuite(Float16TestCase)) theSuite.addTest(common.unittest.makeSuite(Float32TestCase)) theSuite.addTest(common.unittest.makeSuite(Float64TestCase)) theSuite.addTest(common.unittest.makeSuite(Float96TestCase)) theSuite.addTest(common.unittest.makeSuite(Float128TestCase)) theSuite.addTest(common.unittest.makeSuite(Complex64TestCase)) theSuite.addTest(common.unittest.makeSuite(Complex128TestCase)) theSuite.addTest(common.unittest.makeSuite(Complex192TestCase)) theSuite.addTest(common.unittest.makeSuite(Complex256TestCase)) theSuite.addTest(common.unittest.makeSuite(ComprTestCase)) theSuite.addTest(common.unittest.makeSuite(OffsetStrideTestCase)) theSuite.addTest(common.unittest.makeSuite(Fletcher32TestCase)) theSuite.addTest(common.unittest.makeSuite(AllFiltersTestCase)) theSuite.addTest(common.unittest.makeSuite(ReadOutArgumentTests)) theSuite.addTest(common.unittest.makeSuite( SizeOnDiskInMemoryPropertyTestCase)) theSuite.addTest(common.unittest.makeSuite(CloseCopyTestCase)) theSuite.addTest(common.unittest.makeSuite(OpenCopyTestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex1TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex2TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex3TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex4TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex5TestCase)) theSuite.addTest(common.unittest.makeSuite(BigArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(DfltAtomNoReopen)) theSuite.addTest(common.unittest.makeSuite(DfltAtomReopen)) theSuite.addTest(common.unittest.makeSuite(AtomDefaultReprNoReopen)) theSuite.addTest(common.unittest.makeSuite(AtomDefaultReprReopen)) theSuite.addTest(common.unittest.makeSuite(TruncateTestCase)) theSuite.addTest(common.unittest.makeSuite(MDAtomNoReopen)) theSuite.addTest(common.unittest.makeSuite(MDAtomReopen)) theSuite.addTest(common.unittest.makeSuite(MDLargeAtomNoReopen)) theSuite.addTest(common.unittest.makeSuite(MDLargeAtomReopen)) theSuite.addTest(common.unittest.makeSuite(AccessClosedTestCase)) theSuite.addTest(common.unittest.makeSuite(TestCreateCArrayArgs)) if common.heavy: theSuite.addTest(common.unittest.makeSuite(Slices3CArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Slices4CArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Ellipsis4CArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Ellipsis5CArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Ellipsis6CArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Ellipsis7CArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(MD3WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(MD5WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(MD6WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(MD7WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(MD10WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex6TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex7TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex8TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex9TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex10TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex11TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex12TestCase)) theSuite.addTest(common.unittest.makeSuite(Rows64bitsTestCase1)) theSuite.addTest(common.unittest.makeSuite(Rows64bitsTestCase2)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_create.py000066400000000000000000003030611416254111300205350ustar00rootroot00000000000000"""This test unit checks object creation funtions, like open_file, create_table, create_array or create_group. It also checks: - name identifiers in tree objects - title character limit for objects (255) - limit in number in table fields (255) """ import sys import hashlib import tempfile import warnings from pathlib import Path from packaging.version import Version import numpy as np import tables as tb from tables.tests import common class Record(tb.IsDescription): var1 = tb.StringCol(itemsize=4) # 4-character String var2 = tb.IntCol() # integer var3 = tb.Int16Col() # short integer var4 = tb.FloatCol() # double (double-precision) var5 = tb.Float32Col() # float (single-precision) class CreateTestCase(common.TempFileMixin, common.PyTablesTestCase): title = "This is the table title" expectedrows = 100 maxshort = 2 ** 15 maxint = 2_147_483_648 # (2 ** 31) compress = 0 def setUp(self): super().setUp() # Create an instance of HDF5 Table self.root = self.h5file.root # Create a table object self.table = self.h5file.create_table(self.root, 'atable', Record, "Table title") # Create an array object self.array = self.h5file.create_array(self.root, 'anarray', [1], "Array title") # Create a group object self.group = self.h5file.create_group(self.root, 'agroup', "Group title") def test00_isClass(self): """Testing table creation.""" self.assertIsInstance(self.table, tb.Table) self.assertIsInstance(self.array, tb.Array) self.assertIsInstance(self.array, tb.Leaf) self.assertIsInstance(self.group, tb.Group) def test01_overwriteNode(self): """Checking protection against node overwriting.""" try: self.array = self.h5file.create_array(self.root, 'anarray', [1], "Array title") except tb.NodeError: if common.verbose: (type, value, traceback) = sys.exc_info() print("\nGreat!, the next NameError was catched!") print(value) else: self.fail("expected a tables.NodeError") def test02_syntaxname(self): """Checking syntax in object tree names.""" with self.assertWarns(tb.NaturalNameWarning): self.array = self.h5file.create_array(self.root, ' array', [1], "Array title") # another name error with self.assertWarns(tb.NaturalNameWarning): self.array = self.h5file.create_array(self.root, '$array', [1], "Array title") # Finally, test a reserved word with self.assertWarns(tb.NaturalNameWarning): self.array = self.h5file.create_array(self.root, 'for', [1], "Array title") def test03a_titleAttr(self): """Checking the self.title attr in nodes.""" # Close the opened file to destroy the object tree self._reopen() # Now, test that self.title exists and is correct in all the nodes self.assertEqual(self.h5file.root.agroup._v_title, "Group title") self.assertEqual(self.h5file.root.atable.title, "Table title") self.assertEqual(self.h5file.root.anarray.title, "Array title") def test03b_titleLength(self): """Checking large title character length limit (1023)""" titlelength = 1023 # Try to put a very long title on a group object group = self.h5file.create_group(self.root, 'group', "t" * titlelength) self.assertEqual(group._v_title, "t" * titlelength) self.assertEqual(group._f_getattr('TITLE'), "t" * titlelength) # Now, try with a table object table = self.h5file.create_table(self.root, 'table', Record, "t" * titlelength) self.assertEqual(table.title, "t" * titlelength) self.assertEqual(table.get_attr("TITLE"), "t" * titlelength) # Finally, try with an Array object arr = self.h5file.create_array(self.root, 'arr', [1], "t" * titlelength) self.assertEqual(arr.title, "t" * titlelength) self.assertEqual(arr.get_attr("TITLE"), "t" * titlelength) def test04_maxFields(self): """Checking a large number of fields in tables""" # The number of fields for a table varnumber = tb.parameters.MAX_COLUMNS varnames = [] for i in range(varnumber): varnames.append('int%d' % i) # Build a dictionary with the types as values and varnames as keys recordDict = {} i = 0 for varname in varnames: recordDict[varname] = tb.Col.from_type("int32", dflt=1, pos=i) i += 1 # Append this entry to indicate the alignment! recordDict['_v_align'] = "=" table = self.h5file.create_table(self.root, 'table', recordDict, "MetaRecord instance") row = table.row listrows = [] # Write 10 records for j in range(10): rowlist = [] for i in range(len(table.colnames)): row[varnames[i]] = i * j rowlist.append(i * j) row.append() listrows.append(tuple(rowlist)) # write data on disk table.flush() # Read all the data as a list listout = table.read().tolist() # Compare the input rowlist and output row list. They should # be equal. if common.verbose: print("Original row list:", listrows[-1]) print("Retrieved row list:", listout[-1]) self.assertEqual(listrows, listout) # The next limitation has been released. A warning is still there, though def test05_maxFieldsExceeded(self): """Checking an excess of the maximum number of fields in tables""" # The number of fields for a table varnumber = tb.parameters.MAX_COLUMNS + 1 varnames = [] for i in range(varnumber): varnames.append('int%d' % i) # Build a dictionary with the types as values and varnames as keys recordDict = {} i = 0 for varname in varnames: recordDict[varname] = tb.Col.from_type("int32", dflt=1) i += 1 # Now, create a table with this record object # This way of creating node objects has been deprecated # table = Table(recordDict, "MetaRecord instance") # Attach the table to object tree warnings.filterwarnings("error", category=tb.PerformanceWarning) # Here, a tables.PerformanceWarning should be raised! try: self.h5file.create_table(self.root, 'table', recordDict, "MetaRecord instance") except tb.PerformanceWarning: if common.verbose: (type, value, traceback) = sys.exc_info() print("\nGreat!, the next PerformanceWarning was catched!") print(value) else: self.fail("expected an tables.PerformanceWarning") # Reset the warning warnings.filterwarnings("default", category=tb.PerformanceWarning) # The next limitation has been released def _test06_maxColumnNameLengthExceeded(self): """Checking an excess (256) of the maximum length in column names""" # Build a dictionary with the types as values and varnames as keys recordDict = {} recordDict["a" * 255] = tb.IntCol(dflt=1) recordDict["b" * 256] = tb.IntCol(dflt=1) # Should raise a ValueError # Now, create a table with this record object # This way of creating node objects has been deprecated table = tb.Table(recordDict, "MetaRecord instance") self.assertIsNotNone(table) # Attach the table to object tree # Here, ValueError should be raised! with self.assertRaises(ValueError): self.h5file.create_table(self.root, 'table', recordDict, "MetaRecord instance") def test06_noMaxColumnNameLength(self): """Checking unlimited length in column names""" # Build a dictionary with the types as values and varnames as keys recordDict = {} recordDict["a" * 255] = tb.IntCol(dflt=1, pos=0) recordDict["b" * 1024] = tb.IntCol(dflt=1, pos=1) # Should work well # Attach the table to object tree # Here, IndexError should be raised! table = self.h5file.create_table(self.root, 'table', recordDict, "MetaRecord instance") self.assertEqual(table.colnames[0], "a" * 255) self.assertEqual(table.colnames[1], "b" * 1024) class Record2(tb.IsDescription): var1 = tb.StringCol(itemsize=4) # 4-character String var2 = tb.IntCol() # integer var3 = tb.Int16Col() # short integer class FiltersTreeTestCase(common.TempFileMixin, common.PyTablesTestCase): title = "A title" nrows = 10 def setUp(self): super().setUp() self.populateFile() def populateFile(self): group = self.h5file.root # Create a tree with three levels of depth for j in range(5): # Create a table table = self.h5file.create_table(group, 'table1', Record2, title=self.title, filters=None) # Get the record object associated with the new table d = table.row # Fill the table for i in range(self.nrows): d['var1'] = '%04d' % (self.nrows - i) d['var2'] = i d['var3'] = i * 2 d.append() # This injects the Record values # Flush the buffer for this table table.flush() # Create a couple of arrays in each group var1List = [x['var1'] for x in table.iterrows()] var3List = [x['var3'] for x in table.iterrows()] self.h5file.create_array(group, 'array1', var1List, "col 1") self.h5file.create_array(group, 'array2', var3List, "col 3") # Create a couple of EArrays as well ea1 = self.h5file.create_earray(group, 'earray1', tb.StringAtom(itemsize=4), (0,), "col 1") ea2 = self.h5file.create_earray(group, 'earray2', tb.Int16Atom(), (0,), "col 3") # And fill them with some values ea1.append(var1List) ea2.append(var3List) # Finally a couple of VLArrays too vla1 = self.h5file.create_vlarray( group, 'vlarray1', tb.StringAtom(itemsize=4), "col 1") vla2 = self.h5file.create_vlarray( group, 'vlarray2', tb.Int16Atom(), "col 3") # And fill them with some values vla1.append(var1List) vla2.append(var3List) # Create a new group (descendant of group) if j == 1: # The second level group2 = self.h5file.create_group(group, 'group' + str(j), filters=self.gfilters) elif j == 2: # third level group2 = self.h5file.create_group(group, 'group' + str(j)) else: # The rest of levels group2 = self.h5file.create_group(group, 'group' + str(j), filters=self.filters) # Iterate over this new group (group2) group = group2 def test00_checkFilters(self): """Checking inheritance of filters on trees (open file version)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_checkFilters..." % self.__class__.__name__) # First level check if common.verbose: print("Test filter:", repr(self.filters)) print("Filters in file:", repr(self.h5file.filters)) if self.filters is None: filters = tb.Filters() else: filters = self.filters self.assertEqual(repr(filters), repr(self.h5file.filters)) # The next nodes have to have the same filter properties as # self.filters nodelist = [ '/table1', '/group0/earray1', '/group0/vlarray1', '/group0', ] for node in nodelist: obj = self.h5file.get_node(node) if isinstance(obj, tb.Group): self.assertEqual(repr(filters), repr(obj._v_filters)) else: self.assertEqual(repr(filters), repr(obj.filters)) # Second and third level check group1 = self.h5file.root.group0.group1 if self.gfilters is None: if self.filters is None: gfilters = tb.Filters() else: gfilters = self.filters else: gfilters = self.gfilters if common.verbose: print("Test gfilter:", repr(gfilters)) print("Filters in file:", repr(group1._v_filters)) self.assertEqual(repr(gfilters), repr(group1._v_filters)) # The next nodes have to have the same filter properties as # gfilters nodelist = [ '/group0/group1', '/group0/group1/earray1', '/group0/group1/vlarray1', '/group0/group1/table1', '/group0/group1/group2/table1', ] for node in nodelist: obj = self.h5file.get_node(node) if isinstance(obj, tb.Group): self.assertEqual(repr(gfilters), repr(obj._v_filters)) else: self.assertEqual(repr(gfilters), repr(obj.filters)) # Fourth and fifth level check if self.filters is None: # If None, the filters are inherited! if self.gfilters is None: filters = tb.Filters() else: filters = self.gfilters else: filters = self.filters group3 = self.h5file.root.group0.group1.group2.group3 if common.verbose: print("Test filter:", repr(filters)) print("Filters in file:", repr(group3._v_filters)) self.assertEqual(repr(filters), repr(group3._v_filters)) # The next nodes have to have the same filter properties as # self.filter nodelist = [ '/group0/group1/group2/group3', '/group0/group1/group2/group3/earray1', '/group0/group1/group2/group3/vlarray1', '/group0/group1/group2/group3/table1', '/group0/group1/group2/group3/group4', ] for node in nodelist: obj = self.h5file.get_node(node) if isinstance(obj, tb.Group): self.assertEqual(repr(filters), repr(obj._v_filters)) else: self.assertEqual(repr(filters), repr(obj.filters)) # Checking the special case for Arrays in which the compression # should always be the empty Filter() # The next nodes have to have the same filter properties as # Filter() nodelist = [ '/array1', '/group0/array1', '/group0/group1/array1', '/group0/group1/group2/array1', '/group0/group1/group2/group3/array1', ] for node in nodelist: obj = self.h5file.get_node(node) self.assertEqual(repr(tb.Filters()), repr(obj.filters)) def test01_checkFilters(self): """Checking inheritance of filters on trees (close file version)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_checkFilters..." % self.__class__.__name__) # Close the file self._reopen() # First level check if self.filters is None: filters = tb.Filters() else: filters = self.filters if common.verbose: print("Test filter:", repr(filters)) print("Filters in file:", repr(self.h5file.filters)) self.assertEqual(repr(filters), repr(self.h5file.filters)) # The next nodes have to have the same filter properties as # self.filters nodelist = [ '/table1', '/group0/earray1', '/group0/vlarray1', '/group0', ] for node in nodelist: object_ = self.h5file.get_node(node) if isinstance(object_, tb.Group): self.assertEqual(repr(filters), repr(object_._v_filters)) else: self.assertEqual(repr(filters), repr(object_.filters)) # Second and third level check group1 = self.h5file.root.group0.group1 if self.gfilters is None: if self.filters is None: gfilters = tb.Filters() else: gfilters = self.filters else: gfilters = self.gfilters if common.verbose: print("Test filter:", repr(gfilters)) print("Filters in file:", repr(group1._v_filters)) self.assertEqual(repr(gfilters), repr(group1._v_filters)) # The next nodes have to have the same filter properties as # gfilters nodelist = [ '/group0/group1', '/group0/group1/earray1', '/group0/group1/vlarray1', '/group0/group1/table1', '/group0/group1/group2/table1', ] for node in nodelist: object_ = self.h5file.get_node(node) if isinstance(object_, tb.Group): self.assertEqual(repr(gfilters), repr(object_._v_filters)) else: self.assertEqual(repr(gfilters), repr(object_.filters)) # Fourth and fifth level check if self.filters is None: if self.gfilters is None: filters = tb.Filters() else: filters = self.gfilters else: filters = self.filters group3 = self.h5file.root.group0.group1.group2.group3 if common.verbose: print("Test filter:", repr(filters)) print("Filters in file:", repr(group3._v_filters)) repr(filters) == repr(group3._v_filters) # The next nodes have to have the same filter properties as # self.filters nodelist = [ '/group0/group1/group2/group3', '/group0/group1/group2/group3/earray1', '/group0/group1/group2/group3/vlarray1', '/group0/group1/group2/group3/table1', '/group0/group1/group2/group3/group4', ] for node in nodelist: obj = self.h5file.get_node(node) if isinstance(obj, tb.Group): self.assertEqual(repr(filters), repr(obj._v_filters)) else: self.assertEqual(repr(filters), repr(obj.filters)) # Checking the special case for Arrays in which the compression # should always be the empty Filter() # The next nodes have to have the same filter properties as # Filter() nodelist = [ '/array1', '/group0/array1', '/group0/group1/array1', '/group0/group1/group2/array1', '/group0/group1/group2/group3/array1', ] for node in nodelist: obj = self.h5file.get_node(node) self.assertEqual(repr(tb.Filters()), repr(obj.filters)) class FiltersCase1(FiltersTreeTestCase): filters = tb.Filters() gfilters = tb.Filters(complevel=1) open_kwargs = dict(filters=filters) @common.unittest.skipIf(not common.bzip2_avail, 'BZIP2 compression library not available') class FiltersCase2(FiltersTreeTestCase): filters = tb.Filters(complevel=1, complib="bzip2") gfilters = tb.Filters(complevel=1) open_kwargs = dict(filters=filters) @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class FiltersCase3(FiltersTreeTestCase): filters = tb.Filters(shuffle=True, complib="zlib") gfilters = tb.Filters(complevel=1, shuffle=False, complib="lzo") open_kwargs = dict(filters=filters) class FiltersCase4(FiltersTreeTestCase): filters = tb.Filters(shuffle=True) gfilters = tb.Filters(complevel=1, shuffle=False) open_kwargs = dict(filters=filters) class FiltersCase5(FiltersTreeTestCase): filters = tb.Filters(fletcher32=True) gfilters = tb.Filters(complevel=1, shuffle=False) open_kwargs = dict(filters=filters) class FiltersCase6(FiltersTreeTestCase): filters = None gfilters = tb.Filters(complevel=1, shuffle=False) open_kwargs = dict(filters=filters) class FiltersCase7(FiltersTreeTestCase): filters = tb.Filters(complevel=1) gfilters = None open_kwargs = dict(filters=filters) class FiltersCase8(FiltersTreeTestCase): filters = None gfilters = None open_kwargs = dict(filters=filters) @common.unittest.skipIf(not common.bzip2_avail, 'BZIP2 compression library not available') class FiltersCase9(FiltersTreeTestCase): filters = tb.Filters(shuffle=True, complib="zlib") gfilters = tb.Filters(complevel=5, shuffle=True, complib="bzip2") open_kwargs = dict(filters=filters) @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class FiltersCase10(FiltersTreeTestCase): filters = tb.Filters(shuffle=False, complevel=1, complib="blosc") gfilters = tb.Filters(complevel=5, shuffle=True, complib="blosc") open_kwargs = dict(filters=filters) @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class FiltersCaseBloscBloscLZ(FiltersTreeTestCase): filters = tb.Filters(shuffle=False, complevel=1, complib="blosc:blosclz") gfilters = tb.Filters(complevel=5, shuffle=True, complib="blosc:blosclz") open_kwargs = dict(filters=filters) @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'lz4' not in tb.blosc_compressor_list(), 'lz4 required') class FiltersCaseBloscLZ4(FiltersTreeTestCase): def setUp(self): self.filters = tb.Filters(shuffle=False, complevel=1, complib="blosc:lz4") self.gfilters = tb.Filters(complevel=5, shuffle=True, complib="blosc:lz4") self.open_kwargs = dict(filters=self.filters) super().setUp() @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'lz4' not in tb.blosc_compressor_list(), 'lz4 required') class FiltersCaseBloscLZ4HC(FiltersTreeTestCase): def setUp(self): self.filters = tb.Filters( shuffle=False, complevel=1, complib="blosc:lz4hc") self.gfilters = tb.Filters( complevel=5, shuffle=True, complib="blosc:lz4hc") self.open_kwargs = dict(filters=self.filters) super().setUp() @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf('snappy' not in tb.blosc_compressor_list(), 'snappy required') class FiltersCaseBloscSnappy(FiltersTreeTestCase): def setUp(self): self.filters = tb.Filters( shuffle=False, complevel=1, complib="blosc:snappy") self.gfilters = tb.Filters( complevel=5, shuffle=True, complib="blosc:snappy") self.open_kwargs = dict(filters=self.filters) super().setUp() @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'zlib' not in tb.blosc_compressor_list(), 'zlib required') class FiltersCaseBloscZlib(FiltersTreeTestCase): def setUp(self): self.filters = tb.Filters(shuffle=False, complevel=1, complib="blosc:zlib") self.gfilters = tb.Filters(complevel=5, shuffle=True, complib="blosc:zlib") self.open_kwargs = dict(filters=self.filters) super().setUp() @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'zstd' not in tb.blosc_compressor_list(), 'zstd required') class FiltersCaseBloscZstd(FiltersTreeTestCase): def setUp(self): self.filters = tb.Filters(shuffle=False, complevel=1, complib="blosc:zstd") self.gfilters = tb.Filters(complevel=5, shuffle=True, complib="blosc:zstd") self.open_kwargs = dict(filters=self.filters) super().setUp() @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( common.blosc_version < common.min_blosc_bitshuffle_version, f'BLOSC >= {common.min_blosc_bitshuffle_version} required') class FiltersCaseBloscBitShuffle(FiltersTreeTestCase): filters = tb.Filters(shuffle=False, complevel=1, complib="blosc:blosclz") gfilters = tb.Filters(complevel=5, shuffle=False, bitshuffle=True, complib="blosc:blosclz") open_kwargs = dict(filters=filters) # print("version:", tables.which_lib_version("blosc")[1]) class CopyGroupTestCase(common.TempFileMixin, common.PyTablesTestCase): title = "A title" nrows = 10 def setUp(self): super().setUp() # Create a temporary file self.h5fname2 = tempfile.mktemp(".h5") # Create the destination self.h5file2 = tb.open_file(self.h5fname2, "w") self.populateFile() def populateFile(self): group = self.h5file.root # Add some user attrs: group._v_attrs.attr1 = "an string for root group" group._v_attrs.attr2 = 124 # Create a tree for group_i in range(5): for bgroup_i in range(2): # Create a new group (brother of group) group2 = self.h5file.create_group(group, 'bgroup' + str(bgroup_i), filters=None) # Create a table table = self.h5file.create_table(group2, 'table1', Record2, title=self.title, filters=None) # Get the record object associated with the new table d = table.row # Fill the table for row_i in range(self.nrows): d['var1'] = '%04d' % (self.nrows - row_i) d['var2'] = row_i d['var3'] = row_i * 2 d.append() # This injects the Record values # Flush the buffer for this table table.flush() # Add some user attrs: table.attrs.attr1 = "an string" table.attrs.attr2 = 234 # Create a couple of arrays in each group var1List = [x['var1'] for x in table.iterrows()] var3List = [x['var3'] for x in table.iterrows()] self.h5file.create_array(group2, 'array1', var1List, "col 1") self.h5file.create_array(group2, 'array2', var3List, "col 3") # Create a couple of EArrays as well ea1 = self.h5file.create_earray(group2, 'earray1', tb.StringAtom(itemsize=4), (0,), "col 1") ea2 = self.h5file.create_earray(group2, 'earray2', tb.Int16Atom(), (0,), "col 3") # Add some user attrs: ea1.attrs.attr1 = "an string for earray" ea2.attrs.attr2 = 123 # And fill them with some values ea1.append(var1List) ea2.append(var3List) # Create a new group (descendant of group) group3 = self.h5file.create_group(group, 'group' + str(group_i), filters=None) # Iterate over this new group (group3) group = group3 # Add some user attrs: group._v_attrs.attr1 = "an string for group" group._v_attrs.attr2 = 124 def tearDown(self): # Close the file if self.h5file2.isopen: self.h5file2.close() Path(self.h5fname2).unlink() super().tearDown() def test00_nonRecursive(self): """Checking non-recursive copy of a Group""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_nonRecursive..." % self.__class__.__name__) # Copy a group non-recursively srcgroup = self.h5file.root.group0.group1 # srcgroup._f_copy_children(self.h5file2.root, recursive=False, # filters=self.filters) self.h5file.copy_children(srcgroup, self.h5file2.root, recursive=False, filters=self.filters) if self.close: # Close the destination file self.h5file2.close() # And open it again self.h5file2 = tb.open_file(self.h5fname2, "r") # Check that the copy has been done correctly dstgroup = self.h5file2.root nodelist1 = list(srcgroup._v_children) nodelist2 = list(dstgroup._v_children) # Sort the lists nodelist1.sort() nodelist2.sort() if common.verbose: print("The origin node list -->", nodelist1) print("The copied node list -->", nodelist2) self.assertEqual(srcgroup._v_nchildren, dstgroup._v_nchildren) self.assertEqual(nodelist1, nodelist2) def test01_nonRecursiveAttrs(self): """Checking non-recursive copy of a Group (attributes copied)""" if common.verbose: print('\n', '-=' * 30) print(f"Running {self.__class__.__name__}" f".test01_nonRecursiveAttrs...") # Copy a group non-recursively with attrs srcgroup = self.h5file.root.group0.group1 srcgroup._f_copy_children(self.h5file2.root, recursive=False, filters=self.filters, copyuserattrs=1) if self.close: # Close the destination file self.h5file2.close() # And open it again self.h5file2 = tb.open_file(self.h5fname2, "r") # Check that the copy has been done correctly dstgroup = self.h5file2.root for srcnode in srcgroup: dstnode = getattr(dstgroup, srcnode._v_name) if isinstance(srcnode, tb.Group): srcattrs = srcnode._v_attrs srcattrskeys = srcattrs._f_list("all") dstattrs = dstnode._v_attrs dstattrskeys = dstattrs._f_list("all") else: srcattrs = srcnode.attrs srcattrskeys = srcattrs._f_list("all") dstattrs = dstnode.attrs dstattrskeys = dstattrs._f_list("all") # Filters may differ, do not take into account if self.filters is not None: dstattrskeys.remove('FILTERS') # These lists should already be ordered if common.verbose: print(f"srcattrskeys for node {srcnode._v_name}: " f"{srcattrskeys}") print(f"dstattrskeys for node {dstnode._v_name}: " f"{dstattrskeys}") self.assertEqual(srcattrskeys, dstattrskeys) if common.verbose: print("The attrs names has been copied correctly") # Now, for the contents of attributes for srcattrname in srcattrskeys: srcattrvalue = str(getattr(srcattrs, srcattrname)) dstattrvalue = str(getattr(dstattrs, srcattrname)) self.assertEqual(srcattrvalue, dstattrvalue) if self.filters is not None: self.assertEqual(dstattrs.FILTERS, self.filters) if common.verbose: print("The attrs contents has been copied correctly") def test02_Recursive(self): """Checking recursive copy of a Group""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_Recursive..." % self.__class__.__name__) # Create the destination node group = self.h5file2.root for groupname in self.dstnode.split("/"): if groupname: group = self.h5file2.create_group(group, groupname) dstgroup = self.h5file2.get_node(self.dstnode) # Copy a group non-recursively srcgroup = self.h5file.get_node(self.srcnode) self.h5file.copy_children(srcgroup, dstgroup, recursive=True, filters=self.filters) lenSrcGroup = len(srcgroup._v_pathname) if lenSrcGroup == 1: lenSrcGroup = 0 # Case where srcgroup == "/" if self.close: # Close the destination file self.h5file2.close() # And open it again self.h5file2 = tb.open_file(self.h5fname2, "r") dstgroup = self.h5file2.get_node(self.dstnode) # Check that the copy has been done correctly lenDstGroup = len(dstgroup._v_pathname) if lenDstGroup == 1: lenDstGroup = 0 # Case where dstgroup == "/" first = 1 nodelist1 = [] for node in srcgroup._f_walknodes(): if first: # skip the first group first = 0 continue nodelist1.append(node._v_pathname[lenSrcGroup:]) first = 1 nodelist2 = [] for node in dstgroup._f_walknodes(): if first: # skip the first group first = 0 continue nodelist2.append(node._v_pathname[lenDstGroup:]) if common.verbose: print("The origin node list -->", nodelist1) print("The copied node list -->", nodelist2) self.assertEqual(nodelist1, nodelist2) def test03_RecursiveFilters(self): """Checking recursive copy of a Group (cheking Filters)""" if common.verbose: print('\n', '-=' * 30) print(f"Running {self.__class__.__name__}" f".test03_RecursiveFilters...") # Create the destination node group = self.h5file2.root for groupname in self.dstnode.split("/"): if groupname: group = self.h5file2.create_group(group, groupname) dstgroup = self.h5file2.get_node(self.dstnode) # Copy a group non-recursively srcgroup = self.h5file.get_node(self.srcnode) srcgroup._f_copy_children(dstgroup, recursive=True, filters=self.filters) lenSrcGroup = len(srcgroup._v_pathname) if lenSrcGroup == 1: lenSrcGroup = 0 # Case where srcgroup == "/" if self.close: # Close the destination file self.h5file2.close() # And open it again self.h5file2 = tb.open_file(self.h5fname2, "r") dstgroup = self.h5file2.get_node(self.dstnode) # Check that the copy has been done correctly lenDstGroup = len(dstgroup._v_pathname) if lenDstGroup == 1: lenDstGroup = 0 # Case where dstgroup == "/" first = 1 nodelist1 = {} for node in srcgroup._f_walknodes(): if first: # skip the first group first = 0 continue nodelist1[node._v_name] = node._v_pathname[lenSrcGroup:] first = 1 for node in dstgroup._f_walknodes(): if first: # skip the first group first = 0 continue if isinstance(node, tb.Group): repr(node._v_filters) == repr(nodelist1[node._v_name]) else: repr(node.filters) == repr(nodelist1[node._v_name]) class CopyGroupCase1(CopyGroupTestCase): close = 0 filters = None srcnode = '/group0/group1' dstnode = '/' class CopyGroupCase2(CopyGroupTestCase): close = 1 filters = None srcnode = '/group0/group1' dstnode = '/' class CopyGroupCase3(CopyGroupTestCase): close = 0 filters = None srcnode = '/group0' dstnode = '/group2/group3' class CopyGroupCase4(CopyGroupTestCase): close = 1 filters = tb.Filters(complevel=1) srcnode = '/group0' dstnode = '/group2/group3' class CopyGroupCase5(CopyGroupTestCase): close = 0 filters = tb.Filters() srcnode = '/' dstnode = '/group2/group3' class CopyGroupCase6(CopyGroupTestCase): close = 1 filters = tb.Filters(fletcher32=True) srcnode = '/group0' dstnode = '/group2/group3' class CopyGroupCase7(CopyGroupTestCase): close = 0 filters = tb.Filters(complevel=1, shuffle=False) srcnode = '/' dstnode = '/' @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class CopyGroupCase8(CopyGroupTestCase): close = 1 filters = tb.Filters(complevel=1, complib="lzo") srcnode = '/' dstnode = '/' class CopyFileTestCase(common.TempFileMixin, common.PyTablesTestCase): title = "A title" nrows = 10 def setUp(self): super().setUp() # Create a temporary file self.h5fname2 = tempfile.mktemp(".h5") # Create the source file self.populateFile() def populateFile(self): group = self.h5file.root # Add some user attrs: group._v_attrs.attr1 = "an string for root group" group._v_attrs.attr2 = 124 # Create a tree for group_i in range(5): for bgroup_i in range(2): # Create a new group (brother of group) group2 = self.h5file.create_group(group, 'bgroup' + str(bgroup_i), filters=None) # Create a table table = self.h5file.create_table(group2, 'table1', Record2, title=self.title, filters=None) # Get the record object associated with the new table d = table.row # Fill the table for row_i in range(self.nrows): d['var1'] = '%04d' % (self.nrows - row_i) d['var2'] = row_i d['var3'] = row_i * 2 d.append() # This injects the Record values # Flush the buffer for this table table.flush() # Add some user attrs: table.attrs.attr1 = "an string" table.attrs.attr2 = 234 # Create a couple of arrays in each group var1List = [x['var1'] for x in table.iterrows()] var3List = [x['var3'] for x in table.iterrows()] self.h5file.create_array(group2, 'array1', var1List, "col 1") self.h5file.create_array(group2, 'array2', var3List, "col 3") # Create a couple of EArrays as well ea1 = self.h5file.create_earray(group2, 'earray1', tb.StringAtom(itemsize=4), (0,), "col 1") ea2 = self.h5file.create_earray(group2, 'earray2', tb.Int16Atom(), (0,), "col 3") # Add some user attrs: ea1.attrs.attr1 = "an string for earray" ea2.attrs.attr2 = 123 # And fill them with some values ea1.append(var1List) ea2.append(var3List) # Create a new group (descendant of group) group3 = self.h5file.create_group(group, 'group' + str(group_i), filters=None) # Iterate over this new group (group3) group = group3 # Add some user attrs: group._v_attrs.attr1 = "an string for group" group._v_attrs.attr2 = 124 def tearDown(self): # Close the file if hasattr(self, 'h5file2') and self.h5file2.isopen: self.h5file2.close() if hasattr(self, 'h5fname2') and Path(self.h5fname2).is_file(): Path(self.h5fname2).unlink() super().tearDown() def test00_overwrite(self): """Checking copy of a File (overwriting file)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_overwrite..." % self.__class__.__name__) # Create a temporary file Path(self.h5fname2).write_text('') # Copy the file to the destination self.h5file.copy_file(self.h5fname2, title=self.title, overwrite=1, copyuserattrs=0, filters=None) # Close the original file, if needed if self.close: self._reopen() # ...and open the destination file self.h5file2 = tb.open_file(self.h5fname2, "r") # Check that the copy has been done correctly srcgroup = self.h5file.root dstgroup = self.h5file2.root nodelist1 = list(srcgroup._v_children) nodelist2 = list(dstgroup._v_children) # Sort the lists nodelist1.sort() nodelist2.sort() if common.verbose: print("The origin node list -->", nodelist1) print("The copied node list -->", nodelist2) self.assertEqual(srcgroup._v_nchildren, dstgroup._v_nchildren) self.assertEqual(nodelist1, nodelist2) self.assertEqual(self.h5file2.title, self.title) def test00a_srcdstequal(self): """Checking copy of a File (srcfile == dstfile)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00a_srcdstequal..." % self.__class__.__name__) # Copy the file to the destination self.assertRaises(IOError, self.h5file.copy_file, self.h5file.filename) def test00b_firstclass(self): """Checking copy of a File (first-class function)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00b_firstclass..." % self.__class__.__name__) # Close the temporary file self.h5file.close() # Copy the file to the destination tb.copy_file(self.h5fname, self.h5fname2, title=self.title, copyuserattrs=0, filters=None, overwrite=1) # ...and open the source and destination file self.h5file = tb.open_file(self.h5fname, "r") self.h5file2 = tb.open_file(self.h5fname2, "r") # Check that the copy has been done correctly srcgroup = self.h5file.root dstgroup = self.h5file2.root nodelist1 = list(srcgroup._v_children) nodelist2 = list(dstgroup._v_children) # Sort the lists nodelist1.sort() nodelist2.sort() if common.verbose: print("The origin node list -->", nodelist1) print("The copied node list -->", nodelist2) self.assertEqual(srcgroup._v_nchildren, dstgroup._v_nchildren) self.assertEqual(nodelist1, nodelist2) self.assertEqual(self.h5file2.title, self.title) def test01_copy(self): """Checking copy of a File (attributes not copied)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_copy..." % self.__class__.__name__) # Copy the file to the destination self.h5file.copy_file(self.h5fname2, title=self.title, copyuserattrs=0, filters=self.filters) # Close the original file, if needed if self.close: self._reopen() # ...and open the destination file self.h5file2 = tb.open_file(self.h5fname2, "r") # Check that the copy has been done correctly srcgroup = self.h5file.root dstgroup = self.h5file2.root nodelist1 = list(srcgroup._v_children) nodelist2 = list(dstgroup._v_children) # Sort the lists nodelist1.sort() nodelist2.sort() if common.verbose: print("The origin node list -->", nodelist1) print("The copied node list -->", nodelist2) self.assertEqual(srcgroup._v_nchildren, dstgroup._v_nchildren) self.assertEqual(nodelist1, nodelist2) # print("_v_attrnames-->", self.h5file2.root._v_attrs._v_attrnames) # print("--> <%s,%s>" % (self.h5file2.title, self.title)) self.assertEqual(self.h5file2.title, self.title) # Check that user attributes has not been copied for srcnode in srcgroup: dstnode = getattr(dstgroup, srcnode._v_name) srcattrs = srcnode._v_attrs srcattrskeys = srcattrs._f_list("sys") dstattrs = dstnode._v_attrs dstattrskeys = dstattrs._f_list("all") # Filters may differ, do not take into account if self.filters is not None: dstattrskeys.remove('FILTERS') # These lists should already be ordered if common.verbose: print(f"srcattrskeys for node {srcnode._v_name}: " f"{srcattrskeys}") print(f"dstattrskeys for node {dstnode._v_name}: " f"{dstattrskeys}") self.assertEqual(srcattrskeys, dstattrskeys) if common.verbose: print("The attrs names has been copied correctly") # Now, for the contents of attributes for srcattrname in srcattrskeys: srcattrvalue = str(getattr(srcattrs, srcattrname)) dstattrvalue = str(getattr(dstattrs, srcattrname)) self.assertEqual(srcattrvalue, dstattrvalue) if self.filters is not None: self.assertEqual(dstattrs.FILTERS, self.filters) if common.verbose: print("The attrs contents has been copied correctly") def test02_Attrs(self): """Checking copy of a File (attributes copied)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_Attrs..." % self.__class__.__name__) # Copy the file to the destination self.h5file.copy_file(self.h5fname2, title=self.title, copyuserattrs=1, filters=self.filters) # Close the original file, if needed if self.close: self._reopen() # ...and open the destination file self.h5file2 = tb.open_file(self.h5fname2, "r") # Check that the copy has been done correctly srcgroup = self.h5file.root dstgroup = self.h5file2.root for srcnode in srcgroup: dstnode = getattr(dstgroup, srcnode._v_name) srcattrs = srcnode._v_attrs srcattrskeys = srcattrs._f_list("all") dstattrs = dstnode._v_attrs dstattrskeys = dstattrs._f_list("all") # These lists should already be ordered if common.verbose: print(f"srcattrskeys for node {srcnode._v_name}: " f"{srcattrskeys}") print(f"dstattrskeys for node {dstnode._v_name}: " f"{dstattrskeys}") # Filters may differ, do not take into account if self.filters is not None: dstattrskeys.remove('FILTERS') self.assertEqual(srcattrskeys, dstattrskeys) if common.verbose: print("The attrs names has been copied correctly") # Now, for the contents of attributes for srcattrname in srcattrskeys: srcattrvalue = str(getattr(srcattrs, srcattrname)) dstattrvalue = str(getattr(dstattrs, srcattrname)) self.assertEqual(srcattrvalue, dstattrvalue) if self.filters is not None: self.assertEqual(dstattrs.FILTERS, self.filters) if common.verbose: print("The attrs contents has been copied correctly") class CopyFileCase1(CopyFileTestCase): close = 0 title = "A new title" filters = None class CopyFileCase2(CopyFileTestCase): close = 1 title = "A new title" filters = None class CopyFileCase3(CopyFileTestCase): close = 0 title = "A new title" filters = tb.Filters(complevel=1) class CopyFileCase4(CopyFileTestCase): close = 1 title = "A new title" filters = tb.Filters(complevel=1) class CopyFileCase5(CopyFileTestCase): close = 0 title = "A new title" filters = tb.Filters(fletcher32=True) class CopyFileCase6(CopyFileTestCase): close = 1 title = "A new title" filters = tb.Filters(fletcher32=True) class CopyFileCase7(CopyFileTestCase): close = 0 title = "A new title" filters = tb.Filters(complevel=1, complib="lzo") class CopyFileCase8(CopyFileTestCase): close = 1 title = "A new title" filters = tb.Filters(complevel=1, complib="lzo") class CopyFileCase10(common.TempFileMixin, common.PyTablesTestCase): def test01_notoverwrite(self): """Checking copy of a File (checking not overwriting)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_notoverwrite..." % self.__class__.__name__) # Create two empty files: self.h5fname2 = tempfile.mktemp(".h5") self.h5file2 = tb.open_file(self.h5fname2, "w") self.h5file2.close() # close the second one try: # Copy the first into the second self.assertRaises( IOError, self.h5file.copy_file, self.h5fname2, overwrite=False) finally: # Delete files Path(self.h5fname2).unlink() class GroupFiltersTestCase(common.TempFileMixin, common.PyTablesTestCase): filters = tb.Filters(complevel=4) # something non-default def setUp(self): super().setUp() atom, shape = tb.IntAtom(), (1, 1) create_group = self.h5file.create_group create_carray = self.h5file.create_carray create_group('/', 'implicit_no') create_group('/implicit_no', 'implicit_no') create_carray('/implicit_no/implicit_no', 'implicit_no', atom=atom, shape=shape) create_carray('/implicit_no/implicit_no', 'explicit_no', atom=atom, shape=shape, filters=tb.Filters()) create_carray('/implicit_no/implicit_no', 'explicit_yes', atom=atom, shape=shape, filters=self.filters) create_group('/', 'explicit_yes', filters=self.filters) create_group('/explicit_yes', 'implicit_yes') create_carray('/explicit_yes/implicit_yes', 'implicit_yes', atom=atom, shape=shape) create_carray('/explicit_yes/implicit_yes', 'explicit_yes', atom=atom, shape=shape, filters=self.filters) create_carray('/explicit_yes/implicit_yes', 'explicit_no', atom=atom, shape=shape, filters=tb.Filters()) def _check_filters(self, h5file, filters=None): for node in h5file: # Get node filters. if hasattr(node, 'filters'): node_filters = node.filters else: node_filters = node._v_filters # Compare to given filters. if filters is not None: self.assertEqual(node_filters, filters) return # Guess filters to compare to by node name. if node._v_name.endswith('_no'): self.assertEqual( node_filters, tb.Filters(), "node ``%s`` should have no filters" % node._v_pathname) elif node._v_name.endswith('_yes'): self.assertEqual( node_filters, self.filters, "node ``%s`` should have filters" % node._v_pathname) def test00_propagate(self): """Filters propagating to children.""" self._check_filters(self.h5file) def _test_copyFile(self, filters=None): copyfname = tempfile.mktemp(suffix='.h5') try: self.h5file.copy_file(copyfname, filters=filters) try: copyf = tb.open_file(copyfname) self._check_filters(copyf, filters=filters) finally: copyf.close() finally: Path(copyfname).unlink() def test01_copyFile(self): """Keeping filters when copying a file.""" self._test_copyFile() def test02_copyFile_override(self): """Overriding filters when copying a file.""" self._test_copyFile(self.filters) def _test_change(self, pathname, change_filters, new_filters): group = self.h5file.get_node(pathname) # Check expected current filters. old_filters = tb.Filters() if pathname.endswith('_yes'): old_filters = self.filters self.assertEqual(group._v_filters, old_filters) # Change filters. change_filters(group) self.assertEqual(group._v_filters, new_filters) # Get and check changed filters. if self._reopen(): group = self.h5file.get_node(pathname) self.assertEqual(group._v_filters, new_filters) def test03_change(self): """Changing the filters of a group.""" def set_filters(group): group._v_filters = self.filters self._test_change('/', set_filters, self.filters) def test04_delete(self): """Deleting the filters of a group.""" def del_filters(group): del group._v_filters self._test_change('/explicit_yes', del_filters, tb.Filters()) @common.unittest.skipIf(not common.blosc_avail, 'BLOSC not available') class SetBloscMaxThreadsTestCase(common.TempFileMixin, common.PyTablesTestCase): filters = tb.Filters(complevel=4, complib="blosc") def test00(self): """Checking set_blosc_max_threads()""" nthreads_old = tb.set_blosc_max_threads(4) if common.verbose: print("Previous max threads:", nthreads_old) print("Should be:", self.h5file.params['MAX_BLOSC_THREADS']) self.assertEqual(nthreads_old, self.h5file.params['MAX_BLOSC_THREADS']) self.h5file.create_carray('/', 'some_array', atom=tb.Int32Atom(), shape=(3, 3), filters=self.filters) nthreads_old = tb.set_blosc_max_threads(1) if common.verbose: print("Previous max threads:", nthreads_old) print("Should be:", 4) self.assertEqual(nthreads_old, 4) def test01(self): """Checking set_blosc_max_threads() (re-open)""" nthreads_old = tb.set_blosc_max_threads(4) self.h5file.create_carray('/', 'some_array', atom=tb.Int32Atom(), shape=(3, 3), filters=self.filters) self._reopen() nthreads_old = tb.set_blosc_max_threads(4) if common.verbose: print("Previous max threads:", nthreads_old) print("Should be:", self.h5file.params['MAX_BLOSC_THREADS']) self.assertEqual(nthreads_old, self.h5file.params['MAX_BLOSC_THREADS']) class FilterTestCase(common.PyTablesTestCase): def test_filter_pack_type(self): self.assertEqual(type(tb.Filters()._pack()), np.int64) @staticmethod def _hexl(n): return hex(int(n)) def test_filter_pack_01(self): filter_ = tb.Filters() self.assertEqual(self._hexl(filter_._pack()), '0x0') def test_filter_pack_02(self): filter_ = tb.Filters(1, shuffle=False) self.assertEqual(self._hexl(filter_._pack()), '0x101') def test_filter_pack_03(self): filter_ = tb.Filters(9, 'zlib', shuffle=True, fletcher32=True) self.assertEqual(self._hexl(filter_._pack()), '0x30109') def test_filter_pack_04(self): filter_ = tb.Filters(1, shuffle=False, least_significant_digit=5) self.assertEqual(self._hexl(filter_._pack()), '0x5040101') def test_filter_unpack_01(self): filter_ = tb.Filters._unpack(np.int64(0x0)) self.assertFalse(filter_.shuffle) self.assertFalse(filter_.fletcher32) self.assertEqual(filter_.least_significant_digit, None) self.assertEqual(filter_.complevel, 0) self.assertEqual(filter_.complib, None) def test_filter_unpack_02(self): filter_ = tb.Filters._unpack(np.int64(0x101)) self.assertFalse(filter_.shuffle) self.assertFalse(filter_.fletcher32) self.assertEqual(filter_.least_significant_digit, None) self.assertEqual(filter_.complevel, 1) self.assertEqual(filter_.complib, 'zlib') def test_filter_unpack_03(self): filter_ = tb.Filters._unpack(np.int64(0x30109)) self.assertTrue(filter_.shuffle) self.assertTrue(filter_.fletcher32) self.assertEqual(filter_.least_significant_digit, None) self.assertEqual(filter_.complevel, 9) self.assertEqual(filter_.complib, 'zlib') def test_filter_unpack_04(self): filter_ = tb.Filters._unpack(np.int64(0x5040101)) self.assertFalse(filter_.shuffle) self.assertFalse(filter_.fletcher32) self.assertEqual(filter_.least_significant_digit, 5) self.assertEqual(filter_.complevel, 1) self.assertEqual(filter_.complib, 'zlib') class DefaultDriverTestCase(common.TempFileMixin, common.PyTablesTestCase): DRIVER = None DRIVER_PARAMS = {} open_kwargs = dict(driver=DRIVER, **DRIVER_PARAMS) def setUp(self): super().setUp() # Create an HDF5 file and contents root = self.h5file.root self.h5file.set_node_attr(root, "testattr", 41) self.h5file.create_array(root, "array", [1, 2], title="array") self.h5file.create_table(root, "table", {"var1": tb.IntCol()}, title="table") def assertIsFile(self): self.assertTrue(Path(self.h5fname).is_file()) def test_newFile(self): self.assertIsInstance(self.h5file, tb.File) self.assertIsFile() def test_readFile(self): self.h5file.close() self.h5file = None self.assertIsFile() # Open an existing HDF5 file self.h5file = tb.open_file(self.h5fname, mode="r", driver=self.DRIVER, **self.DRIVER_PARAMS) # check contents root = self.h5file.root self.assertEqual(self.h5file.get_node_attr(root, "testattr"), 41) self.assertIsInstance(root.array, tb.Array) self.assertEqual(root.array._v_title, "array") self.assertIsInstance(root.table, tb.Table) self.assertEqual(root.table._v_title, "table") self.assertIn("var1", root.table.colnames) self.assertEqual(root.table.cols.var1.dtype, tb.IntCol().dtype) def test_openFileA(self): self.h5file.close() self.h5file = None self.assertIsFile() # Open an existing HDF5 file in append mode self.h5file = tb.open_file(self.h5fname, mode="a", driver=self.DRIVER, **self.DRIVER_PARAMS) # check contents root = self.h5file.root self.assertEqual(self.h5file.get_node_attr(root, "testattr"), 41) self.assertIsInstance(root.array, tb.Array) self.assertEqual(root.array._v_title, "array") self.assertIsInstance(root.table, tb.Table) self.assertEqual(root.table._v_title, "table") self.assertIn("var1", root.table.colnames) self.assertEqual(root.table.cols.var1.dtype, tb.IntCol().dtype) # write new data root = self.h5file.root self.h5file.set_node_attr(root, "testattr2", 42) self.h5file.create_array(root, "array2", [1, 2], title="array2") self.h5file.create_table(root, "table2", {"var2": tb.FloatCol()}, title="table2") # check contents self._reopen(mode="a", driver=self.DRIVER, **self.DRIVER_PARAMS) root = self.h5file.root self.assertEqual(self.h5file.get_node_attr(root, "testattr"), 41) self.assertEqual(self.h5file.get_node_attr(root, "testattr2"), 42) self.assertIsInstance(root.array, tb.Array) self.assertEqual(root.array._v_title, "array") self.assertIsInstance(root.array2, tb.Array) self.assertEqual(root.array2._v_title, "array2") self.assertIsInstance(root.table, tb.Table) self.assertEqual(root.table._v_title, "table") self.assertIn("var1", root.table.colnames) self.assertEqual(root.table.cols.var1.dtype, tb.IntCol().dtype) self.assertIsInstance(root.table2, tb.Table) self.assertEqual(root.table2._v_title, "table2") self.assertIn("var2", root.table2.colnames) self.assertEqual(root.table2.cols.var2.dtype, tb.FloatCol().dtype) def test_openFileRW(self): self.h5file.close() self.h5file = None self.assertIsFile() # Open an existing HDF5 file in append mode self.h5file = tb.open_file(self.h5fname, mode="r+", driver=self.DRIVER, **self.DRIVER_PARAMS) # check contents root = self.h5file.root self.assertEqual(self.h5file.get_node_attr(root, "testattr"), 41) self.assertIsInstance(root.array, tb.Array) self.assertEqual(root.array._v_title, "array") self.assertIsInstance(root.table, tb.Table) self.assertEqual(root.table._v_title, "table") self.assertIn("var1", root.table.colnames) self.assertEqual(root.table.cols.var1.dtype, tb.IntCol().dtype) # write new data self.h5file.set_node_attr(root, "testattr2", 42) self.h5file.create_array(root, "array2", [1, 2], title="array2") self.h5file.create_table(root, "table2", {"var2": tb.FloatCol()}, title="table2") # check contents self._reopen(mode="r+", driver=self.DRIVER, **self.DRIVER_PARAMS) root = self.h5file.root self.assertEqual(self.h5file.get_node_attr(root, "testattr"), 41) self.assertEqual(self.h5file.get_node_attr(root, "testattr2"), 42) self.assertIsInstance(root.array, tb.Array) self.assertEqual(root.array._v_title, "array") self.assertIsInstance(root.array2, tb.Array) self.assertEqual(root.array2._v_title, "array2") self.assertIsInstance(root.table, tb.Table) self.assertEqual(root.table._v_title, "table") self.assertIn("var1", root.table.colnames) self.assertEqual(root.table.cols.var1.dtype, tb.IntCol().dtype) self.assertIsInstance(root.table2, tb.Table) self.assertEqual(root.table2._v_title, "table2") self.assertIn("var2", root.table2.colnames) self.assertEqual(root.table2.cols.var2.dtype, tb.FloatCol().dtype) @common.unittest.skipIf(common.hdf5_version < Version("1.8.9"), "requires HDF5 >= 1.8,9") class Sec2DriverTestCase(DefaultDriverTestCase): DRIVER = "H5FD_SEC2" open_kwargs = dict(driver=DRIVER, **DefaultDriverTestCase.DRIVER_PARAMS) def test_get_file_image(self): image = self.h5file.get_file_image() self.assertGreater(len(image), 0) self.assertEqual([i for i in image[:4]], [137, 72, 68, 70]) @common.unittest.skipIf(common.hdf5_version < Version("1.8.9"), "requires HDF5 >= 1.8,9") class StdioDriverTestCase(DefaultDriverTestCase): DRIVER = "H5FD_STDIO" open_kwargs = dict(driver=DRIVER, **DefaultDriverTestCase.DRIVER_PARAMS) def test_get_file_image(self): image = self.h5file.get_file_image() self.assertGreater(len(image), 0) self.assertEqual([i for i in image[:4]], [137, 72, 68, 70]) @common.unittest.skipIf(common.hdf5_version < Version("1.8.9"), "requires HDF5 >= 1.8,9") class CoreDriverTestCase(DefaultDriverTestCase): DRIVER = "H5FD_CORE" open_kwargs = dict(driver=DRIVER, **DefaultDriverTestCase.DRIVER_PARAMS) def test_get_file_image(self): image = self.h5file.get_file_image() self.assertGreater(len(image), 0) self.assertEqual([i for i in image[:4]], [137, 72, 68, 70]) class CoreDriverNoBackingStoreTestCase(common.PyTablesTestCase): DRIVER = "H5FD_CORE" def setUp(self): super().setUp() self.h5fname = tempfile.mktemp(suffix=".h5") self.h5file = None def tearDown(self): if self.h5file: self.h5file.close() elif self.h5fname in tb.file._open_files: open_files = tb.file._open_files for h5file in open_files.get_handlers_by_name(self.h5fname): h5file.close() self.h5file = None if Path(self.h5fname).is_file(): Path(self.h5fname).unlink() super().tearDown() def test_newFile(self): """Ensure that nothing is written to file.""" self.assertFalse(Path(self.h5fname).is_file()) self.h5file = tb.open_file(self.h5fname, mode="w", driver=self.DRIVER, driver_core_backing_store=False) # Create an HDF5 file and contents root = self.h5file.root self.h5file.set_node_attr(root, "testattr", 41) self.h5file.create_array(root, "array", [1, 2], title="array") self.h5file.create_table(root, "table", {"var1": tb.IntCol()}, title="table") self.h5file.close() # flush self.assertFalse(Path(self.h5fname).is_file()) def test_readNewFileW(self): self.assertFalse(Path(self.h5fname).is_file()) # Create an HDF5 file and contents self.h5file = tb.open_file(self.h5fname, mode="w", driver=self.DRIVER, driver_core_backing_store=False) root = self.h5file.root self.h5file.set_node_attr(root, "testattr", 41) self.h5file.create_array(root, "array", [1, 2], title="array") self.h5file.create_table(root, "table", {"var1": tb.IntCol()}, title="table") self.assertEqual(self.h5file.get_node_attr(root, "testattr"), 41) self.assertIsInstance(root.array, tb.Array) self.assertEqual(root.array._v_title, "array") self.assertIsInstance(root.table, tb.Table) self.assertEqual(root.table._v_title, "table") self.assertIn("var1", root.table.colnames) self.assertEqual(root.table.cols.var1.dtype, tb.IntCol().dtype) self.h5file.close() # flush self.assertFalse(Path(self.h5fname).is_file()) def test_readNewFileA(self): self.assertFalse(Path(self.h5fname).is_file()) # Create an HDF5 file and contents self.h5file = tb.open_file(self.h5fname, mode="a", driver=self.DRIVER, driver_core_backing_store=False) root = self.h5file.root self.h5file.set_node_attr(root, "testattr", 41) self.h5file.create_array(root, "array", [1, 2], title="array") self.h5file.create_table(root, "table", {"var1": tb.IntCol()}, title="table") self.assertEqual(self.h5file.get_node_attr(root, "testattr"), 41) self.assertIsInstance(root.array, tb.Array) self.assertEqual(root.array._v_title, "array") self.assertIsInstance(root.table, tb.Table) self.assertEqual(root.table._v_title, "table") self.assertIn("var1", root.table.colnames) self.assertEqual(root.table.cols.var1.dtype, tb.IntCol().dtype) self.h5file.close() # flush self.assertFalse(Path(self.h5fname).is_file()) def test_openNewFileRW(self): self.assertFalse(Path(self.h5fname).is_file()) self.assertRaises(tb.HDF5ExtError, tb.open_file, self.h5fname, mode="r+", driver=self.DRIVER, driver_core_backing_store=False) def test_openNewFileR(self): self.assertFalse(Path(self.h5fname).is_file()) self.assertRaises(tb.HDF5ExtError, tb.open_file, self.h5fname, mode="r", driver=self.DRIVER, driver_core_backing_store=False) def _create_file(self, filename): h5file = tb.open_file(filename, mode="w") root = h5file.root h5file.set_node_attr(root, "testattr", 41) h5file.create_array(root, "array", [1, 2], title="array") h5file.create_table(root, "table", {"var1": tb.IntCol()}, title="table") h5file.close() def test_readFile(self): self._create_file(self.h5fname) self.assertTrue(Path(self.h5fname).is_file()) # Open an existing HDF5 file self.h5file = tb.open_file(self.h5fname, mode="r", driver=self.DRIVER, driver_core_backing_store=False) root = self.h5file.root self.assertEqual(self.h5file.get_node_attr(root, "testattr"), 41) self.assertIsInstance(root.array, tb.Array) self.assertEqual(root.array._v_title, "array") self.assertIsInstance(root.table, tb.Table) self.assertEqual(root.table._v_title, "table") self.assertIn("var1", root.table.colnames) self.assertEqual(root.table.cols.var1.dtype, tb.IntCol().dtype) def _get_digest(self, filename): md5 = hashlib.md5() md5.update(Path(filename).read_bytes()) hexdigest = md5.hexdigest() return hexdigest def test_openFileA(self): self._create_file(self.h5fname) self.assertTrue(Path(self.h5fname).is_file()) # compute the file hash hexdigest = self._get_digest(self.h5fname) # Open an existing HDF5 file in append mode self.h5file = tb.open_file(self.h5fname, mode="a", driver=self.DRIVER, driver_core_backing_store=False) # check contents root = self.h5file.root self.assertEqual(self.h5file.get_node_attr(root, "testattr"), 41) self.assertIsInstance(root.array, tb.Array) self.assertEqual(root.array._v_title, "array") self.assertIsInstance(root.table, tb.Table) self.assertEqual(root.table._v_title, "table") self.assertIn("var1", root.table.colnames) self.assertEqual(root.table.cols.var1.dtype, tb.IntCol().dtype) # write new data root = self.h5file.root self.h5file.set_node_attr(root, "testattr2", 42) self.h5file.create_array(root, "array2", [1, 2], title="array2") self.h5file.create_table(root, "table2", {"var2": tb.FloatCol()}, title="table2") self.h5file.close() # ensure that there is no change on the file on disk self.assertEqual(hexdigest, self._get_digest(self.h5fname)) def test_openFileRW(self): self._create_file(self.h5fname) self.assertTrue(Path(self.h5fname).is_file()) # compute the file hash hexdigest = self._get_digest(self.h5fname) # Open an existing HDF5 file in append mode self.h5file = tb.open_file(self.h5fname, mode="r+", driver=self.DRIVER, driver_core_backing_store=False) # check contents root = self.h5file.root self.assertEqual(self.h5file.get_node_attr(root, "testattr"), 41) self.assertIsInstance(root.array, tb.Array) self.assertEqual(root.array._v_title, "array") self.assertIsInstance(root.table, tb.Table) self.assertEqual(root.table._v_title, "table") self.assertIn("var1", root.table.colnames) self.assertEqual(root.table.cols.var1.dtype, tb.IntCol().dtype) # write new data root = self.h5file.root self.h5file.set_node_attr(root, "testattr2", 42) self.h5file.create_array(root, "array2", [1, 2], title="array2") self.h5file.create_table(root, "table2", {"var2": tb.FloatCol()}, title="table2") self.h5file.close() # ensure that there is no change on the file on disk self.assertEqual(hexdigest, self._get_digest(self.h5fname)) @common.unittest.skipIf(common.hdf5_version < Version("1.8.9"), 'HDF5 >= "1.8.9" required') def test_get_file_image(self): self.h5file = tb.open_file(self.h5fname, mode="w", driver=self.DRIVER, driver_core_backing_store=False) root = self.h5file.root self.h5file.set_node_attr(root, "testattr", 41) self.h5file.create_array(root, "array", [1, 2], title="array") self.h5file.create_table(root, "table", {"var1": tb.IntCol()}, title="table") image = self.h5file.get_file_image() self.assertGreater(len(image), 0) self.assertEqual([i for i in image[:4]], [137, 72, 68, 70]) class SplitDriverTestCase(DefaultDriverTestCase): DRIVER = "H5FD_SPLIT" DRIVER_PARAMS = { "driver_split_meta_ext": "-xm.h5", "driver_split_raw_ext": "-xr.h5", } open_kwargs = dict(driver=DRIVER, **DRIVER_PARAMS) def _getTempFileName(self): return tempfile.mktemp(prefix=self._getName()) def setUp(self): super().setUp() self.h5fnames = [self.h5fname + self.DRIVER_PARAMS[k] for k in ("driver_split_meta_ext", "driver_split_raw_ext")] def tearDown(self): self.h5file.close() for fname in self.h5fnames: if Path(fname).is_file(): Path(fname).unlink() # super().tearDown() common.PyTablesTestCase.tearDown(self) def assertIsFile(self): for fname in self.h5fnames: self.assertTrue(Path(fname).is_file()) class NotSpportedDriverTestCase(common.PyTablesTestCase): DRIVER = None DRIVER_PARAMS = {} EXCEPTION = ValueError def setUp(self): super().setUp() self.h5fname = tempfile.mktemp(suffix=".h5") def tearDown(self): open_files = tb.file._open_files if self.h5fname in open_files: for h5file in open_files.get_handlers_by_name(self.h5fname): h5file.close() if Path(self.h5fname).is_file(): Path(self.h5fname).unlink() super().tearDown() def test_newFile(self): self.assertRaises(self.EXCEPTION, tb.open_file, self.h5fname, mode="w", driver=self.DRIVER, **self.DRIVER_PARAMS) self.assertFalse(Path(self.h5fname).is_file()) if "H5FD_LOG" in tb.hdf5extension._supported_drivers: BaseLogDriverTestCase = DefaultDriverTestCase else: BaseLogDriverTestCase = NotSpportedDriverTestCase class LogDriverTestCase(BaseLogDriverTestCase): DRIVER = "H5FD_LOG" open_kwargs = dict(driver=DRIVER, **BaseLogDriverTestCase.DRIVER_PARAMS) def setUp(self): # local binding self.DRIVER_PARAMS = { "driver_log_file": tempfile.mktemp(suffix=".log") } super().setUp() def tearDown(self): if Path(self.DRIVER_PARAMS["driver_log_file"]).is_file(): Path(self.DRIVER_PARAMS["driver_log_file"]).unlink() super().tearDown() if tb.hdf5extension.HAVE_DIRECT_DRIVER: class DirectDriverTestCase(DefaultDriverTestCase): DRIVER = "H5FD_DIRECT" open_kwargs = dict( driver=DRIVER, **DefaultDriverTestCase.DRIVER_PARAMS ) else: class DirectDriverTestCase(NotSpportedDriverTestCase): DRIVER = "H5FD_DIRECT" EXCEPTION = RuntimeError if tb.hdf5extension.HAVE_WINDOWS_DRIVER: class WindowsDriverTestCase(DefaultDriverTestCase): DRIVER = "H5FD_WINDOWS" open_kwargs = dict( driver=DRIVER, **DefaultDriverTestCase.DRIVER_PARAMS ) else: class WindowsDriverTestCase(NotSpportedDriverTestCase): DRIVER = "H5FD_WINDOWS" EXCEPTION = RuntimeError class FamilyDriverTestCase(NotSpportedDriverTestCase): DRIVER = "H5FD_FAMILY" class MultiDriverTestCase(NotSpportedDriverTestCase): DRIVER = "H5FD_MULTI" class MpioDriverTestCase(NotSpportedDriverTestCase): DRIVER = "H5FD_MPIO" class MpiPosixDriverTestCase(NotSpportedDriverTestCase): DRIVER = "H5FD_MPIPOSIX" class StreamDriverTestCase(NotSpportedDriverTestCase): DRIVER = "H5FD_STREAM" @common.unittest.skipIf(common.hdf5_version < Version("1.8.9"), 'HDF5 >= "1.8.9" required') class InMemoryCoreDriverTestCase(common.PyTablesTestCase): DRIVER = "H5FD_CORE" def setUp(self): super().setUp() self.h5fname = tempfile.mktemp(".h5") self.h5file = None def tearDown(self): if self.h5file: self.h5file.close() self.h5file = None if Path(self.h5fname).is_file(): Path(self.h5fname).unlink() super().tearDown() def _create_image(self, filename="in-memory", title="Title", mode='w'): h5file = tb.open_file(filename, mode=mode, title=title, driver=self.DRIVER, driver_core_backing_store=0) try: h5file.create_array(h5file.root, 'array', [1, 2], title="Array") h5file.create_table(h5file.root, 'table', { 'var1': tb.IntCol()}, "Table") h5file.root._v_attrs.testattr = 41 image = h5file.get_file_image() finally: h5file.close() return image def test_newFileW(self): image = self._create_image(self.h5fname, mode='w') self.assertGreater(len(image), 0) self.assertEqual([i for i in image[:4]], [137, 72, 68, 70]) self.assertFalse(Path(self.h5fname).exists()) def test_newFileA(self): image = self._create_image(self.h5fname, mode='a') self.assertGreater(len(image), 0) self.assertEqual([i for i in image[:4]], [137, 72, 68, 70]) self.assertFalse(Path(self.h5fname).exists()) def test_openFileR(self): image = self._create_image(self.h5fname) self.assertFalse(Path(self.h5fname).exists()) # Open an existing file self.h5file = tb.open_file(self.h5fname, mode="r", driver=self.DRIVER, driver_core_image=image, driver_core_backing_store=0) # Get the CLASS attribute of the arr object self.assertTrue(hasattr(self.h5file.root._v_attrs, "TITLE")) self.assertEqual(self.h5file.get_node_attr("/", "TITLE"), "Title") self.assertTrue(hasattr(self.h5file.root._v_attrs, "testattr")) self.assertEqual(self.h5file.get_node_attr("/", "testattr"), 41) self.assertTrue(hasattr(self.h5file.root, "array")) self.assertEqual(self.h5file.get_node_attr("/array", "TITLE"), "Array") self.assertTrue(hasattr(self.h5file.root, "table")) self.assertEqual(self.h5file.get_node_attr("/table", "TITLE"), "Table") self.assertEqual(self.h5file.root.array.read(), [1, 2]) def test_openFileRW(self): image = self._create_image(self.h5fname) self.assertFalse(Path(self.h5fname).exists()) # Open an existing file self.h5file = tb.open_file(self.h5fname, mode="r+", driver=self.DRIVER, driver_core_image=image, driver_core_backing_store=0) # Get the CLASS attribute of the arr object self.assertTrue(hasattr(self.h5file.root._v_attrs, "TITLE")) self.assertEqual(self.h5file.get_node_attr("/", "TITLE"), "Title") self.assertTrue(hasattr(self.h5file.root._v_attrs, "testattr")) self.assertEqual(self.h5file.get_node_attr("/", "testattr"), 41) self.assertTrue(hasattr(self.h5file.root, "array")) self.assertEqual(self.h5file.get_node_attr("/array", "TITLE"), "Array") self.assertTrue(hasattr(self.h5file.root, "table")) self.assertEqual(self.h5file.get_node_attr("/table", "TITLE"), "Table") self.assertEqual(self.h5file.root.array.read(), [1, 2]) self.h5file.create_array(self.h5file.root, 'array2', list(range(10_000)), title="Array2") self.h5file.root._v_attrs.testattr2 = 42 self.h5file.close() self.assertFalse(Path(self.h5fname).exists()) def test_openFileRW_update(self): filename = tempfile.mktemp(".h5") image1 = self._create_image(filename) self.assertFalse(Path(self.h5fname).exists()) # Open an existing file self.h5file = tb.open_file(self.h5fname, mode="r+", driver=self.DRIVER, driver_core_image=image1, driver_core_backing_store=0) # Get the CLASS attribute of the arr object self.assertTrue(hasattr(self.h5file.root._v_attrs, "TITLE")) self.assertEqual(self.h5file.get_node_attr("/", "TITLE"), "Title") self.assertTrue(hasattr(self.h5file.root._v_attrs, "testattr")) self.assertEqual(self.h5file.get_node_attr("/", "testattr"), 41) self.assertTrue(hasattr(self.h5file.root, "array")) self.assertEqual(self.h5file.get_node_attr("/array", "TITLE"), "Array") self.assertTrue(hasattr(self.h5file.root, "table")) self.assertEqual(self.h5file.get_node_attr("/table", "TITLE"), "Table") self.assertEqual(self.h5file.root.array.read(), [1, 2]) data = list(range(2 * tb.parameters.DRIVER_CORE_INCREMENT)) self.h5file.create_array(self.h5file.root, 'array2', data, title="Array2") self.h5file.root._v_attrs.testattr2 = 42 image2 = self.h5file.get_file_image() self.h5file.close() self.assertFalse(Path(self.h5fname).exists()) self.assertNotEqual(len(image1), len(image2)) self.assertNotEqual(image1, image2) # Open an existing file self.h5file = tb.open_file(self.h5fname, mode="r", driver=self.DRIVER, driver_core_image=image2, driver_core_backing_store=0) # Get the CLASS attribute of the arr object self.assertTrue(hasattr(self.h5file.root._v_attrs, "TITLE")) self.assertEqual(self.h5file.get_node_attr("/", "TITLE"), "Title") self.assertTrue(hasattr(self.h5file.root._v_attrs, "testattr")) self.assertEqual(self.h5file.get_node_attr("/", "testattr"), 41) self.assertTrue(hasattr(self.h5file.root, "array")) self.assertEqual(self.h5file.get_node_attr("/array", "TITLE"), "Array") self.assertTrue(hasattr(self.h5file.root, "table")) self.assertEqual(self.h5file.get_node_attr("/table", "TITLE"), "Table") self.assertEqual(self.h5file.root.array.read(), [1, 2]) self.assertTrue(hasattr(self.h5file.root._v_attrs, "testattr2")) self.assertEqual(self.h5file.get_node_attr("/", "testattr2"), 42) self.assertTrue(hasattr(self.h5file.root, "array2")) self.assertEqual(self.h5file.get_node_attr( "/array2", "TITLE"), "Array2") self.assertEqual(self.h5file.root.array2.read(), data) self.h5file.close() self.assertFalse(Path(self.h5fname).exists()) def test_openFileA(self): image = self._create_image(self.h5fname) self.assertFalse(Path(self.h5fname).exists()) # Open an existing file self.h5file = tb.open_file(self.h5fname, mode="a", driver=self.DRIVER, driver_core_image=image, driver_core_backing_store=0) # Get the CLASS attribute of the arr object self.assertTrue(hasattr(self.h5file.root._v_attrs, "TITLE")) self.assertEqual(self.h5file.get_node_attr("/", "TITLE"), "Title") self.assertTrue(hasattr(self.h5file.root._v_attrs, "testattr")) self.assertEqual(self.h5file.get_node_attr("/", "testattr"), 41) self.assertTrue(hasattr(self.h5file.root, "array")) self.assertEqual(self.h5file.get_node_attr("/array", "TITLE"), "Array") self.assertTrue(hasattr(self.h5file.root, "table")) self.assertEqual(self.h5file.get_node_attr("/table", "TITLE"), "Table") self.assertEqual(self.h5file.root.array.read(), [1, 2]) self.h5file.close() self.assertFalse(Path(self.h5fname).exists()) def test_openFileA_update(self): h5fname = tempfile.mktemp(".h5") image1 = self._create_image(h5fname) self.assertFalse(Path(self.h5fname).exists()) # Open an existing file self.h5file = tb.open_file(self.h5fname, mode="a", driver=self.DRIVER, driver_core_image=image1, driver_core_backing_store=0) # Get the CLASS attribute of the arr object self.assertTrue(hasattr(self.h5file.root._v_attrs, "TITLE")) self.assertEqual(self.h5file.get_node_attr("/", "TITLE"), "Title") self.assertTrue(hasattr(self.h5file.root._v_attrs, "testattr")) self.assertEqual(self.h5file.get_node_attr("/", "testattr"), 41) self.assertTrue(hasattr(self.h5file.root, "array")) self.assertEqual(self.h5file.get_node_attr("/array", "TITLE"), "Array") self.assertTrue(hasattr(self.h5file.root, "table")) self.assertEqual(self.h5file.get_node_attr("/table", "TITLE"), "Table") self.assertEqual(self.h5file.root.array.read(), [1, 2]) data = list(range(2 * tb.parameters.DRIVER_CORE_INCREMENT)) self.h5file.create_array(self.h5file.root, 'array2', data, title="Array2") self.h5file.root._v_attrs.testattr2 = 42 image2 = self.h5file.get_file_image() self.h5file.close() self.assertFalse(Path(self.h5fname).exists()) self.assertNotEqual(len(image1), len(image2)) self.assertNotEqual(image1, image2) # Open an existing file self.h5file = tb.open_file(self.h5fname, mode="r", driver=self.DRIVER, driver_core_image=image2, driver_core_backing_store=0) # Get the CLASS attribute of the arr object self.assertTrue(hasattr(self.h5file.root._v_attrs, "TITLE")) self.assertEqual(self.h5file.get_node_attr("/", "TITLE"), "Title") self.assertTrue(hasattr(self.h5file.root._v_attrs, "testattr")) self.assertEqual(self.h5file.get_node_attr("/", "testattr"), 41) self.assertTrue(hasattr(self.h5file.root, "array")) self.assertEqual(self.h5file.get_node_attr("/array", "TITLE"), "Array") self.assertTrue(hasattr(self.h5file.root, "table")) self.assertEqual(self.h5file.get_node_attr("/table", "TITLE"), "Table") self.assertEqual(self.h5file.root.array.read(), [1, 2]) self.assertTrue(hasattr(self.h5file.root._v_attrs, "testattr2")) self.assertEqual(self.h5file.get_node_attr("/", "testattr2"), 42) self.assertTrue(hasattr(self.h5file.root, "array2")) self.assertEqual(self.h5file.get_node_attr( "/array2", "TITLE"), "Array2") self.assertEqual(self.h5file.root.array2.read(), data) self.h5file.close() self.assertFalse(Path(self.h5fname).exists()) def test_str(self): self.h5file = tb.open_file(self.h5fname, mode="w", title="Title", driver=self.DRIVER, driver_core_backing_store=0) self.h5file.create_array(self.h5file.root, 'array', [1, 2], title="Array") self.h5file.create_table(self.h5file.root, 'table', {'var1': tb.IntCol()}, "Table") self.h5file.root._v_attrs.testattr = 41 # ensure that the __str__ method works even if there is no phisical # file on disk (in which case the os.stat operation for date retrieval # fails) self.assertIsNotNone(str(self.h5file)) self.h5file.close() self.assertFalse(Path(self.h5fname).exists()) class QuantizeTestCase(common.TempFileMixin, common.PyTablesTestCase): mode = "w" title = "This is the table title" expectedrows = 10 appendrows = 5 def setUp(self): super().setUp() self.data = np.linspace(-5., 5., 41) self.randomdata = np.random.random_sample(1_000_000) self.randomints = np.random.randint( -1_000_000, 1_000_000, 1_000_000).astype('int64') self.populateFile() self.h5file.close() self.quantizeddata_0 = np.asarray( [-5.] * 2 + [-4.] * 5 + [-3.] * 3 + [-2.] * 5 + [-1.] * 3 + [0.] * 5 + [1.] * 3 + [2.] * 5 + [3.] * 3 + [4.] * 5 + [5.] * 2) self.quantizeddata_m1 = np.asarray( [-8.] * 4 + [0.] * 33 + [8.] * 4) def populateFile(self): root = self.h5file.root filters = tb.Filters(complevel=1, complib="blosc", least_significant_digit=1) ints = self.h5file.create_carray(root, "integers", tb.Int64Atom(), (1_000_000,), filters=filters) ints[:] = self.randomints floats = self.h5file.create_carray(root, "floats", tb.Float32Atom(), (1_000_000,), filters=filters) floats[:] = self.randomdata data1 = self.h5file.create_carray(root, "data1", tb.Float64Atom(), (41,), filters=filters) data1[:] = self.data filters = tb.Filters(complevel=1, complib="blosc", least_significant_digit=0) data0 = self.h5file.create_carray(root, "data0", tb.Float64Atom(), (41,), filters=filters) data0[:] = self.data filters = tb.Filters(complevel=1, complib="blosc", least_significant_digit=2) data2 = self.h5file.create_carray(root, "data2", tb.Float64Atom(), (41,), filters=filters) data2[:] = self.data filters = tb.Filters(complevel=1, complib="blosc", least_significant_digit=-1) datam1 = self.h5file.create_carray(root, "datam1", tb.Float64Atom(), (41,), filters=filters) datam1[:] = self.data def test00_quantizeData(self): """Checking the quantize() function.""" quantized_0 = tb.utils.quantize(self.data, 0) quantized_1 = tb.utils.quantize(self.data, 1) quantized_2 = tb.utils.quantize(self.data, 2) quantized_m1 = tb.utils.quantize(self.data, -1) np.testing.assert_array_equal(quantized_0, self.quantizeddata_0) np.testing.assert_array_equal(quantized_1, self.data) np.testing.assert_array_equal(quantized_2, self.data) np.testing.assert_array_equal(quantized_m1, self.quantizeddata_m1) def test01_quantizeDataMaxError(self): """Checking the maximum error introduced by the quantize() function.""" quantized_0 = tb.utils.quantize(self.randomdata, 0) quantized_1 = tb.utils.quantize(self.randomdata, 1) quantized_2 = tb.utils.quantize(self.randomdata, 2) quantized_m1 = tb.utils.quantize(self.randomdata, -1) self.assertLess(np.abs(quantized_0 - self.randomdata).max(), 0.5) self.assertLess(np.abs(quantized_1 - self.randomdata).max(), 0.05) self.assertLess(np.abs(quantized_2 - self.randomdata).max(), 0.005) self.assertLess(np.abs(quantized_m1 - self.randomdata).max(), 1.) def test02_array(self): """Checking quantized data as written to disk.""" self.h5file = tb.open_file(self.h5fname, "r") np.testing.assert_array_equal(self.h5file.root.data1[:], self.data) np.testing.assert_array_equal(self.h5file.root.data2[:], self.data) np.testing.assert_array_equal(self.h5file.root.data0[:], self.quantizeddata_0) np.testing.assert_array_equal(self.h5file.root.datam1[:], self.quantizeddata_m1) np.testing.assert_array_equal(self.h5file.root.integers[:], self.randomints) self.assertEqual(self.h5file.root.integers[:].dtype, self.randomints.dtype) self.assertLess( np.abs(self.h5file.root.floats[:] - self.randomdata).max(), 0.05) def suite(): import doctest theSuite = common.unittest.TestSuite() niter = 1 # common.heavy = 1 # Uncomment this only for testing purposes! for i in range(niter): theSuite.addTest(common.unittest.makeSuite(FiltersCase1)) theSuite.addTest(common.unittest.makeSuite(FiltersCase2)) theSuite.addTest(common.unittest.makeSuite(FiltersCase10)) theSuite.addTest(common.unittest.makeSuite(FiltersCaseBloscBloscLZ)) theSuite.addTest(common.unittest.makeSuite(FiltersCaseBloscLZ4)) theSuite.addTest(common.unittest.makeSuite(FiltersCaseBloscLZ4HC)) theSuite.addTest(common.unittest.makeSuite(FiltersCaseBloscSnappy)) theSuite.addTest(common.unittest.makeSuite(FiltersCaseBloscZlib)) theSuite.addTest(common.unittest.makeSuite(FiltersCaseBloscZstd)) theSuite.addTest(common.unittest.makeSuite(FiltersCaseBloscBitShuffle)) theSuite.addTest(common.unittest.makeSuite(CopyGroupCase1)) theSuite.addTest(common.unittest.makeSuite(CopyGroupCase2)) theSuite.addTest(common.unittest.makeSuite(CopyFileCase1)) theSuite.addTest(common.unittest.makeSuite(CopyFileCase2)) theSuite.addTest(common.unittest.makeSuite(GroupFiltersTestCase)) theSuite.addTest(common.unittest.makeSuite(SetBloscMaxThreadsTestCase)) theSuite.addTest(common.unittest.makeSuite(FilterTestCase)) theSuite.addTest(doctest.DocTestSuite(tb.filters)) theSuite.addTest(common.unittest.makeSuite(DefaultDriverTestCase)) theSuite.addTest(common.unittest.makeSuite(Sec2DriverTestCase)) theSuite.addTest(common.unittest.makeSuite(StdioDriverTestCase)) theSuite.addTest(common.unittest.makeSuite(CoreDriverTestCase)) theSuite.addTest(common.unittest.makeSuite( CoreDriverNoBackingStoreTestCase)) theSuite.addTest(common.unittest.makeSuite(SplitDriverTestCase)) theSuite.addTest(common.unittest.makeSuite(LogDriverTestCase)) theSuite.addTest(common.unittest.makeSuite(DirectDriverTestCase)) theSuite.addTest(common.unittest.makeSuite(WindowsDriverTestCase)) theSuite.addTest(common.unittest.makeSuite(FamilyDriverTestCase)) theSuite.addTest(common.unittest.makeSuite(MultiDriverTestCase)) theSuite.addTest(common.unittest.makeSuite(MpioDriverTestCase)) theSuite.addTest(common.unittest.makeSuite(MpiPosixDriverTestCase)) theSuite.addTest(common.unittest.makeSuite(StreamDriverTestCase)) theSuite.addTest(common.unittest.makeSuite(InMemoryCoreDriverTestCase)) theSuite.addTest(common.unittest.makeSuite(QuantizeTestCase)) if common.heavy: theSuite.addTest(common.unittest.makeSuite(CreateTestCase)) theSuite.addTest(common.unittest.makeSuite(FiltersCase3)) theSuite.addTest(common.unittest.makeSuite(FiltersCase4)) theSuite.addTest(common.unittest.makeSuite(FiltersCase5)) theSuite.addTest(common.unittest.makeSuite(FiltersCase6)) theSuite.addTest(common.unittest.makeSuite(FiltersCase7)) theSuite.addTest(common.unittest.makeSuite(FiltersCase8)) theSuite.addTest(common.unittest.makeSuite(FiltersCase9)) theSuite.addTest(common.unittest.makeSuite(CopyFileCase3)) theSuite.addTest(common.unittest.makeSuite(CopyFileCase4)) theSuite.addTest(common.unittest.makeSuite(CopyFileCase5)) theSuite.addTest(common.unittest.makeSuite(CopyFileCase6)) theSuite.addTest(common.unittest.makeSuite(CopyFileCase7)) theSuite.addTest(common.unittest.makeSuite(CopyFileCase8)) theSuite.addTest(common.unittest.makeSuite(CopyFileCase10)) theSuite.addTest(common.unittest.makeSuite(CopyGroupCase3)) theSuite.addTest(common.unittest.makeSuite(CopyGroupCase4)) theSuite.addTest(common.unittest.makeSuite(CopyGroupCase5)) theSuite.addTest(common.unittest.makeSuite(CopyGroupCase6)) theSuite.addTest(common.unittest.makeSuite(CopyGroupCase7)) theSuite.addTest(common.unittest.makeSuite(CopyGroupCase8)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_do_undo.py000066400000000000000000002775551416254111300207430ustar00rootroot00000000000000import warnings import tables as tb from tables.tests import common class BasicTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test for basic Undo/Redo operations.""" _reopen_flag = False """Whether to reopen the file at certain points.""" def _do_reopen(self): if self._reopen_flag: self._reopen('r+') def setUp(self): super().setUp() h5file = self.h5file root = h5file.root # Create an array h5file.create_array(root, 'array', [1, 2], title="Title example") # Create another array object h5file.create_array(root, 'anarray', [1], "Array title") # Create a group object group = h5file.create_group(root, 'agroup', "Group title") # Create a couple of objects there h5file.create_array(group, 'anarray1', [2], "Array title 1") h5file.create_array(group, 'anarray2', [2], "Array title 2") # Create a lonely group in first level h5file.create_group(root, 'agroup2', "Group title 2") # Create a new group in the second level h5file.create_group(group, 'agroup3', "Group title 3") def test00_simple(self): """Checking simple do/undo.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_simple..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray', [3, 4], "Another array") # Now undo the past operation self.h5file.undo() # Check that otherarray does not exist in the object tree self.assertNotIn("/otherarray", self.h5file) self.assertEqual(self.h5file._curaction, 0) self.assertEqual(self.h5file._curmark, 0) # Redo the operation self._do_reopen() self.h5file.redo() if common.verbose: print("Object tree after redo:", self.h5file) # Check that otherarray has come back to life in a sane state self.assertIn("/otherarray", self.h5file) self.assertEqual(self.h5file.root.otherarray.read(), [3, 4]) self.assertEqual(self.h5file.root.otherarray.title, "Another array") self.assertEqual(self.h5file._curaction, 1) self.assertEqual(self.h5file._curmark, 0) def test01_twice(self): """Checking do/undo (twice operations intertwined)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_twice..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray', [3, 4], "Another array") self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") # Now undo the past operations self._do_reopen() self.h5file.undo() self.assertNotIn("/otherarray", self.h5file) self.assertNotIn("/otherarray2", self.h5file) self.assertEqual(self.h5file._curaction, 0) self.assertEqual(self.h5file._curmark, 0) # Redo the operation self.h5file.redo() # Check that otherarray has come back to life in a sane state self.assertIn("/otherarray", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertEqual(self.h5file.root.otherarray.read(), [3, 4]) self.assertEqual(self.h5file.root.otherarray2.read(), [4, 5]) self.assertEqual(self.h5file.root.otherarray.title, "Another array") self.assertEqual(self.h5file.root.otherarray2.title, "Another array 2") self.assertEqual(self.h5file._curaction, 2) self.assertEqual(self.h5file._curmark, 0) def test02_twice2(self): """Checking twice ops and two marks.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_twice2..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray', [3, 4], "Another array") # Put a mark self._do_reopen() self.h5file.mark() self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") self.assertEqual(self.h5file._curaction, 3) self.assertEqual(self.h5file._curmark, 1) # Unwind just one mark self.h5file.undo() self.assertIn("/otherarray", self.h5file) self.assertNotIn("/otherarray2", self.h5file) self.assertEqual(self.h5file._curaction, 2) self.assertEqual(self.h5file._curmark, 1) # Unwind another mark self.h5file.undo() self.assertEqual(self.h5file._curaction, 0) self.assertEqual(self.h5file._curmark, 0) self.assertNotIn("/otherarray", self.h5file) self.assertNotIn("/otherarray2", self.h5file) # Redo until the next mark self.h5file.redo() self.assertIn("/otherarray", self.h5file) self.assertNotIn("/otherarray2", self.h5file) self._do_reopen() self.assertEqual(self.h5file._curaction, 2) self.assertEqual(self.h5file._curmark, 1) # Redo until the end self.h5file.redo() self.assertIn("/otherarray", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertEqual(self.h5file.root.otherarray.read(), [3, 4]) self.assertEqual(self.h5file.root.otherarray2.read(), [4, 5]) self.assertEqual(self.h5file.root.otherarray.title, "Another array") self.assertEqual(self.h5file.root.otherarray2.title, "Another array 2") self.assertEqual(self.h5file._curaction, 3) self.assertEqual(self.h5file._curmark, 1) def test03_6times3marks(self): """Checking with six ops and three marks.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_6times3marks..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [3, 4], "Another array 1") self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") # Put a mark self.h5file.mark() self.h5file.create_array('/', 'otherarray3', [5, 6], "Another array 3") self.h5file.create_array('/', 'otherarray4', [6, 7], "Another array 4") # Put a mark self._do_reopen() self.h5file.mark() self.h5file.create_array('/', 'otherarray5', [7, 8], "Another array 5") self.h5file.create_array('/', 'otherarray6', [8, 9], "Another array 6") # Unwind just one mark self.h5file.undo() self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertIn("/otherarray3", self.h5file) self.assertIn("/otherarray4", self.h5file) self.assertNotIn("/otherarray5", self.h5file) self.assertNotIn("/otherarray6", self.h5file) # Unwind another mark self.h5file.undo() self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) self.assertNotIn("/otherarray5", self.h5file) self.assertNotIn("/otherarray6", self.h5file) # Unwind all marks self.h5file.undo() self.assertNotIn("/otherarray1", self.h5file) self.assertNotIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) self.assertNotIn("/otherarray5", self.h5file) self.assertNotIn("/otherarray6", self.h5file) # Redo until the next mark self._do_reopen() self.h5file.redo() self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) self.assertNotIn("/otherarray5", self.h5file) self.assertNotIn("/otherarray6", self.h5file) # Redo until the next mark self.h5file.redo() self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertIn("/otherarray3", self.h5file) self.assertIn("/otherarray4", self.h5file) self.assertNotIn("/otherarray5", self.h5file) self.assertNotIn("/otherarray6", self.h5file) # Redo until the end self.h5file.redo() self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertIn("/otherarray3", self.h5file) self.assertIn("/otherarray4", self.h5file) self.assertIn("/otherarray5", self.h5file) self.assertIn("/otherarray6", self.h5file) self.assertEqual(self.h5file.root.otherarray1.read(), [3, 4]) self.assertEqual(self.h5file.root.otherarray2.read(), [4, 5]) self.assertEqual(self.h5file.root.otherarray3.read(), [5, 6]) self.assertEqual(self.h5file.root.otherarray4.read(), [6, 7]) self.assertEqual(self.h5file.root.otherarray5.read(), [7, 8]) self.assertEqual(self.h5file.root.otherarray6.read(), [8, 9]) self.assertEqual(self.h5file.root.otherarray1.title, "Another array 1") self.assertEqual(self.h5file.root.otherarray2.title, "Another array 2") self.assertEqual(self.h5file.root.otherarray3.title, "Another array 3") self.assertEqual(self.h5file.root.otherarray4.title, "Another array 4") self.assertEqual(self.h5file.root.otherarray5.title, "Another array 5") self.assertEqual(self.h5file.root.otherarray6.title, "Another array 6") def test04_6times3marksro(self): """Checking with six operations, three marks and do/undo in random order.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_6times3marksro..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [3, 4], "Another array 1") self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") # Put a mark self.h5file.mark() self._do_reopen() self.h5file.create_array('/', 'otherarray3', [5, 6], "Another array 3") self.h5file.create_array('/', 'otherarray4', [6, 7], "Another array 4") # Unwind the previous mark self.h5file.undo() self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) # Put a mark in the middle of stack if common.verbose: print("All nodes:", self.h5file.walk_nodes()) self.h5file.mark() self._do_reopen() self.h5file.create_array('/', 'otherarray5', [7, 8], "Another array 5") self.h5file.create_array('/', 'otherarray6', [8, 9], "Another array 6") self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) self.assertIn("/otherarray5", self.h5file) self.assertIn("/otherarray6", self.h5file) # Unwind previous mark self.h5file.undo() self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) self.assertNotIn("/otherarray5", self.h5file) self.assertNotIn("/otherarray6", self.h5file) # Redo until the last mark self.h5file.redo() self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) self.assertIn("/otherarray5", self.h5file) self.assertIn("/otherarray6", self.h5file) # Redo until the next mark (non-existent, so no action) self._do_reopen() self.h5file.redo() self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) self.assertIn("/otherarray5", self.h5file) self.assertIn("/otherarray6", self.h5file) self.assertEqual(self.h5file.root.otherarray1.read(), [3, 4]) self.assertEqual(self.h5file.root.otherarray2.read(), [4, 5]) self.assertEqual(self.h5file.root.otherarray5.read(), [7, 8]) self.assertEqual(self.h5file.root.otherarray6.read(), [8, 9]) self.assertEqual(self.h5file.root.otherarray1.title, "Another array 1") self.assertEqual(self.h5file.root.otherarray2.title, "Another array 2") self.assertEqual(self.h5file.root.otherarray5.title, "Another array 5") self.assertEqual(self.h5file.root.otherarray6.title, "Another array 6") def test05_destructive(self): """Checking with a destructive action during undo.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_destructive..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [3, 4], "Another array 1") # Put a mark self.h5file.mark() self._do_reopen() self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") # Now undo the past operation self.h5file.undo() # Do the destructive operation self._do_reopen() self.h5file.create_array('/', 'otherarray3', [5, 6], "Another array 3") # Check objects self.assertIn("/otherarray1", self.h5file) self.assertEqual(self.h5file.root.otherarray1.read(), [3, 4]) self.assertEqual(self.h5file.root.otherarray1.title, "Another array 1") self.assertNotIn("/otherarray2", self.h5file) self.assertIn("/otherarray3", self.h5file) self.assertEqual(self.h5file.root.otherarray3.read(), [5, 6]) self.assertEqual(self.h5file.root.otherarray3.title, "Another array 3") def test05b_destructive(self): """Checking with a destructive action during undo (II)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05b_destructive..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [3, 4], "Another array 1") # Put a mark self._do_reopen() self.h5file.mark() self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") # Now undo the past operation self.h5file.undo() # Do the destructive operation self.h5file.create_array('/', 'otherarray3', [5, 6], "Another array 3") # Put a mark self._do_reopen() self.h5file.mark() self.h5file.create_array('/', 'otherarray4', [6, 7], "Another array 4") self.assertIn("/otherarray4", self.h5file) # Now undo the past operation self.h5file.undo() # Check objects self.assertIn("/otherarray1", self.h5file) self.assertEqual(self.h5file.root.otherarray1.read(), [3, 4]) self.assertEqual(self.h5file.root.otherarray1.title, "Another array 1") self.assertNotIn("/otherarray2", self.h5file) self.assertIn("/otherarray3", self.h5file) self.assertEqual(self.h5file.root.otherarray3.read(), [5, 6]) self.assertEqual(self.h5file.root.otherarray3.title, "Another array 3") self.assertNotIn("/otherarray4", self.h5file) def test05c_destructive(self): """Checking with a destructive action during undo (III)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05c_destructive..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [3, 4], "Another array 1") # Put a mark self.h5file.mark() self._do_reopen() self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") # Now undo the past operation self.h5file.undo() # Do the destructive operation self.h5file.create_array('/', 'otherarray3', [5, 6], "Another array 3") # Put a mark self.h5file.mark() self._do_reopen() self.h5file.create_array('/', 'otherarray4', [6, 7], "Another array 4") self.assertIn("/otherarray4", self.h5file) # Now unwind twice self.h5file.undo() self._do_reopen() self.h5file.undo() # Check objects self.assertIn("/otherarray1", self.h5file) self.assertNotIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) def test05d_destructive(self): """Checking with a destructive action during undo (IV)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05d_destructive..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [3, 4], "Another array 1") # Put a mark self._do_reopen() self.h5file.mark() self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") # Now undo the past operation self.h5file.undo() # Do the destructive operation self.h5file.create_array('/', 'otherarray3', [5, 6], "Another array 3") # Put a mark self.h5file.mark() self.h5file.create_array('/', 'otherarray4', [6, 7], "Another array 4") self.assertIn("/otherarray4", self.h5file) # Now, go to the first mark self._do_reopen() self.h5file.undo(0) # Check objects self.assertNotIn("/otherarray1", self.h5file) self.assertNotIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) def test05e_destructive(self): """Checking with a destructive action during undo (V)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05e_destructive..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [3, 4], "Another array 1") # Put a mark self.h5file.mark() self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") # Now undo the past operation self.h5file.undo() self._do_reopen() # Do the destructive operation self.h5file.create_array('/', 'otherarray3', [5, 6], "Another array 3") # Now, unwind the actions self.h5file.undo(0) self._do_reopen() # Check objects self.assertNotIn("/otherarray1", self.h5file) self.assertNotIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) def test05f_destructive(self): """Checking with a destructive creation of existing node during undo""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05f_destructive..." % self.__class__.__name__) self.h5file.enable_undo() self.h5file.create_array('/', 'newarray', [1]) self.h5file.undo() self._do_reopen() self.assertNotIn('/newarray', self.h5file) newarr = self.h5file.create_array('/', 'newarray', [1]) self.h5file.undo() self.assertNotIn('/newarray', self.h5file) self._do_reopen() self.h5file.redo() self.assertIn('/newarray', self.h5file) if not self._reopen_flag: self.assertIs(self.h5file.root.newarray, newarr) def test06_totalunwind(self): """Checking do/undo (total unwind)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06_totalunwind..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray', [3, 4], "Another array") self.h5file.mark() self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") # Now undo the past operations self._do_reopen() self.h5file.undo(0) self.assertNotIn("/otherarray", self.h5file) self.assertNotIn("/otherarray2", self.h5file) def test07_totalrewind(self): """Checking do/undo (total rewind)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test07_totalunwind..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray', [3, 4], "Another array") self.h5file.mark() self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") # Now undo the past operations self.h5file.undo(0) # Redo all the operations self._do_reopen() self.h5file.redo(-1) # Check that objects has come back to life in a sane state self.assertIn("/otherarray", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertEqual(self.h5file.root.otherarray.read(), [3, 4]) self.assertEqual(self.h5file.root.otherarray2.read(), [4, 5]) self.assertEqual(self.h5file.root.otherarray.title, "Another array") self.assertEqual(self.h5file.root.otherarray2.title, "Another array 2") def test08_marknames(self): """Checking mark names.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08_marknames..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [3, 4], "Another array 1") self.h5file.mark("first") self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") self.h5file.mark("second") self.h5file.create_array('/', 'otherarray3', [5, 6], "Another array 3") self.h5file.mark("third") self.h5file.create_array('/', 'otherarray4', [6, 7], "Another array 4") # Now go to mark "first" self.h5file.undo("first") self._do_reopen() self.assertIn("/otherarray1", self.h5file) self.assertNotIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) # Go to mark "third" self.h5file.redo("third") self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) # Now go to mark "second" self.h5file.undo("second") self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) # Go to the end self._do_reopen() self.h5file.redo(-1) self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertIn("/otherarray3", self.h5file) self.assertIn("/otherarray4", self.h5file) # Check that objects has come back to life in a sane state self.assertEqual(self.h5file.root.otherarray1.read(), [3, 4]) self.assertEqual(self.h5file.root.otherarray2.read(), [4, 5]) self.assertEqual(self.h5file.root.otherarray3.read(), [5, 6]) self.assertEqual(self.h5file.root.otherarray4.read(), [6, 7]) def test08_initialmark(self): """Checking initial mark.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08_initialmark..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() initmid = self.h5file.get_current_mark() # Create a new array self.h5file.create_array('/', 'otherarray', [3, 4], "Another array") self.h5file.mark() self._do_reopen() self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") # Now undo the past operations self.h5file.undo(initmid) self.assertNotIn("/otherarray", self.h5file) self.assertNotIn("/otherarray2", self.h5file) # Redo all the operations self.h5file.redo(-1) self._do_reopen() # Check that objects has come back to life in a sane state self.assertIn("/otherarray", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertEqual(self.h5file.root.otherarray.read(), [3, 4]) self.assertEqual(self.h5file.root.otherarray2.read(), [4, 5]) self.assertEqual(self.h5file.root.otherarray.title, "Another array") self.assertEqual(self.h5file.root.otherarray2.title, "Another array 2") def test09_marknames(self): """Checking mark names (wrong direction)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09_marknames..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [3, 4], "Another array 1") self.h5file.mark("first") self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") self.h5file.mark("second") self._do_reopen() self.h5file.create_array('/', 'otherarray3', [5, 6], "Another array 3") self.h5file.mark("third") self.h5file.create_array('/', 'otherarray4', [6, 7], "Another array 4") # Now go to mark "first" self.h5file.undo("first") # Try to undo up to mark "third" with self.assertRaises(tb.UndoRedoError): self.h5file.undo("third") # Now go to mark "third" self.h5file.redo("third") self._do_reopen() # Try to redo up to mark "second" with self.assertRaises(tb.UndoRedoError): self.h5file.redo("second") # Final checks self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) def test10_goto(self): """Checking mark names (goto)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test10_goto..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [3, 4], "Another array 1") self._do_reopen() self.h5file.mark("first") self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") self.h5file.mark("second") self.h5file.create_array('/', 'otherarray3', [5, 6], "Another array 3") self._do_reopen() self.h5file.mark("third") self.h5file.create_array('/', 'otherarray4', [6, 7], "Another array 4") # Now go to mark "first" self.h5file.goto("first") self.assertIn("/otherarray1", self.h5file) self.assertNotIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) # Go to mark "third" self.h5file.goto("third") self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) # Now go to mark "second" self._do_reopen() self.h5file.goto("second") self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) # Go to the end self.h5file.goto(-1) self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertIn("/otherarray3", self.h5file) self.assertIn("/otherarray4", self.h5file) # Check that objects has come back to life in a sane state self.assertIn("/otherarray2", self.h5file) self.assertEqual(self.h5file.root.otherarray1.read(), [3, 4]) self.assertEqual(self.h5file.root.otherarray2.read(), [4, 5]) self.assertEqual(self.h5file.root.otherarray3.read(), [5, 6]) self.assertEqual(self.h5file.root.otherarray4.read(), [6, 7]) def test10_gotoint(self): """Checking mark sequential ids (goto)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test10_gotoint..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [3, 4], "Another array 1") self.h5file.mark("first") self.h5file.create_array('/', 'otherarray2', [4, 5], "Another array 2") self.h5file.mark("second") self._do_reopen() self.h5file.create_array('/', 'otherarray3', [5, 6], "Another array 3") self.h5file.mark("third") self.h5file.create_array('/', 'otherarray4', [6, 7], "Another array 4") # Now go to mark "first" self.h5file.goto(1) self._do_reopen() self.assertIn("/otherarray1", self.h5file) self.assertNotIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) # Go to beginning self.h5file.goto(0) self.assertNotIn("/otherarray1", self.h5file) self.assertNotIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) # Go to mark "third" self._do_reopen() self.h5file.goto(3) self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) # Now go to mark "second" self.h5file.goto(2) self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) self.assertNotIn("/otherarray4", self.h5file) # Go to the end self._do_reopen() self.h5file.goto(-1) self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertIn("/otherarray3", self.h5file) self.assertIn("/otherarray4", self.h5file) # Check that objects has come back to life in a sane state self.assertIn("/otherarray2", self.h5file) self.assertEqual(self.h5file.root.otherarray1.read(), [3, 4]) self.assertEqual(self.h5file.root.otherarray2.read(), [4, 5]) self.assertEqual(self.h5file.root.otherarray3.read(), [5, 6]) self.assertEqual(self.h5file.root.otherarray4.read(), [6, 7]) def test11_contiguous(self): """Creating contiguous marks""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test11_contiguous..." % self.__class__.__name__) self.h5file.enable_undo() m1 = self.h5file.mark() m2 = self.h5file.mark() self.assertNotEqual(m1, m2) self._do_reopen() self.h5file.undo(m1) self.assertEqual(self.h5file.get_current_mark(), m1) self.h5file.redo(m2) self.assertEqual(self.h5file.get_current_mark(), m2) self.h5file.goto(m1) self.assertEqual(self.h5file.get_current_mark(), m1) self.h5file.goto(m2) self.assertEqual(self.h5file.get_current_mark(), m2) self.h5file.goto(-1) self._do_reopen() self.assertEqual(self.h5file.get_current_mark(), m2) self.h5file.goto(0) self.assertEqual(self.h5file.get_current_mark(), 0) def test12_keepMark(self): """Ensuring the mark is kept after an UNDO operation""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test12_keepMark..." % self.__class__.__name__) self.h5file.enable_undo() self.h5file.create_array('/', 'newarray1', [1]) mid = self.h5file.mark() self.assertIsNotNone(mid) self._do_reopen() self.h5file.undo() # We should have moved to the initial mark. self.assertEqual(self.h5file.get_current_mark(), 0) # So /newarray1 should not be there. self.assertNotIn('/newarray1', self.h5file) def test13_severalEnableDisable(self): """Checking that successive enable/disable Undo works""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test13_severalEnableDisable..." % self.__class__.__name__) self.h5file.enable_undo() self.h5file.create_array('/', 'newarray1', [1]) self.h5file.undo() self._do_reopen() # We should have moved to 'mid' mark, not the initial mark. self.assertEqual(self.h5file.get_current_mark(), 0) # So /newarray1 should still be there. self.assertNotIn('/newarray1', self.h5file) # Close this do/undo session self.h5file.disable_undo() # Do something self.h5file.create_array('/', 'newarray2', [1]) # Enable again do/undo self.h5file.enable_undo() self.h5file.create_array('/', 'newarray3', [1]) mid = self.h5file.mark() self.h5file.create_array('/', 'newarray4', [1]) self.h5file.undo() # We should have moved to 'mid' mark, not the initial mark. self.assertEqual(self.h5file.get_current_mark(), mid) # So /newarray2 and /newarray3 should still be there. self.assertNotIn('/newarray1', self.h5file) self.assertIn('/newarray2', self.h5file) self.assertIn('/newarray3', self.h5file) self.assertNotIn('/newarray4', self.h5file) # Close this do/undo session self._do_reopen() self.h5file.disable_undo() # Enable again do/undo self.h5file.enable_undo() self.h5file.create_array('/', 'newarray1', [1]) self.h5file.create_array('/', 'newarray4', [1]) # So /newarray2 and /newarray3 should still be there. self.assertIn('/newarray1', self.h5file) self.assertIn('/newarray2', self.h5file) self.assertIn('/newarray3', self.h5file) self.assertIn('/newarray4', self.h5file) self.h5file.undo() self._do_reopen() self.assertNotIn('/newarray1', self.h5file) self.assertIn('/newarray2', self.h5file) self.assertIn('/newarray3', self.h5file) self.assertNotIn('/newarray4', self.h5file) # Close this do/undo session self.h5file.disable_undo() class PersistenceTestCase(BasicTestCase): """Test for basic Undo/Redo operations with persistence.""" _reopen_flag = True class CreateArrayTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test for create_array operations""" def setUp(self): super().setUp() h5file = self.h5file root = h5file.root # Create an array h5file.create_array(root, 'array', [1, 2], title="Title example") # Create another array object h5file.create_array(root, 'anarray', [1], "Array title") # Create a group object group = h5file.create_group(root, 'agroup', "Group title") # Create a couple of objects there h5file.create_array(group, 'anarray1', [2], "Array title 1") h5file.create_array(group, 'anarray2', [2], "Array title 2") # Create a lonely group in first level h5file.create_group(root, 'agroup2', "Group title 2") # Create a new group in the second level h5file.create_group(group, 'agroup3', "Group title 3") def test00(self): """Checking one action.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [1, 2], "Another array 1") # Now undo the past operation self.h5file.undo() # Check that otherarray does not exist in the object tree self.assertNotIn("/otherarray1", self.h5file) # Redo the operation self.h5file.redo() # Check that otherarray has come back to life in a sane state self.assertIn("/otherarray1", self.h5file) self.assertEqual(self.h5file.root.otherarray1.title, "Another array 1") self.assertEqual(self.h5file.root.otherarray1.read(), [1, 2]) def test01(self): """Checking two actions.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [1, 2], "Another array 1") self.h5file.create_array('/', 'otherarray2', [2, 3], "Another array 2") # Now undo the past operation self.h5file.undo() # Check that otherarray does not exist in the object tree self.assertNotIn("/otherarray1", self.h5file) self.assertNotIn("/otherarray2", self.h5file) # Redo the operation self.h5file.redo() # Check that otherarray has come back to life in a sane state self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertEqual(self.h5file.root.otherarray1.title, "Another array 1") self.assertEqual(self.h5file.root.otherarray2.title, "Another array 2") self.assertEqual(self.h5file.root.otherarray1.read(), [1, 2]) self.assertEqual(self.h5file.root.otherarray2.read(), [2, 3]) def test02(self): """Checking three actions.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [1, 2], "Another array 1") self.h5file.create_array('/', 'otherarray2', [2, 3], "Another array 2") self.h5file.create_array('/', 'otherarray3', [3, 4], "Another array 3") # Now undo the past operation self.h5file.undo() # Check that otherarray does not exist in the object tree self.assertNotIn("/otherarray1", self.h5file) self.assertNotIn("/otherarray2", self.h5file) self.assertNotIn("/otherarray3", self.h5file) # Redo the operation self.h5file.redo() # Check that otherarray has come back to life in a sane state self.assertIn("/otherarray1", self.h5file) self.assertIn("/otherarray2", self.h5file) self.assertIn("/otherarray3", self.h5file) self.assertEqual(self.h5file.root.otherarray1.title, "Another array 1") self.assertEqual(self.h5file.root.otherarray2.title, "Another array 2") self.assertEqual(self.h5file.root.otherarray3.title, "Another array 3") self.assertEqual(self.h5file.root.otherarray1.read(), [1, 2]) self.assertEqual(self.h5file.root.otherarray2.read(), [2, 3]) self.assertEqual(self.h5file.root.otherarray3.read(), [3, 4]) def test03(self): """Checking three actions in different depth levels.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.create_array('/', 'otherarray1', [1, 2], "Another array 1") self.h5file.create_array('/agroup', 'otherarray2', [2, 3], "Another array 2") self.h5file.create_array('/agroup/agroup3', 'otherarray3', [3, 4], "Another array 3") # Now undo the past operation self.h5file.undo() # Check that otherarray does not exist in the object tree self.assertNotIn("/otherarray1", self.h5file) self.assertNotIn("/agroup/otherarray2", self.h5file) self.assertNotIn("/agroup/agroup3/otherarray3", self.h5file) # Redo the operation self.h5file.redo() # Check that otherarray has come back to life in a sane state self.assertIn("/otherarray1", self.h5file) self.assertIn("/agroup/otherarray2", self.h5file) self.assertIn("/agroup/agroup3/otherarray3", self.h5file) self.assertEqual(self.h5file.root.otherarray1.title, "Another array 1") self.assertEqual(self.h5file.root.agroup.otherarray2.title, "Another array 2") self.assertEqual(self.h5file.root.agroup.agroup3.otherarray3.title, "Another array 3") self.assertEqual(self.h5file.root.otherarray1.read(), [1, 2]) self.assertEqual(self.h5file.root.agroup.otherarray2.read(), [2, 3]) self.assertEqual(self.h5file.root.agroup.agroup3.otherarray3.read(), [3, 4]) class CreateGroupTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test for create_group operations""" def setUp(self): super().setUp() h5file = self.h5file root = h5file.root # Create an array h5file.create_array(root, 'array', [1, 2], title="Title example") # Create another array object h5file.create_array(root, 'anarray', [1], "Array title") # Create a group object group = h5file.create_group(root, 'agroup', "Group title") # Create a couple of objects there h5file.create_array(group, 'anarray1', [2], "Array title 1") h5file.create_array(group, 'anarray2', [2], "Array title 2") # Create a lonely group in first level h5file.create_group(root, 'agroup2', "Group title 2") # Create a new group in the second level h5file.create_group(group, 'agroup3', "Group title 3") def test00(self): """Checking one action.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new group self.h5file.create_group('/', 'othergroup1', "Another group 1") # Now undo the past operation self.h5file.undo() # Check that othergroup1 does not exist in the object tree self.assertNotIn("/othergroup1", self.h5file) # Redo the operation self.h5file.redo() # Check that othergroup1 has come back to life in a sane state self.assertIn("/othergroup1", self.h5file) self.assertEqual(self.h5file.root.othergroup1._v_title, "Another group 1") def test01(self): """Checking two actions.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new group self.h5file.create_group('/', 'othergroup1', "Another group 1") self.h5file.create_group('/', 'othergroup2', "Another group 2") # Now undo the past operation self.h5file.undo() # Check that othergroup does not exist in the object tree self.assertNotIn("/othergroup1", self.h5file) self.assertNotIn("/othergroup2", self.h5file) # Redo the operation self.h5file.redo() # Check that othergroup* has come back to life in a sane state self.assertIn("/othergroup1", self.h5file) self.assertIn("/othergroup2", self.h5file) self.assertEqual(self.h5file.root.othergroup1._v_title, "Another group 1") self.assertEqual(self.h5file.root.othergroup2._v_title, "Another group 2") def test02(self): """Checking three actions.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new group self.h5file.create_group('/', 'othergroup1', "Another group 1") self.h5file.create_group('/', 'othergroup2', "Another group 2") self.h5file.create_group('/', 'othergroup3', "Another group 3") # Now undo the past operation self.h5file.undo() # Check that othergroup* does not exist in the object tree self.assertNotIn("/othergroup1", self.h5file) self.assertNotIn("/othergroup2", self.h5file) self.assertNotIn("/othergroup3", self.h5file) # Redo the operation self.h5file.redo() # Check that othergroup* has come back to life in a sane state self.assertIn("/othergroup1", self.h5file) self.assertIn("/othergroup2", self.h5file) self.assertIn("/othergroup3", self.h5file) self.assertEqual(self.h5file.root.othergroup1._v_title, "Another group 1") self.assertEqual(self.h5file.root.othergroup2._v_title, "Another group 2") self.assertEqual(self.h5file.root.othergroup3._v_title, "Another group 3") def test03(self): """Checking three actions in different depth levels.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new group self.h5file.create_group('/', 'othergroup1', "Another group 1") self.h5file.create_group( '/othergroup1', 'othergroup2', "Another group 2") self.h5file.create_group( '/othergroup1/othergroup2', 'othergroup3', "Another group 3") # Now undo the past operation self.h5file.undo() # Check that othergroup* does not exist in the object tree self.assertNotIn("/othergroup1", self.h5file) self.assertNotIn("/othergroup1/othergroup2", self.h5file) self.assertTrue( "/othergroup1/othergroup2/othergroup3" not in self.h5file) # Redo the operation self.h5file.redo() # Check that othergroup* has come back to life in a sane state self.assertIn("/othergroup1", self.h5file) self.assertIn("/othergroup1/othergroup2", self.h5file) self.assertIn("/othergroup1/othergroup2/othergroup3", self.h5file) self.assertEqual(self.h5file.root.othergroup1._v_title, "Another group 1") self.assertEqual(self.h5file.root.othergroup1.othergroup2._v_title, "Another group 2") self.assertEqual( self.h5file.root.othergroup1.othergroup2.othergroup3._v_title, "Another group 3") minRowIndex = 10 def populateTable(where, name): """Create a table under where with name name""" class Indexed(tb.IsDescription): var1 = tb.StringCol(itemsize=4, dflt=b"", pos=1) var2 = tb.BoolCol(dflt=0, pos=2) var3 = tb.IntCol(dflt=0, pos=3) var4 = tb.FloatCol(dflt=0, pos=4) nrows = minRowIndex table = where._v_file.create_table(where, name, Indexed, "Indexed", None, nrows) for i in range(nrows): table.row['var1'] = str(i) # table.row['var2'] = i > 2 table.row['var2'] = i % 2 table.row['var3'] = i table.row['var4'] = float(nrows - i - 1) table.row.append() table.flush() # Index all entries: indexrows = table.cols.var1.create_index() indexrows = table.cols.var2.create_index() indexrows = table.cols.var3.create_index() # Do not index the var4 column # indexrows = table.cols.var4.create_index() if common.verbose: print("Number of written rows:", nrows) print("Number of indexed rows:", table.cols.var1.index.nelements) print("Number of indexed rows(2):", indexrows) class RenameNodeTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test for rename_node operations""" def setUp(self): super().setUp() h5file = self.h5file root = h5file.root # Create an array h5file.create_array(root, 'array', [1, 2], title="Title example") # Create another array object h5file.create_array(root, 'anarray', [1], "Array title") # Create a group object group = h5file.create_group(root, 'agroup', "Group title") # Create a couple of objects there h5file.create_array(group, 'anarray1', [2], "Array title 1") h5file.create_array(group, 'anarray2', [2], "Array title 2") # Create a lonely group in first level h5file.create_group(root, 'agroup2', "Group title 2") # Create a new group in the second level h5file.create_group(group, 'agroup3', "Group title 3") # Create a table in root populateTable(self.h5file.root, 'table') def test00(self): """Checking rename_node (over Groups without children)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.rename_node('/agroup2', 'agroup3') # Now undo the past operation self.h5file.undo() # Check that it does not exist in the object tree self.assertIn("/agroup2", self.h5file) self.assertNotIn("/agroup3", self.h5file) self.assertEqual(self.h5file.root.agroup2._v_title, "Group title 2") # Redo the operation self.h5file.redo() # Check that otherarray has come back to life in a sane state self.assertNotIn("/agroup2", self.h5file) self.assertIn("/agroup3", self.h5file) self.assertEqual(self.h5file.root.agroup3._v_title, "Group title 2") def test01(self): """Checking rename_node (over Groups with children)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.rename_node('/agroup', 'agroup3') # Now undo the past operation self.h5file.undo() # Check that it does not exist in the object tree self.assertIn("/agroup", self.h5file) self.assertNotIn("/agroup3", self.h5file) # Check that children are reachable self.assertIn("/agroup/anarray1", self.h5file) self.assertIn("/agroup/anarray2", self.h5file) self.assertIn("/agroup/agroup3", self.h5file) self.assertEqual(self.h5file.root.agroup._v_title, "Group title") # Redo the operation self.h5file.redo() # Check that otherarray has come back to life in a sane state self.assertNotIn("/agroup", self.h5file) self.assertIn("/agroup3", self.h5file) self.assertEqual(self.h5file.root.agroup3._v_title, "Group title") # Check that children are reachable self.assertIn("/agroup3/anarray1", self.h5file) self.assertIn("/agroup3/anarray2", self.h5file) self.assertIn("/agroup3/agroup3", self.h5file) def test01b(self): """Checking rename_node (over Groups with children 2)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.rename_node('/agroup', 'agroup3') self.h5file.rename_node('/agroup3', 'agroup4') # Now undo the past operation self.h5file.undo() # Check that it does not exist in the object tree self.assertIn("/agroup", self.h5file) self.assertNotIn("/agroup4", self.h5file) # Check that children are reachable self.assertIn("/agroup/anarray1", self.h5file) self.assertIn("/agroup/anarray2", self.h5file) self.assertIn("/agroup/agroup3", self.h5file) self.assertEqual(self.h5file.root.agroup._v_title, "Group title") # Redo the operation self.h5file.redo() # Check that otherarray has come back to life in a sane state self.assertNotIn("/agroup", self.h5file) self.assertIn("/agroup4", self.h5file) self.assertEqual(self.h5file.root.agroup4._v_title, "Group title") # Check that children are reachable self.assertIn("/agroup4/anarray1", self.h5file) self.assertIn("/agroup4/anarray2", self.h5file) self.assertIn("/agroup4/agroup3", self.h5file) def test02(self): """Checking rename_node (over Leaves)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.rename_node('/anarray', 'anarray2') # Now undo the past operation self.h5file.undo() # Check that otherarray does not exist in the object tree self.assertIn("/anarray", self.h5file) self.assertNotIn("/anarray2", self.h5file) self.assertEqual(self.h5file.root.anarray.title, "Array title") # Redo the operation self.h5file.redo() # Check that otherarray has come back to life in a sane state self.assertNotIn("/anarray", self.h5file) self.assertIn("/anarray2", self.h5file) self.assertEqual(self.h5file.root.anarray2.title, "Array title") def test03(self): """Checking rename_node (over Tables)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.rename_node('/table', 'table2') # Now undo the past operation self.h5file.undo() # Check that table2 does not exist in the object tree self.assertIn("/table", self.h5file) table = self.h5file.root.table self.assertIsNotNone(table.cols.var1.index) self.assertIsNotNone(table.cols.var2.index) self.assertIsNotNone(table.cols.var3.index) self.assertIsNone(table.cols.var4.index) self.assertEqual(table.cols.var1.index.nelements, minRowIndex) self.assertEqual(table.cols.var2.index.nelements, minRowIndex) self.assertEqual(table.cols.var3.index.nelements, minRowIndex) self.assertNotIn("/table2", self.h5file) self.assertEqual(self.h5file.root.table.title, "Indexed") # Redo the operation self.h5file.redo() # Check that table2 has come back to life in a sane state self.assertNotIn("/table", self.h5file) self.assertIn("/table2", self.h5file) self.assertEqual(self.h5file.root.table2.title, "Indexed") table = self.h5file.root.table2 self.assertIsNotNone(table.cols.var1.index) self.assertIsNotNone(table.cols.var2.index) self.assertIsNotNone(table.cols.var3.index) self.assertEqual(table.cols.var1.index.nelements, minRowIndex) self.assertEqual(table.cols.var2.index.nelements, minRowIndex) self.assertEqual(table.cols.var3.index.nelements, minRowIndex) self.assertIsNone(table.cols.var4.index) class MoveNodeTestCase(common.TempFileMixin, common.PyTablesTestCase): """Tests for move_node operations""" def setUp(self): super().setUp() h5file = self.h5file root = h5file.root # Create an array h5file.create_array(root, 'array', [1, 2], title="Title example") # Create another array object h5file.create_array(root, 'anarray', [1], "Array title") # Create a group object group = h5file.create_group(root, 'agroup', "Group title") # Create a couple of objects there h5file.create_array(group, 'anarray1', [2], "Array title 1") h5file.create_array(group, 'anarray2', [2], "Array title 2") # Create a lonely group in first level h5file.create_group(root, 'agroup2', "Group title 2") # Create a new group in the second level h5file.create_group(group, 'agroup3', "Group title 3") # Create a table in root populateTable(self.h5file.root, 'table') def test00(self): """Checking move_node (over Leaf)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.move_node('/anarray', '/agroup/agroup3') # Now undo the past operation self.h5file.undo() # Check that it does not exist in the object tree self.assertIn("/anarray", self.h5file) self.assertNotIn("/agroup/agroup3/anarray", self.h5file) self.assertEqual(self.h5file.root.anarray.title, "Array title") # Redo the operation self.h5file.redo() # Check that otherarray has come back to life in a sane state self.assertNotIn("/anarray", self.h5file) self.assertIn("/agroup/agroup3/anarray", self.h5file) self.assertEqual(self.h5file.root.agroup.agroup3.anarray.title, "Array title") def test01(self): """Checking move_node (over Groups with children)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.move_node('/agroup', '/agroup2', 'agroup3') # Now undo the past operation self.h5file.undo() # Check that it does not exist in the object tree self.assertIn("/agroup", self.h5file) self.assertNotIn("/agroup2/agroup3", self.h5file) # Check that children are reachable self.assertIn("/agroup/anarray1", self.h5file) self.assertIn("/agroup/anarray2", self.h5file) self.assertIn("/agroup/agroup3", self.h5file) self.assertEqual(self.h5file.root.agroup._v_title, "Group title") # Redo the operation self.h5file.redo() # Check that otherarray has come back to life in a sane state self.assertNotIn("/agroup", self.h5file) self.assertIn("/agroup2/agroup3", self.h5file) self.assertEqual(self.h5file.root.agroup2.agroup3._v_title, "Group title") # Check that children are reachable self.assertIn("/agroup2/agroup3/anarray1", self.h5file) self.assertIn("/agroup2/agroup3/anarray2", self.h5file) self.assertIn("/agroup2/agroup3/agroup3", self.h5file) def test01b(self): """Checking move_node (over Groups with children 2)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.move_node('/agroup', '/', 'agroup3') self.h5file.move_node('/agroup3', '/agroup2', 'agroup4') # Now undo the past operation self.h5file.undo() # Check that it does not exist in the object tree self.assertIn("/agroup", self.h5file) self.assertNotIn("/agroup2/agroup4", self.h5file) # Check that children are reachable self.assertIn("/agroup/anarray1", self.h5file) self.assertIn("/agroup/anarray2", self.h5file) self.assertIn("/agroup/agroup3", self.h5file) self.assertEqual(self.h5file.root.agroup._v_title, "Group title") # Redo the operation self.h5file.redo() # Check that otherarray has come back to life in a sane state self.assertNotIn("/agroup", self.h5file) self.assertIn("/agroup2/agroup4", self.h5file) self.assertEqual(self.h5file.root.agroup2.agroup4._v_title, "Group title") # Check that children are reachable self.assertIn("/agroup2/agroup4/anarray1", self.h5file) self.assertIn("/agroup2/agroup4/anarray2", self.h5file) self.assertIn("/agroup2/agroup4/agroup3", self.h5file) def test02(self): """Checking move_node (over Leaves)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.move_node('/anarray', '/agroup2', 'anarray2') # Now undo the past operation self.h5file.undo() # Check that otherarray does not exist in the object tree self.assertIn("/anarray", self.h5file) self.assertNotIn("/agroup2/anarray2", self.h5file) self.assertEqual(self.h5file.root.anarray.title, "Array title") # Redo the operation self.h5file.redo() # Check that otherarray has come back to life in a sane state self.assertNotIn("/anarray", self.h5file) self.assertIn("/agroup2/anarray2", self.h5file) self.assertEqual( self.h5file.root.agroup2.anarray2.title, "Array title") def test03(self): """Checking move_node (over Tables)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.move_node('/table', '/agroup2', 'table2') # Now undo the past operation self.h5file.undo() # Check that table2 does not exist in the object tree self.assertIn("/table", self.h5file) self.assertNotIn("/agroup2/table2", self.h5file) table = self.h5file.root.table self.assertIsNotNone(table.cols.var1.index) self.assertIsNotNone(table.cols.var2.index) self.assertIsNotNone(table.cols.var3.index) self.assertIsNone(table.cols.var4.index) self.assertEqual(table.cols.var1.index.nelements, minRowIndex) self.assertEqual(table.cols.var2.index.nelements, minRowIndex) self.assertEqual(table.cols.var3.index.nelements, minRowIndex) self.assertEqual(self.h5file.root.table.title, "Indexed") # Redo the operation self.h5file.redo() # Check that table2 has come back to life in a sane state self.assertNotIn("/table", self.h5file) self.assertIn("/agroup2/table2", self.h5file) self.assertEqual(self.h5file.root.agroup2.table2.title, "Indexed") table = self.h5file.root.agroup2.table2 self.assertIsNotNone(table.cols.var1.index) self.assertIsNotNone(table.cols.var2.index) self.assertIsNotNone(table.cols.var3.index) self.assertEqual(table.cols.var1.index.nelements, minRowIndex) self.assertEqual(table.cols.var2.index.nelements, minRowIndex) self.assertEqual(table.cols.var3.index.nelements, minRowIndex) self.assertIsNone(table.cols.var4.index) class RemoveNodeTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test for remove_node operations""" def setUp(self): super().setUp() h5file = self.h5file root = h5file.root # Create an array h5file.create_array(root, 'array', [1, 2], title="Title example") # Create another array object h5file.create_array(root, 'anarray', [1], "Array title") # Create a group object group = h5file.create_group(root, 'agroup', "Group title") # Create a couple of objects there h5file.create_array(group, 'anarray1', [2], "Array title 1") h5file.create_array(group, 'anarray2', [2], "Array title 2") # Create a lonely group in first level h5file.create_group(root, 'agroup2', "Group title 2") # Create a new group in the second level h5file.create_group(group, 'agroup3', "Group title 3") # Create a table in root populateTable(self.h5file.root, 'table') def test00(self): """Checking remove_node (over Leaf)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Delete an existing array self.h5file.remove_node('/anarray') # Now undo the past operation self.h5file.undo() # Check that it does exist in the object tree self.assertIn("/anarray", self.h5file) self.assertEqual(self.h5file.root.anarray.title, "Array title") # Redo the operation self.h5file.redo() # Check that array has gone again self.assertNotIn("/anarray", self.h5file) def test00b(self): """Checking remove_node (over several Leaves)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00b..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Delete a couple of arrays self.h5file.remove_node('/anarray') self.h5file.remove_node('/agroup/anarray2') # Now undo the past operation self.h5file.undo() # Check that arrays has come into life self.assertIn("/anarray", self.h5file) self.assertIn("/agroup/anarray2", self.h5file) self.assertEqual(self.h5file.root.anarray.title, "Array title") self.assertEqual( self.h5file.root.agroup.anarray2.title, "Array title 2") # Redo the operation self.h5file.redo() # Check that arrays has disappeared again self.assertNotIn("/anarray", self.h5file) self.assertNotIn("/agroup/anarray2", self.h5file) def test00c(self): """Checking remove_node (over Tables)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00c..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Create a new array self.h5file.remove_node('/table') # Now undo the past operation self.h5file.undo() # Check that table2 does not exist in the object tree self.assertIn("/table", self.h5file) table = self.h5file.root.table self.assertIsNotNone(table.cols.var1.index) self.assertIsNotNone(table.cols.var2.index) self.assertIsNotNone(table.cols.var3.index) self.assertIsNone(table.cols.var4.index) self.assertEqual(table.cols.var1.index.nelements, minRowIndex) self.assertEqual(table.cols.var2.index.nelements, minRowIndex) self.assertEqual(table.cols.var3.index.nelements, minRowIndex) self.assertEqual(self.h5file.root.table.title, "Indexed") # Redo the operation self.h5file.redo() # Check that table2 has come back to life in a sane state self.assertNotIn("/table", self.h5file) def test01(self): """Checking remove_node (over Groups with children)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Delete a group recursively self.h5file.remove_node('/agroup', recursive=1) # Now undo the past operation self.h5file.undo() # Check that parent and children has come into life in a sane state self.assertIn("/agroup", self.h5file) self.assertIn("/agroup/anarray1", self.h5file) self.assertIn("/agroup/anarray2", self.h5file) self.assertIn("/agroup/agroup3", self.h5file) self.assertEqual(self.h5file.root.agroup._v_title, "Group title") # Redo the operation self.h5file.redo() # Check that parent and children are not reachable self.assertNotIn("/agroup", self.h5file) self.assertNotIn("/agroup/anarray1", self.h5file) self.assertNotIn("/agroup/anarray2", self.h5file) self.assertNotIn("/agroup/agroup3", self.h5file) def test01b(self): """Checking remove_node (over Groups with children 2)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # Remove a couple of groups self.h5file.remove_node('/agroup', recursive=1) self.h5file.remove_node('/agroup2') # Now undo the past operation self.h5file.undo() # Check that they does exist in the object tree self.assertIn("/agroup", self.h5file) self.assertIn("/agroup2", self.h5file) # Check that children are reachable self.assertIn("/agroup/anarray1", self.h5file) self.assertIn("/agroup/anarray2", self.h5file) self.assertIn("/agroup/agroup3", self.h5file) self.assertEqual(self.h5file.root.agroup._v_title, "Group title") # Redo the operation self.h5file.redo() # Check that groups does not exist again self.assertNotIn("/agroup", self.h5file) self.assertNotIn("/agroup2", self.h5file) # Check that children are not reachable self.assertNotIn("/agroup/anarray1", self.h5file) self.assertNotIn("/agroup/anarray2", self.h5file) self.assertNotIn("/agroup/agroup3", self.h5file) class CopyNodeTestCase(common.TempFileMixin, common.PyTablesTestCase): """Tests for copy_node and copy_children operations""" def setUp(self): super().setUp() h5file = self.h5file root = h5file.root # Create an array h5file.create_array(root, 'array', [1, 2], title="Title example") # Create another array object h5file.create_array(root, 'anarray', [1], "Array title") # Create a group object group = h5file.create_group(root, 'agroup', "Group title") # Create a couple of objects there h5file.create_array(group, 'anarray1', [2], "Array title 1") h5file.create_array(group, 'anarray2', [2], "Array title 2") # Create a lonely group in first level h5file.create_group(root, 'agroup2', "Group title 2") # Create a new group in the second level h5file.create_group(group, 'agroup3', "Group title 3") # Create a table in root populateTable(self.h5file.root, 'table') def test00_copyLeaf(self): """Checking copy_node (over Leaves)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_copyLeaf..." % self.__class__.__name__) # Enable undo/redo. self.h5file.enable_undo() # /anarray => /agroup/agroup3/ new_node = self.h5file.copy_node('/anarray', '/agroup/agroup3') # Undo the copy. self.h5file.undo() # Check that the copied node does not exist in the object tree. self.assertNotIn('/agroup/agroup3/anarray', self.h5file) # Redo the copy. self.h5file.redo() # Check that the copied node exists again in the object tree. self.assertIn('/agroup/agroup3/anarray', self.h5file) self.assertIs(self.h5file.root.agroup.agroup3.anarray, new_node) def test00b_copyTable(self): """Checking copy_node (over Tables)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00b_copyTable..." % self.__class__.__name__) # open the do/undo self.h5file.enable_undo() # /table => /agroup/agroup3/ warnings.filterwarnings("ignore", category=UserWarning) table = self.h5file.copy_node( '/table', '/agroup/agroup3', propindexes=True) warnings.filterwarnings("default", category=UserWarning) self.assertIn("/agroup/agroup3/table", self.h5file) table = self.h5file.root.agroup.agroup3.table self.assertEqual(table.title, "Indexed") self.assertIsNotNone(table.cols.var1.index) self.assertIsNotNone(table.cols.var2.index) self.assertIsNotNone(table.cols.var3.index) self.assertEqual(table.cols.var1.index.nelements, minRowIndex) self.assertEqual(table.cols.var2.index.nelements, minRowIndex) self.assertEqual(table.cols.var3.index.nelements, minRowIndex) self.assertIsNone(table.cols.var4.index) # Now undo the past operation self.h5file.undo() table = self.h5file.root.table self.assertIsNotNone(table.cols.var1.index) self.assertIsNotNone(table.cols.var2.index) self.assertIsNotNone(table.cols.var3.index) self.assertIsNone(table.cols.var4.index) self.assertEqual(table.cols.var1.index.nelements, minRowIndex) self.assertEqual(table.cols.var2.index.nelements, minRowIndex) self.assertEqual(table.cols.var3.index.nelements, minRowIndex) # Check that the copied node does not exist in the object tree. self.assertNotIn("/agroup/agroup3/table", self.h5file) # Redo the operation self.h5file.redo() # Check that table has come back to life in a sane state self.assertIn("/table", self.h5file) self.assertIn("/agroup/agroup3/table", self.h5file) table = self.h5file.root.agroup.agroup3.table self.assertEqual(table.title, "Indexed") self.assertIsNotNone(table.cols.var1.index) self.assertIsNotNone(table.cols.var2.index) self.assertIsNotNone(table.cols.var3.index) self.assertEqual(table.cols.var1.index.nelements, minRowIndex) self.assertEqual(table.cols.var2.index.nelements, minRowIndex) self.assertEqual(table.cols.var3.index.nelements, minRowIndex) self.assertIsNone(table.cols.var4.index) def test01_copyGroup(self): """Copying a group (recursively).""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_copyGroup..." % self.__class__.__name__) # Enable undo/redo. self.h5file.enable_undo() # /agroup => /acopy new_node = self.h5file.copy_node( '/agroup', newname='acopy', recursive=True) # Undo the copy. self.h5file.undo() # Check that the copied node does not exist in the object tree. self.assertNotIn('/acopy', self.h5file) self.assertNotIn('/acopy/anarray1', self.h5file) self.assertNotIn('/acopy/anarray2', self.h5file) self.assertNotIn('/acopy/agroup3', self.h5file) # Redo the copy. self.h5file.redo() # Check that the copied node exists again in the object tree. self.assertIn('/acopy', self.h5file) self.assertIn('/acopy/anarray1', self.h5file) self.assertIn('/acopy/anarray2', self.h5file) self.assertIn('/acopy/agroup3', self.h5file) self.assertIs(self.h5file.root.acopy, new_node) def test02_copyLeafOverwrite(self): """Copying a leaf, overwriting destination.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_copyLeafOverwrite..." % self.__class__.__name__) # Enable undo/redo. self.h5file.enable_undo() # /anarray => /agroup/agroup oldNode = self.h5file.root.agroup new_node = self.h5file.copy_node( '/anarray', newname='agroup', overwrite=True) # Undo the copy. self.h5file.undo() # Check that the copied node does not exist in the object tree. # Check that the overwritten node exists again in the object tree. self.assertIs(self.h5file.root.agroup, oldNode) # Redo the copy. self.h5file.redo() # Check that the copied node exists again in the object tree. # Check that the overwritten node does not exist in the object tree. self.assertIs(self.h5file.root.agroup, new_node) def test03_copyChildren(self): """Copying the children of a group""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_copyChildren..." % self.__class__.__name__) # Enable undo/redo. self.h5file.enable_undo() # /agroup/* => /agroup/ self.h5file.copy_children('/agroup', '/agroup2', recursive=True) # Undo the copy. self.h5file.undo() # Check that the copied nodes do not exist in the object tree. self.assertNotIn('/agroup2/anarray1', self.h5file) self.assertNotIn('/agroup2/anarray2', self.h5file) self.assertNotIn('/agroup2/agroup3', self.h5file) # Redo the copy. self.h5file.redo() # Check that the copied nodes exist again in the object tree. self.assertIn('/agroup2/anarray1', self.h5file) self.assertIn('/agroup2/anarray2', self.h5file) self.assertIn('/agroup2/agroup3', self.h5file) class ComplexTestCase(common.TempFileMixin, common.PyTablesTestCase): """Tests for a mix of all operations""" def setUp(self): super().setUp() h5file = self.h5file root = h5file.root # Create an array h5file.create_array(root, 'array', [1, 2], title="Title example") # Create another array object h5file.create_array(root, 'anarray', [1], "Array title") # Create a group object group = h5file.create_group(root, 'agroup', "Group title") # Create a couple of objects there h5file.create_array(group, 'anarray1', [2], "Array title 1") h5file.create_array(group, 'anarray2', [2], "Array title 2") # Create a lonely group in first level h5file.create_group(root, 'agroup2', "Group title 2") # Create a new group in the second level h5file.create_group(group, 'agroup3', "Group title 3") def test00(self): """Mix of create_array, create_group, renameNone, move_node, remove_node, copy_node and copy_children.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00..." % self.__class__.__name__) # Enable undo/redo. self.h5file.enable_undo() # Create an array self.h5file.create_array(self.h5file.root, 'anarray3', [1], "Array title 3") # Create a group self.h5file.create_group(self.h5file.root, 'agroup3', "Group title 3") # /anarray => /agroup/agroup3/ new_node = self.h5file.copy_node('/anarray3', '/agroup/agroup3') new_node = self.h5file.copy_children( '/agroup', '/agroup3', recursive=1) # rename anarray self.h5file.rename_node('/anarray', 'anarray4') # Move anarray new_node = self.h5file.copy_node('/anarray3', '/agroup') # Remove anarray4 self.h5file.remove_node('/anarray4') # Undo the actions self.h5file.undo() self.assertNotIn('/anarray4', self.h5file) self.assertNotIn('/anarray3', self.h5file) self.assertNotIn('/agroup/agroup3/anarray3', self.h5file) self.assertNotIn('/agroup3', self.h5file) self.assertNotIn('/anarray4', self.h5file) self.assertIn('/anarray', self.h5file) # Redo the actions self.h5file.redo() # Check that the copied node exists again in the object tree. self.assertIn('/agroup/agroup3/anarray3', self.h5file) self.assertIn('/agroup/anarray3', self.h5file) self.assertIn('/agroup3/agroup3/anarray3', self.h5file) self.assertNotIn('/agroup3/anarray3', self.h5file) self.assertIs(self.h5file.root.agroup.anarray3, new_node) self.assertNotIn('/anarray', self.h5file) self.assertNotIn('/anarray4', self.h5file) def test01(self): """Test with multiple generations (Leaf case)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01..." % self.__class__.__name__) # Enable undo/redo. self.h5file.enable_undo() # remove /anarray self.h5file.remove_node('/anarray') # Create an array in the same place self.h5file.create_array(self.h5file.root, 'anarray', [2], "Array title 2") # remove the array again self.h5file.remove_node('/anarray') # Create an array self.h5file.create_array(self.h5file.root, 'anarray', [3], "Array title 3") # remove the array again self.h5file.remove_node('/anarray') # Create an array self.h5file.create_array(self.h5file.root, 'anarray', [4], "Array title 4") # Undo the actions self.h5file.undo() # Check that /anarray is in the correct state before redoing self.assertEqual(self.h5file.root.anarray.title, "Array title") self.assertEqual(self.h5file.root.anarray[:], [1]) # Redo the actions self.h5file.redo() self.assertEqual(self.h5file.root.anarray.title, "Array title 4") self.assertEqual(self.h5file.root.anarray[:], [4]) def test02(self): """Test with multiple generations (Group case)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02..." % self.__class__.__name__) # Enable undo/redo. self.h5file.enable_undo() # remove /agroup self.h5file.remove_node('/agroup2') # Create a group in the same place self.h5file.create_group(self.h5file.root, 'agroup2', "Group title 22") # remove the group self.h5file.remove_node('/agroup2') # Create a group self.h5file.create_group(self.h5file.root, 'agroup2', "Group title 3") # remove the group self.h5file.remove_node('/agroup2') # Create a group self.h5file.create_group(self.h5file.root, 'agroup2', "Group title 4") # Create a child group self.h5file.create_group(self.h5file.root.agroup2, 'agroup5', "Group title 5") # Undo the actions self.h5file.undo() # Check that /agroup is in the state before enabling do/undo self.assertEqual(self.h5file.root.agroup2._v_title, "Group title 2") self.assertIn('/agroup2', self.h5file) # Redo the actions self.h5file.redo() self.assertEqual(self.h5file.root.agroup2._v_title, "Group title 4") self.assertEqual(self.h5file.root.agroup2.agroup5._v_title, "Group title 5") def test03(self): """Test with multiple generations (Group case, recursive remove)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03..." % self.__class__.__name__) # Enable undo/redo. self.h5file.enable_undo() # remove /agroup self.h5file.remove_node('/agroup', recursive=1) # Create a group in the same place self.h5file.create_group(self.h5file.root, 'agroup', "Group title 2") # remove the group self.h5file.remove_node('/agroup') # Create a group self.h5file.create_group(self.h5file.root, 'agroup', "Group title 3") # remove the group self.h5file.remove_node('/agroup') # Create a group self.h5file.create_group(self.h5file.root, 'agroup', "Group title 4") # Create a child group self.h5file.create_group(self.h5file.root.agroup, 'agroup5', "Group title 5") # Undo the actions self.h5file.undo() # Check that /agroup is in the state before enabling do/undo self.assertIn('/agroup', self.h5file) self.assertEqual(self.h5file.root.agroup._v_title, "Group title") self.assertIn('/agroup/anarray1', self.h5file) self.assertIn('/agroup/anarray2', self.h5file) self.assertIn('/agroup/agroup3', self.h5file) self.assertNotIn('/agroup/agroup5', self.h5file) # Redo the actions self.h5file.redo() self.assertIn('/agroup', self.h5file) self.assertEqual(self.h5file.root.agroup._v_title, "Group title 4") self.assertIn('/agroup/agroup5', self.h5file) self.assertEqual( self.h5file.root.agroup.agroup5._v_title, "Group title 5") def test03b(self): """Test with multiple generations (Group case, recursive remove, case 2)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03b..." % self.__class__.__name__) # Enable undo/redo. self.h5file.enable_undo() # Create a new group with a child self.h5file.create_group(self.h5file.root, 'agroup3', "Group title 3") self.h5file.create_group(self.h5file.root.agroup3, 'agroup4', "Group title 4") # remove /agroup3 self.h5file.remove_node('/agroup3', recursive=1) # Create a group in the same place self.h5file.create_group(self.h5file.root, 'agroup3', "Group title 4") # Undo the actions self.h5file.undo() # Check that /agroup is in the state before enabling do/undo self.assertNotIn('/agroup3', self.h5file) # Redo the actions self.h5file.redo() self.assertEqual(self.h5file.root.agroup3._v_title, "Group title 4") self.assertIn('/agroup3', self.h5file) self.assertNotIn('/agroup/agroup4', self.h5file) class AttributesTestCase(common.TempFileMixin, common.PyTablesTestCase): """Tests for operation on attributes""" def setUp(self): super().setUp() # Create an array. array = self.h5file.create_array('/', 'array', [1, 2]) # Set some attributes on it. attrs = array.attrs attrs.attr_1 = 10 attrs.attr_2 = 20 attrs.attr_3 = 30 def test00_setAttr(self): """Setting a nonexistent attribute""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_setAttr..." % self.__class__.__name__) array = self.h5file.root.array attrs = array.attrs self.h5file.enable_undo() setattr(attrs, 'attr_0', 0) self.assertIn('attr_0', attrs) self.assertEqual(attrs.attr_0, 0) self.h5file.undo() self.assertNotIn('attr_0', attrs) self.h5file.redo() self.assertIn('attr_0', attrs) self.assertEqual(attrs.attr_0, 0) def test01_setAttrExisting(self): """Setting an existing attribute""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_setAttrExisting..." % self.__class__.__name__) array = self.h5file.root.array attrs = array.attrs self.h5file.enable_undo() setattr(attrs, 'attr_1', 11) self.assertIn('attr_1', attrs) self.assertEqual(attrs.attr_1, 11) self.h5file.undo() self.assertIn('attr_1', attrs) self.assertEqual(attrs.attr_1, 10) self.h5file.redo() self.assertIn('attr_1', attrs) self.assertEqual(attrs.attr_1, 11) def test02_delAttr(self): """Removing an attribute""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_delAttr..." % self.__class__.__name__) array = self.h5file.root.array attrs = array.attrs self.h5file.enable_undo() delattr(attrs, 'attr_1') self.assertNotIn('attr_1', attrs) self.h5file.undo() self.assertIn('attr_1', attrs) self.assertEqual(attrs.attr_1, 10) self.h5file.redo() self.assertNotIn('attr_1', attrs) def test03_copyNodeAttrs(self): """Copying an attribute set""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_copyNodeAttrs..." % self.__class__.__name__) rattrs = self.h5file.root._v_attrs rattrs.attr_0 = 0 rattrs.attr_1 = 100 array = self.h5file.root.array attrs = array.attrs self.h5file.enable_undo() attrs._f_copy(self.h5file.root) self.assertEqual(rattrs.attr_0, 0) self.assertEqual(rattrs.attr_1, 10) self.assertEqual(rattrs.attr_2, 20) self.assertEqual(rattrs.attr_3, 30) self.h5file.undo() self.assertEqual(rattrs.attr_0, 0) self.assertEqual(rattrs.attr_1, 100) self.assertNotIn('attr_2', rattrs) self.assertNotIn('attr_3', rattrs) self.h5file.redo() self.assertEqual(rattrs.attr_0, 0) self.assertEqual(rattrs.attr_1, 10) self.assertEqual(rattrs.attr_2, 20) self.assertEqual(rattrs.attr_3, 30) def test04_replaceNode(self): """Replacing a node with a rewritten attribute""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_replaceNode..." % self.__class__.__name__) array = self.h5file.root.array attrs = array.attrs self.h5file.enable_undo() attrs.attr_1 = 11 self.h5file.remove_node('/array') arr = self.h5file.create_array('/', 'array', [1]) arr.attrs.attr_1 = 12 self.h5file.undo() self.assertIn('attr_1', self.h5file.root.array.attrs) self.assertEqual(self.h5file.root.array.attrs.attr_1, 10) self.h5file.redo() self.assertIn('attr_1', self.h5file.root.array.attrs) self.assertEqual(self.h5file.root.array.attrs.attr_1, 12) class NotLoggedTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test not logged nodes.""" class NotLoggedArray(tb.node.NotLoggedMixin, tb.Array): pass def test00_hierarchy(self): """Performing hierarchy operations on a not logged node.""" self.h5file.create_group('/', 'tgroup') self.h5file.enable_undo() # Node creation is not undone. arr = self.NotLoggedArray(self.h5file.root, 'test', [1], self._getMethodName()) self.h5file.undo() self.assertIn('/test', self.h5file) # Node movement is not undone. arr.move('/tgroup') self.h5file.undo() self.assertIn('/tgroup/test', self.h5file) # Node removal is not undone. arr.remove() self.h5file.undo() self.assertNotIn('/tgroup/test', self.h5file) def test01_attributes(self): """Performing attribute operations on a not logged node.""" arr = self.NotLoggedArray(self.h5file.root, 'test', [1], self._getMethodName()) self.h5file.enable_undo() # Attribute creation is not undone. arr._v_attrs.foo = 'bar' self.h5file.undo() self.assertEqual(arr._v_attrs.foo, 'bar') # Attribute change is not undone. arr._v_attrs.foo = 'baz' self.h5file.undo() self.assertEqual(arr._v_attrs.foo, 'baz') # Attribute removal is not undone. del arr._v_attrs.foo self.h5file.undo() self.assertRaises(AttributeError, getattr, arr._v_attrs, 'foo') class CreateParentsTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test the ``createparents`` flag.""" def setUp(self): super().setUp() g1 = self.h5file.create_group('/', 'g1') self.h5file.create_group(g1, 'g2') def existing(self, paths): """Return a set of the existing paths in `paths`.""" return frozenset(path for path in paths if path in self.h5file) def basetest(self, doit, pre, post): pre() self.h5file.enable_undo() paths = ['/g1', '/g1/g2', '/g1/g2/g3', '/g1/g2/g3/g4'] for newpath in paths: before = self.existing(paths) doit(newpath) after = self.existing(paths) self.assertTrue(after.issuperset(before)) self.h5file.undo() post(newpath) after = self.existing(paths) self.assertEqual(after, before) def test00_create(self): """Test creating a node.""" def pre(): pass def doit(newpath): self.h5file.create_array(newpath, 'array', [1], createparents=True) self.assertIn(tb.path.join_path(newpath, 'array'), self.h5file) def post(newpath): self.assertNotIn(tb.path.join_path(newpath, 'array'), self.h5file) self.basetest(doit, pre, post) def test01_move(self): """Test moving a node.""" def pre(): self.h5file.create_array('/', 'array', [1]) def doit(newpath): self.h5file.move_node('/array', newpath, createparents=True) self.assertNotIn('/array', self.h5file) self.assertIn(tb.path.join_path(newpath, 'array'), self.h5file) def post(newpath): self.assertIn('/array', self.h5file) self.assertNotIn(tb.path.join_path(newpath, 'array'), self.h5file) self.basetest(doit, pre, post) def test02_copy(self): """Test copying a node.""" def pre(): self.h5file.create_array('/', 'array', [1]) def doit(newpath): self.h5file.copy_node('/array', newpath, createparents=True) self.assertIn(tb.path.join_path(newpath, 'array'), self.h5file) def post(newpath): self.assertNotIn(tb.path.join_path(newpath, 'array'), self.h5file) self.basetest(doit, pre, post) def test03_copyChildren(self): """Test copying the children of a group.""" def pre(): g = self.h5file.create_group('/', 'group') self.h5file.create_array(g, 'array1', [1]) self.h5file.create_array(g, 'array2', [1]) def doit(newpath): self.h5file.copy_children('/group', newpath, createparents=True) self.assertIn(tb.path.join_path(newpath, 'array1'), self.h5file) self.assertIn(tb.path.join_path(newpath, 'array2'), self.h5file) def post(newpath): self.assertNotIn(tb.path.join_path(newpath, 'array1'), self.h5file) self.assertNotIn(tb.path.join_path(newpath, 'array2'), self.h5file) self.basetest(doit, pre, post) def suite(): theSuite = common.unittest.TestSuite() niter = 1 # common.heavy = 1 # uncomment this only for testing purposes for n in range(niter): theSuite.addTest(common.unittest.makeSuite(BasicTestCase)) theSuite.addTest(common.unittest.makeSuite(PersistenceTestCase)) theSuite.addTest(common.unittest.makeSuite(CreateArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(CreateGroupTestCase)) theSuite.addTest(common.unittest.makeSuite(RenameNodeTestCase)) theSuite.addTest(common.unittest.makeSuite(MoveNodeTestCase)) theSuite.addTest(common.unittest.makeSuite(RemoveNodeTestCase)) theSuite.addTest(common.unittest.makeSuite(CopyNodeTestCase)) theSuite.addTest(common.unittest.makeSuite(AttributesTestCase)) theSuite.addTest(common.unittest.makeSuite(ComplexTestCase)) theSuite.addTest(common.unittest.makeSuite(NotLoggedTestCase)) theSuite.addTest(common.unittest.makeSuite(CreateParentsTestCase)) if common.heavy: pass return theSuite if __name__ == '__main__': import sys common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_earray.py000066400000000000000000003105721416254111300205620ustar00rootroot00000000000000import sys from pathlib import Path import numpy as np import tables as tb from tables.tests import common class BasicTestCase(common.TempFileMixin, common.PyTablesTestCase): # Default values obj = None flavor = "numpy" type = 'int32' dtype = 'int32' shape = (2, 0) start = 0 stop = 10 step = 1 length = 1 chunksize = 5 nappends = 10 compress = 0 complib = "zlib" # Default compression library shuffle = 0 fletcher32 = 0 reopen = 1 # Tells whether the file has to be reopened on each test or not def setUp(self): super().setUp() # Create an instance of an HDF5 Table self.rootgroup = self.h5file.root self.populateFile() if self.reopen: # Close the file self.h5file.close() def populateFile(self): group = self.rootgroup obj = self.obj if obj is None: if self.type == "string": atom = tb.StringAtom(itemsize=self.length) else: atom = tb.Atom.from_type(self.type) else: atom = None title = self.__class__.__name__ filters = tb.Filters(complevel=self.compress, complib=self.complib, shuffle=self.shuffle, fletcher32=self.fletcher32) earray = self.h5file.create_earray(group, 'earray1', atom=atom, shape=self.shape, title=title, filters=filters, expectedrows=1, obj=obj) earray.flavor = self.flavor # Fill it with rows self.rowshape = list(earray.shape) if obj is not None: self.rowshape[0] = 0 self.objsize = self.length for i in self.rowshape: if i != 0: self.objsize *= i self.extdim = earray.extdim self.objsize *= self.chunksize self.rowshape[earray.extdim] = self.chunksize if self.type == "string": object = np.ndarray(buffer=b"a"*self.objsize, shape=self.rowshape, dtype="S%s" % earray.atom.itemsize) else: object = np.arange(self.objsize, dtype=earray.atom.dtype.base) object.shape = self.rowshape if common.verbose: if self.flavor == "numpy": print("Object to append -->", object) else: print("Object to append -->", repr(object)) for i in range(self.nappends): if self.type == "string": earray.append(object) else: earray.append(object * i) def _get_shape(self): if self.shape is not None: shape = self.shape else: shape = np.asarray(self.obj).shape return shape def test00_attributes(self): if self.reopen: self._reopen() obj = self.h5file.get_node("/earray1") shape = self._get_shape() shape = list(shape) shape[self.extdim] = self.chunksize * self.nappends if self.obj is not None: shape[self.extdim] += len(self.obj) shape = tuple(shape) self.assertEqual(obj.flavor, self.flavor) self.assertEqual(obj.shape, shape) self.assertEqual(obj.ndim, len(shape)) self.assertEqual(obj.nrows, shape[self.extdim]) self.assertEqual(obj.atom.type, self.type) def test01_iterEArray(self): """Checking enlargeable array iterator.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_iterEArray..." % self.__class__.__name__) # Create an instance of an HDF5 Table if self.reopen: self._reopen() earray = self.h5file.get_node("/earray1") # Choose a small value for buffer size earray.nrowsinbuf = 3 if common.verbose: print("EArray descr:", repr(earray)) print("shape of read array ==>", earray.shape) print("reopening?:", self.reopen) # Build the array to do comparisons if self.type == "string": object_ = np.ndarray(buffer=b"a"*self.objsize, shape=self.rowshape, dtype="S%s" % earray.atom.itemsize) else: object_ = np.arange(self.objsize, dtype=earray.atom.dtype.base) object_.shape = self.rowshape object_ = object_.swapaxes(earray.extdim, 0) if self.obj is not None: initialrows = len(self.obj) else: initialrows = 0 shape = self._get_shape() # Read all the array for idx, row in enumerate(earray): if idx < initialrows: self.assertTrue(common.allequal( row, np.asarray(self.obj[idx]), self.flavor)) continue chunk = int((earray.nrow - initialrows) % self.chunksize) if chunk == 0: if self.type == "string": object__ = object_ else: i = int(earray.nrow - initialrows) object__ = object_ * (i // self.chunksize) object = object__[chunk] # The next adds much more verbosity if common.verbose and 0: print("number of row ==>", earray.nrow) if hasattr(object, "shape"): print("shape should look as:", object.shape) print("row in earray ==>", repr(row)) print("Should look like ==>", repr(object)) self.assertEqual(initialrows + self.nappends * self.chunksize, earray.nrows) self.assertTrue(common.allequal(row, object, self.flavor)) if hasattr(row, "shape"): self.assertEqual(len(row.shape), len(shape) - 1) else: # Scalar case self.assertEqual(len(shape), 1) # Check filters: if self.compress != earray.filters.complevel and common.verbose: print("Error in compress. Class:", self.__class__.__name__) print("self, earray:", self.compress, earray.filters.complevel) self.assertEqual(earray.filters.complevel, self.compress) if self.compress > 0 and tb.which_lib_version(self.complib): self.assertEqual(earray.filters.complib, self.complib) if self.shuffle != earray.filters.shuffle and common.verbose: print("Error in shuffle. Class:", self.__class__.__name__) print("self, earray:", self.shuffle, earray.filters.shuffle) self.assertEqual(self.shuffle, earray.filters.shuffle) if self.fletcher32 != earray.filters.fletcher32 and common.verbose: print("Error in fletcher32. Class:", self.__class__.__name__) print("self, earray:", self.fletcher32, earray.filters.fletcher32) self.assertEqual(self.fletcher32, earray.filters.fletcher32) def test02_sssEArray(self): """Checking enlargeable array iterator with (start, stop, step)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_sssEArray..." % self.__class__.__name__) # Create an instance of an HDF5 Table if self.reopen: self._reopen() earray = self.h5file.get_node("/earray1") # Choose a small value for buffer size earray.nrowsinbuf = 3 if common.verbose: print("EArray descr:", repr(earray)) print("shape of read array ==>", earray.shape) print("reopening?:", self.reopen) # Build the array to do comparisons if self.type == "string": object_ = np.ndarray(buffer=b"a"*self.objsize, shape=self.rowshape, dtype="S%s" % earray.atom.itemsize) else: object_ = np.arange(self.objsize, dtype=earray.atom.dtype.base) object_.shape = self.rowshape object_ = object_.swapaxes(earray.extdim, 0) if self.obj is not None: initialrows = len(self.obj) else: initialrows = 0 shape = self._get_shape() # Read all the array for idx, row in enumerate(earray.iterrows(start=self.start, stop=self.stop, step=self.step)): if idx < initialrows: self.assertTrue(common.allequal( row, np.asarray(self.obj[idx]), self.flavor)) continue if self.chunksize == 1: index = 0 else: index = int((earray.nrow - initialrows) % self.chunksize) if self.type == "string": object__ = object_ else: i = int(earray.nrow - initialrows) object__ = object_ * (i // self.chunksize) object = object__[index] # The next adds much more verbosity if common.verbose and 0: print("number of row ==>", earray.nrow) if hasattr(object, "shape"): print("shape should look as:", object.shape) print("row in earray ==>", repr(row)) print("Should look like ==>", repr(object)) self.assertEqual(initialrows + self.nappends * self.chunksize, earray.nrows) self.assertTrue(common.allequal(row, object, self.flavor)) if hasattr(row, "shape"): self.assertEqual(len(row.shape), len(shape) - 1) else: # Scalar case self.assertEqual(len(shape), 1) def test03_readEArray(self): """Checking read() of enlargeable arrays.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_readEArray..." % self.__class__.__name__) # This conversion made just in case indices are numpy scalars if self.start is not None: self.start = int(self.start) if self.stop is not None: self.stop = int(self.stop) if self.step is not None: self.step = int(self.step) # Create an instance of an HDF5 Table if self.reopen: self._reopen() earray = self.h5file.get_node("/earray1") # Choose a small value for buffer size earray.nrowsinbuf = 3 if common.verbose: print("EArray descr:", repr(earray)) print("shape of read array ==>", earray.shape) print("reopening?:", self.reopen) # Build the array to do comparisons if self.type == "string": object_ = np.ndarray(buffer=b"a"*self.objsize, shape=self.rowshape, dtype="S%s" % earray.atom.itemsize) else: object_ = np.arange(self.objsize, dtype=earray.atom.dtype.base) object_.shape = self.rowshape object_ = object_.swapaxes(earray.extdim, 0) if self.obj is not None: initialrows = len(self.obj) else: initialrows = 0 rowshape = self.rowshape rowshape[self.extdim] *= (self.nappends + initialrows) if self.type == "string": object__ = np.empty(shape=rowshape, dtype=f"S{earray.atom.itemsize}") else: object__ = np.empty(shape=rowshape, dtype=self.dtype) object__ = object__.swapaxes(0, self.extdim) if initialrows: object__[0:initialrows] = self.obj for i in range(self.nappends): j = initialrows + i * self.chunksize if self.type == "string": object__[j:j + self.chunksize] = object_ else: object__[j:j + self.chunksize] = object_ * i stop = self.stop if self.nappends: # stop == None means read only the element designed by start # (in read() contexts) if self.stop is None: if self.start == -1: # corner case stop = earray.nrows else: stop = self.start + 1 # Protection against number of elements less than existing # if rowshape[self.extdim] < self.stop or self.stop == 0: if rowshape[self.extdim] < stop: # self.stop == 0 means last row only in read() # and not in [::] slicing notation stop = rowshape[self.extdim] # do a copy() in order to ensure that len(object._data) # actually do a measure of its length # object = object__[self.start:stop:self.step].copy() object = object__[self.start:self.stop:self.step].copy() # Swap the axes again to have normal ordering if self.flavor == "numpy": object = object.swapaxes(0, self.extdim) else: object = np.empty(shape=self.shape, dtype=self.dtype) # Read all the array try: row = earray.read(self.start, self.stop, self.step) except IndexError: row = np.empty(shape=self.shape, dtype=self.dtype) if common.verbose: if hasattr(object, "shape"): print("shape should look as:", object.shape) print("Object read ==>", repr(row)) print("Should look like ==>", repr(object)) self.assertEqual(initialrows + self.nappends * self.chunksize, earray.nrows) self.assertTrue(common.allequal(row, object, self.flavor)) shape = self._get_shape() if hasattr(row, "shape"): self.assertEqual(len(row.shape), len(shape)) if self.flavor == "numpy": self.assertEqual(row.itemsize, earray.atom.itemsize) else: # Scalar case self.assertEqual(len(shape), 1) def test03_readEArray_out_argument(self): """Checking read() of enlargeable arrays.""" # This conversion made just in case indices are numpy scalars if self.start is not None: self.start = int(self.start) if self.stop is not None: self.stop = int(self.stop) if self.step is not None: self.step = int(self.step) # Create an instance of an HDF5 Table if self.reopen: self._reopen() earray = self.h5file.get_node("/earray1") # Choose a small value for buffer size earray.nrowsinbuf = 3 # Build the array to do comparisons if self.type == "string": object_ = np.ndarray(buffer=b"a"*self.objsize, shape=self.rowshape, dtype="S%s" % earray.atom.itemsize) else: object_ = np.arange(self.objsize, dtype=earray.atom.dtype.base) object_.shape = self.rowshape object_ = object_.swapaxes(earray.extdim, 0) if self.obj is not None: initialrows = len(self.obj) else: initialrows = 0 rowshape = self.rowshape rowshape[self.extdim] *= (self.nappends + initialrows) if self.type == "string": object__ = np.empty(shape=rowshape, dtype=f"S{earray.atom.itemsize}") else: object__ = np.empty(shape=rowshape, dtype=self.dtype) object__ = object__.swapaxes(0, self.extdim) if initialrows: object__[0:initialrows] = self.obj for i in range(self.nappends): j = initialrows + i * self.chunksize if self.type == "string": object__[j:j + self.chunksize] = object_ else: object__[j:j + self.chunksize] = object_ * i stop = self.stop if self.nappends: # stop == None means read only the element designed by start # (in read() contexts) if self.stop is None: if self.start == -1: # corner case stop = earray.nrows else: stop = self.start + 1 # Protection against number of elements less than existing # if rowshape[self.extdim] < self.stop or self.stop == 0: if rowshape[self.extdim] < stop: # self.stop == 0 means last row only in read() # and not in [::] slicing notation stop = rowshape[self.extdim] # do a copy() in order to ensure that len(object._data) # actually do a measure of its length # object = object__[self.start:stop:self.step].copy() object = object__[self.start:self.stop:self.step].copy() # Swap the axes again to have normal ordering if self.flavor == "numpy": object = object.swapaxes(0, self.extdim) else: object = np.empty(shape=self.shape, dtype=self.dtype) # Read all the array try: row = np.empty(earray.shape, dtype=earray.atom.dtype) slice_obj = [slice(None)] * len(earray.shape) # slice_obj[earray.maindim] = slice(self.start, stop, self.step) slice_obj[earray.maindim] = slice(self.start, self.stop, self.step) row = row[tuple(slice_obj)].copy() earray.read(self.start, self.stop, self.step, out=row) except IndexError: row = np.empty(shape=self.shape, dtype=self.dtype) if common.verbose: if hasattr(object, "shape"): print("shape should look as:", object.shape) print("Object read ==>", repr(row)) print("Should look like ==>", repr(object)) self.assertEqual(initialrows + self.nappends * self.chunksize, earray.nrows) self.assertTrue(common.allequal(row, object, self.flavor)) shape = self._get_shape() if hasattr(row, "shape"): self.assertEqual(len(row.shape), len(shape)) if self.flavor == "numpy": self.assertEqual(row.itemsize, earray.atom.itemsize) else: # Scalar case self.assertEqual(len(shape), 1) def test04_getitemEArray(self): """Checking enlargeable array __getitem__ special method.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_getitemEArray..." % self.__class__.__name__) if not hasattr(self, "slices"): # If there is not a slices attribute, create it # This conversion made just in case indices are numpy scalars if self.start is not None: self.start = int(self.start) if self.stop is not None: self.stop = int(self.stop) if self.step is not None: self.step = int(self.step) self.slices = (slice(self.start, self.stop, self.step),) # Create an instance of an HDF5 Table if self.reopen: self._reopen() earray = self.h5file.get_node("/earray1") # Choose a small value for buffer size # earray.nrowsinbuf = 3 # this does not really changes the chunksize if common.verbose: print("EArray descr:", repr(earray)) print("shape of read array ==>", earray.shape) print("reopening?:", self.reopen) # Build the array to do comparisons if self.type == "string": object_ = np.ndarray(buffer=b"a" * self.objsize, shape=self.rowshape, dtype=f"S{earray.atom.itemsize}") else: object_ = np.arange(self.objsize, dtype=earray.atom.dtype.base) object_.shape = self.rowshape object_ = object_.swapaxes(earray.extdim, 0) if self.obj is not None: initialrows = len(self.obj) else: initialrows = 0 rowshape = self.rowshape rowshape[self.extdim] *= (self.nappends + initialrows) if self.type == "string": object__ = np.empty(shape=rowshape, dtype=f"S{earray.atom.itemsize}") else: object__ = np.empty(shape=rowshape, dtype=self.dtype) # Additional conversion for the numpy case object__ = object__.swapaxes(0, earray.extdim) if initialrows: object__[0:initialrows] = self.obj for i in range(self.nappends): j = initialrows + i * self.chunksize if self.type == "string": object__[j:j + self.chunksize] = object_ else: object__[j:j + self.chunksize] = object_ * i if self.nappends: # Swap the axes again to have normal ordering if self.flavor == "numpy": object__ = object__.swapaxes(0, self.extdim) else: object__.swapaxes(0, self.extdim) # do a copy() in order to ensure that len(object._data) # actually do a measure of its length object = object__.__getitem__(self.slices).copy() else: object = np.empty(shape=self.shape, dtype=self.dtype) # Read all the array try: row = earray.__getitem__(self.slices) except IndexError: row = np.empty(shape=self.shape, dtype=self.dtype) if common.verbose: print("Object read:\n", repr(row)) print("Should look like:\n", repr(object)) if hasattr(object, "shape"): print("Original object shape:", self.shape) print("Shape read:", row.shape) print("shape should look as:", object.shape) self.assertEqual(initialrows + self.nappends * self.chunksize, earray.nrows) self.assertTrue(common.allequal(row, object, self.flavor)) if not hasattr(row, "shape"): # Scalar case self.assertEqual(len(self.shape), 1) def test05_setitemEArray(self): """Checking enlargeable array __setitem__ special method.""" if self.__class__.__name__ == "Ellipsis6EArrayTestCase": # We have a problem with test design here, but I think # it is not worth the effort to solve it # F.Alted 2004-10-27 return if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_setitemEArray..." % self.__class__.__name__) if not hasattr(self, "slices"): # If there is not a slices attribute, create it # This conversion made just in case indices are numpy scalars if self.start is not None: self.start = int(self.start) if self.stop is not None: self.stop = int(self.stop) if self.step is not None: self.step = int(self.step) self.slices = (slice(self.start, self.stop, self.step),) # Create an instance of an HDF5 Table if self.reopen: self._reopen(mode="a") earray = self.h5file.get_node("/earray1") # Choose a small value for buffer size # earray.nrowsinbuf = 3 # this does not really changes the chunksize if common.verbose: print("EArray descr:", repr(earray)) print("shape of read array ==>", earray.shape) print("reopening?:", self.reopen) # Build the array to do comparisons if self.type == "string": object_ = np.ndarray(buffer=b"a" * self.objsize, shape=self.rowshape, dtype=f"S{earray.atom.itemsize}") else: object_ = np.arange(self.objsize, dtype=earray.atom.dtype.base) object_.shape = self.rowshape object_ = object_.swapaxes(earray.extdim, 0) if self.obj is not None: initialrows = len(self.obj) else: initialrows = 0 rowshape = self.rowshape rowshape[self.extdim] *= (self.nappends + initialrows) if self.type == "string": object__ = np.empty(shape=rowshape, dtype=f"S{earray.atom.itemsize}") else: object__ = np.empty(shape=rowshape, dtype=self.dtype) # Additional conversion for the numpy case object__ = object__.swapaxes(0, earray.extdim) for i in range(self.nappends): j = initialrows + i * self.chunksize if self.type == "string": object__[j:j + self.chunksize] = object_ else: object__[j:j + self.chunksize] = object_ * i # Modify the earray # earray[j:j + self.chunksize] = object_ * i # earray[self.slices] = 1 if initialrows: object__[0:initialrows] = self.obj if self.nappends: # Swap the axes again to have normal ordering if self.flavor == "numpy": object__ = object__.swapaxes(0, self.extdim) else: object__.swapaxes(0, self.extdim) # do a copy() in order to ensure that len(object._data) # actually do a measure of its length object = object__.__getitem__(self.slices).copy() else: object = np.empty(shape=self.shape, dtype=self.dtype) if self.flavor == "numpy": object = np.asarray(object) if self.type == "string": if hasattr(self, "wslice"): # These sentences should be equivalent # object[self.wslize] = object[self.wslice].pad("xXx") # earray[self.wslice] = earray[self.wslice].pad("xXx") object[self.wslize] = "xXx" earray[self.wslice] = "xXx" elif sum(object[self.slices].shape) != 0: # object[:] = object.pad("xXx") object[:] = "xXx" if object.size > 0: earray[self.slices] = object else: if hasattr(self, "wslice"): object[self.wslice] = object[self.wslice] * 2 + 3 earray[self.wslice] = earray[self.wslice] * 2 + 3 elif sum(object[self.slices].shape) != 0: object = object * 2 + 3 if np.prod(object.shape) > 0: earray[self.slices] = earray[self.slices] * 2 + 3 # Read all the array row = earray.__getitem__(self.slices) try: row = earray.__getitem__(self.slices) except IndexError: print("IndexError!") row = np.empty(shape=self.shape, dtype=self.dtype) if common.verbose: print("Object read:\n", repr(row)) print("Should look like:\n", repr(object)) if hasattr(object, "shape"): print("Original object shape:", self.shape) print("Shape read:", row.shape) print("shape should look as:", object.shape) self.assertEqual(initialrows + self.nappends * self.chunksize, earray.nrows) self.assertTrue(common.allequal(row, object, self.flavor)) if not hasattr(row, "shape"): # Scalar case self.assertEqual(len(self.shape), 1) class BasicWriteTestCase(BasicTestCase): type = 'int32' shape = (0,) chunksize = 5 nappends = 10 step = 1 # wslice = slice(1,nappends,2) wslice = 1 # single element case class Basic2WriteTestCase(BasicTestCase): type = 'int32' dtype = 'i4' shape = (0,) chunksize = 5 nappends = 10 step = 1 wslice = slice(chunksize-2, nappends, 2) # range of elements reopen = 0 # This case does not reopen files class Basic3WriteTestCase(BasicTestCase): obj = [1, 2] type = np.asarray(obj).dtype.name dtype = np.asarray(obj).dtype.str shape = (0,) chunkshape = (5,) step = 1 reopen = 0 # This case does not reopen files class Basic4WriteTestCase(BasicTestCase): obj = np.array([1, 2]) type = obj.dtype.name dtype = obj.dtype.str shape = None chunkshape = (5,) step = 1 reopen = 0 # This case does not reopen files class Basic5WriteTestCase(BasicTestCase): obj = [1, 2] type = np.asarray(obj).dtype.name dtype = np.asarray(obj).dtype.str shape = (0,) chunkshape = (5,) step = 1 reopen = 1 # This case does reopen files class Basic6WriteTestCase(BasicTestCase): obj = np.array([1, 2]) type = obj.dtype.name dtype = obj.dtype.str shape = None chunkshape = (5,) step = 1 reopen = 1 # This case does reopen files class Basic7WriteTestCase(BasicTestCase): obj = [[1, 2], [3, 4]] type = np.asarray(obj).dtype.name dtype = np.asarray(obj).dtype.str shape = (0, 2) chunkshape = (5,) step = 1 reopen = 0 # This case does not reopen files class Basic8WriteTestCase(BasicTestCase): obj = [[1, 2], [3, 4]] type = np.asarray(obj).dtype.name dtype = np.asarray(obj).dtype.str shape = (0, 2) chunkshape = (5,) step = 1 reopen = 1 # This case does reopen files class EmptyEArrayTestCase(BasicTestCase): type = 'int32' dtype = np.dtype('int32') shape = (2, 0) chunksize = 5 nappends = 0 start = 0 stop = 10 step = 1 class NP_EmptyEArrayTestCase(BasicTestCase): type = 'int32' dtype = np.dtype('()int32') shape = (2, 0) chunksize = 5 nappends = 0 class Empty2EArrayTestCase(BasicTestCase): type = 'int32' dtype = 'int32' shape = (2, 0) chunksize = 5 nappends = 0 start = 0 stop = 10 step = 1 reopen = 0 # This case does not reopen files @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class SlicesEArrayTestCase(BasicTestCase): compress = 1 complib = "lzo" type = 'int32' shape = (2, 0) chunksize = 5 nappends = 2 slices = (slice(1, 2, 1), slice(1, 3, 1)) @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class Slices2EArrayTestCase(BasicTestCase): compress = 1 complib = "blosc" type = 'int32' shape = (2, 0, 4) chunksize = 5 nappends = 20 slices = (slice(1, 2, 1), slice(None, None, None), slice(1, 4, 2)) class EllipsisEArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 0) chunksize = 5 nappends = 2 # slices = (slice(1,2,1), Ellipsis) slices = (Ellipsis, slice(1, 2, 1)) class Ellipsis2EArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 0, 4) chunksize = 5 nappends = 20 slices = (slice(1, 2, 1), Ellipsis, slice(1, 4, 2)) @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class Slices3EArrayTestCase(BasicTestCase): compress = 1 # To show the chunks id DEBUG is on complib = "blosc" type = 'int32' shape = (2, 3, 4, 0) chunksize = 5 nappends = 20 slices = (slice(1, 2, 1), slice(0, None, None), slice(1, 4, 2)) # Don't work # slices = (slice(None, None, None), slice(0, None, None), # slice(1,4,1)) # W # slices = (slice(None, None, None), slice(None, None, None), # slice(1,4,2)) # N # slices = (slice(1,2,1), slice(None, None, None), slice(1,4,2)) # N # Disable the failing test temporarily with a working test case slices = (slice(1, 2, 1), slice(1, 4, None), slice(1, 4, 2)) # Y # slices = (slice(1,2,1), slice(0, 4, None), slice(1,4,1)) # Y slices = (slice(1, 2, 1), slice(0, 4, None), slice(1, 4, 2)) # N # slices = (slice(1,2,1), slice(0, 4, None), slice(1,4,2), # slice(0,100,1)) # N class Slices4EArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 4, 0, 5, 6) chunksize = 5 nappends = 20 slices = (slice(1, 2, 1), slice(0, None, None), slice(1, 4, 2), slice(0, 4, 2), slice(3, 5, 2), slice(2, 7, 1)) class Ellipsis3EArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 4, 0) chunksize = 5 nappends = 20 slices = (Ellipsis, slice(0, 4, None), slice(1, 4, 2)) slices = (slice(1, 2, 1), slice(0, 4, None), slice(1, 4, 2), Ellipsis) class Ellipsis4EArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 4, 0) chunksize = 5 nappends = 20 slices = (Ellipsis, slice(0, 4, None), slice(1, 4, 2)) slices = (slice(1, 2, 1), Ellipsis, slice(1, 4, 2)) class Ellipsis5EArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 4, 0) chunksize = 5 nappends = 20 slices = (slice(1, 2, 1), slice(0, 4, None), Ellipsis) class Ellipsis6EArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 4, 0) chunksize = 5 nappends = 2 # The next slices gives problems with setting values (test05) # This is a problem on the test design, not the Array.__setitem__ # code, though. slices = (slice(1, 2, 1), slice(0, 4, None), 2, Ellipsis) class Ellipsis7EArrayTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 4, 0) chunksize = 5 nappends = 2 slices = (slice(1, 2, 1), slice(0, 4, None), slice(2, 3), Ellipsis) class MD3WriteTestCase(BasicTestCase): type = 'int32' shape = (2, 0, 3) chunksize = 4 step = 2 class MD5WriteTestCase(BasicTestCase): type = 'int32' shape = (2, 0, 3, 4, 5) # ok # shape = (1, 1, 0, 1) # Minimum shape that shows problems with HDF5 1.6.1 # shape = (2, 3, 0, 4, 5) # Floating point exception (HDF5 1.6.1) # shape = (2, 3, 3, 0, 5, 6) # Segmentation fault (HDF5 1.6.1) chunksize = 1 nappends = 1 start = 1 stop = 10 step = 10 class MD6WriteTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 3, 0, 5, 6) chunksize = 1 nappends = 10 start = 1 stop = 10 step = 3 class NP_MD6WriteTestCase(BasicTestCase): """Testing NumPy scalars as indexes""" type = 'int32' shape = (2, 3, 3, 0, 5, 6) chunksize = 1 nappends = 10 class MD6WriteTestCase__(BasicTestCase): type = 'int32' shape = (2, 0) chunksize = 1 nappends = 3 start = 1 stop = 3 step = 1 class MD7WriteTestCase(BasicTestCase): type = 'int32' shape = (2, 3, 3, 4, 5, 0, 3) chunksize = 10 nappends = 1 start = 1 stop = 10 step = 2 class MD10WriteTestCase(BasicTestCase): type = 'int32' shape = (1, 2, 3, 4, 5, 5, 4, 3, 2, 0) chunksize = 5 nappends = 10 start = -1 stop = -1 step = 10 class NP_MD10WriteTestCase(BasicTestCase): type = 'int32' shape = (1, 2, 3, 4, 5, 5, 4, 3, 2, 0) chunksize = 5 nappends = 10 class ZlibComprTestCase(BasicTestCase): compress = 1 complib = "zlib" start = 3 # stop = 0 # means last row stop = None # means last row from 0.8 on step = 10 class ZlibShuffleTestCase(BasicTestCase): shuffle = 1 compress = 1 complib = "zlib" # case start < stop , i.e. no rows read start = 3 stop = 1 step = 10 @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class BloscComprTestCase(BasicTestCase): compress = 1 # sss complib = "blosc" chunksize = 10 nappends = 100 start = 3 stop = 10 step = 3 @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class BloscShuffleTestCase(BasicTestCase): compress = 1 shuffle = 1 complib = "blosc" chunksize = 100 nappends = 10 start = 3 stop = 10 step = 7 @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class LZOComprTestCase(BasicTestCase): compress = 1 # sss complib = "lzo" chunksize = 10 nappends = 100 start = 3 stop = 10 step = 3 @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class LZOShuffleTestCase(BasicTestCase): compress = 1 shuffle = 1 complib = "lzo" chunksize = 100 nappends = 10 start = 3 stop = 10 step = 7 @common.unittest.skipIf(not common.bzip2_avail, 'BZIP2 compression library not available') class Bzip2ComprTestCase(BasicTestCase): compress = 1 complib = "bzip2" chunksize = 100 nappends = 10 start = 3 stop = 10 step = 8 @common.unittest.skipIf(not common.bzip2_avail, 'BZIP2 compression library not available') class Bzip2ShuffleTestCase(BasicTestCase): compress = 1 shuffle = 1 complib = "bzip2" chunksize = 100 nappends = 10 start = 3 stop = 10 step = 6 class Fletcher32TestCase(BasicTestCase): compress = 0 fletcher32 = 1 chunksize = 50 nappends = 20 start = 4 stop = 20 step = 7 class AllFiltersTestCase(BasicTestCase): compress = 1 shuffle = 1 fletcher32 = 1 complib = "zlib" chunksize = 20 # sss nappends = 50 start = 2 stop = 99 step = 6 # chunksize = 3 # nappends = 2 # start = 1 # stop = 10 # step = 2 class FloatTypeTestCase(BasicTestCase): type = 'float64' dtype = 'float64' shape = (2, 0) chunksize = 5 nappends = 10 start = 3 stop = 10 step = 20 class ComplexTypeTestCase(BasicTestCase): type = 'complex128' dtype = 'complex128' shape = (2, 0) chunksize = 5 nappends = 10 start = 3 stop = 10 step = 20 class StringTestCase(BasicTestCase): type = "string" length = 20 shape = (2, 0) # shape = (2,0,20) chunksize = 5 nappends = 10 start = 3 stop = 10 step = 20 slices = (slice(0, 1), slice(1, 2)) class String2TestCase(BasicTestCase): type = "string" length = 20 shape = (0,) # shape = (0, 20) chunksize = 5 nappends = 10 start = 1 stop = 10 step = 2 class StringComprTestCase(BasicTestCase): type = "string" length = 20 shape = (20, 0, 10) # shape = (20,0,10,20) compr = 1 # shuffle = 1 # this shouldn't do nothing on chars chunksize = 50 nappends = 10 start = -1 stop = 100 step = 20 class SizeOnDiskInMemoryPropertyTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.array_size = (0, 10) # set chunkshape so it divides evenly into array_size, to avoid # partially filled chunks self.chunkshape = (1000, 10) # approximate size (in bytes) of non-data portion of hdf5 file self.hdf_overhead = 6000 def create_array(self, complevel): filters = tb.Filters(complevel=complevel, complib='blosc') self.array = self.h5file.create_earray('/', 'earray', atom=tb.Int32Atom(), shape=self.array_size, filters=filters, chunkshape=self.chunkshape) def test_zero_length(self): complevel = 0 self.create_array(complevel) self.assertEqual(self.array.size_on_disk, 0) self.assertEqual(self.array.size_in_memory, 0) # add 10 chunks of data in one append def test_no_compression_one_append(self): complevel = 0 self.create_array(complevel) self.array.append([tuple(range(10))] * self.chunkshape[0] * 10) self.assertEqual(self.array.size_on_disk, 10 * 1000 * 10 * 4) self.assertEqual(self.array.size_in_memory, 10 * 1000 * 10 * 4) # add 10 chunks of data in two appends def test_no_compression_multiple_appends(self): complevel = 0 self.create_array(complevel) self.array.append([tuple(range(10))] * self.chunkshape[0] * 5) self.array.append([tuple(range(10))] * self.chunkshape[0] * 5) self.assertEqual(self.array.size_on_disk, 10 * 1000 * 10 * 4) self.assertEqual(self.array.size_in_memory, 10 * 1000 * 10 * 4) def test_with_compression(self): complevel = 1 self.create_array(complevel) self.array.append([tuple(range(10))] * self.chunkshape[0] * 10) file_size = Path(self.h5fname).stat().st_size self.assertTrue( abs(self.array.size_on_disk - file_size) <= self.hdf_overhead) self.assertEqual(self.array.size_in_memory, 10 * 1000 * 10 * 4) self.assertLess(self.array.size_on_disk, self.array.size_in_memory) class OffsetStrideTestCase(common.TempFileMixin, common.PyTablesTestCase): mode = "w" compress = 0 complib = "zlib" # Default compression library def setUp(self): super().setUp() self.rootgroup = self.h5file.root def test01a_String(self): """Checking earray with offseted numpy strings appends.""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a_StringAtom..." % self.__class__.__name__) earray = self.h5file.create_earray(root, 'strings', atom=tb.StringAtom(itemsize=3), shape=(0, 2, 2), title="Array of strings") a = np.array([[["a", "b"], ["123", "45"], ["45", "123"]]], dtype="S3") earray.append(a[:, 1:]) a = np.array([[["s", "a"], ["ab", "f"], ["s", "abc"], ["abc", "f"]]]) earray.append(a[:, 2:]) # Read all the rows: row = earray.read() if common.verbose: print("Object read:", row) print("Nrows in", earray._v_pathname, ":", earray.nrows) print("Second row in earray ==>", row[1].tolist()) self.assertEqual(earray.nrows, 2) self.assertEqual(row[0].tolist(), [[b"123", b"45"], [b"45", b"123"]]) self.assertEqual(row[1].tolist(), [[b"s", b"abc"], [b"abc", b"f"]]) self.assertEqual(len(row[0]), 2) self.assertEqual(len(row[1]), 2) def test01b_String(self): """Checking earray with strided numpy strings appends.""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b_StringAtom..." % self.__class__.__name__) earray = self.h5file.create_earray(root, 'strings', atom=tb.StringAtom(itemsize=3), shape=(0, 2, 2), title="Array of strings") a = np.array([[["a", "b"], ["123", "45"], ["45", "123"]]], dtype="S3") earray.append(a[:, ::2]) a = np.array([[["s", "a"], ["ab", "f"], ["s", "abc"], ["abc", "f"]]]) earray.append(a[:, ::2]) # Read all the rows: row = earray.read() if common.verbose: print("Object read:", row) print("Nrows in", earray._v_pathname, ":", earray.nrows) print("Second row in earray ==>", row[1].tolist()) self.assertEqual(earray.nrows, 2) self.assertEqual(row[0].tolist(), [[b"a", b"b"], [b"45", b"123"]]) self.assertEqual(row[1].tolist(), [[b"s", b"a"], [b"s", b"abc"]]) self.assertEqual(len(row[0]), 2) self.assertEqual(len(row[1]), 2) def test02a_int(self): """Checking earray with offseted NumPy ints appends.""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test02a_int..." % self.__class__.__name__) # Create an string atom earray = self.h5file.create_earray(root, 'EAtom', atom=tb.Int32Atom(), shape=(0, 3), title="array of ints") a = np.array([(0, 0, 0), (1, 0, 3), (1, 1, 1), (0, 0, 0)], dtype='int32') earray.append(a[2:]) # Create an offset a = np.array([(1, 1, 1), (-1, 0, 0)], dtype='int32') earray.append(a[1:]) # Create an offset # Read all the rows: row = earray.read() if common.verbose: print("Object read:", row) print("Nrows in", earray._v_pathname, ":", earray.nrows) print("Third row in vlarray ==>", row[2]) self.assertEqual(earray.nrows, 3) self.assertTrue(common.allequal( row[0], np.array([1, 1, 1], dtype='int32'))) self.assertTrue(common.allequal( row[1], np.array([0, 0, 0], dtype='int32'))) self.assertTrue(common.allequal( row[2], np.array([-1, 0, 0], dtype='int32'))) def test02b_int(self): """Checking earray with strided NumPy ints appends.""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test02b_int..." % self.__class__.__name__) earray = self.h5file.create_earray(root, 'EAtom', atom=tb.Int32Atom(), shape=(0, 3), title="array of ints") a = np.array([(0, 0, 0), (1, 0, 3), (1, 1, 1), (3, 3, 3)], dtype='int32') earray.append(a[::3]) # Create an offset a = np.array([(1, 1, 1), (-1, 0, 0)], dtype='int32') earray.append(a[::2]) # Create an offset # Read all the rows: row = earray.read() if common.verbose: print("Object read:", row) print("Nrows in", earray._v_pathname, ":", earray.nrows) print("Third row in vlarray ==>", row[2]) self.assertEqual(earray.nrows, 3) self.assertTrue(common.allequal( row[0], np.array([0, 0, 0], dtype='int32'))) self.assertTrue(common.allequal( row[1], np.array([3, 3, 3], dtype='int32'))) self.assertTrue(common.allequal( row[2], np.array([1, 1, 1], dtype='int32'))) def test03a_int(self): """Checking earray with byteswapped appends (ints)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test03a_int..." % self.__class__.__name__) earray = self.h5file.create_earray(root, 'EAtom', atom=tb.Int32Atom(), shape=(0, 3), title="array of ints") # Add a native ordered array a = np.array([(0, 0, 0), (1, 0, 3), (1, 1, 1), (3, 3, 3)], dtype='int32') earray.append(a) # Change the byteorder of the array a = a.byteswap() a = a.newbyteorder() # Add a byteswapped array earray.append(a) # Read all the rows: native = earray[:4, :] swapped = earray[4:, :] if common.verbose: print("Native rows:", native) print("Byteorder native rows:", native.dtype.byteorder) print("Swapped rows:", swapped) print("Byteorder swapped rows:", swapped.dtype.byteorder) self.assertTrue(common.allequal(native, swapped)) def test03b_float(self): """Checking earray with byteswapped appends (floats)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test03b_float..." % self.__class__.__name__) earray = self.h5file.create_earray(root, 'EAtom', atom=tb.Float64Atom(), shape=(0, 3), title="array of floats") # Add a native ordered array a = np.array([(0, 0, 0), (1, 0, 3), (1, 1, 1), (3, 3, 3)], dtype='float64') earray.append(a) # Change the byteorder of the array a = a.byteswap() a = a.newbyteorder() # Add a byteswapped array earray.append(a) # Read all the rows: native = earray[:4, :] swapped = earray[4:, :] if common.verbose: print("Native rows:", native) print("Byteorder native rows:", native.dtype.byteorder) print("Swapped rows:", swapped) print("Byteorder swapped rows:", swapped.dtype.byteorder) self.assertTrue(common.allequal(native, swapped)) def test04a_int(self): """Checking earray with byteswapped appends (2, ints)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test04a_int..." % self.__class__.__name__) byteorder = {'little': 'big', 'big': 'little'}[sys.byteorder] earray = self.h5file.create_earray(root, 'EAtom', atom=tb.Int32Atom(), shape=(0, 3), title="array of ints", byteorder=byteorder) # Add a native ordered array a = np.array([(0, 0, 0), (1, 0, 3), (1, 1, 1), (3, 3, 3)], dtype='int32') earray.append(a) # Change the byteorder of the array a = a.byteswap() a = a.newbyteorder() # Add a byteswapped array earray.append(a) # Read all the rows: native = earray[:4, :] swapped = earray[4:, :] if common.verbose: print("Byteorder native rows:", tb.utils.byteorders[native.dtype.byteorder]) print("Byteorder earray on-disk:", earray.byteorder) self.assertEqual(tb.utils.byteorders[native.dtype.byteorder], sys.byteorder) self.assertEqual(earray.byteorder, byteorder) self.assertTrue(common.allequal(native, swapped)) def test04b_int(self): """Checking earray with byteswapped appends (2, ints, reopen)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test04b_int..." % self.__class__.__name__) byteorder = {'little': 'big', 'big': 'little'}[sys.byteorder] earray = self.h5file.create_earray(root, 'EAtom', atom=tb.Int32Atom(), shape=(0, 3), title="array of ints", byteorder=byteorder) self._reopen(mode="a") earray = self.h5file.get_node("/EAtom") # Add a native ordered array a = np.array([(0, 0, 0), (1, 0, 3), (1, 1, 1), (3, 3, 3)], dtype='int32') earray.append(a) # Change the byteorder of the array a = a.byteswap() a = a.newbyteorder() # Add a byteswapped array earray.append(a) # Read all the rows: native = earray[:4, :] swapped = earray[4:, :] if common.verbose: print("Byteorder native rows:", tb.utils.byteorders[native.dtype.byteorder]) print("Byteorder earray on-disk:", earray.byteorder) self.assertEqual(tb.utils.byteorders[native.dtype.byteorder], sys.byteorder) self.assertEqual(earray.byteorder, byteorder) self.assertTrue(common.allequal(native, swapped)) def test04c_float(self): """Checking earray with byteswapped appends (2, floats)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test04c_float..." % self.__class__.__name__) byteorder = {'little': 'big', 'big': 'little'}[sys.byteorder] earray = self.h5file.create_earray(root, 'EAtom', atom=tb.Float64Atom(), shape=(0, 3), title="array of floats", byteorder=byteorder) # Add a native ordered array a = np.array([(0, 0, 0), (1, 0, 3), (1, 1, 1), (3, 3, 3)], dtype='float64') earray.append(a) # Change the byteorder of the array a = a.byteswap() a = a.newbyteorder() # Add a byteswapped array earray.append(a) # Read all the rows: native = earray[:4, :] swapped = earray[4:, :] if common.verbose: print("Byteorder native rows:", tb.utils.byteorders[native.dtype.byteorder]) print("Byteorder earray on-disk:", earray.byteorder) self.assertEqual(tb.utils.byteorders[native.dtype.byteorder], sys.byteorder) self.assertEqual(earray.byteorder, byteorder) self.assertTrue(common.allequal(native, swapped)) def test04d_float(self): """Checking earray with byteswapped appends (2, floats, reopen)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test04d_float..." % self.__class__.__name__) byteorder = {'little': 'big', 'big': 'little'}[sys.byteorder] earray = self.h5file.create_earray(root, 'EAtom', atom=tb.Float64Atom(), shape=(0, 3), title="array of floats", byteorder=byteorder) self._reopen(mode='a') earray = self.h5file.get_node("/EAtom") # Add a native ordered array a = np.array([(0, 0, 0), (1, 0, 3), (1, 1, 1), (3, 3, 3)], dtype='float64') earray.append(a) # Change the byteorder of the array a = a.byteswap() a = a.newbyteorder() # Add a byteswapped array earray.append(a) # Read all the rows: native = earray[:4, :] swapped = earray[4:, :] if common.verbose: print("Byteorder native rows:", tb.utils.byteorders[native.dtype.byteorder]) print("Byteorder earray on-disk:", earray.byteorder) self.assertEqual(tb.utils.byteorders[native.dtype.byteorder], sys.byteorder) self.assertEqual(earray.byteorder, byteorder) self.assertTrue(common.allequal(native, swapped)) class CopyTestCase(common.TempFileMixin, common.PyTablesTestCase): def test01_copy(self): """Checking EArray.copy() method.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_copy..." % self.__class__.__name__) # Create an EArray atom = tb.Int16Atom() array1 = self.h5file.create_earray(self.h5file.root, 'array1', atom=atom, shape=(0, 2), title="title array1") array1.append(np.array([[456, 2], [3, 457]], dtype='int16')) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy it to another location array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) # print("dirs-->", dir(array1), dir(array2)) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal self.assertTrue(common.allequal(array1.read(), array2.read())) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.extdim, array2.extdim) self.assertEqual(array1.flavor, array2.flavor) self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.atom.type, array2.atom.type) self.assertEqual(array1.atom.itemsize, array2.atom.itemsize) self.assertEqual(array1.title, array2.title) self.assertEqual(str(array1.atom), str(array2.atom)) def test02_copy(self): """Checking EArray.copy() method (where specified)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_copy..." % self.__class__.__name__) # Create an EArray atom = tb.Int16Atom() array1 = self.h5file.create_earray(self.h5file.root, 'array1', atom=atom, shape=(0, 2), title="title array1") array1.append(np.array([[456, 2], [3, 457]], dtype='int16')) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy to another location group1 = self.h5file.create_group("/", "group1") array2 = array1.copy(group1, 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.group1.array2 if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) # print("dirs-->", dir(array1), dir(array2)) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal self.assertTrue(common.allequal(array1.read(), array2.read())) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.extdim, array2.extdim) self.assertEqual(array1.flavor, array2.flavor) self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.atom.type, array2.atom.type) self.assertEqual(array1.atom.itemsize, array2.atom.itemsize) self.assertEqual(array1.title, array2.title) self.assertEqual(str(array1.atom), str(array2.atom)) def test03a_copy(self): """Checking EArray.copy() method (python flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03b_copy..." % self.__class__.__name__) atom = tb.Int16Atom() array1 = self.h5file.create_earray(self.h5file.root, 'array1', atom=atom, shape=(0, 2), title="title array1") array1.flavor = "python" array1.append(((456, 2), (3, 457))) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy to another location array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all elements are equal self.assertEqual(array1.read(), array2.read()) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.extdim, array2.extdim) self.assertEqual(array1.flavor, array2.flavor) # Very important here! self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.atom.type, array2.atom.type) self.assertEqual(array1.atom.itemsize, array2.atom.itemsize) self.assertEqual(array1.title, array2.title) self.assertEqual(str(array1.atom), str(array2.atom)) def test03b_copy(self): """Checking EArray.copy() method (python string flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03d_copy..." % self.__class__.__name__) atom = tb.StringAtom(itemsize=3) array1 = self.h5file.create_earray(self.h5file.root, 'array1', atom=atom, shape=(0, 2), title="title array1") array1.flavor = "python" array1.append([["456", "2"], ["3", "457"]]) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy to another location array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all elements are equal self.assertEqual(array1.read(), array2.read()) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.extdim, array2.extdim) self.assertEqual(array1.flavor, array2.flavor) # Very important here! self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.atom.type, array2.atom.type) self.assertEqual(array1.atom.itemsize, array2.atom.itemsize) self.assertEqual(array1.title, array2.title) self.assertEqual(str(array1.atom), str(array2.atom)) def test03c_copy(self): """Checking EArray.copy() method (String flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03e_copy..." % self.__class__.__name__) atom = tb.StringAtom(itemsize=4) array1 = self.h5file.create_earray(self.h5file.root, 'array1', atom=atom, shape=(0, 2), title="title array1") array1.flavor = "numpy" array1.append(np.array([["456", "2"], ["3", "457"]], dtype="S4")) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy to another location array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all elements are equal self.assertTrue(common.allequal(array1.read(), array2.read())) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.extdim, array2.extdim) self.assertEqual(array1.flavor, array2.flavor) # Very important here! self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(array1.atom.type, array2.atom.type) self.assertEqual(array1.atom.itemsize, array2.atom.itemsize) self.assertEqual(array1.title, array2.title) self.assertEqual(str(array1.atom), str(array2.atom)) def test04_copy(self): """Checking EArray.copy() method (checking title copying)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_copy..." % self.__class__.__name__) # Create an EArray atom = tb.Int16Atom() array1 = self.h5file.create_earray(self.h5file.root, 'array1', atom=atom, shape=(0, 2), title="title array1") array1.append(np.array([[456, 2], [3, 457]], dtype='int16')) # Append some user attrs array1.attrs.attr1 = "attr1" array1.attrs.attr2 = 2 if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy it to another Array array2 = array1.copy('/', 'array2', title="title array2") if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 # Assert user attributes if common.verbose: print("title of destination array-->", array2.title) self.assertEqual(array2.title, "title array2") def test05_copy(self): """Checking EArray.copy() method (user attributes copied)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_copy..." % self.__class__.__name__) # Create an EArray atom = tb.Int16Atom() array1 = self.h5file.create_earray(self.h5file.root, 'array1', atom=atom, shape=(0, 2), title="title array1") array1.append(np.array([[456, 2], [3, 457]], dtype='int16')) # Append some user attrs array1.attrs.attr1 = "attr1" array1.attrs.attr2 = 2 if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy it to another Array array2 = array1.copy('/', 'array2', copyuserattrs=1) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Assert user attributes self.assertEqual(array2.attrs.attr1, "attr1") self.assertEqual(array2.attrs.attr2, 2) def test05b_copy(self): """Checking EArray.copy() method (user attributes not copied)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05b_copy..." % self.__class__.__name__) # Create an Array atom = tb.Int16Atom() array1 = self.h5file.create_earray(self.h5file.root, 'array1', atom=atom, shape=(0, 2), title="title array1") array1.append(np.array([[456, 2], [3, 457]], dtype='int16')) # Append some user attrs array1.attrs.attr1 = "attr1" array1.attrs.attr2 = 2 if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy it to another Array array2 = array1.copy('/', 'array2', copyuserattrs=0) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Assert user attributes self.assertEqual(hasattr(array2.attrs, "attr1"), 0) self.assertEqual(hasattr(array2.attrs, "attr2"), 0) class CloseCopyTestCase(CopyTestCase): close = 1 class OpenCopyTestCase(CopyTestCase): close = 0 class CopyIndexTestCase(common.TempFileMixin, common.PyTablesTestCase): nrowsinbuf = 2 def test01_index(self): """Checking EArray.copy() method with indexes.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_index..." % self.__class__.__name__) # Create an EArray atom = tb.Int32Atom() array1 = self.h5file.create_earray(self.h5file.root, 'array1', atom=atom, shape=(0, 2), title="title array1") r = np.arange(200, dtype='int32') r.shape = (100, 2) array1.append(r) # Select a different buffer size: array1.nrowsinbuf = self.nrowsinbuf # Copy to another array array2 = array1.copy("/", 'array2', start=self.start, stop=self.stop, step=self.step) if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal r2 = r[self.start:self.stop:self.step] self.assertTrue(common.allequal(r2, array2.read())) # Assert the number of rows in array if common.verbose: print("nrows in array2-->", array2.nrows) print("and it should be-->", r2.shape[0]) self.assertEqual(r2.shape[0], array2.nrows) def test02_indexclosef(self): """Checking EArray.copy() method with indexes (close file version)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_indexclosef..." % self.__class__.__name__) # Create an EArray atom = tb.Int32Atom() array1 = self.h5file.create_earray(self.h5file.root, 'array1', atom=atom, shape=(0, 2), title="title array1") r = np.arange(200, dtype='int32') r.shape = (100, 2) array1.append(r) # Select a different buffer size: array1.nrowsinbuf = self.nrowsinbuf # Copy to another array array2 = array1.copy("/", 'array2', start=self.start, stop=self.stop, step=self.step) # Close and reopen the file self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("array1-->", array1.read()) print("array2-->", array2.read()) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal r2 = r[self.start:self.stop:self.step] self.assertTrue(common.allequal(r2, array2.read())) # Assert the number of rows in array if common.verbose: print("nrows in array2-->", array2.nrows) print("and it should be-->", r2.shape[0]) self.assertEqual(r2.shape[0], array2.nrows) class CopyIndex1TestCase(CopyIndexTestCase): nrowsinbuf = 1 start = 0 stop = 7 step = 1 class CopyIndex2TestCase(CopyIndexTestCase): nrowsinbuf = 2 start = 0 stop = -1 step = 1 class CopyIndex3TestCase(CopyIndexTestCase): nrowsinbuf = 3 start = 1 stop = 7 step = 1 class CopyIndex4TestCase(CopyIndexTestCase): nrowsinbuf = 4 start = 0 stop = 6 step = 1 class CopyIndex5TestCase(CopyIndexTestCase): nrowsinbuf = 2 start = 3 stop = 7 step = 1 class CopyIndex6TestCase(CopyIndexTestCase): nrowsinbuf = 2 start = 3 stop = 6 step = 2 class CopyIndex7TestCase(CopyIndexTestCase): start = 0 stop = 7 step = 10 class CopyIndex8TestCase(CopyIndexTestCase): start = 6 stop = -1 # Negative values means starting from the end step = 1 class CopyIndex9TestCase(CopyIndexTestCase): start = 3 stop = 4 step = 1 class CopyIndex10TestCase(CopyIndexTestCase): nrowsinbuf = 1 start = 3 stop = 4 step = 2 class CopyIndex11TestCase(CopyIndexTestCase): start = -3 stop = -1 step = 2 class CopyIndex12TestCase(CopyIndexTestCase): start = -1 # Should point to the last element stop = None # None should mean the last element (including it) step = 1 class TruncateTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() # Create an EArray atom = tb.Int16Atom(dflt=3) array1 = self.h5file.create_earray(self.h5file.root, 'array1', atom=atom, shape=(0, 2), title="title array1") # Add a couple of rows array1.append(np.array([[456, 2], [3, 457]], dtype='int16')) def test00_truncate(self): """Checking EArray.truncate() method (truncating to 0 rows)""" array1 = self.h5file.root.array1 # Truncate to 0 elements array1.truncate(0) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 if common.verbose: print("array1-->", array1.read()) self.assertTrue(common.allequal( array1[:], np.array([], dtype='int16').reshape(0, 2))) def test01_truncate(self): """Checking EArray.truncate() method (truncating to 1 rows)""" array1 = self.h5file.root.array1 # Truncate to 1 element array1.truncate(1) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 if common.verbose: print("array1-->", array1.read()) self.assertTrue(common.allequal( array1.read(), np.array([[456, 2]], dtype='int16'))) def test02_truncate(self): """Checking EArray.truncate() method (truncating to == self.nrows)""" array1 = self.h5file.root.array1 # Truncate to 2 elements array1.truncate(2) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 if common.verbose: print("array1-->", array1.read()) self.assertTrue(common.allequal( array1.read(), np.array([[456, 2], [3, 457]], dtype='int16'))) def test03_truncate(self): """Checking EArray.truncate() method (truncating to > self.nrows)""" array1 = self.h5file.root.array1 # Truncate to 4 elements array1.truncate(4) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 if common.verbose: print("array1-->", array1.read()) self.assertEqual(array1.nrows, 4) # Check the original values self.assertTrue(common.allequal( array1[:2], np.array([[456, 2], [3, 457]], dtype='int16'))) # Check that the added rows have the default values self.assertTrue(common.allequal( array1[2:], np.array([[3, 3], [3, 3]], dtype='int16'))) class TruncateOpenTestCase(TruncateTestCase): close = 0 class TruncateCloseTestCase(TruncateTestCase): close = 1 # The next test should be run only in **common.heavy** mode class Rows64bitsTestCase(common.TempFileMixin, common.PyTablesTestCase): open_mode = 'a' narows = 1000 * 1000 # each numpy object will have 1 million entries # narows = 1000 # for testing only nanumber = 1000 * 3 # That should account for more than 2**31-1 def setUp(self): super().setUp() # Create an EArray array = self.h5file.create_earray( self.h5file.root, 'array', atom=tb.Int8Atom(), shape=(0,), filters=tb.Filters(complib='lzo', complevel=1), # Specifying expectedrows takes more # CPU, but less disk expectedrows=self.narows * self.nanumber) # Fill the array na = np.arange(self.narows, dtype='int8') for i in range(self.nanumber): array.append(na) def test01_basiccheck(self): """Some basic checks for earrays exceeding 2**31 rows""" array = self.h5file.root.array if self.close: if common.verbose: # Check how many entries there are in the array print("Before closing") print("Entries:", array.nrows, type(array.nrows)) print("Entries:", array.nrows / (1000 * 1000), "Millions") print("Shape:", array.shape) # Close the file self._reopen() array = self.h5file.root.array if common.verbose: print("After re-open") # Check how many entries there are in the array if common.verbose: print("Entries:", array.nrows, type(array.nrows)) print("Entries:", array.nrows / (1000 * 1000), "Millions") print("Shape:", array.shape) print("Last 10 elements-->", array[-10:]) stop = self.narows % 256 if stop > 127: stop -= 256 start = stop - 10 print("Should look like-->", np.arange(start, stop, dtype='int8')) nrows = self.narows * self.nanumber # check nrows self.assertEqual(array.nrows, nrows) # Check shape self.assertEqual(array.shape, (nrows,)) # check the 10 first elements self.assertTrue(common.allequal( array[:10], np.arange(10, dtype='int8'))) # check the 10 last elements stop = self.narows % 256 if stop > 127: stop -= 256 start = stop - 10 self.assertTrue(common.allequal( array[-10:], np.arange(start, stop, dtype='int8'))) class Rows64bitsTestCase1(Rows64bitsTestCase): close = 0 class Rows64bitsTestCase2(Rows64bitsTestCase): close = 1 # Test for appending zero-sized arrays class ZeroSizedTestCase(common.TempFileMixin, common.PyTablesTestCase): open_mode = 'a' def setUp(self): super().setUp() # Create an EArray ea = self.h5file.create_earray('/', 'test', atom=tb.Int32Atom(), shape=(3, 0)) # Append a single row ea.append([[1], [2], [3]]) def test01_canAppend(self): """Appending zero length array.""" fileh = self.h5file ea = fileh.root.test arr = np.empty(shape=(3, 0), dtype='int32') ea.append(arr) self.assertEqual(ea.nrows, 1, "The number of rows should be 1.") def test02_appendWithWrongShape(self): """Appending zero length array with wrong dimension.""" fileh = self.h5file ea = fileh.root.test arr = np.empty(shape=(3, 0, 3), dtype='int32') self.assertRaises(ValueError, ea.append, arr) # Test for dealing with multidimensional atoms class MDAtomTestCase(common.TempFileMixin, common.PyTablesTestCase): def test01a_append(self): """Append a row to a (unidimensional) EArray with a MD tables.Atom.""" # Create an EArray ea = self.h5file.create_earray('/', 'test', atom=tb.Int32Atom((2, 2)), shape=(0,)) if self.reopen: self._reopen('a') ea = self.h5file.root.test # Append one row ea.append([[[1, 3], [4, 5]]]) self.assertEqual(ea.nrows, 1) if common.verbose: print("First row-->", ea[0]) self.assertTrue(common.allequal( ea[0], np.array([[1, 3], [4, 5]], 'i4'))) def test01b_append(self): """Append several rows to a (unidimensional) EArray with a MD tables.Atom.""" # Create an EArray ea = self.h5file.create_earray('/', 'test', atom=tb.Int32Atom((2, 2)), shape=(0,)) if self.reopen: self._reopen('a') ea = self.h5file.root.test # Append three rows ea.append([[[1]], [[2]], [[3]]]) # Simple broadcast self.assertEqual(ea.nrows, 3) if common.verbose: print("Third row-->", ea[2]) self.assertTrue(common.allequal( ea[2], np.array([[3, 3], [3, 3]], 'i4'))) def test02a_append(self): """Append a row to a (multidimensional) EArray with a MD tables.Atom.""" # Create an EArray ea = self.h5file.create_earray('/', 'test', atom=tb.Int32Atom((2,)), shape=(0, 3)) if self.reopen: self._reopen('a') ea = self.h5file.root.test # Append one row ea.append([[[1, 3], [4, 5], [7, 9]]]) self.assertEqual(ea.nrows, 1) if common.verbose: print("First row-->", ea[0]) self.assertTrue(common.allequal( ea[0], np.array([[1, 3], [4, 5], [7, 9]], 'i4'))) def test02b_append(self): """Append several rows to a (multidimensional) EArray with a MD tables.Atom.""" # Create an EArray ea = self.h5file.create_earray('/', 'test', atom=tb.Int32Atom((2,)), shape=(0, 3)) if self.reopen: self._reopen('a') ea = self.h5file.root.test # Append three rows ea.append([[[1, -3], [4, -5], [-7, 9]], [[-1, 3], [-4, 5], [7, -8]], [[-2, 3], [-5, 5], [7, -9]]]) self.assertEqual(ea.nrows, 3) if common.verbose: print("Third row-->", ea[2]) self.assertTrue(common.allequal( ea[2], np.array([[-2, 3], [-5, 5], [7, -9]], 'i4'))) def test03a_MDMDMD(self): """Complex append of a MD array in a MD EArray with a MD tables.Atom.""" # Create an EArray ea = self.h5file.create_earray('/', 'test', atom=tb.Int32Atom((2, 4)), shape=(0, 2, 3)) if self.reopen: self._reopen('a') ea = self.h5file.root.test # Append three rows # The shape of the atom should be added at the end of the arrays a = np.arange(2 * 3*2*4, dtype='i4').reshape((2, 3, 2, 4)) ea.append([a * 1, a*2, a*3]) self.assertEqual(ea.nrows, 3) if common.verbose: print("Third row-->", ea[2]) self.assertTrue(common.allequal(ea[2], a * 3)) def test03b_MDMDMD(self): """Complex append of a MD array in a MD EArray with a MD atom (II).""" # Create an EArray ea = self.h5file.create_earray('/', 'test', atom=tb.Int32Atom((2, 4)), shape=(2, 0, 3)) if self.reopen: self._reopen('a') ea = self.h5file.root.test # Append three rows # The shape of the atom should be added at the end of the arrays a = np.arange(2 * 3*2*4, dtype='i4').reshape((2, 1, 3, 2, 4)) ea.append(a * 1) ea.append(a * 2) ea.append(a * 3) self.assertEqual(ea.nrows, 3) if common.verbose: print("Third row-->", ea[:, 2, ...]) self.assertTrue(common.allequal(ea[:, 2, ...], a.reshape((2, 3, 2, 4))*3)) def test03c_MDMDMD(self): """Complex append of a MD array in a MD EArray with a MD atom (III).""" # Create an EArray ea = self.h5file.create_earray('/', 'test', atom=tb.Int32Atom((2, 4)), shape=(2, 3, 0)) if self.reopen: self._reopen('a') ea = self.h5file.root.test # Append three rows # The shape of the atom should be added at the end of the arrays a = np.arange(2 * 3*2*4, dtype='i4').reshape((2, 3, 1, 2, 4)) ea.append(a * 1) ea.append(a * 2) ea.append(a * 3) self.assertEqual(ea.nrows, 3) if common.verbose: print("Third row-->", ea[:, :, 2, ...]) self.assertTrue(common.allequal(ea[:, :, 2, ...], a.reshape((2, 3, 2, 4))*3)) class MDAtomNoReopen(MDAtomTestCase): reopen = False class MDAtomReopen(MDAtomTestCase): reopen = True class AccessClosedTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.array = self.h5file.create_earray(self.h5file.root, 'array', atom=tb.Int32Atom(), shape=(0, 10)) self.array.append(np.zeros((10, 10))) def test_read(self): self.h5file.close() self.assertRaises(tb.ClosedNodeError, self.array.read) def test_getitem(self): self.h5file.close() self.assertRaises(tb.ClosedNodeError, self.array.__getitem__, 0) def test_setitem(self): self.h5file.close() self.assertRaises(tb.ClosedNodeError, self.array.__setitem__, 0, 0) def test_append(self): self.h5file.close() self.assertRaises(tb.ClosedNodeError, self.array.append, np.zeros((10, 10))) class TestCreateEArrayArgs(common.TempFileMixin, common.PyTablesTestCase): obj = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) where = '/' name = 'earray' atom = tb.Atom.from_dtype(obj.dtype) shape = (0,) + obj.shape[1:] title = 'title' filters = None expectedrows = 1000 chunkshape = (1, 2) byteorder = None createparents = False def test_positional_args_01(self): self.h5file.create_earray(self.where, self.name, self.atom, self.shape, self.title, self.filters, self.expectedrows, self.chunkshape) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.nrows, 0) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) def test_positional_args_02(self): ptarr = self.h5file.create_earray(self.where, self.name, self.atom, self.shape, self.title, self.filters, self.expectedrows, self.chunkshape) ptarr.append(self.obj) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.obj.shape) self.assertEqual(ptarr.nrows, self.obj.shape[0]) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_positional_args_obj(self): self.h5file.create_earray(self.where, self.name, None, None, self.title, self.filters, self.expectedrows, self.chunkshape, self.byteorder, self.createparents, self.obj) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.obj.shape) self.assertEqual(ptarr.nrows, self.obj.shape[0]) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj(self): self.h5file.create_earray(self.where, self.name, title=self.title, chunkshape=self.chunkshape, obj=self.obj) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.obj.shape) self.assertEqual(ptarr.nrows, self.obj.shape[0]) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_atom_shape_01(self): ptarr = self.h5file.create_earray(self.where, self.name, title=self.title, chunkshape=self.chunkshape, atom=self.atom, shape=self.shape) ptarr.append(self.obj) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.obj.shape) self.assertEqual(ptarr.nrows, self.obj.shape[0]) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_atom_shape_02(self): ptarr = self.h5file.create_earray(self.where, self.name, title=self.title, chunkshape=self.chunkshape, atom=self.atom, shape=self.shape) # ptarr.append(self.obj) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.shape) self.assertEqual(ptarr.nrows, 0) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) def test_kwargs_obj_atom(self): ptarr = self.h5file.create_earray(self.where, self.name, title=self.title, chunkshape=self.chunkshape, obj=self.obj, atom=self.atom) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.obj.shape) self.assertEqual(ptarr.nrows, self.obj.shape[0]) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj_shape(self): ptarr = self.h5file.create_earray(self.where, self.name, title=self.title, chunkshape=self.chunkshape, obj=self.obj, shape=self.shape) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.obj.shape) self.assertEqual(ptarr.nrows, self.obj.shape[0]) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj_atom_shape(self): ptarr = self.h5file.create_earray(self.where, self.name, title=self.title, chunkshape=self.chunkshape, obj=self.obj, atom=self.atom, shape=self.shape) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, self.obj.shape) self.assertEqual(ptarr.nrows, self.obj.shape[0]) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertEqual(ptarr.chunkshape, self.chunkshape) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj_atom_error(self): atom = tb.Atom.from_dtype(np.dtype('complex')) # shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_earray, self.where, self.name, title=self.title, obj=self.obj, atom=atom) def test_kwargs_obj_shape_error(self): # atom = tables.Atom.from_dtype(numpy.dtype('complex')) shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_earray, self.where, self.name, title=self.title, obj=self.obj, shape=shape) def test_kwargs_obj_atom_shape_error_01(self): atom = tb.Atom.from_dtype(np.dtype('complex')) # shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_earray, self.where, self.name, title=self.title, obj=self.obj, atom=atom, shape=self.shape) def test_kwargs_obj_atom_shape_error_02(self): # atom = tables.Atom.from_dtype(numpy.dtype('complex')) shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_earray, self.where, self.name, title=self.title, obj=self.obj, atom=self.atom, shape=shape) def test_kwargs_obj_atom_shape_error_03(self): atom = tb.Atom.from_dtype(np.dtype('complex')) shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_earray, self.where, self.name, title=self.title, obj=self.obj, atom=atom, shape=shape) def suite(): theSuite = common.unittest.TestSuite() niter = 1 # common.heavy = 1 # uncomment this only for testing purposes # theSuite.addTest(unittest.makeSuite(BasicWriteTestCase)) # theSuite.addTest(unittest.makeSuite(Rows64bitsTestCase1)) # theSuite.addTest(unittest.makeSuite(Rows64bitsTestCase2)) for n in range(niter): theSuite.addTest(common.unittest.makeSuite(BasicWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic2WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic3WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic4WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic5WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic6WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic7WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic8WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(EmptyEArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Empty2EArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(SlicesEArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Slices2EArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(EllipsisEArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Ellipsis2EArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Ellipsis3EArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(ZlibComprTestCase)) theSuite.addTest(common.unittest.makeSuite(ZlibShuffleTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscComprTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscShuffleTestCase)) theSuite.addTest(common.unittest.makeSuite(LZOComprTestCase)) theSuite.addTest(common.unittest.makeSuite(LZOShuffleTestCase)) theSuite.addTest(common.unittest.makeSuite(Bzip2ComprTestCase)) theSuite.addTest(common.unittest.makeSuite(Bzip2ShuffleTestCase)) theSuite.addTest(common.unittest.makeSuite(FloatTypeTestCase)) theSuite.addTest(common.unittest.makeSuite(ComplexTypeTestCase)) theSuite.addTest(common.unittest.makeSuite(StringTestCase)) theSuite.addTest(common.unittest.makeSuite(String2TestCase)) theSuite.addTest(common.unittest.makeSuite(StringComprTestCase)) theSuite.addTest(common.unittest.makeSuite( SizeOnDiskInMemoryPropertyTestCase)) theSuite.addTest(common.unittest.makeSuite(OffsetStrideTestCase)) theSuite.addTest(common.unittest.makeSuite(Fletcher32TestCase)) theSuite.addTest(common.unittest.makeSuite(AllFiltersTestCase)) theSuite.addTest(common.unittest.makeSuite(CloseCopyTestCase)) theSuite.addTest(common.unittest.makeSuite(OpenCopyTestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex1TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex2TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex3TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex4TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex5TestCase)) theSuite.addTest(common.unittest.makeSuite(TruncateOpenTestCase)) theSuite.addTest(common.unittest.makeSuite(TruncateCloseTestCase)) theSuite.addTest(common.unittest.makeSuite(ZeroSizedTestCase)) theSuite.addTest(common.unittest.makeSuite(MDAtomNoReopen)) theSuite.addTest(common.unittest.makeSuite(MDAtomReopen)) theSuite.addTest(common.unittest.makeSuite(AccessClosedTestCase)) theSuite.addTest(common.unittest.makeSuite(TestCreateEArrayArgs)) if common.heavy: theSuite.addTest(common.unittest.makeSuite(Slices3EArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Slices4EArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Ellipsis4EArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Ellipsis5EArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Ellipsis6EArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(Ellipsis7EArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(MD3WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(MD5WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(MD6WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(MD7WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(MD10WriteTestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex6TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex7TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex8TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex9TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex10TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex11TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex12TestCase)) theSuite.addTest(common.unittest.makeSuite(Rows64bitsTestCase1)) theSuite.addTest(common.unittest.makeSuite(Rows64bitsTestCase2)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_enum.py000066400000000000000000000533151416254111300202420ustar00rootroot00000000000000"""Test module for enumerated types under PyTables.""" import itertools import operator import tables as tb from tables.tests import common class CreateColTestCase(common.PyTablesTestCase): """Test creating enumerated column descriptions.""" def _createCol(self, enum, dflt, base='uint32', shape=()): """Create and check an enumerated column description.""" enumcol = tb.EnumCol(enum, dflt, base=base, shape=shape) sameEnum = tb.Enum(enum) self.assertEqual(enumcol.type, 'enum') self.assertEqual(enumcol.dtype.base.name, enumcol.base.type) # To avoid 'LongInt' vs 'Int' issues # self.assertEqual(enumcol.dflt, sameEnum[dflt]) self.assertEqual(int(enumcol.dflt), int(sameEnum[dflt])) self.assertEqual(enumcol.dtype.shape, shape) self.assertEqual(enumcol.enum, sameEnum) def test00a_validFromEnum(self): """Describing an enumerated column from an enumeration.""" colors = tb.Enum(['red', 'green', 'blue']) self._createCol(colors, 'red') def test00b_validFromDict(self): """Describing an enumerated column from a dictionary.""" colors = {'red': 4, 'green': 2, 'blue': 1} self._createCol(colors, 'red') def test00c_validFromList(self): """Describing an enumerated column from a list.""" colors = ['red', 'green', 'blue'] self._createCol(colors, 'red') def test00d_invalidFromType(self): """Describing an enumerated column from an invalid object.""" colors = 123 self.assertRaises(TypeError, self._createCol, colors, 'red') def test01_invalidDflt(self): """Describing an enumerated column with an invalid default object.""" colors = {'red': 4, 'green': 2, 'blue': 1} self.assertRaises(KeyError, self._createCol, colors, 'black') def test02a_validDtypeBroader(self): """Describing an enumerated column with a broader type.""" colors = {'red': 4, 'green': 2, 'blue': 1} self._createCol(colors, 'red', 'int64') def test02b_invalidDtypeTooNarrow(self): """Describing an enumerated column with a too narrow type.""" colors = ['e%d' % i for i in range(300)] self.assertRaises(TypeError, self._createCol, colors, 'e0', 'uint8') def test03a_validShapeMD(self): """Describing an enumerated column with multidimensional shape.""" colors = ['red', 'green', 'blue'] self._createCol(colors, 'red', shape=(2,)) def test04a_validReprEnum(self): """Checking the string representation of an enumeration.""" colors = tb.Enum(['red', 'green', 'blue']) enumcol = tb.EnumCol(colors, 'red', base='uint32', shape=()) # needed due to "Hash randomization" (default on python 3.3) template = ( "EnumCol(enum=Enum({%s}), dflt='red', base=UInt32Atom(shape=(), " "dflt=0), shape=(), pos=None)" ) permitations = [ template % ', '.join(items) for items in itertools.permutations( ("'blue': 2", "'green': 1", "'red': 0")) ] self.assertIn(repr(enumcol), permitations) def test99a_nonIntEnum(self): """Describing an enumerated column of floats (not implemented).""" colors = {'red': 1.0} self.assertRaises(NotImplementedError, self._createCol, colors, 'red', base=tb.FloatAtom()) def test99b_nonIntDtype(self): """Describing an enumerated column encoded as floats. (not implemented). """ colors = ['red', 'green', 'blue'] self.assertRaises( NotImplementedError, self._createCol, colors, 'red', 'float64') def test99b_nonScalarEnum(self): """Describing an enumerated column of non-scalars (not implemented).""" colors = {'red': (1, 2, 3)} self.assertRaises(NotImplementedError, self._createCol, colors, 'red', base=tb.IntAtom(shape=3)) class CreateAtomTestCase(common.PyTablesTestCase): """Test creating enumerated atoms.""" def _createAtom(self, enum, dflt, base='uint32', shape=()): """Create and check an enumerated atom.""" enumatom = tb.EnumAtom(enum, dflt, base=base, shape=shape) sameEnum = tb.Enum(enum) self.assertEqual(enumatom.type, 'enum') self.assertEqual(enumatom.dtype.base.name, enumatom.base.type) self.assertEqual(enumatom.shape, shape) self.assertEqual(enumatom.enum, sameEnum) def test00a_validFromEnum(self): """Describing an enumerated atom from an enumeration.""" colors = tb.Enum(['red', 'green', 'blue']) self._createAtom(colors, 'red') def test00b_validFromDict(self): """Describing an enumerated atom from a dictionary.""" colors = {'red': 4, 'green': 2, 'blue': 1} self._createAtom(colors, 'red') def test00c_validFromList(self): """Describing an enumerated atom from a list.""" colors = ['red', 'green', 'blue'] self._createAtom(colors, 'red') def test00d_invalidFromType(self): """Describing an enumerated atom from an invalid object.""" colors = 123 self.assertRaises(TypeError, self._createAtom, colors, 'red') def test02a_validDtypeBroader(self): """Describing an enumerated atom with a broader type.""" colors = {'red': 4, 'green': 2, 'blue': 1} self._createAtom(colors, 'red', base='int64') def test02b_invalidDtypeTooNarrow(self): """Describing an enumerated atom with a too narrow type.""" colors = ['e%d' % i for i in range(300)] self.assertRaises(TypeError, self._createAtom, colors, 'red', 'uint8') def test03a_validShapeMD(self): """Describing an enumerated atom with multidimensional shape.""" colors = ['red', 'green', 'blue'] self._createAtom(colors, 'red', shape=(2,)) def test99a_nonIntEnum(self): """Describing an enumerated atom of floats (not implemented).""" colors = {'red': 1.0} self.assertRaises(NotImplementedError, self._createAtom, colors, 'red', base=tb.FloatAtom()) def test99b_nonIntDtype(self): """Describing an enumerated atom encoded as a float. (not implemented). """ colors = ['red', 'green', 'blue'] self.assertRaises( NotImplementedError, self._createAtom, colors, 'red', 'float64') def test99b_nonScalarEnum(self): """Describing an enumerated atom of non-scalars (not implemented).""" colors = {'red': (1, 2, 3)} self.assertRaises(NotImplementedError, self._createAtom, colors, 'red', base=tb.IntAtom(shape=3)) class EnumTableTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test tables with enumerated columns.""" enum = tb.Enum({'red': 4, 'green': 2, 'blue': 1, 'black': 0}) defaultName = 'black' valueInEnum = enum.red valueOutOfEnum = 1234 enumType = 'uint16' def _description(self, shape=()): class TestDescription(tb.IsDescription): rid = tb.IntCol(pos=0) rcolor = tb.EnumCol( self.enum, self.defaultName, base=self.enumType, shape=shape, pos=1) return TestDescription def test00a_reopen(self): """Reopening a file with tables using enumerated data.""" self.h5file.create_table( '/', 'test', self._description(), title=self._getMethodName()) self._reopen() self.assertEqual( self.h5file.root.test.get_enum('rcolor'), self.enum, "Enumerated type was not restored correctly from disk.") def test00b_reopenMD(self): """Reopening a file with tables using enumerated multi-dimensional data.""" self.h5file.create_table( '/', 'test', self._description((2,)), title=self._getMethodName()) self._reopen() self.assertEqual( self.h5file.root.test.get_enum('rcolor'), self.enum, "Enumerated type was not restored correctly from disk.") def test01_rowAppend(self): """Appending enumerated values using ``row.append()``.""" tbl = self.h5file.create_table( '/', 'test', self._description(), title=self._getMethodName()) appended = [ (10, self.valueInEnum), (20, self.valueOutOfEnum)] row = tbl.row row['rid'] = appended[0][0] row['rcolor'] = appended[0][1] row.append() row['rid'] = appended[1][0] self.assertRaises( ValueError, operator.setitem, row, 'rcolor', appended[1][1]) tbl.flush() tbl.flavor = 'python' read = tbl.read() common.verbosePrint( "* appended value: %s\n" "* read value: %s\n" % (appended[:-1], read)) self.assertEqual( appended[:-1], read, "Written and read values differ.") def test02_append(self): """Appending enumerated values using ``table.append()``.""" tbl = self.h5file.create_table( '/', 'test', self._description(), title=self._getMethodName()) appended = [ (10, self.valueInEnum), (20, self.valueOutOfEnum)] tbl.append(appended) tbl.flush() tbl.flavor = 'python' read = tbl.read() common.verbosePrint( "* appended value: %s\n" "* read value: %s\n" % (appended, read)) self.assertEqual(appended, read, "Written and read values differ.") def test03_setitem(self): """Changing enumerated values using ``table.__setitem__()``.""" tbl = self.h5file.create_table( '/', 'test', self._description(), title=self._getMethodName()) appended = [ (10, self.valueInEnum), (20, self.valueInEnum)] tbl.append(appended) written = [ (10, self.valueInEnum), (20, self.valueOutOfEnum)] tbl[:] = written tbl.flavor = 'python' read = tbl.read() common.verbosePrint( "* written value: %s\n" "* read value: %s\n" % (written, read)) self.assertEqual(written, read, "Written and read values differ.") def test04_multidim(self): """Appending multi-dimensional enumerated data.""" tbl = self.h5file.create_table( '/', 'test', self._description((2,)), title=self._getMethodName()) appended = [ (10, (self.valueInEnum, self.valueOutOfEnum)), (20, (self.valueInEnum, self.valueOutOfEnum))] row = tbl.row row['rid'] = appended[0][0] self.assertRaises( ValueError, operator.setitem, row, 'rcolor', appended[0][1]) tbl.append(appended) tbl.flush() tbl.flavor = 'python' read = tbl.read() for x_appended, x_read in zip(appended, read): self.assertEqual(x_appended[0], x_read[0], "Written and read values differ.") self.assertEqual(x_appended[1][0], x_read[1][0], "Written and read values differ.") self.assertEqual(x_appended[1][1], x_read[1][1], "Written and read values differ.") def test05_where(self): """Searching enumerated data.""" tbl = self.h5file.create_table( '/', 'test', self._description(), title=self._getMethodName()) appended = [ (10, self.valueInEnum), (20, self.valueInEnum), (30, self.valueOutOfEnum)] tbl.append(appended) tbl.flush() searched = [ (row['rid'], row['rcolor']) for row in tbl.where('rcolor == v', {'v': self.valueInEnum})] common.verbosePrint( "* ``valueInEnum``: %s\n" "* ``rcolor`` column: ``%s``\n" "* ``searched``: %s\n" "* Should look like: %s\n" % (self.valueInEnum, tbl.cols.rcolor, searched, appended[:-1])) self.assertEqual( searched, appended[:-1], "Search returned incorrect results.") class EnumEArrayTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test extendable arrays of enumerated values.""" enum = tb.Enum({'red': 4, 'green': 2, 'blue': 1, 'black': 0}) valueInEnum = enum.red valueOutOfEnum = 1234 enumType = 'uint16' def _atom(self, shape=()): return tb.EnumAtom(self.enum, 'red', base=self.enumType, shape=shape) def test00a_reopen(self): """Reopening a file with extendable arrays using enumerated data.""" self.h5file.create_earray( '/', 'test', self._atom(), shape=(0,), title=self._getMethodName()) self.h5file.root.test.flavor = 'python' self._reopen() self.assertEqual( self.h5file.root.test.get_enum(), self.enum, "Enumerated type was not restored correctly from disk.") def test00b_reopenMD(self): """Reopening a file with extendable arrays using enumerated multi-dimensional data.""" self.h5file.create_earray( '/', 'test', self._atom(), shape=(0, 2), title=self._getMethodName()) self.h5file.root.test.flavor = 'python' self._reopen() self.assertEqual( self.h5file.root.test.get_enum(), self.enum, "Enumerated type was not restored correctly from disk.") def test_enum_default_persistence_red(self): dflt = 'red' atom = tb.EnumAtom(self.enum, dflt, base=self.enumType, shape=()) self.h5file.create_earray('/', 'test', atom, shape=(0,), title=self._getMethodName()) self._reopen() self.assertEqual( self.h5file.root.test.get_enum(), self.enum, "Enumerated type was not restored correctly from disk.") self.assertEqual( self.h5file.root.test.atom.dflt, self.enum[dflt], "The default value of enumerated type was not restored correctly " "from disk.") def test_enum_default_persistence_green(self): dflt = 'green' atom = tb.EnumAtom(self.enum, dflt, base=self.enumType, shape=()) self.h5file.create_earray('/', 'test', atom, shape=(0,), title=self._getMethodName()) self._reopen() self.assertEqual( self.h5file.root.test.get_enum(), self.enum, "Enumerated type was not restored correctly from disk.") self.assertEqual( self.h5file.root.test.atom.dflt, self.enum[dflt], "The default value of enumerated type was not restored correctly " "from disk.") def test_enum_default_persistence_blue(self): dflt = 'blue' atom = tb.EnumAtom(self.enum, dflt, base=self.enumType, shape=()) self.h5file.create_earray('/', 'test', atom, shape=(0,), title=self._getMethodName()) self._reopen() self.assertEqual( self.h5file.root.test.get_enum(), self.enum, "Enumerated type was not restored correctly from disk.") self.assertEqual( self.h5file.root.test.atom.dflt, self.enum[dflt], "The default value of enumerated type was not restored correctly " "from disk.") def test_enum_default_persistence_black(self): dflt = 'black' atom = tb.EnumAtom(self.enum, dflt, base=self.enumType, shape=()) self.h5file.create_earray('/', 'test', atom, shape=(0,), title=self._getMethodName()) self._reopen() self.assertEqual( self.h5file.root.test.get_enum(), self.enum, "Enumerated type was not restored correctly from disk.") self.assertEqual( self.h5file.root.test.atom.dflt, self.enum[dflt], "The default value of enumerated type was not restored correctly " "from disk.") def test01_append(self): """Appending scalar elements of enumerated values.""" earr = self.h5file.create_earray( '/', 'test', self._atom(), shape=(0,), title=self._getMethodName()) earr.flavor = 'python' appended = [self.valueInEnum, self.valueOutOfEnum] earr.append(appended) earr.flush() read = earr.read() self.assertEqual(appended, read, "Written and read values differ.") def test02_appendMD(self): """Appending multi-dimensional elements of enumerated values.""" earr = self.h5file.create_earray( '/', 'test', self._atom(), shape=(0, 2), title=self._getMethodName()) earr.flavor = 'python' appended = [ [self.valueInEnum, self.valueOutOfEnum], [self.valueInEnum, self.valueOutOfEnum]] earr.append(appended) earr.flush() read = earr.read() self.assertEqual(appended, read, "Written and read values differ.") def test03_setitem(self): """Changing enumerated values using ``earray.__setitem__()``.""" earr = self.h5file.create_earray( '/', 'test', self._atom(), shape=(0,), title=self._getMethodName()) earr.flavor = 'python' appended = (self.valueInEnum, self.valueInEnum) earr.append(appended) written = [self.valueInEnum, self.valueOutOfEnum] earr[:] = written read = earr.read() self.assertEqual(written, read, "Written and read values differ.") class EnumVLArrayTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test variable-length arrays of enumerated values.""" enum = tb.Enum({'red': 4, 'green': 2, 'blue': 1, 'black': 0}) valueInEnum = enum.red valueOutOfEnum = 1234 enumType = 'uint16' def _atom(self, shape=()): return tb.EnumAtom(self.enum, 'red', base=self.enumType, shape=shape) def test00a_reopen(self): """Reopening a file with variable-length arrays using enumerated data.""" self.h5file.create_vlarray( '/', 'test', self._atom(), title=self._getMethodName()) self.h5file.root.test.flavor = 'python' self._reopen() self.assertEqual( self.h5file.root.test.get_enum(), self.enum, "Enumerated type was not restored correctly from disk.") def test00b_reopenMD(self): """Reopening a file with variable-length arrays using enumerated multi-dimensional data.""" self.h5file.create_vlarray( '/', 'test', self._atom((2,)), title=self._getMethodName()) self.h5file.root.test.flavor = 'python' self._reopen() self.assertEqual( self.h5file.root.test.get_enum(), self.enum, "Enumerated type was not restored correctly from disk.") def test01_append(self): """Appending scalar elements of enumerated values.""" vlarr = self.h5file.create_vlarray( '/', 'test', self._atom(), title=self._getMethodName()) vlarr.flavor = 'python' appended = [ [self.valueInEnum, ], [self.valueInEnum, self.valueOutOfEnum]] vlarr.append(appended[0]) vlarr.append(appended[1]) vlarr.flush() read = vlarr.read() common.verbosePrint( "* appended value: %s\n" "* read value: %s\n" % (appended, read)) self.assertEqual(appended, read, "Written and read values differ.") def test02_appendMD(self): """Appending multi-dimensional elements of enumerated values.""" vlarr = self.h5file.create_vlarray( '/', 'test', self._atom((2,)), title=self._getMethodName()) vlarr.flavor = 'python' appended = [ [[self.valueInEnum, self.valueInEnum], ], [[self.valueInEnum, self.valueOutOfEnum], [self.valueInEnum, self.valueInEnum]]] vlarr.append(appended[0]) vlarr.append(appended[1]) vlarr.flush() read = vlarr.read() common.verbosePrint( "* appended value: %s\n" "* read value: %s\n" % (appended, read)) self.assertEqual(appended, read, "Written and read values differ.") def test03_setitem(self): """Changing enumerated values using ``vlarray.__setitem__()``.""" vlarr = self.h5file.create_vlarray( '/', 'test', self._atom(), title=self._getMethodName()) vlarr.flavor = 'python' appended = (self.valueInEnum, self.valueInEnum) vlarr.append(appended) written = [self.valueInEnum, self.valueOutOfEnum] vlarr[0] = written read = vlarr.read() common.verbosePrint( "* written value: %s\n" "* read value: %s\n" % (written, read)) self.assertEqual(written, read[0], "Written and read values differ.") def suite(): """Return a test suite consisting of all the test cases in the module.""" # These two are for including Enum's doctests here. import doctest theSuite = common.unittest.TestSuite() niter = 1 # theSuite.addTest(unittest.makeSuite(EnumTableTestCase)) for i in range(niter): theSuite.addTest(doctest.DocTestSuite(tb.misc.enum)) theSuite.addTest(common.unittest.makeSuite(CreateColTestCase)) theSuite.addTest(common.unittest.makeSuite(CreateAtomTestCase)) theSuite.addTest(common.unittest.makeSuite(EnumTableTestCase)) theSuite.addTest(common.unittest.makeSuite(EnumEArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(EnumVLArrayTestCase)) return theSuite if __name__ == '__main__': import sys common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_expression.py000066400000000000000000001561051416254111300214760ustar00rootroot00000000000000"""Test module for evaluating expressions under PyTables.""" import numpy as np import tables as tb from tables.tests import common # An example of record class Record(tb.IsDescription): colInt32 = tb.Int32Col() colInt64 = tb.Int64Col() colFloat32 = tb.Float32Col() colFloat64 = tb.Float64Col() colComplex = tb.ComplexCol(itemsize=16) # Helper functions def get_sliced_vars(npvars, start, stop, step): npvars_ = {} for name, var in npvars.items(): if hasattr(var, "__len__"): npvars_[name] = var[start:stop:step] else: npvars_[name] = var return npvars_ def get_sliced_vars2(npvars, start, stop, step, shape, maindim): npvars_ = {} slices = [slice(None) for dim in shape] slices[maindim] = slice(start, stop, step) for name, var in npvars.items(): npvars_[name] = var.__getitem__(tuple(slices)) return npvars_ # Basic tests class ExprTestCase(common.TempFileMixin, common.PyTablesTestCase): # The shape for the variables in expressions shape = (10, 20) def setUp(self): super().setUp() # The expression self.expr = "2 * a*b + c" # Define the NumPy variables to be used in expression N = np.prod(self.shape) self.a = a = np.arange(0, N, dtype='int32').reshape(self.shape) self.b = b = np.arange(N, 2 * N, dtype='int64').reshape(self.shape) self.c = c = np.arange(2 * N, 3*N, dtype='int32').reshape(self.shape) self.r1 = r1 = np.empty(N, dtype='int64').reshape(self.shape) self.npvars = {"a": a, "b": b, "c": c, } # Define other variables, if needed root = self.h5file.root if self.kind == "Array": self.a = self.h5file.create_array(root, "a", a) self.b = self.h5file.create_array(root, "b", b) self.c = self.h5file.create_array(root, "c", c) self.r1 = self.h5file.create_array(root, "r1", r1) elif self.kind == "CArray": self.a = self.h5file.create_carray( root, "a", atom=tb.Atom.from_dtype(a.dtype), shape=self.shape) self.b = self.h5file.create_carray( root, "b", atom=tb.Atom.from_dtype(b.dtype), shape=self.shape) self.c = self.h5file.create_carray( root, "c", atom=tb.Atom.from_dtype(c.dtype), shape=self.shape) self.r1 = self.h5file.create_carray( root, "r1", atom=tb.Atom.from_dtype(r1.dtype), shape=self.shape) self.a[:] = a self.b[:] = b self.c[:] = c elif self.kind == "EArray": shape = list(self.shape) shape[0] = 0 self.a = self.h5file.create_earray( root, "a", atom=tb.Atom.from_dtype(a.dtype), shape=shape) self.b = self.h5file.create_earray( root, "b", atom=tb.Atom.from_dtype(b.dtype), shape=shape) self.c = self.h5file.create_earray( root, "c", atom=tb.Atom.from_dtype(c.dtype), shape=shape) self.r1 = self.h5file.create_earray( root, "r1", atom=tb.Atom.from_dtype(r1.dtype), shape=shape) self.a.append(a) self.b.append(b) self.c.append(c) self.r1.append(r1) # Fill with uninitialized values elif self.kind == "Column": ra = np.rec.fromarrays( [a, b, c, r1], dtype="%si4,%si8,%si4,%si8" % ((self.shape[1:],)*4)) t = self.h5file.create_table(root, "t", ra) self.a = t.cols.f0 self.b = t.cols.f1 self.c = t.cols.f2 self.d = t.cols.f3 self.vars = {"a": self.a, "b": self.b, "c": self.c, } def test00_simple(self): """Checking that expression is correctly evaluated.""" expr = tb.Expr(self.expr, self.vars) r1 = expr.eval() r2 = eval(self.expr, self.npvars) if common.verbose: print("Computed expression:", repr(r1)) print("Should look like:", repr(r2)) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test01_out(self): """Checking that expression is correctly evaluated (`out` param)""" expr = tb.Expr(self.expr, self.vars) expr.set_output(self.r1) r1 = expr.eval() if self.kind != "NumPy": r1 = r1[:] r2 = eval(self.expr, self.npvars) if common.verbose: print("Computed expression:", repr(r1)) print("Should look like:", repr(r2)) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test02_out(self): """Checking that expression is correctly evaluated when slice is outside of data samples (`out` param)""" expr = tb.Expr(self.expr, self.vars) # maybe it's better to use the leading dimemsion instead? maxshape = max(self.shape) start, stop, step = (maxshape + 1, maxshape + 2, None) expr.set_inputs_range(start, stop, step) r1 = expr.eval() # create an empty array with the same dtype and shape zeros = np.zeros(shape=self.shape, dtype=r1.dtype) r2 = zeros[start:stop:step] self.assertListEqual(r1.tolist(), r2.tolist()) if common.verbose: print("Computed expression:", repr(r1)) print("Should look like:", repr(r2)) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") class ExprNumPy(ExprTestCase): kind = "NumPy" class ExprArray(ExprTestCase): kind = "Array" class ExprCArray(ExprTestCase): kind = "CArray" class ExprEArray(ExprTestCase): kind = "EArray" class ExprColumn(ExprTestCase): kind = "Column" # Test for mixed containers class MixedContainersTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() # The expression self.expr = "2 * a*b + c**2+d**2+e-f+g" # Create a directory in file for outputs root = self.h5file.root outs = self.h5file.create_group(root, "outs") # Define the NumPy variables to be used in expression N = np.prod(self.shape) # Initial values for variables a = np.arange(0, N, dtype='int32').reshape(self.shape) b = np.arange(N, 2 * N, dtype='int64').reshape(self.shape) c = np.arange(2 * N, 3*N, dtype='int32').reshape(self.shape) d = np.arange(3 * N, 4*N, dtype='int32').reshape(self.shape) e = np.arange(4 * N, 5*N, dtype='int32').reshape(self.shape) self.f = f = int(3) # a regular python type self.g = g = np.int16(2) # a NumPy scalar type # Original values self.npvars = {"a": a, "b": b, "c": c, "d": d, "e": e, "f": f, "g": g} rnda = b.copy() # ndarray input and output self.a = a self.rnda = rnda # Array input and output self.b = self.h5file.create_array(root, "b", b) self.rarr = self.b.copy(outs) # CArray input and output self.c = self.h5file.create_carray( root, "c", atom=tb.Atom.from_dtype(c.dtype), shape=self.shape) self.c[:] = c self.rcarr = self.c.copy(outs) # EArray input and output eshape = list(self.shape) eshape[0] = 0 self.d = self.h5file.create_earray( root, "d", atom=tb.Atom.from_dtype(d.dtype), shape=eshape) self.d.append(d) self.rearr = self.d.copy(outs) # Column input and output rtype = {} colshape = self.shape[1:] for i, col in enumerate((a, b, c, d, e, rnda)): rtype['f%d' % i] = tb.Col.from_sctype(col.dtype.type, colshape) t = self.h5file.create_table(root, "t", rtype) nrows = self.shape[0] row = t.row for nrow in range(nrows): for i, col in enumerate((a, b, c, d, e, rnda)): row['f%d' % i] = col[nrow] row.append() t.flush() self.e = t.cols.f4 self.rcol = t.cols.f5 # Input vars self.vars = {"a": self.a, "b": self.b, "c": self.c, "d": self.d, "e": self.e, "f": self.f, "g": self.g, } def test00a_simple(self): """Checking expressions with mixed objects.""" expr = tb.Expr(self.expr, self.vars) r1 = expr.eval() r2 = eval(self.expr, self.npvars) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test00b_simple_scalars(self): """Checking that scalars in expression evaluate correctly.""" expr_str = "2 * f + g" expr = tb.Expr(expr_str, self.vars) r1 = expr.eval() r2 = eval(expr_str, self.npvars) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue( r1.shape == r2.shape and r1.dtype == r2.dtype and r1 == r2, "Evaluate is returning a wrong value.") def test01a_out(self): """Checking expressions with mixed objects (`out` param)""" expr = tb.Expr(self.expr, self.vars) for r1 in self.rnda, self.rarr, self.rcarr, self.rearr, self.rcol: if common.verbose: print("Checking output container:", type(r1)) expr.set_output(r1) r1 = expr.eval() if not isinstance(r1, type(self.rnda)): r1 = r1[:] r2 = eval(self.expr, self.npvars) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test01b_out_scalars(self): """Checking expressions with mixed objects (`out` param, scalars)""" if len(self.shape) > 1: # This test is only meant for undimensional outputs return expr_str = "2 * f + g" expr = tb.Expr(expr_str, self.vars) for r1 in self.rnda, self.rarr, self.rcarr, self.rearr, self.rcol: if common.verbose: print("Checking output container:", type(r1)) expr.set_output(r1) r1 = expr.eval() r1 = r1[()] # convert a 0-dim array into a scalar r2 = eval(expr_str, self.npvars) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test02a_sss(self): """Checking mixed objects and start, stop, step (I)""" start, stop, step = (self.start, self.stop, 1) expr = tb.Expr(self.expr, self.vars) expr.set_inputs_range(start, stop, step) r1 = expr.eval() npvars = get_sliced_vars(self.npvars, start, stop, step) r2 = eval(self.expr, npvars) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test02b_sss(self): """Checking mixed objects and start, stop, step (II)""" start, stop, step = (0, self.shape[0], self.step) expr = tb.Expr(self.expr, self.vars) expr.set_inputs_range(start, stop, step) r1 = expr.eval() npvars = get_sliced_vars(self.npvars, start, stop, step) r2 = eval(self.expr, npvars) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test02c_sss(self): """Checking mixed objects and start, stop, step (III)""" start, stop, step = (self.start, self.stop, self.step) expr = tb.Expr(self.expr, self.vars) expr.set_inputs_range(start, stop, step) r1 = expr.eval() npvars = get_sliced_vars(self.npvars, start, stop, step) r2 = eval(self.expr, npvars) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test03_sss(self): """Checking start, stop, step as numpy.int64.""" start, stop, step = [np.int64(i) for i in (self.start, self.stop, self.step)] expr = tb.Expr(self.expr, self.vars) expr.set_inputs_range(start, stop, step) r1 = expr.eval() npvars = get_sliced_vars(self.npvars, start, stop, step) r2 = eval(self.expr, npvars) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") class MixedContainers0(MixedContainersTestCase): shape = (1,) start, stop, step = (0, 1, 1) class MixedContainers1(MixedContainersTestCase): shape = (10,) start, stop, step = (3, 6, 2) class MixedContainers2(MixedContainersTestCase): shape = (10, 5) start, stop, step = (2, 9, 3) class MixedContainers3(MixedContainersTestCase): shape = (10, 3, 2) start, stop, step = (2, -1, 1) # Test for unaligned objects class UnalignedObject(common.PyTablesTestCase): def test00_simple(self): """Checking expressions with unaligned objects.""" # Build unaligned arrays a0 = np.empty(10, dtype="int8") a1 = np.arange(10, dtype="int32") a2 = a1.copy() a3 = a2.copy() ra = np.rec.fromarrays([a0, a1, a2, a3]) # The inputs a = ra['f1'] b = ra['f2'] self.assertEqual(a.flags.aligned, False) self.assertEqual(b.flags.aligned, False) # The expression sexpr = "2 * a + b" expr = tb.Expr(sexpr) r1 = expr.eval() r2 = eval(sexpr) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test01_md(self): """Checking expressions with unaligned objects (MD version)""" # Build unaligned arrays a0 = np.empty((10, 4), dtype="int8") a1 = np.arange(10 * 4, dtype="int32").reshape(10, 4) a2 = a1.copy() a3 = a2.copy() ra = np.rec.fromarrays([a0, a1, a2, a3]) # The inputs a = ra['f1'] b = ra['f2'] self.assertEqual(a.flags.aligned, False) self.assertEqual(b.flags.aligned, False) # The expression sexpr = "2 * a + b" expr = tb.Expr(sexpr) r1 = expr.eval() r2 = eval(sexpr) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") # Test for non-contiguous objects class NonContiguousObject(common.PyTablesTestCase): def test00_simple(self): """Checking expressions with non-contiguous objects""" # Build non-contiguous arrays as inputs a = np.arange(10, dtype="int32") b = a[::2] a = b * 2 self.assertEqual(b.flags.contiguous, False) self.assertEqual(b.flags.aligned, True) # The expression sexpr = "2 * a + b" expr = tb.Expr(sexpr) r1 = expr.eval() r2 = eval(sexpr) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test01a_md(self): """Checking expressions with non-contiguous objects (MD version, I)""" # Build non-contiguous arrays a = np.arange(10 * 4, dtype="int32").reshape(10, 4) b = a[::2] a = b * 2 self.assertEqual(b.flags.contiguous, False) self.assertEqual(b.flags.aligned, True) # The expression sexpr = "2 * a + b" expr = tb.Expr(sexpr) r1 = expr.eval() r2 = eval(sexpr) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test01b_md(self): """Checking expressions with non-contiguous objects (MD version, II)""" # Build non-contiguous arrays a = np.arange(10 * 4, dtype="int32").reshape(10, 4) b = a[:, ::2] a = b * 2 self.assertEqual(b.flags.contiguous, False) self.assertEqual(b.flags.aligned, True) # The expression sexpr = "2 * a + b" expr = tb.Expr(sexpr) r1 = expr.eval() r2 = eval(sexpr) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") # Test for errors class ExprError(common.TempFileMixin, common.PyTablesTestCase): # The shape for the variables in expressions shape = (10,) def setUp(self): super().setUp() # Define the NumPy variables to be used in expression N = np.prod(self.shape) self.a = np.arange(N, dtype='int32').reshape(self.shape) self.b = np.arange(N, dtype='int64').reshape(self.shape) self.c = np.arange(N, dtype='int32').reshape(self.shape) self.r1 = np.empty(N, dtype='int64').reshape(self.shape) def _test00_shape(self): """Checking that inconsistent shapes are detected.""" self.b = self.b.reshape(self.shape+(1,)) expr = "a * b + c" vars_ = {"a": self.a, "b": self.b, "c": self.c, } expr = tb.Expr(expr, vars_) self.assertRaises(ValueError, expr.eval) def test02_uint64(self): """Checking that uint64 arrays in expression are detected.""" self.b = self.b.view('uint64') expr = "a * b + c" vars_ = {"a": self.a, "b": self.b, "c": self.c, } self.assertRaises(NotImplementedError, tb.Expr, expr, vars_) def test03_table(self): """Checking that tables in expression are detected.""" class Rec(tb.IsDescription): col1 = tb.Int32Col() col2 = tb.Int64Col() t = self.h5file.create_table("/", "a", Rec) expr = "a * b + c" vars_ = {"a": t, "b": self.b, "c": self.c, } self.assertRaises(TypeError, tb.Expr, expr, vars_) def test04_nestedcols(self): """Checking that nested cols in expression are detected.""" class Nested(tb.IsDescription): col1 = tb.Int32Col() class col2(tb.IsDescription): col3 = tb.Int64Col() t = self.h5file.create_table("/", "a", Nested) expr = "a * b + c" # The next non-nested column should work a = t.cols.col2.col3 vars_ = {"a": a, "b": self.b, "c": self.c, } expr = tb.Expr(expr, vars_) r1 = expr.eval() self.assertIsNotNone(r1) # But a nested column should not a = t.cols.col2 vars_ = {"a": a, "b": self.b, "c": self.c, } self.assertRaises(TypeError, tb.Expr, expr, vars_) def test05_vlarray(self): """Checking that VLArrays in expression are detected.""" vla = self.h5file.create_vlarray("/", "a", tb.Int32Col()) expr = "a * b + c" vars_ = {"a": vla, "b": self.b, "c": self.c, } self.assertRaises(TypeError, tb.Expr, expr, vars_) # Test for broadcasting arrays class BroadcastTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_simple(self): """Checking broadcast in expression.""" shapes = (self.shape1, self.shape2, self.shape3) # Build arrays with different shapes as inputs a = np.arange(np.prod(shapes[0]), dtype="i4").reshape(shapes[0]) b = np.arange(np.prod(shapes[1]), dtype="i4").reshape(shapes[1]) c = np.arange(np.prod(shapes[2]), dtype="i4").reshape(shapes[2]) root = self.h5file.root if a.shape[0] > 0: a1 = self.h5file.create_array(root, 'a1', a) else: a1 = self.h5file.create_earray( root, 'a1', atom=tb.Int32Col(), shape=a.shape) self.assertIsNotNone(a1) b1 = self.h5file.create_array(root, 'b1', b) self.assertIsNotNone(b1) c1 = self.h5file.create_array(root, 'c1', c) self.assertIsNotNone(c1) # The expression expr = tb.Expr("2 * a1 + b1-c1") r1 = expr.eval() r2 = eval("2 * a + b-c") if common.verbose: print("Tested shapes:", self.shape1, self.shape2, self.shape3) print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") class Broadcast0(BroadcastTestCase): shape1 = (0, 3, 4) shape2 = (3, 4) shape3 = (4,) class Broadcast1(BroadcastTestCase): shape1 = (2, 3, 4) shape2 = (3, 4) shape3 = (4,) class Broadcast2(BroadcastTestCase): shape1 = (3, 4,) shape2 = (3, 4) shape3 = (4,) class Broadcast3(BroadcastTestCase): shape1 = (4,) shape2 = (3, 4) shape3 = (4,) class Broadcast4(BroadcastTestCase): shape1 = (1,) shape2 = (3, 4) shape3 = (4,) class Broadcast5(BroadcastTestCase): shape1 = (1,) shape2 = (3, 1) shape3 = (4,) # Test for different length inputs class DiffLengthTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_simple(self): """Checking different length inputs in expression.""" shapes = (list(self.shape1), list(self.shape2), list(self.shape3)) # Build arrays with different shapes as inputs a = np.arange(np.prod(shapes[0]), dtype="i4").reshape(shapes[0]) b = np.arange(np.prod(shapes[1]), dtype="i4").reshape(shapes[1]) c = np.arange(np.prod(shapes[2]), dtype="i4").reshape(shapes[2]) # The expression expr = tb.Expr("2 * a + b-c") r1 = expr.eval() # Compute the minimum length for shapes maxdim = max([len(shape) for shape in shapes]) minlen = min([shape[0] for i, shape in enumerate(shapes) if len(shape) == maxdim]) for i, shape in enumerate(shapes): if len(shape) == maxdim: shape[0] = minlen # Build arrays with the new shapes as inputs a = np.arange(np.prod(shapes[0]), dtype="i4").reshape(shapes[0]) self.assertIsNotNone(a) b = np.arange(np.prod(shapes[1]), dtype="i4").reshape(shapes[1]) self.assertIsNotNone(b) c = np.arange(np.prod(shapes[2]), dtype="i4").reshape(shapes[2]) self.assertIsNotNone(c) r2 = eval("2 * a + b-c") if common.verbose: print("Tested shapes:", self.shape1, self.shape2, self.shape3) print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") class DiffLength0(DiffLengthTestCase): shape1 = (0,) shape2 = (10,) shape3 = (20,) class DiffLength1(DiffLengthTestCase): shape1 = (3,) shape2 = (10,) shape3 = (20,) class DiffLength2(DiffLengthTestCase): shape1 = (3, 4) shape2 = (2, 3, 4) shape3 = (4, 3, 4) class DiffLength3(DiffLengthTestCase): shape1 = (1, 3, 4) shape2 = (2, 3, 4) shape3 = (4, 3, 4) class DiffLength4(DiffLengthTestCase): shape1 = (0, 3, 4) shape2 = (2, 3, 4) shape3 = (4, 3, 4) # Test for different type inputs class TypesTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_bool(self): """Checking booleans in expression.""" # Build arrays with different shapes as inputs a = np.array([True, False, True]) b = np.array([False, True, False]) root = self.h5file.root a1 = self.h5file.create_array(root, 'a1', a) self.assertIsNotNone(a1) b1 = self.h5file.create_array(root, 'b1', b) self.assertIsNotNone(b1) expr = tb.Expr("a | b") r1 = expr.eval() r2 = eval("a | b") if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test01_shortint(self): """Checking int8,uint8,int16,uint16 and int32 in expression.""" for dtype in 'int8', 'uint8', 'int16', 'uint16', 'int32': if common.verbose: print("Checking type:", dtype) # Build arrays with different shapes as inputs a = np.array([1, 2, 3], dtype) b = np.array([3, 4, 5], dtype) root = self.h5file.root a1 = self.h5file.create_array(root, 'a1', a) b1 = self.h5file.create_array(root, 'b1', b) two = np.int32(2) self.assertIsInstance(two, np.integer) expr = tb.Expr("two * a1-b1") r1 = expr.eval() a = np.array([1, 2, 3], 'int32') b = np.array([3, 4, 5], 'int32') r2 = eval("two * a-b") if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertEqual(r1.dtype, r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") # Remove created leaves a1.remove() b1.remove() def test02_longint(self): """Checking uint32 and int64 in expression.""" for dtype in 'uint32', 'int64': if common.verbose: print("Checking type:", dtype) # Build arrays with different shapes as inputs a = np.array([1, 2, 3], dtype) b = np.array([3, 4, 5], dtype) root = self.h5file.root a1 = self.h5file.create_array(root, 'a1', a) b1 = self.h5file.create_array(root, 'b1', b) expr = tb.Expr("2 * a1-b1") r1 = expr.eval() a = np.array([1, 2, 3], 'int64') b = np.array([3, 4, 5], 'int64') r2 = eval("2 * a-b") if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertEqual(r1.dtype, r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") # Remove created leaves a1.remove() b1.remove() def test03_float(self): """Checking float32 and float64 in expression.""" for dtype in 'float32', 'float64': if common.verbose: print("Checking type:", dtype) # Build arrays with different shapes as inputs a = np.array([1, 2, 3], dtype) b = np.array([3, 4, 5], dtype) root = self.h5file.root a1 = self.h5file.create_array(root, 'a1', a) b1 = self.h5file.create_array(root, 'b1', b) expr = tb.Expr("2 * a1-b1") r1 = expr.eval() a = np.array([1, 2, 3], dtype) b = np.array([3, 4, 5], dtype) r2 = eval("2 * a-b") if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertEqual(r1.dtype, r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") # Remove created leaves a1.remove() b1.remove() def test04_complex(self): """Checking complex64 and complex128 in expression.""" for dtype in 'complex64', 'complex128': if common.verbose: print("Checking type:", dtype) # Build arrays with different shapes as inputs a = np.array([1, 2j, 3 + 2j], dtype) b = np.array([3, 4j, 5 + 1j], dtype) root = self.h5file.root a1 = self.h5file.create_array(root, 'a1', a) b1 = self.h5file.create_array(root, 'b1', b) expr = tb.Expr("2 * a1-b1") r1 = expr.eval() a = np.array([1, 2j, 3 + 2j], 'complex128') b = np.array([3, 4j, 5 + 1j], 'complex128') r2 = eval("2 * a-b") if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertEqual(r1.dtype, r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") # Remove created leaves a1.remove() b1.remove() def test05_string(self): """Checking strings in expression.""" # Build arrays with different shapes as inputs a = np.array(['a', 'bd', 'cd'], 'S') b = np.array(['a', 'bdcd', 'ccdc'], 'S') root = self.h5file.root a1 = self.h5file.create_array(root, 'a1', a) self.assertIsNotNone(a1) b1 = self.h5file.create_array(root, 'b1', b) self.assertIsNotNone(b1) expr = tb.Expr("(a1 > b'a') | ( b1 > b'b')") r1 = expr.eval() r2 = eval("(a > b'a') | ( b > b'b')") if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") # Test for different functions class FunctionsTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_simple(self): """Checking some math functions in expression.""" # Build arrays with different shapes as inputs a = np.array([.1, .2, .3]) b = np.array([.3, .4, .5]) root = self.h5file.root a1 = self.h5file.create_array(root, 'a1', a) self.assertIsNotNone(a1) b1 = self.h5file.create_array(root, 'b1', b) self.assertIsNotNone(b1) # The expression expr = tb.Expr("sin(a1) * sqrt(b1)") r1 = expr.eval() r2 = np.sin(a) * np.sqrt(b) if common.verbose: print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") # Test for EArrays with maindim != 0 class MaindimTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_simple(self): """Checking other dimensions than 0 as main dimension.""" shape = list(self.shape) # Build input arrays a = np.arange(np.prod(shape), dtype="i4").reshape(shape) b = a.copy() c = a.copy() root = self.h5file.root shape[self.maindim] = 0 a1 = self.h5file.create_earray( root, 'a1', atom=tb.Int32Col(), shape=shape) b1 = self.h5file.create_earray( root, 'b1', atom=tb.Int32Col(), shape=shape) c1 = self.h5file.create_earray( root, 'c1', atom=tb.Int32Col(), shape=shape) a1.append(a) b1.append(b) c1.append(c) # The expression expr = tb.Expr("2 * a1 + b1-c1") r1 = expr.eval() r2 = eval("2 * a + b-c") if common.verbose: print("Tested shape:", shape) print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test01_out(self): """Checking other dimensions than 0 as main dimension (out)""" shape = list(self.shape) # Build input arrays a = np.arange(np.prod(shape), dtype="i4").reshape(shape) b = a.copy() c = a.copy() root = self.h5file.root shape[self.maindim] = 0 a1 = self.h5file.create_earray( root, 'a1', atom=tb.Int32Col(), shape=shape) b1 = self.h5file.create_earray( root, 'b1', atom=tb.Int32Col(), shape=shape) c1 = self.h5file.create_earray( root, 'c1', atom=tb.Int32Col(), shape=shape) r1 = self.h5file.create_earray( root, 'r1', atom=tb.Int32Col(), shape=shape) a1.append(a) b1.append(b) c1.append(c) r1.append(c) # The expression expr = tb.Expr("2 * a1 + b1-c1") expr.set_output(r1) expr.eval() r2 = eval("2 * a + b-c") if common.verbose: print("Tested shape:", shape) print("Computed expression:", repr(r1[:]), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1[:], r2), "Evaluate is returning a wrong value.") def test02_diff_in_maindims(self): """Checking different main dimensions in inputs.""" shape = list(self.shape) # Build input arrays a = np.arange(np.prod(shape), dtype="i4").reshape(shape) b = a.copy() c = a.copy() root = self.h5file.root shape2 = shape[:] shape[self.maindim] = 0 shape2[0] = 0 a1 = self.h5file.create_earray( root, 'a1', atom=tb.Int32Col(), shape=shape) self.assertEqual(a1.maindim, self.maindim) b1 = self.h5file.create_earray( root, 'b1', atom=tb.Int32Col(), shape=shape2) self.assertEqual(b1.maindim, 0) c1 = self.h5file.create_earray( root, 'c1', atom=tb.Int32Col(), shape=shape) r1 = self.h5file.create_earray( root, 'r1', atom=tb.Int32Col(), shape=shape) a1.append(a) b1.append(b) c1.append(c) r1.append(c) # The expression expr = tb.Expr("2 * a1 + b1-c1") r1 = expr.eval() r2 = eval("2 * a + b-c") if common.verbose: print("Tested shape:", shape) print("Computed expression:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test03_diff_in_out_maindims(self): """Checking different maindims in inputs and output.""" shape = list(self.shape) # Build input arrays a = np.arange(np.prod(shape), dtype="i4").reshape(shape) b = a.copy() c = a.copy() root = self.h5file.root shape2 = shape[:] shape[self.maindim] = 0 shape2[0] = 0 a1 = self.h5file.create_earray( root, 'a1', atom=tb.Int32Col(), shape=shape) self.assertEqual(a1.maindim, self.maindim) b1 = self.h5file.create_earray( root, 'b1', atom=tb.Int32Col(), shape=shape) c1 = self.h5file.create_earray( root, 'c1', atom=tb.Int32Col(), shape=shape) r1 = self.h5file.create_earray( root, 'r1', atom=tb.Int32Col(), shape=shape2) self.assertEqual(r1.maindim, 0) a1.append(a) b1.append(b) c1.append(c) r1.append(c) # The expression expr = tb.Expr("2 * a1 + b1-c1") expr.set_output(r1) expr.eval() r2 = eval("2 * a + b-c") if common.verbose: print("Tested shape:", shape) print("Computed expression:", repr(r1[:]), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1[:], r2), "Evaluate is returning a wrong value.") def test04_diff_in_out_maindims_lengths(self): """Checking different maindims and lengths in inputs and output.""" shape = list(self.shape) # Build input arrays a = np.arange(np.prod(shape), dtype="i4").reshape(shape) b = a.copy() c = a.copy() root = self.h5file.root shape2 = shape[:] shape[self.maindim] = 0 shape2[0] = 0 a1 = self.h5file.create_earray( root, 'a1', atom=tb.Int32Col(), shape=shape) self.assertEqual(a1.maindim, self.maindim) b1 = self.h5file.create_earray( root, 'b1', atom=tb.Int32Col(), shape=shape) c1 = self.h5file.create_earray( root, 'c1', atom=tb.Int32Col(), shape=shape) r1 = self.h5file.create_earray( root, 'r1', atom=tb.Int32Col(), shape=shape2) self.assertEqual(r1.maindim, 0) a1.append(a) a1.append(a) b1.append(b) b1.append(b) c1.append(c) c1.append(c) r1.append(c) # just once so that output is smaller # The expression expr = tb.Expr("2 * a1 + b1-c1") expr.set_output(r1) # This should raise an error self.assertRaises(ValueError, expr.eval) class Maindim0(MaindimTestCase): maindim = 1 shape = (1, 2) class Maindim1(MaindimTestCase): maindim = 1 shape = (2, 3) class Maindim2(MaindimTestCase): maindim = 1 shape = (2, 3, 4) class Maindim3(MaindimTestCase): maindim = 2 shape = (2, 3, 4) # Test `append` mode flag in `set_output()` class AppendModeTestCase(common.TempFileMixin, common.PyTablesTestCase): def test01_append(self): """Checking append mode in `set_output()`""" shape = [3, 2] # Build input arrays a = np.arange(np.prod(shape), dtype="i4").reshape(shape) b = a.copy() c = a.copy() shape[1] = 0 root = self.h5file.root a1 = self.h5file.create_earray( root, 'a1', atom=tb.Int32Col(), shape=shape) b1 = self.h5file.create_earray( root, 'b1', atom=tb.Int32Col(), shape=shape) c1 = self.h5file.create_earray( root, 'c1', atom=tb.Int32Col(), shape=shape) r1 = self.h5file.create_earray( root, 'r1', atom=tb.Int32Col(), shape=shape) a1.append(a) b1.append(b) c1.append(c) if not self.append: r1.append(c) # The expression expr = tb.Expr("2 * a1 + b1-c1") expr.set_output(r1, append_mode=self.append) expr.eval() r2 = eval("2 * a + b-c") if common.verbose: print("Tested shape:", shape) print("Computed expression:", repr(r1[:]), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1[:], r2), "Evaluate is returning a wrong value.") class AppendModeTrue(AppendModeTestCase): append = True class AppendModeFalse(AppendModeTestCase): append = False # Test for `__iter__()` iterator class iterTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() shape = list(self.shape) # Build input arrays a = np.arange(np.prod(shape), dtype="i4").reshape(shape) b = a.copy() c = a.copy() self.npvars = {'a': a, 'b': b, 'c': c} shape[self.maindim] = 0 root = self.h5file.root a1 = self.h5file.create_earray( root, 'a1', atom=tb.Int32Col(), shape=shape) b1 = self.h5file.create_earray( root, 'b1', atom=tb.Int32Col(), shape=shape) c1 = self.h5file.create_earray( root, 'c1', atom=tb.Int32Col(), shape=shape) a1.append(a) b1.append(b) c1.append(c) self.vars = {'a': a1, 'b': b1, 'c': c1} # The expression self.sexpr = "2 * a + b-c" def test00_iter(self): """Checking the __iter__ iterator.""" expr = tb.Expr(self.sexpr, self.vars) r1 = np.array([row for row in expr]) r2 = eval(self.sexpr, self.npvars) if common.verbose: print("Tested shape, maindim:", self.shape, self.maindim) print("Computed expression:", repr(r1[:]), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1[:], r2), "Evaluate is returning a wrong value.") def test01a_sss(self): """Checking the __iter__ iterator (with ranges, I)""" start, stop, step = self.range_[0], None, None expr = tb.Expr(self.sexpr, self.vars) expr.set_inputs_range(start, stop, step) r1 = np.array([row for row in expr]) npvars = get_sliced_vars2( self.npvars, start, stop, step, self.shape, self.maindim) r2 = eval(self.sexpr, npvars) if common.verbose: print("Tested shape, maindim:", self.shape, self.maindim) print("Computed expression:", repr(r1[:]), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1[:], r2), "Evaluate is returning a wrong value.") def test01b_sss(self): """Checking the __iter__ iterator (with ranges, II)""" start, stop, step = self.range_[0], self.range_[2], None expr = tb.Expr(self.sexpr, self.vars) expr.set_inputs_range(start, stop, step) r1 = np.array([row for row in expr]) npvars = get_sliced_vars2( self.npvars, start, stop, step, self.shape, self.maindim) r2 = eval(self.sexpr, npvars) if common.verbose: print("Tested shape, maindim:", self.shape, self.maindim) print("Computed expression:", repr(r1[:]), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1[:], r2), "Evaluate is returning a wrong value.") def test01c_sss(self): """Checking the __iter__ iterator (with ranges, III)""" start, stop, step = self.range_ expr = tb.Expr(self.sexpr, self.vars) expr.set_inputs_range(start, stop, step) r1 = np.array([row for row in expr]) npvars = get_sliced_vars2( self.npvars, start, stop, step, self.shape, self.maindim) r2 = eval(self.sexpr, npvars) if common.verbose: print("Tested shape, maindim:", self.shape, self.maindim) print("Computed expression:", repr(r1[:]), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1[:], r2), "Evaluate is returning a wrong value.") class iter0(iterTestCase): maindim = 0 shape = (0,) range_ = (1, 2, 1) class iter1(iterTestCase): maindim = 0 shape = (3,) range_ = (1, 2, 1) class iter2(iterTestCase): maindim = 0 shape = (3, 2) range_ = (0, 3, 2) class iter3(iterTestCase): maindim = 1 shape = (3, 2) range_ = (0, 3, 2) class iter4(iterTestCase): maindim = 2 shape = (3, 2, 1) range_ = (1, 3, 2) class iter5(iterTestCase): maindim = 2 shape = (1, 2, 5) range_ = (0, 4, 2) # Test for set_output_range class setOutputRangeTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_simple(self): """Checking the range selection for output.""" shape = list(self.shape) start, stop, step = self.range_ # Build input arrays a = np.arange(np.prod(shape), dtype="i4").reshape(shape) b = a.copy() r = a.copy() root = self.h5file.root a1 = self.h5file.create_array(root, 'a1', a) self.assertIsNotNone(a1) b1 = self.h5file.create_array(root, 'b1', b) self.assertIsNotNone(b1) r1 = self.h5file.create_array(root, 'r1', r) # The expression expr = tb.Expr("a1-b1-1") expr.set_output(r1) expr.set_output_range(start, stop, step) expr.eval() r2 = eval("a-b-1") r[start:stop:step] = r2[:len(range(start, stop, step))] if common.verbose: print("Tested shape:", shape) print("Computed expression:", repr(r1[:]), r1.dtype) print("Should look like:", repr(r), r.dtype) self.assertTrue(common.areArraysEqual(r1[:], r), "Evaluate is returning a wrong value.") def test01_maindim(self): """Checking the range selection for output (maindim > 0)""" shape = list(self.shape) start, stop, step = self.range_ # Build input arrays a = np.arange(np.prod(shape), dtype="i4").reshape(shape) b = a.copy() r = a.copy() shape[self.maindim] = 0 root = self.h5file.root a1 = self.h5file.create_earray( root, 'a1', atom=tb.Int32Col(), shape=shape) b1 = self.h5file.create_earray( root, 'b1', atom=tb.Int32Col(), shape=shape) r1 = self.h5file.create_earray( root, 'r1', atom=tb.Int32Col(), shape=shape) a1.append(a) b1.append(b) r1.append(r) # The expression expr = tb.Expr("a1-b1-1") expr.set_output(r1) expr.set_output_range(start, stop, step) expr.eval() r2 = eval("a-b-1") lsl = tuple([slice(None)] * self.maindim) # print "lsl-->", lsl + (slice(start,stop,step),) lrange = len(range(start, stop, step)) r.__setitem__(lsl + (slice(start, stop, step),), r2.__getitem__(lsl + (slice(0, lrange),))) if common.verbose: print("Tested shape:", shape) print("Computed expression:", repr(r1[:]), r1.dtype) print("Should look like:", repr(r), r.dtype) self.assertTrue(common.areArraysEqual(r1[:], r), "Evaluate is returning a wrong value.") class setOutputRange0(setOutputRangeTestCase): maindim = 0 shape = (10,) range_ = (0, 1, 2) class setOutputRange1(setOutputRangeTestCase): maindim = 0 shape = (10,) range_ = (0, 10, 2) class setOutputRange2(setOutputRangeTestCase): maindim = 0 shape = (10,) range_ = (1, 10, 2) class setOutputRange3(setOutputRangeTestCase): maindim = 0 shape = (10, 1) range_ = (1, 10, 3) class setOutputRange4(setOutputRangeTestCase): maindim = 0 shape = (10, 2) range_ = (1, 10, 3) class setOutputRange5(setOutputRangeTestCase): maindim = 0 shape = (5, 3, 1) range_ = (1, 5, 1) class setOutputRange6(setOutputRangeTestCase): maindim = 1 shape = (2, 5) range_ = (1, 3, 2) class setOutputRange7(setOutputRangeTestCase): maindim = 1 shape = (2, 5, 1) range_ = (1, 3, 2) class setOutputRange8(setOutputRangeTestCase): maindim = 2 shape = (1, 3, 5) range_ = (1, 5, 2) class setOutputRange9(setOutputRangeTestCase): maindim = 3 shape = (1, 3, 4, 5) range_ = (1, 5, 3) # Test for very large inputs class VeryLargeInputsTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_simple(self): """Checking very large inputs.""" shape = self.shape # Use filters so as to not use too much space if tb.which_lib_version("blosc") is not None: filters = tb.Filters(complevel=1, complib='blosc', shuffle=False) elif tb.which_lib_version("lzo") is not None: filters = tb.Filters(complevel=1, complib='lzo', shuffle=False) else: filters = tb.Filters(complevel=1, shuffle=False) # Build input arrays root = self.h5file.root a = self.h5file.create_carray(root, 'a', atom=tb.Float64Atom(dflt=3), shape=shape, filters=filters) self.assertIsNotNone(a) b = self.h5file.create_carray(root, 'b', atom=tb.Float64Atom(dflt=2), shape=shape, filters=filters) self.assertIsNotNone(b) r1 = self.h5file.create_carray(root, 'r1', atom=tb.Float64Atom(dflt=3), shape=shape, filters=filters) # The expression expr = tb.Expr("a * b-6") # Should give 0 expr.set_output(r1) expr.eval() r1 = r1[-10:] # Get the last ten rows r2 = np.zeros(10, dtype='float64') if common.verbose: print("Tested shape:", shape) print("Ten last rows:", repr(r1), r1.dtype) print("Should look like:", repr(r2), r2.dtype) self.assertTrue(common.areArraysEqual(r1, r2), "Evaluate is returning a wrong value.") def test01_iter(self): """Checking very large inputs (__iter__ version)""" shape = self.shape if shape[0] >= 2**24: # The iterator is much more slower, so don't run it for # extremeley large arrays. if common.verbose: print("Skipping this *very* long test") return # Use filters so as to not use too much space if tb.which_lib_version("lzo") is not None: filters = tb.Filters(complevel=1, complib='lzo', shuffle=False) else: filters = tb.Filters(complevel=1, shuffle=False) # Build input arrays root = self.h5file.root a = self.h5file.create_carray(root, 'a', atom=tb.Int32Atom(dflt=1), shape=shape, filters=filters) self.assertIsNotNone(a) b = self.h5file.create_carray(root, 'b', atom=tb.Int32Atom(dflt=2), shape=shape, filters=filters) self.assertIsNotNone(b) r1 = self.h5file.create_carray(root, 'r1', atom=tb.Int32Atom(dflt=3), shape=shape, filters=filters) # The expression expr = tb.Expr("a-b + 1") r1 = sum(expr) # Should give 0 if common.verbose: print("Tested shape:", shape) print("Cummulated sum:", r1) print("Should look like:", 0) self.assertEqual(r1, 0, "Evaluate is returning a wrong value.") # The next can go on regular tests, as it should be light enough class VeryLargeInputs1(VeryLargeInputsTestCase): shape = (2**20,) # larger than any internal I/O buffers # The next is only meant for 'heavy' mode as it can take more than 1 minute # on modern machines class VeryLargeInputs2(VeryLargeInputsTestCase): shape = (2**32 + 1,) # check that arrays > 32-bit are supported def suite(): """Return a test suite consisting of all the test cases in the module.""" theSuite = common.unittest.TestSuite() niter = 1 # common.heavy = 1 # uncomment this only for testing purposes for i in range(niter): theSuite.addTest(common.unittest.makeSuite(ExprNumPy)) theSuite.addTest(common.unittest.makeSuite(ExprArray)) theSuite.addTest(common.unittest.makeSuite(ExprCArray)) theSuite.addTest(common.unittest.makeSuite(ExprEArray)) theSuite.addTest(common.unittest.makeSuite(ExprColumn)) theSuite.addTest(common.unittest.makeSuite(MixedContainers0)) theSuite.addTest(common.unittest.makeSuite(MixedContainers1)) theSuite.addTest(common.unittest.makeSuite(MixedContainers2)) theSuite.addTest(common.unittest.makeSuite(MixedContainers3)) theSuite.addTest(common.unittest.makeSuite(UnalignedObject)) theSuite.addTest(common.unittest.makeSuite(NonContiguousObject)) theSuite.addTest(common.unittest.makeSuite(ExprError)) theSuite.addTest(common.unittest.makeSuite(Broadcast0)) theSuite.addTest(common.unittest.makeSuite(Broadcast1)) theSuite.addTest(common.unittest.makeSuite(Broadcast2)) theSuite.addTest(common.unittest.makeSuite(Broadcast3)) theSuite.addTest(common.unittest.makeSuite(Broadcast4)) theSuite.addTest(common.unittest.makeSuite(Broadcast5)) theSuite.addTest(common.unittest.makeSuite(DiffLength0)) theSuite.addTest(common.unittest.makeSuite(DiffLength1)) theSuite.addTest(common.unittest.makeSuite(DiffLength2)) theSuite.addTest(common.unittest.makeSuite(DiffLength3)) theSuite.addTest(common.unittest.makeSuite(DiffLength4)) theSuite.addTest(common.unittest.makeSuite(TypesTestCase)) theSuite.addTest(common.unittest.makeSuite(FunctionsTestCase)) theSuite.addTest(common.unittest.makeSuite(Maindim0)) theSuite.addTest(common.unittest.makeSuite(Maindim1)) theSuite.addTest(common.unittest.makeSuite(Maindim2)) theSuite.addTest(common.unittest.makeSuite(Maindim3)) theSuite.addTest(common.unittest.makeSuite(AppendModeTrue)) theSuite.addTest(common.unittest.makeSuite(AppendModeFalse)) theSuite.addTest(common.unittest.makeSuite(iter0)) theSuite.addTest(common.unittest.makeSuite(iter1)) theSuite.addTest(common.unittest.makeSuite(iter2)) theSuite.addTest(common.unittest.makeSuite(iter3)) theSuite.addTest(common.unittest.makeSuite(iter4)) theSuite.addTest(common.unittest.makeSuite(iter5)) theSuite.addTest(common.unittest.makeSuite(setOutputRange0)) theSuite.addTest(common.unittest.makeSuite(setOutputRange1)) theSuite.addTest(common.unittest.makeSuite(setOutputRange2)) theSuite.addTest(common.unittest.makeSuite(setOutputRange3)) theSuite.addTest(common.unittest.makeSuite(setOutputRange4)) theSuite.addTest(common.unittest.makeSuite(setOutputRange5)) theSuite.addTest(common.unittest.makeSuite(setOutputRange6)) theSuite.addTest(common.unittest.makeSuite(setOutputRange7)) theSuite.addTest(common.unittest.makeSuite(setOutputRange8)) theSuite.addTest(common.unittest.makeSuite(setOutputRange9)) theSuite.addTest(common.unittest.makeSuite(VeryLargeInputs1)) if common.heavy: theSuite.addTest(common.unittest.makeSuite(VeryLargeInputs2)) return theSuite if __name__ == '__main__': import sys common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_garbage.py000066400000000000000000000027421416254111300206640ustar00rootroot00000000000000"""Test module for detecting uncollectable garbage in PyTables. This test module *must* be loaded in the last place. It just checks for the existence of uncollectable garbage in ``gc.garbage`` after running all the tests. """ import gc from tables.tests import common class GarbageTestCase(common.PyTablesTestCase): """Test for uncollectable garbage.""" def test00(self): """Checking for uncollectable garbage.""" garbageLen = len(gc.garbage) if garbageLen == 0: return # success if common.verbose: classCount = {} # Count uncollected objects for each class. for obj in gc.garbage: objClass = obj.__class__.__name__ if objClass in classCount: classCount[objClass] += 1 else: classCount[objClass] = 1 incidence = ['``%s``: %d' % (cls, cnt) for (cls, cnt) in classCount.items()] print("Class incidence:", ', '.join(incidence)) self.fail("Possible leak: %d uncollected objects." % garbageLen) def suite(): """Return a test suite consisting of all the test cases in the module.""" theSuite = common.unittest.TestSuite() theSuite.addTest(common.unittest.makeSuite(GarbageTestCase)) return theSuite if __name__ == '__main__': import sys common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_hdf5compat.py000066400000000000000000000322011416254111300213170ustar00rootroot00000000000000"""Test module for compatibility with plain HDF files.""" import shutil import tempfile from pathlib import Path import numpy as np import tables as tb from tables.tests import common class PaddedArrayTestCase(common.TestFileMixin, common.PyTablesTestCase): """Test for H5T_COMPOUND (Table) datatype with padding. Regression test for issue gh-734 itemsize.h5 was created with h5py with the array `expectedData` (see below) in the table `/Test`: 'A' and 'B' are 4 + 4 bytes, with 8 bytes padding. $ h5ls -v itemsize.h5 Test Dataset {3/3} Location: 1:800 Links: 1 Storage: 48 logical bytes, 48 allocated bytes, 100.00% utilization Type: struct { "A" +0 native unsigned int "B" +4 native unsigned int } 16 bytes """ h5fname = common.test_filename('itemsize.h5') def test(self): arr = self.h5file.get_node('/Test') data = arr.read() expectedData = np.array( [(1, 11), (2, 12), (3, 13)], dtype={'names': ['A', 'B'], 'formats': [' 2 table.row['var2'] = i % 2 table.row['var3'] = i table.row['var4'] = float(self.nrows - i - 1) table.row.append() table.flush() # Index all entries: for col in table.colinstances.values(): indexrows = col.create_index(_blocksizes=small_blocksizes) if common.verbose: print("Number of written rows:", self.nrows) print("Number of indexed rows:", indexrows) return def test00_flushLastRow(self): """Checking flushing an Index incrementing only the last row.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_flushLastRow..." % self.__class__.__name__) # Open the HDF5 file in append mode self.h5file = tb.open_file(self.h5fname, mode="a") table = self.h5file.root.table # Add just 3 rows more for i in range(3): table.row['var1'] = str(i).encode('ascii') table.row.append() table.flush() # redo the indexes idxcol = table.cols.var1.index if common.verbose: print("Max rows in buf:", table.nrowsinbuf) print("Number of elements per slice:", idxcol.slicesize) print("Chunk size:", idxcol.sorted.chunksize) print("Elements in last row:", idxcol.indicesLR[-1]) # Do a selection results = [p["var1"] for p in table.where('var1 == b"1"')] self.assertEqual(len(results), 2) self.assertEqual(results, [b'1']*2) def test00_update(self): """Checking automatic re-indexing after an update operation.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_update..." % self.__class__.__name__) # Open the HDF5 file in append mode self.h5file = tb.open_file(self.h5fname, mode="a") table = self.h5file.root.table # Modify a couple of columns for i, row in enumerate(table.where("(var3>1) & (var3<5)")): row['var1'] = str(i) row['var3'] = i row.update() table.flush() # redo the indexes idxcol1 = table.cols.var1.index idxcol3 = table.cols.var3.index if common.verbose: print("Dirtyness of var1 col:", idxcol1.dirty) print("Dirtyness of var3 col:", idxcol3.dirty) self.assertEqual(idxcol1.dirty, False) self.assertEqual(idxcol3.dirty, False) # Do a couple of selections results = [p["var1"] for p in table.where('var1 == b"1"')] self.assertEqual(len(results), 2) self.assertEqual(results, [b'1']*2) results = [p["var3"] for p in table.where('var3 == 0')] self.assertEqual(len(results), 2) self.assertEqual(results, [0]*2) def test01_readIndex(self): """Checking reading an Index (string flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_readIndex..." % self.__class__.__name__) # Open the HDF5 file in read-only mode self.h5file = tb.open_file(self.h5fname, mode="r") table = self.h5file.root.table idxcol = table.cols.var1.index if common.verbose: print("Max rows in buf:", table.nrowsinbuf) print("Number of elements per slice:", idxcol.slicesize) print("Chunk size:", idxcol.sorted.chunksize) # Do a selection results = [p["var1"] for p in table.where('var1 == b"1"')] self.assertEqual(len(results), 1) self.assertEqual(results, [b'1']) def test02_readIndex(self): """Checking reading an Index (bool flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_readIndex..." % self.__class__.__name__) # Open the HDF5 file in read-only mode self.h5file = tb.open_file(self.h5fname, mode="r") table = self.h5file.root.table idxcol = table.cols.var2.index if common.verbose: print("Rows in table:", table.nrows) print("Max rows in buf:", table.nrowsinbuf) print("Number of elements per slice:", idxcol.slicesize) print("Chunk size:", idxcol.sorted.chunksize) # Do a selection results = [p["var2"] for p in table.where('var2 == True')] if common.verbose: print("Selected values:", results) self.assertEqual(len(results), self.nrows // 2) self.assertEqual(results, [True]*(self.nrows // 2)) def test03_readIndex(self): """Checking reading an Index (int flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_readIndex..." % self.__class__.__name__) # Open the HDF5 file in read-only mode self.h5file = tb.open_file(self.h5fname, mode="r") table = self.h5file.root.table idxcol = table.cols.var3.index if common.verbose: print("Max rows in buf:", table.nrowsinbuf) print("Number of elements per slice:", idxcol.slicesize) print("Chunk size:", idxcol.sorted.chunksize) # Do a selection results = [p["var3"] for p in table.where('(1 500: tests.append(self.nrows - 500) for limit in tests: handle_a = [0, table.where('(var3 < e)', dict(e=limit))] handle_b = [0, table.where('(var3 < e)', dict(e=limit))] try: while True: next(handle_b[1]) handle_b[0] += 1 except StopIteration: for _ in handle_a[1]: handle_a[0] += 1 for _ in handle_b[1]: handle_b[0] += 1 self.assertEqual(handle_a[0], limit) self.assertEqual(handle_b[0], limit) self.assertEqual( len(list(table.where('(var3 < e)', dict(e=limit)))), limit) small_ss = small_blocksizes[2] class BasicReadTestCase(BasicTestCase): compress = 0 complib = "zlib" shuffle = 0 fletcher32 = 0 nrows = small_ss class ZlibReadTestCase(BasicTestCase): compress = 1 complib = "zlib" shuffle = 0 fletcher32 = 0 nrows = small_ss @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class BloscReadTestCase(BasicTestCase): compress = 1 complib = "blosc" shuffle = 0 fletcher32 = 0 nrows = small_ss @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class LZOReadTestCase(BasicTestCase): compress = 1 complib = "lzo" shuffle = 0 fletcher32 = 0 nrows = small_ss @common.unittest.skipIf(not common.bzip2_avail, 'BZIP2 compression library not available') class Bzip2ReadTestCase(BasicTestCase): compress = 1 complib = "bzip2" shuffle = 0 fletcher32 = 0 nrows = small_ss class ShuffleReadTestCase(BasicTestCase): compress = 1 complib = "zlib" shuffle = 1 fletcher32 = 0 nrows = small_ss class Fletcher32ReadTestCase(BasicTestCase): compress = 1 complib = "zlib" shuffle = 0 fletcher32 = 1 nrows = small_ss class ShuffleFletcher32ReadTestCase(BasicTestCase): compress = 1 complib = "zlib" shuffle = 1 fletcher32 = 1 nrows = small_ss class OneHalfTestCase(BasicTestCase): nrows = small_ss + small_ss//2 class UpperBoundTestCase(BasicTestCase): nrows = small_ss + 1 class LowerBoundTestCase(BasicTestCase): nrows = small_ss * 2-1 class DeepTableIndexTestCase(common.TempFileMixin, common.PyTablesTestCase): nrows = minRowIndex def test01(self): """Checking the indexing of a table in a 2nd level hierarchy""" # Create an instance of an HDF5 Table group = self.h5file.create_group(self.h5file.root, "agroup") # Create a table title = "This is the IndexArray title" table = self.h5file.create_table(group, 'table', TDescr, title, None, self.nrows) for i in range(self.nrows): # Fill rows with defaults table.row.append() table.flush() # Index some column indexrows = table.cols.var1.create_index() self.assertIsNotNone(indexrows) idxcol = table.cols.var1.index # Some sanity checks self.assertEqual(table.colindexed["var1"], 1) self.assertIsNotNone(idxcol) self.assertEqual(idxcol.nelements, self.nrows) def test01b(self): """Checking the indexing of a table in 2nd level (persistent version)""" # Create an instance of an HDF5 Table group = self.h5file.create_group(self.h5file.root, "agroup") # Create a table title = "This is the IndexArray title" table = self.h5file.create_table(group, 'table', TDescr, title, None, self.nrows) for i in range(self.nrows): # Fill rows with defaults table.row.append() table.flush() # Index some column indexrows = table.cols.var1.create_index() self.assertIsNotNone(indexrows) idxcol = table.cols.var1.index # Close and re-open this file self._reopen(mode='a') table = self.h5file.root.agroup.table idxcol = table.cols.var1.index # Some sanity checks self.assertEqual(table.colindexed["var1"], 1) self.assertIsNotNone(idxcol) self.assertEqual(idxcol.nelements, self.nrows) def test02(self): """Checking the indexing of a table in a 4th level hierarchy""" # Create an instance of an HDF5 Table group = self.h5file.create_group(self.h5file.root, "agroup") group = self.h5file.create_group(group, "agroup") group = self.h5file.create_group(group, "agroup") # Create a table title = "This is the IndexArray title" table = self.h5file.create_table(group, 'table', TDescr, title, None, self.nrows) for i in range(self.nrows): # Fill rows with defaults table.row.append() table.flush() # Index some column indexrows = table.cols.var1.create_index() self.assertIsNotNone(indexrows) idxcol = table.cols.var1.index # Some sanity checks self.assertEqual(table.colindexed["var1"], 1) self.assertIsNotNone(idxcol) self.assertEqual(idxcol.nelements, self.nrows) def test02b(self): """Checking the indexing of a table in a 4th level (persistent version)""" # Create an instance of an HDF5 Table group = self.h5file.create_group(self.h5file.root, "agroup") group = self.h5file.create_group(group, "agroup") group = self.h5file.create_group(group, "agroup") # Create a table title = "This is the IndexArray title" table = self.h5file.create_table(group, 'table', TDescr, title, None, self.nrows) for i in range(self.nrows): # Fill rows with defaults table.row.append() table.flush() # Index some column indexrows = table.cols.var1.create_index() self.assertIsNotNone(indexrows) idxcol = table.cols.var1.index # Close and re-open this file self._reopen(mode='a') table = self.h5file.root.agroup.agroup.agroup.table idxcol = table.cols.var1.index # Some sanity checks self.assertEqual(table.colindexed["var1"], 1) self.assertIsNotNone(idxcol) self.assertEqual(idxcol.nelements, self.nrows) def test03(self): """Checking the indexing of a table in a 100th level hierarchy""" # Create an instance of an HDF5 Table group = self.h5file.root for i in range(100): group = self.h5file.create_group(group, "agroup") # Create a table title = "This is the IndexArray title" table = self.h5file.create_table(group, 'table', TDescr, title, None, self.nrows) for i in range(self.nrows): # Fill rows with defaults table.row.append() table.flush() # Index some column indexrows = table.cols.var1.create_index() self.assertIsNotNone(indexrows) idxcol = table.cols.var1.index # Some sanity checks self.assertEqual(table.colindexed["var1"], 1) self.assertIsNotNone(idxcol) self.assertEqual(idxcol.nelements, self.nrows) class IndexProps: def __init__(self, auto=tb.index.default_auto_index, filters=tb.index.default_index_filters): self.auto = auto self.filters = filters DefaultProps = IndexProps() NoAutoProps = IndexProps(auto=False) ChangeFiltersProps = IndexProps( filters=tb.Filters(complevel=6, complib="zlib", shuffle=False, fletcher32=False)) class AutomaticIndexingTestCase(common.TempFileMixin, common.PyTablesTestCase): reopen = 1 iprops = NoAutoProps colsToIndex = ['var1', 'var2', 'var3'] small_blocksizes = (16, 8, 4, 2) def setUp(self): super().setUp() # Create an instance of an HDF5 Table title = "This is the IndexArray title" root = self.h5file.root # Make the chunkshape smaller or equal than small_blocksizes[-1] chunkshape = (2,) self.table = self.h5file.create_table(root, 'table', TDescr, title, None, self.nrows, chunkshape=chunkshape) self.table.autoindex = self.iprops.auto for colname in self.colsToIndex: self.table.colinstances[colname].create_index( _blocksizes=self.small_blocksizes) for i in range(self.nrows): # Fill rows with defaults self.table.row.append() self.table.flush() if self.reopen: self._reopen(mode='a') self.table = self.h5file.root.table def test01_attrs(self): """Checking indexing attributes (part1)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_attrs..." % self.__class__.__name__) table = self.table if self.iprops is DefaultProps: self.assertEqual(table.indexed, 0) else: self.assertEqual(table.indexed, 1) if self.iprops is DefaultProps: self.assertEqual(table.colindexed["var1"], 0) self.assertIsNone(table.cols.var1.index) self.assertEqual(table.colindexed["var2"], 0) self.assertIsNone(table.cols.var2.index) self.assertEqual(table.colindexed["var3"], 0) self.assertIsNone(table.cols.var3.index) self.assertEqual(table.colindexed["var4"], 0) self.assertIsNone(table.cols.var4.index) else: # Check that the var1, var2 and var3 (and only these) # has been indexed self.assertEqual(table.colindexed["var1"], 1) self.assertIsNotNone(table.cols.var1.index) self.assertEqual(table.colindexed["var2"], 1) self.assertIsNotNone(table.cols.var2.index) self.assertEqual(table.colindexed["var3"], 1) self.assertIsNotNone(table.cols.var3.index) self.assertEqual(table.colindexed["var4"], 0) self.assertIsNone(table.cols.var4.index) def test02_attrs(self): """Checking indexing attributes (part2)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_attrs..." % self.__class__.__name__) table = self.table # Check the policy parameters if common.verbose: if table.indexed: print("index props:", table.autoindex) else: print("Table is not indexed") # Check non-default values for index saving policy if self.iprops is NoAutoProps: self.assertFalse(table.autoindex) elif self.iprops is ChangeFiltersProps: self.assertTrue(table.autoindex) # Check Index() objects exists and are properly placed if self.iprops is DefaultProps: self.assertEqual(table.cols.var1.index, None) self.assertEqual(table.cols.var2.index, None) self.assertEqual(table.cols.var3.index, None) self.assertEqual(table.cols.var4.index, None) else: self.assertIsInstance(table.cols.var1.index, tb.index.Index) self.assertIsInstance(table.cols.var2.index, tb.index.Index) self.assertIsInstance(table.cols.var3.index, tb.index.Index) self.assertEqual(table.cols.var4.index, None) def test03_counters(self): """Checking indexing counters""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_counters..." % self.__class__.__name__) table = self.table # Check the counters for indexes if common.verbose: if table.indexed: print("indexedrows:", table._indexedrows) print("unsavedindexedrows:", table._unsaved_indexedrows) index = table.cols.var1.index print("table rows:", table.nrows) print("computed indexed rows:", index.nrows * index.slicesize) else: print("Table is not indexed") if self.iprops is not DefaultProps: index = table.cols.var1.index indexedrows = index.nelements self.assertEqual(table._indexedrows, indexedrows) indexedrows = index.nelements self.assertEqual(table._unsaved_indexedrows, self.nrows - indexedrows) def test04_noauto(self): """Checking indexing counters (non-automatic mode)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_noauto..." % self.__class__.__name__) table = self.table # Force a sync in indexes table.flush_rows_to_index() # Check the counters for indexes if common.verbose: if table.indexed: print("indexedrows:", table._indexedrows) print("unsavedindexedrows:", table._unsaved_indexedrows) index = table.cols.var1.index print("computed indexed rows:", index.nelements) else: print("Table is not indexed") # No unindexated rows should remain index = table.cols.var1.index if self.iprops is DefaultProps: self.assertIsNone(index) else: indexedrows = index.nelements self.assertEqual(table._indexedrows, index.nelements) self.assertEqual(table._unsaved_indexedrows, self.nrows - indexedrows) # Check non-default values for index saving policy if self.iprops is NoAutoProps: self.assertFalse(table.autoindex) elif self.iprops is ChangeFiltersProps: self.assertTrue(table.autoindex) def test05_icounters(self): """Checking indexing counters (remove_rows)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_icounters..." % self.__class__.__name__) table = self.table # Force a sync in indexes table.flush_rows_to_index() # Non indexated rows should remain here if self.iprops is not DefaultProps: indexedrows = table._indexedrows unsavedindexedrows = table._unsaved_indexedrows # Now, remove some rows: table.remove_rows(2, 4) if self.reopen: self._reopen(mode='a') table = self.h5file.root.table # Check the counters for indexes if common.verbose: if table.indexed: print("indexedrows:", table._indexedrows) print("original indexedrows:", indexedrows) print("unsavedindexedrows:", table._unsaved_indexedrows) print("original unsavedindexedrows:", unsavedindexedrows) # index = table.cols.var1.index print("index dirty:", table.cols.var1.index.dirty) else: print("Table is not indexed") # Check the counters self.assertEqual(table.nrows, self.nrows - 2) if self.iprops is NoAutoProps: self.assertTrue(table.cols.var1.index.dirty) # Check non-default values for index saving policy if self.iprops is NoAutoProps: self.assertFalse(table.autoindex) elif self.iprops is ChangeFiltersProps: self.assertTrue(table.autoindex) def test06_dirty(self): """Checking dirty flags (remove_rows action)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06_dirty..." % self.__class__.__name__) table = self.table # Force a sync in indexes table.flush_rows_to_index() # Now, remove some rows: table.remove_rows(3, 5) if self.reopen: self._reopen(mode='a') table = self.h5file.root.table # Check the dirty flag for indexes if common.verbose: print("auto flag:", table.autoindex) for colname in table.colnames: if table.cols._f_col(colname).index: print("dirty flag col %s: %s" % (colname, table.cols._f_col(colname).index.dirty)) # Check the flags for colname in table.colnames: if table.cols._f_col(colname).index: if not table.autoindex: self.assertEqual(table.cols._f_col(colname).index.dirty, True) else: self.assertEqual(table.cols._f_col(colname).index.dirty, False) def test07_noauto(self): """Checking indexing counters (modify_rows, no-auto mode)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test07_noauto..." % self.__class__.__name__) table = self.table # Force a sync in indexes table.flush_rows_to_index() # No unindexated rows should remain here if self.iprops is not DefaultProps: indexedrows = table._indexedrows unsavedindexedrows = table._unsaved_indexedrows # Now, modify just one row: table.modify_rows(3, None, 1, [("asa", 0, 3, 3.1)]) if self.reopen: self._reopen(mode='a') table = self.h5file.root.table # Check the counters for indexes if common.verbose: if table.indexed: print("indexedrows:", table._indexedrows) print("original indexedrows:", indexedrows) print("unsavedindexedrows:", table._unsaved_indexedrows) print("original unsavedindexedrows:", unsavedindexedrows) index = table.cols.var1.index print("computed indexed rows:", index.nelements) else: print("Table is not indexed") # Check the counters self.assertEqual(table.nrows, self.nrows) if self.iprops is NoAutoProps: self.assertTrue(table.cols.var1.index.dirty) # Check the dirty flag for indexes if common.verbose: for colname in table.colnames: if table.cols._f_col(colname).index: print("dirty flag col %s: %s" % (colname, table.cols._f_col(colname).index.dirty)) for colname in table.colnames: if table.cols._f_col(colname).index: if not table.autoindex: self.assertEqual(table.cols._f_col(colname).index.dirty, True) else: self.assertEqual(table.cols._f_col(colname).index.dirty, False) def test07b_noauto(self): """Checking indexing queries (modify in iterator, no-auto mode)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test07b_noauto..." % self.__class__.__name__) table = self.table # Force a sync in indexes table.flush_rows_to_index() # Do a query that uses indexes res = [row.nrow for row in table.where('(var2 == True) & (var3 > 0)')] # Now, modify just one row: for row in table: if row.nrow == 3: row['var1'] = "asa" row['var2'] = True row['var3'] = 3 row['var4'] = 3.1 row.update() table.flush() if self.reopen: self._reopen(mode='a') table = self.h5file.root.table # Do a query that uses indexes resq = [row.nrow for row in table.where('(var2 == True) & (var3 > 0)')] res_ = res + [3] if common.verbose: print("AutoIndex?:", table.autoindex) print("Query results (original):", res) print("Query results (after modifying table):", resq) print("Should look like:", res_) self.assertEqual(res_, resq) def test07c_noauto(self): """Checking indexing queries (append, no-auto mode)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test07c_noauto..." % self.__class__.__name__) table = self.table # Force a sync in indexes table.flush_rows_to_index() # Do a query that uses indexes res = [row.nrow for row in table.where('(var2 == True) & (var3 > 0)')] # Now, append three rows table.append([("asa", True, 1, 3.1)]) table.append([("asb", True, 2, 3.1)]) table.append([("asc", True, 3, 3.1)]) table.flush() if self.reopen: self._reopen(mode='a') table = self.h5file.root.table # Do a query that uses indexes resq = [row.nrow for row in table.where('(var2 == True) & (var3 > 0)')] res_ = res + [table.nrows-3, table.nrows-2, table.nrows-1] if common.verbose: print("AutoIndex?:", table.autoindex) print("Query results (original):", res) print("Query results (after modifying table):", resq) print("Should look like:", res_) self.assertEqual(res_, resq) def test08_dirty(self): """Checking dirty flags (modify_columns)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08_dirty..." % self.__class__.__name__) table = self.table # Force a sync in indexes table.flush_rows_to_index() # Non indexated rows should remain here if self.iprops is not DefaultProps: indexedrows = table._indexedrows self.assertIsNotNone(indexedrows) unsavedindexedrows = table._unsaved_indexedrows self.assertIsNotNone(unsavedindexedrows) # Now, modify a couple of rows: table.modify_columns(1, columns=[["asa", "asb"], [1., 2.]], names=["var1", "var4"]) if self.reopen: self._reopen(mode='a') table = self.h5file.root.table # Check the counters self.assertEqual(table.nrows, self.nrows) if self.iprops is NoAutoProps: self.assertTrue(table.cols.var1.index.dirty) # Check the dirty flag for indexes if common.verbose: for colname in table.colnames: if table.cols._f_col(colname).index: print("dirty flag col %s: %s" % (colname, table.cols._f_col(colname).index.dirty)) for colname in table.colnames: if table.cols._f_col(colname).index: if not table.autoindex: if colname in ["var1"]: self.assertEqual( table.cols._f_col(colname).index.dirty, True) else: self.assertEqual( table.cols._f_col(colname).index.dirty, False) else: self.assertEqual(table.cols._f_col(colname).index.dirty, False) def test09a_propIndex(self): """Checking propagate Index feature in Table.copy() (attrs)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09a_propIndex..." % self.__class__.__name__) table = self.table # Don't force a sync in indexes # table.flush_rows_to_index() # Non indexated rows should remain here if self.iprops is not DefaultProps: indexedrows = table._indexedrows self.assertIsNotNone(indexedrows) unsavedindexedrows = table._unsaved_indexedrows self.assertIsNotNone(unsavedindexedrows) # Now, remove some rows to make columns dirty # table.remove_rows(3,5) # Copy a Table to another location table2 = table.copy("/", 'table2', propindexes=True) if self.reopen: self._reopen(mode='a') table = self.h5file.root.table table2 = self.h5file.root.table2 index1 = table.cols.var1.index index2 = table2.cols.var1.index if common.verbose: print("Copied index:", index2) print("Original index:", index1) if index1: print("Elements in copied index:", index2.nelements) print("Elements in original index:", index1.nelements) # Check the counters self.assertEqual(table.nrows, table2.nrows) if table.indexed: self.assertTrue(table2.indexed) if self.iprops is DefaultProps: # No index: the index should not exist self.assertIsNone(index1) self.assertIsNone(index2) elif self.iprops is NoAutoProps: self.assertIsNotNone(index2) # Check the dirty flag for indexes if common.verbose: for colname in table2.colnames: if table2.cols._f_col(colname).index: print("dirty flag col %s: %s" % (colname, table2.cols._f_col(colname).index.dirty)) for colname in table2.colnames: if table2.cols._f_col(colname).index: self.assertEqual(table2.cols._f_col(colname).index.dirty, False) def test09b_propIndex(self): """Checking that propindexes=False works""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09b_propIndex..." % self.__class__.__name__) table = self.table # Don't force a sync in indexes # table.flush_rows_to_index() # Non indexated rows should remain here if self.iprops is not DefaultProps: indexedrows = table._indexedrows self.assertIsNotNone(indexedrows) unsavedindexedrows = table._unsaved_indexedrows self.assertIsNotNone(unsavedindexedrows) # Now, remove some rows to make columns dirty # table.remove_rows(3,5) # Copy a Table to another location table2 = table.copy("/", 'table2', propindexes=False) if self.reopen: self._reopen(mode='a') table = self.h5file.root.table table2 = self.h5file.root.table2 if common.verbose: print("autoindex?:", self.iprops.auto) print("Copied index indexed?:", table2.cols.var1.is_indexed) print("Original index indexed?:", table.cols.var1.is_indexed) if self.iprops is DefaultProps: # No index: the index should not exist self.assertFalse(table2.cols.var1.is_indexed) self.assertFalse(table.cols.var1.is_indexed) elif self.iprops is NoAutoProps: self.assertFalse(table2.cols.var1.is_indexed) self.assertTrue(table.cols.var1.is_indexed) def test10_propIndex(self): """Checking propagate Index feature in Table.copy() (values)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test10_propIndex..." % self.__class__.__name__) table = self.table # Don't force a sync in indexes # table.flush_rows_to_index() # Non indexated rows should remain here if self.iprops is not DefaultProps: indexedrows = table._indexedrows self.assertIsNotNone(indexedrows) unsavedindexedrows = table._unsaved_indexedrows self.assertIsNotNone(unsavedindexedrows) # Now, remove some rows to make columns dirty # table.remove_rows(3,5) # Copy a Table to another location table2 = table.copy("/", 'table2', propindexes=True) if self.reopen: self._reopen(mode='a') table = self.h5file.root.table table2 = self.h5file.root.table2 index1 = table.cols.var3.index index2 = table2.cols.var3.index if common.verbose: print("Copied index:", index2) print("Original index:", index1) if index1: print("Elements in copied index:", index2.nelements) print("Elements in original index:", index1.nelements) def test11_propIndex(self): """Checking propagate Index feature in Table.copy() (dirty flags)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test11_propIndex..." % self.__class__.__name__) table = self.table # Force a sync in indexes table.flush_rows_to_index() # Non indexated rows should remain here if self.iprops is not DefaultProps: indexedrows = table._indexedrows self.assertIsNotNone(indexedrows) unsavedindexedrows = table._unsaved_indexedrows self.assertIsNotNone(unsavedindexedrows) # Now, modify an indexed column and an unindexed one # to make the "var1" dirty table.modify_columns(1, columns=[["asa", "asb"], [1., 2.]], names=["var1", "var4"]) # Copy a Table to another location table2 = table.copy("/", 'table2', propindexes=True) if self.reopen: self._reopen(mode='a') table = self.h5file.root.table table2 = self.h5file.root.table2 index1 = table.cols.var1.index index2 = table2.cols.var1.index if common.verbose: print("Copied index:", index2) print("Original index:", index1) if index1: print("Elements in copied index:", index2.nelements) print("Elements in original index:", index1.nelements) # Check the dirty flag for indexes if common.verbose: for colname in table2.colnames: if table2.cols._f_col(colname).index: print("dirty flag col %s: %s" % (colname, table2.cols._f_col(colname).index.dirty)) for colname in table2.colnames: if table2.cols._f_col(colname).index: if table2.autoindex: # All the destination columns should be non-dirty because # the copy removes the dirty state and puts the # index in a sane state self.assertEqual(table2.cols._f_col(colname).index.dirty, False) # minRowIndex = 10000 # just if one wants more indexed rows to be checked class AI1TestCase(AutomaticIndexingTestCase): # nrows = 10002 nrows = 102 reopen = 0 iprops = NoAutoProps colsToIndex = ['var1', 'var2', 'var3'] class AI2TestCase(AutomaticIndexingTestCase): # nrows = 10002 nrows = 102 reopen = 1 iprops = NoAutoProps colsToIndex = ['var1', 'var2', 'var3'] class AI4bTestCase(AutomaticIndexingTestCase): # nrows = 10012 nrows = 112 reopen = 1 iprops = NoAutoProps colsToIndex = ['var1', 'var2', 'var3'] class AI5TestCase(AutomaticIndexingTestCase): sbs, bs, ss, cs = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) nrows = ss * 11-1 reopen = 0 iprops = NoAutoProps colsToIndex = ['var1', 'var2', 'var3'] class AI6TestCase(AutomaticIndexingTestCase): sbs, bs, ss, cs = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) nrows = ss * 21 + 1 reopen = 1 iprops = NoAutoProps colsToIndex = ['var1', 'var2', 'var3'] class AI7TestCase(AutomaticIndexingTestCase): sbs, bs, ss, cs = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) nrows = ss * 12-1 # nrows = ss * 1-1 # faster test reopen = 0 iprops = NoAutoProps colsToIndex = ['var1', 'var2', 'var3'] class AI8TestCase(AutomaticIndexingTestCase): sbs, bs, ss, cs = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) nrows = ss * 15 + 100 # nrows = ss * 1 + 100 # faster test reopen = 1 iprops = NoAutoProps colsToIndex = ['var1', 'var2', 'var3'] class AI9TestCase(AutomaticIndexingTestCase): sbs, bs, ss, cs = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) nrows = ss reopen = 0 iprops = DefaultProps colsToIndex = [] class AI10TestCase(AutomaticIndexingTestCase): # nrows = 10002 nrows = 102 reopen = 1 iprops = DefaultProps colsToIndex = [] class AI11TestCase(AutomaticIndexingTestCase): # nrows = 10002 nrows = 102 reopen = 0 iprops = ChangeFiltersProps colsToIndex = ['var1', 'var2', 'var3'] class AI12TestCase(AutomaticIndexingTestCase): # nrows = 10002 nrows = 102 reopen = 0 iprops = ChangeFiltersProps colsToIndex = ['var1', 'var2', 'var3'] class ManyNodesTestCase(common.TempFileMixin, common.PyTablesTestCase): opem_kwargs = dict(node_cache_slots=64) def test00(self): """Indexing many nodes in one single session (based on bug #26)""" IdxRecord = { 'f0': tb.Int8Col(), 'f1': tb.Int8Col(), 'f2': tb.Int8Col(), } for qn in range(5): for sn in range(5): qchr = 'chr' + str(qn) name = 'chr' + str(sn) path = "/at/%s/pt" % (qchr) table = self.h5file.create_table( path, name, IdxRecord, createparents=1) table.cols.f0.create_index() table.cols.f1.create_index() table.cols.f2.create_index() table.row.append() table.flush() class IndexPropsChangeTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test case for changing index properties in a table.""" class MyDescription(tb.IsDescription): icol = tb.IntCol() oldIndexProps = IndexProps() newIndexProps = IndexProps(auto=False, filters=tb.Filters(complevel=9)) def setUp(self): super().setUp() table = self.h5file.create_table('/', 'test', self.MyDescription) table.autoindex = self.oldIndexProps.auto row = table.row for i in range(100): row['icol'] = i % 25 row.append() table.flush() self.table = table def test_attributes(self): """Storing index properties as table attributes.""" for refprops in [self.oldIndexProps, self.newIndexProps]: self.assertEqual(self.table.autoindex, refprops.auto) self.table.autoindex = self.newIndexProps.auto def test_copyattrs(self): """Copying index properties attributes.""" oldtable = self.table newtable = oldtable.copy('/', 'test2') self.assertEqual(oldtable.autoindex, newtable.autoindex) class IndexFiltersTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test case for setting index filters.""" def setUp(self): super().setUp() description = {'icol': tb.IntCol()} self.table = self.h5file.create_table('/', 'test', description) def test_createIndex(self): """Checking input parameters in new indexes.""" # Different from default. argfilters = copy.copy(tb.index.default_index_filters) argfilters.shuffle = not tb.index.default_index_filters.shuffle # Different both from default and the previous one. idxfilters = copy.copy(tb.index.default_index_filters) idxfilters.shuffle = not tb.index.default_index_filters.shuffle idxfilters.fletcher32 = not tb.index.default_index_filters.fletcher32 icol = self.table.cols.icol # First create icol.create_index(kind='ultralight', optlevel=4) self.assertEqual(icol.index.kind, 'ultralight') self.assertEqual(icol.index.optlevel, 4) self.assertEqual(icol.index.filters, tb.index.default_index_filters) icol.remove_index() # Second create icol.create_index(kind='medium', optlevel=3, filters=argfilters) self.assertEqual(icol.index.kind, 'medium') self.assertEqual(icol.index.optlevel, 3) self.assertEqual(icol.index.filters, argfilters) icol.remove_index() def test_reindex(self): """Checking input parameters in recomputed indexes.""" icol = self.table.cols.icol icol.create_index( kind='full', optlevel=5, filters=tb.Filters(complevel=3)) kind = icol.index.kind optlevel = icol.index.optlevel filters = icol.index.filters icol.reindex() ni = icol.index if common.verbose: print(f"Old parameters: {kind}, {optlevel}, {filters}") print("New parameters: {}, {}, {}".format( ni.kind, ni.optlevel, ni.filters)) self.assertEqual(ni.kind, kind) self.assertEqual(ni.optlevel, optlevel) self.assertEqual(ni.filters, filters) class OldIndexTestCase(common.TestFileMixin, common.PyTablesTestCase): h5fname = common.test_filename("idx-std-1.x.h5") def test1_x(self): """Check that files with 1.x indexes are recognized and warned.""" self.assertWarns(tb.exceptions.OldIndexWarning, self.h5file.get_node, "/table") # Sensible parameters for indexing with small blocksizes small_blocksizes = (512, 128, 32, 8) class CompletelySortedIndexTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test case for testing a complete sort in a table.""" nrows = 100 nrowsinbuf = 11 class MyDescription(tb.IsDescription): rcol = tb.IntCol(pos=1) icol = tb.IntCol(pos=2) def setUp(self): super().setUp() table = self.h5file.create_table('/', 'table', self.MyDescription) row = table.row nrows = self.nrows for i in range(nrows): row['rcol'] = i row['icol'] = nrows - i row.append() table.flush() self.table = table self.icol = self.table.cols.icol # A full index with maximum optlevel should always be completely sorted self.icol.create_csindex(_blocksizes=small_blocksizes) def test00_isCompletelySortedIndex(self): """Testing the Column.is_csi property.""" icol = self.icol self.assertEqual(icol.index.is_csi, True) icol.remove_index() # Other kinds than full, should never return a CSI icol.create_index(kind="medium", optlevel=9) self.assertEqual(icol.index.is_csi, False) icol.remove_index() # As the table is small, lesser optlevels should be able to # create a completely sorted index too. icol.create_index(kind="full", optlevel=6) self.assertEqual(icol.index.is_csi, True) # Checking a CSI in a sorted copy self.table.copy("/", 'table2', sortby='icol', checkCSI=True) self.assertEqual(icol.index.is_csi, True) def test01_readSorted1(self): """Testing the Index.read_sorted() method with no arguments.""" icol = self.icol sortedcol = np.sort(icol[:]) sortedcol2 = icol.index.read_sorted() if common.verbose: print("Original sorted column:", sortedcol) print("The values from the index:", sortedcol2) self.assertTrue(common.allequal(sortedcol, sortedcol2)) def test01_readSorted2(self): """Testing the Index.read_sorted() method with arguments (I).""" icol = self.icol sortedcol = np.sort(icol[:])[30:55] sortedcol2 = icol.index.read_sorted(30, 55) if common.verbose: print("Original sorted column:", sortedcol) print("The values from the index:", sortedcol2) self.assertTrue(common.allequal(sortedcol, sortedcol2)) def test01_readSorted3(self): """Testing the Index.read_sorted() method with arguments (II).""" icol = self.icol sortedcol = np.sort(icol[:])[33:97] sortedcol2 = icol.index.read_sorted(33, 97) if common.verbose: print("Original sorted column:", sortedcol) print("The values from the index:", sortedcol2) self.assertTrue(common.allequal(sortedcol, sortedcol2)) def test02_readIndices1(self): """Testing the Index.read_indices() method with no arguments.""" icol = self.icol indicescol = np.argsort(icol[:]).astype('uint64') indicescol2 = icol.index.read_indices() if common.verbose: print("Original indices column:", indicescol) print("The values from the index:", indicescol2) self.assertTrue(common.allequal(indicescol, indicescol2)) def test02_readIndices2(self): """Testing the Index.read_indices() method with arguments (I).""" icol = self.icol indicescol = np.argsort(icol[:])[30:55].astype('uint64') indicescol2 = icol.index.read_indices(30, 55) if common.verbose: print("Original indices column:", indicescol) print("The values from the index:", indicescol2) self.assertTrue(common.allequal(indicescol, indicescol2)) def test02_readIndices3(self): """Testing the Index.read_indices() method with arguments (II).""" icol = self.icol indicescol = np.argsort(icol[:])[33:97].astype('uint64') indicescol2 = icol.index.read_indices(33, 97) if common.verbose: print("Original indices column:", indicescol) print("The values from the index:", indicescol2) self.assertTrue(common.allequal(indicescol, indicescol2)) def test02_readIndices4(self): """Testing the Index.read_indices() method with arguments (III).""" icol = self.icol indicescol = np.argsort(icol[:])[33:97:2].astype('uint64') indicescol2 = icol.index.read_indices(33, 97, 2) if common.verbose: print("Original indices column:", indicescol) print("The values from the index:", indicescol2) self.assertTrue(common.allequal(indicescol, indicescol2)) def test02_readIndices5(self): """Testing the Index.read_indices() method with arguments (IV).""" icol = self.icol indicescol = np.argsort(icol[:])[33:55:5].astype('uint64') indicescol2 = icol.index.read_indices(33, 55, 5) if common.verbose: print("Original indices column:", indicescol) print("The values from the index:", indicescol2) self.assertTrue(common.allequal(indicescol, indicescol2)) def test02_readIndices6(self): """Testing the Index.read_indices() method with step only.""" icol = self.icol indicescol = np.argsort(icol[:])[::3].astype('uint64') indicescol2 = icol.index.read_indices(step=3) if common.verbose: print("Original indices column:", indicescol) print("The values from the index:", indicescol2) self.assertTrue(common.allequal(indicescol, indicescol2)) def test03_getitem1(self): """Testing the Index.__getitem__() method with no arguments.""" icol = self.icol indicescol = np.argsort(icol[:]).astype('uint64') indicescol2 = icol.index[:] if common.verbose: print("Original indices column:", indicescol) print("The values from the index:", indicescol2) self.assertTrue(common.allequal(indicescol, indicescol2)) def test03_getitem2(self): """Testing the Index.__getitem__() method with start.""" icol = self.icol indicescol = np.argsort(icol[:])[31].astype('uint64') indicescol2 = icol.index[31] if common.verbose: print("Original indices column:", indicescol) print("The values from the index:", indicescol2) self.assertTrue(common.allequal(indicescol, indicescol2)) def test03_getitem3(self): """Testing the Index.__getitem__() method with start, stop.""" icol = self.icol indicescol = np.argsort(icol[:])[2:16].astype('uint64') indicescol2 = icol.index[2:16] if common.verbose: print("Original indices column:", indicescol) print("The values from the index:", indicescol2) self.assertTrue(common.allequal(indicescol, indicescol2)) def test04_itersorted1(self): """Testing the Table.itersorted() method with no arguments.""" table = self.table sortedtable = np.sort(table[:], order='icol') sortedtable2 = np.array( [row.fetch_all_fields() for row in table.itersorted( 'icol')], dtype=table._v_dtype) if common.verbose: print("Original sorted table:", sortedtable) print("The values from the iterator:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test04_itersorted2(self): """Testing the Table.itersorted() method with a start.""" table = self.table sortedtable = np.sort(table[:], order='icol')[15:] sortedtable2 = np.array( [row.fetch_all_fields() for row in table.itersorted( 'icol', start=15)], dtype=table._v_dtype) if common.verbose: print("Original sorted table:", sortedtable) print("The values from the iterator:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test04_itersorted3(self): """Testing the Table.itersorted() method with a stop.""" table = self.table sortedtable = np.sort(table[:], order='icol')[:20] sortedtable2 = np.array( [row.fetch_all_fields() for row in table.itersorted( 'icol', stop=20)], dtype=table._v_dtype) if common.verbose: print("Original sorted table:", sortedtable) print("The values from the iterator:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test04_itersorted4(self): """Testing the Table.itersorted() method with a start and stop.""" table = self.table sortedtable = np.sort(table[:], order='icol')[15:20] sortedtable2 = np.array( [row.fetch_all_fields() for row in table.itersorted( 'icol', start=15, stop=20)], dtype=table._v_dtype) if common.verbose: print("Original sorted table:", sortedtable) print("The values from the iterator:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test04_itersorted5(self): """Testing the Table.itersorted() method with a start, stop and step.""" table = self.table sortedtable = np.sort(table[:], order='icol')[15:45:4] sortedtable2 = np.array( [row.fetch_all_fields() for row in table.itersorted( 'icol', start=15, stop=45, step=4)], dtype=table._v_dtype) if common.verbose: print("Original sorted table:", sortedtable) print("The values from the iterator:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test04_itersorted6(self): """Testing the Table.itersorted() method with a start, stop and step.""" table = self.table sortedtable = np.sort(table[:], order='icol')[33:55:5] sortedtable2 = np.array( [row.fetch_all_fields() for row in table.itersorted( 'icol', start=33, stop=55, step=5)], dtype=table._v_dtype) if common.verbose: print("Original sorted table:", sortedtable) print("The values from the iterator:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test04_itersorted7(self): """Testing the Table.itersorted() method with checkCSI=True.""" table = self.table sortedtable = np.sort(table[:], order='icol') sortedtable2 = np.array( [row.fetch_all_fields() for row in table.itersorted( 'icol', checkCSI=True)], dtype=table._v_dtype) if common.verbose: print("Original sorted table:", sortedtable) print("The values from the iterator:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test04_itersorted8(self): """Testing the Table.itersorted() method with a start, stop and negative step.""" # see also gh-252 table = self.table sortedtable = np.sort(table[:], order='icol')[55:33:-5] sortedtable2 = np.array( [row.fetch_all_fields() for row in table.itersorted( 'icol', start=55, stop=33, step=-5)], dtype=table._v_dtype) if common.verbose: print("Original sorted table:", sortedtable) print("The values from the iterator:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test04_itersorted9(self): """Testing the Table.itersorted() method with a negative step -5.""" # see also gh-252 table = self.table sortedtable = np.sort(table[:], order='icol')[::-5] sortedtable2 = np.array( [row.fetch_all_fields() for row in table.itersorted( 'icol', step=-5)], dtype=table._v_dtype) if common.verbose: print("Original sorted table:", sortedtable) print("The values from the iterator:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test04_itersorted10(self): """Testing the Table.itersorted() method with a negative step -1.""" # see also gh-252 table = self.table sortedtable = np.sort(table[:], order='icol')[::-1] sortedtable2 = np.array( [row.fetch_all_fields() for row in table.itersorted( 'icol', step=-1)], dtype=table._v_dtype) if common.verbose: print("Original sorted table:", sortedtable) print("The values from the iterator:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test05_readSorted1(self): """Testing the Table.read_sorted() method with no arguments.""" table = self.table sortedtable = np.sort(table[:], order='icol') sortedtable2 = table.read_sorted('icol') if common.verbose: print("Original sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test05_readSorted2(self): """Testing the Table.read_sorted() method with a start.""" table = self.table sortedtable = np.sort(table[:], order='icol')[16:17] sortedtable2 = table.read_sorted('icol', start=16) if common.verbose: print("Original sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test05_readSorted3(self): """Testing the Table.read_sorted() method with a start and stop.""" table = self.table sortedtable = np.sort(table[:], order='icol')[16:33] sortedtable2 = table.read_sorted('icol', start=16, stop=33) if common.verbose: print("Original sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test05_readSorted4(self): """Testing the Table.read_sorted() method with a start, stop and step.""" table = self.table sortedtable = np.sort(table[:], order='icol')[33:55:5] sortedtable2 = table.read_sorted('icol', start=33, stop=55, step=5) if common.verbose: print("Original sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test05_readSorted5(self): """Testing the Table.read_sorted() method with only a step.""" table = self.table sortedtable = np.sort(table[:], order='icol')[::3] sortedtable2 = table.read_sorted('icol', step=3) if common.verbose: print("Original sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test05_readSorted6(self): """Testing the Table.read_sorted() method with negative step.""" table = self.table sortedtable = np.sort(table[:], order='icol')[::-1] sortedtable2 = table.read_sorted('icol', step=-1) if common.verbose: print("Original sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test05_readSorted7(self): """Testing the Table.read_sorted() method with negative step (II).""" table = self.table sortedtable = np.sort(table[:], order='icol')[::-2] sortedtable2 = table.read_sorted('icol', step=-2) if common.verbose: print("Original sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test05_readSorted8(self): """Testing the Table.read_sorted() method with negative step (III)).""" table = self.table sstart = 100-24-1 sstop = 100-54-1 sortedtable = np.sort(table[:], order='icol')[sstart:sstop:-1] sortedtable2 = table.read_sorted('icol', start=24, stop=54, step=-1) if common.verbose: print("Original sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test05_readSorted9(self): """Testing the Table.read_sorted() method with negative step (IV)).""" table = self.table sstart = 100-14-1 sstop = 100-54-1 sortedtable = np.sort(table[:], order='icol')[sstart:sstop:-3] sortedtable2 = table.read_sorted('icol', start=14, stop=54, step=-3) if common.verbose: print("Original sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test05_readSorted10(self): """Testing the Table.read_sorted() method with negative step (V)).""" table = self.table sstart = 100-24-1 sstop = 100-25-1 sortedtable = np.sort(table[:], order='icol')[sstart:sstop:-2] sortedtable2 = table.read_sorted('icol', start=24, stop=25, step=-2) if common.verbose: print("Original sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test05_readSorted11(self): """Testing the Table.read_sorted() method with start > stop.""" table = self.table sstart = 100-137-1 sstop = 100-25-1 sortedtable = np.sort(table[:], order='icol')[sstart:sstop:-2] sortedtable2 = table.read_sorted('icol', start=137, stop=25, step=-2) if common.verbose: print("Original sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test05a_readSorted12(self): """Testing the Table.read_sorted() method with checkCSI (I).""" table = self.table sortedtable = np.sort(table[:], order='icol') sortedtable2 = table.read_sorted('icol', checkCSI=True) if common.verbose: print("Original sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test05b_readSorted12(self): """Testing the Table.read_sorted() method with checkCSI (II).""" table = self.table self.assertRaises(ValueError, table.read_sorted, "rcol", checkCSI=False) def test06_copy_sorted1(self): """Testing the Table.copy(sortby) method with no arguments.""" table = self.table # Copy to another table table.nrowsinbuf = self.nrowsinbuf table2 = table.copy("/", 'table2', sortby="icol") sortedtable = np.sort(table[:], order='icol') sortedtable2 = table2[:] if common.verbose: print("Original sorted table:", sortedtable) print("The values from copy:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test06_copy_sorted2(self): """Testing the Table.copy(sortby) method with step=-1.""" table = self.table # Copy to another table table.nrowsinbuf = self.nrowsinbuf table2 = table.copy("/", 'table2', sortby="icol", step=-1) sortedtable = np.sort(table[:], order='icol')[::-1] sortedtable2 = table2[:] if common.verbose: print("Original sorted table:", sortedtable) print("The values from copy:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test06_copy_sorted3(self): """Testing the Table.copy(sortby) method with only a start.""" table = self.table # Copy to another table table.nrowsinbuf = self.nrowsinbuf table2 = table.copy("/", 'table2', sortby="icol", start=3) sortedtable = np.sort(table[:], order='icol')[3:4] sortedtable2 = table2[:] if common.verbose: print("Original sorted table:", sortedtable) print("The values from copy:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test06_copy_sorted4(self): """Testing the Table.copy(sortby) method with start, stop.""" table = self.table # Copy to another table table.nrowsinbuf = self.nrowsinbuf table2 = table.copy("/", 'table2', sortby="icol", start=3, stop=40) sortedtable = np.sort(table[:], order='icol')[3:40] sortedtable2 = table2[:] if common.verbose: print("Original sorted table:", sortedtable) print("The values from copy:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test06_copy_sorted5(self): """Testing the Table.copy(sortby) method with start, stop, step.""" table = self.table # Copy to another table table.nrowsinbuf = self.nrowsinbuf table2 = table.copy("/", 'table2', sortby="icol", start=3, stop=33, step=5) sortedtable = np.sort(table[:], order='icol')[3:33:5] sortedtable2 = table2[:] if common.verbose: print("Original sorted table:", sortedtable) print("The values from copy:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test06_copy_sorted6(self): """Testing the Table.copy(sortby) method after table re-opening.""" self._reopen(mode='a') table = self.h5file.root.table # Copy to another table table.nrowsinbuf = self.nrowsinbuf table2 = table.copy("/", 'table2', sortby="icol") sortedtable = np.sort(table[:], order='icol') sortedtable2 = table2[:] if common.verbose: print("Original sorted table:", sortedtable) print("The values from copy:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test06_copy_sorted7(self): """Testing the `checkCSI` parameter of Table.copy() (I).""" table = self.table # Copy to another table table.nrowsinbuf = self.nrowsinbuf table2 = table.copy("/", 'table2', sortby="icol") self.assertRaises(ValueError, table2.copy, "/", 'table3', sortby="rcol", checkCSI=False) def test06_copy_sorted8(self): """Testing the `checkCSI` parameter of Table.copy() (II).""" table = self.table # Copy to another table table.nrowsinbuf = self.nrowsinbuf table2 = table.copy("/", 'table2', sortby="icol") self.assertRaises(ValueError, table2.copy, "/", 'table3', sortby="rcol", checkCSI=True) def test07_isCSI_noelements(self): """Testing the representation of an index with no elements.""" t2 = self.h5file.create_table('/', 't2', self.MyDescription) irows = t2.cols.rcol.create_csindex() if common.verbose: print("repr(t2)-->\n", repr(t2)) self.assertEqual(irows, 0) self.assertEqual(t2.colindexes['rcol'].is_csi, False) class ReadSortedIndexTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test case for testing sorted reading in a "full" sorted column.""" nrows = 100 nrowsinbuf = 11 class MyDescription(tb.IsDescription): rcol = tb.IntCol(pos=1) icol = tb.IntCol(pos=2) def setUp(self): super().setUp() table = self.h5file.create_table('/', 'table', self.MyDescription) row = table.row nrows = self.nrows for i in range(nrows): row['rcol'] = i row['icol'] = nrows - i row.append() table.flush() self.table = table self.icol = self.table.cols.icol # A full index with maximum optlevel should always be completely sorted self.icol.create_index(optlevel=self.optlevel, kind="full", _blocksizes=small_blocksizes) def test01_readSorted1(self): """Testing the Table.read_sorted() method with no arguments.""" table = self.table sortedtable = np.sort(table[:], order='icol') sortedtable2 = table.read_sorted('icol') if common.verbose: print("Sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) # Compare with the sorted read table because we have no # guarantees that read_sorted returns a completely sorted table self.assertTrue(common.allequal( sortedtable, np.sort(sortedtable2, order="icol"))) def test01_readSorted2(self): """Testing the Table.read_sorted() method with no arguments (re-open).""" self._reopen() table = self.h5file.root.table sortedtable = np.sort(table[:], order='icol') sortedtable2 = table.read_sorted('icol') if common.verbose: print("Sorted table:", sortedtable) print("The values from read_sorted:", sortedtable2) # Compare with the sorted read table because we have no # guarantees that read_sorted returns a completely sorted table self.assertTrue(common.allequal( sortedtable, np.sort(sortedtable2, order="icol"))) def test02_copy_sorted1(self): """Testing the Table.copy(sortby) method.""" table = self.table # Copy to another table table.nrowsinbuf = self.nrowsinbuf table2 = table.copy("/", 'table2', sortby="icol") sortedtable = np.sort(table[:], order='icol') sortedtable2 = np.sort(table2[:], order='icol') if common.verbose: print("Original table:", table2[:]) print("The sorted values from copy:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) def test02_copy_sorted2(self): """Testing the Table.copy(sortby) method after table re-opening.""" self._reopen(mode='a') table = self.h5file.root.table # Copy to another table table.nrowsinbuf = self.nrowsinbuf table2 = table.copy("/", 'table2', sortby="icol") sortedtable = np.sort(table[:], order='icol') sortedtable2 = np.sort(table2[:], order='icol') if common.verbose: print("Original table:", table2[:]) print("The sorted values from copy:", sortedtable2) self.assertTrue(common.allequal(sortedtable, sortedtable2)) class ReadSortedIndex0(ReadSortedIndexTestCase): optlevel = 0 class ReadSortedIndex3(ReadSortedIndexTestCase): optlevel = 3 class ReadSortedIndex6(ReadSortedIndexTestCase): optlevel = 6 class ReadSortedIndex9(ReadSortedIndexTestCase): optlevel = 9 class Issue156TestBase(common.TempFileMixin, common.PyTablesTestCase): # field name in table according to which test_copysort() sorts the table sort_field = None def setUp(self): super().setUp() # create nested table class Foo(tb.IsDescription): frame = tb.UInt16Col() class Bar(tb.IsDescription): code = tb.UInt16Col() table = self.h5file.create_table('/', 'foo', Foo, filters=tb.Filters(3, 'zlib'), createparents=True) self.h5file.flush() # fill table with 10 random numbers for k in range(10): row = table.row row['frame'] = np.random.randint(0, 2**16-1) row['Bar/code'] = np.random.randint(0, 2**16-1) row.append() self.h5file.flush() def test_copysort(self): # copy table oldNode = self.h5file.get_node('/foo') # create completely sorted index on a main column oldNode.colinstances[self.sort_field].create_csindex() # this fails on ade2ba123efd267fd31 # see gh-156 new_node = oldNode.copy(newname='foo2', overwrite=True, sortby=self.sort_field, checkCSI=True, propindexes=True) # check column is sorted self.assertTrue(np.all( new_node.col(self.sort_field) == sorted(oldNode.col(self.sort_field)))) # check index is available self.assertIn(self.sort_field, new_node.colindexes) # check CSI was propagated self.assertTrue(new_node.colindexes[self.sort_field].is_csi) class Issue156TestCase01(Issue156TestBase): # sort by field from non nested entry sort_field = 'frame' class Issue156TestCase02(Issue156TestBase): # sort by field from nested entry sort_field = 'Bar/code' class Issue119Time32ColTestCase(common.TempFileMixin, common.PyTablesTestCase): """TimeCol not properly indexing.""" col_typ = tb.Time32Col values = [ 0.93240451618785880, 0.76322375510776170, 0.16695030056300875, 0.91259117097807850, 0.93977847053454630, 0.51450406513503090, 0.24452129962257563, 0.85475938924825230, 0.32512326762476930, 0.75127635627046820, ] def setUp(self): super().setUp() class Descr(tb.IsDescription): when = self.col_typ(pos=1) value = tb.Float32Col(pos=2) self.table = self.h5file.create_table('/', 'test', Descr) self.t = 1321031471.0 # 11/11/11 11:11:11 data = [(self.t + i, item) for i, item in enumerate(self.values)] self.table.append(data) self.h5file.flush() def test_timecol_issue(self): tbl = self.table t = self.t wherestr = '(when >= %d) & (when < %d)' % (t, t + 5) no_index = tbl.read_where(wherestr) tbl.cols.when.create_index(_verbose=False) with_index = tbl.read_where(wherestr) self.assertTrue((no_index == with_index).all()) class Issue119Time64ColTestCase(Issue119Time32ColTestCase): col_typ = tb.Time64Col class TestIndexingNans(common.TempFileMixin, common.PyTablesTestCase): def test_issue_282(self): trMap = {'index': tb.Int64Col(), 'values': tb.FloatCol()} table = self.h5file.create_table('/', 'table', trMap) r = table.row for i in range(5): r['index'] = i r['values'] = np.nan if i == 0 else i r.append() table.flush() table.cols.values.create_index() # retrieve result = table.read_where('(values >= 0)') self.assertEqual(len(result), 4) def test_issue_327(self): table = self.h5file.create_table('/', 'table', dict( index=tb.Int64Col(), values=tb.FloatCol(shape=()), values2=tb.FloatCol(shape=()), )) r = table.row for i in range(5): r['index'] = i r['values'] = np.nan if i == 2 or i == 3 else i r['values2'] = i r.append() table.flush() table.cols.values.create_index() table.cols.values2.create_index() results2 = table.read_where('(values2 > 0)') self.assertEqual(len(results2), 4) results = table.read_where('(values > 0)') self.assertEqual(len(results), 2) def test_issue_327_b(self): table = self.h5file.create_table('/', 'table', dict( index=tb.Int64Col(), values=tb.FloatCol(shape=()), values2=tb.FloatCol(shape=()), )) r = table.row for _ in range(100): for i in range(5): r['index'] = i r['values'] = np.nan if i == 2 or i == 3 else i r['values2'] = i r.append() table.flush() table.cols.values.create_index(_blocksizes=small_blocksizes) table.cols.values2.create_index(_blocksizes=small_blocksizes) results2 = table.read_where('(values2 > 0)') self.assertEqual(len(results2), 400) results = table.read_where('(values > 0)') self.assertEqual(len(results), 200) def test_csindex_nans(self): table = self.h5file.create_table('/', 'table', dict( index=tb.Int64Col(), values=tb.FloatCol(shape=()), values2=tb.FloatCol(shape=()), )) r = table.row for x in range(100): for i in range(5): r['index'] = i r['values'] = np.nan if i == 2 or i == 3 else i r['values2'] = i r.append() table.flush() table.cols.values.create_csindex(_blocksizes=small_blocksizes) table.cols.values2.create_csindex(_blocksizes=small_blocksizes) results2 = table.read_where('(values2 > 0)') self.assertEqual(len(results2), 100*4) results = table.read_where('(values > 0)') self.assertEqual(len(results), 100*2) def suite(): theSuite = common.unittest.TestSuite() niter = 1 # heavy = 1 # Uncomment this only for testing purposes! for n in range(niter): theSuite.addTest(common.unittest.makeSuite(BasicReadTestCase)) theSuite.addTest(common.unittest.makeSuite(ZlibReadTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscReadTestCase)) theSuite.addTest(common.unittest.makeSuite(LZOReadTestCase)) theSuite.addTest(common.unittest.makeSuite(Bzip2ReadTestCase)) theSuite.addTest(common.unittest.makeSuite(ShuffleReadTestCase)) theSuite.addTest(common.unittest.makeSuite(Fletcher32ReadTestCase)) theSuite.addTest( common.unittest.makeSuite(ShuffleFletcher32ReadTestCase)) theSuite.addTest(common.unittest.makeSuite(OneHalfTestCase)) theSuite.addTest(common.unittest.makeSuite(UpperBoundTestCase)) theSuite.addTest(common.unittest.makeSuite(LowerBoundTestCase)) theSuite.addTest(common.unittest.makeSuite(AI1TestCase)) theSuite.addTest(common.unittest.makeSuite(AI2TestCase)) theSuite.addTest(common.unittest.makeSuite(AI9TestCase)) theSuite.addTest(common.unittest.makeSuite(DeepTableIndexTestCase)) theSuite.addTest(common.unittest.makeSuite(IndexPropsChangeTestCase)) theSuite.addTest(common.unittest.makeSuite(IndexFiltersTestCase)) theSuite.addTest(common.unittest.makeSuite(OldIndexTestCase)) theSuite.addTest( common.unittest.makeSuite(CompletelySortedIndexTestCase)) theSuite.addTest(common.unittest.makeSuite(ManyNodesTestCase)) theSuite.addTest(common.unittest.makeSuite(ReadSortedIndex0)) theSuite.addTest(common.unittest.makeSuite(ReadSortedIndex3)) theSuite.addTest(common.unittest.makeSuite(ReadSortedIndex6)) theSuite.addTest(common.unittest.makeSuite(ReadSortedIndex9)) theSuite.addTest(common.unittest.makeSuite(Issue156TestCase01)) theSuite.addTest(common.unittest.makeSuite(Issue156TestCase02)) theSuite.addTest(common.unittest.makeSuite(Issue119Time32ColTestCase)) theSuite.addTest(common.unittest.makeSuite(Issue119Time64ColTestCase)) theSuite.addTest(common.unittest.makeSuite(TestIndexingNans)) if common.heavy: # These are too heavy for normal testing theSuite.addTest(common.unittest.makeSuite(AI4bTestCase)) theSuite.addTest(common.unittest.makeSuite(AI5TestCase)) theSuite.addTest(common.unittest.makeSuite(AI6TestCase)) theSuite.addTest(common.unittest.makeSuite(AI7TestCase)) theSuite.addTest(common.unittest.makeSuite(AI8TestCase)) theSuite.addTest(common.unittest.makeSuite(AI10TestCase)) theSuite.addTest(common.unittest.makeSuite(AI11TestCase)) theSuite.addTest(common.unittest.makeSuite(AI12TestCase)) return theSuite if __name__ == '__main__': import sys common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_indexvalues.py000066400000000000000000003564351416254111300216360ustar00rootroot00000000000000import random import tempfile from pathlib import Path import numpy as np import tables as tb from tables.tests import common # An alias for frozenset fzset = frozenset # To make the tests values reproductibles random.seed(19) # Sensible parameters for indexing with small blocksizes small_blocksizes = (16, 8, 4, 2) # The smaller set of parameters... # The size for medium indexes minRowIndex = 1000 class Small(tb.IsDescription): var1 = tb.StringCol(itemsize=4, dflt=b"") var2 = tb.BoolCol(dflt=0) var3 = tb.IntCol(dflt=0) var4 = tb.FloatCol(dflt=0) class SelectValuesTestCase(common.TempFileMixin, common.PyTablesTestCase): compress = 1 complib = "zlib" shuffle = 1 fletcher32 = 0 chunkshape = 10 buffersize = 0 random = 0 values = None reopen = False def setUp(self): super().setUp() # Create an instance of an HDF5 Table if common.verbose: print("Checking index kind-->", self.kind) self.rootgroup = self.h5file.root self.populateFile() def populateFile(self): # Set a seed for the random generator if needed. # This is useful when one need reproductible results. if self.random and hasattr(self, "seed"): random.seed(self.seed) group = self.rootgroup # Create an table title = "This is the IndexArray title" filters = tb.Filters(complevel=self.compress, complib=self.complib, shuffle=self.shuffle, fletcher32=self.fletcher32) table1 = self.h5file.create_table(group, 'table1', Small, title, filters, self.nrows, chunkshape=(self.chunkshape,)) table2 = self.h5file.create_table(group, 'table2', Small, title, filters, self.nrows, chunkshape=(self.chunkshape,)) count = 0 for i in range(0, self.nrows, self.nrep): for j in range(self.nrep): if self.random: k = random.randrange(self.nrows) elif self.values is not None: lenvalues = len(self.values) if i >= lenvalues: i %= lenvalues k = self.values[i] else: k = i bk = str(k).encode('ascii') table1.row['var1'] = bk table2.row['var1'] = bk table1.row['var2'] = k % 2 table2.row['var2'] = k % 2 table1.row['var3'] = k table2.row['var3'] = k table1.row['var4'] = float(self.nrows - k - 1) table2.row['var4'] = float(self.nrows - k - 1) table1.row.append() table2.row.append() count += 1 table1.flush() table2.flush() if self.buffersize: # Change the buffersize by default table1.nrowsinbuf = self.buffersize # Make sure nrowsinbuf is a multiple of chunkshape table1.nrowsinbuf -= table1.nrowsinbuf % self.chunkshape # Index all entries: for col in table1.colinstances.values(): indexrows = col.create_index( kind=self.kind, _blocksizes=self.blocksizes) if common.verbose: print("Number of written rows:", table1.nrows) print("Number of indexed rows:", indexrows) if self.reopen: self._reopen(mode='a') # flavor changes self.table1 = self.h5file.root.table1 self.table2 = self.h5file.root.table1 def test01a(self): """Checking selecting values from an Index (string flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do some selections and check the results # First selection t1var1 = table1.cols.var1 results1 = [p["var1"] for p in table1.where('(il<=t1var1)&(t1var1<=sl)')] results2 = [p["var1"] for p in table2 if il <= p["var1"] <= sl] results1.sort() results2.sort() if common.verbose: print("Should look like:", results2) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Second selection t1var1 = table1.cols.var1 results1 = [p["var1"] for p in table1.where('(il<=t1var1)&(t1var1 sl')] results2 = [p["var1"] for p in table2 if p["var1"] > sl] results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Fourth selection t1var1 = table1.cols.var1 self.assertIsNotNone(t1var1) results1 = [p["var1"] for p in table1.where('t1var1 >= sl')] results2 = [p["var1"] for p in table2 if p["var1"] >= sl] results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test02a(self): """Checking selecting values from an Index (bool flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02a..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Do some selections and check the results t1var2 = table1.cols.var2 self.assertIsNotNone(t1var2) results1 = [p["var2"] for p in table1.where('t1var2 == True')] results2 = [p["var2"] for p in table2 if p["var2"] is True] if common.verbose: print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test02b(self): """Checking selecting values from an Index (bool flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02b..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Do some selections and check the results t1var2 = table1.cols.var2 self.assertIsNotNone(t1var2) results1 = [p["var2"] for p in table1.where('t1var2 == False')] results2 = [p["var2"] for p in table2 if p["var2"] is False] if common.verbose: print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test03a(self): """Checking selecting values from an Index (int flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03a..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = int(self.il) sl = int(self.sl) # Do some selections and check the results t1col = table1.cols.var3 self.assertIsNotNone(t1col) # First selection results1 = [p["var3"] for p in table1.where('(il<=t1col)&(t1col<=sl)')] results2 = [p["var3"] for p in table2 if il <= p["var3"] <= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Second selection results1 = [p["var3"] for p in table1.where('(il<=t1col)&(t1col sl')] results2 = [p["var3"] for p in table2 if p["var3"] > sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Fourth selection results1 = [p["var3"] for p in table1.where('t1col >= sl')] results2 = [p["var3"] for p in table2 if p["var3"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test03c(self): """Checking selecting values from an Index (long flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03c..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type # il = long(self.il) sl = int(self.sl) # Do some selections and check the results t1col = table1.cols.var3 self.assertIsNotNone(t1col) # First selection results1 = [p["var3"] for p in table1.where('t1col < sl')] results2 = [p["var3"] for p in table2 if p["var3"] < sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Second selection results1 = [p["var3"] for p in table1.where('t1col <= sl')] results2 = [p["var3"] for p in table2 if p["var3"] <= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Third selection results1 = [p["var3"] for p in table1.where('t1col > sl')] results2 = [p["var3"] for p in table2 if p["var3"] > sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Fourth selection results1 = [p["var3"] for p in table1.where('t1col >= sl')] results2 = [p["var3"] for p in table2 if p["var3"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test03d(self): """Checking selecting values from an Index (long and int flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03d..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type # il = int(self.il) sl = int(self.sl) # Do some selections and check the results t1col = table1.cols.var3 self.assertIsNotNone(t1col) # First selection results1 = [p["var3"] for p in table1.where('t1col < sl')] results2 = [p["var3"] for p in table2 if p["var3"] < sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Second selection results1 = [p["var3"] for p in table1.where('t1col <= sl')] results2 = [p["var3"] for p in table2 if p["var3"] <= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Third selection results1 = [p["var3"] for p in table1.where('t1col > sl')] results2 = [p["var3"] for p in table2 if p["var3"] > sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Fourth selection results1 = [p["var3"] for p in table1.where('t1col >= sl')] results2 = [p["var3"] for p in table2 if p["var3"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test04a(self): """Checking selecting values from an Index (float flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04a..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = float(self.il) sl = float(self.sl) # Do some selections and check the results t1col = table1.cols.var4 self.assertIsNotNone(t1col) # First selection results1 = [p["var4"] for p in table1.where('(il<=t1col)&(t1col<=sl)')] results2 = [p["var4"] for p in table2 if il <= p["var4"] <= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1.sort(), results2.sort()) # Second selection results1 = [p["var4"] for p in table1.where('(il<=t1col)&(t1col sl')] results2 = [p["var4"] for p in table2 if p["var4"] > sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Fourth selection results1 = [p["var4"] for p in table1.where('t1col >= sl')] results2 = [p["var4"] for p in table2 if p["var4"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test05a(self): """Checking get_where_list & itersequence (string, python flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05a..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do some selections and check the results t1col = table1.cols.var1 # First selection condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) table1.flavor = "python" rowList1 = table1.get_where_list(condition) results1 = [p['var1'] for p in table1.itersequence(rowList1)] results2 = [p["var1"] for p in table2 if il <= p["var1"] <= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1.sort(), results2.sort()) # Second selection condition = '(il<=t1col)&(t1col sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Fourth selection condition = 't1col>=sl' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) rowList1 = table1.get_where_list(condition) results1 = [p['var1'] for p in table1.itersequence(rowList1)] results2 = [p["var1"] for p in table2 if p["var1"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test06a(self): """Checking get_where_list & itersequence (bool flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06a..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Do some selections and check the results t1var2 = table1.cols.var2 condition = 't1var2==True' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1var2.pathname])) table1.flavor = "python" rowList1 = table1.get_where_list(condition) results1 = [p['var2'] for p in table1.itersequence(rowList1)] results2 = [p["var2"] for p in table2 if p["var2"] is True] if common.verbose: print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test06b(self): """Checking get_where_list & itersequence (numpy bool limits & flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06b..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Do some selections and check the results t1var2 = table1.cols.var2 false = np.bool_(False) self.assertFalse(false) # silence pyflakes condition = 't1var2==false' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1var2.pathname])) table1.flavor = "python" rowList1 = table1.get_where_list(condition) results1 = [p['var2'] for p in table1.itersequence(rowList1)] results2 = [p["var2"] for p in table2 if p["var2"] is False] if common.verbose: print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test07a(self): """Checking get_where_list & itersequence (int flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test07a..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = int(self.il) sl = int(self.sl) # Do some selections and check the results t1col = table1.cols.var3 # First selection condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) table1.flavor = "python" rowList1 = table1.get_where_list(condition) results1 = [p['var3'] for p in table1.itersequence(rowList1)] results2 = [p["var3"] for p in table2 if il <= p["var3"] <= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1.sort(), results2.sort()) # Second selection condition = '(il<=t1col)&(t1col sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Fourth selection condition = 't1col>=sl' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) rowList1 = table1.get_where_list(condition) results1 = [p['var3'] for p in table1.itersequence(rowList1)] results2 = [p["var3"] for p in table2 if p["var3"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test08a(self): """Checking get_where_list & itersequence (float flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08a..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = float(self.il) sl = float(self.sl) # Do some selections and check the results t1col = table1.cols.var4 # First selection condition = '(il<=t1col)&(t1col<=sl)' # results1 = [p["var4"] for p in table1.where(condition)] self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) table1.flavor = "python" rowList1 = table1.get_where_list(condition) results1 = [p['var4'] for p in table1.itersequence(rowList1)] results2 = [p["var4"] for p in table2 if il <= p["var4"] <= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1.sort(), results2.sort()) # Second selection condition = '(il<=t1col)&(t1col sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Fourth selection condition = 't1col>=sl' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) rowList1 = table1.get_where_list(condition) results1 = [p['var4'] for p in table1.itersequence(rowList1)] results2 = [p["var4"] for p in table2 if p["var4"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test09a(self): """Checking non-indexed where() (string flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09a..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 table1._disable_indexing_in_queries() # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do some selections and check the results t1col = table1.cols.var1 self.assertIsNotNone(t1col) # First selection condition = 't1col<=sl' self.assertTrue(not table1.will_query_use_indexing(condition)) results1 = [p['var1'] for p in table1.where( condition, start=2, stop=10)] results2 = [p["var1"] for p in table2.iterrows(2, 10) if p["var1"] <= sl] if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Second selection condition = '(il p["var1"] > sl) ] if common.verbose: print("Limits:", il, sl) print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # This selection to be commented out # condition = 't1col>=sl' # self.assertTrue(not table1.will_query_use_indexing(condition)) # results1 = [p['var1'] for p in table1.where(condition,start=2, # stop=-1,step=1)] # results2 = [p["var1"] for p in table2.iterrows(2, -1, 1) # if p["var1"] >= sl] # if verbose: # print "Limit:", sl # print "Selection results (in-kernel):", results1 # print "Should look like:", results2 # print "Length results:", len(results1) # print "Should be:", len(results2) # self.assertEqual(len(results1), len(results2)) # self.assertEqual(results1, results2) # Fourth selection # results1 = [p['var1'] for p in # table1.where(condition,start=2,stop=-1,step=3)] condition = 't1col>=sl' self.assertTrue(not table1.will_query_use_indexing(condition)) results1 = [p['var1'] for p in table1.where(condition, start=2, stop=-1, step=3)] results2 = [p["var1"] for p in table2.iterrows(2, -1, 3) if p["var1"] >= sl] if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Re-enable the indexing in queries basically to unnail the # condition cache and not raising the performance warning # about some indexes being dirty table1._enable_indexing_in_queries() def test09b(self): """Checking non-indexed where() (float flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09b..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 table1._disable_indexing_in_queries() # Convert the limits to the appropriate type il = float(self.il) sl = float(self.sl) # Do some selections and check the results t1col = table1.cols.var4 self.assertIsNotNone(t1col) # First selection condition = 't1col= sl] if common.verbose: print("Limit:", sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Re-enable the indexing in queries basically to unnail the # condition cache and not raising the performance warning # about some indexes being dirty table1._enable_indexing_in_queries() def test09c(self): """Check non-indexed where() w/ ranges, changing step (string flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09c..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 table1._disable_indexing_in_queries() # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do some selections and check the results t1col = table1.cols.var1 self.assertIsNotNone(t1col) # First selection condition = 't1col>=sl' self.assertTrue(not table1.will_query_use_indexing(condition)) results1 = [p['var1'] for p in table1.where(condition, start=2, stop=-1, step=3)] results2 = [p["var1"] for p in table2.iterrows(2, -1, 3) if p["var1"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Second selection condition = 't1col>=sl' self.assertTrue(not table1.will_query_use_indexing(condition)) results1 = [p['var1'] for p in table1.where(condition, start=5, stop=-1, step=10)] results2 = [p["var1"] for p in table2.iterrows(5, -1, 10) if p["var1"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Third selection condition = 't1col>=sl' self.assertTrue(not table1.will_query_use_indexing(condition)) results1 = [p['var1'] for p in table1.where(condition, start=5, stop=-3, step=11)] results2 = [p["var1"] for p in table2.iterrows(5, -3, 11) if p["var1"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Fourth selection condition = 't1col>=sl' self.assertTrue(not table1.will_query_use_indexing(condition)) results1 = [p['var1'] for p in table1.where(condition, start=2, stop=-1, step=300)] results2 = [p["var1"] for p in table2.iterrows(2, -1, 300) if p["var1"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Re-enable the indexing in queries basically to unnail the # condition cache and not raising the performance warning # about some indexes being dirty table1._enable_indexing_in_queries() def test09d(self): """Checking non-indexed where() w/ ranges, changing step (int flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09d..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 table1._disable_indexing_in_queries() # Convert the limits to the appropriate type il = int(self.il) sl = int(self.sl) # Do some selections and check the results t3col = table1.cols.var3 self.assertIsNotNone(t3col) # First selection condition = 't3col>=sl' self.assertTrue(not table1.will_query_use_indexing(condition)) results1 = [p['var3'] for p in table1.where(condition, start=2, stop=-1, step=3)] results2 = [p["var3"] for p in table2.iterrows(2, -1, 3) if p["var3"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Second selection condition = 't3col>=sl' self.assertTrue(not table1.will_query_use_indexing(condition)) results1 = [p['var3'] for p in table1.where(condition, start=5, stop=-1, step=10)] results2 = [p["var3"] for p in table2.iterrows(5, -1, 10) if p["var3"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Third selection condition = 't3col>=sl' self.assertTrue(not table1.will_query_use_indexing(condition)) results1 = [p['var3'] for p in table1.where(condition, start=5, stop=-3, step=11)] results2 = [p["var3"] for p in table2.iterrows(5, -3, 11) if p["var3"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Fourth selection condition = 't3col>=sl' self.assertTrue(not table1.will_query_use_indexing(condition)) results1 = [p['var3'] for p in table1.where(condition, start=2, stop=-1, step=300)] results2 = [p["var3"] for p in table2.iterrows(2, -1, 300) if p["var3"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Re-enable the indexing in queries basically to unnail the # condition cache and not raising the performance warning # about some indexes being dirty table1._enable_indexing_in_queries() def test10a(self): """Checking indexed where() with ranges (string flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test10a..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do some selections and check the results t1col = table1.cols.var1 # First selection condition = 't1col<=sl' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [ p['var1'] for p in table1.where(condition, start=2, stop=10) ] results2 = [ p["var1"] for p in table2.iterrows(2, 10) if p["var1"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Second selection condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [ p['var1'] for p in table1.where(condition, start=2, stop=30, step=1) ] results2 = [ p["var1"] for p in table2.iterrows(2, 30, 1) if il <= p["var1"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Repeat second selection (testing caches) condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [ p['var1'] for p in table1.where(condition, start=2, stop=30, step=2) ] results2 = [ p["var1"] for p in table2.iterrows(2, 30, 2) if il <= p["var1"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Selection results (indexed):", results1) print("Should look like:", results2) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Third selection condition = '(il= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test10b(self): """Checking indexed where() with ranges (int flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test10b..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = int(self.il) sl = int(self.sl) # Do some selections and check the results t3col = table1.cols.var3 # First selection condition = 't3col<=sl' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t3col.pathname])) results1 = [ p['var3'] for p in table1.where(condition, start=2, stop=10) ] results2 = [ p["var3"] for p in table2.iterrows(2, 10) if p["var3"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Second selection condition = '(il<=t3col)&(t3col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t3col.pathname])) results1 = [ p['var3'] for p in table1.where(condition, start=2, stop=30, step=2) ] results2 = [ p["var3"] for p in table2.iterrows(2, 30, 2) if il <= p["var3"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Third selection condition = '(il= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test10c(self): """Checking indexed where() with ranges, changing step (string flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test10c..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do some selections and check the results t1col = table1.cols.var1 # First selection condition = 't1col>=sl' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [p['var1'] for p in table1.where(condition, start=2, stop=-1, step=3)] results2 = [p["var1"] for p in table2.iterrows(2, -1, 3) if p["var1"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Second selection condition = 't1col>=sl' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [p['var1'] for p in table1.where(condition, start=5, stop=-1, step=10)] results2 = [p["var1"] for p in table2.iterrows(5, -1, 10) if p["var1"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Third selection condition = 't1col>=sl' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [p['var1'] for p in table1.where(condition, start=5, stop=-3, step=11)] results2 = [p["var1"] for p in table2.iterrows(5, -3, 11) if p["var1"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Fourth selection condition = 't1col>=sl' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [p['var1'] for p in table1.where(condition, start=2, stop=-1, step=300)] results2 = [p["var1"] for p in table2.iterrows(2, -1, 300) if p["var1"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test10d(self): """Checking indexed where() with ranges, changing step (int flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test10d..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = int(self.il) sl = int(self.sl) # Do some selections and check the results t3col = table1.cols.var3 # First selection condition = 't3col>=sl' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t3col.pathname])) results1 = [p['var3'] for p in table1.where(condition, start=2, stop=-1, step=3)] results2 = [p["var3"] for p in table2.iterrows(2, -1, 3) if p["var3"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Second selection condition = 't3col>=sl' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t3col.pathname])) results1 = [p['var3'] for p in table1.where(condition, start=5, stop=-1, step=10)] results2 = [p["var3"] for p in table2.iterrows(5, -1, 10) if p["var3"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Third selection condition = 't3col>=sl' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t3col.pathname])) results1 = [p['var3'] for p in table1.where(condition, start=5, stop=-3, step=11)] results2 = [p["var3"] for p in table2.iterrows(5, -3, 11) if p["var3"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Fourth selection condition = 't3col>=sl' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t3col.pathname])) results1 = [p['var3'] for p in table1.where(condition, start=2, stop=-1, step=300)] results2 = [p["var3"] for p in table2.iterrows(2, -1, 300) if p["var3"] >= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test11a(self): """Checking selecting values from an Index via read_coordinates()""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test11a..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do a selection and check the result t1var1 = table1.cols.var1 condition = '(il<=t1var1)&(t1var1<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1var1.pathname]) ) coords1 = table1.get_where_list(condition) table1.flavor = "python" results1 = table1.read_coordinates(coords1, field="var1") results2 = [p["var1"] for p in table2 if il <= p["var1"] <= sl] results1.sort() results2.sort() if common.verbose: print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test12a(self): """Checking selecting values after a Table.append() operation.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test12a..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Append more rows in already created indexes count = 0 for i in range(0, self.nrows//2, self.nrep): for j in range(self.nrep): if self.random: k = random.randrange(self.nrows) elif self.values is not None: lenvalues = len(self.values) if i >= lenvalues: i %= lenvalues k = self.values[i] else: k = i table1.row['var1'] = str(k) table2.row['var1'] = str(k) table1.row['var2'] = k % 2 table2.row['var2'] = k % 2 table1.row['var3'] = k table2.row['var3'] = k table1.row['var4'] = float(self.nrows - k - 1) table2.row['var4'] = float(self.nrows - k - 1) table1.row.append() table2.row.append() count += 1 table1.flush() table2.flush() t1var1 = table1.cols.var1 t1var2 = table1.cols.var2 t1var3 = table1.cols.var3 t1var4 = table1.cols.var4 self.assertFalse(t1var1.index.dirty) self.assertFalse(t1var2.index.dirty) self.assertFalse(t1var3.index.dirty) self.assertFalse(t1var4.index.dirty) # Do some selections and check the results # First selection: string # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') results1 = [p["var1"] for p in table1.where('(il<=t1var1)&(t1var1<=sl)')] results2 = [p["var1"] for p in table2 if il <= p["var1"] <= sl] results1.sort() results2.sort() if common.verbose: print("Should look like:", results2) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Second selection: bool results1 = [p["var2"] for p in table1.where('t1var2 == True')] results2 = [p["var2"] for p in table2 if p["var2"] is True] if common.verbose: print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Third selection: int # Convert the limits to the appropriate type il = int(self.il) sl = int(self.sl) t1var3 = table1.cols.var3 results1 = [p["var3"] for p in table1.where( '(il<=t1var3)&(t1var3<=sl)')] results2 = [p["var3"] for p in table2 if il <= p["var3"] <= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Fourth selection: float # Convert the limits to the appropriate type il = float(self.il) sl = float(self.sl) # Do some selections and check the results results1 = [p["var4"] for p in table1.where( '(il<=t1var4)&(t1var4<=sl)')] results2 = [p["var4"] for p in table2 if il <= p["var4"] <= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1.sort(), results2.sort()) def test13a(self): """Checking repeated queries (checking caches)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test13a..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do some selections and check the results t1col = table1.cols.var1 condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [ p['var1'] for p in table1.where(condition, start=2, stop=30, step=1) ] results2 = [ p["var1"] for p in table2.iterrows(2, 30, 1) if il <= p["var1"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Repeat the selection (testing caches) condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [ p['var1'] for p in table1.where(condition, start=2, stop=30, step=2) ] results2 = [ p["var1"] for p in table2.iterrows(2, 30, 2) if il <= p["var1"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test13b(self): """Checking repeated queries, varying step (checking caches)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test13b..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do some selections and check the results t1col = table1.cols.var1 condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [ p['var1'] for p in table1.where(condition, start=2, stop=30, step=1) ] results2 = [ p["var1"] for p in table2.iterrows(2, 30, 1) if il <= p["var1"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Repeat the selection (testing caches) condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [ p['var1'] for p in table1.where(condition, start=2, stop=30, step=2) ] results2 = [ p["var1"] for p in table2.iterrows(2, 30, 2) if il <= p["var1"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test13c(self): """Checking repeated queries, varying start, stop, step.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test13c..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do some selections and check the results t1col = table1.cols.var1 condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [ p['var1'] for p in table1.where(condition, start=0, stop=1, step=2) ] results2 = [ p["var1"] for p in table2.iterrows(0, 1, 2) if il <= p["var1"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Repeat the selection (testing caches) condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [ p['var1'] for p in table1.where(condition, start=0, stop=5, step=1) ] results2 = [ p["var1"] for p in table2.iterrows(0, 5, 1) if il <= p["var1"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test13d(self): """Checking repeated queries, varying start, stop, step (another twist)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test13d..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do some selections and check the results t1col = table1.cols.var1 condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname]) ) results1 = [ p['var1'] for p in table1.where(condition, start=0, stop=1, step=1) ] results2 = [ p["var1"] for p in table2.iterrows(0, 1, 1) if il <= p["var1"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Repeat the selection (testing caches) condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [ p['var1'] for p in table1.where(condition, start=0, stop=1, step=1) ] results2 = [ p["var1"] for p in table2.iterrows(0, 1, 1) if il <= p["var1"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test13e(self): """Checking repeated queries, with varying condition.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test13e..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do some selections and check the results t1col = table1.cols.var1 condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [ p['var1'] for p in table1.where(condition, start=0, stop=10, step=1) ] results2 = [ p["var1"] for p in table2.iterrows(0, 10, 1) if il <= p["var1"] <= sl ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Repeat the selection with a more complex condition t2col = table1.cols.var2 condition = '(il<=t1col)&(t1col<=sl)&(t2col==True)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname, t2col.pathname])) results1 = [ p['var1'] for p in table1.where(condition, start=0, stop=10, step=1) ] results2 = [ p["var1"] for p in table2.iterrows(0, 10, 1) if il <= p["var1"] <= sl and p["var2"] is True ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test13f(self): """Checking repeated queries, with varying condition.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test13f..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Remove indexes in var2 column table1.cols.var2.remove_index() table2.cols.var2.remove_index() # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do some selections and check the results t1col = table1.cols.var1 t2col = table1.cols.var2 self.assertIsNotNone(t2col) condition = '(il<=t1col)&(t1col<=sl)&(t2col==True)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [p['var1'] for p in table1.where(condition, start=0, stop=10, step=1)] results2 = [ p["var1"] for p in table2.iterrows(0, 10, 1) if il <= p["var1"] <= sl and p["var2"] is True ] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Repeat the selection with a simpler condition condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [p['var1'] for p in table1.where(condition, start=0, stop=10, step=1)] results2 = [p["var1"] for p in table2.iterrows(0, 10, 1) if il <= p["var1"] <= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Repeat again with the original condition, but with a constant constant = True condition = '(il<=t1col)&(t1col<=sl)&(t2col==constant)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [p['var1'] for p in table1.where(condition, start=0, stop=10, step=1)] results2 = [p["var1"] for p in table2.iterrows(0, 10, 1) if il <= p["var1"] <= sl and p["var2"] == constant] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) def test13g(self): """Checking repeated queries, with different limits.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test13g..." % self.__class__.__name__) table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 # Convert the limits to the appropriate type il = str(self.il).encode('ascii') sl = str(self.sl).encode('ascii') # Do some selections and check the results t1col = table1.cols.var1 condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [p['var1'] for p in table1.where(condition, start=0, stop=10, step=1)] results2 = [p["var1"] for p in table2.iterrows(0, 10, 1) if il <= p["var1"] <= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) # Repeat the selection with different limits il, sl = (str(self.il + 1).encode( 'ascii'), str(self.sl-2).encode('ascii')) t2col = table1.cols.var2 self.assertIsNotNone(t2col) condition = '(il<=t1col)&(t1col<=sl)' self.assertTrue( table1.will_query_use_indexing(condition) == fzset([t1col.pathname])) results1 = [p['var1'] for p in table1.where(condition, start=0, stop=10, step=1)] results2 = [p["var1"] for p in table2.iterrows(0, 10, 1) if il <= p["var1"] <= sl] # sort lists (indexing does not guarantee that rows are returned in # order) results1.sort() results2.sort() if common.verbose: print("Limits:", il, sl) print("Length results:", len(results1)) print("Should be:", len(results2)) self.assertEqual(len(results1), len(results2)) self.assertEqual(results1, results2) class SV1aTestCase(SelectValuesTestCase): blocksizes = small_blocksizes chunkshape = 1 buffersize = 2 ss = blocksizes[2] nrows = ss reopen = 0 nrep = ss il = 0 sl = ss class SV1bTestCase(SV1aTestCase): blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) chunkshape = blocksizes[2]//2**9 buffersize = chunkshape * 5 class SV2aTestCase(SelectValuesTestCase): blocksizes = small_blocksizes chunkshape = 2 buffersize = 2 ss = blocksizes[2] nrows = ss * 2-1 reopen = 1 nrep = 1 il = 0 sl = 2 class SV2bTestCase(SV2aTestCase): blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) chunkshape = blocksizes[2]//2**7 buffersize = chunkshape * 20 class SV3aTestCase(SelectValuesTestCase): blocksizes = small_blocksizes chunkshape = 2 buffersize = 3 ss = blocksizes[2] nrows = ss * 5-1 reopen = 1 nrep = 3 il = 0 sl = 3 class SV3bTestCase(SV3aTestCase): blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) # chunkshape = 4 # buffersize = 16 chunkshape = 3 buffersize = 9 class SV4aTestCase(SelectValuesTestCase): blocksizes = small_blocksizes buffersize = 10 ss = blocksizes[2] nrows = ss * 3 reopen = 0 nrep = 1 # il = nrows-cs il = 0 sl = nrows class SV4bTestCase(SV4aTestCase): blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) chunkshape = 500 buffersize = 1000 class SV5aTestCase(SelectValuesTestCase): blocksizes = small_blocksizes ss = blocksizes[2] nrows = ss * 5 reopen = 0 nrep = 1 il = 0 sl = nrows class SV5bTestCase(SV5aTestCase): blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) class SV6aTestCase(SelectValuesTestCase): blocksizes = small_blocksizes ss = blocksizes[2] nrows = ss * 5 + 1 reopen = 0 cs = blocksizes[3] nrep = cs + 1 il = -1 sl = nrows class SV6bTestCase(SV6aTestCase): blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) class SV7aTestCase(SelectValuesTestCase): random = 1 blocksizes = small_blocksizes ss = blocksizes[2] nrows = ss * 5 + 3 reopen = 0 cs = blocksizes[3] nrep = cs-1 il = -10 sl = nrows class SV7bTestCase(SV7aTestCase): blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) class SV8aTestCase(SelectValuesTestCase): random = 0 chunkshape = 1 blocksizes = small_blocksizes ss = blocksizes[2] nrows = ss * 5-3 reopen = 0 cs = blocksizes[3] nrep = cs-1 il = 10 sl = nrows-10 class SV8bTestCase(SV8aTestCase): random = 0 blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) class SV9aTestCase(SelectValuesTestCase): random = 1 blocksizes = small_blocksizes ss = blocksizes[2] nrows = ss * 5 + 11 reopen = 0 cs = blocksizes[3] nrep = cs-1 il = 10 sl = nrows-10 class SV9bTestCase(SV9aTestCase): blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) class SV10aTestCase(SelectValuesTestCase): random = 1 blocksizes = small_blocksizes chunkshape = 1 buffersize = 1 ss = blocksizes[2] nrows = ss reopen = 0 nrep = ss il = 0 sl = ss class SV10bTestCase(SV10aTestCase): blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) chunkshape = 5 buffersize = 6 class SV11aTestCase(SelectValuesTestCase): # This checks a special case that failed. It was discovered in a # random test above (SV10a). It is explicitely put here as a way # to always check that specific case. values = [1, 7, 6, 7, 0, 7, 4, 4, 9, 5] blocksizes = small_blocksizes chunkshape = 1 buffersize = 1 ss = blocksizes[2] nrows = ss reopen = 0 nrep = ss il = 0 sl = ss class SV11bTestCase(SelectValuesTestCase): # This checks a special case that failed. It was discovered in a # random test above (SV10a). It is explicitely put here as a way # to always check that specific case. values = [1, 7, 6, 7, 0, 7, 4, 4, 9, 5] chunkshape = 2 buffersize = 2 blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) ss = blocksizes[2] nrows = ss reopen = 0 nrep = ss il = 0 sl = ss class SV12aTestCase(SelectValuesTestCase): # This checks a special case that failed. It was discovered in a # random test above (SV10b). It is explicitely put here as a way # to always check that specific case. # values = [0, 7, 0, 6, 5, 1, 6, 7, 0, 0] values = [4, 4, 1, 5, 2, 0, 1, 4, 3, 9] blocksizes = small_blocksizes chunkshape = 1 buffersize = 1 ss = blocksizes[2] nrows = ss reopen = 0 nrep = ss il = 0 sl = ss class SV12bTestCase(SelectValuesTestCase): # This checks a special case that failed. It was discovered in a # random test above (SV10b). It is explicitely put here as a way # to always check that specific case. # values = [0, 7, 0, 6, 5, 1, 6, 7, 0, 0] values = [4, 4, 1, 5, 2, 0, 1, 4, 3, 9] blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) chunkshape = 2 buffersize = 2 ss = blocksizes[2] nrows = ss reopen = 1 nrep = ss il = 0 sl = ss class SV13aTestCase(SelectValuesTestCase): values = [0, 7, 0, 6, 5, 1, 6, 7, 0, 0] blocksizes = small_blocksizes chunkshape = 3 buffersize = 5 ss = blocksizes[2] nrows = ss reopen = 0 nrep = ss il = 0 sl = ss class SV13bTestCase(SelectValuesTestCase): values = [0, 7, 0, 6, 5, 1, 6, 7, 0, 0] blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) chunkshape = 5 buffersize = 10 ss = blocksizes[2] nrows = ss reopen = 1 nrep = ss il = 0 sl = ss class SV14aTestCase(SelectValuesTestCase): values = [1, 7, 6, 7, 0, 7, 4, 4, 9, 5] blocksizes = small_blocksizes chunkshape = 2 buffersize = 5 ss = blocksizes[2] nrows = ss reopen = 0 cs = blocksizes[3] nrep = cs il = -5 sl = 500 class SV14bTestCase(SelectValuesTestCase): values = [1, 7, 6, 7, 0, 7, 4, 4, 9, 5] blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) chunkshape = 9 buffersize = 10 ss = blocksizes[2] nrows = ss reopen = 1 nrep = 9 il = 0 cs = blocksizes[3] sl = ss-cs + 1 class SV15aTestCase(SelectValuesTestCase): # Test that checks for case where there are not valid values in # the indexed part, but they exist in the non-indexed region. # At least, test01b takes account of that random = 1 # Both values of seed below triggers a fail in indexing code # seed = 1885 seed = 183 blocksizes = small_blocksizes ss = blocksizes[2] nrows = ss * 5 + 1 reopen = 0 cs = blocksizes[3] nrep = cs-1 il = -10 sl = nrows class SV15bTestCase(SelectValuesTestCase): # Test that checks for case where there are not valid values in # the indexed part, but they exist in the non-indexed region. # At least, test01b takes account of that random = 1 # Both values of seed below triggers a fail in indexing code seed = 1885 # seed = 183 blocksizes = tb.idxutils.calc_chunksize(minRowIndex, memlevel=1) ss = blocksizes[2] nrows = ss * 5 + 1 reopen = 1 cs = blocksizes[3] nrep = cs-1 il = -10 sl = nrows class LastRowReuseBuffers(common.PyTablesTestCase): # Test that checks for possible reuse of buffers coming # from last row in the sorted part of indexes nelem = 1221 np.random.seed(1) random.seed(1) class Record(tb.IsDescription): id1 = tb.Int16Col() def setUp(self): super().setUp() self.h5fname = tempfile.mktemp(".h5") self.h5file = None def tearDown(self): if self.h5file is not None: self.h5file.close() if Path(self.h5fname).is_file(): Path(self.h5fname).unlink() super().tearDown() def test00_lrucache(self): self.h5file = tb.open_file(self.h5fname, 'w', node_cache_slots=64) ta = self.h5file.create_table('/', 'table', self.Record, filters=tb.Filters(1)) id1 = np.random.randint(0, 2**15, self.nelem) ta.append([id1]) ta.cols.id1.create_index() for i in range(self.nelem): nrow = random.randrange(self.nelem) value = id1[nrow] idx = ta.get_where_list('id1 == %s' % value) self.assertGreater(len(idx), 0, f"idx--> {idx} {i} {nrow} {value}") self.assertTrue( nrow in idx, f"nrow not found: {idx} != {nrow}, {value}") def test01_nocache(self): self.h5file = tb.open_file(self.h5fname, 'w', node_cache_slots=0) ta = self.h5file.create_table('/', 'table', self.Record, filters=tb.Filters(1)) id1 = np.random.randint(0, 2**15, self.nelem) ta.append([id1]) ta.cols.id1.create_index() for i in range(self.nelem): nrow = random.randrange(self.nelem) value = id1[nrow] idx = ta.get_where_list('id1 == %s' % value) self.assertGreater(len(idx), 0, f"idx--> {idx} {i} {nrow} {value}") self.assertTrue( nrow in idx, f"nrow not found: {idx} != {nrow}, {value}") def test02_dictcache(self): self.h5file = tb.open_file(self.h5fname, 'w', node_cache_slots=-64) ta = self.h5file.create_table('/', 'table', self.Record, filters=tb.Filters(1)) id1 = np.random.randint(0, 2**15, self.nelem) ta.append([id1]) ta.cols.id1.create_index() for i in range(self.nelem): nrow = random.randrange(self.nelem) value = id1[nrow] idx = ta.get_where_list('id1 == %s' % value) self.assertGreater(len(idx), 0, f"idx--> {idx} {i} {nrow} {value}") self.assertTrue( nrow in idx, f"nrow not found: {idx} != {nrow}, {value}") normal_tests = ( "SV1aTestCase", "SV2aTestCase", "SV3aTestCase", ) heavy_tests = ( # The next are too hard to be in the 'normal' suite "SV1bTestCase", "SV2bTestCase", "SV3bTestCase", "SV4aTestCase", "SV5aTestCase", "SV6aTestCase", "SV7aTestCase", "SV8aTestCase", "SV9aTestCase", "SV10aTestCase", "SV11aTestCase", "SV12aTestCase", "SV13aTestCase", "SV14aTestCase", "SV15aTestCase", # This are properly heavy "SV4bTestCase", "SV5bTestCase", "SV6bTestCase", "SV7bTestCase", "SV8bTestCase", "SV9bTestCase", "SV10bTestCase", "SV11bTestCase", "SV12bTestCase", "SV13bTestCase", "SV14bTestCase", "SV15bTestCase", ) # Base classes for the different type indexes. class UltraLightITableMixin: kind = "ultralight" class LightITableMixin: kind = "light" class MediumITableMixin: kind = "medium" class FullITableMixin: kind = "full" # Parameters for indexed queries. ckinds = ['UltraLight', 'Light', 'Medium', 'Full'] testlevels = ['Normal', 'Heavy'] # Indexed queries: ``[ULMF]I[NH]SVXYTestCase``, where: # # 1. U is for 'UltraLight', L for 'Light', M for 'Medium', F for 'Full' indexes # 2. N is for 'Normal', H for 'Heavy' tests def iclassdata(): for ckind in ckinds: for ctest in normal_tests + heavy_tests: classname = f'{ckind[0]}I{testlevels[common.heavy][0]}{ctest}' # Uncomment the next one and comment the past one if one # don't want to include the methods (testing purposes only) # cbasenames = ( '%sITableMixin' % ckind, "object") cbasenames = ('%sITableMixin' % ckind, ctest) classdict = dict(heavy=bool(ctest in heavy_tests)) yield (classname, cbasenames, classdict) # Create test classes. for (cname, cbasenames, cdict) in iclassdata(): cbases = tuple(eval(cbase) for cbase in cbasenames) class_ = type(cname, cbases, cdict) exec('%s = class_' % cname) # Test case for issue #319 class BuffersizeMultipleChunksize(common.TempFileMixin, common.PyTablesTestCase): open_mode = 'w' def test01(self): np.random.seed(2) n = 700_000 cs = 50_000 nchunks = n // cs arr = np.zeros( (n,), dtype=[('index', 'i8'), ('o', 'i8'), ('value', 'f8')]) arr['index'] = np.arange(n) arr['o'] = np.random.randint(-20_000, -15_000, size=n) arr['value'] = np.random.randn(n) node = self.h5file.create_group('/', 'foo') table = self.h5file.create_table(node, 'table', dict( index=tb.Int64Col(), o=tb.Int64Col(), value=tb.FloatCol(shape=())), expectedrows=10_000_000) table.append(arr) self._reopen('a') v1 = np.unique(arr['o'])[0] v2 = np.unique(arr['o'])[1] res = np.array([v1, v2]) selector = f'((o == {v1}) | (o == {v2}))' if common.verbose: print("selecting values: %s" % selector) table = self.h5file.root.foo.table result = np.unique(table.read_where(selector)['o']) np.testing.assert_almost_equal(result, res) if common.verbose: print("select entire table:") print(f"result: {result}\texpected: {res}") if common.verbose: print("index the column o") table.cols.o.create_index() # this was triggering the issue if common.verbose: print("select via chunks") for i in range(nchunks): result = table.read_where(selector, start=i*cs, stop=(i+1)*cs) result = np.unique(result['o']) np.testing.assert_almost_equal(np.unique(result), res) if common.verbose: print(f"result: {result}\texpected: {res}") # Test case for issue #441 class SideEffectNumPyQuicksort(common.PyTablesTestCase): def test01(self): bug_file = common.test_filename("bug-idx.h5") tmp_file = tempfile.mktemp(".h5") tb.copy_file(bug_file, tmp_file) h5 = tb.open_file(tmp_file, "a") o = h5.root.table vals = o.cols.path[:] npvals = set(np.where(vals == 6)[0]) # Setting the chunkshape is critical for reproducing the bug t = o.copy(newname="table2", chunkshape=2730) t.cols.path.create_index() indexed = {r.nrow for r in t.where('path == 6')} if common.verbose: diffs = sorted(npvals - indexed) print("ndiff:", len(diffs), diffs) self.assertEqual(len(npvals), len(indexed)) h5.close() if Path(tmp_file).is_file(): Path(tmp_file).unlink() # ----------------------------- def suite(): theSuite = common.unittest.TestSuite() niter = 1 for n in range(niter): for cdata in iclassdata(): class_ = eval(cdata[0]) if not class_.heavy: suite_ = common.unittest.makeSuite(class_) theSuite.addTest(suite_) elif common.heavy: suite_ = common.unittest.makeSuite(class_) theSuite.addTest(suite_) theSuite.addTest(common.unittest.makeSuite(LastRowReuseBuffers)) theSuite.addTest( common.unittest.makeSuite(BuffersizeMultipleChunksize)) theSuite.addTest(common.unittest.makeSuite(SideEffectNumPyQuicksort)) return theSuite if __name__ == '__main__': import sys common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_links.py000066400000000000000000000543431416254111300204200ustar00rootroot00000000000000"""Test module for diferent kind of links under PyTables.""" import re import tempfile from pathlib import Path import tables as tb from tables.tests import common # Test for hard links class HardLinkTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self._createFile() def _createFile(self): self.h5file.create_array('/', 'arr1', [1, 2]) group1 = self.h5file.create_group('/', 'group1') arr2 = self.h5file.create_array(group1, 'arr2', [1, 2, 3]) lgroup1 = self.h5file.create_hard_link('/', 'lgroup1', '/group1') self.assertIsNotNone(lgroup1) larr1 = self.h5file.create_hard_link(group1, 'larr1', '/arr1') self.assertIsNotNone(larr1) larr2 = self.h5file.create_hard_link('/', 'larr2', arr2) self.assertIsNotNone(larr2) def test00_create(self): """Creating hard links.""" self._checkEqualityGroup(self.h5file.root.group1, self.h5file.root.lgroup1, hardlink=True) self._checkEqualityLeaf(self.h5file.root.arr1, self.h5file.root.group1.larr1, hardlink=True) self._checkEqualityLeaf(self.h5file.root.lgroup1.arr2, self.h5file.root.larr2, hardlink=True) def test01_open(self): """Opening a file with hard links.""" self._reopen() self._checkEqualityGroup(self.h5file.root.group1, self.h5file.root.lgroup1, hardlink=True) self._checkEqualityLeaf(self.h5file.root.arr1, self.h5file.root.group1.larr1, hardlink=True) self._checkEqualityLeaf(self.h5file.root.lgroup1.arr2, self.h5file.root.larr2, hardlink=True) def test02_removeLeaf(self): """Removing a hard link to a Leaf.""" # First delete the initial link self.h5file.root.arr1.remove() self.assertNotIn('/arr1', self.h5file) # The second link should still be there if common.verbose: print("Remaining link:", self.h5file.root.group1.larr1) self.assertIn('/group1/larr1', self.h5file) # Remove the second link self.h5file.root.group1.larr1.remove() self.assertNotIn('/group1/larr1', self.h5file) def test03_removeGroup(self): """Removing a hard link to a Group.""" if common.verbose: print("Original object tree:", self.h5file) # First delete the initial link self.h5file.root.group1._f_remove(force=True) self.assertNotIn('/group1', self.h5file) # The second link should still be there if common.verbose: print("Remaining link:", self.h5file.root.lgroup1) print("Object tree:", self.h5file) self.assertIn('/lgroup1', self.h5file) # Remove the second link self.h5file.root.lgroup1._g_remove(recursive=True) self.assertNotIn('/lgroup1', self.h5file) if common.verbose: print("Final object tree:", self.h5file) # Test for soft links class SoftLinkTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self._createFile() def _createFile(self): self.h5file.create_array('/', 'arr1', [1, 2]) group1 = self.h5file.create_group('/', 'group1') arr2 = self.h5file.create_array(group1, 'arr2', [1, 2, 3]) lgroup1 = self.h5file.create_soft_link('/', 'lgroup1', '/group1') self.assertIsNotNone(lgroup1) larr1 = self.h5file.create_soft_link(group1, 'larr1', '/arr1') self.assertIsNotNone(larr1) larr2 = self.h5file.create_soft_link('/', 'larr2', arr2) self.assertIsNotNone(larr2) def test00_create(self): """Creating soft links.""" self._checkEqualityGroup(self.h5file.root.group1, self.h5file.root.lgroup1()) self._checkEqualityLeaf(self.h5file.root.arr1, self.h5file.root.group1.larr1()) self._checkEqualityLeaf(self.h5file.root.lgroup1().arr2, self.h5file.root.larr2()) def test01_open(self): """Opening a file with soft links.""" self._reopen() self._checkEqualityGroup(self.h5file.root.group1, self.h5file.root.lgroup1()) self._checkEqualityLeaf(self.h5file.root.arr1, self.h5file.root.group1.larr1()) self._checkEqualityLeaf(self.h5file.root.lgroup1().arr2, self.h5file.root.larr2()) def test02_remove(self): """Removing a soft link.""" # First delete the referred link self.h5file.root.arr1.remove() self.assertNotIn('/arr1', self.h5file) # The soft link should still be there (but dangling) if common.verbose: print("Dangling link:", self.h5file.root.group1.larr1) self.assertIn('/group1/larr1', self.h5file) # Remove the soft link itself self.h5file.root.group1.larr1.remove() self.assertNotIn('/group1/larr1', self.h5file) def test03_copy(self): """Copying a soft link.""" # Copy the link into another location root = self.h5file.root lgroup1 = root.lgroup1 lgroup2 = lgroup1.copy('/', 'lgroup2') self.assertIn('/lgroup1', self.h5file) self.assertIn('/lgroup2', self.h5file) self.assertIn('lgroup2', root._v_children) self.assertIn('lgroup2', root._v_links) if common.verbose: print("Copied link:", lgroup2) # Remove the first link lgroup1.remove() self._checkEqualityGroup(self.h5file.root.group1, self.h5file.root.lgroup2()) def test03_overwrite(self): """Overwrite a soft link.""" # Copy the link into another location root = self.h5file.root lgroup1 = root.lgroup1 lgroup2 = lgroup1.copy('/', 'lgroup2') lgroup2 = lgroup1.copy('/', 'lgroup2', overwrite=True) self.assertIn('/lgroup1', self.h5file) self.assertIn('/lgroup2', self.h5file) self.assertIn('lgroup2', root._v_children) self.assertIn('lgroup2', root._v_links) if common.verbose: print("Copied link:", lgroup2) # Remove the first link lgroup1.remove() self._checkEqualityGroup(self.h5file.root.group1, self.h5file.root.lgroup2()) def test04_move(self): """Moving a soft link.""" # Move the link into another location lgroup1 = self.h5file.root.lgroup1 group2 = self.h5file.create_group('/', 'group2') lgroup1.move(group2, 'lgroup2') lgroup2 = self.h5file.root.group2.lgroup2 if common.verbose: print("Moved link:", lgroup2) self.assertNotIn('/lgroup1', self.h5file) self.assertIn('/group2/lgroup2', self.h5file) self._checkEqualityGroup(self.h5file.root.group1, self.h5file.root.group2.lgroup2()) def test05_rename(self): """Renaming a soft link.""" # Rename the link lgroup1 = self.h5file.root.lgroup1 lgroup1.rename('lgroup2') lgroup2 = self.h5file.root.lgroup2 if common.verbose: print("Moved link:", lgroup2) self.assertNotIn('/lgroup1', self.h5file) self.assertIn('/lgroup2', self.h5file) self._checkEqualityGroup(self.h5file.root.group1, self.h5file.root.lgroup2()) def test06a_relative_path(self): """Using soft links with relative paths.""" # Create new group self.h5file.create_group('/group1', 'group3') # ... and relative link lgroup3 = self.h5file.create_soft_link( '/group1', 'lgroup3', 'group3') if common.verbose: print("Relative path link:", lgroup3) self.assertIn('/group1/lgroup3', self.h5file) self._checkEqualityGroup(self.h5file.root.group1.group3, self.h5file.root.group1.lgroup3()) def test06b_relative_path(self): """Using soft links with relative paths (./ version)""" # Create new group self.h5file.create_group('/group1', 'group3') # ... and relative link lgroup3 = self.h5file.create_soft_link( '/group1', 'lgroup3', './group3') if common.verbose: print("Relative path link:", lgroup3) self.assertIn('/group1/lgroup3', self.h5file) self._checkEqualityGroup(self.h5file.root.group1.group3, self.h5file.root.group1.lgroup3()) def test07_walkNodes(self): """Checking `walk_nodes` with `classname` option.""" links = [node._v_pathname for node in self.h5file.walk_nodes('/', classname="Link")] if common.verbose: print("detected links (classname='Link'):", links) self.assertEqual(links, ['/larr2', '/lgroup1', '/group1/larr1']) links = [node._v_pathname for node in self.h5file.walk_nodes('/', classname="SoftLink")] if common.verbose: print("detected links (classname='SoftLink'):", links) self.assertEqual(links, ['/larr2', '/lgroup1', '/group1/larr1']) def test08__v_links(self): """Checking `Group._v_links`.""" links = [node for node in self.h5file.root._v_links] if common.verbose: print("detected links (under root):", links) self.assertEqual(len(links), 2) links = [node for node in self.h5file.root.group1._v_links] if common.verbose: print("detected links (under /group1):", links) self.assertEqual(links, ['larr1']) def test09_link_to_link(self): """Checking linked links.""" # Create a link to another existing link lgroup2 = self.h5file.create_soft_link( '/', 'lgroup2', '/lgroup1') # Dereference it once: self.assertIs(lgroup2(), self.h5file.get_node('/lgroup1')) if common.verbose: print("First dereference is correct:", lgroup2()) # Dereference it twice: self.assertIs(lgroup2()(), self.h5file.get_node('/group1')) if common.verbose: print("Second dereference is correct:", lgroup2()()) def test10_copy_link_to_file(self): """Checking copying a link to another file.""" fname = tempfile.mktemp(".h5") h5f = tb.open_file(fname, "a") h5f.create_array('/', 'arr1', [1, 2]) h5f.create_group('/', 'group1') lgroup1 = self.h5file.root.lgroup1 lgroup1_ = lgroup1.copy(h5f.root, 'lgroup1') self.assertIn('/lgroup1', self.h5file) self.assertIn('/lgroup1', h5f) self.assertIn(lgroup1_, h5f) if common.verbose: print("Copied link:", lgroup1_, 'in:', lgroup1_._v_file.filename) h5f.close() Path(fname).unlink() def test11_direct_attribute_access(self): """Check direct get/set attributes via link-->target.attribute""" larr1 = self.h5file.get_node('/lgroup1/larr1') arr1 = self.h5file.get_node('/arr1') # get self.assertEqual(larr1.shape, (2,)) self.assertEqual(larr1[:], [1, 2]) # set larr1[0] = -1 self.assertEqual(arr1[:], [-1, 2]) def test12_access_child_node_attributes(self): """Check get/set attributes via link-->target.child.attribute""" lgroup1 = self.h5file.get_node('/lgroup1') arr2 = self.h5file.get_node('/group1/arr2') # get child attribute self.assertEqual(lgroup1.arr2[:], [1, 2, 3]) # set child attribute lgroup1.arr2[0] = -1 self.assertEqual(arr2[:], [-1, 2, 3]) def test13_direct_attribute_access_via_chained_softlinks(self): """Check get/set access via link2-->link1-->target.child.attribute""" self.h5file.get_node('/lgroup1') arr2 = self.h5file.get_node('/group1/arr2') # multiple chained links l_lgroup1 = self.h5file.create_soft_link('/', 'l_lgroup1', '/lgroup1') # get child attribute self.assertEqual(l_lgroup1.arr2[:], [1, 2, 3]) # set child attribute l_lgroup1.arr2[0] = -1 self.assertEqual(arr2[:], [-1, 2, 3]) def test14_child_of_softlink_to_group(self): """Create an array whose parent is a softlink to another group""" self.h5file.get_node('/group1') lgroup1 = self.h5file.get_node('/lgroup1') self.h5file.create_array(lgroup1, 'new_arr', obj=[1, 2, 3]) new_arr2 = self.h5file.get_node('/group1/new_arr') self.assertEqual(new_arr2[:], [1, 2, 3]) def test_str(self): s = str(self.h5file) self.assertEqual(len(re.findall(r'\(SoftLink\)', s)), 3) self.assertEqual(len(re.findall(r'\(dangling\)', s)), 0) def test_str_with_dangling_link(self): self.h5file.root.group1.arr2.remove() s = str(self.h5file) self.assertEqual(len(re.findall(r'\(SoftLink\)', s)), 3) self.assertEqual(len(re.findall(r'\(dangling\)', s)), 1) # Test for external links @common.unittest.skipIf(tb.file._FILE_OPEN_POLICY == 'strict', 'FILE_OPEN_POLICY = "strict"') class ExternalLinkTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.extfname = tempfile.mktemp(".h5") self.exth5file = tb.open_file(self.extfname, "w") self._createFile() def tearDown(self): """Remove ``extfname``.""" extfname = self.extfname self.exth5file.close() super().tearDown() # open_files = tables.file._open_files # if self.extfname in open_files: # #assert False # for handler in open_files.get_handlers_by_name(self.extfname): # handler.close() Path(extfname).unlink() # comment this for debugging purposes only def _createFile(self): self.h5file.create_array('/', 'arr1', [1, 2]) group1 = self.h5file.create_group('/', 'group1') self.h5file.create_array(group1, 'arr2', [1, 2, 3]) # The external file extarr1 = self.exth5file.create_array('/', 'arr1', [1, 2]) self.assertIsNotNone(extarr1) extgroup1 = self.exth5file.create_group('/', 'group1') extarr2 = self.exth5file.create_array(extgroup1, 'arr2', [1, 2, 3]) # Create external links lgroup1 = self.h5file.create_external_link( '/', 'lgroup1', '%s:/group1' % self.extfname) self.assertIsNotNone(lgroup1) larr1 = self.h5file.create_external_link( group1, 'larr1', '%s:/arr1' % self.extfname) self.assertIsNotNone(larr1) larr2 = self.h5file.create_external_link('/', 'larr2', extarr2) self.assertIsNotNone(larr2) # Re-open the external file in 'r'ead-only mode self.exth5file.close() self.exth5file = tb.open_file(self.extfname, "r") def test00_create(self): """Creating soft links.""" self._checkEqualityGroup(self.exth5file.root.group1, self.h5file.root.lgroup1()) self._checkEqualityLeaf(self.exth5file.root.arr1, self.h5file.root.group1.larr1()) self._checkEqualityLeaf(self.h5file.root.lgroup1().arr2, self.h5file.root.larr2()) def test01_open(self): """Opening a file with soft links.""" self._reopen() self._checkEqualityGroup(self.exth5file.root.group1, self.h5file.root.lgroup1()) self._checkEqualityLeaf(self.exth5file.root.arr1, self.h5file.root.group1.larr1()) self._checkEqualityLeaf(self.h5file.root.lgroup1().arr2, self.h5file.root.larr2()) def test02_remove(self): """Removing an external link.""" # Re-open the external file in 'a'ppend mode self.exth5file.close() self.exth5file = tb.open_file(self.extfname, "a") # First delete the referred link self.exth5file.root.arr1.remove() self.assertNotIn('/arr1', self.exth5file) # The external link should still be there (but dangling) if common.verbose: print("Dangling link:", self.h5file.root.group1.larr1) self.assertIn('/group1/larr1', self.h5file) # Remove the external link itself self.h5file.root.group1.larr1.remove() self.assertNotIn('/group1/larr1', self.h5file) def test03_copy(self): """Copying an external link.""" # Copy the link into another location root = self.h5file.root lgroup1 = root.lgroup1 lgroup2 = lgroup1.copy('/', 'lgroup2') self.assertIn('/lgroup1', self.h5file) self.assertIn('/lgroup2', self.h5file) self.assertIn('lgroup2', root._v_children) self.assertIn('lgroup2', root._v_links) if common.verbose: print("Copied link:", lgroup2) # Remove the first link lgroup1.remove() self._checkEqualityGroup(self.exth5file.root.group1, self.h5file.root.lgroup2()) def test03_overwrite(self): """Overwrite an external link.""" # Copy the link into another location root = self.h5file.root lgroup1 = root.lgroup1 lgroup2 = lgroup1.copy('/', 'lgroup2') lgroup2 = lgroup1.copy('/', 'lgroup2', overwrite=True) self.assertIn('/lgroup1', self.h5file) self.assertIn('/lgroup2', self.h5file) self.assertIn('lgroup2', root._v_children) self.assertIn('lgroup2', root._v_links) if common.verbose: print("Copied link:", lgroup2) # Remove the first link lgroup1.remove() self._checkEqualityGroup(self.exth5file.root.group1, self.h5file.root.lgroup2()) def test04_move(self): """Moving an external link.""" # Move the link into another location lgroup1 = self.h5file.root.lgroup1 group2 = self.h5file.create_group('/', 'group2') lgroup1.move(group2, 'lgroup2') lgroup2 = self.h5file.root.group2.lgroup2 if common.verbose: print("Moved link:", lgroup2) self.assertNotIn('/lgroup1', self.h5file) self.assertIn('/group2/lgroup2', self.h5file) self._checkEqualityGroup(self.exth5file.root.group1, self.h5file.root.group2.lgroup2()) def test05_rename(self): """Renaming an external link.""" # Rename the link lgroup1 = self.h5file.root.lgroup1 lgroup1.rename('lgroup2') lgroup2 = self.h5file.root.lgroup2 if common.verbose: print("Moved link:", lgroup2) self.assertNotIn('/lgroup1', self.h5file) self.assertIn('/lgroup2', self.h5file) self._checkEqualityGroup(self.exth5file.root.group1, self.h5file.root.lgroup2()) def test07_walkNodes(self): """Checking `walk_nodes` with `classname` option.""" # Create a new soft link self.h5file.create_soft_link('/group1', 'lgroup3', './group3') links = [node._v_pathname for node in self.h5file.walk_nodes('/', classname="Link")] if common.verbose: print("detected links (classname='Link'):", links) self.assertEqual(links, ['/larr2', '/lgroup1', '/group1/larr1', '/group1/lgroup3']) links = [node._v_pathname for node in self.h5file.walk_nodes('/', classname="ExternalLink")] if common.verbose: print("detected links (classname='ExternalLink'):", links) self.assertEqual(links, ['/larr2', '/lgroup1', '/group1/larr1']) def test08__v_links(self): """Checking `Group._v_links`.""" links = [node for node in self.h5file.root._v_links] if common.verbose: print("detected links (under root):", links) self.assertEqual(len(links), 2) links = [node for node in self.h5file.root.group1._v_links] if common.verbose: print("detected links (under /group1):", links) self.assertEqual(links, ['larr1']) def test09_umount(self): """Checking `umount()` method.""" link = self.h5file.root.lgroup1 self.assertIsNone(link.extfile) # Dereference a external node (and hence, 'mount' a file) enode = link() self.assertIsNotNone(enode) self.assertIsNotNone(link.extfile) # Umount the link link.umount() self.assertIsNone(link.extfile) def test10_copy_link_to_file(self): """Checking copying a link to another file.""" h5fname2 = tempfile.mktemp(".h5") try: with tb.open_file(h5fname2, "a") as h5file2: h5file2.create_array('/', 'arr1', [1, 2]) h5file2.create_group('/', 'group1') lgroup1 = self.h5file.root.lgroup1 lgroup1_ = lgroup1.copy(h5file2.root, 'lgroup1') self.assertIn('/lgroup1', self.h5file) self.assertIn('/lgroup1', h5file2) self.assertIn(lgroup1_, h5file2) if common.verbose: print("Copied link:", lgroup1_, 'in:', lgroup1_._v_file.filename) finally: if Path(h5fname2).is_file(): Path(h5fname2).unlink() def suite(): """Return a test suite consisting of all the test cases in the module.""" theSuite = common.unittest.TestSuite() niter = 1 # common.heavy = 1 # uncomment this only for testing purposes for i in range(niter): theSuite.addTest(common.unittest.makeSuite(HardLinkTestCase)) theSuite.addTest(common.unittest.makeSuite(SoftLinkTestCase)) theSuite.addTest(common.unittest.makeSuite(ExternalLinkTestCase)) return theSuite if __name__ == '__main__': import sys common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_lists.py000066400000000000000000000354501416254111300204340ustar00rootroot00000000000000import tempfile from pathlib import Path import tables as tb from tables.tests import common def WriteRead(filename, testTuple): if common.verbose: print('\n', '-=' * 30) print("Running test for object %s" % type(testTuple)) # Create an instance of HDF5 Table fileh = tb.open_file(filename, mode="w") root = fileh.root try: # Create the array under root and name 'somearray' a = testTuple fileh.create_array(root, 'somearray', a, "Some array") finally: # Close the file fileh.close() # Re-open the file in read-only mode fileh = tb.open_file(filename, mode="r") root = fileh.root # Read the saved array try: b = root.somearray.read() # Compare them. They should be equal. if not a == b and common.verbose: print("Write and read lists/tuples differ!") print("Object written:", a) print("Object read:", b) # Check strictly the array equality assert a == b finally: # Close the file fileh.close() class BasicTestCase(common.PyTablesTestCase): def setUp(self): super().setUp() self.h5fname = tempfile.mktemp(".h5") self.h5file = None def tearDown(self): if self.h5file is not None: self.h5file.close() if Path(self.h5fname).is_file(): Path(self.h5fname).unlink() super().tearDown() def test00_char(self): """Data integrity during recovery (character types)""" a = self.charList WriteRead(self.h5fname, a) def test01_types(self): """Data integrity during recovery (numerical types)""" a = self.numericalList WriteRead(self.h5fname, a) class Basic0DOneTestCase(BasicTestCase): # Scalar case title = "Rank-0 case 1" numericalList = 3 charList = b"3" class Basic0DTwoTestCase(BasicTestCase): # Scalar case title = "Rank-0 case 2" numericalList = 33.34 charList = b"33"*500 # This does not work anymore because I've splitted the chunked arrays to happen # mainly in EArray objects # class Basic1DZeroTestCase(BasicTestCase): # title = "Rank-1 case 0" # numericalList = [] # charList = [] class Basic1DOneTestCase(BasicTestCase): # 1D case title = "Rank-1 case 1" numericalList = [3] charList = [b"a"] class Basic1DTwoTestCase(BasicTestCase): # 1D case title = "Rank-1 case 2" numericalList = [3.2, 4.2] charList = [b"aaa"] class Basic2DTestCase(BasicTestCase): # 2D case title = "Rank-2 case 1" numericalList = [[1, 2]]*5 charList = [[b"qq", b"zz"]]*5 class Basic10DTestCase(BasicTestCase): # 10D case title = "Rank-10 case 1" numericalList = [[[[[[[[[[1, 2], [3, 4]]]]]]]]]]*5 # Dimensions greather than 6 in strings gives some warnings charList = [[[[[[[[[[b"a", b"b"], [b"qq", b"zz"]]]]]]]]]]*5 class ExceptionTestCase(common.PyTablesTestCase): def setUp(self): super().setUp() self.h5fname = tempfile.mktemp(".h5") self.h5file = None def tearDown(self): if self.h5file is not None: self.h5file.close() if Path(self.h5fname).is_file(): Path(self.h5fname).unlink() super().tearDown() def test00_char(self): """Non suppported lists objects (character objects)""" if common.verbose: print('\n', '-=' * 30) print("Running test for %s" % (self.title)) a = self.charList with self.assertRaises((ValueError, TypeError)): WriteRead(self.h5fname, a) def test01_types(self): """Non supported lists object (numerical types)""" a = self.numericalList with self.assertRaises((ValueError, TypeError)): WriteRead(self.h5fname, a) class Basic1DFourTestCase(ExceptionTestCase): title = "Rank-1 case 4 (non-regular list)" numericalList = [3, [4, 5.2]] charList = [b"aaa", [b"bbb", b"ccc"]] class GetItemTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_single(self): """Single element access (character types)""" # Create the array under root and name 'somearray' a = self.charList arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element if common.verbose: print("Original first element:", a[0]) print("Read first element:", arr[0]) self.assertEqual(a[0], arr[0]) def test01_single(self): """Single element access (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalList arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element if common.verbose: print("Original first element:", a[0]) print("Read first element:", arr[0]) self.assertEqual(a[0], arr[0]) def test02_range(self): """Range element access (character types)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element if common.verbose: print("Original elements:", a[1:4]) print("Read elements:", arr[1:4]) self.assertEqual(a[1:4], arr[1:4]) def test03_range(self): """Range element access (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element if common.verbose: print("Original elements:", a[1:4]) print("Read elements:", arr[1:4]) self.assertEqual(a[1:4], arr[1:4]) def test04_range(self): """Range element access, strided (character types)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element if common.verbose: print("Original elements:", a[1:4:2]) print("Read elements:", arr[1:4:2]) self.assertEqual(a[1:4:2], arr[1:4:2]) def test05_range(self): """Range element access (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element if common.verbose: print("Original elements:", a[1:4:2]) print("Read elements:", arr[1:4:2]) self.assertEqual(a[1:4:2], arr[1:4:2]) def test06_negativeIndex(self): """Negative Index element access (character types)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element if common.verbose: print("Original last element:", a[-1]) print("Read last element:", arr[-1]) self.assertEqual(a[-1], arr[-1]) def test07_negativeIndex(self): """Negative Index element access (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element if common.verbose: print("Original before last element:", a[-2]) print("Read before last element:", arr[-2]) self.assertEqual(a[-2], arr[-2]) def test08_negativeRange(self): """Negative range element access (character types)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element if common.verbose: print("Original last elements:", a[-4:-1]) print("Read last elements:", arr[-4:-1]) self.assertEqual(a[-4:-1], arr[-4:-1]) def test09_negativeRange(self): """Negative range element access (numerical types)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element if common.verbose: print("Original last elements:", a[-4:-1]) print("Read last elements:", arr[-4:-1]) self.assertEqual(a[-4:-1], arr[-4:-1]) class GI1ListTestCase(GetItemTestCase): title = "Rank-1 case 1 (lists)" numericalList = [3] numericalListME = [3, 2, 1, 0, 4, 5, 6] charList = [b"3"] charListME = [b"321", b"221", b"121", b"021", b"421", b"521", b"621"] class GI2ListTestCase(GetItemTestCase): # A more complex example title = "Rank-1,2 case 2 (lists)" numericalList = [3, 4] numericalListME = [[3, 2, 1, 0, 4, 5, 6], [2, 1, 0, 4, 5, 6, 7], [4, 3, 2, 1, 0, 4, 5], [3, 2, 1, 0, 4, 5, 6], [3, 2, 1, 0, 4, 5, 6]] charList = [b"a", b"b"] charListME = [ [b"321", b"221", b"121", b"021", b"421", b"521", b"621"], [b"21", b"21", b"11", b"02", b"42", b"21", b"61"], [b"31", b"21", b"12", b"21", b"41", b"51", b"621"], [b"321", b"221", b"121", b"021", b"421", b"521", b"621"], [b"3241", b"2321", b"13216", b"0621", b"4421", b"5421", b"a621"], [b"a321", b"s221", b"d121", b"g021", b"b421", b"5vvv21", b"6zxzxs21"], ] class GeneratorTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00a_single(self): """Testing generator access to Arrays, single elements (char)""" # Create the array under root and name 'somearray' a = self.charList arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element ga = [i for i in a] garr = [i for i in arr] if common.verbose: print("Result of original iterator:", ga) print("Result of read generator:", garr) self.assertEqual(ga, garr) def test00b_me(self): """Testing generator access to Arrays, multiple elements (char)""" # Create the array under root and name 'somearray' a = self.charListME arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element if isinstance(a[0], tuple): ga = [list(i) for i in a] else: ga = [i for i in a] garr = [i for i in arr] if common.verbose: print("Result of original iterator:", ga) print("Result of read generator:", garr) self.assertEqual(ga, garr) def test01a_single(self): """Testing generator access to Arrays, single elements (numeric)""" # Create the array under root and name 'somearray' a = self.numericalList arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element ga = [i for i in a] garr = [i for i in arr] if common.verbose: print("Result of original iterator:", ga) print("Result of read generator:", garr) self.assertEqual(ga, garr) def test01b_me(self): """Testing generator access to Arrays, multiple elements (numeric)""" # Create the array under root and name 'somearray' a = self.numericalListME arr = self.h5file.create_array(self.h5file.root, 'somearray', a, "Some array") # Get and compare an element if isinstance(a[0], tuple): ga = [list(i) for i in a] else: ga = [i for i in a] garr = [i for i in arr] if common.verbose: print("Result of original iterator:", ga) print("Result of read generator:", garr) self.assertEqual(ga, garr) class GE1ListTestCase(GeneratorTestCase): # Scalar case title = "Rank-1 case 1 (lists)" numericalList = [3] numericalListME = [3, 2, 1, 0, 4, 5, 6] charList = [b"3"] charListME = [b"321", b"221", b"121", b"021", b"421", b"521", b"621"] class GE2ListTestCase(GeneratorTestCase): # Scalar case title = "Rank-1,2 case 2 (lists)" numericalList = [3, 4] numericalListME = [[3, 2, 1, 0, 4, 5, 6], [2, 1, 0, 4, 5, 6, 7], [4, 3, 2, 1, 0, 4, 5], [3, 2, 1, 0, 4, 5, 6], [3, 2, 1, 0, 4, 5, 6]] charList = [b"a", b"b"] charListME = [ [b"321", b"221", b"121", b"021", b"421", b"521", b"621"], [b"21", b"21", b"11", b"02", b"42", b"21", b"61"], [b"31", b"21", b"12", b"21", b"41", b"51", b"621"], [b"321", b"221", b"121", b"021", b"421", b"521", b"621"], [b"3241", b"2321", b"13216", b"0621", b"4421", b"5421", b"a621"], [b"a321", b"s221", b"d121", b"g021", b"b421", b"5vvv21", b"6zxzxs21"], ] def suite(): theSuite = common.unittest.TestSuite() niter = 1 for i in range(niter): theSuite.addTest(common.unittest.makeSuite(Basic0DOneTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic0DTwoTestCase)) # theSuite.addTest(unittest.makeSuite(Basic1DZeroTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic1DOneTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic1DTwoTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic1DFourTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic2DTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic10DTestCase)) theSuite.addTest(common.unittest.makeSuite(GI1ListTestCase)) theSuite.addTest(common.unittest.makeSuite(GI2ListTestCase)) theSuite.addTest(common.unittest.makeSuite(GE1ListTestCase)) theSuite.addTest(common.unittest.makeSuite(GE2ListTestCase)) return theSuite if __name__ == '__main__': import sys common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_nestedtypes.py000066400000000000000000001453161416254111300216500ustar00rootroot00000000000000"""Test module for nested types under PyTables.""" import sys import itertools import numpy as np import tables as tb from tables.tests import common minRowIndex = 10 # This is the structure of the table used for testing (DON'T PANIC!): # # +-+---------------------------------+-----+----------+-+-+ # |x|Info |color|info |y|z| # | +-----+--+----------------+----+--+ +----+-----+ | | # | |value|y2|Info2 |name|z2| |Name|Value| | | # | | | +----+-----+--+--+ | | | | | | | # | | | |name|value|y3|z3| | | | | | | | # +-+-----+--+----+-----+--+--+----+--+-----+----+-----+-+-+ # # Please note that some fields are explicitly ordered while others are # ordered alphabetically by name. # The declaration of the nested table: class Info(tb.IsDescription): _v_pos = 3 Name = tb.StringCol(itemsize=2) Value = tb.ComplexCol(itemsize=16) class TestTDescr(tb.IsDescription): """A description that has several nested columns.""" x = tb.Int32Col(dflt=0, shape=2, pos=0) # 0 y = tb.Float64Col(dflt=1, shape=(2, 2)) z = tb.UInt8Col(dflt=1) color = tb.StringCol(itemsize=2, dflt=b" ", pos=2) info = Info() class Info(tb.IsDescription): # 1 _v_pos = 1 name = tb.StringCol(itemsize=2) value = tb.ComplexCol(itemsize=16, pos=0) # 0 y2 = tb.Float64Col(dflt=1, pos=1) # 1 z2 = tb.UInt8Col(dflt=1) class Info2(tb.IsDescription): y3 = tb.Time64Col(dflt=1, shape=2) z3 = tb.EnumCol({'r': 4, 'g': 2, 'b': 1}, 'r', 'int32', shape=2) name = tb.StringCol(itemsize=2) value = tb.ComplexCol(itemsize=16, shape=2) # The corresponding nested array description: testADescr = [ ('x', '(2,)int32'), ('Info', [ ('value', 'complex128'), ('y2', 'float64'), ('Info2', [ ('name', 'a2'), ('value', '(2,)complex128'), ('y3', '(2,)float64'), ('z3', '(2,)int32')]), ('name', 'a2'), ('z2', 'uint8')]), ('color', 'a2'), ('info', [ ('Name', 'a2'), ('Value', 'complex128')]), ('y', '(2,2)float64'), ('z', 'uint8')] # The corresponding nested array description (brief version): testADescr2 = [ ('x', '(2,)i4'), ('Info', [ ('value', '()c16'), ('y2', '()f8'), ('Info2', [ ('name', '()S2'), ('value', '(2,)c16'), ('y3', '(2,)f8'), ('z3', '(2,)i4')]), ('name', '()S2'), ('z2', '()u1')]), ('color', '()S2'), ('info', [ ('Name', '()S2'), ('Value', '()c16')]), ('y', '(2, 2)f8'), ('z', '()u1')] # A nested array for testing: testABuffer = [ # x Info color info y z # value y2 Info2 name z2 Name Value # name value y3 z3 ((3, 2), (6j, 6., ('nn', (6j, 4j), (6., 4.), (1, 2)), 'NN', 8), 'cc', ('NN', 6j), ((6., 4.), (6., 4.)), 8), ((4, 3), (7j, 7., ('oo', (7j, 5j), (7., 5.), (2, 1)), 'OO', 9), 'dd', ('OO', 7j), ((7., 5.), (7., 5.)), 9), ] testAData = np.array(testABuffer, dtype=testADescr) # The name of the column to be searched: testCondCol = 'Info/z2' # The name of a nested column (it can not be searched): testNestedCol = 'Info' # The condition to be applied on the column (all but the last row match it): testCondition = '(2 < col) & (col < 9)' def areDescriptionsEqual(desc1, desc2): """Are both `desc1` and `desc2` equivalent descriptions? The arguments may be description objects (``IsDescription``, ``Description``) or dictionaries. """ if isinstance(desc1, tb.Col): # This is a rough comparison but it suffices here. return (desc1.type == desc2.type and desc2.dtype == desc2.dtype and desc1._v_pos == desc2._v_pos # and desc1.dflt == desc2.dflt) and common.areArraysEqual(desc1.dflt, desc2.dflt)) if hasattr(desc1, '_v_colobjects'): # quacks like a Description cols1 = desc1._v_colobjects elif hasattr(desc1, 'columns'): # quacks like an IsDescription cols1 = desc1.columns else: # hope it quacks like a dictionary cols1 = desc1 if hasattr(desc2, '_v_colobjects'): # quacks like a Description cols2 = desc2._v_colobjects elif hasattr(desc2, 'columns'): # quacks like an IsDescription cols2 = desc2.columns else: # hope it quacks like a dictionary cols2 = desc2 if len(cols1) != len(cols2): return False for (colName, colobj1) in cols1.items(): colobj2 = cols2[colName] if colName == '_v_pos': # The comparison may not be quite exhaustive! return colobj1 == colobj2 if not areDescriptionsEqual(colobj1, colobj2): return False return True # Test creating nested column descriptions class DescriptionTestCase(common.PyTablesTestCase): _TestTDescr = TestTDescr _testADescr = testADescr _testADescr2 = testADescr2 _testAData = testAData def test00_instance(self): """Creating an instance of a nested description.""" self.assertTrue( areDescriptionsEqual(self._TestTDescr, self._TestTDescr()), "Table description does not match the given one.") def test01_instance(self): """Checking attrs of an instance of a nested description.""" descr = tb.description.Description(self._TestTDescr().columns) if common.verbose: print("Generated description:", descr._v_nested_descr) print("Should look like:", self._testADescr2) self.assertEqual(self._testADescr2, descr._v_nested_descr, "Description._v_nested_descr does not match.") # Test creating a nested table and opening it class CreateTestCase(common.TempFileMixin, common.PyTablesTestCase): _TestTDescr = TestTDescr _testABuffer = testABuffer _testAData = testAData def _checkColumns(self, cols, desc): """Check that `cols` has all the accessors for `self._TestTDescr`.""" # ``_desc`` is a leaf column and ``cols`` a ``Column``. if isinstance(desc, tb.Col): return isinstance(cols, tb.Column) # ``_desc`` is a description object and ``cols`` a ``Cols``. descColumns = desc._v_colobjects for colName in descColumns: if colName not in cols._v_colnames: return False if not self._checkColumns(cols._f_col(colName), descColumns[colName]): return False return True def _checkDescription(self, table): """Check that description of `table` matches `self._TestTDescr`.""" # Compare descriptions. self.assertTrue( areDescriptionsEqual(self._TestTDescr, table.description), "Table description does not match the given one.") # Check access to columns. self._checkColumns(table.cols, table.description) def _checkColinstances(self, table): """Check that ``colinstances`` and ``cols`` of `table` match.""" for colpathname in table.description._v_pathnames: self.assertTrue(table.colinstances[colpathname] is table.cols._f_col(colpathname)) def test00_create(self): """Creating a nested table.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) self._checkDescription(tbl) self._checkColinstances(tbl) def test01_open(self): """Opening a nested table.""" self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) self._reopen() tbl = self.h5file.root.test self._checkDescription(tbl) self._checkColinstances(tbl) def test02_NestedRecArrayCompat(self): """Creating a compatible nested record array``.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) nrarr = np.array(testABuffer, dtype=tbl.description._v_nested_descr) self.assertTrue(common.areArraysEqual(nrarr, self._testAData), "Can not create a compatible structured array.") def test03_NRA(self): """Creating a table from a nested record array object.""" tbl = self.h5file.create_table( '/', 'test', self._testAData, title=self._getMethodName()) tbl.flush() readAData = tbl.read() if common.verbose: print("Read data:", readAData) print("Should look like:", self._testAData) self.assertTrue(common.areArraysEqual(self._testAData, readAData), "Written and read values differ.") def test04_NRA2(self): """Creating a table from a generated nested record array object.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) readAData = tbl.read() tbl2 = self.h5file.create_table( '/', 'test2', readAData, title=self._getMethodName()) readAData2 = tbl2.read() self.assertTrue(common.areArraysEqual(self._testAData, readAData2), "Written and read values differ.") # Test writing data in a nested table class WriteTestCase(common.TempFileMixin, common.PyTablesTestCase): _TestTDescr = TestTDescr _testAData = testAData _testCondition = testCondition _testCondCol = testCondCol _testNestedCol = testNestedCol def _testCondVars(self, table): """Get condition variables for the given `table`.""" return {'col': table.cols._f_col(self._testCondCol)} def _testNestedCondVars(self, table): """Get condition variables for the given `table`.""" return {'col': table.cols._f_col(self._testNestedCol)} def _appendRow(self, row, index): """ Append the `index`-th row in `self._testAData` to `row`. Values are set field-by-field (be it nested or not). """ record = self._testAData[index] for fieldName in self._testAData.dtype.names: row[fieldName] = record[fieldName] row.append() def test00_append(self): """Appending a set of rows.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) tbl.flush() if self.reopen: self._reopen() tbl = self.h5file.root.test readAData = tbl.read() self.assertTrue(common.areArraysEqual(self._testAData, readAData), "Written and read values differ.") def test01_row(self): """Appending individual rows.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) row = tbl.row # Add the first row self._appendRow(row, 0) # Add the rest of the rows field by field. for i in range(1, len(self._testAData)): self._appendRow(row, i) tbl.flush() if self.reopen: self._reopen() tbl = self.h5file.root.test readAData = tbl.read() self.assertTrue(common.areArraysEqual(self._testAData, readAData), "Written and read values differ.") def test02_where(self): """Searching nested data.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) tbl.flush() if self.reopen: self._reopen() tbl = self.h5file.root.test searchedCoords = tbl.get_where_list( self._testCondition, self._testCondVars(tbl)) # All but the last row match the condition. searchedCoords.sort() self.assertEqual(searchedCoords.tolist(), list(range(len(self._testAData) - 1)), "Search returned incorrect results.") def test02b_whereAppend(self): """Searching nested data and appending it to another table.""" tbl1 = self.h5file.create_table( '/', 'test1', self._TestTDescr, title=self._getMethodName()) tbl1.append(self._testAData) tbl1.flush() tbl2 = self.h5file.create_table( '/', 'test2', self._TestTDescr, title=self._getMethodName()) tbl1.append_where( tbl2, self._testCondition, self._testCondVars(tbl1)) if self.reopen: self._reopen() tbl1 = self.h5file.root.test1 tbl2 = self.h5file.root.test2 searchedCoords = tbl2.get_where_list( self._testCondition, self._testCondVars(tbl2)) # All but the last row match the condition. searchedCoords.sort() self.assertEqual(searchedCoords.tolist(), list(range(len(self._testAData) - 1)), "Search returned incorrect results.") def test03_colscond(self): """Searching on a column with nested columns.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) tbl.flush() if self.reopen: self._reopen() tbl = self.h5file.root.test self.assertRaises( TypeError, tbl.get_where_list, self._testCondition, self._testNestedCondVars(tbl)) def test04_modifyColumn(self): """Modifying one single nested column (modify_column).""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) tbl.flush() nColumn = self._testNestedCol # Get the nested column data and swap the first and last rows. raTable = self._testAData.copy() raColumn = raTable[nColumn] # The next will not work until NestedRecords supports copies (raColumn[0], raColumn[-1]) = (raColumn[-1], raColumn[0]) # Write the resulting column and re-read the whole table. tbl.modify_column(colname=nColumn, column=raColumn) tbl.flush() if self.reopen: self._reopen() tbl = self.h5file.root.test raReadTable = tbl.read() if common.verbose: print("Table read:", raReadTable) print("Should look like:", raTable) # Compare it to the written one. self.assertTrue(common.areArraysEqual(raTable, raReadTable), "Written and read values differ.") def test05a_modifyColumns(self): """Modifying one nested column (modify_columns).""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) tbl.flush() nColumn = self._testNestedCol # Get the nested column data and swap the first and last rows. raTable = self._testAData.copy() raColumn = raTable[nColumn] (raColumn[0], raColumn[-1]) = (raColumn[-1].copy(), raColumn[0].copy()) newdtype = np.dtype([(nColumn, raTable.dtype.fields[nColumn][0])]) self.assertIsNotNone(newdtype) # Write the resulting column and re-read the whole table. tbl.modify_columns(names=[nColumn], columns=raColumn) tbl.flush() if self.reopen: self._reopen() tbl = self.h5file.root.test raReadTable = tbl.read() if common.verbose: print("Table read:", raReadTable) print("Should look like:", raTable) # Compare it to the written one. self.assertTrue(common.areArraysEqual(raTable, raReadTable), "Written and read values differ.") def test05b_modifyColumns(self): """Modifying two nested columns (modify_columns).""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) tbl.flush() # Get the nested column data and swap the first and last rows. colnames = ['x', 'color'] # Get the first two columns raCols = np.rec.fromarrays([ self._testAData['x'].copy(), self._testAData['color'].copy()], dtype=[('x', '(2,)i4'), ('color', 'a2')]) # descr=tbl.description._v_nested_descr[0:2]) # or... # names=tbl.description._v_nested_names[0:2], # formats=tbl.description._v_nested_formats[0:2]) (raCols[0], raCols[-1]) = (raCols[-1].copy(), raCols[0].copy()) # Write the resulting columns tbl.modify_columns(names=colnames, columns=raCols) tbl.flush() if self.reopen: self._reopen() tbl = self.h5file.root.test # Re-read the appropriate columns raCols2 = np.rec.fromarrays([tbl.cols._f_col('x'), tbl.cols._f_col('color')], dtype=raCols.dtype) if common.verbose: print("Table read:", raCols2) print("Should look like:", raCols) # Compare it to the written one. self.assertTrue(common.areArraysEqual(raCols, raCols2), "Written and read values differ.") def test06_modifyRows(self): """Checking modifying several rows at once (using nested rec array)""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) tbl.flush() # Get the nested record and swap the first and last rows. raTable = self._testAData.copy() (raTable[0], raTable[-1]) = (raTable[-1].copy(), raTable[0].copy()) # Write the resulting nested record and re-read the whole table. tbl.modify_rows(start=0, stop=2, rows=raTable) tbl.flush() if self.reopen: self._reopen() tbl = self.h5file.root.test raReadTable = tbl.read() if common.verbose: print("Table read:", raReadTable) print("Should look like:", raTable) # Compare it to the written one. self.assertTrue(common.areArraysEqual(raTable, raReadTable), "Written and read values differ.") def test07_index(self): """Checking indexes of nested columns.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName(), expectedrows=minRowIndex * 2) for i in range(minRowIndex): tbl.append(self._testAData) tbl.flush() coltoindex = tbl.cols._f_col(self._testCondCol) indexrows = coltoindex.create_index() self.assertIsNotNone(indexrows) if self.reopen: self._reopen() tbl = self.h5file.root.test coltoindex = tbl.cols._f_col(self._testCondCol) if common.verbose: print("Number of written rows:", tbl.nrows) print("Number of indexed rows:", coltoindex.index.nelements) # Check indexing flags: self.assertEqual(tbl.indexed, True, "Table not indexed") self.assertNotEqual(coltoindex.index, None, "Column not indexed") self.assertTrue(tbl.colindexed[ self._testCondCol], "Column not indexed") # Do a look-up for values searchedCoords = tbl.get_where_list( self._testCondition, self._testCondVars(tbl)) searchedCoords.sort() expectedCoords = np.arange(0, minRowIndex * 2, 2, tb.utils.SizeType) if common.verbose: print("Searched coords:", searchedCoords) print("Expected coords:", expectedCoords) # All even rows match the condition. self.assertEqual(searchedCoords.tolist(), expectedCoords.tolist(), "Search returned incorrect results.") def test08_setNestedField(self): """Checking modifying a nested field via natural naming.""" # See ticket #93 (http://www.pytables.org/trac/ticket/93). tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) tbl.flush() oldvalue = tbl.cols.Info.z2[0] tbl.cols.Info.z2[0] = oldvalue + 1 tbl.flush() if self.reopen: self._reopen() tbl = self.h5file.root.test newvalue = tbl.cols.Info.z2[0] self.assertEqual(newvalue, oldvalue + 1) class WriteNoReopen(WriteTestCase): reopen = 0 class WriteReopen(WriteTestCase): reopen = 1 class ReadTestCase(common.TempFileMixin, common.PyTablesTestCase): _TestTDescr = TestTDescr _testABuffer = testABuffer _testAData = testAData _testNestedCol = testNestedCol def test00a_repr(self): """Checking representation of a nested Table.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title="test00") tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test if common.verbose: print("str(tbl)-->", str(tbl)) print("repr(tbl)-->", repr(tbl)) self.assertEqual(str(tbl), "/test (Table(2,)) 'test00'") tblrepr = repr(tbl) # Remove the platform-dependent information (i.e. byteorder) tblrepr = "\n".join(tblrepr.split("\n")[:-2]) + "\n" template = """/test (Table(2,)) 'test00' description := { "x": Int32Col(shape=(2,), dflt=0, pos=0), "Info": { "value": ComplexCol(itemsize=16, shape=(), dflt=0j, pos=0), "y2": Float64Col(shape=(), dflt=1.0, pos=1), "Info2": { "name": StringCol(itemsize=2, shape=(), dflt=b'', pos=0), "value": ComplexCol(itemsize=16, shape=(2,), dflt=0j, pos=1), "y3": Time64Col(shape=(2,), dflt=1.0, pos=2), "z3": EnumCol(enum=Enum({%(value)s}), dflt='%(default)s', base=Int32Atom(shape=(), dflt=0), shape=(2,), pos=3)}, "name": StringCol(itemsize=2, shape=(), dflt=b'', pos=3), "z2": UInt8Col(shape=(), dflt=1, pos=4)}, "color": StringCol(itemsize=2, shape=(), dflt=b' ', pos=2), "info": { "Name": StringCol(itemsize=2, shape=(), dflt=b'', pos=0), "Value": ComplexCol(itemsize=16, shape=(), dflt=0j, pos=1)}, "y": Float64Col(shape=(2, 2), dflt=1.0, pos=4), "z": UInt8Col(shape=(), dflt=1, pos=5)} """ # The problem here is that the order in which items are stored in a # dict can't be assumed to be stable. # From python 3.3 on it is actually no more stable since the # "Hash randomization" feature is enable by default. # # For this reason we generate a representation string for each of the # prmutations of the Enum items. # # Also the default value of enum types is not preserved in HDF5. # It is assumed that the default value is the first one in the array # of Enum names and hence it is also affected by the issue related to # the "Hash randomization" feature. # # Also in this case it is genereted a representation string for each # of the possible default values. enums = [ ', '.join(items) for items in itertools.permutations( ("'r': 4", "'b': 1", "'g': 2")) ] defaults = ('r', 'b', 'g') values = [ template % {'value': v, 'default': d} for v, d in itertools.product(enums, defaults) ] self.assertIn(tblrepr, values) def test00b_repr(self): """Checking representation of a root Column.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title="test00") tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test if common.verbose: print("str(tbl.cols.y)-->'%s'" % str(tbl.cols.y)) print("repr(tbl.cols.y)-->'%s'" % repr(tbl.cols.y)) self.assertEqual(str(tbl.cols.y), "/test.cols.y (Column(2, 2, 2), float64, idx=None)") self.assertEqual(repr(tbl.cols.y), "/test.cols.y (Column(2, 2, 2), float64, idx=None)") def test00c_repr(self): """Checking representation of a nested Column.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title="test00") tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test if common.verbose: print("str(tbl.cols.Info.z2)-->'%s'" % str(tbl.cols.Info.z2)) print("repr(tbl.cols.Info.z2)-->'%s'" % repr(tbl.cols.Info.z2)) self.assertEqual(str(tbl.cols.Info.z2), "/test.cols.Info.z2 (Column(2,), uint8, idx=None)") self.assertEqual(repr(tbl.cols.Info.z2), "/test.cols.Info.z2 (Column(2,), uint8, idx=None)") def test01_read(self): """Checking Table.read with subgroups with a range index with step.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test nrarr = np.rec.array(testABuffer, dtype=tbl.description._v_nested_descr) tblcols = tbl.read(start=0, step=2, field='Info') nrarrcols = nrarr['Info'][0::2] if common.verbose: print("Read cols:", tblcols) print("Should look like:", nrarrcols) self.assertTrue(common.areArraysEqual(nrarrcols, tblcols), "Original array are retrieved doesn't match.") def test01_read_out_arg(self): tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test nrarr = np.rec.array(testABuffer, dtype=tbl.description._v_nested_descr) # When reading an entire nested column, the output array must contain # all fields in the table. The output buffer will contain the contents # of all fields. The selected column alone will be returned from the # method call. all_cols = np.empty(1, tbl.dtype) tblcols = tbl.read(start=0, step=2, field='Info', out=all_cols) nrarrcols = nrarr['Info'][0::2] if common.verbose: print("Read cols:", tblcols) print("Should look like:", nrarrcols) self.assertTrue(common.areArraysEqual(nrarrcols, tblcols), "Original array are retrieved doesn't match.") self.assertTrue(common.areArraysEqual(nrarr[0::2], all_cols), "Output buffer does not match full table.") def test02_read(self): """Checking Table.read with a nested Column.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test tblcols = tbl.read(start=0, step=2, field='Info/value') nrarr = np.rec.array(testABuffer, dtype=tbl.description._v_nested_descr) nrarrcols = nrarr['Info']['value'][0::2] self.assertTrue(common.areArraysEqual(nrarrcols, tblcols), "Original array are retrieved doesn't match.") def test02_read_out_arg(self): """Checking Table.read with a nested Column.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test tblcols = np.empty(1, dtype='c16') tbl.read(start=0, step=2, field='Info/value', out=tblcols) nrarr = np.rec.array(testABuffer, dtype=tbl.description._v_nested_descr) nrarrcols = nrarr['Info']['value'][0::2] self.assertTrue(common.areArraysEqual(nrarrcols, tblcols), "Original array are retrieved doesn't match.") class ReadNoReopen(ReadTestCase): reopen = 0 class ReadReopen(ReadTestCase): reopen = 1 # Checking the Table.Cols accessor class ColsTestCase(common.TempFileMixin, common.PyTablesTestCase): _TestTDescr = TestTDescr _testABuffer = testABuffer _testAData = testAData _testNestedCol = testNestedCol def test00a_repr(self): """Checking string representation of Cols.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title="test00") if self.reopen: self._reopen() tbl = self.h5file.root.test if common.verbose: print("str(tbl.cols)-->", str(tbl.cols)) print("repr(tbl.cols)-->", repr(tbl.cols)) self.assertEqual(str(tbl.cols), "/test.cols (Cols), 6 columns") try: self.assertEqual(repr(tbl.cols), """/test.cols (Cols), 6 columns x (Column(0, 2), ('int32',(2,))) Info (Cols(), Description) color (Column(0,), |S2) info (Cols(), Description) y (Column(0, 2, 2), ('float64',(2, 2))) z (Column(0,), uint8) """ ) except AssertionError: self.assertEqual(repr(tbl.cols), """/test.cols (Cols), 6 columns x (Column(0, 2), ('{}', (2,))) Info (Cols(), Description) color (Column(0,), |S2) info (Cols(), Description) y (Column(0, 2, 2), ('{}', (2, 2))) z (Column(0,), uint8) """.format(np.int32(0).dtype.str, np.float64(0).dtype.str)) def test00b_repr(self): """Checking string representation of nested Cols.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) if self.reopen: self._reopen() tbl = self.h5file.root.test if common.verbose: print("str(tbl.cols.Info)-->", str(tbl.cols.Info)) print("repr(tbl.cols.Info)-->", repr(tbl.cols.Info)) self.assertEqual(str( tbl.cols.Info), "/test.cols.Info (Cols), 5 columns") self.assertEqual(repr(tbl.cols.Info), """/test.cols.Info (Cols), 5 columns value (Column(0,), complex128) y2 (Column(0,), float64) Info2 (Cols(), Description) name (Column(0,), |S2) z2 (Column(0,), uint8) """) def test01a_f_col(self): """Checking cols._f_col() with a subgroup.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) if self.reopen: self._reopen() tbl = self.h5file.root.test tblcol = tbl.cols._f_col(self._testNestedCol) if common.verbose: print("Column group name:", tblcol._v_desc._v_pathname) self.assertEqual(tblcol._v_desc._v_pathname, self._testNestedCol, "Column group name doesn't match.") def test01b_f_col(self): """Checking cols._f_col() with a column.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) if self.reopen: self._reopen() tbl = self.h5file.root.test tblcol = tbl.cols._f_col(self._testNestedCol + "/name") if common.verbose: print("Column name:", tblcol.name) self.assertEqual(tblcol.name, "name", "Column name doesn't match.") def test01c_f_col(self): """Checking cols._f_col() with a nested subgroup.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tblcol = tbl.cols._f_col(self._testNestedCol + "/Info2") if common.verbose: print("Column group name:", tblcol._v_desc._v_pathname) self.assertEqual(tblcol._v_desc._v_pathname, self._testNestedCol + "/Info2", "Column group name doesn't match.") def test02a__len__(self): """Checking cols.__len__() in root level.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) if self.reopen: self._reopen() tbl = self.h5file.root.test length = len(tbl.cols) if common.verbose: print("Column group length:", length) self.assertEqual(length, len(tbl.colnames), "Column group length doesn't match.") def test02b__len__(self): """Checking cols.__len__() in subgroup level.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) if self.reopen: self._reopen() tbl = self.h5file.root.test length = len(tbl.cols.Info) if common.verbose: print("Column group length:", length) self.assertEqual(length, len(tbl.cols.Info._v_colnames), "Column group length doesn't match.") def test03a__getitem__(self): """Checking cols.__getitem__() with a single index.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test nrarr = np.array(testABuffer, dtype=tbl.description._v_nested_descr) tblcols = tbl.cols[1] nrarrcols = nrarr[1] if common.verbose: print("Read cols:", tblcols) print("Should look like:", nrarrcols) self.assertTrue(common.areArraysEqual(nrarrcols, tblcols), "Original array are retrieved doesn't match.") def test03b__getitem__(self): """Checking cols.__getitem__() with a range index.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test nrarr = np.array(testABuffer, dtype=tbl.description._v_nested_descr) tblcols = tbl.cols[0:2] nrarrcols = nrarr[0:2] if common.verbose: print("Read cols:", tblcols) print("Should look like:", nrarrcols) self.assertTrue(common.areArraysEqual(nrarrcols, tblcols), "Original array are retrieved doesn't match.") def test03c__getitem__(self): """Checking cols.__getitem__() with a range index with step.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test nrarr = np.array(testABuffer, dtype=tbl.description._v_nested_descr) tblcols = tbl.cols[0::2] nrarrcols = nrarr[0::2] if common.verbose: print("Read cols:", tblcols) print("Should look like:", nrarrcols) self.assertTrue(common.areArraysEqual(nrarrcols, tblcols), "Original array are retrieved doesn't match.") def test04a__getitem__(self): """Checking cols.__getitem__() with subgroups with a single index.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test nrarr = np.array(testABuffer, dtype=tbl.description._v_nested_descr) tblcols = tbl.cols._f_col('Info')[1] nrarrcols = nrarr['Info'][1] if common.verbose: print("Read cols:", tblcols) print("Should look like:", nrarrcols) self.assertTrue(common.areArraysEqual(nrarrcols, tblcols), "Original array are retrieved doesn't match.") def test04b__getitem__(self): """Checking cols.__getitem__() with subgroups with a range index.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test nrarr = np.array(testABuffer, dtype=tbl.description._v_nested_descr) tblcols = tbl.cols._f_col('Info')[0:2] nrarrcols = nrarr['Info'][0:2] if common.verbose: print("Read cols:", tblcols) print("Should look like:", nrarrcols) self.assertTrue(common.areArraysEqual(nrarrcols, tblcols), "Original array are retrieved doesn't match.") def test04c__getitem__(self): """Checking cols.__getitem__() with subgroups with a range index with step.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test nrarr = np.array(testABuffer, dtype=tbl.description._v_nested_descr) tblcols = tbl.cols._f_col('Info')[0::2] nrarrcols = nrarr['Info'][0::2] if common.verbose: print("Read cols:", tblcols) print("Should look like:", nrarrcols) self.assertTrue(common.areArraysEqual(nrarrcols, tblcols), "Original array are retrieved doesn't match.") def test05a__getitem__(self): """Checking cols.__getitem__() with a column with a single index.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test nrarr = np.array(testABuffer, dtype=tbl.description._v_nested_descr) tblcols = tbl.cols._f_col('Info/value')[1] nrarrcols = nrarr['Info']['value'][1] if common.verbose: print("Read cols:", tblcols) print("Should look like:", nrarrcols) self.assertEqual(nrarrcols, tblcols, "Original array are retrieved doesn't match.") def test05b__getitem__(self): """Checking cols.__getitem__() with a column with a range index.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test nrarr = np.array(testABuffer, dtype=tbl.description._v_nested_descr) tblcols = tbl.cols._f_col('Info/value')[0:2] nrarrcols = nrarr['Info']['value'][0:2] if common.verbose: print("Read cols:", tblcols) print("Should look like:", nrarrcols) self.assertTrue(common.areArraysEqual(nrarrcols, tblcols), "Original array are retrieved doesn't match.") def test05c__getitem__(self): """Checking cols.__getitem__() with a column with a range index with step.""" tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test nrarr = np.array(testABuffer, dtype=tbl.description._v_nested_descr) tblcols = tbl.cols._f_col('Info/value')[0::2] nrarrcols = nrarr['Info']['value'][0::2] if common.verbose: print("Read cols:", tblcols) print("Should look like:", nrarrcols) self.assertTrue(common.areArraysEqual(nrarrcols, tblcols), "Original array are retrieved doesn't match.") def test_01a__iter__(self): tbl = self.h5file.create_table( '/', 'test', self._TestTDescr, title=self._getMethodName()) tbl.append(self._testAData) if self.reopen: self._reopen() tbl = self.h5file.root.test nrarr = np.array(testABuffer, dtype=tbl.description._v_nested_descr) row_num = 0 for item in tbl.cols.Info.value: self.assertEqual(item, nrarr['Info']['value'][row_num]) row_num += 1 self.assertEqual(row_num, len(nrarr)) class ColsNoReopen(ColsTestCase): reopen = 0 class ColsReopen(ColsTestCase): reopen = 1 class Nested(tb.IsDescription): uid = tb.IntCol(pos=1) value = tb.FloatCol(pos=2) class A_Candidate(tb.IsDescription): nested1 = Nested() nested2 = Nested() class B_Candidate(tb.IsDescription): nested1 = Nested nested2 = Nested class C_Candidate(tb.IsDescription): nested1 = Nested() nested2 = Nested Dnested = { 'uid': tb.IntCol(pos=1), 'value': tb.FloatCol(pos=2), } D_Candidate = { "nested1": Dnested, "nested2": Dnested, } E_Candidate = { "nested1": Nested, "nested2": Dnested, } F_Candidate = { "nested1": Nested(), "nested2": Dnested, } # Checking several nested columns declared in the same way class SameNestedTestCase(common.TempFileMixin, common.PyTablesTestCase): correct_names = [ '', # The root of columns 'nested1', 'nested1/uid', 'nested1/value', 'nested2', 'nested2/uid', 'nested2/value', ] def test01a(self): """Checking same nested columns (instance flavor).""" tbl = self.h5file.create_table( '/', 'test', A_Candidate, title=self._getMethodName()) if self.reopen: self._reopen() tbl = self.h5file.root.test names = [col._v_pathname for col in tbl.description._f_walk( type="All")] if common.verbose: print("Pathnames of columns:", names) print("Should look like:", self.correct_names) self.assertEqual(names, self.correct_names, "Column nested names doesn't match.") def test01b(self): """Checking same nested columns (class flavor).""" tbl = self.h5file.create_table( '/', 'test', B_Candidate, title=self._getMethodName()) if self.reopen: self._reopen() tbl = self.h5file.root.test names = [col._v_pathname for col in tbl.description._f_walk( type="All")] if common.verbose: print("Pathnames of columns:", names) print("Should look like:", self.correct_names) self.assertEqual(names, self.correct_names, "Column nested names doesn't match.") def test01c(self): """Checking same nested columns (mixed instance/class flavor).""" tbl = self.h5file.create_table( '/', 'test', C_Candidate, title=self._getMethodName()) if self.reopen: self._reopen() tbl = self.h5file.root.test names = [col._v_pathname for col in tbl.description._f_walk( type="All")] if common.verbose: print("Pathnames of columns:", names) print("Should look like:", self.correct_names) self.assertEqual(names, self.correct_names, "Column nested names doesn't match.") def test01d(self): """Checking same nested columns (dictionary flavor).""" tbl = self.h5file.create_table( '/', 'test', D_Candidate, title=self._getMethodName()) if self.reopen: self._reopen() tbl = self.h5file.root.test names = [col._v_pathname for col in tbl.description._f_walk( type="All")] if common.verbose: print("Pathnames of columns:", names) print("Should look like:", self.correct_names) self.assertEqual(names, self.correct_names, "Column nested names doesn't match.") def test01e(self): """Checking same nested columns (mixed dictionary/class flavor).""" tbl = self.h5file.create_table( '/', 'test', E_Candidate, title=self._getMethodName()) if self.reopen: self._reopen() tbl = self.h5file.root.test names = [col._v_pathname for col in tbl.description._f_walk( type="All")] if common.verbose: print("Pathnames of columns:", names) print("Should look like:", self.correct_names) self.assertEqual(names, self.correct_names, "Column nested names doesn't match.") def test01f(self): """Checking same nested columns (mixed dictionary/instance flavor).""" tbl = self.h5file.create_table( '/', 'test', F_Candidate, title=self._getMethodName()) if self.reopen: self._reopen() tbl = self.h5file.root.test names = [col._v_pathname for col in tbl.description._f_walk( type="All")] if common.verbose: print("Pathnames of columns:", names) print("Should look like:", self.correct_names) self.assertEqual(names, self.correct_names, "Column nested names doesn't match.") def test02a(self): """Indexing two simple columns under the same nested column.""" desc = { 'nested': { 'i1': tb.Int32Col(), 'i2': tb.Int32Col() } } i1 = 'nested/i1' i2 = 'nested/i2' tbl = self.h5file.create_table( '/', 'test', desc, title=self._getMethodName()) row = tbl.row for i in range(1000): row[i1] = i row[i2] = i * 2 row.append() tbl.flush() cols = { 'i1': tbl.cols.nested.i1, 'i2': tbl.cols.nested.i2, } cols['i1'].create_index() cols['i2'].create_index() if self.reopen: self._reopen() tbl = self.h5file.root.test # Redefine the cols dictionary cols = {'i1': tbl.cols.nested.i1, 'i2': tbl.cols.nested.i2, } i1res = [r[i1] for r in tbl.where('i1 < 10', cols)] i2res = [r[i2] for r in tbl.where('i2 < 10', cols)] if common.verbose: print("Retrieved values (i1):", i1res) print("Should look like:", list(range(10))) print("Retrieved values (i2):", i2res) print("Should look like:", list(range(0, 10, 2))) self.assertEqual(i1res, list(range(10)), "Select for nested column (i1) doesn't match.") self.assertEqual(i2res, list(range(0, 10, 2)), "Select for nested column (i2) doesn't match.") def test02b(self): """Indexing two simple columns under the same (very) nested column.""" desc = { 'nested1': { 'nested2': { 'nested3': { 'i1': tb.Int32Col(), 'i2': tb.Int32Col() } } } } i1 = 'nested1/nested2/nested3/i1' i2 = 'nested1/nested2/nested3/i2' tbl = self.h5file.create_table( '/', 'test', desc, title=self._getMethodName()) row = tbl.row for i in range(1000): row[i1] = i row[i2] = i * 2 row.append() tbl.flush() cols = {'i1': tbl.cols.nested1.nested2.nested3.i1, 'i2': tbl.cols.nested1.nested2.nested3.i2, } cols['i1'].create_index() cols['i2'].create_index() if self.reopen: self._reopen() tbl = self.h5file.root.test # Redefine the cols dictionary cols = {'i1': tbl.cols.nested1.nested2.nested3.i1, 'i2': tbl.cols.nested1.nested2.nested3.i2, } i1res = [r[i1] for r in tbl.where('i1 < 10', cols)] i2res = [r[i2] for r in tbl.where('i2 < 10', cols)] if common.verbose: print("Retrieved values (i1):", i1res) print("Should look like:", list(range(10))) print("Retrieved values (i2):", i2res) print("Should look like:", list(range(0, 10, 2))) self.assertEqual(i1res, list(range(10)), "Select for nested column (i1) doesn't match.") self.assertEqual(i2res, list(range(0, 10, 2)), "Select for nested column (i2) doesn't match.") class SameNestedNoReopen(SameNestedTestCase): reopen = 0 class SameNestedReopen(SameNestedTestCase): reopen = 1 class NestedTypesWithGaps(common.TestFileMixin, common.PyTablesTestCase): h5fname = common.test_filename('nested-type-with-gaps.h5') correct_descr = """{ "float": Float32Col(shape=(), dflt=0.0, pos=0), "compound": { "char": Int8Col(shape=(), dflt=0, pos=0), "double": Float64Col(shape=(), dflt=0.0, pos=1)}}""" def test01(self): """Opening a table with nested types with gaps.""" tbl = self.h5file.get_node('/nestedtype') type_descr = repr(tbl.description) if common.verbose: print("Type size with gaps:", tbl.description._v_itemsize) print("And should be: 16") print("Representation of the nested type:\n", type_descr) print("And should be:\n", self.correct_descr) print("Here are the offsets: ", tbl.description._v_offsets) self.assertEqual(tbl.description._v_itemsize, 16) self.assertEqual(type_descr, self.correct_descr) if common.verbose: print("Great! Nested types with gaps recognized correctly.") def suite(): """Return a test suite consisting of all the test cases in the module.""" theSuite = common.unittest.TestSuite() niter = 1 # common.heavy = 1 # uncomment this only for testing purposes for i in range(niter): theSuite.addTest(common.unittest.makeSuite(DescriptionTestCase)) theSuite.addTest(common.unittest.makeSuite(CreateTestCase)) theSuite.addTest(common.unittest.makeSuite(WriteNoReopen)) theSuite.addTest(common.unittest.makeSuite(WriteReopen)) theSuite.addTest(common.unittest.makeSuite(ColsNoReopen)) theSuite.addTest(common.unittest.makeSuite(ColsReopen)) theSuite.addTest(common.unittest.makeSuite(ReadNoReopen)) theSuite.addTest(common.unittest.makeSuite(ReadReopen)) theSuite.addTest(common.unittest.makeSuite(SameNestedNoReopen)) theSuite.addTest(common.unittest.makeSuite(SameNestedReopen)) theSuite.addTest(common.unittest.makeSuite(NestedTypesWithGaps)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_numpy.py000066400000000000000000001434301416254111300204440ustar00rootroot00000000000000import sys import tempfile from pathlib import Path import numpy as np import tables as tb from tables.tests import common typecodes = ['b', 'h', 'i', 'l', 'q', 'f', 'd'] # UInt64 checking disabled on win platforms # because this type is not supported if sys.platform != 'win32': typecodes += ['B', 'H', 'I', 'L', 'Q', 'F', 'D'] else: typecodes += ['B', 'H', 'I', 'L', 'F', 'D'] typecodes += ['b1'] # boolean if hasattr(tb, 'Float16Atom'): typecodes.append('e') if hasattr(tb, 'Float96Atom') or hasattr(tb, 'Float128Atom'): typecodes.append('g') if hasattr(tb, 'Complex192Atom') or hasattr(tb, 'Conplex256Atom'): typecodes.append('G') byteorder = {'little': '<', 'big': '>'}[sys.byteorder] class BasicTestCase(common.PyTablesTestCase): """Basic test for all the supported typecodes present in NumPy. All of them are included on PyTables. """ endiancheck = 0 def WriteRead(self, testArray): if common.verbose: print('\n', '-=' * 30) print("Running test for array with typecode '%s'" % testArray.dtype.char, end=' ') print("for class check:", self.title) # Create an instance of HDF5 Table self.h5fname = tempfile.mktemp(".h5") try: with tb.open_file(self.h5fname, mode="w") as self.h5file: self.root = self.h5file.root # Create the array under root and name 'somearray' a = testArray self.h5file.create_array(self.root, 'somearray', a, "Some array") # Re-open the file in read-only mode with tb.open_file(self.h5fname, mode="r") as self.h5file: self.root = self.h5file.root # Read the saved array b = self.root.somearray.read() # For cases that read returns a python type instead of a # numpy type if not hasattr(b, "shape"): b = np.np.array(b, dtype=a.dtype.str) # Compare them. They should be equal. # if not allequal(a,b, "numpy") and common.verbose: if common.verbose: print("Array written:", a) print("Array written shape:", a.shape) print("Array written itemsize:", a.itemsize) print("Array written type:", a.dtype.char) print("Array read:", b) print("Array read shape:", b.shape) print("Array read itemsize:", b.itemsize) print("Array read type:", b.dtype.char) type_ = self.root.somearray.atom.type # Check strictly the array equality self.assertEqual(type(a), type(b)) self.assertEqual(a.shape, b.shape) self.assertEqual(a.shape, self.root.somearray.shape) self.assertEqual(a.dtype, b.dtype) if a.dtype.char[0] == "S": self.assertEqual(type_, "string") else: self.assertEqual(a.dtype.base.name, type_) self.assertTrue(common.allequal(a, b, "numpy")) finally: # Then, delete the file if Path(self.h5fname).is_file(): Path(self.h5fname).unlink() def test00_char(self): """Data integrity during recovery (character objects)""" a = np.array(self.tupleChar, 'S'+str(len(self.tupleChar))) self.WriteRead(a) def test01_char_nc(self): """Data integrity during recovery (non-contiguous character objects)""" a = np.array(self.tupleChar, 'S'+str(len(self.tupleChar))) if a.shape == (): b = a # We cannot use the indexing notation else: b = a[::2] # Ensure that this numpy string is non-contiguous if a.shape[0] > 2: self.assertEqual(b.flags['CONTIGUOUS'], False) self.WriteRead(b) def test02_types(self): """Data integrity during recovery (numerical types)""" for typecode in typecodes: if self.tupleInt.shape: a = self.tupleInt.astype(typecode) else: # shape is the empty tuple () a = np.array(self.tupleInt, dtype=typecode) self.WriteRead(a) def test03_types_nc(self): """Data integrity during recovery (non-contiguous numerical types)""" for typecode in typecodes: if self.tupleInt.shape: a = self.tupleInt.astype(typecode) else: # shape is the empty tuple () a = np.array(self.tupleInt, dtype=typecode) # This should not be tested for the rank-0 case if len(a.shape) == 0: raise common.unittest.SkipTest b = a[::2] # Ensure that this array is non-contiguous (for non-trivial case) if a.shape[0] > 2: self.assertEqual(b.flags['CONTIGUOUS'], False) self.WriteRead(b) class Basic0DOneTestCase(BasicTestCase): # Rank-0 case title = "Rank-0 case 1" tupleInt = np.array(3) tupleChar = "4" class Basic0DTwoTestCase(BasicTestCase): # Rank-0 case title = "Rank-0 case 2" tupleInt = np.array(33) tupleChar = "44" class Basic1DOneTestCase(BasicTestCase): # 1D case title = "Rank-1 case 1" tupleInt = np.array((3,)) tupleChar = ("a",) class Basic1DTwoTestCase(BasicTestCase): # 1D case title = "Rank-1 case 2" tupleInt = np.array((0, 4)) tupleChar = ("aaa",) class Basic1DThreeTestCase(BasicTestCase): # 1D case title = "Rank-1 case 3" tupleInt = np.array((3, 4, 5)) tupleChar = ("aaaa", "bbb",) class Basic2DTestCase(BasicTestCase): # 2D case title = "Rank-2 case 1" # tupleInt = reshape(np.array(np.arange((4)**2)), (4,)*2) tupleInt = np.ones((4,)*2) tupleChar = [["aaa", "ddddd"], ["d", "ss"], ["s", "tt"]] class Basic10DTestCase(BasicTestCase): # 10D case title = "Rank-10 case 1" # tupleInt = reshape(np.array(np.arange((2)**10)), (2,)*10) tupleInt = np.ones((2,)*10) # tupleChar = reshape(np.array([1],dtype="S1"),(1,)*10) # The next tuple consumes far more time, so this # test should be run in common.heavy mode. tupleChar = np.array(tupleInt, dtype="S1") # class Basic32DTestCase(BasicTestCase): # # 32D case (maximum) # tupleInt = reshape(np.array((22,)), (1,)*32) # # Strings seems to be very slow with somewhat large dimensions # # This should not be run unless the numarray people address this problem # # F. Alted 2006-01-04 # tupleChar = np.array(tupleInt, dtype="S1") class GroupsArrayTestCase(common.TempFileMixin, common.PyTablesTestCase): """This test class checks combinations of arrays with groups. It also uses arrays ranks which ranges until 10. """ def test00_iterativeGroups(self): """Checking combinations of arrays with groups It also uses arrays ranks which ranges until 10. """ if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_iterativeGroups..." % self.__class__.__name__) # Get the root group group = self.h5file.root i = 1 for typecode in typecodes: # Create an array of typecode, with incrementally bigger ranges a = np.ones((2,) * i, typecode) # Save it on the HDF5 file dsetname = 'array_' + typecode if common.verbose: print("Creating dataset:", group._g_join(dsetname)) self.h5file.create_array(group, dsetname, a, "Large array") # Create a new group group = self.h5file.create_group(group, 'group' + str(i)) # increment the range for next iteration i += 1 self._reopen() # Get the root group group = self.h5file.root # Get the metadata on the previosly saved arrays for i in range(1, len(typecodes)): # Create an array for later comparison a = np.ones((2,) * i, typecodes[i - 1]) # Get the dset object hanging from group dset = getattr(group, 'array_' + typecodes[i-1]) # Get the actual array b = dset.read() if not common.allequal(a, b, "numpy") and common.verbose: print("Array a original. Shape: ==>", a.shape) print("Array a original. Data: ==>", a) print("Info from dataset:", dset._v_pathname) print(" shape ==>", dset.shape, end=' ') print(" dtype ==> %s" % dset.dtype) print("Array b read from file. Shape: ==>", b.shape, end=' ') print(". Type ==> %s" % b.dtype.char) self.assertEqual(a.shape, b.shape) if np.dtype('l').itemsize == 4: if (a.dtype.char == "i" or a.dtype.char == "l"): # Special expection. We have no way to distinguish between # "l" and "i" typecode, and we can consider them the same # to all practical effects self.assertIn(b.dtype.char, ("l", "i")) elif (a.dtype.char == "I" or a.dtype.char == "L"): # Special expection. We have no way to distinguish between # "L" and "I" typecode, and we can consider them the same # to all practical effects self.assertIn(b.dtype.char, ("L", "I")) else: self.assertTrue(common.allequal(a, b, "numpy")) elif np.dtype('l').itemsize == 8: if (a.dtype.char == "q" or a.dtype.char == "l"): # Special expection. We have no way to distinguish between # "q" and "l" typecode in 64-bit platforms, and we can # consider them the same to all practical effects self.assertIn(b.dtype.char, ("l", "q")) elif (a.dtype.char == "Q" or a.dtype.char == "L"): # Special expection. We have no way to distinguish between # "Q" and "L" typecode in 64-bit platforms, and we can # consider them the same to all practical effects self.assertIn(b.dtype.char, ("L", "Q")) else: self.assertTrue(common.allequal(a, b, "numpy")) # Iterate over the next group group = getattr(group, 'group' + str(i)) def test01_largeRankArrays(self): """Checking creation of large rank arrays (0 < rank <= 32) It also uses arrays ranks which ranges until maxrank. """ # maximum level of recursivity (deepest group level) achieved: # maxrank = 32 (for a effective maximum rank of 32) # This limit is due to a limit in the HDF5 library. minrank = 1 maxrank = 32 if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_largeRankArrays..." % self.__class__.__name__) print("Maximum rank for tested arrays:", maxrank) group = self.h5file.root if common.verbose: print("Rank array writing progress: ", end=' ') for rank in range(minrank, maxrank + 1): # Create an array of integers, with incrementally bigger ranges a = np.ones((1,) * rank, 'i') if common.verbose: print("%3d," % (rank), end=' ') self.h5file.create_array(group, "array", a, "Rank: %s" % rank) group = self.h5file.create_group(group, 'group' + str(rank)) # Flush the buffers self.h5file.flush() self._reopen() group = self.h5file.root if common.verbose: print() print("Rank array reading progress: ") # Get the metadata on the previosly saved arrays for rank in range(minrank, maxrank + 1): # Create an array for later comparison a = np.ones((1,) * rank, 'i') # Get the actual array b = group.array.read() if common.verbose: print("%3d," % (rank), end=' ') if not a.tolist() == b.tolist() and common.verbose: dset = group.array print("Info from dataset:", dset._v_pathname) print(" Shape: ==>", dset.shape, end=' ') print(" typecode ==> %c" % dset.typecode) print("Array b read from file. Shape: ==>", b.shape, end=' ') print(". Type ==> %c" % b.dtype.char) self.assertEqual(a.shape, b.shape) if a.dtype.char == "i": # Special expection. We have no way to distinguish between # "l" and "i" typecode, and we can consider them the same # to all practical effects self.assertIn(b.dtype.char, ("l", "i")) else: self.assertEqual(a.dtype.char, b.dtype.char) self.assertEqual(a, b) # Iterate over the next group group = self.h5file.get_node(group, 'group' + str(rank)) if common.verbose: print() # This flush the stdout buffer # Test Record class class Record(tb.IsDescription): var1 = tb.StringCol(itemsize=4, dflt=b"abcd", pos=0) var2 = tb.StringCol(itemsize=1, dflt=b"a", pos=1) var3 = tb.BoolCol(dflt=1) var4 = tb.Int8Col(dflt=1) var5 = tb.UInt8Col(dflt=1) var6 = tb.Int16Col(dflt=1) var7 = tb.UInt16Col(dflt=1) var8 = tb.Int32Col(dflt=1) var9 = tb.UInt32Col(dflt=1) var10 = tb.Int64Col(dflt=1) var11 = tb.Float32Col(dflt=1.0) var12 = tb.Float64Col(dflt=1.0) var13 = tb.ComplexCol(itemsize=8, dflt=(1.+0.j)) var14 = tb.ComplexCol(itemsize=16, dflt=(1.+0.j)) if hasattr(tb, 'Float16Col'): var15 = tb.Float16Col(dflt=1.0) if hasattr(tb, 'Float96Col'): var16 = tb.Float96Col(dflt=1.0) if hasattr(tb, 'Float128Col'): var17 = tb.Float128Col(dflt=1.0) if hasattr(tb, 'Complex196Col'): var18 = tb.ComplexCol(itemsize=24, dflt=(1.+0.j)) if hasattr(tb, 'Complex256Col'): var19 = tb.ComplexCol(itemsize=32, dflt=(1.+0.j)) class TableReadTestCase(common.TempFileMixin, common.PyTablesTestCase): nrows = 100 def setUp(self): super().setUp() # Create an instance of an HDF5 Table table = self.h5file.create_table(self.h5file.root, 'table', Record) for i in range(self.nrows): table.row.append() # Fill 100 rows with default values self._reopen(mode='a') def test01_readTableChar(self): """Checking column conversion into NumPy in read(). Char flavor """ table = self.h5file.root.table table.flavor = "numpy" for colname in table.colnames: numcol = table.read(field=colname) typecol = table.coltypes[colname] itemsizecol = table.description._v_dtypes[colname].base.itemsize nctypecode = numcol.dtype.char if typecol == "string": if itemsizecol > 1: orignumcol = np.array(['abcd']*self.nrows, dtype='S4') else: orignumcol = np.array(['a']*self.nrows, dtype='S1') if common.verbose: print("Typecode of NumPy column read:", nctypecode) print("Should look like:", 'c') print("Itemsize of column:", itemsizecol) print("Shape of NumPy column read:", numcol.shape) print("Should look like:", orignumcol.shape) print("First 3 elements of read col:", numcol[:3]) # Check that both NumPy objects are equal self.assertTrue(common.allequal(numcol, orignumcol, "numpy")) def test01_readTableNum(self): """Checking column conversion into NumPy in read(). NumPy flavor """ table = self.h5file.root.table table.flavor = "numpy" for colname in table.colnames: numcol = table.read(field=colname) typecol = table.coltypes[colname] nctypecode = np.sctypeDict[numcol.dtype.char[0]] if typecol != "string": if common.verbose: print("Typecode of NumPy column read:", nctypecode) print("Should look like:", typecol) orignumcol = np.ones(shape=self.nrows, dtype=numcol.dtype.char) # Check that both NumPy objects are equal self.assertTrue(common.allequal(numcol, orignumcol, "numpy")) def test02_readCoordsChar(self): """Column conversion into NumPy in readCoords(). Chars """ table = self.h5file.root.table table.flavor = "numpy" coords = [1, 2, 3] self.nrows = len(coords) for colname in table.colnames: numcol = table.read_coordinates(coords, field=colname) typecol = table.coltypes[colname] itemsizecol = table.description._v_dtypes[colname].base.itemsize nctypecode = numcol.dtype.char if typecol == "string": if itemsizecol > 1: orignumcol = np.array(['abcd']*self.nrows, dtype='S4') else: orignumcol = np.array(['a']*self.nrows, dtype='S1') if common.verbose: print("Typecode of NumPy column read:", nctypecode) print("Should look like:", 'c') print("Itemsize of column:", itemsizecol) print("Shape of NumPy column read:", numcol.shape) print("Should look like:", orignumcol.shape) print("First 3 elements of read col:", numcol[:3]) # Check that both NumPy objects are equal self.assertTrue(common.allequal(numcol, orignumcol, "numpy")) def test02_readCoordsNum(self): """Column conversion into NumPy in read_coordinates(). NumPy. """ table = self.h5file.root.table table.flavor = "numpy" coords = [1, 2, 3] self.nrows = len(coords) for colname in table.colnames: numcol = table.read_coordinates(coords, field=colname) typecol = table.coltypes[colname] type_ = numcol.dtype.type if typecol != "string": if typecol == "int64": return if common.verbose: print("Type of read NumPy column:", type_) print("Should look like:", typecol) orignumcol = np.ones(shape=self.nrows, dtype=numcol.dtype.char) # Check that both NumPy objects are equal self.assertTrue(common.allequal(numcol, orignumcol, "numpy")) def test03_getIndexNumPy(self): """Getting table rows specifyied as NumPy scalar integers.""" table = self.h5file.root.table coords = np.array([1, 2, 3], dtype='int8') for colname in table.colnames: numcol = [table[coord][colname] for coord in coords] typecol = table.coltypes[colname] if typecol != "string": if typecol == "int64": return numcol = np.array(numcol, typecol) if common.verbose: type_ = numcol.dtype.type print("Type of read NumPy column:", type_) print("Should look like:", typecol) orignumcol = np.ones(shape=len(numcol), dtype=numcol.dtype.char) # Check that both NumPy objects are equal self.assertTrue(common.allequal(numcol, orignumcol, "numpy")) def test04_setIndexNumPy(self): """Setting table rows specifyied as NumPy integers.""" self._reopen(mode='a') table = self.h5file.root.table table.flavor = "numpy" coords = np.array([1, 2, 3], dtype='int8') # Modify row 1 # From PyTables 2.0 on, assignments to records can be done # only as tuples (see http://projects.scipy.org/scipy/numpy/ticket/315) # table[coords[0]] = ["aasa","x"]+[232]*12 n = len(Record.columns) - 2 table[coords[0]] = tuple(["aasa", "x"]+[232]*n) # XXX # record = list(table[coords[0]]) record = table.read(coords[0], coords[0] + 1) if common.verbose: print("Original row:\n" "['aasa', 'x', True, -24, 232, 232, 232, 232, 232L, " "232, 232.0, 232.0, (232 + 0j), (232+0j), 232.0, " "(232+0j)]\n") print("Read row:\n", record) self.assertEqual(record['var1'], b'aasa') self.assertEqual(record['var2'], b'x') self.assertEqual(record['var3'], True) self.assertEqual(record['var4'], -24) self.assertEqual(record['var7'], 232) # The declaration of the nested table: class Info(tb.IsDescription): _v_pos = 3 Name = tb.StringCol(itemsize=2) Value = tb.ComplexCol(itemsize=16) class TestTDescr(tb.IsDescription): """A description that has several nested columns.""" x = tb.Int32Col(dflt=0, shape=2, pos=0) # 0 y = tb.FloatCol(dflt=1, shape=(2, 2)) z = tb.UInt8Col(dflt=1) z3 = tb.EnumCol({'r': 4, 'g': 2, 'b': 1}, 'r', 'int32', shape=2) color = tb.StringCol(itemsize=4, dflt=b"ab", pos=2) info = Info() class Info(tb.IsDescription): # 1 _v_pos = 1 name = tb.StringCol(itemsize=2) value = tb.ComplexCol(itemsize=16, pos=0) # 0 y2 = tb.FloatCol(pos=1) # 1 z2 = tb.UInt8Col() class Info2(tb.IsDescription): y3 = tb.Time64Col(shape=2) name = tb.StringCol(itemsize=2) value = tb.ComplexCol(itemsize=16, shape=2) class TableNativeFlavorTestCase(common.TempFileMixin, common.PyTablesTestCase): nrows = 100 def setUp(self): super().setUp() # Create an instance of an HDF5 Table table = self.h5file.create_table(self.h5file.root, 'table', TestTDescr, expectedrows=self.nrows) table.flavor = "numpy" for i in range(self.nrows): table.row.append() # Fill 100 rows with default values table.flush() def test01a_basicTableRead(self): """Checking the return of a NumPy in read().""" if self.close: self._reopen(mode='a') table = self.h5file.root.table data = table[:] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the value of some columns # A flat column col = table.cols.x[:3] self.assertIsInstance(col, np.ndarray) npcol = np.zeros((3, 2), dtype="int32") self.assertTrue(common.allequal(col, npcol, "numpy")) # A nested column col = table.cols.Info[:3] self.assertIsInstance(col, np.ndarray) dtype = [('value', 'c16'), ('y2', 'f8'), ('Info2', [('name', 'S2'), ('value', 'c16', (2,)), ('y3', 'f8', (2,))]), ('name', 'S2'), ('z2', 'u1')] npcol = np.zeros((3,), dtype=dtype) self.assertEqual(col.dtype.descr, npcol.dtype.descr) if common.verbose: print("col-->", col) print("npcol-->", npcol) # A copy() is needed in case the buffer can be in different segments self.assertEqual(bytes(col.copy().data), bytes(npcol.data)) def test01b_basicTableRead(self): """Checking the return of a NumPy in read() (strided version).""" if self.close: self._reopen(mode='a') table = self.h5file.root.table data = table[::3] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the value of some columns # A flat column col = table.cols.x[:9:3] self.assertIsInstance(col, np.ndarray) npcol = np.zeros((3, 2), dtype="int32") self.assertTrue(common.allequal(col, npcol, "numpy")) # A nested column col = table.cols.Info[:9:3] self.assertIsInstance(col, np.ndarray) dtype = [('value', '%sc16' % byteorder), ('y2', '%sf8' % byteorder), ('Info2', [('name', '|S2'), ('value', '%sc16' % byteorder, (2,)), ('y3', '%sf8' % byteorder, (2,))]), ('name', '|S2'), ('z2', '|u1')] npcol = np.zeros((3,), dtype=dtype) self.assertEqual(col.dtype.descr, npcol.dtype.descr) if common.verbose: print("col-->", col) print("npcol-->", npcol) # A copy() is needed in case the buffer can be in different segments self.assertEqual(bytes(col.copy().data), bytes(npcol.data)) def test02_getWhereList(self): """Checking the return of NumPy in get_where_list method.""" if self.close: self._reopen(mode='a') table = self.h5file.root.table data = table.get_where_list('z == 1') if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check that all columns have been selected self.assertEqual(len(data), 100) # Finally, check that the contents are ok self.assertTrue(common.allequal( data, np.arange(100, dtype="i8"), "numpy")) def test03a_readWhere(self): """Checking the return of NumPy in read_where method (strings).""" table = self.h5file.root.table table.cols.color.create_index() if self.close: self._reopen(mode='a') table = self.h5file.root.table data = table.read_where('color == b"ab"') if common.verbose: print("Type of read:", type(data)) print("Length of the data read:", len(data)) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check that all columns have been selected self.assertEqual(len(data), self.nrows) def test03b_readWhere(self): """Checking the return of NumPy in read_where method (numeric).""" table = self.h5file.root.table table.cols.z.create_index() if self.close: self._reopen(mode='a') table = self.h5file.root.table data = table.read_where('z == 0') if common.verbose: print("Type of read:", type(data)) print("Length of the data read:", len(data)) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check that all columns have been selected self.assertEqual(len(data), 0) def test04a_createTable(self): """Checking the Table creation from a numpy recarray.""" dtype = [('value', '%sc16' % byteorder), ('y2', '%sf8' % byteorder), ('Info2', [('name', '|S2'), ('value', '%sc16' % byteorder, (2,)), ('y3', '%sf8' % byteorder, (2,))]), ('name', '|S2'), ('z2', '|u1')] npdata = np.zeros((3,), dtype=dtype) table = self.h5file.create_table(self.h5file.root, 'table2', npdata) if self.close: self._reopen(mode='a') table = self.h5file.root.table2 data = table[:] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) print("Length of the data read:", len(data)) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the type self.assertEqual(data.dtype.descr, npdata.dtype.descr) if common.verbose: print("npdata-->", npdata) print("data-->", data) # A copy() is needed in case the buffer would be in different segments self.assertEqual(bytes(data.copy().data), bytes(npdata.data)) def test04b_appendTable(self): """Checking appending a numpy recarray.""" table = self.h5file.root.table npdata = table[3:6] table.append(npdata) if self.close: self._reopen(mode='a') table = self.h5file.root.table data = table[-3:] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("Last 3 elements of read:", data[-3:]) print("Length of the data read:", len(data)) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the type self.assertEqual(data.dtype.descr, npdata.dtype.descr) if common.verbose: print("npdata-->", npdata) print("data-->", data) # A copy() is needed in case the buffer would be in different segments self.assertEqual(bytes(data.copy().data), bytes(npdata.data)) def test05a_assignColumn(self): """Checking assigning to a column.""" table = self.h5file.root.table table.cols.z[:] = np.zeros((100,), dtype='u1') if self.close: self._reopen(mode='a') table = self.h5file.root.table data = table.cols.z[:] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) print("Length of the data read:", len(data)) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check that all columns have been selected self.assertEqual(len(data), 100) # Finally, check that the contents are ok self.assertTrue(common.allequal( data, np.zeros((100,), dtype="u1"), "numpy")) def test05b_modifyingColumns(self): """Checking modifying several columns at once.""" table = self.h5file.root.table xcol = np.ones((3, 2), 'int32') ycol = np.zeros((3, 2, 2), 'float64') zcol = np.zeros((3,), 'uint8') table.modify_columns(3, 6, 1, [xcol, ycol, zcol], ['x', 'y', 'z']) if self.close: self._reopen(mode='a') table = self.h5file.root.table data = table.cols.y[3:6] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) print("Length of the data read:", len(data)) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the type self.assertEqual(data.dtype.descr, ycol.dtype.descr) if common.verbose: print("ycol-->", ycol) print("data-->", data) # A copy() is needed in case the buffer would be in different segments self.assertEqual(data.copy().data, ycol.data) def test05c_modifyingColumns(self): """Checking modifying several columns using a single numpy buffer.""" table = self.h5file.root.table dtype = [('x', 'i4', (2,)), ('y', 'f8', (2, 2)), ('z', 'u1')] nparray = np.zeros((3,), dtype=dtype) table.modify_columns(3, 6, 1, nparray, ['x', 'y', 'z']) if self.close: self._reopen(mode='a') table = self.h5file.root.table ycol = np.zeros((3, 2, 2), 'float64') data = table.cols.y[3:6] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) print("Length of the data read:", len(data)) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the type self.assertEqual(data.dtype.descr, ycol.dtype.descr) if common.verbose: print("ycol-->", ycol) print("data-->", data) # A copy() is needed in case the buffer would be in different segments self.assertEqual(data.copy().data, ycol.data) def test06a_assignNestedColumn(self): """Checking assigning a nested column (using modify_column).""" table = self.h5file.root.table dtype = [('value', '%sc16' % byteorder), ('y2', '%sf8' % byteorder), ('Info2', [('name', '|S2'), ('value', '%sc16' % byteorder, (2,)), ('y3', '%sf8' % byteorder, (2,))]), ('name', '|S2'), ('z2', '|u1')] npdata = np.zeros((3,), dtype=dtype) data = table.cols.Info[3:6] table.modify_column(3, 6, 1, column=npdata, colname='Info') if self.close: self._reopen(mode='a') table = self.h5file.root.table data = table.cols.Info[3:6] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) print("Length of the data read:", len(data)) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the type self.assertEqual(data.dtype.descr, npdata.dtype.descr) if common.verbose: print("npdata-->", npdata) print("data-->", data) # A copy() is needed in case the buffer would be in different segments self.assertEqual(bytes(data.copy().data), bytes(npdata.data)) def test06b_assignNestedColumn(self): """Checking assigning a nested column (using the .cols accessor).""" table = self.h5file.root.table dtype = [('value', '%sc16' % byteorder), ('y2', '%sf8' % byteorder), ('Info2', [('name', '|S2'), ('value', '%sc16' % byteorder, (2,)), ('y3', '%sf8' % byteorder, (2,))]), ('name', '|S2'), ('z2', '|u1')] npdata = np.zeros((3,), dtype=dtype) # self.assertRaises(NotImplementedError, # table.cols.Info.__setitem__, slice(3,6,1), npdata) table.cols.Info[3:6] = npdata if self.close: self._reopen(mode='a') table = self.h5file.root.table data = table.cols.Info[3:6] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) print("Length of the data read:", len(data)) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the type self.assertEqual(data.dtype.descr, npdata.dtype.descr) if common.verbose: print("npdata-->", npdata) print("data-->", data) # A copy() is needed in case the buffer would be in different segments self.assertEqual(bytes(data.copy().data), bytes(npdata.data)) def test07a_modifyingRows(self): """Checking modifying several rows at once (using modify_rows).""" table = self.h5file.root.table # Read a chunk of the table chunk = table[0:3] # Modify it somewhat chunk['y'][:] = -1 table.modify_rows(3, 6, 1, rows=chunk) if self.close: self._reopen(mode='a') table = self.h5file.root.table ycol = np.zeros((3, 2, 2), 'float64')-1 data = table.cols.y[3:6] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) print("Length of the data read:", len(data)) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the type self.assertEqual(data.dtype.descr, ycol.dtype.descr) if common.verbose: print("ycol-->", ycol) print("data-->", data) self.assertTrue(common.allequal(ycol, data, "numpy")) def test07b_modifyingRows(self): """Checking modifying several rows at once (using cols accessor).""" table = self.h5file.root.table # Read a chunk of the table chunk = table[0:3] # Modify it somewhat chunk['y'][:] = -1 table.cols[3:6] = chunk if self.close: self._reopen(mode='a') table = self.h5file.root.table # Check that some column has been actually modified ycol = np.zeros((3, 2, 2), 'float64')-1 data = table.cols.y[3:6] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) print("Length of the data read:", len(data)) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the type self.assertEqual(data.dtype.descr, ycol.dtype.descr) if common.verbose: print("ycol-->", ycol) print("data-->", data) self.assertTrue(common.allequal(ycol, data, "numpy")) def test08a_modifyingRows(self): """Checking modifying just one row at once (using modify_rows).""" table = self.h5file.root.table # Read a chunk of the table chunk = table[3:4] # Modify it somewhat chunk['y'][:] = -1 table.modify_rows(6, 7, 1, chunk) if self.close: self._reopen(mode='a') table = self.h5file.root.table # Check that some column has been actually modified ycol = np.zeros((2, 2), 'float64')-1 data = table.cols.y[6] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) print("Length of the data read:", len(data)) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the type self.assertEqual(data.dtype.descr, ycol.dtype.descr) if common.verbose: print("ycol-->", ycol) print("data-->", data) self.assertTrue(common.allequal(ycol, data, "numpy")) def test08b_modifyingRows(self): """Checking modifying just one row at once (using cols accessor).""" table = self.h5file.root.table # Read a chunk of the table chunk = table[3:4] # Modify it somewhat chunk['y'][:] = -1 table.cols[6] = chunk if self.close: self._reopen(mode='a') table = self.h5file.root.table # Check that some column has been actually modified ycol = np.zeros((2, 2), 'float64')-1 data = table.cols.y[6] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) print("Length of the data read:", len(data)) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the type self.assertEqual(data.dtype.descr, ycol.dtype.descr) if common.verbose: print("ycol-->", ycol) print("data-->", data) self.assertTrue(common.allequal(ycol, data, "numpy")) def test09a_getStrings(self): """Checking the return of string columns with spaces.""" if self.close: self._reopen(mode='a') table = self.h5file.root.table rdata = table.get_where_list('color == b"ab"') data = table.read_coordinates(rdata) if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check that all columns have been selected self.assertEqual(len(data), 100) # Finally, check that the contents are ok for idata in data['color']: self.assertEqual(idata, np.array("ab", dtype="|S4")) def test09b_getStrings(self): """Checking the return of string columns with spaces. (modify) """ if self.close: self._reopen(mode='a') table = self.h5file.root.table for i in range(50): table.cols.color[i] = "a " table.flush() data = table[:] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check that all columns have been selected self.assertEqual(len(data), 100) # Finally, check that the contents are ok for i in range(100): idata = data['color'][i] if i >= 50: self.assertEqual(idata, np.array("ab", dtype="|S4")) else: self.assertEqual(idata, np.array("a ", dtype="|S4")) def test09c_getStrings(self): """Checking the return of string columns with spaces. (append) """ if self.close: self._reopen(mode='a') table = self.h5file.root.table row = table.row for i in range(50): row["color"] = "a " # note the trailing spaces row.append() table.flush() if self.close: self.h5file.close() self.h5file = tb.open_file(self.h5fname, "a") data = self.h5file.root.table[:] if common.verbose: print("Type of read:", type(data)) print("Description of the record:", data.dtype.descr) print("First 3 elements of read:", data[:3]) # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check that all columns have been selected self.assertEqual(len(data), 150) # Finally, check that the contents are ok for i in range(150): idata = data['color'][i] if i < 100: self.assertEqual(idata, np.array("ab", dtype="|S4")) else: self.assertEqual(idata, np.array("a ", dtype="|S4")) class TableNativeFlavorOpenTestCase(TableNativeFlavorTestCase): close = False class TableNativeFlavorCloseTestCase(TableNativeFlavorTestCase): close = True class AttributesTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() # Create an instance of an HDF5 Table self.h5file.create_group(self.h5file.root, 'group') def test01_writeAttribute(self): """Checking the creation of a numpy attribute.""" group = self.h5file.root.group g_attrs = group._v_attrs g_attrs.numpy1 = np.zeros((1, 1), dtype='int16') if self.close: self._reopen(mode='a') group = self.h5file.root.group g_attrs = group._v_attrs # Check that we can retrieve a numpy object data = g_attrs.numpy1 npcomp = np.zeros((1, 1), dtype='int16') # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the type self.assertEqual(data.dtype.descr, npcomp.dtype.descr) if common.verbose: print("npcomp-->", npcomp) print("data-->", data) self.assertTrue(common.allequal(npcomp, data, "numpy")) def test02_updateAttribute(self): """Checking the modification of a numpy attribute.""" group = self.h5file.root.group g_attrs = group._v_attrs g_attrs.numpy1 = np.zeros((1, 2), dtype='int16') if self.close: self._reopen(mode='a') group = self.h5file.root.group g_attrs = group._v_attrs # Update this attribute g_attrs.numpy1 = np.ones((1, 2), dtype='int16') # Check that we can retrieve a numpy object data = g_attrs.numpy1 npcomp = np.ones((1, 2), dtype='int16') # Check that both NumPy objects are equal self.assertIsInstance(data, np.ndarray) # Check the type self.assertEqual(data.dtype.descr, npcomp.dtype.descr) if common.verbose: print("npcomp-->", npcomp) print("data-->", data) self.assertTrue(common.allequal(npcomp, data, "numpy")) class AttributesOpenTestCase(AttributesTestCase): close = 0 class AttributesCloseTestCase(AttributesTestCase): close = 1 class StrlenTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() # Create an instance of an HDF5 Table group = self.h5file.create_group(self.h5file.root, 'group') tablelayout = {'Text': tb.StringCol(itemsize=1000), } self.table = self.h5file.create_table(group, 'table', tablelayout) self.table.flavor = 'numpy' row = self.table.row row['Text'] = 'Hello Francesc!' # XXX: check unicode --> bytes row.append() row['Text'] = 'Hola Francesc!' # XXX: check unicode --> bytes row.append() self.table.flush() def test01(self): """Checking the lengths of strings (read field).""" if self.close: self._reopen(mode='a') self.table = self.h5file.root.group.table # Get both strings str1 = self.table.col('Text')[0] str2 = self.table.col('Text')[1] if common.verbose: print("string1-->", str1) print("string2-->", str2) # Check that both NumPy objects are equal self.assertEqual(len(str1), len(b'Hello Francesc!')) self.assertEqual(len(str2), len(b'Hola Francesc!')) self.assertEqual(str1, b'Hello Francesc!') self.assertEqual(str2, b'Hola Francesc!') def test02(self): """Checking the lengths of strings (read recarray).""" if self.close: self._reopen(mode='a') self.table = self.h5file.root.group.table # Get both strings str1 = self.table[:]['Text'][0] str2 = self.table[:]['Text'][1] # Check that both NumPy objects are equal self.assertEqual(len(str1), len(b'Hello Francesc!')) self.assertEqual(len(str2), len(b'Hola Francesc!')) self.assertEqual(str1, b'Hello Francesc!') self.assertEqual(str2, b'Hola Francesc!') def test03(self): """Checking the lengths of strings (read recarray, row by row).""" if self.close: self._reopen(mode='a') self.table = self.h5file.root.group.table # Get both strings str1 = self.table[0]['Text'] str2 = self.table[1]['Text'] # Check that both NumPy objects are equal self.assertEqual(len(str1), len(b'Hello Francesc!')) self.assertEqual(len(str2), len(b'Hola Francesc!')) self.assertEqual(str1, b'Hello Francesc!') self.assertEqual(str2, b'Hola Francesc!') class StrlenOpenTestCase(StrlenTestCase): close = 0 class StrlenCloseTestCase(StrlenTestCase): close = 1 def suite(): theSuite = common.unittest.TestSuite() niter = 1 # theSuite.addTest(unittest.makeSuite(StrlenOpenTestCase)) # theSuite.addTest(unittest.makeSuite(Basic0DOneTestCase)) # theSuite.addTest(unittest.makeSuite(GroupsArrayTestCase)) for i in range(niter): theSuite.addTest(common.unittest.makeSuite(Basic0DOneTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic0DTwoTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic1DOneTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic1DTwoTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic1DThreeTestCase)) theSuite.addTest(common.unittest.makeSuite(Basic2DTestCase)) theSuite.addTest(common.unittest.makeSuite(GroupsArrayTestCase)) theSuite.addTest(common.unittest.makeSuite(TableReadTestCase)) theSuite.addTest( common.unittest.makeSuite(TableNativeFlavorOpenTestCase)) theSuite.addTest( common.unittest.makeSuite(TableNativeFlavorCloseTestCase)) theSuite.addTest(common.unittest.makeSuite(AttributesOpenTestCase)) theSuite.addTest(common.unittest.makeSuite(AttributesCloseTestCase)) theSuite.addTest(common.unittest.makeSuite(StrlenOpenTestCase)) theSuite.addTest(common.unittest.makeSuite(StrlenCloseTestCase)) if common.heavy: theSuite.addTest(common.unittest.makeSuite(Basic10DTestCase)) # The 32 dimensions case takes forever to run!! # theSuite.addTest(unittest.makeSuite(Basic32DTestCase)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_queries.py000066400000000000000000001263221416254111300207520ustar00rootroot00000000000000"""Test module for queries on datasets.""" import re import sys import warnings import functools import numpy as np import tables as tb from tables.tests import common # Data parameters # --------------- row_period = 50 """Maximum number of unique rows before they start cycling.""" md_shape = (2, 2) """Shape of multidimensional fields.""" _maxnvalue = row_period + np.prod(md_shape, dtype=tb.utils.SizeType) - 1 _strlen = int(np.log10(_maxnvalue-1)) + 1 str_format = '%%0%dd' % _strlen """Format of string values.""" small_blocksizes = (300, 60, 20, 5) # small_blocksizes = (512, 128, 32, 4) # for manual testing only """Sensible parameters for indexing with small blocksizes.""" # Type information # ---------------- type_info = { 'bool': (np.bool_, bool), 'int8': (np.int8, int), 'uint8': (np.uint8, int), 'int16': (np.int16, int), 'uint16': (np.uint16, int), 'int32': (np.int32, int), 'uint32': (np.uint32, int), 'int64': (np.int64, int), 'uint64': (np.uint64, int), 'float32': (np.float32, float), 'float64': (np.float64, float), 'complex64': (np.complex64, complex), 'complex128': (np.complex128, complex), 'time32': (np.int32, int), 'time64': (np.float64, float), 'enum': (np.uint8, int), # just for these tests 'string': ('S%s' % _strlen, np.string_), # just for these tests } """NumPy and Numexpr type for each PyTables type that will be tested.""" # globals dict for eval() func_info = {'log10': np.log10, 'log': np.log, 'exp': np.exp, 'abs': np.abs, 'sqrt': np.sqrt, 'sin': np.sin, 'cos': np.cos, 'tan': np.tan, 'arcsin': np.arcsin, 'arccos': np.arccos, 'arctan': np.arctan} """functions and NumPy.ufunc() for each function that will be tested.""" if hasattr(np, 'float16'): type_info['float16'] = (np.float16, float) # if hasattr(numpy, 'float96'): # type_info['float96'] = (numpy.float96, float) # if hasattr(numpy, 'float128'): # type_info['float128'] = (numpy.float128, float) # if hasattr(numpy, 'complex192'): # type_info['complex192'] = (numpy.complex192, complex) # if hasattr(numpy, 'complex256'): # type_info['complex256'] = (numpy.complex256, complex) sctype_from_type = {type_: info[0] for (type_, info) in type_info.items()} """Maps PyTables types to NumPy scalar types.""" nxtype_from_type = {type_: info[1] for (type_, info) in type_info.items()} """Maps PyTables types to Numexpr types.""" heavy_types = frozenset(['uint8', 'int16', 'uint16', 'float32', 'complex64']) """PyTables types to be tested only in heavy mode.""" enum = tb.Enum({'n%d' % i: i for i in range(_maxnvalue)}) """Enumerated type to be used in tests.""" # Table description # ----------------- def append_columns(classdict, shape=()): """Append a ``Col`` of each PyTables data type to the `classdict`. A column of a certain TYPE gets called ``c_TYPE``. The number of added columns is returned. """ heavy = common.heavy for (itype, type_) in enumerate(sorted(type_info)): if not heavy and type_ in heavy_types: continue # skip heavy type in non-heavy mode colpos = itype + 1 colname = 'c_%s' % type_ if type_ == 'enum': base = tb.Atom.from_sctype(sctype_from_type[type_]) col = tb.EnumCol(enum, enum(0), base, shape=shape, pos=colpos) else: sctype = sctype_from_type[type_] dtype = np.dtype((sctype, shape)) col = tb.Col.from_dtype(dtype, pos=colpos) classdict[colname] = col ncols = colpos return ncols def nested_description(classname, pos, shape=()): """Return a nested column description with all PyTables data types. A column of a certain TYPE gets called ``c_TYPE``. The nested column will be placed in the position indicated by `pos`. """ classdict = {} append_columns(classdict, shape=shape) classdict['_v_pos'] = pos return type(classname, (tb.IsDescription,), classdict) def table_description(classname, nclassname, shape=()): """Return a table description for testing queries. The description consists of all PyTables data types, both in the top level and in the ``c_nested`` nested column. A column of a certain TYPE gets called ``c_TYPE``. An extra integer column ``c_extra`` is also provided. If a `shape` is given, it will be used for all columns. Finally, an extra indexed column ``c_idxextra`` is added as well in order to provide some basic tests for multi-index queries. """ classdict = {} colpos = append_columns(classdict, shape) ndescr = nested_description(nclassname, colpos, shape=shape) classdict['c_nested'] = ndescr colpos += 1 extracol = tb.IntCol(shape=shape, pos=colpos) classdict['c_extra'] = extracol colpos += 1 idxextracol = tb.IntCol(shape=shape, pos=colpos) classdict['c_idxextra'] = idxextracol colpos += 1 return type(classname, (tb.IsDescription,), classdict) TableDescription = table_description( 'TableDescription', 'NestedDescription') """Unidimensional table description for testing queries.""" MDTableDescription = table_description( 'MDTableDescription', 'MDNestedDescription', shape=md_shape) """Multidimensional table description for testing queries.""" # Table data # ---------- table_data = {} """Cached table data for a given shape and number of rows.""" # Data is cached because computing it row by row is quite slow. Hop! def fill_table(table, shape, nrows): """Fill the given `table` with `nrows` rows of data. Values in the i-th row (where 0 <= i < `row_period`) for a multidimensional field with M elements span from i to i + M-1. For subsequent rows, values repeat cyclically. The same goes for the ``c_extra`` column, but values range from -`row_period`/2 to +`row_period`/2. """ # Reuse already computed data if possible. tdata = table_data.get((shape, nrows)) if tdata is not None: table.append(tdata) table.flush() return heavy = common.heavy size = int(np.prod(shape, dtype=tb.utils.SizeType)) row, value = table.row, 0 for nrow in range(nrows): data = np.arange(value, value + size).reshape(shape) for (type_, sctype) in sctype_from_type.items(): if not heavy and type_ in heavy_types: continue # skip heavy type in non-heavy mode colname = 'c_%s' % type_ ncolname = 'c_nested/%s' % colname if type_ == 'bool': coldata = data > (row_period // 2) elif type_ == 'string': sdata = [str_format % x for x in range(value, value + size)] coldata = np.array(sdata, dtype=sctype).reshape(shape) else: coldata = np.asarray(data, dtype=sctype) row[ncolname] = row[colname] = coldata row['c_extra'] = data - (row_period // 2) row['c_idxextra'] = data - (row_period // 2) row.append() value += 1 if value == row_period: value = 0 table.flush() # Make computed data reusable. tdata = table.read() table_data[(shape, nrows)] = tdata class SilentlySkipTest(common.unittest.SkipTest): pass # Base test cases # --------------- class BaseTableQueryTestCase(common.TempFileMixin, common.PyTablesTestCase): """Base test case for querying tables. Sub-classes must define the following attributes: ``tableDescription`` The description of the table to be created. ``shape`` The shape of data fields in the table. ``nrows`` The number of data rows to be generated for the table. Sub-classes may redefine the following attributes: ``indexed`` Whether columns shall be indexed, if possible. Default is not to index them. ``optlevel`` The level of optimisation of column indexes. Default is 0. """ indexed = False optlevel = 0 colNotIndexable_re = re.compile(r"\bcan not be indexed\b") condNotBoolean_re = re.compile(r"\bdoes not have a boolean type\b") def create_indexes(self, colname, ncolname, extracolname): if not self.indexed: return try: kind = self.kind common.verbosePrint( f"* Indexing ``{colname}`` columns. Type: {kind}.") for acolname in [colname, ncolname, extracolname]: acolumn = self.table.colinstances[acolname] acolumn.create_index( kind=self.kind, optlevel=self.optlevel, _blocksizes=small_blocksizes, _testmode=True) except TypeError as te: if self.colNotIndexable_re.search(str(te)): raise SilentlySkipTest( "Columns of this type can not be indexed.") raise except NotImplementedError: raise SilentlySkipTest( "Indexing columns of this type is not supported yet.") def setUp(self): super().setUp() self.table = self.h5file.create_table( '/', 'test', self.tableDescription, expectedrows=self.nrows) fill_table(self.table, self.shape, self.nrows) class ScalarTableMixin: tableDescription = TableDescription shape = () class MDTableMixin: tableDescription = MDTableDescription shape = md_shape # Test cases on query data # ------------------------ operators = [ None, '~', '<', '<=', '==', '!=', '>=', '>', ('<', '<='), ('>', '>=')] """Comparison operators to check with different types.""" heavy_operators = frozenset(['~', '<=', '>=', '>', ('>', '>=')]) """Comparison operators to be tested only in heavy mode.""" left_bound = row_period // 4 """Operand of left side operator in comparisons with operator pairs.""" right_bound = row_period * 3 // 4 """Operand of right side operator in comparisons with operator pairs.""" func_bound = 0.8 # must be <1 for trig functions to be able to fail """Operand of right side operator in comparisons with functions. """ extra_conditions = [ '', # uses one index '& ((c_extra + 1) < 0)', # uses one index '| (c_idxextra > 0)', # uses two indexes '| ((c_idxextra > 0) | ((c_extra + 1) > 0))', # can't use indexes ] """Extra conditions to append to comparison conditions.""" class TableDataTestCase(BaseTableQueryTestCase): """Base test case for querying table data. Automatically created test method names have the format ``test_XNNNN``, where ``NNNN`` is the zero-padded test number and ``X`` indicates whether the test belongs to the light (``l``) or heavy (``h``) set. """ _testfmt_light = 'test_l%04d' _testfmt_heavy = 'test_h%04d' def create_test_method(type_, op, extracond, func=None): sctype = sctype_from_type[type_] # Compute the value of bounds. condvars = {'bound': right_bound, 'lbound': left_bound, 'rbound': right_bound, 'func_bound': func_bound} for (bname, bvalue) in condvars.items(): if type_ == 'string': bvalue = str_format % bvalue bvalue = nxtype_from_type[type_](bvalue) condvars[bname] = bvalue # Compute the name of columns. colname = 'c_%s' % type_ ncolname = 'c_nested/%s' % colname # Compute the query condition. if not op: # as is cond = colname elif op == '~': # unary cond = '~(%s)' % colname elif op == '<' and func is None: # binary variable-constant cond = '{} {} {}'.format(colname, op, repr(condvars['bound'])) elif isinstance(op, tuple): # double binary variable-constant cond = ('(lbound %s %s) & (%s %s rbound)' % (op[0], colname, colname, op[1])) elif func is not None: cond = f'{func}({colname}) {op} func_bound' else: # function or binary variable-variable cond = f'{colname} {op} bound' if extracond: cond = f'({cond}) {extracond}' def ignore_skipped(oldmethod): @functools.wraps(oldmethod) def newmethod(self, *args, **kwargs): self._verboseHeader() try: return oldmethod(self, *args, **kwargs) except SilentlySkipTest as se: if se.args: msg = se.args[0] else: msg = "" common.verbosePrint("\nSkipped test: %s" % msg) finally: common.verbosePrint('') # separator line between tests return newmethod @ignore_skipped def test_method(self): common.verbosePrint("* Condition is ``%s``." % cond) # Replace bitwise operators with their logical counterparts. pycond = cond for (ptop, pyop) in [('&', 'and'), ('|', 'or'), ('~', 'not')]: pycond = pycond.replace(ptop, pyop) pycond = compile(pycond, '', 'eval') table = self.table self.create_indexes(colname, ncolname, 'c_idxextra') table_slice = dict(start=1, stop=table.nrows - 5, step=3) rownos, fvalues = None, None # Test that both simple and nested columns work as expected. # Knowing how the table is filled, results must be the same. for acolname in [colname, ncolname]: # First the reference Python version. pyrownos, pyfvalues, pyvars = [], [], condvars.copy() for row in table.iterrows(**table_slice): pyvars[colname] = row[acolname] pyvars['c_extra'] = row['c_extra'] pyvars['c_idxextra'] = row['c_idxextra'] try: with warnings.catch_warnings(): warnings.filterwarnings( 'ignore', 'invalid value encountered in arc(cos|sin)', RuntimeWarning) isvalidrow = eval(pycond, func_info, pyvars) except TypeError: raise SilentlySkipTest( "The Python type does not support the operation.") if isvalidrow: pyrownos.append(row.nrow) pyfvalues.append(row[acolname]) pyrownos = np.array(pyrownos) # row numbers already sorted pyfvalues = np.array(pyfvalues, dtype=sctype) pyfvalues.sort() common.verbosePrint(f"* {len(pyrownos)} rows selected by Python " f"from ``{acolname}``.") if rownos is None: rownos = pyrownos # initialise reference results fvalues = pyfvalues else: self.assertTrue(np.all(pyrownos == rownos)) # check self.assertTrue(np.all(pyfvalues == fvalues)) # Then the in-kernel or indexed version. ptvars = condvars.copy() ptvars[colname] = table.colinstances[acolname] ptvars['c_extra'] = table.colinstances['c_extra'] ptvars['c_idxextra'] = table.colinstances['c_idxextra'] try: isidxq = table.will_query_use_indexing(cond, ptvars) # Query twice to trigger possible query result caching. ptrownos = [table.get_where_list(cond, condvars, sort=True, **table_slice) for _ in range(2)] ptfvalues = [ table.read_where(cond, condvars, field=acolname, **table_slice) for _ in range(2) ] except TypeError as te: if self.condNotBoolean_re.search(str(te)): raise SilentlySkipTest("The condition is not boolean.") raise except NotImplementedError: raise SilentlySkipTest( "The PyTables type does not support the operation.") for ptfvals in ptfvalues: # row numbers already sorted ptfvals.sort() common.verbosePrint(f"* {len(ptrownos[0])} rows selected by " f"PyTables from ``{acolname}``", nonl=True) common.verbosePrint(f"(indexing: {'yes' if isidxq else 'no'}).") self.assertTrue(np.all(ptrownos[0] == rownos)) self.assertTrue(np.all(ptfvalues[0] == fvalues)) # The following test possible caching of query results. self.assertTrue(np.all(ptrownos[0] == ptrownos[1])) self.assertTrue(np.all(ptfvalues[0] == ptfvalues[1])) test_method.__doc__ = "Testing ``%s``." % cond return test_method def add_test_method(type_, op, extracond='', func=None): global testn # Decide to which set the test belongs. heavy = type_ in heavy_types or op in heavy_operators if heavy: testfmt = TableDataTestCase._testfmt_heavy else: testfmt = TableDataTestCase._testfmt_light tmethod = create_test_method(type_, op, extracond, func) # The test number is appended to the docstring to help # identify failing methods in non-verbose mode. tmethod.__name__ = testfmt % testn tmethod.__doc__ += testfmt % testn setattr(TableDataTestCase, tmethod.__name__, tmethod) testn += 1 # Create individual tests. You may restrict which tests are generated # by replacing the sequences in the ``for`` statements. For instance: testn = 0 for type_ in type_info: # for type_ in ['string']: for op in operators: # for op in ['!=']: for extracond in extra_conditions: # for extracond in ['']: add_test_method(type_, op, extracond) for type_ in ['float32', 'float64']: for func in func_info: # i for func in ['log10']: for op in operators: add_test_method(type_, op, func=func) # Base classes for non-indexed queries. NX_BLOCK_SIZE1 = 128 # from ``interpreter.c`` in Numexpr NX_BLOCK_SIZE2 = 8 # from ``interpreter.c`` in Numexpr class SmallNITableMixin: nrows = row_period * 2 assert NX_BLOCK_SIZE2 < nrows < NX_BLOCK_SIZE1 assert nrows % NX_BLOCK_SIZE2 != 0 # to have some residual rows class BigNITableMixin: nrows = row_period * 3 assert nrows > NX_BLOCK_SIZE1 + NX_BLOCK_SIZE2 assert nrows % NX_BLOCK_SIZE1 != 0 assert nrows % NX_BLOCK_SIZE2 != 0 # to have some residual rows # Parameters for non-indexed queries. table_sizes = ['Small', 'Big'] heavy_table_sizes = frozenset(['Big']) table_ndims = ['Scalar'] # to enable multidimensional testing, include 'MD' # Non-indexed queries: ``[SB][SM]TDTestCase``, where: # # 1. S is for small and B is for big size table. # Sizes are listed in `table_sizes`. # 2. S is for scalar and M for multidimensional columns. # Dimensionalities are listed in `table_ndims`. def niclassdata(): for size in table_sizes: heavy = size in heavy_table_sizes for ndim in table_ndims: classname = '{}{}TDTestCase'.format(size[0], ndim[0]) cbasenames = ('%sNITableMixin' % size, '%sTableMixin' % ndim, 'TableDataTestCase') classdict = dict(heavy=heavy) yield (classname, cbasenames, classdict) # Base classes for the different type index. class UltraLightITableMixin: kind = "ultralight" class LightITableMixin: kind = "light" class MediumITableMixin: kind = "medium" class FullITableMixin: kind = "full" # Base classes for indexed queries. class SmallSTableMixin: nrows = 50 class MediumSTableMixin: nrows = 100 class BigSTableMixin: nrows = 500 # Parameters for indexed queries. ckinds = ['UltraLight', 'Light', 'Medium', 'Full'] itable_sizes = ['Small', 'Medium', 'Big'] heavy_itable_sizes = frozenset(['Medium', 'Big']) itable_optvalues = [0, 1, 3, 7, 9] heavy_itable_optvalues = frozenset([0, 1, 7, 9]) # Indexed queries: ``[SMB]I[ulmf]O[01379]TDTestCase``, where: # # 1. S is for small, M for medium and B for big size table. # Sizes are listed in `itable_sizes`. # 2. U is for 'ultraLight', L for 'light', M for 'medium', F for 'Full' indexes # Index types are listed in `ckinds`. # 3. 0 to 9 is the desired index optimization level. # Optimizations are listed in `itable_optvalues`. def iclassdata(): for ckind in ckinds: for size in itable_sizes: for optlevel in itable_optvalues: heavy = (optlevel in heavy_itable_optvalues or size in heavy_itable_sizes) classname = '%sI%sO%dTDTestCase' % ( size[0], ckind[0], optlevel) cbasenames = ('%sSTableMixin' % size, '%sITableMixin' % ckind, 'ScalarTableMixin', 'TableDataTestCase') classdict = dict(heavy=heavy, optlevel=optlevel, indexed=True) yield (classname, cbasenames, classdict) # Create test classes. for cdatafunc in [niclassdata, iclassdata]: for (cname, cbasenames, cdict) in cdatafunc(): cbases = tuple(eval(cbase) for cbase in cbasenames) class_ = type(cname, cbases, cdict) exec('%s = class_' % cname) # Test cases on query usage # ------------------------- class BaseTableUsageTestCase(BaseTableQueryTestCase): nrows = row_period _gvar = None """Use this when a global variable is needed.""" class ScalarTableUsageTestCase(ScalarTableMixin, BaseTableUsageTestCase): """Test case for query usage on scalar tables. This also tests for most usage errors and situations. """ def test_empty_condition(self): """Using an empty condition.""" self.assertRaises(SyntaxError, self.table.where, '') def test_syntax_error(self): """Using a condition with a syntax error.""" self.assertRaises(SyntaxError, self.table.where, 'foo bar') def test_unsupported_object(self): """Using a condition with an unsupported object.""" self.assertRaises(TypeError, self.table.where, '[]') self.assertRaises(TypeError, self.table.where, 'obj', {'obj': {}}) self.assertRaises(TypeError, self.table.where, 'c_bool < []') def test_unsupported_syntax(self): """Using a condition with unsupported syntax.""" self.assertRaises(TypeError, self.table.where, 'c_bool[0]') self.assertRaises(TypeError, self.table.where, 'c_bool()') self.assertRaises(NameError, self.table.where, 'c_bool.__init__') def test_no_column(self): """Using a condition with no participating columns.""" self.assertRaises(ValueError, self.table.where, 'True') def test_foreign_column(self): """Using a condition with a column from other table.""" table2 = self.h5file.create_table('/', 'other', self.tableDescription) self.assertRaises(ValueError, self.table.where, 'c_int32_a + c_int32_b > 0', {'c_int32_a': self.table.cols.c_int32, 'c_int32_b': table2.cols.c_int32}) def test_unsupported_op(self): """Using a condition with unsupported operations on types.""" NIE = NotImplementedError self.assertRaises(NIE, self.table.where, 'c_complex128 > 0j') self.assertRaises(NIE, self.table.where, 'c_string + b"a" > b"abc"') def test_not_boolean(self): """Using a non-boolean condition.""" self.assertRaises(TypeError, self.table.where, 'c_int32') def test_nested_col(self): """Using a condition with nested columns.""" self.assertRaises(TypeError, self.table.where, 'c_nested') def test_implicit_col(self): """Using implicit column names in conditions.""" # If implicit columns didn't work, a ``NameError`` would be raised. self.assertRaises(TypeError, self.table.where, 'c_int32') # If overriding didn't work, no exception would be raised. self.assertRaises(TypeError, self.table.where, 'c_bool', {'c_bool': self.table.cols.c_int32}) # External variables do not override implicit columns. def where_with_locals(): c_int32 = self.table.cols.c_bool # this wouldn't cause an error self.assertIsNotNone(c_int32) self.table.where('c_int32') self.assertRaises(TypeError, where_with_locals) def test_condition_vars(self): """Using condition variables in conditions.""" # If condition variables didn't work, a ``NameError`` would be raised. self.assertRaises(NotImplementedError, self.table.where, 'c_string > bound', {'bound': 0}) def where_with_locals(): bound = 'foo' # this wouldn't cause an error # silence pyflakes warnings self.assertIsInstance(bound, str) self.table.where('c_string > bound', {'bound': 0}) self.assertRaises(NotImplementedError, where_with_locals) def where_with_globals(): global _gvar _gvar = 'foo' # this wouldn't cause an error # silence pyflakes warnings self.assertIsInstance(_gvar, str) try: self.table.where('c_string > _gvar', {'_gvar': 0}) finally: del _gvar # to keep global namespace clean self.assertRaises(NotImplementedError, where_with_globals) def test_scopes(self): """Looking up different scopes for variables.""" # Make sure the variable is not implicit. self.assertRaises(NameError, self.table.where, 'col') # First scope: dictionary of condition variables. self.assertRaises(TypeError, self.table.where, 'col', {'col': self.table.cols.c_int32}) # Second scope: local variables. def where_whith_locals(): col = self.table.cols.c_int32 self.assertIsNotNone(col) self.table.where('col') self.assertRaises(TypeError, where_whith_locals) # Third scope: global variables. def where_with_globals(): global _gvar _gvar = self.table.cols.c_int32 # silence pyflakes warnings self.assertIsNotNone(_gvar) try: self.table.where('_gvar') finally: del _gvar # to keep global namespace clean self.assertRaises(TypeError, where_with_globals) class MDTableUsageTestCase(MDTableMixin, BaseTableUsageTestCase): """Test case for query usage on multidimensional tables.""" def test(self): """Using a condition on a multidimensional table.""" # Easy: queries on multidimensional tables are not implemented yet! self.assertRaises(NotImplementedError, self.table.where, 'c_bool') class IndexedTableUsage(ScalarTableMixin, BaseTableUsageTestCase): """Test case for query usage on indexed tables. Indexing could be used in more cases, but it is expected to kick in at least in the cases tested here. """ nrows = 50 indexed = True def setUp(self): super().setUp() self.table.cols.c_bool.create_index(_blocksizes=small_blocksizes) self.table.cols.c_int32.create_index(_blocksizes=small_blocksizes) self.will_query_use_indexing = self.table.will_query_use_indexing self.compileCondition = self.table._compile_condition self.requiredExprVars = self.table._required_expr_vars usable_idxs = set() for expr in self.idx_expr: idxvar = expr[0] if idxvar not in usable_idxs: usable_idxs.add(idxvar) self.usable_idxs = frozenset(usable_idxs) def test(self): for condition in self.conditions: c_usable_idxs = self.will_query_use_indexing(condition, {}) self.assertEqual(c_usable_idxs, self.usable_idxs, f"\nQuery with condition: ``{condition}``\n" f"Computed usable indexes are: " f"``{c_usable_idxs}``\nand should be: " f"``{self.usable_idxs}``") condvars = self.requiredExprVars(condition, None) compiled = self.compileCondition(condition, condvars) c_idx_expr = compiled.index_expressions self.assertEqual(c_idx_expr, self.idx_expr, f"\nWrong index expression in condition:\n" f"``{condition}``\nCompiled index expression is:" f"\n``{c_idx_expr}``\nand should be:\n" f"``{self.idx_expr}``") c_str_expr = compiled.string_expression self.assertEqual(c_str_expr, self.str_expr, f"\nWrong index operations in condition:\n" f"``{condition}``\nComputed index operations are:" f"\n``{c_str_expr}``\nand should be:\n" f"``{self.str_expr}``") common.verbosePrint( f"* Query with condition ``{condition}`` will use variables " f"``{compiled.index_variables}`` for indexing.") class IndexedTableUsage1(IndexedTableUsage): conditions = [ '(c_int32 > 0)', '(c_int32 > 0) & (c_extra > 0)', '(c_int32 > 0) & ((~c_bool) | (c_extra > 0))', '(c_int32 > 0) & ((c_extra < 3) & (c_extra > 0))', ] idx_expr = [('c_int32', ('gt',), (0,))] str_expr = 'e0' class IndexedTableUsage2(IndexedTableUsage): conditions = [ '(c_int32 > 0) & (c_int32 < 5)', '(c_int32 > 0) & (c_int32 < 5) & (c_extra > 0)', '(c_int32 > 0) & (c_int32 < 5) & ((c_bool == True) | (c_extra > 0))', '(c_int32 > 0) & (c_int32 < 5) & ((c_extra > 0) | (c_bool == True))', ] idx_expr = [('c_int32', ('gt', 'lt'), (0, 5))] str_expr = 'e0' class IndexedTableUsage3(IndexedTableUsage): conditions = [ '(c_bool == True)', '(c_bool == True) & (c_extra > 0)', '(c_extra > 0) & (c_bool == True)', '((c_extra > 0) & (c_extra < 4)) & (c_bool == True)', '(c_bool == True) & ((c_extra > 0) & (c_extra < 4))', ] idx_expr = [('c_bool', ('eq',), (True,))] str_expr = 'e0' class IndexedTableUsage4(IndexedTableUsage): conditions = [ '((c_int32 > 0) & (c_bool == True)) & (c_extra > 0)', '((c_int32 > 0) & (c_bool == True)) & ((c_extra > 0)' + ' & (c_extra < 4))', ] idx_expr = [('c_int32', ('gt',), (0,)), ('c_bool', ('eq',), (True,)), ] str_expr = '(e0 & e1)' class IndexedTableUsage5(IndexedTableUsage): conditions = [ '(c_int32 >= 1) & (c_int32 < 2) & (c_bool == True)', '(c_int32 >= 1) & (c_int32 < 2) & (c_bool == True)' + ' & (c_extra > 0)', ] idx_expr = [('c_int32', ('ge', 'lt'), (1, 2)), ('c_bool', ('eq',), (True,)), ] str_expr = '(e0 & e1)' class IndexedTableUsage6(IndexedTableUsage): conditions = [ '(c_int32 >= 1) & (c_int32 < 2) & (c_int32 > 0) & (c_int32 < 5)', '(c_int32 >= 1) & (c_int32 < 2) & (c_int32 > 0) & (c_int32 < 5)' + ' & (c_extra > 0)', ] idx_expr = [('c_int32', ('ge', 'lt'), (1, 2)), ('c_int32', ('gt',), (0,)), ('c_int32', ('lt',), (5,)), ] str_expr = '((e0 & e1) & e2)' class IndexedTableUsage7(IndexedTableUsage): conditions = [ '(c_int32 >= 1) & (c_int32 < 2) & ((c_int32 > 0) & (c_int32 < 5))', '((c_int32 >= 1) & (c_int32 < 2)) & ((c_int32 > 0) & (c_int32 < 5))', '((c_int32 >= 1) & (c_int32 < 2)) & ((c_int32 > 0) & (c_int32 < 5))' + ' & (c_extra > 0)', ] idx_expr = [('c_int32', ('ge', 'lt'), (1, 2)), ('c_int32', ('gt', 'lt'), (0, 5)), ] str_expr = '(e0 & e1)' class IndexedTableUsage8(IndexedTableUsage): conditions = [ '(c_extra > 0) & ((c_int32 > 0) & (c_int32 < 5))', ] idx_expr = [('c_int32', ('gt', 'lt'), (0, 5)), ] str_expr = 'e0' class IndexedTableUsage9(IndexedTableUsage): conditions = [ '(c_extra > 0) & (c_int32 > 0) & (c_int32 < 5)', '((c_extra > 0) & (c_int32 > 0)) & (c_int32 < 5)', '(c_extra > 0) & (c_int32 > 0) & (c_int32 < 5) & (c_extra > 3)', ] idx_expr = [('c_int32', ('gt',), (0,)), ('c_int32', ('lt',), (5,))] str_expr = '(e0 & e1)' class IndexedTableUsage10(IndexedTableUsage): conditions = [ '(c_int32 < 5) & (c_extra > 0) & (c_bool == True)', '(c_int32 < 5) & (c_extra > 2) & c_bool', '(c_int32 < 5) & (c_bool == True) & (c_extra > 0) & (c_extra < 4)', '(c_int32 < 5) & (c_extra > 0) & (c_bool == True) & (c_extra < 4)', ] idx_expr = [('c_int32', ('lt',), (5,)), ('c_bool', ('eq',), (True,))] str_expr = '(e0 & e1)' class IndexedTableUsage11(IndexedTableUsage): """Complex operations are not eligible for indexing.""" conditions = [ 'sin(c_int32) > 0', '(c_int32 * 2.4) > 0', '(c_int32 + c_int32) > 0', 'c_int32**2 > 0', ] idx_expr = [] str_expr = '' class IndexedTableUsage12(IndexedTableUsage): conditions = [ '~c_bool', '~(c_bool)', '~c_bool & (c_extra > 0)', '~(c_bool) & (c_extra > 0)', ] idx_expr = [('c_bool', ('eq',), (False,))] str_expr = 'e0' class IndexedTableUsage13(IndexedTableUsage): conditions = [ '~(c_bool == True)', '~((c_bool == True))', '~(c_bool == True) & (c_extra > 0)', '~((c_bool == True)) & (c_extra > 0)', ] idx_expr = [('c_bool', ('eq',), (False,))] str_expr = 'e0' class IndexedTableUsage14(IndexedTableUsage): conditions = [ '~(c_int32 > 0)', '~((c_int32 > 0)) & (c_extra > 0)', '~(c_int32 > 0) & ((~c_bool) | (c_extra > 0))', '~(c_int32 > 0) & ((c_extra < 3) & (c_extra > 0))', ] idx_expr = [('c_int32', ('le',), (0,))] str_expr = 'e0' class IndexedTableUsage15(IndexedTableUsage): conditions = [ '(~(c_int32 > 0) | ~c_bool)', '(~(c_int32 > 0) | ~(c_bool)) & (c_extra > 0)', '(~(c_int32 > 0) | ~(c_bool == True)) & ((c_extra > 0)' + ' & (c_extra < 4))', ] idx_expr = [('c_int32', ('le',), (0,)), ('c_bool', ('eq',), (False,)), ] str_expr = '(e0 | e1)' class IndexedTableUsage16(IndexedTableUsage): conditions = [ '(~(c_int32 > 0) & ~(c_int32 < 2))', '(~(c_int32 > 0) & ~(c_int32 < 2)) & (c_extra > 0)', '(~(c_int32 > 0) & ~(c_int32 < 2)) & ((c_extra > 0)' + ' & (c_extra < 4))', ] idx_expr = [('c_int32', ('le',), (0,)), ('c_int32', ('ge',), (2,)), ] str_expr = '(e0 & e1)' class IndexedTableUsage17(IndexedTableUsage): conditions = [ '(~(c_int32 > 0) & ~(c_int32 < 2))', '(~(c_int32 > 0) & ~(c_int32 < 2)) & (c_extra > 0)', '(~(c_int32 > 0) & ~(c_int32 < 2)) & ((c_extra > 0)' + ' & (c_extra < 4))', ] idx_expr = [('c_int32', ('le',), (0,)), ('c_int32', ('ge',), (2,)), ] str_expr = '(e0 & e1)' # Negations of complex conditions are not supported yet class IndexedTableUsage18(IndexedTableUsage): conditions = [ '~((c_int32 > 0) & (c_bool))', '~((c_int32 > 0) & (c_bool)) & (c_extra > 0)', '~((c_int32 > 0) & (c_bool)) & ((c_extra > 0)' + ' & (c_extra < 4))', ] idx_expr = [] str_expr = '' class IndexedTableUsage19(IndexedTableUsage): conditions = [ '~((c_int32 > 0) & (c_bool)) & ((c_bool == False)' + ' & (c_extra < 4))', ] idx_expr = [('c_bool', ('eq',), (False,)), ] str_expr = 'e0' class IndexedTableUsage20(IndexedTableUsage): conditions = [ '((c_int32 > 0) & ~(c_bool))', '((c_int32 > 0) & ~(c_bool)) & (c_extra > 0)', '((c_int32 > 0) & ~(c_bool == True)) & ((c_extra > 0) & (c_extra < 4))' ] idx_expr = [ ('c_int32', ('gt',), (0,)), ('c_bool', ('eq',), (False,)), ] str_expr = '(e0 & e1)' class IndexedTableUsage21(IndexedTableUsage): conditions = [ '(~(c_int32 > 0) & (c_bool))', '(~(c_int32 > 0) & (c_bool)) & (c_extra > 0)', '(~(c_int32 > 0) & (c_bool == True)) & ((c_extra > 0)' + ' & (c_extra < 4))', ] idx_expr = [('c_int32', ('le',), (0,)), ('c_bool', ('eq',), (True,)), ] str_expr = '(e0 & e1)' class IndexedTableUsage22(IndexedTableUsage): conditions = [ '~((c_int32 >= 1) & (c_int32 < 2)) & ~(c_bool == True)', '~(c_bool == True) & (c_extra > 0)', '~((c_int32 >= 1) & (c_int32 < 2)) & (~(c_bool == True)' + ' & (c_extra > 0))', ] idx_expr = [('c_bool', ('eq',), (False,)), ] str_expr = 'e0' class IndexedTableUsage23(IndexedTableUsage): conditions = [ 'c_int32 != 1', 'c_bool != False', '~(c_int32 != 1)', '~(c_bool != False)', '(c_int32 != 1) & (c_extra != 2)', ] idx_expr = [] str_expr = '' class IndexedTableUsage24(IndexedTableUsage): conditions = [ 'c_bool', 'c_bool == True', 'True == c_bool', '~(~c_bool)', '~~c_bool', '~~~~c_bool', '~(~c_bool) & (c_extra != 2)', ] idx_expr = [('c_bool', ('eq',), (True,)), ] str_expr = 'e0' class IndexedTableUsage25(IndexedTableUsage): conditions = [ '~c_bool', 'c_bool == False', 'False == c_bool', '~(c_bool)', '~((c_bool))', '~~~c_bool', '~~(~c_bool) & (c_extra != 2)', ] idx_expr = [ ('c_bool', ('eq',), (False,)), ] str_expr = 'e0' class IndexedTableUsage26(IndexedTableUsage): conditions = [ 'c_bool != True', 'True != c_bool', 'c_bool != False', 'False != c_bool', ] idx_expr = [] str_expr = '' class IndexedTableUsage27(IndexedTableUsage): conditions = [ '(c_int32 == 3) | c_bool | (c_int32 == 5)', '(((c_int32 == 3) | (c_bool == True)) | (c_int32 == 5))' + ' & (c_extra > 0)', ] idx_expr = [ ('c_int32', ('eq',), (3,)), ('c_bool', ('eq',), (True,)), ('c_int32', ('eq',), (5,)), ] str_expr = '((e0 | e1) | e2)' class IndexedTableUsage28(IndexedTableUsage): conditions = [ '((c_int32 == 3) | c_bool) & (c_int32 == 5)', '(((c_int32 == 3) | (c_bool == True)) & (c_int32 == 5))' + ' & (c_extra > 0)', ] idx_expr = [ ('c_int32', ('eq',), (3,)), ('c_bool', ('eq',), (True,)), ('c_int32', ('eq',), (5,)), ] str_expr = '((e0 | e1) & e2)' class IndexedTableUsage29(IndexedTableUsage): conditions = [ '(c_int32 == 3) | ((c_int32 == 4) & (c_int32 == 5))', '((c_int32 == 3) | ((c_int32 == 4) & (c_int32 == 5)))' + ' & (c_extra > 0)', ] idx_expr = [ ('c_int32', ('eq',), (4,)), ('c_int32', ('eq',), (5,)), ('c_int32', ('eq',), (3,)), ] str_expr = '((e0 & e1) | e2)' class IndexedTableUsage30(IndexedTableUsage): conditions = [ '((c_int32 == 3) | (c_int32 == 4)) & (c_int32 == 5)', '((c_int32 == 3) | (c_int32 == 4)) & (c_int32 == 5)' + ' & (c_extra > 0)', ] idx_expr = [ ('c_int32', ('eq',), (3,)), ('c_int32', ('eq',), (4,)), ('c_int32', ('eq',), (5,)), ] str_expr = '((e0 | e1) & e2)' class IndexedTableUsage31(IndexedTableUsage): conditions = [ '(c_extra > 0) & ((c_extra < 4) & (c_bool == True))', '(c_extra > 0) & ((c_bool == True) & (c_extra < 5))', '((c_int32 > 0) | (c_extra > 0)) & (c_bool == True)', ] idx_expr = [ ('c_bool', ('eq',), (True,)), ] str_expr = 'e0' class IndexedTableUsage32(IndexedTableUsage): conditions = [ '(c_int32 < 5) & (c_extra > 0) & (c_bool == True) | (c_extra < 4)', ] idx_expr = [] str_expr = '' # Main part # --------- def suite(): """Return a test suite consisting of all the test cases in the module.""" testSuite = common.unittest.TestSuite() cdatafuncs = [niclassdata] # non-indexing data tests cdatafuncs.append(iclassdata) # indexing data tests heavy = common.heavy # Choose which tests to run in classes with autogenerated tests. if heavy: autoprefix = 'test' # all tests else: autoprefix = 'test_l' # only light tests niter = 1 for i in range(niter): # Tests on query data. for cdatafunc in cdatafuncs: for cdata in cdatafunc(): class_ = eval(cdata[0]) if heavy or not class_.heavy: suite_ = common.unittest.makeSuite(class_, prefix=autoprefix) testSuite.addTest(suite_) # Tests on query usage. testSuite.addTest(common.unittest.makeSuite(ScalarTableUsageTestCase)) testSuite.addTest(common.unittest.makeSuite(MDTableUsageTestCase)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage1)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage2)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage3)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage4)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage5)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage6)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage7)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage8)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage9)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage10)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage11)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage12)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage13)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage14)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage15)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage16)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage17)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage18)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage19)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage20)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage21)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage22)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage23)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage24)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage25)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage26)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage27)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage28)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage29)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage30)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage31)) testSuite.addTest(common.unittest.makeSuite(IndexedTableUsage32)) return testSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_ref_array1.mat000066400000000000000000000375001416254111300214600ustar00rootroot00000000000000MATLAB 7.3 MAT-file, Platform: MACI64, Created on: Tue Oct 7 10:58:35 2014 HDF5 schema 1.00 . IM‰HDF  ÿÿÿÿÿÿÿÿ@?ÿÿÿÿÿÿÿÿ`ˆ¨ˆ¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàHEAPXÈANN#refs#@ PTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿHPH%`HEAP Xø 8 MATLAB_emptySNOD8`€ Hh  noiseË)4T 0 MATLAB_classchar @ MATLAB_int_decode  0SNODPÐ (H5PATH/ANN ?@4 4ÿi@Ë)4T 0 MATLAB_classdouble (H5PATH/ANN  ?@4 4ÿ€v@Ë)4T 0 MATLAB_classdouble (H5PATH/ANN   bmewamË)4T 0 MATLAB_classchar @ MATLAB_int_decode  (H5PATH/ANN ?@4 4ÿ@Ë)4T 0 MATLAB_classdouble (H5PATH/ANN øHTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ`¨!@Ø)PHEAP°hø Hh 0 MATLAB_classstruct`€ (H5PATH/#refs#@Ë)4T 8 MATLAB_empty 8 MATLAB_classcanonical emptySNOD@ @@ Ð0Ë)4T 0 MATLAB_classcell (H5PATH/ANN @Ë)4T 0 MATLAB_classchar 0H5PATH /#refs#/b 8 MATLAB_empty @Ë)4T 0 MATLAB_classchar 0H5PATH /#refs#/c 8 MATLAB_empty @Ë)4T 0 MATLAB_classchar 0H5PATH /#refs#/d 8 MATLAB_empty @@ ˆ ¸Ë)4T 0 MATLAB_classcell (H5PATH/ANN ?@4 4ÿð?Ë)4T 0 MATLAB_classdouble 0H5PATH /#refs#/e ?@4 4ÿð?Ë)4T 0 MATLAB_classdouble 0H5PATH /#refs#/f ?@4 4ÿð?Ë)4T 0 MATLAB_classdouble 0H5PATH /#refs#/g @@ ¨Øð"Ë)4T 0 MATLAB_classcell (H5PATH/ANNDBnFSoFSrecordsnFilesLabelRhythmmy_arr8H @Ë)4T 0 MATLAB_classchar 0H5PATH /#refs#/h 8 MATLAB_empty @Ë)4T 0 MATLAB_classchar 0H5PATH /#refs#/iˆ@abcdefghijklH@SNOD@¨ @Ë)4T 0 MATLAB_classchar 0H5PATH /#refs#/j 8 MATLAB_empty @@ &¨'À(Ë)4T 0 MATLAB_classcell (H5PATH/ANNSNODÀ ( Ø ð ` + ?@4 4ÿð?Ë)4T 0 MATLAB_classdouble 0H5PATH /#refs#/k ?@4 4ÿð?Ë)4T 0 MATLAB_classdouble 0H5PATH /#refs#/l ?@4 4ÿð?Ë)4T 0 MATLAB_classdouble 0H5PATH /#refs#/mSNODHØPð" ?@4 4ÿ ` A` A` AË)4T 0 MATLAB_classdouble (H5PATH/ANN Hh ØMATLAB_fields @-@-@-@-@-@- @-@- @- @- GCOLDBnFSoFSrecordsnFilesLabel LabelSampRhythm RhythmSamp signalLðPyTables-3.7.0/tables/tests/test_ref_array2.mat000066400000000000000000000113401416254111300214530ustar00rootroot00000000000000MATLAB 7.3 MAT-file, Platform: PCWIN64, Created on: Thu Jan 07 17:11:02 2016 HDF5 schema 1.00 . IM‰HDF  ÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿ`ˆ¨ˆ¨TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàHEAPXÈ#refs#var@(HTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ0HEAPX8ˆabcdef SNOD HhØ Hh (H5PATH/#refs#@¦ V 8 MATLAB_empty 8 MATLAB_classcanonical emptySNODp  ` (€0˜(pp   ` ¦ V 0 MATLAB_classcell0( ?@4 4ÿð?¦ V 0 MATLAB_classdouble 0H5PATH /#refs#/b8(test¦ V 0 MATLAB_classchar @ MATLAB_int_decode  0H5PATH /#refs#/c(pp€˜¦ V 0 MATLAB_classcell 0H5PATH /#refs#/d( ?@4 4ÿ@¦ V 0 MATLAB_classdouble 0H5PATH /#refs#/e8( inside¦ V 0 MATLAB_classchar @ MATLAB_int_decode  0H5PATH /#refs#/fPyTables-3.7.0/tables/tests/test_suite.py000066400000000000000000000057571416254111300204360ustar00rootroot00000000000000"""Test suite consisting of all testcases.""" import sys from tables.tests import common def suite(): test_modules = [ 'tables.tests.test_attributes', 'tables.tests.test_basics', 'tables.tests.test_create', 'tables.tests.test_backcompat', 'tables.tests.test_types', 'tables.tests.test_lists', 'tables.tests.test_tables', 'tables.tests.test_tablesMD', 'tables.tests.test_array', 'tables.tests.test_earray', 'tables.tests.test_carray', 'tables.tests.test_vlarray', 'tables.tests.test_tree', 'tables.tests.test_timetype', 'tables.tests.test_do_undo', 'tables.tests.test_enum', 'tables.tests.test_nestedtypes', 'tables.tests.test_hdf5compat', 'tables.tests.test_numpy', 'tables.tests.test_queries', 'tables.tests.test_expression', 'tables.tests.test_links', 'tables.tests.test_indexes', 'tables.tests.test_indexvalues', 'tables.tests.test_index_backcompat', 'tables.tests.test_aux', 'tables.tests.test_utils', # Sub-packages 'tables.nodes.tests.test_filenode', ] # print('-=' * 38) # The test for garbage must be run *in the last place*. # Else, it is not as useful. test_modules.append('tables.tests.test_garbage') alltests = common.unittest.TestSuite() if common.show_memory: # Add a memory report at the beginning alltests.addTest(common.unittest.makeSuite(common.ShowMemTime)) for name in test_modules: # Unexpectedly, the following code doesn't seem to work anymore # in python 3 # exec('from %s import suite as test_suite' % name) __import__(name) test_suite = sys.modules[name].suite alltests.addTest(test_suite()) if common.show_memory: # Add a memory report after each test module alltests.addTest(common.unittest.makeSuite(common.ShowMemTime)) return alltests def test(verbose=False, heavy=False): """Run all the tests in the test suite. If *verbose* is set, the test suite will emit messages with full verbosity (not recommended unless you are looking into a certain problem). If *heavy* is set, the test suite will be run in *heavy* mode (you should be careful with this because it can take a lot of time and resources from your computer). Return 0 (os.EX_OK) if all tests pass, 1 in case of failure """ common.print_versions() common.print_heavy(heavy) # What a context this is! # oldverbose, common.verbose = common.verbose, verbose oldheavy, common.heavy = common.heavy, heavy try: result = common.unittest.TextTestRunner( verbosity=1 + int(verbose)).run(suite()) if result.wasSuccessful(): return 0 else: return 1 finally: # common.verbose = oldverbose common.heavy = oldheavy # there are pretty young heavies, too ;) PyTables-3.7.0/tables/tests/test_szip.h5000066400000000000000000000127321416254111300201450ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿÔÿÿÿÿÿÿÿÿ €`HEAP€dset_szipèTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà €˜   ¸¨ èX0àHhhð0и#àð%øÈ(P+@Ø-P`0pè2ˆp5¨ø7È€:à= °ê@ €` ( (szip© ( u‘+APSNODÐTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿã8ç êè ì  @U€Iý *ª“ú(UT'ôxª¨OéAUPŸÓ"ª  ?§…U@QŠª€$þ¨UIýZ*ª“úÈUT'õ¸ª¨OëÁUPŸÐ"ª  ?¡…U@EŠª€$þUIý**ª“úhUT'ôøª¨Oà€ € € € ( € ( € € € € € € € € € € AJª€$þ‡•UIý*ª“úFUT'ô´ª¨Oé¹UPŸÔª  ?©eU@UJª€$þ¯•UIýi*ª“úæUT'ýõð„! á$þƒ•UIý*ª“ú6UT'ô”ª¨OéyUPŸÓ’ª  ?¨eU@@@@„P@P@@@@@@@@@@@ R ª€$þ©UIý\*ª“úÌUT'õÀª¨OëÑUPŸÐBª  ?¡ÅU@F ª€$þ‘UIý,*ª“úlUT'õª¨OêQUPŸÕBª  ?«ÅU@Z ª€$þ¹UIý|*ªÿø@AŠª€$þ( € ( €H €H €H à€ € € € € € € € € € € SJª€$þ«•UIýa*ª“úÖUT'õÔª¨Oûúñ|"„Iý *ª“ú&UT'ôtª¨Oé9UPŸÓª  ?§eU@QJª€$þ§•UIýY*ª“úÆUT'õ´ª¨Oë¹UPŸÐª  ?¡eU@P@@c„@ $@ $@ $Ð@@@@@@@@@@@ PyTables-3.7.0/tables/tests/test_tables.py000066400000000000000000007473371416254111300205660ustar00rootroot00000000000000import itertools import sys import tempfile import struct import platform from pathlib import Path import numpy as np import tables as tb from tables.tests import common # To know whether the interpreter is 32 or 64 bit def is_python_64bit(): return struct.calcsize("P") == 8 # To know whether the os platform is 32 or 64 bit def is_os_64bit(): return platform.machine().endswith('64') # Test Record class class Record(tb.IsDescription): var1 = tb.StringCol(itemsize=4, dflt=b"abcd", pos=0) # 4-character String var2 = tb.IntCol(dflt=1, pos=1) # integer var3 = tb.Int16Col(dflt=2, pos=2) # short integer var4 = tb.Float64Col(dflt=3.1, pos=3) # double (double-precision) var5 = tb.Float32Col(dflt=4.2, pos=4) # float (single-precision) var6 = tb.UInt16Col(dflt=5, pos=5) # unsigned short integer var7 = tb.StringCol(itemsize=1, dflt=b"e", pos=6) # 1-character String var8 = tb.BoolCol(dflt=True, pos=7) # boolean var9 = tb.ComplexCol( itemsize=8, dflt=(0.+1.j), pos=8) # Complex single precision var10 = tb.ComplexCol( itemsize=16, dflt=(1.-0.j), pos=9) # Complex double precision if hasattr(tb, 'Float16Col'): var11 = tb.Float16Col(dflt=6.4) # float (half-precision) if hasattr(tb, 'Float96Col'): var12 = tb.Float96Col(dflt=6.4) # float (extended precision) if hasattr(tb, 'Float128Col'): var13 = tb.Float128Col(dflt=6.4) # float (extended precision) if hasattr(tb, 'Complex192Col'): var14 = tb.ComplexCol( itemsize=24, dflt=(1.-0.j)) # Complex double (extended precision) if hasattr(tb, 'Complex256Col'): var15 = tb.ComplexCol( itemsize=32, dflt=(1.-0.j)) # Complex double (extended precision) # Dictionary definition RecordDescriptionDict = { 'var1': tb.StringCol(itemsize=4, dflt=b"abcd", pos=0), # 4-char String 'var2': tb.IntCol(dflt=1, pos=1), # integer 'var3': tb.Int16Col(dflt=2, pos=2), # short integer 'var4': tb.Float64Col(dflt=3.1, pos=3), # double (double-precision) 'var5': tb.Float32Col(dflt=4.2, pos=4), # float (single-precision) 'var6': tb.UInt16Col(dflt=5, pos=5), # unsigned short integer 'var7': tb.StringCol( itemsize=1, dflt=b"e", pos=6), # 1-character String 'var8': tb.BoolCol(dflt=True, pos=7), # boolean 'var9': tb.ComplexCol( itemsize=8, dflt=(0.+1.j), pos=8), # Complex single precision 'var10': tb.ComplexCol( itemsize=16, dflt=(1.-0.j), pos=9), # Complex double precision } if hasattr(tb, 'Float16Col'): # float (half-precision) RecordDescriptionDict['var11'] = tb.Float16Col(dflt=6.4) if hasattr(tb, 'Float96Col'): # float (extended precision) RecordDescriptionDict['var12'] = tb.Float96Col(dflt=6.4) if hasattr(tb, 'Float128Col'): # float (extended precision) RecordDescriptionDict['var13'] = tb.Float128Col(dflt=6.4) if hasattr(tb, 'Complex192Col'): # Complex double (extended precision) RecordDescriptionDict['var14'] = tb.ComplexCol( itemsize=24, dflt=(1.-0.j)) if hasattr(tb, 'Complex256Col'): # Complex double (extended precision) RecordDescriptionDict['var15'] = tb.ComplexCol( itemsize=32, dflt=(1.-0.j)) # Old fashion of defining tables (for testing backward compatibility) class OldRecord(tb.IsDescription): var1 = tb.StringCol(itemsize=4, dflt=b"abcd", pos=0) var2 = tb.Col.from_type("int32", (), 1, pos=1) var3 = tb.Col.from_type("int16", (), 2, pos=2) var4 = tb.Col.from_type("float64", (), 3.1, pos=3) var5 = tb.Col.from_type("float32", (), 4.2, pos=4) var6 = tb.Col.from_type("uint16", (), 5, pos=5) var7 = tb.StringCol(itemsize=1, dflt=b"e", pos=6) var8 = tb.Col.from_type("bool", shape=(), dflt=1, pos=7) var9 = tb.ComplexCol(itemsize=8, shape=(), dflt=(0.+1.j), pos=8) var10 = tb.ComplexCol(itemsize=16, shape=(), dflt=(1.-0.j), pos=9) if hasattr(tb, 'Float16Col'): var11 = tb.Col.from_type("float16", (), 6.4) if hasattr(tb, 'Float96Col'): var12 = tb.Col.from_type("float96", (), 6.4) if hasattr(tb, 'Float128Col'): var13 = tb.Col.from_type("float128", (), 6.4) if hasattr(tb, 'Complex192Col'): var14 = tb.ComplexCol(itemsize=24, shape=(), dflt=(1.-0.j)) if hasattr(tb, 'Complex256Col'): var15 = tb.ComplexCol(itemsize=32, shape=(), dflt=(1.-0.j)) class BasicTestCase(common.TempFileMixin, common.PyTablesTestCase): # file = "test.h5" open_mode = "w" title = "This is the table title" expectedrows = 100 appendrows = 20 compress = 0 shuffle = 0 bitshuffle = 0 fletcher32 = 0 complib = "zlib" # Default compression library record = Record recarrayinit = 0 maxshort = 1 << 15 def setUp(self): super().setUp() # Create an instance of an HDF5 Table self.rootgroup = self.h5file.root self.populateFile() self.h5file.close() def initRecArray(self): record = self.recordtemplate row = record[0] buflist = [] # Fill the recarray for i in range(self.expectedrows): tmplist = [] var1 = '%04d' % (self.expectedrows - i) tmplist.append(var1) var2 = i tmplist.append(var2) var3 = i % self.maxshort tmplist.append(var3) if isinstance(row['var4'], np.ndarray): tmplist.append([float(i), float(i * i)]) else: tmplist.append(float(i)) if isinstance(row['var5'], np.ndarray): tmplist.append(np.array((float(i),)*4)) else: tmplist.append(float(i)) # var6 will be like var3 but byteswaped tmplist.append(((var3 >> 8) & 0xff) + ((var3 << 8) & 0xff00)) var7 = var1[-1] tmplist.append(var7) if isinstance(row['var8'], np.ndarray): tmplist.append([0, 10]) # should be equivalent to [0,1] else: tmplist.append(10) # should be equivalent to 1 if isinstance(row['var9'], np.ndarray): tmplist.append([0.+float(i)*1j, float(i)+0.j]) else: tmplist.append(float(i)+0j) if isinstance(row['var10'], np.ndarray): tmplist.append([float(i)+0j, 1 + float(i)*1j]) else: tmplist.append(1 + float(i)*1j) if hasattr(tb, 'Float16Col'): if isinstance(row['var11'], np.ndarray): tmplist.append(np.array((float(i),)*4)) else: tmplist.append(float(i)) if hasattr(tb, 'Float96Col'): if isinstance(row['var12'], np.ndarray): tmplist.append(np.array((float(i),)*4)) else: tmplist.append(float(i)) if hasattr(tb, 'Float128Col'): if isinstance(row['var13'], np.ndarray): tmplist.append(np.array((float(i),)*4)) else: tmplist.append(float(i)) if hasattr(tb, 'Complex192Col'): if isinstance(row['var14'], np.ndarray): tmplist.append([float(i)+0j, 1 + float(i)*1j]) else: tmplist.append(1 + float(i)*1j) if hasattr(tb, 'Complex256Col'): if isinstance(row['var15'], np.ndarray): tmplist.append([float(i)+0j, 1 + float(i)*1j]) else: tmplist.append(1 + float(i)*1j) buflist.append(tuple(tmplist)) self.record = np.rec.array(buflist, dtype=record.dtype, shape=self.expectedrows) def populateFile(self): group = self.rootgroup if self.recarrayinit: # Initialize an starting buffer, if any self.initRecArray() for j in range(3): # Create a table filterprops = tb.Filters(complevel=self.compress, shuffle=self.shuffle, bitshuffle=self.bitshuffle, fletcher32=self.fletcher32, complib=self.complib) if j < 2: byteorder = sys.byteorder else: # table2 will be byteswapped byteorder = {"little": "big", "big": "little"}[sys.byteorder] table = self.h5file.create_table(group, 'table'+str(j), self.record, title=self.title, filters=filterprops, expectedrows=self.expectedrows, byteorder=byteorder) if not self.recarrayinit: # Get the row object associated with the new table row = table.row # Fill the table for i in range(self.expectedrows): s = '%04d' % (self.expectedrows - i) row['var1'] = s.encode('ascii') row['var7'] = s[-1].encode('ascii') # row['var7'] = ('%04d' % (self.expectedrows - i))[-1] row['var2'] = i row['var3'] = i % self.maxshort if isinstance(row['var4'], np.ndarray): row['var4'] = [float(i), float(i * i)] else: row['var4'] = float(i) if isinstance(row['var8'], np.ndarray): row['var8'] = [0, 1] else: row['var8'] = 1 if isinstance(row['var9'], np.ndarray): row['var9'] = [0.+float(i)*1j, float(i)+0.j] else: row['var9'] = float(i)+0.j if isinstance(row['var10'], np.ndarray): row['var10'] = [float(i)+0.j, 1.+float(i)*1j] else: row['var10'] = 1.+float(i)*1j if isinstance(row['var5'], np.ndarray): row['var5'] = np.array((float(i),)*4) else: row['var5'] = float(i) if hasattr(tb, 'Float16Col'): if isinstance(row['var11'], np.ndarray): row['var11'] = np.array((float(i),)*4) else: row['var11'] = float(i) if hasattr(tb, 'Float96Col'): if isinstance(row['var12'], np.ndarray): row['var12'] = np.array((float(i),)*4) else: row['var12'] = float(i) if hasattr(tb, 'Float128Col'): if isinstance(row['var13'], np.ndarray): row['var13'] = np.array((float(i),)*4) else: row['var13'] = float(i) if hasattr(tb, 'Complex192Col'): if isinstance(row['var14'], np.ndarray): row['var14'] = [float(i)+0j, 1 + float(i)*1j] else: row['var14'] = 1 + float(i)*1j if hasattr(tb, 'Complex256Col'): if isinstance(row['var15'], np.ndarray): row['var15'] = [float(i)+0j, 1 + float(i)*1j] else: row['var15'] = 1 + float(i)*1j # var6 will be like var3 but byteswaped row['var6'] = (((row['var3'] >> 8) & 0xff) + ((row['var3'] << 8) & 0xff00)) # print("Saving -->", row) row.append() # Flush the buffer for this table table.flush() # Create a new group (descendant of group) group2 = self.h5file.create_group(group, 'group'+str(j)) # Iterate over this new group (group2) group = group2 def test00_description(self): """Checking table description and descriptive fields.""" self.h5file = tb.open_file(self.h5fname) tbl = self.h5file.get_node('/table0') desc = tbl.description if isinstance(self.record, dict): columns = self.record elif isinstance(self.record, np.ndarray): descr, _ = tb.description.descr_from_dtype(self.record.dtype) columns = descr._v_colobjects elif isinstance(self.record, np.dtype): descr, _ = tb.description.descr_from_dtype(self.record) columns = descr._v_colobjects else: # This is an ordinary description. columns = self.record.columns # Check table and description attributes at the same time. # These checks are only valid for non-nested tables. # Column names. fix_n_column = 10 expectedNames = ['var%d' % n for n in range(1, fix_n_column + 1)] types = ("float16", "float96", "float128", "complex192", "complex256") for n, typename in enumerate(types, fix_n_column + 1): name = typename.capitalize() + 'Col' if hasattr(tb, name): expectedNames.append('var%d' % n) self.assertEqual(expectedNames, list(tbl.colnames)) self.assertEqual(expectedNames, list(desc._v_names)) # Column instances. for colname in expectedNames: self.assertTrue(tbl.colinstances[colname] is tbl.cols._f_col(colname)) # Column types. expectedTypes = [columns[colname].dtype for colname in expectedNames] self.assertEqual(expectedTypes, [tbl.coldtypes[v] for v in expectedNames]) self.assertEqual(expectedTypes, [desc._v_dtypes[v] for v in expectedNames]) # Column string types. expectedTypes = [columns[colname].type for colname in expectedNames] self.assertEqual(expectedTypes, [tbl.coltypes[v] for v in expectedNames]) self.assertEqual(expectedTypes, [desc._v_types[v] for v in expectedNames]) # Column defaults. for v in expectedNames: if common.verbose: print("dflt-->", columns[v].dflt, type(columns[v].dflt)) print("coldflts-->", tbl.coldflts[v], type(tbl.coldflts[v])) print("desc.dflts-->", desc._v_dflts[v], type(desc._v_dflts[v])) self.assertTrue( common.areArraysEqual(tbl.coldflts[v], columns[v].dflt)) self.assertTrue( common.areArraysEqual(desc._v_dflts[v], columns[v].dflt)) # Column path names. self.assertEqual(expectedNames, list(desc._v_pathnames)) # Column objects. for colName in expectedNames: expectedCol = columns[colName] col = desc._v_colobjects[colName] self.assertEqual(expectedCol.dtype, col.dtype) self.assertEqual(expectedCol.type, col.type) def test01_readTable(self): """Checking table read.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_readTable..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Choose a small value for buffer size table.nrowsinbuf = 3 # Read the records and select those with "var2" file less than 20 result = [rec['var2'] for rec in table.iterrows() if rec['var2'] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last record in table ==>", rec) print("Total selected records in table ==> ", len(result)) nrows = self.expectedrows - 1 rec = list(table.iterrows())[-1] self.assertEqual((rec['var1'], rec['var2'], rec['var7']), (b"0001", nrows, b"1")) if isinstance(rec['var5'], np.ndarray): self.assertTrue(common.allequal( rec['var5'], np.array((float(nrows),)*4, np.float32))) else: self.assertEqual(rec['var5'], float(nrows)) if isinstance(rec['var9'], np.ndarray): self.assertTrue(common.allequal( rec['var9'], np.array([0.+float(nrows)*1.j, float(nrows)+0.j], np.complex64))) else: self.assertEqual((rec['var9']), float(nrows)+0.j) self.assertEqual(len(result), 20) def test01a_fetch_all_fields(self): """Checking table read (using Row.fetch_all_fields)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a_fetch_all_fields..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Choose a small value for buffer size table.nrowsinbuf = 3 # Read the records and select those with "var2" file less than 20 result = [rec.fetch_all_fields() for rec in table.iterrows() if rec['var2'] < 20] rec = result[-1] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last record in table ==>", rec) print("Total selected records in table ==> ", len(result)) nrows = 20 - 1 strnrows = "%04d" % (self.expectedrows - nrows) strnrows = strnrows.encode('ascii') self.assertEqual((rec['var1'], rec['var2'], rec['var7']), (strnrows, nrows, b"1")) if isinstance(rec['var5'], np.ndarray): self.assertTrue(common.allequal( rec['var5'], np.array((float(nrows),)*4, np.float32))) else: self.assertEqual(rec['var5'], float(nrows)) if isinstance(rec['var9'], np.ndarray): self.assertTrue(common.allequal( rec['var9'], np.array([0.+float(nrows)*1.j, float(nrows)+0.j], np.complex64))) else: self.assertEqual(rec['var9'], float(nrows)+0.j) self.assertEqual(len(result), 20) def test01a_integer(self): """Checking table read (using Row[integer])""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a_integer..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Choose a small value for buffer size table.nrowsinbuf = 3 # Read the records and select those with "var2" file less than 20 result = [rec[1] for rec in table.iterrows() if rec['var2'] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Total selected records in table ==> ", len(result)) print("All results ==>", result) self.assertEqual(len(result), 20) self.assertEqual(result, list(range(20))) def test01a_extslice(self): """Checking table read (using Row[::2])""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a_extslice..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Choose a small value for buffer size table.nrowsinbuf = 3 # Read the records and select those with "var2" file less than 20 result = [rec[::2] for rec in table.iterrows() if rec['var2'] < 20] rec = result[-1] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last record in table ==>", rec) print("Total selected records in table ==> ", len(result)) nrows = 20 - 1 strnrows = "%04d" % (self.expectedrows - nrows) strnrows = strnrows.encode('ascii') self.assertEqual(rec[:2], (strnrows, 19)) self.assertEqual(rec[3], b'1') if isinstance(rec[2], np.ndarray): self.assertTrue(common.allequal( rec[2], np.array((float(nrows),)*4, np.float32))) else: self.assertEqual(rec[2], nrows) if isinstance(rec[4], np.ndarray): self.assertTrue(common.allequal( rec[4], np.array([0.+float(nrows)*1.j, float(nrows)+0.j], np.complex64))) else: self.assertEqual(rec[4], float(nrows)+0.j) self.assertEqual(len(result), 20) def test01a_nofield(self): """Checking table read (using Row['no-field'])""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a_nofield..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Check that a KeyError is raised # self.assertRaises only work with functions # self.assertRaises(KeyError, [rec['no-field'] for rec in table]) with self.assertRaises(KeyError): result = [rec['no-field'] for rec in table] if common.verbose: print('result:', result) def test01a_badtypefield(self): """Checking table read (using Row[{}])""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a_badtypefield..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Check that a TypeError is raised # self.assertRaises only work with functions # self.assertRaises(TypeError, [rec[{}] for rec in table]) with self.assertRaises(TypeError): result = [rec[{}] for rec in table] if common.verbose: print('result:', result) def test01b_readTable(self): """Checking table read and cuts (multidimensional columns case)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b_readTable..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Choose a small value for buffer size table.nrowsinbuf = 3 # Read the records and select those with "var2" file less than 20 result = [rec['var5'] for rec in table.iterrows() if rec['var2'] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last record in table ==>", rec) print("rec['var5'] ==>", rec['var5'], end=' ') print("nrows ==>", table.nrows) print("Total selected records in table ==> ", len(result)) nrows = table.nrows rec = list(table.iterrows())[-1] if isinstance(rec['var5'], np.ndarray): np.testing.assert_array_equal( result[0], np.array((float(0),)*4, np.float32)) np.testing.assert_array_equal( result[1], np.array((float(1),)*4, np.float32)) np.testing.assert_array_equal( result[2], np.array((float(2),)*4, np.float32)) np.testing.assert_array_equal( result[3], np.array((float(3),)*4, np.float32)) np.testing.assert_array_equal( result[10], np.array((float(10),)*4, np.float32)) np.testing.assert_array_equal( rec['var5'], np.array((float(nrows-1),)*4, np.float32)) else: self.assertEqual(rec['var5'], float(nrows - 1)) # Read the records and select those with "var2" file less than 20 result = [record['var10'] for record in table.iterrows() if record['var2'] < 20] if isinstance(rec['var10'], np.ndarray): np.testing.assert_array_equal( result[0], np.array([float(0)+0.j, 1.+float(0)*1j], np.complex128)) np.testing.assert_array_equal( result[1], np.array([float(1)+0.j, 1.+float(1)*1j], np.complex128)) np.testing.assert_array_equal( result[2], np.array([float(2)+0.j, 1.+float(2)*1j], np.complex128)) np.testing.assert_array_equal( result[3], np.array([float(3)+0.j, 1.+float(3)*1j], np.complex128)) np.testing.assert_array_equal( result[10], np.array([float(10)+0.j, 1.+float(10)*1j], np.complex128)) np.testing.assert_array_equal( rec['var10'], np.array([float(nrows-1)+0.j, 1.+float(nrows-1)*1j], np.complex128)) else: self.assertEqual(rec['var10'], 1.+float(nrows-1)*1j) self.assertEqual(len(result), 20) def test01c_readTable(self): """Checking nested iterators (reading)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01c_readTable..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Read the records and select those with "var2" file less than 20 result = [] for rec in table.iterrows(stop=2): for rec2 in table.iterrows(stop=2): if rec2['var2'] < 20: result.append([rec['var2'], rec2['var2']]) if common.verbose: print("result ==>", result) self.assertEqual(result, [[0, 0], [0, 1], [1, 0], [1, 1]]) def test01d_readTable(self): """Checking nested iterators (reading, mixed conditions)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01d_readTable..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Read the records and select those with "var2" file less than 20 result = [] for rec in table.iterrows(stop=2): for rec2 in table.where('var2 < 20', stop=2): result.append([rec['var2'], rec2['var2']]) if common.verbose: print("result ==>", result) self.assertEqual(result, [[0, 0], [0, 1], [1, 0], [1, 1]]) def test01e_readTable(self): """Checking nested iterators (reading, both conditions)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01e_readTable..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Read the records and select those with "var2" file less than 20 result = [] for rec in table.where('var3 < 2'): for rec2 in table.where('var2 < 3'): result.append([rec['var2'], rec2['var3']]) if common.verbose: print("result ==>", result) self.assertEqual(result, [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]]) def test01f_readTable(self): """Checking nested iterators (reading, break in the loop)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01f_readTable..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Read the records and select those with "var2" file less than 20 result = [] for rec in table.where('var3 < 2'): for rec2 in table.where('var2 < 4'): if rec2['var2'] >= 3: break result.append([rec['var2'], rec2['var3']]) if common.verbose: print("result ==>", result) self.assertEqual(result, [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]]) def test01g_readTable(self): """Checking iterator with an evanescent table.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01g_readTable..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") # Read from an evanescent table result = [rec['var2'] for rec in self.h5file.get_node("/table0") if rec['var2'] < 20] self.assertEqual(len(result), 20) def test02_AppendRows(self): """Checking whether appending record rows works or not.""" # Now, open it, but in "append" mode self.h5file = tb.open_file(self.h5fname, mode="a") self.rootgroup = self.h5file.root if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_AppendRows..." % self.__class__.__name__) # Get a table table = self.h5file.get_node("/group0/table1") # Get their row object row = table.row if common.verbose: print("Nrows in old", table._v_pathname, ":", table.nrows) print("Record Format ==>", table.description._v_nested_formats) print("Record Size ==>", table.rowsize) # Append some rows for i in range(self.appendrows): s = '%04d' % (self.appendrows - i) row['var1'] = s.encode('ascii') row['var7'] = s[-1].encode('ascii') row['var2'] = i row['var3'] = i % self.maxshort if isinstance(row['var4'], np.ndarray): row['var4'] = [float(i), float(i * i)] else: row['var4'] = float(i) if isinstance(row['var8'], np.ndarray): row['var8'] = [0, 1] else: row['var8'] = 1 if isinstance(row['var9'], np.ndarray): row['var9'] = [0.+float(i)*1j, float(i)+0.j] else: row['var9'] = float(i)+0.j if isinstance(row['var10'], np.ndarray): row['var10'] = [float(i)+0.j, 1.+float(i)*1j] else: row['var10'] = 1.+float(i)*1j if isinstance(row['var5'], np.ndarray): row['var5'] = np.array((float(i),)*4) else: row['var5'] = float(i) if hasattr(tb, 'Float16Col'): if isinstance(row['var11'], np.ndarray): row['var11'] = np.array((float(i),)*4) else: row['var11'] = float(i) if hasattr(tb, 'Float96Col'): if isinstance(row['var12'], np.ndarray): row['var12'] = np.array((float(i),)*4) else: row['var12'] = float(i) if hasattr(tb, 'Float128Col'): if isinstance(row['var13'], np.ndarray): row['var13'] = np.array((float(i),)*4) else: row['var13'] = float(i) if hasattr(tb, 'Complex192Col'): if isinstance(row['var14'], np.ndarray): row['var14'] = [float(i)+0j, 1 + float(i)*1j] else: row['var14'] = 1 + float(i)*1j if hasattr(tb, 'Complex256Col'): if isinstance(row['var15'], np.ndarray): row['var15'] = [float(i)+0j, 1 + float(i)*1j] else: row['var15'] = 1 + float(i)*1j row.append() # Flush the buffer for this table and read it table.flush() result = [r['var2'] for r in table.iterrows() if r['var2'] < 20] nrows = self.appendrows - 1 row = list(table.iterrows())[-1] self.assertEqual((row['var1'], row['var2'], row['var7']), (b"0001", nrows, b"1")) if isinstance(row['var5'], np.ndarray): self.assertTrue(common.allequal( row['var5'], np.array((float(nrows),)*4, np.float32))) else: self.assertEqual(row['var5'], float(nrows)) if self.appendrows <= 20: add = self.appendrows else: add = 20 self.assertEqual(len(result), 20 + add) # because we appended new rows # This test has been commented out because appending records without # flushing them explicitely is being warned from now on. # F. Alted 2006-08-03 def _test02a_AppendRows(self): """Checking appending records without flushing explicitely.""" # Now, open it, but in "append" mode self.h5file = tb.open_file(self.h5fname, mode="a") self.rootgroup = self.h5file.root if common.verbose: print('\n', '-=' * 30) print("Running %s.test02a_AppendRows..." % self.__class__.__name__) group = self.rootgroup for group_i in range(3): # Get a table table = self.h5file.get_node(group, 'table'+str(group_i)) # Get the next group group = self.h5file.get_node(group, 'group'+str(group_i)) # Get their row object row = table.row if common.verbose: print("Nrows in old", table._v_pathname, ":", table.nrows) print("Record Format ==>", table.description._v_nested_formats) print("Record Size ==>", table.rowsize) # Append some rows for row_i in range(self.appendrows): row['var1'] = '%04d' % (self.appendrows - row_i) row['var7'] = row['var1'][-1] row['var2'] = row_i row['var3'] = row_i % self.maxshort if isinstance(row['var4'], np.ndarray): row['var4'] = [float(row_i), float(row_i * row_i)] else: row['var4'] = float(row_i) if isinstance(row['var8'], np.ndarray): row['var8'] = [0, 1] else: row['var8'] = 1 if isinstance(row['var9'], np.ndarray): row['var9'] = [0.+float(row_i)*1j, float(row_i)+0.j] else: row['var9'] = float(row_i)+0.j if isinstance(row['var10'], np.ndarray): row['var10'] = [float(row_i)+0.j, 1.+float(row_i)*1j] else: row['var10'] = 1.+float(row_i)*1j if isinstance(row['var5'], np.ndarray): row['var5'] = np.array((float(row_i),)*4) else: row['var5'] = float(row_i) if hasattr(tb, 'Float16Col'): if isinstance(row['var11'], np.ndarray): row['var11'] = np.array((float(row_i),)*4) else: row['var11'] = float(row_i) if hasattr(tb, 'Float96Col'): if isinstance(row['var12'], np.ndarray): row['var12'] = np.array((float(row_i),)*4) else: row['var12'] = float(row_i) if hasattr(tb, 'Float128Col'): if isinstance(row['var13'], np.ndarray): row['var13'] = np.array((float(row_i),)*4) else: row['var13'] = float(row_i) if hasattr(tb, 'Complex192Col'): if isinstance(row['var14'], np.ndarray): row['var14'] = [float(row_i)+0j, 1 + float(row_i)*1j] else: row['var14'] = 1 + float(row_i)*1j if hasattr(tb, 'Complex256Col'): if isinstance(row['var15'], np.ndarray): row['var15'] = [float(row_i)+0j, 1 + float(row_i)*1j] else: row['var15'] = 1 + float(row_i)*1j row.append() table.flush() # Close the file and re-open it. self.h5file.close() self.h5file = tb.open_file(self.h5fname, mode="a") table = self.h5file.root.table0 # Flush the buffer for this table and read it result = [r['var2'] for r in table.iterrows() if r['var2'] < 20] nrows = self.appendrows - 1 self.assertEqual((row['var1'], row['var2'], row['var7']), ("0001", nrows, "1")) if isinstance(row['var5'], np.ndarray): self.assertTrue(common.allequal( row['var5'], np.array((float(nrows),)*4, np.float32))) else: self.assertEqual(row['var5'], float(nrows)) if self.appendrows <= 20: add = self.appendrows else: add = 20 self.assertEqual(len(result), 20 + add) # because we appended new rows def test02b_AppendRows(self): """Checking whether appending *and* reading rows works or not""" # Now, open it, but in "append" mode self.h5file = tb.open_file(self.h5fname, mode="a") self.rootgroup = self.h5file.root if common.verbose: print('\n', '-=' * 30) print("Running %s.test02b_AppendRows..." % self.__class__.__name__) # Get a table table = self.h5file.get_node("/group0/table1") if common.verbose: print("Nrows in old", table._v_pathname, ":", table.nrows) print("Record Format ==>", table.description._v_nested_formats) print("Record Size ==>", table.rowsize) # Set a small number of buffer to make this test faster table.nrowsinbuf = 3 # Get their row object row = table.row # Append some rows (3 * table.nrowsinbuf is enough for # checking purposes) for i in range(3 * table.nrowsinbuf): s = '%04d' % (self.appendrows - i) row['var1'] = s.encode('ascii') row['var7'] = s[-1].encode('ascii') # row['var7'] = table.cols['var1'][i][-1] row['var2'] = i row['var3'] = i % self.maxshort if isinstance(row['var4'], np.ndarray): row['var4'] = [float(i), float(i * i)] else: row['var4'] = float(i) if isinstance(row['var8'], np.ndarray): row['var8'] = [0, 1] else: row['var8'] = 1 if isinstance(row['var9'], np.ndarray): row['var9'] = [0.+float(i)*1j, float(i)+0.j] else: row['var9'] = float(i)+0.j if isinstance(row['var10'], np.ndarray): row['var10'] = [float(i)+0.j, 1.+float(i)*1j] else: row['var10'] = 1.+float(i)*1j if isinstance(row['var5'], np.ndarray): row['var5'] = np.array((float(i),)*4) else: row['var5'] = float(i) if hasattr(tb, 'Float16Col'): if isinstance(row['var11'], np.ndarray): row['var11'] = np.array((float(i),)*4) else: row['var11'] = float(i) if hasattr(tb, 'Float96Col'): if isinstance(row['var12'], np.ndarray): row['var12'] = np.array((float(i),)*4) else: row['var12'] = float(i) if hasattr(tb, 'Float128Col'): if isinstance(row['var13'], np.ndarray): row['var13'] = np.array((float(i),)*4) else: row['var13'] = float(i) if hasattr(tb, 'Complex192Col'): if isinstance(row['var14'], np.ndarray): row['var14'] = [float(i)+0j, 1 + float(i)*1j] else: row['var14'] = 1 + float(i)*1j if hasattr(tb, 'Complex256Col'): if isinstance(row['var15'], np.ndarray): row['var15'] = [float(i)+0j, 1 + float(i)*1j] else: row['var15'] = 1 + float(i)*1j row.append() # the next call can mislead the counters result = [row2['var2'] for row2 in table] # warning! the next will result into wrong results # result = [ row['var2'] for row in table ] # This is because the iterator for writing and for reading # cannot be shared! # Do not flush the buffer for this table and try to read it # We are forced now to flush tables after append operations # because of unsolved issues with the LRU cache that are too # difficult to track. # F. Alted 2006-08-03 table.flush() result = [ row3['var2'] for row3 in table.iterrows() if row3['var2'] < 20 ] if common.verbose: print("Result length ==>", len(result)) print("Result contents ==>", result) self.assertEqual(len(result), 20 + 3 * table.nrowsinbuf) self.assertEqual(result, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 1, 2, 3, 4, 5, 6, 7, 8]) # Check consistency of I/O buffers when doing mixed I/O operations # That is, the next should work in these operations # row['var1'] = '%04d' % (self.appendrows - i) # row['var7'] = row['var1'][-1] result7 = [ row4['var7'] for row4 in table.iterrows() if row4['var2'] < 20 ] if common.verbose: print("Result7 length ==>", len(result7)) print("Result7 contents ==>", result7) self.assertEqual( result7, [b'0', b'9', b'8', b'7', b'6', b'5', b'4', b'3', b'2', b'1', b'0', b'9', b'8', b'7', b'6', b'5', b'4', b'3', b'2', b'1', b'0', b'9', b'8', b'7', b'6', b'5', b'4', b'3', b'2']) def test02d_AppendRows(self): """Checking appending using the same Row object after flushing.""" # This test is kind of magic, but it is a good sanity check anyway. # Now, open it, but in "append" mode self.h5file = tb.open_file(self.h5fname, mode="a") self.rootgroup = self.h5file.root if common.verbose: print('\n', '-=' * 30) print("Running %s.test02d_AppendRows..." % self.__class__.__name__) # Get a table table = self.h5file.get_node("/group0/table1") if common.verbose: print("Nrows in old", table._v_pathname, ":", table.nrows) print("Record Format ==>", table.description._v_nested_formats) print("Record Size ==>", table.rowsize) # Set a small number of buffer to make this test faster table.nrowsinbuf = 3 # Get their row object row = table.row # Append some rows for i in range(10): row['var2'] = 100 + i row.append() # Force a flush table.flush() # Add new rows for i in range(9): row['var2'] = 110 + i row.append() table.flush() # XXX al eliminar... result = [ r['var2'] for r in table.iterrows() if 100 <= r['var2'] < 120 ] if common.verbose: print("Result length ==>", len(result)) print("Result contents ==>", result) if table.nrows > 119: # Case for big tables self.assertEqual(len(result), 39) self.assertEqual(result, [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118]) else: self.assertEqual(len(result), 19) self.assertEqual(result, [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118]) def test02e_AppendRows(self): """Checking appending using the Row of an unreferenced table.""" # See ticket #94 (http://www.pytables.org/trac/ticket/94). # Reopen the file in append mode. self.h5file = tb.open_file(self.h5fname, mode='a') # Get the row handler which will outlive the reference to the table. table = self.h5file.get_node('/group0/table1') oldnrows = table.nrows row = table.row # Few appends are made to avoid flushing the buffers in ``row``. # First case: append to an alive (referenced) table. row.append() table.flush() newnrows = table.nrows self.assertEqual(newnrows, oldnrows + 1, "Append to alive table failed.") if self.h5file._node_manager.cache.nslots == 0: # Skip this test from here on because the second case # won't work when thereis not a node cache. return # Second case: append to a dead (unreferenced) table. del table row.append() table = self.h5file.get_node('/group0/table1') table.flush() newnrows = table.nrows self.assertEqual(newnrows, oldnrows + 2, "Append to dead table failed.") # CAVEAT: The next test only works for tables with rows < 2**15 def test03_endianess(self): """Checking if table is endianess aware.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_endianess..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/group0/group1/table2") # Read the records and select the ones with "var3" column less than 20 result = [rec['var2'] for rec in table.iterrows() if rec['var3'] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("On-disk byteorder ==>", table.byteorder) print("Last record in table ==>", rec) print("Selected records ==>", result) print("Total selected records in table ==>", len(result)) nrows = self.expectedrows - 1 self.assertEqual(table.byteorder, {"little": "big", "big": "little"}[sys.byteorder]) rec = list(table.iterrows())[-1] self.assertEqual((rec['var1'], rec['var3']), (b"0001", nrows)) self.assertEqual(len(result), 20) def test04_delete(self): """Checking whether a single row can be deleted.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_delete..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "a") table = self.h5file.get_node("/table0") # Read the records and select the ones with "var2" column less than 20 result = [r['var2'] for r in table.iterrows() if r['var2'] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last selected value ==>", result[-1]) print("Total selected records in table ==>", len(result)) nrows = table.nrows table.nrowsinbuf = 3 # small value of the buffer # Delete the twenty-th row table.remove_rows(19, 20) # Re-read the records result2 = [r['var2'] for r in table.iterrows() if r['var2'] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last selected value ==>", result2[-1]) print("Total selected records in table ==>", len(result2)) self.assertEqual(table.nrows, nrows - 1) self.assertEqual(table.shape, (nrows - 1,)) # Check that the new list is smaller than the original one self.assertEqual(len(result), len(result2) + 1) self.assertEqual(result[:-1], result2) def test04a_delete(self): """Checking whether a single row can be deleted.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_delete..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "a") table = self.h5file.get_node("/table0") # Read the records and select the ones with "var2" column less than 20 result = [r['var2'] for r in table.iterrows() if r['var2'] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last selected value ==>", result[-1]) print("Total selected records in table ==>", len(result)) nrows = table.nrows table.nrowsinbuf = 3 # small value of the buffer # Delete the twenty-th row table.remove_row(19) # Re-read the records result2 = [r['var2'] for r in table.iterrows() if r['var2'] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last selected value ==>", result2[-1]) print("Total selected records in table ==>", len(result2)) self.assertEqual(table.nrows, nrows - 1) self.assertEqual(table.shape, (nrows - 1,)) # Check that the new list is smaller than the original one self.assertEqual(len(result), len(result2) + 1) self.assertEqual(result[:-1], result2) def test04b_delete(self): """Checking whether a range of rows can be deleted.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04b_delete..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "a") table = self.h5file.get_node("/table0") # Read the records and select the ones with "var2" column less than 20 result = [r['var2'] for r in table.iterrows() if r['var2'] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last selected value ==>", result[-1]) print("Total selected records in table ==>", len(result)) nrows = table.nrows table.nrowsinbuf = 4 # small value of the buffer # Delete the last ten rows table.remove_rows(10, 20) # Re-read the records result2 = [r['var2'] for r in table.iterrows() if r['var2'] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last selected value ==>", result2[-1]) print("Total selected records in table ==>", len(result2)) self.assertEqual(table.nrows, nrows - 10) self.assertEqual(table.shape, (nrows - 10,)) # Check that the new list is smaller than the original one self.assertEqual(len(result), len(result2) + 10) self.assertEqual(result[:10], result2) def test04c_delete(self): """Checking whether removing a bad range of rows is detected.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04c_delete..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "a") table = self.h5file.get_node("/table0") # Read the records and select the ones with "var2" column less than 20 result = [r['var2'] for r in table.iterrows() if r['var2'] < 20] nrows = table.nrows table.nrowsinbuf = 5 # small value of the buffer # Delete a too large range of rows table.remove_rows(10, nrows + 100) # Re-read the records result2 = [r['var2'] for r in table.iterrows() if r['var2'] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last selected value ==>", result2[-1]) print("Total selected records in table ==>", len(result2)) self.assertEqual(table.nrows, 10) self.assertEqual(table.shape, (10,)) # Check that the new list is smaller than the original one self.assertEqual(len(result), len(result2) + 10) self.assertEqual(result[:10], result2) def test04d_delete(self): """Checking whether removing rows several times at once is working.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04d_delete..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "a") table = self.h5file.get_node("/table0") # Read the records and select the ones with "var2" column less than 20 result = [r['var2'] for r in table if r['var2'] < 20] nrows = table.nrows nrowsinbuf = table.nrowsinbuf table.nrowsinbuf = 6 # small value of the buffer # Delete some rows table.remove_rows(10, 15) # It's necessary to restore the value of buffer to use the row object # afterwards... table.nrowsinbuf = nrowsinbuf # Append some rows row = table.row for i in range(10, 15): row['var1'] = '%04d' % (self.appendrows - i) # This line gives problems on Windows. Why? # row['var7'] = row['var1'][-1] row['var2'] = i row['var3'] = i % self.maxshort if isinstance(row['var4'], np.ndarray): row['var4'] = [float(i), float(i * i)] else: row['var4'] = float(i) if isinstance(row['var8'], np.ndarray): row['var8'] = [0, 1] else: row['var8'] = 1 if isinstance(row['var9'], np.ndarray): row['var9'] = [0.+float(i)*1j, float(i)+0.j] else: row['var9'] = float(i)+0.j if isinstance(row['var10'], np.ndarray): row['var10'] = [float(i)+0.j, 1.+float(i)*1j] else: row['var10'] = 1.+float(i)*1j if isinstance(row['var5'], np.ndarray): row['var5'] = np.array((float(i),)*4) else: row['var5'] = float(i) if hasattr(tb, 'Float16Col'): if isinstance(row['var11'], np.ndarray): row['var11'] = np.array((float(i),)*4) else: row['var11'] = float(i) if hasattr(tb, 'Float96Col'): if isinstance(row['var12'], np.ndarray): row['var12'] = np.array((float(i),)*4) else: row['var12'] = float(i) if hasattr(tb, 'Float128Col'): if isinstance(row['var13'], np.ndarray): row['var13'] = np.array((float(i),)*4) else: row['var13'] = float(i) if hasattr(tb, 'Complex192Col'): if isinstance(row['var14'], np.ndarray): row['var14'] = [float(i)+0j, 1 + float(i)*1j] else: row['var14'] = 1 + float(i)*1j if hasattr(tb, 'Complex256Col'): if isinstance(row['var15'], np.ndarray): row['var15'] = [float(i)+0j, 1 + float(i)*1j] else: row['var15'] = 1 + float(i)*1j row.append() # Flush the buffer for this table table.flush() # Delete 5 rows more table.remove_rows(5, 10) # Re-read the records result2 = [r['var2'] for r in table if r['var2'] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last selected value ==>", result2[-1]) print("Total selected records in table ==>", len(result2)) self.assertEqual(table.nrows, nrows - 5) self.assertEqual(table.shape, (nrows - 5,)) # Check that the new list is smaller than the original one self.assertEqual(len(result), len(result2) + 5) # The last values has to be equal self.assertEqual(result[10:15], result2[10:15]) def test04e_delete(self): """Checking whether all rows can be deleted.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04e_delete..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "a") table = self.h5file.get_node("/table0") # Read all records result = [r['var2'] for r in table.iterrows()] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last selected value ==>", result[-1]) print("Total selected records in table ==>", len(result)) table.nrowsinbuf = 4 # small value of the buffer # Delete all rows table.remove_rows(0, self.expectedrows) # Re-read the records result2 = [r['var2'] for r in table.iterrows()] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Total selected records in table ==>", len(result2)) self.assertEqual(table.nrows, 0) self.assertEqual(table.shape, (0,)) self.assertEqual(len(result2), 0) def test04f_delete(self): """Checking whether all rows can be deleted.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04e_delete..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "a") table = self.h5file.get_node("/table0") # Read all records result = [r['var2'] for r in table.iterrows()] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last selected value ==>", result[-1]) print("Total selected records in table ==>", len(result)) table.nrowsinbuf = 4 # small value of the buffer # Delete 100 rows table.remove_rows() # Re-read the records result2 = [r['var2'] for r in table.iterrows()] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Total selected records in table ==>", len(result2)) self.assertEqual(table.nrows, 0) self.assertEqual(table.shape, (0,)) self.assertEqual(len(result2), 0) def test04g_delete(self): """Checking whether rows can be deleted with a step parameter.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04e_delete..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "a") table = self.h5file.get_node("/table0") # Read all records result = [r['var2'] for r in table.iterrows()] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last selected value ==>", result[-1]) print("Total selected records in table ==>", len(result)) nrows = table.nrows table.nrowsinbuf = 4 # small value of the buffer # Delete 100 rows table.remove_rows(0, nrows+1, 5) # Re-read the records result2 = [r['var2'] for r in table.iterrows()] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Total selected records in table ==>", len(result2)) outnrows = nrows - nrows // 5 self.assertEqual(table.nrows, outnrows) self.assertEqual(table.shape, (outnrows,)) self.assertEqual(len(result2), outnrows) def test05_filtersTable(self): """Checking tablefilters.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_filtersTable..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Check filters: if self.compress != table.filters.complevel and common.verbose: print("Error in compress. Class:", self.__class__.__name__) print("self, table:", self.compress, table.filters.complevel) self.assertEqual(table.filters.complevel, self.compress) if self.compress > 0 and tb.which_lib_version(self.complib): self.assertEqual(table.filters.complib, self.complib) if self.shuffle != table.filters.shuffle and common.verbose: print("Error in shuffle. Class:", self.__class__.__name__) print("self, table:", self.shuffle, table.filters.shuffle) self.assertEqual(self.shuffle, table.filters.shuffle) if self.bitshuffle != table.filters.bitshuffle and common.verbose: print("Error in bitshuffle. Class:", self.__class__.__name__) print("self, table:", self.bitshuffle, table.filters.bitshuffle) self.assertEqual(self.bitshuffle, table.filters.bitshuffle) if self.fletcher32 != table.filters.fletcher32 and common.verbose: print("Error in fletcher32. Class:", self.__class__.__name__) print("self, table:", self.fletcher32, table.filters.fletcher32) self.assertEqual(self.fletcher32, table.filters.fletcher32) def test06_attributes(self): self.h5file = tb.open_file(self.h5fname) obj = self.h5file.get_node('/table0') self.assertEqual(obj.flavor, 'numpy') self.assertEqual(obj.shape, (self.expectedrows,)) self.assertEqual(obj.ndim, 1) self.assertEqual(obj.nrows, self.expectedrows) def test07_out_of_order_members(self): # If members are stored 'out of order' make sure they are loaded # correctly self.h5file = tb.open_file( common.test_filename("out_of_order_types.h5")) row = self.h5file.get_node('/group/table')[0] self.assertEqual(row[0], b'*'*14) self.assertEqual(row[1], b'-'*9) self.assertEqual(row[2], b'.'*4) class BasicWriteTestCase(BasicTestCase): title = "BasicWrite" class OldRecordBasicWriteTestCase(BasicTestCase): title = "OldRecordBasicWrite" record = OldRecord class DictWriteTestCase(BasicTestCase): # This checks also unidimensional arrays as columns title = "DictWrite" record = RecordDescriptionDict nrows = 21 nrowsinbuf = 3 # Choose a small value for the buffer size start = 0 stop = 10 step = 3 # Pure NumPy dtype class NumPyDTWriteTestCase(BasicTestCase): title = "NumPyDTWriteTestCase" formats = "a4,i4,i2,2f8,f4,i2,a1,b1,c8,c16".split(',') names = 'var1,var2,var3,var4,var5,var6,var7,var8,var9,var10'.split(',') if hasattr(tb, 'Float16Col'): formats.append('f2') names.append('var11') if hasattr(tb, 'Float96Col'): formats.append('f12') names.append('var12') if hasattr(tb, 'Float128Col'): formats.append('f16') names.append('var13') if hasattr(tb, 'Complex192Col'): formats.append('c24') names.append('var14') if hasattr(tb, 'Complex256Col'): formats.append('c32') names.append('var15') record = np.dtype(','.join(formats)) record.names = names class RecArrayOneWriteTestCase(BasicTestCase): title = "RecArrayOneWrite" formats = "a4,i4,i2,2f8,f4,i2,a1,b1,c8,c16".split(',') names = 'var1,var2,var3,var4,var5,var6,var7,var8,var9,var10'.split(',') if hasattr(tb, 'Float16Col'): formats.append('f2') names.append('var11') if hasattr(tb, 'Float96Col'): formats.append('f12') names.append('var12') if hasattr(tb, 'Float128Col'): formats.append('f16') names.append('var13') if hasattr(tb, 'Complex192Col'): formats.append('c24') names.append('var14') if hasattr(tb, 'Complex256Col'): formats.append('c32') names.append('var15') record = np.rec.array( None, shape=0, formats=','.join(formats), names=names) class RecArrayTwoWriteTestCase(BasicTestCase): title = "RecArrayTwoWrite" expectedrows = 100 recarrayinit = 1 formats = "a4,i4,i2,2f8,f4,i2,a1,b1,c8,c16".split(',') names = 'var1,var2,var3,var4,var5,var6,var7,var8,var9,var10'.split(',') if hasattr(tb, 'Float16Col'): formats.append('f2') names.append('var11') if hasattr(tb, 'Float96Col'): formats.append('f12') names.append('var12') if hasattr(tb, 'Float128Col'): formats.append('f16') names.append('var13') if hasattr(tb, 'Complex192Col'): formats.append('c24') names.append('var14') if hasattr(tb, 'Complex256Col'): formats.append('c32') names.append('var15') recordtemplate = np.rec.array( None, shape=1, formats=','.join(formats), names=names) class RecArrayThreeWriteTestCase(BasicTestCase): title = "RecArrayThreeWrite" expectedrows = 100 recarrayinit = 1 formats = "a4,i4,i2,2f8,f4,i2,a1,b1,c8,c16".split(',') names = 'var1,var2,var3,var4,var5,var6,var7,var8,var9,var10'.split(',') if hasattr(tb, 'Float16Col'): formats.append('f2') names.append('var11') if hasattr(tb, 'Float96Col'): formats.append('f12') names.append('var12') if hasattr(tb, 'Float128Col'): formats.append('f16') names.append('var13') if hasattr(tb, 'Complex192Col'): formats.append('c24') names.append('var14') if hasattr(tb, 'Complex256Col'): formats.append('c32') names.append('var15') recordtemplate = np.rec.array( None, shape=1, formats=','.join(formats), names=names) class RecArrayAlignedWriteTestCase(BasicTestCase): title = "RecArrayThreeWrite" expectedrows = 100 recarrayinit = 1 formats = "a4,i4,i2,2f8,f4,i2,a1,b1,c8,c16".split(',') names = 'var1,var2,var3,var4,var5,var6,var7,var8,var9,var10'.split(',') if hasattr(tb, 'Float16Col'): formats.append('f2') names.append('var11') if hasattr(tb, 'Float96Col'): formats.append('f12') names.append('var12') if hasattr(tb, 'Float128Col'): formats.append('f16') names.append('var13') if hasattr(tb, 'Complex192Col'): formats.append('c24') names.append('var14') if hasattr(tb, 'Complex256Col'): formats.append('c32') names.append('var15') recordtemplate = np.rec.array( None, shape=1, formats=','.join(formats), names=names, aligned=True) @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class CompressBloscTablesTestCase(BasicTestCase): title = "CompressBloscTables" compress = 6 complib = "blosc" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class CompressBloscShuffleTablesTestCase(BasicTestCase): title = "CompressBloscTables" compress = 1 shuffle = 1 complib = "blosc" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( common.blosc_version < common.min_blosc_bitshuffle_version, f'BLOSC >= {common.min_blosc_bitshuffle_version} required') class CompressBloscBitShuffleTablesTestCase(BasicTestCase): title = "CompressBloscBitShuffleTables" compress = 1 shuffle = 0 bitshuffle = 1 complib = "blosc:blosclz" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class CompressBloscBloscLZTablesTestCase(BasicTestCase): title = "CompressBloscLZTables" compress = 1 shuffle = 1 complib = "blosc:blosclz" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'lz4' not in tb.blosc_compressor_list(), 'lz4 required') class CompressBloscLZ4TablesTestCase(BasicTestCase): title = "CompressLZ4Tables" compress = 1 shuffle = 1 complib = "blosc:lz4" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'lz4' not in tb.blosc_compressor_list(), 'lz4 required') class CompressBloscLZ4HCTablesTestCase(BasicTestCase): title = "CompressLZ4HCTables" compress = 1 shuffle = 1 complib = "blosc:lz4hc" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf('snappy' not in tb.blosc_compressor_list(), 'snappy required') class CompressBloscSnappyTablesTestCase(BasicTestCase): title = "CompressSnappyTables" compress = 1 shuffle = 1 complib = "blosc:snappy" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'zlib' not in tb.blosc_compressor_list(), 'zlib required') class CompressBloscZlibTablesTestCase(BasicTestCase): title = "CompressZlibTables" compress = 1 shuffle = 1 complib = "blosc:zlib" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'zstd' not in tb.blosc_compressor_list(), 'zstd required') class CompressBloscZstdTablesTestCase(BasicTestCase): title = "CompressZstdTables" compress = 1 shuffle = 1 complib = "blosc:zstd" @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class CompressLZOTablesTestCase(BasicTestCase): title = "CompressLZOTables" compress = 1 complib = "lzo" @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class CompressLZOShuffleTablesTestCase(BasicTestCase): title = "CompressLZOTables" compress = 1 shuffle = 1 complib = "lzo" @common.unittest.skipIf(not common.bzip2_avail, 'BZIP2 compression library not available') class CompressBzip2TablesTestCase(BasicTestCase): title = "CompressBzip2Tables" compress = 1 complib = "bzip2" @common.unittest.skipIf(not common.bzip2_avail, 'BZIP2 compression library not available') class CompressBzip2ShuffleTablesTestCase(BasicTestCase): title = "CompressBzip2Tables" compress = 1 shuffle = 1 complib = "bzip2" class CompressZLIBTablesTestCase(BasicTestCase): title = "CompressOneTables" compress = 1 complib = "zlib" class CompressZLIBShuffleTablesTestCase(BasicTestCase): title = "CompressOneTables" compress = 1 shuffle = 1 complib = "zlib" class Fletcher32TablesTestCase(BasicTestCase): title = "Fletcher32Tables" fletcher32 = 1 shuffle = 0 complib = "zlib" class AllFiltersTablesTestCase(BasicTestCase): title = "AllFiltersTables" compress = 1 fletcher32 = 1 shuffle = 1 complib = "zlib" class CompressTwoTablesTestCase(BasicTestCase): title = "CompressTwoTables" compress = 1 # This checks also unidimensional arrays as columns record = RecordDescriptionDict class BigTablesTestCase(BasicTestCase): title = "BigTables" # 10000 rows takes much more time than we can afford for tests # reducing to 1000 would be more than enough # F. Alted 2004-01-19 # Will be executed only in common.heavy mode expectedrows = 10_000 appendrows = 100 class SizeOnDiskInMemoryPropertyTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() # set chunkshape so it divides evenly into array_size, to avoid # partially filled chunks self.chunkshape = (1000, ) self.dtype = np.format_parser(['i4'] * 10, [], []).dtype # approximate size (in bytes) of non-data portion of hdf5 file self.hdf_overhead = 6000 def create_table(self, complevel): filters = tb.Filters(complevel=complevel, complib='blosc') self.table = self.h5file.create_table('/', 'sometable', self.dtype, filters=filters, chunkshape=self.chunkshape) def test_zero_length(self): complevel = 0 self.create_table(complevel) self.assertEqual(self.table.size_on_disk, 0) self.assertEqual(self.table.size_in_memory, 0) # add 10 chunks of data in one append def test_no_compression_one_append(self): complevel = 0 self.create_table(complevel) self.table.append([tuple(range(10))] * self.chunkshape[0] * 10) self.assertEqual(self.table.size_on_disk, 10 * 1000 * 10 * 4) self.assertEqual(self.table.size_in_memory, 10 * 1000 * 10 * 4) # add 10 chunks of data in two appends def test_no_compression_multiple_appends(self): complevel = 0 self.create_table(complevel) self.table.append([tuple(range(10))] * self.chunkshape[0] * 5) self.table.append([tuple(range(10))] * self.chunkshape[0] * 5) self.assertEqual(self.table.size_on_disk, 10 * 1000 * 10 * 4) self.assertEqual(self.table.size_in_memory, 10 * 1000 * 10 * 4) def test_with_compression(self): complevel = 1 self.create_table(complevel) self.table.append([tuple(range(10))] * self.chunkshape[0] * 10) file_size = Path(self.h5fname).stat().st_size self.assertTrue( abs(self.table.size_on_disk - file_size) <= self.hdf_overhead) self.assertEqual(self.table.size_in_memory, 10 * 1000 * 10 * 4) self.assertLess(self.table.size_on_disk, self.table.size_in_memory) class NonNestedTableReadTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.dtype = np.format_parser(['i4'] * 10, [], []).dtype self.table = self.h5file.create_table('/', 'table', self.dtype) self.shape = (100, ) self.populate_file() def populate_file(self): self.array = np.zeros(self.shape, self.dtype) for row_num, row in enumerate(self.array): start = row_num * len(self.array.dtype.names) for value, col in enumerate(self.array.dtype.names, start): row[col] = value self.table.append(self.array) self.assertEqual(len(self.table), len(self.array)) def test_read_all(self): output = self.table.read() np.testing.assert_array_equal(output, self.array) def test_read_slice1(self): output = self.table.read(0, 51) np.testing.assert_array_equal(output, self.array[0:51]) def test_read_all_rows_specified_field(self): output = self.table.read(field='f1') np.testing.assert_array_equal(output, self.array['f1']) def test_read_slice1_specified_field(self): output = self.table.read(1, 64, field='f1') np.testing.assert_array_equal(output, self.array['f1'][1:64]) def test_out_arg_with_non_numpy_flavor(self): output = np.empty(self.shape, self.dtype) self.table.flavor = 'python' self.assertRaises(TypeError, lambda: self.table.read(out=output)) try: self.table.read(out=output) except TypeError as exc: self.assertIn("Optional 'out' argument may only be", str(exc)) def test_read_all_out_arg(self): output = np.empty(self.shape, self.dtype) self.table.read(out=output) np.testing.assert_array_equal(output, self.array) def test_read_slice1_out_arg(self): output = np.empty((51, ), self.dtype) self.table.read(0, 51, out=output) np.testing.assert_array_equal(output, self.array[0:51]) def test_read_all_rows_specified_field_out_arg(self): output = np.empty(self.shape, 'i4') self.table.read(field='f1', out=output) np.testing.assert_array_equal(output, self.array['f1']) def test_read_slice1_specified_field_out_arg(self): output = np.empty((63, ), 'i4') self.table.read(1, 64, field='f1', out=output) np.testing.assert_array_equal(output, self.array['f1'][1:64]) def test_read_all_out_arg_sliced(self): output = np.empty((200, ), self.dtype) output['f0'] = np.random.randint(0, 10_000, (200, )) output_orig = output.copy() self.table.read(out=output[0:100]) np.testing.assert_array_equal(output[0:100], self.array) np.testing.assert_array_equal(output[100:], output_orig[100:]) def test_all_fields_non_contiguous_slice_contiguous_buffer(self): output = np.empty((50, ), self.dtype) self.table.read(0, 100, 2, out=output) np.testing.assert_array_equal(output, self.array[0:100:2]) def test_specified_field_non_contiguous_slice_contiguous_buffer(self): output = np.empty((50, ), 'i4') self.table.read(0, 100, 2, field='f3', out=output) np.testing.assert_array_equal(output, self.array['f3'][0:100:2]) def test_all_fields_non_contiguous_buffer(self): output = np.empty((100, ), self.dtype) output_slice = output[0:100:2] with self.assertRaisesRegex(ValueError, 'output array not C contiguous'): self.table.read(0, 100, 2, field=None, out=output_slice) def test_specified_field_non_contiguous_buffer(self): output = np.empty((100, ), 'i4') output_slice = output[0:100:2] self.assertRaises(ValueError, self.table.read, 0, 100, 2, 'f3', output_slice) try: self.table.read(0, 100, 2, field='f3', out=output_slice) except ValueError as exc: self.assertEqual('output array not C contiguous', str(exc)) def test_all_fields_buffer_too_small(self): output = np.empty((99, ), self.dtype) self.assertRaises(ValueError, lambda: self.table.read(out=output)) try: self.table.read(out=output) except ValueError as exc: self.assertIn('output array size invalid, got', str(exc)) def test_specified_field_buffer_too_small(self): output = np.empty((99, ), 'i4') self.assertRaises(ValueError, lambda: self.table.read(field='f5', out=output)) try: self.table.read(field='f5', out=output) except ValueError as exc: self.assertIn('output array size invalid, got', str(exc)) def test_all_fields_buffer_too_large(self): output = np.empty((101, ), self.dtype) self.assertRaises(ValueError, lambda: self.table.read(out=output)) try: self.table.read(out=output) except ValueError as exc: self.assertIn('output array size invalid, got', str(exc)) class TableReadByteorderTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.system_byteorder = sys.byteorder self.other_byteorder = { 'little': 'big', 'big': 'little'}[sys.byteorder] self.reverse_byteorders = {'little': '<', 'big': '>'} def create_table(self, byteorder): table_dtype_code = self.reverse_byteorders[byteorder] + 'i4' table_dtype = np.format_parser([table_dtype_code, 'a1'], [], []).dtype self.table = self.h5file.create_table('/', 'table', table_dtype, byteorder=byteorder) input_dtype = np.format_parser(['i4', 'a1'], [], []).dtype self.input_array = np.zeros((10, ), input_dtype) self.input_array['f0'] = np.arange(10) self.input_array['f1'] = b'a' self.table.append(self.input_array) def test_table_system_byteorder_no_out_argument(self): self.create_table(self.system_byteorder) output = self.table.read() self.assertEqual(tb.utils.byteorders[output['f0'].dtype.byteorder], self.system_byteorder) np.testing.assert_array_equal(output['f0'], np.arange(10)) def test_table_other_byteorder_no_out_argument(self): self.create_table(self.other_byteorder) output = self.table.read() self.assertEqual(tb.utils.byteorders[output['f0'].dtype.byteorder], self.system_byteorder) np.testing.assert_array_equal(output['f0'], np.arange(10)) def test_table_system_byteorder_out_argument_system_byteorder(self): self.create_table(self.system_byteorder) out_dtype_code = self.reverse_byteorders[self.system_byteorder] + 'i4' out_dtype = np.format_parser([out_dtype_code, 'a1'], [], []).dtype output = np.empty((10, ), out_dtype) self.table.read(out=output) self.assertEqual(tb.utils.byteorders[output['f0'].dtype.byteorder], self.system_byteorder) np.testing.assert_array_equal(output['f0'], np.arange(10)) def test_table_other_byteorder_out_argument_system_byteorder(self): self.create_table(self.other_byteorder) out_dtype_code = self.reverse_byteorders[self.system_byteorder] + 'i4' out_dtype = np.format_parser([out_dtype_code, 'a1'], [], []).dtype output = np.empty((10, ), out_dtype) self.table.read(out=output) self.assertEqual(tb.utils.byteorders[output['f0'].dtype.byteorder], self.system_byteorder) np.testing.assert_array_equal(output['f0'], np.arange(10)) def test_table_system_byteorder_out_argument_other_byteorder(self): self.create_table(self.system_byteorder) out_dtype_code = self.reverse_byteorders[self.other_byteorder] + 'i4' out_dtype = np.format_parser([out_dtype_code, 'a1'], [], []).dtype output = np.empty((10, ), out_dtype) self.assertRaises(ValueError, lambda: self.table.read(out=output)) try: self.table.read(out=output) except ValueError as exc: self.assertIn("array must be in system's byteorder", str(exc)) def test_table_other_byteorder_out_argument_other_byteorder(self): self.create_table(self.other_byteorder) out_dtype_code = self.reverse_byteorders[self.other_byteorder] + 'i4' out_dtype = np.format_parser([out_dtype_code, 'a1'], [], []).dtype output = np.empty((10, ), out_dtype) self.assertRaises(ValueError, lambda: self.table.read(out=output)) try: self.table.read(out=output) except ValueError as exc: self.assertIn("array must be in system's byteorder", str(exc)) class BasicRangeTestCase(common.TempFileMixin, common.PyTablesTestCase): # file = "test.h5" open_mode = "w" title = "This is the table title" record = Record maxshort = 1 << 15 expectedrows = 100 compress = 0 shuffle = 1 # Default values nrows = 20 nrowsinbuf = 3 # Choose a small value for the buffer size start = 1 stop = nrows checkrecarray = 0 checkgetCol = 0 def setUp(self): super().setUp() # Create an instance of an HDF5 Table self.rootgroup = self.h5file.root self.populateFile() self.h5file.close() def populateFile(self): group = self.rootgroup for j in range(3): # Create a table filterprops = tb.Filters(complevel=self.compress, shuffle=self.shuffle) table = self.h5file.create_table(group, 'table'+str(j), self.record, title=self.title, filters=filterprops, expectedrows=self.expectedrows) # Get the row object associated with the new table row = table.row # Fill the table for i in range(self.expectedrows): row['var1'] = '%04d' % (self.expectedrows - i) row['var7'] = row['var1'][-1] row['var2'] = i row['var3'] = i % self.maxshort if isinstance(row['var4'], np.ndarray): row['var4'] = [float(i), float(i * i)] else: row['var4'] = float(i) if isinstance(row['var5'], np.ndarray): row['var5'] = np.array((float(i),)*4) else: row['var5'] = float(i) # var6 will be like var3 but byteswaped row['var6'] = ( ((row['var3'] >> 8) & 0xff) + ((row['var3'] << 8) & 0xff00) ) row.append() # Flush the buffer for this table table.flush() # Create a new group (descendant of group) group2 = self.h5file.create_group(group, 'group'+str(j)) # Iterate over this new group (group2) group = group2 def check_range(self): # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") table.nrowsinbuf = self.nrowsinbuf resrange = slice(self.start, self.stop, self.step).indices(table.nrows) reslength = len(list(range(*resrange))) # print "self.checkrecarray = ", self.checkrecarray # print "self.checkgetCol = ", self.checkgetCol if self.checkrecarray: recarray = table.read(self.start, self.stop, self.step) result = [] for nrec in range(len(recarray)): if recarray['var2'][nrec] < self.nrows and 0 < self.step: result.append(recarray['var2'][nrec]) elif recarray['var2'][nrec] > self.nrows and 0 > self.step: result.append(recarray['var2'][nrec]) elif self.checkgetCol: column = table.read(self.start, self.stop, self.step, 'var2') result = [] for nrec in range(len(column)): if column[nrec] < self.nrows and 0 < self.step: result.append(column[nrec]) elif column[nrec] > self.nrows and 0 > self.step: result.append(column[nrec]) else: if 0 < self.step: result = [ rec['var2'] for rec in table.iterrows(self.start, self.stop, self.step) if rec['var2'] < self.nrows ] elif 0 > self.step: result = [ rec['var2'] for rec in table.iterrows(self.start, self.stop, self.step) if rec['var2'] > self.nrows ] if self.start < 0: startr = self.expectedrows + self.start else: startr = self.start if self.stop is None: if self.checkrecarray or self.checkgetCol: # data read using the read method stopr = startr + 1 else: # data read using the iterrows method stopr = self.nrows elif self.stop < 0: stopr = self.expectedrows + self.stop else: stopr = self.stop if self.nrows < stopr: stopr = self.nrows if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) if reslength: if self.checkrecarray: print("Last record *read* in recarray ==>", recarray[-1]) elif self.checkgetCol: print("Last value *read* in getCol ==>", column[-1]) else: print("Last record *read* in table range ==>", rec) print("Total number of selected records ==>", len(result)) print("Selected records:\n", result) print("Selected records should look like:\n", list(range(startr, stopr, self.step))) print("start, stop, step ==>", self.start, self.stop, self.step) print("startr, stopr, step ==>", startr, stopr, self.step) self.assertEqual(result, list(range(startr, stopr, self.step))) if not (self.checkrecarray or self.checkgetCol): if startr < stopr and 0 < self.step: rec = [r for r in table.iterrows(self.start, self.stop, self.step) if r['var2'] < self.nrows][-1] if self.nrows < self.expectedrows: self.assertEqual( rec['var2'], list(range(self.start, self.stop, self.step))[-1]) else: self.assertEqual( rec['var2'], list(range(startr, stopr, self.step))[-1]) elif startr > stopr and 0 > self.step: rec = [r['var2'] for r in table.iterrows(self.start, self.stop, self.step) if r['var2'] > self.nrows][0] if self.nrows < self.expectedrows: self.assertEqual( rec, list(range(self.start, self.stop or -1, self.step))[0]) else: self.assertEqual( rec, list(range(startr, stopr or -1, self.step))[0]) # Close the file self.h5file.close() def test01_range(self): """Checking ranges in table iterators (case1)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_range..." % self.__class__.__name__) # Case where step < nrowsinbuf < 2 * step self.nrows = 21 self.nrowsinbuf = 3 self.start = 0 self.stop = self.expectedrows self.step = 2 self.check_range() def test01a_range(self): """Checking ranges in table iterators (case1)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a_range..." % self.__class__.__name__) # Case where step < nrowsinbuf < 2 * step self.nrows = 21 self.nrowsinbuf = 3 self.start = self.expectedrows - 1 self.stop = None self.step = -2 self.check_range() def test02_range(self): """Checking ranges in table iterators (case2)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_range..." % self.__class__.__name__) # Case where step < nrowsinbuf < 10 * step self.nrows = 21 self.nrowsinbuf = 31 self.start = 11 self.stop = self.expectedrows self.step = 3 self.check_range() def test03_range(self): """Checking ranges in table iterators (case3)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_range..." % self.__class__.__name__) # Case where step < nrowsinbuf < 1.1 * step self.nrows = self.expectedrows self.nrowsinbuf = 11 # Choose a small value for the buffer size self.start = 0 self.stop = self.expectedrows self.step = 10 self.check_range() def test04_range(self): """Checking ranges in table iterators (case4)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_range..." % self.__class__.__name__) # Case where step == nrowsinbuf self.nrows = self.expectedrows self.nrowsinbuf = 11 # Choose a small value for the buffer size self.start = 1 self.stop = self.expectedrows self.step = 11 self.check_range() def test05_range(self): """Checking ranges in table iterators (case5)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_range..." % self.__class__.__name__) # Case where step > 1.1 * nrowsinbuf self.nrows = 21 self.nrowsinbuf = 10 # Choose a small value for the buffer size self.start = 1 self.stop = self.expectedrows self.step = 11 self.check_range() def test06_range(self): """Checking ranges in table iterators (case6)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06_range..." % self.__class__.__name__) # Case where step > 3 * nrowsinbuf self.nrows = 3 self.nrowsinbuf = 3 # Choose a small value for the buffer size self.start = 2 self.stop = self.expectedrows self.step = 10 self.check_range() def test07_range(self): """Checking ranges in table iterators (case7)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test07_range..." % self.__class__.__name__) # Case where start == stop self.nrows = 2 self.nrowsinbuf = 3 # Choose a small value for the buffer size self.start = self.nrows self.stop = self.nrows self.step = 10 self.check_range() def test08_range(self): """Checking ranges in table iterators (case8)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08_range..." % self.__class__.__name__) # Case where start > stop self.nrows = 2 self.nrowsinbuf = 3 # Choose a small value for the buffer size self.start = self.nrows + 1 self.stop = self.nrows self.step = 1 self.check_range() def test09_range(self): """Checking ranges in table iterators (case9)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09_range..." % self.__class__.__name__) # Case where stop = None (last row) self.nrows = 100 self.nrowsinbuf = 3 # Choose a small value for the buffer size self.start = 1 self.stop = 2 self.step = 1 self.check_range() def test10_range(self): """Checking ranges in table iterators (case10)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test10_range..." % self.__class__.__name__) # Case where start < 0 and stop = None (last row) self.nrows = self.expectedrows self.nrowsinbuf = 5 # Choose a small value for the buffer size self.start = -6 self.startr = self.expectedrows + self.start self.stop = -5 self.stopr = self.expectedrows self.step = 2 self.check_range() def test10a_range(self): """Checking ranges in table iterators (case10a)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test10a_range..." % self.__class__.__name__) # Case where start < 0 and stop = 0 self.nrows = self.expectedrows self.nrowsinbuf = 5 # Choose a small value for the buffer size self.start = -6 self.startr = self.expectedrows + self.start self.stop = 0 self.stopr = self.expectedrows self.step = 2 self.check_range() def test11_range(self): """Checking ranges in table iterators (case11)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test11_range..." % self.__class__.__name__) # Case where start < 0 and stop < 0 self.nrows = self.expectedrows self.nrowsinbuf = 5 # Choose a small value for the buffer size self.start = -6 self.startr = self.expectedrows + self.start self.stop = -2 self.stopr = self.expectedrows + self.stop self.step = 1 self.check_range() def test12_range(self): """Checking ranges in table iterators (case12)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test12_range..." % self.__class__.__name__) # Case where start < 0 and stop < 0 and start > stop self.nrows = self.expectedrows self.nrowsinbuf = 5 # Choose a small value for the buffer size self.start = -1 self.startr = self.expectedrows + self.start self.stop = -2 self.stopr = self.expectedrows + self.stop self.step = 1 self.check_range() def test13_range(self): """Checking ranges in table iterators (case13)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test13_range..." % self.__class__.__name__) # Case where step < 0 self.step = -11 try: self.check_range() except ValueError: if common.verbose: (type, value, traceback) = sys.exc_info() print("\nGreat!, the next ValueError was catched!") print(value) self.h5file.close() # else: # print rec # self.fail("expected a ValueError") # Case where step == 0 self.step = 0 try: self.check_range() except ValueError: if common.verbose: (type, value, traceback) = sys.exc_info() print("\nGreat!, the next ValueError was catched!") print(value) self.h5file.close() # else: # print rec # self.fail("expected a ValueError") class IterRangeTestCase(BasicRangeTestCase): pass class RecArrayRangeTestCase(BasicRangeTestCase): checkrecarray = 1 class GetColRangeTestCase(BasicRangeTestCase): checkgetCol = 1 def test01_nonexistentField(self): """Checking non-existing Field in getCol method """ if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_nonexistentField..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") self.root = self.h5file.root table = self.h5file.get_node("/table0") with self.assertRaises(KeyError): # column = table.read(field='non-existent-column') table.col('non-existent-column') class GetItemTestCase(common.TempFileMixin, common.PyTablesTestCase): open_mode = "w" title = "This is the table title" record = Record maxshort = 1 << 15 expectedrows = 100 compress = 0 shuffle = 1 # Default values nrows = 20 nrowsinbuf = 3 # Choose a small value for the buffer size start = 1 stop = nrows checkrecarray = 0 checkgetCol = 0 def setUp(self): super().setUp() # Create an instance of an HDF5 Table self.rootgroup = self.h5file.root self.populateFile() self.h5file.close() def populateFile(self): group = self.rootgroup for j in range(3): # Create a table filterprops = tb.Filters(complevel=self.compress, shuffle=self.shuffle) table = self.h5file.create_table(group, 'table'+str(j), self.record, title=self.title, filters=filterprops, expectedrows=self.expectedrows) # Get the row object associated with the new table row = table.row # Fill the table for i in range(self.expectedrows): row['var1'] = '%04d' % (self.expectedrows - i) row['var7'] = row['var1'][-1] row['var2'] = i row['var3'] = i % self.maxshort if isinstance(row['var4'], np.ndarray): row['var4'] = [float(i), float(i * i)] else: row['var4'] = float(i) if isinstance(row['var5'], np.ndarray): row['var5'] = np.array((float(i),)*4) else: row['var5'] = float(i) # var6 will be like var3 but byteswaped row['var6'] = ((row['var3'] >> 8) & 0xff) + \ ((row['var3'] << 8) & 0xff00) row.append() # Flush the buffer for this table table.flush() # Create a new group (descendant of group) group2 = self.h5file.create_group(group, 'group'+str(j)) # Iterate over this new group (group2) group = group2 def test01a_singleItem(self): """Checking __getitem__ method with single parameter (int)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a_singleItem..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 result = table[2] self.assertEqual(result["var2"], 2) result = table[25] self.assertEqual(result["var2"], 25) result = table[self.expectedrows-1] self.assertEqual(result["var2"], self.expectedrows - 1) def test01b_singleItem(self): """Checking __getitem__ method with single parameter (neg. int)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b_singleItem..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 result = table[-5] self.assertEqual(result["var2"], self.expectedrows - 5) result = table[-1] self.assertEqual(result["var2"], self.expectedrows - 1) result = table[-self.expectedrows] self.assertEqual(result["var2"], 0) def test01c_singleItem(self): """Checking __getitem__ method with single parameter (long)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01c_singleItem..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 result = table[2] self.assertEqual(result["var2"], 2) result = table[25] self.assertEqual(result["var2"], 25) result = table[self.expectedrows-1] self.assertEqual(result["var2"], self.expectedrows - 1) def test01d_singleItem(self): """Checking __getitem__ method with single parameter (neg. long)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01d_singleItem..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 result = table[-5] self.assertEqual(result["var2"], self.expectedrows - 5) result = table[-1] self.assertEqual(result["var2"], self.expectedrows - 1) result = table[-self.expectedrows] self.assertEqual(result["var2"], 0) def test01e_singleItem(self): """Checking __getitem__ method with single parameter (rank-0 ints)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01e_singleItem..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 result = table[np.array(2)] self.assertEqual(result["var2"], 2) result = table[np.array(25)] self.assertEqual(result["var2"], 25) result = table[np.array(self.expectedrows-1)] self.assertEqual(result["var2"], self.expectedrows - 1) def test01f_singleItem(self): """Checking __getitem__ method with single parameter (np.uint64)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01f_singleItem..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 result = table[np.uint64(2)] self.assertEqual(result["var2"], 2) def test02_twoItems(self): """Checking __getitem__ method with start, stop parameters.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_twoItem..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 result = table[2:6] self.assertEqual(result["var2"].tolist(), list(range(2, 6))) result = table[2:-6] self.assertEqual(result["var2"].tolist(), list(range( 2, self.expectedrows-6))) result = table[2:] self.assertEqual(result["var2"].tolist(), list(range(2, self.expectedrows))) result = table[-2:] self.assertEqual(result["var2"].tolist(), list(range(self.expectedrows-2, self.expectedrows))) def test03_threeItems(self): """Checking __getitem__ method with start, stop, step parameters.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_threeItem..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 result = table[2:6:3] self.assertEqual(result["var2"].tolist(), list(range(2, 6, 3))) result = table[2::3] self.assertEqual(result["var2"].tolist(), list(range( 2, self.expectedrows, 3))) result = table[:6:2] self.assertEqual(result["var2"].tolist(), list(range(0, 6, 2))) result = table[::] self.assertEqual(result["var2"].tolist(), list(range( 0, self.expectedrows, 1))) def test04_negativeStep(self): """Checking __getitem__ method with negative step parameter.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_negativeStep..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 with self.assertRaises(ValueError): table[2:3:-3] def test06a_singleItemCol(self): """Checking __getitem__ method in Col with single parameter.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06a_singleItemCol..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 colvar2 = table.cols.var2 self.assertEqual(colvar2[2], 2) self.assertEqual(colvar2[25], 25) self.assertEqual(colvar2[self.expectedrows-1], self.expectedrows - 1) def test06b_singleItemCol(self): """Checking __getitem__ method in Col with single parameter (negative)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06b_singleItem..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 colvar2 = table.cols.var2 self.assertEqual(colvar2[-5], self.expectedrows - 5) self.assertEqual(colvar2[-1], self.expectedrows - 1) self.assertEqual(colvar2[-self.expectedrows], 0) def test07_twoItemsCol(self): """Checking __getitem__ method in Col with start, stop parameters.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test07_twoItemCol..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 colvar2 = table.cols.var2 self.assertEqual(colvar2[2:6].tolist(), list(range(2, 6))) self.assertEqual(colvar2[2:-6].tolist(), list(range(2, self.expectedrows - 6))) self.assertEqual(colvar2[2:].tolist(), list(range(2, self.expectedrows))) self.assertEqual(colvar2[-2:].tolist(), list(range(self.expectedrows - 2, self.expectedrows))) def test08_threeItemsCol(self): """Checking __getitem__ method in Col with start, stop, step parameters.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08_threeItemCol..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 colvar2 = table.cols.var2 self.assertEqual(colvar2[2:6:3].tolist(), list(range(2, 6, 3))) self.assertEqual(colvar2[2::3].tolist(), list(range( 2, self.expectedrows, 3))) self.assertEqual(colvar2[:6:2].tolist(), list(range(0, 6, 2))) self.assertEqual(colvar2[::].tolist(), list(range(0, self.expectedrows, 1))) def test09_negativeStep(self): """Checking __getitem__ method in Col with negative step parameter.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09_negativeStep..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 colvar2 = table.cols.var2 with self.assertRaises(ValueError): colvar2[2:3:-3] def test10_list_integers(self): """Checking accessing Table with a list of integers.""" self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 idx = list(range(10, 70, 11)) result = table[idx] self.assertEqual(result["var2"].tolist(), idx) result = table.read_coordinates(idx) self.assertEqual(result["var2"].tolist(), idx) def test11_list_booleans(self): """Checking accessing Table with a list of boolean values.""" self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.root.table0 idx = list(range(10, 70, 11)) selection = [n in idx for n in range(self.expectedrows)] result = table[selection] self.assertEqual(result["var2"].tolist(), idx) result = table.read_coordinates(selection) self.assertEqual(result["var2"].tolist(), idx) class Rec(tb.IsDescription): col1 = tb.IntCol(pos=1) col2 = tb.StringCol(itemsize=3, pos=2) col3 = tb.FloatCol(pos=3) class SetItemTestCase(common.TempFileMixin, common.PyTablesTestCase): def test01(self): """Checking modifying one table row with __setitem__""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing row table[2] = (456, 'db2', 1.2) # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (456, b'db2', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test01b(self): """Checking modifying one table row with __setitem__ (long index)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing row table[2] = (456, 'db2', 1.2) # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (456, b'db2', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test02(self): """Modifying one row, with a step (__setitem__)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify two existing rows rows = np.rec.array([(457, b'db1', 1.2)], formats="i4,a3,f8") table[1:3:2] = rows # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (457, b'db1', 1.2), (457, b'db1', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test03(self): """Checking modifying several rows at once (__setitem__)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify two existing rows rows = np.rec.array([(457, b'db1', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8") # table.modify_rows(start=1, rows=rows) table[1:3] = rows # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (457, b'db1', 1.2), (5, b'de1', 1.3), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test04(self): """Modifying several rows at once, with a step (__setitem__)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify two existing rows rows = np.rec.array([(457, b'db1', 1.2), (6, b'de2', 1.3)], formats="i4,a3,f8") # table[1:4:2] = rows table[1::2] = rows # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (457, b'db1', 1.2), (457, b'db1', 1.2), (6, b'de2', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test05(self): """Checking modifying one column (single element, __setitem__)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing column table.cols.col1[1] = -1 # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (-1, b'ded', 1.3), (457, b'db1', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test06a(self): """Checking modifying one column (several elements, __setitem__)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing column table.cols.col1[1:4] = [2, 3, 4] # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (3, b'db1', 1.2), (4, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test06b(self): """Checking modifying one column (iterator, __setitem__)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing column with self.assertRaises(NotImplementedError): for row in table.iterrows(): row['col1'] = row.nrow + 1 row.append() table.flush() # # Create the modified recarray # r1=np.rec.array([[1,b'dbe',1.2],[2,b'ded',1.3], # [3,b'db1',1.2],[4,b'de1',1.3]], # formats="i4,a3,f8", # names = "col1,col2,col3") # # Read the modified table # if self.reopen: # self.fileh.close() # self.fileh = tables.open_file(self.file, "r") # table = self.fileh.root.recarray # table.nrowsinbuf = self.buffersize # set buffer value # r2 = table.read() # if common.verbose: # print "Original table-->", repr(r2) # print "Should look like-->", repr(r1) # self.assertEqual(r1.tobytes(), r2.tobytes()) # self.assertEqual(table.nrows, 4) def test07(self): """Modifying one column (several elements, __setitem__, step)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (1, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing column table.cols.col1[1:4:2] = [2, 3] # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (457, b'db1', 1.2), (3, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test08(self): """Modifying one column (one element, __setitem__, step)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing column table.cols.col1[1:4:3] = [2] # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (457, b'db1', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test09(self): """Modifying beyond the table extend (__setitem__, step)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Try to modify beyond the extend # This will silently exclude the non-fitting rows rows = np.rec.array( [(457, b'db1', 1.2), (6, b'de2', 1.3)], formats="i4,a3,f8") table[1::2] = rows # How it should look like r1 = np.rec.array([(456, b'dbe', 1.2), (457, b'db1', 1.2), (457, b'db1', 1.2), (6, b'de2', 1.3)], formats="i4,a3,f8") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) class SetItemTestCase1(SetItemTestCase): reopen = 0 buffersize = 1 class SetItemTestCase2(SetItemTestCase): reopen = 1 buffersize = 2 class SetItemTestCase3(SetItemTestCase): reopen = 0 buffersize = 1000 class SetItemTestCase4(SetItemTestCase): reopen = 1 buffersize = 1000 class UpdateRowTestCase(common.TempFileMixin, common.PyTablesTestCase): def test01(self): """Checking modifying one table row with Row.update""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing row for row in table.iterrows(2, 3): (row['col1'], row['col2'], row['col3']) = (456, 'db2', 1.2) row.update() # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (456, b'db2', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test02(self): """Modifying one row, with a step (Row.update)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify two existing rows for row in table.iterrows(1, 3, 2): if row.nrow == 1: (row['col1'], row['col2'], row['col3']) = (457, 'db1', 1.2) elif row.nrow == 3: (row['col1'], row['col2'], row['col3']) = (6, 'de2', 1.3) row.update() # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (457, b'db1', 1.2), (457, b'db1', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test03(self): """Checking modifying several rows at once (Row.update)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify two existing rows for row in table.iterrows(1, 3): if row.nrow == 1: (row['col1'], row['col2'], row['col3']) = (457, 'db1', 1.2) elif row.nrow == 2: (row['col1'], row['col2'], row['col3']) = (5, 'de1', 1.3) row.update() # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (457, b'db1', 1.2), (5, b'de1', 1.3), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test04(self): """Modifying several rows at once, with a step (Row.update)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify two existing rows for row in table.iterrows(1, stop=4, step=2): if row.nrow == 1: (row['col1'], row['col2'], row['col3']) = (457, 'db1', 1.2) elif row.nrow == 3: (row['col1'], row['col2'], row['col3']) = (6, 'de2', 1.3) row.update() # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (457, b'db1', 1.2), (457, b'db1', 1.2), (6, b'de2', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test05(self): """Checking modifying one column (single element, Row.update)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing column for row in table.iterrows(1, 2): row['col1'] = -1 row.update() # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (-1, b'ded', 1.3), (457, b'db1', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test06(self): """Checking modifying one column (several elements, Row.update)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing column for row in table.iterrows(1, 4): row['col1'] = row.nrow + 1 row.update() # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (3, b'db1', 1.2), (4, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test07(self): """Modifying values from a selection""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (1, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just rows with col1 < 456 for row in table.where('col1 < 456'): row['col1'] = 2 row['col2'] = 'ada' row.update() # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ada', 1.3), (457, b'db1', 1.2), (2, b'ada', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test08(self): """Modifying a large table (Row.update)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value nrows = 100 # append new rows row = table.row for i in range(nrows): row['col1'] = i-1 row['col2'] = 'a'+str(i-1) row['col3'] = -1.0 row.append() table.flush() # Modify all the rows for row in table: row['col1'] = row.nrow row['col2'] = 'b'+str(row.nrow) row['col3'] = 0.0 row.update() # Create the modified recarray r1 = np.rec.array( None, shape=nrows, formats="i4,a3,f8", names="col1,col2,col3") for i in range(nrows): r1['col1'][i] = i r1['col2'][i] = 'b'+str(i) r1['col3'][i] = 0.0 # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, nrows) def test08b(self): """Setting values on a large table without calling Row.update""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value nrows = 100 # append new rows row = table.row for i in range(nrows): row['col1'] = i-1 row['col2'] = 'a'+str(i-1) row['col3'] = -1.0 row.append() table.flush() # Modify all the rows (actually don't) for row in table: row['col1'] = row.nrow row['col2'] = 'b'+str(row.nrow) row['col3'] = 0.0 # row.update() # Create the modified recarray r1 = np.rec.array( None, shape=nrows, formats="i4,a3,f8", names="col1,col2,col3") for i in range(nrows): r1['col1'][i] = i-1 r1['col2'][i] = 'a'+str(i-1) r1['col3'][i] = -1.0 # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, nrows) def test09(self): """Modifying selected values on a large table""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value nrows = 100 # append new rows row = table.row for i in range(nrows): row['col1'] = i-1 row['col2'] = 'a'+str(i-1) row['col3'] = -1.0 row.append() table.flush() # Modify selected rows for row in table.where('col1 > nrows-3'): row['col1'] = row.nrow row['col2'] = 'b'+str(row.nrow) row['col3'] = 0.0 row.update() # Create the modified recarray r1 = np.rec.array( None, shape=nrows, formats="i4,a3,f8", names="col1,col2,col3") for i in range(nrows): r1['col1'][i] = i-1 r1['col2'][i] = 'a'+str(i-1) r1['col3'][i] = -1.0 # modify just the last line r1['col1'][i] = i r1['col2'][i] = 'b'+str(i) r1['col3'][i] = 0.0 # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, nrows) def test09b(self): """Modifying selected values on a large table (alternate values)""" # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) table.nrowsinbuf = self.buffersize # set buffer value nrows = 100 # append new rows row = table.row for i in range(nrows): row['col1'] = i-1 row['col2'] = 'a'+str(i-1) row['col3'] = -1.0 row.append() table.flush() # Modify selected rows for row in table.iterrows(step=10): row['col1'] = row.nrow row['col2'] = 'b'+str(row.nrow) row['col3'] = 0.0 row.update() # Create the modified recarray r1 = np.rec.array( None, shape=nrows, formats="i4,a3,f8", names="col1,col2,col3") for i in range(nrows): if i % 10 > 0: r1['col1'][i] = i-1 r1['col2'][i] = 'a'+str(i-1) r1['col3'][i] = -1.0 else: r1['col1'][i] = i r1['col2'][i] = 'b'+str(i) r1['col3'][i] = 0.0 # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, nrows) class UpdateRowTestCase1(UpdateRowTestCase): reopen = 0 buffersize = 1 class UpdateRowTestCase2(UpdateRowTestCase): reopen = 1 buffersize = 2 class UpdateRowTestCase3(UpdateRowTestCase): reopen = 0 buffersize = 1000 class UpdateRowTestCase4(UpdateRowTestCase): reopen = 1 buffersize = 1000 class RecArrayIO(common.TempFileMixin, common.PyTablesTestCase): def test00(self): """Checking saving a regular recarray""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00..." % self.__class__.__name__) # Create a recarray r = np.rec.array( [(456, b'dbe', 1.2), (2, b'de', 1.3)], names='col1,col2,col3') # Save it in a table: self.h5file.create_table(self.h5file.root, 'recarray', r) # Read it again if self.reopen: self._reopen() r2 = self.h5file.root.recarray.read() self.assertEqual(r.tobytes(), r2.tobytes()) def test01(self): """Checking saving a recarray with an offset in its buffer""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01..." % self.__class__.__name__) # Create a recarray r = np.rec.array( [(456, b'dbe', 1.2), (2, b'de', 1.3)], names='col1,col2,col3') # Get an offsetted bytearray r1 = r[1:] # Save it in a table: self.h5file.create_table(self.h5file.root, 'recarray', r1) # Read it again if self.reopen: self._reopen() r2 = self.h5file.root.recarray.read() self.assertEqual(r1.tobytes(), r2.tobytes()) def test02(self): """Checking saving a large recarray with an offset in its buffer""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02..." % self.__class__.__name__) # Create a recarray r = np.rec.array(b'a'*200_000, 'f4,3i4,a5,i2', 3000) # Get an offsetted bytearray r1 = r[2000:] # Save it in a table: self.h5file.create_table(self.h5file.root, 'recarray', r1) # Read it again if self.reopen: self._reopen() r2 = self.h5file.root.recarray.read() self.assertEqual(r1.tobytes(), r2.tobytes()) def test03(self): """Checking saving a strided recarray with an offset in its buffer""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03..." % self.__class__.__name__) # Create a recarray r = np.rec.array(b'a'*200_000, 'f4,3i4,a5,i2', 3000) # Get an strided recarray r2 = r[::2] # Get an offsetted bytearray r1 = r2[1200:] # Save it in a table: self.h5file.create_table(self.h5file.root, 'recarray', r1) # Read it again if self.reopen: self._reopen() r2 = self.h5file.root.recarray.read() self.assertEqual(r1.tobytes(), r2.tobytes()) def test04(self): """Checking appending several rows at once""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04..." % self.__class__.__name__) class Rec(tb.IsDescription): col1 = tb.IntCol(pos=1) col2 = tb.StringCol(itemsize=3, pos=2) col3 = tb.FloatCol(pos=3) # Save it in a table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Create the complete table r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (457, b'db1', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the original table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = self.h5file.root.recarray.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test05(self): """Checking appending several rows at once (close file version)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05..." % self.__class__.__name__) # Save it in a table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) self._reopen() table = self.h5file.root.recarray # Create the complete table r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (457, b'db1', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the original table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = self.h5file.root.recarray.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test06a(self): """Checking modifying one table row (list version)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06a..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing rows table.modify_rows(start=1, rows=[(456, 'db1', 1.2)]) # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (456, b'db1', 1.2), (457, b'db1', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test06b(self): """Checking modifying one table row (recarray version)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06b..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing rows table.modify_rows( start=2, rows=np.rec.array([(456, 'db2', 1.2)], formats="i4,a3,f8")) # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (456, b'db2', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test07a(self): """Checking modifying several rows at once (list version)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test07a..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify two existing rows table.modify_rows(start=1, rows=[(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (457, b'db1', 1.2), (5, b'de1', 1.3), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test07b(self): """Checking modifying several rows at once (recarray version)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test07b..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify two existing rows rows = np.rec.array([(457, b'db1', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8") table.modify_rows(start=1, rows=rows) # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (457, b'db1', 1.2), (5, b'de1', 1.3), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test07c(self): """Checking modifying several rows with a mismatching value""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test07c..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify two existing rows rows = np.rec.array( [(457, b'db1', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8") self.assertRaises(ValueError, table.modify_rows, start=1, stop=2, rows=rows) def test08a(self): """Checking modifying one column (single column version)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08a..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing column table.modify_columns(start=1, columns=[[2, 3, 4]], names=["col1"]) # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (3, b'db1', 1.2), (4, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test08a2(self): """Checking modifying one column (single column version, modify_column)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08a2..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing column table.modify_column(start=1, column=[2, 3, 4], colname="col1") # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (3, b'db1', 1.2), (4, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test08b(self): """Checking modifying one column (single column version, recarray)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08b..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing column columns = np.rec.fromarrays(np.array([[2, 3, 4]]), formats="i4") table.modify_columns(start=1, columns=columns, names=["col1"]) # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (3, b'db1', 1.2), (4, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test08b2(self): """Checking modifying one column (single column version, recarray, modify_column)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08b2..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing column columns = np.rec.fromarrays(np.array([[2, 3, 4]]), formats="i4") table.modify_column(start=1, column=columns, colname="col1") # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'ded', 1.3), (3, b'db1', 1.2), (4, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test08c(self): """Checking modifying one column (single column version, single element)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08c..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify just one existing column # columns = np.rec.fromarrays(np.array([[4]]), formats="i4") # table.modify_columns(start=1, columns=columns, names=["col1"]) table.modify_columns(start=1, columns=[[4]], names=["col1"]) # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (4, b'ded', 1.3), (457, b'db1', 1.2), (5, b'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test09a(self): """Checking modifying table columns (multiple column version)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09a..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify a couple of columns columns = [["aaa", "bbb", "ccc"], [1.2, .1, .3]] table.modify_columns(start=1, columns=columns, names=["col2", "col3"]) # Create the modified recarray r1 = np.rec.array([(456, b'dbe', 1.2), (2, b'aaa', 1.2), (457, b'bbb', .1), (5, b'ccc', .3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test09b(self): """Checking modifying table columns (multiple columns, recarray)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09b..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify a couple of columns columns = np.rec.array([("aaa", 1.2), ("bbb", .1), ("ccc", .3)], formats="a3,f8") table.modify_columns(start=1, columns=columns, names=["col2", "col3"]) # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (2, 'aaa', 1.2), (457, 'bbb', .1), (5, 'ccc', .3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test09c(self): """Checking modifying table columns (single column, step)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09c..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify a couple of columns columns = np.rec.array([("aaa", 1.2), ("bbb", .1)], formats="a3,f8") table.modify_columns(start=1, step=2, columns=columns, names=["col2", "col3"]) # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (2, 'aaa', 1.2), (457, 'db1', 1.2), (5, 'bbb', .1)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test09d(self): """Checking modifying table columns (multiple columns, step)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09d..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) # Modify a couple of columns columns = np.rec.array([("aaa", 1.3), ("bbb", .1)], formats="a3,f8") table.modify_columns(start=0, step=2, columns=columns, names=["col2", "col3"]) # Create the modified recarray r1 = np.rec.array([(456, 'aaa', 1.3), (2, 'ded', 1.3), (457, 'bbb', .1), (5, 'de1', 1.3)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test10a(self): """Checking modifying rows using coordinates (readCoords/modifyCoords).""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test10a..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) columns = table.read_coordinates([0, 3]) # Modify both rows columns['col1'][:] = [55, 56] columns['col3'][:] = [1.9, 1.8] # Modify the table in the same coordinates table.modify_coordinates([0, 3], columns) # Create the modified recarray r1 = np.rec.array([(55, b'dbe', 1.9), (2, b'ded', 1.3), (457, b'db1', 1.2), (56, b'de1', 1.8)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test10b(self): """Checking modifying rows using coordinates (getitem/setitem).""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test10b..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # append new rows r = np.rec.array( [(456, b'dbe', 1.2), (2, b'ded', 1.3)], formats="i4,a3,f8") table.append(r) table.append([(457, b'db1', 1.2), (5, b'de1', 1.3)]) columns = table[[0, 3]] # Modify both rows columns['col1'][:] = [55, 56] columns['col3'][:] = [1.9, 1.8] # Modify the table in the same coordinates table[[0, 3]] = columns # Create the modified recarray r1 = np.rec.array([(55, b'dbe', 1.9), (2, b'ded', 1.3), (457, b'db1', 1.2), (56, b'de1', 1.8)], formats="i4,a3,f8", names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) class RecArrayIO1(RecArrayIO): reopen = 0 class RecArrayIO2(RecArrayIO): reopen = 1 class CopyTestCase(common.TempFileMixin, common.PyTablesTestCase): def assertEqualColinstances(self, table1, table2): """Assert that column instance maps of both tables are equal.""" cinst1, cinst2 = table1.colinstances, table2.colinstances self.assertEqual(len(cinst1), len(cinst2)) for (cpathname, col1) in cinst1.items(): self.assertTrue(cpathname in cinst2) col2 = cinst2[cpathname] self.assertIsInstance(col1, type(col2)) if isinstance(col1, tb.Column): self.assertEqual(col1.name, col2.name) self.assertEqual(col1.pathname, col2.pathname) self.assertEqual(col1.dtype, col2.dtype) self.assertEqual(col1.type, col2.type) elif isinstance(col1, tb.Cols): self.assertEqual(col1._v_colnames, col2._v_colnames) self.assertEqual(col1._v_colpathnames, col2._v_colpathnames) def test01_copy(self): """Checking Table.copy() method.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_copy..." % self.__class__.__name__) # Create a recarray r = np.rec.array( [(456, b'dbe', 1.2), (2, b'de', 1.3)], names='col1,col2,col3', formats=('i4,S3,f8'), aligned=self.aligned) # Save it in a table: table1 = self.h5file.create_table(self.h5file.root, 'table1', r, "title table1") if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') table1 = self.h5file.root.table1 # Copy to another table table2 = table1.copy('/', 'table2') if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 if common.verbose: print("table1-->", table1.read()) print("table2-->", table2.read()) # print "dirs-->", dir(table1), dir(table2) print("attrs table1-->", repr(table1.attrs)) print("attrs table2-->", repr(table2.attrs)) # Check that all the elements are equal for row1 in table1: nrow = row1.nrow # current row # row1 is a Row instance, while table2[] is a # RecArray.Record instance # print "reprs-->", repr(row1), repr(table2.read(nrow)) for colname in table1.colnames: # Both ways to compare works well # self.assertEqual(row1[colname], table2[nrow][colname)) self.assertEqual(row1[colname], table2.read(nrow, field=colname)[0]) # Assert other properties in table self.assertEqual(table1.nrows, table2.nrows) self.assertEqual(table1.shape, table2.shape) self.assertEqual(table1.colnames, table2.colnames) self.assertEqual(table1.coldtypes, table2.coldtypes) self.assertEqualColinstances(table1, table2) self.assertEqual(repr(table1.description), repr(table2.description)) # Check alignment if self.aligned and self.open_kwargs['allow_padding'] is True: self.assertEqual(table1.description._v_offsets, [0, 4, 8]) self.assertEqual(table1.description._v_itemsize, 16) else: self.assertEqual(table1.description._v_offsets, [0, 4, 7]) self.assertEqual(table1.description._v_itemsize, 15) self.assertEqual(table1.description._v_offsets, table2.description._v_offsets) self.assertEqual(table1.description._v_itemsize, table2.description._v_itemsize) # This could be not the same when re-opening the file # self.assertEqual(table1.description._v_ColObjects, # table2.description._v_ColObjects) # Leaf attributes self.assertEqual(table1.title, table2.title) self.assertEqual(table1.filters.complevel, table2.filters.complevel) self.assertEqual(table1.filters.complib, table2.filters.complib) self.assertEqual(table1.filters.shuffle, table2.filters.shuffle) self.assertEqual(table1.filters.fletcher32, table2.filters.fletcher32) def test02_copy(self): """Checking Table.copy() method (where specified)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_copy..." % self.__class__.__name__) # Create a recarray r = np.rec.array( [(b'dbe', 456, 1.2), (b'de', 2, 1.3)], names='col1,col2,col3', formats="S3,i4,f8", aligned=self.aligned) # Save it in a table: table1 = self.h5file.create_table(self.h5file.root, 'table1', r, "title table1") if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') table1 = self.h5file.root.table1 # Copy to another table in another group group1 = self.h5file.create_group("/", "group1") table2 = table1.copy(group1, 'table2') if self.close: if common.verbose: print("(closing file version)") self._reopen() table1 = self.h5file.root.table1 table2 = self.h5file.root.group1.table2 if common.verbose: print("table1-->", table1.read()) print("table2-->", table2.read()) print("attrs table1-->", repr(table1.attrs)) print("attrs table2-->", repr(table2.attrs)) # Check that all the elements are equal for row1 in table1: nrow = row1.nrow # current row for colname in table1.colnames: # Both ways to compare works well # self.assertEqual(row1[colname], table2[nrow][colname)) self.assertEqual(row1[colname], table2.read(nrow, field=colname)[0]) # Assert other properties in table self.assertEqual(table1.nrows, table2.nrows) self.assertEqual(table1.shape, table2.shape) self.assertEqual(table1.colnames, table2.colnames) self.assertEqual(table1.coldtypes, table2.coldtypes) self.assertEqualColinstances(table1, table2) self.assertEqual(repr(table1.description), repr(table2.description)) # Check alignment if self.aligned and self.open_kwargs['allow_padding'] is True: self.assertEqual(table1.description._v_offsets, [0, 4, 8]) self.assertEqual(table1.description._v_itemsize, 16) else: self.assertEqual(table1.description._v_offsets, [0, 3, 7]) self.assertEqual(table1.description._v_itemsize, 15) self.assertEqual(table1.description._v_offsets, table2.description._v_offsets) self.assertEqual(table1.description._v_itemsize, table2.description._v_itemsize) # Leaf attributes self.assertEqual(table1.title, table2.title) self.assertEqual(table1.filters.complevel, table2.filters.complevel) self.assertEqual(table1.filters.complib, table2.filters.complib) self.assertEqual(table1.filters.shuffle, table2.filters.shuffle) self.assertEqual(table1.filters.fletcher32, table2.filters.fletcher32) def test03_copy(self): """Checking Table.copy() method (table larger than buffer)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_copy..." % self.__class__.__name__) # Create a recarray exceeding buffers capability # This works, but takes too much CPU for a test # It is better to reduce the buffer size (table1.nrowsinbuf) # r=np.rec.array(b'aaaabbbbccccddddeeeeffffgggg'*20000, # formats='2i2,i4, (2,3)u2, (1,)f4, f8',shape=700) r = np.rec.array( b'aaaabbbbccccddddeeeeffffgggg' * 200, formats='2i2,i4, (2,3)u2, (1,)f4, f8', shape=7, aligned=self.aligned) # Save it in a table: table1 = self.h5file.create_table(self.h5file.root, 'table1', r, "title table1") if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') table1 = self.h5file.root.table1 # Copy to another table in another group and other title group1 = self.h5file.create_group("/", "group1") table1.nrowsinbuf = 2 # small value of buffer table2 = table1.copy(group1, 'table2', title="title table2") if self.close: if common.verbose: print("(closing file version)") self._reopen() table1 = self.h5file.root.table1 table2 = self.h5file.root.group1.table2 if common.verbose: print("table1-->", table1.read()) print("table2-->", table2.read()) print("attrs table1-->", repr(table1.attrs)) print("attrs table2-->", repr(table2.attrs)) # Check that all the elements are equal for row1 in table1: nrow = row1.nrow # current row for colname in table1.colnames: # self.assertTrue(allequal(row1[colname], # table2[nrow][colname])) self.assertTrue(common.allequal( row1[colname], table2.read(nrow, field=colname)[0])) # Assert other properties in table self.assertEqual(table1.nrows, table2.nrows) self.assertEqual(table1.shape, table2.shape) self.assertEqual(table1.colnames, table2.colnames) self.assertEqual(table1.coldtypes, table2.coldtypes) self.assertEqualColinstances(table1, table2) self.assertEqual(repr(table1.description), repr(table2.description)) # Check alignment if self.aligned and self.open_kwargs['allow_padding'] is True: self.assertEqual(table1.description._v_offsets, [0, 4, 8, 20, 24]) self.assertEqual(table1.description._v_itemsize, 32) else: self.assertEqual(table1.description._v_offsets, [0, 4, 8, 20, 24]) self.assertEqual(table1.description._v_itemsize, 32) self.assertEqual(table1.description._v_offsets, table2.description._v_offsets) self.assertEqual(table1.description._v_itemsize, table2.description._v_itemsize) # Leaf attributes self.assertEqual("title table2", table2.title) self.assertEqual(table1.filters.complevel, table2.filters.complevel) self.assertEqual(table1.filters.complib, table2.filters.complib) self.assertEqual(table1.filters.shuffle, table2.filters.shuffle) self.assertEqual(table1.filters.fletcher32, table2.filters.fletcher32) def test04_copy(self): """Checking Table.copy() method (different compress level)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_copy..." % self.__class__.__name__) # Create a recarray r = np.rec.array([(1.2, b'dbe', 456), (1.3, b'de', 2)], names='col1,col2,col3', formats="f8,S3,i4", aligned=self.aligned) # Save it in a table: table1 = self.h5file.create_table(self.h5file.root, 'table1', r, "title table1") if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') table1 = self.h5file.root.table1 # Copy to another table in another group group1 = self.h5file.create_group("/", "group1") table2 = table1.copy(group1, 'table2', filters=tb.Filters(complevel=6)) if self.close: if common.verbose: print("(closing file version)") self._reopen() table1 = self.h5file.root.table1 table2 = self.h5file.root.group1.table2 if common.verbose: print("table1-->", table1.read()) print("table2-->", table2.read()) print("attrs table1-->", repr(table1.attrs)) print("attrs table2-->", repr(table2.attrs)) # Check that all the elements are equal for row1 in table1: nrow = row1.nrow # current row for colname in table1.colnames: # Both ways to compare works well # self.assertEqual(row1[colname], table2[nrow][colname)) self.assertEqual(row1[colname], table2.read(nrow, field=colname)[0]) # Assert other properties in table self.assertEqual(table1.nrows, table2.nrows) self.assertEqual(table1.shape, table2.shape) self.assertEqual(table1.colnames, table2.colnames) self.assertEqual(table1.coldtypes, table2.coldtypes) self.assertEqualColinstances(table1, table2) self.assertEqual(repr(table1.description), repr(table2.description)) # Check alignment if self.aligned and self.open_kwargs['allow_padding'] is True: self.assertEqual(table1.description._v_offsets, [0, 8, 12]) self.assertEqual(table1.description._v_itemsize, 16) else: self.assertEqual(table1.description._v_offsets, [0, 8, 11]) self.assertEqual(table1.description._v_itemsize, 15) self.assertEqual(table1.description._v_offsets, table2.description._v_offsets) self.assertEqual(table1.description._v_itemsize, table2.description._v_itemsize) # Leaf attributes self.assertEqual(table1.title, table2.title) self.assertEqual(6, table2.filters.complevel) self.assertEqual(1, table2.filters.shuffle) self.assertEqual(table1.filters.fletcher32, table2.filters.fletcher32) def test05_copy(self): """Checking Table.copy() method (user attributes copied)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_copy..." % self.__class__.__name__) # Create a recarray r = np.rec.array([(456, b'dbe', 1.2), (2, b'de', 1.3)], names='col1,col2,col3', formats='i8,S3,f8', aligned=self.aligned) # Save it in a table: table1 = self.h5file.create_table(self.h5file.root, 'table1', r, "title table1") # Add some user attributes table1.attrs.attr1 = "attr1" table1.attrs.attr2 = 2 if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') table1 = self.h5file.root.table1 # Copy to another table in another group group1 = self.h5file.create_group("/", "group1") table2 = table1.copy(group1, 'table2', copyuserattrs=1, filters=tb.Filters(complevel=6)) if self.close: if common.verbose: print("(closing file version)") self._reopen() table1 = self.h5file.root.table1 table2 = self.h5file.root.group1.table2 if common.verbose: print("table1-->", table1.read()) print("table2-->", table2.read()) print("attrs table1-->", repr(table1.attrs)) print("attrs table2-->", repr(table2.attrs)) # Check that all the elements are equal for row1 in table1: nrow = row1.nrow # current row for colname in table1.colnames: # self.assertEqual(row1[colname], table2[nrow][colname)) self.assertEqual(row1[colname], table2.read(nrow, field=colname)[0]) # Assert other properties in table self.assertEqual(table1.nrows, table2.nrows) self.assertEqual(table1.shape, table2.shape) self.assertEqual(table1.colnames, table2.colnames) self.assertEqual(table1.coldtypes, table2.coldtypes) self.assertEqualColinstances(table1, table2) self.assertEqual(repr(table1.description), repr(table2.description)) # Check alignment if self.aligned and self.open_kwargs['allow_padding'] is True: # The conditions for guessing the correct alignment are very # tricky, so better disable the checks. Feel free to re-enable # them during debugging by removing the False condition below. if False: if is_os_64bit() and is_python_64bit(): self.assertEqual(table1.description._v_offsets, [0, 8, 16]) self.assertEqual(table1.description._v_itemsize, 24) elif not is_os_64bit() and not is_python_64bit(): self.assertEqual(table1.description._v_offsets, [0, 8, 12]) self.assertEqual(table1.description._v_itemsize, 20) else: self.assertEqual(table1.description._v_offsets, [0, 8, 11]) self.assertEqual(table1.description._v_itemsize, 19) self.assertEqual(table1.description._v_offsets, table2.description._v_offsets) self.assertEqual(table1.description._v_itemsize, table2.description._v_itemsize) # Leaf attributes self.assertEqual(table1.title, table2.title) self.assertEqual(6, table2.filters.complevel) self.assertEqual(1, table2.filters.shuffle) self.assertEqual(table1.filters.fletcher32, table2.filters.fletcher32) # User attributes self.assertEqual(table2.attrs.attr1, "attr1") self.assertEqual(table2.attrs.attr2, 2) def test05b_copy(self): """Checking Table.copy() method (user attributes not copied)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05b_copy..." % self.__class__.__name__) # Create a recarray r = np.rec.array([(456, b'dbe', 1.2), (2, b'de', 1.3)], names='col1,col2,col3', formats='i8,S3,f4', aligned=self.aligned) # Save it in a table: table1 = self.h5file.create_table(self.h5file.root, 'table1', r, "title table1") # Add some user attributes table1.attrs.attr1 = "attr1" table1.attrs.attr2 = 2 if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') table1 = self.h5file.root.table1 # Copy to another table in another group group1 = self.h5file.create_group("/", "group1") table2 = table1.copy(group1, 'table2', copyuserattrs=0, filters=tb.Filters(complevel=6)) if self.close: if common.verbose: print("(closing file version)") self._reopen() table1 = self.h5file.root.table1 table2 = self.h5file.root.group1.table2 if common.verbose: print("table1-->", table1.read()) print("table2-->", table2.read()) print("attrs table1-->", repr(table1.attrs)) print("attrs table2-->", repr(table2.attrs)) # Check that all the elements are equal for row1 in table1: nrow = row1.nrow # current row for colname in table1.colnames: # self.assertEqual(row1[colname], table2[nrow][colname)) self.assertEqual(row1[colname], table2.read(nrow, field=colname)[0]) # Assert other properties in table self.assertEqual(table1.nrows, table2.nrows) self.assertEqual(table1.shape, table2.shape) self.assertEqual(table1.colnames, table2.colnames) self.assertEqual(table1.coldtypes, table2.coldtypes) self.assertEqualColinstances(table1, table2) self.assertEqual(repr(table1.description), repr(table2.description)) # Check alignment if self.aligned and self.open_kwargs['allow_padding'] is True: self.assertEqual(table1.description._v_offsets, [0, 8, 12]) self.assertEqual(table1.description._v_itemsize, 16) else: self.assertEqual(table1.description._v_offsets, [0, 8, 11]) self.assertEqual(table1.description._v_itemsize, 15) self.assertEqual(table1.description._v_offsets, table2.description._v_offsets) self.assertEqual(table1.description._v_itemsize, table2.description._v_itemsize) # Leaf attributes self.assertEqual(table1.title, table2.title) self.assertEqual(6, table2.filters.complevel) self.assertEqual(1, table2.filters.shuffle) self.assertEqual(table1.filters.fletcher32, table2.filters.fletcher32) # User attributes self.assertEqual(hasattr(table2.attrs, "attr1"), 0) self.assertEqual(hasattr(table2.attrs, "attr2"), 0) class CloseCopyTestCase(CopyTestCase): close = True aligned = False open_kwargs = {'allow_padding': False} class OpenCopyTestCase(CopyTestCase): close = False aligned = False open_kwargs = {'allow_padding': True} class AlignedCloseCopyTestCase(CopyTestCase): close = True aligned = True open_kwargs = {'allow_padding': False} class AlignedOpenCopyTestCase(CopyTestCase): close = False aligned = True open_kwargs = {'allow_padding': True} class AlignedNoPaddingOpenCopyTestCase(CopyTestCase): close = False aligned = True open_kwargs = {'allow_padding': False} class CopyIndexTestCase(common.TempFileMixin, common.PyTablesTestCase): def test01_index(self): """Checking Table.copy() method with indexes.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_index..." % self.__class__.__name__) # Create a recarray exceeding buffers capability r = np.rec.array(b'aaaabbbbccccddddeeeeffffgggg' * 200, formats='2i2, (1,)i4, (2,3)u2, (1,)f4, (1,)f8', shape=10) # The line below exposes a bug in numpy # formats='2i2, i4, (2,3)u2, f4, f8',shape=10) # Save it in a table: table1 = self.h5file.create_table(self.h5file.root, 'table1', r, "title table1") if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') table1 = self.h5file.root.table1 # Copy to another table table1.nrowsinbuf = self.nrowsinbuf table2 = table1.copy("/", 'table2', start=self.start, stop=self.stop, step=self.step) if common.verbose: print("table1-->", table1.read()) print("table2-->", table2.read()) print("attrs table1-->", repr(table1.attrs)) print("attrs table2-->", repr(table2.attrs)) # Check that all the elements are equal r2 = r[self.start:self.stop:self.step] for nrow in range(r2.shape[0]): for colname in table1.colnames: self.assertTrue(common.allequal( r2[nrow][colname], table2[nrow][colname])) # Assert the number of rows in table if common.verbose: print("nrows in table2-->", table2.nrows) print("and it should be-->", r2.shape[0]) self.assertEqual(r2.shape[0], table2.nrows) def test02_indexclosef(self): """Checking Table.copy() method with indexes (close file version)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_indexclosef..." % self.__class__.__name__) # Create a recarray exceeding buffers capability r = np.rec.array(b'aaaabbbbccccddddeeeeffffgggg' * 200, formats='2i2, i4, (2,3)u2, f4, f8', shape=10) # Save it in a table: table1 = self.h5file.create_table(self.h5file.root, 'table1', r, "title table1") if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') table1 = self.h5file.root.table1 # Copy to another table table1.nrowsinbuf = self.nrowsinbuf table2 = table1.copy("/", 'table2', start=self.start, stop=self.stop, step=self.step) self._reopen() table1 = self.h5file.root.table1 table2 = self.h5file.root.table2 if common.verbose: print("table1-->", table1.read()) print("table2-->", table2.read()) print("attrs table1-->", repr(table1.attrs)) print("attrs table2-->", repr(table2.attrs)) # Check that all the elements are equal r2 = r[self.start:self.stop:self.step] for nrow in range(r2.shape[0]): for colname in table1.colnames: self.assertTrue(common.allequal( r2[nrow][colname], table2[nrow][colname])) # Assert the number of rows in table if common.verbose: print("nrows in table2-->", table2.nrows) print("and it should be-->", r2.shape[0]) self.assertEqual(r2.shape[0], table2.nrows) class CopyIndex1TestCase(CopyIndexTestCase): nrowsinbuf = 2 close = 1 start = 0 stop = 7 step = 1 class CopyIndex2TestCase(CopyIndexTestCase): nrowsinbuf = 2 close = 0 start = 0 stop = -1 step = 1 class CopyIndex3TestCase(CopyIndexTestCase): nrowsinbuf = 3 close = 1 start = 1 stop = 7 step = 1 class CopyIndex4TestCase(CopyIndexTestCase): nrowsinbuf = 4 close = 0 start = 0 stop = 6 step = 1 class CopyIndex5TestCase(CopyIndexTestCase): nrowsinbuf = 2 close = 1 start = 3 stop = 7 step = 1 class CopyIndex6TestCase(CopyIndexTestCase): nrowsinbuf = 2 close = 0 start = 3 stop = 6 step = 2 class CopyIndex7TestCase(CopyIndexTestCase): nrowsinbuf = 2 close = 1 start = 0 stop = 7 step = 10 class CopyIndex8TestCase(CopyIndexTestCase): nrowsinbuf = 2 close = 0 start = 6 stop = 3 step = 1 class CopyIndex9TestCase(CopyIndexTestCase): nrowsinbuf = 2 close = 1 start = 3 stop = 4 step = 1 class CopyIndex10TestCase(CopyIndexTestCase): nrowsinbuf = 1 close = 0 start = 3 stop = 4 step = 2 class CopyIndex11TestCase(CopyIndexTestCase): nrowsinbuf = 2 close = 1 start = -3 stop = -1 step = 2 class CopyIndex12TestCase(CopyIndexTestCase): nrowsinbuf = 3 close = 0 start = -1 # Should point to the last element stop = None # None should mean the last element (including it) step = 1 class LargeRowSize(common.TempFileMixin, common.PyTablesTestCase): def test00(self): """Checking saving a Table with a moderately large rowsize""" # Create a recarray r = np.rec.array([(np.arange(100)) * 2]) # Save it in a table: self.h5file.create_table(self.h5file.root, 'largerow', r) # Read it again r2 = self.h5file.root.largerow.read() self.assertEqual(r.tobytes(), r2.tobytes()) def test01(self): """Checking saving a Table with an extremely large rowsize""" # Create a recarray (1.4 MB rowsize) r = np.zeros(10, dtype=np.dtype('(300,100)i4,(400,400)f8')) # From PyTables 1.3 on, we allow row sizes equal or larger than 640 KB self.h5file.create_table(self.h5file.root, 'largerow', r) # Read it again r2 = self.h5file.root.largerow.read() self.assertEqual(r.tobytes(), r2.tobytes()) class DefaultValues(common.TempFileMixin, common.PyTablesTestCase): record = Record def test00(self): """Checking saving a Table with default values (using the same Row)""" # Create a table table = self.h5file.create_table(self.h5file.root, 'table', self.record) table.nrowsinbuf = 46 # minimum amount that reproduces a problem # Take a number of records a bit greater nrows = int(table.nrowsinbuf * 1.1) row = table.row # Fill the table with nrows records for i in range(nrows): if i == 3: row['var2'] = 2 if i == 4: row['var3'] = 3 # This injects the row values. row.append() # We need to flush the buffers in table in order to get an # accurate number of records on it. table.flush() # Create a recarray with the same default values values = [b"abcd", 1, 2, 3.1, 4.2, 5, "e", 1, 1j, 1 + 0j] formats = 'a4,i4,i2,f8,f4,u2,a1,b1,c8,c16'.split(',') if hasattr(tb, 'Float16Col'): values.append(6.4) formats.append('f2') if hasattr(tb, 'Float96Col'): values.append(6.4) formats.append('f12') if hasattr(tb, 'Float128Col'): values.append(6.4) formats.append('f16') if hasattr(tb, 'Complex192Col'): values.append(1.-0.j) formats.append('c24') if hasattr(tb, 'Complex256Col'): values.append(1.-0.j) formats.append('c32') r = np.rec.array([tuple(values)] * nrows, formats=','.join(formats)) # Assign the value exceptions r["f1"][3] = 2 r["f2"][4] = 3 # Read the table in another recarray # r2 = table.read() r2 = table[::] # Equivalent to table.read() # This generates too much output. Activate only when # self.nrowsinbuf is very small (<10) if common.verbose: print("First 10 table values:") for row in table.iterrows(0, 10): print(row) print("The first 5 read recarray values:") print(r2[:5]) print("Records should look like:") print(r[:5]) for name1, name2 in zip(r.dtype.names, r2.dtype.names): self.assertTrue(common.allequal(r[name1], r2[name2])) # The following can give false errors when columns with extended # precision data type are present in the record. # It is probably due to some difference in the value of bits used # for patting (longdoubles use just 80 bits but are stored in 96 or # 128 bits in numpy arrays) # self.assertEqual(r.tobytes(), r2.tobytes()) def test01(self): """Checking saving a Table with default values (using different Row)""" # Create a table table = self.h5file.create_table(self.h5file.root, 'table', self.record) table.nrowsinbuf = 46 # minimum amount that reproduces a problem # Take a number of records a bit greater nrows = int(table.nrowsinbuf * 1.1) # Fill the table with nrows records for i in range(nrows): if i == 3: table.row['var2'] = 2 if i == 4: table.row['var3'] = 3 # This injects the row values. table.row.append() # We need to flush the buffers in table in order to get an # accurate number of records on it. table.flush() # Create a recarray with the same default values values = [b"abcd", 1, 2, 3.1, 4.2, 5, "e", 1, 1j, 1 + 0j] formats = 'a4,i4,i2,f8,f4,u2,a1,b1,c8,c16'.split(',') if hasattr(tb, 'Float16Col'): values.append(6.4) formats.append('f2') if hasattr(tb, 'Float96Col'): values.append(6.4) formats.append('f12') if hasattr(tb, 'Float128Col'): values.append(6.4) formats.append('f16') if hasattr(tb, 'Complex192Col'): values.append(1.-0.j) formats.append('c24') if hasattr(tb, 'Complex256Col'): values.append(1.-0.j) formats.append('c32') r = np.rec.array([tuple(values)] * nrows, formats=','.join(formats)) # Assign the value exceptions r["f1"][3] = 2 r["f2"][4] = 3 # Read the table in another recarray # r2 = table.read() r2 = table[::] # Equivalent to table.read() # This generates too much output. Activate only when # self.nrowsinbuf is very small (<10) if common.verbose: print("First 10 table values:") for row in table.iterrows(0, 10): print(row) print("The first 5 read recarray values:") print(r2[:5]) print("Records should look like:") print(r[:5]) for name1, name2 in zip(r.dtype.names, r2.dtype.names): self.assertTrue(common.allequal(r[name1], r2[name2])) # The following can give false errors when columns with extended # precision data type are present in the record. # It is probably due to some difference in the value of bits used # for patting (longdoubles use just 80 bits but are stored in 96 or # 128 bits in numpy arrays) # self.assertEqual(r.tobytes(), r2.tobytes()) class OldRecordDefaultValues(DefaultValues): title = "OldRecordDefaultValues" record = OldRecord class Record2(tb.IsDescription): var1 = tb.StringCol(itemsize=4, dflt=b"abcd") # 4-character String var2 = tb.IntCol(dflt=1) # integer var3 = tb.Int16Col(dflt=2) # short integer var4 = tb.Float64Col(dflt=3.1) # double (double-precision) class LengthTestCase(common.TempFileMixin, common.PyTablesTestCase): record = Record nrows = 20 def setUp(self): super().setUp() # Create an instance of an HDF5 Table self.rootgroup = self.h5file.root self.populateFile() def populateFile(self): # Create a table table = self.h5file.create_table(self.h5file.root, 'table', self.record, title="__length__ test") # Get the row object associated with the new table row = table.row # Fill the table for i in range(self.nrows): row.append() # Flush the buffer for this table table.flush() self.table = table def test01_lengthrows(self): """Checking __length__ in Table.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_lengthrows..." % self.__class__.__name__) # Number of rows len(self.table) == self.nrows def test02_lengthcols(self): """Checking __length__ in Cols.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_lengthcols..." % self.__class__.__name__) # Number of columns if self.record is Record: len(self.table.cols) == 8 elif self.record is Record2: len(self.table.cols) == 4 def test03_lengthcol(self): """Checking __length__ in Column.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_lengthcol..." % self.__class__.__name__) # Number of rows for all columns column for colname in self.table.colnames: len(getattr(self.table.cols, colname)) == self.nrows class Length1TestCase(LengthTestCase): record = Record nrows = 20 class Length2TestCase(LengthTestCase): record = Record2 nrows = 100 class WhereAppendTestCase(common.TempFileMixin, common.PyTablesTestCase): """Tests `Table.append_where()` method.""" class SrcTblDesc(tb.IsDescription): id = tb.IntCol() v1 = tb.FloatCol() v2 = tb.StringCol(itemsize=8) def setUp(self): super().setUp() tbl = self.h5file.create_table('/', 'test', self.SrcTblDesc) row = tbl.row row['id'] = 1 row['v1'] = 1.5 row['v2'] = 'a' * 8 row.append() row['id'] = 2 row['v1'] = 2.5 row['v2'] = 'b' * 6 row.append() tbl.flush() def test00_same(self): """Query with same storage.""" DstTblDesc = self.SrcTblDesc tbl1 = self.h5file.root.test tbl2 = self.h5file.create_table('/', 'test2', DstTblDesc) tbl1.append_where(tbl2, 'id > 1') # Rows resulting from the query are those in the new table. it2 = iter(tbl2) for r1 in tbl1.where('id > 1'): r2 = next(it2) self.assertTrue(r1['id'] == r2['id'] and r1['v1'] == r2['v1'] and r1['v2'] == r2['v2']) # There are no more rows. self.assertRaises(StopIteration, next, it2) def test01_compatible(self): """Query with compatible storage.""" class DstTblDesc(tb.IsDescription): id = tb.FloatCol() # float, not int v1 = tb.FloatCol() v2 = tb.StringCol(itemsize=16) # a longer column v3 = tb.FloatCol() # extra column tbl1 = self.h5file.root.test tbl2 = self.h5file.create_table('/', 'test2', DstTblDesc) tbl1.append_where(tbl2, 'id > 1') # Rows resulting from the query are those in the new table. it2 = iter(tbl2) for r1 in tbl1.where('id > 1'): r2 = next(it2) self.assertTrue(r1['id'] == r2['id'] and r1['v1'] == r2['v1'] and r1['v2'] == r2['v2']) # There are no more rows. self.assertRaises(StopIteration, next, it2) def test02_lessPrecise(self): """Query with less precise storage.""" class DstTblDesc(tb.IsDescription): id = tb.IntCol() v1 = tb.IntCol() # int, not float v2 = tb.StringCol(itemsize=8) tbl1 = self.h5file.root.test tbl2 = self.h5file.create_table('/', 'test2', DstTblDesc) tbl1.append_where(tbl2, 'id > 1') # Rows resulting from the query are those in the new table. it2 = iter(tbl2) for r1 in tbl1.where('id > 1'): r2 = next(it2) self.assertTrue(r1['id'] == r2['id'] and int(r1['v1']) == r2['v1'] and r1['v2'] == r2['v2']) # There are no more rows. self.assertRaises(StopIteration, next, it2) def test03_incompatible(self): """Query with incompatible storage.""" class DstTblDesc(tb.IsDescription): id = tb.StringCol(itemsize=4) # string, not int v1 = tb.FloatCol() v2 = tb.StringCol(itemsize=8) tbl1 = self.h5file.root.test tbl2 = self.h5file.create_table('/', 'test2', DstTblDesc) self.assertRaises(NotImplementedError, tbl1.append_where, tbl2, 'v1 == b"1"') def test04_noColumn(self): """Query with storage lacking columns.""" class DstTblDesc(tb.IsDescription): # no ``id`` field v1 = tb.FloatCol() v2 = tb.StringCol(itemsize=8) tbl1 = self.h5file.root.test tbl2 = self.h5file.create_table('/', 'test2', DstTblDesc) self.assertRaises(KeyError, tbl1.append_where, tbl2, 'id > 1') def test05_otherFile(self): """Appending to a table in another file.""" h5fname2 = tempfile.mktemp(suffix='.h5') try: with tb.open_file(h5fname2, 'w') as h5file2: tbl1 = self.h5file.root.test tbl2 = h5file2.create_table('/', 'test', self.SrcTblDesc) # RW to RW. tbl1.append_where(tbl2, 'id > 1') # RW to RO. with tb.open_file(h5fname2, 'r') as h5file2: tbl2 = h5file2.root.test self.assertRaises(tb.FileModeError, tbl1.append_where, tbl2, 'id > 1') # RO to RO. self._reopen('r') tbl1 = self.h5file.root.test self.assertRaises(tb.FileModeError, tbl1.append_where, tbl2, 'id > 1') # RO to RW. with tb.open_file(h5fname2, 'a') as h5file2: tbl2 = h5file2.root.test tbl1.append_where(tbl2, 'id > 1') finally: if Path(h5fname2).is_file(): Path(h5fname2).unlink() def test06_wholeTable(self): """Append whole table.""" DstTblDesc = self.SrcTblDesc tbl1 = self.h5file.root.test tbl2 = self.h5file.create_table('/', 'test2', DstTblDesc) tbl1.append_where(tbl2) # Rows resulting from the query are those in the new table. it2 = iter(tbl2) for r1 in tbl1.__iter__(): r2 = next(it2) self.assertTrue(r1['id'] == r2['id'] and r1['v1'] == r2['v1'] and r1['v2'] == r2['v2']) # There are no more rows. self.assertRaises(StopIteration, next, it2) class DerivedTableTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.h5file.create_table('/', 'original', Record) def test00(self): """Deriving a table from the description of another.""" tbl1 = self.h5file.root.original tbl2 = self.h5file.create_table('/', 'derived', tbl1.description) self.assertEqual(tbl1.description, tbl2.description) class ChunkshapeTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.h5file.create_table('/', 'table', Record, chunkshape=13) def test00(self): """Test setting the chunkshape in a table (no reopen).""" tbl = self.h5file.root.table if common.verbose: print("chunkshape-->", tbl.chunkshape) self.assertEqual(tbl.chunkshape, (13,)) def test01(self): """Test setting the chunkshape in a table (reopen).""" self.h5file.close() self.h5file = tb.open_file(self.h5fname, 'r') tbl = self.h5file.root.table if common.verbose: print("chunkshape-->", tbl.chunkshape) self.assertEqual(tbl.chunkshape, (13,)) # Test for appending zero-sized recarrays class ZeroSizedTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() # Create a Table t = self.h5file.create_table('/', 'table', {'c1': tb.Int32Col(), 'c2': tb.Float64Col()}) # Append a single row t.append([(1, 2.2)]) def test01_canAppend(self): """Appending zero length recarray.""" t = self.h5file.root.table a = np.empty(shape=(0,), dtype='i4,f8') t.append(a) self.assertEqual(t.nrows, 1, "The number of rows should be 1.") # Case for testing ticket #103, i.e. selections in columns which are # aligned but that its data length is not an exact multiple of the # length of the record. This exposes the problem only in 32-bit # machines, because in 64-bit machine, 'c2' is unaligned. However, # this should check most platforms where, while not unaligned, # len(datatype) > boundary_alignment is fullfilled. class IrregularStrideTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() class IRecord(tb.IsDescription): c1 = tb.Int32Col(pos=1) c2 = tb.Float64Col(pos=2) table = self.h5file.create_table('/', 'table', IRecord) for i in range(10): table.row['c1'] = i table.row['c2'] = i table.row.append() table.flush() def test00(self): """Selecting rows in a table with irregular stride (but aligned).""" table = self.h5file.root.table coords1 = table.get_where_list('c1<5') coords2 = table.get_where_list('c2<5') if common.verbose: print("\nSelected coords1-->", coords1) print("Selected coords2-->", coords2) self.assertTrue( common.allequal(coords1, np.arange(5, dtype=tb.utils.SizeType))) self.assertTrue( common.allequal(coords2, np.arange(5, dtype=tb.utils.SizeType))) class Issue262TestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() class IRecord(tb.IsDescription): c1 = tb.Int32Col(pos=1) c2 = tb.Float64Col(pos=2) table = self.h5file.create_table('/', 'table', IRecord) table.nrowsinbuf = 3 for i in range(20): table.row['c1'] = i table.row['c2'] = i table.row.append() table.row['c1'] = i % 29 table.row['c2'] = 300 - i table.row.append() table.row['c1'] = 300 - i table.row['c2'] = 100 + i % 30 table.row.append() table.flush() def test_gh260(self): """Regression test for gh-260""" table = self.h5file.root.table coords1 = table.get_where_list('(c1>5)&(c2<30)', start=0, step=2) coords2 = table.get_where_list('(c1>5)&(c2<30)', start=1, step=2) data = table.read() data = data[np.where((data['c1'] > 5) & (data['c2'] < 30))] if common.verbose: print() print("Selected coords1-->", coords1) print("Selected coords2-->", coords2) print("Selected data-->", data) self.assertEqual(len(coords1) + len(coords2), len(data)) def test_gh262_01(self): """Regression test for gh-262 (start=0, step=1)""" table = self.h5file.root.table data = table.get_where_list('(c1>5)&(~(c1>5))', start=0, step=1) if common.verbose: print() print("data -->", data) self.assertEqual(len(data), 0) def test_gh262_02(self): """Regression test for gh-262 (start=1, step=1)""" table = self.h5file.root.table data = table.get_where_list('(c1>5)&(~(c1>5))', start=1, step=1) if common.verbose: print() print("data -->", data) self.assertEqual(len(data), 0) def test_gh262_03(self): """Regression test for gh-262 (start=0, step=2)""" table = self.h5file.root.table data = table.get_where_list('(c1>5)&(~(c1>5))', start=0, step=2) if common.verbose: print() print("data -->", data) self.assertEqual(len(data), 0) def test_gh262_04(self): """Regression test for gh-262 (start=1, step=2)""" table = self.h5file.root.table data = table.get_where_list('(c1>5)&(~(c1>5))', start=1, step=2) if common.verbose: print() print("data -->", data) self.assertEqual(len(data), 0) class TruncateTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() table = self.h5file.create_table('/', 'table', self.IRecord) # Fill just a couple of rows for i in range(2): table.row['c1'] = i table.row['c2'] = i table.row.append() table.flush() # The defaults self.dflts = table.coldflts def test00_truncate(self): """Checking Table.truncate() method (truncating to 0 rows)""" table = self.h5file.root.table # Truncate to 0 elements table.truncate(0) if self.close: if common.verbose: print("(closing file version)") self._reopen() table = self.h5file.root.table if common.verbose: print("table-->", table.read()) self.assertEqual(table.nrows, 0) for row in table: self.assertEqual(row['c1'], row.nrow) def test01_truncate(self): """Checking Table.truncate() method (truncating to 1 rows)""" table = self.h5file.root.table # Truncate to 1 element table.truncate(1) if self.close: if common.verbose: print("(closing file version)") self._reopen() table = self.h5file.root.table if common.verbose: print("table-->", table.read()) self.assertEqual(table.nrows, 1) for row in table: self.assertEqual(row['c1'], row.nrow) def test02_truncate(self): """Checking Table.truncate() method (truncating to == self.nrows)""" table = self.h5file.root.table # Truncate to 2 elements table.truncate(2) if self.close: if common.verbose: print("(closing file version)") self._reopen() table = self.h5file.root.table if common.verbose: print("table-->", table.read()) self.assertEqual(table.nrows, 2) for row in table: self.assertEqual(row['c1'], row.nrow) def test03_truncate(self): """Checking Table.truncate() method (truncating to > self.nrows)""" table = self.h5file.root.table # Truncate to 4 elements table.truncate(4) if self.close: if common.verbose: print("(closing file version)") self._reopen() table = self.h5file.root.table if common.verbose: print("table-->", table.read()) self.assertEqual(table.nrows, 4) # Check the original values for row in table.iterrows(start=0, stop=2): self.assertEqual(row['c1'], row.nrow) # Check that the added rows have the default values for row in table.iterrows(start=2, stop=4): self.assertEqual(row['c1'], self.dflts['c1']) self.assertEqual(row['c2'], self.dflts['c2']) class TruncateOpen1(TruncateTestCase): class IRecord(tb.IsDescription): c1 = tb.Int32Col(pos=1) c2 = tb.FloatCol(pos=2) close = 0 class TruncateOpen2(TruncateTestCase): class IRecord(tb.IsDescription): c1 = tb.Int32Col(pos=1, dflt=3) c2 = tb.FloatCol(pos=2, dflt=-3.1) close = 0 class TruncateClose1(TruncateTestCase): class IRecord(tb.IsDescription): c1 = tb.Int32Col(pos=1) c2 = tb.FloatCol(pos=2) close = 1 class TruncateClose2(TruncateTestCase): class IRecord(tb.IsDescription): c1 = tb.Int32Col(pos=1, dflt=4) c2 = tb.FloatCol(pos=2, dflt=3.1) close = 1 class PointSelectionTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() N = 100 self.working_keyset = [ [0, 1], [0, -1], ] self.not_working_keyset = [ [0, N], [0, N+1], [0, -N-1], ] # Limits for selections self.limits = [ (0, 1), # just one element (20, -10), # no elements (-10, 4), # several elements (0, 10), # several elements (again) ] # Create a sample tables self.data = data = np.arange(N) self.recarr = recarr = np.empty(N, dtype="i4,f4") recarr["f0"][:] = data recarr["f1"][:] = data self.table = self.h5file.create_table(self.h5file.root, 'table', recarr) def test01a_read(self): """Test for point-selections (read, boolean keys).""" data = self.data recarr = self.recarr table = self.table for value1, value2 in self.limits: key = (data >= value1) & (data < value2) if common.verbose: print("Selection to test:", key) a = recarr[key] b = table[key] if common.verbose: print("NumPy selection:", a) print("PyTables selection:", b) np.testing.assert_array_equal( a, b, "NumPy array and PyTables selections does not match.") def test01b_read(self): """Test for point-selections (read, tuples of integers keys).""" data = self.data recarr = self.recarr table = self.table for value1, value2 in self.limits: key = np.where((data >= value1) & (data < value2)) if common.verbose: print("Selection to test:", key, type(key)) a = recarr[key] b = table[key] np.testing.assert_array_equal( a, b, "NumPy array and PyTables selections does not match.") def test01c_read(self): """Test for point-selections (read, tuples of floats keys).""" data = self.data recarr = self.recarr table = self.table for value1, value2 in self.limits: key = np.where((data >= value1) & (data < value2)) if common.verbose: print("Selection to test:", key) recarr[key] fkey = np.array(key, "f4") self.assertRaises(TypeError, table.__getitem__, fkey) def test01d_read(self): """Test for point-selections (read, numpy keys).""" data = self.data recarr = self.recarr table = self.table for value1, value2 in self.limits: key = np.where((data >= value1) & (data < value2))[0] if common.verbose: print("Selection to test:", key, type(key)) a = recarr[key] b = table[key] np.testing.assert_array_equal( a, b, "NumPy array and PyTables selections does not match.") def test01e_read(self): """Test for point-selections (read, list keys).""" data = self.data recarr = self.recarr table = self.table for value1, value2 in self.limits: key = np.where((data >= value1) & (data < value2))[0].tolist() if common.verbose: print("Selection to test:", key, type(key)) a = recarr[key] b = table[key] np.testing.assert_array_equal( a, b, "NumPy array and PyTables selections does not match.") def test01f_read(self): recarr = self.recarr table = self.table for key in self.working_keyset: if common.verbose: print("Selection to test:", key) a = recarr[key] b = table[key] np.testing.assert_array_equal( a, b, "NumPy array and PyTables selections does not match.") def test01g_read(self): table = self.table for key in self.not_working_keyset: if common.verbose: print("Selection to test:", key) self.assertRaises(IndexError, table.__getitem__, key) def test02a_write(self): """Test for point-selections (write, boolean keys).""" data = self.data recarr = self.recarr table = self.table for value1, value2 in self.limits: key = np.where((data >= value1) & (data < value2)) if common.verbose: print("Selection to test:", key) s = recarr[key] # Modify the s recarray s["f0"][:] = data[:len(s)]*2 s["f1"][:] = data[:len(s)]*3 # Modify recarr and table recarr[key] = s table[key] = s a = recarr[:] b = table[:] np.testing.assert_array_equal( a, b, "NumPy array and PyTables modifications does not match.") def test02b_write(self): """Test for point-selections (write, integer keys).""" data = self.data recarr = self.recarr table = self.table for value1, value2 in self.limits: key = np.where((data >= value1) & (data < value2)) if common.verbose: print("Selection to test:", key) s = recarr[key] # Modify the s recarray s["f0"][:] = data[:len(s)]*2 s["f1"][:] = data[:len(s)]*3 # Modify recarr and table recarr[key] = s table[key] = s a = recarr[:] b = table[:] np.testing.assert_array_equal( a, b, "NumPy array and PyTables modifications does not match.") # Test for building very large MD columns without defaults class MDLargeColTestCase(common.TempFileMixin, common.PyTablesTestCase): def test01_create(self): """Create a Table with a very large MD column. Ticket #211.""" N = 2**18 # 4x larger than maximum object header size (64 KB) cols = {'col1': tb.Int8Col(shape=N, dflt=0)} tbl = self.h5file.create_table('/', 'test', cols) tbl.row.append() # add a single row tbl.flush() if self.reopen: self._reopen('a') tbl = self.h5file.root.test # Check the value if common.verbose: print("First row-->", tbl[0]['col1']) np.testing.assert_array_equal(tbl[0]['col1'], np.zeros(N, 'i1')) class MDLargeColNoReopen(MDLargeColTestCase): reopen = False class MDLargeColReopen(MDLargeColTestCase): reopen = True # Test with itertools.groupby that iterates on exhausted Row iterator # See ticket #264. class ExhaustedIter(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() class Observations(tb.IsDescription): market_id = tb.IntCol(pos=0) scenario_id = tb.IntCol(pos=1) value = tb.Float32Col(pos=3) table = self.h5file.create_table('/', 'observations', Observations, chunkshape=32) # fill the database observations = np.arange(225) row = table.row for market_id in range(5): for scenario_id in range(3): for obs in observations: row['market_id'] = market_id row['scenario_id'] = scenario_id row['value'] = obs row.append() table.flush() def average(self, values): return sum(values, 0.0) / len(values) def f_scenario(self, row): return row['scenario_id'] def test00_groupby(self): """Checking iterating an exhausted iterator (ticket #264)""" rows = self.h5file.root.observations.where('(market_id == 3)') scenario_means = [] for scenario_id, rows_grouped in itertools.groupby(rows, self.f_scenario): vals = [row['value'] for row in rows_grouped] scenario_means.append(self.average(vals)) if common.verbose: print('Means -->', scenario_means) self.assertEqual(scenario_means, [112.0, 112.0, 112.0]) def test01_groupby(self): """Checking iterating an exhausted iterator (ticket #264). Reopen.""" self._reopen() rows = self.h5file.root.observations.where('(market_id == 3)') scenario_means = [] for scenario_id, rows_grouped in itertools.groupby(rows, self.f_scenario): vals = [row['value'] for row in rows_grouped] scenario_means.append(self.average(vals)) if common.verbose: print('Means -->', scenario_means) self.assertEqual(scenario_means, [112.0, 112.0, 112.0]) class SpecialColnamesTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_check_names(self): f = self.h5file a = np.array([(1, 2, 3)], dtype=[( "a", int), ("_b", int), ("__c", int)]) t = f.create_table(f.root, "test", a) self.assertEqual(len(t.colnames), 3, "Number of columns incorrect") if common.verbose: print("colnames -->", t.colnames) for name, name2 in zip(t.colnames, ("a", "_b", "__c")): self.assertEqual(name, name2) class RowContainsTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00_row_contains(self): f = self.h5file a = np.array([(1, 2, 3)], dtype="i1,i2,i4") t = f.create_table(f.root, "test", a) row = [r for r in t.iterrows()][0] if common.verbose: print("row -->", row[:]) for item in (1, 2, 3): self.assertIn(item, row) self.assertNotIn(4, row) class AccessClosedTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.table = self.h5file.create_table( self.h5file.root, 'table', Record) row = self.table.row for i in range(10): row['var1'] = '%04d' % i row['var2'] = i row['var3'] = i % 3 row.append() self.table.flush() def test_read(self): self.h5file.close() self.assertRaises( tb.ClosedNodeError, self.table.read) def test_getitem(self): self.h5file.close() self.assertRaises( tb.ClosedNodeError, self.table.__getitem__, 0) def test_setitem(self): data = self.table[0] self.h5file.close() self.assertRaises( tb.ClosedNodeError, self.table.__setitem__, 0, data) def test_append(self): data = self.table[0] self.h5file.close() self.assertRaises( tb.ClosedNodeError, self.table.append, data) def test_readWhere(self): self.h5file.close() self.assertRaises( tb.ClosedNodeError, self.table.read_where, 'var2 > 3') def test_whereAppend(self): self.h5file.close() self.assertRaises( tb.ClosedNodeError, self.table.append_where, self.table, 'var2 > 3') def test_getWhereList(self): self.h5file.close() self.assertRaises( tb.ClosedNodeError, self.table.get_where_list, 'var2 > 3') def test_readSorted(self): self.h5file.close() self.assertRaises( tb.ClosedNodeError, self.table.read_sorted, 'var2') def test_readCoordinates(self): self.h5file.close() self.assertRaises( tb.ClosedNodeError, self.table.read_coordinates, [2, 5]) class ColumnIterationTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.buffer_size = self.h5file.params['IO_BUFFER_SIZE'] def create_non_nested_table(self, nrows, dtype): array = np.empty((nrows, ), dtype) for name in dtype.names: array[name] = np.random.randint(0, 10_000, nrows) table = self.h5file.create_table('/', 'table', dtype) table.append(array) return array, table def iterate(self, array, table): row_num = 0 for item in table.cols.f0: self.assertEqual(item, array['f0'][row_num]) row_num += 1 self.assertEqual(row_num, len(array)) def test_less_than_io_buffer(self): dtype = np.format_parser(['i8'] * 3, [], []).dtype rows_in_buffer = self.buffer_size // dtype[0].itemsize array, table = self.create_non_nested_table(rows_in_buffer // 2, dtype) self.iterate(array, table) def test_more_than_io_buffer(self): dtype = np.format_parser(['i8'] * 3, [], []).dtype rows_in_buffer = self.buffer_size // dtype[0].itemsize array, table = self.create_non_nested_table(rows_in_buffer * 3, dtype) self.iterate(array, table) def test_partially_filled_buffer(self): dtype = np.format_parser(['i8'] * 3, [], []).dtype rows_in_buffer = self.buffer_size // dtype[0].itemsize array, table = self.create_non_nested_table(rows_in_buffer * 2 + 2, dtype) self.iterate(array, table) def test_zero_length_table(self): dtype = np.format_parser(['i8'] * 3, [], []).dtype array, table = self.create_non_nested_table(0, dtype) self.assertEqual(len(table), 0) self.iterate(array, table) class TestCreateTableArgs(common.TempFileMixin, common.PyTablesTestCase): obj = np.array( [('aaaa', 1, 2.1), ('bbbb', 2, 3.2)], dtype=[('name', 'S4'), ('icol', np.int32), ('fcol', np.float32)]) where = '/' name = 'table' description, _ = tb.description.descr_from_dtype(obj.dtype) title = 'title' filters = None expectedrows = 10_000 chunkshape = None byteorder = None createparents = False def test_positional_args_01(self): self.h5file.create_table(self.where, self.name, self.description, self.title, self.filters, self.expectedrows) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (0,)) self.assertEqual(ptarr.nrows, 0) self.assertEqual(tuple(ptarr.colnames), self.obj.dtype.names) def test_positional_args_02(self): ptarr = self.h5file.create_table(self.where, self.name, self.description, self.title, self.filters, self.expectedrows) ptarr.append(self.obj) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (len(self.obj),)) self.assertEqual(ptarr.nrows, len(self.obj)) self.assertEqual(tuple(ptarr.colnames), self.obj.dtype.names) self.assertEqual(nparr.dtype, self.obj.dtype) self.assertTrue(common.allequal(self.obj, nparr)) def test_positional_args_obj(self): self.h5file.create_table(self.where, self.name, None, self.title, self.filters, self.expectedrows, self.chunkshape, self.byteorder, self.createparents, self.obj) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (len(self.obj),)) self.assertEqual(ptarr.nrows, len(self.obj)) self.assertEqual(tuple(ptarr.colnames), self.obj.dtype.names) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj(self): self.h5file.create_table(self.where, self.name, title=self.title, obj=self.obj) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (len(self.obj),)) self.assertEqual(ptarr.nrows, len(self.obj)) self.assertEqual(tuple(ptarr.colnames), self.obj.dtype.names) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_description_01(self): ptarr = self.h5file.create_table(self.where, self.name, title=self.title, description=self.description) ptarr.append(self.obj) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (len(self.obj),)) self.assertEqual(ptarr.nrows, len(self.obj)) self.assertEqual(tuple(ptarr.colnames), self.obj.dtype.names) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_description_02(self): ptarr = self.h5file.create_table(self.where, self.name, title=self.title, description=self.description) # ptarr.append(self.obj) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (0,)) self.assertEqual(ptarr.nrows, 0) self.assertEqual(tuple(ptarr.colnames), self.obj.dtype.names) def test_kwargs_obj_description(self): ptarr = self.h5file.create_table(self.where, self.name, title=self.title, obj=self.obj, description=self.description) self._reopen() ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read() self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (len(self.obj),)) self.assertEqual(ptarr.nrows, len(self.obj)) self.assertEqual(tuple(ptarr.colnames), self.obj.dtype.names) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj_description_error_01(self): self.assertRaises(TypeError, self.h5file.create_table, self.where, self.name, title=self.title, obj=self.obj, description=Record) def test_kwargs_obj_description_error_02(self): self.assertRaises(TypeError, self.h5file.create_table, self.where, self.name, title=self.title, obj=self.obj, description=Record()) def test_kwargs_obj_description_error_03(self): self.assertRaises(TypeError, self.h5file.create_table, self.where, self.name, title=self.title, obj=self.obj, description=RecordDescriptionDict) def suite(): theSuite = common.unittest.TestSuite() niter = 1 # common.heavy = 1 # uncomment this only for testing purposes for n in range(niter): theSuite.addTest(common.unittest.makeSuite(BasicWriteTestCase)) theSuite.addTest( common.unittest.makeSuite(OldRecordBasicWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(DictWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(NumPyDTWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(RecArrayOneWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(RecArrayTwoWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(RecArrayThreeWriteTestCase)) theSuite.addTest( common.unittest.makeSuite(RecArrayAlignedWriteTestCase)) theSuite.addTest( common.unittest.makeSuite(CompressBloscTablesTestCase)) theSuite.addTest(common.unittest.makeSuite( CompressBloscShuffleTablesTestCase)) theSuite.addTest(common.unittest.makeSuite( CompressBloscBitShuffleTablesTestCase)) theSuite.addTest(common.unittest.makeSuite( CompressBloscBloscLZTablesTestCase)) theSuite.addTest( common.unittest.makeSuite(CompressBloscLZ4TablesTestCase)) theSuite.addTest( common.unittest.makeSuite(CompressBloscLZ4HCTablesTestCase)) theSuite.addTest( common.unittest.makeSuite(CompressBloscSnappyTablesTestCase)) theSuite.addTest( common.unittest.makeSuite(CompressBloscZlibTablesTestCase)) theSuite.addTest( common.unittest.makeSuite(CompressBloscZstdTablesTestCase)) theSuite.addTest(common.unittest.makeSuite(CompressLZOTablesTestCase)) theSuite.addTest( common.unittest.makeSuite(CompressLZOShuffleTablesTestCase)) theSuite.addTest(common.unittest.makeSuite(CompressZLIBTablesTestCase)) theSuite.addTest( common.unittest.makeSuite(CompressZLIBShuffleTablesTestCase)) theSuite.addTest(common.unittest.makeSuite(Fletcher32TablesTestCase)) theSuite.addTest(common.unittest.makeSuite(AllFiltersTablesTestCase)) theSuite.addTest(common.unittest.makeSuite(CompressTwoTablesTestCase)) theSuite.addTest(common.unittest.makeSuite( SizeOnDiskInMemoryPropertyTestCase)) theSuite.addTest(common.unittest.makeSuite(NonNestedTableReadTestCase)) theSuite.addTest(common.unittest.makeSuite(TableReadByteorderTestCase)) theSuite.addTest(common.unittest.makeSuite(IterRangeTestCase)) theSuite.addTest(common.unittest.makeSuite(RecArrayRangeTestCase)) theSuite.addTest(common.unittest.makeSuite(GetColRangeTestCase)) theSuite.addTest(common.unittest.makeSuite(GetItemTestCase)) theSuite.addTest(common.unittest.makeSuite(SetItemTestCase1)) theSuite.addTest(common.unittest.makeSuite(SetItemTestCase2)) theSuite.addTest(common.unittest.makeSuite(SetItemTestCase3)) theSuite.addTest(common.unittest.makeSuite(SetItemTestCase4)) theSuite.addTest(common.unittest.makeSuite(UpdateRowTestCase1)) theSuite.addTest(common.unittest.makeSuite(UpdateRowTestCase2)) theSuite.addTest(common.unittest.makeSuite(UpdateRowTestCase3)) theSuite.addTest(common.unittest.makeSuite(UpdateRowTestCase4)) theSuite.addTest(common.unittest.makeSuite(RecArrayIO1)) theSuite.addTest(common.unittest.makeSuite(RecArrayIO2)) theSuite.addTest(common.unittest.makeSuite(OpenCopyTestCase)) theSuite.addTest(common.unittest.makeSuite(CloseCopyTestCase)) theSuite.addTest(common.unittest.makeSuite(AlignedOpenCopyTestCase)) theSuite.addTest(common.unittest.makeSuite(AlignedCloseCopyTestCase)) theSuite.addTest( common.unittest.makeSuite(AlignedNoPaddingOpenCopyTestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex1TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex2TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex3TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex4TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex5TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex6TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex7TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex8TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex9TestCase)) theSuite.addTest(common.unittest.makeSuite(DefaultValues)) theSuite.addTest(common.unittest.makeSuite(OldRecordDefaultValues)) theSuite.addTest(common.unittest.makeSuite(Length1TestCase)) theSuite.addTest(common.unittest.makeSuite(Length2TestCase)) theSuite.addTest(common.unittest.makeSuite(WhereAppendTestCase)) theSuite.addTest(common.unittest.makeSuite(DerivedTableTestCase)) theSuite.addTest(common.unittest.makeSuite(ChunkshapeTestCase)) theSuite.addTest(common.unittest.makeSuite(ZeroSizedTestCase)) theSuite.addTest(common.unittest.makeSuite(IrregularStrideTestCase)) theSuite.addTest(common.unittest.makeSuite(Issue262TestCase)) theSuite.addTest(common.unittest.makeSuite(TruncateOpen1)) theSuite.addTest(common.unittest.makeSuite(TruncateOpen2)) theSuite.addTest(common.unittest.makeSuite(TruncateClose1)) theSuite.addTest(common.unittest.makeSuite(TruncateClose2)) theSuite.addTest(common.unittest.makeSuite(PointSelectionTestCase)) theSuite.addTest(common.unittest.makeSuite(MDLargeColNoReopen)) theSuite.addTest(common.unittest.makeSuite(MDLargeColReopen)) theSuite.addTest(common.unittest.makeSuite(ExhaustedIter)) theSuite.addTest(common.unittest.makeSuite(SpecialColnamesTestCase)) theSuite.addTest(common.unittest.makeSuite(RowContainsTestCase)) theSuite.addTest(common.unittest.makeSuite(AccessClosedTestCase)) theSuite.addTest(common.unittest.makeSuite(ColumnIterationTestCase)) theSuite.addTest(common.unittest.makeSuite(TestCreateTableArgs)) if common.heavy: theSuite.addTest( common.unittest.makeSuite(CompressBzip2TablesTestCase)) theSuite.addTest(common.unittest.makeSuite( CompressBzip2ShuffleTablesTestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex10TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex11TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex12TestCase)) theSuite.addTest(common.unittest.makeSuite(LargeRowSize)) theSuite.addTest(common.unittest.makeSuite(BigTablesTestCase)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_tablesMD.py000066400000000000000000002341231416254111300207670ustar00rootroot00000000000000import sys import numpy as np import tables as tb from tables.tests import common # It is important that columns are ordered according to their names # to ease the comparison with structured arrays. # Test Record class class Record(tb.IsDescription): var0 = tb.StringCol(itemsize=4, dflt=b"", shape=2) # 4-char str array var1 = tb.StringCol(itemsize=4, dflt=[b"abcd", b"efgh"], shape=(2, 2)) var1_ = tb.IntCol(dflt=((1, 1),), shape=2) # integer array var2 = tb.IntCol(dflt=((1, 1), (1, 1)), shape=(2, 2)) # integer array var3 = tb.Int16Col(dflt=2) # short integer var4 = tb.FloatCol(dflt=3.1) # double (double-precision) var5 = tb.Float32Col(dflt=4.2) # float (single-precision) var6 = tb.UInt16Col(dflt=5) # unsigned short integer var7 = tb.StringCol(itemsize=1, dflt=b"e") # 1-character String # Dictionary definition RecordDescriptionDict = { 'var0': tb.StringCol(itemsize=4, dflt=b"", shape=2), # 4-char str array 'var1': tb.StringCol(itemsize=4, dflt=[b"abcd", b"efgh"], shape=(2, 2)), # 'var0': StringCol(itemsize=4, shape=2), # 4-character String # 'var1': StringCol(itemsize=4, shape=(2,2)), # 4-character String 'var1_': tb.IntCol(shape=2), # integer array 'var2': tb.IntCol(shape=(2, 2)), # integer array 'var3': tb.Int16Col(), # short integer 'var4': tb.FloatCol(), # double (double-precision) 'var5': tb.Float32Col(), # float (single-precision) 'var6': tb.Int16Col(), # unsigned short integer 'var7': tb.StringCol(itemsize=1), # 1-character String } # Record class with numpy dtypes (mixed shapes is checked here) class RecordDT(tb.IsDescription): var0 = tb.Col.from_dtype(np.dtype("2S4"), dflt=b"") # shape in dtype var1 = tb.Col.from_dtype(np.dtype(("S4", ( 2, 2))), dflt=[b"abcd", b"efgh"]) # shape is a mix var1_ = tb.Col.from_dtype( np.dtype("2i4"), dflt=((1, 1),)) # shape in dtype var2 = tb.Col.from_sctype("i4", shape=( 2, 2), dflt=((1, 1), (1, 1))) # shape is a mix var3 = tb.Col.from_dtype(np.dtype("i2"), dflt=2) var4 = tb.Col.from_dtype(np.dtype("2f8"), dflt=3.1) var5 = tb.Col.from_dtype(np.dtype("f4"), dflt=4.2) var6 = tb.Col.from_dtype(np.dtype("()u2"), dflt=5) var7 = tb.Col.from_dtype(np.dtype("S1"), dflt=b"e") # no shape class BasicTestCase(common.TempFileMixin, common.PyTablesTestCase): # file = "test.h5" open_mode = "w" title = "This is the table title" expectedrows = 100 appendrows = 20 compress = 0 complib = "zlib" # Default compression library record = Record recarrayinit = 0 maxshort = 1 << 15 def setUp(self): super().setUp() # Create an instance of an HDF5 Table self.rootgroup = self.h5file.root self.populateFile() self.h5file.close() def initRecArray(self): record = self.recordtemplate row = record[0] buflist = [] # Fill the recarray for i in range(self.expectedrows): tmplist = [] # Both forms (list or chararray) works var0 = ['%04d' % (self.expectedrows - i)] * 2 tmplist.append(var0) var1 = [['%04d' % (self.expectedrows - i)] * 2] * 2 tmplist.append(var1) var1_ = (i, 1) tmplist.append(var1_) var2 = ((i, 1), (1, 1)) # *-* tmplist.append(var2) var3 = i % self.maxshort tmplist.append(var3) if isinstance(row['var4'], np.ndarray): tmplist.append([float(i), float(i * i)]) else: tmplist.append(float(i)) if isinstance(row['var5'], np.ndarray): tmplist.append(np.array((float(i),)*4)) else: tmplist.append(float(i)) # var6 will be like var3 but byteswaped tmplist.append(((var3 >> 8) & 0xff) + ((var3 << 8) & 0xff00)) var7 = var1[0][0][-1] tmplist.append(var7) buflist.append(tuple(tmplist)) self.record = np.rec.array(buflist, dtype=record.dtype, shape=self.expectedrows) def populateFile(self): group = self.rootgroup if self.recarrayinit: # Initialize an starting buffer, if any self.initRecArray() for j in range(3): # Create a table filters = tb.Filters(complevel=self.compress, complib=self.complib) if j < 2: byteorder = sys.byteorder else: # table2 will be byteswapped byteorder = {"little": "big", "big": "little"}[sys.byteorder] table = self.h5file.create_table(group, 'table'+str(j), self.record, title=self.title, filters=filters, expectedrows=self.expectedrows, byteorder=byteorder) if not self.recarrayinit: # Get the row object associated with the new table row = table.row # Fill the table for i in range(self.expectedrows): s = '%04d' % (self.expectedrows - i) row['var0'] = s.encode('ascii') row['var1'] = s.encode('ascii') row['var7'] = s[-1].encode('ascii') row['var1_'] = (i, 1) row['var2'] = ((i, 1), (1, 1)) # *-* row['var3'] = i % self.maxshort if isinstance(row['var4'], np.ndarray): row['var4'] = [float(i), float(i * i)] else: row['var4'] = float(i) if isinstance(row['var5'], np.ndarray): row['var5'] = np.array((float(i),)*4) else: row['var5'] = float(i) # var6 will be like var3 but byteswaped row['var6'] = (((row['var3'] >> 8) & 0xff) + ((row['var3'] << 8) & 0xff00)) row.append() # Flush the buffer for this table table.flush() # Create a new group (descendant of group) group2 = self.h5file.create_group(group, 'group'+str(j)) # Iterate over this new group (group2) group = group2 def test00_description(self): """Checking table description and descriptive fields.""" self.h5file = tb.open_file(self.h5fname) tbl = self.h5file.get_node('/table0') desc = tbl.description if isinstance(self.record, dict): columns = self.record elif isinstance(self.record, np.ndarray): descr, _ = tb.description.descr_from_dtype(self.record.dtype) columns = descr._v_colobjects elif isinstance(self.record, np.dtype): descr, _ = tb.description.descr_from_dtype(self.record) columns = descr._v_colobjects else: # This is an ordinary description. columns = self.record.columns # Check table and description attributes at the same time. # These checks are only valid for non-nested tb. # Column names. expectedNames = ['var0', 'var1', 'var1_', 'var2', 'var3', 'var4', 'var5', 'var6', 'var7'] self.assertEqual(expectedNames, list(tbl.colnames)) self.assertEqual(expectedNames, list(desc._v_names)) # Column types. expectedTypes = [columns[colname].dtype for colname in expectedNames] self.assertEqual(expectedTypes, [tbl.coldtypes[v] for v in expectedNames]) self.assertEqual(expectedTypes, [desc._v_dtypes[v] for v in expectedNames]) # Column string types. expectedTypes = [columns[colname].type for colname in expectedNames] self.assertEqual(expectedTypes, [tbl.coltypes[v] for v in expectedNames]) self.assertEqual(expectedTypes, [desc._v_types[v] for v in expectedNames]) # Column defaults. for v in expectedNames: if common.verbose: print("dflt-->", columns[v].dflt) print("coldflts-->", tbl.coldflts[v]) print("desc.dflts-->", desc._v_dflts[v]) self.assertTrue(common.areArraysEqual(tbl.coldflts[v], columns[v].dflt)) self.assertTrue(common.areArraysEqual(desc._v_dflts[v], columns[v].dflt)) # Column path names. self.assertEqual(expectedNames, list(desc._v_pathnames)) # Column objects. for colName in expectedNames: expectedCol = columns[colName] col = desc._v_colobjects[colName] self.assertEqual(expectedCol.dtype, col.dtype) self.assertEqual(expectedCol.type, col.type) def test01_readTable(self): """Checking table read and cuts.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_readTable..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Choose a small value for buffer size table.nrowsinbuf = 3 # Read the records and select those with "var2" file less than 20 result = [r['var2'][0][0] for r in table.iterrows() if r['var2'][0][0] < 20] if common.verbose: print("Table:", repr(table)) print("Nrows in", table._v_pathname, ":", table.nrows) print("Last record in table ==>", r) print("Total selected records in table ==> ", len(result)) nrows = self.expectedrows - 1 r = [r for r in table.iterrows() if r['var2'][0][0] < 20][-1] self.assertEqual(( r['var0'][0], r['var1'][0][0], r['var1_'][0], r['var2'][0][0], r['var7'] ), (b"0001", b"0001", nrows, nrows, b"1")) if isinstance(r['var5'], np.ndarray): self.assertTrue(common.allequal( r['var5'], np.array((nrows,)*4, np.float32))) else: self.assertEqual(r['var5'], float(nrows)) self.assertEqual(len(result), 20) def test01b_readTable(self): """Checking table read and cuts (multidimensional columns case)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b_readTable..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") # Choose a small value for buffer size table.nrowsinbuf = 3 # Read the records and select those with "var2" file less than 20 result1 = [r['var5'] for r in table.iterrows() if r['var2'][0][0] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("Last record in table ==>", r) print("Total selected records in table ==> ", len(result1)) nrows = table.nrows result2 = [r for r in table.iterrows() if r['var2'][0][0] < 20][-1] if isinstance(result2['var5'], np.ndarray): self.assertTrue(common.allequal( result1[0], np.array((float(0),) * 4, np.float32))) self.assertTrue(common.allequal( result1[1], np.array((float(1),) * 4, np.float32))) self.assertTrue(common.allequal( result1[2], np.array((float(2),) * 4, np.float32))) self.assertTrue(common.allequal( result1[3], np.array((float(3),) * 4, np.float32))) self.assertTrue(common.allequal( result1[10], np.array((float(10),) * 4, np.float32))) self.assertTrue(common.allequal( result2['var5'], np.array((float(nrows - 1),) * 4, np.float32) )) else: self.assertEqual(result2['var5'], float(nrows - 1)) self.assertEqual(len(result1), 20) # Read the records and select those with "var2" file less than 20 result1 = [r['var1'] for r in table.iterrows() if r['var2'][0][0] < 20] result2 = [r for r in table.iterrows() if r['var2'][0][0] < 20][-1] if result2['var1'].dtype.char == "S": a = np.array([['%04d' % (self.expectedrows - 0)]*2]*2, 'S') self.assertTrue(common.allequal(result1[0], a)) a = np.array([['%04d' % (self.expectedrows - 1)]*2]*2, 'S') self.assertTrue(common.allequal(result1[1], a)) a = np.array([['%04d' % (self.expectedrows - 2)]*2]*2, 'S') self.assertTrue(common.allequal(result1[2], a)) a = np.array([['%04d' % (self.expectedrows - 3)]*2]*2, 'S') self.assertTrue(common.allequal(result1[3], a)) a = np.array([['%04d' % (self.expectedrows - 10)]*2]*2, 'S') self.assertTrue(common.allequal(result1[10], a)) a = np.array([['%04d' % (1)]*2]*2, 'S') self.assertTrue(common.allequal(result2['var1'], a)) else: self.assertEqual(result1['var1'], "0001") self.assertEqual(len(result1), 20) def test01c_readTable(self): """Checking shape of multidimensional columns.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01c_readTable..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") if common.verbose: print("var2 col shape:", table.cols.var2.shape) print("Should be:", table.cols.var2[:].shape) self.assertEqual(table.cols.var2.shape, table.cols.var2[:].shape) def test02_AppendRows(self): """Checking whether appending record rows works or not.""" # Now, open it, but in "append" mode self.h5file = tb.open_file(self.h5fname, mode="a") self.rootgroup = self.h5file.root if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_AppendRows..." % self.__class__.__name__) # Get a table table = self.h5file.get_node("/group0/table1") # Get their row object row = table.row if common.verbose: print("Nrows in old", table._v_pathname, ":", table.nrows) print("Record Format ==>", table.description._v_nested_formats) print("Record Size ==>", table.rowsize) # Append some rows for i in range(self.appendrows): s = '%04d' % (self.appendrows - i) row['var0'] = s.encode('ascii') row['var1'] = s.encode('ascii') row['var7'] = s[-1].encode('ascii') row['var1_'] = (i, 1) row['var2'] = ((i, 1), (1, 1)) # *-* row['var3'] = i % self.maxshort if isinstance(row['var4'], np.ndarray): row['var4'] = [float(i), float(i * i)] else: row['var4'] = float(i) if isinstance(row['var5'], np.ndarray): row['var5'] = np.array((float(i),)*4) else: row['var5'] = float(i) row.append() # Flush the buffer for this table and read it table.flush() result = [r['var2'][0][0] for r in table.iterrows() if r['var2'][0][0] < 20] row = [r for r in table.iterrows() if r['var2'][0][0] < 20][-1] nrows = self.appendrows - 1 self.assertEqual(( row['var0'][0], row['var1'][0][0], row['var1_'][0], row['var2'][0][0], row['var7']), (b"0001", b"0001", nrows, nrows, b"1")) if isinstance(row['var5'], np.ndarray): self.assertTrue(common.allequal( row['var5'], np.array((float(nrows),) * 4, np.float32))) else: self.assertEqual(row['var5'], float(nrows)) if self.appendrows <= 20: add = self.appendrows else: add = 20 self.assertEqual(len(result), 20 + add) # because we appended new rows # del table # CAVEAT: The next test only works for tables with rows < 2**15 def test03_endianess(self): """Checking if table is endianess aware.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_endianess..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/group0/group1/table2") # Read the records and select the ones with "var3" column less than 20 result = [r['var2'] for r in table.iterrows() if r['var3'] < 20] if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) print("On-disk byteorder ==>", table.byteorder) print("Last record in table ==>", r) print("Total selected records in table ==>", len(result)) nrows = self.expectedrows - 1 r = list(table.iterrows())[-1] self.assertEqual((r['var1'][0][0], r['var3']), (b"0001", nrows)) self.assertEqual(len(result), 20) class BasicWriteTestCase(BasicTestCase): title = "BasicWrite" class DictWriteTestCase(BasicTestCase): # This checks also unidimensional arrays as columns title = "DictWrite" record = RecordDescriptionDict nrows = 21 nrowsinbuf = 3 # Choose a small value for the buffer size start = 0 stop = 10 step = 3 class RecordDTWriteTestCase(BasicTestCase): title = "RecordDTWriteTestCase" record = RecordDT # Pure NumPy dtype class NumPyDTWriteTestCase(BasicTestCase): title = "NumPyDTWriteTestCase" record = np.dtype("(2,)S4,(2,2)S4,(2,)i4,(2,2)i4,i2,2f8,f4,i2,S1") record.names = 'var0,var1,var1_,var2,var3,var4,var5,var6,var7'.split(',') class RecArrayOneWriteTestCase(BasicTestCase): title = "RecArrayOneWrite" record = np.rec.array( None, formats="(2,)S4,(2,2)S4,(2,)i4,(2,2)i4,i2,2f8,f4,i2,S1", names='var0,var1,var1_,var2,var3,var4,var5,var6,var7', shape=0) class RecArrayTwoWriteTestCase(BasicTestCase): title = "RecArrayTwoWrite" expectedrows = 100 recarrayinit = 1 recordtemplate = np.rec.array( None, formats="(2,)a4,(2,2)a4,(2,)i4,(2,2)i4,i2,f8,f4,i2,a1", names='var0,var1,var1_,var2,var3,var4,var5,var6,var7', shape=1) class RecArrayThreeWriteTestCase(BasicTestCase): title = "RecArrayThreeWrite" expectedrows = 100 recarrayinit = 1 recordtemplate = np.rec.array( None, formats="(2,)a4,(2,2)a4,(2,)i4,(2,2)i4,i2,2f8,4f4,i2,a1", names='var0,var1,var1_,var2,var3,var4,var5,var6,var7', shape=1) class RecArrayAlignedWriteTestCase(BasicTestCase): title = "RecArrayThreeWrite" expectedrows = 100 recarrayinit = 1 recordtemplate = np.rec.array( None, formats="(2,)a4,(2,2)a4,(2,)i4,(2,2)i4,i2,2f8,4f4,i2,a1", names='var0,var1,var1_,var2,var3,var4,var5,var6,var7', shape=1, aligned=True) @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class CompressBloscTablesTestCase(BasicTestCase): title = "CompressBloscTables" compress = 1 complib = "blosc" @common.unittest.skipIf(not common.lzo_avail, 'LZO compression library not available') class CompressLZOTablesTestCase(BasicTestCase): title = "CompressLZOTables" compress = 1 complib = "lzo" @common.unittest.skipIf(not common.bzip2_avail, 'BZIP2 compression library not available') class CompressBzip2TablesTestCase(BasicTestCase): title = "CompressBzip2Tables" compress = 1 complib = "bzip2" class CompressZLIBTablesTestCase(BasicTestCase): title = "CompressOneTables" compress = 1 complib = "zlib" class CompressTwoTablesTestCase(BasicTestCase): title = "CompressTwoTables" compress = 1 # This checks also unidimensional arrays as columns record = RecordDescriptionDict class BigTablesTestCase(BasicTestCase): title = "BigTables" # 10000 rows takes much more time than we can afford for tests # reducing to 1000 would be more than enough # F. Alted 2004-01-19 # expectedrows = 10000 # appendrows = 1000 expectedrows = 1000 appendrows = 100 class BasicRangeTestCase(common.TempFileMixin, common.PyTablesTestCase): # file = "test.h5" open_mode = "w" title = "This is the table title" record = Record maxshort = 1 << 15 expectedrows = 100 compress = 0 # Default values nrows = 20 nrowsinbuf = 3 # Choose a small value for the buffer size start = 1 stop = nrows checkrecarray = 0 checkgetCol = 0 def setUp(self): super().setUp() # Create an instance of an HDF5 Table self.rootgroup = self.h5file.root self.populateFile() self.h5file.close() def populateFile(self): group = self.rootgroup for j in range(3): # Create a table table = self.h5file.create_table( group, 'table'+str(j), self.record, title=self.title, filters=tb.Filters(self.compress), expectedrows=self.expectedrows) # Get the row object associated with the new table row = table.row # Fill the table for i in range(self.expectedrows): row['var1'] = '%04d' % (self.expectedrows - i) row['var7'] = row['var1'][0][0][-1] row['var2'] = i row['var3'] = i % self.maxshort if isinstance(row['var4'], np.ndarray): row['var4'] = [float(i), float(i * i)] else: row['var4'] = float(i) if isinstance(row['var5'], np.ndarray): row['var5'] = np.array((float(i),)*4) else: row['var5'] = float(i) # var6 will be like var3 but byteswaped row['var6'] = (((row['var3'] >> 8) & 0xff) + ((row['var3'] << 8) & 0xff00)) row.append() # Flush the buffer for this table table.flush() # Create a new group (descendant of group) group2 = self.h5file.create_group(group, 'group'+str(j)) # Iterate over this new group (group2) group = group2 def check_range(self): # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") table = self.h5file.get_node("/table0") table.nrowsinbuf = self.nrowsinbuf resrange = slice(self.start, self.stop, self.step).indices(table.nrows) reslength = len(list(range(*resrange))) if self.checkrecarray: recarray = table.read(self.start, self.stop, self.step) result = [] for nrec in range(len(recarray)): if recarray['var2'][nrec][0][0] < self.nrows and 0 < self.step: result.append(recarray['var2'][nrec][0][0]) elif (recarray['var2'][nrec][0][0] > self.nrows and 0 > self.step): result.append(recarray['var2'][nrec][0][0]) elif self.checkgetCol: column = table.read(self.start, self.stop, self.step, 'var2') result = [] for nrec in range(len(column)): if column[nrec][0][0] < self.nrows and 0 < self.step: # *-* result.append(column[nrec][0][0]) # *-* elif column[nrec][0][0] > self.nrows and 0 > self.step: # *-* result.append(column[nrec][0][0]) # *-* else: if 0 < self.step: result = [ r['var2'][0][0] for r in table.iterrows(self.start, self.stop, self.step) if r['var2'][0][0] < self.nrows ] elif 0 > self.step: result = [ r['var2'][0][0] for r in table.iterrows(self.start, self.stop, self.step) if r['var2'][0][0] > self.nrows ] if self.start < 0: startr = self.expectedrows + self.start else: startr = self.start if self.stop is None: if self.checkrecarray or self.checkgetCol: # data read using the read method stopr = startr + 1 else: # data read using the iterrows method stopr = self.nrows elif self.stop < 0: stopr = self.expectedrows + self.stop else: stopr = self.stop if self.nrows < stopr: stopr = self.nrows if common.verbose: print("Nrows in", table._v_pathname, ":", table.nrows) if reslength: if self.checkrecarray: print("Last record *read* in recarray ==>", recarray[-1]) elif self.checkgetCol: print("Last value *read* in getCol ==>", column[-1]) else: print("Last record *read* in table range ==>", r) print("Total number of selected records ==>", len(result)) print("Selected records:\n", result) print("Selected records should look like:\n", list(range(startr, stopr, self.step))) print("start, stop, step ==>", startr, stopr, self.step) self.assertEqual(result, list(range(startr, stopr, self.step))) if not (self.checkrecarray or self.checkgetCol): if startr < stopr and 0 < self.step: r = [r['var2'] for r in table.iterrows(self.start, self.stop, self.step) if r['var2'][0][0] < self.nrows][-1] if self.nrows > self.expectedrows: self.assertEqual( r[0][0], list(range(self.start, self.stop, self.step))[-1]) else: self.assertEqual(r[0][0], list(range(startr, stopr, self.step))[-1]) elif startr > stopr and 0 > self.step: r = [r['var2'] for r in table.iterrows(self.start, self.stop, self.step) if r['var2'][0][0] > self.nrows][0] if self.nrows < self.expectedrows: self.assertEqual( r[0][0], list(range(self.start, self.stop or -1, self.step))[0]) else: self.assertEqual( r[0][0], list(range(startr, stopr or -1, self.step))[0]) # Close the file self.h5file.close() def test01_range(self): """Checking ranges in table iterators (case1)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_range..." % self.__class__.__name__) # Case where step < nrowsinbuf < 2 * step self.nrows = 21 self.nrowsinbuf = 3 self.start = 0 self.stop = self.expectedrows self.step = 2 self.check_range() def test01a_range(self): """Checking ranges in table iterators (case1)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_range..." % self.__class__.__name__) # Case where step < nrowsinbuf < 2 * step self.nrows = 21 self.nrowsinbuf = 3 self.start = self.expectedrows - 1 self.stop = None self.step = -2 self.check_range() def test02_range(self): """Checking ranges in table iterators (case2)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_range..." % self.__class__.__name__) # Case where step < nrowsinbuf < 10 * step self.nrows = 21 self.nrowsinbuf = 31 self.start = 11 self.stop = self.expectedrows self.step = 3 self.check_range() def test03_range(self): """Checking ranges in table iterators (case3)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_range..." % self.__class__.__name__) # Case where step < nrowsinbuf < 1.1 * step self.nrows = self.expectedrows self.nrowsinbuf = 11 # Choose a small value for the buffer size self.start = 0 self.stop = self.expectedrows self.step = 10 self.check_range() def test04_range(self): """Checking ranges in table iterators (case4)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_range..." % self.__class__.__name__) # Case where step == nrowsinbuf self.nrows = self.expectedrows self.nrowsinbuf = 11 # Choose a small value for the buffer size self.start = 1 self.stop = self.expectedrows self.step = 11 self.check_range() def test05_range(self): """Checking ranges in table iterators (case5)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_range..." % self.__class__.__name__) # Case where step > 1.1 * nrowsinbuf self.nrows = 21 self.nrowsinbuf = 10 # Choose a small value for the buffer size self.start = 1 self.stop = self.expectedrows self.step = 11 self.check_range() def test06_range(self): """Checking ranges in table iterators (case6)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06_range..." % self.__class__.__name__) # Case where step > 3 * nrowsinbuf self.nrows = 3 self.nrowsinbuf = 3 # Choose a small value for the buffer size self.start = 2 self.stop = self.expectedrows self.step = 10 self.check_range() def test07_range(self): """Checking ranges in table iterators (case7)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test07_range..." % self.__class__.__name__) # Case where start == stop self.nrows = 2 self.nrowsinbuf = 3 # Choose a small value for the buffer size self.start = self.nrows self.stop = self.nrows self.step = 10 self.check_range() def test08_range(self): """Checking ranges in table iterators (case8)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08_range..." % self.__class__.__name__) # Case where start > stop self.nrows = 2 self.nrowsinbuf = 3 # Choose a small value for the buffer size self.start = self.nrows + 1 self.stop = self.nrows self.step = 1 self.check_range() def test09_range(self): """Checking ranges in table iterators (case9)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test09_range..." % self.__class__.__name__) # Case where stop = None self.nrows = 100 self.nrowsinbuf = 3 # Choose a small value for the buffer size self.start = 1 self.stop = 2 self.step = 1 self.check_range() def test10_range(self): """Checking ranges in table iterators (case10)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test10_range..." % self.__class__.__name__) # Case where start < 0 and stop = 0 self.nrows = self.expectedrows self.nrowsinbuf = 5 # Choose a small value for the buffer size self.start = -6 self.startr = self.expectedrows + self.start self.stop = 0 self.stopr = self.expectedrows + self.stop self.step = 2 self.check_range() def test11_range(self): """Checking ranges in table iterators (case11)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test11_range..." % self.__class__.__name__) # Case where start < 0 and stop < 0 self.nrows = self.expectedrows self.nrowsinbuf = 5 # Choose a small value for the buffer size self.start = -6 self.startr = self.expectedrows + self.start self.stop = -2 self.stopr = self.expectedrows + self.stop self.step = 1 self.check_range() def test12_range(self): """Checking ranges in table iterators (case12)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test12_range..." % self.__class__.__name__) # Case where start < 0 and stop < 0 and start > stop self.nrows = self.expectedrows self.nrowsinbuf = 5 # Choose a small value for the buffer size self.start = -1 self.startr = self.expectedrows + self.start self.stop = -2 self.stopr = self.expectedrows + self.stop self.step = 1 self.check_range() def test13_range(self): """Checking ranges in table iterators (case13)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test13_range..." % self.__class__.__name__) # Case where step < 0 self.step = -11 try: self.check_range() except ValueError: if common.verbose: (type, value, traceback) = sys.exc_info() print("\nGreat!, the next ValueError was catched!") self.h5file.close() # else: # self.fail("expected a ValueError") # Case where step == 0 self.step = 0 try: self.check_range() except ValueError: if common.verbose: (type, value, traceback) = sys.exc_info() print("\nGreat!, the next ValueError was catched!") self.h5file.close() # else: # self.fail("expected a ValueError") class IterRangeTestCase(BasicRangeTestCase): pass class RecArrayRangeTestCase(BasicRangeTestCase): checkrecarray = 1 class GetColRangeTestCase(BasicRangeTestCase): checkgetCol = 1 def test01_nonexistentField(self): """Checking non-existing Field in getCol method """ if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_nonexistentField..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") self.root = self.h5file.root table = self.h5file.get_node("/table0") with self.assertRaises(KeyError): table.read(field='non-existent-column') class Rec(tb.IsDescription): col1 = tb.IntCol(pos=1, shape=(2,)) col2 = tb.StringCol(itemsize=3, pos=2, shape=(3,)) col3 = tb.FloatCol(pos=3, shape=(3, 2)) class RecArrayIO(common.TempFileMixin, common.PyTablesTestCase): def test00(self): """Checking saving a normal recarray""" # Create a recarray intlist1 = [[456, 23]*3]*2 intlist2 = np.array([[2, 2]*3]*2, dtype=int) arrlist1 = [['dbe']*2]*3 arrlist2 = [['de']*2]*3 floatlist1 = [[1.2, 2.3]*3]*4 floatlist2 = np.array([[4.5, 2.4]*3]*4) b = [(intlist1, arrlist1, floatlist1), ( intlist2, arrlist2, floatlist2)] r = np.rec.array(b, formats='(2,6)i4,(3,2)a3,(4,6)f8', names='col1,col2,col3') # Save it in a table: self.h5file.create_table(self.h5file.root, 'recarray', r) # Read it again r2 = self.h5file.root.recarray.read() self.assertEqual(r.tobytes(), r2.tobytes()) def test01(self): """Checking saving a recarray with an offset in its buffer""" # Create a recarray intlist1 = [[456, 23]*3]*2 intlist2 = np.array([[2, 2]*3]*2, dtype=int) arrlist1 = [['dbe']*2]*3 arrlist2 = [['de']*2]*3 floatlist1 = [[1.2, 2.3]*3]*4 floatlist2 = np.array([[4.5, 2.4]*3]*4) b = [(intlist1, arrlist1, floatlist1), ( intlist2, arrlist2, floatlist2)] r = np.rec.array(b, formats='(2,6)i4,(3,2)a3,(4,6)f8', names='col1,col2,col3') # Get a view of the recarray r1 = r[1:] # Save it in a table: self.h5file.create_table(self.h5file.root, 'recarray', r1) # Read it again r2 = self.h5file.root.recarray.read() self.assertEqual(r1.tobytes(), r2.tobytes()) def test02(self): """Checking saving a slice of a large recarray""" # Create a recarray intlist1 = [[[23, 24, 35]*6]*6] intlist2 = np.array([[[2, 3, 4]*6]*6], dtype=int) arrlist1 = [['dbe']*2]*3 arrlist2 = [['de']*2]*3 floatlist1 = [[1.2, 2.3]*3]*4 floatlist2 = np.array([[4.5, 2.4]*3]*4) b = [(intlist1, arrlist1, floatlist1), ( intlist2, arrlist2, floatlist2)] r = np.rec.array(b * 300, formats='(1,6,18)i4,(3,2)a3,(4,6)f8', names='col1,col2,col3') # Get an slice of recarray r1 = r[290:292] # Save it in a table: self.h5file.create_table(self.h5file.root, 'recarray', r1) # Read it again r2 = self.h5file.root.recarray.read() self.assertEqual(r1.tobytes(), r2.tobytes()) def test03(self): """Checking saving a slice of an strided recarray""" # Create a recarray intlist1 = [[[23, 24, 35]*6]*6] intlist2 = np.array([[[2, 3, 4]*6]*6], dtype=int) arrlist1 = [['dbe']*2]*3 arrlist2 = [['de']*2]*3 floatlist1 = [[1.2, 2.3]*3]*4 floatlist2 = np.array([[4.5, 2.4]*3]*4) b = [(intlist1, arrlist1, floatlist1), ( intlist2, arrlist2, floatlist2)] r = np.rec.array(b * 300, formats='(1,6,18)i4,(3,2)a3,(4,6)f8', names='col1,col2,col3', shape=600) # Get an strided recarray r2 = r[::2] # Get a slice r1 = r2[148:] # Save it in a table: self.h5file.create_table(self.h5file.root, 'recarray', r1) # Read it again r2 = self.h5file.root.recarray.read() self.assertEqual(r1.tobytes(), r2.tobytes()) def test08a(self): """Checking modifying one column (single column version, list)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08a..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # Append new rows s0, s1, s2, s3 = ['dbe']*3, ['ded']*3, ['db1']*3, ['de1']*3 f0, f1, f2, f3 = [[1.2]*2]*3, [[1.3]*2]*3, [[1.4]*2]*3, [[1.5]*2]*3 r = np.rec.array([([456, 457], s0, f0), ([2, 3], s1, f1)], formats="(2,)i4,(3,)a3,(3,2)f8") table.append(r) table.append([([457, 458], s2, f2), ([5, 6], s3, f3)]) # Modify just one existing column table.cols.col1[1:] = [[[2, 3], [3, 4], [4, 5]]] # Create the modified recarray r1 = np.rec.array([([456, 457], s0, f0), ([2, 3], s1, f1), ([3, 4], s2, f2), ([4, 5], s3, f3)], formats="(2,)i4,(3,)a3,(3,2)f8", names="col1,col2,col3") # Read the modified table r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test08b(self): """Checking modifying one column (single column version, recarray)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08b..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # Append new rows s0, s1, s2, s3 = ['dbe']*3, ['ded']*3, ['db1']*3, ['de1']*3 f0, f1, f2, f3 = [[1.2]*2]*3, [[1.3]*2]*3, [[1.4]*2]*3, [[1.5]*2]*3 r = np.rec.array([([456, 457], s0, f0), ([2, 3], s1, f1)], formats="(2,)i4,(3,)a3,(3,2)f8") table.append(r) table.append([([457, 458], s2, f2), ([5, 6], s3, f3)]) # Modify just one existing column columns = np.rec.fromarrays( np.array([[[2, 3], [3, 4], [4, 5]]]), formats="i4") table.modify_columns(start=1, columns=columns, names=["col1"]) # Create the modified recarray r1 = np.rec.array([([456, 457], s0, f0), ([2, 3], s1, f1), ([3, 4], s2, f2), ([4, 5], s3, f3)], formats="(2,)i4,(3,)a3,(3,2)f8", names="col1,col2,col3") # Read the modified table r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test08b2(self): """Checking modifying one column (single column version, recarray, modify_column)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test08b2..." % self.__class__.__name__) # Create a new table: table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) # Append new rows s0, s1, s2, s3 = ['dbe']*3, ['ded']*3, ['db1']*3, ['de1']*3 f0, f1, f2, f3 = [[1.2]*2]*3, [[1.3]*2]*3, [[1.4]*2]*3, [[1.5]*2]*3 r = np.rec.array([([456, 457], s0, f0), ([2, 3], s1, f1)], formats="(2,)i4,(3,)a3,(3,2)f8") table.append(r) table.append([([457, 458], s2, f2), ([5, 6], s3, f3)]) # Modify just one existing column columns = np.rec.fromarrays( np.array([[[2, 3], [3, 4], [4, 5]]]), formats="i4") table.modify_column(start=1, column=columns, colname="col1") # Create the modified recarray r1 = np.rec.array([([456, 457], s0, f0), ([2, 3], s1, f1), ([3, 4], s2, f2), ([4, 5], s3, f3)], formats="(2,)i4,(3,)a3,(3,2)f8", names="col1,col2,col3") # Read the modified table r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) class DefaultValues(common.TempFileMixin, common.PyTablesTestCase): def test00(self): """Checking saving a Table MD with default values""" # Create a table table = self.h5file.create_table(self.h5file.root, 'table', Record) # Take a number of records a bit large # nrows = int(table.nrowsinbuf * 1.1) nrows = 5 # for test # Fill the table with nrows records for i in range(nrows): if i == 3 or i == 4: table.row['var2'] = ((2, 2), (2, 2)) # *-* # This injects the row values. table.row.append() # We need to flush the buffers in table in order to get an # accurate number of records on it. table.flush() # Create a recarray with the same default values buffer = [( ["\x00"]*2, # just "" does not initialize the buffer properly [["abcd", "efgh"]]*2, (1, 1), ((1, 1), (1, 1)), 2, 3.1, 4.2, 5, "e")] r = np.rec.array( buffer * nrows, formats='(2,)a4,(2,2)a4,(2,)i4,(2,2)i4,i2,f8,f4,u2,a1', names=['var0', 'var1', 'var1_', 'var2', 'var3', 'var4', 'var5', 'var6', 'var7']) # *-* # Assign the value exceptions r["var2"][3] = ((2, 2), (2, 2)) # *-* r["var2"][4] = ((2, 2), (2, 2)) # *-* # Read the table in another recarray r2 = table.read() # This generates too much output. Activate only when # self.nrowsinbuf is very small (<10) if common.verbose and 1: print("Table values:") print(r2) print("Record values:") print(r) # Both checks do work, however, tobytes() seems more stringent. self.assertEqual(r.tobytes(), r2.tobytes()) # self.assertTrue(common.areArraysEqual(r,r2)) class RecordT(tb.IsDescription): var0 = tb.IntCol(dflt=1, shape=()) # native int var1 = tb.IntCol(dflt=[1], shape=(1,)) # 1-D int (one element) var2_s = tb.IntCol(dflt=[1, 1], shape=2) # 1-D int (two elements) var2 = tb.IntCol(dflt=[1, 1], shape=(2,)) # 1-D int (two elements) var3 = tb.IntCol(dflt=[[0, 0], [1, 1]], shape=(2, 2)) # 2-D int class ShapeTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.populateFile() def populateFile(self): table = self.h5file.create_table(self.h5file.root, 'table', RecordT) row = table.row # Fill the table with some rows with default values for i in range(1): row.append() # Flush the buffer for this table table.flush() def test00(self): """Checking scalar shapes""" if self.reopen: self._reopen() table = self.h5file.root.table if common.verbose: print("The values look like:", table.cols.var0[:]) print("They should look like:", [1]) # The real check self.assertEqual(table.cols.var0[:].tolist(), [1]) def test01(self): """Checking undimensional (one element) shapes""" if self.reopen: self._reopen() table = self.h5file.root.table if common.verbose: print("The values look like:", table.cols.var1[:]) print("They should look like:", [[1]]) # The real check self.assertEqual(table.cols.var1[:].tolist(), [[1]]) def test02(self): """Checking undimensional (two elements) shapes""" if self.reopen: self._reopen() table = self.h5file.root.table if common.verbose: print("The values look like:", table.cols.var2[:]) print("They should look like:", [[1, 1]]) # The real check self.assertEqual(table.cols.var2[:].tolist(), [[1, 1]]) self.assertEqual(table.cols.var2_s[:].tolist(), [[1, 1]]) def test03(self): """Checking bidimensional shapes""" if self.reopen: self._reopen() table = self.h5file.root.table if common.verbose: print("The values look like:", table.cols.var3[:]) print("They should look like:", [[[0, 0], [1, 1]]]) # The real check self.assertEqual(table.cols.var3[:].tolist(), [[[0, 0], [1, 1]]]) class ShapeTestCase1(ShapeTestCase): reopen = 0 class ShapeTestCase2(ShapeTestCase): reopen = 1 class SetItemTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() # Create a new table: self.table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) self.table.nrowsinbuf = self.buffersize # set buffer value def test01(self): """Checking modifying one table row with __setitem__""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify just one existing row table[2] = (456, 'db2', 1.2) # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3), (456, 'db2', 1.2), (5, 'de1', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test01b(self): """Checking modifying one table row with __setitem__ (long index)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify just one existing row table[2] = (456, 'db2', 1.2) # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3), (456, 'db2', 1.2), (5, 'de1', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test02(self): """Modifying one row, with a step (__setitem__)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify two existing rows rows = np.rec.array([(457, 'db1', 1.2)], formats=formats) table[1:3:2] = rows # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (457, 'db1', 1.2), (457, 'db1', 1.2), (5, 'de1', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test03(self): """Checking modifying several rows at once (__setitem__)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify two existing rows rows = np.rec.array( [(457, 'db1', 1.2), (5, 'de1', 1.3)], formats=formats) # table.modify_rows(start=1, rows=rows) table[1:3] = rows # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (457, 'db1', 1.2), (5, 'de1', 1.3), (5, 'de1', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test04(self): """Modifying several rows at once, with a step (__setitem__)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify two existing rows rows = np.rec.array([(457, 'db1', 1.2), (6, 'de2', 1.3)], formats=formats) # table[1:4:2] = rows table[1::2] = rows # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (457, 'db1', 1.2), (457, 'db1', 1.2), (6, 'de2', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test05(self): """Checking modifying one column (single element, __setitem__)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify just one existing column table.cols.col1[1] = -1 # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (-1, 'ded', 1.3), (457, 'db1', 1.2), (5, 'de1', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test06a(self): """Checking modifying one column (several elements, __setitem__)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify just one existing column table.cols.col1[1:4] = [(2, 2), (3, 3), (4, 4)] # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3), (3, 'db1', 1.2), (4, 'de1', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test06b(self): """Checking modifying one column (iterator, __setitem__)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify just one existing column with self.assertRaises(NotImplementedError): for row in table.iterrows(): row['col1'] = row.nrow + 1 row.append() table.flush() def test07(self): """Modifying one column (several elements, __setitem__, step)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (1, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify just one existing column table.cols.col1[1:4:2] = [(2, 2), (3, 3)] # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3), (457, 'db1', 1.2), (3, 'de1', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test08(self): """Modifying one column (one element, __setitem__, step)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify just one existing column table.cols.col1[1:4:3] = [(2, 2)] # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3), (457, 'db1', 1.2), (5, 'de1', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test09(self): """Modifying beyond the table extend (__setitem__, step)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Try to modify beyond the extend # This will silently exclude the non-fitting rows rows = np.rec.array([(457, 'db1', 1.2), (6, 'de2', 1.3)], formats=formats) table[1::2] = rows # How it should look like r1 = np.rec.array([(456, 'dbe', 1.2), (457, 'db1', 1.2), (457, 'db1', 1.2), (6, 'de2', 1.3)], formats=formats) # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) class SetItemTestCase1(SetItemTestCase): reopen = 0 buffersize = 1 class SetItemTestCase2(SetItemTestCase): reopen = 1 buffersize = 2 class SetItemTestCase3(SetItemTestCase): reopen = 0 buffersize = 1000 class SetItemTestCase4(SetItemTestCase): reopen = 1 buffersize = 1000 class UpdateRowTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() # Create a new table: self.table = self.h5file.create_table(self.h5file.root, 'recarray', Rec) self.table.nrowsinbuf = self.buffersize # set buffer value def test01(self): """Checking modifying one table row with Row.update""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify just one existing row for row in table.iterrows(2, 3): (row['col1'], row['col2'], row['col3']) = [456, 'db2', 1.2] row.update() # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3), (456, 'db2', 1.2), (5, 'de1', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test02(self): """Modifying one row, with a step (Row.update)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify two existing rows for row in table.iterrows(1, 3, 2): if row.nrow == 1: (row['col1'], row['col2'], row['col3']) = (457, 'db1', 1.2) elif row.nrow == 3: (row['col1'], row['col2'], row['col3']) = (6, 'de2', 1.3) row.update() # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (457, 'db1', 1.2), (457, 'db1', 1.2), (5, 'de1', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test03(self): """Checking modifying several rows at once (Row.update)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify two existing rows for row in table.iterrows(1, 3): if row.nrow == 1: (row['col1'], row['col2'], row['col3']) = (457, 'db1', 1.2) elif row.nrow == 2: (row['col1'], row['col2'], row['col3']) = (5, 'de1', 1.3) row.update() # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (457, 'db1', 1.2), (5, 'de1', 1.3), (5, 'de1', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test04(self): """Modifying several rows at once, with a step (Row.update)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify two existing rows for row in table.iterrows(1, stop=4, step=2): if row.nrow == 1: (row['col1'], row['col2'], row['col3']) = (457, 'db1', 1.2) elif row.nrow == 3: (row['col1'], row['col2'], row['col3']) = (6, 'de2', 1.3) row.update() # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (457, 'db1', 1.2), (457, 'db1', 1.2), (6, 'de2', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test05(self): """Checking modifying one column (single element, Row.update)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify just one existing column for row in table.iterrows(1, 2): row['col1'] = -1 row.update() # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (-1, 'ded', 1.3), (457, 'db1', 1.2), (5, 'de1', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test06(self): """Checking modifying one column (several elements, Row.update)""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify just one existing column for row in table.iterrows(1, 4): row['col1'] = row.nrow + 1 row.update() # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (2, 'ded', 1.3), (3, 'db1', 1.2), (4, 'de1', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test07(self): """Modifying values from a selection""" table = self.table formats = table.description._v_nested_formats # append new rows r = np.rec.array([(456, 'dbe', 1.2), (1, 'ded', 1.3)], formats=formats) table.append(r) table.append([(457, 'db1', 1.2), (5, 'de1', 1.3)]) # Modify just rows with col1 < 456 for row in table.iterrows(): if row['col1'][0] < 456: row['col1'] = 2 row['col2'] = 'ada' row.update() # Create the modified recarray r1 = np.rec.array([(456, 'dbe', 1.2), (2, 'ada', 1.3), (457, 'db1', 1.2), (2, 'ada', 1.3)], formats=formats, names="col1,col2,col3") # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, 4) def test08(self): """Modifying a large table (Row.update)""" table = self.table formats = table.description._v_nested_formats nrows = 100 # append new rows row = table.row for i in range(nrows): row['col1'] = i-1 row['col2'] = 'a'+str(i-1) row['col3'] = -1.0 row.append() table.flush() # Modify all the rows for row in table.iterrows(): row['col1'] = row.nrow row['col2'] = 'b'+str(row.nrow) row['col3'] = 0.0 row.update() # Create the modified recarray r1 = np.rec.array( None, shape=nrows, formats=formats, names="col1,col2,col3") for i in range(nrows): r1['col1'][i] = i r1['col2'][i] = 'b'+str(i) r1['col3'][i] = 0.0 # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, nrows) def test08b(self): """Setting values on a large table without calling Row.update""" table = self.table formats = table.description._v_nested_formats nrows = 100 # append new rows row = table.row for i in range(nrows): row['col1'] = i-1 row['col2'] = 'a'+str(i-1) row['col3'] = -1.0 row.append() table.flush() # Modify all the rows (actually don't) for row in table.iterrows(): row['col1'] = row.nrow row['col2'] = 'b'+str(row.nrow) row['col3'] = 0.0 # row.update() # Create the modified recarray r1 = np.rec.array( None, shape=nrows, formats=formats, names="col1,col2,col3") for i in range(nrows): r1['col1'][i] = i-1 r1['col2'][i] = 'a'+str(i-1) r1['col3'][i] = -1.0 # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, nrows) def test09(self): """Modifying selected values on a large table""" table = self.table formats = table.description._v_nested_formats nrows = 100 # append new rows row = table.row for i in range(nrows): row['col1'] = i-1 row['col2'] = 'a'+str(i-1) row['col3'] = -1.0 row.append() table.flush() # Modify selected rows for row in table.iterrows(): if row['col1'][0] > nrows-3: row['col1'] = row.nrow row['col2'] = 'b'+str(row.nrow) row['col3'] = 0.0 row.update() # Create the modified recarray r1 = np.rec.array( None, shape=nrows, formats=formats, names="col1,col2,col3") for i in range(nrows): r1['col1'][i] = i-1 r1['col2'][i] = 'a'+str(i-1) r1['col3'][i] = -1.0 # modify just the last line r1['col1'][i] = i r1['col2'][i] = 'b'+str(i) r1['col3'][i] = 0.0 # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, nrows) def test09b(self): """Modifying selected values on a large table (alternate values)""" table = self.table formats = table.description._v_nested_formats nrows = 100 # append new rows row = table.row for i in range(nrows): row['col1'] = i-1 row['col2'] = 'a'+str(i-1) row['col3'] = -1.0 row.append() table.flush() # Modify selected rows for row in table.iterrows(step=10): row['col1'] = row.nrow row['col2'] = 'b'+str(row.nrow) row['col3'] = 0.0 row.update() # Create the modified recarray r1 = np.rec.array( None, shape=nrows, formats=formats, names="col1,col2,col3") for i in range(nrows): if i % 10 > 0: r1['col1'][i] = i-1 r1['col2'][i] = 'a'+str(i-1) r1['col3'][i] = -1.0 else: r1['col1'][i] = i r1['col2'][i] = 'b'+str(i) r1['col3'][i] = 0.0 # Read the modified table if self.reopen: self._reopen() table = self.h5file.root.recarray table.nrowsinbuf = self.buffersize # set buffer value r2 = table.read() if common.verbose: print("Original table-->", repr(r2)) print("Should look like-->", repr(r1)) self.assertEqual(r1.tobytes(), r2.tobytes()) self.assertEqual(table.nrows, nrows) class UpdateRowTestCase1(UpdateRowTestCase): reopen = 0 buffersize = 1 class UpdateRowTestCase2(UpdateRowTestCase): reopen = 1 buffersize = 2 class UpdateRowTestCase3(UpdateRowTestCase): reopen = 0 buffersize = 1000 class UpdateRowTestCase4(UpdateRowTestCase): reopen = 1 buffersize = 1000 def suite(): theSuite = common.unittest.TestSuite() niter = 1 # common.heavy = 1 # Uncomment this only for testing purposes for n in range(niter): theSuite.addTest(common.unittest.makeSuite(BasicWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(DictWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(RecordDTWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(NumPyDTWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(RecArrayOneWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(RecArrayTwoWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(RecArrayThreeWriteTestCase)) theSuite.addTest( common.unittest.makeSuite(RecArrayAlignedWriteTestCase)) theSuite.addTest(common.unittest.makeSuite(CompressZLIBTablesTestCase)) theSuite.addTest(common.unittest.makeSuite(CompressTwoTablesTestCase)) theSuite.addTest(common.unittest.makeSuite(IterRangeTestCase)) theSuite.addTest(common.unittest.makeSuite(RecArrayRangeTestCase)) theSuite.addTest(common.unittest.makeSuite(GetColRangeTestCase)) theSuite.addTest(common.unittest.makeSuite(DefaultValues)) theSuite.addTest(common.unittest.makeSuite(RecArrayIO)) theSuite.addTest(common.unittest.makeSuite(ShapeTestCase1)) theSuite.addTest(common.unittest.makeSuite(ShapeTestCase2)) theSuite.addTest(common.unittest.makeSuite(SetItemTestCase1)) theSuite.addTest(common.unittest.makeSuite(SetItemTestCase2)) theSuite.addTest(common.unittest.makeSuite(SetItemTestCase3)) theSuite.addTest(common.unittest.makeSuite(SetItemTestCase4)) theSuite.addTest(common.unittest.makeSuite(UpdateRowTestCase1)) theSuite.addTest(common.unittest.makeSuite(UpdateRowTestCase2)) theSuite.addTest(common.unittest.makeSuite(UpdateRowTestCase3)) theSuite.addTest(common.unittest.makeSuite(UpdateRowTestCase4)) theSuite.addTest( common.unittest.makeSuite(CompressBloscTablesTestCase)) theSuite.addTest(common.unittest.makeSuite(CompressLZOTablesTestCase)) if common.heavy: theSuite.addTest( common.unittest.makeSuite(CompressBzip2TablesTestCase)) theSuite.addTest(common.unittest.makeSuite(BigTablesTestCase)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_timestamps.py000066400000000000000000000137551416254111300214700ustar00rootroot00000000000000"""This test unit checks control of dataset timestamps with track_times. """ import hashlib import sys import time from pathlib import Path import tables as tb from tables.tests import common HEXDIGEST = '2aafb84ab739bb4ae61d2939dc010bfd' class Record(tb.IsDescription): var1 = tb.StringCol(itemsize=4) # 4-character String var2 = tb.IntCol() # integer var3 = tb.Int16Col() # short integer class TrackTimesMixin: def _add_datasets(self, group, j, track_times): # Create a table table = self.h5file.create_table(group, f'table{j}', Record, title=self.title, filters=None, track_times=track_times) # Get the record object associated with the new table d = table.row # Fill the table for i in range(self.nrows): d['var1'] = '%04d' % (self.nrows - i) d['var2'] = i d['var3'] = i * 2 d.append() # This injects the Record values # Flush the buffer for this table table.flush() # Create a couple of arrays in each group var1List = [x['var1'] for x in table.iterrows()] var3List = [x['var3'] for x in table.iterrows()] self.h5file.create_array(group, f'array{j}', var1List, f"col {j}", track_times=track_times) # Create CArrays as well self.h5file.create_carray(group, name=f'carray{j}', obj=var3List, title="col {}".format(j + 2), track_times=track_times) # Create EArrays as well ea = self.h5file.create_earray(group, f'earray{j}', tb.StringAtom(itemsize=4), (0,), "col {}".format(j + 4), track_times=track_times) # And fill them with some values ea.append(var1List) # Finally VLArrays too vla = self.h5file.create_vlarray(group, f'vlarray{j}', tb.Int16Atom(), "col {}".format(j + 6), track_times=track_times) # And fill them with some values vla.append(var3List) class TimestampTestCase(TrackTimesMixin, common.TempFileMixin, common.PyTablesTestCase): title = "A title" nrows = 10 def setUp(self): super().setUp() self.populateFile() def populateFile(self): group = self.h5file.root for j in range(4): track_times = bool(j % 2) self._add_datasets(group, j, track_times) def test00_checkTimestamps(self): """Checking retrieval of timestamps""" for pattern in ('/table{}', '/array{}', '/carray{}', '/earray{}', '/vlarray{}'): # Verify that: # - if track_times was False, ctime is 0 # - if track_times was True, ctime is not 0, # and has either stayed the same or incremented tracked_ctimes = [] for j in range(4): track_times = bool(j % 2) node = pattern.format(j) obj = self.h5file.get_node(node) # Test property retrieval self.assertEqual(obj.track_times, track_times) timestamps = obj._get_obj_timestamps() self.assertEqual(timestamps.atime, 0) self.assertEqual(timestamps.mtime, 0) self.assertEqual(timestamps.btime, 0) if not track_times: self.assertEqual(timestamps.ctime, 0) else: self.assertNotEqual(timestamps.ctime, 0) tracked_ctimes.append(timestamps.ctime) self.assertGreaterEqual(tracked_ctimes[1], tracked_ctimes[0]) class BitForBitTestCase(TrackTimesMixin, common.TempFileMixin, common.PyTablesTestCase): title = "A title" nrows = 10 def repopulateFile(self, track_times): self.h5file.close() self.h5file = tb.open_file(self.h5fname, mode="w") group = self.h5file.root self._add_datasets(group, 1, track_times) self.h5file.close() def test00_checkReproducibility(self): """Checking bit-for-bit reproducibility with no track_times""" self.repopulateFile(track_times=False) hexdigest_wo_track_1 = self._get_digest(self.h5fname) self.repopulateFile(track_times=True) hexdigest_w_track_1 = self._get_digest(self.h5fname) time.sleep(1) self.repopulateFile(track_times=True) hexdigest_w_track_2 = self._get_digest(self.h5fname) self.repopulateFile(track_times=False) hexdigest_wo_track_2 = self._get_digest(self.h5fname) self.assertEqual(HEXDIGEST, hexdigest_wo_track_1) self.assertEqual(hexdigest_wo_track_1, hexdigest_wo_track_2) self.assertNotEqual(hexdigest_wo_track_1, hexdigest_w_track_1) self.assertNotEqual(hexdigest_w_track_1, hexdigest_w_track_2) def _get_digest(self, filename): md5 = hashlib.md5() for data in Path(filename).read_bytes(): md5.update(data) hexdigest = md5.hexdigest() return hexdigest def suite(): theSuite = common.unittest.TestSuite() niter = 1 # common.heavy = 1 # Uncomment this only for testing purposes! for i in range(niter): theSuite.addTest(common.unittest.makeSuite(TimestampTestCase)) theSuite.addTest(common.unittest.makeSuite(BitForBitTestCase)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_timetype.py000066400000000000000000000423741416254111300211410ustar00rootroot00000000000000"""Unit test for the Time datatypes.""" import numpy as np import tables as tb from tables.tests import common class LeafCreationTestCase(common.TempFileMixin, common.PyTablesTestCase): """Tests creating Tables, VLArrays an EArrays with Time data.""" def test00_UnidimLeaves(self): """Creating new nodes with unidimensional time elements.""" # Table creation. class MyTimeRow(tb.IsDescription): intcol = tb.IntCol() t32col = tb.Time32Col() t64col = tb.Time64Col() self.h5file.create_table('/', 'table', MyTimeRow) # VLArray creation. self.h5file.create_vlarray('/', 'vlarray4', tb.Time32Atom()) self.h5file.create_vlarray('/', 'vlarray8', tb.Time64Atom()) # EArray creation. self.h5file.create_earray('/', 'earray4', tb.Time32Atom(), shape=(0,)) self.h5file.create_earray('/', 'earray8', tb.Time64Atom(), shape=(0,)) def test01_MultidimLeaves(self): """Creating new nodes with multidimensional time elements.""" # Table creation. class MyTimeRow(tb.IsDescription): intcol = tb.IntCol(shape=(2, 1)) t32col = tb.Time32Col(shape=(2, 1)) t64col = tb.Time64Col(shape=(2, 1)) self.h5file.create_table('/', 'table', MyTimeRow) # VLArray creation. self.h5file.create_vlarray( '/', 'vlarray4', tb.Time32Atom(shape=(2, 1))) self.h5file.create_vlarray( '/', 'vlarray8', tb.Time64Atom(shape=(2, 1))) # EArray creation. self.h5file.create_earray( '/', 'earray4', tb.Time32Atom(), shape=(0, 2, 1)) self.h5file.create_earray( '/', 'earray8', tb.Time64Atom(), shape=(0, 2, 1)) class OpenTestCase(common.TempFileMixin, common.PyTablesTestCase): """Tests opening a file with Time nodes.""" # The description used in the test Table. class MyTimeRow(tb.IsDescription): t32col = tb.Time32Col(shape=(2, 1)) t64col = tb.Time64Col(shape=(2, 1)) # The atoms used in the test VLArrays. myTime32Atom = tb.Time32Atom(shape=(2, 1)) myTime64Atom = tb.Time64Atom(shape=(2, 1)) def setUp(self): super().setUp() # Create test Table. self.h5file.create_table('/', 'table', self.MyTimeRow) # Create test VLArrays. self.h5file.create_vlarray('/', 'vlarray4', self.myTime32Atom) self.h5file.create_vlarray('/', 'vlarray8', self.myTime64Atom) self._reopen() def test00_OpenFile(self): """Opening a file with Time nodes.""" # Test the Table node. tbl = self.h5file.root.table self.assertEqual( tbl.coldtypes['t32col'], self.MyTimeRow.columns['t32col'].dtype, "Column dtypes do not match.") self.assertEqual( tbl.coldtypes['t64col'], self.MyTimeRow.columns['t64col'].dtype, "Column dtypes do not match.") # Test the VLArray nodes. vla4 = self.h5file.root.vlarray4 self.assertEqual( vla4.atom.dtype, self.myTime32Atom.dtype, "Atom types do not match.") self.assertEqual( vla4.atom.shape, self.myTime32Atom.shape, "Atom shapes do not match.") vla8 = self.h5file.root.vlarray8 self.assertEqual( vla8.atom.dtype, self.myTime64Atom.dtype, "Atom types do not match.") self.assertEqual( vla8.atom.shape, self.myTime64Atom.shape, "Atom shapes do not match.") def test01_OpenFileStype(self): """Opening a file with Time nodes, comparing Atom.stype.""" # Test the Table node. tbl = self.h5file.root.table self.assertEqual( tbl.coltypes['t32col'], self.MyTimeRow.columns['t32col'].type, "Column types do not match.") self.assertEqual( tbl.coltypes['t64col'], self.MyTimeRow.columns['t64col'].type, "Column types do not match.") # Test the VLArray nodes. vla4 = self.h5file.root.vlarray4 self.assertEqual( vla4.atom.type, self.myTime32Atom.type, "Atom types do not match.") vla8 = self.h5file.root.vlarray8 self.assertEqual( vla8.atom.type, self.myTime64Atom.type, "Atom types do not match.") class CompareTestCase(common.TempFileMixin, common.PyTablesTestCase): """Tests whether stored and retrieved time data is kept the same.""" # The description used in the test Table. class MyTimeRow(tb.IsDescription): t32col = tb.Time32Col(pos=0) t64col = tb.Time64Col(shape=(2,), pos=1) # The atoms used in the test VLArrays. myTime32Atom = tb.Time32Atom(shape=(2,)) myTime64Atom = tb.Time64Atom(shape=(2,)) def test00_Compare32VLArray(self): """Comparing written 32-bit time data with read data in a VLArray.""" wtime = np.array((1_234_567_890,) * 2, np.int32) # Create test VLArray with data. vla = self.h5file.create_vlarray('/', 'test', self.myTime32Atom) vla.append(wtime) self._reopen() # Check the written data. rtime = self.h5file.root.test.read()[0][0] self.h5file.close() self.assertTrue(common.allequal(rtime, wtime), "Stored and retrieved values do not match.") def test01_Compare64VLArray(self): """Comparing written 64-bit time data with read data in a VLArray.""" wtime = np.array((1_234_567_890.123456,) * 2, np.float64) # Create test VLArray with data. vla = self.h5file.create_vlarray('/', 'test', self.myTime64Atom) vla.append(wtime) self._reopen() # Check the written data. rtime = self.h5file.root.test.read()[0][0] self.h5file.close() self.assertTrue(common.allequal(rtime, wtime), "Stored and retrieved values do not match.") def test01b_Compare64VLArray(self): """Comparing several written and read 64-bit time values in a VLArray.""" # Create test VLArray with data. vla = self.h5file.create_vlarray('/', 'test', self.myTime64Atom) # Size of the test. nrows = vla.nrowsinbuf + 34 # Add some more rows than buffer. # Only for home checks; the value above should check better # the I/O with multiple buffers. # nrows = 10 for i in range(nrows): j = i * 2 vla.append((j + 0.012, j + 1 + 0.012)) self._reopen() # Check the written data. arr = self.h5file.root.test.read() self.h5file.close() arr = np.array(arr) orig_val = np.arange(0, nrows * 2, dtype=np.int32) + 0.012 orig_val.shape = (nrows, 1, 2) if common.verbose: print("Original values:", orig_val) print("Retrieved values:", arr) self.assertTrue(common.allequal(arr, orig_val), "Stored and retrieved values do not match.") def test02_CompareTable(self): """Comparing written time data with read data in a Table.""" wtime = 1_234_567_890.123456 # Create test Table with data. tbl = self.h5file.create_table('/', 'test', self.MyTimeRow) row = tbl.row row['t32col'] = int(wtime) row['t64col'] = (wtime, wtime) row.append() self._reopen() # Check the written data. recarr = self.h5file.root.test.read(0) self.h5file.close() self.assertEqual(recarr['t32col'][0], int(wtime), "Stored and retrieved values do not match.") comp = (recarr['t64col'][0] == np.array((wtime, wtime))) self.assertTrue(np.alltrue(comp), "Stored and retrieved values do not match.") def test02b_CompareTable(self): """Comparing several written and read time values in a Table.""" # Create test Table with data. tbl = self.h5file.create_table('/', 'test', self.MyTimeRow) # Size of the test. nrows = tbl.nrowsinbuf + 34 # Add some more rows than buffer. # Only for home checks; the value above should check better # the I/O with multiple buffers. # nrows = 10 row = tbl.row for i in range(nrows): row['t32col'] = i j = i * 2 row['t64col'] = (j + 0.012, j+1+0.012) row.append() self._reopen() # Check the written data. recarr = self.h5file.root.test.read() self.h5file.close() # Time32 column. orig_val = np.arange(nrows, dtype=np.int32) if common.verbose: print("Original values:", orig_val) print("Retrieved values:", recarr['t32col'][:]) self.assertTrue(np.alltrue(recarr['t32col'][:] == orig_val), "Stored and retrieved values do not match.") # Time64 column. orig_val = np.arange(0, nrows * 2, dtype=np.int32) + 0.012 orig_val.shape = (nrows, 2) if common.verbose: print("Original values:", orig_val) print("Retrieved values:", recarr['t64col'][:]) self.assertTrue( common.allequal(recarr['t64col'][:], orig_val, np.float64), "Stored and retrieved values do not match.") def test03_Compare64EArray(self): """Comparing written 64-bit time data with read data in an EArray.""" wtime = 1_234_567_890.123456 # Create test EArray with data. ea = self.h5file.create_earray( '/', 'test', tb.Time64Atom(), shape=(0,)) ea.append((wtime,)) self._reopen() # Check the written data. rtime = self.h5file.root.test[0] self.h5file.close() self.assertTrue(common.allequal(rtime, wtime), "Stored and retrieved values do not match.") def test03b_Compare64EArray(self): """Comparing several written and read 64-bit time values in an EArray.""" # Create test EArray with data. ea = self.h5file.create_earray('/', 'test', tb.Time64Atom(), shape=(0, 2)) # Size of the test. nrows = ea.nrowsinbuf + 34 # Add some more rows than buffer. # Only for home checks; the value above should check better # the I/O with multiple buffers. # nrows = 10 for i in range(nrows): j = i * 2 ea.append(((j + 0.012, j + 1 + 0.012),)) self._reopen() # Check the written data. arr = self.h5file.root.test.read() self.h5file.close() orig_val = np.arange(0, nrows * 2, dtype=np.int32) + 0.012 orig_val.shape = (nrows, 2) if common.verbose: print("Original values:", orig_val) print("Retrieved values:", arr) self.assertTrue(common.allequal(arr, orig_val), "Stored and retrieved values do not match.") class UnalignedTestCase(common.TempFileMixin, common.PyTablesTestCase): """Tests writing and reading unaligned time values in a table.""" # The description used in the test Table. # Time fields are unaligned because of 'i8col'. class MyTimeRow(tb.IsDescription): i8col = tb.Int8Col(pos=0) t32col = tb.Time32Col(pos=1) t64col = tb.Time64Col(shape=(2,), pos=2) def test00_CompareTable(self): """Comparing written unaligned time data with read data in a Table.""" # Create test Table with data. tbl = self.h5file.create_table('/', 'test', self.MyTimeRow) # Size of the test. nrows = tbl.nrowsinbuf + 34 # Add some more rows than buffer. # Only for home checks; the value above should check better # the I/O with multiple buffers. # nrows = 10 row = tbl.row for i in range(nrows): row['i8col'] = i row['t32col'] = i j = i * 2 row['t64col'] = (j + 0.012, j+1+0.012) row.append() self._reopen() # Check the written data. recarr = self.h5file.root.test.read() self.h5file.close() # Int8 column. orig_val = np.arange(nrows, dtype=np.int8) if common.verbose: print("Original values:", orig_val) print("Retrieved values:", recarr['i8col'][:]) self.assertTrue(np.alltrue(recarr['i8col'][:] == orig_val), "Stored and retrieved values do not match.") # Time32 column. orig_val = np.arange(nrows, dtype=np.int32) if common.verbose: print("Original values:", orig_val) print("Retrieved values:", recarr['t32col'][:]) self.assertTrue(np.alltrue(recarr['t32col'][:] == orig_val), "Stored and retrieved values do not match.") # Time64 column. orig_val = np.arange(0, nrows * 2, dtype=np.int32) + 0.012 orig_val.shape = (nrows, 2) if common.verbose: print("Original values:", orig_val) print("Retrieved values:", recarr['t64col'][:]) self.assertTrue(common.allequal( recarr['t64col'][:], orig_val, np.float64), "Stored and retrieved values do not match.") class BigEndianTestCase(common.PyTablesTestCase): """Tests for reading big-endian time values in arrays and nested tables.""" def setUp(self): super().setUp() filename = common.test_filename('times-nested-be.h5') self.h5file = tb.open_file(filename, 'r') def tearDown(self): self.h5file.close() super().tearDown() def test00a_Read32Array(self): """Checking Time32 type in arrays.""" # Check the written data. earr = self.h5file.root.earr32[:] # Generate the expected Time32 array. start = 1_178_896_298 nrows = 10 orig_val = np.arange(start, start + nrows, dtype=np.int32) if common.verbose: print("Retrieved values:", earr) print("Should look like:", orig_val) self.assertTrue(np.alltrue(earr == orig_val), "Retrieved values do not match the expected values.") def test00b_Read64Array(self): """Checking Time64 type in arrays.""" # Check the written data. earr = self.h5file.root.earr64[:] # Generate the expected Time64 array. start = 1_178_896_298.832258 nrows = 10 orig_val = np.arange(start, start + nrows, dtype=np.float64) if common.verbose: print("Retrieved values:", earr) print("Should look like:", orig_val) self.assertTrue(np.allclose(earr, orig_val, rtol=1.e-15), "Retrieved values do not match the expected values.") def test01a_ReadPlainColumn(self): """Checking Time32 type in plain columns.""" # Check the written data. tbl = self.h5file.root.tbl t32 = tbl.cols.t32[:] # Generate the expected Time32 array. start = 1_178_896_298 nrows = 10 orig_val = np.arange(start, start + nrows, dtype=np.int32) if common.verbose: print("Retrieved values:", t32) print("Should look like:", orig_val) self.assertTrue(np.alltrue(t32 == orig_val), "Retrieved values do not match the expected values.") def test01b_ReadNestedColumn(self): """Checking Time64 type in nested columns.""" # Check the written data. tbl = self.h5file.root.tbl t64 = tbl.cols.nested.t64[:] # Generate the expected Time64 array. start = 1_178_896_298.832258 nrows = 10 orig_val = np.arange(start, start + nrows, dtype=np.float64) if common.verbose: print("Retrieved values:", t64) print("Should look like:", orig_val) self.assertTrue(np.allclose(t64, orig_val, rtol=1.e-15), "Retrieved values do not match the expected values.") def test02_ReadNestedColumnTwice(self): """Checking Time64 type in nested columns (read twice).""" # Check the written data. tbl = self.h5file.root.tbl dummy = tbl.cols.nested.t64[:] self.assertIsNotNone(dummy) t64 = tbl.cols.nested.t64[:] # Generate the expected Time64 array. start = 1_178_896_298.832258 nrows = 10 orig_val = np.arange(start, start + nrows, dtype=np.float64) if common.verbose: print("Retrieved values:", t64) print("Should look like:", orig_val) self.assertTrue(np.allclose(t64, orig_val, rtol=1.e-15), "Retrieved values do not match the expected values.") def suite(): """suite() -> test suite Returns a test suite consisting of all the test cases in the module. """ theSuite = common.unittest.TestSuite() theSuite.addTest(common.unittest.makeSuite(LeafCreationTestCase)) theSuite.addTest(common.unittest.makeSuite(OpenTestCase)) theSuite.addTest(common.unittest.makeSuite(CompareTestCase)) theSuite.addTest(common.unittest.makeSuite(UnalignedTestCase)) theSuite.addTest(common.unittest.makeSuite(BigEndianTestCase)) return theSuite if __name__ == '__main__': import sys common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_tree.py000066400000000000000000001172021416254111300202310ustar00rootroot00000000000000import sys import tempfile import warnings from pathlib import Path from time import perf_counter as clock import tables as tb from tables.tests import common # Test Record class class Record(tb.IsDescription): var1 = tb.StringCol(itemsize=4) # 4-character String var2 = tb.IntCol() # integer var3 = tb.Int16Col() # short integer var4 = tb.FloatCol() # double (double-precision) var5 = tb.Float32Col() # float (single-precision) class TreeTestCase(common.TempFileMixin, common.PyTablesTestCase): open_mode = "w" title = "This is the table title" expectedrows = 10 appendrows = 5 def setUp(self): super().setUp() # Create an instance of HDF5 Table self.populateFile() self.h5file.close() def populateFile(self): group = self.h5file.root maxshort = 1 << 15 # maxint = 2147483647 # (2 ** 31 - 1) for j in range(3): # Create a table table = self.h5file.create_table(group, 'table'+str(j), Record, title=self.title, filters=None, expectedrows=self.expectedrows) # Get the record object associated with the new table d = table.row # Fill the table for i in range(self.expectedrows): d['var1'] = '%04d' % (self.expectedrows - i) d['var2'] = i d['var3'] = i % maxshort d['var4'] = float(i) d['var5'] = float(i) d.append() # This injects the Record values # Flush the buffer for this table table.flush() # Create a couple of arrays in each group var1List = [x['var1'] for x in table.iterrows()] var4List = [x['var4'] for x in table.iterrows()] self.h5file.create_array(group, 'var1', var1List, "1") self.h5file.create_array(group, 'var4', var4List, "4") # Create a new group (descendant of group) group2 = self.h5file.create_group(group, 'group'+str(j)) # Iterate over this new group (group2) group = group2 def test00_getNode(self): """Checking the File.get_node() with string node names""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_getNode..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") nodelist = ['/', '/table0', '/group0/var1', '/group0/group1/var4'] nodenames = [] for node in nodelist: object = self.h5file.get_node(node) nodenames.append(object._v_pathname) self.assertEqual(nodenames, nodelist) if common.verbose: print("get_node(pathname) test passed") nodegroups = [ '/', '/group0', '/group0/group1', '/group0/group1/group2'] nodenames = ['var1', 'var4'] nodepaths = [] for group in nodegroups: for name in nodenames: try: object = self.h5file.get_node(group, name) except LookupError: pass else: nodepaths.append(object._v_pathname) self.assertEqual(nodepaths, ['/var1', '/var4', '/group0/var1', '/group0/var4', '/group0/group1/var1', '/group0/group1/var4']) if common.verbose: print("get_node(groupname, name) test passed") nodelist = ['/', '/group0', '/group0/group1', '/group0/group1/group2', '/table0'] nodenames = [] groupobjects = [] # warnings.filterwarnings("error", category=UserWarning) for node in nodelist: try: object = self.h5file.get_node(node, classname='Group') except LookupError: if common.verbose: (type, value, traceback) = sys.exc_info() print("\nGreat!, the next LookupError was catched!") print(value) else: nodenames.append(object._v_pathname) groupobjects.append(object) self.assertEqual(nodenames, ['/', '/group0', '/group0/group1', '/group0/group1/group2']) if common.verbose: print("get_node(groupname, classname='Group') test passed") # Reset the warning # warnings.filterwarnings("default", category=UserWarning) nodenames = ['var1', 'var4'] nodearrays = [] for group in groupobjects: for name in nodenames: try: object = self.h5file.get_node(group, name, 'Array') except Exception: pass else: nodearrays.append(object._v_pathname) self.assertEqual(nodearrays, ['/var1', '/var4', '/group0/var1', '/group0/var4', '/group0/group1/var1', '/group0/group1/var4']) if common.verbose: print("get_node(groupobject, name, classname='Array') test passed") def test01_getNodeClass(self): """Checking the File.get_node() with instances""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_getNodeClass..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") # This tree ways of get_node usage should return a table instance table = self.h5file.get_node("/group0/table1") self.assertIsInstance(table, tb.Table) table = self.h5file.get_node("/group0", "table1") self.assertIsInstance(table, tb.Table) table = self.h5file.get_node(self.h5file.root.group0, "table1") self.assertIsInstance(table, tb.Table) # This should return an array instance arr = self.h5file.get_node("/group0/var1") self.assertIsInstance(arr, tb.Array) self.assertIsInstance(arr, tb.Leaf) # And this a Group group = self.h5file.get_node("/group0", "group1", "Group") self.assertIsInstance(group, tb.Group) def test02_listNodes(self): """Checking the File.list_nodes() method""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_listNodes..." % self.__class__.__name__) # Made the warnings to raise an error # warnings.filterwarnings("error", category=UserWarning) self.h5file = tb.open_file(self.h5fname, "r") self.assertRaises(TypeError, self.h5file.list_nodes, '/', 'NoSuchClass') nodelist = ['/', '/group0', '/group0/table1', '/group0/group1/group2', '/var1'] nodenames = [] objects = [] for node in nodelist: try: objectlist = self.h5file.list_nodes(node) except Exception: pass else: objects.extend(objectlist) for object in objectlist: nodenames.append(object._v_pathname) self.assertEqual(nodenames, ['/group0', '/table0', '/var1', '/var4', '/group0/group1', '/group0/table1', '/group0/var1', '/group0/var4']) if common.verbose: print("list_nodes(pathname) test passed") nodenames = [] for node in objects: try: objectlist = self.h5file.list_nodes(node) except Exception: pass else: for object in objectlist: nodenames.append(object._v_pathname) self.assertEqual(nodenames, ['/group0/group1', '/group0/table1', '/group0/var1', '/group0/var4', '/group0/group1/group2', '/group0/group1/table2', '/group0/group1/var1', '/group0/group1/var4']) if common.verbose: print("list_nodes(groupobject) test passed") nodenames = [] for node in objects: try: objectlist = self.h5file.list_nodes(node, 'Leaf') except TypeError: if common.verbose: (type, value, traceback) = sys.exc_info() print("\nGreat!, the next TypeError was catched!") print(value) else: for object in objectlist: nodenames.append(object._v_pathname) self.assertEqual(nodenames, ['/group0/table1', '/group0/var1', '/group0/var4', '/group0/group1/table2', '/group0/group1/var1', '/group0/group1/var4']) if common.verbose: print("list_nodes(groupobject, classname = 'Leaf') test passed") nodenames = [] for node in objects: try: objectlist = self.h5file.list_nodes(node, 'Table') except TypeError: if common.verbose: (type, value, traceback) = sys.exc_info() print("\nGreat!, the next TypeError was catched!") print(value) else: for object in objectlist: nodenames.append(object._v_pathname) self.assertEqual(nodenames, ['/group0/table1', '/group0/group1/table2']) if common.verbose: print("list_nodes(groupobject, classname = 'Table') test passed") # Reset the warning # warnings.filterwarnings("default", category=UserWarning) def test02b_iterNodes(self): """Checking the File.iter_nodes() method""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02b_iterNodes..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") self.assertRaises(TypeError, self.h5file.list_nodes, '/', 'NoSuchClass') nodelist = ['/', '/group0', '/group0/table1', '/group0/group1/group2', '/var1'] nodenames = [] objects = [] for node in nodelist: try: objectlist = [o for o in self.h5file.iter_nodes(node)] except Exception: pass else: objects.extend(objectlist) for object in objectlist: nodenames.append(object._v_pathname) self.assertEqual(nodenames, ['/group0', '/table0', '/var1', '/var4', '/group0/group1', '/group0/table1', '/group0/var1', '/group0/var4']) if common.verbose: print("iter_nodes(pathname) test passed") nodenames = [] for node in objects: try: objectlist = [o for o in self.h5file.iter_nodes(node)] except Exception: pass else: for object in objectlist: nodenames.append(object._v_pathname) self.assertEqual(nodenames, ['/group0/group1', '/group0/table1', '/group0/var1', '/group0/var4', '/group0/group1/group2', '/group0/group1/table2', '/group0/group1/var1', '/group0/group1/var4']) if common.verbose: print("iter_nodes(groupobject) test passed") nodenames = [] for node in objects: try: objectlist = [o for o in self.h5file.iter_nodes(node, 'Leaf')] except TypeError: if common.verbose: (type, value, traceback) = sys.exc_info() print("\nGreat!, the next TypeError was catched!") print(value) else: for object in objectlist: nodenames.append(object._v_pathname) self.assertEqual(nodenames, ['/group0/table1', '/group0/var1', '/group0/var4', '/group0/group1/table2', '/group0/group1/var1', '/group0/group1/var4']) if common.verbose: print("iter_nodes(groupobject, classname = 'Leaf') test passed") nodenames = [] for node in objects: try: objectlist = [o for o in self.h5file.iter_nodes(node, 'Table')] except TypeError: if common.verbose: (type, value, traceback) = sys.exc_info() print("\nGreat!, the next TypeError was catched!") print(value) else: for object in objectlist: nodenames.append(object._v_pathname) self.assertEqual(nodenames, ['/group0/table1', '/group0/group1/table2']) if common.verbose: print("iter_nodes(groupobject, classname = 'Table') test passed") # Reset the warning # warnings.filterwarnings("default", category=UserWarning) def test03_TraverseTree(self): """Checking the File.walk_groups() method""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_TraverseTree..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") groups = [] tables_ = [] arrays = [] for group in self.h5file.walk_groups(): groups.append(group._v_pathname) for table in self.h5file.list_nodes(group, 'Table'): tables_.append(table._v_pathname) for arr in self.h5file.list_nodes(group, 'Array'): arrays.append(arr._v_pathname) self.assertEqual(groups, ["/", "/group0", "/group0/group1", "/group0/group1/group2"]) self.assertEqual( tables_, ["/table0", "/group0/table1", "/group0/group1/table2"]) self.assertEqual(arrays, ['/var1', '/var4', '/group0/var1', '/group0/var4', '/group0/group1/var1', '/group0/group1/var4']) if common.verbose: print("walk_groups() test passed") groups = [] tables_ = [] arrays = [] for group in self.h5file.walk_groups("/group0/group1"): groups.append(group._v_pathname) for table in self.h5file.list_nodes(group, 'Table'): tables_.append(table._v_pathname) for arr in self.h5file.list_nodes(group, 'Array'): arrays.append(arr._v_pathname) self.assertEqual(groups, ["/group0/group1", "/group0/group1/group2"]) self.assertEqual(tables_, ["/group0/group1/table2"]) self.assertEqual(arrays, [ '/group0/group1/var1', '/group0/group1/var4']) if common.verbose: print("walk_groups(pathname) test passed") def test04_walkNodes(self): """Checking File.walk_nodes""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_walkNodes..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") self.assertRaises(TypeError, next, self.h5file.walk_nodes('/', 'NoSuchClass')) groups = [] tables1 = [] tables2 = [] arrays = [] for group in self.h5file.walk_nodes(classname="Group"): groups.append(group._v_pathname) for table in group._f_iter_nodes(classname='Table'): tables1.append(table._v_pathname) # Test the recursivity for table in self.h5file.root._f_walknodes('Table'): tables2.append(table._v_pathname) for arr in self.h5file.walk_nodes(classname='Array'): arrays.append(arr._v_pathname) self.assertEqual(groups, ["/", "/group0", "/group0/group1", "/group0/group1/group2"]) self.assertEqual(tables1, ["/table0", "/group0/table1", "/group0/group1/table2"]) self.assertEqual(tables2, ["/table0", "/group0/table1", "/group0/group1/table2"]) self.assertEqual(arrays, ['/var1', '/var4', '/group0/var1', '/group0/var4', '/group0/group1/var1', '/group0/group1/var4']) if common.verbose: print("File.__iter__() and Group.__iter__ test passed") groups = [] tables_ = [] arrays = [] for group in self.h5file.walk_nodes("/group0/group1", classname="Group"): groups.append(group._v_pathname) for table in group._f_walknodes('Table'): tables_.append(table._v_pathname) for arr in self.h5file.walk_nodes(group, 'Array'): arrays.append(arr._v_pathname) self.assertEqual(groups, ["/group0/group1", "/group0/group1/group2"]) self.assertEqual(tables_, ["/group0/group1/table2"]) self.assertEqual(arrays, [ '/group0/group1/var1', '/group0/group1/var4']) if common.verbose: print("walk_nodes(pathname, classname) test passed") def test05_dir(self): """Checking Group.__dir__""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_dir..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") """ h5file nodes: '/table0', '/var1', '/var4' '/group0/table1', '/group0/var1', '/group0/var4', '/group0/group1/table2', '/group0/group1/var1', '/group0/group1/var4' """ root_dir = dir(self.h5file.root) # Check some regular attributes. self.assertIn('_v_children', root_dir) self.assertIn('_v_attrs', root_dir) self.assertIn('_v_groups', root_dir) self.assertIn('_g_get_child_group_class', root_dir) self.assertIn('_g_get_child_group_class', root_dir) self.assertIn('_f_close', root_dir) # Check children nodes. self.assertIn('group0', root_dir) self.assertIn('table0', root_dir) self.assertIn('var1', root_dir) self.assertNotIn('table1', root_dir) self.assertNotIn('table2', root_dir) self.assertSequenceEqual(sorted(set(root_dir)), sorted(root_dir)) # Check for no duplicates. root_group0_dir = dir(self.h5file.root.group0) self.assertIn('group1', root_group0_dir) self.assertIn('table1', root_group0_dir) self.assertNotIn('table0', root_group0_dir) self.assertNotIn('table2', root_group0_dir) self.assertSequenceEqual(sorted(set(root_group0_dir)), sorted(root_group0_dir)) root_group0_group1_dir = dir(self.h5file.root.group0.group1) self.assertIn('group2', root_group0_group1_dir) self.assertIn('table2', root_group0_group1_dir) self.assertNotIn('table0', root_group0_group1_dir) self.assertNotIn('table1', root_group0_group1_dir) self.assertNotIn('group0', root_group0_group1_dir) self.assertNotIn('group1', root_group0_group1_dir) self.assertSequenceEqual(sorted(set(root_group0_group1_dir)), sorted(root_group0_group1_dir)) if common.verbose: print("Group.__dir__ test passed") def test06_v_groups(self): """Checking Group._v_groups""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06_v_groups..." % self.__class__.__name__) self.h5file = tb.open_file(self.h5fname, "r") """ h5file nodes: '/table0', '/var1', '/var4' '/group0/table1', '/group0/var1', '/group0/var4', '/group0/group1/table2', '/group0/group1/var1', '/group0/group1/var4' """ self.assertIsInstance(self.h5file.root._v_groups, dict) group_names = {'group0'} names = {k for k, v in self.h5file.root._v_groups.iteritems()} self.assertEqual(group_names, names) groups = list(self.h5file.root._v_groups.itervalues()) self.assertEqual(len(groups), len(group_names)) for group in groups: with self.subTest(name=group._v_name): self.assertIn(group._v_name, group_names) if common.verbose: print("Group._v_groups test passed") class DeepTreeTestCase(common.TempFileMixin, common.PyTablesTestCase): """Checks for deep hierarchy levels in PyTables trees.""" def setUp(self): super().setUp() # Here we put a more conservative limit to deal with more platforms # With maxdepth = 64 this test would take less than 40 MB # of main memory to run, which is quite reasonable nowadays. # With maxdepth = 1024 this test will take around 300 MB. if common.heavy: self.maxdepth = 256 # Takes around 60 MB of memory! else: self.maxdepth = 64 # This should be safe for most machines if common.verbose: print("Maximum depth tested :", self.maxdepth) # Open a new empty HDF5 file group = self.h5file.root if common.verbose: print("Depth writing progress: ", end=' ') # Iterate until maxdepth for depth in range(self.maxdepth): # Save it on the HDF5 file if common.verbose: print("%3d," % (depth), end=' ') # Create a couple of arrays here self.h5file.create_array( group, 'array', [1, 1], "depth: %d" % depth) self.h5file.create_array( group, 'array2', [1, 1], "depth: %d" % depth) # And also a group self.h5file.create_group(group, 'group2_' + str(depth)) # Finally, iterate over a new group group = self.h5file.create_group(group, 'group' + str(depth)) # Close the file self.h5file.close() def _check_tree(self, filename): # Open the previous HDF5 file in read-only mode with tb.open_file(filename, mode="r") as h5file: group = h5file.root if common.verbose: print("\nDepth reading progress: ", end=' ') # Get the metadata on the previosly saved arrays for depth in range(self.maxdepth): if common.verbose: print("%3d," % (depth), end=' ') # Check the contents self.assertEqual(group.array[:], [1, 1]) self.assertIn("array2", group) self.assertIn("group2_"+str(depth), group) # Iterate over the next group group = h5file.get_node(group, 'group' + str(depth)) if common.verbose: print() # This flush the stdout buffer def test00_deepTree(self): """Creation of a large depth object tree.""" self._check_tree(self.h5fname) def test01a_copyDeepTree(self): """Copy of a large depth object tree.""" self.h5file = tb.open_file(self.h5fname, mode="r") h5fname2 = tempfile.mktemp(".h5") try: with tb.open_file(h5fname2, mode="w") as h5file2: if common.verbose: print("\nCopying deep tree...") self.h5file.copy_node(self.h5file.root, h5file2.root, recursive=True) self.h5file.close() self._check_tree(h5fname2) finally: if Path(h5fname2).is_file(): Path(h5fname2).unlink() def test01b_copyDeepTree(self): """Copy of a large depth object tree with small node cache.""" self.h5file = tb.open_file(self.h5fname, mode="r", node_cache_slots=10) h5fname2 = tempfile.mktemp(".h5") try: with tb.open_file(h5fname2, mode="w", node_cache_slots=10) as h5file2: if common.verbose: print("\nCopying deep tree...") self.h5file.copy_node(self.h5file.root, h5file2.root, recursive=True) self.h5file.close() self._check_tree(h5fname2) finally: if Path(h5fname2).is_file(): Path(h5fname2).unlink() def test01c_copyDeepTree(self): """Copy of a large depth object tree with no node cache.""" self.h5file = tb.open_file(self.h5fname, mode="r", node_cache_slots=0) h5fname2 = tempfile.mktemp(".h5") try: with tb.open_file(h5fname2, mode="w", node_cache_slots=0) as h5file2: if common.verbose: print("\nCopying deep tree...") self.h5file.copy_node(self.h5file.root, h5file2.root, recursive=True) self.h5file.close() self._check_tree(h5fname2) finally: if Path(h5fname2).is_file(): Path(h5fname2).unlink() @common.unittest.skipUnless(common.heavy, 'only in heavy mode') def test01d_copyDeepTree(self): """Copy of a large depth object tree with static node cache.""" self.h5file = tb.open_file(self.h5fname, mode="r", node_cache_slots=-256) h5fname2 = tempfile.mktemp(".h5") try: with tb.open_file(h5fname2, mode="w", node_cache_slots=-256) as h5file2: if common.verbose: print("\nCopying deep tree...") self.h5file.copy_node(self.h5file.root, h5file2.root, recursive=True) self.h5file.close() self._check_tree(h5fname2) finally: if Path(h5fname2).is_file(): Path(h5fname2).unlink() class WideTreeTestCase(common.TempFileMixin, common.PyTablesTestCase): """Checks for maximum number of children for a Group.""" def test00_Leafs(self): """Checking creation of large number of leafs (1024) per group. Variable 'maxchildren' controls this check. PyTables support up to 4096 children per group, but this would take too much memory (up to 64 MB) for testing purposes (may be we can add a test for big platforms). A 1024 children run takes up to 30 MB. A 512 children test takes around 25 MB. """ if common.heavy: maxchildren = 4096 else: maxchildren = 256 if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_wideTree..." % self.__class__.__name__) print("Maximum number of children tested :", maxchildren) a = [1, 1] if common.verbose: print("Children writing progress: ", end=' ') for child in range(maxchildren): if common.verbose: print("%3d," % (child), end=' ') self.h5file.create_array(self.h5file.root, 'array' + str(child), a, "child: %d" % child) if common.verbose: print() t1 = clock() a = [1, 1] # Open the previous HDF5 file in read-only mode self._reopen() if common.verbose: print("\nTime spent opening a file with %d arrays: %s s" % (maxchildren, clock()-t1)) print("\nChildren reading progress: ", end=' ') # Get the metadata on the previosly saved arrays for child in range(maxchildren): if common.verbose: print("%3d," % (child), end=' ') # Create an array for later comparison # Get the actual array array_ = getattr(self.h5file.root, 'array' + str(child)) b = array_.read() # Arrays a and b must be equal self.assertEqual(a, b) if common.verbose: print() # This flush the stdout buffer def test01_wideTree(self): """Checking creation of large number of groups (1024) per group. Variable 'maxchildren' controls this check. PyTables support up to 4096 children per group, but this would take too much memory (up to 64 MB) for testing purposes (may be we can add a test for big platforms). A 1024 children run takes up to 30 MB. A 512 children test takes around 25 MB. """ if common.heavy: # for big platforms! maxchildren = 4096 else: # for standard platforms maxchildren = 256 if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_wideTree..." % self.__class__.__name__) print("Maximum number of children tested :", maxchildren) if common.verbose: print("Children writing progress: ", end=' ') for child in range(maxchildren): if common.verbose: print("%3d," % (child), end=' ') self.h5file.create_group(self.h5file.root, 'group' + str(child), "child: %d" % child) if common.verbose: print() t1 = clock() # Open the previous HDF5 file in read-only mode self._reopen() if common.verbose: print("\nTime spent opening a file with %d groups: %s s" % (maxchildren, clock()-t1)) print("\nChildren reading progress: ", end=' ') # Get the metadata on the previosly saved arrays for child in range(maxchildren): if common.verbose: print("%3d," % (child), end=' ') # Get the actual group group = getattr(self.h5file.root, 'group' + str(child)) # Arrays a and b must be equal self.assertEqual(group._v_title, "child: %d" % child) if common.verbose: print() # This flush the stdout buffer class HiddenTreeTestCase(common.TempFileMixin, common.PyTablesTestCase): """Check for hidden groups, leaves and hierarchies.""" def setUp(self): super().setUp() self.visible = [] # list of visible object paths self.hidden = [] # list of hidden object paths # Create some visible nodes: a, g, g/a1, g/a2, g/g, g/g/a. h5f = self.h5file h5f.create_array('/', 'a', [0]) g = h5f.create_group('/', 'g') h5f.create_array(g, 'a1', [0]) h5f.create_array(g, 'a2', [0]) g_g = h5f.create_group(g, 'g') h5f.create_array(g_g, 'a', [0]) self.visible.extend(['/a', '/g', '/g/a1', '/g/a2', '/g/g', '/g/g/a']) # Create some hidden nodes: _p_a, _p_g, _p_g/a, _p_g/_p_a, g/_p_a. h5f.create_array('/', '_p_a', [0]) hg = h5f.create_group('/', '_p_g') h5f.create_array(hg, 'a', [0]) h5f.create_array(hg, '_p_a', [0]) h5f.create_array(g, '_p_a', [0]) self.hidden.extend( ['/_p_a', '/_p_g', '/_p_g/a', '/_p_g/_p_a', '/g/_p_a']) # The test behind commented out because the .objects dictionary # has been removed (as well as .leaves and .groups) def _test00_objects(self): """Absence of hidden nodes in `File.objects`.""" objects = self.h5file.objects warnings.filterwarnings('ignore', category=DeprecationWarning) for vpath in self.visible: self.assertTrue( vpath in objects, "Missing visible node ``%s`` from ``File.objects``." % vpath) for hpath in self.hidden: self.assertTrue( hpath not in objects, "Found hidden node ``%s`` in ``File.objects``." % hpath) warnings.filterwarnings('default', category=DeprecationWarning) # The test behind commented out because the .objects dictionary # has been removed (as well as .leaves and .groups) def _test00b_objects(self): """Object dictionaries conformance with ``walk_nodes()``.""" def dictCheck(dictName, classname): file_ = self.h5file objects = getattr(file_, dictName) walkPaths = [node._v_pathname for node in file_.walk_nodes('/', classname)] dictPaths = [path for path in objects] walkPaths.sort() dictPaths.sort() self.assertEqual( walkPaths, dictPaths, "nodes in ``%s`` do not match those from ``walk_nodes()``" % dictName) self.assertEqual( len(walkPaths), len(objects), "length of ``%s`` differs from that of ``walk_nodes()``" % dictName) warnings.filterwarnings('ignore', category=DeprecationWarning) dictCheck('objects', None) dictCheck('groups', 'Group') dictCheck('leaves', 'Leaf') warnings.filterwarnings('default', category=DeprecationWarning) def test01_getNode(self): """Node availability via `File.get_node()`.""" h5f = self.h5file for vpath in self.visible: h5f.get_node(vpath) for hpath in self.hidden: h5f.get_node(hpath) def test02_walkGroups(self): """Hidden group absence in `File.walk_groups()`.""" hidden = self.hidden for group in self.h5file.walk_groups('/'): pathname = group._v_pathname self.assertNotIn(pathname, hidden, f"Walked across hidden group ``{pathname}``.") def test03_walkNodes(self): """Hidden node absence in `File.walk_nodes()`.""" hidden = self.hidden for node in self.h5file.walk_nodes('/'): pathname = node._v_pathname self.assertNotIn(pathname, hidden, f"Walked across hidden node ``{pathname}``.") def test04_listNodesVisible(self): """Listing visible nodes under a visible group (list_nodes).""" hidden = self.hidden for node in self.h5file.list_nodes('/g'): pathname = node._v_pathname self.assertNotIn(pathname, hidden, f"Listed hidden node ``{pathname}``.") def test04b_listNodesVisible(self): """Listing visible nodes under a visible group (iter_nodes).""" hidden = self.hidden for node in self.h5file.iter_nodes('/g'): pathname = node._v_pathname self.assertNotIn(pathname, hidden, f"Listed hidden node ``{pathname}``.") def test05_listNodesHidden(self): """Listing visible nodes under a hidden group (list_nodes).""" hidden = self.hidden node_to_find = '/_p_g/a' found_node = False for node in self.h5file.list_nodes('/_p_g'): pathname = node._v_pathname if pathname == node_to_find: found_node = True self.assertIn(pathname, hidden, f"Listed hidden node ``{pathname}``.") self.assertTrue(found_node, "Hidden node ``%s`` was not listed." % node_to_find) def test05b_iterNodesHidden(self): """Listing visible nodes under a hidden group (iter_nodes).""" hidden = self.hidden node_to_find = '/_p_g/a' found_node = False for node in self.h5file.iter_nodes('/_p_g'): pathname = node._v_pathname if pathname == node_to_find: found_node = True self.assertIn(pathname, hidden, f"Listed hidden node ``{pathname}``.") self.assertTrue(found_node, "Hidden node ``%s`` was not listed." % node_to_find) # The test behind commented out because the .objects dictionary # has been removed (as well as .leaves and .groups) def _test06_reopen(self): """Reopening a file with hidden nodes.""" self.h5file.close() self.h5file = tb.open_file(self.h5fname) self.test00_objects() def test07_move(self): """Moving a node between hidden and visible groups.""" is_visible_node = self.h5file.is_visible_node self.assertFalse(is_visible_node('/_p_g/a')) self.h5file.move_node('/_p_g/a', '/g', 'a') self.assertTrue(is_visible_node('/g/a')) self.h5file.move_node('/g/a', '/_p_g', 'a') self.assertFalse(is_visible_node('/_p_g/a')) def test08_remove(self): """Removing a visible group with hidden children.""" self.assertIn('/g/_p_a', self.h5file) self.h5file.root.g._f_remove(recursive=True) self.assertNotIn('/g/_p_a', self.h5file) class CreateParentsTestCase(common.TempFileMixin, common.PyTablesTestCase): """Test the ``createparents`` flag. These are mainly for the user interface. More thorough tests on the workings of the flag can be found in the ``test_do_undo.py`` module. """ filters = tb.Filters(complevel=4) # simply non-default def setUp(self): super().setUp() self.h5file.create_array('/', 'array', [1]) self.h5file.create_group('/', 'group', filters=self.filters) def test00_parentType(self): """Using the right type of parent node argument.""" h5file, root = self.h5file, self.h5file.root self.assertRaises(TypeError, h5file.create_array, root.group, 'arr', [1], createparents=True) self.assertRaises(TypeError, h5file.copy_node, '/array', root.group, createparents=True) self.assertRaises(TypeError, h5file.move_node, '/array', root.group, createparents=True) self.assertRaises(TypeError, h5file.copy_children, '/group', root, createparents=True) def test01_inside(self): """Placing a node inside a nonexistent child of itself.""" self.assertRaises(tb.NodeError, self.h5file.move_node, '/group', '/group/foo/bar', createparents=True) self.assertNotIn('/group/foo', self.h5file) self.assertRaises(tb.NodeError, self.h5file.copy_node, '/group', '/group/foo/bar', recursive=True, createparents=True) self.assertNotIn('/group/foo', self.h5fname) def test02_filters(self): """Propagating the filters of created parent groups.""" self.h5file.create_group('/group/foo/bar', 'baz', createparents=True) self.assertIn('/group/foo/bar/baz', self.h5file) for group in self.h5file.walk_groups('/group'): self.assertEqual(self.filters, group._v_filters) def suite(): theSuite = common.unittest.TestSuite() # This counter is useful when detecting memory leaks niter = 1 for i in range(niter): theSuite.addTest(common.unittest.makeSuite(TreeTestCase)) theSuite.addTest(common.unittest.makeSuite(DeepTreeTestCase)) theSuite.addTest(common.unittest.makeSuite(WideTreeTestCase)) theSuite.addTest(common.unittest.makeSuite(HiddenTreeTestCase)) theSuite.addTest(common.unittest.makeSuite(CreateParentsTestCase)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_types.py000066400000000000000000000267261416254111300204500ustar00rootroot00000000000000import sys import numpy as np import tables as tb from tables.tests import common # Test Record class class Record(tb.IsDescription): var1 = tb.StringCol(itemsize=4) # 4-character String var2 = tb.Col.from_kind('int') # integer var3 = tb.Col.from_kind('int', itemsize=2) # short integer var4 = tb.Col.from_kind('float') # double (double-precision) var5 = tb.Col.from_kind('float', itemsize=4) # float (single-precision) var6 = tb.Col.from_kind('complex') # double-precision var7 = tb.Col.from_kind('complex', itemsize=8) # single-precision if hasattr(tb, "Float16Atom"): var8 = tb.Col.from_kind('float', itemsize=2) # half-precision if hasattr(tb, "Float96Atom"): var9 = tb.Col.from_kind('float', itemsize=12) # extended-precision if hasattr(tb, "Float128Atom"): var10 = tb.Col.from_kind('float', itemsize=16) # extended-precision if hasattr(tb, "Complex192Atom"): var11 = tb.Col.from_kind('complex', itemsize=24) # extended-precision if hasattr(tb, "Complex256Atom"): var12 = tb.Col.from_kind('complex', itemsize=32) # extended-precision class RangeTestCase(common.TempFileMixin, common.PyTablesTestCase): title = "This is the table title" expectedrows = 100 maxshort = 2 ** 15 maxint = 2_147_483_648 # (2 ** 31) compress = 0 def setUp(self): super().setUp() self.rootgroup = self.h5file.root # Create a table self.table = self.h5file.create_table(self.rootgroup, 'table', Record, self.title) def test00_range(self): """Testing the range check.""" rec = self.table.row # Save a record i = self.maxshort rec['var1'] = '%04d' % (i) rec['var2'] = i rec['var3'] = i rec['var4'] = float(i) rec['var5'] = float(i) rec['var6'] = float(i) rec['var7'] = complex(i, i) if hasattr(tb, "Float16Atom"): rec['var8'] = float(i) if hasattr(tb, "Float96Atom"): rec['var9'] = float(i) if hasattr(tb, "Float128Atom"): rec['var10'] = float(i) try: rec.append() except ValueError: if common.verbose: (type, value, traceback) = sys.exc_info() print("\nGreat!, the next ValueError was catched!") print(value) pass else: if common.verbose: print( "\nNow, the range overflow no longer issues a ValueError") def test01_type(self): """Testing the type check.""" rec = self.table.row # Save a record i = self.maxshort rec['var1'] = '%04d' % (i) rec['var2'] = i rec['var3'] = i % self.maxshort rec['var5'] = float(i) with self.assertRaises(TypeError): rec['var4'] = "124c" rec['var6'] = float(i) rec['var7'] = complex(i, i) if hasattr(tb, "Float16Atom"): rec['var8'] = float(i) if hasattr(tb, "Float96Atom"): rec['var9'] = float(i) if hasattr(tb, "Float128Atom"): rec['var10'] = float(i) # Check the dtype read-only attribute class DtypeTestCase(common.TempFileMixin, common.PyTablesTestCase): def test00a_table(self): """Check dtype accessor for Table objects.""" a = self.h5file.create_table('/', 'table', Record) self.assertEqual(a.dtype, a.description._v_dtype) def test00b_column(self): """Check dtype accessor for Column objects.""" a = self.h5file.create_table('/', 'table', Record) c = a.cols.var3 self.assertEqual(c.dtype, a.description._v_dtype['var3']) def test01_array(self): """Check dtype accessor for Array objects.""" a = self.h5file.create_array('/', 'array', [1, 2]) self.assertEqual(a.dtype, a.atom.dtype) def test02_carray(self): """Check dtype accessor for CArray objects.""" a = self.h5file.create_carray('/', 'array', atom=tb.FloatAtom(), shape=[1, 2]) self.assertEqual(a.dtype, a.atom.dtype) def test03_carray(self): """Check dtype accessor for EArray objects.""" a = self.h5file.create_earray('/', 'array', atom=tb.FloatAtom(), shape=[0, 2]) self.assertEqual(a.dtype, a.atom.dtype) def test04_vlarray(self): """Check dtype accessor for VLArray objects.""" a = self.h5file.create_vlarray('/', 'array', tb.FloatAtom()) self.assertEqual(a.dtype, a.atom.dtype) class ReadFloatTestCase(common.TestFileMixin, common.PyTablesTestCase): h5fname = common.test_filename("float.h5") nrows = 5 ncols = 6 def setUp(self): super().setUp() x = np.arange(self.ncols) y = np.arange(self.nrows) y.shape = (self.nrows, 1) self.values = x + y def test01_read_float16(self): dtype = "float16" if hasattr(np, dtype): ds = getattr(self.h5file.root, dtype) self.assertNotIsInstance(ds, tb.UnImplemented) self.assertEqual(ds.shape, (self.nrows, self.ncols)) self.assertEqual(ds.dtype, dtype) self.assertTrue(common.allequal( ds.read(), self.values.astype(dtype))) else: with self.assertWarns(UserWarning): ds = getattr(self.h5file.root, dtype) self.assertIsInstance(ds, tb.UnImplemented) def test02_read_float32(self): dtype = "float32" ds = getattr(self.h5file.root, dtype) self.assertNotIsInstance(ds, tb.UnImplemented) self.assertEqual(ds.shape, (self.nrows, self.ncols)) self.assertEqual(ds.dtype, dtype) self.assertTrue(common.allequal( ds.read(), self.values.astype(dtype))) def test03_read_float64(self): dtype = "float64" ds = getattr(self.h5file.root, dtype) self.assertNotIsInstance(ds, tb.UnImplemented) self.assertEqual(ds.shape, (self.nrows, self.ncols)) self.assertEqual(ds.dtype, dtype) self.assertTrue(common.allequal( ds.read(), self.values.astype(dtype))) def test04_read_longdouble(self): dtype = "longdouble" if hasattr(tb, "Float96Atom") or hasattr(tb, "Float128Atom"): ds = getattr(self.h5file.root, dtype) self.assertNotIsInstance(ds, tb.UnImplemented) self.assertEqual(ds.shape, (self.nrows, self.ncols)) self.assertEqual(ds.dtype, dtype) self.assertTrue(common.allequal( ds.read(), self.values.astype(dtype))) if hasattr(tb, "Float96Atom"): self.assertEqual(ds.dtype, "float96") elif hasattr(tb, "Float128Atom"): self.assertEqual(ds.dtype, "float128") else: # XXX: check # the behavior depends on the HDF5 lib configuration try: with self.assertWarns(UserWarning): ds = getattr(self.h5file.root, dtype) self.assertIsInstance(ds, tb.UnImplemented) except AssertionError: if not tb.utilsextension._broken_hdf5_long_double(): ds = getattr(self.h5file.root, dtype) self.assertEqual(ds.dtype, "float64") def test05_read_quadprecision_float(self): # XXX: check try: with self.assertWarns(UserWarning): ds = self.h5file.root.quadprecision self.assertIsInstance(ds, tb.UnImplemented) except AssertionError: # NOTE: it would be nice to have some sort of message that warns # against the potential precision loss: the quad-precision # dataset actually uses 128 bits for each element, not just # 80 bits (longdouble) ds = self.h5file.root.quadprecision self.assertEqual(ds.dtype, "longdouble") class AtomTestCase(common.PyTablesTestCase): def test_init_parameters_01(self): atom1 = tb.StringAtom(itemsize=12) atom2 = atom1.copy() self.assertEqual(atom1, atom2) self.assertEqual(str(atom1), str(atom2)) self.assertIsNot(atom1, atom2) def test_init_parameters_02(self): atom1 = tb.StringAtom(itemsize=12) atom2 = atom1.copy(itemsize=100, shape=(2, 2)) self.assertEqual(atom2, tb.StringAtom(itemsize=100, shape=(2, 2), dflt=b'')) def test_init_parameters_03(self): atom1 = tb.StringAtom(itemsize=12) self.assertRaises(TypeError, atom1.copy, foobar=42) def test_from_dtype_01(self): atom1 = tb.Atom.from_dtype(np.dtype((np.int16, (2, 2)))) atom2 = tb.Int16Atom(shape=(2, 2), dflt=0) self.assertEqual(atom1, atom2) self.assertEqual(str(atom1), str(atom2)) def test_from_dtype_02(self): atom1 = tb.Atom.from_dtype(np.dtype('S5'), dflt=b'hello') atom2 = tb.StringAtom(itemsize=5, shape=(), dflt=b'hello') self.assertEqual(atom1, atom2) self.assertEqual(str(atom1), str(atom2)) def test_from_dtype_03(self): with self.assertWarns(Warning): atom1 = tb.Atom.from_dtype(np.dtype('U5'), dflt=b'hello') atom2 = tb.StringAtom(itemsize=5, shape=(), dflt=b'hello') self.assertEqual(atom1, atom2) self.assertEqual(str(atom1), str(atom2)) def test_from_dtype_04(self): atom1 = tb.Atom.from_dtype(np.dtype('float64')) atom2 = tb.Float64Atom(shape=(), dflt=0.0) self.assertEqual(atom1, atom2) self.assertEqual(str(atom1), str(atom2)) def test_from_kind_01(self): atom1 = tb.Atom.from_kind('int', itemsize=2, shape=(2, 2)) atom2 = tb.Int16Atom(shape=(2, 2), dflt=0) self.assertEqual(atom1, atom2) self.assertEqual(str(atom1), str(atom2)) def test_from_kind_02(self): atom1 = tb.Atom.from_kind('int', shape=(2, 2)) atom2 = tb.Int32Atom(shape=(2, 2), dflt=0) self.assertEqual(atom1, atom2) self.assertEqual(str(atom1), str(atom2)) def test_from_kind_03(self): atom1 = tb.Atom.from_kind('int', shape=1) atom2 = tb.Int32Atom(shape=(1,), dflt=0) self.assertEqual(atom1, atom2) self.assertEqual(str(atom1), str(atom2)) def test_from_kind_04(self): atom1 = tb.Atom.from_kind('string', itemsize=5, dflt=b'hello') atom2 = tb.StringAtom(itemsize=5, shape=(), dflt=b'hello') self.assertEqual(atom1, atom2) self.assertEqual(str(atom1), str(atom2)) def test_from_kind_05(self): # ValueError: no default item size for kind ``string`` self.assertRaises(ValueError, tb.Atom.from_kind, 'string', dflt=b'hello') def test_from_kind_06(self): # ValueError: unknown kind: 'Float' self.assertRaises(ValueError, tb.Atom.from_kind, 'Float') def suite(): import doctest theSuite = common.unittest.TestSuite() for i in range(1): theSuite.addTest(doctest.DocTestSuite(tb.atom)) theSuite.addTest(common.unittest.makeSuite(AtomTestCase)) theSuite.addTest(common.unittest.makeSuite(RangeTestCase)) theSuite.addTest(common.unittest.makeSuite(DtypeTestCase)) theSuite.addTest(common.unittest.makeSuite(ReadFloatTestCase)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_utils.py000066400000000000000000000056201416254111300204320ustar00rootroot00000000000000import sys from io import StringIO from unittest.mock import patch import tables.scripts.ptrepack as ptrepack import tables.scripts.ptdump as ptdump import tables.scripts.pttree as pttree from tables.tests import common class ptrepackTestCase(common.PyTablesTestCase): """Test ptrepack""" @patch.object(ptrepack, 'copy_leaf') @patch.object(ptrepack.tb, 'open_file') def test_paths_windows(self, mock_open_file, mock_copy_leaf): """Checking handling of windows filenames: test gh-616""" # this filename has a semi-colon to check for # regression of gh-616 src_fn = 'D:\\window~1\\path\\000\\infile' src_path = '/' dst_fn = 'another\\path\\' dst_path = '/path/in/outfile' argv = ['ptrepack', src_fn + ':' + src_path, dst_fn + ':' + dst_path] with patch.object(sys, 'argv', argv): ptrepack.main() args, kwargs = mock_open_file.call_args_list[0] self.assertEqual(args, (src_fn, 'r')) args, kwargs = mock_copy_leaf.call_args_list[0] self.assertEqual(args, (src_fn, dst_fn, src_path, dst_path)) class ptdumpTestCase(common.PyTablesTestCase): """Test ptdump""" @patch.object(ptdump.tb, 'open_file') @patch('sys.stdout', new_callable=StringIO) def test_paths_windows(self, _, mock_open_file): """Checking handling of windows filenames: test gh-616""" # this filename has a semi-colon to check for # regression of gh-616 (in ptdump) src_fn = 'D:\\window~1\\path\\000\\ptdump' src_path = '/' argv = ['ptdump', src_fn + ':' + src_path] with patch.object(sys, 'argv', argv): ptdump.main() args, kwargs = mock_open_file.call_args_list[0] self.assertEqual(args, (src_fn, 'r')) class pttreeTestCase(common.PyTablesTestCase): """Test ptdump""" @patch.object(pttree.tb, 'open_file') @patch.object(pttree, 'get_tree_str') @patch('sys.stdout', new_callable=StringIO) def test_paths_windows(self, _, mock_get_tree_str, mock_open_file): """Checking handling of windows filenames: test gh-616""" # this filename has a semi-colon to check for # regression of gh-616 (in pttree) src_fn = 'D:\\window~1\\path\\000\\pttree' src_path = '/' argv = ['pttree', src_fn + ':' + src_path] with patch.object(sys, 'argv', argv): pttree.main() args, kwargs = mock_open_file.call_args_list[0] self.assertEqual(args, (src_fn, 'r')) def suite(): theSuite = common.unittest.TestSuite() theSuite.addTest(common.unittest.makeSuite(ptrepackTestCase)) theSuite.addTest(common.unittest.makeSuite(ptdumpTestCase)) theSuite.addTest(common.unittest.makeSuite(pttreeTestCase)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/test_vlarray.py000066400000000000000000004671011416254111300207600ustar00rootroot00000000000000import sys import numpy as np import tables as tb from tables.tests import common class C: c = (3, 4.5) class BasicTestCase(common.TempFileMixin, common.PyTablesTestCase): compress = 0 complib = "zlib" shuffle = 0 bitshuffle = 0 fletcher32 = 0 flavor = "numpy" def setUp(self): super().setUp() # Create an instance of an HDF5 Table self.rootgroup = self.h5file.root self.populateFile() self.h5file.close() def populateFile(self): group = self.rootgroup filters = tb.Filters(complevel=self.compress, complib=self.complib, shuffle=self.shuffle, bitshuffle=self.bitshuffle, fletcher32=self.fletcher32) vlarray = self.h5file.create_vlarray(group, 'vlarray1', atom=tb.Int32Atom(), title="ragged array of ints", filters=filters, expectedrows=1000) vlarray.flavor = self.flavor # Fill it with 5 rows vlarray.append([1, 2]) if self.flavor == "numpy": vlarray.append(np.array([3, 4, 5], dtype='int32')) vlarray.append(np.array([], dtype='int32')) # Empty entry elif self.flavor == "python": vlarray.append((3, 4, 5)) vlarray.append(()) # Empty entry vlarray.append([6, 7, 8, 9]) vlarray.append([10, 11, 12, 13, 14]) def test00_attributes(self): self.h5file = tb.open_file(self.h5fname, "r") obj = self.h5file.get_node("/vlarray1") self.assertEqual(obj.flavor, self.flavor) self.assertEqual(obj.shape, (5,)) self.assertEqual(obj.ndim, 1) self.assertEqual(obj.nrows, 5) self.assertEqual(obj.atom.type, 'int32') def test01_read(self): """Checking vlarray read.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_read..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") vlarray = self.h5file.get_node("/vlarray1") # Choose a small value for buffer size vlarray.nrowsinbuf = 3 # Read some rows row = vlarray.read(0)[0] row2 = vlarray.read(2)[0] if common.verbose: print("Flavor:", vlarray.flavor) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row) nrows = 5 self.assertEqual(nrows, vlarray.nrows) if self.flavor == "numpy": self.assertEqual(type(row), np.ndarray) self.assertTrue(common.allequal( row, np.array([1, 2], dtype='int32'), self.flavor)) self.assertTrue(common.allequal( row2, np.array([], dtype='int32'), self.flavor)) elif self.flavor == "python": self.assertEqual(row, [1, 2]) self.assertEqual(row2, []) self.assertEqual(len(row), 2) # Check filters: if self.compress != vlarray.filters.complevel and common.verbose: print("Error in compress. Class:", self.__class__.__name__) print("self, vlarray:", self.compress, vlarray.filters.complevel) self.assertEqual(vlarray.filters.complevel, self.compress) if self.compress > 0 and tb.which_lib_version(self.complib): self.assertEqual(vlarray.filters.complib, self.complib) if self.shuffle != vlarray.filters.shuffle and common.verbose: print("Error in shuffle. Class:", self.__class__.__name__) print("self, vlarray:", self.shuffle, vlarray.filters.shuffle) self.assertEqual(self.shuffle, vlarray.filters.shuffle) if self.bitshuffle != vlarray.filters.bitshuffle and common.verbose: print("Error in shuffle. Class:", self.__class__.__name__) print("self, vlarray:", self.bitshuffle, vlarray.filters.bitshuffle) self.assertEqual(self.shuffle, vlarray.filters.shuffle) if self.fletcher32 != vlarray.filters.fletcher32 and common.verbose: print("Error in fletcher32. Class:", self.__class__.__name__) print("self, vlarray:", self.fletcher32, vlarray.filters.fletcher32) self.assertEqual(self.fletcher32, vlarray.filters.fletcher32) def test02a_getitem(self): """Checking vlarray __getitem__ (slices)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02a_getitem..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") vlarray = self.h5file.get_node("/vlarray1") rows = [[1, 2], [3, 4, 5], [], [6, 7, 8, 9], [10, 11, 12, 13, 14]] slices = [ slice(None, None, None), slice(1, 1, 1), slice(30, None, None), slice(0, None, None), slice(3, None, 1), slice(3, None, 2), slice(None, 1, None), slice(None, 2, 1), slice(None, 30, 2), slice(None, None, 1), slice(None, None, 2), slice(None, None, 3), ] for slc in slices: # Read the rows in slc rows2 = vlarray[slc] rows1 = rows[slc] rows1f = [] if common.verbose: print("Flavor:", vlarray.flavor) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Original rows ==>", rows1) print("Rows read in vlarray ==>", rows2) if self.flavor == "numpy": for val in rows1: rows1f.append(np.array(val, dtype='int32')) for i in range(len(rows1f)): self.assertTrue(common.allequal( rows2[i], rows1f[i], self.flavor)) elif self.flavor == "python": self.assertEqual(rows2, rows1) def test02b_getitem(self): """Checking vlarray __getitem__ (scalars)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02b_getitem..." % self.__class__.__name__) if self.flavor != "numpy": # This test is only valid for NumPy return # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "r") vlarray = self.h5file.get_node("/vlarray1") # Get a numpy array of objects rows = np.array(vlarray[:], dtype=object) for slc in [0, np.array(1), 2, np.array([3]), [4]]: # Read the rows in slc rows2 = vlarray[slc] rows1 = rows[slc] if common.verbose: print("Flavor:", vlarray.flavor) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Original rows ==>", rows1) print("Rows read in vlarray ==>", rows2) for i in range(len(rows1)): self.assertTrue(common.allequal( rows2[i], rows1[i], self.flavor)) def test03_append(self): """Checking vlarray append.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_append..." % self.__class__.__name__) # Create an instance of an HDF5 Table self.h5file = tb.open_file(self.h5fname, "a") vlarray = self.h5file.get_node("/vlarray1") # Append a new row vlarray.append([7, 8, 9, 10]) # Choose a small value for buffer size vlarray.nrowsinbuf = 3 # Read some rows: row1 = vlarray[0] row2 = vlarray[2] row3 = vlarray[-1] if common.verbose: print("Flavor:", vlarray.flavor) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row1) nrows = 6 self.assertEqual(nrows, vlarray.nrows) if self.flavor == "numpy": self.assertEqual(type(row1), type(np.array([1, 2]))) self.assertTrue(common.allequal( row1, np.array([1, 2], dtype='int32'), self.flavor)) self.assertTrue(common.allequal( row2, np.array([], dtype='int32'), self.flavor)) self.assertTrue(common.allequal( row3, np.array([7, 8, 9, 10], dtype='int32'), self.flavor)) elif self.flavor == "python": self.assertEqual(row1, [1, 2]) self.assertEqual(row2, []) self.assertEqual(row3, [7, 8, 9, 10]) self.assertEqual(len(row3), 4) def test04_get_row_size(self): """Checking get_row_size method.""" self.h5file = tb.open_file(self.h5fname, "a") vlarray = self.h5file.get_node("/vlarray1") self.assertEqual(vlarray.get_row_size(0), 2 * vlarray.atom.size) self.assertEqual(vlarray.get_row_size(1), 3 * vlarray.atom.size) self.assertEqual(vlarray.get_row_size(2), 0 * vlarray.atom.size) self.assertEqual(vlarray.get_row_size(3), 4 * vlarray.atom.size) self.assertEqual(vlarray.get_row_size(4), 5 * vlarray.atom.size) class BasicNumPyTestCase(BasicTestCase): flavor = "numpy" class BasicPythonTestCase(BasicTestCase): flavor = "python" class ZlibComprTestCase(BasicTestCase): compress = 1 complib = "zlib" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class BloscComprTestCase(BasicTestCase): compress = 9 shuffle = 0 complib = "blosc" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class BloscShuffleComprTestCase(BasicTestCase): compress = 6 shuffle = 1 complib = "blosc" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( common.blosc_version < common.min_blosc_bitshuffle_version, f'BLOSC >= {common.min_blosc_bitshuffle_version} required') class BloscBitShuffleComprTestCase(BasicTestCase): compress = 9 bitshuffle = 1 complib = "blosc" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') class BloscBloscLZComprTestCase(BasicTestCase): compress = 9 shuffle = 1 complib = "blosc:blosclz" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'lz4' not in tb.blosc_compressor_list(), 'lz4 required') class BloscLZ4ComprTestCase(BasicTestCase): compress = 9 shuffle = 1 complib = "blosc:lz4" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'lz4' not in tb.blosc_compressor_list(), 'lz4 required') class BloscLZ4HCComprTestCase(BasicTestCase): compress = 9 shuffle = 1 complib = "blosc:lz4hc" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf('snappy' not in tb.blosc_compressor_list(), 'snappy required') class BloscSnappyComprTestCase(BasicTestCase): compress = 9 shuffle = 1 complib = "blosc:snappy" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'zlib' not in tb.blosc_compressor_list(), 'zlib required') class BloscZlibComprTestCase(BasicTestCase): compress = 9 shuffle = 1 complib = "blosc:zlib" @common.unittest.skipIf(not common.blosc_avail, 'BLOSC compression library not available') @common.unittest.skipIf( 'zstd' not in tb.blosc_compressor_list(), 'zstd required') class BloscZstdComprTestCase(BasicTestCase): compress = 9 shuffle = 1 complib = "blosc:zstd" @common.unittest.skipIf( not common.lzo_avail, 'LZO compression library not available') class LZOComprTestCase(BasicTestCase): compress = 1 complib = "lzo" @common.unittest.skipIf(not common.bzip2_avail, 'BZIP2 compression library not available') class Bzip2ComprTestCase(BasicTestCase): compress = 1 complib = "bzip2" class ShuffleComprTestCase(BasicTestCase): compress = 1 shuffle = 1 class TypesTestCase(common.TempFileMixin, common.PyTablesTestCase): open_mode = "w" compress = 0 complib = "zlib" # Default compression library def test01_StringAtom(self): """Checking vlarray with NumPy string atoms ('numpy' flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_StringAtom..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray('/', 'stringAtom', atom=tb.StringAtom(itemsize=3), title="Ragged array of strings") vlarray.flavor = "numpy" vlarray.append(np.array(["1", "12", "123", "1234", "12345"])) vlarray.append(np.array(["1", "12345"])) if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) np.testing.assert_array_equal( row[0], np.array(["1", "12", "123", "123", "123"], 'S')) np.testing.assert_array_equal(row[1], np.array(["1", "123"], 'S')) self.assertEqual(len(row[0]), 5) self.assertEqual(len(row[1]), 2) def test01a_StringAtom(self): """Checking vlarray with NumPy string atoms ('numpy' flavor, strided)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a_StringAtom..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray('/', 'stringAtom', atom=tb.StringAtom(itemsize=3), title="Ragged array of strings") vlarray.flavor = "numpy" vlarray.append(np.array(["1", "12", "123", "1234", "12345"][::2])) vlarray.append(np.array(["1", "12345", "2", "321"])[::3]) if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) np.testing.assert_array_equal(row[0], np.array(["1", "123", "123"], 'S')) np.testing.assert_array_equal(row[1], np.array(["1", "321"], 'S')) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test01a_2_StringAtom(self): """Checking vlarray with NumPy string atoms (NumPy flavor, no conv)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a_2_StringAtom..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray('/', 'stringAtom', atom=tb.StringAtom(itemsize=3), title="Ragged array of strings") vlarray.flavor = "numpy" vlarray.append(np.array(["1", "12", "123", "123"])) vlarray.append(np.array(["1", "2", "321"])) if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) np.testing.assert_array_equal( row[0], np.array(["1", "12", "123", "123"], 'S')) np.testing.assert_array_equal(row[1], np.array(["1", "2", "321"], 'S')) self.assertEqual(len(row[0]), 4) self.assertEqual(len(row[1]), 3) def test01b_StringAtom(self): """Checking vlarray with NumPy string atoms (python flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b_StringAtom..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray('/', 'stringAtom2', atom=tb.StringAtom(itemsize=3), title="Ragged array of strings") vlarray.flavor = "python" vlarray.append(["1", "12", "123", "1234", "12345"]) vlarray.append(["1", "12345"]) if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing String flavor") print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertEqual(row[0], [b"1", b"12", b"123", b"123", b"123"]) self.assertEqual(row[1], [b"1", b"123"]) self.assertEqual(len(row[0]), 5) self.assertEqual(len(row[1]), 2) def test01c_StringAtom(self): """Checking updating vlarray with NumPy string atoms ('numpy' flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01c_StringAtom..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray('/', 'stringAtom', atom=tb.StringAtom(itemsize=3), title="Ragged array of strings") vlarray.flavor = "numpy" vlarray.append(np.array(["1", "12", "123", "1234", "12345"])) vlarray.append(np.array(["1", "12345"])) # Modify the rows vlarray[0] = np.array(["1", "123", "12", "", "12345"]) vlarray[1] = np.array(["44", "4"]) # This should work as well if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([b"1", b"123", b"12", b"", b"123"]))) self.assertTrue(common.allequal( row[1], np.array(["44", "4"], dtype="S3"))) self.assertEqual(len(row[0]), 5) self.assertEqual(len(row[1]), 2) def test01d_StringAtom(self): """Checking updating vlarray with string atoms (String flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01d_StringAtom..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray('/', 'stringAtom2', atom=tb.StringAtom(itemsize=3), title="Ragged array of strings") vlarray.flavor = "python" vlarray.append(["1", "12", "123", "1234", "12345"]) vlarray.append(["1", "12345"]) # Modify the rows vlarray[0] = ["1", "123", "12", "", "12345"] vlarray[1] = ["44", "4"] if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing String flavor") print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertEqual(row[0], [b"1", b"123", b"12", b"", b"123"]) self.assertEqual(row[1], [b"44", b"4"]) self.assertEqual(len(row[0]), 5) self.assertEqual(len(row[1]), 2) def test02_BoolAtom(self): """Checking vlarray with boolean atoms.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_BoolAtom..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray('/', 'BoolAtom', atom=tb.BoolAtom(), title="Ragged array of Booleans") vlarray.append([1, 0, 3]) vlarray.append([-1, 0]) if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([1, 0, 1], dtype='bool'))) self.assertTrue(common.allequal( row[1], np.array([1, 0], dtype='bool'))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test02b_BoolAtom(self): """Checking setting vlarray with boolean atoms.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02b_BoolAtom..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray('/', 'BoolAtom', atom=tb.BoolAtom(), title="Ragged array of Booleans") vlarray.append([1, 0, 3]) vlarray.append([-1, 0]) # Modify the rows vlarray[0] = (0, 1, 3) vlarray[1] = (0, -1) if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([0, 1, 1], dtype='bool'))) self.assertTrue(common.allequal( row[1], np.array([0, 1], dtype='bool'))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test03_IntAtom(self): """Checking vlarray with integer atoms.""" ttypes = [ "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", # "UInt64", # Unavailable in some platforms ] if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_IntAtom..." % self.__class__.__name__) for atype in ttypes: vlarray = self.h5file.create_vlarray( '/', atype, atom=tb.Atom.from_sctype(atype)) vlarray.append([1, 2, 3]) vlarray.append([-1, 0]) if self.reopen: name = vlarray._v_pathname self._reopen(mode='a') vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertTrue( common.allequal(row[0], np.array([1, 2, 3], dtype=atype))) self.assertTrue( common.allequal(row[1], np.array([-1, 0], dtype=atype))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test03a_IntAtom(self): """Checking vlarray with integer atoms (byteorder swapped)""" ttypes = { "int8": np.int8, "uint8": np.uint8, "int16": np.int16, "uint16": np.uint16, "int32": np.int32, "uint32": np.uint32, "int64": np.int64, # "UInt64": numpy.int64, # Unavailable in some platforms } if common.verbose: print('\n', '-=' * 30) print("Running %s.test03a_IntAtom..." % self.__class__.__name__) for atype in ttypes: vlarray = self.h5file.create_vlarray( '/', atype, atom=tb.Atom.from_sctype(ttypes[atype])) a0 = np.array([1, 2, 3], dtype=atype) a0 = a0.byteswap() a0 = a0.newbyteorder() vlarray.append(a0) a1 = np.array([-1, 0], dtype=atype) a1 = a1.byteswap() a1 = a1.newbyteorder() vlarray.append(a1) if self.reopen: name = vlarray._v_pathname self._reopen(mode='a') vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([1, 2, 3], dtype=ttypes[atype]))) self.assertTrue(common.allequal( row[1], np.array([-1, 0], dtype=ttypes[atype]))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test03b_IntAtom(self): """Checking updating vlarray with integer atoms.""" ttypes = [ "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", # "UInt64", # Unavailable in some platforms ] if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_IntAtom..." % self.__class__.__name__) for atype in ttypes: vlarray = self.h5file.create_vlarray( '/', atype, atom=tb.Atom.from_sctype(atype)) vlarray.append([1, 2, 3]) vlarray.append([-1, 0]) # Modify rows vlarray[0] = (3, 2, 1) vlarray[1] = (0, -1) if self.reopen: name = vlarray._v_pathname self._reopen(mode='a') vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertTrue( common.allequal(row[0], np.array([3, 2, 1], dtype=atype))) self.assertTrue( common.allequal(row[1], np.array([0, -1], dtype=atype))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test03c_IntAtom(self): """Checking updating vlarray with integer atoms (byteorder swapped)""" ttypes = { "int8": np.int8, "uint8": np.uint8, "int16": np.int16, "uint16": np.uint16, "int32": np.int32, "uint32": np.uint32, "int64": np.int64, # "UInt64": numpy.int64, # Unavailable in some platforms } if common.verbose: print('\n', '-=' * 30) print("Running %s.test03c_IntAtom..." % self.__class__.__name__) for atype in ttypes: vlarray = self.h5file.create_vlarray( '/', atype, atom=tb.Atom.from_sctype(ttypes[atype])) a0 = np.array([1, 2, 3], dtype=atype) vlarray.append(a0) a1 = np.array([-1, 0], dtype=atype) vlarray.append(a1) # Modify rows a0 = np.array([3, 2, 1], dtype=atype) a0 = a0.byteswap() a0 = a0.newbyteorder() vlarray[0] = a0 a1 = np.array([0, -1], dtype=atype) a1 = a1.byteswap() a1 = a1.newbyteorder() vlarray[1] = a1 if self.reopen: name = vlarray._v_pathname self._reopen(mode='a') vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([3, 2, 1], dtype=ttypes[atype]))) self.assertTrue(common.allequal( row[1], np.array([0, -1], dtype=ttypes[atype]))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test03d_IntAtom(self): """Checking updating vlarray with integer atoms (another byteorder)""" ttypes = { "int8": np.int8, "uint8": np.uint8, "int16": np.int16, "uint16": np.uint16, "int32": np.int32, "uint32": np.uint32, "int64": np.int64, # "UInt64": numpy.int64, # Unavailable in some platforms } if common.verbose: print('\n', '-=' * 30) print("Running %s.test03d_IntAtom..." % self.__class__.__name__) byteorder = {'little': 'big', 'big': 'little'}[sys.byteorder] for atype in ttypes: vlarray = self.h5file.create_vlarray( '/', atype, atom=tb.Atom.from_sctype(ttypes[atype]), byteorder=byteorder) a0 = np.array([1, 2, 3], dtype=atype) vlarray.append(a0) a1 = np.array([-1, 0], dtype=atype) vlarray.append(a1) # Modify rows a0 = np.array([3, 2, 1], dtype=atype) a0 = a0.byteswap() a0 = a0.newbyteorder() vlarray[0] = a0 a1 = np.array([0, -1], dtype=atype) a1 = a1.byteswap() a1 = a1.newbyteorder() vlarray[1] = a1 if self.reopen: name = vlarray._v_pathname self._reopen(mode='a') vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) byteorder2 = tb.utils.byteorders[row[0].dtype.byteorder] if byteorder2 != "irrelevant": self.assertEqual(tb.utils.byteorders[row[0].dtype.byteorder], sys.byteorder) self.assertEqual(vlarray.byteorder, byteorder) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([3, 2, 1], dtype=ttypes[atype]))) self.assertTrue(common.allequal( row[1], np.array([0, -1], dtype=ttypes[atype]))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test04_FloatAtom(self): """Checking vlarray with floating point atoms.""" ttypes = [ "float32", "float64", ] for name in ("float16", "float96", "float128"): atomname = name.capitalize() + 'Atom' if hasattr(tb, atomname): ttypes.append(name) if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_FloatAtom..." % self.__class__.__name__) for atype in ttypes: vlarray = self.h5file.create_vlarray( '/', atype, atom=tb.Atom.from_sctype(atype)) vlarray.append([1.3, 2.2, 3.3]) vlarray.append([-1.3e34, 1.e-32]) if self.reopen: name = vlarray._v_pathname self._reopen(mode='a') vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([1.3, 2.2, 3.3], atype))) self.assertTrue(common.allequal( row[1], np.array([-1.3e34, 1.e-32], atype))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test04a_FloatAtom(self): """Checking vlarray with float atoms (byteorder swapped)""" ttypes = { "float32": np.float32, "float64": np.float64, } if hasattr(tb, "Float16Atom"): ttypes["float16"] = np.float16 if hasattr(tb, "Float96Atom"): ttypes["float96"] = np.float96 if hasattr(tb, "Float128Atom"): ttypes["float128"] = np.float128 if common.verbose: print('\n', '-=' * 30) print("Running %s.test04a_FloatAtom..." % self.__class__.__name__) for atype in ttypes: vlarray = self.h5file.create_vlarray( '/', atype, atom=tb.Atom.from_sctype(ttypes[atype])) a0 = np.array([1.3, 2.2, 3.3], dtype=atype) a0 = a0.byteswap() a0 = a0.newbyteorder() vlarray.append(a0) a1 = np.array([-1.3e34, 1.e-32], dtype=atype) a1 = a1.byteswap() a1 = a1.newbyteorder() vlarray.append(a1) if self.reopen: name = vlarray._v_pathname self._reopen(mode='a') vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([1.3, 2.2, 3.3], dtype=ttypes[atype]))) self.assertTrue(common.allequal( row[1], np.array([-1.3e34, 1.e-32], dtype=ttypes[atype]))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test04b_FloatAtom(self): """Checking updating vlarray with floating point atoms.""" ttypes = [ "float32", "float64", ] for name in ("float16", "float96", "float128"): atomname = name.capitalize() + 'Atom' if hasattr(tb, atomname): ttypes.append(name) if common.verbose: print('\n', '-=' * 30) print("Running %s.test04b_FloatAtom..." % self.__class__.__name__) for atype in ttypes: vlarray = self.h5file.create_vlarray( '/', atype, atom=tb.Atom.from_sctype(atype)) vlarray.append([1.3, 2.2, 3.3]) vlarray.append([-1.3e34, 1.e-32]) # Modifiy some rows vlarray[0] = (4.3, 2.2, 4.3) vlarray[1] = (-1.1e34, 1.3e-32) if self.reopen: name = vlarray._v_pathname self._reopen(mode='a') vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([4.3, 2.2, 4.3], atype))) self.assertTrue( common.allequal(row[1], np.array([-1.1e34, 1.3e-32], atype))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test04c_FloatAtom(self): """Checking updating vlarray with float atoms (byteorder swapped)""" ttypes = { "float32": np.float32, "float64": np.float64, } if hasattr(tb, "Float16Atom"): ttypes["float16"] = np.float16 if hasattr(tb, "Float96Atom"): ttypes["float96"] = np.float96 if hasattr(tb, "Float128Atom"): ttypes["float128"] = np.float128 if common.verbose: print('\n', '-=' * 30) print("Running %s.test04c_FloatAtom..." % self.__class__.__name__) for atype in ttypes: vlarray = self.h5file.create_vlarray( '/', atype, atom=tb.Atom.from_sctype(ttypes[atype])) a0 = np.array([1.3, 2.2, 3.3], dtype=atype) vlarray.append(a0) a1 = np.array([-1, 0], dtype=atype) vlarray.append(a1) # Modify rows a0 = np.array([4.3, 2.2, 4.3], dtype=atype) a0 = a0.byteswap() a0 = a0.newbyteorder() vlarray[0] = a0 a1 = np.array([-1.1e34, 1.3e-32], dtype=atype) a1 = a1.byteswap() a1 = a1.newbyteorder() vlarray[1] = a1 if self.reopen: name = vlarray._v_pathname self._reopen(mode='a') vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([4.3, 2.2, 4.3], dtype=ttypes[atype]))) self.assertTrue(common.allequal( row[1], np.array([-1.1e34, 1.3e-32], dtype=ttypes[atype]))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test04d_FloatAtom(self): """Checking updating vlarray with float atoms (another byteorder)""" ttypes = { "float32": np.float32, "float64": np.float64, } if hasattr(tb, "Float16Atom"): ttypes["float16"] = np.float16 if hasattr(tb, "Float96Atom"): ttypes["float96"] = np.float96 if hasattr(tb, "Float128Atom"): ttypes["float128"] = np.float128 if common.verbose: print('\n', '-=' * 30) print("Running %s.test04d_FloatAtom..." % self.__class__.__name__) byteorder = {'little': 'big', 'big': 'little'}[sys.byteorder] for atype in ttypes: vlarray = self.h5file.create_vlarray( '/', atype, atom=tb.Atom.from_sctype(ttypes[atype]), byteorder=byteorder) a0 = np.array([1.3, 2.2, 3.3], dtype=atype) vlarray.append(a0) a1 = np.array([-1, 0], dtype=atype) vlarray.append(a1) # Modify rows a0 = np.array([4.3, 2.2, 4.3], dtype=atype) a0 = a0.byteswap() a0 = a0.newbyteorder() vlarray[0] = a0 a1 = np.array([-1.1e34, 1.3e-32], dtype=atype) a1 = a1.byteswap() a1 = a1.newbyteorder() vlarray[1] = a1 if self.reopen: name = vlarray._v_pathname self._reopen(mode='a') vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.byteorder, byteorder) self.assertEqual(tb.utils.byteorders[row[0].dtype.byteorder], sys.byteorder) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([4.3, 2.2, 4.3], dtype=ttypes[atype]))) self.assertTrue(common.allequal( row[1], np.array([-1.1e34, 1.3e-32], dtype=ttypes[atype]))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test04_ComplexAtom(self): """Checking vlarray with numerical complex atoms.""" ttypes = [ "complex64", "complex128", ] if hasattr(tb, "Complex192Atom"): ttypes.append("complex192") if hasattr(tb, "Complex256Atom"): ttypes.append("complex256") if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_ComplexAtom..." % self.__class__.__name__) for atype in ttypes: vlarray = self.h5file.create_vlarray( '/', atype, atom=tb.Atom.from_sctype(atype)) vlarray.append([(1.3 + 0j), (0+2.2j), (3.3+3.3j)]) vlarray.append([(0-1.3e34j), (1.e-32 + 0j)]) if self.reopen: name = vlarray._v_pathname self._reopen(mode='a') vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([(1.3 + 0j), (0+2.2j), (3.3+3.3j)], atype))) self.assertTrue(common.allequal( row[1], np.array([(0-1.3e34j), (1.e-32 + 0j)], atype))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test04b_ComplexAtom(self): """Checking modifying vlarray with numerical complex atoms.""" ttypes = [ "complex64", "complex128", ] if hasattr(tb, "Complex192Atom"): ttypes.append("complex192") if hasattr(tb, "Complex256Atom"): ttypes.append("complex256") if common.verbose: print('\n', '-=' * 30) print("Running %s.test04b_ComplexAtom..." % self.__class__.__name__) for atype in ttypes: vlarray = self.h5file.create_vlarray( '/', atype, atom=tb.Atom.from_sctype(atype)) vlarray.append([(1.3 + 0j), (0+2.2j), (3.3+3.3j)]) vlarray.append([(0-1.3e34j), (1.e-32 + 0j)]) # Modify the rows vlarray[0] = ((1.4 + 0j), (0+4.2j), (3.3+4.3j)) vlarray[1] = ((4-1.3e34j), (1.e-32 + 4j)) if self.reopen: name = vlarray._v_pathname self._reopen(mode='a') vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([(1.4 + 0j), (0+4.2j), (3.3+4.3j)], atype))) self.assertTrue(common.allequal( row[1], np.array([(4-1.3e34j), (1.e-32 + 4j)], atype))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 2) def test05_VLStringAtom(self): """Checking vlarray with variable length strings.""" # Skip the test if the default encoding has been mangled. if sys.getdefaultencoding() != 'ascii': return if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_VLStringAtom..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray( '/', "VLStringAtom", atom=tb.VLStringAtom()) vlarray.append(b"asd") vlarray.append(b"asd\xe4") vlarray.append(b"aaana") vlarray.append(b"") # Check for ticket #62. self.assertRaises(TypeError, vlarray.append, [b"foo", b"bar"]) # `VLStringAtom` makes no encoding assumptions. See ticket #51. self.assertRaises(UnicodeEncodeError, vlarray.append, "asd\xe4") if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 4) self.assertEqual(row[0], b"asd") self.assertEqual(row[1], b"asd\xe4") self.assertEqual(row[2], b"aaana") self.assertEqual(row[3], b"") self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 4) self.assertEqual(len(row[2]), 5) self.assertEqual(len(row[3]), 0) def test05b_VLStringAtom(self): """Checking updating vlarray with variable length strings.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05b_VLStringAtom..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray( '/', "VLStringAtom", atom=tb.VLStringAtom()) vlarray.append(b"asd") vlarray.append(b"aaana") # Modify values vlarray[0] = b"as4" vlarray[1] = b"aaanc" self.assertRaises(ValueError, vlarray.__setitem__, 1, b"shrt") self.assertRaises(ValueError, vlarray.__setitem__, 1, b"toolong") if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", repr(row[0])) print("Second row in vlarray ==>", repr(row[1])) self.assertEqual(vlarray.nrows, 2) self.assertEqual(row[0], b"as4") self.assertEqual(row[1], b"aaanc") self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 5) def test06a_Object(self): """Checking vlarray with object atoms.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06a_Object..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray( '/', "Object", atom=tb.ObjectAtom()) vlarray.append( [[1, 2, 3], "aaa", "aaa\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd"]) vlarray.append([3, 4, C()]) vlarray.append(42) if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 3) self.assertEqual( row[0], [[1, 2, 3], "aaa", "aaa\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd"]) list1 = list(row[1]) obj = list1.pop() self.assertEqual(list1, [3, 4]) self.assertEqual(obj.c, C().c) self.assertEqual(row[2], 42) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 3) self.assertRaises(TypeError, len, row[2]) def test06b_Object(self): """Checking updating vlarray with object atoms.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06b_Object..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray('/', "Object", atom=tb.ObjectAtom()) # When updating an object, this seems to change the number # of bytes that pickle.dumps generates # vlarray.append( # ([1,2,3], "aaa", "aaa\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd")) vlarray.append(([1, 2, 3], "aaa", "\xef\xbf\xbd\xef\xbf\xbd4")) # vlarray.append([3,4, C()]) vlarray.append([3, 4, [24]]) # Modify the rows # vlarray[0] = ([1,2,4], "aa4", "aaa\xef\xbf\xbd\xef\xbf\xbd4") vlarray[0] = ([1, 2, 4], "aa4", "\xef\xbf\xbd\xef\xbf\xbd5") # vlarray[1] = (3,4, C()) vlarray[1] = [4, 4, [24]] if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 2) self.assertEqual(row[0], ([1, 2, 4], "aa4", "\xef\xbf\xbd\xef\xbf\xbd5")) list1 = list(row[1]) obj = list1.pop() self.assertEqual(list1, [4, 4]) # self.assertEqual(obj.c, C().c) self.assertEqual(obj, [24]) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 3) def test06c_Object(self): """Checking vlarray with object atoms (numpy arrays as values)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06c_Object..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray('/', "Object", atom=tb.ObjectAtom()) vlarray.append(np.array([[1, 2], [0, 4]], 'i4')) vlarray.append(np.array([0, 1, 2, 3], 'i8')) vlarray.append(np.array(42, 'i1')) if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 3) self.assertTrue(common.allequal( row[0], np.array([[1, 2], [0, 4]], 'i4'))) self.assertTrue(common.allequal(row[1], np.array([0, 1, 2, 3], 'i8'))) self.assertTrue(common.allequal(row[2], np.array(42, 'i1'))) def test06d_Object(self): """Checking updating vlarray with object atoms (numpy arrays)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test06d_Object..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray('/', "Object", atom=tb.ObjectAtom()) vlarray.append(np.array([[1, 2], [0, 4]], 'i4')) vlarray.append(np.array([0, 1, 2, 3], 'i8')) vlarray.append(np.array(42, 'i1')) # Modify the rows. Since PyTables 2.2.1 we use a binary # pickle for arrays and ObjectAtoms, so the next should take # the same space than the above. vlarray[0] = np.array([[1, 0], [0, 4]], 'i4') vlarray[1] = np.array([0, 1, 0, 3], 'i8') vlarray[2] = np.array(22, 'i1') if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 3) self.assertTrue(common.allequal( row[0], np.array([[1, 0], [0, 4]], 'i4'))) self.assertTrue(common.allequal( row[1], np.array([0, 1, 0, 3], 'i8'))) self.assertTrue(common.allequal(row[2], np.array(22, 'i1'))) def test07_VLUnicodeAtom(self): """Checking vlarray with variable length Unicode strings.""" # Skip the test if the default encoding has been mangled. if sys.getdefaultencoding() != 'ascii': return if common.verbose: print('\n', '-=' * 30) print("Running %s.test07_VLUnicodeAtom..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray( '/', "VLUnicodeAtom", atom=tb.VLUnicodeAtom()) vlarray.append("asd") vlarray.append("asd\u0140") vlarray.append("aaana") vlarray.append("") # Check for ticket #62. self.assertRaises(TypeError, vlarray.append, ["foo", "bar"]) # `VLUnicodeAtom` makes no encoding assumptions. self.assertRaises(UnicodeDecodeError, vlarray.append, "asd\xe4") if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 4) self.assertEqual(row[0], "asd") self.assertEqual(row[1], "asd\u0140") self.assertEqual(row[2], "aaana") self.assertEqual(row[3], "") self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 4) self.assertEqual(len(row[2]), 5) self.assertEqual(len(row[3]), 0) def test07b_VLUnicodeAtom(self): """Checking updating vlarray with variable length Unicode strings.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test07b_VLUnicodeAtom..." % self.__class__.__name__) vlarray = self.h5file.create_vlarray( '/', "VLUnicodeAtom", atom=tb.VLUnicodeAtom()) vlarray.append("asd") vlarray.append("aaan\xe4") # Modify values vlarray[0] = "as\xe4" vlarray[1] = "aaan\u0140" self.assertRaises(ValueError, vlarray.__setitem__, 1, "shrt") self.assertRaises(ValueError, vlarray.__setitem__, 1, "toolong") if self.reopen: name = vlarray._v_pathname self._reopen() vlarray = self.h5file.get_node(name) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", repr(row[0])) print("Second row in vlarray ==>", repr(row[1])) self.assertEqual(vlarray.nrows, 2) self.assertEqual(row[0], "as\xe4") self.assertEqual(row[1], "aaan\u0140") self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 5) class TypesReopenTestCase(TypesTestCase): title = "Reopen" reopen = True class TypesNoReopenTestCase(TypesTestCase): title = "No reopen" reopen = False class MDTypesTestCase(common.TempFileMixin, common.PyTablesTestCase): open_mode = "w" compress = 0 complib = "zlib" # Default compression library def setUp(self): super().setUp() self.rootgroup = self.h5file.root def test01_StringAtom(self): """Checking vlarray with MD NumPy string atoms.""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_StringAtom..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray( root, 'stringAtom', tb.StringAtom(itemsize=3, shape=(2,)), "Ragged array of strings") vlarray.append([["123", "45"], ["45", "123"]]) vlarray.append([["s", "abc"], ["abc", "f"], ["s", "ab"], ["ab", "f"]]) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, 2) np.testing.assert_array_equal( row[0], np.array([["123", "45"], ["45", "123"]], 'S')) np.testing.assert_array_equal( row[1], np.array([["s", "abc"], ["abc", "f"], ["s", "ab"], ["ab", "f"]], 'S')) self.assertEqual(len(row[0]), 2) self.assertEqual(len(row[1]), 4) def test01b_StringAtom(self): """Checking vlarray with MD NumPy string atoms ('python' flavor)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b_StringAtom..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray( root, 'stringAtom', tb.StringAtom(itemsize=3, shape=(2,)), "Ragged array of strings") vlarray.flavor = "python" vlarray.append([["123", "45"], ["45", "123"]]) vlarray.append([["s", "abc"], ["abc", "f"], ["s", "ab"], ["ab", "f"]]) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, 2) self.assertEqual(row[0], [[b"123", b"45"], [b"45", b"123"]]) self.assertEqual(row[1], [[b"s", b"abc"], [b"abc", b"f"], [b"s", b"ab"], [b"ab", b"f"]]) self.assertEqual(len(row[0]), 2) self.assertEqual(len(row[1]), 4) def test01c_StringAtom(self): """Checking vlarray with MD NumPy string atoms (with offset)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test01c_StringAtom..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray( root, 'stringAtom', tb.StringAtom(itemsize=3, shape=(2,)), "Ragged array of strings") vlarray.flavor = "python" a = np.array([["a", "b"], ["123", "45"], ["45", "123"]], dtype="S3") vlarray.append(a[1:]) a = np.array([["s", "a"], ["ab", "f"], ["s", "abc"], ["abc", "f"], ["s", "ab"], ["ab", "f"]]) vlarray.append(a[2:]) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, 2) self.assertEqual(row[0], [[b"123", b"45"], [b"45", b"123"]]) self.assertEqual(row[1], [[b"s", b"abc"], [b"abc", b"f"], [b"s", b"ab"], [b"ab", b"f"]]) self.assertEqual(len(row[0]), 2) self.assertEqual(len(row[1]), 4) def test01d_StringAtom(self): """Checking vlarray with MD NumPy string atoms (with stride)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test01d_StringAtom..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray( root, 'stringAtom', tb.StringAtom(itemsize=3, shape=(2,)), "Ragged array of strings") vlarray.flavor = "python" a = np.array([["a", "b"], ["123", "45"], ["45", "123"]], dtype="S3") vlarray.append(a[1::2]) a = np.array([["s", "a"], ["ab", "f"], ["s", "abc"], ["abc", "f"], ["s", "ab"], ["ab", "f"]]) vlarray.append(a[::3]) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, 2) self.assertEqual(row[0], [[b"123", b"45"]]) self.assertEqual(row[1], [[b"s", b"a"], [b"abc", b"f"]]) self.assertEqual(len(row[0]), 1) self.assertEqual(len(row[1]), 2) def test02_BoolAtom(self): """Checking vlarray with MD boolean atoms.""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_BoolAtom..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray(root, 'BoolAtom', tb.BoolAtom(shape=(3,)), "Ragged array of Booleans") vlarray.append([(1, 0, 3), (1, 1, 1), (0, 0, 0)]) vlarray.append([(-1, 0, 0)]) # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([[1, 0, 1], [1, 1, 1], [0, 0, 0]], dtype='bool'))) self.assertTrue(common.allequal( row[1], np.array([[1, 0, 0]], dtype='bool'))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 1) def test02b_BoolAtom(self): """Checking vlarray with MD boolean atoms (with offset)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test02b_BoolAtom..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray(root, 'BoolAtom', tb.BoolAtom(shape=(3,)), "Ragged array of Booleans") a = np.array( [(0, 0, 0), (1, 0, 3), (1, 1, 1), (0, 0, 0)], dtype='bool') vlarray.append(a[1:]) # Create an offset a = np.array([(1, 1, 1), (-1, 0, 0)], dtype='bool') vlarray.append(a[1:]) # Create an offset # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([[1, 0, 1], [1, 1, 1], [0, 0, 0]], dtype='bool'))) self.assertTrue(common.allequal( row[1], np.array([[1, 0, 0]], dtype='bool'))) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 1) def test02c_BoolAtom(self): """Checking vlarray with MD boolean atoms (with strides)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test02c_BoolAtom..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray(root, 'BoolAtom', tb.BoolAtom(shape=(3,)), "Ragged array of Booleans") a = np.array( [(0, 0, 0), (1, 0, 3), (1, 1, 1), (0, 0, 0)], dtype='bool') vlarray.append(a[1::2]) # Create an strided array a = np.array([(1, 1, 1), (-1, 0, 0), (0, 0, 0)], dtype='bool') vlarray.append(a[::2]) # Create an strided array # Read all the rows: row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([[1, 0, 1], [0, 0, 0]], dtype='bool'))) self.assertTrue(common.allequal( row[1], np.array([[1, 1, 1], [0, 0, 0]], dtype='bool'))) self.assertEqual(len(row[0]), 2) self.assertEqual(len(row[1]), 2) def test03_IntAtom(self): """Checking vlarray with MD integer atoms.""" ttypes = [ "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", # "UInt64", # Unavailable in some platforms ] root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_IntAtom..." % self.__class__.__name__) # Create an string atom for atype in ttypes: vlarray = self.h5file.create_vlarray( root, atype, atom=tb.Atom.from_sctype(atype, (2, 3))) vlarray.append([np.ones((2, 3), atype), np.zeros((2, 3), atype)]) vlarray.append([np.ones((2, 3), atype)*100]) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", repr(row[1])) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array([np.ones((2, 3)), np.zeros((2, 3))], atype))) self.assertTrue(common.allequal( row[1], np.array([np.ones((2, 3)) * 100], atype))) self.assertEqual(len(row[0]), 2) self.assertEqual(len(row[1]), 1) def test04_FloatAtom(self): """Checking vlarray with MD floating point atoms.""" ttypes = [ "float32", "float64", "complex64", "complex128", ] for name in ("float16", "float96", "float128"): atomname = name.capitalize() + "Atom" if hasattr(tb, atomname): ttypes.append(name) for itemsize in (192, 256): atomname = "Complex%dAtom" % itemsize if hasattr(tb, atomname): ttypes.append("complex%d" % (itemsize)) root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_FloatAtom..." % self.__class__.__name__) # Create an string atom for atype in ttypes: vlarray = self.h5file.create_vlarray( root, atype, atom=tb.Atom.from_sctype(atype, (5, 2, 6))) vlarray.append([np.ones((5, 2, 6), atype)*1.3, np.zeros((5, 2, 6), atype)]) vlarray.append([np.ones((5, 2, 6), atype)*2.e4]) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing type:", atype) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, 2) self.assertTrue(common.allequal( row[0], np.array( [np.ones((5, 2, 6)) * 1.3, np.zeros((5, 2, 6))], atype))) self.assertTrue(common.allequal( row[1], np.array([np.ones((5, 2, 6)) * 2.e4], atype))) self.assertEqual(len(row[0]), 2) self.assertEqual(len(row[1]), 1) class MDTypesNumPyTestCase(MDTypesTestCase): title = "MDTypes" class AppendShapeTestCase(common.TempFileMixin, common.PyTablesTestCase): open_mode = "w" def setUp(self): super().setUp() self.rootgroup = self.h5file.root def test00_difinputs(self): """Checking vlarray.append() with different inputs.""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test00_difinputs..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray(root, 'vlarray', tb.Int32Atom(), "Ragged array of ints") vlarray.flavor = "python" # Check different ways to input # All of the next should lead to the same rows vlarray.append((1, 2, 3)) # a tuple vlarray.append([1, 2, 3]) # a unique list vlarray.append(np.array([1, 2, 3], dtype='int32')) # and array if self.close: if common.verbose: print("(closing file version)") self._reopen() vlarray = self.h5file.root.vlarray # Read all the vlarray row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 3) self.assertEqual(row[0], [1, 2, 3]) self.assertEqual(row[1], [1, 2, 3]) self.assertEqual(row[2], [1, 2, 3]) def test01_toomanydims(self): """Checking vlarray.append() with too many dimensions.""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_toomanydims..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray(root, 'vlarray', tb.StringAtom(itemsize=3), "Ragged array of strings") # Adding an array with one dimensionality more than allowed with self.assertRaises(ValueError): vlarray.append([["123", "456", "3"]]) if self.close: if common.verbose: print("(closing file version)") self._reopen() vlarray = self.h5file.root.vlarray # Read all the rows (there should be none) row = vlarray.read() if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) self.assertEqual(vlarray.nrows, 0) def test02_zerodims(self): """Checking vlarray.append() with a zero-dimensional array""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_zerodims..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray(root, 'vlarray', tb.Int32Atom(), "Ragged array of ints") vlarray.append(np.zeros(dtype='int32', shape=(6, 0))) if self.close: if common.verbose: print("(closing file version)") self._reopen() vlarray = self.h5file.root.vlarray # Read the only row in vlarray row = vlarray.read(0)[0] if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", repr(row)) self.assertEqual(vlarray.nrows, 1) self.assertTrue(common.allequal( row, np.zeros(dtype='int32', shape=(0,)))) self.assertEqual(len(row), 0) def test03a_cast(self): """Checking vlarray.append() with a casted array (upgrading case)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test03a_cast..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray(root, 'vlarray', tb.Int32Atom(), "Ragged array of ints") # This type has to be upgraded vlarray.append(np.array([1, 2], dtype='int16')) if self.close: if common.verbose: print("(closing file version)") self._reopen() vlarray = self.h5file.root.vlarray # Read the only row in vlarray row = vlarray.read(0)[0] if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", repr(row)) self.assertEqual(vlarray.nrows, 1) self.assertTrue(common.allequal(row, np.array([1, 2], dtype='int32'))) self.assertEqual(len(row), 2) def test03b_cast(self): """Checking vlarray.append() with a casted array (downgrading case)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test03b_cast..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray(root, 'vlarray', tb.Int32Atom(), "Ragged array of ints") # This type has to be downcasted vlarray.append(np.array([1, 2], dtype='float64')) if self.close: if common.verbose: print("(closing file version)") self._reopen() vlarray = self.h5file.root.vlarray # Read the only row in vlarray row = vlarray.read(0)[0] if common.verbose: print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", repr(row)) self.assertEqual(vlarray.nrows, 1) self.assertTrue(common.allequal(row, np.array([1, 2], dtype='int32'))) self.assertEqual(len(row), 2) class OpenAppendShapeTestCase(AppendShapeTestCase): close = 0 class CloseAppendShapeTestCase(AppendShapeTestCase): close = 1 class FlavorTestCase(common.TempFileMixin, common.PyTablesTestCase): open_mode = "w" compress = 0 complib = "zlib" # Default compression library def setUp(self): super().setUp() self.rootgroup = self.h5file.root def test01a_EmptyVLArray(self): """Checking empty vlarrays with different flavors (closing the file)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_EmptyVLArray..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray( root, "vlarray", tb.Atom.from_kind('int', itemsize=4)) vlarray.flavor = self.flavor self.h5file.close() self.h5file = tb.open_file(self.h5fname, "r") # Read all the rows (it should be empty): vlarray = self.h5file.root.vlarray row = vlarray.read() if common.verbose: print("Testing flavor:", self.flavor) print("Object read:", row, repr(row)) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) # Check that the object read is effectively empty self.assertEqual(vlarray.nrows, 0) self.assertEqual(row, []) def test01b_EmptyVLArray(self): """Checking empty vlarrays with different flavors (no closing file)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_EmptyVLArray..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray( root, "vlarray", tb.Atom.from_kind('int', itemsize=4)) vlarray.flavor = self.flavor # Read all the rows (it should be empty): row = vlarray.read() if common.verbose: print("Testing flavor:", self.flavor) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) # Check that the object read is effectively empty self.assertEqual(vlarray.nrows, 0) self.assertEqual(row, []) def test02_BooleanAtom(self): """Checking vlarray with different flavors (boolean versions)""" root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_BoolAtom..." % self.__class__.__name__) # Create an string atom vlarray = self.h5file.create_vlarray(root, "Bool", tb.BoolAtom()) vlarray.flavor = self.flavor vlarray.append([1, 2, 3]) vlarray.append(()) # Empty row vlarray.append([100, 0]) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing flavor:", self.flavor) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 3) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 0) self.assertEqual(len(row[2]), 2) if self.flavor == "python": arr1 = [1, 1, 1] arr2 = [] arr3 = [1, 0] elif self.flavor == "numpy": arr1 = np.array([1, 1, 1], dtype="bool") arr2 = np.array([], dtype="bool") arr3 = np.array([1, 0], dtype="bool") if self.flavor == "numpy": self.assertTrue(common.allequal(row[0], arr1, self.flavor)) self.assertTrue(common.allequal(row[1], arr2, self.flavor)) self.assertTrue(common.allequal(row[1], arr2, self.flavor)) else: # 'python' flavor self.assertEqual(row[0], arr1) self.assertEqual(row[1], arr2) self.assertEqual(row[2], arr3) def test03_IntAtom(self): """Checking vlarray with different flavors (integer versions)""" ttypes = [ "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", # Not checked because some platforms does not support it # "UInt64", ] root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_IntAtom..." % self.__class__.__name__) # Create an string atom for atype in ttypes: vlarray = self.h5file.create_vlarray(root, atype, tb.Atom.from_sctype(atype)) vlarray.flavor = self.flavor vlarray.append([1, 2, 3]) vlarray.append(()) vlarray.append([100, 0]) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing flavor:", self.flavor) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 3) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 0) self.assertEqual(len(row[2]), 2) if self.flavor == "python": arr1 = [1, 2, 3] arr2 = [] arr3 = [100, 0] elif self.flavor == "numpy": arr1 = np.array([1, 2, 3], dtype=atype) arr2 = np.array([], dtype=atype) arr3 = np.array([100, 0], dtype=atype) if self.flavor == "numpy": self.assertTrue(common.allequal(row[0], arr1, self.flavor)) self.assertTrue(common.allequal(row[1], arr2, self.flavor)) self.assertTrue(common.allequal(row[2], arr3, self.flavor)) else: # "python" flavor self.assertEqual(row[0], arr1) self.assertEqual(row[1], arr2) self.assertEqual(row[2], arr3) def test03b_IntAtom(self): """Checking vlarray flavors (integer versions and closed file)""" ttypes = [ "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", # Not checked because some platforms does not support it # "UInt64", ] root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_IntAtom..." % self.__class__.__name__) # Create an string atom for atype in ttypes: vlarray = self.h5file.create_vlarray(root, atype, tb.Atom.from_sctype(atype)) vlarray.flavor = self.flavor vlarray.append([1, 2, 3]) vlarray.append(()) vlarray.append([100, 0]) self._reopen(mode='a') # open in "a"ppend mode root = self.h5file.root # Very important! vlarray = self.h5file.get_node(root, str(atype)) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing flavor:", self.flavor) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 3) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 0) self.assertEqual(len(row[2]), 2) if self.flavor == "python": arr1 = [1, 2, 3] arr2 = [] arr3 = [100, 0] elif self.flavor == "numpy": arr1 = np.array([1, 2, 3], dtype=atype) arr2 = np.array([], dtype=atype) arr3 = np.array([100, 0], dtype=atype) if self.flavor == "numpy": self.assertTrue(common.allequal(row[0], arr1, self.flavor)) self.assertTrue(common.allequal(row[1], arr2, self.flavor)) self.assertTrue(common.allequal(row[2], arr3, self.flavor)) else: # Tuple or List flavors self.assertEqual(row[0], arr1) self.assertEqual(row[1], arr2) self.assertEqual(row[2], arr3) def test04_FloatAtom(self): """Checking vlarray with different flavors (floating point versions)""" ttypes = [ "float32", "float64", "complex64", "complex128", ] for name in ("float16", "float96", "float128"): atomname = name.capitalize() + "Atom" if hasattr(tb, atomname): ttypes.append(name) for itemsize in (192, 256): atomname = "Complex%dAtom" % itemsize if hasattr(tb, atomname): ttypes.append("complex%d" % (itemsize)) root = self.rootgroup if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_FloatAtom..." % self.__class__.__name__) # Create an string atom for atype in ttypes: vlarray = self.h5file.create_vlarray(root, atype, tb.Atom.from_sctype(atype)) vlarray.flavor = self.flavor vlarray.append([1.3, 2.2, 3.3]) vlarray.append(()) vlarray.append([-1.3e34, 1.e-32]) # Read all the rows: row = vlarray.read() if common.verbose: print("Testing flavor:", self.flavor) print("Object read:", row) print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) self.assertEqual(vlarray.nrows, 3) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 0) self.assertEqual(len(row[2]), 2) if self.flavor == "python": arr1 = list(np.array([1.3, 2.2, 3.3], atype)) arr2 = list(np.array([], atype)) arr3 = list(np.array([-1.3e34, 1.e-32], atype)) elif self.flavor == "numpy": arr1 = np.array([1.3, 2.2, 3.3], dtype=atype) arr2 = np.array([], dtype=atype) arr3 = np.array([-1.3e34, 1.e-32], dtype=atype) if self.flavor == "numpy": self.assertTrue(common.allequal(row[0], arr1, self.flavor)) self.assertTrue(common.allequal(row[1], arr2, self.flavor)) self.assertTrue(common.allequal(row[2], arr3, self.flavor)) else: # Tuple or List flavors self.assertEqual(row[0], arr1) self.assertEqual(row[1], arr2) self.assertEqual(row[2], arr3) class NumPyFlavorTestCase(FlavorTestCase): flavor = "numpy" class PythonFlavorTestCase(FlavorTestCase): flavor = "python" class ReadRangeTestCase(common.TempFileMixin, common.PyTablesTestCase): nrows = 100 mode = "w" compress = 0 complib = "zlib" # Default compression library def setUp(self): super().setUp() self.rootgroup = self.h5file.root self.populateFile() self._reopen() def populateFile(self): group = self.rootgroup filters = tb.Filters(complevel=self.compress, complib=self.complib) vlarray = self.h5file.create_vlarray(group, 'vlarray', tb.Int32Atom(), "ragged array if ints", filters=filters, expectedrows=1000) # Fill it with 100 rows with variable length for i in range(self.nrows): vlarray.append(list(range(i))) def test01_start(self): """Checking reads with only a start value""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_start..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Read some rows: row = [] row.append(vlarray.read(0)[0]) row.append(vlarray.read(10)[0]) row.append(vlarray.read(99)[0]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 0) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 99) self.assertTrue(common.allequal(row[0], np.arange(0, dtype='int32'))) self.assertTrue(common.allequal(row[1], np.arange(10, dtype='int32'))) self.assertTrue(common.allequal(row[2], np.arange(99, dtype='int32'))) def test01b_start(self): """Checking reads with only a start value in a slice""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b_start..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Read some rows: row = [] row.append(vlarray[0]) row.append(vlarray[10]) row.append(vlarray[99]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 0) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 99) self.assertTrue(common.allequal(row[0], np.arange(0, dtype='int32'))) self.assertTrue(common.allequal(row[1], np.arange(10, dtype='int32'))) self.assertTrue(common.allequal(row[2], np.arange(99, dtype='int32'))) def test01np_start(self): """Checking reads with only a start value in a slice (numpy indexes)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01np_start..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Read some rows: row = [] row.append(vlarray[np.int8(0)]) row.append(vlarray[np.int32(10)]) row.append(vlarray[np.int64(99)]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 0) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 99) self.assertTrue(common.allequal(row[0], np.arange(0, dtype='int32'))) self.assertTrue(common.allequal(row[1], np.arange(10, dtype='int32'))) self.assertTrue(common.allequal(row[2], np.arange(99, dtype='int32'))) def test02_stop(self): """Checking reads with only a stop value""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_stop..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray.read(stop=1)) row.append(vlarray.read(stop=10)) row.append(vlarray.read(stop=99)) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 1) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 99) self.assertTrue(common.allequal( row[0][0], np.arange(0, dtype='int32'))) for x in range(10): self.assertTrue(common.allequal( row[1][x], np.arange(x, dtype='int32'))) for x in range(99): self.assertTrue(common.allequal( row[2][x], np.arange(x, dtype='int32'))) def test02b_stop(self): """Checking reads with only a stop value in a slice""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02b_stop..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray[:1]) row.append(vlarray[:10]) row.append(vlarray[:99]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 1) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 99) for x in range(1): self.assertTrue(common.allequal( row[0][x], np.arange(0, dtype='int32'))) for x in range(10): self.assertTrue(common.allequal( row[1][x], np.arange(x, dtype='int32'))) for x in range(99): self.assertTrue(common.allequal( row[2][x], np.arange(x, dtype='int32'))) def test03_startstop(self): """Checking reads with a start and stop values""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_startstop..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray.read(0, 10)) row.append(vlarray.read(5, 15)) row.append(vlarray.read(0, 100)) # read all the array if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 10) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 100) for x in range(0, 10): self.assertTrue(common.allequal( row[0][x], np.arange(x, dtype='int32'))) for x in range(5, 15): self.assertTrue(common.allequal( row[1][x-5], np.arange(x, dtype='int32'))) for x in range(0, 100): self.assertTrue(common.allequal( row[2][x], np.arange(x, dtype='int32'))) def test03b_startstop(self): """Checking reads with a start and stop values in slices""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03b_startstop..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray[0:10]) row.append(vlarray[5:15]) row.append(vlarray[:]) # read all the array if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 10) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 100) for x in range(0, 10): self.assertTrue(common.allequal( row[0][x], np.arange(x, dtype='int32'))) for x in range(5, 15): self.assertTrue(common.allequal( row[1][x-5], np.arange(x, dtype='int32'))) for x in range(0, 100): self.assertTrue(common.allequal( row[2][x], np.arange(x, dtype='int32'))) def test04_startstopstep(self): """Checking reads with a start, stop & step values""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_startstopstep..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray.read(0, 10, 2)) row.append(vlarray.read(5, 15, 3)) row.append(vlarray.read(0, 100, 20)) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 5) self.assertEqual(len(row[1]), 4) self.assertEqual(len(row[2]), 5) for x in range(0, 10, 2): self.assertTrue(common.allequal( row[0][x // 2], np.arange(x, dtype='int32'))) for x in range(5, 15, 3): self.assertTrue(common.allequal( row[1][(x - 5) // 3], np.arange(x, dtype='int32'))) for x in range(0, 100, 20): self.assertTrue(common.allequal( row[2][x // 20], np.arange(x, dtype='int32'))) def test04np_startstopstep(self): """Checking reads with a start, stop & step values (numpy indices)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04np_startstopstep..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray.read(np.int8(0), np.int8(10), np.int8(2))) row.append(vlarray.read(np.int8(5), np.int8(15), np.int8(3))) row.append(vlarray.read(np.int8(0), np.int8(100), np.int8(20))) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 5) self.assertEqual(len(row[1]), 4) self.assertEqual(len(row[2]), 5) for x in range(0, 10, 2): self.assertTrue(common.allequal( row[0][x // 2], np.arange(x, dtype='int32'))) for x in range(5, 15, 3): self.assertTrue(common.allequal( row[1][(x - 5) // 3], np.arange(x, dtype='int32'))) for x in range(0, 100, 20): self.assertTrue(common.allequal( row[2][x // 20], np.arange(x, dtype='int32'))) def test04b_slices(self): """Checking reads with start, stop & step values in slices""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04b_slices..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray[0:10:2]) row.append(vlarray[5:15:3]) row.append(vlarray[0:100:20]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 5) self.assertEqual(len(row[1]), 4) self.assertEqual(len(row[2]), 5) for x in range(0, 10, 2): self.assertTrue(common.allequal( row[0][x // 2], np.arange(x, dtype='int32'))) for x in range(5, 15, 3): self.assertTrue(common.allequal( row[1][(x - 5) // 3], np.arange(x, dtype='int32'))) for x in range(0, 100, 20): self.assertTrue(common.allequal( row[2][x // 20], np.arange(x, dtype='int32'))) def test04bnp_slices(self): """Checking reads with start, stop & step values in slices. (numpy indices) """ if common.verbose: print('\n', '-=' * 30) print("Running %s.test04bnp_slices..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray[np.int16(0):np.int16(10):np.int32(2)]) row.append(vlarray[np.int16(5):np.int16(15):np.int64(3)]) row.append(vlarray[np.uint16(0):np.int32(100):np.int8(20)]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 5) self.assertEqual(len(row[1]), 4) self.assertEqual(len(row[2]), 5) for x in range(0, 10, 2): self.assertTrue( common.allequal(row[0][x//2], np.arange(x, dtype='int32'))) for x in range(5, 15, 3): self.assertTrue( common.allequal(row[1][(x-5)//3], np.arange(x, dtype='int32'))) for x in range(0, 100, 20): self.assertTrue( common.allequal(row[2][x//20], np.arange(x, dtype='int32'))) def test05_out_of_range(self): """Checking out of range reads""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_out_of_range..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) with self.assertRaises(IndexError): row = vlarray.read(1000)[0] print("row-->", row) class GetItemRangeTestCase(common.TempFileMixin, common.PyTablesTestCase): nrows = 100 open_mode = "w" compress = 0 complib = "zlib" # Default compression library def setUp(self): super().setUp() self.rootgroup = self.h5file.root self.populateFile() self._reopen() def populateFile(self): group = self.rootgroup filters = tb.Filters(complevel=self.compress, complib=self.complib) vlarray = self.h5file.create_vlarray(group, 'vlarray', tb.Int32Atom(), "ragged array if ints", filters=filters, expectedrows=1000) # Fill it with 100 rows with variable length for i in range(self.nrows): vlarray.append(list(range(i))) def test01_start(self): """Checking reads with only a start value""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_start..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Read some rows: row = [] row.append(vlarray[0]) # rank-0 array should work as a regular index (see #303) row.append(vlarray[np.array(10)]) row.append(vlarray[99]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 0) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 99) self.assertTrue(common.allequal(row[0], np.arange(0, dtype='int32'))) self.assertTrue(common.allequal(row[1], np.arange(10, dtype='int32'))) self.assertTrue(common.allequal(row[2], np.arange(99, dtype='int32'))) def test01b_start(self): """Checking reads with only a start value in a slice""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b_start..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Read some rows: row = [] row.append(vlarray[0]) row.append(vlarray[10]) row.append(vlarray[99]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 0) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 99) self.assertTrue(common.allequal(row[0], np.arange(0, dtype='int32'))) self.assertTrue(common.allequal(row[1], np.arange(10, dtype='int32'))) self.assertTrue(common.allequal(row[2], np.arange(99, dtype='int32'))) def test02_stop(self): """Checking reads with only a stop value""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_stop..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray[:1]) row.append(vlarray[:10]) row.append(vlarray[:99]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("First row in vlarray ==>", row[0]) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 1) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 99) self.assertTrue(common.allequal( row[0][0], np.arange(0, dtype='int32'))) for x in range(10): self.assertTrue(common.allequal( row[1][x], np.arange(x, dtype='int32'))) for x in range(99): self.assertTrue(common.allequal( row[2][x], np.arange(x, dtype='int32'))) def test02b_stop(self): """Checking reads with only a stop value in a slice""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02b_stop..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray[:1]) row.append(vlarray[:10]) row.append(vlarray[:99]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 1) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 99) for x in range(1): self.assertTrue(common.allequal( row[0][x], np.arange(0, dtype='int32'))) for x in range(10): self.assertTrue(common.allequal( row[1][x], np.arange(x, dtype='int32'))) for x in range(99): self.assertTrue(common.allequal( row[2][x], np.arange(x, dtype='int32'))) def test03_startstop(self): """Checking reads with a start and stop values""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_startstop..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray[0:10]) row.append(vlarray[5:15]) row.append(vlarray[0:100]) # read all the array if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 10) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 100) for x in range(0, 10): self.assertTrue(common.allequal( row[0][x], np.arange(x, dtype='int32'))) for x in range(5, 15): self.assertTrue(common.allequal( row[1][x-5], np.arange(x, dtype='int32'))) for x in range(0, 100): self.assertTrue(common.allequal( row[2][x], np.arange(x, dtype='int32'))) def test03b_startstop(self): """Checking reads with a start and stop values in slices""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03b_startstop..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray[0:10]) row.append(vlarray[5:15]) row.append(vlarray[:]) # read all the array if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 10) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 100) for x in range(0, 10): self.assertTrue(common.allequal( row[0][x], np.arange(x, dtype='int32'))) for x in range(5, 15): self.assertTrue(common.allequal( row[1][x-5], np.arange(x, dtype='int32'))) for x in range(0, 100): self.assertTrue(common.allequal( row[2][x], np.arange(x, dtype='int32'))) def test04_slices(self): """Checking reads with a start, stop & step values""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_slices..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray[0:10:2]) row.append(vlarray[5:15:3]) row.append(vlarray[0:100:20]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 5) self.assertEqual(len(row[1]), 4) self.assertEqual(len(row[2]), 5) for x in range(0, 10, 2): self.assertTrue( common.allequal(row[0][x//2], np.arange(x, dtype='int32'))) for x in range(5, 15, 3): self.assertTrue( common.allequal(row[1][(x-5)//3], np.arange(x, dtype='int32'))) for x in range(0, 100, 20): self.assertTrue( common.allequal(row[2][x//20], np.arange(x, dtype='int32'))) def test04bnp_slices(self): """Checking reads with start, stop & step values (numpy indices)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04np_slices..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Choose a small value for buffer size vlarray._nrowsinbuf = 3 # Read some rows: row = [] row.append(vlarray[np.int8(0):np.int8(10):np.int8(2)]) row.append(vlarray[np.int8(5):np.int8(15):np.int8(3)]) row.append(vlarray[np.int8(0):np.int8(100):np.int8(20)]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 5) self.assertEqual(len(row[1]), 4) self.assertEqual(len(row[2]), 5) for x in range(0, 10, 2): self.assertTrue( common.allequal(row[0][x//2], np.arange(x, dtype='int32'))) for x in range(5, 15, 3): self.assertTrue( common.allequal(row[1][(x-5)//3], np.arange(x, dtype='int32'))) for x in range(0, 100, 20): self.assertTrue( common.allequal(row[2][x//20], np.arange(x, dtype='int32'))) def test05_out_of_range(self): """Checking out of range reads""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_out_of_range..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) with self.assertRaises(IndexError): row = vlarray[1000] print("row-->", row) def test05np_out_of_range(self): """Checking out of range reads (numpy indexes)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05np_out_of_range..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) with self.assertRaises(IndexError): row = vlarray[np.int32(1000)] print("row-->", row) class SetRangeTestCase(common.TempFileMixin, common.PyTablesTestCase): nrows = 100 open_mode = "w" compress = 0 complib = "zlib" # Default compression library def setUp(self): super().setUp() self.rootgroup = self.h5file.root self.populateFile() self._reopen(mode='a') def populateFile(self): group = self.rootgroup filters = tb.Filters(complevel=self.compress, complib=self.complib) vlarray = self.h5file.create_vlarray(group, 'vlarray', tb.Int32Atom(), "ragged array if ints", filters=filters, expectedrows=1000) # Fill it with 100 rows with variable length for i in range(self.nrows): vlarray.append(list(range(i))) def test01_start(self): """Checking updates that modifies a complete row""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_start..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Modify some rows: vlarray[0] = vlarray[0]*2 + 3 vlarray[10] = vlarray[10]*2 + 3 vlarray[99] = vlarray[99]*2 + 3 # Read some rows: row = [] row.append(vlarray.read(0)[0]) row.append(vlarray.read(10)[0]) row.append(vlarray.read(99)[0]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 0) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 99) self.assertTrue(common.allequal( row[0], np.arange(0, dtype='int32') * 2 + 3)) self.assertTrue(common.allequal( row[1], np.arange(10, dtype='int32') * 2 + 3)) self.assertTrue(common.allequal( row[2], np.arange(99, dtype='int32') * 2 + 3)) def test01np_start(self): """Checking updates that modifies a complete row""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01np_start..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Modify some rows: vlarray[np.int8(0)] = vlarray[np.int16(0)]*2 + 3 vlarray[np.int8(10)] = vlarray[np.int8(10)]*2 + 3 vlarray[np.int32(99)] = vlarray[np.int64(99)]*2 + 3 # Read some rows: row = [] row.append(vlarray.read(np.int8(0))[0]) row.append(vlarray.read(np.int8(10))[0]) row.append(vlarray.read(np.int8(99))[0]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 0) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 99) self.assertTrue(common.allequal( row[0], np.arange(0, dtype='int32') * 2 + 3)) self.assertTrue(common.allequal( row[1], np.arange(10, dtype='int32') * 2 + 3)) self.assertTrue(common.allequal( row[2], np.arange(99, dtype='int32') * 2 + 3)) def test02_partial(self): """Checking updates with only a part of a row""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_partial..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Modify some rows: vlarray[0] = vlarray[0]*2 + 3 vlarray[10] = vlarray[10]*2 + 3 vlarray[96] = vlarray[99][3:]*2 + 3 # Read some rows: row = [] row.append(vlarray.read(0)[0]) row.append(vlarray.read(10)[0]) row.append(vlarray.read(96)[0]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 0) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 96) self.assertTrue(common.allequal( row[0], np.arange(0, dtype='int32') * 2 + 3)) self.assertTrue(common.allequal( row[1], np.arange(10, dtype='int32') * 2 + 3)) a = np.arange(3, 99, dtype='int32') a = a * 2 + 3 self.assertTrue(common.allequal(row[2], a)) def test03a_several_rows(self): """Checking updating several rows at once (slice style)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03a_several_rows..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Modify some rows: vlarray[3:6] = (vlarray[3]*2 + 3, vlarray[4]*2 + 3, vlarray[5]*2 + 3) # Read some rows: row = [] row.append(vlarray.read(3)[0]) row.append(vlarray.read(4)[0]) row.append(vlarray.read(5)[0]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 3) self.assertEqual(len(row[1]), 4) self.assertEqual(len(row[2]), 5) self.assertTrue(common.allequal( row[0], np.arange(3, dtype='int32') * 2 + 3)) self.assertTrue(common.allequal( row[1], np.arange(4, dtype='int32') * 2 + 3)) self.assertTrue(common.allequal( row[2], np.arange(5, dtype='int32') * 2 + 3)) def test03b_several_rows(self): """Checking updating several rows at once (list style)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03b_several_rows..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Modify some rows: vlarray[[0, 10, 96]] = (vlarray[0]*2 + 3, vlarray[10]*2 + 3, vlarray[96]*2 + 3) # Read some rows: row = [] row.append(vlarray.read(0)[0]) row.append(vlarray.read(10)[0]) row.append(vlarray.read(96)[0]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 0) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 96) self.assertTrue(common.allequal( row[0], np.arange(0, dtype='int32') * 2 + 3)) self.assertTrue(common.allequal( row[1], np.arange(10, dtype='int32') * 2 + 3)) self.assertTrue(common.allequal( row[2], np.arange(96, dtype='int32') * 2 + 3)) def test03c_several_rows(self): """Checking updating several rows at once (NumPy's where style)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03c_several_rows..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray # Modify some rows: vlarray[(np.array([0, 10, 96]),)] = (vlarray[0] * 2 + 3, vlarray[10] * 2 + 3, vlarray[96] * 2 + 3) # Read some rows: row = [] row.append(vlarray.read(0)[0]) row.append(vlarray.read(10)[0]) row.append(vlarray.read(96)[0]) if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) print("Second row in vlarray ==>", row[1]) self.assertEqual(vlarray.nrows, self.nrows) self.assertEqual(len(row[0]), 0) self.assertEqual(len(row[1]), 10) self.assertEqual(len(row[2]), 96) self.assertTrue(common.allequal( row[0], np.arange(0, dtype='int32') * 2 + 3)) self.assertTrue(common.allequal( row[1], np.arange(10, dtype='int32') * 2 + 3)) self.assertTrue(common.allequal( row[2], np.arange(96, dtype='int32') * 2 + 3)) def test04_out_of_range(self): """Checking out of range updates (first index)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_out_of_range..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) with self.assertRaises(IndexError): vlarray[1000] = [1] def test05_value_error(self): """Checking out value errors""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_value_error..." % self.__class__.__name__) vlarray = self.h5file.root.vlarray if common.verbose: print("Nrows in", vlarray._v_pathname, ":", vlarray.nrows) with self.assertRaises(ValueError): vlarray[10] = [1]*100 class CopyTestCase(common.TempFileMixin, common.PyTablesTestCase): close = True def test01a_copy(self): """Checking VLArray.copy() method.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01a_copy..." % self.__class__.__name__) # Create an Vlarray arr = tb.Int16Atom(shape=2) array1 = self.h5file.create_vlarray( self.h5file.root, 'array1', arr, "title array1") array1.flavor = "python" array1.append([[2, 3]]) array1.append(()) # an empty row array1.append([[3, 457], [2, 4]]) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy it to another location array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("array1-->", repr(array1)) print("array2-->", repr(array2)) print("array1[:]-->", repr(array1.read())) print("array2[:]-->", repr(array2.read())) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal self.assertEqual(array1.read(), array2.read()) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.flavor, array2.flavor) self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(repr(array1.atom), repr(array2.atom)) self.assertEqual(array1.title, array2.title) def test01b_copy(self): """Checking VLArray.copy() method (Pseudo-atom case)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01b_copy..." % self.__class__.__name__) # Create an Vlarray arr = tb.VLStringAtom() array1 = self.h5file.create_vlarray( self.h5file.root, 'array1', arr, "title array1") array1.flavor = "python" array1.append(b"a string") array1.append(b"") # an empty row array1.append(b"another string") if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy it to another location array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("array1-->", repr(array1)) print("array2-->", repr(array2)) print("array1[:]-->", repr(array1.read())) print("array2[:]-->", repr(array2.read())) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal self.assertEqual(array1.read(), array2.read()) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.flavor, array2.flavor) self.assertEqual(array1.atom.type, array2.atom.type) self.assertEqual(repr(array1.atom), repr(array2.atom)) self.assertEqual(array1.title, array2.title) def test02_copy(self): """Checking VLArray.copy() method (where specified)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test02_copy..." % self.__class__.__name__) # Create an VLArray arr = tb.Int16Atom(shape=2) array1 = self.h5file.create_vlarray( self.h5file.root, 'array1', arr, "title array1") array1.flavor = "python" array1.append([[2, 3]]) array1.append(()) # an empty row array1.append([[3, 457], [2, 4]]) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy to another location group1 = self.h5file.create_group("/", "group1") array2 = array1.copy(group1, 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.group1.array2 if common.verbose: print("array1-->", repr(array1)) print("array2-->", repr(array2)) print("array1-->", array1.read()) print("array2-->", array2.read()) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Check that all the elements are equal self.assertEqual(array1.read(), array2.read()) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.flavor, array2.flavor) self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(repr(array1.atom), repr(array1.atom)) self.assertEqual(array1.title, array2.title) def test03_copy(self): """Checking VLArray.copy() method ('python' flavor)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test03_copy..." % self.__class__.__name__) # Create an VLArray atom = tb.Int16Atom(shape=2) array1 = self.h5file.create_vlarray( self.h5file.root, 'array1', atom, title="title array1") array1.flavor = "python" array1.append(((2, 3),)) array1.append(()) # an empty row array1.append(((3, 457), (2, 4))) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy to another location array2 = array1.copy('/', 'array2') if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Assert other properties in array self.assertEqual(array1.nrows, array2.nrows) self.assertEqual(array1.shape, array2.shape) self.assertEqual(array1.flavor, array2.flavor) # Very important here self.assertEqual(array1.atom.dtype, array2.atom.dtype) self.assertEqual(repr(array1.atom), repr(array1.atom)) self.assertEqual(array1.title, array2.title) def test04_copy(self): """Checking VLArray.copy() method (checking title copying)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test04_copy..." % self.__class__.__name__) # Create an VLArray atom = tb.Int16Atom(shape=2) array1 = self.h5file.create_vlarray( self.h5file.root, 'array1', atom=atom, title="title array1") array1.append(((2, 3),)) array1.append(()) # an empty row array1.append(((3, 457), (2, 4))) # Append some user attrs array1.attrs.attr1 = "attr1" array1.attrs.attr2 = 2 if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy it to another Array array2 = array1.copy('/', 'array2', title="title array2") if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 # Assert user attributes if common.verbose: print("title of destination array-->", array2.title) self.assertEqual(array2.title, "title array2") def test05_copy(self): """Checking VLArray.copy() method (user attributes copied)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05_copy..." % self.__class__.__name__) # Create an Array atom = tb.Int16Atom(shape=2) array1 = self.h5file.create_vlarray( self.h5file.root, 'array1', atom=atom, title="title array1") array1.append(((2, 3),)) array1.append(()) # an empty row array1.append(((3, 457), (2, 4))) # Append some user attrs array1.attrs.attr1 = "attr1" array1.attrs.attr2 = 2 if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy it to another Array array2 = array1.copy('/', 'array2', copyuserattrs=1) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Assert user attributes self.assertEqual(array2.attrs.attr1, "attr1") self.assertEqual(array2.attrs.attr2, 2) def notest05b_copy(self): """Checking VLArray.copy() method (user attributes not copied)""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test05b_copy..." % self.__class__.__name__) # Create an VLArray atom = tb.Int16Atom(shape=2) array1 = self.h5file.create_vlarray( self.h5file.root, 'array1', atom=atom, title="title array1") array1.append(((2, 3),)) array1.append(()) # an empty row array1.append(((3, 457), (2, 4))) # Append some user attrs array1.attrs.attr1 = "attr1" array1.attrs.attr2 = 2 if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy it to another Array array2 = array1.copy('/', 'array2', copyuserattrs=0) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 array2 = self.h5file.root.array2 if common.verbose: print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) # Assert user attributes self.assertEqual(array2.attrs.attr1, None) self.assertEqual(array2.attrs.attr2, None) class CloseCopyTestCase(CopyTestCase): close = 1 class OpenCopyTestCase(CopyTestCase): close = 0 class CopyIndexTestCase(common.TempFileMixin, common.PyTablesTestCase): def test01_index(self): """Checking VLArray.copy() method with indexes.""" if common.verbose: print('\n', '-=' * 30) print("Running %s.test01_index..." % self.__class__.__name__) # Create an VLArray atom = tb.Int32Atom(shape=(2,)) array1 = self.h5file.create_vlarray( self.h5file.root, 'array1', atom, "t array1") array1.flavor = "python" # The next creates 20 rows of variable length r = [] for row in range(20): r.append([[row, row + 1]]) array1.append([row, row + 1]) if self.close: if common.verbose: print("(closing file version)") self._reopen(mode='a') array1 = self.h5file.root.array1 # Copy to another array array2 = array1.copy("/", 'array2', start=self.start, stop=self.stop, step=self.step) r2 = r[self.start:self.stop:self.step] if common.verbose: print("r2-->", r2) print("array2-->", array2[:]) print("attrs array1-->", repr(array1.attrs)) print("attrs array2-->", repr(array2.attrs)) print("nrows in array2-->", array2.nrows) print("and it should be-->", len(r2)) # Check that all the elements are equal self.assertEqual(r2, array2[:]) # Assert the number of rows in array self.assertEqual(len(r2), array2.nrows) class CopyIndex1TestCase(CopyIndexTestCase): close = 0 start = 0 stop = 7 step = 1 class CopyIndex2TestCase(CopyIndexTestCase): close = 1 start = 0 stop = -1 step = 1 class CopyIndex3TestCase(CopyIndexTestCase): close = 0 start = 1 stop = 7 step = 1 class CopyIndex4TestCase(CopyIndexTestCase): close = 1 start = 0 stop = 6 step = 1 class CopyIndex5TestCase(CopyIndexTestCase): close = 0 start = 3 stop = 7 step = 1 class CopyIndex6TestCase(CopyIndexTestCase): close = 1 start = 3 stop = 6 step = 2 class CopyIndex7TestCase(CopyIndexTestCase): close = 0 start = 0 stop = 7 step = 10 class CopyIndex8TestCase(CopyIndexTestCase): close = 1 start = 6 stop = -1 # Negative values means starting from the end step = 1 class CopyIndex9TestCase(CopyIndexTestCase): close = 0 start = 3 stop = 4 step = 1 class CopyIndex10TestCase(CopyIndexTestCase): close = 1 start = 3 stop = 4 step = 2 class CopyIndex11TestCase(CopyIndexTestCase): close = 0 start = -3 stop = -1 step = 2 class CopyIndex12TestCase(CopyIndexTestCase): close = 1 start = -1 # Should point to the last element stop = None # None should mean the last element (including it) step = 1 class ChunkshapeTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() atom = tb.Int32Atom(shape=(2,)) self.h5file.create_vlarray('/', 'vlarray', atom=atom, title="t array1", chunkshape=13) def test00(self): """Test setting the chunkshape in a table (no reopen).""" vla = self.h5file.root.vlarray if common.verbose: print("chunkshape-->", vla.chunkshape) self.assertEqual(vla.chunkshape, (13,)) def test01(self): """Test setting the chunkshape in a table (reopen).""" self.h5file.close() self.h5file = tb.open_file(self.h5fname, 'r') vla = self.h5file.root.vlarray if common.verbose: print("chunkshape-->", vla.chunkshape) self.assertEqual(vla.chunkshape, (13,)) class VLUEndianTestCase(common.PyTablesTestCase): def setUp(self): super().setUp() self.h5fname = common.test_filename('vlunicode_endian.h5') self.h5file = tb.open_file(self.h5fname) def tearDown(self): self.h5file.close() super().tearDown() def test(self): """Accessing ``vlunicode`` data of a different endianness.""" bedata = self.h5file.root.vlunicode_big[0] ledata = self.h5file.root.vlunicode_little[0] self.assertEqual(bedata, 'para\u0140lel') self.assertEqual(ledata, 'para\u0140lel') class TruncateTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() # Create an VLArray arr = tb.Int16Atom(dflt=3) array1 = self.h5file.create_vlarray( self.h5file.root, 'array1', arr, "title array1") # Add a couple of rows array1.append(np.array([456, 2], dtype='int16')) array1.append(np.array([3], dtype='int16')) def test00_truncate(self): """Checking VLArray.truncate() method (truncating to 0 rows)""" array1 = self.h5file.root.array1 # Truncate to 0 elements array1.truncate(0) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 if common.verbose: print("array1-->", array1.read()) self.assertEqual(array1.nrows, 0) self.assertEqual(array1[:], []) def test01_truncate(self): """Checking VLArray.truncate() method (truncating to 1 rows)""" array1 = self.h5file.root.array1 # Truncate to 1 element array1.truncate(1) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 if common.verbose: print("array1-->", array1.read()) self.assertEqual(array1.nrows, 1) self.assertTrue(common.allequal( array1[0], np.array([456, 2], dtype='int16'))) def test02_truncate(self): """Checking VLArray.truncate() method (truncating to == self.nrows)""" array1 = self.h5file.root.array1 # Truncate to 2 elements array1.truncate(2) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 if common.verbose: print("array1-->", array1.read()) self.assertEqual(array1.nrows, 2) self.assertTrue( common.allequal(array1[0], np.array([456, 2], dtype='int16'))) self.assertTrue(common.allequal( array1[1], np.array([3], dtype='int16'))) def test03_truncate(self): """Checking VLArray.truncate() method (truncating to > self.nrows)""" array1 = self.h5file.root.array1 # Truncate to 4 elements array1.truncate(4) if self.close: if common.verbose: print("(closing file version)") self._reopen() array1 = self.h5file.root.array1 if common.verbose: print("array1-->", array1.read()) self.assertEqual(array1.nrows, 4) # Check the original values self.assertTrue( common.allequal(array1[0], np.array([456, 2], dtype='int16'))) self.assertTrue(common.allequal( array1[1], np.array([3], dtype='int16'))) # Check that the added rows are empty self.assertTrue(common.allequal( array1[2], np.array([], dtype='int16'))) self.assertTrue(common.allequal( array1[3], np.array([], dtype='int16'))) class TruncateOpenTestCase(TruncateTestCase): close = 0 class TruncateCloseTestCase(TruncateTestCase): close = 1 class PointSelectionTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() # The next are valid selections for both NumPy and PyTables self.working_keyset = [ [], # empty list [2], # single-entry list [0, 2], # list [0, -2], # negative values ([0, 2],), # tuple of list np.array([], dtype="i4"), # empty array np.array([1], dtype="i4"), # single-entry array np.array([True, False, True]), # array of bools ] # The next are invalid selections for VLArrays self.not_working_keyset = [ [1, 2, 100], # coordinate 100 > len(vlarray) ([True, False, True],), # tuple of bools ] # Create a sample array arr1 = np.array([5, 6], dtype="i4") arr2 = np.array([5, 6, 7], dtype="i4") arr3 = np.array([5, 6, 9, 8], dtype="i4") self.nparr = np.array([arr1, arr2, arr3], dtype="object") # Create the VLArray self.vlarr = self.h5file.create_vlarray( self.h5file.root, 'vlarray', tb.Int32Atom()) self.vlarr.append(arr1) self.vlarr.append(arr2) self.vlarr.append(arr3) def test01a_read(self): """Test for point-selections (read, boolean keys).""" nparr = self.nparr vlarr = self.vlarr for key in self.working_keyset: if common.verbose: print("Selection to test:", repr(key)) a = nparr[key].tolist() b = vlarr[key] # if common.verbose: # print "NumPy selection:", a, type(a) # print "PyTables selection:", b, type(b) self.assertEqual( repr(a), repr(b), "NumPy array and PyTables selections does not match.") def test01b_read(self): """Test for point-selections (not working selections, read).""" vlarr = self.vlarr for key in self.not_working_keyset: if common.verbose: print("Selection to test:", key) self.assertRaises(IndexError, vlarr.__getitem__, key) class SizeInMemoryPropertyTestCase(common.TempFileMixin, common.PyTablesTestCase): def create_array(self, atom, complevel): filters = tb.Filters(complevel=complevel, complib='blosc') self.array = self.h5file.create_vlarray('/', 'vlarray', atom=atom, filters=filters) def test_zero_length(self): atom = tb.Int32Atom() complevel = 0 self.create_array(atom, complevel) self.assertEqual(self.array.size_in_memory, 0) def int_tests(self, complevel, flavor): atom = tb.Int32Atom() self.create_array(atom, complevel) self.array.flavor = flavor expected_size = 0 for i in range(10): row = np.arange((i + 1) * 10, dtype='i4') self.array.append(row) expected_size += row.nbytes return expected_size def test_numpy_int_numpy_flavor(self): complevel = 0 flavor = 'numpy' expected_size = self.int_tests(complevel, flavor) self.assertEqual(self.array.size_in_memory, expected_size) # compression will have no effect, since this is uncompressed size def test_numpy_int_numpy_flavor_compressed(self): complevel = 1 flavor = 'numpy' expected_size = self.int_tests(complevel, flavor) self.assertEqual(self.array.size_in_memory, expected_size) # flavor will have no effect on what's stored in HDF5 file def test_numpy_int_python_flavor(self): complevel = 0 flavor = 'python' expected_size = self.int_tests(complevel, flavor) self.assertEqual(self.array.size_in_memory, expected_size) # this relies on knowledge of the implementation, so it's not # a great test def test_object_atom(self): atom = tb.ObjectAtom() complevel = 0 self.create_array(atom, complevel) obj = [1, 2, 3] for i in range(10): self.array.append(obj) pickle_array = atom.toarray(obj) expected_size = 10 * pickle_array.nbytes self.assertEqual(self.array.size_in_memory, expected_size) class SizeOnDiskPropertyTestCase(common.TempFileMixin, common.PyTablesTestCase): def create_array(self, atom, complevel): filters = tb.Filters(complevel=complevel, complib='blosc') self.h5file.create_vlarray('/', 'vlarray', atom, filters=filters) self.array = self.h5file.get_node('/', 'vlarray') def test_not_implemented(self): atom = tb.IntAtom() complevel = 0 self.create_array(atom, complevel) self.assertRaises(NotImplementedError, getattr, self.array, 'size_on_disk') class AccessClosedTestCase(common.TempFileMixin, common.PyTablesTestCase): def setUp(self): super().setUp() self.array = self.h5file.create_vlarray( self.h5file.root, 'array', atom=tb.StringAtom(8)) self.array.append([str(i) for i in range(5, 5005, 100)]) def test_read(self): self.h5file.close() self.assertRaises( tb.ClosedNodeError, self.array.read) def test_getitem(self): self.h5file.close() self.assertRaises( tb.ClosedNodeError, self.array.__getitem__, 0) def test_setitem(self): self.h5file.close() self.assertRaises( tb.ClosedNodeError, self.array.__setitem__, 0, '0') def test_append(self): self.h5file.close() self.assertRaises( tb.ClosedNodeError, self.array.append, 'xxxxxxxxx') class TestCreateVLArrayArgs(common.TempFileMixin, common.PyTablesTestCase): obj = np.array([1, 2, 3]) where = '/' name = 'vlarray' atom = tb.Atom.from_dtype(obj.dtype) title = 'title' filters = None expectedrows = None chunkshape = None byteorder = None createparents = False def test_positional_args_01(self): self.h5file.create_vlarray(self.where, self.name, self.atom, self.title, self.filters, self.expectedrows) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (0,)) self.assertEqual(ptarr.nrows, 0) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) def test_positional_args_02(self): ptarr = self.h5file.create_vlarray(self.where, self.name, self.atom, self.title, self.filters, self.expectedrows) ptarr.append(self.obj) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read()[0] self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (1,)) self.assertEqual(ptarr[0].shape, self.obj.shape) self.assertEqual(ptarr.nrows, 1) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertTrue(common.allequal(self.obj, nparr)) def test_positional_args_obj(self): self.h5file.create_vlarray(self.where, self.name, None, self.title, self.filters, self.expectedrows, self.chunkshape, self.byteorder, self.createparents, self.obj) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read()[0] self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (1,)) self.assertEqual(ptarr[0].shape, self.obj.shape) self.assertEqual(ptarr.nrows, 1) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj(self): self.h5file.create_vlarray(self.where, self.name, title=self.title, obj=self.obj) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read()[0] self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (1,)) self.assertEqual(ptarr[0].shape, self.obj.shape) self.assertEqual(ptarr.nrows, 1) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_atom_01(self): ptarr = self.h5file.create_vlarray(self.where, self.name, title=self.title, atom=self.atom) ptarr.append(self.obj) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read()[0] self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (1,)) self.assertEqual(ptarr[0].shape, self.obj.shape) self.assertEqual(ptarr.nrows, 1) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_atom_02(self): ptarr = self.h5file.create_vlarray(self.where, self.name, title=self.title, atom=self.atom) # ptarr.append(self.obj) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (0,)) self.assertEqual(ptarr.nrows, 0) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) def test_kwargs_obj_atom(self): ptarr = self.h5file.create_vlarray(self.where, self.name, title=self.title, obj=self.obj, atom=self.atom) self.h5file.close() self.h5file = tb.open_file(self.h5fname) ptarr = self.h5file.get_node(self.where, self.name) nparr = ptarr.read()[0] self.assertEqual(ptarr.title, self.title) self.assertEqual(ptarr.shape, (1,)) self.assertEqual(ptarr[0].shape, self.obj.shape) self.assertEqual(ptarr.nrows, 1) self.assertEqual(ptarr.atom, self.atom) self.assertEqual(ptarr.atom.dtype, self.atom.dtype) self.assertTrue(common.allequal(self.obj, nparr)) def test_kwargs_obj_atom_error(self): atom = tb.Atom.from_dtype(np.dtype('complex')) # shape = self.shape + self.shape self.assertRaises(TypeError, self.h5file.create_vlarray, self.where, self.name, title=self.title, obj=self.obj, atom=atom) def suite(): theSuite = common.unittest.TestSuite() niter = 1 for n in range(niter): theSuite.addTest(common.unittest.makeSuite(BasicNumPyTestCase)) theSuite.addTest(common.unittest.makeSuite(BasicPythonTestCase)) theSuite.addTest(common.unittest.makeSuite(ZlibComprTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscComprTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscShuffleComprTestCase)) theSuite.addTest( common.unittest.makeSuite(BloscBitShuffleComprTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscBloscLZComprTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscLZ4ComprTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscLZ4HCComprTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscSnappyComprTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscZlibComprTestCase)) theSuite.addTest(common.unittest.makeSuite(BloscZstdComprTestCase)) theSuite.addTest(common.unittest.makeSuite(LZOComprTestCase)) theSuite.addTest(common.unittest.makeSuite(Bzip2ComprTestCase)) theSuite.addTest(common.unittest.makeSuite(TypesReopenTestCase)) theSuite.addTest(common.unittest.makeSuite(TypesNoReopenTestCase)) theSuite.addTest(common.unittest.makeSuite(MDTypesNumPyTestCase)) theSuite.addTest(common.unittest.makeSuite(OpenAppendShapeTestCase)) theSuite.addTest(common.unittest.makeSuite(CloseAppendShapeTestCase)) theSuite.addTest(common.unittest.makeSuite(PythonFlavorTestCase)) theSuite.addTest(common.unittest.makeSuite(NumPyFlavorTestCase)) theSuite.addTest(common.unittest.makeSuite(ReadRangeTestCase)) theSuite.addTest(common.unittest.makeSuite(GetItemRangeTestCase)) theSuite.addTest(common.unittest.makeSuite(SetRangeTestCase)) theSuite.addTest(common.unittest.makeSuite(ShuffleComprTestCase)) theSuite.addTest(common.unittest.makeSuite(CloseCopyTestCase)) theSuite.addTest(common.unittest.makeSuite(OpenCopyTestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex1TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex2TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex3TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex4TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex5TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex6TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex7TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex8TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex9TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex10TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex11TestCase)) theSuite.addTest(common.unittest.makeSuite(CopyIndex12TestCase)) theSuite.addTest(common.unittest.makeSuite(ChunkshapeTestCase)) theSuite.addTest(common.unittest.makeSuite(VLUEndianTestCase)) theSuite.addTest(common.unittest.makeSuite(TruncateOpenTestCase)) theSuite.addTest(common.unittest.makeSuite(TruncateCloseTestCase)) theSuite.addTest(common.unittest.makeSuite(PointSelectionTestCase)) theSuite.addTest( common.unittest.makeSuite(SizeInMemoryPropertyTestCase)) theSuite.addTest(common.unittest.makeSuite(SizeOnDiskPropertyTestCase)) theSuite.addTest(common.unittest.makeSuite(AccessClosedTestCase)) theSuite.addTest(common.unittest.makeSuite(TestCreateVLArrayArgs)) return theSuite if __name__ == '__main__': common.parse_argv(sys.argv) common.print_versions() common.unittest.main(defaultTest='suite') PyTables-3.7.0/tables/tests/time-table-vlarray-1_x.h5000066400000000000000000000072661416254111300223150ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ°ÿÿÿÿÿÿÿÿ €`HEAP0€tablevlarray4vlarray8ÐTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà  Hà¨&t32col t64col@( ˆÿÿÿÿÿÿÿÿ¦SNODÐ( 8 0 ° €ÿÿÿÿÿÿÿÿW2ÇE (CLASSVLARRAY 0FLAVOR numarray 0@0€ÿÿÿÿÿÿÿÿW2ÇE (CLASSVLARRAY 0FLAVOR numarray€` @TITLETest for creating time leaves (CLASSGROUP (VERSION1.0 ØFILTERS²ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I00 sS'complevel' p7 I0 sS'fletcher32' p8 I00 sS'complib' p9 S'zlib' p10 sb. 8PYTABLES_FORMAT_VERSION1.6ÿÿÿÿÿÿÿÿW2ÇE (CLASSTABLE (VERSION2.6 (TITLE 0 NROWS@ 0 FIELD_0_NAMEt32col 0 FIELD_1_NAMEt64col 0FLAVOR numarray 8 AUTOMATIC_INDEX  0 REINDEX  àFILTERS_INDEX²ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I01 sS'complevel' p7 I1 sS'fletcher32' p8 I00 sS'complib' p9 S'zlib' p10 sb. 8 FIELD_0_FILL  @ FIELD_1_FILL ?@4 4ÿÿÿÿÿÿÿÿÿ (VERSION1.2 (TITLEÿÿÿÿÿÿÿÿ (VERSION1.2 (TITLE PyTables-3.7.0/tables/tests/times-nested-be.h5000066400000000000000000000542221416254111300211060ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿŒXÿÿÿÿÿÿÿÿ €`HEAP €tblearr32earr64àTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàntegersiisseeds must be in range(0, 256)Niÿÿÿii(R=RRxRZR/RSR*R$R ttRRR‚RR(RRRxRZRƒR$((Rt__whseedÃs<Z* 'cCsÀ|djo|iƒdSnt|ƒ}t|dƒ\}}t|dƒ\}}t|dƒ\}}||dpd}||dpd}||dpd}|i|||ƒdS(sbSeed from hashable obje Xè  nestedt64@t32 , ð(U SNOD",#ÐTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü0U €` (TITLE (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.0 (VERSION1.0 (TITLE (VERSION1.0 (TITLEFD‡ª ³FD‡ªFD‡« ³FD‡«FD‡¬ ³FD‡¬FD‡­ ³FD‡­FD‡® ³FD‡®FD‡¯ ³FD‡¯FD‡° ³FD‡°FD‡± ³FD‡±FD‡² ³FD‡²FD‡³ ³FD‡³ ÿÿÿÿÿÿÿÿª‡DF (CLASSTABLE (VERSION2.6 (TITLE 0 NROWS @ 0 FIELD_0_NAMEnested 0 FIELD_1_NAMEt32 8 FIELD_0_FILL   @ FIELD_1_FILL!?@4 4ÿ @x  ÿÿÿÿÿÿÿÿ<$ª‡DF (CLASSEARRAY 0 EXTDIM   ¸x@ ÿÿÿÿÿÿÿÿl,ª‡DF (CLASSEARRAY 0 EXTDIM  TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŒ82À3•€òiTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŒHFD‡ªFD‡«FD‡¬FD‡­FD‡®FD‡¯FD‡°FD‡±FD‡²FD‡³FD‡ª ³FD‡« ³FD‡¬ ³FD‡­ ³FD‡® ³FD‡¯ ³FD‡° ³FD‡± ³FD‡² ³FD‡³ ³ PyTables-3.7.0/tables/tests/vlstr_attr.h5000066400000000000000000000122561416254111300203260ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ¨ÿÿÿÿÿÿÿÿ`ˆ¨ hTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿHEAPXÈPˆ  Hvlen_str_scalarˆGCOLvlen_str_scalarvlen_str_array_2vlen_str_array_1vlen_str_array_0vlen_str_matrix_10vlen_str_matrix_11vlen_str_matrix_01vlen_str_matrix_00Ј¨ pvlen_str_arrayˆˆˆ ˆvlen_str_matrixˆˆˆˆ PyTables-3.7.0/tables/tests/vlunicode_endian.h5000066400000000000000000002401461416254111300214310ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿ`@ÿÿÿÿÿÿÿÿ €`HEAP0€vlunicode_bigvlunicode_littleÐTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà R›tNRR R R RRR(Rœ((Rt_test+st__main__(DR|twarningstwarnRDttypest MethodTypeR~tBuiltinMethodTypeRtmathtlogR@texpR^tpiReRRstsqrtRctacosRitcosRdtsinRwtosturandomR"tbinasciithexlifyR!t__all__RYRbRnRrR}R‡t_randomRRRR›Rt_instRRR Xè  ÿÿÿÿÿÿÿÿ( øRF (CLASSVLARRAY (VERSION1.3@pSNODÐ0 TREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€0 GCOL para@lelÀ€` (TITLE (CLASSGROUP (VERSION1.0 8PYTABLES_FORMAT_VERSION2.0 (TITLE 8 PSEUDOATOM vlunicodeX  ÿÿÿÿÿÿÿÿ@¡¥SF (CLASSVLARRAY (VERSION1.3p¹pTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€`ÀGCOL para@lelÀ (TITLE 8 PSEUDOATOM vlunicodep© PyTables-3.7.0/tables/tests/zerodim-attrs-1.3.h5000066400000000000000000000117561416254111300212310ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿèÿÿÿÿÿÿÿÿ €`HEAP€aðTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà (À èß÷B (CLASSARRAY 0FLAVOR NumArray (VERSION2.1SNODЀ` (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS°ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I0 sS'complevel' p7 I0 sS'fletcher32' p8 I0 sS'complib' p9 S'zlib' p10 sb. 8PYTABLES_FORMAT_VERSION1.3 (TITLE   arrscalarxcnumarray.generic ClassicUnpickler p1 (cnumarray.numarraycore NumArray p2 (dp3 S'_type' p4 S'Int32' p5 sS'_bytestride' p6 I4 sS'_itemsize' p7 I4 sS'_shape' p8 (tsS'_version' p9 S'1.4.0' p10 sS'_byteoffset' p11 I0 sS'_byteorder' p12 S'little' p13 sS'_data' p14 cnumarray.memory memory_from_string p15 (S'\x01\x00\x00\x00' tRp16 sS'_strides' p17 (tsS'_flags' p18 I1793 stRp19 . ¨arrdim1†cnumarray.generic ClassicUnpickler p1 (cnumarray.numarraycore NumArray p2 (dp3 S'_type' p4 S'Int32' p5 sS'_bytestride' p6 I4 sS'_itemsize' p7 I4 sS'_shape' p8 (I1 tp9 sS'_version' p10 S'1.4.0' p11 sS'_byteoffset' p12 I0 sS'_byteorder' p13 S'little' p14 sS'_data' p15 cnumarray.memory memory_from_string p16 (S'\x01\x00\x00\x00' tRp17 sS'_strides' p18 (I4 tp19 sS'_flags' p20 I1793 stRp21 . @ pythonscalar  PyTables-3.7.0/tables/tests/zerodim-attrs-1.4.h5000066400000000000000000000104161416254111300212220ustar00rootroot00000000000000‰HDF  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ €`HEAP€aðTREEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà„Zd„Zd„Zdeed„Zded eeed „Z ded „Z ded eeed „Z dedeeed„Z eed„Z ed„Zeeeed„Zeeeeed„Zeed„Zed„Zed„Zed„Zed„Zed„Zed„Zed„Zed„Zeed„Zed„Zed„Zd „Zd!„Zded"„Z dd#„Z!d$„Z"d%„Z#d&„Z$d'„Z%d(„Z&d)„Z'd*„Z(e)d+dƒd,„Z*d-„Z (À sêB (CLASSARRAY 0FLAVOR NumArray (VERSION2.2SNODЀ` (TITLE (CLASSGROUP (VERSION1.0 ÐFILTERS°ccopy_reg _reconstructor p1 (ctables.Leaf Filters p2 c__builtin__ object p3 NtRp4 (dp5 S'shuffle' p6 I0 sS'complevel' p7 I0 sS'fletcher32' p8 I0 sS'complib' p9 S'zlib' p10 sb. 8PYTABLES_FORMAT_VERSION1.4 (TITLE 8 arrscalar  8 arrdim1  8 pythonscalar  PyTables-3.7.0/tables/undoredo.py000066400000000000000000000074611416254111300167150ustar00rootroot00000000000000"""Support for undoing and redoing actions. Functions: * undo(file, operation, *args) * redo(file, operation, *args) * move_to_shadow(file, path) * move_from_shadow(file, path) * attr_to_shadow(file, path, name) * attr_from_shadow(file, path, name) Misc variables: `__docformat__` The format of documentation strings in this module. """ from .path import split_path __docformat__ = 'reStructuredText' """The format of documentation strings in this module.""" def undo(file_, operation, *args): if operation == 'CREATE': undo_create(file_, args[0]) elif operation == 'REMOVE': undo_remove(file_, args[0]) elif operation == 'MOVE': undo_move(file_, args[0], args[1]) elif operation == 'ADDATTR': undo_add_attr(file_, args[0], args[1]) elif operation == 'DELATTR': undo_del_attr(file_, args[0], args[1]) else: raise NotImplementedError("the requested unknown operation %r can " "not be undone; please report this to the " "authors" % operation) def redo(file_, operation, *args): if operation == 'CREATE': redo_create(file_, args[0]) elif operation == 'REMOVE': redo_remove(file_, args[0]) elif operation == 'MOVE': redo_move(file_, args[0], args[1]) elif operation == 'ADDATTR': redo_add_attr(file_, args[0], args[1]) elif operation == 'DELATTR': redo_del_attr(file_, args[0], args[1]) else: raise NotImplementedError("the requested unknown operation %r can " "not be redone; please report this to the " "authors" % operation) def move_to_shadow(file_, path): node = file_._get_node(path) (shparent, shname) = file_._shadow_name() node._g_move(shparent, shname) def move_from_shadow(file_, path): (shparent, shname) = file_._shadow_name() node = shparent._f_get_child(shname) (pname, name) = split_path(path) parent = file_._get_node(pname) node._g_move(parent, name) def undo_create(file_, path): move_to_shadow(file_, path) def redo_create(file_, path): move_from_shadow(file_, path) def undo_remove(file_, path): move_from_shadow(file_, path) def redo_remove(file_, path): move_to_shadow(file_, path) def undo_move(file_, origpath, destpath): (origpname, origname) = split_path(origpath) node = file_._get_node(destpath) origparent = file_._get_node(origpname) node._g_move(origparent, origname) def redo_move(file_, origpath, destpath): (destpname, destname) = split_path(destpath) node = file_._get_node(origpath) destparent = file_._get_node(destpname) node._g_move(destparent, destname) def attr_to_shadow(file_, path, name): node = file_._get_node(path) attrs = node._v_attrs value = getattr(attrs, name) (shparent, shname) = file_._shadow_name() shattrs = shparent._v_attrs # Set the attribute only if it has not been kept in the shadow. # This avoids re-pickling complex attributes on REDO. if shname not in shattrs: shattrs._g__setattr(shname, value) attrs._g__delattr(name) def attr_from_shadow(file_, path, name): (shparent, shname) = file_._shadow_name() shattrs = shparent._v_attrs value = getattr(shattrs, shname) node = file_._get_node(path) node._v_attrs._g__setattr(name, value) # Keeping the attribute in the shadow allows reusing it on Undo/Redo. # shattrs._g__delattr(shname) def undo_add_attr(file_, path, name): attr_to_shadow(file_, path, name) def redo_add_attr(file_, path, name): attr_from_shadow(file_, path, name) def undo_del_attr(file_, path, name): attr_from_shadow(file_, path, name) def redo_del_attr(file_, path, name): attr_to_shadow(file_, path, name) PyTables-3.7.0/tables/unimplemented.py000066400000000000000000000117741416254111300177460ustar00rootroot00000000000000"""Here is defined the UnImplemented class.""" import warnings from . import hdf5extension from .utils import SizeType from .node import Node from .leaf import Leaf class UnImplemented(hdf5extension.UnImplemented, Leaf): """This class represents datasets not supported by PyTables in an HDF5 file. When reading a generic HDF5 file (i.e. one that has not been created with PyTables, but with some other HDF5 library based tool), chances are that the specific combination of datatypes or dataspaces in some dataset might not be supported by PyTables yet. In such a case, this dataset will be mapped into an UnImplemented instance and the user will still be able to access the complete object tree of the generic HDF5 file. The user will also be able to *read and write the attributes* of the dataset, *access some of its metadata*, and perform *certain hierarchy manipulation operations* like deleting or moving (but not copying) the node. Of course, the user will not be able to read the actual data on it. This is an elegant way to allow users to work with generic HDF5 files despite the fact that some of its datasets are not supported by PyTables. However, if you are really interested in having full access to an unimplemented dataset, please get in contact with the developer team. This class does not have any public instance variables or methods, except those inherited from the Leaf class (see :ref:`LeafClassDescr`). """ # Class identifier. _c_classid = 'UNIMPLEMENTED' def __init__(self, parentnode, name): """Create the `UnImplemented` instance.""" # UnImplemented objects always come from opening an existing node # (they can not be created). self._v_new = False """Is this the first time the node has been created?""" self.nrows = SizeType(0) """The length of the first dimension of the data.""" self.shape = (SizeType(0),) """The shape of the stored data.""" self.byteorder = None """The endianness of data in memory ('big', 'little' or 'irrelevant').""" super().__init__(parentnode, name) def _g_open(self): (self.shape, self.byteorder, object_id) = self._open_unimplemented() try: self.nrows = SizeType(self.shape[0]) except IndexError: self.nrows = SizeType(0) return object_id def _g_copy(self, newparent, newname, recursive, _log=True, **kwargs): """Do nothing. This method does nothing, but a ``UserWarning`` is issued. Please note that this method *does not return a new node*, but ``None``. """ warnings.warn( "UnImplemented node %r does not know how to copy itself; skipping" % (self._v_pathname,)) return None # Can you see it? def _f_copy(self, newparent=None, newname=None, overwrite=False, recursive=False, createparents=False, **kwargs): """Do nothing. This method does nothing, since `UnImplemented` nodes can not be copied. However, a ``UserWarning`` is issued. Please note that this method *does not return a new node*, but ``None``. """ # This also does nothing but warn. self._g_copy(newparent, newname, recursive, **kwargs) return None # Can you see it? def __repr__(self): return """{} NOTE: """.format(str(self), self._v_file.filename) # Classes reported as H5G_UNKNOWN by HDF5 class Unknown(Node): """This class represents nodes reported as *unknown* by the underlying HDF5 library. This class does not have any public instance variables or methods, except those inherited from the Node class. """ # Class identifier _c_classid = 'UNKNOWN' def __init__(self, parentnode, name): """Create the `Unknown` instance.""" self._v_new = False super().__init__(parentnode, name) def _g_new(self, parentnode, name, init=False): pass def _g_open(self): return 0 def _g_copy(self, newparent, newname, recursive, _log=True, **kwargs): # Silently avoid doing copies of unknown nodes return None def _g_delete(self, parent): pass def __str__(self): pathname = self._v_pathname classname = self.__class__.__name__ return f"{pathname} ({classname})" def __repr__(self): return f"""{self!s} NOTE: """ # These are listed here for backward compatibility with PyTables 0.9.x indexes class OldIndexArray(UnImplemented): _c_classid = 'IndexArray' PyTables-3.7.0/tables/utils.py000066400000000000000000000325241416254111300162340ustar00rootroot00000000000000"""Utility functions.""" import math import os import sys import warnings import weakref from pathlib import Path from time import perf_counter as clock import numpy as np from .flavor import array_of_flavor # The map between byteorders in NumPy and PyTables byteorders = { '>': 'big', '<': 'little', '=': sys.byteorder, '|': 'irrelevant', } # The type used for size values: indexes, coordinates, dimension # lengths, row numbers, shapes, chunk shapes, byte counts... SizeType = np.int64 def correct_byteorder(ptype, byteorder): """Fix the byteorder depending on the PyTables types.""" if ptype in ['string', 'bool', 'int8', 'uint8', 'object']: return "irrelevant" else: return byteorder def is_idx(index): """Checks if an object can work as an index or not.""" if type(index) is int: return True elif hasattr(index, "__index__"): # Exclude the array([idx]) as working as an index. Fixes #303. if (hasattr(index, "shape") and index.shape != ()): return False try: index.__index__() if isinstance(index, bool): warnings.warn( 'using a boolean instead of an integer will result in an ' 'error in the future', DeprecationWarning, stacklevel=2) return True except TypeError: return False elif isinstance(index, np.integer): return True # For Python 2.4 one should test 0-dim and 1-dim, 1-elem arrays as well elif (isinstance(index, np.ndarray) and (index.shape == ()) and index.dtype.str[1] == 'i'): return True return False def idx2long(index): """Convert a possible index into a long int.""" try: return int(index) except Exception: raise TypeError("not an integer type.") # This is used in VLArray and EArray to produce NumPy object compliant # with atom from a generic python type. If copy is stated as True, it # is assured that it will return a copy of the object and never the same # object or a new one sharing the same memory. def convert_to_np_atom(arr, atom, copy=False): """Convert a generic object into a NumPy object compliant with atom.""" # First, convert the object into a NumPy array nparr = array_of_flavor(arr, 'numpy') # Copy of data if necessary for getting a contiguous buffer, or if # dtype is not the correct one. if atom.shape == (): # Scalar atom case nparr = np.array(nparr, dtype=atom.dtype, copy=copy) else: # Multidimensional atom case. Addresses #133. # We need to use this strange way to obtain a dtype compliant # array because NumPy doesn't honor the shape of the dtype when # it is multidimensional. See: # http://scipy.org/scipy/numpy/ticket/926 # for details. # All of this is done just to taking advantage of the NumPy # broadcasting rules. newshape = nparr.shape[:-len(atom.dtype.shape)] nparr2 = np.empty(newshape, dtype=[('', atom.dtype)]) nparr2['f0'][:] = nparr # Return a view (i.e. get rid of the record type) nparr = nparr2.view(atom.dtype) return nparr # The next is used in Array, EArray and VLArray, and it is a bit more # high level than convert_to_np_atom def convert_to_np_atom2(object, atom): """Convert a generic object into a NumPy object compliant with atom.""" # Check whether the object needs to be copied to make the operation # safe to in-place conversion. copy = atom.type in ['time64'] nparr = convert_to_np_atom(object, atom, copy) # Finally, check the byteorder and change it if needed byteorder = byteorders[nparr.dtype.byteorder] if (byteorder in ['little', 'big'] and byteorder != sys.byteorder): # The byteorder needs to be fixed (a copy is made # so that the original array is not modified) nparr = nparr.byteswap() return nparr def check_file_access(filename, mode='r'): """Check for file access in the specified `mode`. `mode` is one of the modes supported by `File` objects. If the file indicated by `filename` can be accessed using that `mode`, the function ends successfully. Else, an ``IOError`` is raised explaining the reason of the failure. All this paraphernalia is used to avoid the lengthy and scaring HDF5 messages produced when there are problems opening a file. No changes are ever made to the file system. """ path = Path(filename).resolve() if mode == 'r': # The file should be readable. if not os.access(path, os.F_OK): raise OSError(f"``{path}`` does not exist") if not path.is_file(): raise OSError(f"``{path}`` is not a regular file") if not os.access(path, os.R_OK): raise OSError(f"file ``{path}`` exists but it can not be read") elif mode == 'w': if os.access(path, os.F_OK): # Since the file is not removed but replaced, # it must already be accessible to read and write operations. check_file_access(path, 'r+') else: # A new file is going to be created, # so the directory should be writable. if not os.access(path.parent, os.F_OK): raise OSError(f"``{path.parent}`` does not exist") if not path.parent.is_dir(): raise OSError(f"``{path.parent}`` is not a directory") if not os.access(path.parent, os.W_OK): raise OSError( f"directory ``{path.parent}`` exists but it can not be " f"written" ) elif mode == 'a': if os.access(path, os.F_OK): check_file_access(path, 'r+') else: check_file_access(path, 'w') elif mode == 'r+': check_file_access(path, 'r') if not os.access(path, os.W_OK): raise OSError(f"file ``{path}`` exists but it can not be written") else: raise ValueError(f"invalid mode: {mode!r}") def lazyattr(fget): """Create a *lazy attribute* from the result of `fget`. This function is intended to be used as a *method decorator*. It returns a *property* which caches the result of calling the `fget` instance method. The docstring of `fget` is used for the property itself. For instance: >>> class MyClass(object): ... @lazyattr ... def attribute(self): ... 'Attribute description.' ... print('creating value') ... return 10 ... >>> type(MyClass.attribute) >>> MyClass.attribute.__doc__ 'Attribute description.' >>> obj = MyClass() >>> obj.__dict__ {} >>> obj.attribute creating value 10 >>> obj.__dict__ {'attribute': 10} >>> obj.attribute 10 >>> del obj.attribute Traceback (most recent call last): ... AttributeError: can't delete attribute .. warning:: Please note that this decorator *changes the type of the decorated object* from an instance method into a property. """ name = fget.__name__ def newfget(self): mydict = self.__dict__ if name in mydict: return mydict[name] mydict[name] = value = fget(self) return value return property(newfget, None, None, fget.__doc__) def show_stats(explain, tref, encoding=None): """Show the used memory (only works for Linux 2.6.x).""" for line in Path('/proc/self/status').read_text().splitlines(): if line.startswith("VmSize:"): vmsize = int(line.split()[1]) elif line.startswith("VmRSS:"): vmrss = int(line.split()[1]) elif line.startswith("VmData:"): vmdata = int(line.split()[1]) elif line.startswith("VmStk:"): vmstk = int(line.split()[1]) elif line.startswith("VmExe:"): vmexe = int(line.split()[1]) elif line.startswith("VmLib:"): vmlib = int(line.split()[1]) print("Memory usage: ******* %s *******" % explain) print(f"VmSize: {vmsize:>7} kB\tVmRSS: {vmrss:>7} kB") print(f"VmData: {vmdata:>7} kB\tVmStk: {vmstk:>7} kB") print(f"VmExe: {vmexe:>7} kB\tVmLib: {vmlib:>7} kB") tnow = clock() print(f"WallClock time: {tnow - tref:.3f}") return tnow # truncate data before calling __setitem__, to improve compression ratio # this function is taken verbatim from netcdf4-python def quantize(data, least_significant_digit): """quantize data to improve compression. Data is quantized using around(scale*data)/scale, where scale is 2**bits, and bits is determined from the least_significant_digit. For example, if least_significant_digit=1, bits will be 4. """ exp = -least_significant_digit exp = math.floor(exp) if exp < 0 else math.ceil(exp) bits = math.ceil(math.log2(10 ** -exp)) scale = 2 ** bits datout = np.around(scale * data) / scale return datout # Utilities to detect leaked instances. See recipe 14.10 of the Python # Cookbook by Martelli & Ascher. tracked_classes = {} def log_instance_creation(instance, name=None): if name is None: name = instance.__class__.__name__ if name not in tracked_classes: tracked_classes[name] = [] tracked_classes[name].append(weakref.ref(instance)) def string_to_classes(s): if s == '*': c = sorted(tracked_classes) return c else: return s.split() def fetch_logged_instances(classes="*"): classnames = string_to_classes(classes) return [(cn, len(tracked_classes[cn])) for cn in classnames] def count_logged_instances(classes, file=sys.stdout): for classname in string_to_classes(classes): file.write("%s: %d\n" % (classname, len(tracked_classes[classname]))) def list_logged_instances(classes, file=sys.stdout): for classname in string_to_classes(classes): file.write('\n%s:\n' % classname) for ref in tracked_classes[classname]: obj = ref() if obj is not None: file.write(' %s\n' % repr(obj)) def dump_logged_instances(classes, file=sys.stdout): for classname in string_to_classes(classes): file.write('\n%s:\n' % classname) for ref in tracked_classes[classname]: obj = ref() if obj is not None: file.write(' %s:\n' % obj) for key, value in obj.__dict__.items(): file.write(f' {key:>20} : {value}\n') # # A class useful for cache usage # class CacheDict(dict): """A dictionary that prevents itself from growing too much.""" def __init__(self, maxentries): self.maxentries = maxentries super().__init__(self) def __setitem__(self, key, value): # Protection against growing the cache too much if len(self) > self.maxentries: # Remove a 10% of (arbitrary) elements from the cache entries_to_remove = self.maxentries / 10 for k in list(self)[:entries_to_remove]: super().__delitem__(k) super().__setitem__(key, value) class NailedDict: """A dictionary which ignores its items when it has nails on it.""" def __init__(self, maxentries): self.maxentries = maxentries self._cache = {} self._nailcount = 0 # Only a restricted set of dictionary methods are supported. That # is why we buy instead of inherit. # The following are intended to be used by ``Table`` code changing # the set of usable indexes. def clear(self): self._cache.clear() def nail(self): self._nailcount += 1 def unnail(self): self._nailcount -= 1 # The following are intended to be used by ``Table`` code handling # conditions. def __contains__(self, key): if self._nailcount > 0: return False return key in self._cache def __getitem__(self, key): if self._nailcount > 0: raise KeyError(key) return self._cache[key] def get(self, key, default=None): if self._nailcount > 0: return default return self._cache.get(key, default) def __setitem__(self, key, value): if self._nailcount > 0: return cache = self._cache # Protection against growing the cache too much if len(cache) > self.maxentries: # Remove a 10% of (arbitrary) elements from the cache entries_to_remove = max(self.maxentries // 10, 1) for k in list(cache)[:entries_to_remove]: del cache[k] cache[key] = value def detect_number_of_cores(): """Detects the number of cores on a system. Cribbed from pp. """ # Linux, Unix and MacOS: if hasattr(os, "sysconf"): if "SC_NPROCESSORS_ONLN" in os.sysconf_names: # Linux & Unix: ncpus = os.sysconf("SC_NPROCESSORS_ONLN") if isinstance(ncpus, int) and ncpus > 0: return ncpus else: # OSX: return int(os.popen2("sysctl -n hw.ncpu")[1].read()) # Windows: if "NUMBER_OF_PROCESSORS" in os.environ: ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]) if ncpus > 0: return ncpus return 1 # Default def _test(): """Run ``doctest`` on this module.""" import doctest doctest.testmod() if __name__ == '__main__': _test() PyTables-3.7.0/tables/utilsextension.pxd000066400000000000000000000013361416254111300203310ustar00rootroot00000000000000######################################################################## # # License: BSD # Created: March 03, 2008 # Author: Francesc Alted - faltet@pytables.com # # $Id: definitions.pyd 1018 2005-06-20 09:43:34Z faltet $ # ######################################################################## """ These are declarations for functions in utilsextension.pyx that have to be shared with other extensions. """ from .definitions cimport hsize_t, hid_t, hobj_ref_t from numpy cimport ndarray cdef hsize_t *malloc_dims(object) cdef hid_t get_native_type(hid_t) nogil cdef str cstr_to_pystr(const char*) cdef int load_reference(hid_t dataset_id, hobj_ref_t *refbuf, size_t item_size, ndarray nparr) except -1 PyTables-3.7.0/tables/utilsextension.pyx000066400000000000000000001274411416254111300203640ustar00rootroot00000000000000######################################################################## # # License: BSD # Created: May 20, 2005 # Author: Francesc Alted - faltet@pytables.com # # $Id$ # ######################################################################## """Cython utilities for PyTables and HDF5 library.""" import os import sys import warnings try: import zlib zlib_imported = True except ImportError: zlib_imported = False import numpy from .description import Description, Col from .misc.enum import Enum from .exceptions import HDF5ExtError from .atom import Atom, EnumAtom, ReferenceAtom from .utils import check_file_access from libc.stdio cimport stderr from libc.stdlib cimport malloc, free from libc.string cimport strchr, strcmp, strncmp, strlen from cpython.bytes cimport PyBytes_Check, PyBytes_FromStringAndSize from cpython.unicode cimport PyUnicode_DecodeUTF8, PyUnicode_Check from numpy cimport (import_array, ndarray, dtype, npy_int64, PyArray_DATA, PyArray_GETPTR1, PyArray_DescrFromType, npy_intp, NPY_BOOL, NPY_STRING, NPY_INT8, NPY_INT16, NPY_INT32, NPY_INT64, NPY_UINT8, NPY_UINT16, NPY_UINT32, NPY_UINT64, NPY_FLOAT16, NPY_FLOAT32, NPY_FLOAT64, NPY_COMPLEX64, NPY_COMPLEX128) from .definitions cimport (H5ARRAYget_info, H5ARRAYget_ndims, H5ATTRfind_attribute, H5ATTRget_attribute_string, H5D_CHUNKED, H5D_layout_t, H5Dclose, H5Dget_type, H5Dopen, H5E_DEFAULT, H5E_WALK_DOWNWARD, H5E_auto_t, H5E_error_t, H5E_walk_t, H5Eget_msg, H5Eprint, H5Eset_auto, H5Ewalk, H5F_ACC_RDONLY, H5Fclose, H5Fis_hdf5, H5Fopen, H5Gclose, H5Gopen, H5P_DEFAULT, H5T_ARRAY, H5T_BITFIELD, H5T_COMPOUND, H5T_CSET_ASCII, H5T_CSET_UTF8, H5T_C_S1, H5T_DIR_DEFAULT, H5T_ENUM, H5T_FLOAT, H5T_IEEE_F32BE, H5T_IEEE_F32LE, H5T_IEEE_F64BE, H5T_IEEE_F64LE, H5T_INTEGER, H5T_NATIVE_DOUBLE, H5T_NATIVE_LDOUBLE, H5T_NO_CLASS, H5T_OPAQUE, H5T_ORDER_BE, H5T_ORDER_LE, H5T_REFERENCE, H5T_STD_B8BE, H5T_STD_B8LE, H5T_STD_I16BE, H5T_STD_I16LE, H5T_STD_I32BE, H5T_STD_I32LE, H5T_STD_I64BE, H5T_STD_I64LE, H5T_STD_I8BE, H5T_STD_I8LE, H5T_STD_U16BE, H5T_STD_U16LE, H5T_STD_U32BE, H5T_STD_U32LE, H5T_STD_U64BE, H5T_STD_U64LE, H5T_STD_U8BE, H5T_STD_U8LE, H5T_STRING, H5T_TIME, H5T_UNIX_D32BE, H5T_UNIX_D32LE, H5T_UNIX_D64BE, H5T_UNIX_D64LE, H5T_VLEN, H5T_class_t, H5T_sign_t, H5Tarray_create, H5Tclose, H5Tequal, H5Tcopy, H5Tcreate, H5Tenum_create, H5Tenum_insert, H5Tget_array_dims, H5Tget_array_ndims, H5Tget_class, H5Tget_member_name, H5Tget_member_type, H5Tget_member_value, H5Tget_native_type, H5Tget_nmembers, H5Tget_offset, H5Tget_order, H5Tget_member_offset, H5Tget_precision, H5Tget_sign, H5Tget_size, H5Tget_super, H5Tinsert, H5Tis_variable_str, H5Tpack, H5Tset_precision, H5Tset_size, H5Tvlen_create, H5Zunregister, FILTER_BLOSC, PyArray_Scalar, create_ieee_complex128, create_ieee_complex64, create_ieee_float16, create_ieee_complex192, create_ieee_complex256, get_len_of_range, get_order, herr_t, hid_t, hsize_t, hssize_t, htri_t, is_complex, register_blosc, set_order, pt_H5free_memory, H5T_STD_REF_OBJ, H5Rdereference, H5R_OBJECT, H5I_DATASET, H5I_REFERENCE, H5Iget_type, hobj_ref_t, H5Oclose) # Platform-dependent types if sys.byteorder == "little": platform_byteorder = H5T_ORDER_LE # Standard types, independent of the byteorder H5T_STD_B8 = H5T_STD_B8LE H5T_STD_I8 = H5T_STD_I8LE H5T_STD_I16 = H5T_STD_I16LE H5T_STD_I32 = H5T_STD_I32LE H5T_STD_I64 = H5T_STD_I64LE H5T_STD_U8 = H5T_STD_U8LE H5T_STD_U16 = H5T_STD_U16LE H5T_STD_U32 = H5T_STD_U32LE H5T_STD_U64 = H5T_STD_U64LE H5T_IEEE_F32 = H5T_IEEE_F32LE H5T_IEEE_F64 = H5T_IEEE_F64LE H5T_UNIX_D32 = H5T_UNIX_D32LE H5T_UNIX_D64 = H5T_UNIX_D64LE else: # sys.byteorder == "big" platform_byteorder = H5T_ORDER_BE # Standard types, independent of the byteorder H5T_STD_B8 = H5T_STD_B8BE H5T_STD_I8 = H5T_STD_I8BE H5T_STD_I16 = H5T_STD_I16BE H5T_STD_I32 = H5T_STD_I32BE H5T_STD_I64 = H5T_STD_I64BE H5T_STD_U8 = H5T_STD_U8BE H5T_STD_U16 = H5T_STD_U16BE H5T_STD_U32 = H5T_STD_U32BE H5T_STD_U64 = H5T_STD_U64BE H5T_IEEE_F32 = H5T_IEEE_F32BE H5T_IEEE_F64 = H5T_IEEE_F64BE H5T_UNIX_D32 = H5T_UNIX_D32BE H5T_UNIX_D64 = H5T_UNIX_D64BE #---------------------------------------------------------------------------- # Conversion from PyTables string types to HDF5 native types # List only types that are susceptible of changing byteorder # (complex & enumerated types are special and should not be listed here) pttype_to_hdf5 = { 'int8' : H5T_STD_I8, 'uint8' : H5T_STD_U8, 'int16' : H5T_STD_I16, 'uint16' : H5T_STD_U16, 'int32' : H5T_STD_I32, 'uint32' : H5T_STD_U32, 'int64' : H5T_STD_I64, 'uint64' : H5T_STD_U64, 'float32': H5T_IEEE_F32, 'float64': H5T_IEEE_F64, 'float96': H5T_NATIVE_LDOUBLE, 'float128': H5T_NATIVE_LDOUBLE, 'time32' : H5T_UNIX_D32, 'time64' : H5T_UNIX_D64, } # Special cases whose byteorder cannot be directly changed pt_special_kinds = ['complex', 'string', 'enum', 'bool'] # Conversion table from NumPy extended codes prefixes to PyTables kinds npext_prefixes_to_ptkinds = { "S": "string", "b": "bool", "i": "int", "u": "uint", "f": "float", "c": "complex", "t": "time", "e": "enum", } # Names of HDF5 classes hdf5_class_to_string = { H5T_NO_CLASS : 'H5T_NO_CLASS', H5T_INTEGER : 'H5T_INTEGER', H5T_FLOAT : 'H5T_FLOAT', H5T_TIME : 'H5T_TIME', H5T_STRING : 'H5T_STRING', H5T_BITFIELD : 'H5T_BITFIELD', H5T_OPAQUE : 'H5T_OPAQUE', H5T_COMPOUND : 'H5T_COMPOUND', H5T_REFERENCE : 'H5T_REFERENCE', H5T_ENUM : 'H5T_ENUM', H5T_VLEN : 'H5T_VLEN', H5T_ARRAY : 'H5T_ARRAY', } # Depprecated API PTTypeToHDF5 = pttype_to_hdf5 PTSpecialKinds = pt_special_kinds NPExtPrefixesToPTKinds = npext_prefixes_to_ptkinds HDF5ClassToString = hdf5_class_to_string from numpy import sctypeDict cdef int have_float16 = ("float16" in sctypeDict) #---------------------------------------------------------------------- # External declarations # PyTables helper routines. cdef extern from "utils.h": int getLibrary(char *libname) nogil #object getZLIBVersionInfo() object getHDF5VersionInfo() object get_filter_names( hid_t loc_id, char *dset_name) H5T_class_t getHDF5ClassID(hid_t loc_id, char *name, H5D_layout_t *layout, hid_t *type_id, hid_t *dataset_id) nogil # Functions from Blosc cdef extern from "blosc.h" nogil: void blosc_init() int blosc_set_nthreads(int nthreads) const char* blosc_list_compressors() int blosc_compcode_to_compname(int compcode, char **compname) int blosc_get_complib_info(char *compname, char **complib, char **version) cdef extern from "H5ARRAY.h" nogil: herr_t H5ARRAYread(hid_t dataset_id, hid_t type_id, hsize_t start, hsize_t nrows, hsize_t step, int extdim, void *data) # @TODO: use the c_string_type and c_string_encoding global directives # (new in cython 0.19) # TODO: drop cdef str cstr_to_pystr(const char* cstring): return cstring.decode('utf-8') #---------------------------------------------------------------------- # Initialization code # The NumPy API requires this function to be called before # using any NumPy facilities in an extension module. import_array() # NaN-aware sorting with NaN as the greatest element # numpy.isnan only takes floats, this should work for strings too cpdef nan_aware_lt(a, b): return a < b or (b != b and a == a) cpdef nan_aware_le(a, b): return a <= b or b != b cpdef nan_aware_gt(a, b): return a > b or (a != a and b == b) cpdef nan_aware_ge(a, b): return a >= b or a != a def bisect_left(a, x, int lo=0): """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e < x, and all e in a[i:] have e >= x. So if x already appears in the list, i points just before the leftmost x already there. """ cdef int mid, hi = len(a) lo = 0 while lo < hi: mid = (lo+hi)//2 if nan_aware_lt(a[mid], x): lo = mid+1 else: hi = mid return lo def bisect_right(a, x, int lo=0): """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e <= x, and all e in a[i:] have e > x. So if x already appears in the list, i points just beyond the rightmost x already there. """ cdef int mid, hi = len(a) lo = 0 while lo < hi: mid = (lo+hi)//2 if nan_aware_lt(x, a[mid]): hi = mid else: lo = mid+1 return lo cdef register_blosc_(): cdef char *version cdef char *date register_blosc(&version, &date) compinfo = (version, date) free(version) free(date) return compinfo[0].decode('ascii'), compinfo[1].decode('ascii') blosc_version = register_blosc_() # Old versions (<1.4) of the blosc compression library # rely on unaligned memory access, so they are not functional on some # platforms (see https://github.com/FrancescAlted/blosc/issues/3 and # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=661286). # This function has been written by Julian Taylor . def _arch_without_blosc(): import platform arch = platform.machine().lower() for a in ("arm", "sparc", "mips", "aarch64"): if a in arch: return True return False if blosc_version and blosc_version < ('1', '4') and _arch_without_blosc(): # Only use bloc compressor on platforms that actually support it. H5Zunregister(FILTER_BLOSC) blosc_version = None else: blosc_init() # from 1.2 on, Blosc library must be initialized # Important: Blosc calls that modifies global variables in Blosc must be # called from the same extension where Blosc is registered in HDF5. def set_blosc_max_threads(nthreads): """set_blosc_max_threads(nthreads) Set the maximum number of threads that Blosc can use. This actually overrides the :data:`tables.parameters.MAX_BLOSC_THREADS` setting in :mod:`tables.parameters`, so the new value will be effective until this function is called again or a new file with a different :data:`tables.parameters.MAX_BLOSC_THREADS` value is specified. Returns the previous setting for maximum threads. """ return blosc_set_nthreads(nthreads) if sys.platform == "win32": # We need a different approach in Windows, because it complains when # trying to import the extension that is linked with a dynamic library # that is not installed in the system. # Initialize & register lzo if getLibrary("lzo2") == 0 or getLibrary("lzo1") == 0: import tables._comp_lzo lzo_version = tables._comp_lzo.register_() else: lzo_version = None # Initialize & register bzip2 if getLibrary("bzip2") == 0 or getLibrary("libbz2") == 0: import tables._comp_bzip2 bzip2_version = tables._comp_bzip2.register_() else: bzip2_version = None else: # Unix systems # Initialize & register lzo try: import tables._comp_lzo lzo_version = tables._comp_lzo.register_() except ImportError: lzo_version = None # Initialize & register bzip2 try: import tables._comp_bzip2 bzip2_version = tables._comp_bzip2.register_() except ImportError: bzip2_version = None # End of initialization code #--------------------------------------------------------------------- # Error handling helpers cdef herr_t e_walk_cb(unsigned n, const H5E_error_t *err, void *data) with gil: cdef object bt = data # list #cdef char major_msg[256] #cdef char minor_msg[256] #cdef ssize_t msg_len if err == NULL: return -1 #msg_len = H5Eget_msg(err.maj_num, NULL, major_msg, 256) #if msg_len < 0: # major_msg[0] = '\0' #msg_len = H5Eget_msg(err.min_num, NULL, minor_msg, 256) #if msg_len < 0: # minor_msg[0] = '\0' #msg = "%s (MAJOR: %s, MINOR: %s)" % ( # bytes(err.desc).decode('utf-8'), # bytes(major_msg).decode('utf-8'), # bytes(minor_msg).decode('utf-8')) msg = bytes(err.desc).decode('utf-8') bt.append(( bytes(err.file_name).decode('utf-8'), err.line, bytes(err.func_name).decode('utf-8'), msg, )) return 0 def _dump_h5_backtrace(): cdef object bt = [] if H5Ewalk(H5E_DEFAULT, H5E_WALK_DOWNWARD, e_walk_cb, bt) < 0: return None return bt # Initialization of the _dump_h5_backtrace method of HDF5ExtError. # The unusual machinery is needed in order to avoid cirdular dependencies # between modules. HDF5ExtError._dump_h5_backtrace = _dump_h5_backtrace def silence_hdf5_messages(silence=True): """silence_hdf5_messages(silence=True) Silence (or re-enable) messages from the HDF5 C library. The *silence* parameter can be used control the behaviour and reset the standard HDF5 logging. .. versionadded:: 2.4 """ cdef herr_t err if silence: err = H5Eset_auto(H5E_DEFAULT, NULL, NULL) else: err = H5Eset_auto(H5E_DEFAULT, H5Eprint, stderr) if err < 0: raise HDF5ExtError("unable to configure HDF5 internal error handling") # Disable automatic HDF5 error logging silence_hdf5_messages() def _broken_hdf5_long_double(): # HDF5 < 1.8.12 has a bug that prevents correct identification of the # long double data type when the code is built with gcc 4.8. # See also: http://hdf-forum.184993.n3.nabble.com/Issues-with-H5T-NATIVE-LDOUBLE-tt4026450.html return H5Tget_order(H5T_NATIVE_DOUBLE) != H5Tget_order(H5T_NATIVE_LDOUBLE) # Helper functions cdef hsize_t *malloc_dims(object pdims): """Return a malloced hsize_t dims from a python pdims.""" cdef int i, rank cdef hsize_t *dims dims = NULL rank = len(pdims) if rank > 0: dims = malloc(rank * sizeof(hsize_t)) for i in range(rank): dims[i] = pdims[i] return dims cdef hid_t get_native_float_type(hid_t type_id) nogil: """Get a native type of an HDF5 float type. This function also handles half precision (float16) data type. """ cdef hid_t native_type_id cdef size_t precision precision = H5Tget_precision(type_id) if precision == 16 and have_float16: native_type_id = create_ieee_float16(NULL) else: native_type_id = H5Tget_native_type(type_id, H5T_DIR_DEFAULT) return native_type_id # This routine is more complex than required because HDF5 1.6.x does # not implement support for H5Tget_native_type with some types, like # H5T_BITFIELD and probably others. When 1.8.x would be a requisite, # this can be simplified. cdef hid_t get_native_type(hid_t type_id) nogil: """Get the native type of a HDF5 type.""" cdef H5T_class_t class_id, super_class_id cdef hid_t native_type_id = 0, super_type_id, native_super_type_id cdef int rank cdef hsize_t *dims class_id = H5Tget_class(type_id) if class_id == H5T_COMPOUND: return H5Tget_native_type(type_id, H5T_DIR_DEFAULT) elif class_id in (H5T_ARRAY, H5T_VLEN): # Get the array base component super_type_id = H5Tget_super(type_id) # Get the class super_class_id = H5Tget_class(super_type_id) if super_class_id == H5T_FLOAT: # replicate the logic of H5Tget_native_type for H5T_ARRAY and # H5T_VLEN taking into account extended floating point types # XXX: HDF5 error check native_super_type_id = get_native_float_type(super_type_id) H5Tclose(super_type_id) if class_id == H5T_ARRAY: rank = H5Tget_array_ndims(type_id) dims = malloc(rank * sizeof(hsize_t)) H5Tget_array_dims(type_id, dims) native_type_id = H5Tarray_create(native_super_type_id, rank, dims) free(dims) H5Tclose(native_super_type_id) return native_type_id elif class_id == H5T_VLEN: native_type_id = H5Tvlen_create(native_super_type_id) H5Tclose(native_super_type_id) return native_type_id class_id = super_class_id H5Tclose(super_type_id) if class_id == H5T_FLOAT: native_type_id = get_native_float_type(type_id) elif class_id in (H5T_INTEGER, H5T_ENUM): native_type_id = H5Tget_native_type(type_id, H5T_DIR_DEFAULT) else: # Fixing the byteorder for other types shouldn't be needed. # More in particular, H5T_TIME is not managed yet by HDF5 and so this # has to be managed explicitely inside the PyTables extensions. # Regarding H5T_BITFIELD, well, I'm not sure if changing the byteorder # of this is a good idea at all. native_type_id = H5Tcopy(type_id) return native_type_id def encode_filename(object filename): """Return the encoded filename in the filesystem encoding.""" cdef bytes encname if hasattr(os, 'fspath'): filename = os.fspath(filename) if isinstance(filename, (unicode, numpy.str_)): # if type(filename) is unicode: encoding = sys.getfilesystemencoding() encname = filename.encode(encoding, 'replace') else: encname = filename return encname # Main functions def is_hdf5_file(object filename): """is_hdf5_file(filename) Determine whether a file is in the HDF5 format. When successful, it returns a true value if the file is an HDF5 file, false otherwise. If there were problems identifying the file, an HDF5ExtError is raised. """ # Check that the file exists and is readable. check_file_access(filename) # Encode the filename in case it is unicode encname = encode_filename(filename) ret = H5Fis_hdf5(encname) if ret < 0: raise HDF5ExtError("problems identifying file ``%s``" % (filename,)) return ret > 0 def is_pytables_file(object filename): """is_pytables_file(filename) Determine whether a file is in the PyTables format. When successful, it returns the format version string if the file is a PyTables file, None otherwise. If there were problems identifying the file, an HDF5ExtError is raised. """ cdef hid_t file_id cdef object isptf = None # A PYTABLES_FORMAT_VERSION attribute was not found if is_hdf5_file(filename): # Encode the filename in case it is unicode encname = encode_filename(filename) # The file exists and is HDF5, that's ok # Open it in read-only mode file_id = H5Fopen(encname, H5F_ACC_RDONLY, H5P_DEFAULT) isptf = read_f_attr(file_id, 'PYTABLES_FORMAT_VERSION') # Close the file H5Fclose(file_id) # system attributes should always be str if PyBytes_Check(isptf): isptf = isptf.decode('utf-8') return isptf def get_hdf5_version(): """Get the underlying HDF5 library version""" return getHDF5VersionInfo()[1] def which_lib_version(str name): """which_lib_version(name) Get version information about a C library. If the library indicated by name is available, this function returns a 3-tuple containing the major library version as an integer, its full version as a string, and the version date as a string. If the library is not available, None is returned. The currently supported library names are hdf5, zlib, lzo, bzip2, and blosc. If another name is given, a ValueError is raised. """ cdef char *cname = NULL cdef bytes encoded_name encoded_name = name.encode('utf-8') # get the C pointer cname = encoded_name libnames = ('hdf5', 'zlib', 'lzo', 'bzip2', 'blosc') if strcmp(cname, "hdf5") == 0: binver, strver = getHDF5VersionInfo() return (binver, strver, None) # Should be always available elif strcmp(cname, "zlib") == 0: if zlib_imported: return (1, zlib.ZLIB_VERSION, None) elif strcmp(cname, "lzo") == 0: if lzo_version: (lzo_version_string, lzo_version_date) = lzo_version return (lzo_version, lzo_version_string, lzo_version_date) elif strcmp(cname, "bzip2") == 0: if bzip2_version: (bzip2_version_string, bzip2_version_date) = bzip2_version return (bzip2_version, bzip2_version_string, bzip2_version_date) elif strncmp(cname, "blosc", 5) == 0: if blosc_version: (blosc_version_string, blosc_version_date) = blosc_version return (blosc_version, blosc_version_string, blosc_version_date) else: raise ValueError("asked version of unsupported library ``%s``; " "supported library names are ``%s``" % (name, libnames)) # A supported library was specified, but no version is available. return None # A function returning all the compressors supported by local Blosc def blosc_compressor_list(): """ blosc_compressor_list() Returns a list of compressors available in the Blosc build. Parameters ---------- None Returns ------- out : list The list of names. """ list_compr = blosc_list_compressors().decode() clist = [str(cname) for cname in list_compr.split(',')] return clist # Convert compressor code to compressor name def blosc_compcode_to_compname_(compcode): """ blosc_compcode_to_compname() Returns the compressor name associated with compressor code. Parameters ---------- None Returns ------- out : string The name of the compressor. """ cdef const char *cname cdef object compname compname = b"unknown (report this to developers)" if blosc_compcode_to_compname(compcode, &cname) >= 0: compname = cname return compname.decode() def blosc_get_complib_info_(): """Get info from compression libraries included in the current build of blosc. Returns a mapping containing the compressor names as keys and the tuple (complib, version) as values. """ cdef char *complib cdef char *version cinfo = {} for name in blosc_list_compressors().split(b','): ret = blosc_get_complib_info(name, &complib, &version) if ret < 0: continue if isinstance(name, str): cinfo[name] = (complib, version) else: cinfo[name.decode()] = (complib.decode(), version.decode()) free(complib) free(version) return cinfo def which_class(hid_t loc_id, object name): """Detects a class ID using heuristics.""" cdef H5T_class_t class_id cdef H5D_layout_t layout cdef hsize_t nfields cdef char *field_name1 cdef char *field_name2 cdef int i cdef hid_t type_id, dataset_id cdef object classId cdef int rank cdef hsize_t *dims cdef hsize_t *maxdims cdef char byteorder[11] # "irrelevant" fits easily here cdef bytes encoded_name if isinstance(name, unicode): encoded_name = name.encode('utf-8') else: encoded_name = name classId = "UNSUPPORTED" # default value # Get The HDF5 class for the datatype in this dataset class_id = getHDF5ClassID(loc_id, encoded_name, &layout, &type_id, &dataset_id) # Check if this a dataset of supported classtype for ARRAY if ((class_id == H5T_INTEGER) or (class_id == H5T_FLOAT) or (class_id == H5T_BITFIELD) or (class_id == H5T_TIME) or (class_id == H5T_ENUM) or (class_id == H5T_STRING) or (class_id == H5T_ARRAY) or (class_id == H5T_REFERENCE)): if layout == H5D_CHUNKED: if H5ARRAYget_ndims(dataset_id, &rank) < 0: raise HDF5ExtError("Problems getting ndims.") dims = malloc(rank * sizeof(hsize_t)) maxdims = malloc(rank * sizeof(hsize_t)) if H5ARRAYget_info(dataset_id, type_id, dims, maxdims, &class_id, byteorder) < 0: raise HDF5ExtError("Unable to get array info.") classId = "CARRAY" # Check whether some dimension is enlargeable for i in range(rank): if maxdims[i] == -1: classId = "EARRAY" break free(dims) free(maxdims) else: classId = "ARRAY" elif class_id == H5T_COMPOUND: # check whether the type is complex or not iscomplex = False nfields = H5Tget_nmembers(type_id) if nfields == 2: field_name1 = H5Tget_member_name(type_id, 0) field_name2 = H5Tget_member_name(type_id, 1) # The pair ("r", "i") is for PyTables. ("real", "imag") for Octave. if ( (strcmp(field_name1, "real") == 0 and strcmp(field_name2, "imag") == 0) or (strcmp(field_name1, "r") == 0 and strcmp(field_name2, "i") == 0) ): iscomplex = True pt_H5free_memory(field_name1) pt_H5free_memory(field_name2) if layout == H5D_CHUNKED: if iscomplex: classId = "CARRAY" else: classId = "TABLE" else: # Not chunked case # Octave saves complex arrays as non-chunked tables # with two fields: "real" and "imag" # Francesc Alted 2005-04-29 # Get number of records if iscomplex: classId = "ARRAY" # It is probably an Octave complex array else: # Added to support non-chunked tables classId = "TABLE" # A test for supporting non-growable tables elif class_id == H5T_VLEN: if layout == H5D_CHUNKED: classId = "VLARRAY" # Release the datatype. H5Tclose(type_id) # Close the dataset. H5Dclose(dataset_id) # Fallback return classId def get_nested_field(recarray, fieldname): """Get the maybe nested field named `fieldname` from the `recarray`. The `fieldname` may be a simple field name or a nested field name with slah-separated components. """ cdef bytes name = fieldname.encode('utf-8') try: if strchr(name, 47) != NULL: # ord('/') == 47 # It may be convenient to implement this way of descending nested # fields into the ``__getitem__()`` method of a subclass of # ``numpy.ndarray``. -- ivb field = recarray for nfieldname in fieldname.split('/'): field = field[nfieldname] else: # Faster method for non-nested columns field = recarray[fieldname] except KeyError: raise KeyError("no such column: %s" % (fieldname,)) return field def read_f_attr(hid_t file_id, str attr_name): """Read PyTables file attributes (i.e. in root group). Returns the value of the `attr_name` attribute in root group, or `None` if it does not exist. This call cannot fail. """ cdef size_t size cdef char *attr_value cdef int cset = H5T_CSET_ASCII cdef object retvalue cdef bytes encoded_attr_name cdef char *c_attr_name = NULL encoded_attr_name = attr_name.encode('utf-8') # Get the C pointer c_attr_name = encoded_attr_name attr_value = NULL retvalue = None # Check if attribute exists if H5ATTRfind_attribute(file_id, c_attr_name): # Read the attr_name attribute size = H5ATTRget_attribute_string(file_id, c_attr_name, &attr_value, &cset) if size == 0: if cset == H5T_CSET_UTF8: retvalue = numpy.unicode_('') else: retvalue = numpy.bytes_(b'') else: retvalue = (attr_value).rstrip(b'\x00') if cset == H5T_CSET_UTF8: retvalue = retvalue.decode('utf-8') retvalue = numpy.str_(retvalue) else: retvalue = numpy.bytes_(retvalue) # bytes # Important to release attr_value, because it has been malloc'ed! if attr_value: free(attr_value) return retvalue def get_filters(parent_id, name): """Get a dictionary with the filter names and cd_values""" cdef bytes encoded_name encoded_name = name.encode('utf-8') return get_filter_names(parent_id, encoded_name) # This is used by several ._convert_types() methods. def get_type_enum(hid_t h5type): """_getTypeEnum(h5type) -> hid_t Get the native HDF5 enumerated type of `h5type`. If `h5type` is an enumerated type, it is returned. If it is a variable-length type with an enumerated base type, this is returned. If it is a multi-dimensional type with an enumerated base type, this is returned. Else, a ``TypeError`` is raised. """ cdef H5T_class_t typeClass cdef hid_t enumId, enumId2 typeClass = H5Tget_class(h5type) if typeClass < 0: raise HDF5ExtError("failed to get class of HDF5 type") if typeClass == H5T_ENUM: # Get the native type (in order to do byteorder conversions automatically) enumId = H5Tget_native_type(h5type, H5T_DIR_DEFAULT) elif typeClass in (H5T_ARRAY, H5T_VLEN): # The field is multi-dimensional or variable length. enumId2 = H5Tget_super(h5type) enumId = get_type_enum(enumId2) H5Tclose(enumId2) else: raise TypeError( "enumerated values can not be stored using the given type") return enumId def enum_from_hdf5(hid_t enumId, str byteorder): """enum_from_hdf5(enumId) -> (Enum, npType) Convert an HDF5 enumerated type to a PyTables one. This function takes an HDF5 enumerated type and returns an `Enum` instance built from that, and the NumPy type used to encode it. """ cdef hid_t baseId cdef int nelems, npenum, i cdef void *rbuf cdef char *ename cdef ndarray npvalue cdef object dtype cdef str pyename # Find the base type of the enumerated type, and get the atom baseId = H5Tget_super(enumId) atom = atom_from_hdf5_type(baseId) H5Tclose(baseId) if atom.kind not in ('int', 'uint'): raise NotImplementedError("sorry, only integer concrete values are " "supported at this moment") dtype = atom.dtype npvalue = numpy.array((0,), dtype=dtype) rbuf = PyArray_DATA(npvalue) # Get the name and value of each of the members # and put the pair in `enumDict`. enumDict = {} nelems = H5Tget_nmembers(enumId) if enumId < 0: raise HDF5ExtError( "failed to get element count of HDF5 enumerated type") for i in range(nelems): ename = H5Tget_member_name(enumId, i) if ename == NULL: raise HDF5ExtError( "failed to get element name from HDF5 enumerated type") pyename = cstr_to_pystr(ename) pt_H5free_memory(ename) if H5Tget_member_value(enumId, i, rbuf) < 0: raise HDF5ExtError( "failed to get element value from HDF5 enumerated type") enumDict[pyename] = npvalue[0] # converted to NumPy scalar # Build an enumerated type from `enumDict` and return it. return Enum(enumDict), dtype def enum_to_hdf5(object enum_atom, str byteorder): """Convert a PyTables enumerated type to an HDF5 one. This function creates an HDF5 enumerated type from the information contained in `enumAtom` (an ``Atom`` object), with the specified `byteorder` (a string). The resulting HDF5 enumerated type is returned. """ cdef hid_t base_id, enum_id cdef object base_atom cdef ndarray values # Get the base HDF5 type and create the enumerated type. base_atom = Atom.from_dtype(enum_atom.dtype.base) base_id = atom_to_hdf5_type(base_atom, byteorder) try: enum_id = H5Tenum_create(base_id) if enum_id < 0: raise HDF5ExtError("failed to create HDF5 enumerated type") finally: if H5Tclose(base_id) < 0: raise HDF5ExtError("failed to close HDF5 base type") try: # Set the name and value of each of the members. names = enum_atom._names values = enum_atom._values # This saves the default enum value first so that we can restore it default_name = enum_atom._defname index_default = names.index(default_name) H5Tenum_insert(enum_id, default_name.encode('utf-8'), PyArray_GETPTR1(values, index_default)) for i, n in enumerate(names): # Skip the default value as we have already inserted it before if i == index_default: continue if H5Tenum_insert(enum_id, n.encode('utf-8'), PyArray_GETPTR1(values, i)) < 0: raise HDF5ExtError("failed to insert value into HDF5 enumerated type") # Return the new, open HDF5 enumerated type. return enum_id except: if H5Tclose(enum_id) < 0: raise HDF5ExtError("failed to close HDF5 enumerated type") raise def atom_to_hdf5_type(atom, str byteorder): cdef hid_t tid = -1 cdef hid_t tid2 = -1 cdef hsize_t *dims = NULL cdef bytes encoded_byteorder cdef char *cbyteorder = NULL encoded_byteorder = byteorder.encode('utf-8') # Get the C pointer cbyteorder = encoded_byteorder # Create the base HDF5 type if atom.type in pttype_to_hdf5: tid = H5Tcopy(pttype_to_hdf5[atom.type]) # Fix the byteorder if atom.kind != 'time': set_order(tid, cbyteorder) elif atom.type == 'float16': tid = create_ieee_float16(cbyteorder) elif atom.kind in pt_special_kinds: # Special cases (the byteorder doesn't need to be fixed afterwards) if atom.type == 'complex64': tid = create_ieee_complex64(cbyteorder) elif atom.type == 'complex128': tid = create_ieee_complex128(cbyteorder) elif atom.type == 'complex192': tid = create_ieee_complex192(cbyteorder) elif atom.type == 'complex256': tid = create_ieee_complex256(cbyteorder) elif atom.kind == 'string': tid = H5Tcopy(H5T_C_S1); H5Tset_size(tid, atom.itemsize) elif atom.kind == 'bool': tid = H5Tcopy(H5T_STD_B8); elif atom.kind == 'enum': tid = enum_to_hdf5(atom, byteorder) else: raise TypeError("Invalid type for atom %s" % (atom,)) # Create an H5T_ARRAY in case of non-scalar atoms if atom.shape != (): dims = malloc_dims(atom.shape) tid2 = H5Tarray_create(tid, len(atom.shape), dims) free(dims) H5Tclose(tid) tid = tid2 return tid def load_enum(hid_t type_id): """load_enum() -> (Enum, npType) Load the enumerated HDF5 type associated with this type_id. It returns an `Enum` instance built from that, and the NumPy type used to encode it. """ cdef hid_t enumId cdef char c_byteorder[11] # "irrelevant" fits well here cdef str byteorder # Get the enumerated type enumId = get_type_enum(type_id) # Get the byteorder get_order(type_id, c_byteorder) byteorder = cstr_to_pystr(c_byteorder) # Get the Enum and NumPy types and close the HDF5 type. try: return enum_from_hdf5(enumId, byteorder) finally: # (Yes, the ``finally`` clause *is* executed.) if H5Tclose(enumId) < 0: raise HDF5ExtError("failed to close HDF5 enumerated type") def hdf5_to_np_nested_type(hid_t type_id): """Given a HDF5 `type_id`, return a dtype string representation of it.""" cdef hid_t member_type_id cdef hid_t member_offset cdef hsize_t nfields cdef int i cdef char *c_colname cdef H5T_class_t class_id cdef object desc cdef str colname desc = {} # Get the number of members nfields = H5Tget_nmembers(type_id) # Iterate thru the members for i in range(nfields): # Get the member name c_colname = H5Tget_member_name(type_id, i) colname = cstr_to_pystr(c_colname) # Get the member type member_type_id = H5Tget_member_type(type_id, i) member_offset = H5Tget_member_offset(type_id, i) # Get the HDF5 class class_id = H5Tget_class(member_type_id) if class_id == H5T_COMPOUND and not is_complex(member_type_id): desc[colname] = hdf5_to_np_nested_type(member_type_id) desc[colname]["_v_pos"] = i desc[colname]["_v_offset"] = member_offset else: atom = atom_from_hdf5_type(member_type_id, pure_numpy_types=True) desc[colname] = Col.from_atom(atom, pos=i, _offset=member_offset) # Release resources H5Tclose(member_type_id) pt_H5free_memory(c_colname) return desc def hdf5_to_np_ext_type(hid_t type_id, pure_numpy_types=True, atom=False, ptparams=None): """Map the atomic HDF5 type to a string repr of NumPy extended codes. If `pure_numpy_types` is true, detected HDF5 types that does not match pure NumPy types will raise a ``TypeError`` exception. If not, HDF5 types like TIME, VLEN or ENUM are passed through. If `atom` is true, the resulting repr is meant for atoms. If not, the result is meant for attributes. Returns the string repr of type and its shape. The exception is for compounds types, that returns a NumPy dtype and shape instead. """ cdef H5T_sign_t sign cdef hid_t super_type_id, native_type_id cdef H5T_class_t class_id cdef size_t itemsize cdef object stype, shape, shape2 cdef hsize_t *dims # default shape shape = () # Get the HDF5 class class_id = H5Tget_class(type_id) # Get the itemsize itemsize = H5Tget_size(type_id) if class_id == H5T_BITFIELD: stype = "b1" elif class_id == H5T_INTEGER: # Get the sign sign = H5Tget_sign(type_id) if sign > 0: stype = "i%s" % itemsize else: stype = "u%s" % itemsize elif class_id == H5T_FLOAT: stype = "f%s" % itemsize elif class_id == H5T_COMPOUND: if is_complex(type_id): stype = "c%s" % itemsize else: if atom: raise TypeError("the HDF5 class ``%s`` is not supported yet" % hdf5_class_to_string[class_id]) desc = Description(hdf5_to_np_nested_type(type_id), ptparams=ptparams) # stype here is not exactly a string, but the NumPy dtype factory # will deal with this. stype = desc._v_dtype elif class_id == H5T_STRING: if H5Tis_variable_str(type_id): raise TypeError("variable length strings are not supported yet") stype = "S%s" % itemsize elif class_id == H5T_TIME: if pure_numpy_types: raise TypeError("the HDF5 class ``%s`` is not supported yet" % hdf5_class_to_string[class_id]) stype = "t%s" % itemsize elif class_id == H5T_ENUM: if pure_numpy_types: raise TypeError("the HDF5 class ``%s`` is not supported yet" % hdf5_class_to_string[class_id]) stype = "e" elif class_id == H5T_VLEN: if pure_numpy_types: raise TypeError("the HDF5 class ``%s`` is not supported yet" % hdf5_class_to_string[class_id]) # Get the variable length base component super_type_id = H5Tget_super(type_id) # Find the super member format stype, shape = hdf5_to_np_ext_type(super_type_id, pure_numpy_types) # Release resources H5Tclose(super_type_id) elif class_id == H5T_REFERENCE: # only standard referenced objects (for atoms) are now supported if not atom or not H5Tequal(type_id, H5T_STD_REF_OBJ): raise TypeError("the HDF5 class ``%s`` is not supported yet" % hdf5_class_to_string[class_id]) stype = "_ref_" elif class_id == H5T_ARRAY: # Get the array base component super_type_id = H5Tget_super(type_id) # Find the super member format stype, shape2 = hdf5_to_np_ext_type(super_type_id, pure_numpy_types) # Get shape shape = [] ndims = H5Tget_array_ndims(type_id) dims = malloc(ndims * sizeof(hsize_t)) H5Tget_array_dims(type_id, dims) for i in range(ndims): shape.append(dims[i]) # cast to avoid long representation (i.e. 2L) shape = tuple(shape) # Release resources free(dims) H5Tclose(super_type_id) else: # Other types are not supported yet raise TypeError("the HDF5 class ``%s`` is not supported yet" % hdf5_class_to_string[class_id]) return stype, shape def atom_from_hdf5_type(hid_t type_id, pure_numpy_types=False): """Get an atom from a type_id. See `hdf5_to_np_ext_type` for an explanation of the `pure_numpy_types` parameter. """ cdef object stype, shape, atom_, sctype, tsize, kind cdef object dflt, base, enum_, nptype stype, shape = hdf5_to_np_ext_type(type_id, pure_numpy_types, atom=True) # Create the Atom if stype == '_ref_': atom_ = ReferenceAtom(shape=shape) elif stype == 'e': (enum_, nptype) = load_enum(type_id) # Take one of the names as the default in the enumeration. dflt = next(iter(enum_))[0] base = Atom.from_dtype(nptype) atom_ = EnumAtom(enum_, dflt, base, shape=shape) else: kind = npext_prefixes_to_ptkinds[stype[0]] tsize = int(stype[1:]) atom_ = Atom.from_kind(kind, tsize, shape=shape) return atom_ def create_nested_type(object desc, str byteorder): """Create a nested type based on a description and return an HDF5 type.""" cdef hid_t tid, tid2 cdef size_t offset cdef bytes encoded_name tid = H5Tcreate(H5T_COMPOUND, desc._v_itemsize) if tid < 0: return -1 offset = desc._v_offsets[0] if desc._v_offsets else 0 for i, k in enumerate(desc._v_names): obj = desc._v_colobjects[k] if isinstance(obj, Description): tid2 = create_nested_type(obj, byteorder) else: tid2 = atom_to_hdf5_type(obj, byteorder) encoded_name = k.encode('utf-8') if desc._v_offsets: offset = desc._v_offsets[i] H5Tinsert(tid, encoded_name, offset, tid2) if not desc._v_offsets: offset += desc._v_dtype[k].itemsize # Release resources H5Tclose(tid2) return tid cdef int load_reference(hid_t dataset_id, hobj_ref_t *refbuf, size_t item_size, ndarray nparr) except -1: """Load a reference as an array of objects :param dataset_id: dataset of the reference :param refbuf: load the references requested :param item_size: size of the reference in the file read into refbuf :param nparr: numpy object array already pre-allocated with right size and shape for refbuf references """ cdef size_t nelements = nparr.size cdef int i, j cdef hid_t refobj_id = -1 # if valid can be only be a dataset id cdef hid_t reftype_id cdef hid_t disk_type_id = -1 cdef void *rbuf cdef int rank = 0 cdef hsize_t *maxdims = NULL cdef hsize_t *dims = NULL cdef char cbyteorder[11] cdef H5T_class_t class_id cdef hsize_t nrows cdef ndarray nprefarr cdef int extdim cdef hobj_ref_t *newrefbuf = NULL if refbuf == NULL: raise ValueError("Invalid reference buffer") try: for i in range(nelements): refobj_id = H5Rdereference(dataset_id, H5R_OBJECT, &refbuf[i]) if H5Iget_type(refobj_id) != H5I_DATASET: raise ValueError('Invalid reference type %d %d' % (H5Iget_type(refobj_id), item_size)) disk_type_id = H5Dget_type(refobj_id) reftype_id = get_native_type(disk_type_id) # Get the rank for this array object if H5ARRAYget_ndims(refobj_id, &rank) < 0: raise HDF5ExtError("Problems getting ndims!") dims = malloc(rank * sizeof(hsize_t)) maxdims = malloc(rank * sizeof(hsize_t)) # Get info on dimensions, class and type (of base class) ret = H5ARRAYget_info(refobj_id, disk_type_id, dims, maxdims, &class_id, cbyteorder) if ret < 0: raise HDF5ExtError("Unable to get array info.") # Get the extendable dimension (if any) extdim = -1 # default is non-extensible Array for j in range(rank): if maxdims[j] == -1: extdim = j break if extdim < 0: extdim += rank nrows = dims[extdim] # read entire dataset as numpy array stype_, shape_ = hdf5_to_np_ext_type(reftype_id, pure_numpy_types=True, atom=True) if stype_ == "_ref_": dtype_ = numpy.dtype("O", shape_) else: dtype_ = numpy.dtype(stype_, shape_) shape = [] for j in range(rank): shape.append(dims[j]) shape = tuple(shape) nprefarr = numpy.empty(dtype=dtype_, shape=shape) nparr[i] = [nprefarr] # box the array in a list to store it as one object if stype_ == "_ref_": newrefbuf = malloc(nprefarr.size * item_size) rbuf = newrefbuf else: rbuf = PyArray_DATA(nprefarr) # Do the physical read with nogil: ret = H5ARRAYread(refobj_id, reftype_id, 0, nrows, 1, extdim, rbuf) if ret < 0: raise HDF5ExtError("Problems reading the array data.") if stype_ == "_ref_": # recurse to read the reference load_reference(refobj_id, newrefbuf, item_size, nprefarr) # close objects if newrefbuf: free(newrefbuf) newrefbuf = NULL H5Oclose(refobj_id) refobj_id = -1 H5Tclose(reftype_id) reftype_id = -1 H5Tclose(disk_type_id) disk_type_id = -1 free(maxdims) maxdims = NULL free(dims) dims = NULL finally: if newrefbuf: free(newrefbuf) newrefbuf = NULL if refobj_id >= 0: H5Oclose(refobj_id) if reftype_id >= 0: H5Tclose(reftype_id) if disk_type_id >= 0: H5Tclose(disk_type_id) if maxdims: free(maxdims) if dims: free(dims) # no error return 0 ## Local Variables: ## mode: python ## py-indent-offset: 2 ## tab-width: 2 ## fill-column: 78 ## End: PyTables-3.7.0/tables/vlarray.py000066400000000000000000000763601416254111300165620ustar00rootroot00000000000000"""Here is defined the VLArray class.""" import operator import sys import numpy as np from . import hdf5extension from .atom import ObjectAtom, VLStringAtom, VLUnicodeAtom from .flavor import internal_to_flavor from .leaf import Leaf, calc_chunksize from .utils import ( convert_to_np_atom, convert_to_np_atom2, idx2long, correct_byteorder, SizeType, is_idx, lazyattr) # default version for VLARRAY objects # obversion = "1.0" # initial version # obversion = "1.0" # add support for complex datatypes # obversion = "1.1" # This adds support for time datatypes. # obversion = "1.2" # This adds support for enumerated datatypes. # obversion = "1.3" # Introduced 'PSEUDOATOM' attribute. obversion = "1.4" # Numeric and numarray flavors are gone. class VLArray(hdf5extension.VLArray, Leaf): """This class represents variable length (ragged) arrays in an HDF5 file. Instances of this class represent array objects in the object tree with the property that their rows can have a *variable* number of homogeneous elements, called *atoms*. Like Table datasets (see :ref:`TableClassDescr`), variable length arrays can have only one dimension, and the elements (atoms) of their rows can be fully multidimensional. When reading a range of rows from a VLArray, you will *always* get a Python list of objects of the current flavor (each of them for a row), which may have different lengths. This class provides methods to write or read data to or from variable length array objects in the file. Note that it also inherits all the public attributes and methods that Leaf (see :ref:`LeafClassDescr`) already provides. .. note:: VLArray objects also support compression although compression is only performed on the data structures used internally by the HDF5 to take references of the location of the variable length data. Data itself (the raw data) are not compressed or filtered. Please refer to the `VLTypes Technical Note `_ for more details on the topic. Parameters ---------- parentnode The parent :class:`Group` object. name : str The name of this node in its parent group. atom An `Atom` instance representing the *type* and *shape* of the atomic objects to be saved. title A description for this node (it sets the ``TITLE`` HDF5 attribute on disk). filters An instance of the `Filters` class that provides information about the desired I/O filters to be applied during the life of this object. expectedrows A user estimate about the number of row elements that will be added to the growable dimension in the `VLArray` node. If not provided, the default value is ``EXPECTED_ROWS_VLARRAY`` (see ``tables/parameters.py``). If you plan to create either a much smaller or a much bigger `VLArray` try providing a guess; this will optimize the HDF5 B-Tree creation and management process time and the amount of memory used. .. versionadded:: 3.0 chunkshape The shape of the data chunk to be read or written in a single HDF5 I/O operation. Filters are applied to those chunks of data. The dimensionality of `chunkshape` must be 1. If ``None``, a sensible value is calculated (which is recommended). byteorder The byteorder of the data *on disk*, specified as 'little' or 'big'. If this is not specified, the byteorder is that of the platform. track_times Whether time data associated with the leaf are recorded (object access time, raw data modification time, metadata change time, object birth time); default True. Semantics of these times depend on their implementation in the HDF5 library: refer to documentation of the H5O_info_t data structure. As of HDF5 1.8.15, only ctime (metadata change time) is implemented. .. versionadded:: 3.4.3 .. versionchanged:: 3.0 *parentNode* renamed into *parentnode*. .. versionchanged:: 3.0 The *expectedsizeinMB* parameter has been replaced by *expectedrows*. Examples -------- See below a small example of the use of the VLArray class. The code is available in :file:`examples/vlarray1.py`:: import numpy as np import tables as tb # Create a VLArray: fileh = tb.open_file('vlarray1.h5', mode='w') vlarray = fileh.create_vlarray( fileh.root, 'vlarray1', tb.Int32Atom(shape=()), "ragged array of ints", filters=tb.Filters(1)) # Append some (variable length) rows: vlarray.append(np.array([5, 6])) vlarray.append(np.array([5, 6, 7])) vlarray.append([5, 6, 9, 8]) # Now, read it through an iterator: print('-->', vlarray.title) for x in vlarray: print('%s[%d]--> %s' % (vlarray.name, vlarray.nrow, x)) # Now, do the same with native Python strings. vlarray2 = fileh.create_vlarray( fileh.root, 'vlarray2', tb.StringAtom(itemsize=2), "ragged array of strings", filters=tb.Filters(1)) vlarray2.flavor = 'python' # Append some (variable length) rows: print('-->', vlarray2.title) vlarray2.append(['5', '66']) vlarray2.append(['5', '6', '77']) vlarray2.append(['5', '6', '9', '88']) # Now, read it through an iterator: for x in vlarray2: print('%s[%d]--> %s' % (vlarray2.name, vlarray2.nrow, x)) # Close the file. fileh.close() The output for the previous script is something like:: --> ragged array of ints vlarray1[0]--> [5 6] vlarray1[1]--> [5 6 7] vlarray1[2]--> [5 6 9 8] --> ragged array of strings vlarray2[0]--> ['5', '66'] vlarray2[1]--> ['5', '6', '77'] vlarray2[2]--> ['5', '6', '9', '88'] .. rubric:: VLArray attributes The instance variables below are provided in addition to those in Leaf (see :ref:`LeafClassDescr`). .. attribute:: atom An Atom (see :ref:`AtomClassDescr`) instance representing the *type* and *shape* of the atomic objects to be saved. You may use a *pseudo-atom* for storing a serialized object or variable length string per row. .. attribute:: flavor The type of data object read from this leaf. Please note that when reading several rows of VLArray data, the flavor only applies to the *components* of the returned Python list, not to the list itself. .. attribute:: nrow On iterators, this is the index of the current row. .. attribute:: nrows The current number of rows in the array. .. attribute:: extdim The index of the enlargeable dimension (always 0 for vlarrays). """ # Class identifier. _c_classid = 'VLARRAY' @lazyattr def dtype(self): """The NumPy ``dtype`` that most closely matches this array.""" return self.atom.dtype @property def shape(self): """The shape of the stored array.""" return (self.nrows,) @property def size_on_disk(self): """ The HDF5 library does not include a function to determine size_on_disk for variable-length arrays. Accessing this attribute will raise a NotImplementedError. """ raise NotImplementedError('size_on_disk not implemented for VLArrays') @property def size_in_memory(self): """ The size of this array's data in bytes when it is fully loaded into memory. .. note:: When data is stored in a VLArray using the ObjectAtom type, it is first serialized using pickle, and then converted to a NumPy array suitable for storage in an HDF5 file. This attribute will return the size of that NumPy representation. If you wish to know the size of the Python objects after they are loaded from disk, you can use this `ActiveState recipe `_. """ return self._get_memory_size() def __init__(self, parentnode, name, atom=None, title="", filters=None, expectedrows=None, chunkshape=None, byteorder=None, _log=True, track_times=True): self._v_version = None """The object version of this array.""" self._v_new = new = atom is not None """Is this the first time the node has been created?""" self._v_new_title = title """New title for this node.""" self._v_new_filters = filters """New filter properties for this array.""" if expectedrows is None: expectedrows = parentnode._v_file.params['EXPECTED_ROWS_VLARRAY'] self._v_expectedrows = expectedrows """The expected number of rows to be stored in the array. .. versionadded:: 3.0 """ self._v_chunkshape = None """Private storage for the `chunkshape` property of Leaf.""" # Miscellaneous iteration rubbish. self._start = None """Starting row for the current iteration.""" self._stop = None """Stopping row for the current iteration.""" self._step = None """Step size for the current iteration.""" self._nrowsread = None """Number of rows read up to the current state of iteration.""" self._startb = None """Starting row for current buffer.""" self._stopb = None """Stopping row for current buffer. """ self._row = None """Current row in iterators (sentinel).""" self._init = False """Whether we are in the middle of an iteration or not (sentinel).""" self.listarr = None """Current buffer in iterators.""" # Documented (*public*) attributes. self.atom = atom """ An Atom (see :ref:`AtomClassDescr`) instance representing the *type* and *shape* of the atomic objects to be saved. You may use a *pseudo-atom* for storing a serialized object or variable length string per row. """ self.nrow = None """On iterators, this is the index of the current row.""" self.nrows = None """The current number of rows in the array.""" self.extdim = 0 # VLArray only have one dimension currently """The index of the enlargeable dimension (always 0 for vlarrays).""" # Check the chunkshape parameter if new and chunkshape is not None: if isinstance(chunkshape, (int, np.integer)): chunkshape = (chunkshape,) try: chunkshape = tuple(chunkshape) except TypeError: raise TypeError( "`chunkshape` parameter must be an integer or sequence " "and you passed a %s" % type(chunkshape)) if len(chunkshape) != 1: raise ValueError("`chunkshape` rank (length) must be 1: %r" % (chunkshape,)) self._v_chunkshape = tuple(SizeType(s) for s in chunkshape) super().__init__(parentnode, name, new, filters, byteorder, _log, track_times) def _g_post_init_hook(self): super()._g_post_init_hook() self.nrowsinbuf = 100 # maybe enough for most applications # This is too specific for moving it into Leaf def _calc_chunkshape(self, expectedrows): """Calculate the size for the HDF5 chunk.""" # For computing the chunkshape for HDF5 VL types, we have to # choose the itemsize of the *each* element of the atom and # not the size of the entire atom. I don't know why this # should be like this, perhaps I should report this to the # HDF5 list. # F. Alted 2006-11-23 # elemsize = self.atom.atomsize() elemsize = self._basesize # AV 2013-05-03 # This is just a quick workaround tha allows to change the API for # PyTables 3.0 release and remove the expected_mb parameter. # The algorithm for computing the chunkshape should be rewritten as # requested by gh-35. expected_mb = expectedrows * elemsize / 1024 ** 2 chunksize = calc_chunksize(expected_mb) # Set the chunkshape chunkshape = chunksize // elemsize # Safeguard against itemsizes being extremely large if chunkshape == 0: chunkshape = 1 return (SizeType(chunkshape),) def _g_create(self): """Create a variable length array (ragged array).""" atom = self.atom self._v_version = obversion # Check for zero dims in atom shape (not allowed in VLArrays) zerodims = np.sum(np.array(atom.shape) == 0) if zerodims > 0: raise ValueError("When creating VLArrays, none of the dimensions " "of the Atom instance can be zero.") if not hasattr(atom, 'size'): # it is a pseudo-atom self._atomicdtype = atom.base.dtype self._atomicsize = atom.base.size self._basesize = atom.base.itemsize else: self._atomicdtype = atom.dtype self._atomicsize = atom.size self._basesize = atom.itemsize self._atomictype = atom.type self._atomicshape = atom.shape # Compute the optimal chunkshape, if needed if self._v_chunkshape is None: self._v_chunkshape = self._calc_chunkshape(self._v_expectedrows) self.nrows = SizeType(0) # No rows at creation time # Correct the byteorder if needed if self.byteorder is None: self.byteorder = correct_byteorder(atom.type, sys.byteorder) # After creating the vlarray, ``self._v_objectid`` needs to be # set because it is needed for setting attributes afterwards. self._v_objectid = self._create_array(self._v_new_title) # Add an attribute in case we have a pseudo-atom so that we # can retrieve the proper class after a re-opening operation. if not hasattr(atom, 'size'): # it is a pseudo-atom self.attrs.PSEUDOATOM = atom.kind return self._v_objectid def _g_open(self): """Get the metadata info for an array in file.""" self._v_objectid, self.nrows, self._v_chunkshape, atom = \ self._open_array() # Check if the atom can be a PseudoAtom if "PSEUDOATOM" in self.attrs: kind = self.attrs.PSEUDOATOM if kind == 'vlstring': atom = VLStringAtom() elif kind == 'vlunicode': atom = VLUnicodeAtom() elif kind == 'object': atom = ObjectAtom() else: raise ValueError( "pseudo-atom name ``%s`` not known." % kind) elif self._v_file.format_version[:1] == "1": flavor1x = self.attrs.FLAVOR if flavor1x == "VLString": atom = VLStringAtom() elif flavor1x == "Object": atom = ObjectAtom() self.atom = atom return self._v_objectid def _getnobjects(self, nparr): """Return the number of objects in a NumPy array.""" # Check for zero dimensionality array zerodims = np.sum(np.array(nparr.shape) == 0) if zerodims > 0: # No objects to be added return 0 shape = nparr.shape atom_shape = self.atom.shape shapelen = len(nparr.shape) if isinstance(atom_shape, tuple): atomshapelen = len(self.atom.shape) else: atom_shape = (self.atom.shape,) atomshapelen = 1 diflen = shapelen - atomshapelen if shape == atom_shape: nobjects = 1 elif (diflen == 1 and shape[diflen:] == atom_shape): # Check if the leading dimensions are all ones # if shape[:diflen-1] == (1,)*(diflen-1): # nobjects = shape[diflen-1] # shape = shape[diflen:] # It's better to accept only inputs with the exact dimensionality # i.e. a dimensionality only 1 element larger than atom nobjects = shape[0] shape = shape[1:] elif atom_shape == (1,) and shapelen == 1: # Case where shape = (N,) and shape_atom = 1 or (1,) nobjects = shape[0] else: raise ValueError("The object '%s' is composed of elements with " "shape '%s', which is not compatible with the " "atom shape ('%s')." % (nparr, shape, atom_shape)) return nobjects def get_enum(self): """Get the enumerated type associated with this array. If this array is of an enumerated type, the corresponding Enum instance (see :ref:`EnumClassDescr`) is returned. If it is not of an enumerated type, a TypeError is raised. """ if self.atom.kind != 'enum': raise TypeError("array ``%s`` is not of an enumerated type" % self._v_pathname) return self.atom.enum def append(self, sequence): """Add a sequence of data to the end of the dataset. This method appends the objects in the sequence to a *single row* in this array. The type and shape of individual objects must be compliant with the atoms in the array. In the case of serialized objects and variable length strings, the object or string to append is itself the sequence. """ self._g_check_open() self._v_file._check_writable() # Prepare the sequence to convert it into a NumPy object atom = self.atom if not hasattr(atom, 'size'): # it is a pseudo-atom sequence = atom.toarray(sequence) statom = atom.base else: try: # fastest check in most cases len(sequence) except TypeError: raise TypeError("argument is not a sequence") statom = atom if len(sequence) > 0: # The sequence needs to be copied to make the operation safe # to in-place conversion. nparr = convert_to_np_atom2(sequence, statom) nobjects = self._getnobjects(nparr) else: nobjects = 0 nparr = None self._append(nparr, nobjects) self.nrows += 1 def iterrows(self, start=None, stop=None, step=None): """Iterate over the rows of the array. This method returns an iterator yielding an object of the current flavor for each selected row in the array. If a range is not supplied, *all the rows* in the array are iterated upon. You can also use the :meth:`VLArray.__iter__` special method for that purpose. If you only want to iterate over a given *range of rows* in the array, you may use the start, stop and step parameters. Examples -------- :: for row in vlarray.iterrows(step=4): print('%s[%d]--> %s' % (vlarray.name, vlarray.nrow, row)) .. versionchanged:: 3.0 If the *start* parameter is provided and *stop* is None then the array is iterated from *start* to the last line. In PyTables < 3.0 only one element was returned. """ (self._start, self._stop, self._step) = self._process_range( start, stop, step) self._init_loop() return self def __iter__(self): """Iterate over the rows of the array. This is equivalent to calling :meth:`VLArray.iterrows` with default arguments, i.e. it iterates over *all the rows* in the array. Examples -------- :: result = [row for row in vlarray] Which is equivalent to:: result = [row for row in vlarray.iterrows()] """ if not self._init: # If the iterator is called directly, assign default variables self._start = 0 self._stop = self.nrows self._step = 1 # and initialize the loop self._init_loop() return self def _init_loop(self): """Initialization for the __iter__ iterator.""" self._nrowsread = self._start self._startb = self._start self._row = -1 # Sentinel self._init = True # Sentinel self.nrow = SizeType(self._start - self._step) # row number def __next__(self): """Get the next element of the array during an iteration. The element is returned as a list of objects of the current flavor. """ if self._nrowsread >= self._stop: self._init = False raise StopIteration # end of iteration else: # Read a chunk of rows if self._row + 1 >= self.nrowsinbuf or self._row < 0: self._stopb = self._startb + self._step * self.nrowsinbuf self.listarr = self.read(self._startb, self._stopb, self._step) self._row = -1 self._startb = self._stopb self._row += 1 self.nrow += self._step self._nrowsread += self._step return self.listarr[self._row] def __getitem__(self, key): """Get a row or a range of rows from the array. If key argument is an integer, the corresponding array row is returned as an object of the current flavor. If key is a slice, the range of rows determined by it is returned as a list of objects of the current flavor. In addition, NumPy-style point selections are supported. In particular, if key is a list of row coordinates, the set of rows determined by it is returned. Furthermore, if key is an array of boolean values, only the coordinates where key is True are returned. Note that for the latter to work it is necessary that key list would contain exactly as many rows as the array has. Examples -------- :: a_row = vlarray[4] a_list = vlarray[4:1000:2] a_list2 = vlarray[[0,2]] # get list of coords a_list3 = vlarray[[0,-2]] # negative values accepted a_list4 = vlarray[numpy.array([True,...,False])] # array of bools """ self._g_check_open() if is_idx(key): key = operator.index(key) # Index out of range protection if key >= self.nrows: raise IndexError("Index out of range") if key < 0: # To support negative values key += self.nrows (start, stop, step) = self._process_range(key, key + 1, 1) return self.read(start, stop, step)[0] elif isinstance(key, slice): start, stop, step = self._process_range( key.start, key.stop, key.step) return self.read(start, stop, step) # Try with a boolean or point selection elif type(key) in (list, tuple) or isinstance(key, np.ndarray): coords = self._point_selection(key) return self._read_coordinates(coords) else: raise IndexError(f"Invalid index or slice: {key!r}") def _assign_values(self, coords, values): """Assign the `values` to the positions stated in `coords`.""" for nrow, value in zip(coords, values): if nrow >= self.nrows: raise IndexError("First index out of range") if nrow < 0: # To support negative values nrow += self.nrows object_ = value # Prepare the object to convert it into a NumPy object atom = self.atom if not hasattr(atom, 'size'): # it is a pseudo-atom object_ = atom.toarray(object_) statom = atom.base else: statom = atom value = convert_to_np_atom(object_, statom) nobjects = self._getnobjects(value) # Get the previous value nrow = idx2long( nrow) # To convert any possible numpy scalar value nparr = self._read_array(nrow, nrow + 1, 1)[0] nobjects = len(nparr) if len(value) > nobjects: raise ValueError("Length of value (%s) is larger than number " "of elements in row (%s)" % (len(value), nobjects)) try: nparr[:] = value except Exception as exc: # XXX raise ValueError("Value parameter:\n'%r'\n" "cannot be converted into an array object " "compliant vlarray[%s] row: \n'%r'\n" "The error was: <%s>" % (value, nrow, nparr[:], exc)) if nparr.size > 0: self._modify(nrow, nparr, nobjects) def __setitem__(self, key, value): """Set a row, or set of rows, in the array. It takes different actions depending on the type of the *key* parameter: if it is an integer, the corresponding table row is set to *value* (a record or sequence capable of being converted to the table structure). If *key* is a slice, the row slice determined by it is set to *value* (a record array or sequence of rows capable of being converted to the table structure). In addition, NumPy-style point selections are supported. In particular, if key is a list of row coordinates, the set of rows determined by it is set to value. Furthermore, if key is an array of boolean values, only the coordinates where key is True are set to values from value. Note that for the latter to work it is necessary that key list would contain exactly as many rows as the table has. .. note:: When updating the rows of a VLArray object which uses a pseudo-atom, there is a problem: you can only update values with *exactly* the same size in bytes than the original row. This is very difficult to meet with object pseudo-atoms, because :mod:`pickle` applied on a Python object does not guarantee to return the same number of bytes than over another object, even if they are of the same class. This effectively limits the kinds of objects than can be updated in variable-length arrays. Examples -------- :: vlarray[0] = vlarray[0] * 2 + 3 vlarray[99] = arange(96) * 2 + 3 # Negative values for the index are supported. vlarray[-99] = vlarray[5] * 2 + 3 vlarray[1:30:2] = list_of_rows vlarray[[1,3]] = new_1_and_3_rows """ self._g_check_open() self._v_file._check_writable() if is_idx(key): # If key is not a sequence, convert to it coords = [key] value = [value] elif isinstance(key, slice): start, stop, step = self._process_range( key.start, key.stop, key.step) coords = range(start, stop, step) # Try with a boolean or point selection elif type(key) in (list, tuple) or isinstance(key, np.ndarray): coords = self._point_selection(key) else: raise IndexError(f"Invalid index or slice: {key!r}") # Do the assignment row by row self._assign_values(coords, value) # Accessor for the _read_array method in superclass def read(self, start=None, stop=None, step=1): """Get data in the array as a list of objects of the current flavor. Please note that, as the lengths of the different rows are variable, the returned value is a *Python list* (not an array of the current flavor), with as many entries as specified rows in the range parameters. The start, stop and step parameters can be used to select only a *range of rows* in the array. Their meanings are the same as in the built-in range() Python function, except that negative values of step are not allowed yet. Moreover, if only start is specified, then stop will be set to start + 1. If you do not specify neither start nor stop, then *all the rows* in the array are selected. """ self._g_check_open() start, stop, step = self._process_range_read(start, stop, step) if start == stop: listarr = [] else: listarr = self._read_array(start, stop, step) atom = self.atom if not hasattr(atom, 'size'): # it is a pseudo-atom outlistarr = [atom.fromarray(arr) for arr in listarr] else: # Convert the list to the right flavor flavor = self.flavor outlistarr = [internal_to_flavor(arr, flavor) for arr in listarr] return outlistarr def _read_coordinates(self, coords): """Read rows specified in `coords`.""" rows = [] for coord in coords: rows.append(self.read(int(coord), int(coord) + 1, 1)[0]) return rows def _g_copy_with_stats(self, group, name, start, stop, step, title, filters, chunkshape, _log, **kwargs): """Private part of Leaf.copy() for each kind of leaf.""" # Build the new VLArray object object = VLArray( group, name, self.atom, title=title, filters=filters, expectedrows=self._v_expectedrows, chunkshape=chunkshape, _log=_log) # Now, fill the new vlarray with values from the old one # This is not buffered because we cannot forsee the length # of each record. So, the safest would be a copy row by row. # In the future, some analysis can be done in order to buffer # the copy process. nrowsinbuf = 1 (start, stop, step) = self._process_range_read(start, stop, step) # Optimized version (no conversions, no type and shape checks, etc...) nrowscopied = SizeType(0) nbytes = 0 if not hasattr(self.atom, 'size'): # it is a pseudo-atom atomsize = self.atom.base.size else: atomsize = self.atom.size for start2 in range(start, stop, step * nrowsinbuf): # Save the records on disk stop2 = start2 + step * nrowsinbuf if stop2 > stop: stop2 = stop nparr = self._read_array(start=start2, stop=stop2, step=step)[0] nobjects = nparr.shape[0] object._append(nparr, nobjects) nbytes += nobjects * atomsize nrowscopied += 1 object.nrows = nrowscopied return (object, nbytes) def __repr__(self): """This provides more metainfo in addition to standard __str__""" return f"""{self} atom = {self.atom!r} byteorder = {self.byteorder!r} nrows = {self.nrows} flavor = {self.flavor!r}""" PyTables-3.7.0/utils/000077500000000000000000000000001416254111300144025ustar00rootroot00000000000000PyTables-3.7.0/utils/pt2to3000077500000000000000000000001041416254111300154560ustar00rootroot00000000000000#!/usr/bin/env python from tables.scripts.pt2to3 import main main() PyTables-3.7.0/utils/ptdump000077500000000000000000000001041416254111300156340ustar00rootroot00000000000000#!/usr/bin/env python from tables.scripts.ptdump import main main() PyTables-3.7.0/utils/ptrepack000077500000000000000000000001061416254111300161360ustar00rootroot00000000000000#!/usr/bin/env python from tables.scripts.ptrepack import main main() PyTables-3.7.0/utils/pttree000077500000000000000000000001041416254111300156260ustar00rootroot00000000000000#!/usr/bin/env python from tables.scripts.pttree import main main()